diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f339ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.idea +*.log +tmp/ + +*.py[cod] +*.egg* +build +htmlcov +__pycache__ diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..6ddeed4 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,27 @@ +# Hacking + +Everyone +is welcome to [hack][] naxalnet. See below for how to hack. + +## Reporting issues and suggesting ideas + +To report a bug or suggest an idea, create a new issue at + with a +relevant label. + +## Contribute code + +To push to this repo, you need your username to be in the +contributors list. +To add you as a contributor, email any of the authors with +your username: + +- `echo yvoervangbe cyhf akyarg ng qvfebbg qbg bet | tr 'A-Za-z' 'N-ZA-Mn-za-m' | sed 's/plus/+/' | sed 's/ at /@/' | sed 's/dot/./' | tr -d ' '` + +## Packaging + +naxalnet needs distro packages in Debian, Fedora, openSUSE, +and nixos. If you know/like to package it in your distro, +post to issue #6. + +[hack]: https://catb.org/jargon/html/H/hack.html diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1041071 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include LICENSE +include README.md +include naxalnet.service +include systemd-networkd/* diff --git a/Makefile b/Makefile index b8dfece..f81213a 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,8 @@ -PREFIX := /usr +# This makefile uses setup.py under the hood +all: build -install: naxalnet - install -d $(DESTDIR)$(PREFIX)/bin - install -d $(DESTDIR)$(PREFIX)/lib/systemd/system/ - install -m644 naxalnet.service $(DESTDIR)$(PREFIX)/lib/systemd/system/ - install naxalnet $(DESTDIR)$(PREFIX)/bin/ - install -d $(DESTDIR)$(PREFIX)/share/naxalnet/networkd - install -m644 systemd-networkd/* $(DESTDIR)$(PREFIX)/share/naxalnet/networkd +build: + python setup.py build -testdeps: - @for i in networkctl systemctl python3; do \ - echo "Checking for $$i"; \ - which $$i > /dev/null && echo " $$i found" || \ - (echo " $$i not found"; exit 1); \ - done +install: build + python setup.py install --root="$(DESTDIR)/" --optimize=1 --skip-build diff --git a/README.md b/README.md index 23e78bc..1900aec 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ or anyone else advocating for their rights). no error then you already have it) - iwd (for starting ad-hoc network) - python3 +- python-setuptools (for building) - [python-dasbus][] - wifi adapter with ad-hoc support - two or more computers with wifi adapter, called nodes @@ -62,6 +63,14 @@ git clone https://git.disroot.org/pranav/naxalnet.git cd naxalnet ``` + + Run `sudo make install` to install naxalnet. This will install naxalnet in `/usr/bin/naxalnet`. @@ -152,12 +161,12 @@ and others which can work on an intranet. ## Uninstalling -If you installed naxalnet manually, there is now way to uninstall +If you installed naxalnet manually, there is no way to uninstall than manually removing the files: ```sh -sudo rm -rf /usr/{bin,share}/naxalnet \ -/usr/lib/systemd/system/naxalnet.service +sudo pip uninstall naxalnet +sudo rm -rf /usr/share/naxalnet* /usr/lib/systemd/system/naxalnet.service ``` ## Similar projects @@ -181,8 +190,6 @@ the Free Software Foundation, either version 3 of the License, or See [LICENSE](LICENSE) for the complete version of the license. -This project is in alpha stage. Documentation is incomplete. - [batman-adv]: https://www.open-mesh.org/projects/batman-adv/wiki [ipfs]: https://ipfs.io [jami]: https://jami.net diff --git a/naxalnet b/naxalnet deleted file mode 100755 index 57d6f49..0000000 --- a/naxalnet +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 - -""" -Setup a working BATMAN Advanced network -with systemd-networkd and iwd -""" - -# Copyright (C) 2021 The Authors - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import sys -from pathlib import Path -from shutil import copy -from dasbus.connection import SystemMessageBus -from dasbus.error import DBusError - -NETWORKD_CONFIGS = "/usr/share/naxalnet/networkd" -NETWORKD_VOLATILE_DIR = "/run/systemd/network" -ADHOC_SSID = "HelloWorld" -AP_SSID = "NaxalNet" -AP_PASSWD = "naxalnet256" -# Copy networkd configs to volatile dir. -# The D-Bus API does not support creating new interfaces -# or linking to bridges. So we use config files. -# See man:systemd.network(5) -try: - print("Copying network config files") - dest = Path(NETWORKD_VOLATILE_DIR) - src = Path(NETWORKD_CONFIGS) - - # Create the volatile directory if it doesn't exist - dest.mkdir(parents=True, exist_ok=True) - - # Copy all files in src to dest - for i in src.iterdir(): - copy(i, dest) -except PermissionError as error: - print(error) - sys.exit("Make sure you are root") - -# Now, the iwd part -try: - # connect to the System bus - bus = SystemMessageBus() - # iwd proxy - iwd = bus.get_proxy("net.connman.iwd", "/") - # Get list of all devices - print("Finding connected devices") - objects = iwd.GetManagedObjects() - # devices that support ad-hoc - adhoc_devices = [] - # devices that support ap - ap_devices = [] - for path, obj in objects.items(): - if "net.connman.iwd.Device" in obj: - # add all devices to the list - name = obj["net.connman.iwd.Device"]["Name"] - print("Found device", name) - adapter_path = obj["net.connman.iwd.Device"]["Adapter"].get_string() - adapter = objects[adapter_path]["net.connman.iwd.Adapter"] - if "ad-hoc" in adapter["SupportedModes"]: - print(name, "supports ad-hoc") - adhoc_devices.append(path) - if "ap" in adapter["SupportedModes"]: - print(name, "supports ap") - ap_devices.append(path) - - if len(adhoc_devices) != 0: - # Start ad-hoc on first device supporting ad-hoc - dev1path = adhoc_devices.pop() - # The same device is likely to have ap support too. - # But we can't start ad-hoc and ap on the same interface. - # Remove dev1 from ap_devices if it exists there - if dev1path in ap_devices: - ap_devices.remove(dev1path) - print("Working on ad-hoc") - dev1 = bus.get_proxy("net.connman.iwd", dev1path) - print("Starting ad-hoc on", dev1.Name) - if not dev1.Powered: - print("Device is off. Turning on") - dev1.Powered = True - if dev1.Mode != "ad-hoc": - print("Device is in", dev1.Mode) - print("Switching to ad-hoc") - dev1.Mode = "ad-hoc" - # Changing Mode needs connecting to the proxy again - dev1 = bus.get_proxy("net.connman.iwd", dev1path) - # If already connected to ad-hoc, stop it - if dev1.Started is True: - print("Already connected to ad-hoc. Stopping") - dev1.Stop() - # Reconnect to proxy or StartOpen won't work - dev1 = bus.get_proxy("net.connman.iwd", dev1path) - print("Starting ad-hoc network") - dev1.StartOpen(ADHOC_SSID) - - # Start Access point if ap_device is not empty, - # ie, we have more devices - if len(ap_devices) != 0: - print("Working on AP") - dev2path = ap_devices.pop() - dev2 = bus.get_proxy("net.connman.iwd", dev2path) - if not dev1.Powered: - print("Device is off. Turning on") - dev1.Powered = True - if dev2.Mode != "ap": - print(dev2.Name, "is in", dev2.Mode) - print("Switching to ap") - dev2.Mode = "ap" - dev2 = bus.get_proxy("net.connman.iwd", dev2path) - if dev2.Started is True: - print("An AP is already started on", dev2.Name) - print("Stopping") - dev2.Stop() - dev2 = bus.get_proxy("net.connman.iwd", dev2path) - print("Starting AP on", dev2.Name) - dev2.Start(AP_SSID, AP_PASSWD) -except DBusError: - sys.exit("An error occured while communicating with iwd") - -print("Bye") diff --git a/naxalnet/__init__.py b/naxalnet/__init__.py new file mode 100644 index 0000000..c2fbd44 --- /dev/null +++ b/naxalnet/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +__version__ = "0.1.0a" diff --git a/naxalnet/__main__.py b/naxalnet/__main__.py new file mode 100644 index 0000000..4d9d341 --- /dev/null +++ b/naxalnet/__main__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from naxalnet.scripts import here_be_dragons + +if __name__ == "__main__": + here_be_dragons() diff --git a/naxalnet/iwd.py b/naxalnet/iwd.py new file mode 100644 index 0000000..e6a04a8 --- /dev/null +++ b/naxalnet/iwd.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 The naxalnet Authors + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +"""Manage wifi adapter via iwd D-Bus api""" + +from dasbus.connection import SystemMessageBus + +IWD_BUS = "net.connman.iwd" +IWD_ROOT_PATH = "/" +IWD_DEVICE_INTERFACE = "net.connman.iwd.Device" +IWD_ADAPTER_INTERFACE = "net.connman.iwd.Adapter" + +# If you are new to D-Bus, you might want to use a program +# such as D-Feet (https://wiki.gnome.org/Apps/DFeet) for reference. +# And try out iwctl to understand iwd's bus objects + + +class IWD: + """Manage iwd via dbus""" + + def __init__(self, bus=SystemMessageBus()): + # self._bus and self._proxy are meant for use only in this file + self._bus = bus + self.reload() + + def reload(self): + """reload the proxy""" + self._proxy = self._bus.get_proxy(IWD_BUS, IWD_ROOT_PATH) + + def get_name_from_path(self, path: str) -> str: + """ + returns device or adapter name when d-bus path is given as arg + """ + proxy = self._bus.get_proxy(IWD_BUS, path) + return proxy.Name + + def get_device_path_from_name(self, name: str) -> str: + """returns path of device as str""" + device_paths = self.get_all_device_paths() + for i in device_paths: + proxy = self._bus.get_proxy(IWD_BUS, i) + if proxy.Name == name: + return i + # If no devices were found, return None + return None + + def get_adapter_path_from_name(self, name: str) -> str: + """returns path of adapter as str""" + adapter_paths = self.get_all_adapter_paths() + for i in adapter_paths: + proxy = self._bus.get_proxy(IWD_BUS, i) + if proxy.Name == name: + return i + # If no adapters were found + return None + + def get_all_device_paths(self) -> list: + """returns list of paths of all devices""" + objects = self._proxy.GetManagedObjects() + paths = [] + for key, value in objects.items(): + # if value is a device, add its path to paths + if IWD_DEVICE_INTERFACE in value: + paths.append(key) + return paths + + def get_all_adapter_paths(self) -> list: + """returns list of paths of all adapters""" + objects = self._proxy.GetManagedObjects() + paths = [] + + for key, value in objects.items(): + # if value is an adapter, add its path to paths + if IWD_ADAPTER_INTERFACE in value: + paths.append(key) + return paths + + def get_devices(self) -> list: + """ + returns list of device names as str + example: ["wlan0", "wlan1"] + """ + devices = [] + device_paths = self.get_all_device_paths() + + for i in device_paths: + name = self.get_name_from_path(i) + devices.append(name) + return devices + + def get_adapters(self) -> list: + """ + returns list of adapters + example: ["phy0","phy1"] + """ + adapters = [] + adapter_paths = self.get_all_adapter_paths() + + for i in adapter_paths: + name = self.get_name_from_path(i) + adapters.append(name) + return adapters + + +class Device: + """ + control devices with iwd + name: name of device (str) + adapter: name of adapter (str) + """ + + def __init__(self, name: str, bus=SystemMessageBus()): + self._iwd = IWD(bus) + self._bus = self._iwd._bus + self._path = self._iwd.get_device_path_from_name(name) + self.reload() + + def __str__(self): + return self.name + + def is_powered_on(self) -> bool: + """returns True if devie is powered on""" + return self._proxy.Powered + + def power_on(self): + """Turn on the device and reload the proxy""" + self._proxy.Powered = True + self.reload() + + def power_off(self): + """Turn off the device and reload the proxy""" + self._proxy.Powered = False + self.reload() + + def reload(self): + """reload the proxy after changing mode""" + self._proxy = self._bus.get_proxy(IWD_BUS, self._path) + self.name = self._proxy.Name + adapter_path = self._proxy.Adapter + # name of adapter ('phy0' for example) + self.adapter = self._iwd.get_name_from_path(adapter_path) + + def is_adhoc_started(self): + """ + Returns True if an adhoc network is started on this device. + Returns None if device is not powered on or not in ad-hoc mode. + """ + if self.is_powered_on() and self.get_mode() == "ad-hoc": + return self._proxy.Started + # If above condition is not true, return None + return None + + def is_ap_started(self): + """ + Same as is_adhoc_started(), but for ap + """ + if self.is_powered_on() and self.get_mode() == "ap": + return self._proxy.Started + return None + + def get_mode(self) -> str: + """ + returns the mode in which the device is in + example: "ap" + """ + return self._proxy.Mode + + def set_mode(self, mode: str): + """change the device mode to mode""" + self._proxy.Mode = mode + self.reload() + + def start_adhoc_open(self, name: str): + """ + Create ad-hoc network with name, changing mode to ad-hoc + if it isn't already on ad-hoc and power onn the device + if it is off + """ + if self.get_mode() != "ad-hoc": + self.set_mode("ad-hoc") + + if not self.is_powered_on(): + self.power_on() + + # Stop adhoc if already started + self.stop_adhoc() + + self._proxy.StartOpen(name) + + def stop_adhoc(self): + """stop adhoc if adhoc is started""" + if self.is_adhoc_started(): + self._proxy.Stop() + self.reload() + + def start_ap(self, ssid, passwd): + """ + Create ap network, changing mode to ap + if it isn't already on ap and turning + on the device if it is off + """ + if self.get_mode() != "ap": + self.set_mode("ap") + + if not self.is_powered_on(): + self.power_on() + + # Stop ap if already started + self.stop_ap() + + self._proxy.Start(ssid, passwd) + + def stop_ap(self): + """stop ap if an ap is started""" + if self.is_ap_started(): + self._proxy.Stop() + self.reload() + + +class Adapter: + """represents an adapter as a python object""" + + def __init__(self, name: str, bus=SystemMessageBus()): + self._iwd = IWD(bus) + self._bus = self._iwd._bus + self._path = self._iwd.get_adapter_path_from_name(name) + # Initialise self._proxy + self.reload() + + def __str__(self): + return self.name + + def reload(self): + """reload the proxy after changing mode""" + self._proxy = self._bus.get_proxy(IWD_BUS, self._path) + self.name = self._proxy.Name + self.supported_modes = self._proxy.SupportedModes + self.model = self._proxy.Model + self.vendor = self._proxy.Vendor + + def is_powered_on(self) -> bool: + """returns True if adapter is powered on, False otherwise""" + return self._proxy.Powered + + def power_on(self): + """power on the adapter""" + self._proxy.Powered = True + + def power_off(self): + """power off the adapter""" + self._proxy.Powered = False + + def supports_mode(self, mode: str) -> bool: + """ + Returns True if the adapter supports the mode. + mode can be "ad-hoc", "ap" or "station" + """ + return mode in self.supported_modes diff --git a/naxalnet/scripts.py b/naxalnet/scripts.py new file mode 100644 index 0000000..fde65f4 --- /dev/null +++ b/naxalnet/scripts.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 The naxalnet Authors + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Setup a working BATMAN Advanced network +with systemd-networkd and iwd +""" + +import sys +from pathlib import Path +from shutil import copy + +# from dasbus.connection import SystemMessageBus +from dasbus.error import DBusError +from naxalnet.iwd import IWD, Device, Adapter + +NETWORKD_CONFIGS = "/usr/share/naxalnet/networkd" +NETWORKD_VOLATILE_DIR = "/run/systemd/network" +ADHOC_SSID = "HelloWorld" +AP_SSID = "NaxalNet" +AP_PASSWD = "naxalnet256" + + +def here_be_dragons(): + # Copy networkd configs to volatile dir. + # The D-Bus API does not support creating new interfaces + # or linking to bridges. So we use config files. + # See man:systemd.network(5) + + try: + print("Copying network config files") + dest = Path(NETWORKD_VOLATILE_DIR) + src = Path(NETWORKD_CONFIGS) + + # Create the volatile directory if it doesn't exist + dest.mkdir(parents=True, exist_ok=True) + + # Copy all files in src to dest + for i in src.iterdir(): + copy(i, dest) + except PermissionError as error: + print(error) + sys.exit("Make sure you are root") + + # Now, the iwd part + try: + iwd = IWD() + devices = iwd.get_devices() + adhoc_devices = [] + ap_devices = [] + + for i in devices: + d = Device(i) + a = Adapter(d.adapter) + if a.supports_mode("ad-hoc"): + adhoc_devices.append(i) + if a.supports_mode("ap"): + ap_devices.append(i) + + if len(adhoc_devices) != 0: + # Start ad-hoc on first device supporting ad-hoc + adhoc_device = Device(adhoc_devices.pop()) + # The same device is likely to have ap support too. + # But we can't start ad-hoc and ap on the same interface. + # Remove dev1 from ap_devices if it exists there + if adhoc_device.name in ap_devices: + ap_devices.remove(adhoc_device.name) + print("Working on ad-hoc") + adhoc_device.start_adhoc_open(ADHOC_SSID) + # Start Access point if ap_device is not empty, + # ie, we have more devices + if len(ap_devices) != 0: + print("Working on AP") + ap_device = Device(ap_devices.pop()) + ap_device.start_ap(AP_SSID, AP_PASSWD) + except DBusError as error: + print(error) + sys.exit("An error occured while communicating with iwd") + + print("Bye") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4ab3cc1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,36 @@ +[metadata] +name = naxalnet +version = attr: naxalnet.__version__ +description = create mesh networks with batman-adv and systemd +long_description = file: README.md, LICENSE +url = https://git.disroot.org/pranav/naxalnet +author = Pranav Jerry +author_email = libreinator@disroot.org +license = GPLv3 +classifiers = + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 :: Only + +[options] +include_package_data = true +packages = find: +python_requires = >=3.6 +install_requires = + dasbus + +[options.entry_points] +console_scripts = + naxalnet = naxalnet.scripts:here_be_dragons + +[options.data_files] +/usr/lib/systemd/system = + naxalnet.service +/usr/share/naxalnet/networkd = + systemd-networkd/01-batman.netdev + systemd-networkd/02-bridge.netdev + systemd-networkd/03-wireless-ad-hoc.network + systemd-networkd/04-batman.network + systemd-networkd/05-wireless-ap.network + systemd-networkd/06-eth.network + systemd-networkd/07-bridge.network diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..229b2eb --- /dev/null +++ b/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +from setuptools import setup + +setup() diff --git a/systemd-networkd/05-wireless-ap.network b/systemd-networkd/05-wireless-ap.network index b00b1f2..0b35dae 100644 --- a/systemd-networkd/05-wireless-ap.network +++ b/systemd-networkd/05-wireless-ap.network @@ -1,5 +1,7 @@ # This file links any interface in ap mode -# to the bridge we created earlier +# to the bridge we created earlier. +# To start an AP, connect two adapters to the computer +# before starting naxalnet.service # This file won't do anything if an ap interface is not found. [Match] diff --git a/systemd-networkd/06-eth.network b/systemd-networkd/06-eth.network index dad405a..0c81f21 100644 --- a/systemd-networkd/06-eth.network +++ b/systemd-networkd/06-eth.network @@ -2,6 +2,7 @@ # to the bridge made in 02-bridge.netdev [Match] Name=en* +Name=eth* [Network] Bridge=bridge0