--- /dev/null
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+"""
+CLI.py — thin command-line harness
+Version: 0.2.0
+"""
+import sys, argparse
+from text import USAGE, HELP, EXAMPLE, VERSION
+import core
+
+def CLI(argv=None) -> int:
+ argv = argv or sys.argv[1:]
+ if not argv:
+ print(USAGE)
+ return 0
+
+ # simple verbs that bypass argparse (so `help/version/example` always work)
+ simple = {"help": HELP, "--help": HELP, "-h": HELP, "usage": USAGE, "example": EXAMPLE, "version": VERSION}
+ if argv[0] in simple:
+ out = simple[argv[0]]
+ print(out if isinstance(out, str) else out())
+ return 0
+
+ p = argparse.ArgumentParser(prog="subu", add_help=False)
+ p.add_argument("-V", "--Version", action="store_true", help="print version")
+ sub = p.add_subparsers(dest="verb")
+
+ # init
+ ap = sub.add_parser("init")
+ ap.add_argument("token", nargs="?")
+
+ # create/list/info
+ ap = sub.add_parser("create")
+ ap.add_argument("owner")
+ ap.add_argument("name")
+
+ sub.add_parser("list")
+ ap = sub.add_parser("info"); ap.add_argument("subu_id")
+ ap = sub.add_parser("information"); ap.add_argument("subu_id")
+
+ # lo
+ ap = sub.add_parser("lo")
+ ap.add_argument("state", choices=["up","down"])
+ ap.add_argument("subu_id")
+
+ # WG
+ ap = sub.add_parser("WG")
+ ap.add_argument("verb", choices=["global","create","server_provided_public_key","info","information","up","down"])
+ ap.add_argument("arg1", nargs="?")
+ ap.add_argument("arg2", nargs="?")
+
+ # attach/detach
+ ap = sub.add_parser("attach")
+ ap.add_argument("what", choices=["WG"])
+ ap.add_argument("subu_id")
+ ap.add_argument("wg_id")
+
+ ap = sub.add_parser("detach")
+ ap.add_argument("what", choices=["WG"])
+ ap.add_argument("subu_id")
+
+ # network
+ ap = sub.add_parser("network")
+ ap.add_argument("state", choices=["up","down"])
+ ap.add_argument("subu_id")
+
+ # option
+ ap = sub.add_parser("option")
+ ap.add_argument("verb", choices=["set","get","list"])
+ ap.add_argument("subu_id")
+ ap.add_argument("name", nargs="?")
+ ap.add_argument("value", nargs="?")
+
+ # exec
+ ap = sub.add_parser("exec")
+ ap.add_argument("subu_id")
+ ap.add_argument("--", dest="cmd", nargs=argparse.REMAINDER, default=[])
+
+ ns = p.parse_args(argv)
+ if ns.Version:
+ print(VERSION); return 0
+
+ try:
+ if ns.verb == "init":
+ return core.cmd_init(ns.token)
+
+ if ns.verb == "create":
+ core.create_subu(ns.owner, ns.name); return 0
+ if ns.verb == "list":
+ core.list_subu(); return 0
+ if ns.verb in ("info","information"):
+ core.info_subu(ns.subu_id); return 0
+
+ if ns.verb == "lo":
+ core.lo_toggle(ns.subu_id, ns.state); return 0
+
+ if ns.verb == "WG":
+ v = ns.verb
+ if ns.arg1 is None and v in ("info","information"):
+ print("WG info requires WG_ID"); return 2
+ if v == "global":
+ core.wg_global(ns.arg1); return 0
+ if v == "create":
+ wid = core.wg_create(ns.arg1); print(wid); return 0
+ if v == "server_provided_public_key":
+ core.wg_set_pubkey(ns.arg1, ns.arg2); return 0
+ if v in ("info","information"):
+ core.wg_info(ns.arg1); return 0
+ if v == "up":
+ core.wg_up(ns.arg1); return 0
+ if v == "down":
+ core.wg_down(ns.arg1); return 0
+
+ if ns.verb == "attach":
+ if ns.what == "WG":
+ core.attach_wg(ns.subu_id, ns.wg_id); return 0
+
+ if ns.verb == "detach":
+ if ns.what == "WG":
+ core.detach_wg(ns.subu_id); return 0
+
+ if ns.verb == "network":
+ core.network_toggle(ns.subu_id, ns.state); return 0
+
+ if ns.verb == "option":
+ if ns.verb == "option" and ns.name is None and ns.value is None and ns.verb == "list":
+ core.option_list(ns.subu_id); return 0
+ if ns.verb == "set":
+ core.option_set(ns.subu_id, ns.name, ns.value); return 0
+ if ns.verb == "get":
+ core.option_get(ns.subu_id, ns.name); return 0
+ if ns.verb == "list":
+ core.option_list(ns.subu_id); return 0
+
+ if ns.verb == "exec":
+ if not ns.cmd:
+ print("subu exec <Subu_ID> -- <cmd> ..."); return 2
+ core.exec_in_subu(ns.subu_id, ns.cmd); return 0
+
+ print(USAGE); return 2
+ except Exception as e:
+ print(f"error: {e}")
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(CLI())
+++ /dev/null
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-"""
-CLI.py — thin command-line harness
-Version: 0.2.0
-"""
-import sys, argparse
-from text import USAGE, HELP, EXAMPLE, VERSION
-import core
-
-def CLI(argv=None) -> int:
- argv = argv or sys.argv[1:]
- if not argv:
- print(USAGE)
- return 0
-
- # simple verbs that bypass argparse (so `help/version/example` always work)
- simple = {"help": HELP, "--help": HELP, "-h": HELP, "usage": USAGE, "example": EXAMPLE, "version": VERSION}
- if argv[0] in simple:
- out = simple[argv[0]]
- print(out if isinstance(out, str) else out())
- return 0
-
- p = argparse.ArgumentParser(prog="subu", add_help=False)
- p.add_argument("-V", "--Version", action="store_true", help="print version")
- sub = p.add_subparsers(dest="verb")
-
- # init
- ap = sub.add_parser("init")
- ap.add_argument("token", nargs="?")
-
- # create/list/info
- ap = sub.add_parser("create")
- ap.add_argument("owner")
- ap.add_argument("name")
-
- sub.add_parser("list")
- ap = sub.add_parser("info"); ap.add_argument("subu_id")
- ap = sub.add_parser("information"); ap.add_argument("subu_id")
-
- # lo
- ap = sub.add_parser("lo")
- ap.add_argument("state", choices=["up","down"])
- ap.add_argument("subu_id")
-
- # WG
- ap = sub.add_parser("WG")
- ap.add_argument("verb", choices=["global","create","server_provided_public_key","info","information","up","down"])
- ap.add_argument("arg1", nargs="?")
- ap.add_argument("arg2", nargs="?")
-
- # attach/detach
- ap = sub.add_parser("attach")
- ap.add_argument("what", choices=["WG"])
- ap.add_argument("subu_id")
- ap.add_argument("wg_id")
-
- ap = sub.add_parser("detach")
- ap.add_argument("what", choices=["WG"])
- ap.add_argument("subu_id")
-
- # network
- ap = sub.add_parser("network")
- ap.add_argument("state", choices=["up","down"])
- ap.add_argument("subu_id")
-
- # option
- ap = sub.add_parser("option")
- ap.add_argument("verb", choices=["set","get","list"])
- ap.add_argument("subu_id")
- ap.add_argument("name", nargs="?")
- ap.add_argument("value", nargs="?")
-
- # exec
- ap = sub.add_parser("exec")
- ap.add_argument("subu_id")
- ap.add_argument("--", dest="cmd", nargs=argparse.REMAINDER, default=[])
-
- ns = p.parse_args(argv)
- if ns.Version:
- print(VERSION); return 0
-
- try:
- if ns.verb == "init":
- return core.cmd_init(ns.token)
-
- if ns.verb == "create":
- core.create_subu(ns.owner, ns.name); return 0
- if ns.verb == "list":
- core.list_subu(); return 0
- if ns.verb in ("info","information"):
- core.info_subu(ns.subu_id); return 0
-
- if ns.verb == "lo":
- core.lo_toggle(ns.subu_id, ns.state); return 0
-
- if ns.verb == "WG":
- v = ns.verb
- if ns.arg1 is None and v in ("info","information"):
- print("WG info requires WG_ID"); return 2
- if v == "global":
- core.wg_global(ns.arg1); return 0
- if v == "create":
- wid = core.wg_create(ns.arg1); print(wid); return 0
- if v == "server_provided_public_key":
- core.wg_set_pubkey(ns.arg1, ns.arg2); return 0
- if v in ("info","information"):
- core.wg_info(ns.arg1); return 0
- if v == "up":
- core.wg_up(ns.arg1); return 0
- if v == "down":
- core.wg_down(ns.arg1); return 0
-
- if ns.verb == "attach":
- if ns.what == "WG":
- core.attach_wg(ns.subu_id, ns.wg_id); return 0
-
- if ns.verb == "detach":
- if ns.what == "WG":
- core.detach_wg(ns.subu_id); return 0
-
- if ns.verb == "network":
- core.network_toggle(ns.subu_id, ns.state); return 0
-
- if ns.verb == "option":
- if ns.verb == "option" and ns.name is None and ns.value is None and ns.verb == "list":
- core.option_list(ns.subu_id); return 0
- if ns.verb == "set":
- core.option_set(ns.subu_id, ns.name, ns.value); return 0
- if ns.verb == "get":
- core.option_get(ns.subu_id, ns.name); return 0
- if ns.verb == "list":
- core.option_list(ns.subu_id); return 0
-
- if ns.verb == "exec":
- if not ns.cmd:
- print("subu exec <Subu_ID> -- <cmd> ..."); return 2
- core.exec_in_subu(ns.subu_id, ns.cmd); return 0
-
- print(USAGE); return 2
- except Exception as e:
- print(f"error: {e}")
- return 1
-
-if __name__ == "__main__":
- sys.exit(CLI())
+++ /dev/null
-# ===== 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
+++ /dev/null
-# ===== 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
-
+++ /dev/null
-# ===== 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}")
-
+++ /dev/null
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-USAGE = """\
-usage: subu [-V] <verb> [<args>]
-
-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 <TOKEN>
- Create ./subu.db (tables: subu, wg, links, options, state).
- Requires a 6-char token (e.g., dzkq7b). Refuses if DB already exists.
-
- subu create <masu> <subu>
- Make a default subu with netns ns-<Subu_ID> containing lo only (down).
- Returns subu_N.
-
- subu list
- Columns: Subu_ID, Owner, Name, NetNS, WG_Attached?, Up/Down.
-
- subu info <Subu_ID> | subu information <Subu_ID>
- Full record + attached WG(s) + options + iface states.
-
-2.2 Loopback
-
- subu lo up <Subu_ID> | subu lo down <Subu_ID>
- Toggle loopback inside the subu’s netns.
-
-2.3 WireGuard objects (independent)
-
- subu WG global <BaseCIDR>
- e.g., 192.168.112.0/24; allocator hands out /32 peers sequentially.
- Shows current base and next free on success.
-
- subu WG create <host:port>
- Creates WG object; allocates next /32 local IP; AllowedIPs=0.0.0.0/0.
- Returns WG_M.
-
- subu WG server_provided_public_key <WG_ID> <Base64Key>
- Stores server’s pubkey.
-
- subu WG info <WG_ID> | subu WG information <WG_ID>
- Endpoint, allocated IP, pubkey set?, link state (admin/oper).
-
-2.4 Link WG ↔ subu, bring up/down
-
- subu attach WG <Subu_ID> <WG_ID>
- Creates/configures WG device inside ns-<Subu_ID>:
- - device name: subu_<M> (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 <Subu_ID>
- Remove WG device/config from the subu’s netns and remove steering; keep WG object.
-
- subu WG up <WG_ID> | subu WG down <WG_ID>
- 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_ID> | subu network down <Subu_ID>
- Only toggles admin state for all attached ifaces. On “up”, loopback
- is brought up first automatically. No route manipulation.
-
-2.5 Execution
-
- subu exec <Subu_ID> -- <cmd> …
- Run a process inside the subu’s netns.
-
-2.6 Options (persist only, for future policy)
-
- subu option list <Subu_ID>
- subu option get <Subu_ID> [name]
- subu option set <Subu_ID> <name> <value>
-
-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
-"""
+++ /dev/null
-# ===== 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")
-
+++ /dev/null
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-VERSION = "0.1.6"
+++ /dev/null
-# ===== 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 <cidr> | create <host:port> | info | server_provided_public_key <WG_id> <key> | up <WG_id> | down <WG_id>")
- 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 <cidr>'")
- 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")
-
-
+++ /dev/null
-# -*- 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/<Subu_ID>:
- force_connect4, force_sendmsg4, force_ifindex_map
- * Creates a cgroup v2 node at /sys/fs/cgroup/subu/<Subu_ID>
- * 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/<Subu_ID>/*
-CGROOT = Path("/sys/fs/cgroup/subu") # /sys/fs/cgroup/subu/<Subu_ID>
-
-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 <section> pinmaps <maps_dir>
- _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 <maps_dir>/<map_name>.
- # 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 <map> key <00 00 00 00> value <ifindex_le>
- 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 <ns> -o link show <ifname> -> "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/<subu_id>,
- attach to /sys/fs/cgroup/subu/<subu_id>, 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
#!/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