Rice我叫加饭? · 2020年10月28日

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

首发: Rice 嵌入式开发技术分享
作者:RiceDIY

背景

  • 上一篇文章中讲到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协议栈的用户数据部分。如下图红框部分:

image.png

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

WechatIMG184.jpeg

DHCP报文格式

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

image.png

WechatIMG190.jpeg

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

WechatIMG191.jpeg

如何获取IP地址

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

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

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

image.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报文解析。

image.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地址是一致的。!image.png



UDP协议栈系列篇阅读

更多嵌入式技术干货请关注Rice 嵌入式开发技术分享
推荐阅读
关注数
1755
内容数
51
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息