From f85aace31b1a994c81db70bb92333ed3cf44fc3d Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 28 Jan 2025 19:35:27 +0000 Subject: [PATCH] #2887 - Correct networking troubles causing test failures --- src/primaite/game/game.py | 16 +++++------ src/primaite/simulator/network/container.py | 2 +- .../simulator/network/hardware/base.py | 17 +++++++----- .../network/hardware/nodes/host/host_node.py | 27 +++++++++---------- .../hardware/nodes/network/firewall.py | 1 + .../network/hardware/nodes/network/router.py | 23 +++++----------- src/primaite/simulator/network/networks.py | 14 +++++----- .../network/test_frame_transmission.py | 2 +- .../system/test_database_on_node.py | 2 +- .../system/test_ftp_client_server.py | 2 +- .../test_simulation/test_request_response.py | 4 +-- 11 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index f4d118ac..b1ff1f9d 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -278,11 +278,11 @@ class PrimaiteGame: # TODO: handle simulation defaults more cleanly if "node_start_up_duration" in defaults_config: - new_node.start_up_duration = defaults_config["node_startup_duration"] + new_node.config.start_up_duration = defaults_config["node_startup_duration"] if "node_shut_down_duration" in defaults_config: - new_node.shut_down_duration = defaults_config["node_shut_down_duration"] + new_node.config.shut_down_duration = defaults_config["node_shut_down_duration"] if "node_scan_duration" in defaults_config: - new_node.node_scan_duration = defaults_config["node_scan_duration"] + new_node.config.node_scan_duration = defaults_config["node_scan_duration"] if "folder_scan_duration" in defaults_config: new_node.file_system._default_folder_scan_duration = defaults_config["folder_scan_duration"] if "folder_restore_duration" in defaults_config: @@ -337,7 +337,7 @@ class PrimaiteGame: # TODO: handle simulation defaults more cleanly if "service_fix_duration" in defaults_config: - new_service.fixing_duration = defaults_config["service_fix_duration"] + new_service.config.fixing_duration = defaults_config["service_fix_duration"] if "service_restart_duration" in defaults_config: new_service.restart_duration = defaults_config["service_restart_duration"] if "service_install_duration" in defaults_config: @@ -394,8 +394,8 @@ class PrimaiteGame: new_node.connect_nic(NIC(ip_address=nic_cfg["ip_address"], subnet_mask=nic_cfg["subnet_mask"])) # temporarily set to 0 so all nodes are initially on - new_node.start_up_duration = 0 - new_node.shut_down_duration = 0 + new_node.config.start_up_duration = 0 + new_node.config.shut_down_duration = 0 net.add_node(new_node) # run through the power on step if the node is to be turned on at the start @@ -403,8 +403,8 @@ class PrimaiteGame: new_node.power_on() # set start up and shut down duration - new_node.start_up_duration = int(node_cfg.get("start_up_duration", 3)) - new_node.shut_down_duration = int(node_cfg.get("shut_down_duration", 3)) + new_node.config.start_up_duration = int(node_cfg.get("start_up_duration", 3)) + new_node.config.shut_down_duration = int(node_cfg.get("shut_down_duration", 3)) # 1.1 Create Node Sets for node_set_cfg in node_sets_cfg: diff --git a/src/primaite/simulator/network/container.py b/src/primaite/simulator/network/container.py index 982495a4..247c06bb 100644 --- a/src/primaite/simulator/network/container.py +++ b/src/primaite/simulator/network/container.py @@ -201,7 +201,7 @@ class Network(SimComponent): port_str, port.ip_address, port.subnet_mask, - node.default_gateway, + node.config.default_gateway, ] ) print(table) diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 21f2946b..08e100d2 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -1493,17 +1493,12 @@ class Node(SimComponent, ABC): :param hostname: The node hostname on the network. :param operating_state: The node operating state, either ON or OFF. """ - - default_gateway: Optional[IPV4Address] = None - "The default gateway IP address for forwarding network traffic to other networks." operating_state: NodeOperatingState = NodeOperatingState.OFF "The hardware state of the node." network_interfaces: Dict[str, NetworkInterface] = {} "The Network Interfaces on the node." network_interface: Dict[int, NetworkInterface] = {} "The Network Interfaces on the node by port id." - dns_server: Optional[IPv4Address] = None - "List of IP addresses of DNS servers used for name resolution." accounts: Dict[str, Account] = {} "All accounts on the node." applications: Dict[str, Application] = {} @@ -1567,6 +1562,16 @@ class Node(SimComponent, ABC): red_scan_countdown: int = 0 "Time steps until reveal to red scan is complete." + dns_server: Optional[IPv4Address] = None + "List of IP addresses of DNS servers used for name resolution." + + default_gateway: Optional[IPV4Address] = None + "The default gateway IP address for forwarding network traffic to other networks." + + @property + def dns_server(self) -> Optional[IPv4Address]: + return self.config.dns_server + @classmethod def from_config(cls, config: Dict) -> "Node": """Create Node object from a given configuration dictionary.""" @@ -1615,7 +1620,7 @@ class Node(SimComponent, ABC): sys_log=kwargs.get("sys_log"), session_manager=kwargs.get("session_manager"), file_system=kwargs.get("file_system"), - dns_server=kwargs.get("dns_server"), + dns_server=kwargs["config"].dns_server, ) super().__init__(**kwargs) self._install_system_software() diff --git a/src/primaite/simulator/network/hardware/nodes/host/host_node.py b/src/primaite/simulator/network/hardware/nodes/host/host_node.py index 23db025d..3b1d8e48 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/host_node.py +++ b/src/primaite/simulator/network/hardware/nodes/host/host_node.py @@ -46,8 +46,8 @@ class HostARP(ARP): :return: The MAC address of the default gateway if present 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) + if self.software_manager.node.config.default_gateway: + return self.get_arp_cache_mac_address(self.software_manager.node.config.default_gateway) def get_default_gateway_network_interface(self) -> Optional[NIC]: """ @@ -55,8 +55,8 @@ class HostARP(ARP): :return: The NIC associated with the default gateway if it exists in the ARP cache; otherwise, None. """ - if self.software_manager.node.default_gateway and self.software_manager.node.has_enabled_network_interface: - return self.get_arp_cache_network_interface(self.software_manager.node.default_gateway) + if self.software_manager.node.config.default_gateway and self.software_manager.node.has_enabled_network_interface: + return self.get_arp_cache_network_interface(self.software_manager.node.config.default_gateway) def _get_arp_cache_mac_address( self, ip_address: IPV4Address, is_reattempt: bool = False, is_default_gateway_attempt: bool = False @@ -75,7 +75,7 @@ class HostARP(ARP): if arp_entry: return arp_entry.mac_address - if ip_address == self.software_manager.node.default_gateway: + if ip_address == self.software_manager.node.config.default_gateway: is_reattempt = True if not is_reattempt: self.send_arp_request(ip_address) @@ -83,11 +83,11 @@ class HostARP(ARP): ip_address=ip_address, is_reattempt=True, is_default_gateway_attempt=is_default_gateway_attempt ) else: - if self.software_manager.node.default_gateway: + if self.software_manager.node.config.default_gateway: if not is_default_gateway_attempt: - self.send_arp_request(self.software_manager.node.default_gateway) + self.send_arp_request(self.software_manager.node.config.default_gateway) return self._get_arp_cache_mac_address( - ip_address=self.software_manager.node.default_gateway, + ip_address=self.software_manager.node.config.default_gateway, is_reattempt=True, is_default_gateway_attempt=True, ) @@ -118,7 +118,7 @@ class HostARP(ARP): if arp_entry: return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid] else: - if ip_address == self.software_manager.node.default_gateway: + if ip_address == self.software_manager.node.config.default_gateway: is_reattempt = True if not is_reattempt: self.send_arp_request(ip_address) @@ -126,11 +126,11 @@ class HostARP(ARP): ip_address=ip_address, is_reattempt=True, is_default_gateway_attempt=is_default_gateway_attempt ) else: - if self.software_manager.node.default_gateway: + if self.software_manager.node.config.default_gateway: if not is_default_gateway_attempt: - self.send_arp_request(self.software_manager.node.default_gateway) + self.send_arp_request(self.software_manager.node.config.default_gateway) return self._get_arp_cache_network_interface( - ip_address=self.software_manager.node.default_gateway, + ip_address=self.software_manager.node.config.default_gateway, is_reattempt=True, is_default_gateway_attempt=True, ) @@ -333,9 +333,8 @@ class HostNode(Node, identifier="HostNode"): """Configuration Schema for HostNode class.""" hostname: str = "HostNode" - ip_address: IPV4Address = "192.168.0.1" subnet_mask: IPV4Address = "255.255.255.0" - default_gateway: IPV4Address = "192.168.10.1" + ip_address: IPV4Address def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/src/primaite/simulator/network/hardware/nodes/network/firewall.py b/src/primaite/simulator/network/hardware/nodes/network/firewall.py index 01c5159b..99dd48c4 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/firewall.py +++ b/src/primaite/simulator/network/hardware/nodes/network/firewall.py @@ -141,6 +141,7 @@ class Firewall(Router, identifier="firewall"): self.external_outbound_acl.sys_log = kwargs["sys_log"] self.external_outbound_acl.name = f"{kwargs['config'].hostname} - External Outbound" + self.power_on() def _init_request_manager(self) -> RequestManager: """ diff --git a/src/primaite/simulator/network/hardware/nodes/network/router.py b/src/primaite/simulator/network/hardware/nodes/network/router.py index 3ecb761b..ebb35cf3 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/router.py +++ b/src/primaite/simulator/network/hardware/nodes/network/router.py @@ -1211,32 +1211,22 @@ class Router(NetworkNode, identifier="router"): "The Router Interfaces on the node." network_interface: Dict[int, RouterInterface] = {} "The Router Interfaces on the node by port id." - - sys_log: SysLog - acl: AccessControlList - route_table: RouteTable - config: "Router.ConfigSchema" = Field(default_factory=lambda: Router.ConfigSchema()) + config: "Router.ConfigSchema" class ConfigSchema(NetworkNode.ConfigSchema): - """Configuration Schema for Router Objects.""" + + hostname: str = "router" + num_ports: int - num_ports: int = 5 - """Number of ports available for this Router. Default is 5""" - - hostname: str = "Router" - - ports: Dict[Union[int, str], Dict] = {} def __init__(self, **kwargs): if not kwargs.get("sys_log"): kwargs["sys_log"] = SysLog(kwargs["config"].hostname) if not kwargs.get("acl"): - kwargs["acl"] = AccessControlList( - sys_log=kwargs["sys_log"], implicit_action=ACLAction.DENY, name=kwargs["config"].hostname - ) + kwargs["acl"] = AccessControlList(sys_log=kwargs["sys_log"], implicit_action=ACLAction.DENY, name=kwargs["config"].hostname) if not kwargs.get("route_table"): kwargs["route_table"] = RouteTable(sys_log=kwargs["sys_log"]) super().__init__(**kwargs) @@ -1562,7 +1552,7 @@ class Router(NetworkNode, identifier="router"): if markdown: table.set_style(MARKDOWN) table.align = "l" - table.title = f"{self.hostname} Network Interfaces" + table.title = f"{self.config.hostname} Network Interfaces" for port, network_interface in self.network_interface.items(): table.add_row( [ @@ -1666,4 +1656,5 @@ class Router(NetworkNode, identifier="router"): next_hop_ip_address = config["default_route"].get("next_hop_ip_address", None) if next_hop_ip_address: router.route_table.set_default_route_next_hop_ip_address(next_hop_ip_address) + router.operating_state = NodeOperatingState.ON if not (p := config.get("operating_state")) else NodeOperatingState[p.upper()] return router diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py index 0579f137..644b2a4a 100644 --- a/src/primaite/simulator/network/networks.py +++ b/src/primaite/simulator/network/networks.py @@ -40,42 +40,42 @@ def client_server_routed() -> Network: network = Network() # Router 1 - router_1 = Router(hostname="router_1", num_ports=3) + router_1 = Router(config=dict(hostname="router_1", num_ports=3)) 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.2.1", subnet_mask="255.255.255.0") # Switch 1 - switch_1 = Switch(hostname="switch_1", num_ports=6) + switch_1 = Switch(config=dict(hostname="switch_1", num_ports=6)) switch_1.power_on() network.connect(endpoint_a=router_1.network_interface[1], endpoint_b=switch_1.network_interface[6]) router_1.enable_port(1) # Switch 2 - switch_2 = Switch(hostname="switch_2", num_ports=6) + switch_2 = Switch(config=dict(hostname="switch_2", num_ports=6)) switch_2.power_on() network.connect(endpoint_a=router_1.network_interface[2], endpoint_b=switch_2.network_interface[6]) router_1.enable_port(2) # Client 1 - client_1 = Computer( + client_1 = Computer(config=dict( hostname="client_1", ip_address="192.168.2.2", subnet_mask="255.255.255.0", default_gateway="192.168.2.1", start_up_duration=0, - ) + )) client_1.power_on() network.connect(endpoint_b=client_1.network_interface[1], endpoint_a=switch_2.network_interface[1]) # Server 1 - server_1 = Server( + server_1 = Server(config=dict( hostname="server_1", ip_address="192.168.1.2", subnet_mask="255.255.255.0", default_gateway="192.168.1.1", start_up_duration=0, - ) + )) server_1.power_on() network.connect(endpoint_b=server_1.network_interface[1], endpoint_a=switch_1.network_interface[1]) diff --git a/tests/integration_tests/network/test_frame_transmission.py b/tests/integration_tests/network/test_frame_transmission.py index 327c87e5..cff99e07 100644 --- a/tests/integration_tests/network/test_frame_transmission.py +++ b/tests/integration_tests/network/test_frame_transmission.py @@ -41,7 +41,7 @@ def test_multi_nic(): """Tests that Computers with multiple NICs can ping each other and the data go across the correct links.""" network = Network() - node_a = Computer(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0) + node_a = Computer(config=dict(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0)) node_a.power_on() node_b = Computer(hostname="node_b", ip_address="192.168.0.11", subnet_mask="255.255.255.0", start_up_duration=0) diff --git a/tests/integration_tests/system/test_database_on_node.py b/tests/integration_tests/system/test_database_on_node.py index 87aca129..64b6ddbc 100644 --- a/tests/integration_tests/system/test_database_on_node.py +++ b/tests/integration_tests/system/test_database_on_node.py @@ -338,7 +338,7 @@ def test_database_client_cannot_query_offline_database_server(uc2_network): assert db_connection.query("INSERT") is True db_server.power_off() - for i in range(db_server.shut_down_duration + 1): + for i in range(db_server.config.shut_down_duration + 1): uc2_network.apply_timestep(timestep=i) assert db_server.operating_state is NodeOperatingState.OFF diff --git a/tests/integration_tests/system/test_ftp_client_server.py b/tests/integration_tests/system/test_ftp_client_server.py index fa4df0a9..57e42457 100644 --- a/tests/integration_tests/system/test_ftp_client_server.py +++ b/tests/integration_tests/system/test_ftp_client_server.py @@ -87,7 +87,7 @@ def test_ftp_client_tries_to_connect_to_offline_server(ftp_client_and_ftp_server server.power_off() - for i in range(server.shut_down_duration + 1): + for i in range(server.config.shut_down_duration + 1): server.apply_timestep(timestep=i) assert ftp_client.operating_state == ServiceOperatingState.RUNNING diff --git a/tests/integration_tests/test_simulation/test_request_response.py b/tests/integration_tests/test_simulation/test_request_response.py index 21152199..efc97ce6 100644 --- a/tests/integration_tests/test_simulation/test_request_response.py +++ b/tests/integration_tests/test_simulation/test_request_response.py @@ -140,9 +140,9 @@ class TestDataManipulationGreenRequests: client_1 = net.get_node_by_hostname("client_1") client_2 = net.get_node_by_hostname("client_2") - client_1.shut_down_duration = 0 + client_1.config.shut_down_duration = 0 client_1.power_off() - client_2.shut_down_duration = 0 + client_2.config.shut_down_duration = 0 client_2.power_off() client_1_browser_execute_off = net.apply_request(["node", "client_1", "application", "WebBrowser", "execute"])