技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 784|回复: 4
收起左侧

【C】又造了一个轮子——自己实现通过UDP发送DNS查询,并简单分析服务器返回的内容

[复制链接]

993

主题

2190

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16161 个
贡献
31411 次
宅之契约
0 份
在线时间
1543 小时
注册时间
2014-1-26
发表于 2017-2-6 03:54:02 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
所谓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语言表达就是这样的:
  1. #include<stdint.h>

  2. typedef struct
  3. {
  4.         uint16_t Id; // 会话ID
  5.         uint16_t Params; // 参数
  6.         uint16_t NumQuestions; // 问题数
  7.         uint16_t NumAnswers; // 回答数
  8.         uint16_t NumAuthority; // 权威记录数
  9.         uint16_t NumAdditional; // 额外记录数
  10. }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报文头应该是什么样子的?看下面的截图。
send.png
其中ID为0x00DD,参数中RD为1,要求DNS服务器进行一次递归查询,提问数为1,其它都没有。
然后是经过FQDN编码的域名,exmainland.com,查询资源类型为255,也就是“全部”。
最后是查询类,1表示IN

接下来我们看一下“回答”的格式,按照介绍是这样的:
  • 编码为FQDN的域名。
  • 一个16位整数,回答的资源类型
  • 一个16位整数,资源类
  • 一个32位整数,TTL,缓存时间
  • 一个16位整数,资源数据的长度,也就是回答的答案的长度
  • 资源数据,也就是答案,长度取决于资源类型


那么我们实际测试,收到的回执的报文是什么样子的呢?如下图:
recv.png
首先可以看出ID和我们之前的提问是一样的。
其次是,回执报文也包含了“问题”,而不仅仅是“回答”。
这里面共有4条回答,一条A记录,两条NS记录,一条SOA记录。

详细的,发送报文、接收并分析报文的C语言程序源码,请看下面:
[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, 下载次数: 4)

本帖被以下淘专辑推荐:

34

主题

132

帖子

6832

积分

用户组: 管理员

UID
77
精华
11
威望
112 点
宅币
6292 个
贡献
129 次
宅之契约
0 份
在线时间
87 小时
注册时间
2014-2-22
发表于 2017-2-6 07:31:46 | 显示全部楼层
太牛逼了!
回复

使用道具 举报

12

主题

38

帖子

581

积分

用户组: 版主

UID
2185
精华
1
威望
52 点
宅币
364 个
贡献
70 次
宅之契约
0 份
在线时间
36 小时
注册时间
2017-1-16

突出贡献

发表于 2017-2-13 12:39:18 | 显示全部楼层
查询是否答非所问,这个功能对DNS污染似乎有作用
8888005

2

主题

30

帖子

135

积分

用户组: 小·技术宅

UID
2285
精华
0
威望
0 点
宅币
105 个
贡献
0 次
宅之契约
0 份
在线时间
18 小时
注册时间
2017-2-25
发表于 2017-2-25 23:59:22 | 显示全部楼层
DNS服务器只有一个么,// dl_set_dns_server_v4(pdl, "119.29.29.29");   如果这个服务器崩了,那大家都上不了网了,怎么办???

993

主题

2190

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16161 个
贡献
31411 次
宅之契约
0 份
在线时间
1543 小时
注册时间
2014-1-26
 楼主| 发表于 2017-9-8 01:55:16 | 显示全部楼层
乘简 发表于 2017-2-25 23:59
DNS服务器只有一个么,// dl_set_dns_server_v4(pdl, "119.29.29.29");   如果这个服务器崩了,那大家都 ...

如果这个崩了,就换一个服务器

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-7-18 15:00 , Processed in 0.103162 second(s), 15 queries , Gzip On, Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表