QUESTION
getaddrinfo 결과의 첫 주소만 연결하면 충분한가?
getaddrinfo는 DNS 전용 함수가 아니다. /etc/nsswitch.conf 정책에 따라 files, DNS, mDNS 같은 source를 조회하고 address family와 socket type 조건에 맞는 addrinfo list를 만든다.
반환 순서는 destination address selection policy를 반영하지만 첫 주소가 도달 가능하다는 보장은 없다. 각 result를 시도하고 전체 timeout을 관리하거나 IPv6/IPv4 candidate를 병렬화한다.
STRUCTURE
구조 그림
addrinfo 결과는 후보 목록이다. 첫 행이 실패하거나 지연되면 다른 family/address를 전체 deadline 안에서 시도한다.
CALL PATH
호출 흐름
이름 해석 결과와 실제 network connection 결과를 다른 cache/state로 관리한다. address list 하나가 service instance 하나를 뜻하지 않는다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
Linux 6.18.37 LTS 소스 위치
glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.
| 파일 | 함수·구조체 | 여기서 볼 것 |
|---|---|---|
| net/socket.c | __sys_connect() | resolver가 만든 sockaddr를 실제 socket operation에 사용 |
| net/ipv6/af_inet6.c | inet6_create(), inet6_bind() | IPv6 socket family 경로 |
| net/ipv4/af_inet.c | inet_create() | IPv4 socket family 경로와 protocol 선택 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 resolve.c -o resolve01#define _POSIX_C_SOURCE 200809L
02#include <arpa/inet.h>
03#include <netdb.h>
04#include <stdio.h>
05#include <string.h>
06
07int main(int argc, char **argv)
08{
09 if (argc != 3)
10 return 2;
11 struct addrinfo hints;
12 memset(&hints, 0, sizeof(hints));
13 hints.ai_family = AF_UNSPEC;
14 hints.ai_socktype = SOCK_STREAM;
15 hints.ai_protocol = IPPROTO_TCP;
16
17 struct addrinfo *results;
18 int error = getaddrinfo(argv[1], argv[2], &hints, &results);
19 if (error != 0) {
20 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
21 return 1;
22 }
23 for (const struct addrinfo *item = results; item != NULL; item = item->ai_next) {
24 char host[NI_MAXHOST], service[NI_MAXSERV];
25 int rc = getnameinfo(item->ai_addr, item->ai_addrlen,
26 host, sizeof(host), service, sizeof(service),
27 NI_NUMERICHOST | NI_NUMERICSERV);
28 if (rc == 0)
29 printf("family=%d %s:%s\n", item->ai_family, host, service);
30 }
31 freeaddrinfo(results);
32 return 0;
33}
CODE NOTES
코드 조각별 설명
memset(&hints, 0addrinfo의 사용하지 않는 field와 padding을 0으로 시작해 명시한 조건만 resolver에 전달한다.
AF_UNSPECIPv4/IPv6 모두 허용한다. AI_ADDRCONFIG 같은 flag는 host interface 구성에 따라 결과를 줄일 수 있어 요구사항에 맞춰 선택한다.
int error = getaddrinfo반환값은 errno가 아니라 EAI_* 코드다. gai_strerror로 해석하고 EAI_SYSTEM일 때만 errno가 추가 의미를 가진다.
item = item->ai_next연결 가능한 candidate list 전체를 순회한다. ai_addr와 ai_addrlen을 해당 family socket connect에 그대로 사용한다.
NI_NUMERICHOST | NI_NUMERICSERV출력 단계에서 reverse DNS와 service lookup을 다시 하지 않고 numeric address/port만 format한다.
DETAILS
세부 동작
NSS lookup은 blocking 작업일 수 있다
getaddrinfo 호출 thread는 resolver timeout, NSS module, network 응답을 기다릴 수 있다. event loop thread에서 직접 호출하면 모든 connection 처리가 멈춘다.
전용 resolver pool, getaddrinfo_a 같은 비동기 확장, application DNS client를 latency 요구에 맞춰 선택한다.
AI_PASSIVE와 wildcard address
server bind용 NULL node + AI_PASSIVE는 wildcard address를 만든다. AI_PASSIVE가 없으면 loopback address가 나올 수 있다. client와 server hint를 같은 helper로 섞지 않는다.
IPv6 wildcard socket의 v4-mapped 동작은 IPV6_V6ONLY 설정과 OS 정책에 따라 확인한다.
cache TTL과 connection lifetime은 다르다
DNS record TTL이 끝나도 이미 established된 TCP connection이 자동으로 새 address로 이동하지 않는다. resolver cache, connection pool, retry 정책의 수명을 각각 둔다.
negative cache와 deployment address rotation 때 stale pool을 어떻게 drain할지 정한다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
addrinfo list | getaddrinfo가 할당하고 freeaddrinfo에서 전체 해제한다 | family, socktype, protocol, sockaddr |
NSS query state | resolver 호출 동안 source별로 생기고 결과/오류 뒤 정리된다 | timeout, search domain, cache |
connection candidate | addrinfo entry마다 socket/deadline을 만들고 성공 또는 실패에서 닫는다 | address, attempt time, error |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| EAI_AGAIN | 일시 resolver 실패/timeout | NSS source와 retry budget |
| 첫 address에서 오래 멈춤 | candidate 직렬 connect | family별 attempt timeline |
| event loop stall | blocking getaddrinfo를 loop thread에서 호출 | thread stack과 resolver latency |
LAB
직접 확인
- localhost와 실제 hostname에서 /etc/hosts, nsswitch, DNS syscall 차이를 strace로 확인한다.
- AF_INET/AF_INET6/AF_UNSPEC hint 결과를 비교하고 각 candidate connect 오류를 기록한다.
- resolver worker thread와 result eventfd를 만들어 main epoll loop를 block하지 않게 한다.
./resolve localhost 80strace -f -e trace=openat,read,connect,sendto,recvfrom ./resolve localhost 80PRIMARY REFERENCES