int getaddrinfo( // 回傳 0 成功,其它錯誤 const char *node, // 名稱網址或數字網址 const char *service, // 服務 "http" 或 "80" const struct addrinfo *hints,// 位址規範 struct addrinfo **res); // 回傳位址串
網址 (node) 或服務 (service) 其中至少要有一個,加上 hints 的規範,在 res 回傳一串符合的位址。node 如果是名稱網址,需要 DNS 查詢。res 可能回傳多個位址、多個協定 (AF_INET 或 AF_INET6)、多個 socket types (SOCK_STREAM 或 SOCK_DGRAM 等),順序依據 RFC 3484 的定義,會使修改 /etc/gai.conf 調整,上尾愛用 freeaddrinfo() 放掉動態配的記憶體。
service 可以是 service name 或十進位數字字串,決定回傳位址的 port 部份。沒 service 則未定。(疑問:DNS 查詢不會設定回傳的 port?)
hints 和 res 內容攏是 struct addrinfo。
struct addrinfo { int ai_flags; int ai_family; // AF_UNSPEC, AF_INET, AF_INET6 int ai_socktype; // 0, SOCK_STREAM, SOCK_DGRAM int ai_protocol; // socklen_t ai_addrlen; // ai_addr 長度 struct sockaddr *ai_addr; // socket 位址 char *ai_canonname; struct addrinfo *ai_next; // linked-list };
沒 hints 相當於設 ai_socktype 和 ai_protocol 為 0、ai_family 為 AF_UNSPEC、和 ai_flags 為 AI_V4MAPPED | AI_ADDRCONFIG (POSIX 規範是 0)。
ai_flags:- AI_NUMERICHOST:node 必須是數字網址,不進行可能愛開時間的 DNS 查詢。
- AI_PASSIVE:沒 node 時才有作用,回傳的 socket addresses 是 INADDR_ANY 或 IN6ADDR_ANY_INIT,適合 bind() 後 accept() 的 socket,應用在接受任何主機的連線。原本沒設回傳的 socket addresse 適合用在 connect()、sendto()、或 sendmsg(),沒 node 會是 INADDR_LOOPBACK 或 IN6ADDR_LOOPBACK_INIT,應用在打算 communicate with peers running on the same host..
- AI_NUMERICSERV:已知不需要時用來禁止進行 name resolution service,有 service 時,要是數字字串。
- AI_CANONNAME:回傳的第一個位址的 ai_canonname 有主機 official name。
- AI_ADDRCONFIG:自己有設定 IPv4 界面才回傳 IPv4 位址,有設定 IPv6 界面才回傳 IPv6 位址,確保回傳的位址自己是能使用的。loopback 位址不視為設定的界面。
- AI_V4MAPPED:如果 hints.ai_family 是 AF_INET6 且沒有符合的 IPv6 位址,回傳 IPv4-mapped IPv6 addresses。
- AI_ALL:同時回傳 IPv6 和 IPv4-mapped IPv6 addresses,AI_V4MAPPED 有設才有意義。
- AI_IDN:需要的話,node 中非 ASCII 字元依據 locale 先轉成 ASCII Compatible Encoding (ACE) 的 IDN 格式,才進行 name resolution。
- AI_CANONIDN:如果 AI_CANONNAME 有設,回傳 ai_canonname 包含的 ACE xn-- prefix 部份,會依據 locale 轉換。
- AI_IDN_ALLOW_UNASSIGNED:IDNA 處理允許 unassigned Unicode code points。
- AI_IDN_USE_STD3_ASCII_RULES:IDNA 處理檢查 output 是 STD3 conforming hostname。
ai_protocol:
ai_addr:指到 struct sockaddr,內含 sa_family 及 sa_data
回傳值 0 表示成功,其它則有錯誤,可用 gai_strerror() 轉換成錯誤字串。
- EAI_ADDRFAMILY:沒有符合的 ai_family。
- EAI_AGAIN:name server 回暫時失敗,稍後 Try again。
- EAI_BADFLAGS:hints.ai_flags 有錯誤,包括 AI_CANONNAME 但 ai_canonname 是空的。
- EAI_FAIL:name server 回 permanent failure。
- EAI_FAMILY:請求的 ai_family 不支援。
- EAI_MEMORY:Out of memory.
- EAI_NODATA:指定的網址存在,但沒定義任何 network addresses。
- EAI_NONAME:node 或 service 未知。hints.ai_flags 是 AI_NUMERICSERV,但 service 不是數字字串。
- EAI_SERVICE:請求的 service 在指定的 socket type 沒有,可能其它 socket type 有。例如:service 是「shell」需要 stream socket,但 hints.ai_protocol 是 IPPROTO_UDP 或 hints.ai_socktype 是 SOCK_DGRAM。hints.ai_socktype 是 SOCK_RAW 沒有 service 概念,但卻設了 service。
- EAI_SOCKTYPE:請求的 hints.ai_socktype 不支援,包括和 hints.ai_protocol 所指不一致,例如 SOCK_DGRAM 和IPPROTO_TCP。
- EAI_SYSTEM:其它系統錯誤,檢查 errno for details.
inet_aton():只適用於轉換 IPv4 數字 IP。雖然 getaddrinfo() 也可以,但使用了許多系統呼叫。
inet_addr():obsoleted by inet_aton()
inet_pton():轉換 IPv4 或 IPv6 數字位址為二進位。
結合舊式 gethostbyname() 及 getservbyname() 的功能,並且是 reentrant 及支援 IPv6,輸出也方便後續 socket 程式直接利用。
getaddrinfo() 本身並不知道所謂的 DNS,或者對位址 cache,strace 呼叫 getaddrinfo() 的程式,可能呼叫超過 100 個系統呼叫,包括:- 開啟 PF_NETLINK socket 得知有那些界面,是 IPv4 或 IPv6 或兩者。
- socket 連接 Name Service Cache Daemon,開啟 socket /var/run/nscd/socket。依據內部狀態,可能會嘗試兩次。nscd socket 界面似乎沒標準。
- 讀 /etc/nsswitch.conf 得到「hosts: files myhostname dns」說要找 host,首先問 library libnss_files.so,如果失敗問 libnss_myhostname.so,最後問 libnss_dns.so。
- 載入 libnss_files.so 查詢 /etc/hosts。
- 載入 libnss_dns.so 讀 /etc/resolv.conf 進行 DNS 查詢。
- 讀取 getaddrinof() 設定檔 /etc/gai.conf
- 嘗試 socket 連接取得的位址
- man getaddrinfo
- Beej's Guide to Network Programming 正體中文版的 getaddrinfo()-準備開始!
- What does getaddrinfo do?
- dnsmasq 可提供 DNS cache
- 待讀:https://engineering.purdue.edu/kak/compsec/NewLectures
- 待讀:https://libwebsockets.org/lws-api-doc-master/html/md_READMEs_README_8async-dns.html/Lecture17.pdf
- Anatomy of a Linux DNS Lookup - Part I:ping 會查看 nsswitch.conf,但 host 不會。兩者都會查看 /etc/resolv.conf。