daemon: refactored how interfaces are configured, updated link edits to allow proper bi-directional support for network to network interfaces, improved and added more unit tests for link add/edit/delete

This commit is contained in:
Blake Harnden 2022-01-25 09:13:39 -08:00
parent bc8c49c573
commit e9b83b0d28
8 changed files with 348 additions and 252 deletions

View file

@ -13,7 +13,7 @@ import netaddr
from core import utils
from core.configservice.dependencies import ConfigServiceDependencies
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.data import InterfaceData, LinkData
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, TEST, VNODED
@ -1000,20 +1000,6 @@ class CoreNetworkBase(NodeBase):
"""
raise NotImplementedError
@abc.abstractmethod
def linkconfig(
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
) -> None:
"""
Configure link parameters by applying tc queuing disciplines on the interface.
:param iface: interface one
:param options: options for configuring link
:param iface2: interface two
:return: nothing
"""
raise NotImplementedError
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
raise NotImplementedError

View file

@ -3,6 +3,7 @@ virtual ethernet classes that implement the interfaces available under Linux.
"""
import logging
import math
import time
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
@ -13,6 +14,7 @@ from core import utils
from core.emulator.data import LinkOptions
from core.emulator.enumerations import TransportType
from core.errors import CoreCommandError, CoreError
from core.executables import TC
from core.nodes.netclient import LinuxNetClient, get_net_client
logger = logging.getLogger(__name__)
@ -336,6 +338,92 @@ class CoreInterface:
"""
return self.transport_type == TransportType.VIRTUAL
def _set_params_change(self, **kwargs: float) -> bool:
"""
Set parameters to change.
:param kwargs: parameter name and values to change
:return: True if any parameter changed, False otherwise
"""
return any([self.setparam(k, v) for k, v in kwargs.items()])
def config(self, options: LinkOptions, use_local: bool = True) -> None:
"""
Configure interface using tc based on existing state and provided
link options.
:param options: options to configure with
:param use_local: True to use localname for device, False for name
:return: nothing
"""
# determine if any settings have changed
if use_local:
devname = self.localname
changed = self._set_params_change(
bw=options.bandwidth,
delay=options.delay,
loss=options.loss,
duplicate=options.dup,
jitter=options.jitter,
buffer=options.buffer,
)
else:
devname = self.name
changed = self._set_params_change(
n_bw=options.bandwidth,
n_delay=options.delay,
n_loss=options.loss,
n_duplicate=options.dup,
n_jitter=options.jitter,
n_buffer=options.buffer,
)
if not changed:
return
# delete tc configuration or create and add it
if all(
[
options.delay is None or options.delay <= 0,
options.jitter is None or options.jitter <= 0,
options.loss is None or options.loss <= 0,
options.dup is None or options.dup <= 0,
options.bandwidth is None or options.bandwidth <= 0,
options.buffer is None or options.buffer <= 0,
]
):
if not self.getparam("has_netem"):
return
if self.up:
cmd = f"{TC} qdisc delete dev {devname} root handle 10:"
self.host_cmd(cmd)
self.setparam("has_netem", False)
else:
netem = ""
if options.bandwidth is not None:
limit = 1000
bw = options.bandwidth / 1000
if options.buffer is not None and options.buffer > 0:
limit = options.buffer
elif options.delay and options.bandwidth:
delay = options.delay / 1000
limit = max(2, math.ceil((2 * bw * delay) / (8 * self.mtu)))
netem += f" rate {bw}kbit"
netem += f" limit {limit}"
if options.delay is not None:
netem += f" delay {options.delay}us"
if options.jitter is not None:
if options.delay is None:
netem += f" delay 0us {options.jitter}us 25%"
else:
netem += f" {options.jitter}us 25%"
if options.loss is not None and options.loss > 0:
netem += f" loss {min(options.loss, 100)}%"
if options.dup is not None and options.dup > 0:
netem += f" duplicate {min(options.dup, 100)}%"
if self.up:
cmd = f"{TC} qdisc replace dev {devname} root handle 10: netem {netem}"
self.host_cmd(cmd)
self.setparam("has_netem", True)
class Veth(CoreInterface):
"""

View file

@ -3,7 +3,6 @@ Defines network nodes used within core.
"""
import logging
import math
import threading
import time
from collections import OrderedDict
@ -14,7 +13,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Type
import netaddr
from core import utils
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.data import InterfaceData, LinkData
from core.emulator.enumerations import (
LinkTypes,
MessageFlags,
@ -23,7 +22,7 @@ from core.emulator.enumerations import (
RegisterTlvs,
)
from core.errors import CoreCommandError, CoreError
from core.executables import NFTABLES, TC
from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface, GreTap, Veth
from core.nodes.netclient import get_net_client
@ -400,77 +399,6 @@ class CoreNetwork(CoreNetworkBase):
self.linked[iface1][iface2] = True
nft_queue.update(self)
def linkconfig(
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
) -> None:
"""
Configure link parameters by applying tc queuing disciplines on the interface.
:param iface: interface one
:param options: options for configuring link
:param iface2: interface two
:return: nothing
"""
# determine if any settings have changed
changed = any(
[
iface.setparam("bw", options.bandwidth),
iface.setparam("delay", options.delay),
iface.setparam("loss", options.loss),
iface.setparam("duplicate", options.dup),
iface.setparam("jitter", options.jitter),
iface.setparam("buffer", options.buffer),
]
)
if not changed:
return
# delete tc configuration or create and add it
devname = iface.localname
if all(
[
options.delay is None or options.delay <= 0,
options.jitter is None or options.jitter <= 0,
options.loss is None or options.loss <= 0,
options.dup is None or options.dup <= 0,
options.bandwidth is None or options.bandwidth <= 0,
options.buffer is None or options.buffer <= 0,
]
):
if not iface.getparam("has_netem"):
return
if self.up:
cmd = f"{TC} qdisc delete dev {devname} root handle 10:"
iface.host_cmd(cmd)
iface.setparam("has_netem", False)
else:
netem = ""
if options.bandwidth is not None:
limit = 1000
bw = options.bandwidth / 1000
if options.buffer is not None and options.buffer > 0:
limit = options.buffer
elif options.delay and options.bandwidth:
delay = options.delay / 1000
limit = max(2, math.ceil((2 * bw * delay) / (8 * iface.mtu)))
netem += f" rate {bw}kbit"
netem += f" limit {limit}"
if options.delay is not None:
netem += f" delay {options.delay}us"
if options.jitter is not None:
if options.delay is None:
netem += f" delay 0us {options.jitter}us 25%"
else:
netem += f" {options.jitter}us 25%"
if options.loss is not None and options.loss > 0:
netem += f" loss {min(options.loss, 100)}%"
if options.dup is not None and options.dup > 0:
netem += f" duplicate {min(options.dup, 100)}%"
if self.up:
cmd = f"{TC} qdisc replace dev {devname} root handle 10: netem {netem}"
iface.host_cmd(cmd)
iface.setparam("has_netem", True)
def linknet(self, net: CoreNetworkBase) -> CoreInterface:
"""
Link this bridge with another by creating a veth pair and installing

View file

@ -7,14 +7,13 @@ import threading
from pathlib import Path
from typing import TYPE_CHECKING, List, Optional, Tuple
from core.emulator.data import InterfaceData, LinkOptions
from core.emulator.data import InterfaceData
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes, TransportType
from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, TEST, UMOUNT
from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import CoreNetwork
logger = logging.getLogger(__name__)
@ -143,17 +142,6 @@ class PhysicalNode(CoreNodeBase):
if self.up:
self.net_client.device_up(iface.localname)
def linkconfig(
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
) -> None:
"""
Apply tc queing disciplines using linkconfig.
"""
linux_bridge = CoreNetwork(self.session)
linux_bridge.up = True
linux_bridge.linkconfig(iface, options, iface2)
del linux_bridge
def next_iface_id(self) -> int:
with self.lock:
while self.iface_id in self.ifaces: