Android DNS解析

原文地址:http://blog.csdn.net/insswer/article/details/17382535

1. Change of Android4.3


在Android4.3以前,如果系統需要備份/恢復,防火牆以及DNS解析管理,Linux內核微調等,是需要ROOT權限才能進行的。在Android4.3中,Google修改了這一策略,Google向用戶提供API和擴展來完成這些事情。其中DNS解析就是這一改變中的一環。







2. Android的DNS解析



Bionic是Android自己的C庫版本。


在早期版本的Android中,DNS解析的方式類似於Ubuntu等發行版Linux。都是通過resovl.conf文件進行域名解析的。在老版本Android的bionic/libc/docs/overview.txt中可以看到,Android的DNS也是採用NetBSD-derived resolver library來實現,不同的是,bionic對其進行了一些修改。這些修改包括:


1.     resovle.conf文件的位置不再是/etc/resolv.conf,在Android中改爲了/system/etc/resolv.conf。

2.     從系統屬性(SystemProperties)中讀取DNS服務器,比如“net.dns1”,“net.dns2”等。每一個屬性必須包括了DNS服務器的IP地址。

3.     不實現Name ServiceSwitch。

4.     在查詢時,使用一個隨機的查詢ID,而非每次自增1.

5.     在查詢時,將本地客戶端的socket綁定到一個隨機端口以增強安全性。



3. Java與JNI層中DNS解析的公共流程



我們從下面小例子開始分析公共流程中DNS解析所經過的函數,對於Android中JNI和JAVA等層次概念請參考最開始的那一張結構圖:



[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. //獲得www.taobao.com對應的IP地址,並通過Toast的方式打印出來  
  2. try {  
  3.         InetAddress inetAddress = InetAddress.getByName("www.taobao.com");  
  4.         Toast.makeText(MainActivity.this"Address is " + inetAddress.getHostAddress(), Toast.LENGTH_LONG).show();      } catch (UnknownHostException e) {  
  5.                     // TODO Auto-generated catch block  
  6.                     e.printStackTrace();  
  7.       }  

以上Java代碼給出了最簡單的一次DNS解析的方法。主要實現是調用InetAddress類的靜態方法getByName,該方法返回一個InetAddress實例,該實例中包括很多關於域名的信息。


[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static InetAddress getByName(String host) throws UnknownHostException {  
  2.     return getAllByNameImpl(host)[0];  
  3. }  

實際調用getAllByNameImpl函數。該函數內部主要進行三件事情,第一件,如果host是null,那麼調用loopbackAddresses()。如果host是數字形式的地址,那麼調用parseNumericAddressNoThrow解析並返回。如果是一個字符串,則使用lookupHostByName(host)返回一個InetAddress並clone一份返回。

 

lookupHostByName函數首先host的信息是否存在在緩存當中,如果有則返回。如果沒有則:


[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);  

getaddrinfo函數是一個native本地函數,聲明如下:


[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;  

在getaddrinfo對應的JNI層函數中,實際調用了下面函數:


[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);  

getaddrinfo實現自bionic的netbsd庫,具體文件位於/bionic/libc/netbsd/net中,後面我們會分析Android4.2和Android4.3的代碼,來觀察Google在Android4.3中對DNS解析做了什麼樣的修改。

除了getaddrinfo路徑以外,在Java中InetAddress還有其他方式,比如

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public String getHostName() {  
  2.         if (hostname == null) {  
  3.             try {  
  4.                 hostname = getHostByAddrImpl(this).hostName;  
  5.             } catch (UnknownHostException ex) {  
  6.                 hostname = getHostAddress();  
  7.             }  
  8.         }  
  9.         return hostname;  
  10. }  


上述方法,調用了getHostByAddrImpl,在getHostByAddrImpl中:


[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. String hostname = Libcore.os.getnameinfo(address, NI_NAMEREQD);  

調用了getnameinfo方法,該方法同樣是一個native函數,在JNI層對應的函數中直接調用了getnameinfo這個bionic庫的函數:


  1. int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), size, buf, sizeof(buf), NULL, 0, flags);  




4. Android4.2和Android4.3 bionic中DNS解析實現的變化



不管是getaddrinfo還是getnameinfo還是gethostbyname,都是實現在bionic庫中,這裏先以getaddrinfo爲例分析Android4.3前後bionic在DNS解析處通用邏輯的變化。先從4.3以前版本開始。

在getaddrinfo中,關鍵的一步如下:



  1. /*          
  2. * BEGIN ANDROID CHANGES; proxying to the cache 
  3. */  
  4. if (android_getaddrinfo_proxy(hostname, servname, hints, res) == 0) {  
  5. return 0;  
  6. }  

注意上面的註釋,ANDROID_CHANGES,Google在Android4.2.2開始已經打算將所有DNS解析的方式向Netd代理的方式過渡了。後面我們還會看到ANDROID_CHANGES。

然後在android_getaddrinfo_proxy中,我們可以看到如下代碼:


  1. snprintf(propname, sizeof(propname), "net.dns1.%d", getpid());  
  2. if (__system_property_get(propname, propvalue) > 0) {  
  3.         return -1;  
  4.     }  
  5. // Bogus things we can't serialize.  Don't use the proxy.  
  6. if ((hostname != NULL &&  
  7.     strcspn(hostname, " \n\r\t^'\"") != strlen(hostname)) ||  
  8.    (servname != NULL &&  
  9.     strcspn(servname, " \n\r\t^'\"") != strlen(servname))) {  
  10.     return -1;  
  11. }  
  12. …  
  13. // Send the request.  
  14. proxy = fdopen(sock, "r+");  
  15. if (fprintf(proxy, "getaddrinfo %s %s %d %d %d %d",  
  16.         hostname == NULL ? "^" : hostname,  
  17.         servname == NULL ? "^" : servname,  
  18.         hints == NULL ? -1 : hints->ai_flags,  
  19.         hints == NULL ? -1 : hints->ai_family,  
  20.         hints == NULL ? -1 : hints->ai_socktype,  
  21.         hints == NULL ? -1 : hints->ai_protocol) < 0) {  
  22.     goto exit;  
  23. }  
  24. // literal NULL byte at end, required by FrameworkListener  
  25. if (fputc(0, proxy) == EOF ||  
  26.     fflush(proxy) != 0) {  
  27.     goto exit;  
  28. }  

Android會首先嚐試從系統屬性(System Property)中讀取DNS服務器的IP地址,然後使用這個DNS服務器來進行DNS解析。如果沒有設置相關係統屬性,則採用Netd的方式來進行DNS解析。由於在使用Netd方式進行解析的時候server name是不能爲NULL的,所以可以看到上面將server name修改成了’^’。在分析Netd代理之前,我們最好停一停,看看Android4.3後,getaddrinfo是怎麼做的。

 

首先是從JNI層的getaddrinfo的代碼開始:


  1. int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);  

和Android4.2.2沒有變化,直接調用了getaddrinfo,其中第二個參數是NULL。


  1. Int  
  2. getaddrinfo(const char *hostname, const char *servname,  
  3. const struct addrinfo *hints, struct addrinfo **res)  
  4. {  
  5.     return android_getaddrinfoforiface(hostname, servname, hints, NULL, 0, res);  
  6. }  

直接調用了android_getaddrinfoforiface函數。


  1. /* 4.3 */  
  2. static int android_getaddrinfo_proxy(  
  3.     const char *hostname, const char *servname,  
  4.     const struct addrinfo *hints, struct addrinfo **res, const char *iface)  
  5. {  
  6.     int sock;  
  7.     const int one = 1;  
  8.     struct sockaddr_un proxy_addr;  
  9.     FILE* proxy = NULL;  
  10.     int success = 0;  
  11.     *res = NULL;  
  12.   
  13.     if ((hostname != NULL &&  
  14.          strcspn(hostname, " \n\r\t^'\"") != strlen(hostname)) ||  
  15.         (servname != NULL &&  
  16.          strcspn(servname, " \n\r\t^'\"") != strlen(servname))) {  
  17.         return EAI_NODATA;  
  18.     }  
  19.   
  20.     sock = socket(AF_UNIX, SOCK_STREAM, 0);  
  21.     if (sock < 0) {  
  22.         return EAI_NODATA;  
  23.     }  
  24.   
  25.     …….  


很明顯,Android4.3以後刪掉了讀取系統屬性的那一段代碼,這時如果任然採用添加系統屬性的方法來修改DNS服務器將不會產生任何作用。

 

Android除了使用getaddrinfo函數外,系統代碼還會使用gethostbyname等其他路徑。下面我們再看看gethostbyname路徑在Android4.3前後發生的變化。

在給出代碼之前,先說明下gethostbyname函數內部將調用gethostbyname_internal來真正進行DNS解析。


Android4.2.2:



  1. static struct hostent *  
  2. gethostbyname_internal(const char *name, int af, res_state res)  
  3. {  
  4.     …  
  5.   
  6.     rs->host.h_addrtype = af;  
  7.     rs->host.h_length = size;  
  8.     /* 
  9.      * if there aren’t any dots, it could be a user-level alias. 
  10.      * this is also done in res_nquery() since we are not the only 
  11.      * function that looks up host names. 
  12.      */  
  13.     if (!strchr(name, ‘.’) && (cp = __hostalias(name)))  
  14.         name = cp;  
  15.       
  16. /* 
  17.      * disallow names consisting only of digits/dots, unless 
  18.      * they end in a dot. 
  19.      */  
  20.     if (isdigit((u_char) name[0]))  
  21.         for (cp = name;; ++cp) {  
  22.                            …  
  23.         }  
  24.             if (!isdigit((u_char) *cp) && *cp != ‘.’)  
  25.                 break;  
  26.         }  
  27.     if ((isxdigit((u_char) name[0]) && strchr(name, ‘:’) != NULL) ||  
  28.         name[0] == ‘:’)  
  29.         for (cp = name;; ++cp) {  
  30.             if (!*cp) {  
  31.                 …  
  32.             }  
  33.             if (!isxdigit((u_char) *cp) && *cp != ‘:’ && *cp != ‘.’)  
  34.                 break;  
  35.         }  
  36.     hp = NULL;  
  37.     h_errno = NETDB_INTERNAL;  
  38.     if (nsdispatch(&hp, dtab, NSDB_HOSTS, “gethostbyname”,  
  39.         default_dns_files, name, strlen(name), af) != NS_SUCCESS) {  
  40.         return NULL;  
  41.         }  
  42.     h_errno = NETDB_SUCCESS;  
  43.     return hp;  
  44. }  

先不關心使用的localdns是哪個,在Android4.2.2中,gethostbyname_internal直接調用了nsdispatch來進行域名解析。

 

下面再看看Android4.3中的變化:



  1. static struct hostent *  
  2. gethostbyname_internal(const char *name, int af, res_state res, const char *iface, int mark)  
  3. {  
  4. …  
  5.     proxy = android_open_proxy();  
  6.     if (proxy == NULL) goto exit;  
  7.   
  8.     /* This is writing to system/netd/DnsProxyListener.cpp and changes 
  9.      * here need to be matched there */  
  10.     if (fprintf(proxy, “gethostbyname %s %s %d”,  
  11.             iface == NULL ? “^” : iface,  
  12.             name == NULL ? “^” : name,  
  13.             af) < 0) {  
  14.         goto exit;  
  15.     }  
  16.   
  17.     if (fputc(0, proxy) == EOF || fflush(proxy) != 0) {  
  18.         goto exit;  
  19.     }  
  20.   
  21.     result = android_read_hostent(proxy);  
  22.   
  23. exit:  
  24.     if (proxy != NULL) {  
  25.         fclose(proxy);  
  26.     }  
  27.     return result;  
  28. }  

從上面代碼可以看到,Android4.3中徹底全面使用Netd的方式進行了DNS處理。

最後讓我們再看看getnameinfo在bionic的實現。

首先是4.2.2的代碼,路徑上getnameinfo會調用getnameinfo_inet,然後出現下面的代碼:


  1. #ifdef ANDROID_CHANGES  
  2.     struct hostent android_proxy_hostent;  
  3.     char android_proxy_buf[MAXDNAME];  
  4.     int hostnamelen = android_gethostbyaddr_proxy(android_proxy_buf,  
  5.             MAXDNAME, addr, afd->a_addrlen, afd->a_af);  
  6.     if (hostnamelen > 0) {  
  7.         hp = &android_proxy_hostent;  
  8.         hp->h_name = android_proxy_buf;  
  9.     } else if (!hostnamelen) {  
  10.         hp = NULL;  
  11.     } else {  
  12.         hp = gethostbyaddr(addr, afd->a_addrlen, afd->a_af);  
  13.     }  
  14. #else  
  15.     hp = gethostbyaddr(addr, afd->a_addrlen, afd->a_af);  
  16. #endif  

具體如何處理根據ANDROID_CHANGES宏決定,如果定義了該宏,則通過Netd的方式進行。如果沒有則直接調用gethostbyaddr,該函數後面會進行實際的dns解析。

再看看Android4.3中的實現:


  1. int hostnamelen = android_gethostbyaddr_proxy(android_proxy_buf,  
  2.                 MAXDNAME, addr, afd->a_addrlen, afd->a_af, iface, mark);  


強行使用Netd的方式完成DNS的解析。Google在Android4.3後讓DNS解析全部採用Netd代理的方式進行。


Netd是Network Daemon的縮寫,Netd在Android中負責物理端口的網絡操作相關的實現,如Bandwidth,NAT,PPP,soft-ap等。Netd爲Framework隔離了底層網絡接口的差異,提供了統一的調用接口,簡化了整個網絡邏輯的使用。

簡單來說就是Android將監聽/dev/socket/dnsproxyd,如果系統需要DNS解析服務,那麼就需要打開dnsproxyd,然後安裝一定的格式寫入命令,然後監聽等待目標回答。


在分析Netd前,必須知道Netd的權限和所屬。




圖中可以看出,兩者的owner都是root,現在就好理解爲什麼說Android4.3後很多原來功能不需要root的原因了,系統現在採用代理的方式,讓屬於同group的用戶可以藉助Netd來幹一些原來只有root能幹的事情。

Android的初始化大致上可以分爲三個部分:第一部分爲啓動Linux階段,該部分包括bootloader加載kernel與kernel啓動。第二部分爲android的系統啓動,入口爲init程序,這部分包括啓動service manager,啓動Zygote,初始化Java世界等。第三部分爲應用程序啓動,主要爲運行package manager。

與Netd相關聯的是第二部分,也就是init進程。init進程在初始化中會處理/init.rc以及/init.<hardware>.rc兩個初始化腳本,這些腳本決定了Android要啓動哪些系統服務和執行哪些動作。

比如:


[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. service servicemanager /system/bin/servicemanager    
  2.     user system    
  3.     critical    
  4.     onrestart restart zygote    
  5.     onrestart restart media    
  6.     
  7. service vold /system/bin/vold    
  8.     socket vold stream 0660 root mount    
  9.     ioprio be 2    
  10.     
  11. service netd /system/bin/netd    
  12.     socket netd stream 0660 root system    
  13.     socket dnsproxyd stream 0660 root inet    
  14.     
  15. service debuggerd /system/bin/debuggerd    
  16.     
  17. service ril-daemon /system/bin/rild    
  18.     socket rild stream 660 root radio    
  19.     socket rild-debug stream 660 radio system    
  20.     user root    
  21.     group radio cache inet misc audio sdcard_rw    

通過init.rc,我們可以看到netd和dnsproxy的權限和所屬。直接從代碼開始分析,netd源代碼位於/system/netd/main.cpp,由C++編寫。

從上面框架圖中可以得知,netd由四個大部分組成,一部分是NetlinkManager,一個是CommandListener,然後是DnsProxyListener和MDnsSdListener。在main函數中netd依次初始化四個部件:


  1. int main() {  
  2.   
  3.     CommandListener *cl;  
  4.     NetlinkManager *nm;  
  5.     DnsProxyListener *dpl;  
  6. MDnsSdListener *mdnsl;  
  7.   
  8. if (!(nm = NetlinkManager::Instance())) {  
  9.         ALOGE("Unable to create NetlinkManager");  
  10.         exit(1);  
  11.  };  
  12.   
  13. …  
  14.   
  15. cl = new CommandListener(rangeMap);  
  16. nm->setBroadcaster((SocketListener *) cl);  
  17.   
  18.     if (nm->start()) {  
  19.         ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));  
  20.         exit(1);  
  21.     }  
  22. setenv("ANDROID_DNS_MODE""local", 1);  
  23. dpl = new DnsProxyListener(rangeMap);  
  24.   
  25. if (dpl->startListener()) {  
  26.         ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));  
  27.         exit(1);  
  28.     }  
  29.     mdnsl = new MDnsSdListener();  
  30.     if (mdnsl->startListener()) {  
  31.         ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));  
  32.         exit(1);  
  33.     }  
  34.     if (cl->startListener()) {  
  35.         ALOGE("Unable to start CommandListener (%s)", strerror(errno));  
  36.         exit(1);  
  37.   }  

代碼都很簡單,所以不需要贅述,只不過需要注意那句setenv(“ANDROID_DNS_MODE”,”local”,1),這句在後面有大作用。如果看過bionic代碼的同學可能已經有所領悟了。

 

DnsProxyListener實際上就是pthread創造的一個線程,該線程僅僅監聽dnsproxyd這個socket。

 

其他進程如何利用dnsproxyd來進行DNS解析呢?答案很簡單,看到bionic中gethostbyname_internal中的這麼一句:


  1. if (fprintf(proxy, “gethostbyname %s %s %d”,  
  2.             iface == NULL ? “^” : iface,  
  3.             name == NULL ? “^” : name,  
  4.             af) < 0) {  
  5.         goto exit;  
  6.     }  

其他進程打開dnsproxyd後(必須要同一個組),使用命令的方式來申請DNS解析。DnsProxyListener內部邏輯是很複雜的,這裏沒必要深究。現在看看gethostbyname這個命令如何解析。

Netd當中每一個命令對應一個類,該類繼承自NetdCommand類。除此之外,還需要一個XXXXHandler的類來做實際命令的處理工作。XXXX是命令的名稱,比如對於gethostbyname就有兩個類:GetHostByNameCmd

GetHostByNameHandler。既然XXXXhandler中有兩個公共方法,一個threadStart一個叫start。除此之外,還有個私有方法run。對命令的實際處理就是run方法實現的。


  1. void DnsProxyListener::GetHostByNameHandler::run() {  
  2.     …  
  3.     struct hostent* hp;  
  4.   
  5.     hp = android_gethostbynameforiface(mName, mAf, mIface ? mIface : iface, mMark);  
  6.   
  7.     bool success = true;  
  8.     if (hp) {  
  9.         success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;  
  10.         success &= sendhostent(mClient, hp);  
  11.     } else {  
  12.         success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, NULL, 0) == 0;  
  13.     }  
  14.     if (!success) {  
  15.         ALOGW("GetHostByNameHandler: Error writing DNS result to client\n");  
  16.     }  
  17.     mClient->decRef();  
  18. }  

關鍵的兩行代碼是android_gethostbynameforiface和sendBinaryMsg,後者是將前者得到的結果應答給請求DNS解析的進程。


  1. struct hostent *  
  2. android_gethostbynameforiface(const char *name, int af, const char *iface, int mark)  
  3. {  
  4.     struct hostent *hp;  
  5.     res_state res = __res_get_state();  
  6.   
  7.     if (res == NULL)  
  8.         return NULL;  
  9.     hp = gethostbyname_internal(name, af, res, iface, mark);  
  10.     __res_put_state(res);  
  11.     return hp;  
  12. }  

關鍵仍然是調用了gethostbyname_internal。看到這裏,看官們可能就會奇怪了,進程向Netd申請DNS請求的時候,調用的函數就是這個gethostbyname_internal,那麼此時又調用一次豈不是遞歸了?這裏就體現了創造Android工程師的智慧了。第一次調用gethostbyname_internal的時候是進程調用,並且這個時候ANDROID_DNS_MODE沒有設置。第二次調用gethostbyname_internal的時候是Netd調用的,Netd的權限是root的,而且更關鍵的是前面Netd初始化的時候set了ANDROID_DNS_MODE,這兩個不同的地方就影響了整個邏輯。

       除此之外,上方android_gethostbynameforiface函數中調用了__res_get_state函數。該函數獲得了一個和線程相關的DNS服務器信息。去哪個local dns查詢就看這個函數返回的res_thread結構了。這部分內容稍後進行分析。我們繼續關注gethostbyname_internal的實現。


  1. static struct hostent *  
  2. gethostbyname_internal(const char *name, int af, res_state res, const char *iface, int mark)  
  3. {  
  4.     const char *cache_mode = getenv("ANDROID_DNS_MODE");  
  5.     FILE* proxy = NULL;  
  6.     struct hostent *result = NULL;  
  7.   
  8.     if (cache_mode != NULL && strcmp(cache_mode, "local") == 0) {  
  9.         res_setiface(res, iface);  
  10.         res_setmark(res, mark);  
  11.         return gethostbyname_internal_real(name, af, res);  
  12.     }  

這一次判斷cache_mode的語句將爲true,此時進入gethostbyname_internal_real函數來處理DNS請求,後面就不用多分析了,有興趣的童鞋可以繼續跟隨代碼。後面就是構建DNS請求包和發送DNS請求了。






整個DNS解析的流程我們是清楚了,現在我們就要去想辦法修改DNS服務器了。在android_gethostbynameforiface中,通過_res_thread_get函數獲得__res_state。而在_res_thread_get函數中,用pthread_getspecific來獲得與線程相關聯的

_res_key。此時如果pthread_getspecific返回的是NULL說明該函數是第一次被調用,那麼將會通過_res_thread_alloc分配內存然後進行初始化。初始化關鍵語句是res_ninit,該函數由會調用__res_vinit完成具體工作。

這裏先給出__res_state結構的具體信息:


  1. struct __res_state {  
  2.     char    iface[IF_NAMESIZE+1];  
  3.     int retrans;        /* retransmission time interval */  
  4.     int retry;          /* number of times to retransmit */  
  5.     u_int   options;        /* option flags - see below. */  
  6.     int nscount;        /* number of name servers */  
  7.     struct sockaddr_in nsaddr_list[MAXNS];  /* address of name server */  
  8. #define nsaddr  nsaddr_list[0]      /* for backward compatibility */  
  9.     u_short id;         /* current message id */  
  10.     char    *dnsrch[MAXDNSRCH+1];   /* components of domain to search */  
  11.     char    defdname[256];      /* default domain (deprecated) */  
  12.     u_int   pfcode;         /* RES_PRF_ flags - see below. */  
  13.     unsigned ndots:4;       /* threshold for initial abs. query */  
  14.     unsigned nsort:4;       /* number of elements in sort_list[] */  
  15.     char    unused[3];  
  16.     struct {  
  17.         struct in_addr  addr;  
  18.         uint32_t    mask;  
  19.     } sort_list[MAXRESOLVSORT];  
  20.     res_send_qhook qhook;       /* query hook */  
  21.     res_send_rhook rhook;       /* response hook */  
  22.     int res_h_errno;        /* last one set for this context */  
  23.     int _mark;          /* If non-0 SET_MARK to _mark on all request sockets */  
  24.     int _vcsock;        /* PRIVATE: for res_send VC i/o */  
  25.     u_int   _flags;         /* PRIVATE: see below */  
  26.     u_int   _pad;           /* make _u 64 bit aligned */  
  27.     union {  
  28.         /* On an 32-bit arch this means 512b total. */  
  29.         char    pad[72 - 4*sizeof (int) - 2*sizeof (void *)];  
  30.         struct {  
  31.             uint16_t        nscount;  
  32.             uint16_t        nstimes[MAXNS]; /* ms. */  
  33.             int         nssocks[MAXNS];  
  34.             struct __res_state_ext *ext;    /* extention for IPv6 */  
  35.         } _ext;  
  36.     } _u;  
  37.         struct res_static   rstatic[1];  
  38. };  

關鍵的成員是nsaddr_list,現在需要知道該成員何時何處被初始化了。答案是在前面的__res_vinit函數中,不過在深入之前必須要看看__res_ninit函數的註釋部分。這一部分介紹了初始化的大概邏輯。


  1. /* 
  2.  * Set up default settings.  If the configuration file exist, the values 
  3.  * there will have precedence.  Otherwise, the server address is set to 
  4.  * INADDR_ANY and the default domain name comes from the gethostname(). 
  5.  * 
  6.  * An interrim version of this code (BIND 4.9, pre-4.4BSD) used 127.0.0.1 
  7.  * rather than INADDR_ANY ("0.0.0.0") as the default name server address 
  8.  * since it was noted that INADDR_ANY actually meant ``the first interface 
  9.  * you "ifconfig"'d at boot time'' and if this was a SLIP or PPP interface, 
  10.  * it had to be "up" in order for you to reach your own name server.  It 
  11.  * was later decided that since the recommended practice is to always 
  12.  * install local static routes through 127.0.0.1 for all your network 
  13.  * interfaces, that we could solve this problem without a code change. 
  14.  * 
  15.  * The configuration file should always be used, since it is the only way 
  16.  * to specify a default domain.  If you are running a server on your local 
  17.  * machine, you should say "nameserver 0.0.0.0" or "nameserver 127.0.0.1" 
  18.  * in the configuration file. 
  19.  * 
  20.  * Return 0 if completes successfully, -1 on error 
  21.  */  


實際上這個所謂的配置文件正逐步被去掉,在__res_vinit後面有一段被#ifndefANDROID_CHANGES包圍的代碼,這段代碼就是解析/etc/resolv.conf文件的。但是4.3後是#define了ANDROID_CHANGES的。所以ANDROID4.3以後再添加

resolv.conf是沒有意義的了。

註釋中說如果沒有配置文件,則server address設爲INADDR_ANY並且通過gethostname來獲得默認domain name。也就是說,如果在wifi等環境下,DNS服務器都是自動獲取的。



5. 對策與思路


Android4.3之前

在Android4.3以前,如果需要修改DNS服務器,有很多種方法,這些方法的實質就是向系統屬性中添加“net.dns1”字段的信息。這些方法的前提條件都是獲得root權限。具體方法有:

1.     在shell下,直接設置“net.dns1”等的系統屬性。

2.     在init.rc腳本中,添加對“net.dns1”等系統屬性的設置。

3.     在root權限下創建resovle.conf文件並添加相關name server信息。


Android4.3以後


在Android4.3以後,通過系統屬性或者解析文件來手動修改DNS服務器已經是不可能了。主要有兩種方法,一個是在NDK下面修改DNS解析邏輯,第二個是通過Android系統源代碼修改相關邏輯,讓Android4.3的新修改無效,然後重構Android。下面是一個老外基於NDK的修改方案,該方案需要以下權限:

1.     Root權限

2.     對/system文件夾有寫權限

3.     能修改/etc/init.d

 

該方案重寫了DnsProxyListener和bionic解析器邏輯,通過將/dev/socket/dnsproxyd改名然後自己替換它來達到目的。


  1. /* 等待Netd啓動 */  
  2.     while (do_wait && stat(SOCKPATH, &statbuf) < 0) {  
  3.         sleep(1);  
  4.     }  
  5.     /* 將其改名 */  
  6.     if (stat(SOCKPATH, &statbuf) == 0) {  
  7.         unlink(SOCKPATH ".bak");  
  8.         rename(SOCKPATH, SOCKPATH ".bak");  
  9.         restore_oldsock = 1;  
  10.     }  
  11.   
  12.     sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  13.     …  
  14.   
  15.     /* 移花接木 */  
  16. memset(&sock, 0, sizeof(sock));  
  17.     sock.sun_family = AF_UNIX;  
  18.     strcpy(sock.sun_path, SOCKPATH);  
  19.   
  20. if (bind(sockfd, (struct sockaddr *)&sock, sizeof(sock)) < 0)   
  21. …  
  22.   
  23. if (chmod(SOCKPATH, 0660) < 0)   
  24. …  
  25.   
  26. /* 使用命令行或者缺省的IP做爲DNS服務器,然後剩下的邏輯就是修改DnsProxyListener了 */  
  27. if (optind < argc)  
  28.         setup_resolver(argv[optind]);  
  29.     else  
  30.         setup_resolver("223.5.5.5");  

代碼邏輯比較容易理解,但是如何使用呢?很簡單,使用adb將NDK生成的可執行文件拷貝到system目錄下面,然後./dnstool –v 223.5.5.5&即可。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章