#2205 - Added wireless router tests and documentation. Refactored some code based on PR suggestions.
This commit is contained in:
@@ -22,6 +22,7 @@ Contents
|
||||
simulation_components/network/nodes/host_node
|
||||
simulation_components/network/nodes/network_node
|
||||
simulation_components/network/nodes/router
|
||||
simulation_components/network/nodes/wireless_router
|
||||
simulation_components/network/nodes/firewall
|
||||
simulation_components/network/switch
|
||||
simulation_components/network/network
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
|
||||
|
||||
######
|
||||
Router
|
||||
######
|
||||
|
||||
The ``WirelessRouter`` class extends the functionality of the standard ``Router`` class within PrimAITE,
|
||||
integrating wireless networking capabilities. This class enables the simulation of a router that supports both wired
|
||||
and wireless connections, allowing for a more comprehensive network simulation environment.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``WirelessRouter`` class is designed to simulate the operations of a real-world wireless router, offering both
|
||||
Ethernet and Wi-Fi connectivity. This includes managing wireless access points, configuring network interfaces for
|
||||
different frequencies, and handling wireless frames transmission.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- **Dual Interface Support:** Supports both wired (Ethernet) and wireless network interfaces.
|
||||
- **Wireless Access Point Configuration:** Allows configuring a wireless access point, including setting its IP
|
||||
address, subnet mask, and operating frequency.
|
||||
- **Frequency Management:** Utilises the ``AirSpaceFrequency`` enum to set the operating frequency of wireless
|
||||
interfaces, supporting common Wi-Fi bands like 2.4 GHz and 5 GHz.
|
||||
- **Seamless Wireless Communication:** Integrates with the ``AirSpace`` class to manage wireless transmissions across
|
||||
different frequencies, ensuring that wireless communication is realistically simulated.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To use the ``WirelessRouter`` class in a network simulation, instantiate it similarly to a regular router but with
|
||||
additional steps to configure wireless settings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from primaite.simulator.network.hardware.nodes.network.wireless_router import WirelessRouter
|
||||
from primaite.simulator.network.airspace import AirSpaceFrequency
|
||||
|
||||
# Instantiate the WirelessRouter
|
||||
wireless_router = WirelessRouter(hostname="MyWirelessRouter")
|
||||
|
||||
# Configure a wired Ethernet interface
|
||||
wireless_router.configure_port(port=2, ip_address="192.168.1.1", subnet_mask="255.255.255.0")
|
||||
|
||||
# Configure a wireless access point
|
||||
wireless_router.configure_wireless_access_point(
|
||||
port=1, ip_address="192.168.2.1",
|
||||
subnet_mask="255.255.255.0",
|
||||
frequency=AirSpaceFrequency.WIFI_2_4
|
||||
)
|
||||
|
||||
|
||||
|
||||
Integration with AirSpace
|
||||
-------------------------
|
||||
|
||||
The ``WirelessRouter`` class works closely with the ``AirSpace`` class to simulate the transmission of wireless frames.
|
||||
Frames sent from wireless interfaces are transmitted across the simulated airspace, allowing for interactions with
|
||||
other wireless devices within the same frequency band.
|
||||
|
||||
Example Scenario
|
||||
----------------
|
||||
|
||||
This example sets up a network with two PCs (PC A and PC B), each connected to their own `WirelessRouter`
|
||||
(Router 1 and Router 2). These routers are then wirelessly connected to each other, enabling communication between the
|
||||
PCs through the routers over the airspace. Access Control Lists (ACLs) are configured on the routers to permit ARP and
|
||||
ICMP traffic, ensuring basic network connectivity and ping functionality.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from primaite.simulator.network.airspace import AIR_SPACE, AirSpaceFrequency
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
|
||||
from primaite.simulator.network.hardware.nodes.network.wireless_router import WirelessRouter
|
||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
|
||||
network = Network()
|
||||
|
||||
# Configure PC A
|
||||
pc_a = Computer(
|
||||
hostname="pc_a",
|
||||
ip_address="192.168.0.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.0.1",
|
||||
start_up_duration=0,
|
||||
)
|
||||
pc_a.power_on()
|
||||
network.add_node(pc_a)
|
||||
|
||||
# Configure Router 1
|
||||
router_1 = WirelessRouter(hostname="router_1", start_up_duration=0)
|
||||
router_1.power_on()
|
||||
network.add_node(router_1)
|
||||
|
||||
# Configure the connection between PC A and Router 1 port 2
|
||||
router_1.configure_router_interface("192.168.0.1", "255.255.255.0")
|
||||
network.connect(pc_a.network_interface[1], router_1.router_interface)
|
||||
|
||||
# Configure Router 1 ACLs
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.ARP, dst_port=Port.ARP, position=22)
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, protocol=IPProtocol.ICMP, position=23)
|
||||
|
||||
# Configure PC B
|
||||
pc_b = Computer(
|
||||
hostname="pc_b",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.2.1",
|
||||
start_up_duration=0,
|
||||
)
|
||||
pc_b.power_on()
|
||||
network.add_node(pc_b)
|
||||
|
||||
# Configure Router 2
|
||||
router_2 = WirelessRouter(hostname="router_2", start_up_duration=0)
|
||||
router_2.power_on()
|
||||
network.add_node(router_2)
|
||||
|
||||
# Configure the connection between PC B and Router 2 port 2
|
||||
router_2.configure_router_interface("192.168.2.1", "255.255.255.0")
|
||||
network.connect(pc_b.network_interface[1], router_2.router_interface)
|
||||
|
||||
# Configure the wireless connection between Router 1 and Router 2
|
||||
router_1.configure_wireless_access_point(
|
||||
port=1,
|
||||
ip_address="192.168.1.1",
|
||||
subnet_mask="255.255.255.0",
|
||||
frequency=AirSpaceFrequency.WIFI_2_4
|
||||
)
|
||||
router_2.configure_wireless_access_point(
|
||||
port=1,
|
||||
ip_address="192.168.1.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
frequency=AirSpaceFrequency.WIFI_2_4
|
||||
)
|
||||
|
||||
# Configure routes for inter-router communication
|
||||
router_1.route_table.add_route(
|
||||
address="192.168.2.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.2"
|
||||
)
|
||||
|
||||
router_2.route_table.add_route(
|
||||
address="192.168.0.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.1"
|
||||
)
|
||||
|
||||
# Test connectivity
|
||||
print(pc_a.ping(pc_b.network_interface[1].ip_address))
|
||||
print(pc_b.ping(pc_a.network_interface[1].ip_address))
|
||||
|
||||
This setup demonstrates the `WirelessRouter` class's capability to manage both wired and wireless connections within a
|
||||
simulated network environment. By configuring the wireless access points and enabling the appropriate ACL rules, the
|
||||
example facilitates basic network operations such as ARP resolution and ICMP pinging between devices across different
|
||||
network segments.
|
||||
|
||||
Viewing Wireless Network Configuration
|
||||
--------------------------------------
|
||||
|
||||
The `AirSpace.show()` function is an invaluable tool for inspecting the current wireless network configuration within
|
||||
the PrimAITE environment. It presents a table summarising all wireless interfaces, including routers and access points,
|
||||
that are active within the airspace. The table outlines each device's connected node name, MAC address, IP address,
|
||||
subnet mask, operating frequency, and status, providing a comprehensive view of the wireless network topology.
|
||||
|
||||
Example Output
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Below is an example output of the `AirSpace.show()` function, demonstrating the visibility it provides into the
|
||||
wireless network:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
+----------------+-------------------+-------------+---------------+--------------+---------+
|
||||
| Connected Node | MAC Address | IP Address | Subnet Mask | Frequency | Status |
|
||||
+----------------+-------------------+-------------+---------------+--------------+---------+
|
||||
| router_1 | 31:29:46:53:ed:f8 | 192.168.1.1 | 255.255.255.0 | WiFi 2.4 GHz | Enabled |
|
||||
| router_2 | 34:c8:47:43:98:78 | 192.168.1.2 | 255.255.255.0 | WiFi 2.4 GHz | Enabled |
|
||||
+----------------+-------------------+-------------+---------------+--------------+---------+
|
||||
|
||||
This table aids in verifying that wireless devices are correctly configured and operational. It also helps in
|
||||
diagnosing connectivity issues by ensuring that devices are on the correct frequency and have the appropriate network
|
||||
settings. The `Status` column, indicating whether a device is enabled or disabled, further assists in troubleshooting
|
||||
by quickly identifying any devices that are not actively participating in the network.
|
||||
|
||||
Utilising the `AirSpace.show()` function is particularly beneficial in complex network simulations where multiple
|
||||
wireless devices are in use. It provides a snapshot of the wireless landscape, facilitating the understanding of how
|
||||
devices interact within the network and ensuring that configurations are aligned with the intended network architecture.
|
||||
|
||||
The addition of the ``WirelessRouter`` class enriches the PrimAITE simulation toolkit by enabling the simulation of
|
||||
mixed wired and wireless network environments.
|
||||
@@ -122,6 +122,8 @@ class Firewall(Router):
|
||||
"internal_outbound_acl",
|
||||
"dmz_inbound_acl",
|
||||
"dmz_outbound_acl",
|
||||
"external_inbound_acl",
|
||||
"external_outbound_acl",
|
||||
}
|
||||
self._original_state.update(self.model_dump(include=vals_to_include))
|
||||
|
||||
@@ -142,6 +144,8 @@ class Firewall(Router):
|
||||
"internal_outbound_acl": self.internal_outbound_acl.describe_state(),
|
||||
"dmz_inbound_acl": self.dmz_inbound_acl.describe_state(),
|
||||
"dmz_outbound_acl": self.dmz_outbound_acl.describe_state(),
|
||||
"external_inbound_acl": self.external_inbound_acl.describe_state(),
|
||||
"external_outbound_acl": self.external_outbound_acl.describe_state(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -263,12 +267,11 @@ class Firewall(Router):
|
||||
if not permitted:
|
||||
self.sys_log.info(f"Frame blocked at interface {from_network_interface} by rule {rule}")
|
||||
return
|
||||
else:
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
if self.check_send_frame_to_session_manager(frame):
|
||||
# Port is open on this Router so pass Frame up to session manager first
|
||||
@@ -332,12 +335,11 @@ class Firewall(Router):
|
||||
if not permitted:
|
||||
self.sys_log.info(f"Frame blocked at interface {from_network_interface} by rule {rule}")
|
||||
return
|
||||
else:
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
if self.check_send_frame_to_session_manager(frame):
|
||||
# Port is open on this Router so pass Frame up to session manager first
|
||||
@@ -387,12 +389,11 @@ class Firewall(Router):
|
||||
if not permitted:
|
||||
self.sys_log.info(f"Frame blocked at interface {from_network_interface} by rule {rule}")
|
||||
return
|
||||
else:
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
if self.check_send_frame_to_session_manager(frame):
|
||||
# Port is open on this Router so pass Frame up to session manager first
|
||||
|
||||
@@ -35,9 +35,9 @@ def ip_matches_masked_range(ip_to_check: IPV4Address, base_ip: IPV4Address, wild
|
||||
by the wildcard mask. If the resulting masked IP addresses are equal, it means the IP address to check falls within
|
||||
the range defined by the base IP and wildcard mask.
|
||||
|
||||
:param IPv4Address ip_to_check: The IP address to be checked.
|
||||
:param IPv4Address base_ip: The base IP address defining the start of the range.
|
||||
:param IPv4Address wildcard_mask: The wildcard mask specifying which bits to ignore.
|
||||
:param IPV4Address ip_to_check: The IP address to be checked.
|
||||
:param IPV4Address base_ip: The base IP address defining the start of the range.
|
||||
:param IPV4Address wildcard_mask: The wildcard mask specifying which bits to ignore.
|
||||
:return: A boolean value indicating whether the IP address matches the masked range.
|
||||
:rtype: bool
|
||||
|
||||
|
||||
134
tests/integration_tests/network/test_wireless_router.py
Normal file
134
tests/integration_tests/network/test_wireless_router.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import pytest
|
||||
|
||||
from primaite.simulator.network.airspace import AIR_SPACE, AirSpaceFrequency
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
|
||||
from primaite.simulator.network.hardware.nodes.network.wireless_router import WirelessRouter
|
||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def setup_network():
|
||||
network = Network()
|
||||
|
||||
# Configure PC A
|
||||
pc_a = Computer(
|
||||
hostname="pc_a",
|
||||
ip_address="192.168.0.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.0.1",
|
||||
start_up_duration=0,
|
||||
)
|
||||
pc_a.power_on()
|
||||
network.add_node(pc_a)
|
||||
|
||||
# Configure Router 1
|
||||
router_1 = WirelessRouter(hostname="router_1", start_up_duration=0)
|
||||
router_1.power_on()
|
||||
network.add_node(router_1)
|
||||
|
||||
# Configure the connection between PC A and Router 1 port 2
|
||||
router_1.configure_router_interface("192.168.0.1", "255.255.255.0")
|
||||
network.connect(pc_a.network_interface[1], router_1.network_interface[2])
|
||||
|
||||
# Configure Router 1 ACLs
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.ARP, dst_port=Port.ARP, position=22)
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, protocol=IPProtocol.ICMP, position=23)
|
||||
|
||||
# Configure PC B
|
||||
pc_b = Computer(
|
||||
hostname="pc_b",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.2.1",
|
||||
start_up_duration=0,
|
||||
)
|
||||
pc_b.power_on()
|
||||
network.add_node(pc_b)
|
||||
|
||||
# Configure Router 2
|
||||
router_2 = WirelessRouter(hostname="router_2", start_up_duration=0)
|
||||
router_2.power_on()
|
||||
network.add_node(router_2)
|
||||
|
||||
# Configure the connection between PC B and Router 2 port 2
|
||||
router_2.configure_router_interface("192.168.2.1", "255.255.255.0")
|
||||
network.connect(pc_b.network_interface[1], router_2.network_interface[2])
|
||||
|
||||
# Configure Router 2 ACLs
|
||||
|
||||
# Configure the wireless connection between Router 1 port 1 and Router 2 port 1
|
||||
router_1.configure_wireless_access_point("192.168.1.1", "255.255.255.0")
|
||||
router_2.configure_wireless_access_point("192.168.1.2", "255.255.255.0")
|
||||
|
||||
AIR_SPACE.show()
|
||||
|
||||
router_1.route_table.add_route(
|
||||
address="192.168.2.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.2"
|
||||
)
|
||||
|
||||
# Configure Route from Router 2 to PC A subnet
|
||||
router_2.route_table.add_route(
|
||||
address="192.168.0.2", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.1"
|
||||
)
|
||||
|
||||
# Configure PC C
|
||||
pc_c = Computer(
|
||||
hostname="pc_c",
|
||||
ip_address="192.168.3.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.3.1",
|
||||
start_up_duration=0,
|
||||
)
|
||||
pc_c.power_on()
|
||||
network.add_node(pc_c)
|
||||
|
||||
# Configure Router 3
|
||||
router_3 = WirelessRouter(hostname="router_3", start_up_duration=0)
|
||||
router_3.power_on()
|
||||
network.add_node(router_3)
|
||||
|
||||
# Configure the connection between PC C and Router 3 port 2
|
||||
router_3.configure_router_interface("192.168.3.1", "255.255.255.0")
|
||||
network.connect(pc_c.network_interface[1], router_3.network_interface[2])
|
||||
|
||||
# Configure the wireless connection between Router 2 port 1 and Router 3 port 1
|
||||
router_3.configure_wireless_access_point("192.168.1.3", "255.255.255.0")
|
||||
|
||||
# Configure Route from Router 1 to PC C subnet
|
||||
router_1.route_table.add_route(
|
||||
address="192.168.3.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.3"
|
||||
)
|
||||
|
||||
# Configure Route from Router 2 to PC C subnet
|
||||
router_2.route_table.add_route(
|
||||
address="192.168.3.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.3"
|
||||
)
|
||||
|
||||
# Configure Route from Router 3 to PC A and PC B subnets
|
||||
router_3.route_table.add_route(
|
||||
address="192.168.0.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.1"
|
||||
)
|
||||
router_3.route_table.add_route(
|
||||
address="192.168.2.0", subnet_mask="255.255.255.0", next_hop_ip_address="192.168.1.2"
|
||||
)
|
||||
|
||||
return pc_a, pc_b, pc_c, router_1, router_2, router_3
|
||||
|
||||
|
||||
def test_ping_default_gateways(setup_network):
|
||||
pc_a, pc_b, pc_c, router_1, router_2, router_3 = setup_network
|
||||
# Check if each PC can ping its default gateway
|
||||
assert pc_a.ping(pc_a.default_gateway), "PC A should ping its default gateway successfully."
|
||||
assert pc_b.ping(pc_b.default_gateway), "PC B should ping its default gateway successfully."
|
||||
assert pc_c.ping(pc_c.default_gateway), "PC C should ping its default gateway successfully."
|
||||
|
||||
|
||||
def test_cross_router_connectivity_pre_frequency_change(setup_network):
|
||||
pc_a, pc_b, pc_c, router_1, router_2, router_3 = setup_network
|
||||
# Ensure that PCs can ping across routers before any frequency change
|
||||
assert pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should ping PC B across routers successfully."
|
||||
assert pc_a.ping(pc_c.network_interface[1].ip_address), "PC A should ping PC C across routers successfully."
|
||||
assert pc_b.ping(pc_c.network_interface[1].ip_address), "PC B should ping PC C across routers successfully."
|
||||
Reference in New Issue
Block a user