该项目的目的是:
- 了解 IPv4 网络上的 DHCP
- 学习Python(比从头开始多一点😉)
- 服务器更换
DB2DHCP (我的叉子),原创这里 ,为新操作系统组装变得越来越困难。 我不喜欢它是一个二进制文件,无法“立即更改” - 获取工作 DHCP 服务器,该服务器能够使用订户的 MAC 或交换机 MAC+端口组合选择订户的 IP 地址(选项 82)
- 写另一辆自行车(哦!这是我最喜欢的活动)
- 在 Habrahabr 上收到有关您的俱乐部惯用手的评论(或者更好的是,邀请)😉
结果:它有效 😉 在 FreeBSD 和 Ubuntu 操作系统上进行了测试。 理论上,可以要求代码在任何操作系统下工作,因为代码中似乎没有具体的绑定。
小心! 还有很多事情要做。
链接到业余爱好者的存储库
安装、配置和使用“研究硬件”结果的过程就低了很多,然后再讲一点关于DHCP协议的理论。 为了我自己。 对于历史😉
一点理论
什么是 DHCP
这是一种网络协议,允许设备从 DHCP 服务器查找其 IP 地址(以及网关、DNS 等其他参数)。 数据包使用 UDP 协议进行交换。 设备请求网络参数时的总体工作原理如下:
- 设备(客户端)在整个网络中发送 UDP 广播请求 (DHCPDISCOVER),请求“好吧,有人给我一个 IP 地址”。 此外,通常(但并非总是)请求从端口 68(源)发出,目的地是端口 67(目的地)。 有些设备还从端口 67 发送数据包。 客户端设备的 MAC 地址包含在 DHCPDISCOVER 包内。
- 位于网络上的所有 DHCP 服务器(可能有多个)都会形成一个 DHCPOFFER 报价,其中包含发送 DHCPDISCOVER 的设备的网络设置,并通过网络广播它。 根据先前在 DHCPDISCOVER 请求中提供的客户端 MAC 地址来识别此数据包的目标用户。
- 客户端接受带有网络设置建议的数据包,选择最有吸引力的一个(标准可能不同,例如数据包传递的时间、中间路由的数量),并使用网络设置发出“正式请求”DHCPREQUEST从它喜欢的 DHCP 服务器。 在这种情况下,数据包将发送到特定的 DHCP 服务器。
- 收到 DHCPREQUEST 的服务器发送 DHCPACK 格式的数据包,其中再次列出适用于该客户端的网络设置
此外,还有来自客户端的 DHCPINFORM 数据包,其目的是通知 DHCP 服务器“客户端处于活动状态”并且正在使用已发布的网络设置。 在此服务器的实现中,这些数据包将被忽略。
封装格式
一般来说,以太网数据包帧看起来像这样:
在我们的例子中,我们将仅考虑直接来自 UDP 数据包内容的数据,而不考虑 OSI 层协议标头,即 DHCP 结构:
DHCP发现
因此,获取设备 IP 地址的过程始于 DHCP 客户端从端口 68 向 255.255.255.255:67 发送广播请求。 在此数据包中,客户端包含其 MAC 地址,以及它希望从 DHCP 服务器接收的确切内容。 封装结构如下表所示。
DHCPDISCOVER数据包结构表
在包装中的位置
值名称
例子
主意
字节
澄清
1
启动请求
1
十六进制
1
消息类型。 1 - 客户端向服务器发出请求,2 - 服务器向客户端发出响应
2
硬件类型
1
十六进制
1
硬件地址类型,在此协议中1 - MAC
3
硬件地址长度
6
十六进制
1
设备MAC地址长度
4
酒花
1
十六进制
1
中间路线数量
5
交易ID
23:比照:德:1d
十六进制
4
唯一的交易标识符。 由客户端在请求操作开始时生成
7
秒过去了
0
十六进制
4
从获取地址过程开始的时间(以秒为单位)
9
引导标志
0
十六进制
2
可以设置某些标志来指示协议参数
11
客户端 IP 地址
0.0.0.0
行
4
客户端 IP 地址(如果有)
15
您的客户端IP地址
0.0.0.0
行
4
服务器提供的 IP 地址(如果可用)
19
下一个服务器IP地址
0.0.0.0
行
4
服务器 IP 地址(如果已知)
23
中继代理IP地址
172.16.114.41
行
4
中继代理(例如交换机)的IP地址
27
客户端MAC地址
14:d6:4d:a7:c9:55
十六进制
6
数据包发送者(客户端)的MAC地址
31
客户端硬件地址填充
十六进制
10
预留座位。 通常用零填充
41
服务器主机名
行
64
DHCP 服务器名称。 通常不传送
105
启动文件名
行
128
无盘工作站启动时使用的服务器上的文件名
235
魔法饼干
63:82:53:63
十六进制
4
“神奇”数字,根据该数字,包括。 可以判断这个数据包属于DHCP协议
DHCP 选项。 可以按任意顺序进行
236
选项编号
53
十二月
1
选项53,指定DHCP数据包类型
1 - DHCP发现
3 - DHCP 请求
2 - DHCP 报价
5 - DHCPACK
8 - DHCP 信息
选项长度
1
十二月
1
期权价值
1
十二月
1
选项编号
50
十二月
1
客户端想要接收什么IP地址?
选项长度
4
十二月
1
期权价值
172.16.134.61
行
4
选项编号
55
1
客户端请求的网络参数。 成分可能有所不同
01 — 网络掩码
03 - 网关
06-DNS
oc — 主机名
0f——网络域名
1c - 广播请求的地址(广播)
42 - TFTP 服务器名称
79 - 无类静态路由
选项长度
8
1
期权价值
01:03:06:0c:0f:1c:42:79
8
选项编号
82
十二月
选项 82,传输中继器设备的 MAC 地址和一些附加值。
最常见的是,这是运行最终 DHCP 客户端的交换机端口。此选项包含附加参数。第一个字节是“子选项”的编号,第二个字节是其长度,然后是其值。
在本例中,在选项 82 中,子选项是嵌套的:
代理电路 ID = 00:04:00:01:00:04,其中最后两个字节是发出请求的 DHCP 客户端端口
代理远程 ID = 00:06:c8:be:19:93:11:48 - DHCP 中继器设备的 MAC 地址
选项长度
18
十二月
期权价值
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
十六进制
包装结束
255
十二月
1
255表示数据包结束
DHCP提供
一旦服务器收到 DHCPDISCOVER 数据包,并且如果它发现可以向客户端提供所请求的内容,那么它就会为其生成一个响应 - DHCPDISCOVER。 响应通过广播发送到“来自它来的地方”的端口,因为此时客户端还没有IP地址,因此只能接受广播发送的数据包。 客户端通过包内的 MAC 地址以及创建第一个包时生成的交易号来识别这是他的包。
DHCPOFFER数据包结构表
在包装中的位置
值的名称(通用)
例子
主意
字节
澄清
1
启动请求
1
十六进制
1
消息类型。 1 - 客户端向服务器发出请求,2 - 服务器向客户端发出响应
2
硬件类型
1
十六进制
1
硬件地址类型,在此协议中1 - MAC
3
硬件地址长度
6
十六进制
1
设备MAC地址长度
4
酒花
1
十六进制
1
中间路线数量
5
交易ID
23:比照:德:1d
十六进制
4
唯一的交易标识符。 由客户端在请求操作开始时生成
7
秒过去了
0
十六进制
4
从获取地址过程开始的时间(以秒为单位)
9
引导标志
0
十六进制
2
可以设置某些标志来指示协议参数。 在这种情况下,0表示单播请求类型
11
客户端 IP 地址
0.0.0.0
行
4
客户端 IP 地址(如果有)
15
您的客户端IP地址
172.16.134.61
行
4
服务器提供的 IP 地址(如果可用)
19
下一个服务器IP地址
0.0.0.0
行
4
服务器 IP 地址(如果已知)
23
中继代理IP地址
172.16.114.41
行
4
中继代理(例如交换机)的IP地址
27
客户端MAC地址
14:d6:4d:a7:c9:55
十六进制
6
数据包发送者(客户端)的MAC地址
31
客户端硬件地址填充
十六进制
10
预留座位。 通常用零填充
41
服务器主机名
行
64
DHCP 服务器名称。 通常不传送
105
启动文件名
行
128
无盘工作站启动时使用的服务器上的文件名
235
魔法饼干
63:82:53:63
十六进制
4
“神奇”数字,根据该数字,包括。 可以判断这个数据包属于DHCP协议
DHCP 选项。 可以按任意顺序进行
236
选项编号
53
十二月
1
选项 53,定义 DHCP 2 数据包类型 - DHCPOFFER
选项长度
1
十二月
1
期权价值
2
十二月
1
选项编号
1
十二月
1
为 DHCP 客户端提供网络掩码的选项
选项长度
4
十二月
1
期权价值
255.255.224.0
行
4
选项编号
3
十二月
1
为 DHCP 客户端提供默认网关的选项
选项长度
4
十二月
1
期权价值
172.16.12.1
行
4
选项编号
6
十二月
1
向 DNS 客户端提供 DHCP 的选项
选项长度
4
十二月
1
期权价值
8.8.8.8
行
4
选项编号
51
十二月
1
已发布网络参数的生命周期(以秒为单位),之后 DHCP 客户端必须再次请求它们
选项长度
4
十二月
1
期权价值
86400
十二月
4
选项编号
82
十二月
1
选项 82,重复 DHCPDISCOVER 中的内容
选项长度
18
十二月
1
期权价值
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:欧共体
十二月
18
包装结束
255
十二月
1
255表示数据包结束
DHCP请求
客户端收到 DHCPOFFER 后,会形成一个数据包,不向网络上的所有 DHCP 服务器请求网络参数,而仅向他最“喜欢”的特定一台 DHCP 服务器请求网络参数。 “喜欢”标准可以不同,并且取决于客户端的 DHCP 实现。 使用 DHCP 服务器的 MAC 地址指定请求的接收者。 此外,如果之前已经获得了服务器的 IP 地址,则客户端可以发送 DHCPREQUEST 数据包,而无需先生成 DHCPDISCOVER。
DHCPREQUEST数据包结构表
在包装中的位置
值的名称(通用)
例子
主意
字节
澄清
1
启动请求
1
十六进制
1
消息类型。 1 - 客户端向服务器发出请求,2 - 服务器向客户端发出响应
2
硬件类型
1
十六进制
1
硬件地址类型,在此协议中1 - MAC
3
硬件地址长度
6
十六进制
1
设备MAC地址长度
4
酒花
1
十六进制
1
中间路线数量
5
交易ID
23:比照:德:1d
十六进制
4
唯一的交易标识符。 由客户端在请求操作开始时生成
7
秒过去了
0
十六进制
4
从获取地址过程开始的时间(以秒为单位)
9
引导标志
8000
十六进制
2
可以设置某些标志来指示协议参数。 在这种情况下,设置“广播”
11
客户端 IP 地址
0.0.0.0
行
4
客户端 IP 地址(如果有)
15
您的客户端IP地址
172.16.134.61
行
4
服务器提供的 IP 地址(如果可用)
19
下一个服务器IP地址
0.0.0.0
行
4
服务器 IP 地址(如果已知)
23
中继代理IP地址
172.16.114.41
行
4
中继代理(例如交换机)的IP地址
27
客户端MAC地址
14:d6:4d:a7:c9:55
十六进制
6
数据包发送者(客户端)的MAC地址
31
客户端硬件地址填充
十六进制
10
预留座位。 通常用零填充
41
服务器主机名
行
64
DHCP 服务器名称。 通常不传送
105
启动文件名
行
128
无盘工作站启动时使用的服务器上的文件名
235
魔法饼干
63:82:53:63
十六进制
4
“神奇”数字,根据该数字,包括。 可以判断这个数据包属于DHCP协议
DHCP 选项。 可以按任意顺序进行
236
选项编号
53
十二月
3
选项 53,定义 DHCP 数据包类型 3 - DHCPREQUEST
选项长度
1
十二月
1
期权价值
3
十二月
1
选项编号
61
十二月
1
客户端 ID:01(以太网)+ 客户端 MAC 地址
选项长度
7
十二月
1
期权价值
01:2c:ab:25:ff:72:a6
十六进制
7
选项编号
60
十二月
“供应商类别标识符”。 就我而言,它报告 DHCP 客户端版本。 也许其他设备返回不同的东西。 Windows 例如报告 MSFT 5.0
选项长度
11
十二月
期权价值
0.9.8
行
选项编号
55
1
客户端请求的网络参数。 成分可能有所不同
01 — 网络掩码
03 - 网关
06-DNS
oc — 主机名
0f——网络域名
1c - 广播请求的地址(广播)
42 - TFTP 服务器名称
79 - 无类静态路由
选项长度
8
1
期权价值
01:03:06:0c:0f:1c:42:79
8
选项编号
82
十二月
1
选项 82,重复 DHCPDISCOVER 中的内容
选项长度
18
十二月
1
期权价值
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:欧共体
十二月
18
包装结束
255
十二月
1
255表示数据包结束
DHCP确认
作为从 DHCP 服务器确认“是的,没错,这是您的 IP 地址,我不会将其提供给其他任何人”的确认,从服务器到客户端提供 DHCPACK 格式的数据包。 它像其他数据包一样以广播方式发送。 不过,在下面用 Python 实现的 DHCP 服务器的代码中,为了以防万一,我通过将数据包发送到特定的客户端 IP(如果已知)来复制任何广播请求。 而且,DHCP服务器根本不关心DHCPACK报文是否已经到达客户端。 如果客户端没有收到 DHCPACK,那么一段时间后它会简单地重复 DHCPREQUEST
DHCPACK数据包结构表
在包装中的位置
值的名称(通用)
例子
主意
字节
澄清
1
启动请求
2
十六进制
1
消息类型。 1 - 客户端向服务器发出请求,2 - 服务器向客户端发出响应
2
硬件类型
1
十六进制
1
硬件地址类型,在此协议中1 - MAC
3
硬件地址长度
6
十六进制
1
设备MAC地址长度
4
酒花
1
十六进制
1
中间路线数量
5
交易ID
23:比照:德:1d
十六进制
4
唯一的交易标识符。 由客户端在请求操作开始时生成
7
秒过去了
0
十六进制
4
从获取地址过程开始的时间(以秒为单位)
9
引导标志
8000
十六进制
2
可以设置某些标志来指示协议参数。 在这种情况下,设置“广播”
11
客户端 IP 地址
0.0.0.0
行
4
客户端 IP 地址(如果有)
15
您的客户端IP地址
172.16.134.61
行
4
服务器提供的 IP 地址(如果可用)
19
下一个服务器IP地址
0.0.0.0
行
4
服务器 IP 地址(如果已知)
23
中继代理IP地址
172.16.114.41
行
4
中继代理(例如交换机)的IP地址
27
客户端MAC地址
14:d6:4d:a7:c9:55
十六进制
6
数据包发送者(客户端)的MAC地址
31
客户端硬件地址填充
十六进制
10
预留座位。 通常用零填充
41
服务器主机名
行
64
DHCP 服务器名称。 通常不传送
105
启动文件名
行
128
无盘工作站启动时使用的服务器上的文件名
235
魔法饼干
63:82:53:63
十六进制
4
“神奇”数字,根据该数字,包括。 可以判断这个数据包属于DHCP协议
DHCP 选项。 可以按任意顺序进行
236
选项编号
53
十二月
3
选项 53,定义 DHCP 数据包类型 5 - DHCPACK
选项长度
1
十二月
1
期权价值
5
十二月
1
选项编号
1
十二月
1
为 DHCP 客户端提供网络掩码的选项
选项长度
4
十二月
1
期权价值
255.255.224.0
行
4
选项编号
3
十二月
1
为 DHCP 客户端提供默认网关的选项
选项长度
4
十二月
1
期权价值
172.16.12.1
行
4
选项编号
6
十二月
1
向 DNS 客户端提供 DHCP 的选项
选项长度
4
十二月
1
期权价值
8.8.8.8
行
4
选项编号
51
十二月
1
已发布网络参数的生命周期(以秒为单位),之后 DHCP 客户端必须再次请求它们
选项长度
4
十二月
1
期权价值
86400
十二月
4
选项编号
82
十二月
1
选项 82,重复 DHCPDISCOVER 中的内容
选项长度
18
十二月
1
期权价值
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:欧共体
十二月
18
包装结束
255
十二月
1
255表示数据包结束
安装
安装实际上包括安装工作所需的 python 模块。 假设 MySQL 已经安装并配置。
FreeBSD的
pkg 安装 python3 python3 -m Ensurepip pip3 安装 mysql-connector
Ubuntu
sudo apt-get 安装 python3 sudo apt-get 安装 pip3 sudo pip3 安装 mysql-connector
我们创建一个 MySQL 数据库,将 pydhcp.sql 转储上传到其中,并配置配置文件。
布局
所有服务器设置都位于 xml 文件中。 参考文件:
1.0 0.0.0.0 255.255.255.255 192.168.0.71 8600 1 255.255.255.0 192.168.0.1 本地主机测试测试pydhcp option_8.8.8.8_hex:sw_port82:1:20 option_22_hex:sw_port82:2:16 option_18_hex:sw_mac:82:26 40 从用户中选择 ip、掩码、路由器、dns,其中 upper(mac)=upper('{option_3_AgentRemoteId_hex}') 和 upper(port)=upper('{option_1_AgentCircuitId_port_hex}') 从用户中选择 ip、掩码、路由器、dns,其中 upper(mac)=upper('{sw_mac}') 和 upper(port)=upper('{sw_port82}') 从用户中选择 ip、掩码、路由器、dns,其中 upper(mac)=upper('{ClientMacAddress}') 插入历史记录(id,dt,mac,ip,comment)值(null,now(),'{ClientMacAddress}','{RequestedIpAddress}','DHCPACK/INFORM')
现在更详细地了解标签:
dhcpserver 部分描述了启动服务器的基本设置,即:
- host - 服务器在端口 67 上侦听的 IP 地址
- 广播 - 哪个 ip 是 DHCPOFFER 和 DHCPACK 的广播
- DHCPServer - DHCP 服务器的 ip 是什么
- LeaseTime 下发IP地址的租用时间
- ThreadLimit - 有多少线程同时运行来处理端口 67 上传入的 UDP 数据包。它应该有助于高负载项目😉
- defaultMask,defaultRouter,defaultDNS - 如果在数据库中找到 IP,但未为其指定其他参数,则默认情况下向订阅者提供的内容
mysql部分:
主机、用户名、密码、基本名称——一切都是不言而喻的。 大致的数据库结构发布在
查询部分:接收OFFER/ACK的请求描述如下:
- Offer_count — 返回 ip、mask、router、dns 等结果的请求的行数
- Offer_n — 查询字符串。 如果return为空,则执行下面的offer请求
- History_sql - 例如,写入订阅者的“授权历史记录”的查询
请求可以包含选项部分中的任何变量或 DHCP 协议中的选项。
选项部分。 这就是事情变得更有趣的地方。 在这里我们可以创建稍后在查询部分中使用的变量。
例如:
option_82_hex:sw_port1:20:22
,此命令行采用 DHCP 请求选项 82 中的整行(十六进制格式),范围为 20 到 22 字节(含),并将其放入新变量 sw_port1(请求来自的交换机端口)中
option_82_hex:sw_mac:26:40
,定义 sw_mac 变量,从范围 26:40 中获取十六进制
您可以通过使用 -d 开关启动服务器来查看可在查询中使用的所有可能选项。 我们会看到类似这样的日志:
-- 一个 DHCPINFORM 数据包到达端口 67,来自 0025224ad764 , b'x91xa5xe0xa3xa5xa9-x8fx8a' , ('172.30.114.25', 68) {'ClientMacAddress': '0025224ad764', 'ClientMacAddressByte': b'x00 7%"Jx d91d' , ' HType': '以太网', '主机名': b'x5xa0xe3xa5xa9xa8-x8fx43a', 'ReqListDNS': True, 'ReqListDomainName': True, 'ReqListPerfowmRouterDiscover': True, 'ReqListRouter': True, 'ReqListStaticRoute': True, 'ReqListSubnetM 问':True,'ReqListVendorSpecInfo':0.0.0.0,'RequestedIpAddress':'5.0','供应商':b'MSFT 0025224','chaddr':'764ad172.30.128.13','ciaddr':'00' ,'flags':b'x00x172.30.114.25','giaddr':'308','gpoz':6,'hlen':1,'hops':82,'htype':'MAC','magic_cookie': b'cx12Sc','op':'DHCPINFORM','选项12':53,'选项53':55,'选项55':60,'选项60':61,'选项61':82,'选项82':82,' option_12_byte':b'x01x06x00x04x00x01x00x06x02x08x00x06'b'x00x1x9eXx2exb82xad','option_12010600040001000602080006001_hex':'589e2eb82ad','选项_18_len':82 12,'option_01_str':“b'x06x00x04x00x01x00x06x02x08x00x06x00x1x9x2eXx768exb0.0.0.0xad'”,'结果':假,'秒':001, 'siaddr':'589','sw_mac':'2e1eb06ad','sw_port89':'8','xidbyte':b'
因此,我们可以将任何变量包装在 {} 中,它将在 SQL 查询中使用。
让我们记录客户端收到 IP 地址的历史记录:
启动服务器
./pydhcpdb.py -d -c config.xml
--d 控制台输出模式DEBUG
-c <文件名> 配置文件
述职
现在更多关于用 Python 实现服务器的细节。 这是一种痛苦。 Python 是即时学习的。 很多时刻都是以“哇,不知怎的,我成功了”的风格制作的。 根本没有优化,主要是因为Python开发经验很少,所以才留下这样的形式。 我将在“代码”中详细讨论服务器实现中最有趣的方面。
XML配置文件解析器
使用标准 Python 模块 xml.dom。 这看起来很简单,但在实现过程中,网络上明显缺乏使用该模块的清晰文档和示例。
tree = minidom.parse(gconfig["config_file"]) mconfig=tree.getElementsByTagName("mysql") for elem in mconfig: gconfig["mysql_host"]=elem.getElementsByTagName("host")[0].firstChild.data gconfig["mysql_username"]=elem.getElementsByTagName("用户名")[0].firstChild.data gconfig["mysql_password"]=elem.getElementsByTagName("password")[0].firstChild.data gconfig["mysql_basename"] =elem.getElementsByTagName("basename")[0].firstChild.data dconfig=tree.getElementsByTagName("dhcpserver") 对于 dconfig 中的 elem:gconfig["broadcast"]=elem.getElementsByTagName("broadcast")[0]。 firstChild.data gconfig["dhcp_host"]=elem.getElementsByTagName("主机")[0].firstChild.data gconfig["dhcp_LeaseTime"]=elem.getElementsByTagName("LeaseTime")[0].firstChild.data gconfig[" dhcp_ThreadLimit"]=int(elem.getElementsByTagName("ThreadLimit")[0].firstChild.data) gconfig["dhcp_Server"]=elem.getElementsByTagName("DHCPServer")[0].firstChild.data gconfig["dhcp_defaultMask"] =elem.getElementsByTagName("defaultMask")[0].firstChild.data gconfig["dhcp_defaultRouter"]=elem.getElementsByTagName("defaultRouter")[0].firstChild.data gconfig["dhcp_defaultDNS"]=elem.getElementsByTagName(" defaultDNS")[0].firstChild.data qconfig=tree.getElementsByTagName("query") for elem in qconfig: gconfig["offer_count"]=elem.getElementsByTagName("offer_count")[0].firstChild.data for num in range(int(gconfig["offer_count"])): gconfig["offer_"+str(num+1)]=elem.getElementsByTagName("offer_"+str(num+1))[0].firstChild.data gconfig ["history_sql"]=elem.getElementsByTagName("history_sql")[0].firstChild.data options=tree.getElementsByTagName("options") 对于选项中的 elem:node=elem.getElementsByTagName("option") 对于节点中的选项: optionsMod.append(options.firstChild.data)
多线程
奇怪的是,Python 中的多线程实现非常清晰和简单。
def PacketWork(data,addr): ... # 解析传入数据包并响应它的实现 ... while True: data, addr = udp_socket.recvfrom(1024) # 等待 UDP 数据包 thread = threading.Thread( target=PacketWork , args=(data,addr,)).start() # 原样 - 我们在后台使用参数启动先前定义的 PacketWork 函数 while threading.active_count() >gconfig["dhcp_ThreadLimit"]: time. sleep(1) # 如果已经运行的线程数比设置的多,我们就等待,直到线程数减少
接收/发送 DHCP 数据包
为了拦截来自网卡的 UDP 数据包,您需要“提升”套接字:
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP) udp_socket.bind((gconfig["dhcp_host"],67))
,其中标志是:
- AF_INET - 表示地址格式为 IP: 端口。 还可能有 AF_UNIX - 其中地址由文件名给出。
- SOCK_DGRAM - 意味着我们不接受“原始数据包”,而是接受已经通过防火墙且经过部分修剪的数据包。 那些。 我们只收到一个 UDP 数据包,没有 UDP 数据包包装器的“物理”组件。 如果您使用 SOCK_RAW 标志,那么您还需要解析这个“包装器”。
发送数据包可以像广播一样:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #将socket切换为广播模式 rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68))
,以及“包裹来自哪里”的地址:
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 将套接字切换为多监听模式 rz=udp_socket.sendto(packetack, addr)
,其中 SOL_SOCKET 表示设置选项的“协议级别”,
,SO_BROADCAST选项表示头盔包是“广播”的
,SO_REUSEADDR 选项将套接字切换到“多侦听器”模式。 理论上,在这种情况下是不必要的,但在我测试的其中一台 FreeBSD 服务器上,如果没有此选项,代码将无法工作。
解析 DHCP 数据包
这就是我真正喜欢Python的地方。 事实证明,开箱即用的它可以让您非常灵活地使用字节码。 允许它很容易地转换成十进制值、字符串和十六进制——即这就是我们实际需要了解的包的结构。 因此,例如,您可以获取十六进制字节范围并且仅获取字节:
res["xidhex"]=数据[4:8].hex() res["xidbyte"]=数据[4:8]
,将字节打包到一个结构中:
res["flags"]=pack('BB',数据[10],数据[11])
从结构中获取IP:
res["ciaddr"]=socket.inet_ntoa(pack('BBBB',数据[12],数据[13],数据[14],数据[15]));
反之亦然:
res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])
这就是现在的全部内容 😉
来源: habr.com