aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Lindstad <jonaslindstad@gmail.com>2015-02-03 03:44:20 +0100
committerJonas Lindstad <jonaslindstad@gmail.com>2015-02-03 03:44:20 +0100
commite4d0579fd2399c25bc134bd6fb0d1feaa750d5a8 (patch)
tree5e1b5333fcab2685a9f22b2dbb187ff1da5847c4
parent329da6d7b23cb4085083e231ecd84e3df857ffe0 (diff)
First test with Postgres as backend. Separated the lease logic to a separate module. Added cli dump 'in action'
-rw-r--r--junos-bootstrap/dhcpd/module_lease.py85
-rw-r--r--junos-bootstrap/dhcpd/server_dhcp.py82
-rw-r--r--junos-bootstrap/dhcpd/terminal.log26
3 files changed, 171 insertions, 22 deletions
diff --git a/junos-bootstrap/dhcpd/module_lease.py b/junos-bootstrap/dhcpd/module_lease.py
new file mode 100644
index 0000000..8538723
--- /dev/null
+++ b/junos-bootstrap/dhcpd/module_lease.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+'''
+ Created by Jonas 'j' Lindstad for The Gathering 2015
+ License: GPLv3
+
+ Class used to fetch data from the Postgres DB
+
+ Usage examples:
+ lease.debug = True
+ x = lease({'distro_name': 'distro-test', 'distro_phy_port': 'ge-0/0/6'}).get_dict()
+ print('key lookup - hostname: %s' % x['hostname'])
+'''
+
+import psycopg2
+import psycopg2.extras
+
+# settings
+settings = dict(
+ db = dict(
+ user = 'bootstrap',
+ password = 'asdf',
+ dbname = 'bootstrap',
+ host = 'localhost'
+ )
+)
+
+# connect to Postgres DB
+connect_params = ("dbname='%s' user='%s' host='%s' password='%s'" % (settings['db']['dbname'], settings['db']['user'], settings['db']['host'], settings['db']['password']))
+conn = psycopg2.connect(connect_params)
+cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
+
+class lease(object):
+ debug = False
+
+ def __init__(self, identifiers):
+ if len(identifiers) > 0: # 1 or more identifiers - we're good to go
+
+ # build query string
+ where_pieces = []
+ for x in identifiers.items():
+ where_pieces.append(str(x[0]) + " = '" + str(x[1]) + "'")
+ where = ' AND '.join(where_pieces)
+ select = "SELECT * FROM switches WHERE " + where + " LIMIT 1"
+
+ if self.debug is True:
+ print('Executing query: ' + select)
+
+ cur.execute(select)
+
+ rows = cur.fetchall()
+ if len(rows) is 1:
+ if self.debug is True:
+ print('returned from DB:')
+ for key, value in rows[0].items():
+ print('%s: %s' % (key, value))
+
+ self.row = rows[0]
+ else:
+ self.row = False
+ else:
+ print('Missing identifier parameter')
+ exit()
+
+ def get_ip(self):
+ if self.row is not False:
+ return self.row['ip']
+ else:
+ print('identifiers (%s) not found' % self.row)
+ return False
+
+ def get_config(self):
+ if self.row is not False:
+ return self.row['config']
+ else:
+ print('identifiers (%s) not found' % self.row)
+ return False
+
+ def get_dict(self):
+ if self.row is not False:
+ return self.row
+ else:
+ print('identifiers (%s) not found' % self.row)
+ return False
diff --git a/junos-bootstrap/dhcpd/server_dhcp.py b/junos-bootstrap/dhcpd/server_dhcp.py
index 2dd1694..ae3f07c 100644
--- a/junos-bootstrap/dhcpd/server_dhcp.py
+++ b/junos-bootstrap/dhcpd/server_dhcp.py
@@ -12,22 +12,27 @@ 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
- * lease_db
- * Postgres as backend
- * Identifier as dict, which maps to Postgres row names. e.g. lease_db({'distro': 'a', 'port': 'b'}).get_dict()
+ OK * lease_db
+ OK * Postgres as backend
+ OK * Identifier as dict, which maps to Postgres row names. e.g. lease_db({'distro': 'a', 'port': 'b'}).get_dict()
+
'''
-import socket, binascii, time, IN, sys
+import socket, binascii, time, IN
from module_craft_option import craft_option
-
+from module_lease import lease
+
if not hasattr(IN,"SO_BINDTODEVICE"):
IN.SO_BINDTODEVICE = 25 #http://stackoverflow.com/a/8437870/541038
options_raw = {} # TODO - not a nice way to do things
+option_82_dataset = False
+option_82_1 = ''
class lease_db(object):
table = {
@@ -89,8 +94,8 @@ class lease_db(object):
# FUNCTIONS #
#############
-#generator for each of the dhcp fields
-def slicendice(msg,slices):
+# Generator for each of the dhcp fields
+def split_packet(msg,slices):
for x in slices:
yield msg[:x]
msg = msg[x:]
@@ -105,7 +110,7 @@ def hex_ip_to_str(hex_ip):
# formats a MAC address in the format "b827eb9a520f" to "b8:27:eb:9a:52:0f"
def format_hex_mac(hex_mac):
- return ':'.join(str(x) for x in chunk_hex(hex_mac))
+ return ':'.join(str(x) for x in chunk_hex(hex_mac))
# Parses DHCP options - raw = hex options
def parse_options(raw):
@@ -158,10 +163,20 @@ def parse_suboptions(option, raw):
chunked_length = len(chunked)
pointer = 0 # counter - next option start
dataset = {}
+
+ if option is 82: # Option 82 - custom shit: Setting global variable to list
+ global option_82_1
+ # global option_82_dataset
+ # option_82_dataset = []
+
while True:
- length = int(chunked[pointer+1], 16) # option length
- value = raw[2:(length+2)].strip()
- print(' --> suboption %s found - value: "%s"' % (int(chunked[0], 16), value))
+ length = int(chunked[pointer+1], 16) # option length in bytes
+ value = raw[4:(length*2)+(4)]
+
+ 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
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
@@ -174,23 +189,39 @@ def reqparse(message):
data=None
dhcpfields=[1,1,1,1,4,2,2,4,4,4,4,6,10,192,4,message.rfind(b'\xff'),1]
hexmessage=binascii.hexlify(message)
- messagesplit=[binascii.hexlify(x) for x in slicendice(message,dhcpfields)]
+ messagesplit=[binascii.hexlify(x) for x in split_packet(message,dhcpfields)]
# Test parsing
- # options = parse_options(b'3501013c3c4a756e697065722d6578323230302d632d3132742d3267000000000000000000000000000000000000000000000000000000000000000000000000005222012064697374726f2d746573743a67652d302f302f302e303a626f6f747374726170ff')
- options = parse_options(messagesplit[15])
+ options = parse_options(b'3501013c3c4a756e697065722d6578323230302d632d3132742d3267000000000000000000000000000000000000000000000000000000000000000000000000005222012064697374726f2d746573743a67652d302f302f302e303a626f6f747374726170ff')
+ # options = parse_options(messagesplit[15])
+
+ # print(parse_suboptions('82', options[82]))
if 82 in options: # contains option 82 - was forwarded by a DHCP relay
- print('DHCP packet contains option 82 -> should be processed')
+ print(' --> DHCP packet contains option 82 -> should be processed')
+ # print(options[82])
else:
- print('DHCP packet does not contain option 82 -> should be dropped')
+ print(' --> DHCP packet does not contain option 82 -> should be dropped')
if int(messagesplit[10]) is not 0:
- print('DHCP packet forwarded by relay %s' % hex_ip_to_str(messagesplit[10]))
+ print(' --> DHCP packet forwarded by relay %s' % hex_ip_to_str(messagesplit[10]))
else:
- print('DHCP packet not forwarded - direct request')
+ print(' --> DHCP packet not forwarded - direct request')
+
+ # print(b'DHCP XID/Transaction ID:' + messagesplit[4])
+
- print(b'DHCP XID/Transaction ID:' + messagesplit[4])
+
+
+ # Testing - do DB lookup based on option 82
+ if len(option_82_1) > 0:
+ # print(option_82_1)
+ (distro, phy, vlan) = option_82_1.split(':')
+
+ # 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])
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' % format_hex_mac(messagesplit[11]))
@@ -207,7 +238,7 @@ def reqparse(message):
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_db('x').get_ip()) # New IP to client
+ 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
@@ -229,7 +260,7 @@ def reqparse(message):
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_db('x').get_ip()) # New IP to client <<<<<<<<<<<<<<<<<<<<<<<< ALL THE FUCKINGS IN THE WORLD
+ 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
@@ -245,16 +276,23 @@ def reqparse(message):
# common options for both DHCP REPLY and DHCP ACK - should be most of the options
data += craft_option(54).bytes(socket.inet_aton(address)) # Option 54 - DHCP server identifier
+ print(' --> Option 54 (DHCP server identifier): %s' % 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' % '???')
data += craft_option(1).ip(netmask) # Option 1 - Subnet mask
+ print(' --> Option 51 (Subnet mask): %s' % netmask)
# 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(lease_db('x').get_config()) + craft_option(3).string('http')) # Option 43 - ZTP
+ 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'
diff --git a/junos-bootstrap/dhcpd/terminal.log b/junos-bootstrap/dhcpd/terminal.log
new file mode 100644
index 0000000..26e69da
--- /dev/null
+++ b/junos-bootstrap/dhcpd/terminal.log
@@ -0,0 +1,26 @@
+j@lappie:~/git/tgmanage$ sudo python3 junos-bootstrap/dhcpd/server_dhcp.py
+starting main loop
+DHCP broadcast
+ --> processing DHCP options
+ --> option: 53: DHCP Discover (will not be used in reply)
+ --> option: 60: b'Juniper-ex2200-c-12t-2g\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+ --> processing suboption hook for option 82
+ --> suboption 1 found - value: "distro-test:ge-0/0/0.0:bootstrap"
+ --> finished processing suboption 82
+ --> finished processing options
+ --> DHCP packet contains option 82 -> should be processed
+ --> DHCP packet not forwarded - direct request
+ --> Found match in DB with distro_name:distro-test distro_phy_port:ge-0/0/0
+
+
+DHCP REQUEST - client MAC b'b8':b'27':b'eb':b'9a':b'52':b'0f'
+ --> crafting DHCP ACK response
+ --> Option 54 (DHCP server identifier): 10.0.100.2
+ --> Option 51 (Lease time): ???
+ --> Option 51 (Subnet mask): 255.255.255.0
+ --> Option 3 (Default gateway): 10.0.100.2
+ --> Option 43 (Vendor-specific option):
+ --> Suboption 1: /tg15-edge/e-00-0-test
+ --> Suboption 3: http
+ --> replying to <broadcast>
+