第十八章 DHCP18.1 DHCP例程概述 DHCP协议是TCP/IP协议簇中的一种,具体功能为DHCP客户端向DHCP服务器发送自动获取IP请求,DHCP服务器为DNCP客户端自动分配IP地址、网关、DNS服务器地址等信息。 本例程就是W7500EVB以DHCP客户端的角色,向DHCP服务器发送自动获取IP信息的请求并最终成功自动获取IP信息。使用前,我们先了解下DHCP协议。 18.2 DHCP协议简介 在常见的小型网络中,网络管理员都是采用手工分配IP地址的方法。但在中、大型网络中,手动分配IP地址的方法就不太实际了。为了避免多台机器分配IP的困扰,引出了一种高效的IP地址分配方法即:DHCP自动分配IP地址。 在日常生活中,家庭无线路由器都内嵌了DHCP服务器。因此,当PC或智能手机通过路由器联网的时候,无需我们手工输入IP地址、网关、子网掩码和DNS服务器IP地址等网络参数。PC或智能手机都可以通过路由器自动获得这些必要的网络配置信息。这里的PC或智能手机在联网时都会先执行DHCP客户端线程和DHCP服务器通信。 DHCP是Dynamic Host Configuration Protocol的缩写,它是TCP/IP协议簇中的一种,使用UDP协议进行数据报传递,端口号是67。DHCP协议采用客户端/服务器模型,主机地址的动态分配任务由网络主机驱动。当DHCP服务器接收到来自网络主机申请地址的信息时,才会向网络主机发送相关的地址配置等信息,以实现网络主机地址信息的动态配置。通常被应用在大型的局域网络环境中,主要作用是集中的管理、分配IP地址,使网络环境中的主机动态的获得IP地址、网关地址、DNS服务器地址等信息,并能够提升地址的使用率。 使用DHCP时必须在网络上有一台DHCP服务器,而其他机器执行DHCP客户端。当DHCP客户端程序发出一个信息,请求一个动态的IP地址时,DHCP服务器会根据目前已经配置的地址,提供一个可供使用的IP地址和子网掩码给客户端。这些被分配的IP地址都是DHCP服务器预先保留的一个由多个地址组成的地址集,并且它们一般是一段连续的地址。 DHCP使服务器能够动态地为网络中的其他服务器提供IP地址,通过使用DHCP,就可以不给Intranet网中除DHCP、DNS和WINS服务器外的任何服务器设置和维护静态IP地址。使用DHCP可以大大简化配置客户机的TCP/IP的工作,尤其是当某些TCP/IP参数改变时,如网络的大规模重建而引起的IP地址和子网掩码的更改。 DHCPRelay也叫做DHCP中继代理。DHCP中继代理,就是在DHCP服务器和客户端之间转发DHCP数据包。当DHCP客户端与服务器不在同一个子网上,就必须有DHCP中继代理来转发DHCP请求和应答消息。DHCP中继代理的数据转发,与通常路由转发是不同的,通常的路由转发相对来说是透明传输的,设备一般不会修改IP包内容。而DHCP中继代理接收到DHCP消息后,重新生成一个DHCP消息,然后转发出去。在DHCP客户端看来,DHCP中继代理就像DHCP服务器;在DHCP服务器看来,DHCP中继代理就像DHCP客户端。 18.3 DHCP工作流程 首先,DHCP客户端发送DHCPDISCOVER消息(IP地址租用申请),这个消息通过广播方式发出,所有网络中的DHCP服务器都将接收到这个消息。 随后,网络中的DHCP服务器会回应一个DHCPOFFER消息(IP地址租用提供),由于这个时候客户端还没有网络地址,所以DHCPOFFER也是通过广播的方式发送出去的。需要注意的是,由于网络中可能存在不止一台的DHCP服务器,所以,如果不考虑网络丢包的话,客户端将接收到不止一条的DHCPOFFER消息。那么客户端会选择它接收到的第一条DHCPOFFER作为获取配置的服务器。 然后,向该服务器发送DHCPREQUEST消息。虽然这个时候客户端已经明确知道选择的DHCP服务器的地址所在,但仍将采用广播的方式发送DHCPREQUEST消息,这样做不仅可以通知选中的服务器向客户端分配IP地址,同时也可以通知其他没有选中的DHCP服务器不需要再响应它的请求。在DHCPREQUEST消息中将包含客户端申请的IP地址。 最后,DHCP服务器将回送DHCPACK的响应消息来通知客户端可以使用该IP地址,该确认里面包含了分配的IP地址和该地址的一个稳定期限的租约(默认是8天),并同时更新DHCP数据库。 当租约过了一半时(即4天),客户端将和设置它的TCP/IP配置的DHCP服务器更新租约。当租约过了85.7%时,如果客户端仍然无法与当初的DHCP服务器联系上,他将与其他DHCP服务器通信,如果网络中再没有任何DHCP服务器在运行时,该客户端停止使用该IP地址,并重新发送一个DHCPDISCOVER消息,再一次重复整个过程。 DHCP工作时要求客户机和服务器进行交互,由客户端通过广播向服务器发起申请IP地址的请求,然后由服务器分配一个IP地址以及其他的TCP/IP设置信息。DHCPACK整个工作过程如图18.3.1所示,可以分为以下步骤: 图18.3.1 DHCP工作过程 (1)IP地址租用申请(DHCPDISCOVER):DHCP客户机通过UDP68端口发送DHCPDISCOVER广播信息来查找DHCP服务器。网络上每一台安装了TCP/IP协议的主机都会接收到这种广播信息,但只有DHCP服务器才会做出响应。DHCP客户机发送的DHCPDISCOVER数据包的源地址是0.0.0.0,目标地址是255.255.255.255。 (2)IP地址租用提供(DHCPOFFER):当网络中的DHCP服务器接收到DHCPDISCOVER广播时,将确定是否可以用自己的数据库来为该请求提供服务。如果可以为该请求提供服务,DHCP服务器就从尚未出租的IP地址范围中选择最前面的空置IP,连同其他TCP/IP设定,通过UDP67端口以单播DHCPOFFER的形式为客户端提供信息。可能有多台DHCP服务器收到DHCPDISCOVER广播,并且向DHCP客户端响应DHCPOFFER。客户接收到的DHCPOFFER数据包中包含客户的MAC地址,后面跟着服务器能提供的IP地址、子网掩码、租约期限以及DHCP服务器的IP地址。 (3)IP地址租用选择(DHCPREQUEST):DHCP客户端通常是接收第一个收到的DHCPOFFER所提供的信息,并且会向网络发送一个DHCPREQUEST广播风暴,告诉所有DHCP服务器它将接收哪一台服务器提供的IP地址。 (4)IP地址租用确认(DHCPACK):当DHCP服务器收到DHCPREQUEST信息之后,便向DHCP客户端发送一个单播的DHCPACK信息,以确认IP租约的正式生效。然后DHCP客户端便将其TCP/IP协议与网卡绑定。 18.4 DHCP例程解析 在本演示程序中,W7500EVB充当DHCP客户端的角色,DHCP服务器则是我们连接的路由器。现在绝大多数路由器都能够实现DHCP服务器的功能。W7500EVB发送广播封包并接收路由器的IP地址分配,完成了完整的DHCP工作过程。 主程序初始化单片机和W7500EVB,在主循环里面则是主要运行do_dhcp()函数。main.c程序中调用了dhcp.c的DHCP执行函数。这些函数完成DHCP协议的组包及对接收到的数据包的解析。在初始化DHCP客户端的函数中,因为DHCP服务器端监听了UDP的67端口,DHCP客户端需要打开本地68端口同服务器通信。因为IP地址等网络参数由DHCP服务器分配,在进入主循环之前,初始化过程不执行set_W7500_ip()函数。除此之外,初始化函数与Network install例程基本一样,在此就不多讲解。 主要分析DHCP执行函数do_dhcp():
[mw_shl_code=applescript,true]1. void do_dhcp(void)
2. {
3. uint8_t tmp[8];
4. uint32_t my_dhcp_retry = 0;
5. switch(DHCP_run()) //判断DHCP运行状态
6. {
7. case DHCP_IP_ASSIGN: //首次分配IP地址状态
8. case DHCP_IP_CHANGED: //从DHCP服务器获取新的IP地址状态
9. toggle = 1;
10. if(toggle)
11. {
12. toggle = 0;
13. close(SOCK_TCPS);
14. }
15. break;
16. case DHCP_IP_LEASED: //成功租赁到IP地址状态
17. if(toggle)
18. {
19. set_w7500_ip(); //设置IP地址
20. toggle = 0;
21. }
22. break;
23. case DHCP_FAILED: //DHCP 获取失败
24. my_dhcp_retry++;
25. if(my_dhcp_retry > MY_MAX_DHCP_RETRY)
26. {
27. #if DEBUG_MODE != DEBUG_NO
28. printf(">> DHCP %d Failed\r\n",my_dhcp_retry);
29. #endif
30. my_dhcp_retry = 0;
31. DHCP_stop(); // if restart, recall DHCP_init()
32. }
33. break;
34. default:
35. break;
36. }
37. }[/mw_shl_code] 第5行获取DHCP的状态,其实就是通过DHCP_run函数取得W7500EVB动态获取IP过程中返回的状态值。第5行到第36行其实就是一个状态机模式,DHCP的状态分为:DHCP_IP_ASSIGN首次分配IP地址状态、DHCP_IP_CHANGED从DHCP服务器获取新的IP地址状态、DHCP_IP_LEASED成功租赁到IP地址状态、DHCP_FAILED DHCP 获取失败转态,共4个状态。通过switch语句完成对所有状态的处理。第19行将获取的IP地址通过set_w7500_ip()函数写入W7500EVB。 DHCP_run();函数实现了W7500EVB作为DHCP客户端的详细工作过程,由于程序篇幅比较大,此处仅摘录讲解(部分程序删除,请对照源程序)。 [mw_shl_code=applescript,true]1. uint8_t DHCP_run(void)
2. {
3. uint8_t type;
4. uint8_t ret;
5. uint8_t i;
6. if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;
7. if(getSn_SR(DHCP_SOCKET) != SOCK_UDP) //开启UDP模式
8. socket(DHCP_SOCKET, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);
9. ret = DHCP_RUNNING;
10. type = parseDHCPMSG();
11.
12. switch ( dhcp_state )
13. {
14. case STATE_DHCP_INIT : //DHCP初始化状态
15. for (i = 0; i < 4; i++) DHCP_allocated_ip = 0;
16. send_DHCP_DISCOVER(); //发送DISCOVER包
17. dhcp_state = STATE_DHCP_DISCOVER;
18. break;
19. case STATE_DHCP_DISCOVER : //DISCOVER状态
20. if (type == DHCP_OFFER)
21. {
22. #ifdef _DHCP_DEBUG_
23. printf("> Receive DHCP_OFFER\r\n");
24. #endif
25. for (i = 0; i < 4; i++)
26. {
27. DHCP_allocated_ip = pDHCPMSG->yiaddr;
28. }
29. send_DHCP_REQUEST();
30. dhcp_state = STATE_DHCP_REQUEST;
31. } else ret = check_DHCP_timeout();
32. break;
33.
34. case STATE_DHCP_REQUEST : //REQUET请求状态
35. if (type == DHCP_ACK) //DHCP请求响应成功
36. {
37. #ifdef _DHCP_DEBUG_
38. printf("> Receive DHCP_ACK\r\n");
39. #endif
40. printf("> Receive !!!\r\n");
41.
42. if (check_DHCP_leasedIP()) //IP地址租赁成功
43. {
44. // Network info assignment from DHCP
45. dhcp_ip_assign(); //从DHCP服务器分配IP地址
46. reset_DHCP_timeout(); //复位超时时间
47. dhcp_state = STATE_DHCP_LEASED;
48. }
49. else
50. {
51. // IP address conflict occurred
52. reset_DHCP_timeout(); //复位超时时间
53. dhcp_ip_conflict(); //判断iP地址是否冲突
54. dhcp_state = STATE_DHCP_INIT;
55. }
56. }
57. else if (type == DHCP_NAK) //DHCP请求响应不成功
58. {
59. #ifdef _DHCP_DEBUG_
60. printf("> Receive DHCP_NACK\r\n");
61. #endif
62. reset_DHCP_timeout(); //复位超时时间
63. dhcp_state = STATE_DHCP_DISCOVER;
64. }
65. else ret = check_DHCP_timeout(); //判断请求是否超时
66. break;
67. case STATE_DHCP_LEASED : //IP地址租赁状态
68. ret = DHCP_IP_LEASED;
69. if ((dhcp_lease_time != INFINITE_LEASETIME) && ((dhcp_lease_time/2) < dhcp_tick_1s))
70. {
71. #ifdef _DHCP_DEBUG_
72. printf("> Maintains the IP address \r\n");
73. #endif
74. type = 0;
75. for (i = 0; i < 4; i++)
76. {
77. OLD_allocated_ip = DHCP_allocated_ip; //保存得到的IP地址
78. }
79. DHCP_XID++;
80. send_DHCP_REQUEST(); //发送请求
81. reset_DHCP_timeout();
82. dhcp_state = STATE_DHCP_REREQUEST;
83. }
84. break;
85. case STATE_DHCP_REREQUEST : //重新发送请求,判断IP地址是否成功分配 87. ret = DHCP_IP_LEASED;
88. if (type == DHCP_ACK) //重传请求应答成功
89. {
90. dhcp_retry_count = 0;
91. if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
92. OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
93. OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
94. OLD_allocated_ip[3] != DHCP_allocated_ip[3])
95. {
96. ret = DHCP_IP_CHANGED;
97. dhcp_ip_update();
98. #ifdef _DHCP_DEBUG_
99. printf(">IP changed.\r\n");
100. #endif
101. }
102. #ifdef _DHCP_DEBUG_
103. else printf(">IP is continued.\r\n");
104. #endif
105. reset_DHCP_timeout();
106. dhcp_state = STATE_DHCP_LEASED;
107. }
108. else if (type == DHCP_NAK) //重传请求应答不成功
109. {
110. #ifdef _DHCP_DEBUG_
111. printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
112. #endif
113. reset_DHCP_timeout(); //复位超时时间
114. dhcp_state = STATE_DHCP_DISCOVER;
115. } else ret = check_DHCP_timeout();
116. break;
117. default :
118. break;
119. }
120. return ret;
121. }[/mw_shl_code] DHCP协议客户端也是一个状态机的实现,它的工作过程是这样的:W7500作为客户端初始化时,我们设定DHCP的状态为STATE_DHCP_READY,首先如果现在DHCP socket处于打开状态,我们解析获得的数据包类型,并存入变量Type中;如果现在DHCP socket处于关闭状态,我们初始化DHCP socket并开启一个端口,端口号为了方便辨识,我们命名为DHCP_CLIENT_PORT,即68,协议类型为UDP协议。之后就开始了我们的状态机模式。 7~8行判断SOCKET如果关闭,初始化DHCP客户端,打开端口。第10行是解析DHCP服务器的应答信息。第16行就是发送DISCOVER 封包,然后客户端就跳转到STATE_DHCP_DISCOVER状态。第19行,当处于DISCOVER状态时,如果接收到Offer封包,客户端就会发送REQUEST封包,然后进入STATE_DHCP_REQUEST状态,如果没有接收到,就检查时间是否超时,跳出循环。第34行,当处于REQUEST状态时,如果接收到ACK封包,确认是不是自己的IP地址,如果确认是IP租约,就跳转到IP租约(租约时间由路由器自动分配,或设置)绑定状态,返回DHCP的IP更新的状态值,并把获得的IP地址等信息写入单片机程序的变量中,并给W7500配置。然后打印串口信息出来,我们就能看到我们所获得的信息;如果和先前的IP租约不一致或者接收到NAK封包,返回到READY状态,并返回IP地址冲突的值。这就是DHCP中一个典型的获取IP的流程。 此外,DHCP还有对网络参数的分配的管理。也就是状态机中的“续租”处理部分。第67行,当处于绑定IP地址的状态时,如果租约时间超过一半,第77行会把现在客户端的IP地址copy到原来的IP地址处,并再次发送REQUEST封包进入STATE_DHCP_REREQUEST状态,跳出循环,相当于发出一个“续租”的请求。第86行,当处于REREQUEST状态时,我们分以下情况:当接收到ACK封包的情况下,如果IP地址与原来的不同,则IP地址更新,返回IP更新的状态值;如果相同则完成IP续租的过程,仍然使用原来的IP地址。 至此,DHCP例程代码解析就结束了。将DHCP例程编译烧录后打印串口信息结果如图18.4.1。 图18.4.1 DHCP例程打印结果 打印串口信息显示已获取IP地址,我们尝试PC机能否Ping通W7500EVB。结果如图18.4.2,成功Ping通W7500EVB,至此证明W7500EVB自动获取IP地址配置成功,已与PC机接入同一网络中。 图18.4.2 Ping结果
|