#1706 - Finished up the Node and Switch MVP. Added full extensive documentation on what's happening at each step.

This commit is contained in:
Chris McCarthy
2023-08-08 20:22:18 +01:00
parent 4e4c2b501a
commit 9fbc3c91f7
10 changed files with 702 additions and 91 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -6,7 +6,7 @@
Simulation
==========
.. TODO:: Add spiel here about what the simulation is.
Contents

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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")

View File

@@ -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