#2248 - Further fixes. All router integration tests now passing.

This commit is contained in:
Chris McCarthy
2024-02-07 19:44:40 +00:00
parent 7bbfd564fb
commit 5e25fefa14
8 changed files with 315 additions and 137 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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:
"""

View File

@@ -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