1.DHCP协议介绍:
DHCP(Dynamic Host Configuration Protocol)是一种用于自动分配IP地址和其他网络配置信息的协议。它允许网络中的设备(如计算机、手机、打印机等)在连接到网络时自动获取IP地址、子网掩码、默认网关、DNS服务器等信息,而无需手动配置。
DHCP工作原理是通过DHCP服务器向网络中的设备提供IP地址和其他配置信息。当设备连接到网络时,它会发送一个DHCP请求,请求一个可用的IP地址和其他配置信息。DHCP服务器收到请求后,会分配一个可用的IP地址,并将其他配置信息一并发送给设备,使设备能够顺利地加入到网络中。
DHCP的优点是简化了网络管理,减少了手动配置的工作量,同时也提高了网络的灵活性和可扩展性。通过DHCP,管理员可以更轻松地管理网络中的设备,并且可以更快速地进行网络扩展和变更。
2.DHCP报文
DHCP报文种类:
DHCP一共有8中报文,各种类型报文的基本功能如下:
报文类型 | 说明 |
Discover(0x01) | DHCP客户端在请求IP地址时并不知道DHCP服务器的位置,因此DHCP客户端会在本地网络内以广播方式发送Discover请求报文,以发现网络中的DHCP服务器。所有收到Discover报文的DHCP服务器都会发送应答报文,DHCP客户端据此可以知道网络中存在的DHCP服务器的位置。 |
Offer(0x02) | DHCP服务器收到Discover报文后,就会在所配置的地址池中查找一个合适的IP地址,加上相应的租约期限和其他配置信息(如网关、DNS服务器等),构造一个Offer报文,发送给DHCP客户端,告知用户本服务器可以为其提供IP地址。但这个报文只是告诉DHCP客户端可以提供IP地址,最终还需要客户端通过ARP来检测该IP地址是否重复。 |
Request(0x03) | DHCP客户端可能会收到很多Offer请求报文,所以必须在这些应答中选择一个。通常是选择第一个Offer应答报文的服务器作为自己的目标服务器,并向该服务器发送一个广播的Request请求报文,通告选择的服务器,希望获得所分配的IP地址。另外,DHCP客户端在成功获取IP地址后,在地址使用租期达到50%时,会向DHCP服务器发送单播Request请求报文请求续延租约,如果没有收到ACK报文,在租期达到87.5%时,会再次发送广播的Request请求报文以请求续延租约。 |
Decline(0x04) | DHCP客户端收到DHCP服务器ACK应答报文后,通过地址冲突检测发现服务器分配的地址冲突或者由于其他原因导致不能使用,则会向DHCP服务器发送Decline请求报文,通知服务器所分配的IP地址不可用,以期获得新的IP地址。 |
ACK(0x05) | DHCP服务器收到Request请求报文后,根据Request报文中携带的用户MAC来查找有没有相应的租约记录,如果有则发送ACK应答报文,通知用户可以使用分配的IP地址。 |
NAK(0x06) | 如果DHCP服务器收到Request请求报文后,没有发现有相应的租约记录或者由于某些原因无法正常分配IP地址,则向DHCP客户端发送NAK应答报文,通知用户无法分配合适的IP地址。 |
Release(0x07) | 当DHCP客户端不再需要使用分配IP地址时(一般出现在客户端关机、下线等状况)就会主动向DHCP服务器发送RELEASE请求报文,告知服务器用户不再需要分配IP地址,请求DHCP服务器释放对应的IP地址。 |
Inform(0x08) | DHCP客户端如果需要从DHCP服务器端获取更为详细的配置信息,则向DHCP服务器发送Inform请求报文;DHCP服务器在收到该报文后,将根据租约进行查找到相应的配置信息后,向DHCP客户端发送ACK应答报文。目前基本上不用了。 |
DHCP报文格式:
字段 | 长度 | 说明 |
op | 1byte | 是报文的操作类型,分为请求报文和响应报文,1为请求报文;2为响应报文。具体的报文类型在option字段中标识。 |
htype | 1byte | 硬件地址的长度,以太网的硬件地址长度为6bytes。 |
hlen | 1byte | 硬件地址的长度,以太网的硬件地址长度为6bytes。 |
hops | 1byte | 表示当前dhcp报文经过的DHCP中继的数目,每经过一个DHCP中继这个字段就加1. |
xid | 4bytes | 由client端产生的随机数,用于匹配请求和应答报文,就是匹配应答报文是对哪个请求报文做出应答。 |
secs | 2bytes | 客户端进入IP地址申请进程的时间或者更新IP地址进程的时间;由客户端软件根据情况设定。目前没有使用,固定为0。 |
flags | 2bytes | 是标志字段,16比特中只使用了最高位比特(即最左边的比特),这个个比特是广播响应标识位,用来标识DHCP服务器发出的响应报文是广播还是单播,0是单播,1是广播。其余的比特位保留不用,都为0. |
ciaddr | 4bytes | 是客户端的IP地址,可以是client自己的IP地址,也可以是server分配给client的IP地址。 |
yiaddr | 4bytes | (Your IP Address),是server分配给client的IP地址。 |
siaddr | 4bytes | 是client端获取IP地址等信息的server端的地址。 |
giaddr | 4bytes | 是client发出请求报文后经过的第一个中继的IP地址。 |
chaddr | 16bytes | 是client端的硬件地址,在client发出报文时会把自己网卡的硬件地址写进这个字段。 |
sname | 64bytes | 服务器主机名,是client端获取IP地址等信息的服务器名称。 |
file | 128bytes | 是client的启动配置文件名,是服务器为client指定的启动配置文件名及路径信息,由服务器填写。 |
options | 可变 | 是可选变长的选项字段,这个字段包含了终端的初始配置信息和网络配置信息,包括报文类型,有效租期,DNS服务器的IP地址等配置信息。 |
2.DHCP流程:
DHCP的流程分为四个阶段:
分别是:
1.发现阶段:DHCP客户端向DHCP服务端发送DHCP_DISCOVER报文。
2.提供阶段:DHCP服务端收到客户端DHCP_DISCOVER报文后,向发送DHCP客户端发送DHCP_OFFER报文,该报文中包含尚未分配的地址信息。
3.选择阶段:DHCP客户端选择IP地址,然后广播发送DHCP_REQUEST包,宣告使用它挑选的DHCP服务器地址并正式请求DHCP服务器分配地址
4.确认阶段:当DHCP服务器收到DHCP客户端的DHCP_REQUEST包后,便向客户端发送包含它所提供的IP地址及其他配置信息的DHCPACK确认包。
3.代码分析:
首先初始化单片机的外设:时钟、spi、uart、24c02、配置w5500参数等。
然后调用函数:init_dhcp_client函数对DHCP进行初始化,主要设置ip、mac等信息,把dhcp_state状态设置为STATE_DHCP_READY
void init_dhcp_client(void)
{
uint8 txsize[MAX_SOCK_NUM] = {2, 2, 2, 2, 2, 2, 2, 2};
uint8 rxsize[MAX_SOCK_NUM] = {2, 2, 2, 2, 2, 2, 2, 2};
//uint8 ip_broadcast[4]={255,};
uint8 ip_0[4] = {0,};
DHCP_XID = 0x12345678;
memset(OLD_SIP, 0, sizeof(OLD_SIP));
memset(DHCP_SIP, 0, sizeof(DHCP_SIP));
memset(DHCP_REAL_SIP, 0, sizeof(GET_SN_MASK));
iinchip_init();
setSHAR(ConfigMsg.mac);
setSUBR(ip_0);
setGAR(ip_0);
setSIPR(ip_0);
printf("MAC=%02x:%02x:%02x:%02x:%02x:%02x\r\n", DHCP_CHADDR[0], DHCP_CHADDR[1], DHCP_CHADDR[2], DHCP_CHADDR[3], DHCP_CHADDR[4], DHCP_CHADDR[5]);
sysinit(txsize, rxsize);
//clear ip setted flag
dhcp_state = STATE_DHCP_READY;
#ifdef _DHCP_DEBUG
printf("init_dhcp_client:%u\r\n", SOCK_DHCP);
#endif
}
进入while循环执行DHCP_run()函数
uint8_t DHCP_run(void)
{
uint8_t type;
uint8_t ret;
if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;
if(getSn_SR(SOCK_DHCP) != SOCK_UDP)
socket(SOCK_DHCP, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);
ret = DHCP_RUNNING;
type = parseDHCPMSG();
switch ( dhcp_state )
{
case STATE_DHCP_READY :
DHCP_allocated_ip[0] = 0;
DHCP_allocated_ip[1] = 0;
DHCP_allocated_ip[2] = 0;
DHCP_allocated_ip[3] = 0;
send_DHCP_DISCOVER();
dhcp_time = 0;
dhcp_state = STATE_DHCP_DISCOVER;
break;
case STATE_DHCP_DISCOVER :
if (type == DHCP_OFFER)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_OFFER\r\n");
#endif
DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];
send_DHCP_REQUEST();
dhcp_time = 0;
dhcp_state = STATE_DHCP_REQUEST;
}
else
ret = check_DHCP_timeout();
break;
case STATE_DHCP_REQUEST :
if (type == DHCP_ACK)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_ACK\r\n");
#endif
if (check_DHCP_leasedIP())
{
// Network info assignment from DHCP
printf("ip:%d.%d.%d.%d\r\n", DHCP_allocated_ip[0], DHCP_allocated_ip[1], DHCP_allocated_ip[2], DHCP_allocated_ip[3]);
printf("sn:%d.%d.%d.%d\r\n", DHCP_allocated_sn[0], DHCP_allocated_sn[1], DHCP_allocated_sn[2], DHCP_allocated_sn[3]);
printf("gw:%d.%d.%d.%d\r\n", DHCP_allocated_gw[0], DHCP_allocated_gw[1], DHCP_allocated_gw[2], DHCP_allocated_gw[3]);
dhcp_ip_assign();
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_LEASED;
}
else
{
// IP address conflict occurred
reset_DHCP_timeout();
dhcp_ip_conflict();
dhcp_state = STATE_DHCP_READY;
}
}
else if (type == DHCP_NAK)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_NACK\r\n");
#endif
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_DISCOVER;
}
else
ret = check_DHCP_timeout();
break;
case STATE_DHCP_LEASED :
ret = DHCP_IP_LEASED;
if ((dhcp_lease_time != DEFAULT_LEASETIME) && ((dhcp_lease_time / 2) < dhcp_time))
{
#ifdef _DHCP_DEBUG_
printf("> Maintains the IP address \r\n");
#endif
type = 0;
OLD_allocated_ip[0] = DHCP_allocated_ip[0];
OLD_allocated_ip[1] = DHCP_allocated_ip[1];
OLD_allocated_ip[2] = DHCP_allocated_ip[2];
OLD_allocated_ip[3] = DHCP_allocated_ip[3];
DHCP_XID++;
send_DHCP_REQUEST();
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_REREQUEST;
}
break;
case STATE_DHCP_REREQUEST :
ret = DHCP_IP_LEASED;
if (type == DHCP_ACK)
{
dhcp_retry_count = 0;
if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
OLD_allocated_ip[3] != DHCP_allocated_ip[3])
{
ret = DHCP_IP_CHANGED;
dhcp_ip_update();
#ifdef _DHCP_DEBUG_
printf(">IP changed.\r\n");
#endif
}
#ifdef _DHCP_DEBUG_
else printf(">IP is continued.\r\n");
#endif
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_LEASED;
}
else if (type == DHCP_NAK)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
#endif
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_DISCOVER;
}
else ret = check_DHCP_timeout();
break;
default :
break;
}
return ret;
}
分析DHCP_run函数:
1. 打开socket,调用parseDHCPMSG()函数,接受数据并解析,将收到的数据进行保存。
int8_t parseDHCPMSG(void)//20180625
{
uint8_t svr_addr[6];
uint16_t svr_port;
uint16_t len;
uint8_t * p;
uint8_t * e;
uint8_t type;
uint8_t opt_len;
if((len = getSn_RX_RSR(SOCK_DHCP)) > 0)
{
len = recvfrom(SOCK_DHCP, (uint8_t *)pDHCPMSG, len, svr_addr, &svr_port);
#ifdef _DHCP_DEBUG_
printf("DHCP message : %d.%d.%d.%d(%d) %d received. \r\n", svr_addr[0], svr_addr[1], svr_addr[2], svr_addr[3], svr_port, len);
#endif
}
else return 0;
if (svr_port == DHCP_SERVER_PORT)
{
// compare mac address
if ( (pDHCPMSG->chaddr[0] != DHCP_CHADDR[0]) || (pDHCPMSG->chaddr[1] != DHCP_CHADDR[1]) ||
(pDHCPMSG->chaddr[2] != DHCP_CHADDR[2]) || (pDHCPMSG->chaddr[3] != DHCP_CHADDR[3]) ||
(pDHCPMSG->chaddr[4] != DHCP_CHADDR[4]) || (pDHCPMSG->chaddr[5] != DHCP_CHADDR[5]) )
return 0;
type = 0;
p = (uint8_t *)(&pDHCPMSG->op);//2
p = p + 240; // 240 = sizeof(RIP_MSG) + MAGIC_COOKIE size in RIP_MSG.opt - sizeof(RIP_MSG.opt)
e = p + (len - 240);
while ( p < e )
{
switch ( *p )
{
case endOption :
p = e; // for break while(p < e)
break;
case padOption :
p++;
break;
case dhcpMessageType :
p++;
p++;
type = *p++;
break;
case subnetMask :
p++;
p++;
DHCP_allocated_sn[0] = *p++;
DHCP_allocated_sn[1] = *p++;
DHCP_allocated_sn[2] = *p++;
DHCP_allocated_sn[3] = *p++;
break;
case routersOnSubnet :
p++;
opt_len = *p++;
DHCP_allocated_gw[0] = *p++;
DHCP_allocated_gw[1] = *p++;
DHCP_allocated_gw[2] = *p++;
DHCP_allocated_gw[3] = *p++;
p = p + (opt_len - 4);
break;
case dns :
p++;
opt_len = *p++;
DHCP_allocated_dns[0] = *p++;
DHCP_allocated_dns[1] = *p++;
DHCP_allocated_dns[2] = *p++;
DHCP_allocated_dns[3] = *p++;
p = p + (opt_len - 4);
break;
case dhcpIPaddrLeaseTime :
p++;
opt_len = *p++;
dhcp_lease_time = *p++;
dhcp_lease_time = (dhcp_lease_time << 8) + *p++;
dhcp_lease_time = (dhcp_lease_time << 8) + *p++;
dhcp_lease_time = (dhcp_lease_time << 8) + *p++;
#ifdef _DHCP_DEBUG_
// dhcp_lease_time = 10;
// printf("dhcp_lease_time:%d\r\n",(int)dhcp_lease_time);
#endif
break;
case dhcpServerIdentifier :
p++;
opt_len = *p++;
DHCP_SIP[0] = *p++;
DHCP_SIP[1] = *p++;
DHCP_SIP[2] = *p++;
DHCP_SIP[3] = *p++;
break;
default :
p++;
opt_len = *p++;
p += opt_len;
break;
} // switch
} // while
} // if
return type;
}
2.然后进入switch状态机,首先dhcp_state的值为STATE_DHCP_READY,执行一下代码:
进入发现阶段:DHCP客户端向DHCP服务端发送DHCP_DISCOVER报文,然后把dhcp_state的值设为STATE_DHCP_DISCOVER。
case STATE_DHCP_READY :
DHCP_allocated_ip[0] = 0;
DHCP_allocated_ip[1] = 0;
DHCP_allocated_ip[2] = 0;
DHCP_allocated_ip[3] = 0;
send_DHCP_DISCOVER();
dhcp_time = 0;
dhcp_state = STATE_DHCP_DISCOVER;
break;
3.然后退出switch,再次调用parseDHCPMSG()函数去接受DHCP服务端发来的数据并解析,再次进入switch状态机中,执行STATE_DHCP_DISCOVER代码,如果数据包的类型是DHCP_OFFER
就执行以下代码:DHCP客户端选择IP地址,然后广播发送DHCP_REQUEST包,宣告使用它挑选的DHCP服务器地址并正式请求DHCP服务器分配地址。
case STATE_DHCP_DISCOVER :
if (type == DHCP_OFFER)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_OFFER\r\n");
#endif
DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];
send_DHCP_REQUEST();
dhcp_time = 0;
dhcp_state = STATE_DHCP_REQUEST;
}
4.如果正确接收到DHCP服务端的DHAP_ACK数据包,则DHCP请求就成功了。执行一下代码:
若是DHAP_ACK数据包,则调用check_DHCP_leasedIP()函数检查IP是否有效,然后我们去设置w5500的IP等信息,把dhcp_state状态设为STATE_DHCP_LEASED,若是DHCP_NAK数据包,调用reset_DHCP_timeout()把超时时间复位, 然后将dhcp_state值改为STATE_DHCP_DISCOVER重新进入发现阶段。
case STATE_DHCP_REQUEST :
if (type == DHCP_ACK)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_ACK\r\n");
#endif
if (check_DHCP_leasedIP())
{
// Network info assignment from DHCP
printf("ip:%d.%d.%d.%d\r\n", DHCP_allocated_ip[0], DHCP_allocated_ip[1], DHCP_allocated_ip[2], DHCP_allocated_ip[3]);
printf("sn:%d.%d.%d.%d\r\n", DHCP_allocated_sn[0], DHCP_allocated_sn[1], DHCP_allocated_sn[2], DHCP_allocated_sn[3]);
printf("gw:%d.%d.%d.%d\r\n", DHCP_allocated_gw[0], DHCP_allocated_gw[1], DHCP_allocated_gw[2], DHCP_allocated_gw[3]);
dhcp_ip_assign();
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_LEASED;
}
else
{
// IP address conflict occurred
reset_DHCP_timeout();
dhcp_ip_conflict();
dhcp_state = STATE_DHCP_READY;
}
}
else if (type == DHCP_NAK)
{
#ifdef _DHCP_DEBUG_
printf("> Receive DHCP_NACK\r\n");
#endif
reset_DHCP_timeout();
dhcp_state = STATE_DHCP_DISCOVER;
}
else
ret = check_DHCP_timeout();
break;
05_W5500_UDP通信 <--------- 上一篇 下一篇----------> 07——W5500