From d559e21cec481587cd14c22d6f2d5ce97552e233 Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Mon, 3 Nov 2025 02:41:22 +0000 Subject: [PATCH] renaming modules --- developer/source/manager.tgz | Bin 0 -> 13901 bytes developer/source/manager/CLI.py | 146 ++++++++++ developer/source/manager/bpf_force_egress.c | 43 +++ developer/source/manager/core.py | 254 ++++++++++++++++++ developer/source/manager/subu.py | 138 ---------- .../source/manager/subu_BPF_force_egress.c | 44 --- developer/source/manager/subu_core.py | 235 ---------------- developer/source/manager/temp.sh | 40 +++ developer/source/manager/text.py | 109 ++++++++ developer/source/manager/worker_bpf.py | 78 ++++++ 10 files changed, 670 insertions(+), 417 deletions(-) create mode 100644 developer/source/manager.tgz create mode 100644 developer/source/manager/CLI.py create mode 100644 developer/source/manager/bpf_force_egress.c create mode 100644 developer/source/manager/core.py delete mode 100755 developer/source/manager/subu.py delete mode 100644 developer/source/manager/subu_BPF_force_egress.c delete mode 100644 developer/source/manager/subu_core.py create mode 100644 developer/source/manager/temp.sh create mode 100644 developer/source/manager/text.py create mode 100644 developer/source/manager/worker_bpf.py diff --git a/developer/source/manager.tgz b/developer/source/manager.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d585797dacc0932649ea3628240cf0739df4ffcc GIT binary patch literal 13901 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 literal 0 HcmV?d00001 diff --git a/developer/source/manager/CLI.py b/developer/source/manager/CLI.py new file mode 100644 index 0000000..a79691e --- /dev/null +++ b/developer/source/manager/CLI.py @@ -0,0 +1,146 @@ +#!/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 -- ..."); 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()) diff --git a/developer/source/manager/bpf_force_egress.c b/developer/source/manager/bpf_force_egress.c new file mode 100644 index 0000000..c3aedec --- /dev/null +++ b/developer/source/manager/bpf_force_egress.c @@ -0,0 +1,43 @@ +// -*- 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 +#include +#include + +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; +} diff --git a/developer/source/manager/core.py b/developer/source/manager/core.py new file mode 100644 index 0000000..c363ec2 --- /dev/null +++ b/developer/source/manager/core.py @@ -0,0 +1,254 @@ +# -*- 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 ` 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 ` 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) diff --git a/developer/source/manager/subu.py b/developer/source/manager/subu.py deleted file mode 100755 index 92aa8e7..0000000 --- a/developer/source/manager/subu.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/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 [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 -- ") - - 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()) diff --git a/developer/source/manager/subu_BPF_force_egress.c b/developer/source/manager/subu_BPF_force_egress.c deleted file mode 100644 index 15a0085..0000000 --- a/developer/source/manager/subu_BPF_force_egress.c +++ /dev/null @@ -1,44 +0,0 @@ -// -*- 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 -#include -#include - -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"; diff --git a/developer/source/manager/subu_core.py b/developer/source/manager/subu_core.py deleted file mode 100644 index 45ab787..0000000 --- a/developer/source/manager/subu_core.py +++ /dev/null @@ -1,235 +0,0 @@ -# -*- 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 ` 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) diff --git a/developer/source/manager/temp.sh b/developer/source/manager/temp.sh new file mode 100644 index 0000000..36855b6 --- /dev/null +++ b/developer/source/manager/temp.sh @@ -0,0 +1,40 @@ +# 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. ==" diff --git a/developer/source/manager/text.py b/developer/source/manager/text.py new file mode 100644 index 0000000..84f6762 --- /dev/null +++ b/developer/source/manager/text.py @@ -0,0 +1,109 @@ +# -*- 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 + subu create + subu list + subu info | subu information + + subu lo up|down + + subu WG global + subu WG create + subu WG server_provided_public_key + subu WG info|information + subu WG up + subu WG down + + subu attach WG + subu detach WG + + subu network up|down + + subu option set + subu option get + subu option list + + subu exec -- ... +""" + +HELP = """\ +Subu manager (v0.2.0) + +1) Init + subu init + Creates ./subu.db. Refuses to run if db exists. + +2) Subu + subu create + subu list + subu info + +3) Loopback + subu lo up|down + +4) WireGuard objects (independent of subu) + subu WG global # e.g., 192.168.112.0/24 + subu WG create # allocates next /32 + subu WG server_provided_public_key + subu WG info + subu WG up / subu WG down # admin toggle after attached + +5) Attach/detach + eBPF steering + subu attach WG + - Creates WG dev as subu_ inside ns-subu_, 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 + - Deletes device, removes cgroup + BPF + +6) Network aggregate + subu network up|down + - Ensures lo up on 'up', toggles attached WG ifaces + +7) Options + subu option set|get|list ... + +8) Exec + subu exec -- ... +""" + +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 diff --git a/developer/source/manager/worker_bpf.py b/developer/source/manager/worker_bpf.py new file mode 100644 index 0000000..96aef14 --- /dev/null +++ b/developer/source/manager/worker_bpf.py @@ -0,0 +1,78 @@ +# -*- 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 -- 2.20.1