출처 Randal E.Byrant, David R. O'Hallaron, 컴퓨터 시스템 Computer Systems A Programmer's Perspective, 김형신 옮김 (퍼스트북), Pearson
nslookup 명령어는 DNS에 도메인 이름에 매핑된 IP 주소를 조회하거나 반대로 IP 주소에 매핑된 도메인 이름을 조회할 때 사용하는 명령어이다.
CSAPP에는 NSLOOKUP과 비슷한 역할을 하는 HOSTINFO 프로그램 코드가 나와있다.
아래 코드는 교재의 코드에서 csapp.h을 include하던 것을 Linux에 기본 내장되어 있는 표준 시스템 헤더를 include하도록 바꾸고 불필요한 wrapper 함수는 걷어낸 것이다.
#include <netdb.h> /* struct addrinfo, function getnameinfo, getaddrinfo */
#include <netinet/in.h> /* struct in_addr, function htonl */
#include <arpa/inet.h> /* function inet_ntop */
#include <stdio.h>
#include <stdlib.h> /* function exit */
#include <string.h> /* function memset */
#define MAXLINE 8192 /* Max text line length */
int main(int argc, char **argv) {
struct addrinfo *p, *listp, hints;
char buf[MAXLINE];
int rc, flags;
if (argc!=2) {
fprintf(stderr, "usage: %s <domain name>\n", argv[0]);
exit(0);
}
/* Get a list of addrinfo records */
memset (&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* IPv4 only */
hints.ai_socktype = SOCK_STREAM; /* Connections only */
if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
exit (1);
}
/* Walk the list and display each IP address */
flags = NI_NUMERICHOST; /* Display address string instead of domain name */
for (p = listp; p; p = p->ai_next) {
getnameinfo (p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
printf("%s\n", buf);
}
/* Clean up */
freeaddrinfo(listp);
exit(0);
}
이 hostinfo 코드만 봐서는 어떻게 네트워크를 통해 DNS 조회 작업이 이루어지는지 알기 어렵다. 따라서 hostinfo를 호출했을 때 실제로 어떤 일들이 일어나는지 strace 명령어를 이용하여 알아보겠다.
strace는 Linux에서 사용되는 디버깅 및 트레이싱 도구로, 실행 중인 프로그램이 수행하는 시스템 호출(system calls)과 해당 호출에 의해 반환된 결과를 추적한다.
strace 명령어만 쓰면 터미널에 결과가 출력되고 위와 같이 -o 옵션을 주면서 결과를 저장할 경로를 명시하면 해당 경로에 결과가 저장된다. 네트워크에 관련된 결과만 보려면 -e trace=network 옵션을 줄 수도 있다.
위 명령어를 실행시켰을 때 hostinfo_ouput.txt에 저장된 결과는 다음과 같다.
프로그램을 실행하고 실행 파일을 로드하는 과정에서 일어나는 메모리 관련 시스템 콜들은 정확히 어떤 목적으로 이루어지고 있는지 완전하게 파악하기는 어렵지만 그래도 전체 흐름을 살펴보기 위해 전체 결과를 가져왔다. 눈여겨 봐야 할 부분은 소켓 인터페이스 함수들이 호출되는 부분이다.
1. 프로그램 실행
execve("lib/build/hostinfo", ["lib/build/hostinfo", "www.naver.com"], 0x7fff4e3f9228 /* 35 vars */) = 0
brk(NULL) = 0x59977268b000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd9fc10fb0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4b3ca25000
execve는 execute vector environment의 줄임말이다. 첫 번째 인자에 있는 실행 파일을 실행시킨다. 두 번째 인자는 전달되는 추가 인자이고 세 번째 인자는 환경 변수 포인터이다.
brk는 프로세스의 힙 영역을 다루는 시스템 콜이다. 인자로 NULL을 전달하면 힙 메모리 끝 주소가 출력된다.
arch_ptrctl은 특정 아키텍처의 프로세스나 스레드 상태를 설정한다. FS 레지스터나 GS 레지스터를 다루는 것으로 보인다. 첫 번째 인자 op는 작업을 선택해서 두 번째 인자 addr를 그 작업에게 전달한다. EINVAL은 op가 유효한 작업이 아닐 때 발생하는 에러이다. 해당 시스템 콜은 실패했지만 실행에 큰 영향이 없는 것으로 보인다.
mmap은 메모리를 동적 할당하는 시스템 콜이다. 읽기와 쓰기 권한이 있는 8KB의 메모리를 할당하고 있다. 이 영역은 다른 프로세스에 의해 변경되더라도 현재 프로세스에는 영향이 미치지 않도록 복사본 방식으로 처리된다. 익명 페이지로 할당되고 있다.
2. 실행 파일 로드
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=33087, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 33087, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4b3ca1c000
close(3)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4b3c600000
mprotect(0x7f4b3c628000, 2023424, PROT_NONE) = 0
mmap(0x7f4b3c628000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f4b3c628000
mmap(0x7f4b3c7bd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f4b3c7bd000
mmap(0x7f4b3c816000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7f4b3c816000
mmap(0x7f4b3c81c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4b3c81c000
close(3)
access는 지정된 파일에 대한 접근 권한을 확인하는 데 사용된다. "/etc/ld.so.preload" 파일은 시스템의 다른 라이브러리들보다 먼저 로드될 라이브러리를 명시한 파일이다. 이 파일에 대한 읽기 권한 확인을 시도했으나 해당 파일이 없어 작업에 실패했다.
openat은 파일을 여는 시스템 콜이다. 현재 작업 디렉토리에서 "/lib/x86_64-linux-gnu/libc.so.6"라는 파일을 읽기 전용으로, 그리고 파일 디스크립터가 자식 프로세스에 의해 상속되지 않도록 (exec을 호출하면 이 파일 디스크립터가 자동으로 닫히게 설정한 것) 열고 있다. 파일 열기가 성공하여 파일 디스크립터 3이 반환되었다.
newfsstatat은 파일의 상태를 가져오는 시스템 콜이다. 이 호출은 세 번째 인자로 전달된 주소에 파일의 메타 데이터를 반환한다.
mmap은 파일 디스크립터 3이 가리키는 파일 "/etc/ld.so.cache"을 메모리에 매핑한다.
close는 파일 디스크립터를 닫는 시스템 콜이다. 3번 파일 디스크립터에 연결된 리소스를 해제한다.
read와 pread64는 "/lib/x86_64-linux-gnu/libc.so.6" 파일을 지정된 크기만큼 읽어들인다.
mprotect는 특정 메모리 영역에 대한 접근 권한을 설정하거나 변경한다.
3. 스레드 관련 정보 설정 외
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4b3ca19000
arch_prctl(ARCH_SET_FS, 0x7f4b3ca19740) = 0
set_tid_address(0x7f4b3ca19a10) = 183206
set_robust_list(0x7f4b3ca19a20, 24) = 0
rseq(0x7f4b3ca1a0e0, 0x20, 0, 0x53053053) = 0
mprotect(0x7f4b3c816000, 16384, PROT_READ) = 0
mprotect(0x599760f29000, 4096, PROT_READ) = 0
mprotect(0x7f4b3ca5f000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f4b3ca1c000, 33087) = 0
getrandom("\xfc\x49\x33\x43\xa7\x68\x92\xe2", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x59977268b000
brk(0x5997726ac000) = 0x5997726ac000
스레드 환경에 필요한 정보들을 준비하거나 필요 없는 메모리를 해제한다.
4. DNS 설정 조회
newfstatat(AT_FDCWD, "/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=950, ...}, 0) = 0
openat(AT_FDCWD, "/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=92, ...}, AT_EMPTY_PATH) = 0
read(3, "# The \"order\" line is only used "..., 4096) = 92
read(3, "", 4096) = 0
close(3) = 0
futex(0x7f4b3c82332c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=950, ...}, AT_EMPTY_PATH) = 0
read(3, "# This is /run/systemd/resolve/s"..., 4096) = 950
read(3, "", 4096) = 0
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=950, ...}, AT_EMPTY_PATH) = 0
close(3)
"/etc/resolv.conf"는 리눅스 시스템에서 DNS 설정을 관리하는 파일이다. EC2 우분투 기준 nameserver에 127.0.0.53가 매핑되어 있다.
5. NSCD 연결 시도
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)
유닉스 도메인 소켓 (파일 디스크립터 3번)을 사용하여 NSCD(Network Service Cache Daemon)에 연결을 시도했으나 해당 파일이 존재하지 않아 연결이 실패했다. 네트워크 서비스 캐시 데몬은 리눅스 시스템에서 DNS 질의 결과를 캐시하여 동일한 도메인 이름에 대한 요청이 발생할 때마다 DNS 서버에 직접 질의하는 대신 캐시된 값을 빠르게 반환해주는 서비스이다.
6. 네임 서비스 스위치 설정 조회
newfstatat(AT_FDCWD, "/etc/nsswitch.conf", {st_mode=S_IFREG|0644, st_size=510, ...}, 0) = 0
newfstatat(AT_FDCWD, "/", {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=510, ...}, AT_EMPTY_PATH) = 0
read(3, "# /etc/nsswitch.conf\n#\n# Example"..., 4096) = 510
read(3, "", 4096) = 0
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=510, ...}, AT_EMPTY_PATH) = 0
close(3)
"/etc/nsswitch.conf" 파일은 네임 서비스 스위치 설정을 관리하는 파일로, 시스템에서 사용자 정보, 호스트 이름 정보 등을 어떻게 조회할지에 대한 방식을 지정한다.
7. DNS 서버 질의 이전에 로컬 파일 조회
openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=221, ...}, AT_EMPTY_PATH) = 0
lseek(3, 0, SEEK_SET) = 0
read(3, "127.0.0.1 localhost\n\n# The follo"..., 4096) = 221
read(3, "", 4096) = 0
close(3)
"/etc/hosts" 파일은 리눅스 시스템에서 호스트 이름과 IP 주소 매핑 정보를 저장하는 파일이다. 이 파일은 DNS 서버를 사용하기 전에 시스템이 먼저 참조하는 로컬 데이터베이스 역할을 한다.
8. DNS에 www.naver.com 질의
clock_gettime(CLOCK_MONOTONIC, {tv_sec=854213, tv_nsec=904890534}) = 0
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
setsockopt(3, SOL_IP, IP_RECVERR, [1], 4) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
clock_gettime(CLOCK_REALTIME, {tv_sec=1736748130, tv_nsec=706298670}) = 0
poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
sendto(3, "\343b\1 \0\1\0\0\0\0\0\1\3www\5naver\3com\0\0\1\0\1\0"..., 42, MSG_NOSIGNAL, NULL, 0) = 42
poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}])
ioctl(3, FIONREAD, [140]) = 0
recvfrom(3, "\343b\201\200\0\1\0\5\0\0\0\1\3www\5naver\3com\0\0\1\0\1\300"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [28 => 16]) = 140
close(3)
UDP 통신으로 www.naver.com의 IP 주소를 질의한다.
9. IP 프로토콜 선택
openat(AT_FDCWD, "/etc/gai.conf", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2584, ...}, AT_EMPTY_PATH) = 0
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2584, ...}, AT_EMPTY_PATH) = 0
read(3, "# Configuration for getaddrinfo("..., 4096) = 2584
read(3, "", 4096) = 0
close(3) = 0
futex(0x7f4b3c8221c4, FUTEX_WAKE_PRIVATE, 2147483647) = 0
"/etc/gai.conf"는 getaddrinfo 함수와 관련된 DNS 설정을 포함하는 구성 파일이다. 주로 IPv4와 IPv6 주소간 우선순위 설정을 제공한다. EC2의 "etc/gai.conf" 파일에는 내용이 없었다.
10. 호스트의 네트워크 인터페이스 등의 정보 조회
socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3
bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0
getsockname(3, {sa_family=AF_NETLINK, nl_pid=183206, nl_groups=00000000}, [12]) = 0
sendto(3, [{nlmsg_len=20, nlmsg_type=RTM_GETADDR, nlmsg_flags=NLM_F_REQUEST|NLM_F_DUMP, nlmsg_seq=1736748130, nlmsg_pid=0}, {ifa_family=AF_UNSPEC, ...}], 20, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 20
recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[[{nlmsg_len=76, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=1736748130, nlmsg_pid=183206}, {ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")}, [[{nla_len=8, nla_type=IFA_ADDRESS}, inet_addr("127.0.0.1")], [{nla_len=8, nla_type=IFA_LOCAL}, inet_addr("127.0.0.1")], [{nla_len=7, nla_type=IFA_LABEL}, "lo"], [{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=251, tstamp=251}]]], [{nlmsg_len=96, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=1736748130, nlmsg_pid=183206}, {ifa_family=AF_INET, ifa_prefixlen=20, ifa_flags=0, ifa_scope=RT_SCOPE_UNIVERSE, ifa_index=if_nametoindex("eth0")}, [[{nla_len=8, nla_type=IFA_ADDRESS}, inet_addr("{호스트의 IP 주소}")], [{nla_len=8, nla_type=IFA_LOCAL}, inet_addr("{호스트의 IP 주소}")], [{nla_len=8, nla_type=IFA_BROADCAST}, inet_addr("{호스트의 IP 주소}")], [{nla_len=9, nla_type=IFA_LABEL}, "eth0"], [{nla_len=8, nla_type=IFA_FLAGS}, 0], [{nla_len=8, nla_type=IFA_RT_PRIORITY}, 100], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=1809, ifa_valid=1809, cstamp=631, tstamp=85242228}]]]], iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 172
recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[[{nlmsg_len=72, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=1736748130, nlmsg_pid=183206}, {ifa_family=AF_INET6, ifa_prefixlen=128, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")}, [[{nla_len=20, nla_type=IFA_ADDRESS}, inet_pton(AF_INET6, "::1")], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=251, tstamp=251}], [{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT]]], [{nlmsg_len=80, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=1736748130, nlmsg_pid=183206}, {ifa_family=AF_INET6, ifa_prefixlen=64, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_LINK, ifa_index=if_nametoindex("eth0")}, [[{nla_len=20, nla_type=IFA_ADDRESS}, inet_pton(AF_INET6, "fe80::82:76ff:fe74:cf53")], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=629, tstamp=629}], [{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT], [{nla_len=5, nla_type=0xb /* IFA_??? */}, "\x03"]]]], iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 152
recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[{nlmsg_len=20, nlmsg_type=NLMSG_DONE, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=1736748130, nlmsg_pid=183206}, 0], iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 20
close(3)
NETLINK_ROUTE 소켓을 사용하여 시스템의 IP 주소 정보 (루프백 인터페이스, 이더넷 인터페이스)를 조회한다.
11. 연결 시도
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("223.130.192.247")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(50108), sin_addr=inet_addr("{호스트의 IP 주소}")}, [28 => 16]) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("223.130.192.248")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(34196), sin_addr=inet_addr("{호스트의 IP 주소}")}, [28 => 16]) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("223.130.200.219")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(50829), sin_addr=inet_addr("{호스트의 IP 주소}")}, [28 => 16]) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("223.130.200.236")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(51966), sin_addr=inet_addr("{호스트의 IP 주소}")}, [28 => 16]) = 0
close(3)
getsockname 함수로 로컬 포트를 확인한 후에 connect 함수로 UDP 소켓을 사용하여 IP 주소에 연결을 시도한다.
12. 결과 출력
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0
write(1, "223.130.192.247\n", 16) = 16
write(1, "223.130.192.248\n", 16) = 16
write(1, "223.130.200.219\n", 16) = 16
write(1, "223.130.200.236\n", 16) = 16
exit_group(0) = ?
+++ exited with 0 +++
표준 출력 (1번 파일 디스크립터)에 IP 주소들을 출력한다.
소감
getaddrinfo의 내부 동작을 보려고 하다보니 strace라는 강력한 명령어를 알게 됐는데 이것이 이번에 가장 큰 수확이었던 것 같다. 앞으로 시스템 콜들을 공부할 때 strace를 이용하면 많은 도움이 될 것 같다. 소켓 통신을 할 때 사용되는 매크로 정의, 구조체가 너무 많아서 코드 이해가 좀 어렵다. low level 코드는 잠시 미뤄두고 추상화 함수들을 위주로 공부를 먼저 하는 게 순서인 것 같다.
'Computer System > 네트워크' 카테고리의 다른 글
[네트워크] Little Endian <-> Big Endian, binary <-> dotted-decimal 변환하기 (0) | 2025.01.13 |
---|---|
[네트워크] 10강 전송 프로토콜: UDP와 TCP (0) | 2025.01.03 |
[네트워크] 9강 멀티 캐스팅 (0) | 2025.01.03 |
[네트워크] 2강 스위칭 (0) | 2025.01.02 |
[네트워크] 8강 패킷의 라우팅 (0) | 2025.01.02 |