diff --git a/network/blatto/blatto.config-defaults b/network/blatto/blatto.config-defaults index de825791b67a1932bcfc3459d72bb702a9f0375c..80a304a1ac8009af1b07fc2dba2fa7545f861ffd 100644 --- a/network/blatto/blatto.config-defaults +++ b/network/blatto/blatto.config-defaults @@ -12,6 +12,7 @@ true ${blatto_wg_port:=120$blatto_wg_vlid} true ${blatto_wg_ip:=$blatto_public_ipv4} true ${blatto_upstreams:=mn awn mul} +true ${blatto_upstream_default_id:=1} true ${blatto_upstream_mn_id:=2} true ${blatto_upstream_awn_id:=3} true ${blatto_upstream_mul_id:=4} @@ -26,3 +27,6 @@ true ${blatto_ipv6:=${blatto_v6net}::${blatto_device_id}} true ${blatto_untr_ipv4:=$blatto_ipv4_prefix.7$blatto_user_id.$blatto_device_id} true ${blatto_wg_ipv4:=$blatto_wg_v4net.$blatto_device_id} true ${blatto_wg_ipv6:=${blatto_wg_v6net}::${blatto_device_id}} + +true ${blatto_wholev4:=$blatto_ipv4_prefix.0.0/16} +true ${blatto_wholev6:=${blatto_ipv6_prefix}00::0/56} diff --git a/network/blatto/scripts/con-sm b/network/blatto/scripts/con-sm index 02392cd717411a26f30aa3b1174a0ba7008ca486..3bbb86bb6f36fe6ca9cf58e7aefb42e6b1e87d41 100755 --- a/network/blatto/scripts/con-sm +++ b/network/blatto/scripts/con-sm @@ -15,11 +15,11 @@ ip route add default via $blatto_v4net.1 dev $interface metric 1000 ip route add default via $blatto_v6net::1 dev $interface metric 1000 ip route add $blatto_v4net.0/24 dev $interface metric 100 table 12 -ip route add default via $blatto_v4net.1 dev $interface metric 100 table 12 +ip route add $blatto_wholev4 via $blatto_v4net.1 dev $interface metric 100 table 12 ip -6 route add $blatto_v6net::0/64 dev $interface metric 100 table 12 -ip -6 route add default via $blatto_v6net::1 dev $interface metric 100 table 12 +ip -6 route add $blatto_wholev6 via $blatto_v6net::1 dev $interface metric 100 table 12 -for ups in $blatto_upstreams +for ups in default $blatto_upstreams do ups_id=blatto_upstream_${ups}_id ups_id=${!ups_id} diff --git a/network/blatto/untr-bl b/network/blatto/untr-bl index 8c913e803ac74b51b733d9a490943d6636895c48..4edee86f701e874a38a289bc30f44c2460425c8c 100755 --- a/network/blatto/untr-bl +++ b/network/blatto/untr-bl @@ -8,7 +8,8 @@ ifname=untr-bl ip link add $ifname type sit remote $blatto_ipv4_prefix.70.1 local $blatto_ipv4 mode any ip link set $ifname up ip a add $blatto_untr_ipv4/32 dev $ifname -ip route add default dev $ifname dev $ifname-$ups table 612 +ip route add default dev $ifname dev $ifname table 6121 +ip route add default dev $ifname dev $ifname table 6 for ups in $blatto_upstreams do diff --git a/network/blatto/wg-blatto b/network/blatto/wg-blatto index dcb2329a6b7532ac4e0e2f4b909e84451897dd72..2655602a2eeed0baf8286e0f399f002211c30e45 100755 --- a/network/blatto/wg-blatto +++ b/network/blatto/wg-blatto @@ -25,16 +25,21 @@ do ip link del wg-blatto2$ups || true ip link add wg-blatto2$ups type sit remote $blatto_wg_v4net.$ups_id local any mode any ip link set wg-blatto2$ups up - ip route add $blatto_wg_v4net/24 dev wg-blatto table 12$ups_id metric 1100 src $blatto_wg_ipv4 - ip route add $blatto_wg_v6net/64 dev wg-blatto table 12$ups_id metric 1100 src $blatto_wg_ipv4 + ip route add $blatto_wg_v4net.0/24 dev wg-blatto table 12$ups_id metric 1100 src $blatto_wg_ipv4 + ip route add $blatto_wg_v6net::0/64 dev wg-blatto table 12$ups_id metric 1100 src $blatto_wg_ipv6 ip route add default dev wg-blatto2$ups table 12$ups_id metric 1100 src $blatto_wg_ipv4 ip route add default dev wg-blatto2$ups table 12$ups_id metric 1100 src $blatto_wg_ipv6 done -ip route add $blatto_wg_v4net/24 dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv4 -ip route add $blatto_wg_v6net/64 dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv4 -ip route add default dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv4 -ip route add default dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv6 +ip route add $blatto_wg_v4net.0/24 dev wg-blatto table 121 metric 1100 src $blatto_wg_ipv4 +ip route add $blatto_wg_v6net::0/64 dev wg-blatto table 121 metric 1100 src $blatto_wg_ipv6 +ip route add default dev wg-blatto table 121 metric 1100 src $blatto_wg_ipv4 +ip route add default dev wg-blatto table 121 metric 1100 src $blatto_wg_ipv6 + +ip route add $blatto_wg_v4net.0/24 dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv4 +ip route add $blatto_wg_v6net::0/64 dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv6 +ip route add $blatto_wholev4 dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv4 +ip route add $blatto_wholev6 dev wg-blatto table 12 metric 1100 src $blatto_wg_ipv6 mkdir /run/wg-blatto/ echo $adopt > /run/wg-blatto/adopt @@ -46,8 +51,11 @@ then ip addr add $blatto_ipv4/32 dev wg-blatto metric 1000 ip addr add $blatto_ipv6/128 dev wg-blatto metric 1000 - ip route add default dev wg-blatto table 12 metric 1000 src $blatto_ipv4 - ip route add default dev wg-blatto table 12 metric 1000 src $blatto_ipv6 + ip route add $blatto_wholev4 dev wg-blatto table 12 metric 1000 src $blatto_ipv4 + ip route add $blatto_wholev6 dev wg-blatto table 12 metric 1000 src $blatto_ipv6 + + ip route add default dev wg-blatto table 121 metric 1000 src $blatto_ipv4 + ip route add default dev wg-blatto table 121 metric 1000 src $blatto_ipv6 for ups in $blatto_upstreams do @@ -59,5 +67,5 @@ then fi # HACK -ip addr del $blatto_Wg_ipv4/24 dev wg-blatto metric 1100 -ip addr add $blatto_Wg_ipv4/24 dev wg-blatto metric 1100 +ip addr del $blatto_wg_ipv4/24 dev wg-blatto metric 1100 +ip addr add $blatto_wg_ipv4/24 dev wg-blatto metric 1100 diff --git a/network/dhcpcd.enter-hook-defaults b/network/dhcpcd.enter-hook-defaults index 8f3b0d1db9b8e9d76b71d96b2d0dc99a009d9c37..2842d2c999cb5df94255fa573131f8bd47597c9f 100644 --- a/network/dhcpcd.enter-hook-defaults +++ b/network/dhcpcd.enter-hook-defaults @@ -10,11 +10,11 @@ then conntrack_hack route 10.12.11.0/24 dev $interface metric 300 table 12 - route default via 10.12.11.1 dev $interface metric 300 table 12 + route 10.12.0.0/16 via 10.12.11.1 dev $interface metric 300 table 12 route6 2a01:510:d504:751a::0/64 dev $interface metric 300 table 12 - route6 default via 2a01:510:d504:751a::1 dev $interface metric 300 table 12 + route6 2a01:510:d504:7500::0/56 via 2a01:510:d504:751a::1 dev $interface metric 300 table 12 - for i in 2 3 4 + for i in 1 2 3 4 do route default via 10.12.11.$i metric 300 table 12$i route 10.12.11.0/24 dev $interface metric 300 table 12$i diff --git a/network/init.sh b/network/init.sh index 57550c517848e9d1a3802c6d4181994e1c6de0d0..c0dbb58e3219551b47824566fe94066d2dcf494d 100755 --- a/network/init.sh +++ b/network/init.sh @@ -1,7 +1,7 @@ #!/bin/bash cd "$(dirname "$0")" . ../userconfig-lib.sh -version 9 +version 10 need_root install_begin @@ -14,6 +14,9 @@ confln jk-net.rules /etc/udev/rules.d/ cr confln iwd.conf /etc/iwd/main.conf c +confln ip-man /usr/bin/ c +init-service ip-man root "/usr/bin/ip-man server" "" "" + h=$(hostname) for i in $h/scripts/*; do diff --git a/network/ip-man b/network/ip-man new file mode 100755 index 0000000000000000000000000000000000000000..b50ee3fced2baa2dd6f7d7495aeadbbadace166e --- /dev/null +++ b/network/ip-man @@ -0,0 +1,433 @@ +#!/bin/python3 +import subprocess +import sys, os, pathlib +import argparse +import time +import json +from dataclasses import dataclass +import functools +from typing import Optional, Any +import traceback + +######################## +# Global configuration # +######################## + +socket_path = '/run/ip-man-socket' + + +force=False +no_daemon=False +verbose=1 # daemon is verbose + +is_daemon = False +if __name__ == "__main__": + if len(sys.argv)>=2 and sys.argv[1]=="server": + is_daemon = True + + +######################## +# Utils # +######################## + +class S(): + ''' + Class for nice formated long text area. + + time.Use S-""" + Text + """ + + It will remove all tailing and leading empty lines. + Then it will remove as many posiible leading spaces + from each lines (from each the same number of spaces). + ''' + def __sub__(_, a): + lines = a.split("\n") + while len(lines) and lines[0].strip() == "": + lines.pop(0) + while len(lines) and lines[-1].strip() == "": + lines.pop(-1) + + def space_count(s): + r = 0 + while r < len(s) and s[r]==' ': + r += 1 + return r + + to_remove = min(space_count(l) for l in lines if l.strip() != "") + return "\n".join("" if len(l) < to_remove else l[to_remove:] for l in lines) +S=S() + +def escape_sh(*arg): + return " ".join("'" + s.replace("'", "'\"'\"'") + "'" for s in arg) + +def r(*arg, check=None, stdin=None): + if check is None: + check = not force + if verbose: print(">", " ".join(arg)) + if stdin is None: + subprocess.run(arg, check=check) + else: + subprocess.run(arg, check=check, input=stdin) + +def nft(rules): + if verbose: print("\n".join("@ "+i for i in rules.split("\n"))) + subprocess.run(["nft", rules], check=not force) + + + +def get_spec(f): + import inspect + if 'spec' not in f.__dict__: + f.spec = inspect.getfullargspec(f) + return f.spec + +def internal_cmd(f): + return cmd(f, internal=True) +def cmd(f, internal=False): + if f is None: return f + import inspect + spec = get_spec(f) + (subcommands_internal if internal else subcommands)[f.__name__] = f + f.parser = (subparsers_internal if internal else subparsers).add_parser(f.__name__) + # print() + # print(f) + #fprint(spec) + def process_arg(name, has_default, default): + annotation = spec.annotations.get(name, None) + if annotation in [str, int, float]: + f.parser.add_argument( + ("--" if has_default else "")+arg, + type=annotation, + ) + if annotation in [list[str], list[int], list[float]]: + f.parser.add_argument( + ("--" if has_default else "")+arg, + type=annotation.__args__[0], + action="append", + ) + if annotation in [bool]: + if has_default and default is True: + f.parser.add_argument( + "--no_"+arg, + action="store_false", + dest=arg, + default=True, + ) + else: + f.parser.add_argument( + "--"+arg, + action="store_true", + ) + + for i, arg in enumerate(spec.args): + has_default = spec.defaults is not None and i >= len(spec.args) - len(spec.defaults) + default = None + if has_default: + default = spec.defaults[i - len(spec.args) + len(spec.defaults)] + process_arg(arg, has_default, default) + + for i, arg in enumerate(spec.kwonlyargs): + default = spec.kwonlydefaults[arg] + process_arg(arg, True, default) + + if spec.varargs is not None: + arg = spec.varargs + annotation = spec.annotations.get(arg, None) + f.parser.add_argument( + arg, + type=str, nargs=argparse.REMAINDER, + ) + + return f +@dataclass +class Ucred: + pid: int + uid: int + gid: int + +def my_ucred(): + return Ucred(os.getpid(), os.getuid(), os.getgid()) + +def recvall(sock): + BUFF_SIZE = 4096 + data = bytearray() + while True: + packet = sock.recv(BUFF_SIZE) + if len(packet) == 0: + break + data.extend(packet) + return data + + +daemon_funcs = {} + +def ask_server(in_struct): + import socket + connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + for i in range(100): + try: + connection.connect(socket_path) + except ConnectionRefusedError: + time.sleep(0.1) + continue + break + else: + connection.connect(socket_path) + + if verbose: print("ASK", in_struct) + in_data = json.dumps(in_struct).encode('utf-8') + connection.sendall(in_data) + connection.shutdown(socket.SHUT_WR) + out_data = recvall(connection) + out_struct = json.loads(out_data) + if verbose: print("->", out_struct) + return out_struct + +def daemon(root_only=False): + def ll(f): + spec = get_spec(f) + assert spec.args[0] == 'ucred' + spec = spec._replace(args=spec.args[1:]) + + if root_only: + @functools.wraps(f) + def l(ucred, *arg, **kvarg): + assert ucred.uid == 0 + f(ucred, *arg, **kvarg) + l.spec = get_spec(f) + daemon_funcs[f.__name__] = l + else: + daemon_funcs[f.__name__] = f + + if is_daemon: + return f + + # TODO validate types + if root_only and my_ucred().uid != 0: + return None + @functools.wraps(f) + def l(*arg, **kvarg): + if no_daemon: + return f(my_ucred(), *arg, **kvarg) + r = ask_server({"fname":f.__name__, "arg": arg, "kvarg": kvarg}) + if "exception" in r: + print(r["exception"], file=sys.stderr) + exit(1) + return r["return"] + + l.spec = spec + return l + return ll + +######################## +# Argparser # +######################## + +parser = argparse.ArgumentParser() + + +subparsers = parser.add_subparsers(help="commands", dest="subcommand") +subcommands = {} + +parser_internal = subparsers.add_parser("internal") +subparsers_internal = parser_internal.add_subparsers(help="internal_commands", dest="subcommand_internal") +subcommands_internal = {} + +parser.add_argument("-f", "--force", action='store_true') +parser.add_argument("-s", "--socket", type=str) +parser.add_argument("-v", "--verbose", action='count') +parser.add_argument("-D", "--no-daemon", action='store_true') + +def run_args(args): + import inspect + if verbose: print(args) + if not args.subcommand: + parser.print_help() + return + if args.subcommand == "internal": + if not args.subcommand_internal: + parser_internal.print_help() + return + f = subcommands_internal[args.subcommand_internal] + else: + f = subcommands[args.subcommand] + spec = get_spec(f) + f_kvarg = {} + f_arg = [] + + def process_arg(name, has_default, default): + if has_default and args.__dict__[name] is None: + return + val = args.__dict__[name] + annotation = spec.annotations.get(name, None) + if has_default: + if args.__dict__[name] is not None: + f_kvarg[name] = val + else: + f_arg.append(val) + + for i, arg in enumerate(spec.args): + has_default = spec.defaults is not None and i >= len(spec.args) - len(spec.defaults) + default = None + if has_default: + default = spec.defaults[i - len(spec.args) + len(spec.defaults)] + process_arg(arg, has_default, default) + + for i, arg in enumerate(spec.kwonlyargs): + default = spec.kwonlydefaults[arg] + process_arg(arg, True, default) + + if spec.varargs is not None: + arg = spec.varargs + annotation = spec.annotations.get(arg, None) + if annotation == tuple[str, ...]: + f_arg += args.__dict__[arg] + + if verbose: print(f_arg, f_kvarg) + r = f(*f_arg, **f_kvarg) + if r is not None: + if isinstance(r, tuple) or isinstance(r, list): + for i in r: + print(i) + else: + print(r) + +################### +# LOGIC # +################### + +def ip(*args, check=True): + args = list(args) + if args[0] in [6, -6]: + args[0] = "-6" + if args[0] in [4, -4]: + args[0] = "-4" + for i, v in enumerate(args): + if type(v) == int: + args[i] = str(v) + print(args) + r = subprocess.run(["ip", "-j", *args], stdout=subprocess.PIPE, encoding="utf-8", check=check) + return json.loads(r.stdout) if r.stdout else None + +def inet(*args, **kvargs): + return ip(-4, *args, **kvargs), ip(-6, *args, **kvargs) + +@cmd +@daemon(root_only=True) +def init(ucred): + for p in [10001, 10012, 10038]: + inet("rule", "del", "priority", p, check=False) + ip("rule", "add", "priority", 10001, "to", "95.85.217.30", "dport", "12061", "goto", 32766) + inet("rule", "add", "priority", 10012, "table", 12) + inet("rule", "add", "priority", 10038, "table", 38) + +def find_empty_priority(base, used, range_size=10): + while True: + if all(i not in used for i in range(base, base + range_size)): + return base + base += range_size +@cmd +@daemon(root_only=True) +def replace_rule(ucred, *tables: tuple[str, ...], priority_base: int = 20000, iif: str = None, blackhole: bool = False, v6: bool = True): + before = inet("rule") + priority = find_empty_priority(priority_base, {r["priority"] for x in before for r in x }) + match = [] + if iif: + match += ["iif", iif] + assert len(tables)<=8 + if not v6: + ip(-6, "rule","add", "priority", priority+0, *match, "blackhole") + for i, table in enumerate(tables): + inet("rule", "add", "priority", priority+i+1, *match, "table", table) + if blackhole: + inet("rule", "add", "priority", priority+9, *match, "blackhole") + for ipv, rlist in zip([4,6], before): + for r in rlist: + if iif: + if "iif" in r and iif == r["iif"]: + ip(ipv, "rule", "del", "priority", r["priority"]) + if not iif: + if r["priority"] not in [0, 10001, 10012, 10038, 32766, 32767] and "iif" not in r and "dst" not in r: + ip(ipv, "rule", "del", "priority", r["priority"]) + + + +################### +# MAIN # +################### + +def main_daemon(): + import socket + import struct + try: + os.unlink(socket_path) + except OSError: + if os.path.exists(socket_path): + raise + server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + server.bind(socket_path) + os.chmod(socket_path, 0o777) + + + for arg in sys.argv[2:]: + p = subprocess.Popen([sys.argv[0], *arg.split(" ")]) + + try: + while True: + server.listen(1) + print('Server is listening for incoming connections...') + connection, client_address = server.accept() + print('Connection from', str(connection), connection, client_address) + _ucred = struct.Struct("=iII") + pid, uid, gid = _ucred.unpack(connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, _ucred.size)) + ucred = Ucred(pid, uid, gid) + + in_data = recvall(connection) + in_struct = json.loads(in_data) + print("IN", in_struct) + f = daemon_funcs[in_struct["fname"]] + try: + res = f(ucred, *in_struct["arg"], **in_struct["kvarg"]) + except Exception as e: + traceback.print_exception(e) + out_struct = {'exception': str(type(e))} + else: + out_struct = {'return': res} + print("OUT", out_struct) + out_data = json.dumps(out_struct).encode('utf-8') + sys.stdout.flush() + sys.stderr.flush() + try: + connection.sendall(out_data) + except Exception as e: + traceback.print_exception(e) + try: + connection.close() + except Exception as e: + traceback.print_exception(e) + finally: + os.unlink(socket_path) + exit(1) + +def main(): + args = parser.parse_args() + + global verbose + verbose = args.verbose + global no_daemon + no_daemon = args.no_daemon + force = args.force + if args.socket is not None: + socket_path = args.socket + + run_args(args) + + + +if __name__ == "__main__": + if is_daemon: + main_daemon() + else: + main()