Android4.3前后DNS解析简单研究

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 plain copy 在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 plain copy 在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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);  

getaddrinfo函数是一个native本地函数,声明如下:


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

在getaddrinfo对应的JNI层函数中,实际调用了下面函数:


[java]  view plain copy 在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 plain copy 在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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. String hostname = Libcore.os.getnameinfo(address, NI_NAMEREQD);  

调用了getnameinfo方法,该方法同样是一个native函数,在JNI层对应的函数中直接调用了getnameinfo这个bionic库的函数:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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中,关键的一步如下:



[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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中,我们可以看到如下代码:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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的代码开始:


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

和Android4.2.2没有变化,直接调用了getaddrinfo,其中第二个参数是NULL。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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函数。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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:



[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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中的变化:



[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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,然后出现下面的代码:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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中的实现:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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 plain copy 在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依次初始化四个部件:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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中的这么一句:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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方法实现的。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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解析的进程。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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的实现。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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结构的具体信息:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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函数的注释部分。这一部分介绍了初始化的大概逻辑。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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改名然后自己替换它来达到目的。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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&即可。







评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值