【C】又造了一个轮子——自己实现通过UDP发送DNS查询,并简单分析服务器返回的内容
所谓DNS查询,就是查询一个域名的相关信息,一般情况下就是查域名解析的IP地址了。举个例子,我的浏览器要通过TCP/IP协议访问www.baidu.com的80端口并发送HTTP请求,但我要连接到百度的服务器的话,我必须先知道百度服务器的IP地址。此时一般情况下直接调用getaddrinfo()就可以取得百度服务器的IP地址了,但这个函数又是怎么知道百度服务器的IP地址的呢?它靠的是DNS查询(以及读DNS缓存、hosts等)。
DNS查询的过程,通常是通过UDP 53端口发送一个请求报文到DNS服务器上,然后等服务器发送回执,最后分析回执内容。非常简单。
不过UDP发送包裹如果超过了512字节,你就必须手动截断,否则服务器不收。这是服务器的一种自我保护手段,同时也是为了保证不丢包。
每个包裹的结构,无论DNS查询和响应,都是下面的格式:
[*]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格式后,就是“www0xaa55com”(其中用方括号框起来的,表示一个字节的数值)
另外注意,表示字符串长度的那个字节的数值,只有在它高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_tsa_reserved; // = {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;
size_t cbrecv;
uint8_t recvbuf;
}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;
}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;
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 = {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 = '.';
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 = {0};
static char name_record = {0};
struct
{
uint16_t Type;
uint16_t Class;
uint32_t TTL;
uint16_t RDLength;
char RData;
} *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;
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,
(uint8_t)record,
(uint8_t)record,
(uint8_t)record);
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 \n", argv);
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);
dl_start_form(pdl);
if(!dl_add_domain(pdl, argv, 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;
}源码下载:
这份代码可以移植到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_DNSMessageHeaderandQuestionSectionFormat.htm 太牛逼了! 查询是否答非所问,这个功能对DNS污染似乎有作用:P;P DNS服务器只有一个么,// dl_set_dns_server_v4(pdl, "119.29.29.29"); 如果这个服务器崩了,那大家都上不了网了,怎么办??? 乘简 发表于 2017-2-25 23:59
DNS服务器只有一个么,// dl_set_dns_server_v4(pdl, "119.29.29.29"); 如果这个服务器崩了,那大家都 ...
如果这个崩了,就换一个服务器
页:
[1]