synit/packaging/packages/synit-config/files/usr/lib/synit/python/synit/daemon/wifi_daemon.py

150 lines
4.9 KiB
Python

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.debug(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 = []
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('SCAN', handle_scan_failure)
def handle_scan_failure(reply):
if reply == 'FAIL':
send_command('TERMINATE')
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()