#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.

This commit is contained in:
Chris McCarthy
2024-02-01 23:05:14 +00:00
parent 9577f212f8
commit 1964ab4635
5 changed files with 85 additions and 63 deletions

View File

@@ -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.protocols.arp import ARPEntry, ARPPacket
from primaite.simulator.network.transmission.data_link_layer import EthernetHeader, Frame 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.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.applications.application import Application
from primaite.simulator.system.core.packet_capture import PacketCapture from primaite.simulator.system.core.packet_capture import PacketCapture
from primaite.simulator.system.core.session_manager import SessionManager 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 minimized or controlled to specific subnets. It is mainly used by the router to prevent ARP requests being
sent back to their source. sent back to their source.
""" """
for nic in self.nics.values(): pass
use_nic = True # for nic in self.nics.values():
if ignore_networks: # use_nic = True
for ipv4 in ignore_networks: # if ignore_networks:
if ipv4 in nic.ip_network: # for ipv4 in ignore_networks:
use_nic = False # if ipv4 in nic.ip_network:
if nic.enabled and use_nic: # use_nic = False
self.sys_log.info(f"Sending ARP request from NIC {nic} for ip {target_ip_address}") # if nic.enabled and use_nic:
udp_header = UDPHeader(src_port=Port.ARP, dst_port=Port.ARP) # 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( # # Network Layer
src_ip_address=nic.ip_address, dst_ip_address=target_ip_address, protocol=IPProtocol.UDP # 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") # # Data Link Layer
arp_packet = ARPPacket( # ethernet_header = EthernetHeader(src_mac_addr=nic.mac_address, dst_mac_addr="ff:ff:ff:ff:ff:ff")
sender_ip_address=nic.ip_address, # arp_packet = ARPPacket(
sender_mac_addr=nic.mac_address, # sender_ip_address=nic.ip_address,
target_ip_address=target_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) # 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): 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. 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. 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. :param arp_cache: The ARP cache for resolving IP to MAC address mappings.
""" """
self.sys_log: SysLog = sys_log self.sys_log: SysLog = sys_log
self.arp: ARPCache = arp_cache self.software_manager: SoftwareManager = None ## noqa
self.request_replies = {} self.request_replies = {}
def clear(self): def clear(self):
@@ -884,11 +885,11 @@ class ICMP:
if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST: if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST:
if not is_reattempt: if not is_reattempt:
self.sys_log.info(f"Received echo request from {frame.ip.src_ip_address}") 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: 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) self.process_icmp(frame=frame, from_nic=from_nic, is_reattempt=True)
return 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 :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. 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: if not nic:
return pings, None return pings, None
# ARP entry exists # ARP entry exists
sequence += 1 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) tcp_header = TCPHeader(src_port=Port.ARP, dst_port=Port.ARP)
# Network Layer # Network Layer
@@ -998,7 +999,6 @@ class Node(SimComponent):
root: Path root: Path
"Root directory for simulation output." "Root directory for simulation output."
sys_log: SysLog sys_log: SysLog
arp: ARPCache
icmp: ICMP icmp: ICMP
session_manager: SessionManager session_manager: SessionManager
software_manager: SoftwareManager software_manager: SoftwareManager
@@ -1042,10 +1042,8 @@ class Node(SimComponent):
kwargs["default_gateway"] = IPv4Address(kwargs["default_gateway"]) kwargs["default_gateway"] = IPv4Address(kwargs["default_gateway"])
if not kwargs.get("sys_log"): if not kwargs.get("sys_log"):
kwargs["sys_log"] = SysLog(kwargs["hostname"]) 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"): 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"): if not kwargs.get("session_manager"):
kwargs["session_manager"] = SessionManager(sys_log=kwargs.get("sys_log"), arp_cache=kwargs.get("arp")) kwargs["session_manager"] = SessionManager(sys_log=kwargs.get("sys_log"), arp_cache=kwargs.get("arp"))
if not kwargs.get("root"): if not kwargs.get("root"):
@@ -1061,12 +1059,13 @@ class Node(SimComponent):
dns_server=kwargs.get("dns_server"), dns_server=kwargs.get("dns_server"),
) )
super().__init__(**kwargs) super().__init__(**kwargs)
self.arp.nics = self.nics self.icmp.software_manager = self.software_manager
self.arp.node = self self.session_manager.node = self
self.session_manager.software_manager = self.software_manager self.session_manager.software_manager = self.software_manager
self._install_system_software() self._install_system_software()
self.set_original_state() self.set_original_state()
def set_original_state(self): def set_original_state(self):
"""Sets the original state.""" """Sets the original state."""
for software in self.software_manager.software.values(): for software in self.software_manager.software.values():
@@ -1489,8 +1488,8 @@ class Node(SimComponent):
""" """
if self.operating_state == NodeOperatingState.ON: if self.operating_state == NodeOperatingState.ON:
if frame.ip: if frame.ip:
if frame.ip.src_ip_address in self.arp: if frame.ip.src_ip_address in self.software_manager.arp:
self.arp.add_arp_cache_entry( 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 ip_address=frame.ip.src_ip_address, mac_address=frame.ethernet.src_mac_addr, nic=from_nic
) )
if frame.ip.protocol == IPProtocol.ICMP: if frame.ip.protocol == IPProtocol.ICMP:

View File

@@ -6,6 +6,7 @@ from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING, Union
from prettytable import MARKDOWN, PrettyTable from prettytable import MARKDOWN, PrettyTable
from primaite.simulator.core import SimComponent 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.data_link_layer import EthernetHeader, Frame
from primaite.simulator.network.transmission.network_layer import IPPacket, IPProtocol from primaite.simulator.network.transmission.network_layer import IPPacket, IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader, UDPHeader 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.sessions_by_uuid: Dict[str, Session] = {}
self.sys_log: SysLog = sys_log self.sys_log: SysLog = sys_log
self.software_manager: SoftwareManager = None # Noqa self.software_manager: SoftwareManager = None # Noqa
self.arp_cache: "ARPCache" = arp_cache self.node: Node = None # noqa
def describe_state(self) -> Dict: def describe_state(self) -> Dict:
""" """
@@ -138,9 +141,17 @@ class SessionManager:
dst_port = None dst_port = None
return protocol, with_ip_address, src_port, dst_port 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( def resolve_outbound_transmission_details(
self, dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None, session_id: Optional[str] = None self, dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None, session_id: Optional[str] = None
) -> Tuple[Optional["NIC"], Optional[str], Optional[IPProtocol], bool]: ) -> 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 is_broadcast = False
outbound_nic = None outbound_nic = None
dst_mac_address = None dst_mac_address = None
@@ -160,7 +171,7 @@ class SessionManager:
dst_ip_address = dst_ip_address.broadcast_address dst_ip_address = dst_ip_address.broadcast_address
if dst_ip_address: if dst_ip_address:
# Find a suitable NIC for the broadcast # 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: if dst_ip_address in nic.ip_network and nic.enabled:
dst_mac_address = "ff:ff:ff:ff:ff:ff" dst_mac_address = "ff:ff:ff:ff:ff:ff"
outbound_nic = nic outbound_nic = nic
@@ -168,18 +179,18 @@ class SessionManager:
else: else:
# Resolve MAC address for unicast transmission # Resolve MAC address for unicast transmission
use_default_gateway = True 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: 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 break
if dst_ip_address: if dst_ip_address:
use_default_gateway = False 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: if use_default_gateway:
dst_mac_address = self.arp_cache.get_default_gateway_mac_address() dst_mac_address = self.software_manager.arp.get_default_gateway_mac_address()
outbound_nic = self.arp_cache.get_default_gateway_nic() outbound_nic = self.software_manager.arp.get_default_gateway_nic()
return outbound_nic, dst_mac_address, protocol, is_broadcast return outbound_nic, dst_mac_address, protocol, is_broadcast
def receive_payload_from_software_manager( def receive_payload_from_software_manager(
@@ -203,15 +214,23 @@ class SessionManager:
:param session_id: The Session ID from which the payload originates. Optional. :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. :return: The outcome of sending the frame, or None if sending was unsuccessful.
""" """
print(ip_protocol) if isinstance(payload, ARPPacket):
outbound_nic, dst_mac_address, protocol, is_broadcast = self.resolve_outbound_transmission_details( # ARP requests need to be handles differently
dst_ip_address=dst_ip_address, session_id=session_id 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: if protocol:
ip_protocol = protocol ip_protocol = protocol
print(ip_protocol)
# Check if outbound NIC and destination MAC address are resolved # Check if outbound NIC and destination MAC address are resolved
if not outbound_nic or not dst_mac_address: if not outbound_nic or not dst_mac_address:
@@ -224,21 +243,21 @@ class SessionManager:
src_port=dst_port, src_port=dst_port,
dst_port=dst_port, dst_port=dst_port,
) )
elif ip_protocol == IPProtocol: elif ip_protocol == IPProtocol.UDP:
udp_header = UDPHeader( udp_header = UDPHeader(
src_port=dst_port, src_port=dst_port,
dst_port=dst_port, dst_port=dst_port,
) )
# Construct the frame for transmission # Construct the frame for transmission
frame = Frame( frame = Frame(
ethernet=EthernetHeader(src_mac_addr=outbound_nic.mac_address, dst_mac_addr=dst_mac_address), 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, tcp=tcp_header,
udp_header=udp_header, udp=udp_header,
payload=payload, payload=payload,
) )
print(frame)
# Manage session for unicast transmission # Manage session for unicast transmission
if not (is_broadcast and session_id): if not (is_broadcast and session_id):

View File

@@ -15,6 +15,7 @@ if TYPE_CHECKING:
from primaite.simulator.system.core.session_manager import SessionManager from primaite.simulator.system.core.session_manager import SessionManager
from primaite.simulator.system.core.sys_log import SysLog from primaite.simulator.system.core.sys_log import SysLog
from primaite.simulator.network.hardware.base import Node, NIC from primaite.simulator.network.hardware.base import Node, NIC
from primaite.simulator.system.services.arp.arp import ARP
from typing import Type, TypeVar from typing import Type, TypeVar
@@ -46,6 +47,10 @@ class SoftwareManager:
self.file_system: FileSystem = file_system self.file_system: FileSystem = file_system
self.dns_server: Optional[IPv4Address] = dns_server 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]: def get_open_ports(self) -> List[Port]:
""" """
Get a list of open ports. Get a list of open ports.

View File

@@ -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 minimized or controlled to specific subnets. It is mainly used by the router to prevent ARP requests being
sent back to their source. sent back to their source.
""" """
vals: Tuple = self.software_manager.session_manager.resolve_outbound_transmission_details(target_ip_address) outbound_nic = self.software_manager.session_manager.resolve_outbound_nic(target_ip_address)
outbound_nic, _, _, _ = vals
if outbound_nic: if outbound_nic:
self.sys_log.info(f"Sending ARP request from NIC {outbound_nic} for ip {target_ip_address}") self.sys_log.info(f"Sending ARP request from NIC {outbound_nic} for ip {target_ip_address}")
arp_packet = ARPPacket( arp_packet = ARPPacket(
@@ -114,8 +113,8 @@ class ARP(Service):
sender_mac_addr=outbound_nic.mac_address, sender_mac_addr=outbound_nic.mac_address,
target_ip_address=target_ip_address, target_ip_address=target_ip_address,
) )
self.software_manager.session_manager.receive_payload_from_software_manage( self.software_manager.session_manager.receive_payload_from_software_manager(
payload=arp_packet, dst_port=Port.ARP, ip_protocol=self.protocol payload=arp_packet, dst_ip_address=target_ip_address, dst_port=Port.ARP, ip_protocol=self.protocol
) )
else: else:
print(f"failed for {target_ip_address}") print(f"failed for {target_ip_address}")

View File

@@ -53,7 +53,7 @@ class HostARP(ARP):
arp_entry = self.arp.get(ip_address) arp_entry = self.arp.get(ip_address)
if arp_entry: if arp_entry:
return self.nics[arp_entry.nic_uuid] return self.software_manager.node.nics[arp_entry.nic_uuid]
else: else:
if not is_reattempt: if not is_reattempt:
self.send_arp_request(ip_address) self.send_arp_request(ip_address)