From 5e25fefa14e95abfbee522c84b6ae5471e30e7c1 Mon Sep 17 00:00:00 2001 From: Chris McCarthy Date: Wed, 7 Feb 2024 19:44:40 +0000 Subject: [PATCH] #2248 - Further fixes. All router integration tests now passing. --- .../simulator/network/hardware/base.py | 5 +- .../network/hardware/nodes/host/host_node.py | 31 +- .../network/hardware/nodes/network/router.py | 317 ++++++++++++------ .../simulator/system/core/session_manager.py | 41 ++- .../simulator/system/services/arp/arp.py | 27 +- .../simulator/system/services/icmp/icmp.py | 13 +- .../system/services/ntp/ntp_client.py | 10 +- .../system/services/ntp/ntp_server.py | 8 +- 8 files changed, 315 insertions(+), 137 deletions(-) diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 5299b3dd..0bb68147 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -5,7 +5,7 @@ import secrets from abc import abstractmethod, ABC from ipaddress import IPv4Address, IPv4Network from pathlib import Path -from typing import Any, Literal, Union +from typing import Any, Union from typing import Dict, Optional from prettytable import MARKDOWN, PrettyTable @@ -370,11 +370,11 @@ class IPWiredNetworkInterface(WiredNetworkInterface, Layer3Interface, ABC): def enable(self): super().enable() try: + pass self._connected_node.default_gateway_hello() except AttributeError: pass - @abstractmethod def receive_frame(self, frame: Frame) -> bool: """ @@ -386,7 +386,6 @@ class IPWiredNetworkInterface(WiredNetworkInterface, Layer3Interface, ABC): pass - class WirelessNetworkInterface(NetworkInterface, ABC): """ Represents a wireless network interface in a network device. 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 eefee304..df60edc0 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/host_node.py +++ b/src/primaite/simulator/network/hardware/nodes/host/host_node.py @@ -63,20 +63,22 @@ class HostARP(ARP): if arp_entry: return arp_entry.mac_address + + if ip_address == self.software_manager.node.default_gateway: + is_reattempt = True + 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 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 - ) + 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]: @@ -104,6 +106,8 @@ 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: + is_reattempt = True if not is_reattempt: self.send_arp_request(ip_address) return self._get_arp_cache_network_interface( @@ -147,6 +151,7 @@ class HostARP(ARP): return # Matched ARP request + # TODO: try taking this out self.add_arp_cache_entry( ip_address=arp_packet.sender_ip_address, mac_address=arp_packet.sender_mac_addr, network_interface=from_network_interface diff --git a/src/primaite/simulator/network/hardware/nodes/network/router.py b/src/primaite/simulator/network/hardware/nodes/network/router.py index 06464fd9..dbe3e2c6 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/router.py +++ b/src/primaite/simulator/network/hardware/nodes/network/router.py @@ -1,23 +1,27 @@ from __future__ import annotations +import secrets from enum import Enum from ipaddress import IPv4Address, IPv4Network from typing import Dict, Any from typing import List, Optional, Tuple, Union from prettytable import MARKDOWN, PrettyTable +from pydantic import ValidationError from primaite.simulator.core import RequestManager, RequestType, SimComponent from primaite.simulator.network.hardware.base import IPWiredNetworkInterface from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState from primaite.simulator.network.hardware.nodes.network.network_node import NetworkNode from primaite.simulator.network.protocols.arp import ARPPacket +from primaite.simulator.network.protocols.icmp import ICMPType, ICMPPacket 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 +from primaite.utils.validators import IPV4Address class ACLAction(Enum): @@ -542,64 +546,179 @@ class RouterARP(ARP): """ router: Optional[Router] = None - def get_arp_cache_mac_address(self, ip_address: IPv4Address) -> Optional[str]: + def _get_arp_cache_mac_address( + self, ip_address: IPV4Address, is_reattempt: bool = False, is_default_route_attempt: bool = False + ) -> Optional[str]: arp_entry = self.arp.get(ip_address) if arp_entry: return arp_entry.mac_address + + if not is_reattempt: + route = self.router.route_table.find_best_route(ip_address) + if route and route != self.router.route_table.default_route: + self.send_arp_request(route.next_hop_ip_address) + return self._get_arp_cache_mac_address( + ip_address=route.next_hop_ip_address, + is_reattempt=True, + is_default_route_attempt=is_default_route_attempt + ) + else: + if self.router.route_table.default_route: + if not is_default_route_attempt: + self.send_arp_request(self.router.route_table.default_route.next_hop_ip_address) + return self._get_arp_cache_mac_address( + ip_address=self.router.route_table.default_route.next_hop_ip_address, + is_reattempt=True, + is_default_route_attempt=True + ) return None - def get_arp_cache_network_interface(self, ip_address: IPv4Address) -> Optional[RouterInterface]: + def get_arp_cache_mac_address(self, ip_address: IPv4Address) -> Optional[str]: + 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_route_attempt: bool = False + ) -> 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 + + if not is_reattempt: + route = self.router.route_table.find_best_route(ip_address) + if route and route != self.router.route_table.default_route: + self.send_arp_request(route.next_hop_ip_address) + return self._get_arp_cache_network_interface( + ip_address=route.next_hop_ip_address, + is_reattempt=True, + is_default_route_attempt=is_default_route_attempt + ) + else: + if self.router.route_table.default_route: + if not is_default_route_attempt: + self.send_arp_request(self.router.route_table.default_route.next_hop_ip_address) + return self._get_arp_cache_network_interface( + ip_address=self.router.route_table.default_route.next_hop_ip_address, + is_reattempt=True, + is_default_route_attempt=True + ) return None + + + def get_arp_cache_network_interface(self, ip_address: IPv4Address) -> Optional[RouterInterface]: + + return self._get_arp_cache_network_interface(ip_address) + 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 + if from_network_interface.enabled and from_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) + +class RouterICMP(ICMP): + """ + The Router Internet Control Message Protocol (ICMP) service. + + Extends the ICMP service to provide router-specific functionalities for processing ICMP packets. This class is + responsible for handling ICMP operations such as echo requests and replies in the context of a router. + + Inherits from: + ICMP: Inherits core functionalities for handling ICMP operations, including the processing of echo requests + and replies. + """ + + router: Optional[Router] = None + + def _process_icmp_echo_request(self, frame: Frame, from_network_interface): + """ + Processes an ICMP echo request received by the service. + + :param frame: The network frame containing the ICMP echo request. + """ + self.sys_log.info(f"Received echo request from {frame.ip.src_ip_address}") + + network_interface = self.software_manager.session_manager.resolve_outbound_network_interface( + frame.ip.src_ip_address + ) + + if not network_interface: + self.sys_log.error( + "Cannot send ICMP echo reply as there is no outbound Network Interface to use. Try configuring the default gateway." + ) + return + + icmp_packet = ICMPPacket( + icmp_type=ICMPType.ECHO_REPLY, + icmp_code=0, + identifier=frame.icmp.identifier, + sequence=frame.icmp.sequence + 1, + ) + payload = secrets.token_urlsafe(int(32 / 1.3)) # Standard ICMP 32 bytes size + self.sys_log.info(f"Sending echo reply to {frame.ip.dst_ip_address}") + + self.software_manager.session_manager.receive_payload_from_software_manager( + payload=payload, + dst_ip_address=frame.ip.src_ip_address, + dst_port=self.port, + ip_protocol=self.protocol, + icmp_packet=icmp_packet + ) + def receive(self, payload: Any, session_id: str, **kwargs) -> bool: """ - Processes received data, handling ARP packets. + Processes received data, specifically handling ICMP echo requests and replies. - :param payload: The payload received. + This method determines the appropriate action based on the packet type and the destination IP address's + association with the router interfaces. + + Initially, it checks if the destination IP address of the ICMP packet corresponds to any router interface. If + the packet is not destined for an enabled interface but still matches a router interface, it is redirected + back to the router for further processing. This ensures proper handling of packets intended for the router + itself or needing to be routed to other destinations. + + :param payload: The payload received, expected to be an ICMP packet. :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. + :param kwargs: Additional keyword arguments, including 'frame' (the received network frame) and + 'from_network_interface' (the router interface that received the frame). + :return: True if the ICMP packet was processed successfully, False otherwise. False indicates either the packet + was not ICMP, the destination IP does not correspond to an enabled router interface (and no further action + was required), or the ICMP packet type is not handled by this method. """ - if not super().receive(payload, session_id, **kwargs): + frame: Frame = kwargs["frame"] + from_network_interface = kwargs["from_network_interface"] + + # Check for the presence of an ICMP payload in the frame. + if not frame.icmp: return False - arp_packet: ARPPacket = payload - from_network_interface: RouterInterface = kwargs["from_network_interface"] + # If the frame's destination IP address corresponds to any router interface, not just enabled ones. + if not self.router.ip_is_router_interface(frame.ip.dst_ip_address): + # If the frame is not for this router, pass it back down to the Router for potential further routing. + self.router.process_frame(frame=frame, from_network_interface=from_network_interface) + return True - 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 + # Ensure the destination IP address corresponds to an enabled router interface. + if not self.router.ip_is_router_interface(frame.ip.dst_ip_address, enabled_only=True): + return False - # 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) + # Process ICMP echo requests and replies. + if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST: + self._process_icmp_echo_request(frame, from_network_interface) + elif frame.icmp.icmp_type == ICMPType.ECHO_REPLY: + self._process_icmp_echo_reply(frame) return True @@ -720,10 +839,11 @@ class Router(NetworkNode): 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(RouterICMP) + icmp: RouterICMP = self.software_manager.icmp # noqa + icmp.router = self self.software_manager.install(RouterARP) arp: RouterARP = self.software_manager.arp # noqa arp.router = self @@ -756,6 +876,15 @@ class Router(NetworkNode): rm.add_request("acl", RequestType(func=self.acl._request_manager)) return rm + def ip_is_router_interface(self, ip_address: IPV4Address, enabled_only: bool = False) -> bool: + for router_interface in self.network_interface.values(): + if router_interface.ip_address == ip_address: + if enabled_only: + return router_interface.enabled + else: + return True + return False + def _get_port_of_nic(self, target_nic: RouterInterface) -> Optional[int]: """ Retrieve the port number for a given NIC. @@ -778,6 +907,61 @@ class Router(NetworkNode): state["acl"] = self.acl.describe_state() return state + def receive_frame(self, frame: Frame, from_network_interface: RouterInterface): + """ + Receive a frame from a RouterInterface and processes it based on its protocol. + + :param frame: The incoming frame. + :param from_network_interface: The network interface where the frame is coming from. + """ + + if self.operating_state != NodeOperatingState.ON: + return + + protocol = frame.ip.protocol + src_ip_address = frame.ip.src_ip_address + dst_ip_address = frame.ip.dst_ip_address + src_port = None + dst_port = None + if frame.ip.protocol == IPProtocol.TCP: + src_port = frame.tcp.src_port + dst_port = frame.tcp.dst_port + elif frame.ip.protocol == IPProtocol.UDP: + src_port = frame.udp.src_port + dst_port = frame.udp.dst_port + + # Check if it's permitted + permitted, rule = self.acl.is_permitted( + protocol=protocol, + src_ip_address=src_ip_address, + src_port=src_port, + dst_ip_address=dst_ip_address, + dst_port=dst_port, + ) + + if not permitted: + 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 + + 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 + ) + + send_to_session_manager = False + if ((frame.icmp and self.ip_is_router_interface(dst_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: + self.process_frame(frame, from_network_interface) + def process_frame(self, frame: Frame, from_network_interface: RouterInterface) -> None: """ Process a Frame. @@ -790,14 +974,18 @@ class Router(NetworkNode): 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.") + self.sys_log.info(f"Dropping frame destined for this router on a port that isn't open.") return 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 target_mac: + self.sys_log.info(f"Frame dropped as ARP cannot be resolved for {frame.ip.dst_ip_address}") + # TODO: Send something back to src, is it some sort of ICMP? + return if not network_interface: self.sys_log.info(f"Destination {frame.ip.dst_ip_address} is unreachable") @@ -828,7 +1016,7 @@ class Router(NetworkNode): 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: - network_interface = self.software_managerarp.get_arp_cache_network_interface(route.next_hop_ip_address) + network_interface = self.software_manager.arp.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") @@ -852,73 +1040,6 @@ class Router(NetworkNode): frame.ethernet.dst_mac_addr = target_mac network_interface.send_frame(frame) - def receive_frame(self, frame: Frame, from_network_interface: RouterInterface): - """ - Receive a frame from a RouterInterface and processes it based on its protocol. - - :param frame: The incoming frame. - :param from_network_interface: The network interface where the frame is coming from. - """ - - 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 - src_port = None - dst_port = None - if frame.ip.protocol == IPProtocol.TCP: - src_port = frame.tcp.src_port - dst_port = frame.tcp.dst_port - elif frame.ip.protocol == IPProtocol.UDP: - src_port = frame.udp.src_port - dst_port = frame.udp.dst_port - - # Check if it's permitted - permitted, rule = self.acl.is_permitted( - protocol=protocol, - src_ip_address=src_ip_address, - src_port=src_port, - dst_ip_address=dst_ip_address, - dst_port=dst_port, - ) - - if not permitted: - 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.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: - self.process_frame(frame, from_network_interface) - def configure_port(self, port: int, ip_address: Union[IPv4Address, str], subnet_mask: Union[IPv4Address, str]): """ Configure the IP settings of a given port. @@ -936,7 +1057,7 @@ class Router(NetworkNode): 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): diff --git a/src/primaite/simulator/system/core/session_manager.py b/src/primaite/simulator/system/core/session_manager.py index eafdac8e..4ef10a14 100644 --- a/src/primaite/simulator/system/core/session_manager.py +++ b/src/primaite/simulator/system/core/session_manager.py @@ -147,20 +147,34 @@ class SessionManager: return self.software_manager.arp.get_default_gateway_network_interface() def resolve_outbound_transmission_details( - self, dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None, session_id: Optional[str] = None - ) -> Tuple[Optional['NetworkInterface'], Optional[str], IPv4Address, Optional[IPProtocol], bool]: - if not isinstance(dst_ip_address, (IPv4Address, IPv4Network)): + self, + dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None, + src_port: Optional[Port] = None, + dst_port: Optional[Port] = None, + protocol: Optional[IPProtocol] = None, + session_id: Optional[str] = None + ) -> Tuple[ + Optional['NetworkInterface'], + Optional[str], IPv4Address, + Optional[Port], + Optional[Port], + Optional[IPProtocol], + bool + ]: + if dst_ip_address and not isinstance(dst_ip_address, (IPv4Address, IPv4Network)): dst_ip_address = IPv4Address(dst_ip_address) is_broadcast = False outbound_network_interface = None dst_mac_address = None - protocol = None # Use session details if session_id is provided if session_id: session = self.sessions_by_uuid[session_id] + dst_ip_address = session.with_ip_address protocol = session.protocol + src_port = session.src_port + dst_port = session.dst_port # Determine if the payload is for broadcast or unicast @@ -183,19 +197,20 @@ class SessionManager: dst_mac_address = self.software_manager.arp.get_arp_cache_mac_address(dst_ip_address) break - if dst_ip_address: + if dst_mac_address: use_default_gateway = False outbound_network_interface = self.software_manager.arp.get_arp_cache_network_interface(dst_ip_address) if use_default_gateway: dst_mac_address = self.software_manager.arp.get_default_gateway_mac_address() outbound_network_interface = self.software_manager.arp.get_default_gateway_network_interface() - return outbound_network_interface, dst_mac_address, dst_ip_address, protocol, is_broadcast + return outbound_network_interface, dst_mac_address, dst_ip_address, src_port, dst_port, protocol, is_broadcast def receive_payload_from_software_manager( self, payload: Any, dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None, + src_port: Optional[Port] = None, dst_port: Optional[Port] = None, session_id: Optional[str] = None, ip_protocol: IPProtocol = IPProtocol.TCP, @@ -224,10 +239,15 @@ class SessionManager: is_broadcast = payload.request ip_protocol = IPProtocol.UDP else: + vals = self.resolve_outbound_transmission_details( - dst_ip_address=dst_ip_address, session_id=session_id + dst_ip_address=dst_ip_address, + src_port=src_port, + dst_port=dst_port, + protocol=ip_protocol, + session_id=session_id ) - outbound_network_interface, dst_mac_address, dst_ip_address, protocol, is_broadcast = vals + outbound_network_interface, dst_mac_address, dst_ip_address, src_port, dst_port, protocol, is_broadcast = vals if protocol: ip_protocol = protocol @@ -235,6 +255,11 @@ class SessionManager: if not outbound_network_interface or not dst_mac_address: return False + if not (src_port or dst_port): + raise ValueError( + f"Failed to resolve src or dst port. Have you sent the port from the service or application?" + ) + tcp_header = None udp_header = None if ip_protocol == IPProtocol.TCP: diff --git a/src/primaite/simulator/system/services/arp/arp.py b/src/primaite/simulator/system/services/arp/arp.py index 6a82432e..6a04e845 100644 --- a/src/primaite/simulator/system/services/arp/arp.py +++ b/src/primaite/simulator/system/services/arp/arp.py @@ -109,9 +109,24 @@ class ARP(Service): :param target_ip_address: The target IP address for which the MAC address is being requested. """ + if target_ip_address in self.arp: + return + + use_default_gateway = True + for network_interface in self.software_manager.node.network_interfaces.values(): + if target_ip_address in network_interface.ip_network: + use_default_gateway = False + break + + if use_default_gateway: + if self.software_manager.node.default_gateway: + target_ip_address = self.software_manager.node.default_gateway + else: + return + outbound_network_interface = self.software_manager.session_manager.resolve_outbound_network_interface( target_ip_address - ) + ) if outbound_network_interface: self.sys_log.info(f"Sending ARP request from NIC {outbound_network_interface} for ip {target_ip_address}") arp_packet = ARPPacket( @@ -124,7 +139,7 @@ class ARP(Service): ) else: self.sys_log.error( - "Cannot send ARP request as there is no outbound NIC to use. Try configuring the default gateway." + "Cannot send ARP request as there is no outbound Network Interface to use. Try configuring the default gateway." ) def send_arp_reply(self, arp_reply: ARPPacket): @@ -151,12 +166,12 @@ class ARP(Service): ) else: self.sys_log.error( - "Cannot send ARP reply as there is no outbound NIC to use. Try configuring the default gateway." + "Cannot send ARP reply as there is no outbound Network Interface to use. Try configuring the default gateway." ) @abstractmethod - def _process_arp_request(self, arp_packet: ARPPacket, from_network_interface: NIC): + def _process_arp_request(self, arp_packet: ARPPacket, from_network_interface: NetworkInterface): """ Processes an incoming ARP request. @@ -168,7 +183,7 @@ class ARP(Service): f"{arp_packet.sender_mac_addr}/{arp_packet.sender_ip_address} " ) - def _process_arp_reply(self, arp_packet: ARPPacket, from_network_interface: NIC): + def _process_arp_reply(self, arp_packet: ARPPacket, from_network_interface: NetworkInterface): """ Processes an incoming ARP reply. @@ -197,7 +212,7 @@ class ARP(Service): if not super().receive(payload, session_id, **kwargs): return False - from_network_interface = kwargs.get("from_network_interface") + from_network_interface = kwargs["from_network_interface"] if payload.request: self._process_arp_request(arp_packet=payload, from_network_interface=from_network_interface) else: diff --git a/src/primaite/simulator/system/services/icmp/icmp.py b/src/primaite/simulator/system/services/icmp/icmp.py index be943c28..3ff7b21c 100644 --- a/src/primaite/simulator/system/services/icmp/icmp.py +++ b/src/primaite/simulator/system/services/icmp/icmp.py @@ -14,7 +14,7 @@ _LOGGER = getLogger(__name__) class ICMP(Service): """ - The Internet Control Message Protocol (ICMP) services. + The Internet Control Message Protocol (ICMP) service. Enables the sending and receiving of ICMP messages such as echo requests and replies. This is typically used for network diagnostics, notably the ping command. @@ -91,7 +91,7 @@ class ICMP(Service): if not network_interface: self.sys_log.error( - "Cannot send ICMP echo request as there is no outbound NIC to use. Try configuring the default gateway." + "Cannot send ICMP echo request as there is no outbound Network Interface to use. Try configuring the default gateway." ) return pings, None @@ -109,12 +109,14 @@ class ICMP(Service): ) return sequence, icmp_packet.identifier - def _process_icmp_echo_request(self, frame: Frame): + def _process_icmp_echo_request(self, frame: Frame, from_network_interface): """ Processes an ICMP echo request received by the service. :param frame: The network frame containing the ICMP echo request. """ + if frame.ip.dst_ip_address != from_network_interface.ip_address: + return self.sys_log.info(f"Received echo request from {frame.ip.src_ip_address}") network_interface = self.software_manager.session_manager.resolve_outbound_network_interface( @@ -123,7 +125,7 @@ class ICMP(Service): if not network_interface: self.sys_log.error( - "Cannot send ICMP echo reply as there is no outbound NIC to use. Try configuring the default gateway." + "Cannot send ICMP echo reply as there is no outbound Network Interface to use. Try configuring the default gateway." ) return @@ -173,12 +175,13 @@ class ICMP(Service): :return: True if the payload was processed successfully, otherwise False. """ frame: Frame = kwargs["frame"] + from_network_interface = kwargs["from_network_interface"] if not frame.icmp: return False if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST: - self._process_icmp_echo_request(frame) + self._process_icmp_echo_request(frame, from_network_interface) elif frame.icmp.icmp_type == ICMPType.ECHO_REPLY: self._process_icmp_echo_reply(frame) return True diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index e8c3d0cb..dc143895 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -108,9 +108,13 @@ class NTPClient(Service): def request_time(self) -> None: """Send request to ntp_server.""" - ntp_server_packet = NTPPacket() - - self.send(payload=ntp_server_packet, dest_ip_address=self.ntp_server) + self.software_manager.session_manager.receive_payload_from_software_manager( + payload=NTPPacket(), + dst_ip_address=self.ntp_server, + src_port=self.port, + dst_port=self.port, + ip_protocol=self.protocol, + ) def apply_timestep(self, timestep: int) -> None: """ diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 0a66384a..8e362880 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -69,5 +69,11 @@ class NTPServer(Service): time = datetime.now() payload = payload.generate_reply(time) # send reply - self.send(payload, session_id) + self.software_manager.session_manager.receive_payload_from_software_manager( + payload=payload, + src_port=self.port, + dst_port=self.port, + ip_protocol=self.protocol, + session_id=session_id + ) return True