rtl8723cs-wowlan: automatically derive the IP address to watch for ARP packets on

This commit is contained in:
Colin 2023-10-11 02:41:49 +00:00
parent 5a753583bf
commit 08c92151eb
3 changed files with 40 additions and 11 deletions

View File

@ -36,7 +36,7 @@ doas rtl8723cs-wowlan tcp --dest-port 22
# wake on notification (ntfy/Universal Push)
doas rtl8723cs-wowlan tcp --source-port 2587
# wake if someone doesn't know how to route to us, because that could obstruct the above
doas rtl8723cs-wowlan arp --dest-ip 10.78.79.54
doas rtl8723cs-wowlan arp --dest-ip SELF
echo "calling suspend for duration: $suspend_time"

View File

@ -1,5 +1,6 @@
{ static-nix-shell
, iw
, moreutils
, wirelesstools
}:
@ -7,7 +8,7 @@ static-nix-shell.mkPython3Bin {
pname = "rtl8723cs-wowlan";
src = ./.;
pkgs = {
inherit iw wirelesstools;
inherit iw moreutils wirelesstools;
};
}

View File

@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p iw -p wirelesstools
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p iw -p moreutils -p wirelesstools
# vim: set filetype=python :
# common operations:
@ -7,6 +7,8 @@
# arp --dest-ip A.B.C.D
# tcp --source-port N
KNOWN_INTERFACES = ("wlan0", "wlp3s0", "enp5s0")
import argparse
import logging
import subprocess
@ -17,6 +19,21 @@ def octet_to_hex(o: int) -> str:
''' format an octet as a two-character hex string (lowercase) '''
return '%02X' % o
def get_ipaddrs() -> list['IpAddr']:
''' return the IP address of all known interfaces '''
addrs = []
for iface in KNOWN_INTERFACES:
try:
# errors if interface isn't known, or ip addr fails to parse (e.g. 'NON-IP')
maybe_addr = subprocess.check_output(['ifdata', '-pa', iface]).decode('utf-8')
addr = IpAddr(maybe_addr)
except: pass
else:
addrs.append(addr)
logger.debug(f'get_ipaddrs got: {[str(a) for a in addrs]}')
return addrs
class Encodable:
@staticmethod
def get_octets(e: 'Encodable|None', len_: int) -> int|None:
@ -48,6 +65,13 @@ class IpAddr(Encodable):
pieces = addr.split('.')
self._octets = [int(p) if p else 0 for p in pieces]
@staticmethod
def parse_any(addr: str) -> list['IpAddr']:
if addr == 'SELF':
return get_ipaddrs()
else:
return [IpAddr(addr)]
def octets(self) -> list[int]:
return self._octets
@ -137,9 +161,13 @@ class TcpFrame(Encodable):
]
def build_arp(dest_ip: str|None = None) -> EthernetFrame:
dest_ip = IpAddr(dest_ip) if dest_ip is not None else None
return EthernetFrame(EtherType.ARP, ArpFrame(dest_ip))
def build_arp(dest_ip: str|None = None) -> list[EthernetFrame]:
dest_ips = IpAddr.parse_any(dest_ip) if dest_ip is not None else []
# if no known IPs, wake on any ARP.
# the only way we could see an ARP message at this point is if the controller associates with a network
# while we're sleep (or while racing to sleep)
if dest_ips == []: dest_ips = [None]
return [EthernetFrame(EtherType.ARP, ArpFrame(ip)) for ip in dest_ips]
def build_tcp(source_port: int|None = None, dest_port: int|None = None) -> EthernetFrame:
source_port = Port(source_port) if source_port is not None else None
@ -169,7 +197,7 @@ def main():
arp_parser = subparsers.add_parser('arp', help="wake on ARP request")
arp_parser.set_defaults(type_='arp')
arp_parser.add_argument('--dest-ip')
arp_parser.add_argument('--dest-ip', help="a.b.c.d or the special 'SELF' for automatic")
tcp_parser = subparsers.add_parser('tcp', help="wake on TCP packet")
tcp_parser.set_defaults(type_='tcp')
@ -187,13 +215,13 @@ def main():
exec_(['iw', 'phy0', 'wowlan', 'enable', 'any'])
exec_(['iwpriv', 'wlan0', 'wow_set_pattern', 'clean'])
frame = None
frames = []
if args.type_ == 'arp':
frame = build_arp(dest_ip=args.dest_ip)
frames = build_arp(dest_ip=args.dest_ip)
if args.type_ == 'tcp':
frame = build_tcp(source_port=args.source_port, dest_port=args.dest_port)
frames = [ build_tcp(source_port=args.source_port, dest_port=args.dest_port) ]
if frame is not None:
for frame in frames:
pattern = str(frame)
exec_(['iwpriv', 'wlan0', 'wow_set_pattern', f'pattern={pattern}'])