#2248 - Further fixes. All router integration tests now passing.
This commit is contained in:
@@ -5,7 +5,7 @@ import secrets
|
|||||||
from abc import abstractmethod, ABC
|
from abc import abstractmethod, ABC
|
||||||
from ipaddress import IPv4Address, IPv4Network
|
from ipaddress import IPv4Address, IPv4Network
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Literal, Union
|
from typing import Any, Union
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from prettytable import MARKDOWN, PrettyTable
|
from prettytable import MARKDOWN, PrettyTable
|
||||||
@@ -370,11 +370,11 @@ class IPWiredNetworkInterface(WiredNetworkInterface, Layer3Interface, ABC):
|
|||||||
def enable(self):
|
def enable(self):
|
||||||
super().enable()
|
super().enable()
|
||||||
try:
|
try:
|
||||||
|
pass
|
||||||
self._connected_node.default_gateway_hello()
|
self._connected_node.default_gateway_hello()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def receive_frame(self, frame: Frame) -> bool:
|
def receive_frame(self, frame: Frame) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -386,7 +386,6 @@ class IPWiredNetworkInterface(WiredNetworkInterface, Layer3Interface, ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WirelessNetworkInterface(NetworkInterface, ABC):
|
class WirelessNetworkInterface(NetworkInterface, ABC):
|
||||||
"""
|
"""
|
||||||
Represents a wireless network interface in a network device.
|
Represents a wireless network interface in a network device.
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ class HostARP(ARP):
|
|||||||
|
|
||||||
if arp_entry:
|
if arp_entry:
|
||||||
return arp_entry.mac_address
|
return arp_entry.mac_address
|
||||||
else:
|
|
||||||
|
if ip_address == self.software_manager.node.default_gateway:
|
||||||
|
is_reattempt = True
|
||||||
if not is_reattempt:
|
if not is_reattempt:
|
||||||
self.send_arp_request(ip_address)
|
self.send_arp_request(ip_address)
|
||||||
return self._get_arp_cache_mac_address(
|
return self._get_arp_cache_mac_address(
|
||||||
@@ -104,6 +106,8 @@ class HostARP(ARP):
|
|||||||
if arp_entry:
|
if arp_entry:
|
||||||
return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid]
|
return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid]
|
||||||
else:
|
else:
|
||||||
|
if ip_address == self.software_manager.node.default_gateway:
|
||||||
|
is_reattempt = True
|
||||||
if not is_reattempt:
|
if not is_reattempt:
|
||||||
self.send_arp_request(ip_address)
|
self.send_arp_request(ip_address)
|
||||||
return self._get_arp_cache_network_interface(
|
return self._get_arp_cache_network_interface(
|
||||||
@@ -147,6 +151,7 @@ class HostARP(ARP):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Matched ARP request
|
# Matched ARP request
|
||||||
|
# TODO: try taking this out
|
||||||
self.add_arp_cache_entry(
|
self.add_arp_cache_entry(
|
||||||
ip_address=arp_packet.sender_ip_address, mac_address=arp_packet.sender_mac_addr,
|
ip_address=arp_packet.sender_ip_address, mac_address=arp_packet.sender_mac_addr,
|
||||||
network_interface=from_network_interface
|
network_interface=from_network_interface
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import secrets
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from ipaddress import IPv4Address, IPv4Network
|
from ipaddress import IPv4Address, IPv4Network
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from typing import List, Optional, Tuple, Union
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
from prettytable import MARKDOWN, PrettyTable
|
from prettytable import MARKDOWN, PrettyTable
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
||||||
from primaite.simulator.network.hardware.base import IPWiredNetworkInterface
|
from primaite.simulator.network.hardware.base import IPWiredNetworkInterface
|
||||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
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.hardware.nodes.network.network_node import NetworkNode
|
||||||
from primaite.simulator.network.protocols.arp import ARPPacket
|
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.data_link_layer import Frame
|
||||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from primaite.simulator.system.core.sys_log import SysLog
|
from primaite.simulator.system.core.sys_log import SysLog
|
||||||
from primaite.simulator.system.services.arp.arp import ARP
|
from primaite.simulator.system.services.arp.arp import ARP
|
||||||
from primaite.simulator.system.services.icmp.icmp import ICMP
|
from primaite.simulator.system.services.icmp.icmp import ICMP
|
||||||
|
from primaite.utils.validators import IPV4Address
|
||||||
|
|
||||||
|
|
||||||
class ACLAction(Enum):
|
class ACLAction(Enum):
|
||||||
@@ -542,29 +546,79 @@ class RouterARP(ARP):
|
|||||||
"""
|
"""
|
||||||
router: Optional[Router] = None
|
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)
|
arp_entry = self.arp.get(ip_address)
|
||||||
|
|
||||||
if arp_entry:
|
if arp_entry:
|
||||||
return arp_entry.mac_address
|
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
|
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)
|
arp_entry = self.arp.get(ip_address)
|
||||||
if arp_entry:
|
if arp_entry:
|
||||||
return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid]
|
return self.software_manager.node.network_interfaces[arp_entry.network_interface_uuid]
|
||||||
|
|
||||||
for network_interface in self.router.network_interfaces.values():
|
for network_interface in self.router.network_interfaces.values():
|
||||||
if ip_address in network_interface.ip_network:
|
if ip_address in network_interface.ip_network:
|
||||||
return network_interface
|
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
|
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):
|
def _process_arp_request(self, arp_packet: ARPPacket, from_network_interface: RouterInterface):
|
||||||
super()._process_arp_request(arp_packet, from_network_interface)
|
super()._process_arp_request(arp_packet, from_network_interface)
|
||||||
|
|
||||||
# If the target IP matches one of the router's NICs
|
# If the target IP matches one of the router's NICs
|
||||||
for network_interface in self.router.network_interfaces.values():
|
if from_network_interface.enabled and from_network_interface.ip_address == arp_packet.target_ip_address:
|
||||||
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)
|
arp_reply = arp_packet.generate_reply(from_network_interface.mac_address)
|
||||||
self.send_arp_reply(arp_reply)
|
self.send_arp_reply(arp_reply)
|
||||||
return
|
return
|
||||||
@@ -573,33 +627,98 @@ class RouterARP(ARP):
|
|||||||
if arp_packet.target_ip_address == from_network_interface.ip_address:
|
if arp_packet.target_ip_address == from_network_interface.ip_address:
|
||||||
super()._process_arp_reply(arp_packet, from_network_interface)
|
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:
|
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 session_id: The session ID associated with the received data.
|
||||||
:param kwargs: Additional keyword arguments.
|
:param kwargs: Additional keyword arguments, including 'frame' (the received network frame) and
|
||||||
:return: True if the payload was processed successfully, otherwise False.
|
'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
|
return False
|
||||||
|
|
||||||
arp_packet: ARPPacket = payload
|
# If the frame's destination IP address corresponds to any router interface, not just enabled ones.
|
||||||
from_network_interface: RouterInterface = kwargs["from_network_interface"]
|
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.
|
||||||
for network_interface in self.router.network_interfaces.values():
|
self.router.process_frame(frame=frame, from_network_interface=from_network_interface)
|
||||||
# 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
|
return True
|
||||||
|
|
||||||
# ARP frame is not for this router, pass back down to Router to continue routing
|
# Ensure the destination IP address corresponds to an enabled router interface.
|
||||||
frame: Frame = kwargs["frame"]
|
if not self.router.ip_is_router_interface(frame.ip.dst_ip_address, enabled_only=True):
|
||||||
self.router.process_frame(frame=frame, from_network_interface=from_network_interface)
|
return False
|
||||||
|
|
||||||
|
# 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
|
return True
|
||||||
|
|
||||||
@@ -720,10 +839,11 @@ class Router(NetworkNode):
|
|||||||
|
|
||||||
self.set_original_state()
|
self.set_original_state()
|
||||||
|
|
||||||
|
|
||||||
def _install_system_software(self):
|
def _install_system_software(self):
|
||||||
"""Install System Software - software that is usually provided with the OS."""
|
"""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)
|
self.software_manager.install(RouterARP)
|
||||||
arp: RouterARP = self.software_manager.arp # noqa
|
arp: RouterARP = self.software_manager.arp # noqa
|
||||||
arp.router = self
|
arp.router = self
|
||||||
@@ -756,6 +876,15 @@ class Router(NetworkNode):
|
|||||||
rm.add_request("acl", RequestType(func=self.acl._request_manager))
|
rm.add_request("acl", RequestType(func=self.acl._request_manager))
|
||||||
return rm
|
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]:
|
def _get_port_of_nic(self, target_nic: RouterInterface) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Retrieve the port number for a given NIC.
|
Retrieve the port number for a given NIC.
|
||||||
@@ -778,6 +907,61 @@ class Router(NetworkNode):
|
|||||||
state["acl"] = self.acl.describe_state()
|
state["acl"] = self.acl.describe_state()
|
||||||
return 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:
|
def process_frame(self, frame: Frame, from_network_interface: RouterInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Process a Frame.
|
Process a Frame.
|
||||||
@@ -790,14 +974,18 @@ class Router(NetworkNode):
|
|||||||
if frame.ip:
|
if frame.ip:
|
||||||
for network_interface in self.network_interfaces.values():
|
for network_interface in self.network_interfaces.values():
|
||||||
if network_interface.ip_address == frame.ip.dst_ip_address:
|
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
|
return
|
||||||
|
|
||||||
network_interface: RouterInterface = self.software_manager.arp.get_arp_cache_network_interface(
|
network_interface: RouterInterface = self.software_manager.arp.get_arp_cache_network_interface(
|
||||||
frame.ip.dst_ip_address
|
frame.ip.dst_ip_address
|
||||||
)
|
)
|
||||||
target_mac = self.software_manager.arp.get_arp_cache_mac_address(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:
|
if not network_interface:
|
||||||
self.sys_log.info(f"Destination {frame.ip.dst_ip_address} is unreachable")
|
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:
|
def route_frame(self, frame: Frame, from_network_interface: RouterInterface) -> None:
|
||||||
route = self.route_table.find_best_route(frame.ip.dst_ip_address)
|
route = self.route_table.find_best_route(frame.ip.dst_ip_address)
|
||||||
if route:
|
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)
|
target_mac = self.software_manager.arp.get_arp_cache_mac_address(route.next_hop_ip_address)
|
||||||
if not network_interface:
|
if not network_interface:
|
||||||
self.sys_log.info(f"Destination {frame.ip.dst_ip_address} is unreachable")
|
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
|
frame.ethernet.dst_mac_addr = target_mac
|
||||||
network_interface.send_frame(frame)
|
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]):
|
def configure_port(self, port: int, ip_address: Union[IPv4Address, str], subnet_mask: Union[IPv4Address, str]):
|
||||||
"""
|
"""
|
||||||
Configure the IP settings of a given port.
|
Configure the IP settings of a given port.
|
||||||
|
|||||||
@@ -147,20 +147,34 @@ class SessionManager:
|
|||||||
return self.software_manager.arp.get_default_gateway_network_interface()
|
return self.software_manager.arp.get_default_gateway_network_interface()
|
||||||
|
|
||||||
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,
|
||||||
) -> Tuple[Optional['NetworkInterface'], Optional[str], IPv4Address, Optional[IPProtocol], bool]:
|
dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None,
|
||||||
if not isinstance(dst_ip_address, (IPv4Address, IPv4Network)):
|
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)
|
dst_ip_address = IPv4Address(dst_ip_address)
|
||||||
is_broadcast = False
|
is_broadcast = False
|
||||||
outbound_network_interface = None
|
outbound_network_interface = None
|
||||||
dst_mac_address = None
|
dst_mac_address = None
|
||||||
protocol = None
|
|
||||||
|
|
||||||
# Use session details if session_id is provided
|
# Use session details if session_id is provided
|
||||||
if session_id:
|
if session_id:
|
||||||
session = self.sessions_by_uuid[session_id]
|
session = self.sessions_by_uuid[session_id]
|
||||||
|
|
||||||
dst_ip_address = session.with_ip_address
|
dst_ip_address = session.with_ip_address
|
||||||
protocol = session.protocol
|
protocol = session.protocol
|
||||||
|
src_port = session.src_port
|
||||||
|
dst_port = session.dst_port
|
||||||
|
|
||||||
# Determine if the payload is for broadcast or unicast
|
# 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)
|
dst_mac_address = self.software_manager.arp.get_arp_cache_mac_address(dst_ip_address)
|
||||||
break
|
break
|
||||||
|
|
||||||
if dst_ip_address:
|
if dst_mac_address:
|
||||||
use_default_gateway = False
|
use_default_gateway = False
|
||||||
outbound_network_interface = self.software_manager.arp.get_arp_cache_network_interface(dst_ip_address)
|
outbound_network_interface = self.software_manager.arp.get_arp_cache_network_interface(dst_ip_address)
|
||||||
|
|
||||||
if use_default_gateway:
|
if use_default_gateway:
|
||||||
dst_mac_address = self.software_manager.arp.get_default_gateway_mac_address()
|
dst_mac_address = self.software_manager.arp.get_default_gateway_mac_address()
|
||||||
outbound_network_interface = self.software_manager.arp.get_default_gateway_network_interface()
|
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(
|
def receive_payload_from_software_manager(
|
||||||
self,
|
self,
|
||||||
payload: Any,
|
payload: Any,
|
||||||
dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None,
|
dst_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None,
|
||||||
|
src_port: Optional[Port] = None,
|
||||||
dst_port: Optional[Port] = None,
|
dst_port: Optional[Port] = None,
|
||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
ip_protocol: IPProtocol = IPProtocol.TCP,
|
ip_protocol: IPProtocol = IPProtocol.TCP,
|
||||||
@@ -224,10 +239,15 @@ class SessionManager:
|
|||||||
is_broadcast = payload.request
|
is_broadcast = payload.request
|
||||||
ip_protocol = IPProtocol.UDP
|
ip_protocol = IPProtocol.UDP
|
||||||
else:
|
else:
|
||||||
|
|
||||||
vals = self.resolve_outbound_transmission_details(
|
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:
|
if protocol:
|
||||||
ip_protocol = protocol
|
ip_protocol = protocol
|
||||||
|
|
||||||
@@ -235,6 +255,11 @@ class SessionManager:
|
|||||||
if not outbound_network_interface or not dst_mac_address:
|
if not outbound_network_interface or not dst_mac_address:
|
||||||
return False
|
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
|
tcp_header = None
|
||||||
udp_header = None
|
udp_header = None
|
||||||
if ip_protocol == IPProtocol.TCP:
|
if ip_protocol == IPProtocol.TCP:
|
||||||
|
|||||||
@@ -109,6 +109,21 @@ class ARP(Service):
|
|||||||
|
|
||||||
:param target_ip_address: The target IP address for which the MAC address is being requested.
|
: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(
|
outbound_network_interface = self.software_manager.session_manager.resolve_outbound_network_interface(
|
||||||
target_ip_address
|
target_ip_address
|
||||||
)
|
)
|
||||||
@@ -124,7 +139,7 @@ class ARP(Service):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.sys_log.error(
|
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):
|
def send_arp_reply(self, arp_reply: ARPPacket):
|
||||||
@@ -151,12 +166,12 @@ class ARP(Service):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.sys_log.error(
|
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
|
@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.
|
Processes an incoming ARP request.
|
||||||
|
|
||||||
@@ -168,7 +183,7 @@ class ARP(Service):
|
|||||||
f"{arp_packet.sender_mac_addr}/{arp_packet.sender_ip_address} "
|
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.
|
Processes an incoming ARP reply.
|
||||||
|
|
||||||
@@ -197,7 +212,7 @@ class ARP(Service):
|
|||||||
if not super().receive(payload, session_id, **kwargs):
|
if not super().receive(payload, session_id, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from_network_interface = kwargs.get("from_network_interface")
|
from_network_interface = kwargs["from_network_interface"]
|
||||||
if payload.request:
|
if payload.request:
|
||||||
self._process_arp_request(arp_packet=payload, from_network_interface=from_network_interface)
|
self._process_arp_request(arp_packet=payload, from_network_interface=from_network_interface)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ _LOGGER = getLogger(__name__)
|
|||||||
|
|
||||||
class ICMP(Service):
|
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
|
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.
|
network diagnostics, notably the ping command.
|
||||||
@@ -91,7 +91,7 @@ class ICMP(Service):
|
|||||||
|
|
||||||
if not network_interface:
|
if not network_interface:
|
||||||
self.sys_log.error(
|
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
|
return pings, None
|
||||||
|
|
||||||
@@ -109,12 +109,14 @@ class ICMP(Service):
|
|||||||
)
|
)
|
||||||
return sequence, icmp_packet.identifier
|
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.
|
Processes an ICMP echo request received by the service.
|
||||||
|
|
||||||
:param frame: The network frame containing the ICMP echo request.
|
: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}")
|
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(
|
network_interface = self.software_manager.session_manager.resolve_outbound_network_interface(
|
||||||
@@ -123,7 +125,7 @@ class ICMP(Service):
|
|||||||
|
|
||||||
if not network_interface:
|
if not network_interface:
|
||||||
self.sys_log.error(
|
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
|
return
|
||||||
|
|
||||||
@@ -173,12 +175,13 @@ class ICMP(Service):
|
|||||||
:return: True if the payload was processed successfully, otherwise False.
|
:return: True if the payload was processed successfully, otherwise False.
|
||||||
"""
|
"""
|
||||||
frame: Frame = kwargs["frame"]
|
frame: Frame = kwargs["frame"]
|
||||||
|
from_network_interface = kwargs["from_network_interface"]
|
||||||
|
|
||||||
if not frame.icmp:
|
if not frame.icmp:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST:
|
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:
|
elif frame.icmp.icmp_type == ICMPType.ECHO_REPLY:
|
||||||
self._process_icmp_echo_reply(frame)
|
self._process_icmp_echo_reply(frame)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -108,9 +108,13 @@ class NTPClient(Service):
|
|||||||
|
|
||||||
def request_time(self) -> None:
|
def request_time(self) -> None:
|
||||||
"""Send request to ntp_server."""
|
"""Send request to ntp_server."""
|
||||||
ntp_server_packet = NTPPacket()
|
self.software_manager.session_manager.receive_payload_from_software_manager(
|
||||||
|
payload=NTPPacket(),
|
||||||
self.send(payload=ntp_server_packet, dest_ip_address=self.ntp_server)
|
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:
|
def apply_timestep(self, timestep: int) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -69,5 +69,11 @@ class NTPServer(Service):
|
|||||||
time = datetime.now()
|
time = datetime.now()
|
||||||
payload = payload.generate_reply(time)
|
payload = payload.generate_reply(time)
|
||||||
# send reply
|
# 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
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user