babyos2(41) network(7) -- udp, dns resolve

前面为babyos2实现了简单的raw socket及从用户态ping指定IP地址。而通常意义的ping不但可以ping某个具体的地址,还可以ping baidu.com这种域名。而从内核角度看,要发送IP包,必须知道目的IP地址。所以需要经过域名转换,将给定的域名转换成IP地址,这就是常说的DNS(Domain Name System)的主要功能。反映到用户态就算常用的gethostbyname()。

这里准备为babyos2实现简单的域名解析,而dns请求可以用UDP也可以用TCP,而通常可以用UDP自己控制超时重传等,所以在实现DNS之前,需要实现简单的UDP发送功能。

因为babyos2还不支持SOCK_DGRAM,所以暂时只是从内核发送DNS请求,接收并解析DNS回复,还不具备从用户态通过socket发送请求的能力,这是下一步要做的。

1.UDP 和 UDP首部

UDP是一个简单的面向数据报的传输层协议:进程的每个输出操作正好产生一个UDP数据报,并组装成一份待发送的IP数据报。UDP封装成一份IP数据报格式如下图:


其中UDP首部格式如下:


端口号表示发送进程和接收进程,用于当内核收到一份UDP数据报时交给哪个进程处理。UDP和TCP都使用端口号,但是相互独立的,但是如果TCP和UDP同时提供某种知名服务,两个协议通常选择相同的端口号,这是为了方便,而不是协议本身要求。

class udp_hdr_t {
public:
    void init(uint16 src, uint16 dest, uint16 len, uint16 checksum);
    
    uint16 m_src_port;            /* source port number */
    uint16 m_dst_port;            /* destination port number */
    uint16 m_length;              /* length */
    uint16 m_check_sum;           /* checksum */
};


2.UDP checksum

跟IP校验和不同,UDP校验覆盖UDP首部和UDP数据,UDP校验和计算的时候需要添加一个伪首部:


如果发送端没有计算校验和而接收端检测到校验和有差错,那么UDP数据报就要被悄悄地丢弃,不产生任何差错报文。

class udp_pseudo_hdr_t {
public:
    void init(uint32 src_ip, uint32 dst_ip, uint8 protocol, uint16 udp_len);

    uint32 m_src_ip;
    uint32 m_dst_ip;
    uint8  m_zero;
    uint8  m_protocol;
    uint16 m_udp_len;
};

int32 udp_t::transmit(uint32 ip, uint16 port, uint8* data, uint32 len)
{
    uint16 total = len + sizeof(udp_hdr_t);

    udp_pseudo_hdr_t pseudo_hdr;
    pseudo_hdr.init(net_t::htonl(os()->get_net()->get_ipaddr()),
                    net_t::htonl(ip),
                    ip_t::PROTO_UDP,
                    net_t::htons(total));

    udp_hdr_t hdr;
    hdr.init(net_t::htons(50105), 
             net_t::htons(port), 
             net_t::htons(total), 
             0);

    net_buf_t* buffer = os()->get_net()->alloc_net_buffer(total + sizeof(pseudo_hdr));
    if (buffer == NULL) {
        return -1;
    }

    buffer->append(&pseudo_hdr, sizeof(udp_pseudo_hdr_t));
    buffer->append(&hdr, sizeof(udp_hdr_t));
    buffer->append(data, len);

    udp_hdr_t *header = (udp_hdr_t *) buffer->get_data();
    header->m_check_sum = net_t::check_sum(buffer->get_data(), buffer->get_data_len());

    buffer->pop_front(sizeof(udp_pseudo_hdr_t));
    os()->get_net()->get_ip()->transmit(ip, buffer->get_data(), buffer->get_data_len(), ip_t::PROTO_UDP);

    return 0;
}

3.DNS和DNS报文格式

DNS指域名系统,是一种用于TCP/IP应用程序的分布式数据库,它提供主机名字和IP地址之间的转换及有关电子邮件的选路信息。从应用角度来看,对DNS的访问是通过一个地址解析器(resolver)来完成的。Unix系统中,主要通过gethostbyname和gethostbyaddr来完成。解析器通常是应用程序的一部分,并不像TCP/IP协议那样是OS内核的一部分。因为babyos2还不支持SOCK_RAW所以暂时从内核发送并解析DNS数据。

DNS报文格式如下:


标识用来确定响应跟查询是否匹配,babyos2暂时不考虑它。

标记反映了查询码,返回码等许多标记,具体可查阅相关书籍,不详细描述,babyos2暂时不考虑过多标记。

问题数指query的数量,资源记录数指回答数,babyos2只考虑这两个量,暂不考虑授权记录和额外信息。

查询问题格式如下:

class dns_hdr_flag_t {
public:
    uint16  m_flags_rcode :  4;     /* response code */
    uint16  m_flags_z :      3;     /* zero, not used */
    uint16  m_flags_ra :     1;     /* recursion available */
    uint16  m_flags_rd :     1;     /* recursion desired */
    uint16  m_flags_tc :     1;     /* truncated */
    uint16  m_flags_aa :     1;     /* authoritative answer */
    uint16  m_flags_opcode : 4;     /* 0: standard query, 4: notify, 5: update, other not used */
    uint16  m_flags_qr :     1;     /* 0: query, 1: reply */
};

class dns_hdr_t {
public:
    uint16  m_transaction_id;
    union {
        dns_hdr_flag_t  m_flags;
        uint16          m_flags_val;
    };
    uint16  m_qd_count;
    uint16  m_an_count;
    uint16  m_ns_count;
    uint16  m_ar_count;
};

4.DNS 问题区段格式


查询名是可变长度的一个区段,后面再描述。

查询类型:


如果要从host -> ip一般填 A(1).

查询类一般填1,指互联网地址。

5.DNS 名称和标签,DNS query


DNS名称标签采用上面的方式,先给出长度,后面跟给定长度个数字符,最后以0结尾表示结束。长度最大值为63,最高两个比特用途下面描述。DNS query代码如下:

int32 dns_t::parse_name(const char* name, net_buf_t* buffer)
{
    char tmp[256] = {0};
    const char* p = name;

    while (*p != '\0') {
        const char* begin = p;
        while (*p != '\0' && *p != '.') {
            p++;
        }
        if (p - begin > MAX_LABEL_LEN) {
            return -1;
        }
        
        uint8 count = p - begin;
        buffer->append(&count, 1);
        buffer->append(begin, count);

        strncpy(tmp, begin, count);
        if (*p == '\0') {
            count = 0;
            buffer->append(&count, 1);
            break;
        }
        p++;
    }

    return 0;
}

int32 dns_t::query(const char* name)
{
    console()->kprintf(GREEN, "dns query: %s\n", name);

    dns_hdr_t hdr;
    hdr.m_transaction_id         = net_t::htons(0x7844); 
    hdr.m_flags.m_flags_qr       = 0;
    hdr.m_flags.m_flags_opcode   = 0;
    hdr.m_flags.m_flags_aa       = 0;
    hdr.m_flags.m_flags_tc       = 0;
    hdr.m_flags.m_flags_rd       = 1;
    hdr.m_flags.m_flags_ra       = 0;
    hdr.m_flags.m_flags_z        = 0;
    hdr.m_flags.m_flags_rcode    = 0;
    hdr.m_flags_val              = net_t::htons(hdr.m_flags_val);
    hdr.m_qd_count               = net_t::htons(1);
    hdr.m_an_count               = net_t::htons(0);
    hdr.m_ns_count               = net_t::htons(0);
    hdr.m_ar_count               = net_t::htons(0);

    net_buf_t* buffer = os()->get_net()->alloc_net_buffer(512);
    if (buffer == NULL) {
        return -1;
    }

    buffer->append(&hdr, sizeof(dns_hdr_t));
    parse_name(name, buffer);


    uint16 query_type = net_t::htons(1);            /* host address */
    uint16 query_class = net_t::htons(1);
    buffer->append(&query_type, sizeof(query_type));
    buffer->append(&query_class,sizeof(query_class));

    uint32 dns_ip = os()->get_net()->get_dns_addr();
    os()->get_net()->get_udp()->transmit(dns_ip, 53, buffer->get_data(), buffer->get_data_len());

    return 0;
}

6.DNS 压缩标签

当长度高两个比特为1时(大于63),表示这是一个压缩标签。该字节后面的6个比特加上下一个字节的8个比特共14个比特表示一个偏移量,表示后面的数据需要到该DNS数据中这个偏移地址去找。


如0xc00c二进制为1100 0000 0000 1100高两位为1,表示为一个压缩标签,去掉高两个比特剩下14个比特表示偏移量12,表示从dns数据偏移量为12处开始解析该标签。

DNS名称解析代码:

uint32 dns_t::resolve_name(const uint8* dns_data, const uint8* data, char* name)
{
    const uint8* start = data;
    uint8 len = (uint8) *data++;
    uint32 total = 0;
    while (len != 0) {
        if (len <= MAX_LABEL_LEN) {
            memcpy(name, data, len);
            name += len;
            data += len;
        }
        else {
            uint16 offset = ((len & 0x3f) << 8 | *data++);
            const uint8* p = dns_data + offset;
            uint32 count = resolve_name(dns_data, p, name);
            name += strlen(name);
            break;
        }

        len = *data++;
        if (len != 0) {
            *name++ = '.';
        }
    }

    *name++ = '\0';
    return data - start;
}

7.DNS回答区段格式



8.DNS resolve

当answer的type为5(CNAME)时,表示将一个名称映射到另一个。为1(A)时表示是一个IP地址。DNS resolve代码如下:

int32 dns_t::resolve(net_buf_t* buffer)
{
    dns_hdr_t* hdr = (dns_hdr_t *) buffer->get_data();

    uint16 query_count = net_t::ntohs(hdr->m_qd_count);
    uint16 answer_count = net_t::ntohs(hdr->m_an_count);
    console()->kprintf(CYAN, "ID: 0x%x, flags: 0x%x, questions num: %u, answer num: %u\n", 
            net_t::ntohs(hdr->m_transaction_id), net_t::ntohs(hdr->m_flags_val), 
            query_count, answer_count);

    uint8* dns_data = buffer->get_data();
    uint8* p = dns_data + sizeof(dns_hdr_t);
    char name[MAX_DNS_PACKET_SIZE] = {0};

    console()->kprintf(GREEN, "queries:\n");
    for (int i = 0; i < query_count; i++) {
        memset(name, 0, MAX_DNS_PACKET_SIZE);
        p += resolve_name(dns_data, p, name);
        uint16* query_type = (uint16 *) p;
        uint16* query_class = (uint16 *) (query_type + 1);
        console()->kprintf(CYAN, "%s, type 0x%4x, class 0x%4x\n", name, 
                net_t::ntohs(*query_type), net_t::ntohs(*query_class));
        p = (uint8 *) (query_class + 1);
    }

    console()->kprintf(PINK, "answers:\n");
    for (int i = 0; i < answer_count; i++) {
        memset(name, 0, MAX_DNS_PACKET_SIZE);
        p += resolve_name(dns_data, p, name);

        uint16* ans_type = (uint16 *) p;
        uint16* ans_class = (uint16 *) (ans_type + 1);
        uint32* ttl = (uint32 *) (ans_class + 1);
        uint16* data_len = (uint16 *) (ttl + 1);
        console()->kprintf(YELLOW, "%s, type 0x%4x, class 0x%4x, 0x%8x, 0x%4x -> ",  name, 
                net_t::ntohs(*ans_type), net_t::ntohs(*ans_class), 
                net_t::ntohl(*ttl), net_t::ntohs(*data_len));

        p = (uint8 *) (data_len + 1);
        if (net_t::ntohs(*ans_type) == RR_TYPE_A) {
            uint32* ip = (uint32 *) p;
            net_t::dump_ip_addr(net_t::ntohl(*ip));
        }
        else if (net_t::ntohs(*ans_type) == RR_TYPE_CNAME) {
        memset(name, 0, MAX_DNS_PACKET_SIZE);
            resolve_name(dns_data, p, name);
            console()->kprintf(PINK, "%s", name);
        }
        p += net_t::ntohs(*data_len);
        console()->kprintf(YELLOW, "\n");
    }

    return 0;
}

9.测试结果



上图为从内核构造并发送dns请求,请求的域名为www.baidu.com

从结果看,

www.baidu.com -> www.ashifen.com 是一个名称映射

www.ashifen.com -> 115.239.210.27

www.ashifen.com -> 115.239.211.112

得到两个IP,一个备用的?得再查查书。。


后面的计划:

1)支持SOCK_DGRAM

2)从用户态发送并解析DNS

3)gethostbyname

4)完善UDP

5)UDP echo


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guzhou_diaoke/article/details/79948073
个人分类: babyos2
所属专栏: babyos2
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭