网上学来的代码。大部分是别人写的,自己改进了一下,添加了若干注释。
请注意在获取MX Domain Name那一步的时候,即:
这里有疑问啊,对于DNS解析的话,不是+2啊。
参照:http://hi.baidu.com/grdd/blog/item/df56ba51469a6710367abebe.html
请注意在获取MX Domain Name那一步的时候,即:
// 第10.3步:获取域名。
//资源数据则根据类型字段的值有不同的格式. 对于A类型, 资源数据是IP地址. 对于MX查询, 资源数据是优先值和域名, 域名的格式与查询名字段格式相同
memset(name,0,MAXDNAME);
if(dn_expand(response.buf,responseend,responsepos + 2,name,MAXDNAME) < 0)
这里有疑问啊,对于DNS解析的话,不是+2啊。
参照:http://hi.baidu.com/grdd/blog/item/df56ba51469a6710367abebe.html
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <errno.h>
#include "dns.h"
extern int res_query();
extern int res_search();
extern int errno;
extern int h_errno;
//getshort函数从指定地址读取16bit网络字节顺序的数据(big-endian), 并将其转换成little-endian的顺序返回.
static unsigned short getshort(unsigned char *c)
{
unsigned short u; //2字节
u = c[0]; //将1字节装进来,低四位有值
return (u << 8) + c[1]; //移动到高四位,然后加上一个字节的数
}
/*
* 定义了一个联合体变量名为response, 用于存储DNS响应报文. HEADER是用于存储DNS首部的结构体
* HEADER结构体中本文会用到的成员是dncount和ancout, 分别表示问题数和资源记录数
* PACKETSZ定义在<arpa/nameser_compat.h>中, 这个宏表示一个报文的最大长度
*/
static union
{
HEADER hdr; //首部
unsigned char buf[PACKETSZ]; //整个缓冲区
} response;
static int responselen; // 响应报文的长度
static unsigned char *responseend; // 指向了响应报文最后一个字节之后的一字节,即response.buf+responselen
static unsigned char *responsepos; // 指向了即将处理的字段
static int numanswers; // 还未处理的回答数
static char name[MAXDNAME]; // 用来存储主机名, 长度是MAXDNAME个字节, MAXDNAME定义在<arpa/nameser.h>中, 表示域名的最大长度
static unsigned short pref; // 用来存储MX记录的优先值.
void dns_init()
{
res_init(); // 【第一步】读取配置文件并修改环境变量:LOCALDOMAIN. 在调用其他地址解析函数前通常要先调用res_init. 如果执行成功, 函数返回0; 否则返回-1.
memset(name,0,MAXDNAME);
}
/*
* 功能:1.发起一个指定查询名(domain)和查询类型(type)的DNS查询;
* 2.让指针指向第一个回答字段。
* 3.获取资源记录数。
* 返回:如果发生错误, 函数返回-1, 否则返回资源记录数。
*/
int dns_resolve(char *domain,int type)
{
int n;
int i;
errno=0;
if(NULL == domain)
return -1;
//【第二步】发起一条指定类型(C_IN)的DNS查询,这个查询是核心!!!后面都从这个结果中提取信息
responselen = res_search(domain, C_IN, type, response.buf, sizeof(response));
if(responselen <= 0)
return -1;
if(responselen >= sizeof(response))
responselen = sizeof(response);
// 【第三步】以下就是将获取到的报文中我们想要的信息提取出来!
responseend = response.buf + responselen; // 第1步:获取报文结束字符后面那个位置
responsepos = response.buf + sizeof(HEADER); // 第2步:跳过12字节长的首部:HEADER是一个12字节长的首部,包括问题数和资源记录数
n = ntohs(response.hdr.qdcount); // 第3步:报文中的问题数(需要进行网络字节序转换)
while(n-- > 0) //第4步:跳过所有的“查询问题”字段, 让responsepos指向第一个回答的首地址
{
//第4.1步:获得查询问题字段中“查询名”的长度
i = dn_expand(response.buf, responseend, responsepos, name, MAXDNAME);
responsepos += i; //第4.2步:使得responsepos指向了“查询类型”字段的首地址
i = responseend - responsepos; //第4.3步:获取这个首地址到结束处的长度(因为指向最后一个字符的后面那个字节,所以不必+1)
//QFIXEDSZ宏定义在<arpa/nameser_compat.h>中, 其值等于4. 它表示DNS查询报文中问题部分的定长字段的字节数, 即查询类型和查询类两个字段的总长度
if(i < QFIXEDSZ) // 第4.4步:验证剩余长度。如果这个长度比QFIXEDSZ还小,说明报文不正常
return -1;
responsepos += QFIXEDSZ; // 第4.5步:指向下一个“查询问题”字段
}
// 第5步:获取资源记录数并返回。
numanswers = ntohs(response.hdr.ancount); //将资源记录数的值赋给numanswers
return numanswers;
}
/*
* 功能:从回答内容中提取1个得到的域名。并将指针移动。
*/
int dns_findmx(int wanttype)
{
unsigned short rrtype;
unsigned short rrdlen;
int i;
if(numanswers <=0)
return DNS_MSG_END;
numanswers--;
if(responsepos == responseend)
return -1;
//回答字段是以资源记录格式存储的. 第一项是域名. 因此用dn_expand函数将该字段还原为普通字符串格式. 并将返回的该字段长度赋值给i.
// 第6步:获取回答字段的第一项:域名。
i = dn_expand(response.buf, responseend, responsepos, name, MAXDNAME);
//printf("Domain: %s\t", name);
if(i < 0)
return -1;
responsepos += i; // 第7步:指针跳过域名。
i = responseend - responsepos; // 第8步:获取剩下的字节数并判断。小于10说明非法。
//资源记录中, 定长字段的总长度为10字节,即类型, 类, 生存时间和资源数据长度字段的总长度
if(i < 10)
return -1;
// 第8步:利用getshort函数分别获得“类型”和“资源数据长度”字段的值
rrtype = getshort(responsepos);
rrdlen = getshort(responsepos + 8);
responsepos += 10; // 第9步:使其指向资源数据的首地址
if(rrtype == wanttype) // 第10步:如果这条资源记录的类型是函数参数所指定的类型, 则对其进行处理. MX记录中
{
// 第10.1步:判断资源数据长度是否合法。
// 资源数据内第一项存储着16bit的优先值, 之后存储着1字节的主机名, 则资源数据长度至少要3字节。
if(rrdlen < 3)
return -1;
// 第10.2步:从资源数据中获得优先值存储在pref中. 由于报文中的优先值是按照网络字节顺序存储的, 因此需要将其转换成little-endian顺序后存到pref中
pref = getshort(responsepos);
// 第10.3步:获取域名。
//资源数据则根据类型字段的值有不同的格式. 对于A类型, 资源数据是IP地址. 对于MX查询, 资源数据是优先值和域名, 域名的格式与查询名字段格式相同
memset(name,0,MAXDNAME);
if(dn_expand(response.buf,responseend,responsepos + 2,name,MAXDNAME) < 0)
return -1;
//printf("MX domain: %s \n", name);
responsepos += rrdlen;
return strlen(name);
}
//该资源记录的类型与参数指定类型不符, 则跳过该条记录, 将responsepos调整至下一条资源记录.
responsepos += rrdlen;
return 0;
}
/*
* dns_get_mxrr函数将pref的值(优先值)存储到p指向的地址中,
* 并将name中存储的主机名复制到dn指向的地址中,
* 复制的长度不超过len个字节.
* 如果len个字节不足以存储整个主机名, 则返回-1, 否则返回0.
*/
int dns_get_mxrr(unsigned short *p, unsigned char *dn,unsigned int len)
{
*p = pref;
strncpy(dn,name,len);
if(len < (strlen(name)+1))
return -1;
return 0;
}
int get_mx_name(char *addr, Mx_type *mx, int *num)
{
char dname[MAXDNAME];
int i;
unsigned short p;
dns_init();
i = dns_resolve(addr, T_MX);
if(i<0)
{
//fprintf(stderr, "No MX\n");
return -1;
}
*num = 0;
while(dns_findmx(T_MX)!=DNS_MSG_END &&
(!dns_get_mxrr(&p, dname, MAXDNAME)) )
{
strncpy(mx[*num].addr, dname, MAXDNAME);
mx[*num].pref = p;
(*num)++;
}
return 0;
}
//直接调用函数的用法——便于理解
int main(int argc, char *argv[])
{
Mx_type mx[32] = {0};
int i, num, ret;
//printf("sizeof HEADER: %d; MAXDNAME: %d; \n", sizeof (HEADER), MAXDNAME);
if(argc!=2)
{
fprintf(stderr,"Usage: command <arg1>\n");
exit(-1);
}
ret = get_mx_name(argv[1], mx, &num);
if (ret != 0) {
printf("No mx\n");
exit(-1);
}
printf("[Num] [Domain name]\t\t[Pref]\t[MX Domain name]\n");
for(i=0; i<num; i++)
printf("%-5d Dmain:%s\t\t%d\t%s\n",
i, argv[1], mx[i].pref, mx[i].addr);
return 0;
}