--- /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
+// -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*-
+// bpf_force_egress.c — MVP scaffold to validate UID and prep metadata
+// Version 0.2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+char LICENSE[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, __u32); // tgid
+ __type(value, __u32); // reserved (target ifindex placeholder)
+ __uint(max_entries, 1024);
+} subu_tgid2if SEC(".maps");
+
+// Helper: return 0 = allow, <0 reject
+static __always_inline int allow_uid(struct bpf_sock_addr *ctx) {
+ // MVP: just accept everyone; you can gate on UID 2017 with bpf_get_current_uid_gid()
+ // __u32 uid = (__u32)(bpf_get_current_uid_gid() & 0xffffffff);
+ // if (uid != 2017) return -1;
+ return 0;
+}
+
+// Hook: cgroup/connect4 — runs before connect(2) proceeds
+SEC("cgroup/connect4")
+int subu_connect4(struct bpf_sock_addr *ctx)
+{
+ if (allow_uid(ctx) < 0) return -1;
+ // Future: read pinned map/meta, set SO_* via bpf_setsockopt when permitted
+ return 0;
+}
+
+// Hook: cgroup/post_bind4 — runs after a local bind is chosen
+SEC("cgroup/post_bind4")
+int subu_post_bind4(struct bpf_sock *sk)
+{
+ // Future: enforce bound dev if kernel helper allows; record tgid->ifindex
+ __u32 tgid = bpf_get_current_pid_tgid() >> 32;
+ __u32 val = 0;
+ bpf_map_update_elem(&subu_tgid2if, &tgid, &val, BPF_ANY);
+ return 0;
+}
--- /dev/null
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+"""
+core.py — worker API for subu manager
+Version: 0.2.0
+"""
+import os, sqlite3, subprocess
+from pathlib import Path
+from contextlib import closing
+from text import VERSION
+from worker_bpf import ensure_mounts, install_steering, remove_steering, BpfError
+
+DB_FILE = Path("./subu.db")
+WG_GLOBAL_FILE = Path("./WG_GLOBAL")
+
+def run(cmd, check=True):
+ r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ if check and r.returncode != 0:
+ 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
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-subu.py — CLI only.
-- No-args prints USAGE.
-- `help` / `usage` / `example` / `version` are handled *before* argparse.
-- `-h` / `--help` are mapped to `help`.
-- Delegates real work to subu_core.dispatch(args).
-"""
-
-from __future__ import annotations
-import argparse
-import sys
-
-try:
- from subu_version import VERSION
-except Exception:
- VERSION = "0.0.0-unknown"
-
-try:
- from subu_text import USAGE, HELP, EXAMPLE
-except Exception:
- USAGE = "usage: subu <verb> [args]\n"
- HELP = "help text unavailable (subu_text import failed)\n"
- EXAMPLE = "example text unavailable (subu_text import failed)\n"
-
-# -------------------------------
-# Parser construction (verbs that do real work)
-# -------------------------------
-def _build_parser() -> argparse.ArgumentParser:
- # add_help=False so -h/--help don't get auto-bound; we intercept them manually
- p = argparse.ArgumentParser(
- prog="subu",
- description="Manage subu containers, namespaces, and WireGuard attachments.",
- add_help=False,
- )
- # keep -V only; -h/--help are handled by pre-parse
- p.add_argument("-V", "--version", action="store_true",
- help="Print version and exit.")
-
- sub = p.add_subparsers(dest="verb",
- metavar="{init,create,info,information,WG,attach,detach,network,lo,option,exec}",
- required=False)
-
- sub.add_parser("init", help="Initialize new subu database (refuses if exists).")
- sub.add_parser("create", help="Create a subu (defaults only).")
- sub.add_parser("info", help="Show info about a subu.")
- sub.add_parser("information", help="Alias of 'info'.")
- sub.add_parser("WG", help="WireGuard operations.")
- sub.add_parser("attach", help="Attach WG to subu (netns + cgroup/eBPF).")
- sub.add_parser("detach", help="Detach WG from subu.")
- sub.add_parser("network", help="Bring attached ifaces up/down in the subu netns.")
- sub.add_parser("lo", help="Bring loopback up/down in the subu netns.")
- sub.add_parser("option", help="Persisted options (list/get/set).")
- sub.add_parser("exec", help="Execute a command inside the subu netns: subu exec <id> -- <cmd...>")
-
- return p
-
-def _print_topic_help(parser: argparse.ArgumentParser, topic: str) -> bool:
- """Try to print help for a specific subparser topic. Returns True if found."""
- for action in getattr(parser, "_subparsers", [])._actions:
- if isinstance(action, argparse._SubParsersAction):
- if topic in action.choices:
- action.choices[topic].print_help()
- return True
- if topic == "information" and "info" in action.choices:
- action.choices["info"].print_help()
- return True
- return False
-
-# -------------------------------
-# CLI entry (parse only)
-# -------------------------------
-def CLI(argv=None) -> int:
- argv = sys.argv[1:] if argv is None else argv
- parser = _build_parser()
-
- # 0) No args => USAGE
- if not argv:
- sys.stdout.write(USAGE)
- return 0
-
- # 1) Pre-parse intercepts (robust vs. argparse)
- first = argv[0]
- if first in ("-h", "--help", "help"):
- topic = argv[1] if len(argv) > 1 and argv[0] == "help" else None
- if topic:
- # Topic-aware help if possible; else fall back to full HELP
- if not _print_topic_help(parser, topic):
- sys.stdout.write(HELP)
- else:
- sys.stdout.write(HELP)
- return 0
-
- if first in ("usage",):
- sys.stdout.write(USAGE)
- return 0
-
- if first in ("example",):
- sys.stdout.write(EXAMPLE)
- return 0
-
- if first in ("version",):
- print(VERSION)
- return 0
-
- # 2) Normal parse
- try:
- args = parser.parse_args(argv)
- except SystemExit as e:
- return int(e.code)
-
- # 3) Global -V/--version
- if getattr(args, "version", False):
- print(VERSION)
- return 0
-
- # 4) Delegate to worker layer
- try:
- from subu_core import dispatch # type: ignore
- except Exception as e:
- sys.stderr.write(f"subu: internal error: cannot import subu_core.dispatch: {e}\n")
- return 1
-
- try:
- rc = dispatch(args)
- return int(rc) if rc is not None else 0
- except KeyboardInterrupt:
- return 130
- except SystemExit as e:
- return int(e.code)
- except Exception as e:
- sys.stderr.write(f"subu: error: {e}\n")
- return 1
-
-if __name__ == "__main__":
- sys.exit(CLI())
+++ /dev/null
-// -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
-// eBPF: force sockets inside this cgroup to use a specific ifindex
-// Hooks: cgroup/connect4 and cgroup/sendmsg4
-// Logic: read ifindex from array map[0], then setsockopt(SO_BINDTOIFINDEX)
-
-#include <linux/bpf.h>
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_endian.h>
-
-struct {
- __uint(type, BPF_MAP_TYPE_ARRAY);
- __uint(max_entries, 1);
- __type(key, __u32);
- __type(value, __u32); // ifindex
- __uint(pinning, LIBBPF_PIN_BY_NAME);
-} force_ifindex_map SEC(".maps");
-
-static __always_inline int force_bind(struct bpf_sock_addr *ctx)
-{
- __u32 k = 0;
- __u32 *ifx = bpf_map_lookup_elem(&force_ifindex_map, &k);
- if (!ifx || !*ifx)
- return 1; // allow pass-through if not configured
-
- int val = (int)*ifx;
- // This sets sk->sk_bound_dev_if equivalently to userland SO_BINDTOIFINDEX.
- // Ignore return (verifier- & failure-friendly).
- (void)bpf_setsockopt(ctx, SOL_SOCKET, SO_BINDTOIFINDEX, &val, sizeof(val));
- return 1;
-}
-
-SEC("cgroup/connect4")
-int force_dev_connect4(struct bpf_sock_addr *ctx)
-{
- return force_bind(ctx);
-}
-
-SEC("cgroup/sendmsg4")
-int force_dev_sendmsg4(struct bpf_sock_addr *ctx)
-{
- return force_bind(ctx);
-}
-
-char _license[] SEC("license") = "GPL";
+++ /dev/null
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-"""
-subu_core.py — main worker layer for Subu management
-Version 0.1.6
-"""
-
-import os, sqlite3, subprocess
-from pathlib import Path
-from contextlib import closing
-from subu_worker_bpf import install_steering, remove_steering, BpfError
-
-DB_FILE = Path("./subu.db")
-
-# ---------------------------------------------------------------------
-# SQLite helpers
-# ---------------------------------------------------------------------
-
-def db_connect():
- if not DB_FILE.exists():
- raise FileNotFoundError("subu.db not found; run `subu init <token>` first")
- return sqlite3.connect(DB_FILE)
-
-def db_init():
- if DB_FILE.exists():
- raise FileExistsError("Database already exists")
- 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("✅ subu.db created")
-
-# ---------------------------------------------------------------------
-# System helpers
-# ---------------------------------------------------------------------
-
-def run(cmd, check=True):
- r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
- if check and r.returncode != 0:
- raise RuntimeError(f"cmd failed: {' '.join(cmd)}\n{r.stderr}")
- return r.stdout.strip()
-
-def create_netns(nsname: str):
- run(["ip", "netns", "add", nsname])
- run(["ip", "-n", nsname, "link", "set", "lo", "down"])
- return nsname
-
-def delete_netns(nsname: str):
- run(["ip", "netns", "delete", nsname], check=False)
-
-def ifindex_in_netns(nsname: str, ifname: str) -> int:
- out = run(["ip", "-n", nsname, "-o", "link", "show", ifname])
- return int(out.split(":", 1)[0])
-
-# ---------------------------------------------------------------------
-# Subu operations
-# ---------------------------------------------------------------------
-
-def create_subu(owner: str, name: str) -> str:
- with closing(db_connect()) as db:
- c = db.cursor()
- c.execute("INSERT INTO subu (owner, name, netns) VALUES (?, ?, ?)",
- (owner, name, f"ns-{owner}-{name}"))
- subu_id = c.lastrowid
- db.commit()
- nsname = f"ns-subu_{subu_id}"
- create_netns(nsname)
- print(f"Created subu_{subu_id} ({owner}:{name}) with netns {nsname}")
- return f"subu_{subu_id}"
-
-def list_subu():
- with closing(db_connect()) 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_connect()) as db:
- for row in db.execute("SELECT * FROM subu WHERE id=?", (sid,)):
- print(row)
-
-def lo_toggle(subu_id: str, state: str):
- sid = int(subu_id.split("_")[1])
- with closing(db_connect()) as db:
- row = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()
- if not row: raise ValueError("subu not found")
- ns = row[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"loopback {state} in {subu_id}")
-
-# ---------------------------------------------------------------------
-# WireGuard operations
-# ---------------------------------------------------------------------
-
-def wg_global(basecidr: str):
- Path("./WG_GLOBAL").write_text(basecidr.strip() + "\n")
- print(f"Base CIDR set to {basecidr}")
-
-def wg_create(endpoint: str) -> str:
- base = Path("./WG_GLOBAL").read_text().strip() if Path("./WG_GLOBAL").exists() else None
- if not base:
- raise RuntimeError("No WG global base; set with `subu WG global`")
- with closing(db_connect()) as db:
- c = db.cursor()
- # trivial allocator: next /32 by count
- idx = c.execute("SELECT COUNT(*) FROM wg").fetchone()[0]
- octets = base.split(".")
- octets[3] = str(2 + idx)
- local_ip = ".".join(octets) + "/32"
- 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"Created WG_{wid} ({endpoint}) local_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_connect()) as db:
- db.execute("UPDATE wg SET pubkey=? WHERE id=?", (key, wid))
- db.commit()
- print(f"Public key stored for {wg_id}")
-
-def wg_info(wg_id: str):
- wid = int(wg_id.split("_")[1])
- with closing(db_connect()) as db:
- row = db.execute("SELECT * FROM wg WHERE id=?", (wid,)).fetchone()
- if not row: print("WG not found")
- else: print(row)
-
-# ---------------------------------------------------------------------
-# Attach / Detach with eBPF steering
-# ---------------------------------------------------------------------
-
-def attach_wg(subu_id: str, wg_id: str):
- sid = int(subu_id.split("_")[1])
- wid = int(wg_id.split("_")[1])
- wg_ifname = f"subu_{wid}"
- netns = f"ns-{subu_id}"
-
- # Create WG device inside namespace
- run(["ip", "link", "add", wg_ifname, "type", "wireguard"])
- run(["ip", "link", "set", wg_ifname, "netns", netns])
- # Configure MTU + accept_local
- run(["ip", "-n", netns, "link", "set", wg_ifname, "mtu", "1420"])
- run(["ip", "-n", netns, "link", "set", "dev", wg_ifname, "up"])
- print(f"Attached {wg_id} as {wg_ifname} inside {netns}")
-
- # Install steering
- try:
- install_steering(subu_id, netns, wg_ifname)
- print(f"Installed eBPF steering for {subu_id} via {wg_ifname}")
- except BpfError as e:
- print(f"warning: steering failed: {e}")
-
- # Update DB linkage
- with closing(db_connect()) as db:
- db.execute("UPDATE subu SET wg_id=? WHERE id=?", (wid, sid))
- db.commit()
-
-def detach_wg(subu_id: str):
- sid = int(subu_id.split("_")[1])
- with closing(db_connect()) as db:
- row = db.execute("SELECT wg_id, netns FROM subu WHERE id=?", (sid,)).fetchone()
- if not row or row[0] is None:
- print("nothing attached")
- return
- wid, ns = row
- wg_ifname = f"subu_{wid}"
- run(["ip", "-n", ns, "link", "del", wg_ifname], check=False)
- db.execute("UPDATE subu SET wg_id=NULL WHERE id=?", (sid,))
- db.commit()
- try:
- remove_steering(subu_id)
- print(f"Removed steering for {subu_id}")
- except BpfError as e:
- print(f"warning: remove steering failed: {e}")
-
-# ---------------------------------------------------------------------
-# Network up/down aggregate
-# ---------------------------------------------------------------------
-
-def network_toggle(subu_id: str, state: str):
- sid = int(subu_id.split("_")[1])
- with closing(db_connect()) as db:
- row = db.execute("SELECT netns, wg_id FROM subu WHERE id=?", (sid,)).fetchone()
- if not row: raise ValueError("subu not found")
- ns, wid = row
- # bring lo up first if needed
- if state == "up":
- run(["ip", "netns", "exec", ns, "ip", "link", "set", "lo", "up"], check=False)
- # bring attached iface
- if wid:
- ifname = f"subu_{wid}"
- run(["ip", "-n", ns, "link", "set", "dev", ifname, state], check=False)
- with closing(db_connect()) as db:
- db.execute("UPDATE subu SET network_state=? WHERE id=?", (state, sid))
- db.commit()
- print(f"{subu_id}: network {state}")
-
-# ---------------------------------------------------------------------
-# Exec inside namespace
-# ---------------------------------------------------------------------
-
-def exec_in_subu(subu_id: str, cmd: list):
- sid = int(subu_id.split("_")[1])
- with closing(db_connect()) as db:
- ns = db.execute("SELECT netns FROM subu WHERE id=?", (sid,)).fetchone()[0]
- full = ["ip", "netns", "exec", ns] + cmd
- os.execvp(full[0], full)
--- /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
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+VERSION = "0.2.0"
+
+USAGE = """\
+subu — Subu manager (v0.2.0)
+
+Usage:
+ subu # usage
+ subu help # detailed help
+ subu example # example workflow
+ subu version # print version
+
+ subu init <TOKEN>
+ subu create <owner> <name>
+ subu list
+ subu info <Subu_ID> | subu information <Subu_ID>
+
+ subu lo up|down <Subu_ID>
+
+ subu WG global <BaseCIDR>
+ subu WG create <host:port>
+ subu WG server_provided_public_key <WG_ID> <Base64Key>
+ subu WG info|information <WG_ID>
+ subu WG up <WG_ID>
+ subu WG down <WG_ID>
+
+ subu attach WG <Subu_ID> <WG_ID>
+ subu detach WG <Subu_ID>
+
+ subu network up|down <Subu_ID>
+
+ subu option set <Subu_ID> <name> <value>
+ subu option get <Subu_ID> <name>
+ subu option list <Subu_ID>
+
+ subu exec <Subu_ID> -- <cmd> ...
+"""
+
+HELP = """\
+Subu manager (v0.2.0)
+
+1) Init
+ subu init <TOKEN>
+ Creates ./subu.db. Refuses to run if db exists.
+
+2) Subu
+ subu create <owner> <name>
+ subu list
+ subu info <Subu_ID>
+
+3) Loopback
+ subu lo up|down <Subu_ID>
+
+4) WireGuard objects (independent of subu)
+ subu WG global <BaseCIDR> # e.g., 192.168.112.0/24
+ subu WG create <host:port> # allocates next /32
+ subu WG server_provided_public_key <WG_ID> <Base64Key>
+ subu WG info <WG_ID>
+ subu WG up <WG_ID> / subu WG down <WG_ID> # admin toggle after attached
+
+5) Attach/detach + eBPF steering
+ subu attach WG <Subu_ID> <WG_ID>
+ - Creates WG dev as subu_<M> inside ns-subu_<N>, assigns /32, MTU 1420
+ - Installs per-subu cgroup + loads eBPF scaffold (UID check, metadata map)
+ - Keeps device admin-down until `subu network up`
+ subu detach WG <Subu_ID>
+ - Deletes device, removes cgroup + BPF
+
+6) Network aggregate
+ subu network up|down <Subu_ID>
+ - Ensures lo up on 'up', toggles attached WG ifaces
+
+7) Options
+ subu option set|get|list ...
+
+8) Exec
+ subu exec <Subu_ID> -- <cmd> ...
+"""
+
+EXAMPLE = """\
+# 0) Init
+subu init dzkq7b
+
+# 1) Create Subu
+subu create Thomas US
+# -> subu_1
+
+# 2) WG pool once
+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
+"""
+
+def VERSION_string():
+ return VERSION
--- /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