0xAA55 发表于 2017-2-6 03:54:02

【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

Golden Blonde 发表于 2017-2-6 07:31:46

太牛逼了!

黑魔法师Rabbit 发表于 2017-2-13 12:39:18

查询是否答非所问,这个功能对DNS污染似乎有作用:P;P

乘简 发表于 2017-2-25 23:59:22

DNS服务器只有一个么,// dl_set_dns_server_v4(pdl, "119.29.29.29");   如果这个服务器崩了,那大家都上不了网了,怎么办???

0xAA55 发表于 2017-9-8 01:55:16

乘简 发表于 2017-2-25 23:59
DNS服务器只有一个么,// dl_set_dns_server_v4(pdl, "119.29.29.29");   如果这个服务器崩了,那大家都 ...

如果这个崩了,就换一个服务器
页: [1]
查看完整版本: 【C】又造了一个轮子——自己实现通过UDP发送DNS查询,并简单分析服务器返回的内容