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()