Merge pull request 'logging' (#16) from logging into master

Reviewed-on: https://git.disroot.org/pranav/naxalnet/pulls/16

See CHANGELOG and issue #13 for details
This commit is contained in:
Pranav Jerry 2021-09-05 08:33:32 +00:00
commit 33156e6a8d
17 changed files with 268 additions and 76 deletions

151
.gitignore vendored
View File

@ -1,10 +1,145 @@
.DS_Store
.idea
*.log
tmp/
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod] *.py[cod]
*.egg* *$py.class
build
htmlcov # C extensions
__pycache__ *.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# End of https://www.toptal.com/developers/gitignore/api/python

View File

@ -1,5 +1,10 @@
# Changelog # Changelog
## [Unreleased][] - 2021-09-05
- Now logs to systemd journal. New dependency - `python3-systemd`
- Fixed dependency order in systemd service
## [v0.3.0][] - 2021-08-19 ## [v0.3.0][] - 2021-08-19
- Support for arguments - Support for arguments

View File

@ -17,7 +17,7 @@ network.
<!-- NOTE TO ACTIVISTS <!-- NOTE TO ACTIVISTS
Running this program in the world's largest partly-free democracy Running this program in the world's largest (partly-free) democracy
may result in you getting arrested under the UAPA, and not may result in you getting arrested under the UAPA, and not
getting bail because of false evidence planted in your phone by getting bail because of false evidence planted in your phone by
Pegasus, or by a forensic lab in Gujarat. Pegasus, or by a forensic lab in Gujarat.
@ -41,16 +41,17 @@ with anyone currently at risk of death in overcrowded prisons.
## Requirements ## Requirements
- [systemd v248 or more][batman-systemd] - [systemd-networkd v248 or more][batman-systemd]
- Linux kernel with batman-adv module - Linux kernel with batman-adv module
- [iwd][] - [iwd][] for controlling the WiFi adapter
- python - python3
- python-setuptools (for building and installing) - python3-setuptools, for building and installing naxalnet
- [dasbus][] - python3-systemd, for logging to systemd journal
- WiFi adapter with ad-hoc support - [dasbus][], for communicating with iwd
- two or more computers, or laptops with WiFi adapter, called nodes - two or more machines with a WiFi adapter having ad-hoc support, called
nodes or peers
- batctl (optional, for debugging) - batctl (optional, for debugging)
- python pip (optional, for uninstalling) - python3-pip (optional, for `make uninstall` to work)
## Installing ## Installing
@ -74,17 +75,17 @@ you need it.
naxalnet is not packaged for Ubuntu, so you will have to build naxalnet is not packaged for Ubuntu, so you will have to build
and install it manually. and install it manually.
Currently, only the [unreleased 21.10][ubuntu-systemd] comes with the Currently, only the **unreleased 21.10** comes with the
required version of systemd. Therefore, naxalnet won't work on Ubuntu required version of systemd. Therefore, naxalnet **won't work on Ubuntu
21.04 or older. 21.04 or older**.
<!-- TODO: remove this message when systemd 248 arrives in 21.04 --> <!-- TODO: remove this message if systemd 248 arrives in 21.04 -->
Install the requirements from the Ubuntu repositories: Install the requirements from the Ubuntu repositories:
```sh ```sh
# batctl is optional # batctl is optional
sudo apt install systemd python3-pip iwd batctl build-essential sudo apt install python3-pip python3-systemd iwd batctl build-essential
# Now, install dasbus with pip # Now, install dasbus with pip
sudo pip3 install dasbus sudo pip3 install dasbus
``` ```
@ -96,11 +97,11 @@ Now follow the instructions in the
naxalnet is not packaged for Fedora, so it should be installed naxalnet is not packaged for Fedora, so it should be installed
manually. naxalnet requires atleast systemd v248 which is only manually. naxalnet requires atleast systemd v248 which is only
available on Fedora 34 and above. Install the dependencies: available on **Fedora 34 and above**. Install the dependencies:
```sh ```sh
# systemd-resolved may be required for rawhide # systemd-resolved may be required for rawhide
sudo dnf install systemd-networkd iwd python3-dasbus python3-setuptools sudo dnf install systemd-networkd iwd python3-dasbus python3-setuptools python3-systemd
``` ```
Now head over to the [next section][install-manual] to install naxalnet. Now head over to the [next section][install-manual] to install naxalnet.
@ -221,10 +222,10 @@ naxalnet --help
## How it works ## How it works
There are three modes commonly supported by WiFi adapters - There are three modes commonly supported by WiFi adapters - `ap` (WiFi
`ap` (WiFi hotspot), `station` (for joining WiFi networks) and `ad-hoc` hotspot), `station` (for joining WiFi networks) and `ad-hoc` (for
(for decentralised networks). There are some other modes too, decentralised networks). There are other modes supported by some WiFi
like `p2p` (WiFi direct), but we won't go into the details. adapters too, like `p2p` (WiFi direct), but this program doesn't use them.
naxalnet uses two modes - `ad-hoc` and `ap`, for connecting to the naxalnet uses two modes - `ad-hoc` and `ap`, for connecting to the
mesh. naxalnet uses iwd to start an `ad-hoc` network and configures mesh. naxalnet uses iwd to start an `ad-hoc` network and configures
@ -234,8 +235,8 @@ naxalnet starts an ad-hoc on one of them and an ap on the other.
You can use the ap for connecting mobile phones and other devices You can use the ap for connecting mobile phones and other devices
to the mesh network. to the mesh network.
Read the code to learn the details. Read the code and the documentation to learn the details.
See [systemd-networkd](systemd-networkd) to see how See the directory [systemd-networkd](systemd-networkd) to see how
systemd-networkd configures the network. systemd-networkd configures the network.
## Use cases ## Use cases
@ -245,15 +246,22 @@ systemd-networkd configures the network.
naxalnet can be used to share connections in remote areas. naxalnet can be used to share connections in remote areas.
You need at least one device with internet access. You need at least one device with internet access.
<!--
### Malign democracies and well-established institutions
Withheld due to national security reasons.
-->
### Internet shutdown ### Internet shutdown
You can communicate with neighbouring devices running naxalnet, You can communicate with neighbouring devices running naxalnet, using
using services like [IPFS][], [Jami][], [Secure Scuttlebutt][ssb] services like [IPFS][], [Jami][], [Secure Scuttlebutt][ssb] and others
and others which can work on an intranet. which can work on an intranet. They should be installed on your
They should be installed on your machine _before_ your friendly machine _before_ your friendly democratic government announces an
democratic government announces an [internet shutdown][], since you [internet shutdown][], since you cannot download and install them
cannot download and install them during a shutdown. during a shutdown. When a shutdown occurs, [enable naxalnet][enablenx].
When a shutdown occurs, [enable naxalnet][enablenx]
## Uninstalling ## Uninstalling
@ -273,10 +281,16 @@ See [HACKING.md](HACKING.md)
## Similar projects ## Similar projects
The following projects are similar to naxalnet, but are not designed Many projects make setting up B.A.T.M.A.N. Advanced mesh networks with
to be used in a machine with WiFi adapter. If you live in WiFi routers easier. They are easier to setup and are more
an area where the materials required for any of them are easily configurable. But naxalnet is different from them. It simplifies
available, consider using them instead of naxalnet. setting up mesh networks with _laptops or computers_, and is not
designed to work with routers.
The following projects does something similar to naxalnet, but
requires special devices or routers to work. If you live in an area
where the materials required for any of them are easily available,
consider using them instead of naxalnet.
- [LibreMesh][]: framework for OpenWrt-based firmwares - [LibreMesh][]: framework for OpenWrt-based firmwares
- [disaster.radio][]: solar-powered communications network - [disaster.radio][]: solar-powered communications network
@ -306,6 +320,5 @@ See [LICENSE](LICENSE) for the complete version of the license.
[iwd]: https://iwd.wiki.kernel.org "WiFi daemon" [iwd]: https://iwd.wiki.kernel.org "WiFi daemon"
[free-sw]: https://gnu.org/philosophy/free-sw.html "What is free software?" [free-sw]: https://gnu.org/philosophy/free-sw.html "What is free software?"
[enablenx]: #running-at-boot [enablenx]: #running-at-boot
[ubuntu-systemd]: https://packages.ubuntu.com/impish/systemd
[requirements]: #requirements [requirements]: #requirements
[install-manual]: #manually [install-manual]: #manually

View File

@ -7,6 +7,8 @@ Requires=systemd-networkd.service
Requires=iwd.service Requires=iwd.service
Wants=systemd-resolved.service Wants=systemd-resolved.service
After=iwd.service After=iwd.service
After=systemd-networkd.service
After=systemd-resolved.service
# Stops NetworkManager and wpa_supplicant if already running # Stops NetworkManager and wpa_supplicant if already running
Conflicts=NetworkManager.service Conflicts=NetworkManager.service
Conflicts=wpa_supplicant.service Conflicts=wpa_supplicant.service
@ -15,6 +17,7 @@ After=NetworkManager.service
After=wpa_supplicant.service After=wpa_supplicant.service
[Service] [Service]
# TODO: change to notify when naxalnet becomes a daemon
Type=oneshot Type=oneshot
RemainAfterExit=yes RemainAfterExit=yes
Restart=on-failure Restart=on-failure
@ -27,20 +30,14 @@ ExecStartPre=/usr/bin/sleep 2
ExecStart=/usr/bin/naxalnet ExecStart=/usr/bin/naxalnet
# Reload systemd-networkd after naxalnet exits # Reload systemd-networkd after naxalnet exits
ExecStartPost=/usr/bin/networkctl reload ExecStartPost=/usr/bin/networkctl reload
# Delete all files in /run/systemd/network # Delete all files starting with mesh.* in /run/systemd/network
ExecStop=/usr/bin/find /run/systemd/network -type f -delete ExecStop=/usr/bin/find /run/systemd/network -type f -delete -name "mesh.*"
# Delete the interfaces created... # Delete the interfaces created...
ExecStopPost=/usr/bin/networkctl delete bridge0 bat0 ExecStopPost=/usr/bin/networkctl delete bridge0 bat0
# ... and reload the configuration files. # ... and reload the configuration files.
ExecStopPost=/usr/bin/networkctl reload ExecStopPost=/usr/bin/networkctl reload
# Make python flush messages instead of buffering.
# When reading the systemd journal, we need to see the messages # Disable python buffering
# in the exact order. There will suddenly be a lot of messages
# from naxalnet, networkd, iwd and resolved. When buffering is on,
# we won't see the messages from naxalnet in the order they were
# printed. This, among other problems, make it hard while debugging.
# This will no longer be needed when naxalnet sends its messages
# directly to the systtemd journal instead of stdout.
Environment=PYTHONUNBUFFERED=1 Environment=PYTHONUNBUFFERED=1
[Install] [Install]

View File

@ -36,4 +36,4 @@ See README.md for documentation.
# #
# In case you forgot to change the version, skip the number # In case you forgot to change the version, skip the number
# and put the next number in the next commit. # and put the next number in the next commit.
__version__ = "0.3.0" __version__ = "0.3.0a2"

View File

@ -46,6 +46,7 @@ from pathlib import Path
from configparser import ConfigParser from configparser import ConfigParser
from argparse import ArgumentParser, Namespace from argparse import ArgumentParser, Namespace
from naxalnet.default import CONFIG, CONFIG_FILES, CONFIG_DIRS from naxalnet.default import CONFIG, CONFIG_FILES, CONFIG_DIRS
from naxalnet.log import logger
def get_config_files(): def get_config_files():
@ -54,6 +55,7 @@ def get_config_files():
of files that exists as pathlib.Path objects of files that exists as pathlib.Path objects
""" """
config_files = [] config_files = []
for directory in CONFIG_DIRS: for directory in CONFIG_DIRS:
path = Path(directory) path = Path(directory)
if path.exists(): if path.exists():
@ -68,12 +70,14 @@ def parse_config():
Parse all configuration files, with the values in Parse all configuration files, with the values in
default.py as fallback default.py as fallback
""" """
logger.debug("Parsing config files")
parser = ConfigParser() parser = ConfigParser()
# encoded defaults # encoded defaults
parser.read_dict(CONFIG) parser.read_dict(CONFIG)
# read config files # read config files
files = get_config_files() files = get_config_files()
for i in files: for i in files:
logger.debug("Reading config file %s", str(i))
parser.read_file(i.open()) parser.read_file(i.open())
return parser return parser
@ -141,4 +145,5 @@ def parse_args() -> Namespace:
help="prints the version and exit", help="prints the version and exit",
) )
logger.debug("Parsing arguments")
return parser.parse_args() return parser.parse_args()

View File

@ -58,8 +58,8 @@ and what they mean:
- node: a machine that runs naxalnet and is therefore - node: a machine that runs naxalnet and is therefore
connected to the mesh. connected to the mesh.
""" """
from dasbus.connection import SystemMessageBus from dasbus.connection import SystemMessageBus
from naxalnet.log import logger
IWD_BUS = "net.connman.iwd" IWD_BUS = "net.connman.iwd"
IWD_ROOT_PATH = "/" IWD_ROOT_PATH = "/"
@ -165,8 +165,8 @@ class Device:
adapter: name of adapter (str) adapter: name of adapter (str)
""" """
def __init__(self, name: str, bus=SystemMessageBus()): def __init__(self, name: str):
self._iwd = IWD(bus) self._iwd = IWD()
self._bus = self._iwd._bus self._bus = self._iwd._bus
self._path = self._iwd.get_device_path_from_name(name) self._path = self._iwd.get_device_path_from_name(name)
self.reload() self.reload()
@ -181,11 +181,13 @@ class Device:
def power_on(self): def power_on(self):
"""Turn on the device and reload the proxy""" """Turn on the device and reload the proxy"""
self._proxy.Powered = True self._proxy.Powered = True
logger.debug("Powered on %s", self.name)
self.reload() self.reload()
def power_off(self): def power_off(self):
"""Turn off the device and reload the proxy""" """Turn off the device and reload the proxy"""
self._proxy.Powered = False self._proxy.Powered = False
logger.debug("Powered off %s", self.name)
self.reload() self.reload()
def reload(self): def reload(self):
@ -225,6 +227,7 @@ class Device:
def set_mode(self, mode: str): def set_mode(self, mode: str):
"""change the device mode to mode""" """change the device mode to mode"""
self._proxy.Mode = mode self._proxy.Mode = mode
logger.debug("Set mode on %s to %s", self.name, mode)
self.reload() self.reload()
def start_adhoc_open(self, name: str): def start_adhoc_open(self, name: str):
@ -242,11 +245,13 @@ class Device:
# Stop adhoc if already started # Stop adhoc if already started
self.stop_adhoc() self.stop_adhoc()
logger.debug("Starting ad-hoc on %s", self.name)
self._proxy.StartOpen(name) self._proxy.StartOpen(name)
def stop_adhoc(self): def stop_adhoc(self):
"""stop adhoc if adhoc is started""" """stop adhoc if adhoc is started"""
if self.is_adhoc_started(): if self.is_adhoc_started():
logger.debug("Stopping ad-hoc on %s", self.name)
self._proxy.Stop() self._proxy.Stop()
self.reload() self.reload()
@ -265,11 +270,15 @@ class Device:
# Stop ap if already started # Stop ap if already started
self.stop_ap() self.stop_ap()
logger.debug(
"Starting ap on %s with ssid %s and password %s", self.name, ssid, passwd
)
self._proxy.Start(ssid, passwd) self._proxy.Start(ssid, passwd)
def stop_ap(self): def stop_ap(self):
"""stop ap if an ap is started""" """stop ap if an ap is started"""
if self.is_ap_started(): if self.is_ap_started():
logger.debug("Stopping ap on %s", self.name)
self._proxy.Stop() self._proxy.Stop()
self.reload() self.reload()
@ -277,8 +286,8 @@ class Device:
class Adapter: class Adapter:
"""represents an adapter as a python object""" """represents an adapter as a python object"""
def __init__(self, name: str, bus=SystemMessageBus()): def __init__(self, name: str):
self._iwd = IWD(bus) self._iwd = IWD()
self._bus = self._iwd._bus self._bus = self._iwd._bus
self._path = self._iwd.get_adapter_path_from_name(name) self._path = self._iwd.get_adapter_path_from_name(name)
# Initialise self._proxy # Initialise self._proxy
@ -300,10 +309,12 @@ class Adapter:
def power_on(self): def power_on(self):
"""power on the adapter""" """power on the adapter"""
self._proxy.Powered = True self._proxy.Powered = True
logger.debug("Powered on adapter %s", self.name)
def power_off(self): def power_off(self):
"""power off the adapter""" """power off the adapter"""
self._proxy.Powered = False self._proxy.Powered = False
logger.debug("Powered off adapter %s", self.name)
def supports_mode(self, mode: str) -> bool: def supports_mode(self, mode: str) -> bool:
""" """

16
naxalnet/log.py Normal file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
"""
log.py
------
This file contains the logger object, which is required for logging
to the systemd journal
"""
import logging
from systemd import journal
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(journal.JournalHandler())

View File

@ -34,6 +34,7 @@ from dasbus.error import DBusError
from naxalnet import __version__ from naxalnet import __version__
from naxalnet.iwd import Adapter, Device, IWD from naxalnet.iwd import Adapter, Device, IWD
from naxalnet.config import parse_args from naxalnet.config import parse_args
from naxalnet.log import logger
def copy_files(args): def copy_files(args):
@ -44,7 +45,7 @@ def copy_files(args):
See man:systemd.network(5) See man:systemd.network(5)
""" """
print("Copying network config files") logger.info("Copying network config files")
dest = Path(args.networkd_runtime_dir) dest = Path(args.networkd_runtime_dir)
src = Path(args.networkd_config_dir) src = Path(args.networkd_config_dir)
@ -74,10 +75,13 @@ def setup_devices(args):
# ad-hoc or ap. Many adapters will support both, # ad-hoc or ap. Many adapters will support both,
# so we will prioritise ad-hoc over ap. # so we will prioritise ad-hoc over ap.
device = Device(i) device = Device(i)
logger.debug("Found device %s", device.name)
adapter = Adapter(device.adapter) adapter = Adapter(device.adapter)
if adapter.supports_mode("ad-hoc"): if adapter.supports_mode("ad-hoc"):
logger.debug("The device %s can be used for ad-hoc", device.name)
adhoc_devices.append(i) adhoc_devices.append(i)
if adapter.supports_mode("ap"): if adapter.supports_mode("ap"):
logger.debug("The device %s can be used for ap", device.name)
ap_devices.append(i) ap_devices.append(i)
if len(adhoc_devices) != 0: if len(adhoc_devices) != 0:
@ -88,11 +92,12 @@ def setup_devices(args):
# So we will remove adhoc_device from ap_devices if it exists there # So we will remove adhoc_device from ap_devices if it exists there
if adhoc_device.name in ap_devices: if adhoc_device.name in ap_devices:
ap_devices.remove(adhoc_device.name) ap_devices.remove(adhoc_device.name)
print("Starting mesh on", adhoc_device.name) logger.info("Starting mesh on %s", adhoc_device.name)
# Turn on adapter if it is off # Turn on adapter if it is off
# See issue #9 # See issue #9
adhoc_adapter = Adapter(adhoc_device.adapter) adhoc_adapter = Adapter(adhoc_device.adapter)
if not adhoc_adapter.is_powered_on(): if not adhoc_adapter.is_powered_on():
logger.debug("Adapter %s is off. Turning on", adhoc_adapter.name)
adhoc_adapter.power_on() adhoc_adapter.power_on()
adhoc_device.reload() adhoc_device.reload()
adhoc_device.start_adhoc_open(args.adhoc_name) adhoc_device.start_adhoc_open(args.adhoc_name)
@ -100,16 +105,20 @@ def setup_devices(args):
# ie, we have more devices # ie, we have more devices
if len(ap_devices) != 0: if len(ap_devices) != 0:
ap_device = Device(ap_devices.pop()) ap_device = Device(ap_devices.pop())
print("Starting WiFi Access point on", ap_device.name) logger.info("Starting WiFi Access Point on %s", ap_device.name)
print('Use "naxalnet --print-wifi" to get password') logger.info("Use naxalnet --print-wifi to get password")
# Turn on adapter if it is off # Turn on adapter if it is off
# See issue #9 # See issue #9
ap_adapter = Adapter(ap_device.adapter) ap_adapter = Adapter(ap_device.adapter)
if not ap_adapter.is_powered_on(): if not ap_adapter.is_powered_on():
logger.debug("Adapter %s is off. Turning on", ap_adapter.name)
ap_adapter.power_on() ap_adapter.power_on()
ap_adapter.reload() ap_adapter.reload()
ap_device.start_ap(args.ap_ssid, args.ap_passwd) ap_device.start_ap(args.ap_ssid, args.ap_passwd)
# naxalnet prints Bye if no errors occured
logger.info("Bye")
def print_wifi(args): def print_wifi(args):
""" """
@ -144,14 +153,13 @@ def here_be_dragons():
try: try:
copy_files(args) copy_files(args)
except PermissionError as error: except PermissionError as error:
print(error) logger.error("Cannot copy file: %s", error)
sys.exit("Make sure you are root") sys.exit(3)
try: try:
setup_devices(args) setup_devices(args)
except DBusError as error: except DBusError:
print(error) logger.exception("Error while communicating with iwd")
sys.exit("An error occured while communicating with iwd") sys.exit(4)
# naxalnet will print Bye if no errors occured logger.debug("Finished.")
print("Bye")

View File

@ -19,9 +19,11 @@ packages = find:
python_requires = >=3.6 python_requires = >=3.6
install_requires = install_requires =
dasbus dasbus
# pathlib, configparser and argparse are in the standard library
configparser configparser
pathlib pathlib
argparse argparse
systemd
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
@ -33,10 +35,10 @@ lib/systemd/system =
/etc/naxalnet = /etc/naxalnet =
naxalnet.conf.example naxalnet.conf.example
share/naxalnet/networkd = share/naxalnet/networkd =
systemd-networkd/01-batman.netdev systemd-networkd/mesh.01-batman.netdev
systemd-networkd/02-bridge.netdev systemd-networkd/mesh.02-bridge.netdev
systemd-networkd/03-wireless-ad-hoc.network systemd-networkd/mesh.03-wireless-ad-hoc.network
systemd-networkd/04-batman.network systemd-networkd/mesh.04-batman.network
systemd-networkd/05-wireless-ap.network systemd-networkd/mesh.05-wireless-ap.network
systemd-networkd/06-eth.network systemd-networkd/mesh.06-eth.network
systemd-networkd/07-bridge.network systemd-networkd/mesh.07-bridge.network