网络协议-DNS组包实例

DNS应用

根据DNS报文格式,可以通过自组DNS请求报文,判断当前设备是否已连接外网。DNS报文格式链接: DNS报文格式.

1. 结构体构造

DNS请求报文为header + 正文,其中正文为name+type+class。所以我们需要构造的DNS请求报文格式为header+name+type+class,header可用一个结构体表示,长度为16 * 6位,type+class可用一个结构体表示,长度为16 * 2位。
具体如下:

typedef struct
{
    u16 id;
    u16 flags;
    u16 ques;
    u16 answer;
    u16 auth;
    u16 addi;
}dns_header; // DNS请求头

typedef struct
{
    u16 type;
    u16 class;
}dns_query; // DNS请求正文

2. name处理

在DNS报文中,需要注意域名的编码。请求的域名中没有“.”,域名中的“.”被编码为元信息,指示接下来的多少字节是有效信息。
看个例子,我要请求www.google.com.hk。
报文中的name为 03 77 77 77 06 67 6f 6f 67 6c 65 03 63 6f 6d 02 68 6b 00,即为03 w w w 06 g o o g l e 03 c o m 02 h k 00
其中加粗的就是元信息,最后一个字符为0。

3. 完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define TRUE  0
#define FALSE 1
#define MAX_SIZE 1024

/*
 * DNS header :
 *
 * |  ID             |  flag            |
 * |  Questions      |  Answer          |
 * |  Authority RRs  |  Additional RRs  |
 * |                Name                |
 * |  Type           |  Class           |
 *
 * size : 2 + 2 + 2 + 2 + 2 + 1 + NameLen + 1 + 2 + 2
 */

typedef unsigned char uchar;
typedef unsigned short u16;

typedef struct
{
    u16 id;
    u16 flags;
    u16 ques;
    u16 answer;
    u16 auth;
    u16 addi;
}dns_header;

typedef struct
{
    u16 type;
    u16 class;
}dns_query;

static int socket_init(void)
{
    return socket(AF_INET, SOCK_DGRAM, 0);
}

int get_host_by_name(const char *name)
{
    if (!name || 0 == strlen(name))
        return FALSE;

    int fd = socket_init();
    if (fd < 0)
    {
        printf("socket_init error, socket creat fail \n");
         return FALSE;
    }

    int i = 0;
    char *tmpPtr = NULL;
    char sendBuff[MAX_SIZE];
    dns_header *dnsheader_p = (dns_header *)sendBuff;
    dns_query * dnsquery_p = NULL;

    // DNS header 构造
    memset(sendBuff, 0, MAX_SIZE);
    dnsheader_p->id = (u16)1;
    dnsheader_p->flags = htons(0x0100);
    dnsheader_p->ques = htons(1);
    dnsheader_p->answer = htons(0);
    dnsheader_p->auth = htons(0);
    dnsheader_p->addi = htons(0);

    // DNS name 填充
    strcpy(sendBuff + sizeof(dns_header) + 1, name);
    tmpPtr = sendBuff + sizeof(dns_header) + 1;
    // www.baidu.com -> 3www5baidu3com0
    while (tmpPtr < (sendBuff + sizeof(dns_header) + 1 + strlen(name)))
    {
        if (*tmpPtr != '.')
        {
            i++;
        }
        else
        {
            *(tmpPtr - i - 1) = i;
            i = 0;
        }
        tmpPtr++;
    }
    *(tmpPtr - i - 1) = i;

    // DNS query 构造
    dnsquery_p = (dns_query *)(sendBuff + sizeof(dns_header) + 2 + strlen(name));
    dnsquery_p->type = htons(1);
    dnsquery_p->class = htons(1); // 1 name -> ipv4 28 name -> ipv6

    struct timeval tv;
    struct sockaddr_in servaddr;

    tv.tv_sec = 5;
    tv.tv_usec = 5;

    // 发送DNS请求包
    memset(&servaddr, 0, sizeof(struct sockaddr_in));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(53);
    if (inet_pton(AF_INET, "114.114.114.114", &servaddr.sin_addr) < 0)
    {
        printf("inet_pton error, get sin_addr fail \n");
        close(fd);
        return FALSE;
    }
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval));
    if (sendto(fd, sendBuff, sizeof(dns_header) + 1 + strlen(name) + 1 + sizeof(dns_query),
                        0, (const struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)) < 0)
    {
        printf("send to error, send buff fail \n");
        close(fd);
        return FALSE;
    }

    // 接收DNS响应包
    int len, size;
    size = sizeof(struct sockaddr_in);
    if ((len = recvfrom(fd, sendBuff, MAX_SIZE, 0, (struct sockaddr *)&servaddr, (socklen_t *)&size)) < 0)
    {
        if (errno == EWOULDBLOCK)
        {
            printf("recvfrom error, timeout \n");
        }
        else
        {
            printf("recvfrom error, unknow fail\n");
        }
        close(fd);
        return FALSE;
    }

    if (0 == dnsheader_p->answer)
    {
        printf("ack error, no answer \n");
        close(fd);
        return FALSE;
    }

    tmpPtr = sendBuff + len - 4;
    printf("parsing %s success ! it’s addr is %u.%u.%u.%u \n", name, 
    								(uchar)*tmpPtr, (uchar)*(tmpPtr+1), 
    								(uchar)*(tmpPtr+2), (uchar)*(tmpPtr+3));
    close(fd);
    return TRUE;
}

void usage(void)
{
    printf("usage :\n\n\t./test name\n\n\teg. ./test www.zhihu.com\n\n");
    exit(-1);
}

int main(int argc, char *argv[])
{
    if (argc != 2)
        usage();

    get_host_by_name(argv[1]);

    return 0;
}

程序执行结果如下:
网络协议-DNS组包实例

上一篇:在Android中进行Sip通话


下一篇:来自Asterisk 1.8忽略的来自Android SIP演示的取消请求