Python脚本实现dhcp报文的发送并模拟多用户并发拿取地址

背景

遇到一个测试项目,需要模拟多用户去dhcp拿取地址,其实现在有很多工具都可以实现,如龙卷风这些,但龙卷风好像大佬没有维护后,经常出现识别不了网卡,好像也不支持持续发送dhcp广播报文,导致不能满足我目前的测试环境,于是结合python+scapy实现多用户并发持续发送dhcp广播报文拿取地址的实验

dhcp报文的交互流程

1、实验只针对ipv4网络环境

Python脚本实现dhcp报文的发送并模拟多用户并发拿取地址_第1张图片

备注:只有路由器发送了ack后才代表给你分配的地址已经可以使用了,所以 在用python模拟终端拿取地址的过程,首先需要发送dhcp discover报文进行广播发现,等待路由器回复了dhcp offer后,需要捕获offer报文并提取offer报文中的相关必要字段如xid,your client ip,dhcp server等,然后将提取后的字段填到dhcp request报文中并发送出去,到这里,python需要做的事情其实就已经完成了。

python+scapy模拟dhcp discover报文

import threading
import time
import re
from scapy.all import *
from scapy.layers.l2 import Ether
import binascii
from queue import Queue

DISCOVER_THREADS_NUM = 10
REQUEST_THREADS_NUM = 10

dhcp_offer_queue = Queue()
mac_ip_queue = Queue()

class DhcpDiscoverThread(threading.Thread):
    def __init__(self, mac):
        super().__init__()
        self.mac = mac
        # self.dstmac= "FF:FF:FF:FF:"
    
    def run(self):
        xid = random.randint(0, 0xFFFFFFFF)
        srcmac=binascii.a2b_hex(self.mac.replace(":",""))
        options = binascii.a2b_hex("63825363")
        discover_pkt = Ether(dst = "FF:FF:FF:FF:FF:FF",src = srcmac)\
             /scapy.all.IP(tos = None,len = None,id = 1,flags = "",frag = 0,ttl = 64 ,proto = 17,chksum = None,src ="0.0.0.0",dst = "255.255.255.255")\
             /scapy.all.UDP(sport = 68,dport = 67,len = None,chksum = None)\
             /scapy.all.BOOTP(op = 1,xid = xid,htype = 1,hlen = 6,hops = 0,secs = 0,flags = "B",ciaddr = "0.0.0.0",yiaddr = "0.0.0.0",siaddr = "0.0.0.0",giaddr = "0.0.0.0",chaddr = srcmac,sname = "",file = "",options = options)\
             /scapy.all.DHCP(options=[("message-type","discover"),("client_id",b"\x01"+srcmac),("requested_addr","192.168.1.6"),("hostname","chahong"),("vendor_class_id",'MSFT 5.0'),("param_req_list",[1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252]),"end"])
        offers = []
        def offer_handler(pkt):
            if pkt.getlayer("DHCP").options[0][1] == 2 and pkt.getlayer("Ethernet").src =="94:e3:ee:bb:69:6a":
                offers.append(pkt)
                print("已经收到")
        sendp(discover_pkt, iface="以太网",verbose=False)
        # print(1111111111)
        sniff(prn=offer_handler, iface="以太网",filter="udp and (port 67 or port 68)", timeout=10)
        dhcp_offer_queue.put(offers)
        # for offer in offers:
        #     ip_address =str(offer["BOOTP"].yiaddr)
        #     dhcp_server =str(offer["BOOTP"].siaddr)
        #     xid = offer["BOOTP"].xid
        #     mad_addr = offer["BOOTP"].chaddr
        #     mac_ip_queue.put((ip_address,dhcp_server,xid,mad_addr))

2、这里使用多线程的run方法,后续需要模拟并发终端去拿地址,如何组包,建议使用wires hark在自己的电脑上进行抓包,然后使用pkt.show()方法查看对应包的layer字段,然后一层一层的进行组包,有些包组出来可能相关字段名字不多,建议查看scapy的相关源码进行组包;

我这里的discover_pkt的组成由以太网头部(Ether)/IPV4头部(IP)/UDP头部(UDP)/BOOTP/DHCP组成,最复杂的一成就是bootp和dhcp头部

下面是这两部分使用python打印出来的一个dhcp discover在python中相关字段的描述,然后就比较容易看懂我上面的组的包。

使用这段code可以打印,前提是你要使用wireshark抓到包并报错对应文件问pcap格式

pcap = r'D:\wireshark_pcap\xxxx_dhcp.pcap'

f=rdpcap(pcap)

f[0].show()

Python脚本实现dhcp报文的发送并模拟多用户并发拿取地址_第2张图片

 备注:报文里面的有些字段需要进行转码,比如srcmac要转化成二进制的格式,总之我写的脚本里面不需要进行大改,,一些字符串我已经转好格式,都是踩过的坑才搞好。

python+scapy模拟dhcp request报文

class DhcpRequestThread(threading.Thread):
    def __init__(self,mac):
        super().__init__()
        self.requestip=mac[0]
        self.dhcpserver=mac[1]
        self.xid=mac[2]
        self.srcmac=mac[3]
    
    def run(self):
        src_mac = ":".join([binascii.hexlify(self.srcmac[i:i+1]).decode() for i in range(0, 6)])#将二进制字符串转化为16禁止字符串然后在转化成11:22:33这种格式的mac地址
        # formatted_mac = ':'.join(mac[i:i+2] for i in range(0, len(mac), 2))
            # srcmac=binascii.a2b_hex(self.srcmac.replace(":",""))
        options = binascii.a2b_hex("63825363")
        pkt_dhcp_quest = Ether(dst = "FF:FF:FF:FF:FF:FF",src = src_mac)\
                         /scapy.all.IP(tos = None,len = None,id = 1,flags = "",frag = 0,ttl = 64 ,proto = 17,chksum = None,src ="0.0.0.0",dst = "255.255.255.255")\
                         /scapy.all.UDP(sport = 68,dport = 67,len = None,chksum = None)\
                         /scapy.all.BOOTP(op = 1,htype = 1,hlen = 6,hops = 0,xid =  self.xid,secs = 0,flags = "B",ciaddr = "0.0.0.0",yiaddr = "0.0.0.0",siaddr = "0.0.0.0",giaddr = "0.0.0.0",chaddr = self.srcmac,sname = "",file = "",options = options)\
                         /scapy.all.DHCP(options=[("message-type","request"),("client_id",b"\x01"+ self.srcmac),("requested_addr",self.requestip),("server_id",self.dhcpserver),("hostname",b'DESKTOP-7716SI9'),("vendor_class_id",b'MSFT 5.0'),("param_req_list",[1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252]),"end"])
        sendp(pkt_dhcp_quest,iface="以太网",verbose=False)
        # print(222222222222222)

组包方法和上面一样,要发送dhcp request报文的前提是要捕获到对应的dhcp offer报文

原代码如下:

import threading
import time
import re
from scapy.all import *
from scapy.layers.l2 import Ether
import binascii
from queue import Queue

DISCOVER_THREADS_NUM = 3
REQUEST_THREADS_NUM = 3

dhcp_offer_queue = Queue()
mac_ip_queue = Queue()

class DhcpDiscoverThread(threading.Thread):
    def __init__(self, mac):
        super().__init__()#使用run方法必须要要调用父类thread中的构造方法
        self.mac = mac
        # self.dstmac= "FF:FF:FF:FF:"
    
    def run(self):
        xid = random.randint(0, 0xFFFFFFFF)
        srcmac=binascii.a2b_hex(self.mac.replace(":",""))
        # print("arcmac%s"%srcmac)
        options = binascii.a2b_hex("63825363")
        discover_pkt = Ether(dst = "FF:FF:FF:FF:FF:FF",src = srcmac)\
             /scapy.all.IP(tos = None,len = None,id = 1,flags = "",frag = 0,ttl = 64 ,proto = 17,chksum = None,src ="0.0.0.0",dst = "255.255.255.255")\
             /scapy.all.UDP(sport = 68,dport = 67,len = None,chksum = None)\
             /scapy.all.BOOTP(op = 1,xid = xid,htype = 1,hlen = 6,hops = 0,secs = 0,flags = "B",ciaddr = "0.0.0.0",yiaddr = "0.0.0.0",siaddr = "0.0.0.0",giaddr = "0.0.0.0",chaddr = srcmac,sname = "",file = "",options = options)\
             /scapy.all.DHCP(options=[("message-type","discover"),("client_id",b"\x01"+srcmac),("requested_addr","192.168.1.6"),("hostname","chahong"),("vendor_class_id",'MSFT 5.0'),("param_req_list",[1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252]),"end"])
        offers = []
        def offer_handler(pkt):
            if pkt.getlayer("DHCP").options[0][1] == 2:
                offers.append(pkt)
                print("已经收到")
        sendp(discover_pkt, iface="以太网",verbose=False)
        # print(1111111111)
        sniff(prn=offer_handler, iface="以太网",filter="udp and (port 67 or port 68)", timeout=10)
        dhcp_offer_queue.put(offers)
        # for offer in offers:
        #     ip_address =str(offer["BOOTP"].yiaddr)
        #     dhcp_server =str(offer["BOOTP"].siaddr)
        #     xid = offer["BOOTP"].xid
        #     mad_addr = offer["BOOTP"].chaddr
        #     mac_ip_queue.put((ip_address,dhcp_server,xid,mad_addr))

class DhcpRequestThread(threading.Thread):
    def __init__(self,mac):
        super().__init__()
        self.requestip=mac[0]
        self.dhcpserver=mac[1]
        self.xid=mac[2]
        self.srcmac=mac[3]
    
    def run(self):
        src_mac = ":".join([binascii.hexlify(self.srcmac[i:i+1]).decode() for i in range(0, 6)])#将二进制字符串转化为16禁止字符串然后在转化成11:22:33这种格式的mac地址
        # formatted_mac = ':'.join(mac[i:i+2] for i in range(0, len(mac), 2))
            # srcmac=binascii.a2b_hex(self.srcmac.replace(":",""))
        options = binascii.a2b_hex("63825363")
        srcmac=binascii.a2b_hex(src_mac.replace(":",""))
        # print("testest%s"%srcmac)
        pkt_dhcp_quest = Ether(dst = "FF:FF:FF:FF:FF:FF",src = src_mac)\
                         /scapy.all.IP(tos = None,len = None,id = 1,flags = "",frag = 0,ttl = 64 ,proto = 17,chksum = None,src ="0.0.0.0",dst = "255.255.255.255")\
                         /scapy.all.UDP(sport = 68,dport = 67,len = None,chksum = None)\
                         /scapy.all.BOOTP(op = 1,htype = 1,hlen = 6,hops = 0,xid =  self.xid,secs = 0,flags = "B",ciaddr = "0.0.0.0",yiaddr = "0.0.0.0",siaddr = "0.0.0.0",giaddr = "0.0.0.0",chaddr = self.srcmac,sname = "",file = "",options = options)\
                         /scapy.all.DHCP(options=[("message-type","request"),("client_id",b"\x01"+ srcmac),("requested_addr",self.requestip),("server_id",self.dhcpserver),("hostname",b'DESKTOP-7716SI9'),("vendor_class_id",b'MSFT 5.0'),("param_req_list",[1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252]),"end"])
        sendp(pkt_dhcp_quest,iface="以太网",verbose=False)
        # print(222222222222222)

def start_dhcp_discover_threads(mac_addresses):
    threads = []
    count = 0
    for mac in mac_addresses:
        thread = DhcpDiscoverThread(mac)
        thread.start()
        threads.append(thread)
        count += 1
        if count % DISCOVER_THREADS_NUM == 0 or count == len(mac_addresses):
            for t in threads:
                t.join()
            threads = []
            offers = []
            while not dhcp_offer_queue.empty():
                offers += dhcp_offer_queue.get()
            start_dhcp_request_threads(offers)
            # dhcp_offer_queue.Queue().queue.clear()

            
def start_dhcp_request_threads(offers):
    # print(6666666666)
    # print(offers)
    threads = []
    count = 0
    mac_ip_list = []
    for offer in offers:
        xid = offer["BOOTP"].xid
        dhcp_server_ip = offer["BOOTP"].siaddr
        requested_ip = offer["BOOTP"].yiaddr
        mac_address = offer["BOOTP"].chaddr
        mac_ip_list.append((requested_ip, dhcp_server_ip, xid, mac_address))
    # print(mac_ip_list)
    #不知道为啥有重复的列表去重
    mac_ip_list=list(set(mac_ip_list))
    print(mac_ip_list)
    for mac in mac_ip_list:
         thread = DhcpRequestThread(mac)
         thread.start()
         threads.append(thread)
         count += 1
         print(count)
         if count % REQUEST_THREADS_NUM == 0 or count == len(mac_addresses):
            for t in threads:
                t.join()
            threads = []
         
        
    # for i in range(REQUEST_THREADS_NUM):
    #     thread = DhcpRequestThread(mac_ip_list)
    #     thread.start()
    #     threads.append(thread)
    # for t in threads:
    #     t.join()

if __name__ == "__main__":
    mac_addresses = []
    for i in range(51):
        mac_address = "{:012x}".format(int("000002000001", 16) + 1 * i)
        mac_address = re.sub("(.{2})", "\\1:", mac_address, 0)[:-1]
        mac_addresses.append(mac_address)
    # print(mac_addresses)

    offers = start_dhcp_discover_threads(mac_addresses)
    # print(offers)
    start_dhcp_request_threads(offers)

主要采用了多线程和队列的方法来完成多用户去dhcp拿取地址;大致流程就是先通过start_dhcp_discover_threads(mac_addresses)传入所有的mac地址,这里以列表的形式,然后start_dhcp_discover_threads方法通过多线程去用这些mac地址发送dhcp discover方法,每次只启动10个线程,然后DhcpDiscoverThread这个类就是去发送dhcp discover报文,并捕获DHCP offer报文,并将其保存到一个队列里面,后面来取,10个线程发送完了后,start_dhcp_discover_threads在来取上述队列里面存放的所有抓到的dhcp offer 报文并返回,最后start_dhcp_request_threads传入所返回的offer,也是一个列表,然后在从offer报文中取出必要字段用于后面的dhcp request报文的发送,必要字段有 xid,dhcp_server_ip,requested_ip,mac_address,最后在用多线程去启动发送dhcp request报文。大致流程就是这样。

实验结果:

Python脚本实现dhcp报文的发送并模拟多用户并发拿取地址_第3张图片

 Python脚本实现dhcp报文的发送并模拟多用户并发拿取地址_第4张图片

 经测试,脚本有效,且在路由器的后台的dhcp.lease文件也看到了路由器所分配的地址。

总结

如需一直发送dhcp广播报文,可以在后面加个死循环,建议不要在公司的办公网使用,之前搞了一次导致好多电脑重启后都不能拿地址,最后重启了上游的交换机就好了。

你可能感兴趣的:(网络,linux,python)