├── README └── dhcp_test.py /README: -------------------------------------------------------------------------------- 1 | Script will load test a DHCP server by continuosly requesting address leases in a loop using randomly generated mac addresses. This will run serially as written, if you want to have multiple scripts running you will 2 | need to run it in several processes. Be aware that if you run it in multiple processes you may run into a number of lease failures on your DHCP client 3 | do to multiple discover packets hitting before a request hits thus several requests for the same address may occur. This is the normal behavior as in a real 4 | setup a client would then retry several times in the event this occurs. (one thing you *MAY* need to do is set promiscuous for the pcap object -> the open_live call) 5 | 6 | This is by no means a comprehensive DHCP test, just a little one off script to vefiy that a server is able to handle load numbers. 7 | 8 | -Couple of pretty simple but sometimes forgotten notes: 9 | *Make sure your DHCP server is reachable via it's IP (the client curring the script can see the subnet it's on) 10 | *Make sure your DHCP server isn't attempting to lease its own ip (in other words don't assign an ip for testing within the lease range) 11 | 12 | Usage: dhcp_test.py [DHCP server IP] [DHCP server port - Optional defaults to 67] [Number of Loops - Optional defaults to 1] 13 | -------------------------------------------------------------------------------- /dhcp_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Script will load test a DHCP server by continuosly requesting address leases in a loop using randomly generated mac addresses. This will run serially as written, if you want to have multiple scripts running you will 5 | need to run it in several processes. Be aware that if you run it in multiple processes you may run into a number of lease failures on your DHCP client 6 | do to multiple discover packets hitting before a request hits thus several requests for the same address may occur. This is the normal behavior as in a real 7 | setup a client would then retry several times in the event this occurs. (one thing you *MAY* need to do is set promiscuous for the pcap object -> the open_live call) 8 | 9 | This is by no means a comprehensive DHCP test, just a little one off script to vefiy that a server is able to handle load numbers. 10 | 11 | -Couple of pretty simple but sometimes forgotten notes: 12 | *Make sure your DHCP server is reachable via it's IP (the client curring the script can see the subnet it's on) 13 | *Make sure your DHCP server isn't attempting to lease its own ip (in other words don't assign an ip for testing within the lease range) 14 | 15 | Usage: dhcp_test.py [DHCP server IP] [DHCP server port - Optional defaults to 67] [Number of Loops - Optional defaults to 1] 16 | 17 | ''' 18 | 19 | from random import Random 20 | from optparse import OptionParser 21 | from pydhcplib.dhcp_packet import DhcpPacket 22 | from pydhcplib.dhcp_network import DhcpClient 23 | from pydhcplib.type_hw_addr import hwmac 24 | from pydhcplib.type_ipv4 import ipv4 25 | import socket 26 | import sys 27 | import time 28 | import pcap 29 | import struct 30 | 31 | r = Random() 32 | r.seed() 33 | 34 | break_wait = 0 35 | res = None 36 | 37 | dhcp_ip = '' 38 | 39 | # generamte a random mac address 40 | def genmac(): 41 | i = [] 42 | for z in xrange(6): 43 | i.append(r.randint(0,255)) 44 | return ':'.join(map(lambda x: "%02x"%x,i)) 45 | 46 | #generate a random xid 47 | def genxid(): 48 | decxid = r.randint(0,0xffffffff) 49 | xid = [] 50 | for i in xrange(4): 51 | xid.insert(0, decxid & 0xff) 52 | decxid = decxid >> 8 53 | return xid 54 | 55 | def get_packet(pktlen, data, timestamp): 56 | global dhcp_ip 57 | global break_wait 58 | global res 59 | if not data: 60 | return 61 | if data[12:14]=='\x08\x00': 62 | decoded=decode_ip_packet(data[14:]) 63 | if decoded['source_address'] == dhcp_ip: 64 | res = decoded['destination_address'] # take advantage of CNR using the new ip as the desination address... 65 | break_wait = 1 66 | 67 | # send the request to the server 68 | def issueRequest(serverip, serverport, timeout, req): 69 | global break_wait 70 | global res 71 | 72 | # Reset the global vars we will use here 73 | break_wait = 0 74 | res = None 75 | client = DhcpClient(client_listen_port=67, server_listen_port=serverport) 76 | client.dhcp_socket.settimeout(timeout) 77 | if serverip == '0.0.0.0': 78 | req.SetOption('flags',[128, 0]) 79 | req_type = req.GetOption('dhcp_message_type')[0] 80 | 81 | pcap_obj = pcap.pcapObject() 82 | dev = pcap.lookupdev() 83 | pcap_obj.open_live(dev, 1600, 0, 100) 84 | pcap_obj.setfilter("udp port 67", 0, 0) 85 | sent = 0 86 | while break_wait < 1: 87 | if(sent < 1): 88 | sent = 1 89 | client.SendDhcpPacketTo(req,serverip,serverport) 90 | if req_type == 3 or req_type == 7: 91 | return 92 | pcap_obj.dispatch(1, get_packet) 93 | 94 | return res 95 | 96 | #set up a dhcp packet, this defaults to the discover type 97 | def preparePacket(xid=None,giaddr='0.0.0.0',chaddr='00:00:00:00:00:00',ciaddr='0.0.0.0', yiaddr='0.0.0.0', msgtype='discover',required_opts=[]): 98 | req = DhcpPacket() 99 | req.SetOption('op',[1]) 100 | req.SetOption('htype',[1]) 101 | req.SetOption('hlen',[6]) 102 | req.SetOption('hops',[0]) 103 | if not xid: 104 | xid = genxid() 105 | req.SetOption('xid',xid) 106 | req.SetOption('giaddr',ipv4(giaddr).list()) 107 | req.SetOption('chaddr',hwmac(chaddr).list() + [0] * 10) 108 | req.SetOption('ciaddr',ipv4(ciaddr).list()) 109 | if msgtype == 'request': 110 | mt = 3 111 | elif msgtype == 'release': 112 | mt = 7 113 | else: 114 | mt = 1 115 | if mt == 3: 116 | req.SetOption('yiaddr', ipv4(yiaddr).list()) 117 | req.SetOption('request_ip_address', ipv4(yiaddr).list()) 118 | req.SetOption('dhcp_message_type',[mt]) 119 | return req 120 | 121 | # decode the packect so we can get information such as the source address to verify if the reply is comeing from where we expect 122 | def decode_ip_packet(s): 123 | d={} 124 | d['version']=(ord(s[0]) & 0xf0) >> 4 125 | d['header_len']=ord(s[0]) & 0x0f 126 | d['tos']=ord(s[1]) 127 | d['total_len']=socket.ntohs(struct.unpack('H',s[2:4])[0]) 128 | d['id']=socket.ntohs(struct.unpack('H',s[4:6])[0]) 129 | d['flags']=(ord(s[6]) & 0xe0) >> 5 130 | d['fragment_offset']=socket.ntohs(struct.unpack('H',s[6:8])[0] & 0x1f) 131 | d['ttl']=ord(s[8]) 132 | d['protocol']=ord(s[9]) 133 | d['checksum']=socket.ntohs(struct.unpack('H',s[10:12])[0]) 134 | d['source_address']=pcap.ntoa(struct.unpack('i',s[12:16])[0]) 135 | d['destination_address']=pcap.ntoa(struct.unpack('i',s[16:20])[0]) 136 | if d['header_len']>5: 137 | d['options']=s[20:4*(d['header_len']-5)] 138 | else: 139 | d['options']=None 140 | d['data']=s[4*d['header_len']:] 141 | return d 142 | 143 | # start of the global section, this is the "main" entry point 144 | dhcp_ip = "0.0.0.0" 145 | dhcp_port = 67 146 | loops = 1 147 | if len(sys.argv) != 3 and len(sys.argv) != 4 and len(sys.argv) != 5: 148 | pass 149 | print "Usage: dhcp_test.py [DHCP server IP] [DHCP server port - Optional defaults to 67] [Number of Loops - Optional defaults to 1]" 150 | sys.exit(0) 151 | elif len(sys.argv) != 4 and len(sys.argv) != 5: 152 | loops = 1 153 | dhcp_port = 67 154 | dhcp_ip = sys.argv[1] 155 | elif len(sys.argv) != 5: 156 | loops = 1 157 | dhcp_ip = sys.argv[1] 158 | dhcp_port = int(sys.argv[2]) 159 | else: 160 | loops = int(sys.argv[3]) 161 | dhcp_port = int(sys.argv[2]) 162 | dhcp_ip = sys.argv[1] 163 | 164 | leases = {} 165 | run_loops = loops 166 | #run this as many times as needed to test your server 167 | while run_loops > 0: 168 | #get a mac address 169 | mac = genmac() 170 | 171 | # create a discovery packet 172 | disc_packet = preparePacket(None, '0.0.0.0', mac, '0.0.0.0', '0.0.0.0', 'discover', [1,3,6,51]) 173 | 174 | #send the discover request to the server 175 | ip_issued = issueRequest(dhcp_ip, dhcp_port, 4, disc_packet) 176 | 177 | # use the returned discovered ip to create a request packet 178 | req_packet = preparePacket(None, '0.0.0.0', mac, '0.0.0.0', ip_issued, 'request', [1,3,6,51]) 179 | 180 | #issue the actual lease request 181 | res = issueRequest(dhcp_ip, dhcp_port, 4, req_packet) 182 | 183 | #just print out if we get a bad lease reply 184 | if ip_issued == '255.255.255.255': 185 | print mac 186 | print ip_issued 187 | print "error getting lease" 188 | else: 189 | leases[ip_issued] = mac 190 | run_loops = run_loops - 1 191 | 192 | #pause before we release all of the addresses in case we want to view them in the DHCP server 193 | entered = raw_input("Press 'Enter' key to continue...") 194 | 195 | #loop through all our leases and tell the DHCP server we are done with them 196 | for k, v in leases.iteritems(): 197 | rel_packet = preparePacket(None, '0.0.0.0', v, k, '0.0.0.0', 'release', [1,3,6,51]) 198 | ip_issued = issueRequest(dhcp_ip, dhcp_port, 4, rel_packet) 199 | --------------------------------------------------------------------------------