diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7e3466..dd8afbce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Network Hardware - Added base hardware module with NIC, SwitchPort, Node, Switch, and Link. Nodes and Switches have +fundamental services like ARP, ICMP, and PCAP running them by default. +- Network Transmission - Modelled OSI Model layers 1 through to 5 with various classes for creating network frames and +transmitting them from a Service/Application, down through the layers, over the wire, and back up through the layers to +a Service/Application another machine. +- system - Added the core structure of Application, Services, and Components. Also added a SoftwareManager and +SessionManager. - File System - ability to emulate a node's file system during a simulation ## [2.0.0] - 2023-07-26 diff --git a/docs/_static/four_node_two_switch_network.png b/docs/_static/four_node_two_switch_network.png new file mode 100644 index 00000000..42839107 Binary files /dev/null and b/docs/_static/four_node_two_switch_network.png differ diff --git a/docs/source/simulation.rst b/docs/source/simulation.rst index b9f921c2..a2784628 100644 --- a/docs/source/simulation.rst +++ b/docs/source/simulation.rst @@ -6,7 +6,7 @@ Simulation ========== -.. TODO:: Add spiel here about what the simulation is. + Contents diff --git a/docs/source/simulation_components/network/base_hardware.rst b/docs/source/simulation_components/network/base_hardware.rst index c3891a6e..5335091f 100644 --- a/docs/source/simulation_components/network/base_hardware.rst +++ b/docs/source/simulation_components/network/base_hardware.rst @@ -5,71 +5,622 @@ Base Hardware ============= -The physical layer components are models of a ``NIC`` (Network Interface Card) and a ``Link``. These components allow -modelling of layer 1 (physical layer) in the OSI model. +The physical layer components are models of a NIC (Network Interface Card), SwitchPort, Node, Switch, and a Link. +These components allow modelling of layer 1 (physical layer) in the OSI model and the nodes that connect to and +transmit across layer 1. NIC ### -The ``NIC`` class is a realistic model of a Network Interface Card. The ``NIC`` acts as the interface between the -``Node`` and the ``Link``. +The NIC class provides a realistic model of a Network Interface Card. The NIC acts as the interface between a Node and +a Link, handling IP and MAC addressing, status, and sending/receiving frames. -NICs have the following attributes: +Addressing +********** -- **ip_address:** The IPv4 address assigned to the NIC. -- **subnet_mask:** The subnet mask assigned to the NIC. -- **gateway:** The default gateway IP address for forwarding network traffic to other networks. -- **mac_address:** The MAC address of the NIC. Defaults to a randomly set MAC address. -- **speed:** The speed of the NIC in Mbps (default is 100 Mbps). -- **mtu:** The Maximum Transmission Unit (MTU) of the NIC in Bytes, representing the largest data packet size it can handle without fragmentation (default is 1500 B). -- **wake_on_lan:** Indicates if the NIC supports Wake-on-LAN functionality. -- **dns_servers:** List of IP addresses of DNS servers used for name resolution. -- **connected_link:** The link to which the NIC is connected. -- **enabled:** Indicates whether the NIC is enabled. +A NIC has both an IPv4 address and MAC address assigned: -**Basic Example** +- **ip_address** - The IPv4 address assigned to the NIC for communication on an IP network. +- **subnet_mask** - The subnet mask that defines the network subnet. +- **gateway** - The default gateway IP address for routing traffic beyond the local network. +- **mac_address** - A unique MAC address assigned to the NIC by the manufacturer. + +Status +****** + +The status of the NIC is represented by: + +- **enabled** - Indicates if the NIC is active/enabled or disabled/down. It must be enabled to send/receive frames. +- **connected_node** - The Node instance the NIC is attached to. +- **connected_link** - The Link instance the NIC is wired to. + +Packet Capture +************** + +- **pcap** - A PacketCapture instance attached to the NIC for capturing all frames sent and received. This allows packet +capture and analysis. + +Sending/Receiving Frames +************************ + +The NIC can send and receive Frames to/from the connected Link: + +- **send_frame()** - Sends a Frame through the NIC onto the attached Link. +- **receive_frame()** - Receives a Frame from the attached Link and processes it. + +This allows a NIC to handle sending, receiving, and forwarding of network traffic at layer 2 of the OSI model. +The Frames contain network data encapsulated with various protocol headers. + +Basic Usage +*********** .. code-block:: python nic1 = NIC( - ip_address="192.168.1.100", + ip_address="192.168.0.100", subnet_mask="255.255.255.0", - gateway="192.168.1.1" + gateway="192.168.0.1" ) + nic1.enable() + frame = Frame(...) + nic1.send_frame(frame) + +SwitchPort +########## + +The SwitchPort models a port on a network switch. It has similar attributes and methods to NIC for addressing, status, +packet capture, sending/receiving frames, etc. + +Key attributes: + +- **port_num**: The port number on the switch. +- **connected_switch**: The switch to which this port belongs. + +Node +#### + +The Node class represents a base node that communicates on the Network. + +Network Interfaces +****************** + +A Node will typically have one or more NICs attached to it for network connectivity: + +- **nics** - A dictionary containing the NIC instances attached to the Node. NICs can be added/removed. + +Configuration +************* + +- **hostname** - Configured hostname of the Node. +- **operating_state** - Current operating state like ON or OFF. The NICs will be enabled/disabled based on this. + +Network Services +**************** + +A Node runs various network services and components for handling traffic: + +- **session_manager** - Handles establishing sessions to/from the Node. +- **software_manager** - Manages software and applications on the Node. +- **arp** - ARP cache for resolving IP addresses to MAC addresses. +- **icmp** - ICMP service for responding to pings and echo requests. +- **sys_log** - System log service for logging internal events and messages. + +The SysLog provides a logging mechanism for the Node: + +The SysLog records informational, warning, and error events that occur on the Node during simulation. This allows +debugging and tracing program execution and network activity for each simulated Node. Other Node services like ARP and +ICMP, along with custom Applications, services, and Processes will log to the SysLog. + +Sending/Receiving +***************** + +The Node handles sending and receiving Frames via its attached NICs: + +- **send_frame()** - Sends a Frame to the network through one of the Node's NICs. +- **receive_frame()** - Receives a Frame from the network through a NIC. The Node then processes it appropriately based +on the protocols and payload. + +Basic Usage +*********** + +.. code-block:: python + + node1 = Node(hostname='server1') + node1.operating_state = NodeOperatingState.ON + + nic1 = NIC() + node1.connect_nic(nic1) + + Send a frame + frame = Frame(...) + node1.send_frame(frame) + +The Node class brings together the NICs, configuration, and services to model a full network node that can send, +receive, process, and forward traffic on a simulated network. + + +Switch +###### + +The Switch subclass models a network switch. It inherits from Node and acts at layer 2 of the OSI model to forward +frames based on MAC addresses. + +Inherits Node Capabilities +************************** + +Since Switch subclasses Node, it inherits all capabilities from Node like: + +- **Managing NICs** +- **Running network services like ARP, ICMP** +- **Sending and receiving frames** +- **Maintaining system logs** + +Ports +***** + +A Switch has multiple ports implemented using SwitchPort instances: + +- **switch_ports** - A dictionary mapping port numbers to SwitchPort instances. +- **num_ports** - The number of ports the Switch has. + +Forwarding +********** + +A Switch forwards frames between ports based on the destination MAC: + +- **dst_mac_table** - MAC address table that maps MACs to SwitchPorts. +- **forward_frame()** - Forwards a frame out the port associated with the destination MAC. + +When a frame is received on a SwitchPort: + +1. The source MAC address is extracted from the frame. +2. An entry is added to dst_mac_table that maps this source MAC to the SwitchPort it was received on. +3. When a frame with that destination MAC is received in the future, it will be forwarded out this SwitchPort. + +This allows the Switch to dynamically build up a mapping table between MAC addresses and SwitchPorts based on traffic +received. If no entry exists for a destination MAC, it floods the frame out all ports. Link #### -The ``Link`` class represents a physical link between two network endpoints. +The Link class represents a physical link or connection between two network endpoints like NICs or SwitchPorts. -Links have the following attributes: +Endpoints +********* -- **endpoint_a:** The first NIC connected to the Link. -- **endpoint_b:** The second NIC connected to the Link. -- **bandwidth:** The bandwidth of the Link in Mbps (default is 100 Mbps). -- **current_load:** The current load on the link in Mbps. +A Link connects two endpoints: -**Basic Example** +- **endpoint_a** - The first endpoint, a NIC or SwitchPort. +- **endpoint_b** - The second endpoint, a NIC or SwitchPort. + +Transmission +************ + +Links transmit Frames between the endpoints: + +- **transmit_frame()** - Sends a Frame from one endpoint to the other. + +Uses bandwidth/load properties to determine if transmission is possible. + +Bandwidth & Load +**************** + +- **bandwidth** - The total capacity of the Link in Mbps. +- **current_load** - The current bandwidth utilization of the Link in Mbps. + +As Frames are sent over the Link, the load increases. The Link tracks if there is enough unused capacity to transmit a +Frame based on its size and the current load. + +Status +****** + +- **up** - Boolean indicating if the Link is currently up/active based on the endpoint status. +- **endpoint_up()/down()** - Notifies the Link when an endpoint goes up or down. + +This allows the Link to realistically model the connection and transmission characteristics between two endpoints. + +Putting it all Together +####################### + +We'll now demonstrate how the nodes, NICs, switches, and links connect in a network, including full code examples and +syslog extracts to illustrate the step-by-step process. + +To demonstrate successful network communication between nodes and switches, we'll model a standard network with four +PC's and two switches. + + +.. image:: ../../../_static/four_node_two_switch_network.png + +Create Nodes & NICs +******************* + +First, we'll create the four nodes, each with a single NIC. .. code-block:: python - nic1 = NIC( - ip_address="192.168.1.100", - subnet_mask="255.255.255.0", - gateway="192.168.1.1" - ) - nic1 = NIC( - ip_address="192.168.1.101", - subnet_mask="255.255.255.0", - gateway="192.168.1.1" + pc_a = Node(hostname="pc_a") + nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1") + pc_a.connect_nic(nic_a) + pc_a.power_on() + + pc_b = Node(hostname="pc_b") + nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1") + pc_b.connect_nic(nic_b) + pc_b.power_on() + + pc_c = Node(hostname="pc_c") + nic_c = NIC(ip_address="192.168.0.12", subnet_mask="255.255.255.0", gateway="192.168.0.1") + pc_c.connect_nic(nic_c) + pc_c.power_on() + + pc_d = Node(hostname="pc_d") + nic_d = NIC(ip_address="192.168.0.13", subnet_mask="255.255.255.0", gateway="192.168.0.1") + pc_d.connect_nic(nic_d) + pc_d.power_on() + + +This produces: + +**node_a NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+----------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+==========+ +| 80:af:f2:f6:58:b7 | 102.169.0.10 | 255.255.255.0 | 192.168.0.1 | 100 | Disabled | ++-------------------+--------------+---------------+-----------------+--------------+----------+ + +**node_a sys log** + +.. code-block:: + + 2023-08-08 15:50:08,355 INFO: Connected NIC 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,355 INFO: Turned on + +**node_b NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+----------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+==========+ +| 98:ad:eb:7c:dc:cb | 102.169.0.11 | 255.255.255.0 | 192.168.0.1 | 100 | Disabled | ++-------------------+--------------+---------------+-----------------+--------------+----------+ + +**node_b sys log** + +.. code-block:: + + 2023-08-08 15:50:08,357 INFO: Connected NIC 98:ad:eb:7c:dc:cb/192.168.0.11 + 2023-08-08 15:50:08,357 INFO: Turned on + +**node_c NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+----------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+==========+ +| bc:72:82:5d:82:a4 | 102.169.0.12 | 255.255.255.0 | 192.168.0.1 | 100 | Disabled | ++-------------------+--------------+---------------+-----------------+--------------+----------+ + +**node_c sys log** + +.. code-block:: + + 2023-08-08 15:50:08,358 INFO: Connected NIC bc:72:82:5d:82:a4/192.168.0.12 + 2023-08-08 15:50:08,358 INFO: Turned on + +**node_d NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+----------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+==========+ +| 84:20:7c:ec:a5:c6 | 102.169.0.13 | 255.255.255.0 | 192.168.0.1 | 100 | Disabled | ++-------------------+--------------+---------------+-----------------+--------------+----------+ + +**node_d sys log** + +.. code-block:: + + 2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13 + 2023-08-08 15:50:08,360 INFO: Turned on + + +Create Switches +*************** + +Next, we'll create four six-port switches: + +.. code-block:: python + + switch_1 = Switch(hostname="switch_1", num_ports=6) + switch_1.power_on() + + switch_2 = Switch(hostname="switch_2", num_ports=6) + switch_2.power_on() + +This produces: + +**switch_1 MAC table** + ++------+-------------------+--------------+----------+ +| Port | MAC Address | Speed (Mbps) | Status | ++======+===================+==============+==========+ +| 1 | 9d:ac:59:a0:05:13 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 2 | 45:f5:8e:b6:f5:d3 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 3 | ef:f5:b9:28:cb:ae | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 4 | 88:76:0a:72:fc:14 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 5 | 79:de:da:bd:e2:ba | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 6 | 91:d5:83:a0:02:f2 | 100 | Disabled | ++------+-------------------+--------------+----------+ + +**switch_1 sys log** + +.. code-block:: + + 2023-08-08 15:50:08,373 INFO: Turned on + +**switch_2 MAC table** + ++------+-------------------+--------------+----------+ +| Port | MAC Address | Speed (Mbps) | Status | ++======+===================+==============+==========+ +| 1 | aa:58:fa:66:d7:be | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 2 | 72:d2:1e:88:e9:45 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 3 | 8a:fc:2a:56:d5:c5 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 4 | fb:b5:9a:04:4a:49 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 5 | 88:aa:48:d0:21:9e | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 6 | 96:77:39:d1:de:44 | 100 | Disabled | ++------+-------------------+--------------+----------+ + +**switch_2 sys log** + +.. code-block:: + + 2023-08-08 15:50:08,374 INFO: Turned on + +Create Links +************ + +Finally, we'll create the five links that connect the nodes and the switches: + +.. code-block:: python + + link_nic_a_switch_1 = Link(endpoint_a=nic_a, endpoint_b=switch_1.switch_ports[1]) + link_nic_b_switch_1 = Link(endpoint_a=nic_b, endpoint_b=switch_1.switch_ports[2]) + link_nic_c_switch_2 = Link(endpoint_a=nic_c, endpoint_b=switch_2.switch_ports[1]) + link_nic_d_switch_2 = Link(endpoint_a=nic_d, endpoint_b=switch_2.switch_ports[2]) + link_switch_1_switch_2 = Link( + endpoint_a=switch_1.switch_ports[6], endpoint_b=switch_2.switch_ports[6] ) - link = Link( - endpoint_a=nic1, - endpoint_b=nic2, - bandwidth=1000 - ) +This produces: -Link, NIC, Node Interface -######################### +**node_a NIC table** -.. image:: ../../../_static/node_nic_link_component_diagram.png ++-------------------+--------------+---------------+-----------------+--------------+---------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+=========+ +| 80:af:f2:f6:58:b7 | 102.169.0.10 | 255.255.255.0 | 192.168.0.1 | 100 | Enabled | ++-------------------+--------------+---------------+-----------------+--------------+---------+ + +**node_a sys log** + +.. code-block:: + + 2023-08-08 15:50:08,355 INFO: Connected NIC 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,355 INFO: Turned on + 2023-08-08 15:50:08,355 INFO: NIC 80:af:f2:f6:58:b7/192.168.0.10 enabled + +**node_b NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+---------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+=========+ +| 98:ad:eb:7c:dc:cb | 102.169.0.11 | 255.255.255.0 | 192.168.0.1 | 100 | Enabled | ++-------------------+--------------+---------------+-----------------+--------------+---------+ + +**node_b sys log** + +.. code-block:: + + 2023-08-08 15:50:08,357 INFO: Connected NIC 98:ad:eb:7c:dc:cb/192.168.0.11 + 2023-08-08 15:50:08,357 INFO: Turned on + 2023-08-08 15:50:08,357 INFO: NIC 98:ad:eb:7c:dc:cb/192.168.0.11 enabled + +**node_c NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+---------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+=========+ +| bc:72:82:5d:82:a4 | 102.169.0.12 | 255.255.255.0 | 192.168.0.1 | 100 | Enabled | ++-------------------+--------------+---------------+-----------------+--------------+---------+ + +**node_c sys log** + +.. code-block:: + + 2023-08-08 15:50:08,358 INFO: Connected NIC bc:72:82:5d:82:a4/192.168.0.12 + 2023-08-08 15:50:08,358 INFO: Turned on + 2023-08-08 15:50:08,358 INFO: NIC bc:72:82:5d:82:a4/192.168.0.12 enabled + +**node_d NIC table** + ++-------------------+--------------+---------------+-----------------+--------------+---------+ +| MAC Address | IP Address | Subnet Mask | Default Gateway | Speed (Mbps) | Status | ++===================+==============+===============+=================+==============+=========+ +| 84:20:7c:ec:a5:c6 | 102.169.0.13 | 255.255.255.0 | 192.168.0.1 | 100 | Enabled | ++-------------------+--------------+---------------+-----------------+--------------+---------+ + +**node_d sys log** + +.. code-block:: + + 2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13 + 2023-08-08 15:50:08,360 INFO: Turned on + 2023-08-08 15:50:08,360 INFO: NIC 84:20:7c:ec:a5:c6/192.168.0.13 enabled + +**switch_1 MAC table** + ++------+-------------------+--------------+----------+ +| Port | MAC Address | Speed (Mbps) | Status | ++======+===================+==============+==========+ +| 1 | 9d:ac:59:a0:05:13 | 100 | Enabled | ++------+-------------------+--------------+----------+ +| 2 | 45:f5:8e:b6:f5:d3 | 100 | Enabled | ++------+-------------------+--------------+----------+ +| 3 | ef:f5:b9:28:cb:ae | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 4 | 88:76:0a:72:fc:14 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 5 | 79:de:da:bd:e2:ba | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 6 | 91:d5:83:a0:02:f2 | 100 | Enabled | ++------+-------------------+--------------+----------+ + + +**switch_1 sys log** + +.. code-block:: + + 2023-08-08 15:50:08,373 INFO: Turned on + 2023-08-08 15:50:08,378 INFO: SwitchPort 9d:ac:59:a0:05:13 enabled + 2023-08-08 15:50:08,380 INFO: SwitchPort 45:f5:8e:b6:f5:d3 enabled + 2023-08-08 15:50:08,384 INFO: SwitchPort 91:d5:83:a0:02:f2 enabled + + +**switch_2 MAC table** + ++------+-------------------+--------------+----------+ +| Port | MAC Address | Speed (Mbps) | Status | ++======+===================+==============+==========+ +| 1 | aa:58:fa:66:d7:be | 100 | Enabled | ++------+-------------------+--------------+----------+ +| 2 | 72:d2:1e:88:e9:45 | 100 | Enabled | ++------+-------------------+--------------+----------+ +| 3 | 8a:fc:2a:56:d5:c5 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 4 | fb:b5:9a:04:4a:49 | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 5 | 88:aa:48:d0:21:9e | 100 | Disabled | ++------+-------------------+--------------+----------+ +| 6 | 96:77:39:d1:de:44 | 100 | Enabled | ++------+-------------------+--------------+----------+ + + +**switch_2 sys log** + +.. code-block:: + + 2023-08-08 15:50:08,374 INFO: Turned on + 2023-08-08 15:50:08,381 INFO: SwitchPort aa:58:fa:66:d7:be enabled + 2023-08-08 15:50:08,383 INFO: SwitchPort 72:d2:1e:88:e9:45 enabled + 2023-08-08 15:50:08,384 INFO: SwitchPort 96:77:39:d1:de:44 enabled + + +Perform Ping +************ + +Now with the network setup and operational, we can perform a ping to confirm that communication between nodes over a +switched network is possible. In the below example, we ping 192.168.0.13 (node_d) from node_a: + +.. code-block:: python + + pc_a.ping("192.168.0.13") + + +This produces: + +**node_a sys log** + +.. code-block:: + + 2023-08-08 15:50:08,355 INFO: Connected NIC 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,355 INFO: Turned on + 2023-08-08 15:50:08,355 INFO: NIC 80:af:f2:f6:58:b7/192.168.0.10 enabled + 2023-08-08 15:50:08,406 INFO: Attempting to ping 192.168.0.13 + 2023-08-08 15:50:08,406 INFO: No entry in ARP cache for 192.168.0.13 + 2023-08-08 15:50:08,406 INFO: Sending ARP request from NIC 80:af:f2:f6:58:b7/192.168.0.10 for ip 192.168.0.13 + 2023-08-08 15:50:08,413 INFO: Received ARP response for 192.168.0.13 from 84:20:7c:ec:a5:c6 via NIC 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,413 INFO: Adding ARP cache entry for 84:20:7c:ec:a5:c6/192.168.0.13 via NIC 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,415 INFO: Sending echo request to 192.168.0.13 + 2023-08-08 15:50:08,417 INFO: Received echo reply from 192.168.0.13 + 2023-08-08 15:50:08,419 INFO: Sending echo request to 192.168.0.13 + 2023-08-08 15:50:08,421 INFO: Received echo reply from 192.168.0.13 + 2023-08-08 15:50:08,422 INFO: Sending echo request to 192.168.0.13 + 2023-08-08 15:50:08,424 INFO: Received echo reply from 192.168.0.13 + 2023-08-08 15:50:08,425 INFO: Sending echo request to 192.168.0.13 + 2023-08-08 15:50:08,427 INFO: Received echo reply from 192.168.0.13 + + +**node_b sys log** + +.. code-block:: + + 2023-08-08 15:50:08,357 INFO: Connected NIC 98:ad:eb:7c:dc:cb/192.168.0.11 + 2023-08-08 15:50:08,357 INFO: Turned on + 2023-08-08 15:50:08,357 INFO: NIC 98:ad:eb:7c:dc:cb/192.168.0.11 enabled + 2023-08-08 15:50:08,410 INFO: Received ARP request for 192.168.0.13 from 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,410 INFO: Ignoring ARP request for 192.168.0.13 + + +**node_c sys log** + +.. code-block:: + + 2023-08-08 15:50:08,358 INFO: Connected NIC bc:72:82:5d:82:a4/192.168.0.12 + 2023-08-08 15:50:08,358 INFO: Turned on + 2023-08-08 15:50:08,358 INFO: NIC bc:72:82:5d:82:a4/192.168.0.12 enabled + 2023-08-08 15:50:08,411 INFO: Received ARP request for 192.168.0.13 from 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,411 INFO: Ignoring ARP request for 192.168.0.13 + + +**node_d sys log** + +.. code-block:: + + 2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13 + 2023-08-08 15:50:08,360 INFO: Turned on + 2023-08-08 15:50:08,360 INFO: NIC 84:20:7c:ec:a5:c6/192.168.0.13 enabled + 2023-08-08 15:50:08,412 INFO: Received ARP request for 192.168.0.13 from 80:af:f2:f6:58:b7/192.168.0.10 + 2023-08-08 15:50:08,412 INFO: Adding ARP cache entry for 80:af:f2:f6:58:b7/192.168.0.10 via NIC 84:20:7c:ec:a5:c6/192.168.0.13 + 2023-08-08 15:50:08,412 INFO: Sending ARP reply from 84:20:7c:ec:a5:c6/192.168.0.13 to 192.168.0.10/80:af:f2:f6:58:b7 + 2023-08-08 15:50:08,416 INFO: Received echo request from 192.168.0.10 + 2023-08-08 15:50:08,417 INFO: Sending echo reply to 192.168.0.10 + 2023-08-08 15:50:08,420 INFO: Received echo request from 192.168.0.10 + 2023-08-08 15:50:08,420 INFO: Sending echo reply to 192.168.0.10 + 2023-08-08 15:50:08,423 INFO: Received echo request from 192.168.0.10 + 2023-08-08 15:50:08,423 INFO: Sending echo reply to 192.168.0.10 + 2023-08-08 15:50:08,426 INFO: Received echo request from 192.168.0.10 + 2023-08-08 15:50:08,426 INFO: Sending echo reply to 192.168.0.10 + + + +**switch_1 sys log** + +.. code-block:: + + 2023-08-08 15:50:08,373 INFO: Turned on + 2023-08-08 15:50:08,378 INFO: SwitchPort 9d:ac:59:a0:05:13 enabled + 2023-08-08 15:50:08,380 INFO: SwitchPort 45:f5:8e:b6:f5:d3 enabled + 2023-08-08 15:50:08,384 INFO: SwitchPort 91:d5:83:a0:02:f2 enabled + 2023-08-08 15:50:08,409 INFO: Added MAC table entry: Port 1 -> 80:af:f2:f6:58:b7 + 2023-08-08 15:50:08,413 INFO: Added MAC table entry: Port 6 -> 84:20:7c:ec:a5:c6 + + + +**switch_2 sys log** + +.. code-block:: + + 2023-08-08 15:50:08,374 INFO: Turned on + 2023-08-08 15:50:08,381 INFO: SwitchPort aa:58:fa:66:d7:be enabled + 2023-08-08 15:50:08,383 INFO: SwitchPort 72:d2:1e:88:e9:45 enabled + 2023-08-08 15:50:08,384 INFO: SwitchPort 96:77:39:d1:de:44 enabled + 2023-08-08 15:50:08,411 INFO: Added MAC table entry: Port 6 -> 80:af:f2:f6:58:b7 + 2023-08-08 15:50:08,412 INFO: Added MAC table entry: Port 2 -> 84:20:7c:ec:a5:c6 diff --git a/docs/source/simulation_components/network/transport_to_data_link_layer.rst b/docs/source/simulation_components/network/transport_to_data_link_layer.rst index 9332b57c..4961d337 100644 --- a/docs/source/simulation_components/network/transport_to_data_link_layer.rst +++ b/docs/source/simulation_components/network/transport_to_data_link_layer.rst @@ -55,6 +55,19 @@ PrimAITE-specific metadata required for reinforcement learning (RL) purposes. Data Link Layer (Layer 2) ######################### +**ARPEntry:** Represents an entry in the ARP cache. It consists of the following fields: + + - **mac_address:** The MAC address associated with the IP address. + - **nic_uuid:** The NIC (Network Interface Card) UUID through which the NIC with the IP address is reachable. + +**ARPPacket:** Represents the ARP layer of a network frame, and it includes the following fields: + + - **request:** ARP operation. Set to True for a request and False for a reply. + - **sender_mac_addr:** Sender's MAC address. + - **sender_ip:** Sender's IP address (IPv4 format). + - **target_mac_addr:** Target's MAC address. + - **target_ip:** Target's IP address (IPv4 format). + **EthernetHeader:** Represents the Ethernet layer of a network frame. It includes source and destination MAC addresses. This header is used to identify the physical hardware addresses of devices on a local network. diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 739fb933..eb406521 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -6,6 +6,8 @@ from enum import Enum from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, List, Optional, Tuple, Union +from prettytable import PrettyTable + from primaite import getLogger from primaite.exceptions import NetworkError from primaite.simulator.core import SimComponent @@ -136,22 +138,23 @@ class NIC(SimComponent): if self.connected_node: if self.connected_node.operating_state == NodeOperatingState.ON: self.enabled = True - _LOGGER.info(f"NIC {self} enabled") + self.connected_node.sys_log.info(f"NIC {self} enabled") self.pcap = PacketCapture(hostname=self.connected_node.hostname, ip_address=self.ip_address) if self.connected_link: self.connected_link.endpoint_up() else: - _LOGGER.info(f"NIC {self} cannot be enabled as the endpoint is not turned on") + self.connected_node.sys_log.error(f"NIC {self} cannot be enabled as the endpoint is not turned on") else: - msg = f"NIC {self} cannot be enabled as it is not connected to a Node" - _LOGGER.error(msg) - raise NetworkError(msg) + _LOGGER.error(f"NIC {self} cannot be enabled as it is not connected to a Node") def disable(self): """Disable the NIC.""" if self.enabled: self.enabled = False - _LOGGER.info(f"NIC {self} disabled") + if self.connected_node: + self.connected_node.sys_log.info(f"NIC {self} disabled") + else: + _LOGGER.info(f"NIC {self} disabled") if self.connected_link: self.connected_link.endpoint_down() @@ -161,7 +164,6 @@ class NIC(SimComponent): :param link: The link to which the NIC is connected. :type link: :class:`~primaite.simulator.network.transmission.physical_layer.Link` - :raise NetworkError: When an attempt to connect a Link is made while the NIC has a connected Link. """ if not self.connected_link: if self.connected_link != link: @@ -169,11 +171,9 @@ class NIC(SimComponent): self.connected_link = link _LOGGER.info(f"NIC {self} connected to Link {link}") else: - _LOGGER.warning(f"Cannot connect link to NIC ({self.mac_address}) as it is already connected") + _LOGGER.error(f"Cannot connect Link to NIC ({self.mac_address}) as it is already connected") else: - msg = f"Cannot connect link to NIC ({self.mac_address}) as it already has a connection" - _LOGGER.error(msg) - raise NetworkError(msg) + _LOGGER.error(f"Cannot connect Link to NIC ({self.mac_address}) as it already has a connection") def disconnect_link(self): """Disconnect the NIC from the connected Link.""" @@ -293,12 +293,14 @@ class SwitchPort(SimComponent): if self.connected_node: if self.connected_node.operating_state == NodeOperatingState.ON: self.enabled = True - _LOGGER.info(f"SwitchPort {self} enabled") + self.connected_node.sys_log.info(f"SwitchPort {self} enabled") self.pcap = PacketCapture(hostname=self.connected_node.hostname) if self.connected_link: self.connected_link.endpoint_up() else: - _LOGGER.info(f"SwitchPort {self} cannot be enabled as the endpoint is not turned on") + self.connected_node.sys_log.info( + f"SwitchPort {self} cannot be enabled as the endpoint is not turned on" + ) else: msg = f"SwitchPort {self} cannot be enabled as it is not connected to a Node" _LOGGER.error(msg) @@ -308,7 +310,10 @@ class SwitchPort(SimComponent): """Disable the SwitchPort.""" if self.enabled: self.enabled = False - _LOGGER.info(f"SwitchPort {self} disabled") + if self.connected_node: + self.connected_node.sys_log.info(f"SwitchPort {self} disabled") + else: + _LOGGER.info(f"SwitchPort {self} disabled") if self.connected_link: self.connected_link.endpoint_down() @@ -317,7 +322,6 @@ class SwitchPort(SimComponent): Connect the SwitchPort to a link. :param link: The link to which the SwitchPort is connected. - :raise NetworkError: When an attempt to connect a Link is made while the SwitchPort has a connected Link. """ if not self.connected_link: if self.connected_link != link: @@ -326,11 +330,9 @@ class SwitchPort(SimComponent): _LOGGER.info(f"SwitchPort {self} connected to Link {link}") self.enable() else: - _LOGGER.warning(f"Cannot connect link to SwitchPort ({self.mac_address}) as it is already connected") + _LOGGER.error(f"Cannot connect Link to SwitchPort {self.mac_address} as it is already connected") else: - msg = f"Cannot connect link to SwitchPort ({self.mac_address}) as it already has a connection" - _LOGGER.error(msg) - raise NetworkError(msg) + _LOGGER.error(f"Cannot connect link to SwitchPort {self.mac_address} as it already has a connection") def disconnect_link(self): """Disconnect the SwitchPort from the connected Link.""" @@ -815,16 +817,34 @@ class Node(SimComponent): super().__init__(**kwargs) self.arp.nics = self.nics - def turn_on(self): - """Turn on the Node, enabling its NICs if it is in the OFF state.""" + def show(self): + """Prints a table of the NICs on the Node..""" + from prettytable import PrettyTable + + table = PrettyTable(["MAC Address", "Address", "Default Gateway", "Speed", "Status"]) + + for nic in self.nics.values(): + table.add_row( + [ + nic.mac_address, + f"{nic.ip_address}/{nic.ip_network.prefixlen}", + nic.gateway, + nic.speed, + "Enabled" if nic.enabled else "Disabled", + ] + ) + print(table) + + def power_on(self): + """Power on the Node, enabling its NICs if it is in the OFF state.""" if self.operating_state == NodeOperatingState.OFF: self.operating_state = NodeOperatingState.ON self.sys_log.info("Turned on") for nic in self.nics.values(): nic.enable() - def turn_off(self): - """Turn off the Node, disabling its NICs if it is in the ON state.""" + def power_off(self): + """Power off the Node, disabling its NICs if it is in the ON state.""" if self.operating_state == NodeOperatingState.ON: for nic in self.nics.values(): nic.disable() @@ -934,6 +954,14 @@ class Switch(Node): dst_mac_table: Dict[str, SwitchPort] = {} "A MAC address table mapping destination MAC addresses to corresponding SwitchPorts." + def show(self): + """Prints a table of the SwitchPorts on the Switch.""" + table = PrettyTable(["Port", "MAC Address", "Speed", "Status"]) + + for port_num, port in self.switch_ports.items(): + table.add_row([port_num, port.mac_address, port.speed, "Enabled" if port.enabled else "Disabled"]) + print(table) + def describe_state(self) -> Dict: """TODO.""" pass diff --git a/src/primaite/simulator/network/nodes/__init__.py b/src/primaite/simulator/network/hardware/nodes/__init__.py similarity index 100% rename from src/primaite/simulator/network/nodes/__init__.py rename to src/primaite/simulator/network/hardware/nodes/__init__.py diff --git a/src/primaite/simulator/network/nodes/switch.py b/src/primaite/simulator/network/nodes/switch.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration_tests/network/test_frame_transmission.py b/tests/integration_tests/network/test_frame_transmission.py index 27545edc..3840c302 100644 --- a/tests/integration_tests/network/test_frame_transmission.py +++ b/tests/integration_tests/network/test_frame_transmission.py @@ -2,15 +2,17 @@ from primaite.simulator.network.hardware.base import Link, NIC, Node, Switch def test_node_to_node_ping(): + """Tests two Nodes are able to ping each other.""" + # TODO Add actual checks. Manual check performed for now. node_a = Node(hostname="node_a") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1") node_a.connect_nic(nic_a) - node_a.turn_on() + node_a.power_on() node_b = Node(hostname="node_b") nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1") node_b.connect_nic(nic_b) - node_b.turn_on() + node_b.power_on() Link(endpoint_a=nic_a, endpoint_b=nic_b) @@ -18,22 +20,24 @@ def test_node_to_node_ping(): def test_multi_nic(): + """Tests that Nodes with multiple NICs can ping each other and the data go across the correct links.""" + # TODO Add actual checks. Manual check performed for now. node_a = Node(hostname="node_a") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1") node_a.connect_nic(nic_a) - node_a.turn_on() + node_a.power_on() node_b = Node(hostname="node_b") nic_b1 = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1") nic_b2 = NIC(ip_address="10.0.0.12", subnet_mask="255.0.0.0", gateway="10.0.0.1") node_b.connect_nic(nic_b1) node_b.connect_nic(nic_b2) - node_b.turn_on() + node_b.power_on() node_c = Node(hostname="node_c") nic_c = NIC(ip_address="10.0.0.13", subnet_mask="255.0.0.0", gateway="10.0.0.1") node_c.connect_nic(nic_c) - node_c.turn_on() + node_c.power_on() Link(endpoint_a=nic_a, endpoint_b=nic_b1) @@ -45,30 +49,38 @@ def test_multi_nic(): def test_switched_network(): - node_a = Node(hostname="node_a") + """Tests a larges network of Nodes and Switches with one node pinging another.""" + # TODO Add actual checks. Manual check performed for now. + pc_a = Node(hostname="pc_a") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1") - node_a.connect_nic(nic_a) - node_a.turn_on() + pc_a.connect_nic(nic_a) + pc_a.power_on() - node_b = Node(hostname="node_b") + pc_b = Node(hostname="pc_b") nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1") - node_b.connect_nic(nic_b) - node_b.turn_on() + pc_b.connect_nic(nic_b) + pc_b.power_on() - node_c = Node(hostname="node_c") + pc_c = Node(hostname="pc_c") nic_c = NIC(ip_address="192.168.0.12", subnet_mask="255.255.255.0", gateway="192.168.0.1") - node_c.connect_nic(nic_c) - node_c.turn_on() + pc_c.connect_nic(nic_c) + pc_c.power_on() - switch_1 = Switch(hostname="switch_1") - switch_1.turn_on() + pc_d = Node(hostname="pc_d") + nic_d = NIC(ip_address="192.168.0.13", subnet_mask="255.255.255.0", gateway="192.168.0.1") + pc_d.connect_nic(nic_d) + pc_d.power_on() - switch_2 = Switch(hostname="switch_2") - switch_2.turn_on() + switch_1 = Switch(hostname="switch_1", num_ports=6) + switch_1.power_on() - Link(endpoint_a=nic_a, endpoint_b=switch_1.switch_ports[1]) - Link(endpoint_a=nic_b, endpoint_b=switch_1.switch_ports[2]) - Link(endpoint_a=switch_1.switch_ports[24], endpoint_b=switch_2.switch_ports[24]) - Link(endpoint_a=nic_c, endpoint_b=switch_2.switch_ports[1]) + switch_2 = Switch(hostname="switch_2", num_ports=6) + switch_2.power_on() - node_a.ping("192.168.0.12") + link_nic_a_switch_1 = Link(endpoint_a=nic_a, endpoint_b=switch_1.switch_ports[1]) + link_nic_b_switch_1 = Link(endpoint_a=nic_b, endpoint_b=switch_1.switch_ports[2]) + link_nic_c_switch_2 = Link(endpoint_a=nic_c, endpoint_b=switch_2.switch_ports[1]) + link_nic_d_switch_2 = Link(endpoint_a=nic_d, endpoint_b=switch_2.switch_ports[2]) + link_switch_1_switch_2 = Link(endpoint_a=switch_1.switch_ports[6], endpoint_b=switch_2.switch_ports[6]) + + pc_a.ping("192.168.0.13") diff --git a/tests/integration_tests/network/test_link_connection.py b/tests/integration_tests/network/test_link_connection.py index 50abed77..92909cf6 100644 --- a/tests/integration_tests/network/test_link_connection.py +++ b/tests/integration_tests/network/test_link_connection.py @@ -6,13 +6,13 @@ def test_link_up(): node_a = Node(hostname="node_a") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1") node_a.connect_nic(nic_a) - node_a.turn_on() + node_a.power_on() assert nic_a.enabled node_b = Node(hostname="node_b") nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1") node_b.connect_nic(nic_b) - node_b.turn_on() + node_b.power_on() assert nic_b.enabled