Back
Featured image of post DNS协议解析

DNS协议解析

基础知识

DNS

Domain Name System 域名系统,是一个分布式数据库,用于映射IP与域名

每级域名长度限制为63,总长度限制为253,使用TCP UDP端口53

DNS分层

顶级域:com org等 第二级域:baidu google等 第三级域:www edu等

域名解析

静态映射:在本机上配置域名和ip映射并直接使用

动态映射:使用DNS域名解析系统,在DNS服务器上配置ip到域名的映射

域名服务器

根域名服务器: 共a-m十三台(十三个ip)但拥有很多镜像服务器,镜像与本体使用同一个ip,存有顶级域名服务器的ip 顶级域名服务器:管理在该顶级域名服务器下注册的二级域名 权限域名服务器:一个区域的域名解析 本地域名服务器:处理本地的请求,保存本地的映射

域名解析方式

迭代查询:本机请求本地域名服务器,本地域名服务器开始迭代的查询各个层级服务器,先查询根获得顶级的ip然后根据获得的ip查询顶级在获得区域的ip依次迭代查到请求的映射

递归查询:递归查询时只发出一次请求然后等待接收到最终结果,在上面的步骤中本机使用的就是递归查询

协议报文格式

dns_dp
dns_dp
dns_dp
dns_dp
dns_dp
dns_dp
dns_dp
dns_dp

具体查看文档

DNS client UDP编程

首先需要自己定义数据结构用于存储dns报文

struct dns_header{

    unsigned short id;
    unsigned short flags;
    
    unsigned short questions;
    unsigned short answer;

    unsigned short authority;
    unsigned short additional;

};
struct dns_question {

    int length;
    unsigned short qtype;
    unsigned short qclass;
    unsigned char *name;

};

这里只需要question和header是因为我们作为client只实现发送A请求也就是获取域名的ipv4地址,在实现中header的授权码和附加码都不需要使用只需要使用questions id和flags即可

先建立UDP套接字用于发送UDP报文:

#define DNS_SERVER_PORT  53
#define DNS_SERVER_IP "114.114.114.114"

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if(sockfd < 0)
{
    return -1;
}

struct sockaddr_in servaddr = {0};    
servaddr.sin_family = AF_INET;
servaddr.sin_port  = htons(DNS_SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);

然后使用connect检测开辟本机到DNS服务器的通路:

int ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

ret返回值可以用于检测通路是否存在

然后开始初始化要发送的DNS报文:

1.初始化dns header:

struct dns_header header = {0};
dns_create_header(&header);

使用的dns_create_header:

int dns_create_header(struct dns_header *header)
{
    if(header == NULL) return -1;

    memset(header, 0, sizeof(struct dns_header));


    //random
    srandom(time(NULL));
    header->id = random();

    header->flags = htons(0x0100);

    header->questions = htons(1);
    
    return 0;

}

这里的id为随机生成 questions为1代表一个问题 flags的0x0100查表后代表:期望递归 传地址来初始化header

然后就是初始化正文部分的question:

struct dns_question question = {0};
dns_create_question(&question, domain);

dns_create_question的实现:

int dns_create_question(struct dns_question* question, const char *hostname)
{

    if(question == NULL || hostname == NULL) return -1;

    memset(question, 0, sizeof(struct dns_question));

    question->name = (char*)malloc(strlen(hostname) + 2);

    if(question->name == NULL)
    {
        return -1;
    }

    question->length = strlen(hostname) + 2;

    question->qtype = htons(1);

    question->qclass = htons(1);

    //name
    const char delim[2] = ".";
    char *hostname_dup = strdup(hostname);
    char *token = strtok(hostname_dup, delim);

    char *qname = question->name;

    while(token != NULL)
    {
        size_t len = strlen(token);
        *qname = len;
        qname++;

        strncpy(qname, token, len+1); // +1 copy \0
        qname += len;
        
        token = strtok(NULL, delim);
    }


    free(hostname_dup);

}

这里正文的初始化使用循环和strtok分别取出每一级域名然后形成正文部分,www.baidu.com –> 3www5baidu3com0

接下来就可以将已经初始化好的header和question组装成dns报文request:

char request[1024] = {0};
int length = dns_build_request(&header, &question, request, 1024);

dns_build_request的实现:

int dns_build_request(struct dns_header* header, struct dns_question* question, char *request, int rlen )
{   
    if(header == NULL || question == NULL || request == NULL)
    return -1;
    memset(request, 0 , rlen);

    //header --> request

    int offset = 0;
    memcpy(request, header, sizeof(struct dns_header));
    offset = sizeof(struct dns_header);

    //question --> request

    memcpy(request+offset, question->name, question->length);
    offset += question->length;

    memcpy(request+offset, &question->qtype, sizeof(question->qtype));
    offset += sizeof(question->qtype); 

    memcpy(request+offset, &question->qclass, sizeof(question->qclass));
    offset += sizeof(question->qclass); 

    return offset;

}

可以看到就是按照协议进行拼接

接下来就可以调用unix 网络编程capi来通过udp套接字发送正文:

//request
int slen = sendto(sockfd, request, length, 0, (struct sockaddr*)&servaddr, sizeof(struct sockaddr));

至此发送就结束了

我们发送了dns查询报文dns服务器会返回结果,所以可以通过套接字来接收这个结果,并打印出来验证一下:

//recvfrom()
char response[1024] = {0};
struct sockaddr_in addr;
size_t addr_len = sizeof(struct sockaddr_in);


int n =recvfrom(sockfd, response, sizeof(response), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);

printf("recvfrom : %d, %s\n", n, response);

也可以写代码解析返回的结果,具体查看代码就可以

至此就实现了一个dns客户端用于给dns服务器发送查询报文来查询一个域名的ipv4地址

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy