Android系統中,一個網絡類型可以設定多個DNS,Android系統中,最多可以設定三個DNS,可以通過其屬性net.dns*檢視。DNS在dhcp等流程之後,會通過netd設定到libc,DNS解析是通過libc實作。當解析一個域名時第一個DNS逾時,則會使用第二個、第三個DNS解析,解析後的結果會進行緩存。
最近需要在Android framework中增加DNS檢測功能,在網絡連接配接後,檢測DNS是否有效,并将有效的DNS盡量置前。盡量保證第一個DNS的有效性,加快對新域名的解析速度。
framework需要修改的點有兩個:
1)需要增加DNS檢測接口;
2)在檢測完成後調用DNS設定接口重新設定DNS
DNS檢測接口實作
1、在NetworkUtils 工具類函數中增加native接口checkDnsValid
public class NetworkUtils {
......省略
/** check dns server */
public native static int checkDnsValid(String dns, String host);
......省略
}
2、JNI實作:
frameworks/base/core/jni/android_net_NetUtils.cpp
......省略
static jint android_net_utils_checkDnsValid(JNIEnv* env, jobject clazz, jstring dns, jstring host)
{
int result = 0;
const char *dnsStr = env->GetStringUTFChars(dns, NULL);
const char* hostStr= env->GetStringUTFChars(host, NULL);
ALOGD("android_net_utils_checkDnsValid : DNS[%s] HOST[%s] \n",dnsStr,hostStr);
char* hostIp =::ngethost((unsigned char*)hostStr ,1, dnsStr);//T_A Ipv4 address
if(hostIp != NULL){
result = 1;
ALOGD("android_net_utils_checkDnsValid : DNS[%s] [%s:%s] \n",dnsStr,hostStr,hostIp);
}
env->ReleaseStringUTFChars(dns, dnsStr);
env->ReleaseStringUTFChars(host, hostStr);
return (jint)result;
}
......省略
{ "checkDnsValid", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)android_net_utils_checkDnsValid },
......省略
};
核心部分,DNS檢測接口ngethost的實作,檢測接口參考開源代碼并加以優化,類似的功能開源的工具有nslookup等。通過對指定域名,如百度www.baidu.com的解析,來判斷DNS是否有效,逾時時間為5s。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#define LOG_TAG "NetUtils"
#define T_A 1 /*Ipv4 address*/
#define T_NS 2 /*Nameserver*/
#define T_CNAME 5 /*canonical name*/
#define T_SOA 6 /*start of authority zone */
#define T_PTR 12 /*domain name pointer */
#define T_MX 15 /*Mail server*/
char * ngethost (unsigned char* , int, const char *);
static void reverseIP(char *,char *);
static void removeDotsFromName(unsigned char*,unsigned char*);
static unsigned char* ReadName (unsigned char*,unsigned char*,int*);
/*DNS header*/
struct DNS_HEADER
{
unsigned short id; // identification number
# if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned char rd :1; // recursion desired
unsigned char tc :1; // truncated message
unsigned char aa :1; // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1; // query/response flag
unsigned char rcode :4; // response code
unsigned char cd :1; // checking disabled
unsigned char ad :1; // authenticated data
unsigned char z :1; // reserved and unused
unsigned char ra :1; // recursion available
# endif
# if __BYTE_ORDER == __BIG_ENDIAN
unsigned char qr :1;
unsigned char opcode :4;
unsigned char aa :1;
unsigned char tc :1;
unsigned char rd :1;
unsigned char ra :1;
unsigned char z :1;
unsigned char ad :1;
unsigned char cd :1;
unsigned char rcode :4;
# endif
unsigned short q_count; // number of question entries
unsigned short ans_count; // number of answer entries
unsigned short auth_count; // number of authority entries
unsigned short add_count; // number of resource entries
};
struct QUESTION /*QUESTION DATA*/
{
unsigned short qtype; /*query type:IN,NS,CNAME,SOA,PTR,MX*/
unsigned short qclass; /*query class:IN or CHAOS*/
};
#pragma pack(push, 1)
struct R_DATA /*RESOURCE RECORD DATA*/
{
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
};
#pragma pack(pop)
struct RES_RECORD /*RESOURCE RECORD FIELD:AUTHORITATIVE,ANSWER or ADDITIONAL*/
{
unsigned char *name;
struct R_DATA *resource;
unsigned char *rdata;
};
typedef struct /*QUESTION FIELD*/
{
unsigned char *name;
struct QUESTION *ques;
} QUERY;
void reverseIP(char *addr, char *tar ) /*change a.b.c.d to d.c.b.a.in-addr.arpa*/
{
int i,j,count_dots=0,pos=0;
char buffer[10];
for(i=strlen(addr)-1;i>=0;i--)
{
if(addr[i]=='.')
{
for(j=count_dots-1;j>=0;j--)
{
*(tar+pos)=buffer[j];
pos++;
}
*(tar+pos)='.';
pos++;
count_dots=0;
}
else
{
buffer[count_dots]=addr[i];
count_dots++;
}
}
for(j=count_dots-1;j>=0;j--)
{
*(tar+pos)=buffer[j];
pos++;
}
char *arpa = ".in-addr.arpa";
for(i=0;i<14;i++)
{
*(tar+pos) = *arpa;
pos++;
arpa++;
}
}
/*perform nslookup*/
char* ngethost(unsigned char *host , int query_type, const char *dns_server)
{
unsigned char buf[65536],*qname,*reader;
int i , j , stop , s;
struct sockaddr_in a,dest;
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
struct RES_RECORD answers[50],auth[50],addinfo[50];
struct DNS_HEADER *dns = NULL;
struct QUESTION *qinfo = NULL;
ALOGD("Resolving %s from dns[%s]\n" , host,dns_server);
s = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP);
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); /*set timeout on this socket*/
dest.sin_family = AF_INET;
dest.sin_port = htons(53);
dest.sin_addr.s_addr = inet_addr(dns_server);
dns = (struct DNS_HEADER *)&buf; /*DNS HEADER*/
dns->id = (unsigned short) htons(getpid());
dns->qr = 0;
dns->opcode = 0; /*standard query*/
dns->aa = 0;
dns->tc = 1;
dns->rd = 1; /*recursion desired*/
dns->ra = 0;
dns->z = 0;
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;
dns->q_count = htons(1);
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
qname =(unsigned char*)&buf[sizeof(struct DNS_HEADER)]; /*DNS QUESTION NAME.ANY JUNK VALUE WILL DO*/
removeDotsFromName(qname , host);
qinfo =(struct QUESTION*)&buf[sizeof(struct DNS_HEADER) + (strlen((const char*)qname) + 1)]; /*DNS QUESTION TYPE AND CLASS*/
qinfo->qtype = htons( query_type );
qinfo->qclass = htons(1);
if( sendto(s,(char*)buf,sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION),0,(struct sockaddr*)&dest,sizeof(dest)) < 0){
ALOGD("\nSending Packet to %s faile\n",dns_server);
goto fail;
}
i=sizeof(dest);
if(recvfrom (s,(char*)buf , 65536 , 0 , (struct sockaddr*)&dest , (socklen_t*)&i ) < 0){
goto fail;
}
dns = (struct DNS_HEADER*) buf;
if(dns->ra==0){
goto fail;
}
if(dns->rcode==0)
{
reader = &buf[sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION)]; /*THE RESPONSE*/
stop=0;
for(i=0;i<ntohs(dns->ans_count);i++)
{
answers[i].name=ReadName(reader,buf,&stop);
reader = reader + stop;
answers[i].resource = (struct R_DATA*)(reader);
reader = reader + sizeof(struct R_DATA);
if(ntohs(answers[i].resource->type) == 1) /*read address*/
{
answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));
for(j=0 ; j<ntohs(answers[i].resource->data_len) ; j++)
answers[i].rdata[j]=reader[j];
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
reader = reader + ntohs(answers[i].resource->data_len);
}
else /*read name*/
{
answers[i].rdata = ReadName(reader,buf,&stop);
reader = reader + stop;
}
}
for(i=0;i<ntohs(dns->auth_count);i++)
{
auth[i].name=ReadName(reader,buf,&stop);
reader+=stop;
auth[i].resource=(struct R_DATA*)(reader);
reader+=sizeof(struct R_DATA);
if(ntohs(auth[i].resource->type)==1) /*read address*/
{
auth[i].rdata = (unsigned char*)malloc(ntohs(auth[i].resource->data_len));
for(j=0;j<ntohs(auth[i].resource->data_len);j++)
auth[i].rdata[j]=reader[j];
auth[i].rdata[ntohs(auth[i].resource->data_len)]='\0';
reader+=ntohs(auth[i].resource->data_len);
}
else /*read name*/
{
auth[i].rdata=ReadName(reader,buf,&stop);
reader+=stop;
}
}
for(i=0;i<ntohs(dns->add_count);i++)
{
addinfo[i].name=ReadName(reader,buf,&stop);
reader+=stop;
addinfo[i].resource=(struct R_DATA*)(reader);
reader+=sizeof(struct R_DATA);
if(ntohs(addinfo[i].resource->type)==1) /*read address*/
{
addinfo[i].rdata = (unsigned char*)malloc(ntohs(addinfo[i].resource->data_len));
for(j=0;j<ntohs(addinfo[i].resource->data_len);j++){
addinfo[i].rdata[j]=reader[j];
}
addinfo[i].rdata[ntohs(addinfo[i].resource->data_len)]='\0';
reader+=ntohs(addinfo[i].resource->data_len);
}
else /*read name*/
{
addinfo[i].rdata=ReadName(reader,buf,&stop);
reader+=stop;
}
}
for(i=0 ; i < ntohs(dns->ans_count) ; i++)
{
ALOGD("----------ntohs(dns->ans_count[%d])--------------\n",i);
if( ntohs(answers[i].resource->type) == T_A) //IPv4 address
{
ALOGD("---ntohs(answers[%d].resource->type) == T_A--\n",i);
long *p;
p=(long*)answers[i].rdata;
a.sin_addr.s_addr=(*p);
ALOGD("has IPv4 address : %s \n",inet_ntoa(a.sin_addr));
if(s > 0) close(s);
return inet_ntoa(a.sin_addr);
}
else if(ntohs(answers[i].resource->type)== T_CNAME)
ALOGD("has alias name : %s \n",answers[i].rdata);
else if(ntohs(answers[i].resource->type)== T_PTR)
ALOGD("has domain name :%s \n",answers[i].rdata);
}
}
fail:
if(s > 0) close(s);
return NULL;
}
u_char* ReadName(unsigned char* reader,unsigned char* buffer,int* count)
{
unsigned char *name;
unsigned int p=0,jumped=0,offset;
int i , j;
*count = 1;
name = (unsigned char*)malloc(256); /*maximum allowed length is 256*/
name[0]='\0';
while(*reader!=0)
{
if(*reader>=192)
{
offset = (*reader)*256 + *(reader+1) - 49152;
reader = buffer + offset - 1;
jumped = 1;
}
else
name[p++]=*reader;
reader = reader+1;
if(jumped==0)
*count = *count + 1;
}
name[p]='\0';
if(jumped==1)
*count = *count + 1;
for(i=0;i<(int)strlen((const char*)name);i++)
{
p=name[i];
for(j=0;j<(int)p;j++)
{
name[i]=name[i+1];
i=i+1;
}
name[i]='.';
}
name[i-1]='\0';
return name;
}
void removeDotsFromName(unsigned char* dns,unsigned char* host)
{
int lock = 0 , i;
strcat((char*)host,".");
for(i = 0 ; i < strlen((char*)host) ; i++)
{
if(host[i]=='.')
{
*dns++ = i-lock; /*replace the dot with the number of characters after it before the next dot*/
for(;lock<i;lock++)
*dns++=host[lock];
lock++;
}
}
*dns++='\0';
}
DNS檢測流程
在網絡連接配接後,framework層在ConnectivityService中處理所有類型的網絡連接配接,handleDnsConfigurationChange函數則是對DNS設定的地方。可以在此進行DNS檢測,也可以在handleConnect中進行。
例如在handleConnect中增加,
1)如果目前網絡類型設定的DNS個數>=2,則進行DNS檢測。
2)聯網後1s,在背景調用NetworkUtils.checkDnsValid(value,“www.baidu.com”);檢測設定的各個DNS有效性。
3)如果第一個有效的DNS index 滿足0<index<dnses.size,即說明需要重置DNS了。将第一個有效的DNS置于最前。
// TODO: add for check dns
NetworkStateTracker nt = mNetTrackers[newNetType];
if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()){
final LinkProperties p = nt.getLinkProperties();
if (p == null) return;
final Collection<InetAddress> dnses = p.getDnses();
if(dnses.size() < 2) return;
Runnable dnsCheckPorcess = new Runnable(){
public void run() {
synchronized(this){
try {
Collection<InetAddress> dnsesVaild = new ArrayList();
int vaildIndex = 0;
for (InetAddress dns : dnses) {
String value = dns.getHostAddress();
int ret = NetworkUtils.checkDnsValid(value,"www.baidu.com");
Slog.d("NetUtils","checking " + value + ">>>result:" + ret);
if(ret==1) {
dnsesVaild.add(dns);
break;
}
vaildIndex++;
}
if(vaildIndex == 0/*first dns is vaild*/
||vaildIndex >= dnses.size()/*no vaild dns*/){
Slog.d("NetUtils","no need reset dns");
return;
}
for (InetAddress dns : dnses) {
if(!dnsesVaild.contains(dns)){
dnsesVaild.add(dns);
}
}
Slog.d("NetUtils",">>>>>>>>>>>>>>>>>>>>>>>>>>>>reset dns");
mNetd.setDnsServersForInterface(p.getInterfaceName(),NetworkUtils.makeStrings(dnsesVaild), p.getDomains());
} catch (Exception e) {
Slog.e("NetUtils","exception checkDnsValid: " + e);
}
}
}
};
mHandler.postDelayed(dnsCheckPorcess,1000);
}
DNS檢測重置
重置參考handleDnsConfigurationChange代碼實作即可,framework層對DNS的操作,均是通過netd設定到底層,詳細了解需要參考framework層NetworkManagementService和netd實作代碼。
重置如下
mNetd.setDnsServersForInterface(p.getInterfaceName(),NetworkUtils.makeStrings(dnsesVaild), p.getDomains());
但這裡沒有清除DNS緩存。
驗證方法
WiFi聯網後,手動設定DNS,第一個DNS随便填一個不可用的IP,第二個DNS填8.8.8.8,嘗試ping一個新的域名,看是否能很快回報結果,确實如此。
同時抓列印,抓包,看日志是否符合代碼流程,同時看網絡包第一個使用的DNS是8.8.8.8