ARTICLE · 1338911
lwip源码分析 之 DHCP协议实现
lwip源码分析 之 DHCP协议实现
一,dhcp协议简介二,源码解析2.1 dhcp结构体2.2 开始广播2.3 回调接口a,发送请求b,等待ack2.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结构体
structdhcp{u32_t xid; //事务ID,随机数,有客户端生成,服务器Reply时,会把Request中的Transaction拷贝到Reply报文中。structudp_pcb *pcb;//连接dhcp服务器的udp控制块structdhcp_msg *msg_in;//dhcp输入报文u8_t state; //当前dhcp状态机u8_t tries; //重请求次数u8_t subnet_mask_given; //是否有子网掩码structpbuf *p_out;/* 输出的报文的pbuf pbuf of outcoming msg */structdhcp_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; //服务器提供的ipip_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_tdhcp_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客户端专属端口68udp_connect(dhcp->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT); //连接到远程dhcp服务器端口67udp_recv(dhcp->pcb, dhcp_recv, netif); //设置dhcp接收回调函数和参数result = dhcp_discover(netif); //dhcp客户端开始运行if (result != ERR_OK) {dhcp_stop(netif); //错了,停止dhcpreturn ERR_MEM;}netif->flags |= NETIF_FLAG_DHCP; //置位dhcp成功return result;}
**可见这个函数主要是为dhcp结构体分配内存,并建立了一个udp连接,注册好dhcp的回调函数(当收到ducp服务器发送的报文时会进入回调函数),并调用dhcp_discover();函数开始广播dhcp发现报文。**接着看这个函数:
staticerr_tdhcp_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为0dhcp_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++; //重发次数+1msecs = (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报文的。staticerr_tdhcp_create_msg(struct netif *netif, struct dhcp *dhcp, u8_t message_type){u16_t i;static u32_t xid = 0xABCD0000; //静态的事务id//申请dhcp报文的pbufdhcp->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 trafficbefore 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服务端的消息时,就会调用设置的回调函数。
staticvoiddhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port){structnetif *netif = (structnetif *)arg;//获取网口structdhcp *dhcp = netif->dhcp;structdhcp_msg *reply_msg = (structdhcp_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);#elsedhcp_bind(netif); //将dhcp申请到的ip绑定到netif#endif}
//以下三种状态的dhcp也能进行绑定
elseif ((dhcp->state == DHCP_REBOOTING) || (dhcp->state == DHCP_REBINDING) || (dhcp->state == DHCP_RENEWING)) {dhcp_bind(netif);}}
//根据dhcp协议,服务器返回nack报文,客户端需要重新广播,重新获取
ipelseif ((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状态(结合上图)
elseif ((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
staticvoiddhcp_handle_offer(struct netif *netif){struct dhcp *dhcp = netif->dhcp;
//服务器ip不为0
if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_SERVER_ID)) {//获取服务器ipip4_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_tdhcp_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++; //重试次数+1msecs = (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地址了。
staticvoiddhcp_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)));}}staticvoiddhcp_bind(struct netif *netif){u32_t timeout;structdhcp *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));} elseif (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请求超时
voiddhcp_fine_tmr(){struct netif *netif = netif_list;//遍历所有网口while (netif != NULL) {if (netif->dhcp != NULL) {//request_timeout -1,表示过去500msif (netif->dhcp->request_timeout > 1) {netif->dhcp->request_timeout--;}elseif (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()是超时处理函数:
staticvoiddhcp_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报文丢失
} elseif (dhcp->state == DHCP_REQUESTING) {if (dhcp->tries <= 5) {//重新与该服务器联系,让她重新ackdhcp_select(netif);} else {//该服务器可能挂了,我们重新广播发现报文,选择别的服务器dhcp_release(netif); //清除之前的ipdhcp_discover(netif);}}
//DHCP_RENEWING下,可能是再续约报文丢失
elseif (dhcp->state == DHCP_RENEWING) {dhcp_renew(netif); //重新发送续约报文} elseif (dhcp->state == DHCP_REBINDING) { //重新绑定状态下,可能是报文丢失或服务器挂了
//超过8次无ack,就重新广播,选择其他服务器
if (dhcp->tries <= 8) {dhcp_rebind(netif);} else {dhcp_release(netif); //释放资源dhcp_discover(netif); //重新广播}} elseif (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); 函数。
voiddhcp_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超时} elseif (netif->dhcp->t1_timeout-- == 1) {dhcp_t1_timeout(netif); //续约超时}}netif = netif->next;}}
原文链接:https://blog.csdn.net/weixin_44821644/article/details/114077628















































































