From 8e97204fd6cec6e36ceabe4cc56514c1e6262464 Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Mon, 3 Nov 2025 05:32:37 +0000 Subject: [PATCH] name updates --- developer/source/manager/{CLI.py => CLI} | 0 developer/source/manager/subu_bpf.py | 25 --- developer/source/manager/subu_db.py | 176 ------------------ developer/source/manager/subu_net.py | 60 ------ developer/source/manager/subu_text.py | 152 --------------- developer/source/manager/subu_utils.py | 34 ---- developer/source/manager/subu_version.py | 2 - developer/source/manager/subu_wg.py | 139 -------------- developer/source/manager/subu_worker_bpf.py | 195 -------------------- developer/source/manager/test.sh | 18 +- 10 files changed, 9 insertions(+), 792 deletions(-) rename developer/source/manager/{CLI.py => CLI} (100%) mode change 100644 => 100755 delete mode 100644 developer/source/manager/subu_bpf.py delete mode 100644 developer/source/manager/subu_db.py delete mode 100644 developer/source/manager/subu_net.py delete mode 100644 developer/source/manager/subu_text.py delete mode 100644 developer/source/manager/subu_utils.py delete mode 100644 developer/source/manager/subu_version.py delete mode 100644 developer/source/manager/subu_wg.py delete mode 100644 developer/source/manager/subu_worker_bpf.py diff --git a/developer/source/manager/CLI.py b/developer/source/manager/CLI old mode 100644 new mode 100755 similarity index 100% rename from developer/source/manager/CLI.py rename to developer/source/manager/CLI diff --git a/developer/source/manager/subu_bpf.py b/developer/source/manager/subu_bpf.py deleted file mode 100644 index d698a9a..0000000 --- a/developer/source/manager/subu_bpf.py +++ /dev/null @@ -1,25 +0,0 @@ -# ===== File: subu_bpf.py ===== -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -""" -Stub for eBPF steering (cgroup/connect4+sendmsg4 hooks) to enforce sk_bound_dev_if. -Implementation notes: - * We will later compile a small eBPF C program (libbpf/bpftool) that: - - on connect4/sendmsg4: if process UID==subu UID -> sets sk_bound_dev_if to WG ifindex - * For now, we provide placeholders that pretend success. -""" - -import subu_utils as U -import subu_db as DB - - -def install_steer(subu_id: str, wg_ifindex: int): - # TODO: load BPF, attach to cgroup v2 path; store cgroup path in DB - DB.update_subu_cgroup(subu_id, "/sys/fs/cgroup/subu_placeholder") - return 0 - - -def remove_steer(subu_id: str): - # TODO: detach and unload - return 0 diff --git a/developer/source/manager/subu_db.py b/developer/source/manager/subu_db.py deleted file mode 100644 index 40c5f25..0000000 --- a/developer/source/manager/subu_db.py +++ /dev/null @@ -1,176 +0,0 @@ -# ===== File: subu_db.py ===== -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -import os, sqlite3, json -import subu_utils as U - -SCHEMA = { - "meta": "CREATE TABLE IF NOT EXISTS meta (k TEXT PRIMARY KEY, v TEXT)" , - "subu": """ - CREATE TABLE IF NOT EXISTS subu ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - masu TEXT NOT NULL, - subu TEXT NOT NULL, - uid INTEGER, - netns TEXT, - cgroup_path TEXT, - UNIQUE(masu, subu) - )""", - "wg": """ - CREATE TABLE IF NOT EXISTS wg ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - remote TEXT NOT NULL, - pubkey TEXT, - dev TEXT, - addr TEXT, - state TEXT DEFAULT 'down' - )""", - "attach": """ - CREATE TABLE IF NOT EXISTS attach ( - subu_id INTEGER NOT NULL, - wg_id INTEGER NOT NULL, - PRIMARY KEY (subu_id, wg_id) - )""", -} - -class NotInitializedError(Exception): - pass - - -def require_initialized(): - if not os.path.exists(U.path_db()): - raise NotInitializedError() - - -def connect(): - return sqlite3.connect(U.path_db()) - - -def init_db(): - if os.path.exists(U.path_db()): - return False - con = sqlite3.connect(U.path_db()) - try: - cur = con.cursor() - for sql in SCHEMA.values(): - cur.execute(sql) - con.commit() - return True - finally: - con.close() - - -def put_meta(k, v): - con = connect(); cur = con.cursor() - cur.execute("INSERT OR REPLACE INTO meta(k,v) VALUES(?,?)", (k, v)) - con.commit(); con.close() - -def get_meta(k, default=None): - con = connect(); cur = con.cursor() - cur.execute("SELECT v FROM meta WHERE k=?", (k,)) - row = cur.fetchone(); con.close() - return row[0] if row else default - - -def create_subu(masu, subu): - con = connect(); cur = con.cursor() - cur.execute("INSERT INTO subu(masu,subu) VALUES(?,?)", (masu, subu)) - con.commit() - sid = cur.lastrowid - con.close() - return f"subu_{sid}" - - -def list_subu(): - con = connect(); cur = con.cursor() - cur.execute("SELECT id,masu,subu,uid,netns,cgroup_path FROM subu ORDER BY id") - rows = cur.fetchall(); con.close(); return rows - - -def subu_by_id(subu_id): - if not subu_id.startswith("subu_"): - raise ValueError("bad subu id") - sid = int(subu_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("SELECT id,masu,subu,uid,netns,cgroup_path FROM subu WHERE id=?", (sid,)) - row = cur.fetchone(); con.close(); return row - - -def update_subu_netns(subu_id, netns): - sid = int(subu_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("UPDATE subu SET netns=? WHERE id=?", (netns, sid)) - con.commit(); con.close() - - -def update_subu_uid(subu_id, uid): - sid = int(subu_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("UPDATE subu SET uid=? WHERE id=?", (uid, sid)) - con.commit(); con.close() - - -def update_subu_cgroup(subu_id, path): - sid = int(subu_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("UPDATE subu SET cgroup_path=? WHERE id=?", (path, sid)) - con.commit(); con.close() - -# WG - -def wg_set_global_base(cidr): - put_meta("wg_base_cidr", cidr) - - -def wg_create(remote): - con = connect(); cur = con.cursor() - cur.execute("INSERT INTO wg(remote) VALUES(?)", (remote,)) - con.commit(); wid = cur.lastrowid; con.close(); return f"WG_{wid}" - - -def wg_list(): - con = connect(); cur = con.cursor() - cur.execute("SELECT id,remote,pubkey,dev,addr,state FROM wg ORDER BY id") - rows = cur.fetchall(); con.close(); return rows - - -def wg_by_id(wg_id): - if not wg_id.startswith("WG_"): - raise ValueError("bad WG id") - wid = int(wg_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("SELECT id,remote,pubkey,dev,addr,state FROM wg WHERE id=?", (wid,)) - row = cur.fetchone(); con.close(); return row - - -def wg_update(wg_id, **kv): - wid = int(wg_id.split("_")[1]) - con = connect(); cur = con.cursor() - cols = ",".join([f"{k}=?" for k in kv.keys()]) - cur.execute(f"UPDATE wg SET {cols} WHERE id=?", [*kv.values(), wid]) - con.commit(); con.close() - - -def attach(subu_id, wg_id): - sid = int(subu_id.split("_")[1]) - wid = int(wg_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("INSERT OR REPLACE INTO attach(subu_id,wg_id) VALUES(?,?)", (sid, wid)) - con.commit(); con.close() - - -def detach(subu_id, wg_id): - sid = int(subu_id.split("_")[1]) - wid = int(wg_id.split("_")[1]) - con = connect(); cur = con.cursor() - cur.execute("DELETE FROM attach WHERE subu_id=? AND wg_id=?", (sid, wid)) - con.commit(); con.close() - - -def attached_wg_ids(sid: int): - con = connect(); cur = con.cursor() - cur.execute("SELECT wg_id FROM attach WHERE subu_id=?", (sid,)) - rows = [f"WG_{r[0]}" for r in cur.fetchall()] - con.close(); return rows - diff --git a/developer/source/manager/subu_net.py b/developer/source/manager/subu_net.py deleted file mode 100644 index 86182e8..0000000 --- a/developer/source/manager/subu_net.py +++ /dev/null @@ -1,60 +0,0 @@ -# ===== File: subu_net.py ===== -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -import subu_utils as U -import subu_db as DB - - -def ensure_netns(ns): - # create netns if missing - rc, out = U.run("ip netns list", capture=True, check=False) - if ns not in out.split(): - U.run(f"ip netns add {ns}") - - -def lo(ns, action: str): - if action == "up": - U.run(f"ip -n {ns} link set lo up") - else: - U.run(f"ip -n {ns} link set lo down") - - -def cmd_lo(sid, action) -> int: - row = DB.subu_by_id(sid) - if not row: - return U.err("unknown subu id") - ns = row[4] or sid - ensure_netns(ns) - lo(ns, action) - return 0 - - -def cmd_network_up(sid) -> int: - row = DB.subu_by_id(sid) - if not row: - return U.err("unknown subu id") - ns = row[4] or sid - ensure_netns(ns) - lo(ns, "up") - # bring all attached WG up - sid_int = int(sid.split("_")[1]) - for wid in DB.attached_wg_ids(sid_int): - import subu_wg as WG - WG.cmd_wg_up(wid) - return U.ok(f"network up for {sid}") - - -def cmd_network_down(sid) -> int: - row = DB.subu_by_id(sid) - if not row: - return U.err("unknown subu id") - ns = row[4] or sid - # bring attached WG down first - sid_int = int(sid.split("_")[1]) - for wid in DB.attached_wg_ids(sid_int): - import subu_wg as WG - WG.cmd_wg_down(wid) - # leave lo state alone per spec (no warning here) - return U.ok(f"network down for {sid}") - diff --git a/developer/source/manager/subu_text.py b/developer/source/manager/subu_text.py deleted file mode 100644 index 88434e4..0000000 --- a/developer/source/manager/subu_text.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -USAGE = """\ -usage: subu [-V] [] - -Quick verbs: - usage Show this usage summary - help [topic] Detailed help; same as -h / --help - example End-to-end example session - version Print version - -Main verbs: - init Initialize a new subu database (refuses if it exists) - create Create a minimal subu record (defaults only) - info | information Show details for a subu - WG WireGuard object operations - attach Attach a WG object to a subu (netns + install steering) - detach Detach WG from a subu (remove steering) - network Bring attached ifaces up/down inside the subu netns - lo Bring loopback up/down inside the subu netns - option Persisted options (list/set/get for future policy) - exec Run a command inside the subu netns - -Tip: `subu help` (or `subu --help`) shows detailed help; `subu help WG` shows topic help. -""" - -HELP = """\ -subu — manage subu containers, namespaces, and WG attachments - -2.1 Core - - subu init - Create ./subu.db (tables: subu, wg, links, options, state). - Requires a 6-char token (e.g., dzkq7b). Refuses if DB already exists. - - subu create - Make a default subu with netns ns- containing lo only (down). - Returns subu_N. - - subu list - Columns: Subu_ID, Owner, Name, NetNS, WG_Attached?, Up/Down. - - subu info | subu information - Full record + attached WG(s) + options + iface states. - -2.2 Loopback - - subu lo up | subu lo down - Toggle loopback inside the subu’s netns. - -2.3 WireGuard objects (independent) - - subu WG global - e.g., 192.168.112.0/24; allocator hands out /32 peers sequentially. - Shows current base and next free on success. - - subu WG create - Creates WG object; allocates next /32 local IP; AllowedIPs=0.0.0.0/0. - Returns WG_M. - - subu WG server_provided_public_key - Stores server’s pubkey. - - subu WG info | subu WG information - Endpoint, allocated IP, pubkey set?, link state (admin/oper). - -2.4 Link WG ↔ subu, bring up/down - - subu attach WG - Creates/configures WG device inside ns-: - - device name: subu_ (M from WG_ID) - - set local /32, MTU 1420, accept_local=1 - - no default route is added - - installs eBPF steering (force egress via this device) automatically - - subu detach WG - Remove WG device/config from the subu’s netns and remove steering; keep WG object. - - subu WG up | subu WG down - Toggle interface admin state in the subu’s netns (must be attached). - On “up”, warn if loopback is currently down in that netns. - - subu network up | subu network down - Only toggles admin state for all attached ifaces. On “up”, loopback - is brought up first automatically. No route manipulation. - -2.5 Execution - - subu exec -- … - Run a process inside the subu’s netns. - -2.6 Options (persist only, for future policy) - - subu option list - subu option get [name] - subu option set - -2.7 Meta - - subu usage - Short usage summary (also printed when no args are given). - - subu help [topic] - This help (or per-topic help such as `subu help WG`). - - subu example - A concrete end-to-end scenario. - - subu version - Print version (same as -V / --version). -""" - -EXAMPLE = """\ -# 0) Safe init (refuses if ./subu.db exists) -subu init dzkq7b -# -> created ./subu.db - -# 1) Create Subu -subu create Thomas US -# -> Subu_ID: subu_7 -# -> netns: ns-subu_7 with lo (down) - -# 2) Define WG pool (once per host) -subu WG global 192.168.112.0/24 -# -> base set; next free: 192.168.112.2/32 - -# 3) Create WG object with endpoint -subu WG create ReasoningTechnology.com:51820 -# -> WG_ID: WG_0 -# -> local IP: 192.168.112.2/32 -# -> AllowedIPs: 0.0.0.0/0 - -# 4) Add server public key -subu WG server_provided_public_key WG_0 ABCDEFG...xyz= -# -> saved - -# 5) Attach WG to Subu (device created/configured in ns + steering) -subu attach WG subu_7 WG_0 -# -> device ns-subu_7/subu_0 configured (no default route) -# -> steering installed: egress forced via subu_0 - -# 6) Bring network up (lo first, then attached ifaces) -subu network up subu_7 -# -> lo up; subu_0 admin up - -# 7) Start the WG engine inside the netns -subu WG up WG_0 -# -> up, handshakes should start (warn if lo was down) - -# 8) Test from inside the subu -subu exec subu_7 -- curl -4v https://ifconfig.me -""" diff --git a/developer/source/manager/subu_utils.py b/developer/source/manager/subu_utils.py deleted file mode 100644 index 3594c1c..0000000 --- a/developer/source/manager/subu_utils.py +++ /dev/null @@ -1,34 +0,0 @@ -# ===== File: subu_utils.py ===== -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -import os, sys, subprocess, shlex - - -def ok(msg: str, code: int = 0) -> int: - print(msg) - return code - -def err(msg: str, code: int = 2) -> int: - print(f"❌ {msg}") - return code - - -def run(cmd: str, check=True, capture=False, env=None, ns_enter=None): - """Run shell command. If ns_enter is a netns name, prefix with `ip netns exec`. - Returns (rc, outstr). - """ - if ns_enter: - cmd = f"ip netns exec {shlex.quote(ns_enter)} {cmd}" - p = subprocess.run(cmd, shell=True, env=env, - stdout=subprocess.PIPE if capture else None, - stderr=subprocess.STDOUT) - out = p.stdout.decode() if p.stdout else "" - if check and p.returncode != 0: - raise RuntimeError(f"command failed ({p.returncode}): {cmd}\n{out}") - return p.returncode, out - - -def path_db(): - return os.path.abspath("subu.db") - diff --git a/developer/source/manager/subu_version.py b/developer/source/manager/subu_version.py deleted file mode 100644 index bccd1dd..0000000 --- a/developer/source/manager/subu_version.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- -VERSION = "0.1.6" diff --git a/developer/source/manager/subu_wg.py b/developer/source/manager/subu_wg.py deleted file mode 100644 index 0eca02c..0000000 --- a/developer/source/manager/subu_wg.py +++ /dev/null @@ -1,139 +0,0 @@ -# ===== File: subu_wg.py ===== -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -import ipaddress -import subu_utils as U -import subu_db as DB - -PREFIX_DEV = "subu_" # device name base; dev = f"{PREFIX_DEV}{WG_id_num}" - - -def cmd_wg_help() -> int: - print("WG commands: global | create | info | server_provided_public_key | up | down ") - return 0 - - -def cmd_wg_global(base_cidr: str) -> int: - # validate CIDR - try: - net = ipaddress.ip_network(base_cidr, strict=False) - if net.version != 4: - return U.err("only IPv4 supported for WG base") - except Exception as e: - return U.err(f"invalid cidr: {e}") - DB.wg_set_global_base(base_cidr) - return U.ok(f"WG base set to {base_cidr}") - - -def allocate_addr_for(wg_id: str) -> str: - base = DB.get_meta("wg_base_cidr") - if not base: - raise RuntimeError("WG base not set; run 'subu WG global '") - net = ipaddress.ip_network(base, strict=False) - wid = int(wg_id.split("_")[1]) - host = list(net.hosts())[wid + 1] # skip .1 for potential gateway - return f"{host}/32" - - -def ensure_device(wg_id: str): - # create device if missing and store dev+addr in DB - row = DB.wg_by_id(wg_id) - if not row: - raise RuntimeError("unknown WG id") - _, remote, pubkey, dev, addr, state = row - if not dev: - dev = f"{PREFIX_DEV}{wg_id.split('_')[1]}" - DB.wg_update(wg_id, dev=dev) - if not addr: - addr = allocate_addr_for(wg_id) - DB.wg_update(wg_id, addr=addr) - # ensure link exists in root netns - rc, out = U.run("ip link show", capture=True, check=False) - if f": {dev}:" not in out: - # create WG link skeleton; full wg config is deferred - U.run(f"ip link add {dev} type wireguard") - U.run(f"ip addr add {addr} dev {dev}") - return dev, addr - - -def move_to_netns(wg_id: str, ns: str): - dev, _ = ensure_device(wg_id) - # if already in ns, ip will refuse — treat as ok - U.run(f"ip link set {dev} netns {ns}", check=False) - - -def detach_device(wg_id: str): - row = DB.wg_by_id(wg_id) - if not row: return - dev = row[3] - if not dev: return - # best effort delete (must run either in owning ns or root if present there) - # try root first - rc, out = U.run(f"ip link del {dev}", check=False) - if rc != 0: - # try to find owning ns? (skipped for brevity) - pass - - -def cmd_wg_create(remote: str) -> int: - wid = DB.wg_create(remote) - print(wid) - return 0 - - -def cmd_wg_info() -> int: - rows = DB.wg_list() - for wid, remote, pubkey, dev, addr, state in rows: - print(f"WG_{wid}: remote={remote} dev={dev} addr={addr} state={state} pubkey={'set' if pubkey else 'unset'}") - return 0 - - -def cmd_wg_set_server_pub(wg_id: str, pub: str) -> int: - DB.wg_update(wg_id, pubkey=pub) - return U.ok(f"Set server pubkey for {wg_id}") - - -def _ns_of_wg(wg_id: str): - # discover netns from attachment - rows = DB.list_subu() - for sid, *_ in rows: - attached = DB.attached_wg_ids(sid) - if wg_id in attached: - row = DB.subu_by_id(f"subu_{sid}") - return row[4] or f"subu_{sid}" - return None - - -def cmd_wg_up(wg_id: str) -> int: - row = DB.wg_by_id(wg_id) - if not row: - return U.err("unknown WG id") - dev, addr = ensure_device(wg_id) - ns = _ns_of_wg(wg_id) - if ns: - # bring lo up silently before bringing WG up - U.run(f"ip -n {ns} link set lo up", check=False) - U.run(f"ip -n {ns} link set {dev} up") - else: - U.run(f"ip link set {dev} up") - DB.wg_update(wg_id, state="up") - return U.ok(f"WG {wg_id} up") - - -def cmd_wg_down(wg_id: str) -> int: - row = DB.wg_by_id(wg_id) - if not row: - return U.err("unknown WG id") - dev = row[3] - if not dev: - return U.err("WG device not created yet") - ns = _ns_of_wg(wg_id) - if ns: - U.run(f"ip -n {ns} link set {dev} down", check=False) - else: - U.run(f"ip link set {dev} down", check=False) - DB.wg_update(wg_id, state="down") - return U.ok(f"WG {wg_id} down") - - diff --git a/developer/source/manager/subu_worker_bpf.py b/developer/source/manager/subu_worker_bpf.py deleted file mode 100644 index 0c71d78..0000000 --- a/developer/source/manager/subu_worker_bpf.py +++ /dev/null @@ -1,195 +0,0 @@ -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- -""" -subu_worker_bpf.py — build, load, and manage eBPF steering for a Subu - -What it does: - * Compiles subu_bpf_force_egress.c -> /var/lib/subu/bpf/subu_force_egress.bpf.o - * Creates pins under /sys/fs/bpf/subu/: - force_connect4, force_sendmsg4, force_ifindex_map - * Creates a cgroup v2 node at /sys/fs/cgroup/subu/ - * Attaches programs (connect4, sendmsg4) to that cgroup - * Writes the target ifindex into map[0] - * Idempotent: re-running updates ifindex and ensures attachments - -Requirements: - * bpffs mounted at /sys/fs/bpf - * cgroup v2 mounted at /sys/fs/cgroup - * tools: clang, bpftool - * privileges: CAP_BPF + CAP_SYS_ADMIN -""" - -import os -import shutil -import subprocess -from pathlib import Path -from typing import Dict - -BPF_SRC = Path("subu_bpf_force_egress.c") -BUILD_DIR = Path("/var/lib/subu/bpf") -BPFFS_DIR = Path("/sys/fs/bpf") -BPF_PIN_BASE = BPFFS_DIR / "subu" # /sys/fs/bpf/subu//* -CGROOT = Path("/sys/fs/cgroup/subu") # /sys/fs/cgroup/subu/ - -OBJ_NAME = "subu_force_egress.bpf.o" -PROG_CONNECT_PIN = "force_connect4" -PROG_SENDMSG_PIN = "force_sendmsg4" -MAP_IFINDEX_PIN = "force_ifindex_map" # matches map name in C - -class BpfError(RuntimeError): - pass - -def _run(cmd, check=True): - r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - if check and r.returncode != 0: - raise BpfError(f"cmd failed: {' '.join(cmd)}\nstdout:\n{r.stdout}\nstderr:\n{r.stderr}") - return r - -def _which_or_die(tool: str): - if not shutil.which(tool): - raise BpfError(f"Missing required tool: {tool}") - -def ensure_prereqs(): - _which_or_die("clang") - _which_or_die("bpftool") - # bpffs? - if not BPFFS_DIR.exists(): - raise BpfError(f"{BPFFS_DIR} not mounted; try: mount -t bpf bpf {BPFFS_DIR}") - # cgroup v2? - cgroot = Path("/sys/fs/cgroup") - if not (cgroot / "cgroup.controllers").exists(): - raise BpfError("cgroup v2 not mounted; e.g.: mount -t cgroup2 none /sys/fs/cgroup") - -def ensure_dirs(subu_id: str) -> Dict[str, Path]: - BUILD_DIR.mkdir(parents=True, exist_ok=True) - (BPF_PIN_BASE).mkdir(parents=True, exist_ok=True) - pin_dir = BPF_PIN_BASE / subu_id - pin_dir.mkdir(parents=True, exist_ok=True) - - CGROOT.mkdir(parents=True, exist_ok=True) - cgdir = CGROOT / subu_id - cgdir.mkdir(parents=True, exist_ok=True) - - return { - "build_obj": BUILD_DIR / OBJ_NAME, - "pin_dir": pin_dir, - "pin_prog_connect": pin_dir / PROG_CONNECT_PIN, - "pin_prog_sendmsg": pin_dir / PROG_SENDMSG_PIN, - "pin_map_ifindex": pin_dir / MAP_IFINDEX_PIN, - "cgdir": cgdir, - } - -def compile_bpf(obj_path: Path): - if not BPF_SRC.exists(): - raise BpfError(f"BPF source not found: {BPF_SRC}") - cmd = [ - "clang", "-O2", "-g", - "-target", "bpf", - "-D__TARGET_ARCH_x86", - "-c", str(BPF_SRC), - "-o", str(obj_path), - ] - _run(cmd) - -def _load_prog_with_pinmaps(obj: Path, section: str, prog_pin: Path, maps_dir: Path): - # bpftool prog load OBJ PIN_PATH section
pinmaps - _run([ - "bpftool", "prog", "load", - str(obj), str(prog_pin), - "section", section, - "pinmaps", str(maps_dir), - ]) - -def load_and_pin_all(p: Dict[str, Path]): - obj = p["build_obj"] - maps_dir = p["pin_dir"] - # Load two sections; maps get pinned into maps_dir once (first load). - _load_prog_with_pinmaps(obj, "cgroup/connect4", p["pin_prog_connect"], maps_dir) - _load_prog_with_pinmaps(obj, "cgroup/sendmsg4", p["pin_prog_sendmsg"], maps_dir) - - # Ensure map exists where we expect it (pinned by name from C) - if not p["pin_map_ifindex"].exists(): - # Some bpftool/libbpf combos pin maps directly as /. - # If not present, try to locate by name and pin. - # Find map id by name: - out = _run(["bpftool", "map", "show"]).stdout.splitlines() - target_id = None - for line in out: - # sample: "123: array name force_ifindex_map flags 0x0 ..." - if " name " + MAP_IFINDEX_PIN + " " in line: - # id is before colon - try: - target_id = line.strip().split(":", 1)[0] - int(target_id) # validate - break - except Exception: - pass - if not target_id: - raise BpfError(f"Unable to find map '{MAP_IFINDEX_PIN}' to pin") - _run(["bpftool", "map", "pin", "id", target_id, str(p["pin_map_ifindex"])]) - -def attach_to_cgroup(p: Dict[str, Path]): - # Attach programs to the subu-specific cgroup - _run(["bpftool", "cgroup", "attach", str(p["cgdir"]), "connect4", "pinned", str(p["pin_prog_connect"])]) - _run(["bpftool", "cgroup", "attach", str(p["cgdir"]), "sendmsg4", "pinned", str(p["pin_prog_sendmsg"])]) - -def detach_from_cgroup(p: Dict[str, Path]): - _run(["bpftool", "cgroup", "detach", str(p["cgdir"]), "connect4"], check=False) - _run(["bpftool", "cgroup", "detach", str(p["cgdir"]), "sendmsg4"], check=False) - -def set_ifindex(p: Dict[str, Path], ifindex: int): - # bpftool map update pinned key <00 00 00 00> value - key_hex = "00 00 00 00".split() - val_hex = ifindex.to_bytes(4, "little").hex(" ").split() - _run(["bpftool", "map", "update", "pinned", str(p["pin_map_ifindex"]), "key", *key_hex, "value", *val_hex]) - -def _ifindex_in_netns(netns_name: str, ifname: str) -> int: - # ip -n -o link show -> "7: subu_0: <...>" - r = _run(["ip", "-n", netns_name, "-o", "link", "show", ifname]) - first = r.stdout.strip().split(":", 1)[0] - return int(first) - -def install_steering(subu_id: str, netns_name: str, wg_ifname: str): - """ - Build/load eBPF programs, pin them under /sys/fs/bpf/subu/, - attach to /sys/fs/cgroup/subu/, and set map[0]=ifindex(of wg_ifname in netns). - Idempotent across repeated calls. - """ - ensure_prereqs() - paths = ensure_dirs(subu_id) - # compile if missing or stale - if (not paths["build_obj"].exists()) or (paths["build_obj"].stat().st_mtime < BPF_SRC.stat().st_mtime): - compile_bpf(paths["build_obj"]) - - # if pins already exist, keep them and just ensure attached + value updated - pins_exist = all(paths[k].exists() for k in ("pin_prog_connect", "pin_prog_sendmsg")) - if not pins_exist: - load_and_pin_all(paths) - - # compute ifindex inside the subu netns - ifindex = _ifindex_in_netns(netns_name, wg_ifname) - set_ifindex(paths, ifindex) - - # ensure cgroup attachments in place - attach_to_cgroup(paths) - -def remove_steering(subu_id: str): - """ - Detach cgroup hooks and unpin programs/maps. Leaves the cgroup dir. - """ - ensure_prereqs() - paths = ensure_dirs(subu_id) - # detach (ignore failure) - detach_from_cgroup(paths) - # unpin objects - for key in ("pin_prog_connect", "pin_prog_sendmsg", "pin_map_ifindex"): - try: - p = paths[key] - if p.exists(): - p.unlink() - except Exception: - pass - # try to remove empty pin dir - try: - paths["pin_dir"].rmdir() - except Exception: - pass diff --git a/developer/source/manager/test.sh b/developer/source/manager/test.sh index b5960f4..706250b 100644 --- a/developer/source/manager/test.sh +++ b/developer/source/manager/test.sh @@ -1,13 +1,13 @@ #!/bin/env bash set -x -./subu.py # -> USAGE (exit 0) -./subu.py usage # -> USAGE -./subu.py -h # -> HELP -./subu.py --help # -> HELP -./subu.py help # -> HELP -./subu.py help WG # -> WG topic help (or full HELP if topic unknown) -./subu.py example # -> EXAMPLE -./subu.py version # -> 0.1.4 -./subu.py -V # -> 0.1.4 +./CLI # -> USAGE (exit 0) +./CLI usage # -> USAGE +./CLI -h # -> HELP +./CLI --help # -> HELP +./CLI help # -> HELP +./CLI help WG # -> WG topic help (or full HELP if topic unknown) +./CLI example # -> EXAMPLE +./CLI version # -> 0.1.4 +./CLI -V # -> 0.1.4 -- 2.20.1