#2248 - Big refactor of base with all Network Interface subclasses created to allow for proper management of ports on devices as it was starting to get messy with the Router. Some routing tests still need fixing as ARP doesn't seem to be working properly
This commit is contained in:
@@ -7,11 +7,11 @@ from prettytable import MARKDOWN, PrettyTable
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
||||
from primaite.simulator.network.hardware.base import Link, NIC, Node, SwitchPort
|
||||
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.router import Router
|
||||
from primaite.simulator.network.hardware.nodes.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.switch import Switch
|
||||
from primaite.simulator.network.hardware.base import Link, Node, WiredNetworkInterface
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.network.router import Router
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.network.switch import Switch
|
||||
from primaite.simulator.system.applications.application import Application
|
||||
from primaite.simulator.system.services.service import Service
|
||||
|
||||
@@ -62,8 +62,8 @@ class Network(SimComponent):
|
||||
for node in self.nodes.values():
|
||||
node.power_on()
|
||||
|
||||
for nic in node.nics.values():
|
||||
nic.enable()
|
||||
for network_interface in node.network_interfaces.values():
|
||||
network_interface.enable()
|
||||
# Reset software
|
||||
for software in node.software_manager.software.values():
|
||||
if isinstance(software, Service):
|
||||
@@ -148,7 +148,7 @@ class Network(SimComponent):
|
||||
table.title = "IP Addresses"
|
||||
for nodes in nodes_type_map.values():
|
||||
for node in nodes:
|
||||
for i, port in node.ethernet_port.items():
|
||||
for i, port in node.network_interface.items():
|
||||
table.add_row([node.hostname, i, port.ip_address, port.subnet_mask, node.default_gateway])
|
||||
print(table)
|
||||
|
||||
@@ -209,8 +209,8 @@ class Network(SimComponent):
|
||||
node_b = link.endpoint_b._connected_node
|
||||
hostname_a = node_a.hostname if node_a else None
|
||||
hostname_b = node_b.hostname if node_b else None
|
||||
port_a = link.endpoint_a._port_num_on_node
|
||||
port_b = link.endpoint_b._port_num_on_node
|
||||
port_a = link.endpoint_a.port_num
|
||||
port_b = link.endpoint_b.port_num
|
||||
state["links"][uuid] = link.describe_state()
|
||||
state["links"][uuid]["hostname_a"] = hostname_a
|
||||
state["links"][uuid]["hostname_b"] = hostname_b
|
||||
@@ -272,7 +272,7 @@ class Network(SimComponent):
|
||||
self._node_request_manager.remove_request(name=node.uuid)
|
||||
|
||||
def connect(
|
||||
self, endpoint_a: Union[NIC, SwitchPort], endpoint_b: Union[NIC, SwitchPort], **kwargs
|
||||
self, endpoint_a: Union[WiredNetworkInterface], endpoint_b: Union[WiredNetworkInterface], **kwargs
|
||||
) -> Optional[Link]:
|
||||
"""
|
||||
Connect two endpoints on the network by creating a link between their NICs/SwitchPorts.
|
||||
@@ -280,9 +280,9 @@ class Network(SimComponent):
|
||||
.. note:: If the nodes owning the endpoints are not already in the network, they are automatically added.
|
||||
|
||||
:param endpoint_a: The first endpoint to connect.
|
||||
:type endpoint_a: Union[NIC, SwitchPort]
|
||||
:type endpoint_a: WiredNetworkInterface
|
||||
:param endpoint_b: The second endpoint to connect.
|
||||
:type endpoint_b: Union[NIC, SwitchPort]
|
||||
:type endpoint_b: WiredNetworkInterface
|
||||
:raises RuntimeError: If any validation or runtime checks fail.
|
||||
"""
|
||||
node_a: Node = endpoint_a.parent
|
||||
|
||||
@@ -2,9 +2,9 @@ from ipaddress import IPv4Address
|
||||
from typing import Optional
|
||||
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
|
||||
from primaite.simulator.network.hardware.nodes.switch import Switch
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
|
||||
from primaite.simulator.network.hardware.nodes.network.switch import Switch
|
||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
|
||||
@@ -111,7 +111,7 @@ def create_office_lan(
|
||||
if num_of_switches > 1:
|
||||
network.connect(core_switch.switch_ports[core_switch_port], switch.switch_ports[24])
|
||||
else:
|
||||
network.connect(router.ethernet_ports[1], switch.switch_ports[24])
|
||||
network.connect(router.network_interface[1], switch.switch_ports[24])
|
||||
|
||||
# Add PCs to the LAN and connect them to switches
|
||||
for i in range(1, num_pcs + 1):
|
||||
@@ -127,7 +127,7 @@ def create_office_lan(
|
||||
core_switch_port += 1
|
||||
network.connect(core_switch.switch_ports[core_switch_port], switch.switch_ports[24])
|
||||
else:
|
||||
network.connect(router.ethernet_ports[1], switch.switch_ports[24])
|
||||
network.connect(router.network_interface[1], switch.switch_ports[24])
|
||||
|
||||
# Create and add a PC to the network
|
||||
pc = Computer(
|
||||
@@ -142,7 +142,7 @@ def create_office_lan(
|
||||
|
||||
# Connect the PC to the switch
|
||||
switch_port += 1
|
||||
network.connect(switch.switch_ports[switch_port], pc.ethernet_port[1])
|
||||
network.connect(switch.switch_ports[switch_port], pc.network_interface[1])
|
||||
switch.switch_ports[switch_port].enable()
|
||||
|
||||
return network
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
from abc import ABC
|
||||
from ipaddress import IPv4Network
|
||||
from typing import Dict
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from primaite.utils.validators import IPV4Address
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
from typing import Dict
|
||||
|
||||
from primaite.simulator.network.hardware.base import WirelessNetworkInterface
|
||||
from primaite.simulator.network.hardware.network_interface.layer_3_interface import Layer3Interface
|
||||
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame
|
||||
|
||||
|
||||
class WirelessAccessPoint(WirelessNetworkInterface, Layer3Interface):
|
||||
"""
|
||||
Represents a Wireless Access Point (AP) in a network.
|
||||
|
||||
This class models a Wireless Access Point, a device that allows wireless devices to connect to a wired network
|
||||
using Wi-Fi or other wireless standards. The Wireless Access Point bridges the wireless and wired segments of
|
||||
the network, allowing wireless devices to communicate with other devices on the network.
|
||||
|
||||
As an integral component of wireless networking, a Wireless Access Point provides functionalities for network
|
||||
management, signal broadcasting, security enforcement, and connection handling. It also possesses Layer 3
|
||||
capabilities such as IP addressing and subnetting, allowing for network segmentation and routing.
|
||||
|
||||
Inherits from:
|
||||
- WirelessNetworkInterface: Provides basic properties and methods specific to wireless interfaces.
|
||||
- Layer3Interface: Provides Layer 3 properties like ip_address and subnet_mask, enabling the device to manage
|
||||
network traffic and routing.
|
||||
|
||||
This class can be further specialised or extended to support specific features or standards related to wireless
|
||||
networking, such as different Wi-Fi versions, frequency bands, or advanced security protocols.
|
||||
"""
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
# Get the state from the WirelessNetworkInterface
|
||||
state = WirelessNetworkInterface.describe_state(self)
|
||||
|
||||
# Update the state with information from Layer3Interface
|
||||
state.update(Layer3Interface.describe_state(self))
|
||||
|
||||
# Update the state with NIC-specific information
|
||||
state.update(
|
||||
{
|
||||
"wake_on_lan": self.wake_on_lan,
|
||||
}
|
||||
)
|
||||
|
||||
return state
|
||||
|
||||
def enable(self):
|
||||
"""Enable the interface."""
|
||||
pass
|
||||
|
||||
def disable(self):
|
||||
"""Disable the interface."""
|
||||
pass
|
||||
|
||||
def send_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Attempts to send a network frame through the interface.
|
||||
|
||||
:param frame: The network frame to be sent.
|
||||
:return: A boolean indicating whether the frame was successfully sent.
|
||||
"""
|
||||
pass
|
||||
|
||||
def receive_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Receives a network frame on the interface.
|
||||
|
||||
:param frame: The network frame being received.
|
||||
:return: A boolean indicating whether the frame was successfully received.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
String representation of the NIC.
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
@@ -0,0 +1,81 @@
|
||||
from typing import Dict
|
||||
|
||||
from primaite.simulator.network.hardware.base import WirelessNetworkInterface
|
||||
from primaite.simulator.network.hardware.network_interface.layer_3_interface import Layer3Interface
|
||||
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame
|
||||
|
||||
|
||||
class WirelessNIC(WirelessNetworkInterface, Layer3Interface):
|
||||
"""
|
||||
Represents a Wireless Network Interface Card (Wireless NIC) in a network device.
|
||||
|
||||
This class encapsulates the functionalities and attributes of a wireless NIC, combining the characteristics of a
|
||||
wireless network interface with Layer 3 features. It is capable of connecting to wireless networks, managing
|
||||
wireless-specific properties such as signal strength and security protocols, and also handling IP-related
|
||||
functionalities like IP addressing and subnetting.
|
||||
|
||||
Inherits from:
|
||||
- WirelessNetworkInterface: Provides basic properties and methods specific to wireless interfaces.
|
||||
- Layer3Interface: Provides Layer 3 properties like ip_address and subnet_mask, enabling the device to participate
|
||||
in IP-based networking.
|
||||
|
||||
This class can be extended to include more advanced features or to tailor its behavior for specific types of
|
||||
wireless networks or protocols.
|
||||
"""
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
# Get the state from the WirelessNetworkInterface
|
||||
state = WirelessNetworkInterface.describe_state(self)
|
||||
|
||||
# Update the state with information from Layer3Interface
|
||||
state.update(Layer3Interface.describe_state(self))
|
||||
|
||||
# Update the state with NIC-specific information
|
||||
state.update(
|
||||
{
|
||||
"wake_on_lan": self.wake_on_lan,
|
||||
}
|
||||
)
|
||||
|
||||
return state
|
||||
|
||||
def enable(self):
|
||||
"""Enable the interface."""
|
||||
pass
|
||||
|
||||
def disable(self):
|
||||
"""Disable the interface."""
|
||||
pass
|
||||
|
||||
def send_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Attempts to send a network frame through the interface.
|
||||
|
||||
:param frame: The network frame to be sent.
|
||||
:return: A boolean indicating whether the frame was successfully sent.
|
||||
"""
|
||||
pass
|
||||
|
||||
def receive_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Receives a network frame on the interface.
|
||||
|
||||
:param frame: The network frame being received.
|
||||
:return: A boolean indicating whether the frame was successfully received.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
String representation of the NIC.
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
@@ -1,63 +0,0 @@
|
||||
from primaite.simulator.network.hardware.base import NIC, Node
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.services.arp.host_arp import HostARP
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.icmp.icmp import ICMP
|
||||
from primaite.simulator.system.services.ntp.ntp_client import NTPClient
|
||||
|
||||
|
||||
class Host(Node):
|
||||
"""
|
||||
A basic Host class.
|
||||
|
||||
Example:
|
||||
>>> pc_a = Host(
|
||||
hostname="pc_a",
|
||||
ip_address="192.168.1.10",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1"
|
||||
)
|
||||
>>> pc_a.power_on()
|
||||
|
||||
Instances of computer come 'pre-packaged' with the following:
|
||||
|
||||
* Core Functionality:
|
||||
* Packet Capture
|
||||
* Sys Log
|
||||
* Services:
|
||||
* ARP Service
|
||||
* ICMP Service
|
||||
* DNS Client
|
||||
* FTP Client
|
||||
* NTP Client
|
||||
* Applications:
|
||||
* Web Browser
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect_nic(NIC(ip_address=kwargs["ip_address"], subnet_mask=kwargs["subnet_mask"]))
|
||||
self._install_system_software()
|
||||
|
||||
def _install_system_software(self):
|
||||
"""Install System Software - software that is usually provided with the OS."""
|
||||
# ARP Service
|
||||
self.software_manager.install(HostARP)
|
||||
|
||||
# ICMP Service
|
||||
self.software_manager.install(ICMP)
|
||||
|
||||
# DNS Client
|
||||
self.software_manager.install(DNSClient)
|
||||
|
||||
# FTP Client
|
||||
self.software_manager.install(FTPClient)
|
||||
|
||||
# NTP Client
|
||||
self.software_manager.install(NTPClient)
|
||||
|
||||
# Web Browser
|
||||
self.software_manager.install(WebBrowser)
|
||||
|
||||
super()._install_system_software()
|
||||
@@ -1,11 +1,7 @@
|
||||
from primaite.simulator.network.hardware.base import NIC, Node
|
||||
from primaite.simulator.network.hardware.nodes.host import Host
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.network.hardware.nodes.host.host_node import HostNode
|
||||
|
||||
|
||||
class Computer(Host):
|
||||
class Computer(HostNode):
|
||||
"""
|
||||
A basic Computer class.
|
||||
|
||||
354
src/primaite/simulator/network/hardware/nodes/host/host_node.py
Normal file
354
src/primaite/simulator/network/hardware/nodes/host/host_node.py
Normal file
@@ -0,0 +1,354 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.simulator.network.hardware.base import IPWiredNetworkInterface
|
||||
from primaite.simulator.network.hardware.base import Node
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.core.packet_capture import PacketCapture
|
||||
from primaite.simulator.system.services.arp.arp import ARP, ARPPacket
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.icmp.icmp import ICMP
|
||||
from primaite.simulator.system.services.ntp.ntp_client import NTPClient
|
||||
from primaite.utils.validators import IPV4Address
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
# Lives here due to pydantic circular dependency issue :(
|
||||
class HostARP(ARP):
|
||||
"""
|
||||
The Host ARP Service.
|
||||
|
||||
Extends the ARP service with functionalities specific to a host within the network. It provides mechanisms to
|
||||
resolve and cache MAC addresses and NICs for given IP addresses, focusing on the host's perspective, including
|
||||
handling the default gateway.
|
||||
"""
|
||||
|
||||
def get_default_gateway_mac_address(self) -> Optional[str]:
|
||||
"""
|
||||
Retrieves the MAC address of the default gateway from the ARP cache.
|
||||
|
||||
:return: The MAC address of the default gateway if it exists in the ARP cache, otherwise None.
|
||||
"""
|
||||
if self.software_manager.node.default_gateway:
|
||||
return self.get_arp_cache_mac_address(self.software_manager.node.default_gateway)
|
||||
|
||||
def get_default_gateway_network_interface(self) -> Optional[NIC]:
|
||||
"""
|
||||
Retrieves the NIC associated with the default gateway from the ARP cache.
|
||||
|
||||
:return: The NIC associated with the default gateway if it exists in the ARP cache, otherwise None.
|
||||
"""
|
||||
if self.software_manager.node.default_gateway:
|
||||
return self.get_arp_cache_network_interface(self.software_manager.node.default_gateway)
|
||||
|
||||
def _get_arp_cache_mac_address(
|
||||
self, ip_address: IPV4Address, is_reattempt: bool = False, is_default_gateway_attempt: bool = False
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Internal method to retrieve the MAC address associated with an IP address from the ARP cache.
|
||||
|
||||
:param ip_address: The IP address whose MAC address is to be retrieved.
|
||||
:param is_reattempt: Indicates if this call is a reattempt after a failed initial attempt.
|
||||
:param is_default_gateway_attempt: Indicates if this call is an attempt to get the default gateway's MAC address.
|
||||
:return: The MAC address associated with the IP address if found, otherwise None.
|
||||
"""
|
||||
arp_entry = self.arp.get(ip_address)
|
||||
|
||||
if arp_entry:
|
||||
return arp_entry.mac_address
|
||||
else:
|
||||
if not is_reattempt:
|
||||
self.send_arp_request(ip_address)
|
||||
return self._get_arp_cache_mac_address(
|
||||
ip_address=ip_address, is_reattempt=True, is_default_gateway_attempt=is_default_gateway_attempt
|
||||
)
|
||||
else:
|
||||
if self.software_manager.node.default_gateway:
|
||||
if not is_default_gateway_attempt:
|
||||
self.send_arp_request(self.software_manager.node.default_gateway)
|
||||
return self._get_arp_cache_mac_address(
|
||||
ip_address=self.software_manager.node.default_gateway, is_reattempt=True,
|
||||
is_default_gateway_attempt=True
|
||||
)
|
||||
return None
|
||||
|
||||
def get_arp_cache_mac_address(self, ip_address: IPV4Address) -> Optional[str]:
|
||||
"""
|
||||
Retrieves the MAC address associated with an IP address from the ARP cache.
|
||||
|
||||
:param ip_address: The IP address whose MAC address is to be retrieved.
|
||||
:return: The MAC address associated with the IP address if found, otherwise None.
|
||||
"""
|
||||
return self._get_arp_cache_mac_address(ip_address)
|
||||
|
||||
def _get_arp_cache_network_interface(
|
||||
self, ip_address: IPV4Address, is_reattempt: bool = False, is_default_gateway_attempt: bool = False
|
||||
) -> Optional[NIC]:
|
||||
"""
|
||||
Internal method to retrieve the NIC associated with an IP address from the ARP cache.
|
||||
|
||||
:param ip_address: The IP address whose NIC is to be retrieved.
|
||||
:param is_reattempt: Indicates if this call is a reattempt after a failed initial attempt.
|
||||
:param is_default_gateway_attempt: Indicates if this call is an attempt to get the NIC of the default gateway.
|
||||
:return: The NIC associated with the IP address if found, otherwise None.
|
||||
"""
|
||||
arp_entry = self.arp.get(ip_address)
|
||||
|
||||
if arp_entry:
|
||||
return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid]
|
||||
else:
|
||||
if not is_reattempt:
|
||||
self.send_arp_request(ip_address)
|
||||
return self._get_arp_cache_network_interface(
|
||||
ip_address=ip_address, is_reattempt=True, is_default_gateway_attempt=is_default_gateway_attempt
|
||||
)
|
||||
else:
|
||||
if self.software_manager.node.default_gateway:
|
||||
if not is_default_gateway_attempt:
|
||||
self.send_arp_request(self.software_manager.node.default_gateway)
|
||||
return self._get_arp_cache_network_interface(
|
||||
ip_address=self.software_manager.node.default_gateway, is_reattempt=True,
|
||||
is_default_gateway_attempt=True
|
||||
)
|
||||
return None
|
||||
|
||||
def get_arp_cache_network_interface(self, ip_address: IPV4Address) -> Optional[NIC]:
|
||||
"""
|
||||
Retrieves the NIC associated with an IP address from the ARP cache.
|
||||
|
||||
:param ip_address: The IP address whose NIC is to be retrieved.
|
||||
:return: The NIC associated with the IP address if found, otherwise None.
|
||||
"""
|
||||
return self._get_arp_cache_network_interface(ip_address)
|
||||
|
||||
def _process_arp_request(self, arp_packet: ARPPacket, from_network_interface: NIC):
|
||||
"""
|
||||
Processes an ARP request.
|
||||
|
||||
Adds a new entry to the ARP cache if the target IP address matches the NIC's IP address and sends an ARP
|
||||
reply back.
|
||||
|
||||
:param arp_packet: The ARP packet containing the request.
|
||||
:param from_network_interface: The NIC that received the ARP request.
|
||||
"""
|
||||
super()._process_arp_request(arp_packet, from_network_interface)
|
||||
# Unmatched ARP Request
|
||||
if arp_packet.target_ip_address != from_network_interface.ip_address:
|
||||
self.sys_log.info(
|
||||
f"Ignoring ARP request for {arp_packet.target_ip_address}. Current IP address is {from_network_interface.ip_address}"
|
||||
)
|
||||
return
|
||||
|
||||
# Matched ARP request
|
||||
self.add_arp_cache_entry(
|
||||
ip_address=arp_packet.sender_ip_address, mac_address=arp_packet.sender_mac_addr,
|
||||
network_interface=from_network_interface
|
||||
)
|
||||
arp_packet = arp_packet.generate_reply(from_network_interface.mac_address)
|
||||
self.send_arp_reply(arp_packet)
|
||||
|
||||
|
||||
class NIC(IPWiredNetworkInterface):
|
||||
"""
|
||||
Represents a Network Interface Card (NIC) in a Host Node.
|
||||
|
||||
A NIC is a hardware component that provides a computer or other network device with the ability to connect to a
|
||||
network. It operates at both Layer 2 (Data Link Layer) and Layer 3 (Network Layer) of the OSI model, meaning it
|
||||
can interpret both MAC addresses and IP addresses. This class combines the functionalities of
|
||||
WiredNetworkInterface and Layer3Interface, allowing the NIC to manage physical connections and network layer
|
||||
addressing.
|
||||
|
||||
Inherits from:
|
||||
- WiredNetworkInterface: Provides properties and methods specific to wired connections, including methods to connect
|
||||
and disconnect from network links and to manage the enabled/disabled state of the interface.
|
||||
- Layer3Interface: Provides properties for Layer 3 network configuration, such as IP address and subnet mask.
|
||||
"""
|
||||
wake_on_lan: bool = False
|
||||
"Indicates if the NIC supports Wake-on-LAN functionality."
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
# Get the state from the IPWiredNetworkInterface
|
||||
state = super().describe_state()
|
||||
|
||||
# Update the state with NIC-specific information
|
||||
state.update(
|
||||
{
|
||||
"wake_on_lan": self.wake_on_lan,
|
||||
}
|
||||
)
|
||||
|
||||
return state
|
||||
|
||||
def set_original_state(self):
|
||||
"""Sets the original state."""
|
||||
vals_to_include = {"ip_address", "subnet_mask", "mac_address", "speed", "mtu", "wake_on_lan", "enabled"}
|
||||
self._original_state = self.model_dump(include=vals_to_include)
|
||||
|
||||
def receive_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Attempt to receive and process a network frame from the connected Link.
|
||||
|
||||
This method processes a frame if the NIC is enabled. It checks the frame's destination and TTL, captures the
|
||||
frame using PCAP, and forwards it to the connected Node if valid. Returns True if the frame is processed,
|
||||
False otherwise (e.g., if the NIC is disabled, or TTL expired).
|
||||
|
||||
:param frame: The network frame being received.
|
||||
:return: True if the frame is processed and passed to the node, False otherwise.
|
||||
"""
|
||||
if self.enabled:
|
||||
frame.decrement_ttl()
|
||||
if frame.ip and frame.ip.ttl < 1:
|
||||
self._connected_node.sys_log.info(f"Frame discarded at {self} as TTL limit reached")
|
||||
return False
|
||||
frame.set_received_timestamp()
|
||||
self.pcap.capture_inbound(frame)
|
||||
# If this destination or is broadcast
|
||||
accept_frame = False
|
||||
|
||||
# Check if it's a broadcast:
|
||||
if frame.ethernet.dst_mac_addr == "ff:ff:ff:ff:ff:ff":
|
||||
if frame.ip.dst_ip_address in {self.ip_address, self.ip_network.broadcast_address}:
|
||||
accept_frame = True
|
||||
else:
|
||||
if frame.ethernet.dst_mac_addr == self.mac_address:
|
||||
accept_frame = True
|
||||
|
||||
if accept_frame:
|
||||
self._connected_node.receive_frame(frame=frame, from_network_interface=self)
|
||||
return True
|
||||
return False
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
String representation of the NIC.
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
|
||||
|
||||
class HostNode(Node):
|
||||
"""
|
||||
Represents a host node in the network.
|
||||
|
||||
Extends the basic functionality of a Node with host-specific services and applications. A host node typically
|
||||
represents an end-user device in the network, such as a Computer or a Server, and is capable of initiating and
|
||||
responding to network communications.
|
||||
|
||||
Example:
|
||||
>>> pc_a = HostNode(
|
||||
hostname="pc_a",
|
||||
ip_address="192.168.1.10",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1"
|
||||
)
|
||||
>>> pc_a.power_on()
|
||||
|
||||
The host comes pre-installed with core functionalities and a suite of services and applications, making it ready
|
||||
for various network operations and tasks. These include:
|
||||
|
||||
Core Functionality:
|
||||
-------------------
|
||||
|
||||
* Packet Capture: Monitors and logs network traffic.
|
||||
* Sys Log: Logs system events and errors.
|
||||
|
||||
Services:
|
||||
---------
|
||||
|
||||
* ARP (Address Resolution Protocol) Service: Resolves IP addresses to MAC addresses.
|
||||
* ICMP (Internet Control Message Protocol) Service: Handles ICMP operations, such as ping requests.
|
||||
* DNS (Domain Name System) Client: Resolves domain names to IP addresses.
|
||||
* FTP (File Transfer Protocol) Client: Enables file transfers between the host and FTP servers.
|
||||
* NTP (Network Time Protocol) Client: Synchronizes the system clock with NTP servers.
|
||||
|
||||
Applications:
|
||||
------------
|
||||
|
||||
* Web Browser: Provides web browsing capabilities.
|
||||
"""
|
||||
network_interfaces: Dict[str, NIC] = {}
|
||||
"The Network Interfaces on the node."
|
||||
network_interface: Dict[int, NIC] = {}
|
||||
"The NICs on the node by port id."
|
||||
|
||||
def __init__(self, ip_address: IPV4Address, subnet_mask: IPV4Address, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect_nic(NIC(ip_address=ip_address, subnet_mask=subnet_mask))
|
||||
|
||||
def _install_system_software(self):
|
||||
"""Install System Software - software that is usually provided with the OS."""
|
||||
# ARP Service
|
||||
self.software_manager.install(HostARP)
|
||||
|
||||
# ICMP Service
|
||||
self.software_manager.install(ICMP)
|
||||
|
||||
# DNS Client
|
||||
self.software_manager.install(DNSClient)
|
||||
|
||||
# FTP Client
|
||||
self.software_manager.install(FTPClient)
|
||||
|
||||
# NTP Client
|
||||
self.software_manager.install(NTPClient)
|
||||
|
||||
# Web Browser
|
||||
self.software_manager.install(WebBrowser)
|
||||
|
||||
super()._install_system_software()
|
||||
|
||||
def default_gateway_hello(self):
|
||||
if self.operating_state == NodeOperatingState.ON and self.default_gateway:
|
||||
self.software_manager.arp.get_default_gateway_mac_address()
|
||||
|
||||
def receive_frame(self, frame: Frame, from_network_interface: NIC):
|
||||
"""
|
||||
Receive a Frame from the connected NIC and process it.
|
||||
|
||||
Depending on the protocol, the frame is passed to the appropriate handler such as ARP or ICMP, or up to the
|
||||
SessionManager if no code manager exists.
|
||||
|
||||
:param frame: The Frame being received.
|
||||
:param from_network_interface: The NIC that received the frame.
|
||||
"""
|
||||
super().receive_frame(frame, from_network_interface)
|
||||
|
||||
# Check if the destination port is open on the Node
|
||||
dst_port = None
|
||||
if frame.tcp:
|
||||
dst_port = frame.tcp.dst_port
|
||||
elif frame.udp:
|
||||
dst_port = frame.udp.dst_port
|
||||
|
||||
accept_frame = False
|
||||
if frame.icmp or dst_port in self.software_manager.get_open_ports():
|
||||
# accept the frame as the port is open or if it's an ICMP frame
|
||||
accept_frame = True
|
||||
|
||||
# TODO: add internal node firewall check here?
|
||||
|
||||
if accept_frame:
|
||||
self.session_manager.receive_frame(frame, from_network_interface)
|
||||
else:
|
||||
# denied as port closed
|
||||
self.sys_log.info(f"Ignoring frame for port {frame.tcp.dst_port.value} from {frame.ip.src_ip_address}")
|
||||
# TODO: do we need to do anything more here?
|
||||
pass
|
||||
@@ -1,7 +1,7 @@
|
||||
from primaite.simulator.network.hardware.nodes.host import Host
|
||||
from primaite.simulator.network.hardware.nodes.host.host_node import HostNode
|
||||
|
||||
|
||||
class Server(Host):
|
||||
class Server(HostNode):
|
||||
"""
|
||||
A basic Server class.
|
||||
|
||||
@@ -28,4 +28,4 @@ class Server(Host):
|
||||
* Applications:
|
||||
* Web Browser
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from primaite.simulator.network.hardware.base import Node, NetworkInterface
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame
|
||||
|
||||
|
||||
class NetworkNode(Node):
|
||||
""""""
|
||||
|
||||
def receive_frame(self, frame: Frame, from_network_interface: NetworkInterface):
|
||||
pass
|
||||
@@ -1,19 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
from enum import Enum
|
||||
from ipaddress import IPv4Address, IPv4Network
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from typing import Dict, Any
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
from prettytable import MARKDOWN, PrettyTable
|
||||
|
||||
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
||||
from primaite.simulator.network.hardware.base import NIC, Node
|
||||
from primaite.simulator.network.hardware.base import IPWiredNetworkInterface
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
from primaite.simulator.network.transmission.data_link_layer import EthernetHeader, Frame
|
||||
from primaite.simulator.network.transmission.network_layer import IPPacket, IPProtocol
|
||||
from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader
|
||||
from primaite.simulator.network.hardware.nodes.network.network_node import NetworkNode
|
||||
from primaite.simulator.network.protocols.arp import ARPPacket
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame
|
||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
from primaite.simulator.system.core.sys_log import SysLog
|
||||
from primaite.simulator.system.services.arp.arp import ARP
|
||||
from primaite.simulator.system.services.icmp.icmp import ICMP
|
||||
|
||||
|
||||
class ACLAction(Enum):
|
||||
@@ -197,14 +201,14 @@ class AccessControlList(SimComponent):
|
||||
return self._acl
|
||||
|
||||
def add_rule(
|
||||
self,
|
||||
action: ACLAction,
|
||||
protocol: Optional[IPProtocol] = None,
|
||||
src_ip_address: Optional[Union[str, IPv4Address]] = None,
|
||||
src_port: Optional[Port] = None,
|
||||
dst_ip_address: Optional[Union[str, IPv4Address]] = None,
|
||||
dst_port: Optional[Port] = None,
|
||||
position: int = 0,
|
||||
self,
|
||||
action: ACLAction,
|
||||
protocol: Optional[IPProtocol] = None,
|
||||
src_ip_address: Optional[Union[str, IPv4Address]] = None,
|
||||
src_port: Optional[Port] = None,
|
||||
dst_ip_address: Optional[Union[str, IPv4Address]] = None,
|
||||
dst_port: Optional[Port] = None,
|
||||
position: int = 0,
|
||||
) -> None:
|
||||
"""
|
||||
Add a new ACL rule.
|
||||
@@ -251,12 +255,12 @@ class AccessControlList(SimComponent):
|
||||
raise ValueError(f"Cannot remove ACL rule, position {position} is out of bounds.")
|
||||
|
||||
def is_permitted(
|
||||
self,
|
||||
protocol: IPProtocol,
|
||||
src_ip_address: Union[str, IPv4Address],
|
||||
src_port: Optional[Port],
|
||||
dst_ip_address: Union[str, IPv4Address],
|
||||
dst_port: Optional[Port],
|
||||
self,
|
||||
protocol: IPProtocol,
|
||||
src_ip_address: Union[str, IPv4Address],
|
||||
src_port: Optional[Port],
|
||||
dst_ip_address: Union[str, IPv4Address],
|
||||
dst_port: Optional[Port],
|
||||
) -> Tuple[bool, Optional[Union[str, ACLRule]]]:
|
||||
"""
|
||||
Check if a packet with the given properties is permitted through the ACL.
|
||||
@@ -278,23 +282,23 @@ class AccessControlList(SimComponent):
|
||||
continue
|
||||
|
||||
if (
|
||||
(rule.src_ip_address == src_ip_address or rule.src_ip_address is None)
|
||||
and (rule.dst_ip_address == dst_ip_address or rule.dst_ip_address is None)
|
||||
and (rule.protocol == protocol or rule.protocol is None)
|
||||
and (rule.src_port == src_port or rule.src_port is None)
|
||||
and (rule.dst_port == dst_port or rule.dst_port is None)
|
||||
(rule.src_ip_address == src_ip_address or rule.src_ip_address is None)
|
||||
and (rule.dst_ip_address == dst_ip_address or rule.dst_ip_address is None)
|
||||
and (rule.protocol == protocol or rule.protocol is None)
|
||||
and (rule.src_port == src_port or rule.src_port is None)
|
||||
and (rule.dst_port == dst_port or rule.dst_port is None)
|
||||
):
|
||||
return rule.action == ACLAction.PERMIT, rule
|
||||
|
||||
return self.implicit_action == ACLAction.PERMIT, f"Implicit {self.implicit_action.name}"
|
||||
|
||||
def get_relevant_rules(
|
||||
self,
|
||||
protocol: IPProtocol,
|
||||
src_ip_address: Union[str, IPv4Address],
|
||||
src_port: Port,
|
||||
dst_ip_address: Union[str, IPv4Address],
|
||||
dst_port: Port,
|
||||
self,
|
||||
protocol: IPProtocol,
|
||||
src_ip_address: Union[str, IPv4Address],
|
||||
src_port: Port,
|
||||
dst_ip_address: Union[str, IPv4Address],
|
||||
dst_port: Port,
|
||||
) -> List[ACLRule]:
|
||||
"""
|
||||
Get the list of relevant rules for a packet with given properties.
|
||||
@@ -316,11 +320,11 @@ class AccessControlList(SimComponent):
|
||||
continue
|
||||
|
||||
if (
|
||||
(rule.src_ip_address == src_ip_address or rule.src_ip_address is None)
|
||||
or (rule.dst_ip_address == dst_ip_address or rule.dst_ip_address is None)
|
||||
or (rule.protocol == protocol or rule.protocol is None)
|
||||
or (rule.src_port == src_port or rule.src_port is None)
|
||||
or (rule.dst_port == dst_port or rule.dst_port is None)
|
||||
(rule.src_ip_address == src_ip_address or rule.src_ip_address is None)
|
||||
or (rule.dst_ip_address == dst_ip_address or rule.dst_ip_address is None)
|
||||
or (rule.protocol == protocol or rule.protocol is None)
|
||||
or (rule.src_port == src_port or rule.src_port is None)
|
||||
or (rule.dst_port == dst_port or rule.dst_port is None)
|
||||
):
|
||||
relevant_rules.append(rule)
|
||||
|
||||
@@ -437,11 +441,11 @@ class RouteTable(SimComponent):
|
||||
pass
|
||||
|
||||
def add_route(
|
||||
self,
|
||||
address: Union[IPv4Address, str],
|
||||
subnet_mask: Union[IPv4Address, str],
|
||||
next_hop_ip_address: Union[IPv4Address, str],
|
||||
metric: float = 0.0,
|
||||
self,
|
||||
address: Union[IPv4Address, str],
|
||||
subnet_mask: Union[IPv4Address, str],
|
||||
next_hop_ip_address: Union[IPv4Address, str],
|
||||
metric: float = 0.0,
|
||||
):
|
||||
"""
|
||||
Add a route to the routing table.
|
||||
@@ -528,7 +532,79 @@ class RouteTable(SimComponent):
|
||||
table.add_row([index, f"{route.address}/{network.prefixlen}", route.next_hop_ip_address, route.metric])
|
||||
print(table)
|
||||
|
||||
class RouterNIC(NIC):
|
||||
|
||||
class RouterARP(ARP):
|
||||
"""
|
||||
Inherits from ARPCache and adds router-specific ARP packet processing.
|
||||
|
||||
:ivar SysLog sys_log: A system log for logging messages.
|
||||
:ivar Router router: The router to which this ARP cache belongs.
|
||||
"""
|
||||
router: Optional[Router] = None
|
||||
|
||||
def get_arp_cache_mac_address(self, ip_address: IPv4Address) -> Optional[str]:
|
||||
arp_entry = self.arp.get(ip_address)
|
||||
|
||||
if arp_entry:
|
||||
return arp_entry.mac_address
|
||||
return None
|
||||
|
||||
def get_arp_cache_network_interface(self, ip_address: IPv4Address) -> Optional[RouterInterface]:
|
||||
|
||||
arp_entry = self.arp.get(ip_address)
|
||||
if arp_entry:
|
||||
return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid]
|
||||
for network_interface in self.router.network_interfaces.values():
|
||||
if ip_address in network_interface.ip_network:
|
||||
return network_interface
|
||||
return None
|
||||
|
||||
def _process_arp_request(self, arp_packet: ARPPacket, from_network_interface: RouterInterface):
|
||||
super()._process_arp_request(arp_packet, from_network_interface)
|
||||
|
||||
# If the target IP matches one of the router's NICs
|
||||
for network_interface in self.router.network_interfaces.values():
|
||||
if network_interface.enabled and network_interface.ip_address == arp_packet.target_ip_address:
|
||||
arp_reply = arp_packet.generate_reply(from_network_interface.mac_address)
|
||||
self.send_arp_reply(arp_reply)
|
||||
return
|
||||
|
||||
def _process_arp_reply(self, arp_packet: ARPPacket, from_network_interface: RouterInterface):
|
||||
if arp_packet.target_ip_address == from_network_interface.ip_address:
|
||||
super()._process_arp_reply(arp_packet, from_network_interface)
|
||||
|
||||
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||
"""
|
||||
Processes received data, handling ARP packets.
|
||||
|
||||
:param payload: The payload received.
|
||||
:param session_id: The session ID associated with the received data.
|
||||
:param kwargs: Additional keyword arguments.
|
||||
:return: True if the payload was processed successfully, otherwise False.
|
||||
"""
|
||||
if not super().receive(payload, session_id, **kwargs):
|
||||
return False
|
||||
|
||||
arp_packet: ARPPacket = payload
|
||||
from_network_interface: RouterInterface = kwargs["from_network_interface"]
|
||||
|
||||
for network_interface in self.router.network_interfaces.values():
|
||||
# ARP frame is for this Router
|
||||
if network_interface.ip_address == arp_packet.target_ip_address:
|
||||
if payload.request:
|
||||
self._process_arp_request(arp_packet=arp_packet, from_network_interface=from_network_interface)
|
||||
else:
|
||||
self._process_arp_reply(arp_packet=arp_packet, from_network_interface=from_network_interface)
|
||||
return True
|
||||
|
||||
# ARP frame is not for this router, pass back down to Router to continue routing
|
||||
frame: Frame = kwargs["frame"]
|
||||
self.router.process_frame(frame=frame, from_network_interface=from_network_interface)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class RouterNIC(IPWiredNetworkInterface):
|
||||
"""
|
||||
A Router-specific Network Interface Card (NIC) that extends the standard NIC functionality.
|
||||
|
||||
@@ -561,7 +637,7 @@ class RouterNIC(NIC):
|
||||
self.pcap.capture_inbound(frame)
|
||||
# If this destination or is broadcast
|
||||
if frame.ethernet.dst_mac_addr == self.mac_address or frame.ethernet.dst_mac_addr == "ff:ff:ff:ff:ff:ff":
|
||||
self._connected_node.receive_frame(frame=frame, from_nic=self)
|
||||
self._connected_node.receive_frame(frame=frame, from_network_interface=self)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -569,21 +645,63 @@ class RouterNIC(NIC):
|
||||
return f"{self.mac_address}/{self.ip_address}"
|
||||
|
||||
|
||||
class Router(Node):
|
||||
class RouterInterface(IPWiredNetworkInterface):
|
||||
"""
|
||||
Represents a Router Interface.
|
||||
|
||||
Router interfaces are used to connect routers to networks. They can route packets across different networks,
|
||||
hence have IP addressing information.
|
||||
|
||||
Inherits from:
|
||||
- WiredNetworkInterface: Provides properties and methods specific to wired connections.
|
||||
- Layer3Interface: Provides Layer 3 properties like ip_address and subnet_mask.
|
||||
"""
|
||||
|
||||
def receive_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Receives a network frame on the interface.
|
||||
|
||||
:param frame: The network frame being received.
|
||||
:return: A boolean indicating whether the frame was successfully received.
|
||||
"""
|
||||
if self.enabled:
|
||||
frame.decrement_ttl()
|
||||
if frame.ip and frame.ip.ttl < 1:
|
||||
self._connected_node.sys_log.info("Frame discarded as TTL limit reached")
|
||||
return False
|
||||
frame.set_received_timestamp()
|
||||
self.pcap.capture_inbound(frame)
|
||||
# If this destination or is broadcast
|
||||
if frame.ethernet.dst_mac_addr == self.mac_address or frame.ethernet.dst_mac_addr == "ff:ff:ff:ff:ff:ff":
|
||||
self._connected_node.receive_frame(frame=frame, from_network_interface=self)
|
||||
return True
|
||||
return False
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
String representation of the NIC.
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
|
||||
|
||||
class Router(NetworkNode):
|
||||
"""
|
||||
A class to represent a network router node.
|
||||
|
||||
:ivar str hostname: The name of the router node.
|
||||
:ivar int num_ports: The number of ports in the router.
|
||||
:ivar dict kwargs: Optional keyword arguments for SysLog, ACL, RouteTable, RouterARPCache, RouterICMP.
|
||||
:ivar dict kwargs: Optional keyword arguments for SysLog, ACL, RouteTable, RouterARP, RouterICMP.
|
||||
"""
|
||||
|
||||
num_ports: int
|
||||
ethernet_ports: Dict[int, RouterNIC] = {}
|
||||
network_interfaces: Dict[str, RouterInterface] = {}
|
||||
"The Router Interfaces on the node."
|
||||
network_interface: Dict[int, RouterInterface] = {}
|
||||
"The Router Interfaceson the node by port id."
|
||||
acl: AccessControlList
|
||||
route_table: RouteTable
|
||||
# arp: RouterARPCache
|
||||
# icmp: RouterICMP
|
||||
|
||||
def __init__(self, hostname: str, num_ports: int = 5, **kwargs):
|
||||
if not kwargs.get("sys_log"):
|
||||
@@ -592,23 +710,28 @@ class Router(Node):
|
||||
kwargs["acl"] = AccessControlList(sys_log=kwargs["sys_log"], implicit_action=ACLAction.DENY)
|
||||
if not kwargs.get("route_table"):
|
||||
kwargs["route_table"] = RouteTable(sys_log=kwargs["sys_log"])
|
||||
# if not kwargs.get("arp"):
|
||||
# kwargs["arp"] = RouterARPCache(sys_log=kwargs.get("sys_log"), router=self)
|
||||
# if not kwargs.get("icmp"):
|
||||
# kwargs["icmp"] = RouterICMP(sys_log=kwargs.get("sys_log"), arp_cache=kwargs.get("arp"), router=self)
|
||||
super().__init__(hostname=hostname, num_ports=num_ports, **kwargs)
|
||||
# TODO: Install RouterICMP
|
||||
# TODO: Install RouterARP
|
||||
for i in range(1, self.num_ports + 1):
|
||||
nic = RouterNIC(ip_address="127.0.0.1", subnet_mask="255.0.0.0", gateway="0.0.0.0")
|
||||
self.connect_nic(nic)
|
||||
self.ethernet_ports[i] = nic
|
||||
network_interface = RouterInterface(ip_address="127.0.0.1", subnet_mask="255.0.0.0", gateway="0.0.0.0")
|
||||
self.connect_nic(network_interface)
|
||||
self.network_interface[i] = network_interface
|
||||
|
||||
self.arp.nics = self.nics
|
||||
self.icmp.arp = self.arp
|
||||
self._set_default_acl()
|
||||
|
||||
self.set_original_state()
|
||||
|
||||
|
||||
def _install_system_software(self):
|
||||
"""Install System Software - software that is usually provided with the OS."""
|
||||
self.software_manager.install(ICMP)
|
||||
self.software_manager.install(RouterARP)
|
||||
arp: RouterARP = self.software_manager.arp # noqa
|
||||
arp.router = self
|
||||
|
||||
def _set_default_acl(self):
|
||||
self.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.ARP, dst_port=Port.ARP, position=22)
|
||||
self.acl.add_rule(action=ACLAction.PERMIT, protocol=IPProtocol.ICMP, position=23)
|
||||
|
||||
def set_original_state(self):
|
||||
"""Sets the original state."""
|
||||
self.acl.set_original_state()
|
||||
@@ -619,11 +742,11 @@ class Router(Node):
|
||||
|
||||
def reset_component_for_episode(self, episode: int):
|
||||
"""Reset the original state of the SimComponent."""
|
||||
self.arp.clear()
|
||||
self.software_manager.arp.clear()
|
||||
self.acl.reset_component_for_episode(episode)
|
||||
self.route_table.reset_component_for_episode(episode)
|
||||
for i, nic in self.ethernet_ports.items():
|
||||
nic.reset_component_for_episode(episode)
|
||||
for i, network_interface in self.network_interface.items():
|
||||
network_interface.reset_component_for_episode(episode)
|
||||
self.enable_port(i)
|
||||
|
||||
super().reset_component_for_episode(episode)
|
||||
@@ -633,15 +756,15 @@ class Router(Node):
|
||||
rm.add_request("acl", RequestType(func=self.acl._request_manager))
|
||||
return rm
|
||||
|
||||
def _get_port_of_nic(self, target_nic: NIC) -> Optional[int]:
|
||||
def _get_port_of_nic(self, target_nic: RouterInterface) -> Optional[int]:
|
||||
"""
|
||||
Retrieve the port number for a given NIC.
|
||||
|
||||
:param target_nic: Target network interface.
|
||||
:return: The port number if NIC is found, otherwise None.
|
||||
"""
|
||||
for port, nic in self.ethernet_ports.items():
|
||||
if nic == target_nic:
|
||||
for port, network_interface in self.network_interface.items():
|
||||
if network_interface == target_nic:
|
||||
return port
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
@@ -655,83 +778,98 @@ class Router(Node):
|
||||
state["acl"] = self.acl.describe_state()
|
||||
return state
|
||||
|
||||
def process_frame(self, frame: Frame, from_nic: NIC, re_attempt: bool = False) -> None:
|
||||
def process_frame(self, frame: Frame, from_network_interface: RouterInterface) -> None:
|
||||
"""
|
||||
Process a Frame.
|
||||
|
||||
:param frame: The frame to be routed.
|
||||
:param from_nic: The source network interface.
|
||||
:param re_attempt: Flag to indicate if the routing is a reattempt.
|
||||
:param from_network_interface: The source network interface.
|
||||
"""
|
||||
# Check if src ip is on network of one of the NICs
|
||||
nic = self.arp.get_arp_cache_nic(frame.ip.dst_ip_address)
|
||||
target_mac = self.arp.get_arp_cache_mac_address(frame.ip.dst_ip_address)
|
||||
# check if frame is addressed to this Router but has failed to be received by a service of application at the
|
||||
# receive_frame stage
|
||||
if frame.ip:
|
||||
for network_interface in self.network_interfaces.values():
|
||||
if network_interface.ip_address == frame.ip.dst_ip_address:
|
||||
self.sys_log.info(f"Dropping frame destined for this router on an port that isn't open.")
|
||||
return
|
||||
|
||||
if re_attempt and not nic:
|
||||
network_interface: RouterInterface = self.software_manager.arp.get_arp_cache_network_interface(
|
||||
frame.ip.dst_ip_address
|
||||
)
|
||||
target_mac = self.software_manager.arp.get_arp_cache_mac_address(frame.ip.dst_ip_address)
|
||||
self.software_manager.arp.show()
|
||||
|
||||
if not network_interface:
|
||||
self.sys_log.info(f"Destination {frame.ip.dst_ip_address} is unreachable")
|
||||
# TODO: Send something back to src, is it some sort of ICMP?
|
||||
return
|
||||
|
||||
if not nic:
|
||||
self.arp.send_arp_request(
|
||||
frame.ip.dst_ip_address, ignore_networks=[frame.ip.src_ip_address, from_nic.ip_address]
|
||||
)
|
||||
return self.process_frame(frame=frame, from_nic=from_nic, re_attempt=True)
|
||||
|
||||
if not nic.enabled:
|
||||
self.sys_log.info(f"Frame dropped as NIC {nic} is not enabled")
|
||||
if not network_interface.enabled:
|
||||
self.sys_log.info(f"Frame dropped as NIC {network_interface} is not enabled")
|
||||
# TODO: Send something back to src, is it some sort of ICMP?
|
||||
return
|
||||
|
||||
if frame.ip.dst_ip_address in nic.ip_network:
|
||||
from_port = self._get_port_of_nic(from_nic)
|
||||
to_port = self._get_port_of_nic(nic)
|
||||
if frame.ip.dst_ip_address in network_interface.ip_network:
|
||||
from_port = self._get_port_of_nic(from_network_interface)
|
||||
to_port = self._get_port_of_nic(network_interface)
|
||||
self.sys_log.info(f"Forwarding frame to internally from port {from_port} to port {to_port}")
|
||||
frame.decrement_ttl()
|
||||
if frame.ip and frame.ip.ttl < 1:
|
||||
self.sys_log.info("Frame discarded as TTL limit reached")
|
||||
# TODO: Send something back to src, is it some sort of ICMP?
|
||||
return
|
||||
frame.ethernet.src_mac_addr = nic.mac_address
|
||||
frame.ethernet.src_mac_addr = network_interface.mac_address
|
||||
frame.ethernet.dst_mac_addr = target_mac
|
||||
nic.send_frame(frame)
|
||||
network_interface.send_frame(frame)
|
||||
return
|
||||
else:
|
||||
self._route_frame(frame, from_nic)
|
||||
self.route_frame(frame, from_network_interface)
|
||||
|
||||
def _route_frame(self, frame: Frame, from_nic: NIC, re_attempt: bool = False) -> None:
|
||||
def route_frame(self, frame: Frame, from_network_interface: RouterInterface) -> None:
|
||||
route = self.route_table.find_best_route(frame.ip.dst_ip_address)
|
||||
if route:
|
||||
nic = self.arp.get_arp_cache_nic(route.next_hop_ip_address)
|
||||
target_mac = self.arp.get_arp_cache_mac_address(route.next_hop_ip_address)
|
||||
if re_attempt and not nic:
|
||||
network_interface = self.software_managerarp.get_arp_cache_network_interface(route.next_hop_ip_address)
|
||||
target_mac = self.software_manager.arp.get_arp_cache_mac_address(route.next_hop_ip_address)
|
||||
if not network_interface:
|
||||
self.sys_log.info(f"Destination {frame.ip.dst_ip_address} is unreachable")
|
||||
# TODO: Send something back to src, is it some sort of ICMP?
|
||||
return
|
||||
|
||||
if not nic:
|
||||
self.arp.send_arp_request(frame.ip.dst_ip_address, ignore_networks=[frame.ip.src_ip_address])
|
||||
return self.process_frame(frame=frame, from_nic=from_nic, re_attempt=True)
|
||||
|
||||
if not nic.enabled:
|
||||
self.sys_log.info(f"Frame dropped as NIC {nic} is not enabled")
|
||||
if not network_interface.enabled:
|
||||
self.sys_log.info(f"Frame dropped as NIC {network_interface} is not enabled")
|
||||
# TODO: Send something back to src, is it some sort of ICMP?
|
||||
return
|
||||
|
||||
from_port = self._get_port_of_nic(from_nic)
|
||||
to_port = self._get_port_of_nic(nic)
|
||||
from_port = self._get_port_of_nic(from_network_interface)
|
||||
to_port = self._get_port_of_nic(network_interface)
|
||||
self.sys_log.info(f"Routing frame to internally from port {from_port} to port {to_port}")
|
||||
frame.decrement_ttl()
|
||||
if frame.ip and frame.ip.ttl < 1:
|
||||
self.sys_log.info("Frame discarded as TTL limit reached")
|
||||
# TODO: Send something back to src, is it some sort of ICMP?
|
||||
return
|
||||
frame.ethernet.src_mac_addr = nic.mac_address
|
||||
frame.ethernet.src_mac_addr = network_interface.mac_address
|
||||
frame.ethernet.dst_mac_addr = target_mac
|
||||
nic.send_frame(frame)
|
||||
network_interface.send_frame(frame)
|
||||
|
||||
def receive_frame(self, frame: Frame, from_nic: NIC):
|
||||
def receive_frame(self, frame: Frame, from_network_interface: RouterInterface):
|
||||
"""
|
||||
Receive a frame from a NIC and processes it based on its protocol.
|
||||
Receive a frame from a RouterInterface and processes it based on its protocol.
|
||||
|
||||
:param frame: The incoming frame.
|
||||
:param from_nic: The network interface where the frame is coming from.
|
||||
:param from_network_interface: The network interface where the frame is coming from.
|
||||
"""
|
||||
process_frame = False
|
||||
|
||||
if self.operating_state != NodeOperatingState.ON:
|
||||
return
|
||||
|
||||
if frame.ip and self.software_manager.arp:
|
||||
self.software_manager.arp.add_arp_cache_entry(
|
||||
ip_address=frame.ip.src_ip_address,
|
||||
mac_address=frame.ethernet.src_mac_addr,
|
||||
network_interface=from_network_interface
|
||||
)
|
||||
|
||||
protocol = frame.ip.protocol
|
||||
src_ip_address = frame.ip.src_ip_address
|
||||
dst_ip_address = frame.ip.dst_ip_address
|
||||
@@ -754,21 +892,32 @@ class Router(Node):
|
||||
)
|
||||
|
||||
if not permitted:
|
||||
at_port = self._get_port_of_nic(from_nic)
|
||||
at_port = self._get_port_of_nic(from_network_interface)
|
||||
self.sys_log.info(f"Frame blocked at port {at_port} by rule {rule}")
|
||||
return
|
||||
self.arp.add_arp_cache_entry(src_ip_address, frame.ethernet.src_mac_addr, from_nic)
|
||||
if frame.ip.protocol == IPProtocol.ICMP:
|
||||
self.icmp.process_icmp(frame=frame, from_nic=from_nic)
|
||||
|
||||
self.software_manager.arp.add_arp_cache_entry(
|
||||
ip_address=src_ip_address, mac_address=frame.ethernet.src_mac_addr,
|
||||
network_interface=from_network_interface
|
||||
)
|
||||
|
||||
# Check if the destination port is open on the Node
|
||||
dst_port = None
|
||||
if frame.tcp:
|
||||
dst_port = frame.tcp.dst_port
|
||||
elif frame.udp:
|
||||
dst_port = frame.udp.dst_port
|
||||
|
||||
send_to_session_manager = False
|
||||
if ((frame.icmp and dst_ip_address == from_network_interface.ip_address)
|
||||
or (dst_port in self.software_manager.get_open_ports())):
|
||||
send_to_session_manager = True
|
||||
|
||||
if send_to_session_manager:
|
||||
# Port is open on this Router so pass Frame up to session manager first
|
||||
self.session_manager.receive_frame(frame, from_network_interface)
|
||||
else:
|
||||
if src_port == Port.ARP:
|
||||
self.arp.process_arp_packet(from_nic=from_nic, frame=frame, route_table=self.route_table)
|
||||
return
|
||||
else:
|
||||
# All other traffic
|
||||
process_frame = True
|
||||
if process_frame:
|
||||
self.process_frame(frame, from_nic)
|
||||
self.process_frame(frame, from_network_interface)
|
||||
|
||||
def configure_port(self, port: int, ip_address: Union[IPv4Address, str], subnet_mask: Union[IPv4Address, str]):
|
||||
"""
|
||||
@@ -782,10 +931,12 @@ class Router(Node):
|
||||
ip_address = IPv4Address(ip_address)
|
||||
if not isinstance(subnet_mask, IPv4Address):
|
||||
subnet_mask = IPv4Address(subnet_mask)
|
||||
nic = self.ethernet_ports[port]
|
||||
nic.ip_address = ip_address
|
||||
nic.subnet_mask = subnet_mask
|
||||
self.sys_log.info(f"Configured port {port} with ip_address={ip_address}/{nic.ip_network.prefixlen}")
|
||||
network_interface = self.network_interface[port]
|
||||
network_interface.ip_address = ip_address
|
||||
network_interface.subnet_mask = subnet_mask
|
||||
self.sys_log.info(
|
||||
f"Configured Network Interface {network_interface}"
|
||||
)
|
||||
self.set_original_state()
|
||||
|
||||
def enable_port(self, port: int):
|
||||
@@ -794,9 +945,9 @@ class Router(Node):
|
||||
|
||||
:param port: The port to enable.
|
||||
"""
|
||||
nic = self.ethernet_ports.get(port)
|
||||
if nic:
|
||||
nic.enable()
|
||||
network_interface = self.network_interface.get(port)
|
||||
if network_interface:
|
||||
network_interface.enable()
|
||||
|
||||
def disable_port(self, port: int):
|
||||
"""
|
||||
@@ -804,9 +955,9 @@ class Router(Node):
|
||||
|
||||
:param port: The port to disable.
|
||||
"""
|
||||
nic = self.ethernet_ports.get(port)
|
||||
if nic:
|
||||
nic.disable()
|
||||
network_interface = self.network_interface.get(port)
|
||||
if network_interface:
|
||||
network_interface.disable()
|
||||
|
||||
def show(self, markdown: bool = False):
|
||||
"""
|
||||
@@ -820,14 +971,14 @@ class Router(Node):
|
||||
table.set_style(MARKDOWN)
|
||||
table.align = "l"
|
||||
table.title = f"{self.hostname} Ethernet Interfaces"
|
||||
for port, nic in self.ethernet_ports.items():
|
||||
for port, network_interface in self.network_interface.items():
|
||||
table.add_row(
|
||||
[
|
||||
port,
|
||||
nic.mac_address,
|
||||
f"{nic.ip_address}/{nic.ip_network.prefixlen}",
|
||||
nic.speed,
|
||||
"Enabled" if nic.enabled else "Disabled",
|
||||
network_interface.mac_address,
|
||||
f"{network_interface.ip_address}/{network_interface.ip_network.prefixlen}",
|
||||
network_interface.speed,
|
||||
"Enabled" if network_interface.enabled else "Disabled",
|
||||
]
|
||||
)
|
||||
print(table)
|
||||
@@ -1,16 +1,93 @@
|
||||
from typing import Dict
|
||||
from __future__ import annotations
|
||||
from typing import Dict, Optional
|
||||
|
||||
from prettytable import MARKDOWN, PrettyTable
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.exceptions import NetworkError
|
||||
from primaite.simulator.network.hardware.base import Link, Node, SwitchPort
|
||||
from primaite.simulator.network.hardware.base import WiredNetworkInterface, NetworkInterface, Link
|
||||
from primaite.simulator.network.hardware.nodes.network.network_node import NetworkNode
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class Switch(Node):
|
||||
class SwitchPort(WiredNetworkInterface):
|
||||
"""
|
||||
Represents a Switch Port.
|
||||
|
||||
Switch ports connect devices within the same network. They operate at the data link layer (Layer 2) of the OSI model
|
||||
and are responsible for receiving and forwarding frames based on MAC addresses. Despite operating at Layer 2,
|
||||
they are an essential part of network infrastructure, enabling LAN segmentation, bandwidth management, and
|
||||
the creation of VLANs.
|
||||
|
||||
Inherits from:
|
||||
- WiredNetworkInterface: Provides properties and methods specific to wired connections.
|
||||
|
||||
Switch ports typically do not have IP addresses assigned to them as they function at Layer 2, but managed switches
|
||||
can have management IP addresses for remote management and configuration purposes.
|
||||
"""
|
||||
_connected_node: Optional[Switch] = None
|
||||
"The Switch to which the SwitchPort is connected."
|
||||
|
||||
def set_original_state(self):
|
||||
"""Sets the original state."""
|
||||
vals_to_include = {"port_num", "mac_address", "speed", "mtu", "enabled"}
|
||||
self._original_state = self.model_dump(include=vals_to_include)
|
||||
super().set_original_state()
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state.update(
|
||||
{
|
||||
"mac_address": self.mac_address,
|
||||
"speed": self.speed,
|
||||
"mtu": self.mtu,
|
||||
"enabled": self.enabled,
|
||||
}
|
||||
)
|
||||
return state
|
||||
|
||||
def send_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Attempts to send a network frame through the interface.
|
||||
|
||||
:param frame: The network frame to be sent.
|
||||
:return: A boolean indicating whether the frame was successfully sent.
|
||||
"""
|
||||
if self.enabled:
|
||||
self.pcap.capture_outbound(frame)
|
||||
self._connected_link.transmit_frame(sender_nic=self, frame=frame)
|
||||
return True
|
||||
# Cannot send Frame as the SwitchPort is not enabled
|
||||
return False
|
||||
|
||||
def receive_frame(self, frame: Frame) -> bool:
|
||||
"""
|
||||
Receives a network frame on the interface.
|
||||
|
||||
:param frame: The network frame being received.
|
||||
:return: A boolean indicating whether the frame was successfully received.
|
||||
"""
|
||||
if self.enabled:
|
||||
frame.decrement_ttl()
|
||||
if frame.ip and frame.ip.ttl < 1:
|
||||
self._connected_node.sys_log.info("Frame discarded as TTL limit reached")
|
||||
return False
|
||||
self.pcap.capture_inbound(frame)
|
||||
self._connected_node.receive_frame(frame=frame, from_network_interface=self)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Switch(NetworkNode):
|
||||
"""
|
||||
A class representing a Layer 2 network switch.
|
||||
|
||||
@@ -30,7 +107,7 @@ class Switch(Node):
|
||||
self.switch_ports = {i: SwitchPort() for i in range(1, self.num_ports + 1)}
|
||||
for port_num, port in self.switch_ports.items():
|
||||
port._connected_node = self
|
||||
port._port_num_on_node = port_num
|
||||
port.port_num = port_num
|
||||
port.parent = self
|
||||
port.port_num = port_num
|
||||
|
||||
@@ -78,16 +155,16 @@ class Switch(Node):
|
||||
self.sys_log.info(f"Removed MAC table entry: Port {mac_table_port.port_num} -> {mac_address}")
|
||||
self._add_mac_table_entry(mac_address, switch_port)
|
||||
|
||||
def forward_frame(self, frame: Frame, incoming_port: SwitchPort):
|
||||
def receive_frame(self, frame: Frame, from_network_interface: SwitchPort):
|
||||
"""
|
||||
Forward a frame to the appropriate port based on the destination MAC address.
|
||||
|
||||
:param frame: The Frame to be forwarded.
|
||||
:param incoming_port: The port number from which the frame was received.
|
||||
:param frame: The Frame being received.
|
||||
:param from_network_interface: The SwitchPort that received the frame.
|
||||
"""
|
||||
src_mac = frame.ethernet.src_mac_addr
|
||||
dst_mac = frame.ethernet.dst_mac_addr
|
||||
self._add_mac_table_entry(src_mac, incoming_port)
|
||||
self._add_mac_table_entry(src_mac, from_network_interface)
|
||||
|
||||
outgoing_port = self.mac_address_table.get(dst_mac)
|
||||
if outgoing_port and dst_mac.lower() != "ff:ff:ff:ff:ff:ff":
|
||||
@@ -95,7 +172,7 @@ class Switch(Node):
|
||||
else:
|
||||
# If the destination MAC is not in the table, flood to all ports except incoming
|
||||
for port in self.switch_ports.values():
|
||||
if port.enabled and port != incoming_port:
|
||||
if port.enabled and port != from_network_interface:
|
||||
port.send_frame(frame)
|
||||
|
||||
def disconnect_link_from_port(self, link: Link, port_number: int):
|
||||
@@ -1,11 +1,12 @@
|
||||
from ipaddress import IPv4Address
|
||||
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.base import NIC, NodeOperatingState
|
||||
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
|
||||
from primaite.simulator.network.hardware.nodes.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.switch import Switch
|
||||
from primaite.simulator.network.hardware.base import NodeOperatingState
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.host_node import NIC
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.network.switch import Switch
|
||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
@@ -40,13 +41,13 @@ def client_server_routed() -> Network:
|
||||
# Switch 1
|
||||
switch_1 = Switch(hostname="switch_1", num_ports=6)
|
||||
switch_1.power_on()
|
||||
network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[6])
|
||||
network.connect(endpoint_a=router_1.network_interface[1], endpoint_b=switch_1.switch_ports[6])
|
||||
router_1.enable_port(1)
|
||||
|
||||
# Switch 2
|
||||
switch_2 = Switch(hostname="switch_2", num_ports=6)
|
||||
switch_2.power_on()
|
||||
network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[6])
|
||||
network.connect(endpoint_a=router_1.network_interface[2], endpoint_b=switch_2.switch_ports[6])
|
||||
router_1.enable_port(2)
|
||||
|
||||
# Client 1
|
||||
@@ -58,7 +59,7 @@ def client_server_routed() -> Network:
|
||||
operating_state=NodeOperatingState.ON,
|
||||
)
|
||||
client_1.power_on()
|
||||
network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1])
|
||||
network.connect(endpoint_b=client_1.network_interface[1], endpoint_a=switch_2.switch_ports[1])
|
||||
|
||||
# Server 1
|
||||
server_1 = Server(
|
||||
@@ -69,7 +70,7 @@ def client_server_routed() -> Network:
|
||||
operating_state=NodeOperatingState.ON,
|
||||
)
|
||||
server_1.power_on()
|
||||
network.connect(endpoint_b=server_1.ethernet_port[1], endpoint_a=switch_1.switch_ports[1])
|
||||
network.connect(endpoint_b=server_1.network_interface[1], endpoint_a=switch_1.switch_ports[1])
|
||||
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.ARP, dst_port=Port.ARP, position=22)
|
||||
|
||||
@@ -126,13 +127,13 @@ def arcd_uc2_network() -> Network:
|
||||
# Switch 1
|
||||
switch_1 = Switch(hostname="switch_1", num_ports=8, operating_state=NodeOperatingState.ON)
|
||||
switch_1.power_on()
|
||||
network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[8])
|
||||
network.connect(endpoint_a=router_1.network_interface[1], endpoint_b=switch_1.switch_ports[8])
|
||||
router_1.enable_port(1)
|
||||
|
||||
# Switch 2
|
||||
switch_2 = Switch(hostname="switch_2", num_ports=8, operating_state=NodeOperatingState.ON)
|
||||
switch_2.power_on()
|
||||
network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[8])
|
||||
network.connect(endpoint_a=router_1.network_interface[2], endpoint_b=switch_2.switch_ports[8])
|
||||
router_1.enable_port(2)
|
||||
|
||||
# Client 1
|
||||
@@ -145,7 +146,7 @@ def arcd_uc2_network() -> Network:
|
||||
operating_state=NodeOperatingState.ON,
|
||||
)
|
||||
client_1.power_on()
|
||||
network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1])
|
||||
network.connect(endpoint_b=client_1.network_interface[1], endpoint_a=switch_2.switch_ports[1])
|
||||
client_1.software_manager.install(DataManipulationBot)
|
||||
db_manipulation_bot: DataManipulationBot = client_1.software_manager.software.get("DataManipulationBot")
|
||||
db_manipulation_bot.configure(
|
||||
@@ -167,7 +168,7 @@ def arcd_uc2_network() -> Network:
|
||||
client_2.power_on()
|
||||
web_browser = client_2.software_manager.software.get("WebBrowser")
|
||||
web_browser.target_url = "http://arcd.com/users/"
|
||||
network.connect(endpoint_b=client_2.ethernet_port[1], endpoint_a=switch_2.switch_ports[2])
|
||||
network.connect(endpoint_b=client_2.network_interface[1], endpoint_a=switch_2.switch_ports[2])
|
||||
|
||||
# Domain Controller
|
||||
domain_controller = Server(
|
||||
@@ -180,7 +181,7 @@ def arcd_uc2_network() -> Network:
|
||||
domain_controller.power_on()
|
||||
domain_controller.software_manager.install(DNSServer)
|
||||
|
||||
network.connect(endpoint_b=domain_controller.ethernet_port[1], endpoint_a=switch_1.switch_ports[1])
|
||||
network.connect(endpoint_b=domain_controller.network_interface[1], endpoint_a=switch_1.switch_ports[1])
|
||||
|
||||
# Database Server
|
||||
database_server = Server(
|
||||
@@ -192,7 +193,7 @@ def arcd_uc2_network() -> Network:
|
||||
operating_state=NodeOperatingState.ON,
|
||||
)
|
||||
database_server.power_on()
|
||||
network.connect(endpoint_b=database_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[3])
|
||||
network.connect(endpoint_b=database_server.network_interface[1], endpoint_a=switch_1.switch_ports[3])
|
||||
|
||||
ddl = """
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
@@ -270,7 +271,7 @@ def arcd_uc2_network() -> Network:
|
||||
|
||||
database_client: DatabaseClient = web_server.software_manager.software.get("DatabaseClient")
|
||||
database_client.configure(server_ip_address=IPv4Address("192.168.1.14"))
|
||||
network.connect(endpoint_b=web_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[2])
|
||||
network.connect(endpoint_b=web_server.network_interface[1], endpoint_a=switch_1.switch_ports[2])
|
||||
database_client.run()
|
||||
database_client.connect()
|
||||
|
||||
@@ -291,7 +292,7 @@ def arcd_uc2_network() -> Network:
|
||||
)
|
||||
backup_server.power_on()
|
||||
backup_server.software_manager.install(FTPServer)
|
||||
network.connect(endpoint_b=backup_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[4])
|
||||
network.connect(endpoint_b=backup_server.network_interface[1], endpoint_a=switch_1.switch_ports[4])
|
||||
|
||||
# Security Suite
|
||||
security_suite = Server(
|
||||
@@ -303,9 +304,9 @@ def arcd_uc2_network() -> Network:
|
||||
operating_state=NodeOperatingState.ON,
|
||||
)
|
||||
security_suite.power_on()
|
||||
network.connect(endpoint_b=security_suite.ethernet_port[1], endpoint_a=switch_1.switch_ports[7])
|
||||
network.connect(endpoint_b=security_suite.network_interface[1], endpoint_a=switch_1.switch_ports[7])
|
||||
security_suite.connect_nic(NIC(ip_address="192.168.10.110", subnet_mask="255.255.255.0"))
|
||||
network.connect(endpoint_b=security_suite.ethernet_port[2], endpoint_a=switch_2.switch_ports[7])
|
||||
network.connect(endpoint_b=security_suite.network_interface[2], endpoint_a=switch_2.switch_ports[7])
|
||||
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.ARP, dst_port=Port.ARP, position=22)
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ class ARPEntry(BaseModel):
|
||||
Represents an entry in the ARP cache.
|
||||
|
||||
:param mac_address: The MAC address associated with the IP address.
|
||||
:param nic: The NIC through which the NIC with the IP address is reachable.
|
||||
:param network_interface_uuid: The UIId of the Network Interface through which the NIC with the IP address is
|
||||
reachable.
|
||||
"""
|
||||
|
||||
mac_address: str
|
||||
nic_uuid: str
|
||||
network_interface_uuid: str
|
||||
|
||||
|
||||
class ARPPacket(DataPacket):
|
||||
|
||||
Reference in New Issue
Block a user