daemon: updates to switch from using ebtables to nftables for wlan linking/unlinking
This commit is contained in:
parent
208c746b67
commit
30291a8438
14 changed files with 153 additions and 206 deletions
|
@ -57,7 +57,7 @@ fpm -s dir -t deb -n core-distributed \
|
||||||
-d "procps" \
|
-d "procps" \
|
||||||
-d "libc6 >= 2.14" \
|
-d "libc6 >= 2.14" \
|
||||||
-d "bash >= 3.0" \
|
-d "bash >= 3.0" \
|
||||||
-d "ebtables" \
|
-d "nftables" \
|
||||||
-d "iproute2" \
|
-d "iproute2" \
|
||||||
-d "libev4" \
|
-d "libev4" \
|
||||||
-d "openssh-server" \
|
-d "openssh-server" \
|
||||||
|
@ -77,7 +77,7 @@ fpm -s dir -t rpm -n core-distributed \
|
||||||
-d "ethtool" \
|
-d "ethtool" \
|
||||||
-d "procps-ng" \
|
-d "procps-ng" \
|
||||||
-d "bash >= 3.0" \
|
-d "bash >= 3.0" \
|
||||||
-d "ebtables" \
|
-d "nftables" \
|
||||||
-d "iproute" \
|
-d "iproute" \
|
||||||
-d "libev" \
|
-d "libev" \
|
||||||
-d "net-tools" \
|
-d "net-tools" \
|
||||||
|
|
|
@ -123,9 +123,9 @@ if test "x$enable_daemon" = "xyes"; then
|
||||||
AC_MSG_ERROR([Could not locate sysctl (from procps package).])
|
AC_MSG_ERROR([Could not locate sysctl (from procps package).])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AC_CHECK_PROG(ebtables_path, ebtables, $as_dir, no, $SEARCHPATH)
|
AC_CHECK_PROG(nftables_path, nft, $as_dir, no, $SEARCHPATH)
|
||||||
if test "x$ebtables_path" = "xno" ; then
|
if test "x$nftables_path" = "xno" ; then
|
||||||
AC_MSG_ERROR([Could not locate ebtables (from ebtables package).])
|
AC_MSG_ERROR([Could not locate nftables (from nftables package).])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH)
|
AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH)
|
||||||
|
|
|
@ -7,15 +7,15 @@ SYSCTL: str = "sysctl"
|
||||||
IP: str = "ip"
|
IP: str = "ip"
|
||||||
ETHTOOL: str = "ethtool"
|
ETHTOOL: str = "ethtool"
|
||||||
TC: str = "tc"
|
TC: str = "tc"
|
||||||
EBTABLES: str = "ebtables"
|
|
||||||
MOUNT: str = "mount"
|
MOUNT: str = "mount"
|
||||||
UMOUNT: str = "umount"
|
UMOUNT: str = "umount"
|
||||||
OVS_VSCTL: str = "ovs-vsctl"
|
OVS_VSCTL: str = "ovs-vsctl"
|
||||||
TEST: str = "test"
|
TEST: str = "test"
|
||||||
|
NFTABLES: str = "nft"
|
||||||
|
|
||||||
COMMON_REQUIREMENTS: List[str] = [
|
COMMON_REQUIREMENTS: List[str] = [
|
||||||
BASH,
|
BASH,
|
||||||
EBTABLES,
|
NFTABLES,
|
||||||
ETHTOOL,
|
ETHTOOL,
|
||||||
IP,
|
IP,
|
||||||
MOUNT,
|
MOUNT,
|
||||||
|
|
|
@ -669,7 +669,7 @@ class CoreClient:
|
||||||
else:
|
else:
|
||||||
services = self.session.default_services.get(model)
|
services = self.session.default_services.get(model)
|
||||||
if services:
|
if services:
|
||||||
node.config_services = services.copy()
|
node.config_services = set(services)
|
||||||
logger.info(
|
logger.info(
|
||||||
"add node(%s) to session(%s), coordinates(%s, %s)",
|
"add node(%s) to session(%s), coordinates(%s, %s)",
|
||||||
node.name,
|
node.name,
|
||||||
|
|
|
@ -947,9 +947,9 @@ class CoreNetworkBase(NodeBase):
|
||||||
will run on, default is None for localhost
|
will run on, default is None for localhost
|
||||||
"""
|
"""
|
||||||
super().__init__(session, _id, name, server)
|
super().__init__(session, _id, name, server)
|
||||||
self.brname = None
|
self.brname: Optional[str] = None
|
||||||
self._linked = {}
|
self._linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {}
|
||||||
self._linked_lock = threading.Lock()
|
self._linked_lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def startup(self) -> None:
|
def startup(self) -> None:
|
||||||
|
|
|
@ -21,7 +21,7 @@ from core.emulator.enumerations import (
|
||||||
RegisterTlvs,
|
RegisterTlvs,
|
||||||
)
|
)
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.executables import EBTABLES, TC
|
from core.executables import NFTABLES, TC
|
||||||
from core.nodes.base import CoreNetworkBase
|
from core.nodes.base import CoreNetworkBase
|
||||||
from core.nodes.interface import CoreInterface, GreTap, Veth
|
from core.nodes.interface import CoreInterface, GreTap, Veth
|
||||||
from core.nodes.netclient import get_net_client
|
from core.nodes.netclient import get_net_client
|
||||||
|
@ -36,31 +36,31 @@ if TYPE_CHECKING:
|
||||||
WirelessModelType = Type[WirelessModel]
|
WirelessModelType = Type[WirelessModel]
|
||||||
|
|
||||||
LEARNING_DISABLED: int = 0
|
LEARNING_DISABLED: int = 0
|
||||||
ebtables_lock: threading.Lock = threading.Lock()
|
NFTABLES_LOCK: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
class EbtablesQueue:
|
class NftablesQueue:
|
||||||
"""
|
"""
|
||||||
Helper class for queuing up ebtables commands into rate-limited
|
Helper class for queuing up nftables commands into rate-limited
|
||||||
atomic commits. This improves performance and reliability when there are
|
atomic commits. This improves performance and reliability when there are
|
||||||
many WLAN link updates.
|
many WLAN link updates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# update rate is every 300ms
|
# update rate is every 300ms
|
||||||
rate: float = 0.3
|
rate: float = 0.3
|
||||||
# ebtables
|
atomic_file: str = "/tmp/pycore.nftables.atomic"
|
||||||
atomic_file: str = "/tmp/pycore.ebtables.atomic"
|
chain: str = "forward"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the helper class, but don't start the update thread
|
Initialize the helper class, but don't start the update thread
|
||||||
until a WLAN is instantiated.
|
until a WLAN is instantiated.
|
||||||
"""
|
"""
|
||||||
self.doupdateloop: bool = False
|
self.running: bool = False
|
||||||
self.updatethread: Optional[threading.Thread] = None
|
self.run_thread: Optional[threading.Thread] = None
|
||||||
# this lock protects cmds and updates lists
|
# this lock protects cmds and updates lists
|
||||||
self.updatelock: threading.Lock = threading.Lock()
|
self.lock: threading.Lock = threading.Lock()
|
||||||
# list of pending ebtables commands
|
# list of pending nftables commands
|
||||||
self.cmds: List[str] = []
|
self.cmds: List[str] = []
|
||||||
# list of WLANs requiring update
|
# list of WLANs requiring update
|
||||||
self.updates: List["CoreNetwork"] = []
|
self.updates: List["CoreNetwork"] = []
|
||||||
|
@ -68,192 +68,164 @@ class EbtablesQueue:
|
||||||
# using this queue
|
# using this queue
|
||||||
self.last_update_time: Dict["CoreNetwork", float] = {}
|
self.last_update_time: Dict["CoreNetwork", float] = {}
|
||||||
|
|
||||||
def startupdateloop(self, wlan: "CoreNetwork") -> None:
|
def start(self, net: "CoreNetwork") -> None:
|
||||||
"""
|
"""
|
||||||
Kick off the update loop; only needs to be invoked once.
|
Start thread to listen for updates for the provided network.
|
||||||
|
:param net: network to start checking updates
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with self.updatelock:
|
with self.lock:
|
||||||
self.last_update_time[wlan] = time.monotonic()
|
self.last_update_time[net] = time.monotonic()
|
||||||
if self.doupdateloop:
|
if self.running:
|
||||||
return
|
return
|
||||||
self.doupdateloop = True
|
self.running = True
|
||||||
self.updatethread = threading.Thread(target=self.updateloop, daemon=True)
|
self.run_thread = threading.Thread(target=self.run, daemon=True)
|
||||||
self.updatethread.start()
|
self.run_thread.start()
|
||||||
|
|
||||||
def stopupdateloop(self, wlan: "CoreNetwork") -> None:
|
def stop(self, net: "CoreNetwork") -> None:
|
||||||
"""
|
"""
|
||||||
Kill the update loop thread if there are no more WLANs using it.
|
Stop updates for network, when no networks remain, stop update thread.
|
||||||
|
:param net: network to stop watching updates
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with self.updatelock:
|
with self.lock:
|
||||||
try:
|
self.last_update_time.pop(net, None)
|
||||||
del self.last_update_time[wlan]
|
if self.last_update_time:
|
||||||
except KeyError:
|
return
|
||||||
logger.exception(
|
self.running = False
|
||||||
"error deleting last update time for wlan, ignored before: %s", wlan
|
if self.run_thread:
|
||||||
)
|
self.run_thread.join()
|
||||||
if len(self.last_update_time) > 0:
|
self.run_thread = None
|
||||||
return
|
|
||||||
self.doupdateloop = False
|
|
||||||
if self.updatethread:
|
|
||||||
self.updatethread.join()
|
|
||||||
self.updatethread = None
|
|
||||||
|
|
||||||
def ebatomiccmd(self, cmd: str) -> str:
|
def last_update(self, net: "CoreNetwork") -> float:
|
||||||
"""
|
"""
|
||||||
Helper for building ebtables atomic file command list.
|
Return the time elapsed since this network was last updated.
|
||||||
|
:param net: network node
|
||||||
:param cmd: ebtable command
|
|
||||||
:return: ebtable atomic command
|
|
||||||
"""
|
|
||||||
return f"{EBTABLES} --atomic-file {self.atomic_file} {cmd}"
|
|
||||||
|
|
||||||
def lastupdate(self, wlan: "CoreNetwork") -> float:
|
|
||||||
"""
|
|
||||||
Return the time elapsed since this WLAN was last updated.
|
|
||||||
|
|
||||||
:param wlan: wlan entity
|
|
||||||
:return: elpased time
|
:return: elpased time
|
||||||
"""
|
"""
|
||||||
try:
|
if net in self.last_update_time:
|
||||||
elapsed = time.monotonic() - self.last_update_time[wlan]
|
elapsed = time.monotonic() - self.last_update_time[net]
|
||||||
except KeyError:
|
else:
|
||||||
self.last_update_time[wlan] = time.monotonic()
|
self.last_update_time[net] = time.monotonic()
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
|
|
||||||
return elapsed
|
return elapsed
|
||||||
|
|
||||||
def updated(self, wlan: "CoreNetwork") -> None:
|
def updated(self, net: "CoreNetwork") -> None:
|
||||||
"""
|
"""
|
||||||
Keep track of when this WLAN was last updated.
|
Keep track of when this network was last updated.
|
||||||
|
|
||||||
:param wlan: wlan entity
|
:param net: network node
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
self.last_update_time[wlan] = time.monotonic()
|
self.last_update_time[net] = time.monotonic()
|
||||||
self.updates.remove(wlan)
|
self.updates.remove(net)
|
||||||
|
|
||||||
def updateloop(self) -> None:
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
Thread target that looks for WLANs needing update, and
|
Thread target that looks for networks needing update, and
|
||||||
rate limits the amount of ebtables activity. Only one userspace program
|
rate limits the amount of nftables activity. Only one userspace program
|
||||||
should use ebtables at any given time, or results can be unpredictable.
|
should use nftables at any given time, or results can be unpredictable.
|
||||||
|
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
while self.doupdateloop:
|
while self.running:
|
||||||
with self.updatelock:
|
with self.lock:
|
||||||
for wlan in self.updates:
|
for net in self.updates:
|
||||||
# Check if wlan is from a previously closed session. Because of the
|
if not net.up:
|
||||||
# rate limiting scheme employed here, this may happen if a new session
|
self.updated(net)
|
||||||
# is started soon after closing a previous session.
|
|
||||||
# TODO: if these are WlanNodes, this will never throw an exception
|
|
||||||
try:
|
|
||||||
wlan.session
|
|
||||||
except Exception:
|
|
||||||
# Just mark as updated to remove from self.updates.
|
|
||||||
self.updated(wlan)
|
|
||||||
continue
|
continue
|
||||||
|
if self.last_update(net) > self.rate:
|
||||||
if self.lastupdate(wlan) > self.rate:
|
self.build_cmds(net)
|
||||||
self.buildcmds(wlan)
|
self.commit(net)
|
||||||
self.ebcommit(wlan)
|
self.updated(net)
|
||||||
self.updated(wlan)
|
|
||||||
|
|
||||||
time.sleep(self.rate)
|
time.sleep(self.rate)
|
||||||
|
|
||||||
def ebcommit(self, wlan: "CoreNetwork") -> None:
|
def commit(self, net: "CoreNetwork") -> None:
|
||||||
"""
|
"""
|
||||||
Perform ebtables atomic commit using commands built in the self.cmds list.
|
Commit changes to nftables for the provided network.
|
||||||
|
:param net: network to commit nftables changes
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
# save kernel ebtables snapshot to a file
|
if not self.cmds:
|
||||||
args = self.ebatomiccmd("--atomic-save")
|
return
|
||||||
wlan.host_cmd(args)
|
# write out nft commands to file
|
||||||
|
for cmd in self.cmds:
|
||||||
|
net.host_cmd(f"echo {cmd} >> {self.atomic_file}", shell=True)
|
||||||
|
# read file as atomic change
|
||||||
|
net.host_cmd(f"{NFTABLES} -f {self.atomic_file}")
|
||||||
|
# remove file
|
||||||
|
net.host_cmd(f"rm -f {self.atomic_file}")
|
||||||
|
self.cmds.clear()
|
||||||
|
|
||||||
# modify the table file using queued ebtables commands
|
def update(self, net: "CoreNetwork") -> None:
|
||||||
for c in self.cmds:
|
|
||||||
args = self.ebatomiccmd(c)
|
|
||||||
wlan.host_cmd(args)
|
|
||||||
self.cmds = []
|
|
||||||
|
|
||||||
# commit the table file to the kernel
|
|
||||||
args = self.ebatomiccmd("--atomic-commit")
|
|
||||||
wlan.host_cmd(args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
wlan.host_cmd(f"rm -f {self.atomic_file}")
|
|
||||||
except CoreCommandError:
|
|
||||||
logger.exception("error removing atomic file: %s", self.atomic_file)
|
|
||||||
|
|
||||||
def ebchange(self, wlan: "CoreNetwork") -> None:
|
|
||||||
"""
|
"""
|
||||||
Flag a change to the given WLAN's _linked dict, so the ebtables
|
Flag this network has an update, so the nftables chain will be rebuilt.
|
||||||
chain will be rebuilt at the next interval.
|
:param net: wlan network
|
||||||
|
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with self.updatelock:
|
with self.lock:
|
||||||
if wlan not in self.updates:
|
if net not in self.updates:
|
||||||
self.updates.append(wlan)
|
self.updates.append(net)
|
||||||
|
|
||||||
def buildcmds(self, wlan: "CoreNetwork") -> None:
|
def build_cmds(self, net: "CoreNetwork") -> None:
|
||||||
"""
|
"""
|
||||||
Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
|
Inspect linked nodes for a network, and rebuild the nftables chain commands.
|
||||||
|
:param net: network to build commands for
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with wlan._linked_lock:
|
with net._linked_lock:
|
||||||
if wlan.has_ebtables_chain:
|
if net.has_nftables_chain:
|
||||||
# flush the chain
|
self.cmds.append(f"flush table bridge {net.brname}")
|
||||||
self.cmds.append(f"-F {wlan.brname}")
|
|
||||||
else:
|
else:
|
||||||
wlan.has_ebtables_chain = True
|
net.has_nftables_chain = True
|
||||||
self.cmds.extend(
|
policy = net.policy.value.lower()
|
||||||
[
|
self.cmds.append(f"add table bridge {net.brname}")
|
||||||
f"-N {wlan.brname} -P {wlan.policy.value}",
|
self.cmds.append(
|
||||||
f"-A FORWARD --logical-in {wlan.brname} -j {wlan.brname}",
|
f"add chain bridge {net.brname} {self.chain} {{type filter hook "
|
||||||
]
|
f"forward priority 0\\; policy {policy}\\;}}"
|
||||||
)
|
)
|
||||||
|
# add default rule to accept all traffic not for this bridge
|
||||||
|
self.cmds.append(
|
||||||
|
f"add rule bridge {net.brname} {self.chain} "
|
||||||
|
f"ibriport != {net.brname} accept"
|
||||||
|
)
|
||||||
# rebuild the chain
|
# rebuild the chain
|
||||||
for iface1, v in wlan._linked.items():
|
for iface1, v in net._linked.items():
|
||||||
for oface2, linked in v.items():
|
for iface2, linked in v.items():
|
||||||
if wlan.policy == NetworkPolicy.DROP and linked:
|
policy = None
|
||||||
self.cmds.extend(
|
if net.policy == NetworkPolicy.DROP and linked:
|
||||||
[
|
policy = "accept"
|
||||||
f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j ACCEPT",
|
elif net.policy == NetworkPolicy.ACCEPT and not linked:
|
||||||
f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j ACCEPT",
|
policy = "drop"
|
||||||
]
|
if policy:
|
||||||
|
self.cmds.append(
|
||||||
|
f"add rule bridge {net.brname} {self.chain} "
|
||||||
|
f"iif {iface1.localname} oif {iface2.localname} "
|
||||||
|
f"{policy}"
|
||||||
)
|
)
|
||||||
elif wlan.policy == NetworkPolicy.ACCEPT and not linked:
|
self.cmds.append(
|
||||||
self.cmds.extend(
|
f"add rule bridge {net.brname} {self.chain} "
|
||||||
[
|
f"oif {iface1.localname} iif {iface2.localname} "
|
||||||
f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j DROP",
|
f"{policy}"
|
||||||
f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j DROP",
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# a global object because all WLANs share the same queue
|
# a global object because all networks share the same queue
|
||||||
# cannot have multiple threads invoking the ebtables commnd
|
# cannot have multiple threads invoking the nftables commnd
|
||||||
ebq: EbtablesQueue = EbtablesQueue()
|
nft_queue: NftablesQueue = NftablesQueue()
|
||||||
|
|
||||||
|
|
||||||
def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None:
|
def nftables_cmds(call: Callable[..., str], cmds: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Run ebtable commands.
|
Run nftable commands.
|
||||||
|
|
||||||
:param call: function to call commands
|
:param call: function to call commands
|
||||||
:param cmds: commands to call
|
:param cmds: commands to call
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with ebtables_lock:
|
with NFTABLES_LOCK:
|
||||||
for args in cmds:
|
for cmd in cmds:
|
||||||
call(args)
|
call(cmd)
|
||||||
|
|
||||||
|
|
||||||
class CoreNetwork(CoreNetworkBase):
|
class CoreNetwork(CoreNetworkBase):
|
||||||
|
@ -285,11 +257,11 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
if name is None:
|
if name is None:
|
||||||
name = str(self.id)
|
name = str(self.id)
|
||||||
if policy is not None:
|
if policy is not None:
|
||||||
self.policy = policy
|
self.policy: NetworkPolicy = policy
|
||||||
self.name: Optional[str] = name
|
self.name: Optional[str] = name
|
||||||
sessionid = self.session.short_session_id()
|
sessionid = self.session.short_session_id()
|
||||||
self.brname: str = f"b.{self.id}.{sessionid}"
|
self.brname: str = f"b.{self.id}.{sessionid}"
|
||||||
self.has_ebtables_chain: bool = False
|
self.has_nftables_chain: bool = False
|
||||||
|
|
||||||
def host_cmd(
|
def host_cmd(
|
||||||
self,
|
self,
|
||||||
|
@ -324,9 +296,9 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
:raises CoreCommandError: when there is a command exception
|
:raises CoreCommandError: when there is a command exception
|
||||||
"""
|
"""
|
||||||
self.net_client.create_bridge(self.brname)
|
self.net_client.create_bridge(self.brname)
|
||||||
self.has_ebtables_chain = False
|
self.has_nftables_chain = False
|
||||||
self.up = True
|
self.up = True
|
||||||
ebq.startupdateloop(self)
|
nft_queue.start(self)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -336,23 +308,19 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
"""
|
"""
|
||||||
if not self.up:
|
if not self.up:
|
||||||
return
|
return
|
||||||
ebq.stopupdateloop(self)
|
nft_queue.stop(self)
|
||||||
try:
|
try:
|
||||||
self.net_client.delete_bridge(self.brname)
|
self.net_client.delete_bridge(self.brname)
|
||||||
if self.has_ebtables_chain:
|
if self.has_nftables_chain:
|
||||||
cmds = [
|
cmds = [f"{NFTABLES} delete table bridge {self.brname}"]
|
||||||
f"{EBTABLES} -D FORWARD --logical-in {self.brname} -j {self.brname}",
|
nftables_cmds(self.host_cmd, cmds)
|
||||||
f"{EBTABLES} -X {self.brname}",
|
|
||||||
]
|
|
||||||
ebtablescmds(self.host_cmd, cmds)
|
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logger.exception("error during shutdown")
|
logging.exception("error during shutdown")
|
||||||
# removes veth pairs used for bridge-to-bridge connections
|
# removes veth pairs used for bridge-to-bridge connections
|
||||||
for iface in self.get_ifaces():
|
for iface in self.get_ifaces():
|
||||||
iface.shutdown()
|
iface.shutdown()
|
||||||
self.ifaces.clear()
|
self.ifaces.clear()
|
||||||
self._linked.clear()
|
self._linked.clear()
|
||||||
del self.session
|
|
||||||
self.up = False
|
self.up = False
|
||||||
|
|
||||||
def attach(self, iface: CoreInterface) -> None:
|
def attach(self, iface: CoreInterface) -> None:
|
||||||
|
@ -404,8 +372,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
|
|
||||||
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Unlink two interfaces, resulting in adding or removing ebtables
|
Unlink two interfaces, resulting in adding or removing filtering rules.
|
||||||
filtering rules.
|
|
||||||
|
|
||||||
:param iface1: interface one
|
:param iface1: interface one
|
||||||
:param iface2: interface two
|
:param iface2: interface two
|
||||||
|
@ -415,13 +382,12 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
if not self.linked(iface1, iface2):
|
if not self.linked(iface1, iface2):
|
||||||
return
|
return
|
||||||
self._linked[iface1][iface2] = False
|
self._linked[iface1][iface2] = False
|
||||||
|
nft_queue.update(self)
|
||||||
ebq.ebchange(self)
|
|
||||||
|
|
||||||
def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Link two interfaces together, resulting in adding or removing
|
Link two interfaces together, resulting in adding or removing
|
||||||
ebtables filtering rules.
|
filtering rules.
|
||||||
|
|
||||||
:param iface1: interface one
|
:param iface1: interface one
|
||||||
:param iface2: interface two
|
:param iface2: interface two
|
||||||
|
@ -431,8 +397,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
if self.linked(iface1, iface2):
|
if self.linked(iface1, iface2):
|
||||||
return
|
return
|
||||||
self._linked[iface1][iface2] = True
|
self._linked[iface1][iface2] = True
|
||||||
|
nft_queue.update(self)
|
||||||
ebq.ebchange(self)
|
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
||||||
|
@ -986,7 +951,7 @@ class WlanNode(CoreNetwork):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
super().startup()
|
super().startup()
|
||||||
ebq.ebchange(self)
|
nft_queue.update(self)
|
||||||
|
|
||||||
def attach(self, iface: CoreInterface) -> None:
|
def attach(self, iface: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -63,8 +63,8 @@ eval "$ifcommand" | awk '
|
||||||
/b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
/b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
||||||
'
|
'
|
||||||
|
|
||||||
ebtables -L FORWARD | awk '
|
nft list ruleset | awk '
|
||||||
/^-.*b\./ {print "removing ebtables " $0; system("ebtables -D FORWARD " $0); print "removing ebtables chain " $4; system("ebtables -X " $4);}
|
$3 ~ /^b\./ {print "removing nftables " $3; system("nft delete table bridge " $3);}
|
||||||
'
|
'
|
||||||
|
|
||||||
rm -rf /tmp/pycore*
|
rm -rf /tmp/pycore*
|
||||||
|
|
|
@ -51,8 +51,8 @@ filesystem in CORE.
|
||||||
|
|
||||||
CORE combines these namespaces with Linux Ethernet bridging to form networks.
|
CORE combines these namespaces with Linux Ethernet bridging to form networks.
|
||||||
Link characteristics are applied using Linux Netem queuing disciplines.
|
Link characteristics are applied using Linux Netem queuing disciplines.
|
||||||
Ebtables is Ethernet frame filtering on Linux bridges. Wireless networks are
|
Nftables provides Ethernet frame filtering on Linux bridges. Wireless networks are
|
||||||
emulated by controlling which interfaces can send and receive with ebtables
|
emulated by controlling which interfaces can send and receive with nftables
|
||||||
rules.
|
rules.
|
||||||
|
|
||||||
## Prior Work
|
## Prior Work
|
||||||
|
|
|
@ -104,7 +104,7 @@ vcmd -c /tmp/pycore.50160/n1 -- /sbin/ip -4 ro
|
||||||
|
|
||||||
A script named *core-cleanup* is provided to clean up any running CORE emulations. It will attempt to kill any
|
A script named *core-cleanup* is provided to clean up any running CORE emulations. It will attempt to kill any
|
||||||
remaining vnoded processes, kill any EMANE processes, remove the :file:`/tmp/pycore.*` session directories, and remove
|
remaining vnoded processes, kill any EMANE processes, remove the :file:`/tmp/pycore.*` session directories, and remove
|
||||||
any bridges or *ebtables* rules. With a *-d* option, it will also kill any running CORE daemon.
|
any bridges or *nftables* rules. With a *-d* option, it will also kill any running CORE daemon.
|
||||||
|
|
||||||
### netns command
|
### netns command
|
||||||
|
|
||||||
|
@ -121,5 +121,5 @@ ip link show type bridge
|
||||||
# view the netem rules used for applying link effects
|
# view the netem rules used for applying link effects
|
||||||
tc qdisc show
|
tc qdisc show
|
||||||
# view the rules that make the wireless LAN work
|
# view the rules that make the wireless LAN work
|
||||||
ebtables -L
|
nft list ruleset
|
||||||
```
|
```
|
||||||
|
|
|
@ -172,7 +172,7 @@ will draw the link with a dashed line.
|
||||||
Wireless nodes, i.e. those connected to a WLAN node, can be assigned to
|
Wireless nodes, i.e. those connected to a WLAN node, can be assigned to
|
||||||
different emulation servers and participate in the same wireless network
|
different emulation servers and participate in the same wireless network
|
||||||
only if an EMANE model is used for the WLAN. The basic range model does
|
only if an EMANE model is used for the WLAN. The basic range model does
|
||||||
not work across multiple servers due to the Linux bridging and ebtables
|
not work across multiple servers due to the Linux bridging and nftables
|
||||||
rules that are used.
|
rules that are used.
|
||||||
|
|
||||||
**NOTE: The basic range wireless model does not support distributed emulation,
|
**NOTE: The basic range wireless model does not support distributed emulation,
|
||||||
|
|
|
@ -544,7 +544,7 @@ on platform. See the table below for a brief overview of wireless model types.
|
||||||
|
|
||||||
|Model|Type|Supported Platform(s)|Fidelity|Description|
|
|Model|Type|Supported Platform(s)|Fidelity|Description|
|
||||||
|-----|----|---------------------|--------|-----------|
|
|-----|----|---------------------|--------|-----------|
|
||||||
|Basic|on/off|Linux|Low|Ethernet bridging with ebtables|
|
|Basic|on/off|Linux|Low|Ethernet bridging with nftables|
|
||||||
|EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types|
|
|EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types|
|
||||||
|
|
||||||
To quickly build a wireless network, you can first place several router nodes
|
To quickly build a wireless network, you can first place several router nodes
|
||||||
|
|
|
@ -15,20 +15,14 @@ containers, as a general rule you should select a machine having as much RAM and
|
||||||
|
|
||||||
* Linux Kernel v3.3+
|
* Linux Kernel v3.3+
|
||||||
* iproute2 4.5+ is a requirement for bridge related commands
|
* iproute2 4.5+ is a requirement for bridge related commands
|
||||||
* ebtables not backed by nftables
|
* nftables compatible kernel and nft command line tool
|
||||||
|
|
||||||
### Supported Linux Distributions
|
### Supported Linux Distributions
|
||||||
Plan is to support recent Ubuntu and CentOS LTS releases.
|
Plan is to support recent Ubuntu and CentOS LTS releases.
|
||||||
|
|
||||||
Verified:
|
Verified:
|
||||||
* Ubuntu - 18.04, 20.04
|
* Ubuntu - 18.04, 20.04
|
||||||
* CentOS - 7.8, 8.0*
|
* CentOS - 7.8, 8.0
|
||||||
|
|
||||||
> **NOTE:** Ubuntu 20.04 requires installing legacy ebtables for WLAN
|
|
||||||
> functionality
|
|
||||||
|
|
||||||
> **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not
|
|
||||||
> function properly
|
|
||||||
|
|
||||||
> **NOTE:** CentOS 8 does not have the netem kernel mod available by default
|
> **NOTE:** CentOS 8 does not have the netem kernel mod available by default
|
||||||
|
|
||||||
|
|
|
@ -521,7 +521,7 @@ on platform. See the table below for a brief overview of wireless model types.
|
||||||
|
|
||||||
|Model|Type|Supported Platform(s)|Fidelity|Description|
|
|Model|Type|Supported Platform(s)|Fidelity|Description|
|
||||||
|-----|----|---------------------|--------|-----------|
|
|-----|----|---------------------|--------|-----------|
|
||||||
|Basic|on/off|Linux|Low|Ethernet bridging with ebtables|
|
|Basic|on/off|Linux|Low|Ethernet bridging with nftables|
|
||||||
|EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types|
|
|EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types|
|
||||||
|
|
||||||
To quickly build a wireless network, you can first place several router nodes
|
To quickly build a wireless network, you can first place several router nodes
|
||||||
|
|
16
tasks.py
16
tasks.py
|
@ -159,14 +159,14 @@ def check_existing_core(c: Context, hide: bool) -> None:
|
||||||
def install_system(c: Context, os_info: OsInfo, hide: bool) -> None:
|
def install_system(c: Context, os_info: OsInfo, hide: bool) -> None:
|
||||||
if os_info.like == OsLike.DEBIAN:
|
if os_info.like == OsLike.DEBIAN:
|
||||||
c.run(
|
c.run(
|
||||||
"sudo apt install -y automake pkg-config gcc libev-dev ebtables "
|
"sudo apt install -y automake pkg-config gcc libev-dev nftables "
|
||||||
"iproute2 ethtool tk python3-tk bash",
|
"iproute2 ethtool tk python3-tk bash",
|
||||||
hide=hide
|
hide=hide
|
||||||
)
|
)
|
||||||
elif os_info.like == OsLike.REDHAT:
|
elif os_info.like == OsLike.REDHAT:
|
||||||
c.run(
|
c.run(
|
||||||
"sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ "
|
"sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ "
|
||||||
"libev-devel iptables-ebtables iproute python3-devel python3-tkinter "
|
"libev-devel nftables iproute python3-devel python3-tkinter "
|
||||||
"tk ethtool make bash",
|
"tk ethtool make bash",
|
||||||
hide=hide
|
hide=hide
|
||||||
)
|
)
|
||||||
|
@ -179,18 +179,6 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None:
|
||||||
print("sudo yum update")
|
print("sudo yum update")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# attempt to setup legacy ebtables when an nftables based version is found
|
|
||||||
r = c.run("ebtables -V", hide=hide)
|
|
||||||
if "nf_tables" in r.stdout:
|
|
||||||
if not c.run(
|
|
||||||
"sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy",
|
|
||||||
warn=True,
|
|
||||||
hide=hide
|
|
||||||
):
|
|
||||||
print(
|
|
||||||
"\nWARNING: unable to setup ebtables-legacy, WLAN will not work"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def install_grpcio(c: Context, hide: bool) -> None:
|
def install_grpcio(c: Context, hide: bool) -> None:
|
||||||
c.run(
|
c.run(
|
||||||
|
|
Loading…
Reference in a new issue