- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
所谓DNS查询,就是查询一个域名的相关信息,一般情况下就是查域名解析的IP地址了。
举个例子,我的浏览器要通过TCP/IP协议访问www.baidu.com的80端口并发送HTTP请求,但我要连接到百度的服务器的话,我必须先知道百度服务器的IP地址。此时一般情况下直接调用getaddrinfo()就可以取得百度服务器的IP地址了,但这个函数又是怎么知道百度服务器的IP地址的呢?它靠的是DNS查询(以及读DNS缓存、hosts等)。
DNS查询的过程,通常是通过UDP 53端口发送一个请求报文到DNS服务器上,然后等服务器发送回执,最后分析回执内容。非常简单。
不过UDP发送包裹如果超过了512字节,你就必须手动截断,否则服务器不收。这是服务器的一种自我保护手段,同时也是为了保证不丢包。
每个包裹的结构,无论DNS查询和响应,都是下面的格式:
其中DNS报文头必不可少,它的结构用C语言表达就是这样的:- #include<stdint.h>
- typedef struct
- {
- uint16_t Id; // 会话ID
- uint16_t Params; // 参数
- uint16_t NumQuestions; // 问题数
- uint16_t NumAnswers; // 回答数
- uint16_t NumAuthority; // 权威记录数
- uint16_t NumAdditional; // 额外记录数
- }dns_query_t, *dns_query_p;
复制代码 其中,“参数”的内容,如下:
第15位:QR:这个DNS报文是一条响应时为1,是一条查询时为0
第14,13,12,11位:Opcode:操作码,决定报文是被用来做啥的,情况如下:- 0000 一个标准的查询
- 0001 反向查询,此功能已过时,现在没几个服务器会管它。请看RFC 3425
- 0010 查询服务器的状态
- 0100 通知,由主DNS服务器通知底下的子DNS服务器数据区域发生改变,让子DNS服务器更新自己的数据区域。看RFC 1996
- 0101 DDNS(动态域名解析)提示服务器更新域名解析。看RFC 2136
第10位:AA:由DNS服务器发送响应时设置,为1时表示这条DNS回执来自于一个权威服务器的数据。
第9位:TC:设置为1的时候,表示包裹长度超过512字节,已经被截断。TCP没有消息长度限制,UDP有。
第8位:RD:客户端发送DNS请求时,要求DNS服务器进行一次递归查询,也就是说,如果DNS服务器没有相应记录,那么这个DNS服务器就会发送查询到别的DNS服务器,然后返回查询的结果。
第7位:RA:服务端发送回执的时候,设为1表示服务器支持递归查询,0则表示不支持。
第6,5,4位:Z:无用,设为0
第3,2,1,0位:RCode:由服务器返回,表示查询的状态,有以下的几个数值:- 0:一切正常
- 1:包裹格式异常
- 2:服务器出错
- 3:没有查询结果
- 4:服务器没有指定的功能
- 5:拒绝服务
- 6:本来应该没有的域名,但是有
- 7:有一个资源记录集存在,本来不应该有
- 8:有一个资源记录集应该有,但它没有
- 9:查询的服务器并不权威
- 10:相关的信息并不是当前域存在的
接下来说一下DNS查询中,“问题”的格式。- 编码为FQDN的域名,所谓FQDN就是“fully qualified domain name”,它的格式,是把域名中的字符串按照句点“.”拆分开,变成多个字符串,并每个字符串的前面用一个字节存储它的长度,然后在最后一个字符串的末尾追加'\0'结尾,举个例子,“www.0xaa55.com”这个域名,转换为FQDN格式后,就是“[3]www[6]0xaa55[3]com[0]”(其中用方括号框起来的,表示一个字节的数值)
另外注意,表示字符串长度的那个字节的数值,只有在它高2位都是0的时候,它才表示长度,否则它是另一个含义——指针。
这个“指针”表示域名的后半部分在整个DNS报文的指定偏移处存储,并且指针的数值(也就是相对于报文起始的偏移量)由当前字节的低6位和下一个字节共同组成一个14位整数。 - 一个16位整数,查询资源类型,数值如下:
- a = 1 地址
- ns = 2 域名服务器地址
- cname = 5 别名
- soa = 6 权威服务器的起始
- wks = 11 知名源
- ptr = 12 指针
- mx = 15 邮件交换
- txt = 16 文本信息
- aaaa = 28 IPv6地址
- srv = 33 服务
- all = 255 全部
- 一个16位整数,查询类,一般为htons(0x0001)(也就是“IN”,而“IN”表示“Internet”,因特网)
举个例子,一个DNS报文头应该是什么样子的?看下面的截图。
其中ID为0x00DD,参数中RD为1,要求DNS服务器进行一次递归查询,提问数为1,其它都没有。
然后是经过FQDN编码的域名,exmainland.com,查询资源类型为255,也就是“全部”。
最后是查询类,1表示IN
接下来我们看一下“回答”的格式,按照介绍是这样的:- 编码为FQDN的域名。
- 一个16位整数,回答的资源类型
- 一个16位整数,资源类
- 一个32位整数,TTL,缓存时间
- 一个16位整数,资源数据的长度,也就是回答的答案的长度
- 资源数据,也就是答案,长度取决于资源类型
那么我们实际测试,收到的回执的报文是什么样子的呢?如下图:
首先可以看出ID和我们之前的提问是一样的。
其次是,回执报文也包含了“问题”,而不仅仅是“回答”。
这里面共有4条回答,一条A记录,两条NS记录,一条SOA记录。
详细的,发送报文、接收并分析报文的C语言程序源码,请看下面:- //=============================================================================
- //作者:0xAA55
- //网站:http://www.0xaa55.com
- //请保留原作者信息
- //-----------------------------------------------------------------------------
- #include<stdio.h>
- #include<errno.h>
- #include<stdint.h>
- #include<stdlib.h>
- #include<stdarg.h>
- #include<malloc.h>
- #include<memory.h>
- #include<time.h>
- #define DNS_LOOKUP_PORT 53
- #ifdef WIN32
- #include<WinSock2.h>
- #include<Ws2tcpip.h>
- #define sockerr_native_errno WSAGetLastError()
- //=============================================================================
- //函数:sockerr_param
- //描述:判断socket出错是不是因为参数不对
- //-----------------------------------------------------------------------------
- static int sockerr_param()
- {
- switch(sockerr_native_errno)
- {
- case WSANOTINITIALISED: case WSAEAFNOSUPPORT:
- case WSAEPROTOTYPE: case WSAEINVAL:
- case WSAESOCKTNOSUPPORT: case WSAENOTSOCK:
- case WSAEADDRNOTAVAIL: case WSAEFAULT:
- case WSAEDESTADDRREQ:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_system
- //描述:判断socket出错是不是因为系统的问题
- //-----------------------------------------------------------------------------
- static int sockerr_system()
- {
- switch(sockerr_native_errno)
- {
- case WSAENETDOWN: case WSAEMFILE:
- case WSAEINVALIDPROVIDER: case WSAEINVALIDPROCTABLE:
- case WSAENOBUFS: case WSAEPROTONOSUPPORT:
- case WSAEPROVIDERFAILEDINIT: case WSAEACCES:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_addrinuse
- //描述:判断socket出错是不是因为地址已经被占用
- //-----------------------------------------------------------------------------
- static int sockerr_addrinuse()
- {
- switch(sockerr_native_errno)
- {
- case WSAEADDRINUSE:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_wouldblock
- //描述:判断socket出错是不是因为它需要再来一遍
- //-----------------------------------------------------------------------------
- static int sockerr_wouldblock()
- {
- switch(sockerr_native_errno)
- {
- case WSAEWOULDBLOCK:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_notconn
- //描述:判断socket出错是不是因为它没有连接上
- //-----------------------------------------------------------------------------
- static int sockerr_notconn()
- {
- switch(sockerr_native_errno)
- {
- case WSAENOTCONN:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_connbreak
- //描述:判断socket出错是不是因为它的连接断开了
- //-----------------------------------------------------------------------------
- static int sockerr_connbreak()
- {
- switch(sockerr_native_errno)
- {
- case WSAECONNABORTED:
- case WSAECONNREFUSED:
- case WSAECONNRESET:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:close_socket
- //描述:关闭套接字,Windows的socket不是文件描述符
- //-----------------------------------------------------------------------------
- static void close_socket(int sockfd)
- {
- closesocket(sockfd);
- }
- //=============================================================================
- //函数:relax
- //描述:释放CPU
- //-----------------------------------------------------------------------------
- static void relax()
- {
- Sleep(0);
- }
- #else
- #include<unistd.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include<arpa/inet.h>
- #define sockerr_native_errno errno
- //=============================================================================
- //函数:sockerr_param
- //描述:判断socket出错是不是因为参数不对
- //-----------------------------------------------------------------------------
- static int sockerr_param()
- {
- switch(sockerr_native_errno)
- {
- case EBADF: case ENOTSOCK: case EAFNOSUPPORT:
- case EINVAL: case EPROTONOSUPPORT:
- case EADDRNOTAVAIL: case EFAULT:
- case ENAMETOOLONG: case EDESTADDRREQ:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_system
- //描述:判断socket出错是不是因为系统的问题
- //-----------------------------------------------------------------------------
- static int sockerr_system()
- {
- switch(sockerr_native_errno)
- {
- case EACCES: case EMFILE: case ENFILE:
- case ENOBUFS: case ENOMEM: case ELOOP:
- case ENOENT: case ENOTDIR: case EROFS:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_addrinuse
- //描述:判断socket出错是不是因为地址已经被占用
- //-----------------------------------------------------------------------------
- static int sockerr_addrinuse()
- {
- switch(sockerr_native_errno)
- {
- case EADDRINUSE:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_wouldblock
- //描述:判断socket出错是不是因为它需要再来一遍
- //-----------------------------------------------------------------------------
- static int sockerr_wouldblock()
- {
- switch(sockerr_native_errno)
- {
- #if EAGAIN != EWOULDBLOCK
- case EAGAIN:
- #endif
- case EWOULDBLOCK:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_notconn
- //描述:判断socket出错是不是因为它没有连接上
- //-----------------------------------------------------------------------------
- static int sockerr_notconn()
- {
- switch(sockerr_native_errno)
- {
- case ENOTCONN:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:sockerr_connbreak
- //描述:判断socket出错是不是因为它的连接断开了
- //-----------------------------------------------------------------------------
- static int sockerr_connbreak()
- {
- switch(sockerr_native_errno)
- {
- case ECONNRESET:
- return 1;
- default:
- return 0;
- }
- }
- //=============================================================================
- //函数:relax
- //描述:释放CPU
- //-----------------------------------------------------------------------------
- static void relax()
- {
- sleep(0);
- }
- //=============================================================================
- //函数:close_socket
- //描述:关闭套接字,Linux的socket是一个文件描述符
- //-----------------------------------------------------------------------------
- static void close_socket(int sockfd)
- {
- close(sockfd);
- }
- #endif
- typedef struct
- {
- uint16_t sa_family; // AF_INET
- uint16_t sa_port; // htons(port_no)
- uint32_t sa_addr; // 127.0.0.1 = 0x0100007F
- uint8_t sa_reserved[8]; // = {0}
- }sockaddr_v4_t, *sockaddr_v4_p;
- typedef struct
- {
- int sockfd;
- struct sockaddr dns_server_addr;
- void *msgtail;
- void *userdata; // 这个变量留着给用户存自己的东西
-
- int is_tcp;
- int conn_status;
-
- size_t cbmsg;
- size_t cbsend;
- uint8_t sendbuf[8192];
- size_t cbrecv;
- uint8_t recvbuf[8192];
- }dns_lookup_t, *dns_lookup_p;
- #define MAX_SEND_UDP 512
- typedef struct
- {
- uint16_t Id;
- uint16_t Params;
- uint16_t NumQuestions;
- uint16_t NumAnswers;
- uint16_t NumAuthority;
- uint16_t NumAdditional;
- }dns_query_t, *dns_query_p;
- typedef struct
- {
- dns_query_t query;
- char body[1];
- }dns_msg_t, *dns_msg_p;
- #define dnsp_response 0x8000
- #define dnsp_stdquery 0x0000
- #define dnsp_invquery 0x0800 // 过时
- #define dnsp_status 0x1000
- #define dnsp_notify 0x2000
- #define dnsp_update 0x2800 // DDNS更新
- #define dnsp_aa 0x0400 // 权威
- #define dnsp_tc 0x0200 // 截断
- #define dnsp_rd 0x0100 // 请求用递归查询(客户端发送)
- #define dnsp_ra 0x0080 // 可以用递归查询(服务器返回)
- typedef enum
- {
- dnsperr_format_err = 0x0001, // 格式错误
- dnsperr_server_fail = 0x0002, // 服务器gg
- dnsperr_name_err = 0x0003, // 没查到
- dnsperr_not_implemented = 0x0004, // 功能不存在
- dnsperr_refused = 0x0005, // 拒绝服务
- dnsperr_yx_domain = 0x0006, // 本来应该没有的域名,但是有
- dnsperr_yx_rr_set = 0x0007, // 有一个资源记录集存在,本来不应该有
- dnsperr_nx_rr_set = 0x0008, // 有一个资源记录集应该有,但它没有
- dnsperr_notauth = 0x0009, // 查询的服务器并不权威
- dnsperr_notzone = 0x000a, // 相关的信息并不是当前域存在的
- }dnsp_err_t, *dnsp_err_p;
- typedef enum
- {
- dns_rr_a = 1, // 地址
- dns_rr_ns = 2, // 域名服务器地址
- dns_rr_cname = 5, // 别名
- dns_rr_soa = 6, // 权威服务器的起始
- dns_rr_wks = 11, // 知名源
- dns_rr_ptr = 12, // 指针
- dns_rr_mx = 15, // 邮件交换
- dns_rr_txt = 16, // 文本信息
- dns_rr_aaaa = 28, // IPv6地址
- dns_rr_srv = 33, // 服务
- dns_rr_all = 255, // 全部
- }dns_rr_t, *dns_rr_p;
- typedef enum
- {
- dpr_success = 0, // 分析成功
- dpr_not_received, // 没有接收到包裹
- dpr_incomplete_package, // 没有接收全包裹,TCP的话需要继续接收,UDP的话
- // 则表明,这个回答太长了,应该换成TCP(虽然不是所有的DNS服务器都支持TCP)
- dpr_no_answers, // 服务器表示无可奉告
- dpr_bad_package, // 坏包裹——得到的回答并不对应我们的提问
- }dns_parse_result_t, *dns_parse_result_p;
- //=============================================================================
- //函数:_TimeStr
- //描述:返回当前时间的字符串地址,HH:MM:SS,非线程安全,永远返回同一个字符串缓
- // 冲区,配合_Log使用。禁止多线程使用它。
- //-----------------------------------------------------------------------------
- static char*_TimeStr()
- {
- static char _TimeBuf[9];
- struct tm*pTime;
- time_t _Time;
- time(&_Time);
- pTime = gmtime(&_Time);
- sprintf(_TimeBuf, "%.2d:%.2d:%.2d",
- pTime->tm_hour, pTime->tm_min, pTime->tm_sec);
- return _TimeBuf;
- }
- //=============================================================================
- //函数:_Log
- //描述:写入日志到stderr
- //-----------------------------------------------------------------------------
- static void _Log(const char*szFormat, ...)
- {
- va_list ap;
- va_start(ap, szFormat);
- vfprintf(stderr, szFormat, ap);
- va_end(ap);
- }
- //=============================================================================
- //函数:_LogSockErr
- //描述:记录错误原因
- //-----------------------------------------------------------------------------
- static void _LogSockErr(const char *szSource)
- {
- _Log("[%s]: %s: %d(%s)\n",
- _TimeStr(), szSource, sockerr_native_errno,
- sockerr_system()?"system issue":
- sockerr_addrinuse()?"address already in use":
- sockerr_connbreak()?"disconnected":
- sockerr_notconn()?"not connected":
- sockerr_param()?"bad parameters":
- sockerr_wouldblock()?"not replied":
- "unknown");
- }
- //=============================================================================
- //函数:_new_nonblocking_socket
- //描述:创建一个非阻塞IO的socket
- //-----------------------------------------------------------------------------
- static int _new_nonblocking_socket(int af, int type, int protocol)
- {
- int sockfd;
- #ifdef WIN32
- sockfd = socket(af, type, protocol);
- #else
- sockfd = socket(af, type | SOCK_NONBLOCK, protocol);
- #endif
- if(sockfd < 0)
- {
- _LogSockErr("Create socket failed");
- return sockfd;
- }
-
- #ifdef WIN32
- {
- unsigned long mode = 1;
- if(ioctlsocket(sockfd, FIONBIO, &mode) != 0)
- {
- _LogSockErr("Failed to set a socket to non-blocking mode");
- close_socket(sockfd);
- return -1;
- }
- }
- #endif
- return sockfd;
- }
- //=============================================================================
- //函数:_bind_ipv4_port
- //描述:绑定IPV4端口
- //-----------------------------------------------------------------------------
- static int _bind_ipv4_port(int sockfd, uint16_t Port)
- {
- sockaddr_v4_t sav4 = {0};
- int ret;
- sav4.sa_family = AF_INET;
- sav4.sa_port = htons(Port);
- ret = bind(sockfd, (struct sockaddr*)&sav4, sizeof(sav4));
- if(ret < 0)
- {
- _LogSockErr("Failed to bind port");
- }
- return ret;
- }
- //=============================================================================
- //函数:dl_delete
- //描述:擦屁股
- //-----------------------------------------------------------------------------
- void dl_delete(dns_lookup_p pdl)
- {
- if(pdl)
- {
- if(pdl->sockfd >= 0)
- close_socket(pdl->sockfd);
- free(pdl);
- }
- }
- //=============================================================================
- //函数:dl_set_dns_server_v4
- //描述:设置DNS服务器的地址
- //-----------------------------------------------------------------------------
- void dl_set_dns_server_v4(dns_lookup_p dl, const char *pszAddr)
- {
- dl->dns_server_addr.sa_family = AF_INET;
- ((sockaddr_v4_p)&dl->dns_server_addr)->sa_port = htons(DNS_LOOKUP_PORT);
- ((sockaddr_v4_p)&dl->dns_server_addr)->sa_addr = inet_addr(pszAddr);
- }
- //=============================================================================
- //函数:dl_create_tcp
- //描述:创建一个TCP协议的查询器,初始化它,以用于准备查询
- //-----------------------------------------------------------------------------
- dns_lookup_p dl_create_tcp()
- {
- dns_lookup_p pRet = /* VS的编辑器会给右边加波浪线 */malloc(sizeof(dns_lookup_t));
- if(!pRet)
- {
- _Log("[%s]: malloc() failed: %d.\n", _TimeStr(), errno);
- return NULL;
- }
- memset(pRet, 0, sizeof(dns_lookup_t));
- pRet->is_tcp = 1;
- pRet->sockfd = _new_nonblocking_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(pRet->sockfd < 0)
- goto ErrReturn;
- //_bind_ipv4_port(pRet->sockfd, DNS_LOOKUP_PORT);
- dl_set_dns_server_v4(pRet, "8.8.4.4");
- return pRet;
- ErrReturn:
- dl_delete(pRet);
- return NULL;
- }
- //=============================================================================
- //函数:dl_create_udp
- //描述:创建一个UDP协议的查询器,初始化它,以用于准备查询
- //-----------------------------------------------------------------------------
- dns_lookup_p dl_create_udp()
- {
- dns_lookup_p pRet =
- /* VS的编辑器会给右边加该死的波浪线 */malloc(sizeof(dns_lookup_t));
- if(!pRet)
- {
- _Log("[%s]: malloc() failed: %d.\n", _TimeStr(), errno);
- return NULL;
- }
- memset(pRet, 0, sizeof(dns_lookup_t));
- pRet->sockfd = _new_nonblocking_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if(pRet->sockfd < 0)
- goto ErrReturn;
- {
- long v = 1;
- if(setsockopt(pRet->sockfd, SOL_SOCKET, SO_REUSEADDR,
- (char*)&v, sizeof(v)) < 0)
- {
- _Log("[%s]: setsockopt(SO_REUSEADDR) failed: %d.\n", _TimeStr(),
- sockerr_native_errno);
- }
- }
- _bind_ipv4_port(pRet->sockfd, DNS_LOOKUP_PORT);
- dl_set_dns_server_v4(pRet, "8.8.4.4");
- return pRet;
- ErrReturn:
- dl_delete(pRet);
- return NULL;
- }
- //=============================================================================
- //函数:dl_start_form
- //描述:开始构造查询
- //-----------------------------------------------------------------------------
- void dl_start_form(dns_lookup_p dl)
- {
- dns_msg_p pmsg = (dns_msg_p)dl->sendbuf;
-
- memset(dl->sendbuf, 0, sizeof(dl->sendbuf));
- srand(clock());
- pmsg->query.Id = htons(rand());
- pmsg->query.Params = htons(dnsp_rd);
- dl->msgtail = pmsg->body;
- dl->cbmsg = sizeof(dns_query_t);
- }
- //=============================================================================
- //函数:_to_fqdn
- //描述:分析域名,改变格式
- //-----------------------------------------------------------------------------
- static size_t _to_fqdn(uint8_t *buf, size_t cbbuf, const char *pszDomain)
- {
- char*delim = buf++;
- size_t cb = 1;
- *delim = 0;
- while(cbbuf)
- {
- switch(*pszDomain)
- {
- case '.':
- delim = buf++;
- cbbuf--; cb++;
- *delim = 0;
- pszDomain++;
- break;
- case '\0':
- delim = buf;
- *delim = 0;
- return ++cb;
- default:
- *buf++ = *pszDomain++;
- cbbuf--; cb++;
- (*delim)++;
- break;
- }
- }
- return 0;
- }
- //=============================================================================
- //函数:_from_fqdn
- //描述:把FQDN转换为用句点隔开的域名
- //-----------------------------------------------------------------------------
- static size_t _from_fqdn(char *buf, size_t cbbuf, const uint8_t *fqdn)
- {
- size_t cb = 0;
- for(;*fqdn;)
- {
- if(*fqdn < cbbuf)
- memcpy(buf, fqdn+1, *fqdn);
- else
- return 0;
- cbbuf -= *fqdn;
- buf += *fqdn;
- *buf++ = '.';
- cb += *fqdn + 1;
- fqdn += *fqdn + 1;
- }
- *buf = '\0';
- return cb;
- }
- //=============================================================================
- //函数:dl_add_domain
- //描述:添加一个域名到查询表格,失败返回0
- //-----------------------------------------------------------------------------
- int dl_add_domain
- (
- dns_lookup_p dl,
- const char *pszDomain,
- dns_rr_t rr,
- int rrclass
- )
- {
- dns_msg_p pmsg = (dns_msg_p)dl->sendbuf;
- size_t cbdomain;
-
- if(sizeof(dl->sendbuf) - dl->cbmsg < 4)
- {
- _Log("[%s]: No enough space to add a new domain query for %s\n",
- _TimeStr(), pszDomain);
- return 0;
- }
- cbdomain = _to_fqdn(dl->msgtail, sizeof(dl->sendbuf) - dl->cbmsg - 4, pszDomain);
- if(!cbdomain)
- {
- _Log("[%s]: No enough space to add a new domain query for %s\n",
- _TimeStr(), pszDomain);
- return 0;
- }
- dl->msgtail = (uint8_t*)dl->msgtail + cbdomain;
- dl->cbmsg += cbdomain;
-
- *((uint16_t*)dl->msgtail) = htons(rr);
- dl->msgtail = (uint16_t*)dl->msgtail + 1;
- *((uint16_t*)dl->msgtail) = htons(rrclass);
- dl->msgtail = (uint16_t*)dl->msgtail + 1;
- dl->cbmsg += 4;
- pmsg->query.NumQuestions = htons(htons(pmsg->query.NumQuestions) + 1);
- return 1;
- }
- //=============================================================================
- //函数:dl_try_send_query_udp
- //描述:发送一个查询到dns服务器,通过UDP协议。
- //失败返回-1,没发出去返回0(需再来一遍),成功返回1
- //-----------------------------------------------------------------------------
- static int dl_try_send_query_udp(dns_lookup_p dl)
- {
- int sr;
- dl->cbsend = 0;
- dl->cbrecv = 0;
-
- // 判断截断
- if(dl->cbmsg > MAX_SEND_UDP)
- {
- dl->cbmsg = MAX_SEND_UDP;
- ((dns_query_p)dl->sendbuf)->Params |= dnsp_tc;
- }
- sr = sendto(dl->sockfd, dl->sendbuf, dl->cbmsg, 0,
- &dl->dns_server_addr, sizeof(dl->dns_server_addr));
- if(sr == dl->cbmsg)
- {
- dl->cbsend = sr;
- return 1; // 发送成功
- }
- else
- {
- if(sockerr_wouldblock())
- return 0;
- else
- {
- _LogSockErr("Send DNS loop up query failed");
- return-1;
- }
- }
- }
- //=============================================================================
- //函数:dl_try_send_query_tcp
- //描述:尝试发送一个查询到dns服务器,通过TCPIP协议。
- //失败返回-1,需要再次调用返回0,成功返回1
- //-----------------------------------------------------------------------------
- static int dl_try_send_query_tcp(dns_lookup_p dl)
- {
- int cr;
- int sr;
-
- switch(dl->conn_status)
- {
- case 0:
- cr = connect(dl->sockfd, &dl->dns_server_addr, sizeof(dl->dns_server_addr));
- dl->conn_status = 1;
- if(cr < 0)
- {
- if(sockerr_wouldblock())
- return 0;
- else
- {
- _LogSockErr("Send DNS loop up query failed");
- return-1;
- }
- }
- dl->cbsend = 0;
- dl->cbrecv = 0;
- //return 0;
- case 1:
- sr = send(dl->sockfd, dl->sendbuf + dl->cbsend, dl->cbmsg - dl->cbsend, 0);
- if(sr > 0)
- {
- dl->cbsend += sr;
- if(dl->cbsend >= dl->cbmsg)
- return 1;
- else
- return 0;
- }
- else
- {
- if(sockerr_wouldblock() || sockerr_notconn())
- return 0;
- else
- {
- _LogSockErr("Send DNS loop up query failed");
- return-1;
- }
- }
- default:
- _Log("Unknown error.\n");
- return-1;
- }
- }
- //=============================================================================
- //函数:dl_try_send_query
- //描述:尝试发送一个查询到dns服务器。
- //失败返回-1,需要再次调用返回0,成功返回1
- //-----------------------------------------------------------------------------
- int dl_try_send_query(dns_lookup_p dl)
- {
- if(dl->is_tcp)
- return dl_try_send_query_tcp(dl);
- else
- return dl_try_send_query_udp(dl);
- }
- //=============================================================================
- //函数:dl_send_query
- //描述:发送一个查询到dns服务器,失败返回0
- //-----------------------------------------------------------------------------
- int dl_send_query(dns_lookup_p dl, clock_t timeout_ms)
- {
- clock_t c_start = clock();
- for(;;)
- {
- switch(dl_try_send_query(dl))
- {
- case 1:
- return 1;
- case-1:
- return 0;
- }
- if(clock() - c_start > (timeout_ms * CLOCKS_PER_SEC) / 1000)
- return 0;
- }
- }
- //=============================================================================
- //函数:dl_poll_reply_udp
- //描述:检查DNS服务器是否返回了回复,有回复时返回1,没回复时返回0,出错返回-1
- //-----------------------------------------------------------------------------
- static int dl_poll_reply_udp(dns_lookup_p dl)
- {
- int fromlen = sizeof(dl->dns_server_addr);
- int rr = recvfrom(dl->sockfd, dl->recvbuf, sizeof(dl->recvbuf), 0,
- &dl->dns_server_addr, &fromlen);
- if(rr > 0)
- {
- dl->cbrecv = rr;
- return 1;
- }
- else
- {
- if(sockerr_wouldblock())
- return 0;
- else
- {
- _LogSockErr("Failed to receive reply");
- return-1;
- }
- }
- }
- //=============================================================================
- //函数:dl_poll_reply_tcp
- //描述:检查DNS服务器是否返回了回复,有回复时返回1,没回复时返回0,出错返回-1
- //-----------------------------------------------------------------------------
- static int dl_poll_reply_tcp(dns_lookup_p dl)
- {
- int rr;
-
- if(dl->cbrecv >= sizeof(dl->recvbuf))
- return 1;
- rr = recv(dl->sockfd, dl->recvbuf + dl->cbrecv,
- sizeof(dl->recvbuf) - dl->cbrecv, 0);
- if(rr > 0)
- {
- dl->cbrecv += rr;
- return 1;
- }
- else if(rr == 0)
- {
- return 0;
- }
- else
- {
- if(sockerr_wouldblock())
- return 0;
- else
- {
- _LogSockErr("Failed to receive reply");
- return-1;
- }
- }
- }
- //=============================================================================
- //函数:dl_poll_reply
- //描述:检查DNS服务器是否返回了回复,有回复时返回1,没回复时返回0,出错返回-1
- //-----------------------------------------------------------------------------
- int dl_poll_reply(dns_lookup_p dl)
- {
- if(dl->is_tcp)
- return dl_poll_reply_tcp(dl);
- else
- return dl_poll_reply_udp(dl);
- }
- //=============================================================================
- //函数:dl_parse_name
- //描述:分析DNS服务器发回的回复信息中的QNAME,返回一个字符串
- //-----------------------------------------------------------------------------
- const char *dl_parse_name
- (
- dns_lookup_p dl,
- size_t name_offset,
- size_t*srcbytes
- )
- {
- static char name[256] = {0};
- size_t cbsource = 0;
- size_t cbname = 0;
- const char *pch;
-
- if(name_offset >= dl->cbrecv)
- return NULL;
- Parse_From_Entry:
- pch = dl->recvbuf + name_offset;
- for(;*pch;)
- {
- if(((*pch) & 0xC0) == 0xC0)
- {
- // 发现“指针”
- size_t ptr = ntohs(*(uint16_t*)pch) & 0x3FFF; // 取指针
- if(ptr >= dl->cbrecv)
- return NULL;
- // 记录源长度就此为止
- cbsource += 2;
- if(srcbytes)
- *srcbytes = cbsource;
- // 跳到指针的位置
- name_offset = ptr;
- srcbytes = NULL;
- // 继续分析
- goto Parse_From_Entry;
- }
- else if(((*pch) & 0xC0) == 0x00)
- {
- name_offset += *pch + 1;
- if(name_offset >= dl->cbrecv)
- return NULL;
- cbsource += *pch + 1;
- memcpy(name + cbname, pch+1, *pch);
- cbname += *pch;
- name[cbname++] = '.';
- pch += *pch + 1;
- }
- else
- return NULL;
- }
- name[--cbname] = '\0';
- cbsource ++;
- if(srcbytes)
- *srcbytes = cbsource;
- return name;
- }
- //=============================================================================
- //函数:dl_parse_reply
- //描述:分析DNS服务器发回的回复信息
- //-----------------------------------------------------------------------------
- dns_parse_result_t dl_parse_reply
- (
- dns_lookup_p dl,
- void(*on_get_answer)
- (
- dns_lookup_p dl,
- const char *name, // 对应的域名(已经转换为用句点分隔的字符串)
- dns_rr_t resource_record_type, // 记录类型
- uint16_t _class, // 类,一般为1(IN)
- uint32_t ttl, // 缓存时间
- uint16_t record_length, // 记录长度
- const char *record // 记录内容
- )
- )
- {
- char*pch;
- char*pQuestionSend;
- char*pQuestionRecv;
- char*pAnswer;
- size_t cbdomain = 0;
- size_t cbparse = 0;
- size_t i;
- dns_msg_p pSendHead = (dns_msg_p)dl->sendbuf;
- dns_msg_p pRecvHead = (dns_msg_p)dl->recvbuf;
- uint16_t numQuestions, numAnswers;
- // 判断是否收到包裹了
- if(!dl->cbrecv)
- return dpr_not_received;
- if(dl->cbrecv <= sizeof(dns_query_t))
- return dpr_incomplete_package;
- cbparse += sizeof(dns_query_t);
- // 判断ID是否对应
- if(pRecvHead->query.Id != pSendHead->query.Id)
- return dpr_bad_package;
- // 判断对方是否给了回答
- if(!(pRecvHead->query.Params & dnsp_response))
- return dpr_bad_package; // 被反问了
- numQuestions = ntohs(pRecvHead->query.NumQuestions);
- numAnswers = ntohs(pRecvHead->query.NumAnswers);
- // 对方举一反三了……
- if(numQuestions > ntohs(pSendHead->query.NumQuestions))
- return dpr_bad_package;
- // 判断回答
- if(!numAnswers)
- return dpr_no_answers; // 对方表示无可奉告
- // 遍历检查所有的提问
- pQuestionSend = pSendHead->body;
- pQuestionRecv = pRecvHead->body;
- for(i = 0; i < numQuestions; i++)
- {
- cbdomain = 0;
- for(pch = pQuestionRecv; *pch; pch += *pch + 1)
- cbdomain += *pch + 1;
- cbdomain ++; // 有个'\0'结尾
- cbparse += cbdomain + 4;
- if(cbparse > dl->cbrecv)
- return dpr_incomplete_package;
-
- // 判断是不是答非所问
- if(memcmp(pQuestionSend, pQuestionRecv, cbdomain + 4))
- return dpr_bad_package;
- pQuestionSend += cbdomain + 4;
- pQuestionRecv += cbdomain + 4;
- }
- if(cbparse + 12 > dl->cbrecv)
- return dpr_incomplete_package;
- for(i = 0; i < numAnswers; i++)
- {
- static char ans_domain[256] = {0};
- static char name_record[256] = {0};
- struct
- {
- uint16_t Type;
- uint16_t Class;
- uint32_t TTL;
- uint16_t RDLength;
- char RData[1];
- } *answer_struct;
- size_t cbname;
- const char*pszname;
- // NAME
- pszname = dl_parse_name(dl, cbparse, &cbname);
- if(!pszname)
- return dpr_bad_package;
- strcpy(ans_domain, pszname);
- cbparse += cbname;
-
- if(cbparse + 10 > dl->cbrecv)
- return dpr_incomplete_package;
- answer_struct = (void*)(dl->recvbuf + cbparse);
- cbparse += 10;
-
- cbparse += ntohs(answer_struct->RDLength);
- if(cbparse > dl->cbrecv)
- return dpr_incomplete_package;
- on_get_answer(dl,
- ans_domain,
- ntohs(answer_struct->Type),
- ntohs(answer_struct->Class),
- ntohl(answer_struct->TTL),
- ntohs(answer_struct->RDLength),
- answer_struct->RData);
- }
- return dpr_success;
- }
- //=============================================================================
- //函数:_print_ipv6_addr
- //描述:打印IPV6地址
- //-----------------------------------------------------------------------------
- void _print_ipv6_addr(FILE*fp, void *addr)
- {
- char buf[40];
- inet_ntop(AF_INET6, addr, buf, sizeof(buf));
- fputs(buf, fp);
- }
- //=============================================================================
- //函数:demo_get_answer
- //描述:示例回调函数,取得解析结果
- //-----------------------------------------------------------------------------
- void demo_get_answer
- (
- dns_lookup_p dl,
- const char *name, // 对应的域名(已经转换为用句点分隔的字符串)
- dns_rr_t resource_record_type, // 记录类型
- uint16_t _class, // 记录长度
- uint32_t ttl, // 缓存时间
- uint16_t record_length, // 记录长度
- const char *record // 记录内容
- )
- {
- printf("Name: %s\nType: %u(%s)\nTTL: %u\n",
- name,
- resource_record_type,
- resource_record_type == dns_rr_a?"A":
- resource_record_type == dns_rr_ns?"NS":
- resource_record_type == dns_rr_cname?"CNAME":
- resource_record_type == dns_rr_soa?"SOA":
- resource_record_type == dns_rr_wks?"WKS":
- resource_record_type == dns_rr_ptr?"PTR":
- resource_record_type == dns_rr_mx?"MX":
- resource_record_type == dns_rr_srv?"SRV":
- resource_record_type == dns_rr_aaaa?"AAAA":
- "Unknown",
- ttl);
- switch(resource_record_type)
- {
- case dns_rr_a:
- printf("Value: %u.%u.%u.%u\n",
- (uint8_t)record[0],
- (uint8_t)record[1],
- (uint8_t)record[2],
- (uint8_t)record[3]);
- break;
- case dns_rr_cname:
- case dns_rr_ns:
- case dns_rr_ptr:
- printf("Value: %s\n",
- dl_parse_name(dl, (ptrdiff_t)record - (ptrdiff_t)dl->recvbuf, NULL));
- break;
- case dns_rr_aaaa:
- printf("Value: ");
- _print_ipv6_addr(stdout, (void*)record);
- printf("\n");
- break;
- default:
- break;
- }
- }
- //=============================================================================
- //函数:main
- //描述:程序入口点
- //-----------------------------------------------------------------------------
- int main(int argc,char**argv)
- {
- dns_lookup_p pdl = NULL;
- int ret = 1;
- if(argc < 2)
- {
- fprintf(stderr, "Usage: %s domain [dns server ip]\n", argv[0]);
- return-1;
- }
- #ifdef WIN32
- {
- WSADATA wsaData;
- if(WSAStartup(WINSOCK_VERSION, &wsaData) < 0)
- {
- fprintf(stderr, "WSAStartup(WINSOCK_VERSION, &wsaData) failed with %u\n.", WSAGetLastError());
- return-1;
- }
- }
- #endif
- pdl = dl_create_udp();
- if(!pdl)goto EOP;
- // dl_set_dns_server_v4(pdl, "119.29.29.29");
- // 我这的路由器只允许我把DNS查询发到网关
- // dl_set_dns_server_v4(pdl, "192.168.11.1");
-
- if(argc >= 3)
- dl_set_dns_server_v4(pdl, argv[2]);
- dl_start_form(pdl);
- if(!dl_add_domain(pdl, argv[1], dns_rr_all, 1))
- goto EOP;
-
- dl_send_query(pdl, 1000);
- {
- clock_t _cstart = clock();
- const clock_t timeout = CLOCKS_PER_SEC * 10; // 等10秒
- for(;;relax())
- {
- clock_t _now;
- switch(dl_poll_reply(pdl))
- {
- case 1:
- _cstart = clock();
- fprintf(stderr, "Response received.\n");
- ret = dl_parse_reply(pdl, demo_get_answer);
- if(ret == dpr_no_answers)
- fprintf(stderr, "no answers.\n");
- if(!pdl->is_tcp)
- goto EOP;
- break;
- case-1:
- goto EOP;
- }
-
- _now = clock();
- if(_now - _cstart >= timeout)
- {
- fprintf(stderr, "Timed out, no response.\n");
- goto EOP;
- }
- }
- }
- ret = 0;
- EOP:
- dl_delete(pdl);
- pdl = NULL;
-
- #ifdef WIN32
- WSACleanup();
- #endif
- return ret;
- }
复制代码 源码下载:
dnslkup.c
(29.6 KB, 下载次数: 10)
这份代码可以移植到Linux上运行,简单地打印DNS查询的结果。
参考资料:
RFC 1034
RFC 1035
RFC 1536
RFC 1996
RFC 2136
RFC 3425
RFC 5966
RFC 7766
http://www.zytrax.com/books/dns/ch15/
http://www.tcpipguide.com/free/t ... onSectionFormat.htm |
|