如何用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客户端(一)