Merge remote-tracking branch 'origin/dev' into feature/2319-database-admin
This commit is contained in:
@@ -634,8 +634,8 @@ simulation:
|
||||
nodes:
|
||||
|
||||
- ref: router_1
|
||||
type: router
|
||||
hostname: router_1
|
||||
type: router
|
||||
num_ports: 5
|
||||
ports:
|
||||
1:
|
||||
@@ -670,18 +670,18 @@ simulation:
|
||||
protocol: ICMP
|
||||
|
||||
- ref: switch_1
|
||||
type: switch
|
||||
hostname: switch_1
|
||||
type: switch
|
||||
num_ports: 8
|
||||
|
||||
- ref: switch_2
|
||||
type: switch
|
||||
hostname: switch_2
|
||||
type: switch
|
||||
num_ports: 8
|
||||
|
||||
- ref: domain_controller
|
||||
type: server
|
||||
hostname: domain_controller
|
||||
type: server
|
||||
ip_address: 192.168.1.10
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
@@ -693,8 +693,8 @@ simulation:
|
||||
arcd.com: 192.168.1.12 # web server
|
||||
|
||||
- ref: web_server
|
||||
type: server
|
||||
hostname: web_server
|
||||
type: server
|
||||
ip_address: 192.168.1.12
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
@@ -710,8 +710,8 @@ simulation:
|
||||
|
||||
|
||||
- ref: database_server
|
||||
type: server
|
||||
hostname: database_server
|
||||
type: server
|
||||
ip_address: 192.168.1.14
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
@@ -725,8 +725,8 @@ simulation:
|
||||
type: FTPClient
|
||||
|
||||
- ref: backup_server
|
||||
type: server
|
||||
hostname: backup_server
|
||||
type: server
|
||||
ip_address: 192.168.1.16
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
@@ -736,8 +736,8 @@ simulation:
|
||||
type: FTPServer
|
||||
|
||||
- ref: security_suite
|
||||
type: server
|
||||
hostname: security_suite
|
||||
type: server
|
||||
ip_address: 192.168.1.110
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
@@ -748,8 +748,8 @@ simulation:
|
||||
subnet_mask: 255.255.255.0
|
||||
|
||||
- ref: client_1
|
||||
type: computer
|
||||
hostname: client_1
|
||||
type: computer
|
||||
ip_address: 192.168.10.21
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.10.1
|
||||
@@ -775,8 +775,8 @@ simulation:
|
||||
type: DNSClient
|
||||
|
||||
- ref: client_2
|
||||
type: computer
|
||||
hostname: client_2
|
||||
type: computer
|
||||
ip_address: 192.168.10.22
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.10.1
|
||||
|
||||
@@ -572,7 +572,7 @@ class NetworkNICDisableAction(NetworkNICAbstractAction):
|
||||
class ActionManager:
|
||||
"""Class which manages the action space for an agent."""
|
||||
|
||||
_act_class_identifiers: Dict[str, type] = {
|
||||
act_class_identifiers: Dict[str, type] = {
|
||||
"DONOTHING": DoNothingAction,
|
||||
"NODE_SERVICE_SCAN": NodeServiceScanAction,
|
||||
"NODE_SERVICE_STOP": NodeServiceStopAction,
|
||||
@@ -733,7 +733,7 @@ class ActionManager:
|
||||
# and `options` is an optional dict of options to pass to the init method of the action class
|
||||
act_type = act_spec.get("type")
|
||||
act_options = act_spec.get("options", {})
|
||||
self.actions[act_type] = self._act_class_identifiers[act_type](self, **global_action_args, **act_options)
|
||||
self.actions[act_type] = self.act_class_identifiers[act_type](self, **global_action_args, **act_options)
|
||||
|
||||
self.action_map: Dict[int, Tuple[str, Dict]] = {}
|
||||
"""
|
||||
|
||||
@@ -13,7 +13,7 @@ the structure:
|
||||
- type: DATABASE_FILE_INTEGRITY
|
||||
weight: 0.5
|
||||
options:
|
||||
node_ref: database_server
|
||||
node_name: database_server
|
||||
folder_name: database
|
||||
file_name: database.db
|
||||
|
||||
@@ -21,7 +21,7 @@ the structure:
|
||||
- type: WEB_SERVER_404_PENALTY
|
||||
weight: 0.5
|
||||
options:
|
||||
node_ref: web_server
|
||||
node_name: web_server
|
||||
service_ref: web_server_database_client
|
||||
```
|
||||
"""
|
||||
@@ -184,7 +184,7 @@ class WebServer404Penalty(AbstractReward):
|
||||
service_name = config.get("service_name")
|
||||
if not (node_hostname and service_name):
|
||||
msg = (
|
||||
f"{cls.__name__} could not be initialised from config because node_ref and service_ref were not "
|
||||
f"{cls.__name__} could not be initialised from config because node_name and service_ref were not "
|
||||
"found in reward config."
|
||||
)
|
||||
_LOGGER.warning(msg)
|
||||
@@ -285,13 +285,14 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward):
|
||||
class RewardFunction:
|
||||
"""Manages the reward function for the agent."""
|
||||
|
||||
__rew_class_identifiers: Dict[str, Type[AbstractReward]] = {
|
||||
rew_class_identifiers: Dict[str, Type[AbstractReward]] = {
|
||||
"DUMMY": DummyReward,
|
||||
"DATABASE_FILE_INTEGRITY": DatabaseFileIntegrity,
|
||||
"WEB_SERVER_404_PENALTY": WebServer404Penalty,
|
||||
"WEBPAGE_UNAVAILABLE_PENALTY": WebpageUnavailablePenalty,
|
||||
"GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY": GreenAdminDatabaseUnreachablePenalty,
|
||||
}
|
||||
"""List of reward class identifiers."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialise the reward function object."""
|
||||
@@ -338,7 +339,7 @@ class RewardFunction:
|
||||
for rew_component_cfg in config["reward_components"]:
|
||||
rew_type = rew_component_cfg["type"]
|
||||
weight = rew_component_cfg.get("weight", 1.0)
|
||||
rew_class = cls.__rew_class_identifiers[rew_type]
|
||||
rew_class = cls.rew_class_identifiers[rew_type]
|
||||
rew_instance = rew_class.from_config(config=rew_component_cfg.get("options", {}))
|
||||
new.register_component(component=rew_instance, weight=weight)
|
||||
return new
|
||||
|
||||
@@ -16,6 +16,7 @@ from primaite.simulator.network.hardware.base import NodeOperatingState
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.host_node import NIC
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.network.firewall import Firewall
|
||||
from primaite.simulator.network.hardware.nodes.network.router import Router
|
||||
from primaite.simulator.network.hardware.nodes.network.switch import Switch
|
||||
from primaite.simulator.network.nmne import set_nmne_config
|
||||
@@ -42,6 +43,7 @@ APPLICATION_TYPES_MAPPING = {
|
||||
"DataManipulationBot": DataManipulationBot,
|
||||
"DoSBot": DoSBot,
|
||||
}
|
||||
"""List of available applications that can be installed on nodes in the PrimAITE Simulation."""
|
||||
|
||||
SERVICE_TYPES_MAPPING = {
|
||||
"DNSClient": DNSClient,
|
||||
@@ -53,6 +55,7 @@ SERVICE_TYPES_MAPPING = {
|
||||
"NTPClient": NTPClient,
|
||||
"NTPServer": NTPServer,
|
||||
}
|
||||
"""List of available services that can be installed on nodes in the PrimAITE Simulation."""
|
||||
|
||||
|
||||
class PrimaiteGameOptions(BaseModel):
|
||||
@@ -229,28 +232,36 @@ class PrimaiteGame:
|
||||
new_node = Computer(
|
||||
hostname=node_cfg["hostname"],
|
||||
ip_address=node_cfg["ip_address"],
|
||||
subnet_mask=node_cfg["subnet_mask"],
|
||||
subnet_mask=IPv4Address(node_cfg.get("subnet_mask", "255.255.255.0")),
|
||||
default_gateway=node_cfg["default_gateway"],
|
||||
dns_server=node_cfg["dns_server"],
|
||||
operating_state=NodeOperatingState.ON,
|
||||
dns_server=node_cfg.get("dns_server", None),
|
||||
operating_state=NodeOperatingState.ON
|
||||
if not (p := node_cfg.get("operating_state"))
|
||||
else NodeOperatingState[p.upper()],
|
||||
)
|
||||
elif n_type == "server":
|
||||
new_node = Server(
|
||||
hostname=node_cfg["hostname"],
|
||||
ip_address=node_cfg["ip_address"],
|
||||
subnet_mask=node_cfg["subnet_mask"],
|
||||
subnet_mask=IPv4Address(node_cfg.get("subnet_mask", "255.255.255.0")),
|
||||
default_gateway=node_cfg["default_gateway"],
|
||||
dns_server=node_cfg.get("dns_server"),
|
||||
operating_state=NodeOperatingState.ON,
|
||||
dns_server=node_cfg.get("dns_server", None),
|
||||
operating_state=NodeOperatingState.ON
|
||||
if not (p := node_cfg.get("operating_state"))
|
||||
else NodeOperatingState[p.upper()],
|
||||
)
|
||||
elif n_type == "switch":
|
||||
new_node = Switch(
|
||||
hostname=node_cfg["hostname"],
|
||||
num_ports=node_cfg.get("num_ports"),
|
||||
operating_state=NodeOperatingState.ON,
|
||||
num_ports=int(node_cfg.get("num_ports", "8")),
|
||||
operating_state=NodeOperatingState.ON
|
||||
if not (p := node_cfg.get("operating_state"))
|
||||
else NodeOperatingState[p.upper()],
|
||||
)
|
||||
elif n_type == "router":
|
||||
new_node = Router.from_config(node_cfg)
|
||||
elif n_type == "firewall":
|
||||
new_node = Firewall.from_config(node_cfg)
|
||||
else:
|
||||
_LOGGER.warning(f"invalid node type {n_type} in config")
|
||||
if "services" in node_cfg:
|
||||
@@ -263,6 +274,9 @@ class PrimaiteGame:
|
||||
new_node.software_manager.install(SERVICE_TYPES_MAPPING[service_type])
|
||||
new_service = new_node.software_manager.software[service_type]
|
||||
game.ref_map_services[service_ref] = new_service.uuid
|
||||
|
||||
# start the service
|
||||
new_service.start()
|
||||
else:
|
||||
msg = f"Configuration contains an invalid service type: {service_type}"
|
||||
_LOGGER.error(msg)
|
||||
@@ -282,18 +296,16 @@ class PrimaiteGame:
|
||||
if service_type == "DatabaseService":
|
||||
if "options" in service_cfg:
|
||||
opt = service_cfg["options"]
|
||||
new_service.password = opt.get("backup_server_ip", None)
|
||||
new_service.configure_backup(backup_server=IPv4Address(opt.get("backup_server_ip")))
|
||||
new_service.start()
|
||||
if service_type == "FTPServer":
|
||||
if "options" in service_cfg:
|
||||
opt = service_cfg["options"]
|
||||
new_service.server_password = opt.get("server_password")
|
||||
new_service.start()
|
||||
if service_type == "NTPClient":
|
||||
if "options" in service_cfg:
|
||||
opt = service_cfg["options"]
|
||||
new_service.ntp_server = IPv4Address(opt.get("ntp_server_ip"))
|
||||
new_service.start()
|
||||
if "applications" in node_cfg:
|
||||
for application_cfg in node_cfg["applications"]:
|
||||
new_application = None
|
||||
@@ -309,13 +321,16 @@ class PrimaiteGame:
|
||||
_LOGGER.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
# run the application
|
||||
new_application.run()
|
||||
|
||||
if application_type == "DataManipulationBot":
|
||||
if "options" in application_cfg:
|
||||
opt = application_cfg["options"]
|
||||
new_application.configure(
|
||||
server_ip_address=IPv4Address(opt.get("server_ip")),
|
||||
server_password=opt.get("server_password"),
|
||||
payload=opt.get("payload"),
|
||||
payload=opt.get("payload", "DELETE"),
|
||||
port_scan_p_of_success=float(opt.get("port_scan_p_of_success", "0.1")),
|
||||
data_manipulation_p_of_success=float(opt.get("data_manipulation_p_of_success", "0.1")),
|
||||
)
|
||||
@@ -330,7 +345,6 @@ class PrimaiteGame:
|
||||
if "options" in application_cfg:
|
||||
opt = application_cfg["options"]
|
||||
new_application.target_url = opt.get("target_url")
|
||||
|
||||
elif application_type == "DoSBot":
|
||||
if "options" in application_cfg:
|
||||
opt = application_cfg["options"]
|
||||
@@ -347,10 +361,20 @@ class PrimaiteGame:
|
||||
for nic_num, nic_cfg in node_cfg["network_interfaces"].items():
|
||||
new_node.connect_nic(NIC(ip_address=nic_cfg["ip_address"], subnet_mask=nic_cfg["subnet_mask"]))
|
||||
|
||||
# temporarily set to 0 so all nodes are initially on
|
||||
new_node.start_up_duration = 0
|
||||
new_node.shut_down_duration = 0
|
||||
|
||||
net.add_node(new_node)
|
||||
new_node.power_on()
|
||||
# run through the power on step if the node is to be turned on at the start
|
||||
if new_node.operating_state == NodeOperatingState.ON:
|
||||
new_node.power_on()
|
||||
game.ref_map_nodes[node_ref] = new_node.uuid
|
||||
|
||||
# set start up and shut down duration
|
||||
new_node.start_up_duration = int(node_cfg.get("start_up_duration", 3))
|
||||
new_node.shut_down_duration = int(node_cfg.get("shut_down_duration", 3))
|
||||
|
||||
# 2. create links between nodes
|
||||
for link_cfg in links_cfg:
|
||||
node_a = net.nodes[game.ref_map_nodes[link_cfg["endpoint_a_ref"]]]
|
||||
|
||||
@@ -12,8 +12,8 @@ class _SimOutput:
|
||||
self._path: Path = (
|
||||
_PRIMAITE_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
)
|
||||
self.save_pcap_logs: bool = True
|
||||
self.save_sys_logs: bool = True
|
||||
self.save_pcap_logs: bool = False
|
||||
self.save_sys_logs: bool = False
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# flake8: noqa
|
||||
"""Core of the PrimAITE Simulator."""
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Callable, ClassVar, Dict, List, Optional, Union
|
||||
from abc import abstractmethod
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
@@ -168,7 +168,9 @@ class WirelessNetworkInterface(NetworkInterface, ABC):
|
||||
|
||||
self.enabled = True
|
||||
self._connected_node.sys_log.info(f"Network Interface {self} enabled")
|
||||
self.pcap = PacketCapture(hostname=self._connected_node.hostname, interface_num=self.port_num)
|
||||
self.pcap = PacketCapture(
|
||||
hostname=self._connected_node.hostname, port_num=self.port_num, port_name=self.port_name
|
||||
)
|
||||
AIR_SPACE.add_wireless_interface(self)
|
||||
|
||||
def disable(self):
|
||||
|
||||
@@ -94,6 +94,9 @@ class NetworkInterface(SimComponent, ABC):
|
||||
port_num: Optional[int] = None
|
||||
"The port number assigned to this interface on the connected node."
|
||||
|
||||
port_name: Optional[str] = None
|
||||
"The port name assigned to this interface on the connected node."
|
||||
|
||||
pcap: Optional[PacketCapture] = None
|
||||
"A PacketCapture instance for capturing and analysing packets passing through this interface."
|
||||
|
||||
@@ -248,7 +251,7 @@ class NetworkInterface(SimComponent, ABC):
|
||||
|
||||
:return: A string combining the port number and the mac address
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}"
|
||||
return f"Port {self.port_name if self.port_name else self.port_num}: {self.mac_address}"
|
||||
|
||||
|
||||
class WiredNetworkInterface(NetworkInterface, ABC):
|
||||
@@ -293,7 +296,9 @@ class WiredNetworkInterface(NetworkInterface, ABC):
|
||||
|
||||
self.enabled = True
|
||||
self._connected_node.sys_log.info(f"Network Interface {self} enabled")
|
||||
self.pcap = PacketCapture(hostname=self._connected_node.hostname, interface_num=self.port_num)
|
||||
self.pcap = PacketCapture(
|
||||
hostname=self._connected_node.hostname, port_num=self.port_num, port_name=self.port_name
|
||||
)
|
||||
if self._connected_link:
|
||||
self._connected_link.endpoint_up()
|
||||
|
||||
@@ -1024,7 +1029,7 @@ class Node(SimComponent):
|
||||
self.sys_log.info("Resetting")
|
||||
self.power_off()
|
||||
|
||||
def connect_nic(self, network_interface: NetworkInterface):
|
||||
def connect_nic(self, network_interface: NetworkInterface, port_name: Optional[str] = None):
|
||||
"""
|
||||
Connect a Network Interface to the node.
|
||||
|
||||
@@ -1036,7 +1041,9 @@ class Node(SimComponent):
|
||||
new_nic_num = len(self.network_interfaces)
|
||||
self.network_interface[new_nic_num] = network_interface
|
||||
network_interface._connected_node = self
|
||||
network_interface._port_num_on_node = new_nic_num
|
||||
network_interface.port_num = new_nic_num
|
||||
if port_name:
|
||||
network_interface.port_name = port_name
|
||||
network_interface.parent = self
|
||||
self.sys_log.info(f"Connected Network Interface {network_interface}")
|
||||
if self.operating_state == NodeOperatingState.ON:
|
||||
|
||||
@@ -83,4 +83,4 @@ class WirelessAccessPoint(IPWirelessNetworkInterface):
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
return f"Port {self.port_name if self.port_name else self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
|
||||
@@ -80,4 +80,4 @@ class WirelessNIC(IPWirelessNetworkInterface):
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
return f"Port {self.port_name if self.port_name else self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, ClassVar, Dict, Optional
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.simulator.network.hardware.base import IPWiredNetworkInterface, Link, Node
|
||||
@@ -250,7 +250,7 @@ class NIC(IPWiredNetworkInterface):
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
return f"Port {self.port_name if self.port_name else self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
|
||||
|
||||
class HostNode(Node):
|
||||
@@ -297,6 +297,16 @@ class HostNode(Node):
|
||||
* Web Browser: Provides web browsing capabilities.
|
||||
"""
|
||||
|
||||
SYSTEM_SOFTWARE: ClassVar[Dict] = {
|
||||
"HostARP": HostARP,
|
||||
"ICMP": ICMP,
|
||||
"DNSClient": DNSClient,
|
||||
"FTPClient": FTPClient,
|
||||
"NTPClient": NTPClient,
|
||||
"WebBrowser": WebBrowser,
|
||||
}
|
||||
"""List of system software that is automatically installed on nodes."""
|
||||
|
||||
network_interfaces: Dict[str, NIC] = {}
|
||||
"The Network Interfaces on the node."
|
||||
network_interface: Dict[int, NIC] = {}
|
||||
@@ -313,23 +323,8 @@ class HostNode(Node):
|
||||
This method equips the host with essential network services and applications, preparing it for various
|
||||
network-related tasks and operations.
|
||||
"""
|
||||
# ARP Service
|
||||
self.software_manager.install(HostARP)
|
||||
|
||||
# ICMP Service
|
||||
self.software_manager.install(ICMP)
|
||||
|
||||
# DNS Client
|
||||
self.software_manager.install(DNSClient)
|
||||
|
||||
# FTP Client
|
||||
self.software_manager.install(FTPClient)
|
||||
|
||||
# NTP Client
|
||||
self.software_manager.install(NTPClient)
|
||||
|
||||
# Web Browser
|
||||
self.software_manager.install(WebBrowser)
|
||||
for _, software_class in self.SYSTEM_SOFTWARE.items():
|
||||
self.software_manager.install(software_class)
|
||||
|
||||
super()._install_system_software()
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Dict, Final, Optional, Union
|
||||
|
||||
from prettytable import MARKDOWN, PrettyTable
|
||||
from pydantic import validate_call
|
||||
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
from primaite.simulator.network.hardware.nodes.network.router import (
|
||||
AccessControlList,
|
||||
ACLAction,
|
||||
@@ -10,6 +12,8 @@ from primaite.simulator.network.hardware.nodes.network.router import (
|
||||
RouterInterface,
|
||||
)
|
||||
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.core.sys_log import SysLog
|
||||
from primaite.utils.validators import IPV4Address
|
||||
|
||||
@@ -85,7 +89,17 @@ class Firewall(Router):
|
||||
if not kwargs.get("sys_log"):
|
||||
kwargs["sys_log"] = SysLog(hostname)
|
||||
|
||||
super().__init__(hostname=hostname, num_ports=3, **kwargs)
|
||||
super().__init__(hostname=hostname, num_ports=0, **kwargs)
|
||||
|
||||
self.connect_nic(
|
||||
RouterInterface(ip_address="127.0.0.1", subnet_mask="255.0.0.0", gateway="0.0.0.0", port_name="external")
|
||||
)
|
||||
self.connect_nic(
|
||||
RouterInterface(ip_address="127.0.0.1", subnet_mask="255.0.0.0", gateway="0.0.0.0", port_name="internal")
|
||||
)
|
||||
self.connect_nic(
|
||||
RouterInterface(ip_address="127.0.0.1", subnet_mask="255.0.0.0", gateway="0.0.0.0", port_name="dmz")
|
||||
)
|
||||
|
||||
# Initialise ACLs for internal and dmz interfaces with a default DENY policy
|
||||
self.internal_inbound_acl = AccessControlList(
|
||||
@@ -473,3 +487,123 @@ class Firewall(Router):
|
||||
"""
|
||||
self.configure_port(DMZ_PORT_ID, ip_address, subnet_mask)
|
||||
self.dmz_port.enable()
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, cfg: dict) -> "Firewall":
|
||||
"""Create a firewall based on a config dict."""
|
||||
firewall = Firewall(
|
||||
hostname=cfg["hostname"],
|
||||
operating_state=NodeOperatingState.ON
|
||||
if not (p := cfg.get("operating_state"))
|
||||
else NodeOperatingState[p.upper()],
|
||||
)
|
||||
if "ports" in cfg:
|
||||
internal_port = cfg["ports"]["internal_port"]
|
||||
external_port = cfg["ports"]["external_port"]
|
||||
dmz_port = cfg["ports"]["dmz_port"]
|
||||
|
||||
# configure internal port
|
||||
firewall.configure_internal_port(
|
||||
ip_address=IPV4Address(internal_port.get("ip_address")),
|
||||
subnet_mask=IPV4Address(internal_port.get("subnet_mask", "255.255.255.0")),
|
||||
)
|
||||
|
||||
# configure external port
|
||||
firewall.configure_external_port(
|
||||
ip_address=IPV4Address(external_port.get("ip_address")),
|
||||
subnet_mask=IPV4Address(external_port.get("subnet_mask", "255.255.255.0")),
|
||||
)
|
||||
|
||||
# configure dmz port
|
||||
firewall.configure_dmz_port(
|
||||
ip_address=IPV4Address(dmz_port.get("ip_address")),
|
||||
subnet_mask=IPV4Address(dmz_port.get("subnet_mask", "255.255.255.0")),
|
||||
)
|
||||
if "acl" in cfg:
|
||||
# acl rules for internal_inbound_acl
|
||||
if cfg["acl"]["internal_inbound_acl"]:
|
||||
for r_num, r_cfg in cfg["acl"]["internal_inbound_acl"].items():
|
||||
firewall.internal_inbound_acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p],
|
||||
src_ip_address=r_cfg.get("src_ip"),
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
|
||||
# acl rules for internal_outbound_acl
|
||||
if cfg["acl"]["internal_outbound_acl"]:
|
||||
for r_num, r_cfg in cfg["acl"]["internal_outbound_acl"].items():
|
||||
firewall.internal_outbound_acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p],
|
||||
src_ip_address=r_cfg.get("src_ip"),
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
|
||||
# acl rules for dmz_inbound_acl
|
||||
if cfg["acl"]["dmz_inbound_acl"]:
|
||||
for r_num, r_cfg in cfg["acl"]["dmz_inbound_acl"].items():
|
||||
firewall.dmz_inbound_acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p],
|
||||
src_ip_address=r_cfg.get("src_ip"),
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
|
||||
# acl rules for dmz_outbound_acl
|
||||
if cfg["acl"]["dmz_outbound_acl"]:
|
||||
for r_num, r_cfg in cfg["acl"]["dmz_outbound_acl"].items():
|
||||
firewall.dmz_outbound_acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p],
|
||||
src_ip_address=r_cfg.get("src_ip"),
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
|
||||
# acl rules for external_inbound_acl
|
||||
if cfg["acl"]["external_inbound_acl"]:
|
||||
for r_num, r_cfg in cfg["acl"]["external_inbound_acl"].items():
|
||||
firewall.external_inbound_acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p],
|
||||
src_ip_address=r_cfg.get("src_ip"),
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
|
||||
# acl rules for external_outbound_acl
|
||||
if cfg["acl"]["external_outbound_acl"]:
|
||||
for r_num, r_cfg in cfg["acl"]["external_outbound_acl"].items():
|
||||
firewall.external_outbound_acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p],
|
||||
src_ip_address=r_cfg.get("src_ip"),
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
|
||||
if "routes" in cfg:
|
||||
for route in cfg.get("routes"):
|
||||
firewall.route_table.add_route(
|
||||
address=IPv4Address(route.get("address")),
|
||||
subnet_mask=IPv4Address(route.get("subnet_mask", "255.255.255.0")),
|
||||
next_hop_ip_address=IPv4Address(route.get("next_hop_ip_address")),
|
||||
metric=float(route.get("metric", 0)),
|
||||
)
|
||||
return firewall
|
||||
|
||||
@@ -998,7 +998,7 @@ class RouterInterface(IPWiredNetworkInterface):
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
return f"Port {self.port_name if self.port_name else self.port_num}: {self.mac_address}/{self.ip_address}"
|
||||
|
||||
|
||||
class Router(NetworkNode):
|
||||
@@ -1398,21 +1398,23 @@ class Router(NetworkNode):
|
||||
:return: Configured router.
|
||||
:rtype: Router
|
||||
"""
|
||||
new = Router(
|
||||
router = Router(
|
||||
hostname=cfg["hostname"],
|
||||
num_ports=cfg.get("num_ports"),
|
||||
operating_state=NodeOperatingState.ON,
|
||||
num_ports=int(cfg.get("num_ports", "5")),
|
||||
operating_state=NodeOperatingState.ON
|
||||
if not (p := cfg.get("operating_state"))
|
||||
else NodeOperatingState[p.upper()],
|
||||
)
|
||||
if "ports" in cfg:
|
||||
for port_num, port_cfg in cfg["ports"].items():
|
||||
new.configure_port(
|
||||
router.configure_port(
|
||||
port=port_num,
|
||||
ip_address=port_cfg["ip_address"],
|
||||
subnet_mask=port_cfg["subnet_mask"],
|
||||
subnet_mask=IPv4Address(port_cfg.get("subnet_mask", "255.255.255.0")),
|
||||
)
|
||||
if "acl" in cfg:
|
||||
for r_num, r_cfg in cfg["acl"].items():
|
||||
new.acl.add_rule(
|
||||
router.acl.add_rule(
|
||||
action=ACLAction[r_cfg["action"]],
|
||||
src_port=None if not (p := r_cfg.get("src_port")) else Port[p],
|
||||
dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p],
|
||||
@@ -1421,4 +1423,12 @@ class Router(NetworkNode):
|
||||
dst_ip_address=r_cfg.get("dst_ip"),
|
||||
position=r_num,
|
||||
)
|
||||
return new
|
||||
if "routes" in cfg:
|
||||
for route in cfg.get("routes"):
|
||||
router.route_table.add_route(
|
||||
address=IPv4Address(route.get("address")),
|
||||
subnet_mask=IPv4Address(route.get("subnet_mask", "255.255.255.0")),
|
||||
next_hop_ip_address=IPv4Address(route.get("next_hop_ip_address")),
|
||||
metric=float(route.get("metric", 0)),
|
||||
)
|
||||
return router
|
||||
|
||||
@@ -80,7 +80,10 @@ class WirelessAccessPoint(IPWirelessNetworkInterface):
|
||||
|
||||
:return: A string combining the port number, MAC address and IP address of the NIC.
|
||||
"""
|
||||
return f"Port {self.port_num}: {self.mac_address}/{self.ip_address} ({self.frequency})"
|
||||
return (
|
||||
f"Port {self.port_name if self.port_name else self.port_num}: "
|
||||
f"{self.mac_address}/{self.ip_address} ({self.frequency})"
|
||||
)
|
||||
|
||||
|
||||
class WirelessRouter(Router):
|
||||
|
||||
@@ -9,11 +9,18 @@ _LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class IPProtocol(Enum):
|
||||
"""Enum representing transport layer protocols in IP header."""
|
||||
"""
|
||||
Enum representing transport layer protocols in IP header.
|
||||
|
||||
.. _List of IPProtocols:
|
||||
"""
|
||||
|
||||
TCP = "tcp"
|
||||
"""Transmission Control Protocol."""
|
||||
UDP = "udp"
|
||||
"""User Datagram Protocol."""
|
||||
ICMP = "icmp"
|
||||
"""Internet Control Message Protocol."""
|
||||
|
||||
|
||||
class Precedence(Enum):
|
||||
|
||||
@@ -5,7 +5,11 @@ from pydantic import BaseModel
|
||||
|
||||
|
||||
class Port(Enum):
|
||||
"""Enumeration of common known TCP/UDP ports used by protocols for operation of network applications."""
|
||||
"""
|
||||
Enumeration of common known TCP/UDP ports used by protocols for operation of network applications.
|
||||
|
||||
.. _List of Ports:
|
||||
"""
|
||||
|
||||
NONE = 0
|
||||
"Place holder for a non-port."
|
||||
|
||||
@@ -21,7 +21,13 @@ class PacketCapture:
|
||||
The PCAPs are logged to: <simulation output directory>/<hostname>/<hostname>_<ip address>_pcap.log
|
||||
"""
|
||||
|
||||
def __init__(self, hostname: str, ip_address: Optional[str] = None, interface_num: Optional[int] = None):
|
||||
def __init__(
|
||||
self,
|
||||
hostname: str,
|
||||
ip_address: Optional[str] = None,
|
||||
port_num: Optional[int] = None,
|
||||
port_name: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initialize the PacketCapture process.
|
||||
|
||||
@@ -32,9 +38,12 @@ class PacketCapture:
|
||||
"The hostname for which PCAP logs are being recorded."
|
||||
self.ip_address: str = ip_address
|
||||
"The IP address associated with the PCAP logs."
|
||||
self.interface_num = interface_num
|
||||
self.port_num = port_num
|
||||
"The interface num on the Node."
|
||||
|
||||
self.port_name = port_name
|
||||
"The interface name on the Node."
|
||||
|
||||
self.inbound_logger = None
|
||||
self.outbound_logger = None
|
||||
|
||||
@@ -79,10 +88,12 @@ class PacketCapture:
|
||||
|
||||
def _get_logger_name(self, outbound: bool = False) -> str:
|
||||
"""Get PCAP the logger name."""
|
||||
if self.port_name:
|
||||
return f"{self.hostname}_{self.port_name}_{'outbound' if outbound else 'inbound'}_pcap"
|
||||
if self.ip_address:
|
||||
return f"{self.hostname}_{self.ip_address}_{'outbound' if outbound else 'inbound'}_pcap"
|
||||
if self.interface_num:
|
||||
return f"{self.hostname}_port-{self.interface_num}_{'outbound' if outbound else 'inbound'}_pcap"
|
||||
if self.port_num:
|
||||
return f"{self.hostname}_port-{self.port_num}_{'outbound' if outbound else 'inbound'}_pcap"
|
||||
return f"{self.hostname}_{'outbound' if outbound else 'inbound'}_pcap"
|
||||
|
||||
def _get_log_path(self, outbound: bool = False) -> Path:
|
||||
|
||||
@@ -23,6 +23,7 @@ class DatabaseService(Service):
|
||||
"""
|
||||
|
||||
password: Optional[str] = None
|
||||
"""Password that needs to be provided by clients if they want to connect to the DatabaseService."""
|
||||
|
||||
backup_server_ip: IPv4Address = None
|
||||
"""IP address of the backup server."""
|
||||
|
||||
Reference in New Issue
Block a user