dhcp协议_教你动手写UDP协议栈-DHCP数据包解析<2>

f0fa0bb127fd83be09754bce1048c686.png

背景

  • 在上一篇文章中讲到UDP的基本内容,UDP的三层封包协议和UDP的软件开发。在上一篇文章中获取从机IP地址的方法是很简单粗暴的,说实在的是一个错误的做法。虽然也是截取DHCP数据包,但是方法不对,所以今天我们来描述一下如何通过正确的方式获取IP地址。
  • DHCP(动态主机配置协议),它是一种局域网的网络协议,使用的还是UDP数据包,DHCP采用C/S模式,分服务端采用67端口号和客户端采用78端口号。DHCP客户端向DHCP服务端发送报文的过程称为请求报文,DHCP服务端向DHCP客户端发送报文称为应答报文。DHCP可以为客户机自动分配IP地址,子网掩码,缺省网关,DNS服务器的IP地址等,并能够提升地址的使用率。

UDP理论讲解

DHCP报文种类

DHCP报文属于UDP报文,DHCP协议包含在UDP协议栈的用户数据部分。如下图红框部分:

9023639efad69c39c02e93fd58df6da1.png

DHCP报文的种类有八种:分别为DHCP Discover、DHCP Offer、DHCP Request、DHCP ACK、DHCP NAK、DHCP Release、DHCP Decline、DHCP Inform。各种类型报文的基本功能如下(表格内容参考网上资料):

ble data- draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">DHCP报文类型说明

DHCP报文格式

DHCP的8种报文格式是一样的,它是通过报文中的字段的取值不同,来划分类型和信息。如下图为DHCP报文格式:

0134e861ad46ba5b7ce62723d396c243.pngble
da ta-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">字段长度说明

DHCP报文中部分可选字段的说明

ble da ta-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">代码长度说明

如何获取IP地址

在每次的连接中,客户端都会主动发送DHCP请求,从而获取IP地址等信息。如下图

eb939151c40fc60a4e8859837679613d.png

在上述的描述中,我们DHCP包的各个字段进行了描述。但是我们真正用到的就几个字段。

  • DHCP报文的Chaddr字段:客户端MAC地址,区分每一个客户端的最好方式就是MAC地址,因为它是唯一标志客户端设备。所以我们可以通过DHCP报文中的CHaddr字段来捕获属于本客户端的DHCP报文。
  • DHCP报文的可选字段(代码:53):我们通过此字段来捕获DHCP ACK报文。
  • DHCP报文的Yiaddr字段:当上面两个字段条件满足,我们就可以通过此字段获取客户端的IP地址。

70bca05c61d1defc0563e1d5fd2bfdad.png

下面我们来说说代码实现:

DHCP报文结构体定义

注意:在下面的结构体中多了一个cookie字段,其实它是包含在可选字段的前四个字节。为了方便写代码,所以直接在独立出来。

struct mini_udp_dhcp_msg {
 rt_uint8_t op;
 rt_uint8_t htype;
 rt_uint8_t hlen;
 rt_uint8_t hops;
 rt_uint8_t xid[4]; 
 rt_uint16_t secs;
 rt_uint16_t flags;
 rt_uint8_t ciaddr[4];
 rt_uint8_t yiaddr[4];
 rt_uint8_t siaddr[4];
 rt_uint8_t giaddr[4];
 rt_uint8_t chaddr[16];
 rt_uint8_t sname[64];
 rt_uint8_t file[128];
    rt_uint8_t cookie[4];
 rt_uint8_t options[312];
};

DHCP报文捕获代码修改

在原来的代码中进行修改,增加DHCP报文解析。

66b1bd5da187f2e70367a4d8f8099ac1.png
int mini_udp_input(const void *packet, uint32_t packet_len)
{
    struct mini_mac_header *mac_hdr = NULL;
    struct mini_ip_header *ip_hdr = NULL;
    struct mini_udp_header *udp_hdr = NULL;

    mac_hdr = (struct mini_mac_header *)(packet);
    if(mac_hdr->type != htons(ETHTYPE_IP))  //判断类型
    {
        return -1;
    }

    ip_hdr = (struct mini_ip_header *)((uint8_t *)mac_hdr + MAC_HDR_SIZE);
    if(IPH_V_GET(ip_hdr) != 4)  //判断版本是否为IPV4
    {
        return -1;
    }        

    if(IPPROTO_UDP != IPH_PROTO_GET(ip_hdr))    //判断是否为数据报
    {
        return -1;
    }

    udp_hdr = (struct mini_udp_header *)((uint8_t *)ip_hdr + IP_HDR_SIZE);
    
    switch(ntohs(udp_hdr->src_port))
    {
        case DHCP_SERVER_PORT:  //读取DHCP包,获取本地IP
        {     
            // 获取DHCP报文
            struct mini_udp_dhcp_msg *dhcp_msg = (struct mini_udp_dhcp_msg *)((rt_uint8_t *)udp_hdr + UDP_HDR_SIZE);
            // 判断是否为本客户端的MAC地址
            if(mac_cmp(dhcp_msg->chaddr))
            {
                rt_uint32_t option_length = packet_len - member_offset(struct mini_udp_dhcp_msg, options);
                rt_uint8_t *option_start  = dhcp_msg->options;
                rt_uint8_t *option_end    = option_start + option_length;
                // 在可选字段中查找(代码53)
                while (option_start < option_end) { 
                    if((uint8_t)*option_start == DHCP_OPTION_CODE_MSG_TYPE)
                    {   
                        // 判断是否为DHCP ACK报文
                        if(*(option_start + 2) == DHCP_MESSAGE_TYPE_ACK)
                        {
                            rt_memcpy(&udp_info.local_ip, &dhcp_msg->yiaddr, sizeof(struct ip_addr));        //save local ip
                            LOG_I("local ip: %d:%d:%d:%d", udp_info.local_ip.addr[0], udp_info.local_ip.addr[1], 
                                                            udp_info.local_ip.addr[2], udp_info.local_ip.addr[3]);
                            break;
                        }
                    }
                    option_start += option_start[1] + 2;
                }
            }
            break;
        }
        case NTP_SERVER_PORT:   //接收指定端口号的广播包,并dump出来。
        {
            hex_dump(packet, packet_len);
            mini_udp_output(mac_hdr, ip_hdr, "Rice is best", sizeof("Rice is best"));   //接收成功,返回数据"Rice is best"
            break;
        }
        default:
        {
            return -1;
        }
    }
    return 0; 
}

验证报文捕获结果

通过查看Wireshark抓包工具和客户端捕获的IP地址是一致的。

dda8ad2c0b72168d1f67840728608951.png


本文首发于微信公众号『Rice嵌入式开发技术分享』,欢迎GZ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值