DHCP介绍
- 1 DHCP简述
- 2 DHCP协议分析
- 2.1 主要流程
- 2.2 DHCP全部报文介绍
- 2.3 IP租用更新报文
- 2.4 DHCP协议抓包分析
- 3 DHCP应用
- 3.1 DNSmasq参数配置
- 3.2 DNSmasq框架代码
- 3.2.1 创建socket监听67端口
- 3.2.2 监听67端口
- 3.2.3 处理DHCP请求
- 3.3 DNSmasq模块排障方法
- 4 常见问题排查
- 4.1 问题分类
- 4.2 上位机不发起DHCP请求
- 4.3 网络环境问题
- 4.4 dnsmasq不响应DHCP请求
- 4.4.1 确认是否为配置问题
- 4.4.2 检查dnsmasq日志,确认是否存在明显报错
- 4.4.3 检查dnsmasq协议处理流程中增加日志,确认未响应DHCP报文原因
- 5 典型案例分享
1 DHCP简述
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是IETF为实现IP的自动配置而设计的协议,前身是BOOTP协议,属于局域网的网络协议,使用UDP协议工作,常用的2个端口:67(DHCP server),68(DHCP client)。
DHCP通常被用于局域网环境,主要作用是集中的管理、分配IP地址,使client动态地获得IP地址、Gateway地址、DNS服务器地址等信息,并能够提升地址的使用率。简单来说,DHCP就是一个不需要账号密码登录的、自动给内网机器分配IP地址等信息的协议。
DHCP协议支持C/S(客户端/服务器)结构,主要分为两部分:
- DHCP客户端:通常为网络中的PC、打印机等终端设备,使用从DHCP服务器分配下来的IP信息,包括IP地址、DNS等。
- DHCP服务器:所有的IP网络设定信息都由DHCP服务器集中管理,并处理客户端的DHCP请求。
DHCP采用UDP作为传输协议,客户端发送消息到DHCP服务器的的67号端口,服务器返回消息给客户端的68号端口。
2 DHCP协议分析
2.1 主要流程
- 客户端第一次登录网络的时候,计算机发现本机上没有任何IP地址设定,将以广播方式发送DHCP-DISCOVER 消息来寻找DHCP服务器,即向255.255.255.255发送特定的广播信息。网络上每一台安装了TCP/IP协议的主机都会接收这个广播信息,但只有DHCP服务器才会做出响应。
- 局域网内的DHCP 服务器收到 DHCP 客户端广播的DHCP-DISCOVER 消息后,从自身管理的 IP 地址池挑选一个空闲 IP,单播回复一个带可用IP地址的DHCP-OFFER 消息给请求IP的 DHCP 客户端。
- DHCP 客户端在收到 DHCP 服务器的DHCP-OFFER 消息后,选择第一个接收到的提供信息,再次以广播的方式回答一个DHCP-REQUEST 消息,告知自己准备使用 DHCP-OFFER 消息给出的IP 地址。
(1)客户端初始化时,广播发送DHCP-REQUEST报文来回应服务器的DHCP-OFFER报文。
(2)客户端重启初始后,广播发送DHCP-REQUEST报文来确认先前被分配的IP地址等配置信息。
(3)当客户端已知和某个IP地址绑定后,单播发送DHCP-REQUEST报文来延长IP地址租期 - DHCP 服务器接收到 DHCP 客户端的 IP 请求消息 DHCP-REQUEST后,确认地址池中的这个地址没有被分配,单播回复一个 DHCP-ACK 告知 DHCP 客户端,你申请的 IP 成功了,DHCP 服务端会将之记录在案:该 IP 已经分配出去了。如果被分配了 ,就会回复DHCP-NAK报文,告诉CLient 地址已经被分配了。
当客户端收到DHCP服务器分配的地址后,会向这个广播域内发送一个免费ARP的请求,如果没有人响应这个请求,客户端才正式使用这个地址,如果有人回应的话,会返回一个DHCP-Decline报文,要求服务器重新获取地址。
2.2 DHCP全部报文介绍
1. DHCP DISCOVER
客户端开始DHCP过程的第一个报文,是DHCP协议的开始,是请求IP地址和其它配置参数的广播报文。
DHCP客户端初始化TCP/IP,通过UDP端口67向网络中发送一个DHCPDISCOVER广播包,来寻找局域网中的DHCP服务器,请求租用IP地址。该广播包中的源IP地址为0.0.0.0,目标IP地址为255.255.255.255;包中还包含客户机的MAC地址和计算机名。网络上所有支持TCP/IP的主机都会收到该DHCP DISCOVER报文,但是只有DHCP Server会响应该报文。
2. DHCP OFFER
服务器对DHCP DISCOVER报文的响应,是包含有效IP地址及配置的单播(或广播)报文。
任何接收到DHCPDISCOVER广播包并且能够提供IP地址的DHCP服务器,通过解析报文,查询dhcpd.conf配置文件,如果在地址池中能找到合适的IP地址,将通过UDP端口68给客户机回应一个DHCPOFFER广播包,提供一个IP地址。该广播包的源IP地址为DCHP服务器IP,目标IP地址为 255.255.255.255;包中还包含提供的IP地址、客户端的MAC地址、子网掩码、服务器的识别符及租期等信息。
如果存在多个DHCP服务器,会发送多个DHCP OFFER报文。
3. DHCP REQUEST
客户端对于服务器发出的DHCP OFFER所做出的响应,表示接受相关配置。客户端续延IP地址租期时也会发出该报文。
客户端从不止一台DHCP服务器接收到提供之后,会选择第一个收到的DHCPOFFER 包,并向网络中广播一个 DHCPREQUEST消息包,表明自己已经接受了一个DHCP服务器提供的IP地址。该广播包中包含所接受的IP地址和服务器的IP地址。 所有其他的DHCP服务器撤消它们的提供以便将IP地址提供给下一次IP租用请求。
4. DHCP DECLINE
当客户端发现服务器分配的IP地址无法使用(如IP地址冲突时),将发出此报文,通知服务器禁止使用该IP地址。
5. DHCP ACK
服务器在接收到客户端发来的DHCP REQUEST之后发出的成功确认的报文。客户端收到此报文后,才真正获得了IP地址和相关的配置信息。
被客户端选择的DHCP服务器在收到DHCPREQUEST广播后,会广播返回给客户端一个DHCPACK消息包,表明已经接受客户端的选择,并将这一IP地址的合法租用以及其他的配置信息都放入该广播包发给客户端。
6. DHCP NAK
DHCP ACK的相反报文,表示服务器拒绝了客户端的请求。服务器对客户端的DHCP REQUEST报文的拒绝响应报文。
客户端收到此报文后,会重新开始新的DHCP过程。
7. DHCP RELEASE
一般出现在客户端关机、下线等情况。这个报文将会使DHCP服务器释放发出此报文的客户端的IP地址。客户端主动释放服务器分配的IP地址。当服务器收到此报文后,则回收该IP地址,并可以将其分配给其它的客户端。
8. DHCP INFORM
客户端发出的服务器请求一些信息的报文。客户端获得IP地址后,发送此报文请求获取服务器的其它一些网络配置信息,如DNS等。
2.3 IP租用更新报文
客户端获取的IP一般是有租期,到期前需要更新租期,这个过程是通过租用更新数据包来完成的。
- 在当前租期已过去50%时,DHCP客户端直接向为其提供IP地址的DHCP服务器单播发送DHCP-REQUEST消息包,请求服务器更新租期。服务器收到以后,如果可以继续使用IP地址的话,会响应Client DHCP-ACK,如果该IP不能继续分配,则响应Client DHCP-NAK。
如果客户端接收到该服务器回应的DHCP-ACK消息包,客户端就根据包中所提供的新的租期以及其它已经更新的TCP/IP参数,更新自己的配置,IP租用更新完成。如果没收到该服务器的回复,则客户端继续使用现有的IP地址,因为当前租期还有50%。 - 如果在租期过去50%时未能成功更新,则客户端将在当前租期过去87.5%时再次向为其提供IP地址的DHCP服务器联系。如果联系不成功,则重新开始IP租用过程。
- 如果DHCP客户端重新启动时,它自动发送DHCP-REQUEST广播给DHCP服务器以便请求继续租用原来使用的IP地址。
如果服务器收到这个消息,确认客户端可以使用该地址,则回答一个DHCP-ACK确认消息;如果此IP地址已经无法再分配,则返回一个DHCP-NAK否认信息,客户端收到这个信息以后,则必须重新发送DHCP-DISCOVER消息获取新的地址。
DHCP客户机在发出IP租用请求的DHCP-DISCOVER广播包后,将花费1秒钟的时间等待DHCP服务器的回应,如果1秒钟没有服务器的回应,它会将这一广播包重新广播四次(以2,4,8和16秒为间隔,加上1~1000毫秒之间随机长度的时间)。四次之后,如果仍未能收到服务器的回应,则运行Windows 2000的DHCP客户机将从169.254.0.0/16(169.254.0.1~169.254.255.254)这个自动保留的私有IP地址(APIPA)中选用一个IP地址,而运行其他操作系统的DHCP 客户机将无法获得IP地址。DHCP客户机仍然每隔5分钟重新广播一次,如果收到某个服务器的回应,则继续IP租用过程。
2.4 DHCP协议抓包分析
- Dhcp Discover:获取DHCP服务器IP,广播包 0.0.0.0:68 <-> 255.255.255.255:67,67、68端口为DHCP协议端口。
- DHCP Offer:返回DHCP服务器地址+分配给客户端的IP。
- DHCP request:请求分配IP地址,请求的IP为DHCP-discover分配的IP。
- DHCP ACK:分配ip地址成功。
3 DHCP应用
DNSmasq是一个小巧且方便地用于配置DNS和DHCP的工具,适用于小型网络,它提供了DNS功能和可选择的DHCP功能。它服务那些只在本地适用的域名,这些域名是不会在全球的DNS服务器中出现的。DHCP服务器和DNS服务器结合,并且允许DHCP分配的地址能在DNS中正常解析,而这些DHCP分配的地址和相关命令可以配置到每台主机中,也可以配置到一台核心设备中(比如路由器),DNSmasq支持静态和动态两种DHCP配置方式。
3.1 DNSmasq参数配置
我们的绝大部分模组使用linux原生的DNSmasq提供ipv4的dhcp分配服务。以下为我们大部分模组正在使用的配置参数。
主要参数为:
- dhcp-range:dhcp地址池配置
- -i 绑定接口br-lan,即只在br-lan口提供dhcp服务
- -K PC端请求地址池外的地址时,是否发送NAK,让PC重新获取IP地址
DNSmasq支持的全部参数见 dnsmasq option.c
3.2 DNSmasq框架代码
DNSmasq提供dhcpv4、dhcpv6、dns等多种功能,我们这里只关注dhcpv4功能。DNSmasq dhcpv4的基本原理是创建监听本机67端口,接收外界DHCP协议报文,并处理。
3.2.1 创建socket监听67端口
dnsmasq.c:main() -> dhcp_init() ->make_fd()
main()
-> dhcp_init()
->make_fd()
上图中的port是dhcp_init中传参daemon->dhcp_server_port
3.2.2 监听67端口
- Dnsmasq.c:main() 中调用 poll_listen 将创建好的dhcp_fd添加到pollfds数组中
- do_poll 监听poll_fds中所有socket的事件
3.2.3 处理DHCP请求
- Dnsmasq监听67端口,当收到PC dhcp报文时,会调用dhcp_packet->dhcp_replay处理dhcp数据包。
- 具体的dhcp报文处理流程都在rfc2131.c dhcp_replay中,直接搜索DHCPDISCOVER找到对应的报文的处理逻辑
代码逻辑非常多,但原理比较简单,主要是根据DHCP协议的标准,响应对应的报文。这里不做赘述。
3.3 DNSmasq模块排障方法
- 查看dnsmasq模块日志,/etc/dnsmasq.conf配置中增加如下配置,并重启dnsmasq,可以查看日志,但是dnsmasq日志较少,只有关键错误才会打印日志。
log-queries
log-facility=/var/log/dnsmasq.log
查看log方式:
a. cat /var/log/messages|grep dnsmasq
b. cat /var/log/dnsmasq.log
- DHCP协议的处理过程都在 rfc2131.c dhcp_replay函数中、可以在具体的报文处理逻辑中添加日志,定位具体原因。
4 常见问题排查
4.1 问题分类
DHCP问题可以分为如下3类问题: 上位机问题、PC不发起DHCP请求;网络问题,dnsmasq未收到DHCP请求;dnsmasq问题,dnsmasq不响应DHCP请求。
PC和bridge0口同时抓包,并根据数据包确定问题分类(注:模组的抓包网卡、需要以具体的网络拓扑为准)
- PC端抓包抓不到DHCP请求:判断为上位机不发起dhcp请求。
- PC端抓到DHCP请求,bridge0抓不到:判断为网络问题。
- PC端和bridge0都能抓到包:判断为dnsmasq不响应DHCP请求。
4.2 上位机不发起DHCP请求
- 检查上位机是否开启了DHCP:上位机PC安装了DHCP管控软件,当反复插拔设备USB时,PC会主动关闭了DHCP开关。
- 模组DHCP配置变更时,是否有执行链路的Down/Up
模组DHCP配置变更时,会通过触发链路的Down/Up,触发PC重新发起DHCP请求;RNDIS拨号情况下,是通过 OEM_CTRL_RNDIS_MEDIA_DISCONNECT请求触发rndis0口的DOWN/UP,触发PC重新发起获取IP请求;物理网卡情况下,高通的QCMAP_CLI是通过 ifconfig down/up 触发链路的DOWN/UP,OEM是通过 ethtool -s eth0 autoneg off/on 触发链路的DOWN/UP。
kernel/msm-5.15 / drivers/usb/gadget/function/f_gsi.c
static long gsi_ctrl_dev_ioctl(struct file *fp, unsigned int cmd,
unsigned long arg)
{
…
switch (cmd) {
…
case OEM _CTRL_RNDIS_MEDIA_DISCONNECT:
{
rndis_set_param_medium(gsi->params, RNDIS_MEDIUM_802_3, 0);
rndis_signal_disconnect(gsi->params);
printk(KERN_DEBUG "%s: OEM _CTRL_RNDIS_MEDIA_DISCONNECT\n",__func__);
break;
}
case OEM_CTRL_RNDIS_MEDIA_CONNECT:
{
gsi_rndis_open(gsi);
printk(KERN_DEBUG "%s: OEM_CTRL_RNDIS_MEDIA_CONNECT \n",__func__);
break;
}
…
}
4.3 网络环境问题
PC端发起了DHCP请求,但是bridge0口抓不到包,可以判断为网络环境问题(注:需要先梳理下拓扑,大部分情况下为bridge0口收包);排查方法为梳理网络拓扑、并在关键节点抓包;可以参考下网络问题排查类的排障文档
4.4 dnsmasq不响应DHCP请求
4.4.1 确认是否为配置问题
收集正常(可以获取IP)和异常(无法获取IP)情况下,dnsmasq的配置信息,并对比。ps www|grep dnsmasq并逐一对比以下配置
4.4.2 检查dnsmasq日志,确认是否存在明显报错
日志添加方式,参考Dnsmasq模块排障方法章节
4.4.3 检查dnsmasq协议处理流程中增加日志,确认未响应DHCP报文原因
具体的dhcp报文处理流程都在rfc2131.c dhcp_replay中,直接搜索DHCPDISCOVER找到对应的报文的处理逻辑,添加日志,并抓取正常和异常情况的日志,对比确认,未响应DHCP请求的原因
5 典型案例分享
问题现象: PC获取不到IP地址
排障过程
- PC端抓包分析
PC端抓包:dhcp discover请求无响应
- 确认是否为网络不通导致
Bridge0抓包:bridge0口收到了PC的dhcp discover请求
- 确认是否为dnsmasq配置问题
收集正常(可以获取IP)和异常(无法获取IP)情况下,dnsmasq的配置信息,并对比
ps www|grep dnsmasq并逐一对比以下配置
对比结果:配置完全一致,非配置原因导致 - 调试dnsmasq-discover报文处理流程,确认丢包原因
调试结果:没走到dnsmasq处理DHCPDISCOVER的逻辑中 - 确认poll_check是否检测到dhcp收包事件
上面已经介绍过,dnsmsasq框架代码基本逻辑为,收集所有fd, 插入到pollfd数组中,并poll 监听所有事件
a.打印出所有pollfd及事件
结果显示:dhcpfd没有收包事件产生
Brige0收到了数据包,但dhcpfd没有被设置收包事件POLLIN事件。两个思路排查:
a. 收包角度:从linux kernel netif_receivce_skb收包开始打印整个收包流程,定位丢包点,并分析原因,耗时较长
b. 排查socket、setsockopt、bind、poll等函数
先尝试排查是否是socket相关接口导致 - socket相关接口做基本排查
a. socket():dnsmasq日志打印dhcpfd = 10, ls -l /proc/pid/fd查看dhcpfd是否正常
b. 查看bind是否成功:
Netstat查看所有监听端口,67端口监听成功
c. setsockopt排查:
setsockopt嫌疑比较大的接口 SO_BINDTODEVICE参数
检查dnsmasq日志是否有相关报错
检查结果:无相关报错
注释掉 bindtodevice的调用处,测试问题不复现
- 梳理 setsockopt 源码,由 kernel net/core/sock.c 将socket绑定到对应网口
原理:根据网口名(br-lan) 获取net结构体,并找到将网口的ifindex记录到sock结构体中
- 内核增加日志:打印出绑定的具体信息,用于确认,将dhcpfd绑定到了哪个网口
原理:根据网口名(br-lan) 获取net结构体,并找到将网口的ifindex记录都socket中
结果显示:将dhcpfd绑定到了br-lan接口ifindex == 25
- ip link show 确认br-lan接口的最新信息: br-lan的 index已经变成了28
网卡的ifIndex 只有在物理硬件设备断开重连后才会更新,分析应用层代码,确认是否有会导致网卡断开重连的操作。
分析结果:USB_STOP 会触发 rndis0接口重新加载, rndis接口是br-lan的子接口,USB_STOP和USB_START都会触发br-lan接口更新
#define USB_STOP "sbin/start_usb stop"
#define USB_START "sbin/start_usb start"
将USB_START USB_STOP改为 OEM_CTRL_RNDIS_MEDIA_CONNECT/DISCONNECT消息后,问题修复。