From 89ad22acebbd39cc6bf7a30808b104f46bd1152b Mon Sep 17 00:00:00 2001 From: Chris McCarthy Date: Thu, 31 Aug 2023 13:35:56 +0100 Subject: [PATCH] #1800 - Synced with dev. - Added the UC2 network. - Added a Computer class. --- src/primaite/simulator/network/container.py | 29 +++- .../network/hardware/nodes/computer.py | 44 +++++ src/primaite/simulator/network/networks.py | 154 ++++++++++++++++++ 3 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 src/primaite/simulator/network/hardware/nodes/computer.py create mode 100644 src/primaite/simulator/network/networks.py diff --git a/src/primaite/simulator/network/container.py b/src/primaite/simulator/network/container.py index 85676034..ac502d84 100644 --- a/src/primaite/simulator/network/container.py +++ b/src/primaite/simulator/network/container.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Union +from typing import Any, Dict, Union, Optional from primaite import getLogger from primaite.simulator.core import Action, ActionManager, AllowAllValidator, SimComponent @@ -58,6 +58,19 @@ class Network(SimComponent): node.parent = self _LOGGER.info(f"Added node {node.uuid} to Network {self.uuid}") + def get_node_by_hostname(self, hostname: str) -> Optional[Node]: + """ + Get a Node from the Network by its hostname. + + .. note:: Assumes hostnames on the network are unique. + + :param hostname: The Node hostname. + :return: The Node if it exists in the network. + """ + for node in self.nodes.values(): + if node.hostname == hostname: + return node + def remove_node(self, node: Node) -> None: """ Remove a node from the network. @@ -72,7 +85,8 @@ class Network(SimComponent): node.parent = None _LOGGER.info(f"Removed node {node.uuid} from network {self.uuid}") - def connect(self, endpoint_a: Union[NIC, SwitchPort], endpoint_b: Union[NIC, SwitchPort], **kwargs) -> None: + def connect(self, endpoint_a: Union[Node, NIC, SwitchPort], endpoint_b: Union[Node, NIC, SwitchPort], **kwargs) -> \ + None: """Connect two nodes on the network by creating a link between an NIC/SwitchPort of each one. :param endpoint_a: The endpoint to which to connect the link on the first node @@ -81,16 +95,19 @@ class Network(SimComponent): :type endpoint_b: Union[NIC, SwitchPort] :raises RuntimeError: _description_ """ - node_a = endpoint_a.parent - node_b = endpoint_b.parent + node_a: Node = endpoint_a.parent if not isinstance(endpoint_a, Node) else endpoint_a + node_b: Node = endpoint_b.parent if not isinstance(endpoint_b, Node) else endpoint_b if node_a not in self: self.add_node(node_a) if node_b not in self: self.add_node(node_b) if node_a is node_b: - _LOGGER.warn(f"Cannot link endpoint {endpoint_a} to {endpoint_b} because they belong to the same node.") + _LOGGER.warning(f"Cannot link endpoint {endpoint_a} to {endpoint_b} because they belong to the same node.") return - + if isinstance(endpoint_a, Node) and len(endpoint_a.nics) == 1: + endpoint_a = list(endpoint_a.nics.values())[0] + if isinstance(endpoint_b, Node) and len(endpoint_b.nics) == 1: + endpoint_b = list(endpoint_b.nics.values())[0] link = Link(endpoint_a=endpoint_a, endpoint_b=endpoint_b, **kwargs) self.links[link.uuid] = link link.parent = self diff --git a/src/primaite/simulator/network/hardware/nodes/computer.py b/src/primaite/simulator/network/hardware/nodes/computer.py new file mode 100644 index 00000000..8dfb7540 --- /dev/null +++ b/src/primaite/simulator/network/hardware/nodes/computer.py @@ -0,0 +1,44 @@ +from ipaddress import IPv4Address + +from primaite.simulator.network.hardware.base import Node, NIC + + +class Computer(Node): + """ + A basic computer class. + + Example: + >>> pc_a = Computer( + 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: + * ARP. + * ICMP. + * Packet Capture. + * Sys Log. + * Services: + * DNS Client. + * FTP Client. + * LDAP Client. + * NTP Client. + * Applications: + * Email Client. + * Web Browser. + * Processes: + * Placeholder. + """ + + def __init__(self, **kwargs): + for key in {"ip_address", "subnet_mask", "default_gateway"}: + if key in kwargs: + if not isinstance(kwargs[key], IPv4Address): + kwargs[key] = IPv4Address(kwargs[key]) + super().__init__(**kwargs) + self.connect_nic(NIC(ip_address=kwargs["ip_address"], subnet_mask=kwargs["subnet_mask"])) diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py new file mode 100644 index 00000000..0eccefa4 --- /dev/null +++ b/src/primaite/simulator/network/networks.py @@ -0,0 +1,154 @@ +from primaite.simulator.network.container import Network +from primaite.simulator.network.hardware.base import Switch, NIC +from primaite.simulator.network.hardware.nodes.computer import Computer +from primaite.simulator.network.hardware.nodes.router import Router, ACLAction +from primaite.simulator.network.transmission.network_layer import IPProtocol +from primaite.simulator.network.transmission.transport_layer import Port + + +def arcd_uc2_network() -> Network: + """ + Models the ARCD Use Case 2 Network. + + +------------+ + | domain_ | + +------------+ controller | + | | | + | +------------+ + | + | + +------------+ | +------------+ + | | | | | + | client_1 +---------+ | +---------+ web_server | + | | | | | | | + +------------+ | | | +------------+ + +--+---------+ +------------+ +------+--+--+ + | | | | | | + | switch_2 +------+ router_1 +------+ switch_1 | + | | | | | | + +--+------+--+ +------------+ +--+---+--+--+ + +------------+ | | | | | +------------+ + | | | | | | | | database | + | client_2 +---------+ | | | +---------+ _server | + | | | | | | | + +------------+ | | | +------------+ + | +------------+ | | + | | security | | | + +---------+ _suite +---------+ | +------------+ + | | | | backup_ | + +------------+ +------------+ server | + | | + +------------+ + + Example: + >>> network = arcd_uc2_network() + >>> network.get_node_by_hostname("client_1").ping("192.168.1.10") + + """ + network = Network() + + # Router 1 + router_1 = Router(hostname="router_1", num_ports=5) + router_1.power_on() + router_1.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0") + router_1.configure_port(port=2, ip_address="192.168.10.1", subnet_mask="255.255.255.0") + + # Switch 1 + switch_1 = Switch(hostname="switch_1", num_ports=8) + switch_1.power_on() + network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[8]) + router_1.enable_port(1) + + # Switch 2 + switch_2 = Switch(hostname="switch_2", num_ports=8) + switch_2.power_on() + network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[8]) + router_1.enable_port(2) + + # Client 1 + client_1 = Computer( + hostname="client_1", + ip_address="192.168.10.21", + subnet_mask="255.255.255.0", + default_gateway="192.168.10.1" + ) + client_1.power_on() + network.connect(endpoint_a=client_1, endpoint_b=switch_2.switch_ports[1]) + + # Client 2 + client_2 = Computer( + hostname="client_2", + ip_address="192.168.10.22", + subnet_mask="255.255.255.0", + default_gateway="192.168.10.1" + ) + client_2.power_on() + network.connect(endpoint_a=client_2, endpoint_b=switch_2.switch_ports[2]) + + # Domain Controller + domain_controller = Computer( + hostname="domain_controller", + ip_address="192.168.1.10", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1" + ) + domain_controller.power_on() + network.connect(endpoint_a=domain_controller, endpoint_b=switch_1.switch_ports[1]) + + # Web Server + web_server = Computer( + hostname="web_server", + ip_address="192.168.1.12", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1" + ) + web_server.power_on() + network.connect(endpoint_a=web_server, endpoint_b=switch_1.switch_ports[2]) + + # Database Server + database_server = Computer( + hostname="database_server", + ip_address="192.168.1.14", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1" + ) + database_server.power_on() + network.connect(endpoint_a=database_server, endpoint_b=switch_1.switch_ports[3]) + + # Backup Server + backup_server = Computer( + hostname="backup_server", + ip_address="192.168.1.16", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1" + ) + backup_server.power_on() + network.connect(endpoint_a=backup_server, endpoint_b=switch_1.switch_ports[4]) + + # Security Suite + security_suite = Computer( + hostname="security_suite", + ip_address="192.168.1.110", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1" + ) + security_suite.power_on() + network.connect(endpoint_a=security_suite, endpoint_b=switch_1.switch_ports[7]) + security_suite_external_nic = NIC(ip_address="192.168.10.110", subnet_mask="255.255.255.0") + security_suite.connect_nic(security_suite_external_nic) + network.connect(endpoint_a=security_suite_external_nic, endpoint_b=switch_2.switch_ports[7]) + + router_1.acl.add_rule( + action=ACLAction.PERMIT, + src_port=Port.ARP, + dst_port=Port.ARP, + position=22 + ) + + router_1.acl.add_rule( + action=ACLAction.PERMIT, + protocol=IPProtocol.ICMP, + position=23 + ) + + return network