From b8ba97742dfeea9a5a98dfc287683fde8b7ab1bf Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 9 Feb 2022 15:50:10 +0100 Subject: [PATCH] Progress --- .../etc/syndicate/boot/010-run-oneshot.pr | 19 +++ .../etc/syndicate/core/machine-dataspace.pr | 5 + .../files/etc/syndicate/services/network.pr | 36 ---- .../etc/syndicate/services/userSettings.pr | 20 +++ .../files/etc/syndicate/services/wifi.pr | 34 ++++ .../synit-config/files/sbin/synit-init.sh | 1 + .../python/synit/daemon/interface_monitor.py | 5 +- .../synit/daemon/user_settings_daemon.py | 48 ++++++ .../synit/python/synit/daemon/wifi_daemon.py | 155 ++++++++++++++++++ .../files/usr/lib/synit/user-settings-daemon | 5 + .../files/usr/lib/synit/wifi-daemon | 5 + 11 files changed, 296 insertions(+), 37 deletions(-) create mode 100644 packaging/packages/synit-config/files/etc/syndicate/boot/010-run-oneshot.pr create mode 100644 packaging/packages/synit-config/files/etc/syndicate/services/userSettings.pr create mode 100644 packaging/packages/synit-config/files/etc/syndicate/services/wifi.pr create mode 100644 packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/user_settings_daemon.py create mode 100644 packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/wifi_daemon.py create mode 100755 packaging/packages/synit-config/files/usr/lib/synit/user-settings-daemon create mode 100755 packaging/packages/synit-config/files/usr/lib/synit/wifi-daemon diff --git a/packaging/packages/synit-config/files/etc/syndicate/boot/010-run-oneshot.pr b/packaging/packages/synit-config/files/etc/syndicate/boot/010-run-oneshot.pr new file mode 100644 index 0000000..b7d0b61 --- /dev/null +++ b/packaging/packages/synit-config/files/etc/syndicate/boot/010-run-oneshot.pr @@ -0,0 +1,19 @@ +; Sending causes the command to be run. +; +?? [ + let ?id = timestamp + let ?facet = facet + let ?d = + > + + ? complete> [$facet ! stop] + ? failed> [$facet ! stop] +] + +; If the restart policy is not specified, it is defaulted to `on-error`. +; +?? ! diff --git a/packaging/packages/synit-config/files/etc/syndicate/core/machine-dataspace.pr b/packaging/packages/synit-config/files/etc/syndicate/core/machine-dataspace.pr index f08ecc7..83ac803 100644 --- a/packaging/packages/synit-config/files/etc/syndicate/core/machine-dataspace.pr +++ b/packaging/packages/synit-config/files/etc/syndicate/core/machine-dataspace.pr @@ -1,2 +1,7 @@ let ?ds = dataspace + +$ds ? ?r [ + $log ! + ?- $log ! +] diff --git a/packaging/packages/synit-config/files/etc/syndicate/services/network.pr b/packaging/packages/synit-config/files/etc/syndicate/services/network.pr index 5e448ee..d47346c 100644 --- a/packaging/packages/synit-config/files/etc/syndicate/services/network.pr +++ b/packaging/packages/synit-config/files/etc/syndicate/services/network.pr @@ -20,30 +20,6 @@ ["udhcpc" "-i" $ifname "-fR" "-s" "/usr/lib/synit/udhcpc.script"]> ] -? >> [ - [ - "wpa_supplicant" "-Dnl80211,wext" "-C/run/wpa_supplicant" "-i" $ifname - ]> -] - -?? ! - -?? [ - let ?id = timestamp - let ?facet = facet - let ?d = - > - - ? complete> [$facet ! stop] - ? failed> [$facet ! stop] -] - -;--------------------------------------------------------------------------- - - ?- $log ! - ] - $machine ? [ $config > ] - $machine ? [ - $config >> - ] - $machine ? [ - $config > - ] - $machine ? [ $config ] diff --git a/packaging/packages/synit-config/files/etc/syndicate/services/userSettings.pr b/packaging/packages/synit-config/files/etc/syndicate/services/userSettings.pr new file mode 100644 index 0000000..834e3af --- /dev/null +++ b/packaging/packages/synit-config/files/etc/syndicate/services/userSettings.pr @@ -0,0 +1,20 @@ +let ?settingsDir = "/etc/syndicate/user-settings" + +let ?settings = <* $config [ + > +]> + +> + +> + + +? ?cap> [ + $cap { + config: $config + settingsDir: $settingsDir + } +] diff --git a/packaging/packages/synit-config/files/etc/syndicate/services/wifi.pr b/packaging/packages/synit-config/files/etc/syndicate/services/wifi.pr new file mode 100644 index 0000000..4318dd6 --- /dev/null +++ b/packaging/packages/synit-config/files/etc/syndicate/services/wifi.pr @@ -0,0 +1,34 @@ +? [ + $machine ? [ + $config [ + >> + > > up>> + >> + ] + ] + + $machine ? [ + $config > + ] +] + +? >> [ + { + argv: "/usr/lib/synit/wifi-daemon" + protocol: application/syndicate + }> + ? [ + ? > ?cap> [ + $cap { + machine: $machine + ifname: $ifname + } + ] + ] +] + +? >> [ + [ + "wpa_supplicant" "-Dnl80211,wext" "-C/run/wpa_supplicant" "-i" $ifname + ]> +] diff --git a/packaging/packages/synit-config/files/sbin/synit-init.sh b/packaging/packages/synit-config/files/sbin/synit-init.sh index 05af726..75d855d 100755 --- a/packaging/packages/synit-config/files/sbin/synit-init.sh +++ b/packaging/packages/synit-config/files/sbin/synit-init.sh @@ -20,6 +20,7 @@ mount -t cgroup2 none /sys/fs/cgroup mount -o rw,remount / +mkdir -p /etc/syndicate/user-settings mkdir -p /run/etc/syndicate/core mkdir -p /usr/local/etc/syndicate/core mkdir -p /run/etc/syndicate/services diff --git a/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/interface_monitor.py b/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/interface_monitor.py index 1d608e2..b0d8d3f 100644 --- a/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/interface_monitor.py +++ b/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/interface_monitor.py @@ -12,7 +12,10 @@ import threading import pyroute2 from pr2modules.iwutil import IW -schemas = preserves.schema.load_schema_file('/usr/share/synit/schemas/schema-bundle.prb') +try: + schemas = preserves.schema.load_schema_file('/usr/share/synit/schemas/schema-bundle.prb') +except: + schemas = preserves.schema.load_schema_file('/home/tonyg/src/syndicate-system/protocols/schema-bundle.bin') network = schemas.network class LenientFormatter(Formatter): diff --git a/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/user_settings_daemon.py b/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/user_settings_daemon.py new file mode 100644 index 0000000..ad4daec --- /dev/null +++ b/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/user_settings_daemon.py @@ -0,0 +1,48 @@ +import sys +import os +import preserves.schema +import hashlib + +from syndicate import patterns as P, relay, turn, dataspace, Symbol, canonicalize, stringify +from syndicate.actor import find_loop +from syndicate.during import During + +try: + schemas = preserves.schema.load_schema_file('/usr/share/synit/schemas/schema-bundle.prb') +except: + schemas = preserves.schema.load_schema_file('/home/tonyg/src/syndicate-system/protocols/schema-bundle.bin') +userSettings = schemas.userSettings + +def digest(item): + return hashlib.sha1(canonicalize(item)).hexdigest() + +@relay.service(name='user_settings_daemon', debug=False) +@During().add_handler +def main(args): + config = args[Symbol('config')].embeddedValue + settingsDir = args[Symbol('settingsDir')] + + def assert_item(item): + filename = os.path.join(settingsDir, digest(item) + '.pr') + turn.log.info(f'Asserting: {item} --> {filename}') + with open(filename, 'wt') as f: + f.write(stringify(item, indent=2) + '\n') + + def retract_item(item): + filename = os.path.join(settingsDir, digest(item) + '.pr') + turn.log.info(f'Retracting: {item} --> {filename}') + try: + os.unlink(filename) + except FileNotFoundError: + pass + + @dataspace.during(config, P.bind(P.quote(userSettings.Command(P.u_, P.u_)))) + def handle_command(c): + c = userSettings.Command.try_decode(c) + if c is None: return + c.action._accept({ + 'assert': assert_item, + 'retract': retract_item, + }) + if c.reply.VARIANT.name == 'replyWanted': + turn.publish(c.reply.value, userSettings.CommandReply()) diff --git a/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/wifi_daemon.py b/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/wifi_daemon.py new file mode 100644 index 0000000..634db9f --- /dev/null +++ b/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/wifi_daemon.py @@ -0,0 +1,155 @@ +import sys +import os +import socket +import threading +import atexit +import preserves.schema +import time +import re + +from syndicate import patterns as P, relay, turn, dataspace, Symbol +from syndicate.actor import find_loop +from syndicate.during import During + +try: + schemas = preserves.schema.load_schema_file('/usr/share/synit/schemas/schema-bundle.prb') +except: + schemas = preserves.schema.load_schema_file('/home/tonyg/src/syndicate-system/protocols/schema-bundle.bin') +network = schemas.network + +server_socket_dir = '/run/wpa_supplicant' + +class WPASupplicantClient: + # https://w1.fi/wpa_supplicant/devel/ctrl_iface_page.html + # + # According to the code (!) any response starting with '<' is an event, not a reply. + + def __init__(self, interface, log): + self.interface = interface + self.log = log + self.server_socket_path = os.path.join(server_socket_dir, interface) + self.client_socket_path = f'/tmp/wpa_cli_py_{interface}_{os.getpid()}' + + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + self.sock.bind(self.client_socket_path) + self.sock.connect(self.server_socket_path) + + self.send('ATTACH') + answer = self.recv(timeout_sec=3.0) + if answer != 'OK': + raise AssertionError(f'wpa_supplicant replied {repr(answer)} to ATTACH') + + self.cleanup = lambda: self.close() + atexit.register(self.cleanup) + + def close(self): + if self.cleanup: + atexit.unregister(self.cleanup) + self.cleanup = None + self.send('DETACH') + self.sock.close() + os.unlink(self.client_socket_path) + + def send(self, command): + self.sock.send(command.encode('utf-8')) + + def recv(self, timeout_sec=None): + self.sock.settimeout(timeout_sec) + try: + reply = self.sock.recv(65536) + self.log.info(f'Raw input: {repr(reply)}') + return reply.strip().decode('utf-8') + except TimeoutError: + return None + finally: + self.sock.settimeout(None) + +def gather_events(facet, callback, client, loop): + facet.log.debug('Background socket read thread started') + try: + while True: + facet.log.debug('waiting for event...') + e = client.recv() + # OMG python's incredibly broken mutable-variable-closures bit me AGAIN. I've had + # to add a layer of gratuitous rebinding of `e` plus the `lambda:` below to make it + # properly close over the current binding of `e`. + def handler_for_specific_e(e): + return lambda: callback(e) + turn.external(facet, handler_for_specific_e(e), loop=loop) + except Exception as e: + facet.log.debug(e) + finally: + facet.log.debug('Background socket read thread terminated') + +@relay.service(name='wifi_daemon', debug=False) +@During().add_handler +def main(args): + machine_ds = args[Symbol('machine')].embeddedValue + ifname = args[Symbol('ifname')] + + client = WPASupplicantClient(ifname, turn.log) + commands = [] + active_network_id = None + + def send_next_command(): + while len(commands) > 0: + turn.log.info(f'Sending {commands[0][0]}') + client.send(commands[0][0]) + if commands[0][1] is None: + commands.pop(0) + continue + else: + break + + def send_command(c, handler = lambda reply: None): + turn.log.info(f'Scheduling {c}') + commands.append((c, handler)) + if len(commands) == 1: + send_next_command() + + def cleanout_networks(nets): + for line in nets.split('\n'): + if line.startswith('network id'): + continue + turn.log.info(f'Cleaning out old network: {repr(line)}') + send_command('REMOVE_NETWORK ' + line.split('\t')[0]) + send_command('ADD_NETWORK', remember_network_id) + + def remember_network_id(netid): + nonlocal active_network_id + active_network_id = netid + turn.log.info(f'Network ID: {active_network_id}') + def handle_scan_failure(reply): + if reply == 'FAIL': + send_command('TERMINATE') + send_command('SCAN', handle_scan_failure) + + send_command('LEVEL 3') + send_command('LIST_NETWORKS', cleanout_networks) + + def handle_event(e): + if e[0] == '<': + # It's an event + m = re.match(r'<(\d+)>(.*)', e) + if m: + (_level, message) = m.groups() + if message == 'CTRL-EVENT-SCAN-RESULTS': + send_command('SCAN_RESULTS') + turn.log.info(f'Level {_level}: {message}') + else: + turn.log.info(f'Unusual event: {e}') + else: + if len(commands) > 0: + turn.log.info(f'REPLY: {e}') + commands[0][1](e) + commands.pop(0) + send_next_command() + else: + turn.log.warning(f'Unexpected reply: {e}') + + facet = turn.active_facet() + loop = find_loop() + turn.log.info('Starting background read thread') + threading.Thread( + name='background-wpa_supplicant-read-thread', + target=lambda: gather_events(facet, handle_event, client, loop)).start() diff --git a/packaging/packages/synit-config/files/usr/lib/synit/user-settings-daemon b/packaging/packages/synit-config/files/usr/lib/synit/user-settings-daemon new file mode 100755 index 0000000..ca90286 --- /dev/null +++ b/packaging/packages/synit-config/files/usr/lib/synit/user-settings-daemon @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'python')) +import synit.daemon.user_settings_daemon diff --git a/packaging/packages/synit-config/files/usr/lib/synit/wifi-daemon b/packaging/packages/synit-config/files/usr/lib/synit/wifi-daemon new file mode 100755 index 0000000..4a65673 --- /dev/null +++ b/packaging/packages/synit-config/files/usr/lib/synit/wifi-daemon @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'python')) +import synit.daemon.wifi_daemon