如何用W7100A實現DNS客戶端(二)【Final】

這篇文檔將會介紹DNS以及如何用iMCU7100EVB來實現DNS客戶端,並且通過實際例子演示該功能。

在昨天的博文“如何用W7100A實現DNS客戶端(一)”我們給大家介紹了第二章域名系統以及第三章DNS演示的部分,今天繼續與大家分享第四章實現代碼的內容。本文中所有的示例代碼全部基於Keil編譯環境。

這是本篇文檔的最後一部分,希望對大家有所幫助。

第一部分請參考:如何用W7100A實現DNS客戶端(一)

4 實現代碼

本章將會介紹相關的示例代碼,這些代碼被下載到iMCU7100EVB開發板後,開發板利用UDP協議實現DNS客戶端的功能。更多關於UDP協議的詳細信息,請參考文檔‘如何使用W7100A實現UDP通信’。

4.1 dns_query()函數

該DNS客戶端的示例代碼是用dns_query()函數完成的。其它的低級函數被dns_query()函數調用執行。代碼4.1為dns_query()函數的代碼。
[sourcecode language=”c”]uint8 dns_query(uint8 s, char * name) {
uint8 xdata DNS_IP[4] = {xxx,xxx,xxx,xxx}; //DNS server IP, User must change it(please ask your ISP)
struct dhdr xdata dhp; //Structure variable for DNS header section uint8 xdata ip[4];
uint16 xdata len, port, cnt; uint8 xdata err; for(;socket(s, Sn_MR_UDP, 5000, 0)!=1;); //Open the UDP socket
len = dns_makequery(0, (uint8*)name, dns_buf, MAX_BUF_SIZE); //Make DNS query
sendto(s, dns_buf, len, DNS_IP, IPPORT_DOMAIN); //Send DNS query to DNS server
cnt = 0;
while (1){
if ((len = getSn_RX_RSR(s)) > 0){
if (len > MAX_BUF_SIZE) len = MAX_BUF_SIZE;
len = recvfrom(s, dns_buf, len, ip, &port); //Receive DNS answer from DNS server
close(s);
break;
}
wait_10ms(1);
if (cnt++ == 0x100){ // Timeout check return -1;
break;
}
}
err = parseMSG(&dhp, dns_buf); //Analyze the DNS answer
if(err == 1) return 1; //Success to find
else return -1; //Fail to find
}[/sourcecode]

所有的變量都定義於dns_query()函數內,結構變量dhdr位於dns.c的頭文件中。DNS_IP保存DNS服務器的IP地址,必須正確輸入。

首先,打開UDP socket。在代碼4.2中,將端口號設置為5000(端口號可以根據用戶的需求進行修改),然後利用dns_makequery()函數(後面將會詳細介紹該函數)創建一個查詢信息。在查詢信息創建後,調用sento()函數將該信息作為一個UDP數據包發送到服務器。

在發送完查詢信息後,iMCU7100EVB等待DNS服務器的響應。通過getSn_RX_RSR()函數來確認DNS服務器是否接收到響應信息;如果在一段時間後沒有收到響應,就會認為是超時。一旦有響應,通過recvfrom()函數接收響應,並且調用parseMSG()函數來解析該響應信息。parseMSG()函數可以檢測來自DNS服務器應答的rcode(DNS信息報文頭段);並且返回1或者0。若返回值為1(rcode=0), 表示域名搜索成功。除了0之外的其它值都表示發生錯誤,但是在示例代碼中,對於其它錯誤parseMSG()函數的返回值都將為0。

4.2 dns_makequery()函數

dns_makequery()函數由dns_query()函數調用來創建DNS查詢信息。更多的詳細信息,請參考第2章DNS查詢信息的配置。該函數的返回值為DNS緩存器的信息指針。

[sourcecode language=”c”]/*
********************************************************************************
* MAKE DNS QUERY MESSAGE
*
* Description : This function makes DNS query message.
* Arguments : op – Recursion desired
* name – is a pointer to the domain name.
* buf – is a pointer to the buffer for DNS message.
* len – is the MAX. size of buffer.
* Returns : the pointer to the DNS message.
* Note :
********************************************************************************
*/
int dns_makequery(uint16 op, uint8 * name, uint8 * buf, uint16 len)
{
uint8 xdata *cp;
char xdata *cp1, *dname;
char xdata sname[MAX_BUF_SIZE];
// char xdata *dname;
uint16 xdata p, dlen;
// uint16 xdata dlen;

cp = buf;

MSG_ID++;
cp = put16(cp, MSG_ID);
p = (op << 11) | 0x0100; /* Recursion desired */
cp = put16(cp, p);
cp = put16(cp, 1);
cp = put16(cp, 0);
cp = put16(cp, 0);
cp = put16(cp, 0);

strcpy(sname, name);
dname = sname;
dlen = strlen(dname);
for (;;)
{
/* Look for next dot */
cp1 = strchr(dname, ‘.’);

if (cp1 != NULL) len = cp1 – dname; /* More to come */
else len = dlen; /* Last component */

*cp++ = len; /* Write length of component */
if (len == 0) break;

/* Copy component up to (but not including) dot */
strncpy((char *)cp, dname, len);
cp += len;
if (cp1 == NULL)
{
*cp++ = 0; /* Last one; write null and finish */
break;
}
dname += len+1;
dlen -= len+1;
}

cp = put16(cp, 0x0001); /* type */
cp = put16(cp, 0x0001); /* class */

return ((int)((uint16)(cp) – (uint16)(buf)));
}[/sourcecode]

查詢信息是由dns_makequery()函數創建的,具體格式在第2章已經介紹。在緩存器中輸入DNS信息時要用到Put16()函數,該函數將16位的輸入保存到8位的緩存器中。

4.3 parseMSG()函數

parseMSG()由dns_query()函數調用,用來分析接收到的DNS信息。首先分析報文頭段,然後調用dns_question()函數來分析問題段,之後再次調用dns_answer()函數分析應答段。最後將已經分析完成的應答信息、與域名相對應的IP地址保存到一個變量中通過printf()函數進行輸出。

[sourcecode language=”c”]/*
********************************************************************************
* PARSE THE DNS REPLY
*
* Description : This function parses the reply message from DNS server.
* Arguments : dhdr – is a pointer to the header for DNS message
* buf – is a pointer to the reply message.
* len – is the size of reply message.
* Returns : None
* Note :
********************************************************************************
*/
uint8 parseMSG(struct dhdr * dhdr, uint8 * buf)
{
uint16 xdata tmp;
uint16 xdata i;
uint8 xdata * msg;
uint8 xdata * cp;

msg = buf;
memset(dhdr, 0, sizeof(*dhdr));

dhdr->id = get16(&msg[0]);
tmp = get16(&msg[2]);
if (tmp & 0x8000) dhdr->qr = 1;

dhdr->opcode = (tmp >> 11) & 0xf;

if (tmp & 0x0400) dhdr->aa = 1;
if (tmp & 0x0200) dhdr->tc = 1;
if (tmp & 0x0100) dhdr->rd = 1;
if (tmp & 0x0080) dhdr->ra = 1;

dhdr->rcode = tmp & 0xf;
dhdr->qdcount = get16(&msg[4]);
dhdr->ancount = get16(&msg[6]);
dhdr->nscount = get16(&msg[8]);
dhdr->arcount = get16(&msg[10]);

/* Now parse the variable length sections */
cp = &msg[12];

/* Question section */
for (i = 0; i < dhdr->qdcount; i++)
{
cp = dns_question(msg, cp);
}

/* Answer section */
for (i = 0; i < dhdr->ancount; i++)
{
cp = dns_answer(msg, cp);
}

/* Name server (authority) section */
for (i = 0; i < dhdr->nscount; i++)
{
;
}

/* Additional section */
for (i = 0; i < dhdr->arcount; i++)
{
;
}
if(dhdr -> rcode == 0) return 1; //Means No Error
else return 0;

}[/sourcecode]

4.4 dns_question()函數

dns_question()函數由parseMSG()函數調用,用來分析問題段(如:Qname、Qtype、

Qclass)。更多的詳細信息,請參考RFC1034和RFC1035文檔中問題段的值。

[sourcecode language=”c”]/*
********************************************************************************
* PARSE QUESTION SECTION
*
* Description : This function parses the qeustion record of the reply message.
* Arguments : msg – is a pointer to the reply message
* cp – is a pointer to the qeustion record.
* Returns : a pointer the to next record.
* Note :
********************************************************************************
*/
uint8 * dns_question(uint8 * msg, uint8 * cp)
{
int xdata len;
char xdata name[MAX_BUF_SIZE];
len = parse_name(msg, cp, name, MAX_BUF_SIZE);

if (len == -1) return 0;

cp += len;
cp += 2; /* type */
cp += 2; /* class */

return cp;
}[/sourcecode]

4.5 dns_answer()函數

dns_answer()函數由parseMSG()函數調用,用來對資源記錄(RRs)段(Name、Type、

Class、TTL、Rdlength、Rddata)進行分析。在對所有的信息類型解析完成之後,如果信息的類型為TypeA、TypePTR、TypeHINFO、TypeMX、TypeSOA或者TypeTXT時,需要對其進行再次解析。

更多的詳細信息,請參考RFC1034和RFC1035文檔中對各信息類型的介紹。

[sourcecode language=”c”]/*
********************************************************************************
* PARSE ANSWER SECTION
*
* Description : This function parses the answer record of the reply message.
* Arguments : msg – is a pointer to the reply message
* cp – is a pointer to the answer record.
* Returns : a pointer the to next record.
* Note :
********************************************************************************
*/
uint8 * dns_answer(uint8 * msg, uint8 * cp)
{
int xdata len, type;
char xdata name[MAX_BUF_SIZE];

len = parse_name(msg, cp, name, MAX_BUF_SIZE);

if (len == -1) return 0;

cp += len;
type = get16(cp);
cp += 2; /* type */
cp += 2; /* class */
cp += 4; /* ttl */
cp += 2; /* len */

switch (type)
{
case TYPE_A: /* Just read the address directly into the structure */
SIP[0] = *cp++;
SIP[1] = *cp++;
SIP[2] = *cp++;
SIP[3] = *cp++;
break;
case TYPE_CNAME:
case TYPE_MB:
case TYPE_MG:
case TYPE_MR:
case TYPE_NS:
case TYPE_PTR:
/* These types all consist of a single domain name */
/* convert it to ascii format */
len = parse_name(msg, cp, name, MAX_BUF_SIZE);
if(len == -1) return 0;

cp += len;
break;
case TYPE_HINFO:
len = *cp++;
cp += len;

len = *cp++;
cp += len;
break;
case TYPE_MX:
cp += 2;
/* Get domain name of exchanger */
len = parse_name(msg, cp, name, MAX_BUF_SIZE);
if(len == -1) return 0;

cp += len;
break;
case TYPE_SOA:
/* Get domain name of name server */
len = parse_name(msg, cp, name, MAX_BUF_SIZE);
if(len == -1) return 0;

cp += len;

/* Get domain name of responsible person */
len = parse_name(msg, cp, name, MAX_BUF_SIZE);
if(len == -1) return 0;

cp += len;

cp += 4;
cp += 4;
cp += 4;
cp += 4;
cp += 4;
break;
case TYPE_TXT:
/* Just stash */
break;
default:
/* Ignore */
break;
}

return cp;
}[/sourcecode]

4.6 parse_name()函數

parse_name()函數只分析來自DNS應答信息的name段。parse_name()函數也可以改變DNS信息格式從而使用戶更容易進行操作,在分析完成之後返回Name段的長度。

[sourcecode language=”c”]/*
********************************************************************************
* CONVERT A DOMAIN NAME TO THE HUMAN-READABLE FORM
*
* Description : This function converts a compressed domain name to the human-readable form
* Arguments : msg – is a pointer to the reply message
* compressed – is a pointer to the domain name in reply message.
* buf – is a pointer to the buffer for the human-readable form name.
* len – is the MAX. size of buffer.
* Returns : the length of compressed message
* Note :
********************************************************************************
*/
int parse_name(uint8 * msg, uint8 * compressed, char * buf, uint16 len)
{
uint16 xdata slen; // Length of current segment
uint8 xdata * cp;
int xdata clen = 0; // Total length of compressed name
int xdata indirect = 0; // Set if indirection encountered
int xdata nseg = 0; // Total number of segments in name

cp = compressed;

for (;;)
{
slen = *cp++; // Length of this segment

if (!indirect) clen++;

if ((slen & 0xc0) == 0xc0)
{
if(!indirect) clen++;
indirect = 1;
// Follow indirection
cp = &msg[((slen & 0x3f)<<8) + *cp];
slen = *cp++;
}

if (slen == 0) break; /* zero length == all done */

len -= slen + 1;

if (len < 0) return -1;

if (!indirect) clen += slen;

while (slen– != 0) *buf++ = (char)*cp++;
*buf++ = ‘.’;
nseg++;
}

if (nseg == 0)
{
/* Root name; represent as single dot */
*buf++ = ‘.’;
len–;
}

*buf++ = ‘\0’;
len–;

return clen; /* Length of compressed message */
}[/sourcecode]

有關產品W7100A的更多應用博文,請參考下列文章:

如何用W7100A實現DNS客戶端

如何用W7100A實現HTTP客戶端(一)