aboutsummaryrefslogtreecommitdiffstats
path: root/junos-bootstrap/dhcpd/server_dhcp.py
diff options
context:
space:
mode:
authorJonas Lindstad <jonaslindstad@gmail.com>2015-02-05 00:32:54 +0100
committerJonas Lindstad <jonaslindstad@gmail.com>2015-02-05 00:32:54 +0100
commit4a73a3fc9f4d89690d9fb61a096a05506b108164 (patch)
tree4887289f2bd6c464eb68286e37148b82728a71ff /junos-bootstrap/dhcpd/server_dhcp.py
parenta9148c574f46ba34b4a7e4abab3b7e8186573a9e (diff)
Updated with todays work. Cleaned up, better checks, better CLI output
Diffstat (limited to 'junos-bootstrap/dhcpd/server_dhcp.py')
-rw-r--r--junos-bootstrap/dhcpd/server_dhcp.py224
1 files changed, 130 insertions, 94 deletions
diff --git a/junos-bootstrap/dhcpd/server_dhcp.py b/junos-bootstrap/dhcpd/server_dhcp.py
index 4a4295f..d642917 100644
--- a/junos-bootstrap/dhcpd/server_dhcp.py
+++ b/junos-bootstrap/dhcpd/server_dhcp.py
@@ -15,7 +15,6 @@ Based on the work of psychomario - https://github.com/psychomario
TODO
- * only process if option 82 and GIADDR != '00000000' is set in discover/request
* try/catch around each incomming packet - prevents DHCP-server from crashing if it receives a malformed packet
'''
@@ -29,6 +28,7 @@ if not hasattr(IN,"SO_BINDTODEVICE"):
options_raw = {} # TODO - not a nice way to do things
option_82_1 = ''
+client = ''
# Length of DHCP fields in octets, and their placement in packet.
# Ref: http://4.bp.blogspot.com/-IyYoFjAC4l8/UXuo16a3sII/AAAAAAAAAXQ/b6BojbYXoXg/s1600/DHCPTitle.JPG
@@ -75,9 +75,13 @@ def format_hex_mac(hex_mac):
def six_byte_mac_to_str(mac):
return ':'.join('%02x' % byte for byte in mac)
+# b'b827eb9a520f' => 'b8:27:eb:9a:52:0f'
+def prettyprint_hex_as_str(hex):
+ return ':'.join('%02x' % byte for byte in binascii.unhexlify(hex))
+
# Parses DHCP options - raw = hex options
def parse_options(raw):
- print(' --> processing DHCP options')
+ print('[%s] --> processing DHCP options' % client)
chunked = chunk_hex(raw)
chunked_length = len(chunked)
pointer = 0 # counter - next option start
@@ -104,24 +108,36 @@ def parse_options(raw):
elif option is 53:
# options[option] = 1 # Not adding DHCP DISCOVER to the options list, becouse it will not be used further on
if int(chunked[pointer+2], 16) is 1:
- print(' --> option: %s: %s' % (option, 'DHCP Discover (will not be used in reply)'))
+ print('[%s] --> option: %s: %s' % (client, option, 'DHCP Discover (will not be used in reply)'))
else:
- print(' --> option: %s: %s' % (option, asciivalue))
+ print('[%s] --> option: %s: %s' % (client, option, asciivalue))
else:
options[option] = asciivalue
- print(' --> option: %s: %s' % (option, asciivalue))
+ # TODO: Formating....
+ try:
+ if len(asciivalue) > 30:
+ print('[%s] --> option: %s: %s' % (client, option, asciivalue[:26] + ' [...]'))
+ else:
+ print('[%s] --> option: %s: %s' % (client, option, asciivalue))
+ except Exception:
+ if len(asciivalue) > 30:
+ print('[%s] --> option: %s: %s' % (client, option, prettyprint_hex_as_str(option_payload)[:26] + ' [...]'))
+ else:
+ print('[%s] --> option: %s: %s' % (client, option, prettyprint_hex_as_str(option_payload)))
+ pass
+
pointer = pointer + length + 2 # place pointer at the next options' option ID/code field
if int(chunked[pointer], 16) is 255: # end of DHCP options - allways last field
- print(' --> finished processing options')
+ print('[%s] --> Finished processing options' % client)
break
return options
# Parses suboptions
def parse_suboptions(option, raw):
- print(' --> processing suboption hook for option %s' % option)
+ print('[%s] --> processing suboption hook for option %s' % (client, option))
chunked = chunk_hex(raw)
chunked_length = len(chunked)
pointer = 0 # counter - next option start
@@ -137,11 +153,12 @@ def parse_suboptions(option, raw):
if option is 82 and int(chunked[0], 16) is 1: # Option 82 - custom shit: Putting data in list
option_82_1 = binascii.unhexlify(value).decode()
- print(' --> suboption %s found - value: "%s"' % (int(chunked[0], 16), binascii.unhexlify(value).decode())) # will fail on non-ascii characters
+ print('[%s] --> suboption %s found - value: "%s"' % (client, int(chunked[0], 16), binascii.unhexlify(value).decode())) # will fail on non-ascii characters
+
dataset[int(chunked[0], 16)] = value
pointer = pointer + length + 2 # place pointer at the next options' option ID/code field
if pointer not in chunked: # end of DHCP options - allways last field
- print(' --> finished processing suboption %s' % option)
+ print('[%s] --> Finished processing suboption %s' % (client, option))
break
return dataset
@@ -152,108 +169,126 @@ def reqparse(message):
hexmessage=binascii.hexlify(message)
messagesplit=[binascii.hexlify(x) for x in split_packet(message,dhcpfields)]
- # Test parsing
- options = parse_options(b'3501013c3c4a756e697065722d6578323230302d632d3132742d3267000000000000000000000000000000000000000000000000000000000000000000000000005222012064697374726f2d746573743a67652d302f302f302e303a626f6f747374726170ff')
- # options = parse_options(messagesplit[15])
+ global client
+ client = prettyprint_hex_as_str(messagesplit[11])
- # print(parse_suboptions('82', options[82]))
+ print('[%s] Parsing DHCP packet from client' % client)
+
+ #
+ # Logical checks to decide to whether respond or reject
+ #
- if 82 in options: # contains option 82 - was forwarded by a DHCP relay
- print(' --> DHCP packet contains option 82 -> should be processed')
- # print(options[82])
- else:
- print(' --> DHCP packet does not contain option 82 -> should be dropped')
+ # DHCP request has been forwarded by DHCP relay
+ if int(messagesplit[10]) is 0:
+ print('[%s] Rejecting to process DHCP packet - not forwarded by DHCP relay' % client)
+ return False
- if int(messagesplit[10]) is not 0:
- print(' --> DHCP packet forwarded by relay %s' % hex_ip_to_str(messagesplit[10]))
+ # Process DHCP options
+ # Test data from EX2200 first boot up
+ options = parse_options(b'3501013c3c4a756e697065722d6578323230302d632d3132742d3267000000000000000000000000000000000000000000000000000000000000000000000000005222012064697374726f2d746573743a67652d302f302f302e303a626f6f747374726170ff')
+ # options = parse_options(messagesplit[15])
+
+ # Option 82 is set in the packet
+ if 82 not in options:
+ print('[%s] Rejecting to process DHCP packet - DHCP option 82 not set' % client)
+ return False
+
+ # Check DHCP request type
+ if messagesplit[15][:6] == b'350101':
+ mode = 'dhcp_discover'
+ print('[%s] --> DHCP packet type: DHCP DISCOVER' % client)
+ elif messagesplit[15][:6] == b'350103':
+ mode = 'dhcp_request'
+ print('[%s] --> DHCP packet type: DHCP REQUEST' % client)
else:
- print(' --> DHCP packet not forwarded - direct request')
-
- # print(b'DHCP XID/Transaction ID:' + messagesplit[4])
+ print('[%s] Rejecting to process DHCP packet - option 53 not first in DHCP request' % client)
+ return False
+ #
+ # Packet passes our requirements
+ #
+ print('[%s] --> DHCP packet contains option 82 - continues to process' % client)
+ print('[%s] --> DHCP packet forwarded by relay %s' % (client, hex_ip_to_str(messagesplit[10])))
+ print('[%s] --> DHCP XID/Transaction ID: %s' % (client, prettyprint_hex_as_str(messagesplit[4])))
-
-
- # Testing - do DB lookup based on option 82
+ # Handle DB request - do DB lookup based on option 82
+ print('[%s] --> Looking up in the DB' % (client))
if len(option_82_1) > 0:
(distro, phy, vlan) = option_82_1.split(':')
+ print('[%s] --> Query details: distro_name:%s, distro_phy_port:%s' % (client, distro, phy.split('.')[0]))
- # lease.debug = True
if lease({'distro_name': distro, 'distro_phy_port': phy.split('.')[0]}).get_dict() is not False:
lease_details = lease({'distro_name': distro, 'distro_phy_port': phy[:-2]}).get_dict()
- print(' --> Found match in DB with distro_name:' + distro + ' distro_phy_port:' + phy.split('.')[0])
+ print('[%s] --> Data found, switch exists in DB - ready to craft response' % client)
+ else:
+ print('[%s] --> Data not found, switch does not exists in DB' % client)
+ return False
- if messagesplit[15][:6] == b'350101': # option 53 (should allways be the first option in DISCOVER/REQUEST) - identifies DHCP packet type - discover/request/offer/ack++
- print('\n\nDHCP DISCOVER - client MAC %s' % six_byte_mac_to_str(messagesplit[11]))
- print(' --> crafting DHCP OFFER response')
-
- # DHCP OFFER details - Options
- data = b'\x02' # Message type - boot reply
- data += b'\x01' # Hardware type - ethernet
- data += b'\x06' # Hardware address length - 6 octets for MAC
- data += b'\x00' # Hops
- data += binascii.unhexlify(messagesplit[4]) # XID / Transaction ID
- data += b'\x00\x01' # seconds elapsed - 1 second
- data += b'\x80\x00' # BOOTP flags - broadcast (unicast: 0x0000)
- data += b'\x00'*4 # Client IP address
- data += socket.inet_aton(lease_details['mgmt_addr']) # New IP to client
- data += socket.inet_aton(address) # Next server IP addres - self
- data += binascii.unhexlify(messagesplit[10]) # Relay agent IP - DHCP forwarder
- data += binascii.unhexlify(messagesplit[11]) # Client MAC
- data += b'\x00'*202 # Client hardware address padding (10) + Server hostname (64) + Boot file name (128)
- data += b'\x63\x82\x53\x63' # Magic cookie
+ if mode == 'dhcp_discover':
+ print('[%s] --> crafting DHCP OFFER response' % client)
- # DHCP Options - ordered by pcapng "proof of concept" file
- data += craft_option(53).raw_hex(b'\x02') # Option 53 - DHCP OFFER
-
- elif messagesplit[15][:6] == b'350103':
- print('\n\nDHCP REQUEST - client MAC %s' % four_byte_mac_to_str(messagesplit[11]))
- print(' --> crafting DHCP ACK response')
+ if mode == 'dhcp_request':
+ print('[%s] --> crafting DHCP ACK response' % client)
- data = b'\x02' # Message type - boot reply
- data += b'\x01' # Hardware type - ethernet
- data += b'\x06' # Hardware address length - 6 octets for MAC
- data += b'\x00' # Hops
- data += binascii.unhexlify(messagesplit[4]) # XID / Transaction ID
- data += b'\x00\x01' # seconds elapsed - 1 second
- data += b'\x80\x00' # BOOTP flags - broadcast (unicast: 0x0000)
- data += b'\x00'*4 # Client IP address
- data += socket.inet_aton(lease_details['mgmt_addr']) # New IP to client <<<<<<<<<<<<<<<<<<<<<<<< ALL THE FUCKINGS IN THE WORLD
- data += socket.inet_aton(address) # Next server IP addres - self
- data += binascii.unhexlify(messagesplit[10]) # Relay agent IP - DHCP forwarder
- data += binascii.unhexlify(messagesplit[11]) # Client MAC
- data += b'\x00'*202 # Client hardware address padding (10) + Server hostname (64) + Boot file name (128)
- data += b'\x63\x82\x53\x63' # Magic cookie
- # DHCP Options - ordered by pcapng "proof of concept" file
- data += b'\x35\x01\05' # Option 53 - DHCP ACK
- else:
- print('Unexpected DHCP option 53 - stopping processing request')
- return None
-
+ print('[%s] --> XID/Transaction ID: %s' % (client, prettyprint_hex_as_str(messagesplit[4])))
+ print('[%s] --> Client IP: %s' % (client, lease_details['mgmt_addr']))
+ print('[%s] --> Next server IP: %s' % (client, address))
+ print('[%s] --> DHCP forwarder IP: %s' % (client, hex_ip_to_str(messagesplit[10])))
+ print('[%s] --> Client MAC: %s' % (client, client))
+
+ data = b'\x02' # Message type - boot reply
+ data += b'\x01' # Hardware type - ethernet
+ data += b'\x06' # Hardware address length - 6 octets for MAC
+ data += b'\x00' # Hops
+ data += binascii.unhexlify(messagesplit[4]) # XID / Transaction ID
+ data += b'\x00\x01' # seconds elapsed - 1 second
+ data += b'\x80\x00' # BOOTP flags - broadcast (unicast: 0x0000)
+ data += b'\x00'*4 # Client IP address
+ data += socket.inet_aton(lease_details['mgmt_addr']) # New IP to client
+ data += socket.inet_aton(address) # Next server IP address
+ data += binascii.unhexlify(messagesplit[10]) # Relay agent IP - DHCP forwarder
+ data += binascii.unhexlify(messagesplit[11]) # Client MAC
+ data += b'\x00'*202 # Client hardware address padding (10) + Server hostname (64) + Boot file name (128)
+ data += b'\x63\x82\x53\x63' # Magic cookie
+
+ #
+ # Craft DHCP options
+ #
+ print('[%s] --> Completed DHCP header structure, building DHCP options' % client)
+ if mode == 'dhcp_discover':
+ print('[%s] --> Option 53: DHCP OFFER (2)' % client)
+ data += craft_option(53).raw_hex(b'\x02') # Option 53 - DHCP OFFER
- # common options for both DHCP REPLY and DHCP ACK - should be most of the options
+ if mode == 'dhcp_request':
+ print('[%s] --> Option 53: DHCP ACK (5)' % client)
+ data += craft_option(53).raw_hex(b'\x05') # Option 53 - DHCP ACK
+
data += craft_option(54).bytes(socket.inet_aton(address)) # Option 54 - DHCP server identifier
- print(' --> Option 54 (DHCP server identifier): %s' % address)
+ print('[%s] --> Option 54 (DHCP server identifier): %s' % (client, address))
+
data += craft_option(51).raw_hex(b'\x00\x00\xff\x00') # Option 51 - Lease time left padded with "0"
- print(' --> Option 51 (Lease time): %s' % '???')
+ print('[%s] --> Option 51 (Lease time): %s' % (client, '65536'))
+
data += craft_option(1).ip(netmask) # Option 1 - Subnet mask
- print(' --> Option 51 (Subnet mask): %s' % netmask)
+ print('[%s] --> Option 1 (subnet mask): %s' % (client, netmask))
+
+ data += craft_option(3).bytes(messagesplit[10]) # Option 3 - Default gateway (set to DHCP forwarders IP)
+ print('[%s] --> Option 3 (default gateway): %s' % (client, address)) # TODO - FIX BASED ON CIDR IN DB
+
+ data += craft_option(150).bytes(socket.inet_aton(address)) # Option 150 - TFTP Server. Used as target for the Zero Touch Protocol
+ print('[%s] --> Option 150 (Cisco proprietary TFTP server(s)): %s' % (client, address)) # TODO - FIX BASED ON CIDR IN DB
+
+ # http://www.juniper.net/documentation/en_US/junos13.2/topics/concept/software-image-and-configuration-automatic-provisioning-understanding.html
+ data += craft_option(43).bytes(craft_option(0).string('/junos/' + junos_file) + craft_option(1).string('/tg15-edge/' + lease_details['hostname']) + craft_option(3).string('http')) # Option 43 - ZTP
+ print('[%s] --> Option 43 (Vendor-specific option):' % client)
+ print('[%s] --> Suboption 0: %s' % (client, '/junos/' + junos_file))
+ print('[%s] --> Suboption 1: %s' % (client, '/tg15-edge/' + lease_details['hostname']))
+ print('[%s] --> Suboption 3: %s' % (client, 'http'))
- # Set option 3 - default gateway. Only applicable if messagesplit[10] (DHCP forwarder (GIADDR)) is set
- if messagesplit[10] == b'00000000':
- data += craft_option(3).bytes(socket.inet_aton(address)) # Option 3 - Default gateway (set to DHCP servers IP)
- print(' --> Option 3 (Default gateway): %s' % address)
- else:
- data += craft_option(3).bytes(messagesplit[10]) # Option 3 - Default gateway (set to DHCP forwarders IP)
- data += craft_option(150).bytes(socket.inet_aton(address)) # Option 150 - TFTP Server
- data += craft_option(43).bytes(craft_option(1).string('/tg15-edge/' + lease_details['hostname']) + craft_option(3).string('http')) # Option 43 - ZTP
- print(' --> Option 43 (Vendor-specific option):')
- print(' --> Suboption 1: %s' % '/tg15-edge/' + lease_details['hostname'])
- print(' --> Suboption 3: %s' % 'http')
# data += '\x03\x04' + option82_raw # Option 82 - with suboptions
+
data += b'\xff'
-
return data
if __name__ == "__main__":
@@ -263,7 +298,8 @@ if __name__ == "__main__":
netmask = '255.255.255.0'
tftp = address
gateway = address
- leasetime=86400 #int
+ leasetime = 86400 #int
+ junos_file = 'jinstall-ex-2200-12.3R6.6-domestic-signed.tgz'
# Setting up the server, and how it will communicate
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # IPv4 UDP socket
@@ -274,20 +310,20 @@ if __name__ == "__main__":
# Starting the whole loop
print('Starting main loop')
- while 1: #main loop
+ while True: #main loop
try:
message, addressf = s.recvfrom(8192)
# print(message)
if message.startswith(b'\x01'): # UDP payload is DHCP request (discover, request, release)
if addressf[0] == '0.0.0.0':
- print('DHCP broadcast')
+ print('[%s] DHCP broadcast' % client)
reply_to = '<broadcast>'
else:
- print('DHCP unicast - DHCP forwarding')
+ print('[%s] DHCP unicast - DHCP forwarding' % client)
reply_to = addressf[0]
data=reqparse(message) # Parse the DHCP request
if data:
- print(' --> replying to %s' % reply_to)
+ print('[%s] --> replying to %s' % (client, reply_to))
# print(b'replying with UDP payload: ' + data)
s.sendto(data, ('<broadcast>', 68)) # Sends reply
except KeyboardInterrupt: