Merge remote-tracking branch 'origin/dev' into 4.0.0-dev

This commit is contained in:
Marek Wolan
2025-02-10 14:39:28 +00:00
parent 0d1edf0362
commit 96549e68aa
71 changed files with 2700 additions and 367 deletions

View File

@@ -8,7 +8,7 @@ from primaite.config.load import data_manipulation_config_path
from primaite.game.game import PrimaiteGame
from tests import TEST_ASSETS_ROOT
BASIC_CONFIG = TEST_ASSETS_ROOT / "configs/basic_switched_network.yaml"
BASIC_SWITCHED_NETWORK_CONFIG = TEST_ASSETS_ROOT / "configs/basic_switched_network.yaml"
def load_config(config_path: Union[str, Path]) -> PrimaiteGame:
@@ -24,3 +24,42 @@ def test_thresholds():
game = load_config(data_manipulation_config_path())
assert game.options.thresholds is not None
def test_nmne_threshold():
"""Test that the NMNE thresholds are properly loaded in by observation."""
game = load_config(BASIC_SWITCHED_NETWORK_CONFIG)
assert game.options.thresholds["nmne"] is not None
# get NIC observation
nic_obs = game.agents["defender"].observation_manager.obs.components["NODES"].hosts[0].nics[0]
assert nic_obs.low_nmne_threshold == 5
assert nic_obs.med_nmne_threshold == 25
assert nic_obs.high_nmne_threshold == 100
def test_file_access_threshold():
"""Test that the NMNE thresholds are properly loaded in by observation."""
game = load_config(BASIC_SWITCHED_NETWORK_CONFIG)
assert game.options.thresholds["file_access"] is not None
# get file observation
file_obs = game.agents["defender"].observation_manager.obs.components["NODES"].hosts[0].folders[0].files[0]
assert file_obs.low_file_access_threshold == 2
assert file_obs.med_file_access_threshold == 5
assert file_obs.high_file_access_threshold == 10
def test_app_executions_threshold():
"""Test that the NMNE thresholds are properly loaded in by observation."""
game = load_config(BASIC_SWITCHED_NETWORK_CONFIG)
assert game.options.thresholds["app_executions"] is not None
# get application observation
app_obs = game.agents["defender"].observation_manager.obs.components["NODES"].hosts[0].applications[0]
assert app_obs.low_app_execution_threshold == 2
assert app_obs.med_app_execution_threshold == 3
assert app_obs.high_app_execution_threshold == 5

View File

@@ -0,0 +1,64 @@
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
from pathlib import Path
from typing import Union
import yaml
from primaite.game.game import PrimaiteGame
from primaite.simulator.file_system.file_type import FileType
from tests import TEST_ASSETS_ROOT
BASIC_CONFIG = TEST_ASSETS_ROOT / "configs/nodes_with_initial_files.yaml"
def load_config(config_path: Union[str, Path]) -> PrimaiteGame:
"""Returns a PrimaiteGame object which loads the contents of a given yaml path."""
with open(config_path, "r") as f:
cfg = yaml.safe_load(f)
return PrimaiteGame.from_config(cfg)
def test_node_file_system_from_config():
"""Test that the appropriate files are instantiated in nodes when loaded from config."""
game = load_config(BASIC_CONFIG)
client_1 = game.simulation.network.get_node_by_hostname("client_1")
assert client_1.software_manager.software.get("database-service") # database service should be installed
assert client_1.file_system.get_file(folder_name="database", file_name="database.db") # database files should exist
assert client_1.software_manager.software.get("web-server") # web server should be installed
assert client_1.file_system.get_file(folder_name="primaite", file_name="index.html") # web files should exist
client_2 = game.simulation.network.get_node_by_hostname("client_2")
# database service should not be installed
assert client_2.software_manager.software.get("database-service") is None
# database files should not exist
assert client_2.file_system.get_file(folder_name="database", file_name="database.db") is None
# web server should not be installed
assert client_2.software_manager.software.get("web-server") is None
# web files should not exist
assert client_2.file_system.get_file(folder_name="primaite", file_name="index.html") is None
empty_folder = client_2.file_system.get_folder(folder_name="empty_folder")
assert empty_folder
assert len(empty_folder.files) == 0 # should have no files
password_file = client_2.file_system.get_file(folder_name="root", file_name="passwords.txt")
assert password_file # should exist
assert password_file.file_type is FileType.TXT
assert password_file.size == 663
downloads_folder = client_2.file_system.get_folder(folder_name="downloads")
assert downloads_folder # downloads folder should exist
test_txt = downloads_folder.get_file(file_name="test.txt")
assert test_txt # test.txt should exist
assert test_txt.file_type is FileType.TXT
unknown_file_type = downloads_folder.get_file(file_name="another_file.pwtwoti")
assert unknown_file_type # unknown_file_type should exist
assert unknown_file_type.file_type is FileType.UNKNOWN

View File

@@ -49,7 +49,7 @@ class GigaSwitch(NetworkNode, discriminator="gigaswitch"):
if markdown:
table.set_style(MARKDOWN)
table.align = "l"
table.title = f"{self.hostname} Switch Ports"
table.title = f"{self.config.hostname} Switch Ports"
for port_num, port in self.network_interface.items():
table.add_row([port_num, port.mac_address, port.speed, "Enabled" if port.enabled else "Disabled"])
print(table)

View File

@@ -106,7 +106,6 @@ def test_remote_login_change_password(game_and_agent_fixture: Tuple[PrimaiteGame
"username": "user123",
"current_password": "password",
"new_password": "different_password",
"remote_ip": str(server_1.network_interface[1].ip_address),
},
)
agent.store_action(action)
@@ -146,7 +145,6 @@ def test_change_password_logs_out_user(game_and_agent_fixture: Tuple[PrimaiteGam
"username": "user123",
"current_password": "password",
"new_password": "different_password",
"remote_ip": str(server_1.network_interface[1].ip_address),
},
)
agent.store_action(action)
@@ -166,3 +164,55 @@ def test_change_password_logs_out_user(game_and_agent_fixture: Tuple[PrimaiteGam
assert server_1.file_system.get_folder("folder123") is None
assert server_1.file_system.get_file("folder123", "doggo.pdf") is None
def test_local_terminal(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
game, agent = game_and_agent_fixture
client_1 = game.simulation.network.get_node_by_hostname("client_1")
# create a new user account on server_1 that will be logged into remotely
client_1_usm: UserManager = client_1.software_manager.software["user-manager"]
client_1_usm.add_user("user123", "password", is_admin=True)
action = (
"node-send-local-command",
{
"node_name": "client_1",
"username": "user123",
"password": "password",
"command": ["file_system", "create", "file", "folder123", "doggo.pdf", False],
},
)
agent.store_action(action)
game.step()
assert client_1.file_system.get_folder("folder123")
assert client_1.file_system.get_file("folder123", "doggo.pdf")
# Change password
action = (
"node-account-change-password",
{
"node_name": "client_1",
"username": "user123",
"current_password": "password",
"new_password": "different_password",
},
)
agent.store_action(action)
game.step()
action = (
"node-send-local-command",
{
"node_name": "client_1",
"username": "user123",
"password": "password",
"command": ["file_system", "create", "file", "folder123", "cat.pdf", False],
},
)
agent.store_action(action)
game.step()
assert client_1.file_system.get_file("folder123", "cat.pdf") is None
client_1.session_manager.show()

View File

@@ -0,0 +1,176 @@
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
import pytest
from primaite.simulator.network.hardware.nodes.host.computer import Computer
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
from primaite.utils.validation.port import Port, PORT_LOOKUP
@pytest.fixture
def game_and_agent_fixture(game_and_agent):
"""Create a game with a simple agent that can be controlled by the tests."""
game, agent = game_and_agent
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
client_1.start_up_duration = 3
return (game, agent)
def test_user_account_add_user_action(game_and_agent_fixture):
"""Tests the add user account action."""
game, agent = game_and_agent_fixture
client_1 = game.simulation.network.get_node_by_hostname("client_1")
assert len(client_1.user_manager.users) == 1 # admin is created by default
assert len(client_1.user_manager.admins) == 1
# add admin account
action = (
"node-account-add-user",
{"node_name": "client_1", "username": "admin_2", "password": "e-tronic-boogaloo", "is_admin": True},
)
agent.store_action(action)
game.step()
assert len(client_1.user_manager.users) == 2 # new user added
assert len(client_1.user_manager.admins) == 2
# add non admin account
action = (
"node-account-add-user",
{"node_name": "client_1", "username": "leeroy.jenkins", "password": "no_plan_needed", "is_admin": False},
)
agent.store_action(action)
game.step()
assert len(client_1.user_manager.users) == 3 # new user added
assert len(client_1.user_manager.admins) == 2
def test_user_account_disable_user_action(game_and_agent_fixture):
"""Tests the disable user account action."""
game, agent = game_and_agent_fixture
client_1 = game.simulation.network.get_node_by_hostname("client_1")
client_1.user_manager.add_user(username="test", password="password", is_admin=True)
assert len(client_1.user_manager.users) == 2 # new user added
assert len(client_1.user_manager.admins) == 2
test_user = client_1.user_manager.users.get("test")
assert test_user
assert test_user.disabled is not True
# disable test account
action = (
"node-account-disable-user",
{
"node_name": "client_1",
"username": "test",
},
)
agent.store_action(action)
game.step()
assert test_user.disabled
def test_user_account_change_password_action(game_and_agent_fixture):
"""Tests the change password user account action."""
game, agent = game_and_agent_fixture
client_1 = game.simulation.network.get_node_by_hostname("client_1")
client_1.user_manager.add_user(username="test", password="password", is_admin=True)
test_user = client_1.user_manager.users.get("test")
assert test_user.password == "password"
# change account password
action = (
"node-account-change-password",
{"node_name": "client_1", "username": "test", "current_password": "password", "new_password": "2Hard_2_Hack"},
)
agent.store_action(action)
game.step()
assert test_user.password == "2Hard_2_Hack"
def test_user_account_create_terminal_action(game_and_agent_fixture):
"""Tests that agents can use the terminal to create new users."""
game, agent = game_and_agent_fixture
router = game.simulation.network.get_node_by_hostname("router")
router.acl.add_rule(action=ACLAction.PERMIT, src_port=PORT_LOOKUP["SSH"], dst_port=PORT_LOOKUP["SSH"], position=4)
server_1 = game.simulation.network.get_node_by_hostname("server_1")
server_1_usm = server_1.software_manager.software["user-manager"]
server_1_usm.add_user("user123", "password", is_admin=True)
action = (
"node-session-remote-login",
{
"node_name": "client_1",
"username": "user123",
"password": "password",
"remote_ip": str(server_1.network_interface[1].ip_address),
},
)
agent.store_action(action)
game.step()
assert agent.history[-1].response.status == "success"
# Create a new user account via terminal.
action = (
"node-send-remote-command",
{
"node_name": "client_1",
"remote_ip": str(server_1.network_interface[1].ip_address),
"command": ["service", "user-manager", "add_user", "new_user", "new_pass", True],
},
)
agent.store_action(action)
game.step()
new_user = server_1.user_manager.users.get("new_user")
assert new_user
assert new_user.password == "new_pass"
assert new_user.disabled is not True
def test_user_account_disable_terminal_action(game_and_agent_fixture):
"""Tests that agents can use the terminal to disable users."""
game, agent = game_and_agent_fixture
router = game.simulation.network.get_node_by_hostname("router")
router.acl.add_rule(action=ACLAction.PERMIT, src_port=PORT_LOOKUP["SSH"], dst_port=PORT_LOOKUP["SSH"], position=4)
server_1 = game.simulation.network.get_node_by_hostname("server_1")
server_1_usm = server_1.software_manager.software["user-manager"]
server_1_usm.add_user("user123", "password", is_admin=True)
action = (
"node-session-remote-login",
{
"node_name": "client_1",
"username": "user123",
"password": "password",
"remote_ip": str(server_1.network_interface[1].ip_address),
},
)
agent.store_action(action)
game.step()
assert agent.history[-1].response.status == "success"
# Disable a user via terminal
action = (
"node-send-remote-command",
{
"node_name": "client_1",
"remote_ip": str(server_1.network_interface[1].ip_address),
"command": ["service", "user-manager", "disable_user", "user123"],
},
)
agent.store_action(action)
game.step()
new_user = server_1.user_manager.users.get("user123")
assert new_user
assert new_user.disabled is True

View File

@@ -44,6 +44,38 @@ def test_file_observation(simulation):
assert observation_state.get("health_status") == 3 # corrupted
def test_config_file_access_categories(simulation):
pc: Computer = simulation.network.get_node_by_hostname("client_1")
file_obs = FileObservation(
where=["network", "nodes", pc.config.hostname, "file_system", "folders", "root", "files", "dog.png"],
include_num_access=False,
file_system_requires_scan=True,
thresholds={"file_access": {"low": 3, "medium": 6, "high": 9}},
)
assert file_obs.high_file_access_threshold == 9
assert file_obs.med_file_access_threshold == 6
assert file_obs.low_file_access_threshold == 3
with pytest.raises(Exception):
# should throw an error
FileObservation(
where=["network", "nodes", pc.config.hostname, "file_system", "folders", "root", "files", "dog.png"],
include_num_access=False,
file_system_requires_scan=True,
thresholds={"file_access": {"low": 9, "medium": 6, "high": 9}},
)
with pytest.raises(Exception):
# should throw an error
FileObservation(
where=["network", "nodes", pc.config.hostname, "file_system", "folders", "root", "files", "dog.png"],
include_num_access=False,
file_system_requires_scan=True,
thresholds={"file_access": {"low": 3, "medium": 9, "high": 9}},
)
def test_folder_observation(simulation):
"""Test the folder observation."""
pc: Computer = simulation.network.get_node_by_hostname("client_1")

View File

@@ -77,6 +77,14 @@ def test_nic(simulation):
nic_obs = NICObservation(where=["network", "nodes", pc.config.hostname, "NICs", 1], include_nmne=True)
# The Simulation object created by the fixture also creates the
# NICObservation class with the NICObservation.capture_nmnme class variable
# set to False. Under normal (non-test) circumstances this class variable
# is set from a config file such as data_manipulation.yaml. So although
# capture_nmne is set to True in the NetworkInterface class it's still False
# in the NICObservation class so we set it now.
nic_obs.capture_nmne = True
# Set the NMNE configuration to capture DELETE/ENCRYPT queries as MNEs
nmne_config = {
"capture_nmne": True, # Enable the capture of MNEs
@@ -115,14 +123,11 @@ def test_nic_categories(simulation):
assert nic_obs.low_nmne_threshold == 0 # default
@pytest.mark.skip(reason="Feature not implemented yet")
def test_config_nic_categories(simulation):
pc: Computer = simulation.network.get_node_by_hostname("client_1")
nic_obs = NICObservation(
where=["network", "nodes", pc.hostname, "NICs", 1],
low_nmne_threshold=3,
med_nmne_threshold=6,
high_nmne_threshold=9,
where=["network", "nodes", pc.config.hostname, "NICs", 1],
thresholds={"nmne": {"low": 3, "medium": 6, "high": 9}},
include_nmne=True,
)
@@ -133,20 +138,16 @@ def test_config_nic_categories(simulation):
with pytest.raises(Exception):
# should throw an error
NICObservation(
where=["network", "nodes", pc.hostname, "NICs", 1],
low_nmne_threshold=9,
med_nmne_threshold=6,
high_nmne_threshold=9,
where=["network", "nodes", pc.config.hostname, "NICs", 1],
thresholds={"nmne": {"low": 9, "medium": 6, "high": 9}},
include_nmne=True,
)
with pytest.raises(Exception):
# should throw an error
NICObservation(
where=["network", "nodes", pc.hostname, "NICs", 1],
low_nmne_threshold=3,
med_nmne_threshold=9,
high_nmne_threshold=9,
where=["network", "nodes", pc.config.hostname, "NICs", 1],
thresholds={"nmne": {"low": 3, "medium": 9, "high": 9}},
include_nmne=True,
)

View File

@@ -39,6 +39,8 @@ def test_host_observation(simulation):
folders=[],
network_interfaces=[],
file_system_requires_scan=True,
services_requires_scan=True,
applications_requires_scan=True,
include_users=False,
)

View File

@@ -0,0 +1,28 @@
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
import json
from primaite.session.environment import PrimaiteGymEnv
from primaite.session.io import PrimaiteIO
from tests import TEST_ASSETS_ROOT
DATA_MANIPULATION_CONFIG = TEST_ASSETS_ROOT / "configs" / "data_manipulation.yaml"
def test_obs_data_in_log_file():
"""Create a log file of AgentHistoryItems and check observation data is
included. Assumes that data_manipulation.yaml has an agent labelled
'defender' with a non-null observation space.
The log file will be in:
primaite/VERSION/sessions/YYYY-MM-DD/HH-MM-SS/agent_actions
"""
env = PrimaiteGymEnv(DATA_MANIPULATION_CONFIG)
env.reset()
for _ in range(10):
env.step(0)
env.reset()
io = PrimaiteIO()
path = io.generate_agent_actions_save_path(episode=1)
with open(path, "r") as f:
j = json.load(f)
assert type(j["0"]["defender"]["observation"]) == dict

View File

@@ -29,7 +29,9 @@ def test_service_observation(simulation):
ntp_server = pc.software_manager.software.get("ntp-server")
assert ntp_server
service_obs = ServiceObservation(where=["network", "nodes", pc.config.hostname, "services", "ntp-server"])
service_obs = ServiceObservation(
where=["network", "nodes", pc.config.hostname, "services", "ntp-server"], services_requires_scan=True
)
assert service_obs.space["operating_status"] == spaces.Discrete(7)
assert service_obs.space["health_status"] == spaces.Discrete(5)
@@ -54,7 +56,9 @@ def test_application_observation(simulation):
web_browser: WebBrowser = pc.software_manager.software.get("web-browser")
assert web_browser
app_obs = ApplicationObservation(where=["network", "nodes", pc.config.hostname, "applications", "web-browser"])
app_obs = ApplicationObservation(
where=["network", "nodes", pc.config.hostname, "applications", "web-browser"], applications_requires_scan=True
)
web_browser.close()
observation_state = app_obs.observe(simulation.describe_state())
@@ -69,3 +73,33 @@ def test_application_observation(simulation):
assert observation_state.get("health_status") == 1
assert observation_state.get("operating_status") == 1 # running
assert observation_state.get("num_executions") == 1
def test_application_executions_categories(simulation):
pc: Computer = simulation.network.get_node_by_hostname("client_1")
app_obs = ApplicationObservation(
where=["network", "nodes", pc.config.hostname, "applications", "WebBrowser"],
applications_requires_scan=False,
thresholds={"app_executions": {"low": 3, "medium": 6, "high": 9}},
)
assert app_obs.high_app_execution_threshold == 9
assert app_obs.med_app_execution_threshold == 6
assert app_obs.low_app_execution_threshold == 3
with pytest.raises(Exception):
# should throw an error
ApplicationObservation(
where=["network", "nodes", pc.config.hostname, "applications", "WebBrowser"],
applications_requires_scan=False,
thresholds={"app_executions": {"low": 9, "medium": 6, "high": 9}},
)
with pytest.raises(Exception):
# should throw an error
ApplicationObservation(
where=["network", "nodes", pc.config.hostname, "applications", "WebBrowser"],
applications_requires_scan=False,
thresholds={"app_executions": {"low": 3, "medium": 9, "high": 9}},
)

View File

@@ -7,6 +7,7 @@ import yaml
from primaite.config.load import data_manipulation_config_path
from primaite.game.agent.interface import AgentHistoryItem
from primaite.session.environment import PrimaiteGymEnv
from primaite.simulator import SIM_OUTPUT
@pytest.fixture()
@@ -33,6 +34,11 @@ def test_rng_seed_set(create_env):
assert a == b
# Check that seed log file was created.
path = SIM_OUTPUT.path / "seed.log"
with open(path, "r") as file:
assert file
def test_rng_seed_unset(create_env):
"""Test with no RNG seed."""
@@ -48,3 +54,19 @@ def test_rng_seed_unset(create_env):
b = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "do-nothing"]
assert a != b
def test_for_generated_seed():
"""
Show that setting generate_seed_value to true producess a valid seed.
"""
with open(data_manipulation_config_path(), "r") as f:
cfg = yaml.safe_load(f)
cfg["game"]["generate_seed_value"] = True
PrimaiteGymEnv(env_config=cfg)
path = SIM_OUTPUT.path / "seed.log"
with open(path, "r") as file:
data = file.read()
assert data.split(" ")[3] != None

View File

@@ -22,6 +22,7 @@ from primaite.game.game import PrimaiteGame
from primaite.session.environment import PrimaiteGymEnv
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
from primaite.simulator.network.hardware.nodes.network.firewall import Firewall
from primaite.simulator.network.hardware.nodes.network.router import Router
from primaite.simulator.system.applications.application import ApplicationOperatingState
from primaite.simulator.system.applications.web_browser import WebBrowser
from primaite.simulator.system.software import SoftwareHealthState
@@ -107,7 +108,7 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox
"""
Test that the RouterACLAddRuleAction can form a request and that it is accepted by the simulation.
The acl starts off with 4 rules, and we add a rule, and check that the acl now has 5 rules.
The ACL starts off with 4 rules, and we add a rule, and check that the ACL now has 5 rules.
"""
game, agent = game_and_agent
@@ -164,11 +165,9 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox
},
)
agent.store_action(action)
print(agent.most_recent_action)
game.step()
print(agent.most_recent_action)
# 5: Check that the ACL now has 6 rules, but that server_1 can still ping server_2
print(router.acl.show())
assert router.acl.num_rules == 6
assert server_1.ping("10.0.2.3") # Can ping server_2
@@ -180,7 +179,8 @@ def test_router_acl_removerule_integration(game_and_agent: Tuple[PrimaiteGame, P
# 1: Check that http traffic is going across the network nicely.
client_1 = game.simulation.network.get_node_by_hostname("client_1")
server_1 = game.simulation.network.get_node_by_hostname("server_1")
router = game.simulation.network.get_node_by_hostname("router")
router: Router = game.simulation.network.get_node_by_hostname("router")
assert router.acl.num_rules == 4
browser: WebBrowser = client_1.software_manager.software.get("web-browser")
browser.run()

View File

@@ -1,5 +1,11 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
from itertools import product
import yaml
from primaite.config.load import data_manipulation_config_path
from primaite.game.agent.observations.nic_observations import NICObservation
from primaite.session.environment import PrimaiteGymEnv
from primaite.simulator.network.container import Network
from primaite.simulator.network.hardware.nodes.host.host_node import NIC
from primaite.simulator.network.hardware.nodes.host.server import Server
@@ -277,3 +283,19 @@ def test_capture_nmne_observations(uc2_network: Network):
assert web_nic_obs["outbound"] == expected_nmne
assert db_nic_obs["inbound"] == expected_nmne
uc2_network.apply_timestep(timestep=0)
def test_nmne_parameter_settings():
"""
Check that the four permutations of the values of capture_nmne and
include_nmne work as expected.
"""
with open(data_manipulation_config_path(), "r") as f:
cfg = yaml.safe_load(f)
DEFENDER = 3
for capture, include in product([True, False], [True, False]):
cfg["simulation"]["network"]["nmne_config"]["capture_nmne"] = capture
cfg["agents"][DEFENDER]["observation_space"]["options"]["components"][0]["options"]["include_nmne"] = include
PrimaiteGymEnv(env_config=cfg)

View File

@@ -1,6 +1,7 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
from primaite.simulator.network.hardware.nodes.network.router import RouterARP
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router, RouterARP
from primaite.simulator.system.services.arp.arp import ARP
from primaite.utils.validation.port import PORT_LOOKUP
from tests.integration_tests.network.test_routing import multi_hop_network
@@ -48,3 +49,19 @@ def test_arp_fails_for_network_address_between_routers(multi_hop_network):
actual_result = router_1_arp.get_arp_cache_mac_address(router_1.network_interface[1].ip_network.network_address)
assert actual_result == expected_result
def test_arp_not_affected_by_acl(multi_hop_network):
pc_a = multi_hop_network.get_node_by_hostname("pc_a")
router_1: Router = multi_hop_network.get_node_by_hostname("router_1")
# Add explicit rule to block ARP traffic. This shouldn't actually stop ARP traffic
# as it operates a different layer within the network.
router_1.acl.add_rule(action=ACLAction.DENY, src_port=PORT_LOOKUP["ARP"], dst_port=PORT_LOOKUP["ARP"], position=23)
pc_a_arp: ARP = pc_a.software_manager.arp
expected_result = router_1.network_interface[2].mac_address
actual_result = pc_a_arp.get_arp_cache_mac_address(router_1.network_interface[2].ip_address)
assert actual_result == expected_result