diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py index b7bd2e95..0b6fe8d4 100644 --- a/src/primaite/simulator/network/networks.py +++ b/src/primaite/simulator/network/networks.py @@ -51,14 +51,22 @@ def client_server_routed() -> Network: # Client 1 client_1 = Computer( - hostname="client_1", ip_address="192.168.2.2", subnet_mask="255.255.255.0", default_gateway="192.168.2.1" + hostname="client_1", + ip_address="192.168.2.2", + subnet_mask="255.255.255.0", + default_gateway="192.168.2.1", + operating_state=NodeOperatingState.ON, ) client_1.power_on() network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1]) # Server 1 server_1 = Server( - hostname="server_1", ip_address="192.168.1.2", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" + hostname="server_1", + ip_address="192.168.1.2", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1", + operating_state=NodeOperatingState.ON, ) server_1.power_on() network.connect(endpoint_b=server_1.ethernet_port[1], endpoint_a=switch_1.switch_ports[1]) diff --git a/src/primaite/simulator/system/services/ftp/ftp_client.py b/src/primaite/simulator/system/services/ftp/ftp_client.py index b73eec7e..263d09b4 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_client.py +++ b/src/primaite/simulator/system/services/ftp/ftp_client.py @@ -264,8 +264,11 @@ class FTPClient(FTPServiceABC): This helps prevent an FTP request loop - FTP client and servers can exist on the same node. """ + if not self._can_perform_action(): + return False + if payload.status_code is None: - self.sys_log.error(f"FTP Server could not be found - Error Code: {payload.status_code.value}") + self.sys_log.error(f"FTP Server could not be found - Error Code: {FTPStatusCode.NOT_FOUND.value}") return False self.sys_log.info(f"{self.name}: Received FTP Response {payload.ftp_command.name} {payload.status_code.value}") diff --git a/tests/conftest.py b/tests/conftest.py index 8a1f885c..55db53c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,9 @@ from primaite.session.session import PrimaiteSession from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.node_operating_state import 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.networks import arcd_uc2_network from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port @@ -152,3 +154,83 @@ def client_server() -> Tuple[Computer, Server]: assert link.is_up return computer, server + + +@pytest.fixture(scope="function") +def example_network() -> Network: + """ + Create the network used for testing. + + Should only contain the nodes and links. + This would act as the base network and services and applications are installed in the relevant test file, + + -------------- -------------- + | client_1 |----- ----| server_1 | + -------------- | -------------- ------------ -------------- | -------------- + ------| switch_1 |------| router |------| switch_2 |------ + -------------- | -------------- ------------ -------------- | -------------- + | client_2 |---- ----| server_2 | + -------------- -------------- + """ + network = Network() + + # Router 1 + router_1 = Router(hostname="router_1", num_ports=5, operating_state=NodeOperatingState.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, operating_state=NodeOperatingState.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, operating_state=NodeOperatingState.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", + operating_state=NodeOperatingState.ON, + ) + network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=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", + operating_state=NodeOperatingState.ON, + ) + network.connect(endpoint_b=client_2.ethernet_port[1], endpoint_a=switch_2.switch_ports[2]) + + # Domain Controller + server_1 = Server( + hostname="server_1", + ip_address="192.168.1.10", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1", + operating_state=NodeOperatingState.ON, + ) + + network.connect(endpoint_b=server_1.ethernet_port[1], endpoint_a=switch_1.switch_ports[1]) + + # Database Server + server_2 = Server( + hostname="server_2", + ip_address="192.168.1.14", + subnet_mask="255.255.255.0", + default_gateway="192.168.1.1", + operating_state=NodeOperatingState.ON, + ) + network.connect(endpoint_b=server_2.ethernet_port[1], endpoint_a=switch_1.switch_ports[3]) + + 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 diff --git a/tests/integration_tests/network/test_network_creation.py b/tests/integration_tests/network/test_network_creation.py index 91218068..0af44dbb 100644 --- a/tests/integration_tests/network/test_network_creation.py +++ b/tests/integration_tests/network/test_network_creation.py @@ -2,6 +2,28 @@ import pytest from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.base import NIC, Node +from primaite.simulator.network.hardware.nodes.computer import Computer +from primaite.simulator.network.hardware.nodes.server import Server +from primaite.simulator.network.networks import client_server_routed + + +def test_network(example_network): + network: Network = example_network + client_1: Computer = network.get_node_by_hostname("client_1") + client_2: Computer = network.get_node_by_hostname("client_2") + server_1: Server = network.get_node_by_hostname("server_1") + server_2: Server = network.get_node_by_hostname("server_2") + + assert client_1.ping(client_2.ethernet_port[1].ip_address) + assert client_2.ping(client_1.ethernet_port[1].ip_address) + + assert server_1.ping(server_2.ethernet_port[1].ip_address) + assert server_2.ping(server_1.ethernet_port[1].ip_address) + + assert client_1.ping(server_1.ethernet_port[1].ip_address) + assert client_2.ping(server_1.ethernet_port[1].ip_address) + assert client_1.ping(server_2.ethernet_port[1].ip_address) + assert client_2.ping(server_2.ethernet_port[1].ip_address) def test_adding_removing_nodes(): diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py index c079ebc4..1d7355a2 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py @@ -1,3 +1,5 @@ +from ipaddress import IPv4Address + import pytest from primaite.simulator.network.hardware.base import Node @@ -7,6 +9,7 @@ from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPS from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.services.ftp.ftp_client import FTPClient +from primaite.simulator.system.services.service import ServiceOperatingState @pytest.fixture(scope="function") @@ -48,3 +51,72 @@ def test_ftp_client_store_file(ftp_client): ftp_client_service.receive(response) assert ftp_client.file_system.get_file(folder_name="downloads", file_name="file.txt") + + +def test_ftp_should_not_process_commands_if_service_not_running(ftp_client): + """Method _process_ftp_command should return false if service is not running.""" + payload: FTPPacket = FTPPacket( + ftp_command=FTPCommand.PORT, + ftp_command_args=Port.FTP, + status_code=FTPStatusCode.OK, + ) + + ftp_client_service: FTPClient = ftp_client.software_manager.software["FTPClient"] + ftp_client_service.stop() + assert ftp_client_service.operating_state is ServiceOperatingState.STOPPED + assert ftp_client_service._process_ftp_command(payload=payload).status_code is FTPStatusCode.ERROR + + +def test_ftp_tries_to_senf_file__that_does_not_exist(ftp_client): + """Method send_file should return false if no file to send.""" + assert ftp_client.file_system.get_file(folder_name="root", file_name="test.txt") is None + + ftp_client_service: FTPClient = ftp_client.software_manager.software["FTPClient"] + assert ftp_client_service.operating_state is ServiceOperatingState.RUNNING + assert ( + ftp_client_service.send_file( + dest_ip_address=IPv4Address("192.168.1.1"), + src_folder_name="root", + src_file_name="test.txt", + dest_folder_name="root", + dest_file_name="text.txt", + ) + is False + ) + + +def test_offline_ftp_client_receives_request(ftp_client): + """Receive should return false if the node the ftp client is installed on is offline.""" + ftp_client_service: FTPClient = ftp_client.software_manager.software["FTPClient"] + ftp_client.power_off() + + for i in range(ftp_client.shut_down_duration + 1): + ftp_client.apply_timestep(timestep=i) + + assert ftp_client.operating_state is NodeOperatingState.OFF + assert ftp_client_service.operating_state is ServiceOperatingState.STOPPED + + payload: FTPPacket = FTPPacket( + ftp_command=FTPCommand.PORT, + ftp_command_args=Port.FTP, + status_code=FTPStatusCode.OK, + ) + + assert ftp_client_service.receive(payload=payload) is False + + +def test_receive_should_fail_if_payload_is_not_ftp(ftp_client): + """Receive should return false if the node the ftp client is installed on is not an FTPPacket.""" + ftp_client_service: FTPClient = ftp_client.software_manager.software["FTPClient"] + assert ftp_client_service.receive(payload=None) is False + + +def test_receive_should_ignore_payload_with_none_status_code(ftp_client): + """Receive should ignore payload with no set status code to prevent infinite send/receive loops.""" + payload: FTPPacket = FTPPacket( + ftp_command=FTPCommand.PORT, + ftp_command_args=Port.FTP, + status_code=None, + ) + ftp_client_service: FTPClient = ftp_client.software_manager.software["FTPClient"] + assert ftp_client_service.receive(payload=payload) is False