lwip源码分析 之 DHCP协议实现
一,dhcp协议简介
二,源码解析
2.1 dhcp结构体
2.2 开始广播
2.3 回调接口
a,发送请求
b,等待ack
2.4 其他情况
一,dhcp协议简介
dhcp协议是动态主机配置协议,是互联网的基本协议。lwip内核也实现了该协议。关于lwip协议的具体内容,请先转到:https://www.cnblogs.com/dhcpclass/p/17123195.html
lwip实现dhcp的代码在core/dhcp.c中。
二,源码解析
应用程序要使用dhcp协议,只需要在代码中调用:
err_t dhcp_start(struct netif *netif);
并在定时调用以下两个定时器,就能获得ip地址给网口。
/** 每一分钟调用一次 */
void dhcp_coarse_tmr(void);
/** 500ms调用一次 */
void dhcp_fine_tmr(void);
首先认识描述dhcp客户端的结构体:
2.1 dhcp结构体
struct dhcp
{
u32_t xid; //事务ID,随机数,有客户端生成,服务器Reply时,会把Request中的Transaction拷贝到Reply报文中。
struct udp_pcb *pcb; //连接dhcp服务器的udp控制块
struct dhcp_msg *msg_in; //dhcp输入报文
u8_t state; //当前dhcp状态机
u8_t tries; //重请求次数
u8_t subnet_mask_given; //是否有子网掩码
struct pbuf *p_out; /* 输出的报文的pbuf pbuf of outcoming msg */
struct dhcp_msg *msg_out; /* 输出的报文 outgoing msg */
u16_t options_out_len; /* 输出的报文的选项长度 outgoing msg options length */
//请求超时时间
u16_t request_timeout; /* #ticks with period DHCP_FINE_TIMER_SECS for request timeout */
u16_t t1_timeout; /* #ticks with period DHCP_COARSE_TIMER_SECS for renewal time */
u16_t t2_timeout; /* #ticks with period DHCP_COARSE_TIMER_SECS for rebind time */
ip_addr_t server_ip_addr; //dhcp服务器地址
ip_addr_t offered_ip_addr; //服务器提供的ip
ip_addr_t offered_sn_mask; //服务器提供的子网掩码
ip_addr_t offered_gw_addr; //服务器提供的网关地址
//dhcp报文选项字段中的时间
u32_t offered_t0_lease; /* 租借地址的时间 lease period (in seconds) */
u32_t offered_t1_renew; /* 续租的时间 recommended renew time (usually 50% of lease period) */
u32_t offered_t2_rebind; /* 租约重新设定的时间 recommended rebind time (usually 66% of lease period) */
/* @todo: LWIP_DHCP_BOOTP_FILE configuration option?
integrate with possible TFTP-client for booting? */
};
dhcp报文实质上是udp报文,故结构体有udp控制块。其中需要关注与时间有关的变量:
u16_t request_timeout; //请求超时时间
u16_t t1_timeout; //续租超时时间
u16_t t2_timeout; //重绑定超时时间
这三个变量会在每次定时函数被执行时减1,当他们其中一个为0时,表示超时时间到,会执行相应的代码来使dhcp客户端保持正常运行。另外三个时间变量也与此相关:
u32_t offered_t0_lease; /* 租借地址的时间 lease period (in seconds) */
u32_t offered_t1_renew; /* 续租的时间 recommended renew time (usually 50% of lease period) */
u32_t offered_t2_rebind;/* 租约重新设定的时间 recommended rebind time (usually 66% of lease period) */
这三个变量是由dhcp服务器返回的报文里的选项参数决定的,每一个dhcp服务器报文到来,这三个变量就会对timeout进行更新。
最后还要注意dhcp客户端状态的转换,dhcp客户端的状态由10种,但我们只需要知道主要的4种:
#define DHCP_OFF 0
#define DHCP_REQUESTING 1
#define DHCP_INIT 2
#define DHCP_REBOOTING 3
#define DHCP_REBINDING 4
#define DHCP_RENEWING 5
#define DHCP_SELECTING 6
#define DHCP_INFORMING 7
#define DHCP_CHECKING 8
#define DHCP_PERMANENT 9
#define DHCP_BOUND 10
与dhcp建立过程对应的状态如图:
设备刚刚启动dhcp客户端时:
续约时间到,dhcp客户端续约当前的ip。
2.2 开始广播
有了以上的认识,对代码逻辑的理解就会更简单。应用代码中,只需要调用dhcp_start()就能开启dhcp功能,那么这个函数它干嘛了?
err_t
dhcp_start(struct netif *netif)
{
struct dhcp *dhcp;
err_t result = ERR_OK;
LWIP_ERROR("netif != NULL", (netif != NULL), return ERR_ARG;);
dhcp = netif->dhcp;
netif->flags &= ~NETIF_FLAG_DHCP; //清除dhcp成功标志
//检查网络接口是否是以太网
if ((netif->flags & NETIF_FLAG_ETHARP) == 0) {
return ERR_ARG;
}
/* check MTU of the netif */
if (netif->mtu < DHCP_MAX_MSG_LEN_MIN_REQUIRED) {
return ERR_MEM;
}
//该网络接口未使用过dhcp客户端
if (dhcp == NULL) {
dhcp = (struct dhcp *)mem_malloc(sizeof(struct dhcp)); //分配一个dhcp内存
if (dhcp == NULL) {
return ERR_MEM;
}
netif->dhcp = dhcp; //保存dhcp客户端到当前网口
} else { //接口已经有一个dhcp客户端
if (dhcp->pcb != NULL) {
udp_remove(dhcp->pcb); //为了重新配置ip,需要先将dhcp客户端的pcb移除
}
LWIP_ASSERT("pbuf p_out wasn't freed", dhcp->p_out == NULL);
LWIP_ASSERT("reply wasn't freed", dhcp->msg_in == NULL );
}
memset(dhcp, 0, sizeof(struct dhcp)); //清零dhcp内存
dhcp->pcb = udp_new(); //创建新的udp控制块
if (dhcp->pcb == NULL) {
return ERR_MEM;
}
dhcp->pcb->so_options |= SOF_BROADCAST; //开启发送和接收的广播
udp_bind(dhcp->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT); //绑定本地ip(全0)和端口 dhcp客户端专属端口68
udp_connect(dhcp->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); //连接到远程dhcp服务器端口67
udp_recv(dhcp->pcb, dhcp_recv, netif); //设置dhcp接收回调函数和参数
result = dhcp_discover(netif); //dhcp客户端开始运行
if (result != ERR_OK) {
dhcp_stop(netif); //错了,停止dhcp
return ERR_MEM;
}
netif->flags |= NETIF_FLAG_DHCP; //置位dhcp成功
return result;
}
**可见这个函数主要是为dhcp结构体分配内存,并建立了一个udp连接,注册好dhcp的回调函数(当收到ducp服务器发送的报文时会进入回调函数),并调用dhcp_discover();函数开始广播dhcp发现报文。**接着看这个函数:
static err_t
dhcp_discover(struct netif *netif)
{
struct dhcp *dhcp = netif->dhcp; //使用当前网口的dhcp客户端
err_t result = ERR_OK;
u16_t msecs;
ip_addr_set_any(&dhcp->offered_ip_addr); //初始化dhcp->offered_ip_addr为0
dhcp_set_state(dhcp, DHCP_SELECTING); //dhcp为状态
result = dhcp_create_msg(netif, dhcp, DHCP_DISCOVER); //创建dhcp发现报文首部
if (result == ERR_OK) {
//填充发现报文的选项字段(较为复杂,参考协议)
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover: making request\n"));
dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif));
dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, 4/*num options*/);
dhcp_option_byte(dhcp, DHCP_OPTION_SUBNET_MASK);
dhcp_option_byte(dhcp, DHCP_OPTION_ROUTER);
dhcp_option_byte(dhcp, DHCP_OPTION_BROADCAST);
dhcp_option_byte(dhcp, DHCP_OPTION_DNS_SERVER);
dhcp_option_trailer(dhcp);
//为p_out减少内存
pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
//将p_out的数据发送,等待dhcp服务器返回报文
udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
dhcp_delete_msg(dhcp); //释放dhcp发现报文的内存
} else {
}
dhcp->tries++; //重发次数+1
msecs = (dhcp->tries < 6 ? 1 << dhcp->tries : 60) * 1000;
dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; //计算请求超时时间
return result;
}
该函数先将dhcp客户端设置为DHCP_SELECTING状态,然后调用dhcp_create_msg()构造dhcp发现报文并填充选项字段,调用udp_sendto_if()将udp报文广播出去,并设置请求超时时间,等待dhcp客户端的回答。
那么dhcp发现报文如何构造,下图所示dhcp报文内容:
dhcp_create_msg():就是按照上图构造dhcp报文的。
static err_t
dhcp_create_msg(struct netif *netif, struct dhcp *dhcp, u8_t message_type)
{
u16_t i;
static u32_t xid = 0xABCD0000; //静态的事务id
//申请dhcp报文的pbuf
dhcp->p_out = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcp_msg), PBUF_RAM);
if (dhcp->p_out == NULL) {
return ERR_MEM;
}
//如果是一个新的dhcp客户端,xid就更新。重绑定的情况下xid不需要更新
if (dhcp->tries == 0) {
xid++;
}
dhcp->xid = xid;
//dhcp_msg指向dhcp报文的缓存地址
dhcp->msg_out = (struct dhcp_msg *)dhcp->p_out->payload;
//对dhcp报文填充内容,也就是填充入p_out->payload;
dhcp->msg_out->op = DHCP_BOOTREQUEST;
dhcp->msg_out->htype = DHCP_HTYPE_ETH;
dhcp->msg_out->hlen = netif->hwaddr_len;
dhcp->msg_out->hops = 0;
dhcp->msg_out->xid = htonl(dhcp->xid);
dhcp->msg_out->secs = 0;
/* we don't need the broadcast flag since we can receive unicast traffic
before being fully configured! */
dhcp->msg_out->flags = 0;
ip_addr_set_zero(&dhcp->msg_out->ciaddr); //设置当前客户端ip为0
//下面根据报文类型和dhcp客户端的不同,有不同的逻辑
if ((message_type == DHCP_INFORM) || (message_type == DHCP_DECLINE) ||
((message_type == DHCP_REQUEST) && /* DHCP_BOUND not used for sending! */
((dhcp->state==DHCP_RENEWING) || dhcp->state==DHCP_REBINDING))) {
//通知,拒绝类型的报文
//重新请求或重新绑定状态下的请求报文
//以上情况的dhcp客户端都已经有ip地址了,所以构造的报文的ciaddr为接口地址
ip_addr_copy(dhcp->msg_out->ciaddr, netif->ip_addr);
}
//下面三个地址清零,这是服务器即将分配的地址
ip_addr_set_zero(&dhcp->msg_out->yiaddr);
ip_addr_set_zero(&dhcp->msg_out->siaddr);
ip_addr_set_zero(&dhcp->msg_out->giaddr);
for (i = 0; i < DHCP_CHADDR_LEN; i++) {
//将网口硬件地址复制到报文硬件地址选项
dhcp->msg_out->chaddr[i] = (i < netif->hwaddr_len) ? netif->hwaddr[i] : 0/* pad byte*/;
}
//清零服务端名字
for (i = 0; i < DHCP_SNAME_LEN; i++) {
dhcp->msg_out->sname[i] = 0;
}
for (i = 0; i < DHCP_FILE_LEN; i++) {
dhcp->msg_out->file[i] = 0;
}
dhcp->msg_out->cookie = PP_HTONL(DHCP_MAGIC_COOKIE);
dhcp->options_out_len = 0; //初始选项长度为0
//用一个随便的数组填充报文的选项字段
for (i = 0; i < DHCP_OPTIONS_LEN; i++) {
dhcp->msg_out->options[i] = (u8_t)i; /* for debugging only, no matter if truncated */
}
//填充报文选项字段中的报文类型
dhcp_option(dhcp, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN);
dhcp_option_byte(dhcp, message_type);
return ERR_OK;
}
2.3 回调接口
广播完成后,只需等待dhcp服务器们发来reply信息。当udp控制块收到dhcp服务端的消息时,就会调用设置的回调函数。
static void
dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
{
struct netif *netif = (struct netif *)arg; //获取网口
struct dhcp *dhcp = netif->dhcp;
struct dhcp_msg *reply_msg = (struct dhcp_msg *)p->payload; //获取返回的数据
u8_t msg_type;
u8_t i;
//检查返回数据长度
if (p->len < DHCP_MIN_REPLY_LEN) {
goto free_pbuf_and_return;
}
//检查报文是否是reply类型
if (reply_msg->op != DHCP_BOOTREPLY) {
goto free_pbuf_and_return;
}
//检查reply报文的客户端硬件地址跟本地网口地址对不对得上
for (i = 0; i < netif->hwaddr_len; i++) {
if (netif->hwaddr[i] != reply_msg->chaddr[i]) {
goto free_pbuf_and_return; //对不上
}
}
//检查事物id与当前dhcp客户端的xid对不对
if (ntohl(reply_msg->xid) != dhcp->xid) {
goto free_pbuf_and_return;
}
//解析reply报文的内容
if (dhcp_parse_reply(dhcp, p) != ERR_OK) {
goto free_pbuf_and_return;
}
//检查选项字段中是否有Message Type
if (!dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE)) {
goto free_pbuf_and_return;
}
//获取报文类型
msg_type = (u8_t)dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE);
if (msg_type == DHCP_ACK) {
if (dhcp->state == DHCP_REQUESTING) {
//dhcp报文是ack类型,且客户端处于请求状态(联系上图)
dhcp_handle_ack(netif);
#if DHCP_DOES_ARP_CHECK
/* check if the acknowledged lease address is already in use */
dhcp_check(netif);
#else
dhcp_bind(netif); //将dhcp申请到的ip绑定到netif
#endif
}
//以下三种状态的dhcp也能进行绑定
else if ((dhcp->state == DHCP_REBOOTING) || (dhcp->state == DHCP_REBINDING) || (dhcp->state == DHCP_RENEWING)) {
dhcp_bind(netif);
}
}
//根据dhcp协议,服务器返回nack报文,客户端需要重新广播,重新获取ip
else if ((msg_type == DHCP_NAK) &&
((dhcp->state == DHCP_REBOOTING) || (dhcp->state == DHCP_REQUESTING) ||
(dhcp->state == DHCP_REBINDING) || (dhcp->state == DHCP_RENEWING ))) {
dhcp_handle_nak(netif); //重新广播获取ip
}
//offer类型的报文且dhcp处于selecting状态(结合上图)
else if ((msg_type == DHCP_OFFER) && (dhcp->state == DHCP_SELECTING)) {
dhcp->request_timeout = 0; //关闭请求超时
dhcp_handle_offer(netif); //保存ip 并进入请求状态
}
free_pbuf_and_return:
dhcp->msg_in = NULL;
pbuf_free(p);
}
回调函数首先解析dhcp报文的内容和选项字段,选项字段的值被保存在全局变量 dhcp_rx_options_val[] 中。然后根据报文类型,dhcp客户端所处的状态,判断当前dhcp连接的状态,进行进一步的处理。如图,一共有三种情况需要进一步处理。
a,发送请求
假设客户端发出发现广播,接收到服务器们的offer报文,接下来客户端应该选择一个服务器,广播请求报文,请求指定服务器的ip。这个功能在dhcp_handle_offer(netif);实现,dhcp_select(netif);则完成了请求报文的构造与发送功能。
//保存服务器的ip和分配的ip
static void
dhcp_handle_offer(struct netif *netif)
{
struct dhcp *dhcp = netif->dhcp;
//服务器ip不为0
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_SERVER_ID)) {
//获取服务器ip
ip4_addr_set_u32(&dhcp->server_ip_addr, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_SERVER_ID)));
//获取服务器分配的ip
ip_addr_copy(dhcp->offered_ip_addr, dhcp->msg_in->yiaddr);
dhcp_select(netif); //广播请求报文
} else {
}
}
static err_t
dhcp_select(struct netif *netif)
{
struct dhcp *dhcp = netif->dhcp;
err_t result;
u16_t msecs;
dhcp_set_state(dhcp, DHCP_REQUESTING); //请求状态
//构造请求报文,填充选项字段,并广播出去。
result = dhcp_create_msg(netif, dhcp, DHCP_REQUEST);
if (result == ERR_OK) {
dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif));
dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
//将offer的ip地址填入“ 请求的IP地址 ”选项
dhcp_option_long(dhcp, ntohl(ip4_addr_get_u32(&dhcp->offered_ip_addr)));
dhcp_option(dhcp, DHCP_OPTION_SERVER_ID, 4);
//将服务器地址填入
dhcp_option_long(dhcp, ntohl(ip4_addr_get_u32(&dhcp->server_ip_addr)));
dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, 4/*num options*/);
dhcp_option_byte(dhcp, DHCP_OPTION_SUBNET_MASK);
dhcp_option_byte(dhcp, DHCP_OPTION_ROUTER);
dhcp_option_byte(dhcp, DHCP_OPTION_BROADCAST);
dhcp_option_byte(dhcp, DHCP_OPTION_DNS_SERVER);
dhcp_option_trailer(dhcp);
pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
//通过网口将请求报文广播,等待服务器的ack
udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
dhcp_delete_msg(dhcp); //删除报文
} else {
}
dhcp->tries++; //重试次数+1
msecs = (dhcp->tries < 6 ? 1 << dhcp->tries : 60) * 1000;
dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS; //计算超时时间
return result;
}
b,等待ack
接收到服务器返回的ack报文后,提取报文中的时间,ip地址信息到dhcp客户端,并由此更新dhcp的超时时间,最后设置与dhcp对应的网口的ip地址,掩码和网关,使能网口。到此,网口就有了一个属于自己的ip地址了。
static void
dhcp_handle_ack(struct netif *netif)
{
struct dhcp *dhcp = netif->dhcp;
//先将本地dhcp结构的两个变量清零
ip_addr_set_zero(&dhcp->offered_sn_mask);
ip_addr_set_zero(&dhcp->offered_gw_addr);
//以下都是根据ack报文的内来给dhcp客户端的变量赋值
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_LEASE_TIME)) {
//更新租约时间
dhcp->offered_t0_lease = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_LEASE_TIME);
}
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_T1)) {
//更新续约时间
dhcp->offered_t1_renew = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_T1);
} else {
//否则,计算续约时间
dhcp->offered_t1_renew = dhcp->offered_t0_lease / 2;
}
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_T2)) {
dhcp->offered_t2_rebind = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_T2);
} else {
dhcp->offered_t2_rebind = dhcp->offered_t0_lease;
}
//复制服务器提供的ip到dhcp客户端
ip_addr_copy(dhcp->offered_ip_addr, dhcp->msg_in->yiaddr);
//获取子网掩码字段
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_SUBNET_MASK)) {
//复制子网掩码到dhcp
ip4_addr_set_u32(&dhcp->offered_sn_mask, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_SUBNET_MASK)));
dhcp->subnet_mask_given = 1;//标记
} else {
dhcp->subnet_mask_given = 0;
}
//获取网关字段
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER)) {
ip4_addr_set_u32(&dhcp->offered_gw_addr, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_ROUTER)));
}
}
static void
dhcp_bind(struct netif *netif)
{
u32_t timeout;
struct dhcp *dhcp;
ip_addr_t sn_mask, gw_addr;
dhcp = netif->dhcp;
//由ack报文提供的租约时间 更新dhcp客户端的超时时间
if (dhcp->offered_t1_renew != 0xffffffffUL) {
timeout = (dhcp->offered_t1_renew + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS;
if(timeout > 0xffff) {
timeout = 0xffff;
}
dhcp->t1_timeout = (u16_t)timeout;
if (dhcp->t1_timeout == 0) {
dhcp->t1_timeout = 1;
}
}
if (dhcp->offered_t2_rebind != 0xffffffffUL) {
timeout = (dhcp->offered_t2_rebind + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS;
if(timeout > 0xffff) {
timeout = 0xffff;
}
dhcp->t2_timeout = (u16_t)timeout;
if (dhcp->t2_timeout == 0) {
dhcp->t2_timeout = 1;
}
}
//获取子网掩码
if (dhcp->subnet_mask_given) {
ip_addr_copy(sn_mask, dhcp->offered_sn_mask);
} else {
//无提供掩码
u8_t first_octet = ip4_addr1(&dhcp->offered_ip_addr);
if (first_octet <= 127) {
ip4_addr_set_u32(&sn_mask, PP_HTONL(0xff000000UL));
} else if (first_octet >= 192) {
ip4_addr_set_u32(&sn_mask, PP_HTONL(0xffffff00UL));
} else {
ip4_addr_set_u32(&sn_mask, PP_HTONL(0xffff0000UL));
}
}
//获取网关地址
ip_addr_copy(gw_addr, dhcp->offered_gw_addr);
if (ip_addr_isany(&gw_addr)) {
//无提供网关,则将网络的第一个主机作为网关
ip_addr_get_network(&gw_addr, &dhcp->offered_ip_addr, &sn_mask);
ip4_addr_set_u32(&gw_addr, ip4_addr_get_u32(&gw_addr) | PP_HTONL(0x00000001UL));
}
//更新网口的ip,子网掩码,网关为dhcp获取到的
netif_set_ipaddr(netif, &dhcp->offered_ip_addr);
netif_set_netmask(netif, &sn_mask);
netif_set_gw(netif, &gw_addr);
netif_set_up(netif); //使能网口
dhcp_set_state(dhcp, DHCP_BOUND); //绑定状态
}
2.4 其他情况
剩下的情况包括租约时间的更新,重新绑定等,这些与时间相关的代码,
三,定时器1
继续上一章,dhcp客户端运行两个定时器,分别周期为1分钟的 void dhcp_coarse_tmr(void); 和 每周期为500ms的 void dhcp_fine_tmr(void);
先看dhcp_fine_tmr()定时任务
//处理dhcp请求超时
void
dhcp_fine_tmr()
{
struct netif *netif = netif_list;
//遍历所有网口
while (netif != NULL) {
if (netif->dhcp != NULL) {
//request_timeout -1,表示过去500ms
if (netif->dhcp->request_timeout > 1) {
netif->dhcp->request_timeout--;
}
else if (netif->dhcp->request_timeout == 1) { //请求时间超时
netif->dhcp->request_timeout--; //此时timeout=0;
dhcp_timeout(netif); //执行超时处理
}
}
netif = netif->next; //检查下一个网口
}
}
当dhcp广播请求报文后就进入requesting状态,并设置request_timeout时间。在dhcp_fine_tmr()中,判断是否超时。dhcp_timeout()是超时处理函数:
static void
dhcp_timeout(struct netif *netif)
{
struct dhcp *dhcp = netif->dhcp;
//DHCP_BACKING_OFF和DHCP_SELECTING下超时是因为服务器们无响应,可能是广播的发现报文失踪了
if ((dhcp->state == DHCP_BACKING_OFF) || (dhcp->state == DHCP_SELECTING)) {
LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_timeout(): restarting discovery\n"));
dhcp_discover(netif); //重新广播发现报文
//dhcp处于DHCP_REQUESTING状态而超时,可能是服务器的ack报文丢失
} else if (dhcp->state == DHCP_REQUESTING) {
if (dhcp->tries <= 5) {
//重新与该服务器联系,让她重新ack
dhcp_select(netif);
} else {
//该服务器可能挂了,我们重新广播发现报文,选择别的服务器
dhcp_release(netif); //清除之前的ip
dhcp_discover(netif);
}
}
//DHCP_RENEWING下,可能是再续约报文丢失
else if (dhcp->state == DHCP_RENEWING) {
dhcp_renew(netif); //重新发送续约报文
} else if (dhcp->state == DHCP_REBINDING) { //重新绑定状态下,可能是报文丢失或服务器挂了
//超过8次无ack,就重新广播,选择其他服务器
if (dhcp->tries <= 8) {
dhcp_rebind(netif);
} else {
dhcp_release(netif); //释放资源
dhcp_discover(netif); //重新广播
}
} else if (dhcp->state == DHCP_REBOOTING) {
if (dhcp->tries < REBOOT_TRIES) {
dhcp_reboot(netif);
} else {
dhcp_discover(netif);
}
}
}
在超时处理函数中,超时的原因可能是报文丢失或服务器关闭等。所以针对超时的处理有报文重发和重新广播两种方式。
四、 定时器2
通过dhcp协议获取的ip地址是有使用时间的。当过了租用时间的一半时,客户端要发送续约报文给服务器,通知服务器是否继续使用该ip地址;
client发送Rebind报文给任何可以用的服务器(Rebind报文中没有server id选项),以延长分配给client的地址的生存时间并且更新其它配置参数,这个消息是在发送Renew消息没有回应后才发送。
dhcp_coarse_tmr()定时器以一分钟的周期执行,检查续约时间和rebind时间是否超时,若超时则分别执行 dhcp_renew(netif); 和 dhcp_rebind(netif); 函数。
void
dhcp_coarse_tmr()
{
struct netif *netif = netif_list;
//遍历网口,检查t1,t2是否超时
while (netif != NULL) {
if (netif->dhcp != NULL) {
if (netif->dhcp->t2_timeout-- == 1) {
dhcp_t2_timeout(netif); //rebind超时
} else if (netif->dhcp->t1_timeout-- == 1) {
dhcp_t1_timeout(netif); //续约超时
}
}
netif = netif->next;
}
}
原文链接:https://blog.csdn.net/weixin_44821644/article/details/114077628
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?