From 1964ab4635d93134ed8c8e7e631ae2f7ff0d8b59 Mon Sep 17 00:00:00 2001 From: Chris McCarthy Date: Thu, 1 Feb 2024 23:05:14 +0000 Subject: [PATCH] #2248 - Lots more progress. Can now use ARP as a service properly. Also integrated the new ARP into the old ICMP which works. Next step is to more ICMP into services. --- .../simulator/network/hardware/base.py | 79 +++++++++---------- .../simulator/system/core/session_manager.py | 55 ++++++++----- .../simulator/system/core/software_manager.py | 5 ++ .../simulator/system/services/arp/arp.py | 7 +- .../simulator/system/services/arp/host_arp.py | 2 +- 5 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 4537adc2..0113c2b4 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -18,7 +18,7 @@ from primaite.simulator.network.hardware.node_operating_state import NodeOperati from primaite.simulator.network.protocols.arp import ARPEntry, ARPPacket from primaite.simulator.network.transmission.data_link_layer import EthernetHeader, Frame from primaite.simulator.network.transmission.network_layer import ICMPPacket, ICMPType, IPPacket, IPProtocol -from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader, UDPHeader +from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader from primaite.simulator.system.applications.application import Application from primaite.simulator.system.core.packet_capture import PacketCapture from primaite.simulator.system.core.session_manager import SessionManager @@ -761,29 +761,30 @@ class ARPCache: minimized or controlled to specific subnets. It is mainly used by the router to prevent ARP requests being sent back to their source. """ - for nic in self.nics.values(): - use_nic = True - if ignore_networks: - for ipv4 in ignore_networks: - if ipv4 in nic.ip_network: - use_nic = False - if nic.enabled and use_nic: - self.sys_log.info(f"Sending ARP request from NIC {nic} for ip {target_ip_address}") - udp_header = UDPHeader(src_port=Port.ARP, dst_port=Port.ARP) - - # Network Layer - ip_packet = IPPacket( - src_ip_address=nic.ip_address, dst_ip_address=target_ip_address, protocol=IPProtocol.UDP - ) - # Data Link Layer - ethernet_header = EthernetHeader(src_mac_addr=nic.mac_address, dst_mac_addr="ff:ff:ff:ff:ff:ff") - arp_packet = ARPPacket( - sender_ip_address=nic.ip_address, - sender_mac_addr=nic.mac_address, - target_ip_address=target_ip_address, - ) - frame = Frame(ethernet=ethernet_header, ip=ip_packet, udp=udp_header, payload=arp_packet) - nic.send_frame(frame) + pass + # for nic in self.nics.values(): + # use_nic = True + # if ignore_networks: + # for ipv4 in ignore_networks: + # if ipv4 in nic.ip_network: + # use_nic = False + # if nic.enabled and use_nic: + # self.sys_log.info(f"Sending ARP request from NIC {nic} for ip {target_ip_address}") + # udp_header = UDPHeader(src_port=Port.ARP, dst_port=Port.ARP) + # + # # Network Layer + # ip_packet = IPPacket( + # src_ip_address=nic.ip_address, dst_ip_address=target_ip_address, protocol=IPProtocol.UDP + # ) + # # Data Link Layer + # ethernet_header = EthernetHeader(src_mac_addr=nic.mac_address, dst_mac_addr="ff:ff:ff:ff:ff:ff") + # arp_packet = ARPPacket( + # sender_ip_address=nic.ip_address, + # sender_mac_addr=nic.mac_address, + # target_ip_address=target_ip_address, + # ) + # frame = Frame(ethernet=ethernet_header, ip=ip_packet, udp=udp_header, payload=arp_packet) + # nic.send_frame(frame) def send_arp_reply(self, arp_reply: ARPPacket, from_nic: NIC): """ @@ -860,7 +861,7 @@ class ICMP: Provides functionalities for managing and handling ICMP packets, including echo requests and replies. """ - def __init__(self, sys_log: SysLog, arp_cache: ARPCache): + def __init__(self, sys_log: SysLog): """ Initialize the ICMP (Internet Control Message Protocol) service. @@ -868,7 +869,7 @@ class ICMP: :param arp_cache: The ARP cache for resolving IP to MAC address mappings. """ self.sys_log: SysLog = sys_log - self.arp: ARPCache = arp_cache + self.software_manager: SoftwareManager = None ## noqa self.request_replies = {} def clear(self): @@ -884,11 +885,11 @@ class ICMP: if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST: if not is_reattempt: self.sys_log.info(f"Received echo request from {frame.ip.src_ip_address}") - target_mac_address = self.arp.get_arp_cache_mac_address(frame.ip.src_ip_address) + target_mac_address = self.software_manager.arp.get_arp_cache_mac_address(frame.ip.src_ip_address) - src_nic = self.arp.get_arp_cache_nic(frame.ip.src_ip_address) + src_nic = self.software_manager.arp.get_arp_cache_nic(frame.ip.src_ip_address) if not src_nic: - self.arp.send_arp_request(frame.ip.src_ip_address) + self.software_manager.arp.send_arp_request(frame.ip.src_ip_address) self.process_icmp(frame=frame, from_nic=from_nic, is_reattempt=True) return @@ -934,16 +935,16 @@ class ICMP: :return: A tuple containing the next sequence number and the identifier, or (0, None) if the target IP address was not found in the ARP cache. """ - nic = self.arp.get_arp_cache_nic(target_ip_address) + nic = self.software_manager.arp.get_arp_cache_nic(target_ip_address) if not nic: return pings, None # ARP entry exists sequence += 1 - target_mac_address = self.arp.get_arp_cache_mac_address(target_ip_address) + target_mac_address = self.software_manager.arp.get_arp_cache_mac_address(target_ip_address) - src_nic = self.arp.get_arp_cache_nic(target_ip_address) + src_nic = self.software_manager.arp.get_arp_cache_nic(target_ip_address) tcp_header = TCPHeader(src_port=Port.ARP, dst_port=Port.ARP) # Network Layer @@ -998,7 +999,6 @@ class Node(SimComponent): root: Path "Root directory for simulation output." sys_log: SysLog - arp: ARPCache icmp: ICMP session_manager: SessionManager software_manager: SoftwareManager @@ -1042,10 +1042,8 @@ class Node(SimComponent): kwargs["default_gateway"] = IPv4Address(kwargs["default_gateway"]) if not kwargs.get("sys_log"): kwargs["sys_log"] = SysLog(kwargs["hostname"]) - if not kwargs.get("arp"): - kwargs["arp"] = ARPCache(sys_log=kwargs.get("sys_log")) if not kwargs.get("icmp"): - kwargs["icmp"] = ICMP(sys_log=kwargs.get("sys_log"), arp_cache=kwargs.get("arp")) + kwargs["icmp"] = ICMP(sys_log=kwargs.get("sys_log")) if not kwargs.get("session_manager"): kwargs["session_manager"] = SessionManager(sys_log=kwargs.get("sys_log"), arp_cache=kwargs.get("arp")) if not kwargs.get("root"): @@ -1061,12 +1059,13 @@ class Node(SimComponent): dns_server=kwargs.get("dns_server"), ) super().__init__(**kwargs) - self.arp.nics = self.nics - self.arp.node = self + self.icmp.software_manager = self.software_manager + self.session_manager.node = self self.session_manager.software_manager = self.software_manager self._install_system_software() self.set_original_state() + def set_original_state(self): """Sets the original state.""" for software in self.software_manager.software.values(): @@ -1489,8 +1488,8 @@ class Node(SimComponent): """ if self.operating_state == NodeOperatingState.ON: if frame.ip: - if frame.ip.src_ip_address in self.arp: - self.arp.add_arp_cache_entry( + if frame.ip.src_ip_address in 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, nic=from_nic ) if frame.ip.protocol == IPProtocol.ICMP: diff --git a/src/primaite/simulator/system/core/session_manager.py b/src/primaite/simulator/system/core/session_manager.py index 8c305032..15001806 100644 --- a/src/primaite/simulator/system/core/session_manager.py +++ b/src/primaite/simulator/system/core/session_manager.py @@ -6,6 +6,7 @@ from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING, Union from prettytable import MARKDOWN, PrettyTable from primaite.simulator.core import SimComponent +from primaite.simulator.network.protocols.arp import ARPPacket 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, UDPHeader @@ -80,7 +81,9 @@ class SessionManager: self.sessions_by_uuid: Dict[str, Session] = {} self.sys_log: SysLog = sys_log self.software_manager: SoftwareManager = None # Noqa - self.arp_cache: "ARPCache" = arp_cache + self.node: Node = None # noqa + + def describe_state(self) -> Dict: """ @@ -138,9 +141,17 @@ class SessionManager: dst_port = None return protocol, with_ip_address, src_port, dst_port + def resolve_outbound_nic(self, dst_ip_address: IPv4Address) -> Optional[NIC]: + for nic in self.node.nics.values(): + if dst_ip_address in nic.ip_network and nic.enabled: + return nic + return self.software_manager.arp.get_default_gateway_nic() + def resolve_outbound_transmission_details( self, dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None, session_id: Optional[str] = None ) -> Tuple[Optional["NIC"], Optional[str], Optional[IPProtocol], bool]: + if not isinstance(dst_ip_address, IPv4Address): + dst_ip_address = IPv4Address(dst_ip_address) is_broadcast = False outbound_nic = None dst_mac_address = None @@ -160,7 +171,7 @@ class SessionManager: dst_ip_address = dst_ip_address.broadcast_address if dst_ip_address: # Find a suitable NIC for the broadcast - for nic in self.arp_cache.nics.values(): + for nic in self.node.nics.values(): if dst_ip_address in nic.ip_network and nic.enabled: dst_mac_address = "ff:ff:ff:ff:ff:ff" outbound_nic = nic @@ -168,18 +179,18 @@ class SessionManager: else: # Resolve MAC address for unicast transmission use_default_gateway = True - for nic in self.arp_cache.nics.values(): + for nic in self.node.nics.values(): if dst_ip_address in nic.ip_network and nic.enabled: - dst_mac_address = self.arp_cache.get_arp_cache_mac_address(dst_ip_address) + dst_mac_address = self.software_manager.arp.get_arp_cache_mac_address(dst_ip_address) break if dst_ip_address: use_default_gateway = False - outbound_nic = self.arp_cache.get_arp_cache_nic(dst_ip_address) + outbound_nic = self.software_manager.arp.get_arp_cache_nic(dst_ip_address) if use_default_gateway: - dst_mac_address = self.arp_cache.get_default_gateway_mac_address() - outbound_nic = self.arp_cache.get_default_gateway_nic() + dst_mac_address = self.software_manager.arp.get_default_gateway_mac_address() + outbound_nic = self.software_manager.arp.get_default_gateway_nic() return outbound_nic, dst_mac_address, protocol, is_broadcast def receive_payload_from_software_manager( @@ -203,15 +214,23 @@ class SessionManager: :param session_id: The Session ID from which the payload originates. Optional. :return: The outcome of sending the frame, or None if sending was unsuccessful. """ - print(ip_protocol) - outbound_nic, dst_mac_address, protocol, is_broadcast = self.resolve_outbound_transmission_details( - dst_ip_address=dst_ip_address, session_id=session_id - ) + if isinstance(payload, ARPPacket): + # ARP requests need to be handles differently + if payload.request: + dst_mac_address = "ff:ff:ff:ff:ff:ff" + else: + dst_mac_address = payload.sender_mac_addr + outbound_nic = self.resolve_outbound_nic(payload.target_ip_address) + is_broadcast = payload.request + ip_protocol = IPProtocol.UDP + else: + outbound_nic, dst_mac_address, protocol, is_broadcast = self.resolve_outbound_transmission_details( + dst_ip_address=dst_ip_address, session_id=session_id + ) - if protocol: - ip_protocol = protocol + if protocol: + ip_protocol = protocol - print(ip_protocol) # Check if outbound NIC and destination MAC address are resolved if not outbound_nic or not dst_mac_address: @@ -224,21 +243,21 @@ class SessionManager: src_port=dst_port, dst_port=dst_port, ) - elif ip_protocol == IPProtocol: + elif ip_protocol == IPProtocol.UDP: udp_header = UDPHeader( src_port=dst_port, dst_port=dst_port, ) # Construct the frame for transmission + frame = Frame( ethernet=EthernetHeader(src_mac_addr=outbound_nic.mac_address, dst_mac_addr=dst_mac_address), - ip=IPPacket(src_ip_address=outbound_nic.ip_address, dst_ip_address=dst_ip_address, ip_protocol=ip_protocol), + ip=IPPacket(src_ip_address=outbound_nic.ip_address, dst_ip_address=dst_ip_address, protocol=ip_protocol), tcp=tcp_header, - udp_header=udp_header, + udp=udp_header, payload=payload, ) - print(frame) # Manage session for unicast transmission if not (is_broadcast and session_id): diff --git a/src/primaite/simulator/system/core/software_manager.py b/src/primaite/simulator/system/core/software_manager.py index e1ec6698..f23e2f55 100644 --- a/src/primaite/simulator/system/core/software_manager.py +++ b/src/primaite/simulator/system/core/software_manager.py @@ -15,6 +15,7 @@ if TYPE_CHECKING: from primaite.simulator.system.core.session_manager import SessionManager from primaite.simulator.system.core.sys_log import SysLog from primaite.simulator.network.hardware.base import Node, NIC + from primaite.simulator.system.services.arp.arp import ARP from typing import Type, TypeVar @@ -46,6 +47,10 @@ class SoftwareManager: self.file_system: FileSystem = file_system self.dns_server: Optional[IPv4Address] = dns_server + @property + def arp(self) -> 'ARP': + return self.software.get("ARP") # noqa + def get_open_ports(self) -> List[Port]: """ Get a list of open ports. diff --git a/src/primaite/simulator/system/services/arp/arp.py b/src/primaite/simulator/system/services/arp/arp.py index 46bc151d..136718c2 100644 --- a/src/primaite/simulator/system/services/arp/arp.py +++ b/src/primaite/simulator/system/services/arp/arp.py @@ -105,8 +105,7 @@ class ARP(Service): minimized or controlled to specific subnets. It is mainly used by the router to prevent ARP requests being sent back to their source. """ - vals: Tuple = self.software_manager.session_manager.resolve_outbound_transmission_details(target_ip_address) - outbound_nic, _, _, _ = vals + outbound_nic = self.software_manager.session_manager.resolve_outbound_nic(target_ip_address) if outbound_nic: self.sys_log.info(f"Sending ARP request from NIC {outbound_nic} for ip {target_ip_address}") arp_packet = ARPPacket( @@ -114,8 +113,8 @@ class ARP(Service): sender_mac_addr=outbound_nic.mac_address, target_ip_address=target_ip_address, ) - self.software_manager.session_manager.receive_payload_from_software_manage( - payload=arp_packet, dst_port=Port.ARP, ip_protocol=self.protocol + self.software_manager.session_manager.receive_payload_from_software_manager( + payload=arp_packet, dst_ip_address=target_ip_address, dst_port=Port.ARP, ip_protocol=self.protocol ) else: print(f"failed for {target_ip_address}") diff --git a/src/primaite/simulator/system/services/arp/host_arp.py b/src/primaite/simulator/system/services/arp/host_arp.py index 678bedbe..8bf3369b 100644 --- a/src/primaite/simulator/system/services/arp/host_arp.py +++ b/src/primaite/simulator/system/services/arp/host_arp.py @@ -53,7 +53,7 @@ class HostARP(ARP): arp_entry = self.arp.get(ip_address) if arp_entry: - return self.nics[arp_entry.nic_uuid] + return self.software_manager.node.nics[arp_entry.nic_uuid] else: if not is_reattempt: self.send_arp_request(ip_address)