#2248 - ICMP now working as a service using the session manager for transmission. Now started to comb through the tests to fix anything up.
This commit is contained in:
@@ -30,11 +30,11 @@ class HostARP(ARP):
|
||||
ip_address=ip_address, is_reattempt=True, is_default_gateway_attempt=is_default_gateway_attempt
|
||||
)
|
||||
else:
|
||||
if self.node.default_gateway:
|
||||
if self.software_manager.node.default_gateway:
|
||||
if not is_default_gateway_attempt:
|
||||
self.send_arp_request(self.node.default_gateway)
|
||||
self.send_arp_request(self.software_manager.node.default_gateway)
|
||||
return self._get_arp_cache_mac_address(
|
||||
ip_address=self.node.default_gateway, is_reattempt=True, is_default_gateway_attempt=True
|
||||
ip_address=self.software_manager.node.default_gateway, is_reattempt=True, is_default_gateway_attempt=True
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -61,11 +61,11 @@ class HostARP(ARP):
|
||||
ip_address=ip_address, is_reattempt=True, is_default_gateway_attempt=is_default_gateway_attempt
|
||||
)
|
||||
else:
|
||||
if self.node.default_gateway:
|
||||
if self.software_manager.node.default_gateway:
|
||||
if not is_default_gateway_attempt:
|
||||
self.send_arp_request(self.node.default_gateway)
|
||||
self.send_arp_request(self.software_manager.node.default_gateway)
|
||||
return self._get_arp_cache_nic(
|
||||
ip_address=self.node.default_gateway, is_reattempt=True, is_default_gateway_attempt=True
|
||||
ip_address=self.software_manager.node.default_gateway, is_reattempt=True, is_default_gateway_attempt=True
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
98
src/primaite/simulator/system/services/arp/router_arp.py
Normal file
98
src/primaite/simulator/system/services/arp/router_arp.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# class RouterARPCache(ARPCache):
|
||||
# """
|
||||
# Inherits from ARPCache and adds router-specific ARP packet processing.
|
||||
#
|
||||
# :ivar SysLog sys_log: A system log for logging messages.
|
||||
# :ivar Router router: The router to which this ARP cache belongs.
|
||||
# """
|
||||
#
|
||||
# def __init__(self, sys_log: SysLog, router: Router):
|
||||
# super().__init__(sys_log)
|
||||
# self.router: Router = router
|
||||
#
|
||||
# def process_arp_packet(
|
||||
# self, from_nic: NIC, frame: Frame, route_table: RouteTable, is_reattempt: bool = False
|
||||
# ) -> None:
|
||||
# """
|
||||
# Processes a received ARP (Address Resolution Protocol) packet in a router-specific way.
|
||||
#
|
||||
# This method is responsible for handling both ARP requests and responses. It processes ARP packets received on a
|
||||
# Network Interface Card (NIC) and performs actions based on whether the packet is a request or a reply. This
|
||||
# includes updating the ARP cache, forwarding ARP replies, sending ARP requests for unknown destinations, and
|
||||
# handling packet TTL (Time To Live).
|
||||
#
|
||||
# The method first checks if the ARP packet is a request or a reply. For ARP replies, it updates the ARP cache
|
||||
# and forwards the reply if necessary. For ARP requests, it checks if the target IP matches one of the router's
|
||||
# NICs and sends an ARP reply if so. If the destination is not directly connected, it consults the routing table
|
||||
# to find the best route and reattempts ARP request processing if needed.
|
||||
#
|
||||
# :param from_nic: The NIC that received the ARP packet.
|
||||
# :param frame: The frame containing the ARP packet.
|
||||
# :param route_table: The routing table of the router.
|
||||
# :param is_reattempt: Flag to indicate if this is a reattempt of processing the ARP packet, defaults to False.
|
||||
# """
|
||||
# arp_packet = frame.arp
|
||||
#
|
||||
# # ARP Reply
|
||||
# if not arp_packet.request:
|
||||
# if arp_packet.target_ip_address == from_nic.ip_address:
|
||||
# # reply to the Router specifically
|
||||
# self.sys_log.info(
|
||||
# f"Received ARP response for {arp_packet.sender_ip_address} "
|
||||
# f"from {arp_packet.sender_mac_addr} via NIC {from_nic}"
|
||||
# )
|
||||
# self.add_arp_cache_entry(
|
||||
# ip_address=arp_packet.sender_ip_address,
|
||||
# mac_address=arp_packet.sender_mac_addr,
|
||||
# nic=from_nic,
|
||||
# )
|
||||
# return
|
||||
#
|
||||
# # # Reply for a connected requested
|
||||
# # nic = self.get_arp_cache_nic(arp_packet.target_ip_address)
|
||||
# # if nic:
|
||||
# # self.sys_log.info(
|
||||
# # f"Forwarding arp reply for {arp_packet.target_ip_address}, from {arp_packet.sender_ip_address}"
|
||||
# # )
|
||||
# # arp_packet.sender_mac_addr = nic.mac_address
|
||||
# # frame.decrement_ttl()
|
||||
# # if frame.ip and frame.ip.ttl < 1:
|
||||
# # self.sys_log.info("Frame discarded as TTL limit reached")
|
||||
# # return
|
||||
# # nic.send_frame(frame)
|
||||
# # return
|
||||
#
|
||||
# # ARP Request
|
||||
# self.sys_log.info(
|
||||
# f"Received ARP request for {arp_packet.target_ip_address} from "
|
||||
# f"{arp_packet.sender_mac_addr}/{arp_packet.sender_ip_address} "
|
||||
# )
|
||||
# # Matched ARP request
|
||||
# self.add_arp_cache_entry(
|
||||
# ip_address=arp_packet.sender_ip_address, mac_address=arp_packet.sender_mac_addr, nic=from_nic
|
||||
# )
|
||||
#
|
||||
# # If the target IP matches one of the router's NICs
|
||||
# for nic in self.nics.values():
|
||||
# if nic.enabled and nic.ip_address == arp_packet.target_ip_address:
|
||||
# arp_reply = arp_packet.generate_reply(from_nic.mac_address)
|
||||
# self.send_arp_reply(arp_reply, from_nic)
|
||||
# return
|
||||
#
|
||||
# # # Check Route Table
|
||||
# # route = route_table.find_best_route(arp_packet.target_ip_address)
|
||||
# # if route and route != self.router.route_table.default_route:
|
||||
# # nic = self.get_arp_cache_nic(route.next_hop_ip_address)
|
||||
# #
|
||||
# # if not nic:
|
||||
# # if not is_reattempt:
|
||||
# # self.send_arp_request(route.next_hop_ip_address, ignore_networks=[frame.ip.src_ip_address])
|
||||
# # return self.process_arp_packet(from_nic, frame, route_table, is_reattempt=True)
|
||||
# # else:
|
||||
# # self.sys_log.info("Ignoring ARP request as destination unavailable/No ARP entry found")
|
||||
# # return
|
||||
# # else:
|
||||
# # arp_reply = arp_packet.generate_reply(from_nic.mac_address)
|
||||
# # self.send_arp_reply(arp_reply, from_nic)
|
||||
# # return
|
||||
#
|
||||
@@ -5,8 +5,8 @@ from typing import Dict, Any, Union, Optional, Tuple
|
||||
from primaite import getLogger
|
||||
from primaite.simulator.network.hardware.base import NIC
|
||||
from primaite.simulator.network.protocols.icmp import ICMPPacket, ICMPType
|
||||
from primaite.simulator.network.transmission.data_link_layer import Frame, EthernetHeader
|
||||
from primaite.simulator.network.transmission.network_layer import IPProtocol, IPPacket
|
||||
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.services.service import Service
|
||||
|
||||
@@ -14,6 +14,12 @@ _LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class ICMP(Service):
|
||||
"""
|
||||
The Internet Control Message Protocol (ICMP) services.
|
||||
|
||||
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.
|
||||
"""
|
||||
request_replies: Dict = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -26,53 +32,22 @@ class ICMP(Service):
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
"""Clears the ICMP request replies tracker."""
|
||||
"""
|
||||
Clears the ICMP request and reply tracker.
|
||||
|
||||
This is typically used to reset the state of the service, removing all tracked ICMP echo requests and their
|
||||
corresponding replies.
|
||||
"""
|
||||
self.request_replies.clear()
|
||||
|
||||
def _send_icmp_echo_request(
|
||||
self, target_ip_address: IPv4Address, sequence: int = 0, identifier: Optional[int] = None, pings: int = 4
|
||||
) -> Tuple[int, Union[int, None]]:
|
||||
"""
|
||||
Send an ICMP echo request (ping) to a target IP address and manage the sequence and identifier.
|
||||
|
||||
:param target_ip_address: The target IP address to send the ping.
|
||||
:param sequence: The sequence number of the echo request. Defaults to 0.
|
||||
:param identifier: An optional identifier for the ICMP packet. If None, a default will be used.
|
||||
: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.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.software_manager.arp.get_arp_cache_mac_address(target_ip_address)
|
||||
|
||||
src_nic = self.software_manager.arp.get_arp_cache_nic(target_ip_address)
|
||||
|
||||
# Network Layer
|
||||
ip_packet = IPPacket(
|
||||
src_ip_address=nic.ip_address,
|
||||
dst_ip_address=target_ip_address,
|
||||
protocol=IPProtocol.ICMP,
|
||||
)
|
||||
# Data Link Layer
|
||||
ethernet_header = EthernetHeader(src_mac_addr=src_nic.mac_address, dst_mac_addr=target_mac_address)
|
||||
icmp_packet = ICMPPacket(identifier=identifier, sequence=sequence)
|
||||
payload = secrets.token_urlsafe(int(32 / 1.3)) # Standard ICMP 32 bytes size
|
||||
frame = Frame(ethernet=ethernet_header, ip=ip_packet, icmp=icmp_packet, payload=payload)
|
||||
nic.send_frame(frame)
|
||||
return sequence, icmp_packet.identifier
|
||||
|
||||
def ping(self, target_ip_address: Union[IPv4Address, str], pings: int = 4) -> bool:
|
||||
"""
|
||||
Ping an IP address, performing a standard ICMP echo request/response.
|
||||
Pings a target IP address by sending an ICMP echo request and waiting for a reply.
|
||||
|
||||
:param target_ip_address: The target IP address to ping.
|
||||
:param pings: The number of pings to attempt, default is 4.
|
||||
:return: True if the ping is successful, otherwise False.
|
||||
:param target_ip_address: The IP address to be pinged.
|
||||
:param pings: The number of echo requests to send. Defaults to 4.
|
||||
:return: True if the ping was successful (i.e., if a reply was received for every request sent), otherwise
|
||||
False.
|
||||
"""
|
||||
if not self._can_perform_action():
|
||||
return False
|
||||
@@ -101,37 +76,79 @@ class ICMP(Service):
|
||||
|
||||
return passed
|
||||
|
||||
def _process_icmp_echo_request(self, frame: Frame, from_nic: NIC, is_reattempt: bool = False):
|
||||
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.software_manager.arp.get_arp_cache_mac_address(frame.ip.src_ip_address)
|
||||
def _send_icmp_echo_request(
|
||||
self, target_ip_address: IPv4Address, sequence: int = 0, identifier: Optional[int] = None, pings: int = 4
|
||||
) -> Tuple[int, Union[int, None]]:
|
||||
"""
|
||||
Sends an ICMP echo request to a specified target IP address.
|
||||
|
||||
src_nic = self.software_manager.arp.get_arp_cache_nic(frame.ip.src_ip_address)
|
||||
if not src_nic:
|
||||
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
|
||||
:param target_ip_address: The target IP address for the echo request.
|
||||
:param sequence: The sequence number of the echo request.
|
||||
:param identifier: The identifier for the ICMP packet. If None, a default identifier is used.
|
||||
:param pings: The number of pings to send. Defaults to 4.
|
||||
:return: A tuple containing the next sequence number and the identifier.
|
||||
"""
|
||||
nic = self.software_manager.session_manager.resolve_outbound_nic(target_ip_address)
|
||||
|
||||
# Network Layer
|
||||
ip_packet = IPPacket(
|
||||
src_ip_address=src_nic.ip_address, dst_ip_address=frame.ip.src_ip_address, protocol=IPProtocol.ICMP
|
||||
if not nic:
|
||||
self.sys_log.error(
|
||||
"Cannot send ICMP echo request as there is no outbound NIC to use. Try configuring the default gateway."
|
||||
)
|
||||
# Data Link Layer
|
||||
ethernet_header = EthernetHeader(src_mac_addr=src_nic.mac_address, dst_mac_addr=target_mac_address)
|
||||
icmp_reply_packet = ICMPPacket(
|
||||
icmp_type=ICMPType.ECHO_REPLY,
|
||||
icmp_code=0,
|
||||
identifier=frame.icmp.identifier,
|
||||
sequence=frame.icmp.sequence + 1,
|
||||
return pings, None
|
||||
|
||||
sequence += 1
|
||||
|
||||
icmp_packet = ICMPPacket(identifier=identifier, sequence=sequence)
|
||||
payload = secrets.token_urlsafe(int(32 / 1.3)) # Standard ICMP 32 bytes size
|
||||
|
||||
self.software_manager.session_manager.receive_payload_from_software_manager(
|
||||
payload=payload,
|
||||
dst_ip_address=target_ip_address,
|
||||
dst_port=self.port,
|
||||
ip_protocol=self.protocol,
|
||||
icmp_packet=icmp_packet
|
||||
)
|
||||
return sequence, icmp_packet.identifier
|
||||
|
||||
def _process_icmp_echo_request(self, frame: Frame):
|
||||
"""
|
||||
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}")
|
||||
|
||||
nic = self.software_manager.session_manager.resolve_outbound_nic(frame.ip.src_ip_address)
|
||||
|
||||
if not nic:
|
||||
self.sys_log.error(
|
||||
"Cannot send ICMP echo reply as there is no outbound NIC to use. Try configuring the default gateway."
|
||||
)
|
||||
payload = secrets.token_urlsafe(int(32 / 1.3)) # Standard ICMP 32 bytes size
|
||||
frame = Frame(ethernet=ethernet_header, ip=ip_packet, icmp=icmp_reply_packet, payload=payload)
|
||||
self.sys_log.info(f"Sending echo reply to {frame.ip.dst_ip_address}")
|
||||
return
|
||||
|
||||
src_nic.send_frame(frame)
|
||||
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}")
|
||||
|
||||
def _process_icmp_echo_reply(self, frame: Frame, from_nic: NIC, is_reattempt: bool = False):
|
||||
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 _process_icmp_echo_reply(self, frame: Frame):
|
||||
"""
|
||||
Processes an ICMP echo reply received by the service, logging the reply details.
|
||||
|
||||
:param frame: The network frame containing the ICMP echo reply.
|
||||
"""
|
||||
time = frame.transmission_duration()
|
||||
time_str = f"{time}ms" if time > 0 else "<1ms"
|
||||
self.sys_log.info(
|
||||
@@ -146,14 +163,21 @@ class ICMP(Service):
|
||||
self.request_replies[frame.icmp.identifier] += 1
|
||||
|
||||
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||
"""
|
||||
Processes received data, handling ICMP echo requests and replies.
|
||||
|
||||
:param payload: The payload received.
|
||||
: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.
|
||||
"""
|
||||
frame: Frame = kwargs["frame"]
|
||||
from_nic = kwargs["from_nic"]
|
||||
|
||||
if not frame.icmp:
|
||||
return False
|
||||
|
||||
if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST:
|
||||
self._process_icmp_echo_request(frame, from_nic)
|
||||
self._process_icmp_echo_request(frame)
|
||||
elif frame.icmp.icmp_type == ICMPType.ECHO_REPLY:
|
||||
self._process_icmp_echo_reply(frame, from_nic)
|
||||
self._process_icmp_echo_reply(frame)
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user