很好的 DHCP协议与dhcpcd分析【转】

时间:2022-11-08 19:54:46

本文转载自://bbsmax.ikafan.com/static/L3Byb3h5L2h0dHAvYmxvZy5jc2RuLm5ldC9nanNpc2kvYXJ0aWNsZS9kZXRhaWxzLzE4MDUyMzY5.jpg

第一部分 DHCP工作过程

DHCP的工作过程主要分为以下六个阶段: 
    发现阶段,即DHCP客户端寻找DHCP服务器的阶段。DHCP客户端首先以广播方式发送DHCP DISCOVER发现信息来寻找DHCP服务器(因为DHCP服务器的IP地址对于客户端来说是未知的),即客户端向地址255.255.255.255发送特定的广播信息。请求信息主要包含客户端的网卡MAC地址还有客户端的计算机名称。网络上每一台安装了TCP/IP协议的主机都会接收到这种广播信息,但只有DHCP服务器才会做出响应。

提供阶段,即DHCP服务器提供IP地址的阶段。在网络中接收到DHCP DISCOVER发现信息的DHCP服务器都会做出响应,它从尚未出租的IP地址中挑选一个分配给DHCP客户端,向DHCP客户端发送一个包含出租的IP地址和其他设置的DHCP OFFER提供信息。

选择阶段,即DHCP客户端选择某台DHCP服务器提供的IP地址的阶段。如果网络中有多台DHCP服务器向DHCP客户端发来的DHCP OFFER提供信息,则DHCP客户端只接受第一个收到的DHCP OFFER提供信息。在客户端收到DHCP OFFER提供信息后,会以广播方式回答一个DHCP REQUEST请求信息,所有的DHCP服务器都会收到这个信息,该信息中包含它所选定的DHCP服务器和服务器提供的IP地址。之所以要以广播方式回答,是为了通知所有的DHCP服务器,他将选择某台DHCP服务器所提供的IP地址,其他没有被选择的DHCP服务器则会收回发出的IP地址。

 确认阶段,即DHCP服务器确认所提供的IP地址的阶段。当DHCP服务器收到DHCP客户端回答的DHCP REQUEST请求信息之后,它便向DHCP客户端发送一个包含它所提供的IP地址和其他设置的DHCP ACK确认信息,告诉DHCP客户端可以使用它所提供的IP地址。至此DHCP客户端可以使用DHCP服务器所提供的IP地址与网卡绑定。

重新登录阶段。以后DHCP客户端每次重新登录网络时,就不需要再发送DHCP DISCOVER发现信息了,而是直接发送包含前一次所分配的IP地址的DHCP REQUEST请求信息。当DHCP服务器收到这一信息后,它会尝试让DHCP客户端继续使用原来的IP地址,并回答一个DHCP ACK确认信息。如果此IP地址已无法再分配给原来的DHCP客户端使用时(比如此IP地址已分配给其它DHCP客户端使用),则DHCP服务器给DHCP客户端回答一个DHCP NACK否认信息。当原来的DHCP客户端收到此DHCP NACK否认信息后,它就必须重新发送DHCP DISCOVER发现信息来请求新的IP地址。 更新租约阶段。DHCP服务器所提供的IP地址一般都是有期限的,我们把这个期限称为租期,租期的长短通过DHCP服务器来设置。设置这个期限是为了让那些过了租期又不活动的IP能空出来,由DHCP服务器重新分配给DHCP客户端,这样就会有效减少IP地址的浪费现象。期满后DHCP服务器便会收回出租的IP地址。如果DHCP客户端要延长其IP租约,则必须更新其IP租约。DHCP客户端启动时和IP租约期限过一半时,DHCP客户端都会自动向DHCP服务器发送更新其IP租约的信息。

如果DHCP客户端一直开启,客户端会在租约过去50%的时候向原DHCP服务器提出DHCP REQUEST请求信息,信息中包含一个客户端正使用的IP地址,并请求服务机延长对此地址的租用。如果原服务器同意会发出DHCP ACK确认信息。这样,续租成功,客户端获得新的租约。如果这个请求信息没有得到回复,因为租约尚未结束,DHCP客户端会继续使用原来的IP,并且每隔大约2分钟向原DHCP服务器再次发送DHCP REQUEST请求信息申请续租。如果到了租约期限的87.5%,DHCP客户端依然没有收到原DHCP服务器的DHCP ACK确认信息,则客户端转为重新绑定状态。在重新绑定状态下,DHCP客户端会以广播的方式向网络中的所有服务器发送DHCP REQUEST请求信息,如果有DHCP服务器响应,并发回DHCP ACK确认信息则DHCP客户端从新的DHCP服务器获得新的IP地址还有新的租约。如果直到租约结束也没有收到任何DHCP服务器的DHCP ACK确认信息,则DHCP客户端会停用租来的IP地址,然后返回初始化状态。

第二部分 DHCP 报文

DHCP报文是承载于UDP上的高层协议报文,采用67(DHCP服务器)和68(DHCP客户端)两个端口号。DHCP报文的格式如下图所示。

图1 DHCP报文格式

很好的 DHCP协议与dhcpcd分析【转】

< 所有DHCP提供的配置信息都在options字段中,这才是精华部分 >

报文中各字段的描述如下:

op,报文类型,1表示请求报文,2表示回应报文。
htype,硬件地址类型,1表示10Mb/s的以太网的硬件地址。
hlen,硬件地址长度,以太网中该值为6。
hops,跳数。客户端设置为0,也能被一个代理服务器设置。
xid,事务ID,由客户端选择的一个随机数,被服务器和客户端用来在它们之间交流请求和响应,客户端用它对请求和应答进行匹配。该ID由客户端设置并由服务器返回,为32位整数。
secs,由客户端填充,表示从客户端开始获得IP地址或IP地址续借后所使用了的秒数。
flags,标志字段。这个16比特的字段,目前只有最左边的一个比特有用,该位为0,表示单播,为1表示广播。
ciaddr,客户端的IP地址。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充。
yiaddr,"你自己的"或客户端的IP地址。
siaddr,表明DHCP协议流程的下一个阶段要使用的服务器的IP地址。
giaddr,DHCP中继器的IP地址。//注意:不是地址池中定义的网关
chaddr,客户端硬件地址。客户端必须设置它的"chaddr"字段。UDP数据包中的以太网帧首部也有该字段,但通常通过查看UDP数据包来确定以太网帧首部中的该字段获取该值比较困难或者说不可能,而在UDP协议承载的DHCP报文中设置该字段,用户进程就可以很容易地获取该值。
sname,可选的服务器主机名,该字段是空结尾的字符串,由服务器填写。
file,启动文件名,是一个空结尾的字符串。DHCP Discover报文中是"generic"名字或空字符,DHCP Offer报文中提供有效的目录路径全名。
options,可选参数域,格式为"代码+长度+数据"。

DHCP Options

Option id

Length(字节)

描述

1

4

Subnet Mask

3

n*4

Router(网关)

6

n*4

DNS Server

7

n*4

Log Server

26

2

Interface MTU

33

n*8

Static route

35

4

ARP cache timeout

42

n*4

NTP servers

51

4

IP address lease time

53

1

Message type 1-DHCPDISCOVER 2-DHCPOFFER 3-DHCPREQUEST 4-DHCPDECLINE 5-DHCPACK 6-DHCPNAK 7-DHCPRELEASE 8-DHCPINFORM

54

4

DHCP Server Identifier

60

n

华为自定义:可配置该终端设备在发起DHCP请求时,通过Option 60携带域信息。ME60收到DHCP报文时,可根据Option 60中携带的域信息来分配IP地址。

82

n

华为自定义:ME60作为DHCP Relay,在中继用户DHCP报文时,可在Option 82中填写用户的物理位置信息,通知DHCP服务器按物理位置信息对为用户分配IP地址。

DHCP报文类型

DHCP共有八种报文,分别为DHCP Discover、DHCP Offer、DHCP Request、DHCP ACK、DHCP NAK、DHCP Release、DHCP Decline、DHCP Inform。各报文类型功能如表1所述。

DHCP报文类型

描述

DHCP Discover

DHCP客户端请求地址时,并不知道DHCP服务器的位置,因此DHCP客户端会在本地网络内以广播方式发送请求报文,这个报文成为Discover报文,目的是发现网络中的DHCP服务器,所有收到Discover报文的DHCP服务器都会发送回应报文,DHCP客户端据此可以知道网络中存在的DHCP服务器的位置。

DHCP Offer

DHCP服务器收到Discover报文后,就会在所配置的地址池中查找一个合适的IP地址,加上相应的租约期限和其他配置信息(如网关、DNS服务器等),构造一个Offer报文,发送给用户,告知用户本服务器可以为其提供IP地址。< 只是告诉client可以提供,是预分配,还需要client通过ARP检测该IP是否重复>

DHCP Request

DHCP客户端可能会收到很多Offer,所以必须在这些回应中选择一个。Client通常选择第一个回应Offer报文的服务器作为自己的目标服务器,并回应一个广播Request报文,通告选择的服务器。DHCP客户端成功获取IP地址后,在地址使用租期过去1/2时,会向DHCP服务器发送单播Request报文续延租期,如果没有收到DHCP ACK报文,在租期过去3/4时,发送广播Request报文续延租期。

DHCP ACK

DHCP服务器收到Request报文后,根据Request报文中携带的用户MAC来查找有没有相应的租约记录,如果有则发送ACK报文作为回应,通知用户可以使用分配的IP地址。

DHCP NAK

如果DHCP服务器收到Request报文后,没有发现有相应的租约记录或者由于某些原因无法正常分配IP地址,则发送NAK报文作为回应,通知用户无法分配合适的IP地址。

DHCP Release

当用户不再需要使用分配IP地址时,就会主动向DHCP服务器发送Release报文,告知服务器用户不再需要分配IP地址,DHCP服务器会释放被绑定的租约。

DHCP Decline

DHCP客户端收到DHCP服务器回应的ACK报文后,通过地址冲突检测发现服务器分配的地址冲突或者由于其他原因导致不能使用,则发送Decline报文,通知服务器所分配的IP地址不可用。

DHCP Inform

DHCP客户端如果需要从DHCP服务器端获取更为详细的配置信息,则发送Inform报文向服务器进行请求,服务器收到该报文后,将根据租约进行查找,找到相应的配置信息后,发送ACK报文回应DHCP客户端。< 极少用到>

第三部分 dhcpcd分析

dhcpcd一共有7种状态:
REBOOTING , INIT , SELECTING , REQUESTING , BOUND , REBINDING , RENEWING

很好的 DHCP协议与dhcpcd分析【转】

很好的 DHCP协议与dhcpcd分析【转】

重新启动DHCPCD时的动作,这时直接发生request申请上次获取到的IP。

很好的 DHCP协议与dhcpcd分析【转】

重新启动DHCPCD时的动作,这时直接发生request申请上次获取到的IP。

很好的 DHCP协议与dhcpcd分析【转】

用户初始化动作,发送discover,转换到SELECTING状态。

很好的 DHCP协议与dhcpcd分析【转】

很好的 DHCP协议与dhcpcd分析【转】

接收服务器的ACK报文,对下发的IP 进行验证,然后转换到BOUND状态。

很好的 DHCP协议与dhcpcd分析【转】

很好的 DHCP协议与dhcpcd分析【转】

将获取到的IP设置成网卡的地址,进入绑定状态,等待超时。

很好的 DHCP协议与dhcpcd分析【转】

很好的 DHCP协议与dhcpcd分析【转】

当租约定时器超时的时候,发送request,进行续约。

很好的 DHCP协议与dhcpcd分析【转】

很好的 DHCP协议与dhcpcd分析【转】

当80%租期的时候,服务器还没有回应ACK,则进行广播request,若在原租期到期时
收到了ACK 则进入BOUND状态,否则转换到INIT状态。

第四部分 android dhcpcd守护进程

最近在调android ethernet功能,android本身不带 ethernet 功能,需要打patch。这个patch可以在setting里出来 ethernet configuration 选项。即添加了用户配置IP的功能。
我打上patch之后,点击选上DHCP功能,结果路由器一直不能自动分配IP。

经检测,命令行里运行 netcfg eth0 up dhcp 时,ethernet能被正常启动,DHCP能分配到IP。

但是Setting里选上时,dhcp却不能正常分配IP。 这很费解,我先后查看了,
/system/core/libnetutils/*
/externel/dhcpcd/* 
发现都没什么问题。

在Setting中点击turn on ethernet选项时,从log看到能调到
1. E/EthernetStateTracker( 185): DhcpHandler: DHCP request failed: Timed out waiting for dhcpcd to start
2. D/EthernetStateTracker( 185): DhcpHandler: DHCP request started

说明patch是好的能正常工作,能正常掉用dhcp,只是DHCP运行不成功。

然后我查了 getprop: 显示 init.svc.dhcpcd_eth0 = stop
正常应该是running的,这样DHCP 才能运行成功。

最后调试了两天,才搞明白,原来是 init.rc 中 dhcpcd_eth0 守护进程的问题:

改成:
1. on property:init.svc.dhcpcd_eth0=stopped
2.     start dhcpcd_eth0
3. 
4. service dhcpcd_eth0 /system/bin/dhcpcd -ABKL -f /system/etc/dhcpcd/dhcpcd.conf -d eth0
5.     class main
6.     disabled
7.     oneshot

这样就可以了。

这时init.svc.dhcpcd_eth0 就会是 running 了。这时再点击Setting -> ethernet configuration, DHCP就能正常分配IP了。

libnetutils 和dhcpcd 调用过程如下:

1. libnetutils 调用过程:
1. jni 
2. =>runDhcp 
3. =>android_net_utils_runDhcp 
4. libs/netutils/dhcp_utils.c 
5. =>dhcp_do_request 
6. => 
7. static const char DAEMON_NAME[] = "dhcpcd"; 
8. static const char DAEMON_PROP_NAME[] = "init.svc.dhcpcd"; 
9. static const char DHCP_PROP_NAME_PREFIX[] = "dhcp"; 
10. const char *ctrl_prop = "ctl.start"; 
11. const char *desired_status = "running"; 
12. snprintf(result_prop_name, sizeof(result_prop_name), "%s.%s.result", 
13. DHCP_PROP_NAME_PREFIX, 
14. interface); 
15. property_set(result_prop_name, "");//设置dhcp.eth0.result="";等到成功完成dhcp之后, 
16. property_set(ctrl_prop, DAEMON_NAME);//向名字为dhcpcd的service,发送"ctrl.start"启动命令字,该service在init.rc中 
17. //init.rc中dhcpcd服务进程命令字 
18. //service dhcpcd /system/bin/dhcpcd eth0 
19. // disabled 
20. // oneshot 
21. wait_for_property(DAEMON_PROP_NAME, desired_status, 10); 
22. //init.c=>init进程 
23. //=>handle_property_set_fd因为是"ctrl.start"命令字,所以调用handle_control_message处理控制信息 
24. //=>handle_control_message 
25. //=>msg_start 
26. //=> 
27. // struct service *svc = service_find_by_name(name); 
28. // service_start(svc);//启动svc,即执行:/system/bin/dhcpcd eth0 
29. //=>service_start 
30. //=>pid = fork(); 
31. // if(pid == 0)execve(svc->args[0], (char**) svc->args, (char**) ENV);子进程执行execve运行/system/bin/dhcpcd,参数为eth0 
32. //=>否则父进程,即init进程将 
33. //=>notify_service_state(svc->name, "running");设置该svc的状态prop 
34. // snprintf(pname, sizeof(pname), "init.svc.%s", name); 
35. // property_set(pname, state);//所以这样上面wait_for_property(DAEMON_PROP_NAME, desired_status, 10);也才能够正常pass[luther.gliethttp]. 
36. wait_for_property(result_prop_name, NULL, 15);//等待dhcp.eth0.result=非空

2. dhcpcd 调用过程:

1. system/extra/dhcpcd-4.0.0-beta9/dhcpcd.c
2. dhcpcd
3. =>main
4. # define SYSCONFDIR "/system/etc/dhcpcd"
5. #define PACKAGE "dhcpcd"
6. # define CONFIG SYSCONFDIR "/" PACKAGE ".conf"
7. # define LIBEXECDIR "/system/etc/dhcpcd"
8. # define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks"
9. =>strlcpy(options->script, SCRIPT, sizeof(options->script));//默认的options->script="/system/etc/dhcpcd /dhcpcd-run-hooks"
10. =>f = fopen(cf ? cf : CONFIG, "r");//如果没有指定.conf文件,那么使用默认.conf文件
11. =>parse_config_line//解析"/system/etc/dhcpcd/dhcpcd.conf"默认配置文件
12. =>parse_option
13. =>如果在"/system/etc/dhcpcd/dhcpcd.conf"有"script"这个节
14. =>那么执行strlcpy(options->script, oarg, sizeof(options->script));直接拷贝
15. /*
16. {"script", required_argument, NULL, 'c'},
17. {"option", required_argument, NULL, 'o'},
18. "/system/etc/dhcpcd/dhcpcd.conf"中的部分内容如下:
19. ...
20. option domain_name_servers, domain_name, domain_search, host_name
21. ...
22. */
23. =>dhcp_run
24. =>handle_dhcp_packet
25. =>handle_dhcp
26. =>bind_dhcp
27.   reason = "TIMEOUT";reason = "BOUND";reason = "REBIND";reason = "RENEW";
28. system/extra/dhcpcd-4.0.0-beta9/configure.c
29. => configure(iface, reason, state->new, state->old, &state->lease, options, 1);
30. //如果dhcp超时或者dhcp成功,都会调用exec_script来执行脚本,
31. //执行setprop dhcp.${interface}.result "failed"或者
32. //执行setprop dhcp.${interface}.result "ok"
33. =>exec_script(options, iface->name, reason, NULL, old);
34. =>然后configure_env通过环境变量将reason传递到脚本中
35. int exec_script(const struct options *options, const char *iface, const char *reason,
36.      const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo)
37. =>pid = fork();
38. =>if(pid == 0)execve(options->script, argv, env);//子进程执行脚本,默认"/system/etc/dhcpcd/dhcpcd-run-hooks"
39. //dhcpcd-run-hooks脚本会根据level值,决定是否执行system/etc/dhcpcd/dhcpcd-hook/*目录下的相应文件
40. //我们的系统在该system/etc/dhcpcd/dhcpcd-hook/*目录下有如下3个文件
41. //95-configured
42. //20-dns.conf
43. //01-test
44. =>父进程返回while (waitpid(pid, &status, 0) == -1)等待子进程脚本执行完成
45. 
46. system/extra/dhcpcd-4.0.0-beta9/dhcpcd-hooks/20-dns.conf
47. system/extra/dhcpcd-4.0.0-beta9/dhcpcd-hooks/95-configured
48.     ...
49.     setprop dhcp.${interface}.ipaddress "${new_ip_address}"
50.     setprop dhcp.${interface}.result "ok"//设置属性为ok
51.     setprop dhcp.${interface}.result "failed"
52.     ...

第五部分 一些总结

1、当我们的客户机无法找到 DHCP服务器时,它将从 TCP/IP的 B类网段 169.254.0.0中挑选一个 IP地址作为自己的 IP地址,而继续每隔 5分钟尝试与 DHCP服务器进行通信。(这里的这个 B类地址被称为 APIPA,即自动分配私有 IP地址!)

2、IP租约的更新,当客户机重新启动或租期达 50%时,客户机不会从第一步(DHCPdiscover)开始重新申请 IP,而是从第三步(DHCPrequest)开始哦~只有当租期达 87.5%时,它才从第一步(DHCPdiscover)开始重新申请!

3、客户机这里还有两条命令,希望大家给记住:
ipconfig/release : 是用来 IP租约的释放。使用 DHCPrelease消息!
ipconfig/renew : 是用来 IP租约的更新。使用 DHCPdiscover消息!

4、客户机必须要经过四步的情况:
(1)第一次扮演 DHCP客户机角色。
(2)IP被 DHCP服务器收回。
(3)客户机自己释放了 IP,并重租一个 IP时。
(4)客户机更换网卡了。
(5)客户机转移到另一网段时。

5、当网络中有服务器不停地发NAK包时,客户端会获取不到ip地址,因为客户端收到此包就会重启dhcp过程。造成一直获取不到ip地址,这种情况一般是大网开启了某些参数检查,或者dhcp服务器不想有人获得ip地址,这时有两种解决办法,一种是满足dhcp服务器条件,一种是修改客户端在NAK的时候不重新启动dhcp过程。