Android DNS之getaddrinfo()的實現
阿新 • • 發佈:2018-11-11
這篇筆記分析了庫函式getaddrinfo()的程式碼實現。
原型解讀
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res);
- hostname: 和gethostbyname()的入參hostname相同,要查詢的域名;
- servname:要查詢的服務名,如果指定,則返回值中還會有服務名對應的埠號;也就是該函式還有getservbyname()的功能;
- hints:表面意思為暗示,使用該引數可以對查詢結果進行約束,只返回那些滿足條件的查詢結果;後面稱該引數為過濾器
- res:該引數用於儲存指向查詢結果地址的指標,儲存查詢結果的記憶體有該函式內部分配,使用完畢後呼叫者需要呼叫freeaddrinfo()清除,呼叫者只需要提供儲存struct addrinfo *的地址即可。
關於該函式的更詳細介紹可以參考getaddrinfo(3)。
入口
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
//增加了入參NETID_UNSET和MARK_UNSET
return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
int android_getaddrinfofornet(const char *hostname, const char *servname,
const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
//入參中netid和mark值可以影響DNS請求包從哪個網絡卡出去,屬於網路相關的配置,將這些資訊組織成引數
//netcontext繼續向下傳遞
struct android_net_context netcontext = {
.app_netid = netid,
.app_mark = mark,
.dns_netid = netid,
.dns_mark = mark,
.uid = NET_CONTEXT_INVALID_UID,
};
return android_getaddrinfofornetcontext(hostname, servname, hints, &netcontext, res);
}
這裡有必要對這兩個函式做個說明:
- getaddrinfo()是標準的libc介面,系統中Framework以下的C/C++程式碼可以呼叫該介面實現DNS查詢;
- android_getaddrinfofornet()是Android對libc的擴充套件,當APP在Framework通過InetAddress類提供的介面進行DNS查詢時,最終會通過JNI呼叫到該函式,也就是說該介面是給Framework使用的。
android_getaddrinfofornetcontext()
該函式的作用主要是對輸入引數的合法性進行檢查,以及處理hostname為空或者是純數字格式的情形,如果需要真正發起DNS請求,呼叫explore_fqdn()完成。
/* 定義了faimly、socktype、protocol欄位的有效組合 */
static const struct explore explore[] = {
#ifdef INET6
{ PF_INET6, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
{ PF_INET6, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
{ PF_INET6, SOCK_RAW, ANY, NULL, 0x05 },
#endif
{ PF_INET, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
{ PF_INET, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
{ PF_INET, SOCK_RAW, ANY, NULL, 0x05 },
{ PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
{ PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
{ PF_UNSPEC, SOCK_RAW, ANY, NULL, 0x05 },
{ -1, 0, 0, NULL, 0 },
};
int android_getaddrinfofornetcontext(const char *hostname, const char *servname,
const struct addrinfo *hints, const struct android_net_context *netcontext,
struct addrinfo **res)
{
//這裡定義了3個addrinfo結構變數,後面對於這些變數的使用非常繞,要注意理解下面的註釋說明
struct addrinfo sentinel;
struct addrinfo *cur;
int error = 0;
struct addrinfo ai;
struct addrinfo ai0;
struct addrinfo *pai;
const struct explore *ex;
/* hostname is allowed to be NULL */
/* servname is allowed to be NULL */
/* hints is allowed to be NULL */
assert(res != NULL);
assert(netcontext != NULL);
//sentinel清零,並且cur指向該結構
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
//pai指向ai,並且對ai的成員進行初始化
pai = &ai;
pai->ai_flags = 0;
pai->ai_family = PF_UNSPEC;
pai->ai_socktype = ANY;
pai->ai_protocol = ANY;
pai->ai_addrlen = 0;
pai->ai_canonname = NULL;
pai->ai_addr = NULL;
pai->ai_next = NULL;
//hostname和servname二者不能同時為空,至少要指定一個
if (hostname == NULL && servname == NULL)
return EAI_NONAME;
//如果指定了過濾器,則對過濾器成員進行檢查,因為作為過濾器,並非addrinfo中的每個成員都是可以指定的
if (hints) {
/* error check for hints */
//作為過濾器,addrinfo的這四個成員是不能指定的,必須保持為0才行
if (hints->ai_addrlen || hints->ai_canonname ||
hints->ai_addr || hints->ai_next)
ERR(EAI_BADHINTS); /* xxx */
//ai_flags中有沒有指定非法的標記
if (hints->ai_flags & ~AI_MASK)
ERR(EAI_BADFLAGS);
//ai_faimly成員只可取下面這三個值,其它值均非法
switch (hints->ai_family) {
case PF_UNSPEC:
case PF_INET:
#ifdef INET6
case PF_INET6:
#endif
break;
default:
ERR(EAI_FAMILY);
}
//從這裡可以看出,過濾器引數最終是被儲存在了ai中,這就是ai的作用
memcpy(pai, hints, sizeof(*pai));
/*
* if both socktype/protocol are specified, check if they
* are meaningful combination.
*/
//如果同時指定了hints引數中的ai_socktype和ai_protocol欄位(非0),那麼繼續檢查這兩個引數的組合是否合理
//因為並非所有的組合都是有效的,比如socktype指定為STREAM,但是protocol指定為DGRAM就不合理
if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
//expore陣列定義了faimly、socktype、protocol的有效組合
for (ex = explore; ex->e_af >= 0; ex++) {
if (pai->ai_family != ex->e_af)
continue;
if (ex->e_socktype == ANY)
continue;
if (ex->e_protocol == ANY)
continue;
//如果是非法組合返回BADHINTS錯誤
if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) {
ERR(EAI_BADHINTS);
}
}
}
}//end of "if (hints)"
/*
* check for special cases. (1) numeric servname is disallowed if
* socktype/protocol are left unspecified. (2) servname is disallowed
* for raw and other inet{,6} sockets.
*/
//這段邏輯用於檢查服務名和hints引數是否存在衝突(見註釋)
if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
|| MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
) {
//ai0的作用就是備份hints的內容
ai0 = *pai; /* backup *pai */
if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
pai->ai_family = PF_INET6;
#else
pai->ai_family = PF_INET;
#endif
}
error = get_portmatch(pai, servname);
if (error)
ERR(error);
//將備份的hints引數內容再賦值回去,保證這步檢查不會修改hints本身的內容
*pai = ai0;
}
ai0 = *pai;
//處理hostname為空,或者hostname為純數字的情形
/* NULL hostname, or numeric hostname */
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* PF_UNSPEC entries are prepared for DNS queries only */
if (ex->e_af == PF_UNSPEC)
continue;
if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
continue;
if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
continue;
if (pai->ai_family == PF_UNSPEC)
pai->ai_family = ex->e_af;
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
if (hostname == NULL)
//處理只請求服務名轉換的情況,轉黃結果儲存到cur->ai_next中。
//從這裡可以看出sentinel的作用來,它本身不儲存查詢結果,但是查詢結果就儲存在
//sentinel.ai_next後面的addrinfo連結串列中,而cur指標永遠指向當前連結串列的最後一個成員,
//這樣便於查詢過程中不斷新增查詢結果,見下面的while迴圈
error = explore_null(pai, servname, &cur->ai_next);
else
//嘗試按照數字格式解析hostname以及服務名裝換
error = explore_numeric_scope(pai, hostname, servname,
&cur->ai_next);
if (error)
goto free;
//移動cur指標,使其指向當前查詢結果連結串列的最後一個成員
while (cur->ai_next)
cur = cur->ai_next;
}
/*
* XXX
* If numeric representation of AF1 can be interpreted as FQDN
* representation of AF2, we need to think again about the code below.
*/
//如果這裡非空,說明上一個for迴圈中已經解析的結果,請求的hostname就是為空或者是純數字格式,
//那麼無需再發起DNS查詢,返回結果即可
if (sentinel.ai_next)
goto good;
//hostname為空的場景上面應該已經處理,不應該會執行到這裡
if (hostname == NULL)
ERR(EAI_NODATA);
//hostname為數字格式的情形上面應該已經處理,不應該會執行到這裡
if (pai->ai_flags & AI_NUMERICHOST)
ERR(EAI_NONAME);
//這裡是Android的變化,就是將DNS查詢導向到netd中,然後由netd處理後再回到這裡,
//這個過程和gethostbyname()中的處理流程一致,可見gethostbyname()實現中的分析
#if defined(__ANDROID__)
//非netd呼叫會阻塞到這裡,等待netd查詢完後返回
int gai_error = android_getaddrinfo_proxy(
hostname, servname, hints, res, netcontext->app_netid);
//如果是netd呼叫,那麼android_getaddrinfo_proxy()會返回EAI_SYSTEM錯誤,繼續後續的邏輯
if (gai_error != EAI_SYSTEM) {
return gai_error;
}
#endif
/*
* hostname as alphabetical name.
* we would like to prefer AF_INET6 than AF_INET, so we'll make a
* outer loop by AFs.
*/
//按照explore陣列中指定的有效組合,呼叫explore_fqdn()進行DNS查詢
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* require exact match for family field */
if (pai->ai_family != ex->e_af)
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype,
WILD_SOCKTYPE(ex))) {
continue;
}
if (!MATCH(pai->ai_protocol, ex->e_protocol,
WILD_PROTOCOL(ex))) {
continue;
}
//這步會執行,以為explore中,socktype欄位都是固定的,並非通配
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
//進行DNS查詢
error = explore_fqdn(
pai, hostname, servname, &cur->ai_next, netcontext);
//查詢結果指標cur前移
while (cur && cur->ai_next)
cur = cur->ai_next;
}
//sentinel.ai_next不為空,說明有查詢結果,所以設定error為0
if (sentinel.ai_next)
error = 0;
//有錯誤發生,需要呼叫freeaddrinfo()釋放可能已經分配的記憶體空間
if (error)
goto free;
if (error == 0) {
if (sentinel.ai_next) {
good:
//查詢過程沒有問題 && 查詢到了結果,返回成功
*res = sentinel.ai_next;
return SUCCESS;
} else
//查詢過程沒有問題(error=0),但是沒有查到結果(sentinel.ai_next=NULL),也是失敗
error = EAI_FAIL;
}
free:
bad:
//查詢失敗,或者查詢過程出錯,釋放記憶體後返回錯誤碼
if (sentinel.ai_next)
freeaddrinfo(sentinel.ai_next);
*res = NULL;
return error;
}
explore_fqdn()
看到該函式的程式碼,一定會非常熟悉,在gethostbyname()的實現中,已經見過這種查詢表方式,這裡不再贅述。
/*
* FQDN hostname, DNS lookup
*/
static int explore_fqdn(const struct addrinfo *pai, const char *hostname,
const char *servname, struct addrinfo **res,
const struct android_net_context *netcontext)
{
struct addrinfo *result;
struct addrinfo *cur;
int error = 0;
//_files_getaddrinfo()完成基於檔案的查詢;_dns_getaddrinfo()完成基於DNS伺服器的查詢;
//_yp_getaddrinfo()不會被呼叫
static const ns_dtab dtab[] = {
NS_FILES_CB(_files_getaddrinfo, NULL)
{ NSSRC_DNS, _dns_getaddrinfo, NULL }, /* force -DHESIOD */
NS_NIS_CB(_yp_getaddrinfo, NULL)
{ 0, 0, 0 }
};
assert(pai != NULL);
/* hostname may be NULL */
/* servname may be NULL */
assert(res != NULL);
result = NULL;
/*
* if the servname does not match socktype/protocol, ignore it.
*/
//如果指定了servname,檢查socktype和protocol是否有衝突,Android中不涉及
if (get_portmatch(pai, servname) != 0)
return 0;
//呼叫nsdispatch()進行查詢分發
switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
default_dns_files, hostname, pai, netcontext)) {
case NS_TRYAGAIN:
error = EAI_AGAIN;
goto free;
case NS_UNAVAIL:
error = EAI_FAIL;
goto free;
case NS_NOTFOUND:
error = EAI_NODATA;
goto free;
case NS_SUCCESS:
error = 0;
//如果hostname查詢成功,如果指定了servname,將對應的埠名轉換為埠號,Android不涉及
for (cur = result; cur; cur = cur->ai_next) {
GET_PORT(cur, servname);
/* canonname should be filled already */
}
break;
}
*res = result;
return 0;
free:
if (result)
freeaddrinfo(result);
return error;
}
_dns_getaddrinfo()
我們不關注基於檔案的查詢_files_getaddrinfo()的實現,只看基於DNS伺服器的查詢實現。
static int _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
{
struct addrinfo *ai;
querybuf *buf, *buf2;
const char *name;
const struct addrinfo *pai;
struct addrinfo sentinel, *cur;
struct res_target q, q2;
res_state res;
const struct android_net_context *netcontext;
//從explore_fqdn()傳入的三個引數
name = va_arg(ap, char *);
pai = va_arg(ap, const struct addrinfo *);
netcontext = va_arg(ap, const struct android_net_context *);
memset(&q, 0, sizeof(q));
memset(&q2, 0, sizeof(q2));
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
//buf和buf2用於儲存查詢結果,見下文的getanswer()
buf = malloc(sizeof(*buf));
if (buf == NULL) {
h_errno = NETDB_INTERNAL;
return NS_NOTFOUND;
}
buf2 = malloc(sizeof(*buf2));
if (buf2 == NULL) {
free(buf);
h_errno = NETDB_INTERNAL;
return NS_NOTFOUND;
}
//這段邏輯非常重要,決定了是否發起AAAA與A查詢以及它們的順序
switch (pai->ai_family) {
case AF_UNSPEC:
//當沒有指定具體的failmy時,優先IPv6
/* prefer IPv6 */
q.name = name;
q.qclass = C_IN;
q.answer = buf->buf;
q.anslen = sizeof(buf->bu