--- /dev/null
+
+def attach_wg(subu_id: str, wg_id: str):
+ ensure_mounts()
+ sid = int(subu_id.split("_")[1]); wid = int(wg_id.split("_")[1])
+ with closing(_db()) as db:
+ r = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()
+ if not r: raise ValueError("subu not found")
+ ns = r[0]
+ w = db.execute("SELECT endpoint, local_ip, pubkey FROM wg WHERE id=?", (wid,)).fetchone()
+ if not w: raise ValueError("WG not found")
+ endpoint, local_ip, pubkey = w
+
+ ifname = f"subu_{wid}"
+ # create WG link in init ns, move to netns
+ run(["ip", "link", "add", ifname, "type", "wireguard"])
+ run(["ip", "link", "set", ifname, "netns", ns])
+ run(["ip", "-n", ns, "addr", "add", local_ip, "dev", ifname], check=False)
+ run(["ip", "-n", ns, "link", "set", "dev", ifname, "mtu", "1420"])
+ run(["ip", "-n", ns, "link", "set", "dev", ifname, "down"]) # keep engine down until `network up`
+
+ # install steering (MVP: create cgroup + attach bpf program)
+ try:
+ install_steering(subu_id, ns, ifname)
+ print(f"{subu_id}: eBPF steering installed -> {ifname}")
+ except BpfError as e:
+ print(f"{subu_id}: steering warning: {e}")
+
+ with closing(_db()) as db:
+ db.execute("UPDATE subu SET wg_id=? WHERE id=?", (wid, sid))
+ db.commit()
+ print(f"attached {wg_id} to {subu_id} in {ns} as {ifname}")
+
+def detach_wg(subu_id: str):
+ ensure_mounts()
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ r = db.execute("SELECT netns,wg_id FROM subu WHERE id=?", (sid,)).fetchone()
+ if not r: print("not found"); return
+ ns, wid = r
+ if wid is None:
+ print("nothing attached"); return
+ ifname = f"subu_{wid}"
+ run(["ip", "-n", ns, "link", "del", ifname], check=False)
+ try:
+ remove_steering(subu_id)
+ except BpfError as e:
+ print(f"steering remove warn: {e}")
+ with closing(_db()) as db:
+ db.execute("UPDATE subu SET wg_id=NULL WHERE id=?", (sid,))
+ db.commit()
+ print(f"detached WG_{wid} from {subu_id}")
+
--- /dev/null
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+"""
+worker_bpf.py — create per-subu cgroups and load eBPF (MVP)
+Version: 0.2.0
+"""
+import os, subprocess, json
+from pathlib import Path
+
+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)}\n{r.stderr}")
+ return r.stdout.strip()
+
+def ensure_mounts():
+ # ensure bpf and cgroup v2 are mounted
+ try:
+ Path("/sys/fs/bpf").mkdir(parents=True, exist_ok=True)
+ run(["mount","-t","bpf","bpf","/sys/fs/bpf"], check=False)
+ except Exception:
+ pass
+ try:
+ Path("/sys/fs/cgroup").mkdir(parents=True, exist_ok=True)
+ run(["mount","-t","cgroup2","none","/sys/fs/cgroup"], check=False)
+ except Exception:
+ pass
+
+def cgroup_path(subu_id: str) -> str:
+ return f"/sys/fs/cgroup/{subu_id}"
+
+def install_steering(subu_id: str, netns: str, ifname: str):
+ ensure_mounts()
+ cg = Path(cgroup_path(subu_id))
+ cg.mkdir(parents=True, exist_ok=True)
+
+ # compile BPF
+ obj = Path("./bpf_force_egress.o")
+ src = Path("./bpf_force_egress.c")
+ if not src.exists():
+ raise BpfError("bpf_force_egress.c missing next to manager")
+
+ # Build object (requires clang/llc/bpftool)
+ run(["clang","-O2","-g","-target","bpf","-c",str(src),"-o",str(obj)])
+
+ # Load program into bpffs; attach to cgroup/inet4_connect + inet4_post_bind (MVP)
+ pinned = f"/sys/fs/bpf/{subu_id}_egress"
+ run(["bpftool","prog","loadall",str(obj),pinned], check=True)
+
+ # Attach to hooks (MVP validation hooks)
+ # NOTE: these are safe no-ops for now; they validate UID and stash ifindex map.
+ for hook in ("cgroup/connect4","cgroup/post_bind4"):
+ run(["bpftool","cgroup","attach",cgroup_path(subu_id),"attach",hook,"pinned",f"{pinned}/prog_0"], check=False)
+
+ # Write metadata for ifname (saved for future prog versions)
+ meta = {"ifname": ifname}
+ Path(f"/sys/fs/bpf/{subu_id}_meta.json").write_text(json.dumps(meta))
+
+def remove_steering(subu_id: str):
+ cg = cgroup_path(subu_id)
+ # Detach whatever is attached
+ for hook in ("cgroup/connect4","cgroup/post_bind4"):
+ subprocess.run(["bpftool","cgroup","detach",cg,"detach",hook], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ # Remove pinned prog dir
+ pinned = Path(f"/sys/fs/bpf/{subu_id}_egress")
+ if pinned.exists():
+ subprocess.run(["bpftool","prog","detach",str(pinned)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ try:
+ for p in pinned.glob("*"): p.unlink()
+ pinned.rmdir()
+ except Exception:
+ pass
+ # Remove cgroup dir
+ try:
+ Path(cg).rmdir()
+ except Exception:
+ pass
from contextlib import closing
from text import VERSION
from worker_bpf import ensure_mounts, install_steering, remove_steering, BpfError
+import db
DB_FILE = Path("./subu.db")
WG_GLOBAL_FILE = Path("./WG_GLOBAL")
raise RuntimeError(f"cmd failed: {' '.join(cmd)}\n{r.stderr}")
return r.stdout.strip()
-# ---------------- DB ----------------
-def _db():
- if not DB_FILE.exists():
- raise FileNotFoundError("subu.db not found; run `subu init <token>` first")
- return sqlite3.connect(DB_FILE)
-def cmd_init(token: str|None):
- if DB_FILE.exists():
- raise FileExistsError("db already exists")
- if not token or len(token) < 6:
- raise ValueError("init requires a 6+ char token")
- with closing(sqlite3.connect(DB_FILE)) as db:
- c = db.cursor()
- c.executescript("""
- CREATE TABLE subu (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- owner TEXT,
- name TEXT,
- netns TEXT,
- lo_state TEXT DEFAULT 'down',
- wg_id INTEGER,
- network_state TEXT DEFAULT 'down'
- );
- CREATE TABLE wg (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- endpoint TEXT,
- local_ip TEXT,
- allowed_ips TEXT,
- pubkey TEXT,
- state TEXT DEFAULT 'down'
- );
- CREATE TABLE options (
- subu_id INTEGER,
- name TEXT,
- value TEXT,
- PRIMARY KEY (subu_id, name)
- );
- """)
- db.commit()
- print(f"created subu.db (v{VERSION})")
-
-# ------------- Subu ops -------------
-def create_subu(owner: str, name: str) -> str:
- with closing(_db()) as db:
- c = db.cursor()
- subu_netns = f"ns-subu_tmp" # temp; we rename after ID known
- c.execute("INSERT INTO subu (owner, name, netns) VALUES (?, ?, ?)",
- (owner, name, subu_netns))
- sid = c.lastrowid
- netns = f"ns-subu_{sid}"
- c.execute("UPDATE subu SET netns=? WHERE id=?", (netns, sid))
- db.commit()
-
- # create netns
- run(["ip", "netns", "add", netns])
- run(["ip", "-n", netns, "link", "set", "lo", "down"])
- print(f"Created subu_{sid} ({owner}:{name}) with netns {netns}")
- return f"subu_{sid}"
-
-def list_subu():
- with closing(_db()) as db:
- for row in db.execute("SELECT id, owner, name, netns, lo_state, wg_id, network_state FROM subu"):
- print(row)
-
-def info_subu(subu_id: str):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- row = db.execute("SELECT * FROM subu WHERE id=?", (sid,)).fetchone()
- if not row:
- print("not found"); return
- print(row)
- wg = db.execute("SELECT wg_id FROM subu WHERE id=?", (sid,)).fetchone()[0]
- if wg is not None:
- wrow = db.execute("SELECT * FROM wg WHERE id=?", (wg,)).fetchone()
- print("WG:", wrow)
- opts = db.execute("SELECT name,value FROM options WHERE subu_id=?", (sid,)).fetchall()
- print("Options:", opts)
-
-def lo_toggle(subu_id: str, state: str):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- ns = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()
- if not ns: raise ValueError("subu not found")
- ns = ns[0]
- run(["ip", "netns", "exec", ns, "ip", "link", "set", "lo", state])
- db.execute("UPDATE subu SET lo_state=? WHERE id=?", (state, sid))
- db.commit()
- print(f"{subu_id}: lo {state}")
-
-# ------------- WG ops ---------------
-def wg_global(basecidr: str):
- WG_GLOBAL_FILE.write_text(basecidr.strip()+"\n")
- print(f"WG pool base = {basecidr}")
-
-def _alloc_ip(idx: int, base: str) -> str:
- # simplistic /24 allocator: base must be x.y.z.0/24
- prefix = base.split("/")[0].rsplit(".", 1)[0]
- host = 2 + idx
- return f"{prefix}.{host}/32"
-
-def wg_create(endpoint: str) -> str:
- if not WG_GLOBAL_FILE.exists():
- raise RuntimeError("set WG base with `subu WG global <CIDR>` first")
- base = WG_GLOBAL_FILE.read_text().strip()
- with closing(_db()) as db:
- c = db.cursor()
- idx = c.execute("SELECT COUNT(*) FROM wg").fetchone()[0]
- local_ip = _alloc_ip(idx, base)
- c.execute("INSERT INTO wg (endpoint, local_ip, allowed_ips) VALUES (?, ?, ?)",
- (endpoint, local_ip, "0.0.0.0/0"))
- wid = c.lastrowid
- db.commit()
- print(f"WG_{wid} endpoint={endpoint} ip={local_ip}")
- return f"WG_{wid}"
-
-def wg_set_pubkey(wg_id: str, key: str):
- wid = int(wg_id.split("_")[1])
- with closing(_db()) as db:
- db.execute("UPDATE wg SET pubkey=? WHERE id=?", (key, wid))
- db.commit()
- print("ok")
-
-def wg_info(wg_id: str):
- wid = int(wg_id.split("_")[1])
- with closing(_db()) as db:
- row = db.execute("SELECT * FROM wg WHERE id=?", (wid,)).fetchone()
- print(row if row else "not found")
-
-def wg_up(wg_id: str):
- wid = int(wg_id.split("_")[1])
- # Admin-up of WG device handled via network_toggle once attached.
- print(f"{wg_id}: up (noop until attached)")
-
-def wg_down(wg_id: str):
- wid = int(wg_id.split("_")[1])
- print(f"{wg_id}: down (noop until attached)")
-
-# ---------- attach/detach + BPF ----------
-def attach_wg(subu_id: str, wg_id: str):
- ensure_mounts()
- sid = int(subu_id.split("_")[1]); wid = int(wg_id.split("_")[1])
- with closing(_db()) as db:
- r = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()
- if not r: raise ValueError("subu not found")
- ns = r[0]
- w = db.execute("SELECT endpoint, local_ip, pubkey FROM wg WHERE id=?", (wid,)).fetchone()
- if not w: raise ValueError("WG not found")
- endpoint, local_ip, pubkey = w
-
- ifname = f"subu_{wid}"
- # create WG link in init ns, move to netns
- run(["ip", "link", "add", ifname, "type", "wireguard"])
- run(["ip", "link", "set", ifname, "netns", ns])
- run(["ip", "-n", ns, "addr", "add", local_ip, "dev", ifname], check=False)
- run(["ip", "-n", ns, "link", "set", "dev", ifname, "mtu", "1420"])
- run(["ip", "-n", ns, "link", "set", "dev", ifname, "down"]) # keep engine down until `network up`
-
- # install steering (MVP: create cgroup + attach bpf program)
- try:
- install_steering(subu_id, ns, ifname)
- print(f"{subu_id}: eBPF steering installed -> {ifname}")
- except BpfError as e:
- print(f"{subu_id}: steering warning: {e}")
-
- with closing(_db()) as db:
- db.execute("UPDATE subu SET wg_id=? WHERE id=?", (wid, sid))
- db.commit()
- print(f"attached {wg_id} to {subu_id} in {ns} as {ifname}")
-
-def detach_wg(subu_id: str):
- ensure_mounts()
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- r = db.execute("SELECT netns,wg_id FROM subu WHERE id=?", (sid,)).fetchone()
- if not r: print("not found"); return
- ns, wid = r
- if wid is None:
- print("nothing attached"); return
- ifname = f"subu_{wid}"
- run(["ip", "-n", ns, "link", "del", ifname], check=False)
- try:
- remove_steering(subu_id)
- except BpfError as e:
- print(f"steering remove warn: {e}")
- with closing(_db()) as db:
- db.execute("UPDATE subu SET wg_id=NULL WHERE id=?", (sid,))
- db.commit()
- print(f"detached WG_{wid} from {subu_id}")
-
-# ------------- network up/down -------------
-def network_toggle(subu_id: str, state: str):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- ns, wid = db.execute("SELECT netns,wg_id FROM subu WHERE id=?", (sid,)).fetchone()
- # always make sure lo up on 'up'
- if state == "up":
- run(["ip", "netns", "exec", ns, "ip", "link", "set", "lo", "up"], check=False)
- if wid is not None:
- ifname = f"subu_{wid}"
- run(["ip", "-n", ns, "link", "set", "dev", ifname, state], check=False)
- with closing(_db()) as db:
- db.execute("UPDATE subu SET network_state=? WHERE id=?", (state, sid))
- db.commit()
- print(f"{subu_id}: network {state}")
-
-# ------------- options ----------------
-def option_set(subu_id: str, name: str, value: str):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- db.execute("INSERT INTO options (subu_id,name,value) VALUES(?,?,?) "
- "ON CONFLICT(subu_id,name) DO UPDATE SET value=excluded.value",
- (sid, name, value))
- db.commit()
- print("ok")
-
-def option_get(subu_id: str, name: str):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- row = db.execute("SELECT value FROM options WHERE subu_id=? AND name=?", (sid,name)).fetchone()
- print(row[0] if row else "")
-
-def option_list(subu_id: str):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- rows = db.execute("SELECT name,value FROM options WHERE subu_id=?", (sid,)).fetchall()
- for n,v in rows:
- print(f"{n}={v}")
-
-# ------------- exec -------------------
-def exec_in_subu(subu_id: str, cmd: list):
- sid = int(subu_id.split("_")[1])
- with closing(_db()) as db:
- ns = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()[0]
- os.execvp("ip", ["ip","netns","exec", ns] + cmd)
--- /dev/null
+import os
+import pwd
+import grp
+import subprocess
+from contextlib import closing
+
+def _db():
+ if not DB_FILE.exists():
+ raise FileNotFoundError("subu.db not found; run `subu init <token>` first")
+ return sqlite3.connect(DB_FILE)
+
+def init_db(path: str = DB_PATH):
+ """
+ Initialise subu.db if missing; refuse to overwrite existing file.
+ """
+ if os.path.exists(path):
+ print(f"subu: db already exists at {path}")
+ return
+
+ with closing(sqlite3.connect(path)) as db:
+ db.executescript(SCHEMA_SQL)
+ db.execute(
+ "INSERT INTO meta(key,value) VALUES ('created_at', datetime('now'))"
+ )
+ db.commit()
+ print(f"subu: created new db at {path}")
+
+
+def cmd_init(token: str|None):
+ if DB_FILE.exists():
+ raise FileExistsError("db already exists")
+ if not token or len(token) < 6:
+ raise ValueError("init requires a 6+ char token")
+ with closing(sqlite3.connect(DB_FILE)) as db:
+ c = db.cursor()
+ c.executescript("""
+ CREATE TABLE subu (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ owner TEXT,
+ name TEXT,
+ netns TEXT,
+ lo_state TEXT DEFAULT 'down',
+ wg_id INTEGER,
+ network_state TEXT DEFAULT 'down'
+ );
+ CREATE TABLE wg (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ endpoint TEXT,
+ local_ip TEXT,
+ allowed_ips TEXT,
+ pubkey TEXT,
+ state TEXT DEFAULT 'down'
+ );
+ CREATE TABLE options (
+ subu_id INTEGER,
+ name TEXT,
+ value TEXT,
+ PRIMARY KEY (subu_id, name)
+ );
+ """)
+ db.commit()
+ print(f"created subu.db (v{VERSION})")
+
+def _first_free_id(db, table: str) -> int:
+ """
+ Return the smallest non-negative integer not in table.id.
+ Assumes 'id' INTEGER PRIMARY KEY in that table.
+ """
+ rows = db.execute(f"SELECT id FROM {table} ORDER BY id ASC").fetchall()
+ used = {r[0] for r in rows}
+ i = 0
+ while i in used:
+ i += 1
+ return i
+
+def get_subu_by_full_unix_name(full_unix_name: str):
+ """
+ Return the DB row for a subu with this full_unix_name, or None.
+ """
+ with closing(open_db()) as db:
+ row = db.execute(
+ "SELECT id, owner, name, full_unix_name, path, netns_name "
+ "FROM subu WHERE full_unix_name = ?",
+ (full_unix_name,)
+ ).fetchone()
+ return row
--- /dev/null
+
+def exec_in_subu(subu_id: str, cmd: list):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ ns = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()[0]
+ os.execvp("ip", ["ip","netns","exec", ns] + cmd)
--- /dev/null
+
+def network_toggle(subu_id: str, state: str):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ ns, wid = db.execute("SELECT netns,wg_id FROM subu WHERE id=?", (sid,)).fetchone()
+ # always make sure lo up on 'up'
+ if state == "up":
+ run(["ip", "netns", "exec", ns, "ip", "link", "set", "lo", "up"], check=False)
+ if wid is not None:
+ ifname = f"subu_{wid}"
+ run(["ip", "-n", ns, "link", "set", "dev", ifname, state], check=False)
+ with closing(_db()) as db:
+ db.execute("UPDATE subu SET network_state=? WHERE id=?", (state, sid))
+ db.commit()
+ print(f"{subu_id}: network {state}")
+
+def _create_netns_for_subu(subu_id_num: int, netns_name: str):
+ """
+ Create the network namespace & bring lo down.
+ """
+ # ip netns add ns-subu_<id>
+ run(["ip", "netns", "add", netns_name])
+ # ip netns exec ns-subu_<id> ip link set lo down
+ run(["ip", "netns", "exec", netns_name, "ip", "link", "set", "lo", "down"])
--- /dev/null
+
+def option_set(subu_id: str, name: str, value: str):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ db.execute("INSERT INTO options (subu_id,name,value) VALUES(?,?,?) "
+ "ON CONFLICT(subu_id,name) DO UPDATE SET value=excluded.value",
+ (sid, name, value))
+ db.commit()
+ print("ok")
+
+def option_get(subu_id: str, name: str):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ row = db.execute("SELECT value FROM options WHERE subu_id=? AND name=?", (sid,name)).fetchone()
+ print(row[0] if row else "")
+
+def option_list(subu_id: str):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ rows = db.execute("SELECT name,value FROM options WHERE subu_id=?", (sid,)).fetchall()
+ for n,v in rows:
+ print(f"{n}={v}")
+
--- /dev/null
+verbs = [
+ "usage",
+ "help",
+ "example",
+ "version",
+ "init",
+ "make",
+ "create",
+ "info",
+ "information",
+ "WG",
+ "attach",
+ "detach",
+ "network",
+ "lo",
+ "option",
+ "exec",
+]
+
+p_make = subparsers.add_parser(
+ "make",
+ help="Create a Subu with hierarchical name + Unix user/groups + netns",
+)
+p_make.add_argument(
+ "path",
+ nargs="+",
+ help="Full Subu path, e.g. 'Thomas US' or 'Thomas new-subu Rabbit'",
+)
+
+elif args.verb == "make":
+ subu_id = core.make_subu(args.path)
+ print(subu_id)
--- /dev/null
+CREATE TABLE subu (
+ id INTEGER PRIMARY KEY,
+ owner TEXT NOT NULL, -- root user, e.g. 'Thomas'
+ name TEXT NOT NULL, -- leaf, e.g. 'US', 'Rabbit'
+ full_unix_name TEXT NOT NULL UNIQUE, -- e.g. 'Thomas_US_Rabbit'
+ path TEXT NOT NULL, -- e.g. 'Thomas US Rabbit'
+ netns_name TEXT NOT NULL,
+ wg_id INTEGER, -- nullable for now
+ created_at TEXT NOT NULL,
+ updated_at TEXT NOT NULL
+);
--- /dev/null
+# ------------- Subu ops -------------
+def create_subu(owner: str, name: str) -> str:
+ with closing(_db()) as db:
+ c = db.cursor()
+ subu_netns = f"ns-subu_tmp" # temp; we rename after ID known
+ c.execute("INSERT INTO subu (owner, name, netns) VALUES (?, ?, ?)",
+ (owner, name, subu_netns))
+ sid = c.lastrowid
+ netns = f"ns-subu_{sid}"
+ c.execute("UPDATE subu SET netns=? WHERE id=?", (netns, sid))
+ db.commit()
+
+ # create netns
+ run(["ip", "netns", "add", netns])
+ run(["ip", "-n", netns, "link", "set", "lo", "down"])
+ print(f"Created subu_{sid} ({owner}:{name}) with netns {netns}")
+ return f"subu_{sid}"
+
+def list_subu():
+ with closing(_db()) as db:
+ for row in db.execute("SELECT id, owner, name, netns, lo_state, wg_id, network_state FROM subu"):
+ print(row)
+
+def info_subu(subu_id: str):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ row = db.execute("SELECT * FROM subu WHERE id=?", (sid,)).fetchone()
+ if not row:
+ print("not found"); return
+ print(row)
+ wg = db.execute("SELECT wg_id FROM subu WHERE id=?", (sid,)).fetchone()[0]
+ if wg is not None:
+ wrow = db.execute("SELECT * FROM wg WHERE id=?", (wg,)).fetchone()
+ print("WG:", wrow)
+ opts = db.execute("SELECT name,value FROM options WHERE subu_id=?", (sid,)).fetchall()
+ print("Options:", opts)
+
+def lo_toggle(subu_id: str, state: str):
+ sid = int(subu_id.split("_")[1])
+ with closing(_db()) as db:
+ ns = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()
+ if not ns: raise ValueError("subu not found")
+ ns = ns[0]
+ run(["ip", "netns", "exec", ns, "ip", "link", "set", "lo", state])
+ db.execute("UPDATE subu SET lo_state=? WHERE id=?", (state, sid))
+ db.commit()
+ print(f"{subu_id}: lo {state}")
+
+# ---------------- High-level Subu factory ----------------
+
+def make_subu(path_tokens: list[str]) -> str:
+ """
+ Create a new Subu with hierarchical name and full wiring:
+
+ path_tokens: ['Thomas', 'US'] or ['Thomas', 'new-subu', 'Rabbit']
+
+ Rules:
+ - len(path_tokens) >= 2
+ - parent path (everything except last token) must already exist
+ as:
+ * a Unix user (for len==2: just the top-level user, e.g. 'Thomas')
+ * and as a Subu in our DB if len > 2 (e.g. 'Thomas_new-subu')
+ - new Unix user name is path joined by '_', e.g. 'Thomas_new-subu_Rabbit'
+ - mas u(root) is path_tokens[0]
+ - groups:
+ <masu>
+ <masu>-incommon
+
+ Side effects:
+ - DB row in 'subu' (id, owner, name, full_unix_name, path, netns_name, ...)
+ - netns ns-subu_<id> created with lo down
+ - Unix user created/ensured
+ - Unix groups ensured and membership updated
+
+ Returns: textual Subu_ID, e.g. 'subu_7'.
+ """
+ if not path_tokens or len(path_tokens) < 2:
+ raise SystemExit("subu: make requires at least two path elements, e.g. 'Thomas US'")
+
+ # Normalised pieces
+ path_tokens = [p.strip() for p in path_tokens if p.strip()]
+ if len(path_tokens) < 2:
+ raise SystemExit("subu: make requires at least two non-empty path elements")
+
+ masu = path_tokens[0] # root user / owner
+ leaf = path_tokens[-1] # new subu leaf
+ parent_tokens = path_tokens[:-1] # parent path
+ full_unix_name = "_".join(path_tokens) # e.g. 'Thomas_new-subu_Rabbit'
+ parent_unix_name = "_".join(parent_tokens)
+ path_str = " ".join(path_tokens) # e.g. 'Thomas new-subu Rabbit'
+
+ # 1) Enforce parent existing
+
+ # Case A: top-level subu (e.g. ['Thomas', 'US'])
+ if len(path_tokens) == 2:
+ # Require the root user to exist as a Unix user
+ if not _user_exists(masu):
+ raise SystemExit(
+ f"subu: cannot make '{path_str}': root user '{masu}' does not exist"
+ )
+ else:
+ # Case B: deeper subu: require parent subu exists in our DB
+ parent_row = get_subu_by_full_unix_name(parent_unix_name)
+ if not parent_row:
+ raise SystemExit(
+ f"subu: cannot make '{path_str}': parent subu '{parent_unix_name}' does not exist"
+ )
+
+ # Also forbid duplicate full_unix_name
+ existing = get_subu_by_full_unix_name(full_unix_name)
+ if existing:
+ raise SystemExit(
+ f"subu: subu with name '{full_unix_name}' already exists (id=subu_{existing[0]})"
+ )
+
+ # 2) Insert DB row and allocate ID + netns_name
+
+ with closing(open_db()) as db:
+ subu_id_num = _first_free_id(db, "subu")
+ netns_name = f"ns-subu_{subu_id_num}"
+
+ db.execute(
+ "INSERT INTO subu(id, owner, name, full_unix_name, path, netns_name, wg_id, created_at, updated_at) "
+ "VALUES (?, ?, ?, ?, ?, ?, NULL, datetime('now'), datetime('now'))",
+ (subu_id_num, masu, leaf, full_unix_name, path_str, netns_name)
+ )
+ db.commit()
+
+ subu_id = f"subu_{subu_id_num}"
+
+ # 3) Create netns + lo down
+ _create_netns_for_subu(subu_id_num, netns_name)
+
+ # 4) Ensure Unix user + groups
+
+ unix_user = full_unix_name
+ group_masu = masu
+ group_incommon = f"{masu}-incommon"
+
+ _ensure_group(group_masu)
+ _ensure_group(group_incommon)
+
+ _ensure_user(unix_user, group_masu)
+ _add_user_to_group(unix_user, group_masu) # mostly redundant but explicit
+ _add_user_to_group(unix_user, group_incommon)
+
+ print(f"Created Subu {subu_id} for path '{path_str}' with Unix user '{unix_user}' "
+ f"and netns '{netns_name}'")
+
+ return subu_id
+++ /dev/null
-# from: /home/Thomas/subu_data/developer/project/active/subu/developer/source/manager
-
-set -euo pipefail
-
-echo "== 1) Backup legacy-prefixed modules =="
-mkdir -p _old_prefixed
-for f in subu_*.py; do
- [ -f "$f" ] && mv -v "$f" _old_prefixed/
-done
-[ -f subu_worker_bpf.py ] && mv -v subu_worker_bpf.py _old_prefixed/ || true
-
-echo "== 2) Ensure only the new module names remain =="
-# Keep these (already present in your tar):
-# CLI.py core.py text.py worker_bpf.py bpf_force_egress.c
-ls -1
-
-echo "== 3) Make CLI runnable as 'subu' =="
-# Make sure CLI has a shebang; add if missing
-if ! head -n1 CLI.py | grep -q '^#!/usr/bin/env python3'; then
- (printf '%s\n' '#!/usr/bin/env python3' ; cat CLI.py) > .CLI.tmp && mv .CLI.tmp CLI.py
-fi
-chmod +x CLI.py
-ln -sf CLI.py subu
-chmod +x subu
-
-echo "== 4) Quick import sanity =="
-# Fail if any of the remaining files still import the old module names
-bad=$(grep -R --line-number -E 'import +subu_|from +subu_' -- *.py || true)
-if [ -n "$bad" ]; then
- echo "Found old-style imports; please fix:" >&2
- echo "$bad" >&2
- exit 1
-fi
-
-echo "== 5) Show version and help =="
-./subu version || true
-./subu help || true
-./subu || true # should print usage by default
-
-echo "== Done. If this looks good, you can delete _old_prefixed when ready. =="
+++ /dev/null
-#!/bin/env bash
-
-set -x
-./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
-
+++ /dev/null
-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
-set +x
+++ /dev/null
-++ ./subu.py
-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 + cgroup/eBPF)
- detach Detach WG from a subu
- network Bring all 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.
-++ ./subu.py 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 + cgroup/eBPF)
- detach Detach WG from a subu
- network Bring all 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.
-++ ./subu.py -h
-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, Steer?
-
- 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 — steering uses eBPF)
- - v1: enforce one WG per Subu; error if another attached
-
- subu detach WG <Subu_ID>
- Remove WG device/config from the subu’s netns; 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).
-
- 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 & (future) steering
-
- subu exec <Subu_ID> -- <cmd> …
- Run a process inside the subu’s netns.
-
- subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
- (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
- for TCP/UDP. Default: disabled.
-
-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).
-++ ./subu.py --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, Steer?
-
- 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 — steering uses eBPF)
- - v1: enforce one WG per Subu; error if another attached
-
- subu detach WG <Subu_ID>
- Remove WG device/config from the subu’s netns; 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).
-
- 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 & (future) steering
-
- subu exec <Subu_ID> -- <cmd> …
- Run a process inside the subu’s netns.
-
- subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
- (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
- for TCP/UDP. Default: disabled.
-
-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).
-++ ./subu.py 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, Steer?
-
- 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 — steering uses eBPF)
- - v1: enforce one WG per Subu; error if another attached
-
- subu detach WG <Subu_ID>
- Remove WG device/config from the subu’s netns; 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).
-
- 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 & (future) steering
-
- subu exec <Subu_ID> -- <cmd> …
- Run a process inside the subu’s netns.
-
- subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
- (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
- for TCP/UDP. Default: disabled.
-
-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).
-++ ./subu.py help WG
-usage: subu WG [-h]
-
-options:
- -h, --help show this help message and exit
-++ ./subu.py 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)
-subu attach WG subu_7 WG_0
-# -> device ns-subu_7/subu_0 configured (no default route)
-
-# 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
-
-# 8) Test from inside the subu
-subu exec subu_7 -- curl -4v https://ifconfig.me
-++ ./subu.py version
-0.1.3
-++ ./subu.py -V
-0.1.3
-++ set +x
"""
EXAMPLE = """\
-# 0) Init
-subu init dzkq7b
+# 0) Initialise the subu database (once per directory)
+subu init
+# -> created ./subu.db
+# If ./subu.db already exists, init will fail with an error and do nothing.
-# 1) Create Subu
+# 1) Create a Subu “US” owned by user Thomas
subu create Thomas US
-# -> subu_1
+# -> Subu_ID: subu_7
+# -> netns: ns-subu_7 with lo (down)
-# 2) WG pool once
+# 2) Define a global WireGuard address pool (once per host)
subu WG global 192.168.112.0/24
-
-# 3) Create WG object with endpoint
-subu WG create ReasoningTechnology.com:51820
-# -> WG_1
-
-# 4) Pubkey (placeholder)
-subu WG server_provided_public_key WG_1 ABCDEFG...xyz=
-
-# 5) Attach device and install cgroup+BPF steering
-subu attach WG subu_1 WG_1
-
-# 6) Bring network up (lo + WG)
-subu network up subu_1
-
-# 7) Test inside ns
-subu exec subu_1 -- curl -4v https://ifconfig.me
+# -> base set; next free: 192.168.112.2/32
+
+# 3) Create a WG object with endpoint (ReasoningTechnology server)
+subu WG create 35.194.71.194:51820
+# or: 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 (example key)
+subu WG server_provided_public_key WG_0 ABCDEFG...xyz=
+# -> saved
+
+# 5) Attach WG to the Subu
+subu attach WG subu_7 WG_0
+# -> creates device ns-subu_7/subu_0
+# -> assigns 192.168.112.2/32, MTU 1420, accept_local=1
+# -> enforces egress steering via cgroup/eBPF for UID(s) of subu_7
+# -> warns if lo is down in the netns
+
+# 6) Bring networking up for the Subu
+subu network up subu_7
+# -> brings lo up in ns-subu_7
+# -> brings subu_0 admin up
+
+# 7) Start the WireGuard engine for this WG
+subu WG up WG_0
+# -> interface up; handshake should start if keys/endpoint are correct
+
+# 8) Run a command inside the Subu’s netns
+subu exec subu_7 -- curl -4v https://ifconfig.me
+# Traffic from this process should egress via subu_0/US tunnel.
"""
def VERSION_string():
--- /dev/null
+# ---------------- Unix users & groups ----------------
+
+def _group_exists(name: str) -> bool:
+ try:
+ grp.getgrnam(name)
+ return True
+ except KeyError:
+ return False
+
+def _user_exists(name: str) -> bool:
+ try:
+ pwd.getpwnam(name)
+ return True
+ except KeyError:
+ return False
+
+def _ensure_group(name: str):
+ if not _group_exists(name):
+ # groupadd <name>
+ run(["groupadd", name])
+
+def _ensure_user(name: str, primary_group: str):
+ if not _user_exists(name):
+ # useradd -m -g <primary_group> -s /bin/bash <name>
+ run(["useradd", "-m", "-g", primary_group, "-s", "/bin/bash", name])
+
+def _add_user_to_group(user: str, group: str):
+ run(["usermod", "-aG", group, user])
--- /dev/null
+
+def wg_global(basecidr: str):
+ WG_GLOBAL_FILE.write_text(basecidr.strip()+"\n")
+ print(f"WG pool base = {basecidr}")
+
+def _alloc_ip(idx: int, base: str) -> str:
+ # simplistic /24 allocator: base must be x.y.z.0/24
+ prefix = base.split("/")[0].rsplit(".", 1)[0]
+ host = 2 + idx
+ return f"{prefix}.{host}/32"
+
+def wg_create(endpoint: str) -> str:
+ if not WG_GLOBAL_FILE.exists():
+ raise RuntimeError("set WG base with `subu WG global <CIDR>` first")
+ base = WG_GLOBAL_FILE.read_text().strip()
+ with closing(_db()) as db:
+ c = db.cursor()
+ idx = c.execute("SELECT COUNT(*) FROM wg").fetchone()[0]
+ local_ip = _alloc_ip(idx, base)
+ c.execute("INSERT INTO wg (endpoint, local_ip, allowed_ips) VALUES (?, ?, ?)",
+ (endpoint, local_ip, "0.0.0.0/0"))
+ wid = c.lastrowid
+ db.commit()
+ print(f"WG_{wid} endpoint={endpoint} ip={local_ip}")
+ return f"WG_{wid}"
+
+def wg_set_pubkey(wg_id: str, key: str):
+ wid = int(wg_id.split("_")[1])
+ with closing(_db()) as db:
+ db.execute("UPDATE wg SET pubkey=? WHERE id=?", (key, wid))
+ db.commit()
+ print("ok")
+
+def wg_info(wg_id: str):
+ wid = int(wg_id.split("_")[1])
+ with closing(_db()) as db:
+ row = db.execute("SELECT * FROM wg WHERE id=?", (wid,)).fetchone()
+ print(row if row else "not found")
+
+def wg_up(wg_id: str):
+ wid = int(wg_id.split("_")[1])
+ # Admin-up of WG device handled via network_toggle once attached.
+ print(f"{wg_id}: up (noop until attached)")
+
+def wg_down(wg_id: str):
+ wid = int(wg_id.split("_")[1])
+ print(f"{wg_id}: down (noop until attached)")
+
+++ /dev/null
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-"""
-worker_bpf.py — create per-subu cgroups and load eBPF (MVP)
-Version: 0.2.0
-"""
-import os, subprocess, json
-from pathlib import Path
-
-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)}\n{r.stderr}")
- return r.stdout.strip()
-
-def ensure_mounts():
- # ensure bpf and cgroup v2 are mounted
- try:
- Path("/sys/fs/bpf").mkdir(parents=True, exist_ok=True)
- run(["mount","-t","bpf","bpf","/sys/fs/bpf"], check=False)
- except Exception:
- pass
- try:
- Path("/sys/fs/cgroup").mkdir(parents=True, exist_ok=True)
- run(["mount","-t","cgroup2","none","/sys/fs/cgroup"], check=False)
- except Exception:
- pass
-
-def cgroup_path(subu_id: str) -> str:
- return f"/sys/fs/cgroup/{subu_id}"
-
-def install_steering(subu_id: str, netns: str, ifname: str):
- ensure_mounts()
- cg = Path(cgroup_path(subu_id))
- cg.mkdir(parents=True, exist_ok=True)
-
- # compile BPF
- obj = Path("./bpf_force_egress.o")
- src = Path("./bpf_force_egress.c")
- if not src.exists():
- raise BpfError("bpf_force_egress.c missing next to manager")
-
- # Build object (requires clang/llc/bpftool)
- run(["clang","-O2","-g","-target","bpf","-c",str(src),"-o",str(obj)])
-
- # Load program into bpffs; attach to cgroup/inet4_connect + inet4_post_bind (MVP)
- pinned = f"/sys/fs/bpf/{subu_id}_egress"
- run(["bpftool","prog","loadall",str(obj),pinned], check=True)
-
- # Attach to hooks (MVP validation hooks)
- # NOTE: these are safe no-ops for now; they validate UID and stash ifindex map.
- for hook in ("cgroup/connect4","cgroup/post_bind4"):
- run(["bpftool","cgroup","attach",cgroup_path(subu_id),"attach",hook,"pinned",f"{pinned}/prog_0"], check=False)
-
- # Write metadata for ifname (saved for future prog versions)
- meta = {"ifname": ifname}
- Path(f"/sys/fs/bpf/{subu_id}_meta.json").write_text(json.dumps(meta))
-
-def remove_steering(subu_id: str):
- cg = cgroup_path(subu_id)
- # Detach whatever is attached
- for hook in ("cgroup/connect4","cgroup/post_bind4"):
- subprocess.run(["bpftool","cgroup","detach",cg,"detach",hook], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
- # Remove pinned prog dir
- pinned = Path(f"/sys/fs/bpf/{subu_id}_egress")
- if pinned.exists():
- subprocess.run(["bpftool","prog","detach",str(pinned)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
- try:
- for p in pinned.glob("*"): p.unlink()
- pinned.rmdir()
- except Exception:
- pass
- # Remove cgroup dir
- try:
- Path(cg).rmdir()
- except Exception:
- pass
--- /dev/null
+#+TITLE: Subu Manager Specification
+#+AUTHOR: Reasoning Technology / Thomas Walker Lynch
+#+DATE: 2025-11-03
+#+LANGUAGE: en
+#+STARTUP: overview
+#+PROPERTY: header-args :results output
+
+* Overview
+The *Subu Manager* is a command-line orchestration tool for creating and managing
+*lightweight user-containers* (“subus”) with isolated namespaces, private
+WireGuard interfaces, and enforced network routing rules.
+
+It unifies several Linux primitives:
+
+- Unix users and groups (for identity & filesystem isolation)
+- Network namespaces (for network isolation)
+- WireGuard interfaces (for VPN / tunnel endpoints)
+- eBPF cgroup programs (for routing enforcement)
+- SQLite database (for persistence and state tracking)
+
+The manager is designed to evolve toward a full *Subu Light Container System*,
+where each user has nested subordinate users, and each subu can have its own
+network, security policies, and forwarding rules.
+
+---
+
+* Architecture Summary
+** Components
+1. =CLI.py= :: command-line interface
+2. =core.py= :: high-level orchestration logic
+3. =db.py= (planned) :: schema definition and migration
+4. =userutil.py= (planned) :: Unix account and group management helpers
+5. =netutil.py= (planned) :: namespace and interface creation
+6. =bpfutil.py= (planned) :: cgroup/eBPF setup
+
+** Data persistence
+All persistent configuration lives in =subu.db= (SQLite).
+This file contains:
+- *meta* :: creation time, schema version
+- *subu* :: all subuser accounts and their namespace info
+- *wg* :: WireGuard endpoints
+- *links* :: relationships between subu and wg interfaces
+- *options* :: boolean or key/value runtime options
+
+---
+
+* Command Overview
+Each =CLI.py= command corresponds to a top-level operation. The CLI delegates
+to core functions in =core.py=.
+
+| Command | Description | Implementation |
+|----------+--------------+----------------|
+| =init= | Create the SQLite DB and schema | `core.init_db()` |
+| =make= | Create a new subu hierarchy (user, netns, groups) | `core.make_subu(path_tokens)` |
+| =info= / =information= | Print full record of a subu | `core.get_subu_info()` |
+| =WG= | Manage WireGuard objects and their mapping | `core.create_wg()`, `core.attach_wg()` |
+| =attach= / =detach= | Link or unlink WG interface to subu namespace | `core.attach_wg_to_subu()` |
+| =network up/down= | Bring up or down all attached ifaces | `core.network_toggle()` |
+| =lo up/down= | Bring loopback up/down in subu netns | `core.lo_toggle()` |
+| =option add/remove/list= | Manage options | `core.option_add()` etc. |
+| =exec= | Run command inside subu netns | `core.exec_in_netns()` |
+| =help= / =usage= / =example= | Documentation commands | CLI only |
+| =version= | Print program version | constant in `core.VERSION` |
+
+---
+
+* Subu Creation Flow (=make=)
+
+** Syntax
+#+begin_example
+./CLI.py make Thomas new-subu Rabbit
+#+end_example
+
+** Behavior
+- Verifies that *parent path* (all but last token) exists.
+ - If two-level (e.g. =Thomas US=), requires Unix user =Thomas= exists.
+ - If deeper (e.g. =Thomas new-subu Rabbit=), requires DB entry for
+ =Thomas_new-subu=.
+- Allocates next available subu ID (first free integer).
+- Inserts row in DB with:
+ - =id=, =owner=, =name=, =full_unix_name=, =path=, =netns_name=
+- Creates network namespace =ns-subu_<id>=
+- Brings =lo= down inside that namespace.
+- Ensures Unix groups:
+ - =<masu>=
+ - =<masu>-incommon=
+- Ensures Unix user:
+ - =<masu>_<subu>...= (underscores for hierarchy)
+- Adds new user to both groups.
+
+** Implementation
+#+begin_src python
+def make_subu(path_tokens: list[str]) -> str:
+ # 1. Validate hierarchy, check parent
+ # 2. Allocate ID (via _first_free_id)
+ # 3. Insert into DB (open_db)
+ # 4. Create netns (ip netns add ...)
+ # 5. Ensure groups/users (useradd, groupadd)
+ # 6. Return subu_X identifier
+#+end_src
+
+---
+
+* User and Group Management
+
+** Goals
+Each subu is a Linux user; hierarchy is mirrored in usernames:
+#+begin_example
+Thomas_US
+Thomas_US_Rabbit
+Thomas_local
+#+end_example
+
+Each subu belongs to:
+- group =Thomas=
+- group =Thomas-incommon=
+
+** Implementation Functions
+#+begin_src python
+def _group_exists(name): ...
+def _user_exists(name): ...
+def _ensure_group(name): ...
+def _ensure_user(name, primary_group): ...
+def _add_user_to_group(user, group): ...
+#+end_src
+
+---
+
+* Database Schema (summary)
+
+#+begin_src sql
+CREATE TABLE meta (
+ key TEXT PRIMARY KEY,
+ value TEXT
+);
+
+CREATE TABLE subu (
+ id INTEGER PRIMARY KEY,
+ owner TEXT NOT NULL,
+ name TEXT NOT NULL,
+ full_unix_name TEXT NOT NULL UNIQUE,
+ path TEXT NOT NULL,
+ netns_name TEXT NOT NULL,
+ wg_id INTEGER,
+ created_at TEXT NOT NULL,
+ updated_at TEXT NOT NULL
+);
+
+CREATE TABLE wg (
+ id INTEGER PRIMARY KEY,
+ endpoint TEXT,
+ local_ip TEXT,
+ server_pubkey TEXT,
+ created_at TEXT,
+ updated_at TEXT
+);
+
+CREATE TABLE links (
+ subu_id INTEGER,
+ wg_id INTEGER,
+ FOREIGN KEY(subu_id) REFERENCES subu(id),
+ FOREIGN KEY(wg_id) REFERENCES wg(id)
+);
+
+CREATE TABLE options (
+ subu_id INTEGER,
+ name TEXT,
+ value TEXT,
+ FOREIGN KEY(subu_id) REFERENCES subu(id)
+);
+#+end_src
+
+---
+
+* Networking and Namespaces
+Each subu has a private namespace.
+
+** Steps
+1. =ip netns add ns-subu_<id>=
+2. =ip netns exec ns-subu_<id> ip link set lo down=
+3. Optionally attach WG interfaces (later).
+
+** Implementation
+#+begin_src python
+def _create_netns_for_subu(subu_id_num, netns_name):
+ run(["ip", "netns", "add", netns_name])
+ run(["ip", "netns", "exec", netns_name, "ip", "link", "set", "lo", "down"])
+#+end_src
+
+---
+
+* WireGuard Integration
+Each subu may have exactly one WG interface.
+
+** Workflow
+1. Allocate new WG object via =subu WG create <endpoint>=
+2. Record server-provided key via =subu WG server_provided_public_key=
+3. Attach interface via =subu attach WG <Subu_ID> <WG_ID>=
+4. Bring network up (includes WG admin up).
+
+** Implementation (planned)
+#+begin_src python
+def create_wg(endpoint): ...
+def attach_wg_to_subu(subu_id, wg_id): ...
+def wg_up(wg_id): ...
+def wg_down(wg_id): ...
+#+end_src
+
+---
+
+* eBPF Steering (Planned)
+The manager will attach an eBPF program to the subu’s cgroup that:
+
+- Hooks =connect()=, =bind()=, =sendmsg()=
+- Forces =SO_BINDTOIFINDEX=subu_<M>= for all sockets created by the subu
+- Guarantees all UID traffic egresses through its WG interface
+- Reuses kernel routing for MTU/GSO logic, but overrides device binding
+
+** Implementation Sketch
+#+begin_src python
+def attach_egress_bpf(subu_id, ifindex):
+ # load compiled eBPF ELF (bpf_prog_load)
+ # attach to cgroup of the subu user (BPF_PROG_ATTACH)
+ pass
+#+end_src
+
+---
+
+* Options and Policies
+
+Options are persisted flags controlling runtime behavior.
+
+| Option Name | Purpose | Default |
+|--------------+----------+----------|
+| =local_forwarding= | Enable 127/8 forwarding to WG peer | off |
+| =steer_enabled= | Enable cgroup eBPF steering | on |
+
+** Implementation
+#+begin_src python
+def option_add(subu_id, name):
+ set_option(subu_id, name, "1")
+
+def option_remove(subu_id, name):
+ db.execute("DELETE FROM options WHERE subu_id=? AND name=?", ...)
+#+end_src
+
+---
+
+* Command Examples
+
+#+begin_example
+# 0) Initialize
+CLI.py init
+# -> creates ./subu.db
+
+# 1) Create first subu
+CLI.py make Thomas US
+# -> user Thomas_US, netns ns-subu_0
+
+# 2) Create hierarchical subu
+CLI.py make Thomas new-subu Rabbit
+# -> requires Thomas_new-subu exists
+
+# 3) Bring network up
+CLI.py network up subu_0
+
+# 4) Create WireGuard pool and object
+CLI.py WG global 192.168.112.0/24
+CLI.py WG create ReasoningTechnology.com:51820
+
+# 5) Attach and activate
+CLI.py attach WG subu_0 WG_0
+CLI.py WG up WG_0
+
+# 6) Inspect
+CLI.py info subu_0
+CLI.py option list subu_0
+#+end_example
+
+---
+
+* Future Work
+1. 127/8 forwarding rewrite & mapping
+2. Server-side sifter for mapped local addresses
+3. GUI configuration (subu-light control panel)
+4. BPF loader / verifier integration
+5. Persistent daemon mode for live control
+6. Automated namespace cleanup and audit
+7. JSON-RPC or REST management API
--- /dev/null
+#!/bin/env bash
+
+set -x
+./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
+
--- /dev/null
+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
+set +x
--- /dev/null
+++ ./subu.py
+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 + cgroup/eBPF)
+ detach Detach WG from a subu
+ network Bring all 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.
+++ ./subu.py 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 + cgroup/eBPF)
+ detach Detach WG from a subu
+ network Bring all 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.
+++ ./subu.py -h
+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, Steer?
+
+ 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 — steering uses eBPF)
+ - v1: enforce one WG per Subu; error if another attached
+
+ subu detach WG <Subu_ID>
+ Remove WG device/config from the subu’s netns; 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).
+
+ 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 & (future) steering
+
+ subu exec <Subu_ID> -- <cmd> …
+ Run a process inside the subu’s netns.
+
+ subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
+ (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
+ for TCP/UDP. Default: disabled.
+
+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).
+++ ./subu.py --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, Steer?
+
+ 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 — steering uses eBPF)
+ - v1: enforce one WG per Subu; error if another attached
+
+ subu detach WG <Subu_ID>
+ Remove WG device/config from the subu’s netns; 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).
+
+ 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 & (future) steering
+
+ subu exec <Subu_ID> -- <cmd> …
+ Run a process inside the subu’s netns.
+
+ subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
+ (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
+ for TCP/UDP. Default: disabled.
+
+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).
+++ ./subu.py 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, Steer?
+
+ 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 — steering uses eBPF)
+ - v1: enforce one WG per Subu; error if another attached
+
+ subu detach WG <Subu_ID>
+ Remove WG device/config from the subu’s netns; 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).
+
+ 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 & (future) steering
+
+ subu exec <Subu_ID> -- <cmd> …
+ Run a process inside the subu’s netns.
+
+ subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
+ (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
+ for TCP/UDP. Default: disabled.
+
+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).
+++ ./subu.py help WG
+usage: subu WG [-h]
+
+options:
+ -h, --help show this help message and exit
+++ ./subu.py 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)
+subu attach WG subu_7 WG_0
+# -> device ns-subu_7/subu_0 configured (no default route)
+
+# 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
+
+# 8) Test from inside the subu
+subu exec subu_7 -- curl -4v https://ifconfig.me
+++ ./subu.py version
+0.1.3
+++ ./subu.py -V
+0.1.3
+++ set +x
--- /dev/null
+#!/bin/env bash
+
+set -x
+./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
+
--- /dev/null
+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
+set +x
--- /dev/null
+++ ./subu.py
+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 + cgroup/eBPF)
+ detach Detach WG from a subu
+ network Bring all 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.
+++ ./subu.py 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 + cgroup/eBPF)
+ detach Detach WG from a subu
+ network Bring all 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.
+++ ./subu.py -h
+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, Steer?
+
+ 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 — steering uses eBPF)
+ - v1: enforce one WG per Subu; error if another attached
+
+ subu detach WG <Subu_ID>
+ Remove WG device/config from the subu’s netns; 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).
+
+ 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 & (future) steering
+
+ subu exec <Subu_ID> -- <cmd> …
+ Run a process inside the subu’s netns.
+
+ subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
+ (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
+ for TCP/UDP. Default: disabled.
+
+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).
+++ ./subu.py --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, Steer?
+
+ 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 — steering uses eBPF)
+ - v1: enforce one WG per Subu; error if another attached
+
+ subu detach WG <Subu_ID>
+ Remove WG device/config from the subu’s netns; 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).
+
+ 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 & (future) steering
+
+ subu exec <Subu_ID> -- <cmd> …
+ Run a process inside the subu’s netns.
+
+ subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
+ (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
+ for TCP/UDP. Default: disabled.
+
+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).
+++ ./subu.py 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, Steer?
+
+ 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 — steering uses eBPF)
+ - v1: enforce one WG per Subu; error if another attached
+
+ subu detach WG <Subu_ID>
+ Remove WG device/config from the subu’s netns; 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).
+
+ 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 & (future) steering
+
+ subu exec <Subu_ID> -- <cmd> …
+ Run a process inside the subu’s netns.
+
+ subu steer enable <Subu_ID> | subu steer disable <Subu_ID>
+ (Future) Attach/detach eBPF cgroup programs to force SO_BINDTOIFINDEX=subu_<M>
+ for TCP/UDP. Default: disabled.
+
+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).
+++ ./subu.py help WG
+usage: subu WG [-h]
+
+options:
+ -h, --help show this help message and exit
+++ ./subu.py 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)
+subu attach WG subu_7 WG_0
+# -> device ns-subu_7/subu_0 configured (no default route)
+
+# 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
+
+# 8) Test from inside the subu
+subu exec subu_7 -- curl -4v https://ifconfig.me
+++ ./subu.py version
+0.1.3
+++ ./subu.py -V
+0.1.3
+++ set +x