Merged PR 447: Bandwidth load / capacity checks before transmission
## Summary This pull request introduces significant enhancements to the AirSpace class within our network simulation software, aimed at improving the realism, configurability, and accuracy of wireless network simulations. These changes include the addition of new enums and attributes, enhancements to the configuration schema, and improvements in bandwidth management and transmission logic. **Additions** - **Enums and Attributes:** - **AirSpaceEnvironmentType Enum**: Defines various environmental settings that affect wireless signal propagation and interference. - **ChannelWidth Enum**: Specifies available channel width options for wireless interfaces. - **Channel Width Attribute**: Added to WirelessNetworkInterface for dynamic adjustments based on the operational environment. - **airspace_key Attribute**: A tuple identifying the frequency and channel width combination for bandwidth management. - **airspace_environment_type Attribute**: Sets the overall environmental context of the airspace, influencing all contained devices. - **Functional Enhancements:** - **SNR and Capacity Calculation Functions**: New functions estimate_snr and calculate_total_channel_capacity have been implemented to compute signal-to-noise ratios and channel capacities dynamically. - **show_bandwidth_load Function**: Provides a visual representation of the current bandwidth load across different channels. - **Dynamic Speed Setting**: The speed attribute of WirelessInterface is now adjusted dynamically based on frequency, channel width, and environment. - **Configuration and Testing:** - **Configuration Schema Update**: The simulation.network config file schema now supports setting the airspace_environment_type. **Changes** - **Interface and Performance Adjustments:** - **NetworkInterface Speed Type**: Changed from int to float for more precise speed definitions. - **Transmission Feasibility Check**: Updated the _can_transmit function in Link to better handle current load and bandwidth capacities. - **WirelessRouter Configurations**: The configure_wireless_access_point function now takes channel_width as an additional parameter. - **Grouping Adjustments**: WirelessNetworkInterfaces are now categorized by both AirSpaceFrequency and ChannelWidth. - **Transmission Logic Overhaul:** - **Interface Adjustments**: Modifying an interface's settings now necessitates its temporary removal from the airspace, followed by a recalculation of its data rate and reintegration under new settings. - **Blocking Overloads**: Strengthened the logic in AirSpace to prevent transmissions that would surpass the available capacity. **Fixes** - **Transmission Permission Logic**: Fixed the can_transmit_frame function to accurately enforce transmission limits based on current network load and available bandwidth. **Conclusion** These updates significantly enhance the fidelity and flexibility of our network simulation tool, enabling more accurate m...
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import yaml
|
||||
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.network.airspace import (
|
||||
AirspaceEnvironmentType,
|
||||
AirSpaceFrequency,
|
||||
calculate_total_channel_capacity,
|
||||
ChannelWidth,
|
||||
)
|
||||
from primaite.simulator.network.hardware.nodes.network.wireless_router import WirelessRouter
|
||||
from tests import TEST_ASSETS_ROOT
|
||||
|
||||
|
||||
def test_wireless_wan_wifi_5_80_channel_width_urban():
|
||||
config_path = TEST_ASSETS_ROOT / "configs" / "wireless_wan_wifi_5_80_channel_width_urban.yaml"
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config_dict = yaml.safe_load(f)
|
||||
network = PrimaiteGame.from_config(cfg=config_dict).simulation.network
|
||||
|
||||
airspace = network.airspace
|
||||
|
||||
assert airspace.airspace_environment_type == AirspaceEnvironmentType.URBAN
|
||||
|
||||
router_1: WirelessRouter = network.get_node_by_hostname("router_1")
|
||||
router_2: WirelessRouter = network.get_node_by_hostname("router_2")
|
||||
|
||||
expected_speed = calculate_total_channel_capacity(
|
||||
channel_width=ChannelWidth.WIDTH_80_MHZ,
|
||||
frequency=AirSpaceFrequency.WIFI_5,
|
||||
environment_type=AirspaceEnvironmentType.URBAN,
|
||||
)
|
||||
|
||||
assert router_1.wireless_access_point.speed == expected_speed
|
||||
assert router_2.wireless_access_point.speed == expected_speed
|
||||
|
||||
pc_a = network.get_node_by_hostname("pc_a")
|
||||
pc_b = network.get_node_by_hostname("pc_b")
|
||||
|
||||
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_a.ping(pc_b.network_interface[1].ip_address), "PC A should ping PC B across routers successfully."
|
||||
assert pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should ping PC A across routers successfully."
|
||||
|
||||
|
||||
def test_wireless_wan_wifi_5_80_channel_width_blocked():
|
||||
config_path = TEST_ASSETS_ROOT / "configs" / "wireless_wan_wifi_5_80_channel_width_blocked.yaml"
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config_dict = yaml.safe_load(f)
|
||||
network = PrimaiteGame.from_config(cfg=config_dict).simulation.network
|
||||
|
||||
airspace = network.airspace
|
||||
|
||||
assert airspace.airspace_environment_type == AirspaceEnvironmentType.BLOCKED
|
||||
|
||||
router_1: WirelessRouter = network.get_node_by_hostname("router_1")
|
||||
router_2: WirelessRouter = network.get_node_by_hostname("router_2")
|
||||
|
||||
expected_speed = calculate_total_channel_capacity(
|
||||
channel_width=ChannelWidth.WIDTH_80_MHZ,
|
||||
frequency=AirSpaceFrequency.WIFI_5,
|
||||
environment_type=AirspaceEnvironmentType.BLOCKED,
|
||||
)
|
||||
|
||||
assert router_1.wireless_access_point.speed == expected_speed
|
||||
assert router_2.wireless_access_point.speed == expected_speed
|
||||
|
||||
pc_a = network.get_node_by_hostname("pc_a")
|
||||
pc_b = network.get_node_by_hostname("pc_b")
|
||||
|
||||
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 not pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should ping PC B across routers unsuccessfully."
|
||||
assert not pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should ping PC A across routers unsuccessfully."
|
||||
|
||||
|
||||
def test_wireless_wan_blocking_and_unblocking_airspace():
|
||||
config_path = TEST_ASSETS_ROOT / "configs" / "wireless_wan_wifi_5_80_channel_width_urban.yaml"
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config_dict = yaml.safe_load(f)
|
||||
network = PrimaiteGame.from_config(cfg=config_dict).simulation.network
|
||||
|
||||
airspace = network.airspace
|
||||
|
||||
assert airspace.airspace_environment_type == AirspaceEnvironmentType.URBAN
|
||||
|
||||
pc_a = network.get_node_by_hostname("pc_a")
|
||||
pc_b = network.get_node_by_hostname("pc_b")
|
||||
|
||||
assert pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should ping PC B across routers successfully."
|
||||
assert pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should ping PC A across routers successfully."
|
||||
|
||||
airspace.airspace_environment_type = AirspaceEnvironmentType.BLOCKED
|
||||
|
||||
assert not pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should ping PC B across routers unsuccessfully."
|
||||
assert not pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should ping PC A across routers unsuccessfully."
|
||||
|
||||
airspace.airspace_environment_type = AirspaceEnvironmentType.URBAN
|
||||
|
||||
assert pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should ping PC B across routers successfully."
|
||||
assert pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should ping PC A across routers successfully."
|
||||
@@ -0,0 +1,138 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from primaite.simulator.file_system.file_type import FileType
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
from tests.integration_tests.network.test_wireless_router import wireless_wan_network
|
||||
from tests.integration_tests.system.test_ftp_client_server import ftp_client_and_ftp_server
|
||||
|
||||
|
||||
def test_wireless_link_loading(wireless_wan_network):
|
||||
client, server, router_1, router_2 = wireless_wan_network
|
||||
|
||||
# Configure Router 1 ACLs
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, position=1)
|
||||
|
||||
# Configure Router 2 ACLs
|
||||
router_2.acl.add_rule(action=ACLAction.PERMIT, position=1)
|
||||
|
||||
airspace = router_1.airspace
|
||||
|
||||
client.software_manager.install(FTPClient)
|
||||
ftp_client: FTPClient = client.software_manager.software.get("FTPClient")
|
||||
ftp_client.start()
|
||||
|
||||
server.software_manager.install(FTPServer)
|
||||
ftp_server: FTPServer = server.software_manager.software.get("FTPServer")
|
||||
ftp_server.start()
|
||||
|
||||
client.file_system.create_file(file_name="mixtape", size=10 * 10**6, file_type=FileType.MP3, folder_name="music")
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
# Reset the physical links between the host nodes and the routers
|
||||
client.network_interface[1]._connected_link.pre_timestep(1)
|
||||
server.network_interface[1]._connected_link.pre_timestep(1)
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape1.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
# Reset the physical links between the host nodes and the routers
|
||||
client.network_interface[1]._connected_link.pre_timestep(1)
|
||||
server.network_interface[1]._connected_link.pre_timestep(1)
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape2.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
# Reset the physical links between the host nodes and the routers
|
||||
client.network_interface[1]._connected_link.pre_timestep(1)
|
||||
server.network_interface[1]._connected_link.pre_timestep(1)
|
||||
|
||||
assert not ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape3.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
# Reset the physical links between the host nodes and the routers
|
||||
client.network_interface[1]._connected_link.pre_timestep(1)
|
||||
server.network_interface[1]._connected_link.pre_timestep(1)
|
||||
|
||||
airspace.reset_bandwidth_load()
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape3.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
|
||||
def test_wired_link_loading(ftp_client_and_ftp_server):
|
||||
ftp_client, computer, ftp_server, server = ftp_client_and_ftp_server
|
||||
|
||||
link = computer.network_interface[1]._connected_link # noqa
|
||||
|
||||
assert link.is_up
|
||||
|
||||
link.pre_timestep(1)
|
||||
|
||||
computer.file_system.create_file(
|
||||
file_name="mixtape", size=10 * 10**6, file_type=FileType.MP3, folder_name="music"
|
||||
)
|
||||
link_load = link.current_load
|
||||
assert link_load == 0.0
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
new_link_load = link.current_load
|
||||
assert new_link_load > link_load
|
||||
|
||||
assert not ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape1.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
link.pre_timestep(2)
|
||||
|
||||
link_load = link.current_load
|
||||
assert link_load == 0.0
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape1.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
new_link_load = link.current_load
|
||||
assert new_link_load > link_load
|
||||
Reference in New Issue
Block a user