相关信息
ESP-IDF版本:V4.1
DHCP服务器相关文件路径:
必要结构:
- dhcps_msg:DHCP报文数据结构;
- list_node:每个Client申请的IP租赁信息链表节点,pnode指向实际的IP租赁信息结构、pnext指向下一个节点;
- dhcps_pool:实际保存的IP租赁信息结构,包括IP地址、MAC地址、以及lease_timer剩余租赁时间;
- dhcps_lease_t:保存DHCP可分配的IP地址范围,包括enable使能、start_ip起始地址、以及end_ip结束地址;
必要变量:
存储模式:
通常情况下,CPU在内存中存储模式为小端模式,而网络数据传输中采用大端模式,所以数据在传输之前,需要将内存中储存的数据由小端模式转换为大端模式,才能进行传输。
*请注意:在后面的代码分析中,小端模式被称作主机模式、大端模式被称作网络模式。
DHCP服务器启动
系统调用void dhcps_start(struct netif *netif, ip4_addr_t ip)函数启动DHCP服务,netif表示使用的网卡设备,ip表示DHCP服务器IP,网络模式存储。该函数主要完成以下几个功能:
- 为DHCP服务器创建一个UDP对象,用于网络通信;
- 调用 dhcps_poll_set函数初始化IP租赁地址池,设置起始和结束IP地址;
- 初始化可租赁IP地址计数,client_address_plus.addr 设置为IP租赁地址池的起始地址;
- 调用lwip库函数udp_bind将DHCP服务器的UDP对象与DHCP服务器IP以及端口绑定;
- 调用lwip库函数udp_recv等待客户端的数据,数据处理由handle_dhcp函数实现;
IP租赁地址池初始化
在DHCP服务器启动过程中,调用static void dhcps_poll_set(u32_t ip)函数对IP租赁地址池初始化,其主要初始化部分代码如下:
/*获取地址,ip为网络模式,local_ip和softap_ip为主机模式*/
local_ip = softap_ip = htonl(ip);
/*获取subnet信息,oftap_ip为主机模式*/
softap_ip &= 0xFFFFFF00;
/*获取内网编号,local_ip为主机模式*/
local_ip &= 0xFF;
/*下面的范围调整为何设置为128,目前不太清楚用意,有可能为自定义*/
/*若内网编号大于128,local_ip为主机模式*/
if (local_ip >= 0x80)
{
/*调整内网编号,DHCPS_MAX_LEASE=0x64*/
local_ip -= DHCPS_MAX_LEASE;
}
else
{
/*不大于128则+1用于起始地址*/
local_ip ++;
}
/*初始化地址池结构*/
bzero(&dhcps_poll, sizeof(dhcps_poll));
/*设置起始地址为 域信息|新的域编号,dhcps_poll.start_ip.addr 为主机模式*/
dhcps_poll.start_ip.addr = softap_ip | local_ip;
/*设置结束地址为 与信息|新的域编号-最大分配数量-1,dhcps_poll.end_ip为主机模式*/
dhcps_poll.end_ip.addr = softap_ip | (local_ip + DHCPS_MAX_LEASE - 1);
/*大小端转换*/
/*dhcps_poll.start_ip为网络模式*/
dhcps_poll.start_ip.addr = htonl(dhcps_poll.start_ip.addr);
/*dhcps_poll.end_ip为网络模式*/
dhcps_poll.end_ip.addr = htonl(dhcps_poll.end_ip.addr);
DHCP服务器数据接收与处理
DHCP服务器启动后,通过UDP在端口67等待Client的请求报文,当lwip收到报文后,会交给static void handle_dhcp(void *arg,struct udp_pcb *pcb,struct pbuf *p,const ip_addr_t *addr,u16_t port)函数进行报文处理。该函数主要完成以下几个功能:
- 定义一个DHCP报文帧格式的对象,用于保存接收的报文struct dhcps_msg *pmsg_dhcps = NULL;
- 将数据从pbuf结构(lwip数据结构,详情参建lwip文档)拷贝到 pmsg_dhcps,并获取报文长度保存到tlen中;
- 调用parse_msg(pmsg_dhcps, tlen – 240)对报文进行解析,240见注解1。tlen-240表示options内数据的长度;
- 根据解析结果,若为OFFER状态则发送OFFER报文、若为ACK状态则发送ACK报文、NAK状态则发送NAK报文;
- 释放相关内存;
*注解1:240字节长度=
op(1)+htype(1)+hlen(1)+hops(1)+xid(4)+secs(2)+flags(2)+ciaddr(4)+yiaddr(4)+siaddr(4)+giaddr(4)+chaddr(16)+sname(64)+file(128)+magic_cookie(4);
报文解析
DHCP报文解析过程由static s16_t parse_msg(struct dhcps_msg *m, u16_t len)实现。首先,系统检查options中包含的magic cookie字段是否为0x63538263,如果不满足,则丢弃这个报文。在协议规定中,如果DHCP报文中存在options,那么必须在options开头写入magic cookie标志。随后系统在dhcps_poll对象中选择一个可用的IP地址,dhcps_poll规定了Client可分配的IP地址范围。系统通过两个变量获取当前可用的IP,分别是first_address和client_address_plus,第一个变量保存最小的可用IP地址,第二个变量保存下一个可分配的IP地址。具体信息由下面的代码进行分析:
ip4_addr_t addr_tmp;
/*定义一个地址池对象*/
struct dhcps_pool *pdhcps_pool = NULL;
/*定义一个链表节点*/
list_node *pnode = NULL;
list_node *pback_node = NULL;
/*定义第一个可用ip地址*/
ip4_addr_t first_address;
bool flag = false;
/*获取ip地址池中的第一个ip地址,dhcps_poll.start_ip,first_address为网络模式*/
first_address.addr = dhcps_poll.start_ip.addr;
/*获取客户端当前可用的地址,client_address_plus以及client_address为网络模式*/
client_address.addr = client_address_plus.addr;
/*IP租赁更新标志*/
renew = false;
if (plist != NULL)
{
for (pback_node = plist; pback_node != NULL; pback_node = pback_node->pnext)
{
pdhcps_pool = pback_node->pnode;
/*检查当前Client的mac地址是否以前申请过IP租赁信息*/
if (memcmp(pdhcps_pool->mac, m->chaddr