diff --git a/README.md b/README.md index feec9d0..7e0f8cc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ -# Midas +# midas +A topology aware zero touch provisioning (ZTP) tool for network devices. +##Β Supported operating systems: + - Juniper Junos + - Cisco IOS + +## Modules +### midasd +The socket DHCP portion of midas. + +### midast +The networkx topology portion of midas. + +### midasp +The YAML/Jinja provisioning portion of midas. + +## Installation / usage +``` + $ cd midas + $ chmod +x midas + $ sudo ./midas +``` + +## Tips + - When building your topology.yaml the visualisation builds top to bottom, arrange your nodes and edges in such a way that they correspond to the correct side of the graph, for neatness. + +## Dependencies + - networkx + - pygraphviz + - yaml + - jinja2 diff --git a/client b/client new file mode 100755 index 0000000..251594d --- /dev/null +++ b/client @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +import socket, sys + +MAX_BYTES = 1024 +ServerPort = 67 +ClientPort = 68 + +class DHCPClient(): + def client(self): + print("DHCP client is starting...\n") + dest = ('', ServerPort) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + s.bind(('0.0.0.0', ServerPort)) + + print("Sending Juniper DHCP discovery.") + packet = DHCPClient.mock_juniper_packet() + s.sendto(packet, dest) + + def mock_juniper_packet(): + return b'\x01\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x013\x04\x00\x01Q\x80\x0c\nLAB-SRX300\xff\x00' + + def craft_discover_packet(): + OP = bytes([0x01]) + HTYPE = bytes([0x01]) + HLEN = bytes([0x06]) + HOPS = bytes([0x00]) + XID = bytes([0x39, 0x03, 0xF3, 0x26]) + SECS = bytes([0x00, 0x00]) + FLAGS = bytes([0x00, 0x00]) + CIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + YIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + SIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + GIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + CHADDR1 = bytes([0x00, 0x05, 0x3C, 0x04]) + CHADDR2 = bytes([0x8D, 0x59, 0x00, 0x00]) + CHADDR3 = bytes([0x00, 0x00, 0x00, 0x00]) + CHADDR4 = bytes([0x00, 0x00, 0x00, 0x00]) + CHADDR5 = bytes(192) + Magiccookie = bytes([0x63, 0x82, 0x53, 0x63]) + DHCPOptions1 = bytes([53, 1, 1]) #DHCP Discover (value = 1) + DHCPOptions2 = bytes([50, 4, 0xC0, 0xA8, 0x01, 0x64]) + + packet = ( + OP + + HTYPE + + HLEN + + HOPS + + XID + + SECS + + FLAGS + + CIADDR + + YIADDR + + SIADDR + + GIADDR + + CHADDR1 + + CHADDR2 + + CHADDR3 + + CHADDR4 + + CHADDR5 + + Magiccookie + + DHCPOptions1 + + DHCPOptions2 + ) + + return packet + + def craft_request_packet(): + OP = bytes([0x01]) + HTYPE = bytes([0x01]) + HLEN = bytes([0x06]) + HOPS = bytes([0x00]) + XID = bytes([0x39, 0x03, 0xF3, 0x26]) + SECS = bytes([0x00, 0x00]) + FLAGS = bytes([0x00, 0x00]) + CIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + YIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + SIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + GIADDR = bytes([0x00, 0x00, 0x00, 0x00]) + CHADDR1 = bytes([0x00, 0x0C, 0x29, 0xDD]) + CHADDR2 = bytes([0x5C, 0xA7, 0x00, 0x00]) + CHADDR3 = bytes([0x00, 0x00, 0x00, 0x00]) + CHADDR4 = bytes([0x00, 0x00, 0x00, 0x00]) + CHADDR5 = bytes(192) + Magiccookie = bytes([0x63, 0x82, 0x53, 0x63]) + DHCPOptions1 = bytes([53, 1, 3]) #DHCP Discover (value = 3) + DHCPOptions2 = bytes([50, 4, 0xC0, 0xA8, 0x01, 0x64]) + DHCPOptions3 = bytes([54, 4, 0xC0, 0xA8, 0x01, 0x01]) + + packet = ( + OP + + HTYPE + + HLEN + + HOPS + + XID + + SECS + + FLAGS + + CIADDR + + YIADDR + + SIADDR + + GIADDR + + CHADDR1 + + CHADDR2 + + CHADDR3 + + CHADDR4 + + CHADDR5 + + Magiccookie + + DHCPOptions1 + + DHCPOptions2 + + DHCPOptions3 + ) + + return packet + +if __name__ == '__main__': + dhcp_client = DHCPClient() + dhcp_client.client() \ No newline at end of file diff --git a/dhcp/.DS_Store b/dhcp/.DS_Store new file mode 100644 index 0000000..c279a36 Binary files /dev/null and b/dhcp/.DS_Store differ diff --git a/dhcp/midasd.py b/dhcp/midasd.py new file mode 100644 index 0000000..d921f74 --- /dev/null +++ b/dhcp/midasd.py @@ -0,0 +1,375 @@ +import socket +import struct + +from dataclasses import dataclass +from datetime import datetime +from utils.log import log + +# Reference docs +# https://docs.microsoft.com/en-us/windows-server/troubleshoot/dynamic-host-configuration-protocol-basics + +FORMAT_STRING = ( + "!" # Specifies network byte order + "s" # OP: 1 byte + "s" # HTYPE: 1 byte + "s" # HLEN: 1 byte + "s" # HOPS: 1 byte + "4s" # XID: 4 bytes + "2s" # SECS: 2 bytes + "2s" # FLAGS: 2 bytes + "4s" # CIADDR: 4 bytes + "4s" # YIADDR: 4 bytes + "4s" # SIADDR: 4 bytes + "4s" # GIADDR: 4 bytes + "6s" # CHADDR: 6 bytes + "10s" # CHADDR: 10 bytes + "192s" # OVERFLOW: 192 bytes + "4s" # MAGICCOOKIE: 4 bytes +) # 240 bytes total + +OPTION_DESCRIPTIONS = { + 1: 'Subnet Mask', + 3: 'Default Gateway', + 12: 'Hostname', + 43: 'Vendor Specific Information', + 50: 'Requested IP Address', + 51: 'IP Address Lease Time', + 53: 'DHCP Message Type', + 54: 'DHCP Server IP Address', + 55: 'Parameter Request List', + 57: 'DHCP Maximum Message Size', + 60: 'Class Identifier', + 61: 'Client Identifier', + 67: 'Boot File Name', + 150: 'TFTP Server IP Address', +} + +@dataclass() +class DHCPPacket: + # Based on DHCP Discovery packet header from: https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol + #Β Packet header field byte sizing: https://support.huawei.com/enterprise/en/doc/EDOC1100058931/25cd2dfc/dhcp-messages + # DHCP packet types: https://www.omnisecu.com/tcpip/dhcp-dynamic-host-configuration-protocol-message-options.php + + OP: bytes + HTYPE: bytes + HLEN: bytes + HOPS: bytes + XID: bytes + SECS: bytes + FLAGS: bytes + CIADDR: bytes # Client IP address + YIADDR: bytes # DHCP Server IP address + SIADDR: bytes # Server IP address + GIADDR: bytes # Gateway IP address + CHADDR: bytes # Client hardware address + CHADDR_PADDING: bytes + OVERFLOW: bytes # Overflow space for BOOTP legacy + MCOOKIE: bytes + OPTIONS: bytes + END: bytes = bytes([0xFF]) + +class Packet(): + @staticmethod + def construct_tlv(_type, value): + ''' + Summary: + Constructs a DHCP TLV - Type | Length | Value. + + Takes: + _type: typically the DHCP option number + value: value of the TLV, can be text or integer + + Returns: + A bytes object TLV + ''' + if isinstance(value, str): + value = str.encode(value) + return bytes([_type, len(value)]) + value + + @staticmethod + def construct_junos_suboptions(config_filename): + ''' + Summary: + Constructs Junos specific suboptions required for Zero Touch Provisioning to work. + + 0: firmware file + 1: configuration file + 3: transfer mode + + Additional options are available, see: + https://www.juniper.net/documentation/us/en/software/junos/junos-install-upgrade/topics/topic-map/zero-touch-provision.html#id-zero-touch-provisioning-using-dhcp-options + + Takes: + config_filename: filename and path of the configuration file to be retrieved and applied to the client + + Returns: + A bytes object consisting of multiple TLVs + ''' + return Packet.construct_tlv( + _type=43, + value=b''.join( + [ + #Packet.construct_tlv(0, '/path/to/junosimage.tgz'), # Image filename and path, for upgrading firmware + Packet.construct_tlv(1, config_filename), # Configuration filename and path + Packet.construct_tlv(3, 'tftp'), # Transfer mode + ] + ) + ) + + @staticmethod + def construct_reply_packet(reference_packet, _type, source_ip, topology): + ''' + Summary: + Constructs a DHCP packet of type Offer or Ack, modelled from a received packet with some options changed/added. + Ack packets are the exact same as Offer packets, just with the packet type TLV adjusted. + Different options are changed/added based on client operating system extracted from the networkx Graph object. + + Takes: + reference_packet: received DHCP packet to model the reply packet from + _type: type of packet to construct, Offer or Ack + source_ip: IP address that the DHCP packet was received from + topology: networkx Graph object detailing the network topology + + Returns: + DHCP packet of type Offer or Ack + ''' + packet_data = struct.unpack(FORMAT_STRING, reference_packet[:240]) + reply_packet_object = DHCPPacket(*packet_data, OPTIONS=reference_packet[240:len(reference_packet)]) + + giaddr, yiaddr = Packet.process_giaddr(source_ip) + client_device_name, client_device_os = topology.get_client_calling_for_ip(source_ip, _type) + + reply_packet_object.OP = bytes([0x02]) + reply_packet_object.YIADDR = bytes([yiaddr[0], yiaddr[1], yiaddr[2], yiaddr[3]]) # Client IP + reply_packet_object.SIADDR = bytes([172, 16, 0, 200]) # Server IP: 172.16.0.200 + + if _type == 'offer': + packet_type = bytes([53, 1, 2]) # DHCP offer packet + elif _type == 'ack': + packet_type = bytes([53, 1, 5]) # DHCP ack packet + + if client_device_os == 'junos': + reply_packet_object.OPTIONS = b"".join( + [ + packet_type, + bytes([54, 4, 172, 16, 0, 200]), # Server identifier: 172.16.0.200 + bytes([51, 4, 0x00, 0x01, 0x51, 0x80]), # Lease time: 86400 + bytes([1, 4, 255, 255, 255, 254]), # Subnet mask: 255.255.255.254 + bytes([3, 4, giaddr[0], giaddr[1], giaddr[2], giaddr[3]]), # Default gateway + bytes([150, 4, 172, 16, 0, 200]), # TFTP server: 172.16.0.200 + Packet.construct_junos_suboptions(f'/configs/{client_device_name}.conf'), # Juniper specific suboptions + ] + ) + elif client_device_os == 'cisco_ios': + reply_packet_object.OPTIONS = b"".join( + [ + packet_type, + bytes([54, 4, 172, 16, 0, 200]), # Server identifier: 172.16.0.200 + bytes([51, 4, 0x00, 0x01, 0x51, 0x80]), # Lease time: 86400 + bytes([1, 4, 255, 255, 255, 254]), # Subnet mask: 255.255.255.254 + bytes([3, 4, giaddr[0], giaddr[1], giaddr[2], giaddr[3]]), # Default gateway + bytes([150, 4, 172, 16, 0, 200]), # TFTP server: 172.16.0.200 + Packet.construct_tlv(67, f'/configs/{client_device_name}.conf'), # Cisco specific bootfile + ] + ) + + reply_packet_list = [] + + for field, byte_value in reply_packet_object.__dict__.items(): + reply_packet_list.append(byte_value) + + reply_packet = b''.join(value for value in reply_packet_list) + + return reply_packet + + @staticmethod + def examine_packet(raw_packet): + ''' + Summary: + Examines a DHCP packet and splits it into packet data and DHCP options. + It's expected that DHCP packet length after 240 will be DHCP options. + + Takes: + raw_packet: raw DHCP packet to examine + + Returns: + packet: DHCP packet data minus options + options: DHCP packet options + options_descriptions: DHCP packet options descriptions + ''' + packet_data = struct.unpack(FORMAT_STRING, raw_packet[:240]) + packet = DHCPPacket(*packet_data, OPTIONS=raw_packet[240:len(raw_packet)]) + + options, options_descriptions = Packet.extract_options(packet.OPTIONS) + + return packet, options, options_descriptions + + @staticmethod + def extract_options(packet_data): + ''' + Summary: + Extracts DHCP options from a given raw DHCP packet. + + Takes: + packet_data: DHCP packet as raw bytes + + Returns: + option_dict: dictionairy of DHCP options extracted from a packet + option_description_dict: dictionairy of DHCP option descriptions that were found in a packet + ''' + option_dict = {} + option_description_dict = {} + + index = 0 + while index < len(packet_data): + option_number = packet_data[index] + if option_number == 255: + break + option_length = packet_data[index + 1] + option_value, option_description = Packet.make_human_readable(option_number, packet_data[index + 2 : index + 2 + option_length]) + + option_dict[option_number] = option_value + option_description_dict[option_number] = option_description + index += 2 + option_length + + + return option_dict, option_description_dict + + @staticmethod + def make_human_readable(option_number, raw_value): + ''' + Summary: + Takes a raw DHCP option and makes it human readable. + + DHCP options: + https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml + + Takes: + option_number: DHCP option number + raw_value: raw bytes value received with the option number + + Returns: + option_value: human readable value of the option number + option_description: description of the option + ''' + if option_number in [1, 3, 50, 54, 150]: + octets = list(raw_value) + option_value = f'{octets[0]}.{octets[1]}.{octets[2]}.{octets[3]}' + elif option_number in [50, 55]: + option_value = list(raw_value) + elif option_number == 53: + option_value = list(raw_value)[0] + elif option_number in [60, 12, 61, 67, 43]: + option_value = raw_value.decode() + elif option_number in [51, 57]: + option_value = int.from_bytes(raw_value, byteorder='big') + else: + option_value = raw_value + + if option_number in OPTION_DESCRIPTIONS: + option_description = OPTION_DESCRIPTIONS[option_number] + else: + option_description = 'Unknown' + + return option_value, option_description + + @staticmethod + def process_giaddr(giaddr): + ''' + Summary: + Calculates IP address to offer the client and turns string giaddr into a list of 4 integers. + + Takes: + giaddr: gateway or relay IP that the DHCP packet was received from (string) + + Returns: + giaddr: gateway or relay IP that the DHCP packet was received from (list of 4 integers) + yiaddr: IP to offer the client (list of 4 integers) + ''' + octets = giaddr.split('.') + last_octet = int(octets[3]) + giaddr = [int(octets[0]), int(octets[1]), int(octets[2]), int(octets[3])] + + if last_octet % 2 == 0: + yiaddr = [int(octets[0]), int(octets[1]), int(octets[2]), int(last_octet + 1)] + else: + yiaddr = [int(octets[0]), int(octets[1]), int(octets[2]), int(last_octet - 1)] + + return giaddr, yiaddr + +class DHCPServer(): + MAX_BYTES = 1024 + SERVER_IP = '172.16.0.200' + PORT = 67 + + def create_socket(self): + ''' + Summary: + Creates a socket to allow the DHCP server to send and receive data to/from. + + Socket characteristics: + AF_INET: type of socket, address format (host, port) + SOCK_DGRAM: socket protocol, UDP + SOL_SOCKET (socket options): + SO_REUSEADDR: socket address and port can be reused + SO_BROADCAST: datagrams can be broadcast from this socket + + Socket bound to: + SERVER_IP + PORT + ''' + _socket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + _socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1) + _socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) + _socket.bind((self.SERVER_IP, self.PORT)) + + return _socket + + def run(self, topology): + ''' + Summary: + Runs the DHCP server allowing packets to be sent and received through the created socket. + + Takes: + topology: networkx Graph object detailing the network topology + ''' + log('DHCP server is starting...', 'info') + socket = self.create_socket() + + while True: + try: + log('Waiting for DHCP packets...', 'info') + received_packet, (source_address, source_port) = socket.recvfrom(self.MAX_BYTES) + log(f'DHCP packet received from IP {source_address} on port {source_port}', 'info') + packet_data, packet_options, option_descriptions = Packet.examine_packet(received_packet) + log(f'Received packet options:', 'info') + for option_number, option_value in packet_options.items(): + log(f'{option_number} [{option_descriptions[option_number]}]: {option_value}', 'info') + + if packet_options[53] == 1: + log('Received DHCP discover packet', 'info') + offer_packet = Packet.construct_reply_packet(received_packet, 'offer', source_address, topology) + log('Constructing DHCP offer packet...', 'info') + packet_data, packet_options, option_descriptions = Packet.examine_packet(offer_packet) + log(f'Offer packet options:', 'info') + for option_number, option_value in packet_options.items(): + log(f'{option_number} [{option_descriptions[option_number]}]: {option_value}', 'info') + socket.sendto(offer_packet, (source_address, self.PORT)) + + elif packet_options[53] == 3: + log('Received DHCP request packet', 'info') + ack_packet = Packet.construct_reply_packet(offer_packet, 'ack', source_address, topology) + log('Constructing DHCP ack packet...', 'info') + packet_data, packet_options, option_descriptions = Packet.examine_packet(ack_packet) + log(f'Ack packet options:', 'info') + for option_number, option_value in packet_options.items(): + log(f'{option_number} [{option_descriptions[option_number]}]: {option_value}', 'info') + socket.sendto(ack_packet, (source_address, self.PORT)) + + elif packet_options[53] == 7: + log('Received DHCP release packet', 'info') + log('See you around, partner\U0001F920', 'info') + + except KeyboardInterrupt: + log('Exiting...', 'info') + break diff --git a/dhcp/packet_examples/.DS_Store b/dhcp/packet_examples/.DS_Store new file mode 100644 index 0000000..1036a0f Binary files /dev/null and b/dhcp/packet_examples/.DS_Store differ diff --git a/dhcp/packet_examples/cisco_ios/Ack b/dhcp/packet_examples/cisco_ios/Ack new file mode 100644 index 0000000..1b76a83 --- /dev/null +++ b/dhcp/packet_examples/cisco_ios/Ack @@ -0,0 +1 @@ +b'\x02\x01\x06\x01\x00\x00\x02+\x00\x04\x80\x00\x00\x00\x00\x00\n\x00\x00\x03\xac\x10\x00\xc8\n\x00\x00\x02\xcc\x16~/!Z\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x056\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x02\x96\x04\xac\x10\x00\xc8C\x17/configs/LAB-897VA.conf\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/cisco_ios/Discover b/dhcp/packet_examples/cisco_ios/Discover new file mode 100644 index 0000000..a9ec75e --- /dev/null +++ b/dhcp/packet_examples/cisco_ios/Discover @@ -0,0 +1 @@ +b'\x01\x01\x06\x01\x00\x00\x02+\x00\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x02\xcc\x16~/!Z\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x019\x02\x04\xb0=\x19\x00cisco-cc16.7e2f.215a-Gi8\x0c\x06Router7\x0b\x01B\x06\x0f,\x03C\x0c!\x96+<\x08ciscopnp\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/cisco_ios/Offer b/dhcp/packet_examples/cisco_ios/Offer new file mode 100644 index 0000000..44f41ab --- /dev/null +++ b/dhcp/packet_examples/cisco_ios/Offer @@ -0,0 +1 @@ +b'\x02\x01\x06\x01\x00\x00\x02+\x00\x04\x80\x00\x00\x00\x00\x00\n\x00\x00\x03\xac\x10\x00\xc8\n\x00\x00\x02\xcc\x16~/!Z\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x026\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x02\x96\x04\xac\x10\x00\xc8C\x17/configs/LAB-897VA.conf\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/cisco_ios/Release b/dhcp/packet_examples/cisco_ios/Release new file mode 100644 index 0000000..4f8708a --- /dev/null +++ b/dhcp/packet_examples/cisco_ios/Release @@ -0,0 +1 @@ +b'\x01\x01\x06\x00\x00\x00\x02+\x00\x00\x00\x00\n\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcc\x16~/!Z\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x076\x04\xac\x10\x00\xc8=\x19\x00cisco-cc16.7e2f.215a-Gi8<\x08ciscopnp\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/cisco_ios/Request b/dhcp/packet_examples/cisco_ios/Request new file mode 100644 index 0000000..76d2329 --- /dev/null +++ b/dhcp/packet_examples/cisco_ios/Request @@ -0,0 +1 @@ +b'\x01\x01\x06\x01\x00\x00\x02+\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x02\xcc\x16~/!Z\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x039\x02\x04\xb0=\x19\x00cisco-cc16.7e2f.215a-Gi86\x04\xac\x10\x00\xc82\x04\n\x00\x00\x03\x0c\x06Router7\x0b\x01B\x06\x0f,\x03C\x0c!\x96+<\x08ciscopnp\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/junos/Ack b/dhcp/packet_examples/junos/Ack new file mode 100644 index 0000000..f7cc85b --- /dev/null +++ b/dhcp/packet_examples/junos/Ack @@ -0,0 +1 @@ +b'\x02\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\n\x00\x00\x01\xac\x10\x00\xc8\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x056\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x00\x96\x04\xac\x10\x00\xc8+ \x01\x18/configs/LAB-SRX300.conf\x03\x04tftp\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/junos/Discover b/dhcp/packet_examples/junos/Discover new file mode 100644 index 0000000..327c6cf --- /dev/null +++ b/dhcp/packet_examples/junos/Discover @@ -0,0 +1 @@ +b'\x01\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x013\x04\x00\x01Q\x80\x0c\nLAB-SRX300\xff\x00' \ No newline at end of file diff --git a/dhcp/packet_examples/junos/Offer b/dhcp/packet_examples/junos/Offer new file mode 100644 index 0000000..3ffbf60 --- /dev/null +++ b/dhcp/packet_examples/junos/Offer @@ -0,0 +1 @@ +b'\x02\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\n\x00\x00\x01\xac\x10\x00\xc8\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x026\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x00\x96\x04\xac\x10\x00\xc8+ \x01\x18/configs/LAB-SRX300.conf\x03\x04tftp\xff' \ No newline at end of file diff --git a/dhcp/packet_examples/junos/Release b/dhcp/packet_examples/junos/Release new file mode 100644 index 0000000..b1e0f94 --- /dev/null +++ b/dhcp/packet_examples/junos/Release @@ -0,0 +1 @@ +b'\x01\x01\x06\x00;\x1b\x1f\xf6\x00\x00\x00\x00\n\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\xbf\x94\xb3n\x0e\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc6\x04\xac\x10\x00\xc85\x01\x07\x0c\nLAB-SRX300\xff\x00' \ No newline at end of file diff --git a/dhcp/packet_examples/junos/Request b/dhcp/packet_examples/junos/Request new file mode 100644 index 0000000..51ec065 --- /dev/null +++ b/dhcp/packet_examples/junos/Request @@ -0,0 +1 @@ +b'\x01\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc6\x04\xac\x10\x00\xc87\x0e\x033\x01\x0f\x06BCx,+\x96\x0c\x07*2\x04\n\x00\x00\x015\x01\x033\x04\x00\x01Q\x80\x0c\nLAB-SRX300\xff\x00' \ No newline at end of file diff --git a/dhcp/settings.yaml b/dhcp/settings.yaml new file mode 100644 index 0000000..2a3cd5b --- /dev/null +++ b/dhcp/settings.yaml @@ -0,0 +1,2 @@ +DHCP_SERVER_IP: 172.16.0.200 +TFTP_SERVER_IP: 172.16.0.200 \ No newline at end of file diff --git a/logs/Cisco and Juniper.log b/logs/Cisco and Juniper.log new file mode 100644 index 0000000..92f72f8 Binary files /dev/null and b/logs/Cisco and Juniper.log differ diff --git a/logs/Cisco.log b/logs/Cisco.log new file mode 100644 index 0000000..e725654 Binary files /dev/null and b/logs/Cisco.log differ diff --git a/logs/Juniper.log b/logs/Juniper.log new file mode 100644 index 0000000..a71e2bd --- /dev/null +++ b/logs/Juniper.log @@ -0,0 +1,57 @@ +2023-04-07 20:17:16,743 [INFO] πŸ‘Ύ | Building topology... +2023-04-07 20:17:16,793 [INFO] πŸ‘Ύ | Topology name: Lab +2023-04-07 20:17:16,796 [INFO] πŸ‘Ύ | Drawing topology... +2023-04-07 20:17:17,538 [INFO] πŸ‘Ύ | Topology drawn, saved as: topology/topology.png +2023-04-07 20:17:17,540 [INFO] πŸ‘Ύ | Topology build complete +2023-04-07 20:17:17,543 [INFO] πŸ‘½ | Rendering configs... +2023-04-07 20:17:17,575 [INFO] πŸ‘½ | Rendered /srv/tftp/configs/LAB-897VA.conf +2023-04-07 20:17:17,597 [INFO] πŸ‘½ | Rendered /srv/tftp/configs/LAB-SRX300.conf +2023-04-07 20:17:17,599 [INFO] πŸ‘½ | Config rendering complete +2023-04-07 20:17:17,602 [INFO] πŸ€– | DHCP server is starting... +2023-04-07 20:17:17,604 [INFO] πŸ€– | Waiting for DHCP packets... +2023-04-07 20:19:23,166 [INFO] πŸ€– | DHCP packet received from IP 10.0.0.0 on port 67 +2023-04-07 20:19:23,169 [INFO] πŸ€– | Received packet options: +2023-04-07 20:19:23,172 [INFO] πŸ€– | 53 [DHCP Message Type]: 1 +2023-04-07 20:19:23,175 [INFO] πŸ€– | 51 [IP Address Lease Time]: 86400 +2023-04-07 20:19:23,178 [INFO] πŸ€– | 12 [Hostname]: LAB-SRX300 +2023-04-07 20:19:23,180 [INFO] πŸ€– | Received DHCP discover packet +2023-04-07 20:19:23,183 [INFO] πŸ‘Ύ | Saw client: LAB-SRX300 +2023-04-07 20:19:23,186 [INFO] πŸ‘Ύ | OS: junos +2023-04-07 20:19:23,188 [INFO] πŸ€– | Constructing DHCP offer packet... +2023-04-07 20:19:23,191 [INFO] πŸ€– | Offer packet options: +2023-04-07 20:19:23,193 [INFO] πŸ€– | 53 [DHCP Message Type]: 2 +2023-04-07 20:19:23,196 [INFO] πŸ€– | 54 [DHCP Server IP Address]: 172.16.0.200 +2023-04-07 20:19:23,198 [INFO] πŸ€– | 51 [IP Address Lease Time]: 86400 +2023-04-07 20:19:23,200 [INFO] πŸ€– | 1 [Subnet Mask]: 255.255.255.254 +2023-04-07 20:19:23,202 [INFO] πŸ€– | 3 [Default Gateway]: 10.0.0.0 +2023-04-07 20:19:23,205 [INFO] πŸ€– | 150 [TFTP Server IP Address]: 172.16.0.200 +2023-04-07 20:19:23,207 [INFO] πŸ€– | 43 [Vendor Specific Information]: /configs/LAB-SRX300.conftftp +2023-04-07 20:19:23,209 [INFO] πŸ€– | Waiting for DHCP packets... +2023-04-07 20:19:23,212 [INFO] πŸ€– | DHCP packet received from IP 10.0.0.0 on port 67 +2023-04-07 20:19:23,215 [INFO] πŸ€– | Received packet options: +2023-04-07 20:19:23,217 [INFO] πŸ€– | 54 [DHCP Server IP Address]: 172.16.0.200 +2023-04-07 20:19:23,219 [INFO] πŸ€– | 55 [Parameter Request List]: [3, 51, 1, 15, 6, 66, 67, 120, 44, 43, 150, 12, 7, 42] +2023-04-07 20:19:23,221 [INFO] πŸ€– | 50 [Requested IP Address]: 10.0.0.1 +2023-04-07 20:19:23,223 [INFO] πŸ€– | 53 [DHCP Message Type]: 3 +2023-04-07 20:19:23,225 [INFO] πŸ€– | 51 [IP Address Lease Time]: 86400 +2023-04-07 20:19:23,227 [INFO] πŸ€– | 12 [Hostname]: LAB-SRX300 +2023-04-07 20:19:23,229 [INFO] πŸ€– | Received DHCP request packet +2023-04-07 20:19:23,231 [INFO] πŸ€– | Constructing DHCP ack packet... +2023-04-07 20:19:23,233 [INFO] πŸ€– | Ack packet options: +2023-04-07 20:19:23,235 [INFO] πŸ€– | 53 [DHCP Message Type]: 5 +2023-04-07 20:19:23,237 [INFO] πŸ€– | 54 [DHCP Server IP Address]: 172.16.0.200 +2023-04-07 20:19:23,239 [INFO] πŸ€– | 51 [IP Address Lease Time]: 86400 +2023-04-07 20:19:23,241 [INFO] πŸ€– | 1 [Subnet Mask]: 255.255.255.254 +2023-04-07 20:19:23,243 [INFO] πŸ€– | 3 [Default Gateway]: 10.0.0.0 +2023-04-07 20:19:23,245 [INFO] πŸ€– | 150 [TFTP Server IP Address]: 172.16.0.200 +2023-04-07 20:19:23,247 [INFO] πŸ€– | 43 [Vendor Specific Information]: /configs/LAB-SRX300.conftftp +2023-04-07 20:19:23,249 [INFO] πŸ€– | Waiting for DHCP packets... +2023-04-07 20:22:32,406 [INFO] πŸ€– | DHCP packet received from IP 10.0.0.1 on port 68 +2023-04-07 20:22:32,410 [INFO] πŸ€– | Received packet options: +2023-04-07 20:22:32,413 [INFO] πŸ€– | 54 [DHCP Server IP Address]: 172.16.0.200 +2023-04-07 20:22:32,417 [INFO] πŸ€– | 53 [DHCP Message Type]: 7 +2023-04-07 20:22:32,419 [INFO] πŸ€– | 12 [Hostname]: LAB-SRX300 +2023-04-07 20:22:32,421 [INFO] πŸ€– | Received DHCP release packet +2023-04-07 20:22:32,422 [INFO] πŸ€– | See you around, partner🀠 +2023-04-07 20:22:32,424 [INFO] πŸ€– | Waiting for DHCP packets... +2023-04-07 20:23:00,802 [INFO] πŸ€– | Exiting... diff --git a/logs/midas_2023-04-08_14:16:10.log b/logs/midas_2023-04-08_14:16:10.log new file mode 100644 index 0000000..500dff6 --- /dev/null +++ b/logs/midas_2023-04-08_14:16:10.log @@ -0,0 +1,11 @@ +2023-04-08 14:16:10,531 [INFO] πŸ‘Ύ | Building topology... +2023-04-08 14:16:10,581 [INFO] πŸ‘Ύ | Topology name: Lab +2023-04-08 14:16:10,584 [INFO] πŸ‘Ύ | Drawing topology... +2023-04-08 14:16:11,510 [INFO] πŸ‘Ύ | Topology drawn, saved as: topology/topology.png +2023-04-08 14:16:11,512 [INFO] πŸ‘Ύ | Topology build complete +2023-04-08 14:16:11,515 [INFO] πŸ‘½ | Rendering configs... +2023-04-08 14:16:11,549 [INFO] πŸ‘½ | Rendered /srv/tftp/configs/LAB-897VA.conf +2023-04-08 14:16:11,573 [INFO] πŸ‘½ | Rendered /srv/tftp/configs/LAB-SRX300.conf +2023-04-08 14:16:11,575 [INFO] πŸ‘½ | Config rendering complete +2023-04-08 14:16:11,580 [INFO] πŸ€– | DHCP server is starting... +2023-04-08 14:16:11,582 [INFO] πŸ€– | Waiting for DHCP packets... diff --git a/midas b/midas new file mode 100755 index 0000000..6be1afd --- /dev/null +++ b/midas @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +from utils.log import get_loggers +from topology.midast import Topology +from provisioning.midasp import Provisioning +from dhcp.midasd import DHCPServer + +if __name__ == '__main__': + get_loggers() + topology = Topology() + topology.build() + provisioning = Provisioning() + provisioning.render(topology) + dhcp_server = DHCPServer() + dhcp_server.run(topology) + +# Todo +# 1. Implement giaddr inspection as backup to packet source address, current using socket source IP instead + # Not needed? Adds further complexity and lines of code, socket already reports source IP address +# 2. Add fingerprints / persistent lease storage system (ensure that duplicate packets are only printed once, i.e. Cisco sends multiple release packets) +# 3. Work out what variables can be made more dynamic, i.e. reply packet options - build from a YAML file? +# 4. Make IP allocation & config var (YAML) generation automatic +# 5. Implement socket.inet_aton() function +# 6. PCAP from PC to capture DHCP option values that are not yet printed in a human readable format +# 7. Mount lab Pi as NFS share on valykrie Pi to create automatic backups with duplicati diff --git a/provisioning/.DS_Store b/provisioning/.DS_Store new file mode 100644 index 0000000..3d15c86 Binary files /dev/null and b/provisioning/.DS_Store differ diff --git a/provisioning/midasp.py b/provisioning/midasp.py new file mode 100644 index 0000000..7d82c32 --- /dev/null +++ b/provisioning/midasp.py @@ -0,0 +1,35 @@ +import yaml + +from jinja2 import Environment, FileSystemLoader +from utils.log import log + +class Provisioning(): + def render(self, topology): + ''' + Summary: + Builds a configuration file for all network devices in the topology. + + Takes: + topology: networkx Graph object detailing the network topology + ''' + log('Rendering configs...', 'info') + for node in topology.G.nodes: + if topology.G.nodes[node].get('no_render') == True: + continue + + node_os = topology.G.nodes[node]['os'] + + template_path = Environment(loader=FileSystemLoader('provisioning/templates/'), trim_blocks=True, lstrip_blocks=True) + device_vars = yaml.load(open('provisioning/vars/' + node + '.yaml'), Loader=yaml.SafeLoader) + + template = template_path.get_template(node_os + '.j2') + rendered_config = template.render(device_vars) + + config_path = '/srv/tftp/configs/' + node + '.conf' + + with open(config_path, 'w') as configuration: + configuration.write(rendered_config) + + log(f'Rendered {config_path}', 'info') + + log('Config rendering complete', 'info') diff --git a/provisioning/templates/cisco_ios.j2 b/provisioning/templates/cisco_ios.j2 new file mode 100644 index 0000000..8dbdd9d --- /dev/null +++ b/provisioning/templates/cisco_ios.j2 @@ -0,0 +1,199 @@ +service timestamps debug datetime msec +service timestamps log datetime msec +no service password-encryption +no service password-recovery +! +hostname {{ hostname }} +! +boot-start-marker +boot-end-marker +! +! +! +no aaa new-model +! +! +! +! +! +! +! +! +! +! +! +! +! +! +no ip domain lookup +ip domain name yourdomain.com +ip cef +no ipv6 cef +! +! +! +! +! +multilink bundle-name authenticated +! +! +! +! +! +! +! +! +cts logging verbose +license udi pid C897VA-K9 sn FCZ202990B6 +! +! +username neteng privilege 15 secret Juniper1 +! +! +! +! +! +controller VDSL 0 + shutdown +lldp run +! +ip ssh version 2 +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + ip address {{ loopback_ip }} 255.255.255.255 +! +interface ATM0 + no ip address + shutdown + no atm ilmi-keepalive +! +interface BRI0 + no ip address + encapsulation hdlc + shutdown + isdn termination multidrop +! +interface Ethernet0 + no ip address + shutdown +! +interface GigabitEthernet0 + description "Management Network" + switchport access vlan 10 + no ip address +! +interface GigabitEthernet1 + description "{{ hostname }} gigabitEthernet1 <--> ge-0/0/1 LAB-SRX300" + switchport access vlan 20 + no ip address +! +interface GigabitEthernet2 + description "{{ hostname }} gigabitEthernet2 <--> ge-0/0/2 LAB-SRX300" + switchport access vlan 30 + no ip address +! +interface GigabitEthernet3 + no ip address +! +interface GigabitEthernet4 + no ip address +! +interface GigabitEthernet5 + no ip address +! +interface GigabitEthernet6 + no ip address +! +interface GigabitEthernet7 + no ip address +! +interface GigabitEthernet8 + description "{{ hostname }} gigabitEthernet2 <--> gigabitEthernet1 LAB-RELAY" + ip address 10.0.0.3 255.255.255.254 + duplex auto + speed auto + no shut +! +interface Vlan1 + no ip address +! +interface Vlan10 + ip address {{ management_ip }} 255.255.255.0 +! +interface Vlan20 + ip address 10.0.0.5 255.255.255.254 + ip ospf authentication message-digest + ip ospf message-digest-key 1 md5 Juniper1 + ip ospf network point-to-point + ip ospf 1 area 0 +! +interface Vlan30 + ip address 10.0.0.7 255.255.255.254 + ip ospf authentication message-digest + ip ospf message-digest-key 1 md5 Juniper1 + ip ospf network point-to-point + ip ospf 1 area 0 +! +router ospf 1 + router-id {{ loopback_ip }} + passive-interface Loopback0 +! +ip forward-protocol nd +no ip http server +no ip http secure-server +! +! +! +! +! +control-plane +! +! +mgcp behavior rsip-range tgcp-only +mgcp behavior comedia-role none +mgcp behavior comedia-check-media-src disable +mgcp behavior comedia-sdp-force disable +! +mgcp profile default +! +! +! +! +! +! +! +line con 0 + login local + no modem enable +line aux 0 +line vty 0 4 + login local + transport input ssh telnet +line vty 5 15 + login local + transport input ssh telnet +! +scheduler allocate 20000 1000 +! +! +! +event manager applet crypto-key + event timer cron cron-entry "@reboot" + action 1.0 cli command "enable" + action 1.1 cli command "config t" + action 1.2 cli command "file prompt quiet" + action 1.3 cli command "crypto key generate rsa modulus 2048" + action 1.4 cli command "no event manager applet crypto-key" + action 1.5 cli command "do wr mem" +! +end \ No newline at end of file diff --git a/provisioning/templates/junos.j2 b/provisioning/templates/junos.j2 new file mode 100644 index 0000000..d61e95c --- /dev/null +++ b/provisioning/templates/junos.j2 @@ -0,0 +1,280 @@ +system { + host-name {{ hostname }}; + root-authentication { + plain-text-password-value "Juniper1"; + } + login { + user datatech { + uid 2001; + class read-only; + authentication { + plain-text-password-value "Juniper1"; + } + } + user neteng { + uid 2000; + class super-user; + authentication { + plain-text-password-value "Juniper1"; + } + } + } + services { + ssh { + root-login allow; + } + netconf { + ssh; + } + } + tacplus-server { + 10.10.10.10 secret "Juniper1"; ## SECRET-DATA + 11.11.11.11 secret "Juniper1"; ## SECRET-DATA + } + syslog { + archive size 100k files 3; + user * { + any emergency; + } + file messages { + any notice; + authorization info; + } + file interactive-commands { + interactive-commands any; + } + } +} +security { + authentication-key-chains { + key-chain BGP-KC-LHR14-R101-NCL62-R2 { + key 1 { + secret "$9$Vyws4JZjq.57-YoZU.m"; ## SECRET-DATA + start-time "2022-7-1.00:00:00 +0000"; + } + key 2 { + secret "$9$0Yfd1clVbs2oJdV69pOSybs2aUj5TF9AuiHP5FnpuNdVbs24aZ"; ## SECRET-DATA + start-time "2023-4-3.15:13:45 +0000"; + } + } + key-chain BGP-KC-NCL62-R2-SC-FW2 { + key 1 { + secret "$9$8x0Xxdws4ZGiKM7Vs2GU"; ## SECRET-DATA + start-time "2022-7-1.00:00:00 +0000"; + } + key 2 { + secret "$9$B74EeW24JikmlKDH.m3n1RhyvWxNV24JikSreKLX7-VYGjzF6"; ## SECRET-DATA + start-time "2023-4-3.15:42:04 +0000"; + } + } + key-chain BRMA-KC-LHR30-R101-NCL60-R1 { + key 1 { + apply-flags omit; + secret "$9$1HCREyeK87NbuOhrKMN-"; ## SECRET-DATA + key-name 4953bd1120ffcc31e1d044870c52d67b215c04f0c2ba1fccc970fa16d18a6b6f; + start-time "2023-3-31.14:22:24 +0000"; + } + } + } + forwarding-options { + family { + mpls { + mode packet-based; + } + } + } + macsec { + connectivity-association BRMA-WAN-LHR30-R101-NCL60-R1 { + security-mode static-cak; + mka { + transmit-interval 6000; + sak-rekey-interval 60; + } + pre-shared-key-chain BRMA-KC-LHR30-R101-NCL60-R1; + } + interfaces { + ge-0/0/7 { + connectivity-association BRMA-WAN-LHR30-R101-NCL60-R1; + } + } + } +} +interfaces { + ge-0/0/0 { + description "{{ hostname }} ge-0/0/0 <--> gigabitEthernet0 LAB-RELAY"; + unit 0 { + family inet { + address 10.0.0.1/31; + } + } + } + ge-0/0/1 { + description "{{ hostname }} ge-0/0/1 <--> gigabitEthernet1 LAB-897VA"; + unit 0 { + family inet { + address 10.0.0.4/31; + } + } + } + ge-0/0/2 { + description "{{ hostname }} ge-0/0/2 <--> gigabitEthernet2 LAB-897VA"; + unit 0 { + family inet { + address 10.0.0.6/31; + } + } + } + ge-0/0/5 { + description "Management Network"; + unit 0 { + family inet { + address {{ management_ip }}/24; + } + } + } + lo0 { + unit 0 { + family inet { + address {{ loopback_ip }}/32; + } + } + } +} +snmp { + v3 { + usm { + local-engine { + user snmp-user { + authentication-sha { + authentication-password "Juniper1"; ## SECRET-DATA + } + privacy-aes128 { + privacy-password "Juniper1"; ## SECRET-DATA + } + } + } + } + } +} +policy-options { + policy-statement BN-10-RMA-EXPORT { + term BGP { + from protocol bgp; + then { + community add BN-10-RMA-TARGET; + accept; + } + } + term OSPF { + from protocol ospf; + then { + community add BN-10-RMA-TARGET; + accept; + } + } + term AGGREGATE { + from protocol aggregate; + then { + community add BN-10-RMA-TARGET; + accept; + } + } + term REJECT-ALL { + then reject; + } + } + policy-statement BN-10-RMA-IMPORT { + term BGP { + from { + protocol bgp; + community BN-10-RMA-TARGET; + } + then accept; + } + term REJECT-ALL { + then reject; + } + } + community BN-10-RMA-TARGET members target:65100:1000; +} +access { + radius-server { + 10.10.10.10 secret "Juniper1"; ## SECRET-DATA + 11.11.11.11 secret "Juniper1"; ## SECRET-DATA + } +} +routing-instances { + BN-10-RMA { + protocols { + bgp { + group SEC-NET-FW { + type external; + description "eBGP to Security Network Firewall"; + local-address 192.168.32.78; + hold-time 30; + peer-as 64900; + neighbor 192.168.32.79 { + description ncl62-sc-fw2; + authentication-key-chain BGP-KC-NCL62-R2-SC-FW2; + } + } + traceoptions { + file bgp.log; + flag state; + } + log-updown; + } + } + instance-type vrf; + route-distinguisher 65100:1000; + vrf-import BN-10-RMA-IMPORT; + vrf-export BN-10-RMA-EXPORT; + vrf-table-label; + } +} +protocols { + ospf { + area 0.0.0.0 { + interface ge-0/0/1.0 { + interface-type p2p; + authentication { + md5 1 key "Juniper1"; ## SECRET-DATA + } + } + interface ge-0/0/2.0 { + interface-type p2p; + authentication { + md5 1 key "Juniper1"; ## SECRET-DATA + } + } + interface lo0.0 { + passive; + } + } + } + bgp { + group IBGP-FULL-MESH { + type internal; + description "IBGP Full Mesh"; + hold-time 30; + multipath { + multiple-as; + } + neighbor 172.17.0.0 { + description lhr14-bn-com-agg-r101; + authentication-key-chain BGP-KC-LHR14-R101-NCL62-R2; + } + } + traceoptions { + file bgp.log; + flag state; + } + log-updown; + } + lldp { + interface all; + } +} +routing-options { + autonomous-system 65100; +} \ No newline at end of file diff --git a/provisioning/vars/LAB-897VA.yaml b/provisioning/vars/LAB-897VA.yaml new file mode 100644 index 0000000..6dd7661 --- /dev/null +++ b/provisioning/vars/LAB-897VA.yaml @@ -0,0 +1,3 @@ +hostname: LAB-897VA +management_ip: 172.16.0.3 +loopback_ip: 2.2.2.2 \ No newline at end of file diff --git a/provisioning/vars/LAB-SRX300.yaml b/provisioning/vars/LAB-SRX300.yaml new file mode 100644 index 0000000..b37f005 --- /dev/null +++ b/provisioning/vars/LAB-SRX300.yaml @@ -0,0 +1,3 @@ +hostname: LAB-SRX300 +management_ip: 172.16.0.1 +loopback_ip: 1.1.1.1 \ No newline at end of file diff --git a/topology/midast.py b/topology/midast.py new file mode 100644 index 0000000..0761206 --- /dev/null +++ b/topology/midast.py @@ -0,0 +1,87 @@ +import networkx as nx +import yaml + +from networkx.drawing.nx_agraph import to_agraph +from utils.log import log + +class Topology(): + def __init__(self): + self.G = nx.MultiGraph() + + def build(self): + ''' + Summary: + Builds a network topology from the topology.yaml file into memory, stored in a networkx Graph object. + Once the networkx Graph object has been built, it's drawn and saved as topology.png. + + Nodes: network devices + Node attributes: dict of attributes assigned to a specific node + Edges: links between network devices + Edge attributes: dict of attributes assigned to a specific edge + ''' + log('Building topology...', 'info') + + topology_data = yaml.load(open('topology/topology.yaml'), Loader=yaml.SafeLoader) + + for topology_name, topology_vars in topology_data.items(): + log(f'Topology name: {topology_name}', 'info') + + for node, node_attributes in topology_vars['nodes'].items(): + self.G.add_node(node) + self.G.nodes[node]['shape'] = 'box' + for attribute_name, attribute_value in node_attributes.items(): + self.G.nodes[node][attribute_name] = attribute_value + + for edge_number, edge_attributes in topology_vars['edges'].items(): + self.G.add_edge(edge_attributes['a_end'], edge_attributes['b_end']) + nx.set_edge_attributes( + self.G, + { + (edge_attributes['a_end'], + edge_attributes['b_end'], + edge_attributes['edge_index']): edge_attributes + } + ) + + for node1, node2, edge_attributes in self.G.edges(data=True): + edge_attributes['label'] = f""" + {edge_attributes['a_end']} ({self.G.nodes[edge_attributes['a_end']]['os']}) + {edge_attributes['a_end_ip']}, {edge_attributes['a_end_interface']}\n + {edge_attributes['b_end']} ({self.G.nodes[edge_attributes['b_end']]['os']}) + {edge_attributes['b_end_ip']}, {edge_attributes['b_end_interface']} + """ + + log('Drawing topology...', 'info') + vis = to_agraph(self.G) + vis.layout('dot') + vis_path = 'topology/topology.png' + vis.draw(vis_path) + + log(f'Topology drawn, saved as: {vis_path}', 'info') + log('Topology build complete', 'info') + + def get_client_calling_for_ip(self, giaddr, _type): + ''' + Summary: + Gets the hostname and operating system of the client calling for an IP from the networkx Graph object. + + Takes: + giaddr: Gateway or relay IP that the DHCP packet was received from + _type: Type of DHCP packet, if type is offer log the client name and OS + + Returns: + client_device_name: hostname of the client device + client_device_os: operating system of the client device + ''' + for node1, node2, edge_attributes in self.G.edges(data=True): + if giaddr in edge_attributes.values(): + if giaddr == edge_attributes['a_end_ip']: + client_device_name, client_device_os = edge_attributes['b_end'], self.G.nodes[edge_attributes['b_end']]['os'] + elif giaddr == edge_attributes['b_end_ip']: + client_device_name, client_device_os = edge_attributes['a_end'], self.G.nodes[edge_attributes['a_end']]['os'] + + if _type == 'offer': + log(f'Saw client: {client_device_name}', 'info') + log(f'OS: {client_device_os}', 'info') + + return client_device_name, client_device_os diff --git a/topology/topology.png b/topology/topology.png new file mode 100644 index 0000000..bd16923 Binary files /dev/null and b/topology/topology.png differ diff --git a/topology/topology.yaml b/topology/topology.yaml new file mode 100644 index 0000000..afc2db4 --- /dev/null +++ b/topology/topology.yaml @@ -0,0 +1,54 @@ +Lab: + nodes: + PI-DHCP: + os: ubuntu + no_render: True + LAB-RELAY: + os: cisco_ios + no_render: True + LAB-897VA: + os: cisco_ios + LAB-SRX300: + os: junos + edges: + 1: + a_end: LAB-RELAY + b_end: LAB-SRX300 + a_end_ip: 10.0.0.0 + b_end_ip: 10.0.0.1 + a_end_interface: GigabitEthernet0 + b_end_interface: ge-0/0/0 + edge_index: 0 + 2: + a_end: LAB-RELAY + b_end: LAB-897VA + a_end_ip: 10.0.0.2 + b_end_ip: 10.0.0.3 + a_end_interface: GigabitEthernet1 + b_end_interface: GigabitEthernet8 + edge_index: 0 + 3: + a_end: PI-DHCP + b_end: LAB-RELAY + a_end_ip: 172.16.0.200 + b_end_ip: 172.16.0.2 + a_end_interface: eth0 + b_end_interface: GigabitEthernet8 + edge_index: 0 + 4: + a_end: LAB-897VA + b_end: LAB-SRX300 + a_end_ip: 10.0.0.5 + b_end_ip: 10.0.0.4 + a_end_interface: GigabitEthernet1 + b_end_interface: ge-0/0/1 + edge_index: 0 + 5: + a_end: LAB-897VA + b_end: LAB-SRX300 + a_end_ip: 10.0.0.7 + b_end_ip: 10.0.0.6 + a_end_interface: GigabitEthernet2 + b_end_interface: ge-0/0/2 + edge_index: 1 + diff --git a/utils/log.py b/utils/log.py new file mode 100644 index 0000000..fb96f86 --- /dev/null +++ b/utils/log.py @@ -0,0 +1,63 @@ +import logging +import sys +import os +import inspect + +from datetime import datetime + +def get_loggers(): + ''' + Summary: + Creates 3 loggers with the same datetime; midasd-logger (DHCP), midast-logger (Topology), midasp-logger (Provisioning). + Each logger has a different emoji to make log sources easily identifiable. + ''' + now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + + for logger_name in ['midasd-logger', 'midast-logger', 'midasp-logger']: + logger = logging.getLogger(logger_name) + + logger.setLevel(logging.DEBUG) + if logger_name == 'midasd-logger': + formatter = logging.Formatter('%(asctime)s [%(levelname)s] \U0001F916 | %(message)s') + elif logger_name == 'midast-logger': + formatter = logging.Formatter('%(asctime)s [%(levelname)s] \U0001F47E | %(message)s') + elif logger_name == 'midasp-logger': + formatter = logging.Formatter('%(asctime)s [%(levelname)s] \U0001F47D | %(message)s') + + file_handler = logging.FileHandler(os.getcwd() + '/logs/midas_' + now + '.log') + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + + stream_handler = logging.StreamHandler(sys.stdout) + stream_handler.setFormatter(formatter) + + logger.addHandler(file_handler) + logger.addHandler(stream_handler) + +def log(log_message, level): + ''' + Summary: + Logs information to logfile at the specified level. + + Takes: + log_message: Information to log + level: Level of which to log the information at + ''' + caller_filename = inspect.stack()[1].filename + + if 'midasd' in caller_filename: + logger = logging.getLogger('midasd-logger') + elif 'midast' in caller_filename: + logger = logging.getLogger('midast-logger') + elif 'midasp' in caller_filename: + logger = logging.getLogger('midasp-logger') + + log_message_types = { + 'debug': logger.debug, + 'info': logger.info, + 'warning': logger.warning, + 'error': logger.error, + 'critical': logger.critical + } + + log_message_types[level](log_message)