#1715 - Added Link class in physical_layer.py. Also added NIC class in physical_layer.py for #1672. Added attributes and public API functions. test_physical_layer.py ready to house the tests once logic has been implemented.

This commit is contained in:
Chris McCarthy
2023-07-31 15:55:22 +01:00
parent 020682f546
commit 59394c3642
7 changed files with 248 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
from __future__ import annotations
import re
import secrets
from ipaddress import IPv4Address
from typing import Dict, List, Optional
from primaite.simulator.core import SimComponent
def generate_mac_address(oui: Optional[str] = None) -> str:
"""
Generate a random MAC Address..
:Example:
>>> generate_mac_address()
'ef:7e:97:c8:a8:ce'
>>> generate_mac_address(oui='aa:bb:cc')
'aa:bb:cc:42:ba:41'
:param oui: The Organizationally Unique Identifier (OUI) portion of the MAC address. It should be a string with
the first 3 bytes (24 bits) in the format "XX:XX:XX".
:raises ValueError: If the 'oui' is not in the correct format (hexadecimal and 6 characters).
"""
random_bytes = [secrets.randbits(8) for _ in range(6)]
if oui:
oui_pattern = re.compile(r"^([0-9A-Fa-f]{2}[:-]){2}[0-9A-Fa-f]{2}$")
if not oui_pattern.match(oui):
raise ValueError(
f"Invalid oui. The oui should be in the format 'xx:xx:xx', where x is a hexadecimal digit, got '{oui}'."
)
oui_bytes = [int(chunk, 16) for chunk in oui.split(":")]
mac = oui_bytes + random_bytes[len(oui_bytes) :]
else:
mac = random_bytes
return ":".join(f"{b:02x}" for b in mac)
class Link(SimComponent):
"""
Represents a network link between two network interface cards (NICs).
:param endpoint_a: The first NIC connected to the Link.
:type endpoint_a: NIC
:param endpoint_b: The second NIC connected to the Link.
:type endpoint_b: NIC
:param bandwidth: The bandwidth of the Link in Mbps (default is 100 Mbps).
:type bandwidth: int
"""
endpoint_a: NIC
endpoint_b: NIC
bandwidth: int
current_load: int = 0
def __init__(self, endpoint_a: NIC, endpoint_b: NIC, bandwidth: int = 100):
"""
Initialize the Link instance.
When a Link is created, it automatically connects the endpoints to itself.
:param endpoint_a: The first NIC connected to the link.
:type endpoint_a: NIC
:param endpoint_b: The second NIC connected to the link.
:type endpoint_b: NIC
:param bandwidth: The bandwidth of the link in Mbps (default is 100 Mbps).
:type bandwidth: int
:raise ValueError: If endpoint_a equals endpoint_b.
"""
super().__init__(endpoint_a=endpoint_a, endpoint_b=endpoint_b, bandwidth=bandwidth)
if self.endpoint_a == self.endpoint_b:
raise ValueError("endpoint_a and endpoint_b cannot be the same NIC")
def send_frame(self, sender_nic: NIC, frame):
"""
Send a network frame from one NIC to another connected NIC.
:param sender_nic: The NIC sending the frame.
:type sender_nic: NIC
:param frame: The network frame to be sent.
:type frame: Frame
"""
pass
def receive_frame(self, sender_nic: NIC, frame):
"""
Receive a network frame from a connected NIC.
:param sender_nic: The NIC sending the frame.
:type sender_nic: NIC
:param frame: The network frame being received.
:type frame: Frame
"""
pass
def describe_state(self) -> Dict:
"""
Get the current state of the Libk as a dict.
:return: A dict containing the current state of the Link.
"""
pass
def apply_action(self, action: str):
"""
Apply an action to the Link.
:param action: The action to be applied.
:type action: str
"""
pass
class NIC(SimComponent):
"""
Models a Network Interface Card (NIC) in a computer or network device.
:param ip_address: The IPv4 address assigned to the NIC.
:param subnet_mask: The subnet mask assigned to the NIC.
:param gateway: The default gateway IP address for forwarding network traffic to other networks.
:param mac_address: The MAC address of the NIC. Defaults to a randomly set MAC address.
:param speed: The speed of the NIC in Mbps.
:param mtu: The Maximum Transmission Unit (MTU) of the NIC in Bytes, representing the largest data packet size it
can handle without fragmentation.
:param wake_on_lan: Indicates if the NIC supports Wake-on-LAN functionality.
:param dns_servers: List of IP addresses of DNS servers used for name resolution.
:param connected_link: The link to which the NIC is connected (default is None).
:param enabled: Indicates whether the NIC is enabled.
"""
ip_address: IPv4Address
"The IP address assigned to the NIC for communication on an IP-based network."
subnet_mask: str
"The subnet mask assigned to the NIC."
gateway: IPv4Address
"The default gateway IP address for forwarding network traffic to other networks. Randomly generated upon creation."
mac_address: str = generate_mac_address()
"The MAC address of the NIC. Defaults to a randomly set MAC address."
speed: Optional[int] = 100
"The speed of the NIC in Mbps. Default is 100 Mbps."
mtu: Optional[int] = 1500
"The Maximum Transmission Unit (MTU) of the NIC in Bytes. Default is 1500 B"
wake_on_lan: Optional[bool] = False
"Indicates if the NIC supports Wake-on-LAN functionality."
dns_servers: List[IPv4Address] = []
"List of IP addresses of DNS servers used for name resolution."
connected_link: Optional[Link] = None
"The Link to which the NIC is connected."
enabled: bool = False
"Indicates whether the NIC is enabled."
def connect_link(self, link: Link):
"""
Connect the NIC to a link.
:param link: The link to which the NIC is connected.
:type link: :class:`~primaite.simulator.network.physical_layer.Link`
"""
pass
def disconnect_link(self):
"""Disconnect the NIC from the connected :class:`~primaite.simulator.network.physical_layer.Link`."""
pass
def add_dns_server(self, ip_address: IPv4Address):
"""
Add a DNS server IP address.
:param ip_address: The IP address of the DNS server to be added.
:type ip_address: ipaddress.IPv4Address
"""
pass
def remove_dns_server(self, ip_address: IPv4Address):
"""
Remove a DNS server IP Address.
:param ip_address: The IP address of the DNS server to be removed.
:type ip_address: ipaddress.IPv4Address
"""
pass
def send_frame(self, frame):
"""
Send a network frame from the NIC to the connected link.
:param frame: The network frame to be sent.
:type frame: :class:`~primaite.simulator.network.osi_layers.Frame`
"""
pass
def receive_frame(self, frame):
"""
Receive a network frame from the connected link.
The Frame is passed to the Node.
:param frame: The network frame being received.
:type frame: :class:`~primaite.simulator.network.osi_layers.Frame`
"""
pass
def describe_state(self) -> Dict:
"""
Get the current state of the NIC as a dict.
:return: A dict containing the current state of the NIC.
"""
pass
def apply_action(self, action: str):
"""
Apply an action to the NIC.
:param action: The action to be applied.
:type action: str
"""
pass

View File

@@ -0,0 +1,26 @@
import re
import pytest
from primaite.simulator.network.physical_layer import generate_mac_address
def test_mac_address_generation():
"""Tests random mac address generation."""
mac_address = generate_mac_address()
assert re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac_address)
def test_mac_address_with_oui():
"""Tests random mac address generation with oui."""
oui = "aa:bb:cc"
mac_address = generate_mac_address(oui=oui)
assert re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac_address)
assert mac_address[:8] == oui
def test_invalid_oui_mac_address():
"""Tests random mac address generation fails with invalid oui."""
invalid_oui = "aa-bb-cc"
with pytest.raises(ValueError):
generate_mac_address(oui=invalid_oui)