网络通讯开发系列如何使用C语言编程通过UDP通讯解析域名

Posted 架构师李肯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络通讯开发系列如何使用C语言编程通过UDP通讯解析域名相关的知识,希望对你有一定的参考价值。


1 前言

相信大家在平时的网络开发中,对域名的接触一定非常多。
对于域名的定义,
域名(英语:Domain Name),又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识(有时也指地理位置)
通俗来说,某个域名就是代表网络上的某个服务器主机,而根据网络协议分层只有IP层,并没有域名层,所以这里就涉及到域名到IP地址的转换,也就是我们常说的域名解析
在平时开发中,我们都是直接调用系统接口实现域名解析,那么我们有没有方法直接根据网络协议组包来完成域名解析呢?
本文就这个问题,将会给出答案。

2 知识点分析

2.1 网络分层

我们熟知的网络七层协议模型如下图所示:

每一层都都有自己的独立的作用:

在我们这章节中重点关注网络层。

2.2 DNS协议

DNs协议的定义:
我们常说的DNS解析,实则就是通过DNS协议,发起解析请求,从域名服务器上取得响应的。
DNS报文分为查询请求报文和查询相应报文,两类报文结构基本相同,结构如下:

详细可参见这里

3 代码实现

直接上实现代码:


#include <stdio.h>
#include <stdint.h>

#include "dns.h"

//#define DNS_PLATFORM_WINDOWS              1  //表示windows平台
#define DNS_PLATFORM_LINUX                  1  //表示linux平台

#define DNS_SERVER_PORT                     53
#define DNS_MAX_IP_LEN                      16 // only for IPV4
#define DNS_MAX_IP_CNT                      1
#define DNS_MAX_TRY_CNT                     1
#define DNS_TIMEOUTS                        5  // 5 seconds
#define DNS_PACKET_ID                       0x55AA
#define DNS_DEBUG_ENABLE                    0  // debug enable

#if (DNS_DEBUG_ENABLE)
#define DNS_PRINTF(fmt, arg...)             MSG_PRINTF(LOG_INFO, fmt, ##arg)
#define DNS_ORG_PRINTF(fmt, arg...)         MSG_ORG_PRINTF(LOG_INFO, fmt, ##arg)
#define DNS_HEXDUMP(title, buf, len)        MSG_INFO_ARRAY(title, buf, len)
#else
#define DNS_PRINTF(fmt, arg...)             do  while(0)
#define DNS_ORG_PRINTF(fmt, arg...)         do  while(0)
#define DNS_HEXDUMP(title, buf, len)        do  while(0)
#endif

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a)                   sizeof(a)/sizeof(a[0])
#endif

/* 
Add your dns server here, but considering the IP white list !
*/
static const char *g_dns_server_list[] = 

    "114.114.114.114",  // 114 DNS   
    "8.8.8.8",          // Google DNS
    "208.67.222.222",   // OpenDNS
;

#if defined (DNS_PLATFORM_LINUX)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#endif

#if defined (DNS_PLATFORM_WINDOWS)
#include <winsock2.h> 
#endif

#if defined (DNS_PLATFORM_WINDOWS)
#pragma comment(lib,"ws2_32.lib")  
#endif

/* 域名查询请求报文头定义,1字节对齐*/
#if defined(DNS_PLATFORM_WINDOWS)
#pragma  pack(push)
#pragma  pack(1)
#endif

struct DnsHeader 
    unsigned short id;

    unsigned char rd:1;         /* 期望递归解析*/
    unsigned char tc:1;         /* 报文未截断 */
    unsigned char aa:1;         /* 授权解析服务器 */
    unsigned char opcode:4;     /* 标准查询 */
    unsigned char qr:1;         /* 0:查询,1:响应 */

    unsigned char rcode:4;      /* 响应吗:0没有出错 */
    unsigned char z:3;          /* 保留将来使用 */
    unsigned char ra:1;         /* DNS服务器是否支持递归解析 */

    unsigned short qdCount;     /* 问题数*/
    unsigned short anCount;     /* 应答数*/
    unsigned short nsCount;     /* 授权机构数 */
    unsigned short arCount;     /* 附加信息数 */
 
#if defined (DNS_PLATFORM_LINUX)
__attribute__((packed))
#endif
;

#if defined(DNS_PLATFORM_WINDOWS)
#pragma  pack(pop)
#endif

#define COMMENT_MAX 64
#define SYMBOL_MAX  8

struct QueryType 
    unsigned short q_class;
    unsigned char mnemonic_symbol[SYMBOL_MAX];
    unsigned char comment[COMMENT_MAX];
;


static unsigned short packet_id = DNS_PACKET_ID;

/* 交换字节序 */
static inline void exchangeByteOrder(unsigned char *byte)

    unsigned char temp;

    temp = byte[0];
    byte[0] = byte[1];
    byte[1] = temp;


/* 交换一个字的字节顺序 */
static inline void exchangeWordOrder(unsigned int *word)

    unsigned char temp;
    unsigned char *ptr[4];

    ptr[0] = (unsigned char *)(word);
    ptr[1] = ((unsigned char *)(word))+1;
    ptr[2] = ((unsigned char *)(word))+2;
    ptr[3] = ((unsigned char *)(word))+3;

    temp = *ptr[0];
    *ptr[0] = *ptr[3];
    *ptr[3] = temp;

    temp = *ptr[1];
    *ptr[1] = *ptr[2];
    *ptr[2] = temp;


/* 构造 DNS 请求报文包头(12 bytes) */
static int dns_packet_hdr_construct(struct DnsHeader *header)

    /* 定义DNS查询报文请求头,没有使用的字段必须设置为0 */
    header->id = packet_id;     /* ID ,由进程任意指定,以作标识 */
    header->rd = 1;             /*期望递归解析*/
    header->tc = 0;
    header->aa = 0;
    header->opcode = 0;         /*标准查询 */
    header->qr = 0;
    header->rcode = 0;
    header->z = 0;
    header->ra = 0;
    header->qdCount = 1;        /*问题数量 */
    header->anCount = 0;
    header->nsCount = 0;
    header->arCount = 0;

    /* 16 bits 的本地格式转换为网络格式 */
    exchangeByteOrder((unsigned char *)&header->anCount);
    exchangeByteOrder((unsigned char *)&header->qdCount);
    exchangeByteOrder((unsigned char *)&header->nsCount);
    exchangeByteOrder((unsigned char *)&header->arCount);

    return sizeof(struct DnsHeader);


/* 构造 NDS 请求报文内容 */
static int dns_packet_body_construct(char *packet_body, const char *buf)

    const char *domain_ptr = buf ;
    int i = 0, j = 0;

    while(*domain_ptr != '\\0') 
        if(*domain_ptr != '.') 
            packet_body[i+1] = *domain_ptr;
            j++;
         else 
            packet_body[i-j] = j;
            j = 0;
        
        i++; domain_ptr++;
    
    packet_body[i-j] = j;

    packet_body[++i] = 0;           /* null */
    packet_body[++i] = 0x00;
    packet_body[++i] = 0x01;        /* 查询类型  为 1 : IPv4地址 */

    packet_body[++i] = 0x00;        /* 查询类 为 1 : Inetnet数据 */
    packet_body[++i] = 0x01;

    return i;


/**
 * 解析 问题
 * packet:数据包头部指针
 * body:  指向问题域开始部分
 */
static const char *dns_packet_question_resovle(const struct DnsHeader *packet, const char *body)

    const char *ptr;
    unsigned char cnt = 0;
    unsigned char len = 0;

    (void)len;
    DNS_PRINTF("the queston: \\n");
    DNS_PRINTF("Name : ");

    ptr = body;
    while(*ptr != '\\0') 
        cnt = *ptr++;
        while(cnt--) 
            DNS_ORG_PRINTF("%c", *ptr);
            ptr++;
        
        if(*ptr != '\\0') 
            DNS_ORG_PRINTF(".");
        
    
    DNS_ORG_PRINTF("\\n");

    ptr++;  /* skip null */
    DNS_PRINTF("Type : ");
    if(*ptr == 0x0 && *(ptr+1) == 0x1) 
        DNS_ORG_PRINTF("Host Address\\n");
    else
        DNS_ORG_PRINTF("%02x%02x\\n",*ptr, *(ptr+1));
    

    ptr += 2; /* skip Type area */
    DNS_PRINTF("Class : ");
    if(*ptr == 0x0 && *(ptr+1) == 0x01) 
        DNS_ORG_PRINTF("IN\\n");
     else 
        DNS_ORG_PRINTF("%02x%02x\\n", *ptr, *(ptr+1));
    

    ptr += 2;  /* skip Class area */

    return ptr;


/* 解析名字 */
static const char *dns_packet_name_resovle(const struct DnsHeader *packet, const  char *name)

    const char *p;
    unsigned char cnt,len;

    p = name;
    len = 0;

    (void)len;
    while(*p != '\\0') 
        if(((*p & 0xC0) >> 6) == 0x3) 
            p = (const char *)packet + (((*p & 0x3F)<<8) | *(p+1)); 
            p = dns_packet_name_resovle(packet, p);
         else 
            cnt = *p++;
            while(cnt--) 
                DNS_ORG_PRINTF("%c",*p);
                p++;
            

            if(*p != '\\0') 
                DNS_ORG_PRINTF(".");
            
        
    

    return  p;


/* 解析资源数据 */
static const char *dns_packet_rr_resovle(const struct DnsHeader *packet, const  char *rr, 
                                    unsigned short rlen, char *ipout, int *iplen)

    const char *p;
    unsigned char cnt = 0;

    if (*iplen)  /* *clear ip length */
        *iplen = -1;
    

    if(rlen == 0x04)         /* IP addr */
        DNS_ORG_PRINTF("%d.%d.%d.%d\\n", (unsigned char)rr[0],(unsigned char)rr[1],
            (unsigned char)rr[2],(unsigned char)rr[3]);
        if (ipout && iplen) 
            snprintf(ipout, *iplen, "%d.%d.%d.%d", (unsigned char)rr[0],(unsigned char)rr[1],
                                        (unsigned char)rr[2],(unsigned char)rr[3]);
            *iplen = strlen(ipout);
        
        return (rr += 4);
    

    while(rlen > 0) 
        if(((*rr & 0xC0) >> 6) == 0x03) 
            p = (const char *)packet + ((*rr & 0x3F)<<8 | (*(rr+1)));
            rlen -= 2;
            cnt = *p++;
            rr +=  2;
         else 
            p = rr;
            cnt = *p++;
            rlen -= ( cnt + 1 );
            rr += ( cnt + 1 );
        

        while(cnt--) 
            DNS_ORG_PRINTF("%c", *p);
            p++;
        

        if(rlen != 0) 
            DNS_ORG_PRINTF(".");
        
    

    DNS_ORG_PRINTF("\\n");

    return rr;


/**
 * 解析 应答
 * packet:数据包头部指针
 * body:  指向应答域开始部分
 */
static const char *dns_packet_response_resovle(const struct DnsHeader *packet, const char *body, 
                                                    char *ipout, int *iplen)

    const char *ptr;
    unsigned char cnt = 0;
    unsigned char len = 0;
    unsigned int  ttl = 0;
    unsigned short rs_len = 0;

    (void)cnt;
    (void)len;

    DNS_PRINTF("the answer:\\n");
    DNS_PRINTF("Name : ");
    
    ptr = dns_packet_name_resovle(packet, body);

    DNS_ORG_PRINTF("%s\\n", ptr);

    ptr = body + 2;  /* skip null ,Get Type (2 bytes) */
    DNS_PRINTF("Type : ");
    if((*ptr == 0x00) && (*(ptr+1) == 0x01)) 
        DNS_ORG_PRINTF("Host Address\\n");
     else if ((*ptr == 0x00) && (*(ptr+1) == 0x05)) 
        DNS_ORG_PRINTF("CNAME (Canonical name for an alias)\\n");
     else 
        DNS_ORG_PRINTF("%02x%02x\\n",*ptr, *(ptr+1));
    

    ptr += 2; /* skip Type area , Get Class (2 bytes) */
    DNS_PRINTF("Class : ");
    if(*ptr == 0x00 && *(ptr+1) == 0x01) 
        DNS_ORG_PRINTF("IN\\n");
     MicroPython ESP32 入网和udp数据接收通讯示例

如何编程使上位机(界面c#)与下位机(单片机keil c)通过TCP/UDP协议来实现通信,最好有源代码,谢谢~~

UDP之socket通讯

C#里使用UdpClient和线程来创建UDP网络通讯

C#里使用UdpClient和线程来创建UDP网络通讯

im即时通讯开发:快速理解TCP和UDP的差异