Python中的DHCP + Mysql服务器



该项目的目的是:

  • 通过IPv4网络学习DHCP
  • 学习Python(仅是从零开始;)
  • 替换了原来的DB2DHCP服务器(我的叉子),在新的OS下组装起来越来越难。 是的,我不喜欢该二进制文件,无法“立即更改”
  • 获得可以使用订户的mac或一堆mac交换机+端口选择订户IP地址的功能的DHCP服务器(选项82)
  • 写另一辆自行车(哦!这是我最喜欢的消遣)
  • 让Lyuli谈论他对Habrahabr的s眼(或最好邀请);)

结果:它有效;)在FreeBSD和Ubuntu OS上测试。 从理论上讲,可以要求代码在任何操作系统下都可以工作,因为 代码中没有特定的绑定。
注意事项 还有很多。

链接到存储库,以使粉丝“动起来”

安装,配置和使用“硬件研究”结果的过程要少得多,其次是有关DHCP协议的一些理论。 为了我自己。 对于这个故事;)

一点理论


什么是DHCP?


这是一种网络协议,允许设备从DHCP服务器中找到其IP地址(以及其他参数,如网关,DNS等)。 数据包通过UDP交换。 请求网络参数时设备的一般操作原理如下:

  1. 设备(客户端)通过网络发出UDP广播请求(DHCPDISCOVER),请求为“嗯,有人,给我IP地址”。 通常(但并非总是),请求来自端口68(源),而目的地是端口67(目的地)。 一些设备还从端口67发送数据包。 在DHCPDISCOVER数据包内,包括客户端设备的MAC地址。
  2. 网络上所有(可能有多个)DHCP服务器的形式,是发送带有网络设置的DHCPDISCOVER DHCPOFFER语句的设备,它也通过网络广播该设备。 此数据包旨在提供给谁的标识将到达DHCPDISCOVER请求中较早提供的客户端的MAC地址。
  3. 客户端接收具有网络设置提议的数据包,选择最具吸引力的数据包(条件可能有所不同,例如,根据数据包传递的时间,中间路由的数量),并使DHCP服务器通过网络设置“正式请求” DHCPREQUEST。 在这种情况下,数据包将到达特定的DHCP服务器。
  4. 收到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交易编号23:比照:de:1天十六进制4唯一交易标识符。 由客户端在请求操作开始时生成
7秒逝去0十六进制4从获取地址开始的时间(以秒为单位)
9Bootp标志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服务器主机名弦乐64DHCP服务器的名称。 通常不传送
105启动文件名弦乐128启动期间无盘工作站使用的服务器上的文件名
235魔术饼干63:82:53:63十六进制4“魔术”数字,包括。 您可以确定此数据包属于DHCP协议
DHCP选项。 可以按任何顺序进行
236选件编号53十二月1个选项53指定DHCP数据包的类型

1-DHCP发现
3-DHCP请求
2-DHCPOFFER
5-DHCPACK
8-DHCPINFORM
选项长度1个十二月1个
期权价值1个十二月1个
选件编号50十二月1个客户想要接收什么IP地址
选项长度4十二月1个
期权价值172.16.134.61弦乐4
选件编号551个客户端请求的网络参数。 成分可能不同

01-网络掩码
03-网关
06-DNS
oc-主机名
0f-网络域名
1c-广播请求的地址(广播)
42-TFTP服务器名称
79-无类静态路由
选项长度81个
期权价值01:03:06:0c:0f:1c:42:798
选件编号82十二月选项82,其中转发器设备的MAC地址和一些其他值被传输。

最常见的是运行DHCP终端客户端的交换机端口,此选项“嵌入”了其他选项,第一个字节是“ suboption”的编号,第二个字节是其长度,然后是其值。

在这种情况下,在选项82中,子选项是嵌套的:
代理电路ID = 00:04:00:01:00:04,其中最后两个字节是发出请求的DHCP客户端端口

代理远程ID = 00:06:c8:是:19:93:11:48-DHCP中继设备的MAC地址
选项长度18岁十二月
期权价值01:06
00:04:00:01:00:04
02:08
00:06:c8:是:19:93:11:48
十六进制
包装结束255十二月1个255表示包装的结尾


DHCP优惠


服务器一旦收到DHCPDISCOVER数据包,并且如果发现它可以向客户端提供所请求的内容,则对它形成响应-DHCPOFFER。 答案被发送到端口“从那里来”,广播,因为 目前,客户端还没有IP地址,因此,只有发送广播后,他才能接收数据包。 客户端通过该数据包内其地址处的MAC识别出该数据包是他的数据包,以及该客户在创建第一个数据包时生成的交易号。

DHCPOFFER数据包结构表
包装位置价值名称(通用)例子投稿字节数解说
1个开机要求1个十六进制1个消息类型。 1-从客户端到服务器的请求,2-从服务器到客户端的响应
2硬件类型1个十六进制1个硬件地址的类型,在此协议中为1-MAC
3硬件长度6十六进制1个设备MAC地址长度
4啤酒花1个十六进制1个中间路线数
5交易编号23:比照:de:1天十六进制4唯一交易标识符。 由客户端在请求操作开始时生成
7秒逝去0十六进制4从获取地址开始的时间(以秒为单位)
9Bootp标志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服务器主机名弦乐64DHCP服务器的名称。 通常不传送
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个向DHCP客户端提供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:4天:ec
十二月18岁
包装结束255十二月1个255表示包装的结尾


DHCP请求


客户端收到DHCPOFFER之后,它会形成一个包,其中包含对网络参数的请求,该请求不是发给网络中的所有DHCP服务器的,而是发给一个特定的服务器,它最喜欢“ DHCPOFFER”的提议。 “喜欢”的标准可以不同,并且取决于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交易编号23:比照:de:1天十六进制4唯一交易标识符。 由客户端在请求操作开始时生成
7秒逝去0十六进制4从获取地址开始的时间(以秒为单位)
9Bootp标志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服务器主机名弦乐64DHCP服务器的名称。 通常不传送
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(用于Ehernet)+客户端的MAC地址
选项长度7十二月1个
期权价值01:2c:ab:25:ff:72:a6十六进制7
选件编号60十二月“供应商类别标识符”。 就我而言,报告了DHCP客户端的版本。 也许其他设备返回了其他内容。 Windows例如报告MSFT 5.0
选项长度11十二月
期权价值udhcp 0.9.8弦乐
选件编号551个客户端请求的网络参数。 成分可能不同

01-网络掩码
03-网关
06-DNS
oc-主机名
0f-网络域名
1c-广播请求的地址(广播)
42-TFTP服务器名称
79-无类静态路由
选项长度81个
期权价值01:03:06:0c:0f:1c:42:798
选件编号82十二月1个选项82,重复DHCPDISCOVER中的内容
选项长度18岁十二月1个
期权价值01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4天:ec
十二月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交易编号23:比照:de:1天十六进制4唯一交易标识符。 由客户端在请求操作开始时生成
7秒逝去0十六进制4从获取地址开始的时间(以秒为单位)
9Bootp标志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服务器主机名弦乐64DHCP服务器的名称。 通常不传送
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个向DHCP客户端提供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:4天:ec
十二月18岁
包装结束255十二月1个255表示包装的结尾



安装方式


安装实际上包括安装作业所需的python模块。 假定已经安装和配置了MySQL。

Freebsd


  pkg安装python3
 python3 -m确保
 pip3安装mysql连接器 

的Ubuntu


 须藤apt-get install python3
须藤apt-get install pip3
 sudo pip3安装mysql连接器 

我们创建MySQL数据库,填写转储pydhcp.sql,配置配置文件。

构型


所有服务器设置均为xml文件格式。 参考文件:

  <?xml version =“ 1.0”?>
 <配置>
     <dhcpserver>
	 <host> 0.0.0.0 </ host>
         <broadcast> 255.255.255.255 </ broadcast>
         <DHCPServer> 192.168.0.71 </ DHCPServer>
	 <LeaseTime> 8600 </ LeaseTime>
	 <ThreadLimit> 1 </ ThreadLimit>
         <defaultMask> 255.255.255.0 </ defaultMask>
         <defaultRouter> 192.168.0.1 </ defaultRouter>
         <defaultDNS> 8.8.8.8 </ defaultDNS>
     </ dhcpserver>
     <mysql>
         <host>本地主机</ host>
	 <username>测试</ username>
	 <password>测试</ password>
	 <basename> pydhcp </ basename>
     </ mysql>
     <选项>
        <option> option_82_hex:sw_port1:20:22 </ option>       
        <option> option_82_hex:sw_port2:16:18 </ option>       
        <option> option_82_hex:sw_mac:26:40 </ option>
     </ options>    
     <查询>
         <offer_count> 3 </ offer_count>
	 <offer_1>从上位(mac)=上位('{option_82_AgentRemoteId_hex}')和上位(端口)=上位('{option_82_AgentCircuitId_port_hex}')的用户中选择ip,掩码,路由器和dns </ offer_1>
         <offer_2>从上层(mac)=上层('{sw_mac}')和上层(端口)=上层('{sw_port2}')的用户中选择IP,掩码,路由器,dns </ offer_2>
         <offer_3>从上位(mac)=上位('{ClientMacAddress}')的用户中选择IP,掩码,路由器,dns </ offer_3>
	 <history_sql>插入到历史记录(id,dt,mac,ip,注释)值(null,now(),'{ClientMacAddress}','{RequestedIpAddress}','DHCPACK / INFORM')</ history_sql>
     </ query>
 </ config> 

现在更详细地介绍标签:

dhcpserver部分描述了启动服务器的基本设置,即:
  • 主机-服务器在端口67上侦听的IP地址
  • 广播-该IP是DHCPOFFER和DHCPACK的广播
  • DHCPServer-什么是DHCP服务器的IP
  • 颁发的IP地址的LeaseTime租赁时间
  • ThreadLimit-同时运行多少个线程来处理端口67上的传入UDP数据包。假定这将对高负载的项目有所帮助;)
  • defaultMask,defaultRouter,defaultDNS-如果在数据库中找到IP,但默认未提供给订户,则为订户默认提供什么

Mysql部分:

主机,用户名,密码,基本名-一切都能说明一切。 GitHub上发布的示例数据库结构

查询部分:本部分描述对OFFER / ACK的请求:

  • offer_count-请求的行数,返回的格式为ip,mask,router,dns
  • offer_n是查询字符串。 如果退货为空,则执行以下报价请求
  • history_sql-例如,订户向“授权历史记录”的写入请求

选项部分中的任何变量或DHCP协议中的选项都可以参与请求。

部分选项。 在这里,它已经变得更加有趣了。 在这里,我们可以创建变量,以便稍后在查询部分中使用。

例如:

option_82_hex:sw_port1:20:22 

,此行是将选项82的DHCP请求中出现的整行(十六进制格式)从20到22字节(包括20和22字节)的命令全部放入并放入新变量sw_port1(请求来自的交换机端口)中

 option_82_hex:sw_mac:26:40 

,定义sw_mac变量,取26:40范围内的十六进制

通过使用-d开关启动服务器,可以查看可以在查询中使用的所有可能选项。 我们将看到类似以下日志的内容:

  -DHCPINFORM数据包来自端口0025224ad764的67,b'\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a',('172.30.114.25',68)
 {'ClientMacAddress':'0025224ad764',
  'ClientMacAddressByte':b'\ x00%“ J \ xd7d”,
  'HType':'以太网',
  'HostName':b'\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a',
  'ReqListDNS':是的,
  'ReqListDomainName':是的,
  'ReqListPerfowmRouterDiscover':是的,
  'ReqListRouter':是的,
  'ReqListStaticRoute':是的,
  'ReqListSubnetMask':是的,
  'ReqListVendorSpecInfo':43
  'RequestedIpAddress':'0.0.0.0',
  '供应商':b'MSFT 5.0',
  'chaddr':'0025224ad764',
  'ciaddr':'172.30.128.13',
  '标志':b'\ x00 \ x00',
  'giaddr':'172.30.114.25',
  'gpoz':308,
  '海伦':6,
  'hops':1
  'htype':'MAC',
  'magic_cookie':b'c \ x82Sc',
  'op':'DHCPINFORM',
  'option12':12,
  'option53':53
  'option55':55,
  'option60':60,
  'option61':61,
  'option82':82,
  'option_82_byte':b'\ x12 \ x01 \ x06 \ x00 \ x04 \ x00 \ x01 \ x00 \ x06 \ x02 \ x08 \ x00'
                    b'\ x06 \ x00 \ x1eX \ x9e \ xb2 \ xad',
  'option_82_hex':'12010600040001000602080006001e589eb2ad',
  'option_82_len':18,
  'option_82_str':“ b'\\ x12 \\ x01 \\ x06 \\ x00 \\ x04 \\ x00 \\ x01 \\ x00 \\ x06 \\ x02 \\ x08 \\ x00 \\ x06 \\ x00 \ \ x1eX \\ x9e \\ xb2 \\ xad'“,
  “结果”:错误,
  '秒':768,
  'siaddr':'0.0.0.0',
  'sw_mac':'001e589eb2ad',
  'sw_port1':'06',
  'xidbyte':b'<\ x89} \ x8c',
  'xidhex':'3c897d8c',
  'yiaddr':'0.0.0.0'} 

因此,我们可以将任何变量包装在{}中,并将其用于SQL查询中。

让我们获取客户端接收IP地址的历史记录:





服务器启动


./pydhcpdb.py -d -c config.xml

-d输出模式到DEBUG控制台
-c <文件名>配置文件

汇报


现在,更多有关在Python中实现服务器的信息。 这很痛苦。 Python是动态研究的。 很多时候都是这样的:“哇,我以某种方式做了什么。” 根本没有优化,之所以采用这种形式,主要是因为对python的开发经验很少。 我将在“代码”中介绍服务器实现的最有趣的时刻。

XML配置文件解析器


使用标准的Python模块xml.dom。 这似乎很简单,但是在实施过程中,使用此模块的网络上明显缺乏明智的文档和示例。

 树= minidom.parse(gconfig [“ config_file”])
     mconfig = tree.getElementsByTagName(“ mysql”)
    对于mconfig中的elem:        
         gconfig [“ mysql_host”] = elem.getElementsByTagName(“主机”)[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(“查询”)
    对于qconfig中的elem:  
         gconfig [“ offer_count”] = elem.getElementsByTagName(“ offer_count”)[0] .firstChild.data                          
        对于范围内的num(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                          
    选项= tree.getElementsByTagName(“选项”)       
    对于选项中的元素:          
        节点= elem.getElementsByTagName(“选项”)
        对于节点中的选项:
             optionsMod.append(options.firstChild.data) 

多线程


奇怪的是,Python中的多线程实现非常清晰,简单。

  def PacketWork(数据,地址): 
 ...
 #对收到的包进行分析,并给出答案
 ...


而True:
     data,addr = udp_socket.recvfrom(1024)#等待UDP数据包
     thread = threading.Thread(target = PacketWork,args =(data,addr,))。start()#-运行前面在后台使用参数定义的PacketWork函数
    而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)#将套接字切换到广播模式
                     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”] =包(“ BB”,数据[10],数据[11]) 

从结构获取IP:

  res [“ ciaddr”] = socket.inet_ntoa(pack('BBBB',data [12],data [13],data [14],data [15])); 


反之亦然:

  res = res + socket.inet_pton(socket.AF_INET,gconfig [“ dhcp_Server”]) 

就这些;)

Source: https://habr.com/ru/post/zh-CN445338/


All Articles