init
This commit is contained in:
commit
f0c1c36418
11 changed files with 833 additions and 0 deletions
4
configs/1.json
Normal file
4
configs/1.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"#note": "plain old shadowsocks config json, mode defaults to ipv4_tcp_udp, see 'MODES' in ss.py",
|
||||
"server":"1.2.3.4"
|
||||
}
|
12
configs/2.json
Normal file
12
configs/2.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"modes": [
|
||||
"ipv4_tcp_udp",
|
||||
"ipv6_tcp_udp"
|
||||
],
|
||||
"common": {
|
||||
"server":"1.2.3.4",
|
||||
"server_port":443,
|
||||
"plugin":"v2ray-plugin",
|
||||
"plugin_opts":"tls;host=1.2.3.4;path=/;mux=0"
|
||||
}
|
||||
}
|
26
configs/4.json
Normal file
26
configs/4.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"modes": [
|
||||
"ipv4_tcp_only",
|
||||
"ipv4_udp_only",
|
||||
"ipv6_tcp_only",
|
||||
"ipv6_udp_only"
|
||||
],
|
||||
"common": {
|
||||
"server":"1.2.3.4",
|
||||
"server_port":443,
|
||||
"plugin":"v2ray-plugin",
|
||||
"plugin_opts":"tls;host=1.2.3.4;path=/;mux=0"
|
||||
},
|
||||
"ipv4_tcp_only": {
|
||||
"server_port":123
|
||||
},
|
||||
"ipv4_udp_only": {
|
||||
"server_port":443
|
||||
},
|
||||
"ipv6_tcp_only": {
|
||||
"server_port":123
|
||||
},
|
||||
"ipv6_udp_only": {
|
||||
"server_port":443
|
||||
}
|
||||
}
|
41
configs/config.common.inc
Normal file
41
configs/config.common.inc
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"modes": [
|
||||
"ipv4_tcp_udp"
|
||||
],
|
||||
"common": {
|
||||
"server_port":443,
|
||||
"local_address":"127.0.0.1",
|
||||
"local_port":1080,
|
||||
"password":"hello_kitty",
|
||||
"timeout":3600,
|
||||
"method":"aes-256-gcm",
|
||||
"mode":"tcp_and_udp",
|
||||
"fast_open":true,
|
||||
"ipv6_first":false,
|
||||
"reuse_port":true,
|
||||
"protocol":"redir",
|
||||
"plugin_mode":"tcp_only",
|
||||
"tcp_redir":"redirect",
|
||||
"#tcp_redir":"tproxy",
|
||||
"udp_redir":"tproxy"
|
||||
},
|
||||
"ipv4_tcp_udp": {},
|
||||
"ipv6_tcp_udp": {
|
||||
"local_address":"::1"
|
||||
},
|
||||
"ipv4_tcp_only": {
|
||||
"mode": "tcp_only"
|
||||
},
|
||||
"ipv4_udp_only": {
|
||||
"mode": "udp_only"
|
||||
},
|
||||
"ipv6_tcp_only": {
|
||||
"local_address":"::1",
|
||||
"mode": "tcp_only"
|
||||
},
|
||||
"ipv6_udp_only": {
|
||||
"local_address":"::1",
|
||||
"mode": "udp_only"
|
||||
},
|
||||
"ipv4_ipv6_tcp_udp": {}
|
||||
}
|
14
ss-autostart.service
Normal file
14
ss-autostart.service
Normal file
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Configure transparent proxy
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
RemainAfterExit=true
|
||||
Environment="SCRIPT=/path/to/ss.py"
|
||||
ExecStart=/usr/bin/python ${SCRIPT} up
|
||||
TimeoutStopSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
286
ss.py
Executable file
286
ss.py
Executable file
|
@ -0,0 +1,286 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from functools import reduce
|
||||
import json
|
||||
import subprocess
|
||||
import ipaddress
|
||||
import socket
|
||||
|
||||
script_path = Path(__file__).parent
|
||||
local_config_path = script_path / "configs"
|
||||
include_config = local_config_path / "config.common.inc"
|
||||
ss_config_paths = [Path("/etc/shadowsocks"), Path("/etc/shadowsocks-rust"), Path("/etc/shadowsocks-libev")]
|
||||
ss_service_name = "shadowsocks-libev-redir@" # "shadowsocks-rust@"
|
||||
ss_prefix = "autogen-"
|
||||
ss_config_path =[p for p in ss_config_paths if p.exists()][0]
|
||||
ss_config_get = lambda name: ss_config_path / f"{ss_prefix}{name}.json"
|
||||
ss_service_get = lambda name: f"{ss_service_name}{ss_prefix}{name}.service"
|
||||
nft_rule_redir = script_path / "transparent-proxy.nft"
|
||||
nft_rule_v6_redir = script_path / "transparent-proxy-v6.nft"
|
||||
nft_rule_tproxy = script_path / "transparent-proxy-tproxy.nft"
|
||||
nft_rule_v6_tproxy = script_path / "transparent-proxy-v6-tproxy.nft"
|
||||
chnroute = "/etc/dnsmasq.d/chinadns_chnroute.txt"
|
||||
chnroute6 = "/etc/dnsmasq.d/chinadns_chnroute6.txt"
|
||||
proxy_interfaces = []
|
||||
proxy_interfaces_v6 = [] or proxy_interfaces
|
||||
extra_bypass = []
|
||||
|
||||
|
||||
def gen_configs(config_name: str) -> dict:
|
||||
config_inc = json.loads(include_config.read_text())
|
||||
RUST_ATTRS = {'mv': ('local_address', 'local_port', 'mode', 'protocol'), 'mvdel': ('tcp_redir', 'udp_redir')}
|
||||
assert config_inc['common']['tcp_redir'] in ('redirect', 'tproxy')
|
||||
assert config_inc['common']['udp_redir'] == 'tproxy'
|
||||
rust_only = config_inc['common']['tcp_redir'] != 'redirect'
|
||||
config = json.loads((local_config_path / f"{config_name}.json").read_text())
|
||||
MODES = ("ipv4_tcp_udp", "ipv6_tcp_udp", "ipv4_tcp_only", "ipv4_udp_only", "ipv6_tcp_only", "ipv6_udp_only", "ipv4_ipv6_tcp_udp")
|
||||
# handle legacy config
|
||||
config_common = config if "modes" not in config else config["common"]
|
||||
config_inc["common"] = dict(sorted({**config_inc["common"], **config_common}.items()))
|
||||
if "modes" in config:
|
||||
config_inc["modes"] = config["modes"]
|
||||
assert all(m in MODES for m in config_inc["modes"])
|
||||
assert config_inc["modes"]
|
||||
def assert_overlap():
|
||||
_f = lambda ipx, proto: len([m for m in config["modes"] if ipx in m and proto in m]) > 1
|
||||
IPX = ("ipv4", "ipv6")
|
||||
L4PROTO = ("tcp", "udp")
|
||||
assert not any(_f(ipx, proto) for ipx in IPX for proto in L4PROTO) # has overlap
|
||||
ipvx_enabled = lambda ipx: any(ipx in m for m in config_inc['modes'])
|
||||
# has both tcp and udp
|
||||
assert all(any(True for m in config["modes"] if ipx in m and proto in m) for ipx in IPX if ipvx_enabled(ipx) for proto in L4PROTO)
|
||||
assert_overlap()
|
||||
for m in config_inc["modes"]:
|
||||
config_inc[m] = dict(sorted({**config_inc["common"], **config_inc.get(m, dict()), **config.get(m, dict())}.items()))
|
||||
for idx, m in enumerate(config_inc["modes"]):
|
||||
config_inc[m] = dict(sorted({**config_inc["common"], **config_inc.get(m, dict())}.items()))
|
||||
if rust_only:
|
||||
config_inc[m]['locals'] = [{k: config_inc[m][k] for k in reduce(lambda x,y:x+y, RUST_ATTRS.values())}]
|
||||
for _a in reduce(lambda x,y:x+y, RUST_ATTRS.values()):
|
||||
config_inc[m][f"#{_a}"] = config_inc[m].pop(_a, None)
|
||||
else:
|
||||
for _a in reduce(lambda x,y:x+y, RUST_ATTRS.values()):
|
||||
config_inc[m][f"#{_a}"] = config_inc[m].get(_a, None)
|
||||
for _a in RUST_ATTRS['mvdel']:
|
||||
config_inc[m].pop(_a, None)
|
||||
if idx == 0:
|
||||
config_inc[m]['_meta_name'] = config_name
|
||||
return config_inc
|
||||
|
||||
def print_config_names(do_print=True) -> str:
|
||||
def get_current_up() -> str:
|
||||
primary_conf = ss_config_get(0)
|
||||
try:
|
||||
if primary_conf.exists():
|
||||
current_up = json.loads(primary_conf.read_text())['_meta_name']
|
||||
return current_up
|
||||
except Exception:
|
||||
return ""
|
||||
current_up = get_current_up()
|
||||
if do_print:
|
||||
for conf in local_config_path.iterdir():
|
||||
if conf.name.endswith('.json'):
|
||||
name = conf.name[:-len('.json')]
|
||||
_c = gen_configs(name)
|
||||
c = _c[_c["modes"][0]]
|
||||
server_info = " %s \t(%s:%d)" % (name, c["server"], c["server_port"])
|
||||
if name == current_up:
|
||||
server_info = ">" + server_info[1:]
|
||||
print(server_info)
|
||||
return current_up
|
||||
|
||||
def stop_and_remove(config_name):
|
||||
service = ss_service_get(config_name)
|
||||
if not subprocess.run(["systemctl", "is-active", service], check=False, capture_output=True).returncode:
|
||||
if subprocess.run(["systemctl", "stop", service], check=False).returncode:
|
||||
print(f"[!] systemctl stop {service} failed")
|
||||
ss_config_get(config_name).unlink()
|
||||
|
||||
def stop_all_configs():
|
||||
for conf in ss_config_path.iterdir():
|
||||
if conf.name.endswith(".json") and conf.name.startswith(ss_prefix):
|
||||
name = conf.name[len(ss_prefix):-len(".json")]
|
||||
service = ss_service_get(name)
|
||||
if not subprocess.run(["systemctl", "is-active", service], check=False, capture_output=True).returncode:
|
||||
if subprocess.run(["systemctl", "stop", service], check=False).returncode:
|
||||
print(f"[!] systemctl stop {service} failed")
|
||||
print(f"stopped {service}")
|
||||
|
||||
def write_and_enable_configs(config_dict, dry_run=False) -> bool:
|
||||
changed = [False, False, False]
|
||||
def mark_changed(x):
|
||||
changed[x] = True
|
||||
idx_to_name = {k: v for k, v in enumerate(config_dict['modes'])}
|
||||
for conf in ss_config_path.iterdir():
|
||||
if conf.name.endswith(".json") and conf.name.startswith(ss_prefix):
|
||||
name = conf.name[len(ss_prefix):-len(".json")]
|
||||
try:
|
||||
idx = int(name)
|
||||
assert idx in idx_to_name
|
||||
except Exception:
|
||||
if dry_run:
|
||||
print(f"check failed: should stop and remove {conf.name=}")
|
||||
else:
|
||||
stop_and_remove(name)
|
||||
mark_changed(0)
|
||||
for idx, name in enumerate(config_dict['modes']):
|
||||
cfgname = str(idx)
|
||||
cfg = ss_config_get(cfgname)
|
||||
old = cfg.read_text() if cfg.exists() else ""
|
||||
new = json.dumps({k:v for k, v in config_dict[name].items() if not k.startswith("#")})
|
||||
config_same = new == old
|
||||
if not config_same:
|
||||
if dry_run:
|
||||
print(f"check failed: should write {cfgname} {name}")
|
||||
else:
|
||||
cfg.write_text(new)
|
||||
mark_changed(1)
|
||||
systemd_ret = subprocess.run(["systemctl", "is-active", ss_service_get(cfgname)], check=False, capture_output=True).returncode
|
||||
def restart_service(name):
|
||||
service = ss_service_get(name)
|
||||
if dry_run:
|
||||
print(f"check failed: should start {service}")
|
||||
else:
|
||||
if subprocess.run(["systemctl", "restart", service], check=False).returncode:
|
||||
print(f"[!] systemctl start {service} failed")
|
||||
mark_changed(2)
|
||||
if systemd_ret:
|
||||
restart_service(cfgname)
|
||||
else:
|
||||
if not config_same:
|
||||
restart_service(cfgname)
|
||||
if changed[0]:
|
||||
print("deleted old config")
|
||||
if changed[1]:
|
||||
print("wrote new config")
|
||||
if changed[2]:
|
||||
print("restart systemd")
|
||||
|
||||
def invoke_self_with_sudo():
|
||||
assert os.getuid() != 0
|
||||
import sys
|
||||
return subprocess.run(["sudo", sys.executable, *sys.argv], check=False).returncode
|
||||
|
||||
def prepare_cgroup_path():
|
||||
CGv2_ROOT = Path('/sys/fs/cgroup')
|
||||
needed_slices = ('ss_bp.slice', 'ss_bp_tcp.slice', 'ss_bp_udp.slice', 'ss_fw.slice', 'ss_fw_tcp.slice', 'ss_fw_udp.slice')
|
||||
for slice in needed_slices:
|
||||
(CGv2_ROOT / slice).mkdir(exist_ok=True)
|
||||
|
||||
def process_nft_rule(configs: dict) -> list:
|
||||
nft_rule, nft_rule_v6 = (nft_rule_redir, nft_rule_v6_redir) \
|
||||
if configs['common']['tcp_redir'] == 'redirect' \
|
||||
else (nft_rule_tproxy, nft_rule_v6_tproxy)
|
||||
def get_family_proto_config(family: int, l4proto: str) -> str:
|
||||
filter_family = [m for m in configs['modes'] if f"ipv{family}" in m]
|
||||
mode = [m for m in filter_family if l4proto in m][0]
|
||||
return mode
|
||||
def process_nft_rule(family: int) -> str:
|
||||
nft_lines = list(filter(None, (nft_rule_v6 if family == 6 else nft_rule).read_text().split('\n')))
|
||||
nft_lines = nft_lines[nft_lines.index('## DO NOT CHANGE THIS LINE'):]
|
||||
|
||||
_tcp = configs[get_family_proto_config(family, 'tcp')]
|
||||
_udp = configs[get_family_proto_config(family, 'udp')]
|
||||
def get_server(hostname_or_ip: str):
|
||||
try:
|
||||
server = ipaddress.ip_address(hostname_or_ip)
|
||||
except ValueError:
|
||||
server = ipaddress.ip_address(socket.getaddrinfo(hostname_or_ip, None, type=socket.SOCK_RAW)[0][4][0])
|
||||
return server
|
||||
_tcp_server = get_server(_tcp['server'])
|
||||
_udp_server = get_server(_udp['server'])
|
||||
proxy_ifs_real = proxy_interfaces_v6 if family == 6 else proxy_interfaces
|
||||
nft_define = {
|
||||
'tcp_host': f"@empty_ipv{family}" if _tcp_server.version != family else str(_tcp_server),
|
||||
'udp_host': f"@empty_ipv{family}" if _udp_server.version != family else str(_udp_server),
|
||||
'tcp_proxy_ifnames': "{ %s }" % ', '.join([f'"{x}"' for x in proxy_ifs_real]) if proxy_ifs_real else '@empty_str',
|
||||
'udp_proxy_ifnames': "{ %s }" % ', '.join([f'"{x}"' for x in proxy_ifs_real]) if proxy_ifs_real else '@empty_str',
|
||||
'tcp_server_port': _tcp['server_port'],
|
||||
'udp_server_port': _udp['server_port'],
|
||||
'tcp_local_port': _tcp['#local_port'],
|
||||
'udp_local_port': _udp['#local_port']
|
||||
}
|
||||
nft_lines = [f"define {k} = {v}" for k, v in nft_define.items()] + nft_lines
|
||||
return '\n'.join(nft_lines)
|
||||
ipvx_enabled = lambda x: any(f"ipv{x}" in m for m in configs['modes'])
|
||||
return {x: process_nft_rule(x) for x in (4, 6) if ipvx_enabled(x)}
|
||||
|
||||
def flush_nft() -> bool:
|
||||
nft = '\n'.join((
|
||||
'add table ip transparent_proxy',
|
||||
'delete table ip transparent_proxy',
|
||||
'add table ip6 transparent_proxy_v6',
|
||||
'delete table ip6 transparent_proxy_v6',
|
||||
'add table ip6 output_deny',
|
||||
'delete table ip6 output_deny',
|
||||
)).encode('utf-8')
|
||||
if subprocess.run(["nft", "-f", "-"], input=nft, check=False).returncode:
|
||||
print("[!] nft flush failed")
|
||||
return False
|
||||
return True
|
||||
|
||||
def flush_iproute2() -> None:
|
||||
ip_batch = '\n'.join(('route flush table 100', 'rule del fwmark 0xdeaf table 100')).encode('utf-8')
|
||||
subprocess.run(["ip", "-force", "-batch", "-"], input=ip_batch, check=False, stderr=subprocess.DEVNULL)
|
||||
subprocess.run(["ip", "-6", "-force", "-batch", "-"], input=ip_batch, check=False, stderr=subprocess.DEVNULL) # always run v6 cleanup
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='ss.py')
|
||||
parser.add_argument('action', type=str, default='info', nargs='?', choices=['info', 'up', 'down'], help='what to do')
|
||||
parser.add_argument('config', type=str, default=None, nargs='?', help='config name')
|
||||
parser.add_argument('-s', '--stop-all', action='store_true', help='stop systemd units')
|
||||
args = parser.parse_args()
|
||||
if args.action == 'info':
|
||||
name = print_config_names()
|
||||
if name:
|
||||
if (local_config_path / f"{name}.json").exists():
|
||||
write_and_enable_configs(gen_configs(name), dry_run=True)
|
||||
else:
|
||||
print(f"[!] current config {name}.json is missing")
|
||||
elif args.action == 'up':
|
||||
if os.getuid() != 0:
|
||||
return invoke_self_with_sudo()
|
||||
prepare_cgroup_path()
|
||||
if not args.config:
|
||||
name = print_config_names(do_print=False)
|
||||
args.config = name
|
||||
print("autoselected config %s" % name)
|
||||
assert args.config
|
||||
configs = gen_configs(args.config)
|
||||
write_and_enable_configs(configs)
|
||||
ipvx_enabled = lambda x: any(f"ipv{x}" in m for m in configs['modes'])
|
||||
nfts = {k: v.encode('utf-8') for k, v in process_nft_rule(configs).items()}
|
||||
flush_iproute2()
|
||||
ip_batch = '\n'.join(('route add local default dev lo table 100', 'rule add fwmark 0xdeaf table 100')).encode('utf-8')
|
||||
for x in (4, 6):
|
||||
if ipvx_enabled(x):
|
||||
if subprocess.run(["ip", f"-{x}", "-force", "-batch", "-"], input=ip_batch, check=False).returncode:
|
||||
print(f"[!] iproute2 ipv{x} failed")
|
||||
flush_nft()
|
||||
for x, nft in nfts.items():
|
||||
if subprocess.run(["nft", "-f", "-"], input=nft, check=False).returncode:
|
||||
print(f"[!] nft ipv{x} failed, flushing")
|
||||
flush_nft()
|
||||
break
|
||||
else:
|
||||
bp = [ipaddress.ip_network(net) for net in extra_bypass]
|
||||
for x in (4, 6):
|
||||
if ipvx_enabled(x):
|
||||
nft_chnroute = list(filter(None, Path(chnroute6 if x==6 else chnroute).read_text().split('\n')))
|
||||
nft_chnroute.extend([str(net) for net in bp if net.version == x])
|
||||
nft_chnroute_rule = '\n'.join([(f"add element {'ip6' if x==6 else 'ip'} "
|
||||
f"transparent_proxy{'_v6' if x==6 else ''} chnroute {{ {ipx} }}") for ipx in nft_chnroute]).encode('utf-8')
|
||||
if subprocess.run(["nft", "-f", "-"], input=nft_chnroute_rule, check=False).returncode:
|
||||
print("[!] nft chnroute failed")
|
||||
elif args.action == 'down':
|
||||
if os.getuid() != 0:
|
||||
return invoke_self_with_sudo()
|
||||
flush_iproute2()
|
||||
flush_nft()
|
||||
if args.stop_all:
|
||||
stop_all_configs()
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main() or 0)
|
98
transparent-proxy-tproxy.nft
Normal file
98
transparent-proxy-tproxy.nft
Normal file
|
@ -0,0 +1,98 @@
|
|||
define tcp_host = @empty_ipv4
|
||||
define udp_host = @empty_ipv4
|
||||
define tcp_proxy_ifnames = @empty_str
|
||||
define udp_proxy_ifnames = @empty_str
|
||||
define tcp_server_port = 443
|
||||
define udp_server_port = 443
|
||||
define tcp_local_port = 1080
|
||||
define udp_local_port = 1080
|
||||
|
||||
## DO NOT CHANGE THIS LINE
|
||||
# need "ss_bp.slice", "ss_bp_tcp.slice", "ss_bp_udp.slice", "ss_fw.slice", "ss_fw_tcp.slice", "ss_fw_udp.slice"
|
||||
|
||||
add table ip transparent_proxy
|
||||
delete table ip transparent_proxy
|
||||
table ip transparent_proxy {
|
||||
set empty_ipv4 {
|
||||
type ipv4_addr
|
||||
flags constant
|
||||
}
|
||||
set empty_str {
|
||||
typeof iifname
|
||||
flags constant
|
||||
}
|
||||
set chnroute {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
|
||||
elements = {
|
||||
0.0.0.0/8,
|
||||
10.0.0.0/8,
|
||||
100.64.0.0/10,
|
||||
127.0.0.0/8,
|
||||
169.254.0.0/16,
|
||||
172.16.0.0/12,
|
||||
192.0.0.0/24,
|
||||
192.0.2.0/24,
|
||||
192.88.99.0/24,
|
||||
192.168.0.0/16,
|
||||
198.18.0.0/15,
|
||||
198.51.100.0/24,
|
||||
203.0.113.0/24,
|
||||
224.0.0.0/4,
|
||||
240.0.0.0/4,
|
||||
255.255.255.255,
|
||||
}
|
||||
}
|
||||
|
||||
chain mangle_prerouting {
|
||||
type filter hook prerouting priority mangle
|
||||
policy accept
|
||||
|
||||
ip protocol { tcp, udp } iif lo meta mark 0xdeaf goto tcp_udp_tproxy
|
||||
ip protocol tcp iifname $tcp_proxy_ifnames ip daddr != @chnroute goto tcp_udp_forward_conditional_tproxy
|
||||
ip protocol udp iifname $udp_proxy_ifnames ip daddr != @chnroute goto tcp_udp_forward_conditional_tproxy
|
||||
}
|
||||
|
||||
chain mangle_output {
|
||||
type route hook output priority mangle
|
||||
policy accept
|
||||
|
||||
ip protocol tcp ct direction reply accept
|
||||
ip protocol tcp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_tcp.slice" } accept
|
||||
ip protocol udp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_udp.slice" } accept
|
||||
ip protocol tcp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_tcp.slice" } goto tcp_udp_output_mark
|
||||
ip protocol udp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_udp.slice" } goto tcp_udp_output_mark
|
||||
ip protocol tcp ip daddr $tcp_host tcp dport != $tcp_server_port goto tcp_udp_output_mark
|
||||
ip protocol udp ip daddr $udp_host udp dport != $udp_server_port goto tcp_udp_output_mark
|
||||
ip protocol tcp ip daddr $tcp_host accept
|
||||
ip protocol udp ip daddr $udp_host accept
|
||||
ip protocol { tcp, udp } ip daddr != @chnroute goto tcp_udp_output_mark
|
||||
}
|
||||
chain tcp_udp_output_mark {
|
||||
ip protocol { tcp, udp } mark set 0xdeaf
|
||||
}
|
||||
chain tcp_udp_forward_conditional_tproxy {
|
||||
ip protocol tcp ip daddr $tcp_host tcp dport != $tcp_server_port meta mark set 0x0000deaf goto tcp_udp_tproxy
|
||||
ip protocol udp ip daddr $udp_host udp dport != $udp_server_port meta mark set 0x0000deaf goto tcp_udp_tproxy
|
||||
ip protocol tcp ip daddr $tcp_host accept
|
||||
ip protocol udp ip daddr $udp_host accept
|
||||
ip protocol { tcp, udp } ip daddr != @chnroute meta mark set 0x0000deaf goto tcp_udp_tproxy
|
||||
}
|
||||
chain tcp_udp_tproxy {
|
||||
ip protocol tcp tproxy to 127.0.0.1:$tcp_local_port
|
||||
ip protocol udp tproxy to 127.0.0.1:$udp_local_port
|
||||
}
|
||||
}
|
||||
|
||||
add table ip6 output_deny
|
||||
delete table ip6 output_deny
|
||||
table ip6 output_deny {
|
||||
chain output {
|
||||
type filter hook output priority filter
|
||||
policy accept
|
||||
|
||||
ip6 daddr != { ::/127, ::ffff:0:0/96, ::ffff:0:0:0/96, 64:ff9b::/96, 64:ff9b:1::/48, 100::/64, 2001:0000::/32, 2001:20::/28, 2001:db8::/32, fc00::/7, fe80::/64, ff00::/8 } reject
|
||||
}
|
||||
}
|
87
transparent-proxy-v6-tproxy.nft
Normal file
87
transparent-proxy-v6-tproxy.nft
Normal file
|
@ -0,0 +1,87 @@
|
|||
define tcp_host = @empty_ipv6
|
||||
define udp_host = @empty_ipv6
|
||||
define tcp_proxy_ifnames = @empty_str
|
||||
define udp_proxy_ifnames = @empty_str
|
||||
define tcp_server_port = 443
|
||||
define udp_server_port = 443
|
||||
define tcp_local_port = 1080
|
||||
define udp_local_port = 1080
|
||||
|
||||
## DO NOT CHANGE THIS LINE
|
||||
# need "ss_bp.slice", "ss_bp_tcp.slice", "ss_bp_udp.slice", "ss_fw.slice", "ss_fw_tcp.slice", "ss_fw_udp.slice"
|
||||
|
||||
# this works since v4 rule is always loaded first
|
||||
add table ip6 output_deny
|
||||
delete table ip6 output_deny
|
||||
|
||||
add table ip6 transparent_proxy_v6
|
||||
delete table ip6 transparent_proxy_v6
|
||||
table ip6 transparent_proxy_v6 {
|
||||
set empty_ipv6 {
|
||||
type ipv6_addr
|
||||
flags constant
|
||||
}
|
||||
set empty_str {
|
||||
typeof iifname
|
||||
flags constant
|
||||
}
|
||||
set chnroute {
|
||||
type ipv6_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
|
||||
elements = {
|
||||
::/127,
|
||||
::ffff:0:0/96,
|
||||
::ffff:0:0:0/96,
|
||||
64:ff9b::/96,
|
||||
64:ff9b:1::/48,
|
||||
100::/64,
|
||||
2001:0000::/32,
|
||||
2001:20::/28,
|
||||
2001:db8::/32,
|
||||
fc00::/7,
|
||||
fe80::/64,
|
||||
ff00::/8,
|
||||
}
|
||||
}
|
||||
|
||||
chain mangle_prerouting {
|
||||
type filter hook prerouting priority mangle
|
||||
policy accept
|
||||
|
||||
meta l4proto { tcp, udp } iif lo meta mark 0xdeaf goto tcp_udp_tproxy
|
||||
meta l4proto tcp iifname $tcp_proxy_ifnames ip6 daddr != @chnroute goto tcp_udp_forward_conditional_tproxy
|
||||
meta l4proto udp iifname $udp_proxy_ifnames ip6 daddr != @chnroute goto tcp_udp_forward_conditional_tproxy
|
||||
}
|
||||
|
||||
chain mangle_output {
|
||||
type route hook output priority mangle
|
||||
policy accept
|
||||
|
||||
meta l4proto tcp ct direction reply accept
|
||||
meta l4proto tcp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_tcp.slice" } accept
|
||||
meta l4proto udp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_udp.slice" } accept
|
||||
meta l4proto tcp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_tcp.slice" } goto tcp_udp_output_mark
|
||||
meta l4proto udp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_udp.slice" } goto tcp_udp_output_mark
|
||||
meta l4proto tcp ip6 daddr $tcp_host tcp dport != $tcp_server_port goto tcp_udp_output_mark
|
||||
meta l4proto udp ip6 daddr $udp_host udp dport != $udp_server_port goto tcp_udp_output_mark
|
||||
meta l4proto tcp ip6 daddr $tcp_host accept
|
||||
meta l4proto udp ip6 daddr $udp_host accept
|
||||
meta l4proto { tcp, udp } ip6 daddr != @chnroute goto tcp_udp_output_mark
|
||||
}
|
||||
chain tcp_udp_output_mark {
|
||||
meta l4proto { tcp, udp } mark set 0xdeaf
|
||||
}
|
||||
chain tcp_udp_forward_conditional_tproxy {
|
||||
meta l4proto tcp ip6 daddr $tcp_host tcp dport != $tcp_server_port meta mark set 0x0000deaf goto tcp_udp_tproxy
|
||||
meta l4proto udp ip6 daddr $udp_host udp dport != $udp_server_port meta mark set 0x0000deaf goto tcp_udp_tproxy
|
||||
meta l4proto tcp ip6 daddr $tcp_host accept
|
||||
meta l4proto udp ip6 daddr $udp_host accept
|
||||
meta l4proto { tcp, udp } ip6 daddr != @chnroute meta mark set 0x0000deaf goto tcp_udp_tproxy
|
||||
}
|
||||
chain tcp_udp_tproxy {
|
||||
meta l4proto tcp tproxy to [::1]:$tcp_local_port
|
||||
meta l4proto udp tproxy to [::1]:$udp_local_port
|
||||
}
|
||||
}
|
105
transparent-proxy-v6.nft
Normal file
105
transparent-proxy-v6.nft
Normal file
|
@ -0,0 +1,105 @@
|
|||
define tcp_host = @empty_ipv6
|
||||
define udp_host = @empty_ipv6
|
||||
define tcp_proxy_ifnames = @empty_str
|
||||
define udp_proxy_ifnames = @empty_str
|
||||
define tcp_server_port = 443
|
||||
define udp_server_port = 443
|
||||
define tcp_local_port = 1080
|
||||
define udp_local_port = 1080
|
||||
|
||||
## DO NOT CHANGE THIS LINE
|
||||
# need "ss_bp.slice", "ss_bp_tcp.slice", "ss_bp_udp.slice", "ss_fw.slice", "ss_fw_tcp.slice", "ss_fw_udp.slice"
|
||||
|
||||
# this works since v4 rule is always loaded first
|
||||
add table ip6 output_deny
|
||||
delete table ip6 output_deny
|
||||
|
||||
add table ip6 transparent_proxy_v6
|
||||
delete table ip6 transparent_proxy_v6
|
||||
table ip6 transparent_proxy_v6 {
|
||||
set empty_ipv6 {
|
||||
type ipv6_addr
|
||||
flags constant
|
||||
}
|
||||
set empty_str {
|
||||
typeof iifname
|
||||
flags constant
|
||||
}
|
||||
set chnroute {
|
||||
type ipv6_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
|
||||
elements = {
|
||||
::/127,
|
||||
::ffff:0:0/96,
|
||||
::ffff:0:0:0/96,
|
||||
64:ff9b::/96,
|
||||
64:ff9b:1::/48,
|
||||
100::/64,
|
||||
2001:0000::/32,
|
||||
2001:20::/28,
|
||||
2001:db8::/32,
|
||||
fc00::/7,
|
||||
fe80::/64,
|
||||
ff00::/8,
|
||||
}
|
||||
}
|
||||
|
||||
# tcp part
|
||||
|
||||
chain nat_prerouting {
|
||||
type nat hook prerouting priority dstnat
|
||||
policy accept
|
||||
|
||||
meta l4proto tcp iifname $tcp_proxy_ifnames jump tcp_pre_redirect
|
||||
}
|
||||
chain nat_output {
|
||||
type nat hook output priority -100
|
||||
policy accept
|
||||
|
||||
meta l4proto tcp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_tcp.slice" } accept
|
||||
meta l4proto tcp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_tcp.slice" } goto tcp_redirect
|
||||
meta l4proto tcp jump tcp_pre_redirect
|
||||
}
|
||||
chain tcp_pre_redirect {
|
||||
meta l4proto tcp ip6 daddr $tcp_host tcp dport != $tcp_server_port goto tcp_redirect
|
||||
meta l4proto tcp ip6 daddr $tcp_host accept
|
||||
meta l4proto tcp ip6 daddr != @chnroute goto tcp_redirect
|
||||
}
|
||||
chain tcp_redirect {
|
||||
meta l4proto tcp redirect to :$tcp_local_port
|
||||
}
|
||||
|
||||
# udp part
|
||||
|
||||
chain mangle_prerouting {
|
||||
type filter hook prerouting priority mangle
|
||||
policy accept
|
||||
|
||||
meta l4proto udp iif lo meta mark 0xdeaf goto udp_tproxy
|
||||
meta l4proto udp iifname $udp_proxy_ifnames ip6 daddr != @chnroute goto udp_forward_conditional_tproxy
|
||||
}
|
||||
|
||||
chain mangle_output {
|
||||
type route hook output priority mangle
|
||||
policy accept
|
||||
|
||||
meta l4proto udp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_udp.slice" } accept
|
||||
meta l4proto udp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_udp.slice" } goto udp_output_mark
|
||||
meta l4proto udp ip6 daddr $udp_host udp dport != $udp_server_port goto udp_output_mark
|
||||
meta l4proto udp ip6 daddr $udp_host accept
|
||||
meta l4proto udp ip6 daddr != @chnroute goto udp_output_mark
|
||||
}
|
||||
chain udp_output_mark {
|
||||
meta l4proto udp mark set 0xdeaf
|
||||
}
|
||||
chain udp_forward_conditional_tproxy {
|
||||
meta l4proto udp ip6 daddr $udp_host udp dport != $udp_server_port meta mark set 0x0000deaf goto udp_tproxy
|
||||
meta l4proto udp ip6 daddr $udp_host accept
|
||||
meta l4proto udp ip6 daddr != @chnroute meta mark set 0x0000deaf goto udp_tproxy
|
||||
}
|
||||
chain udp_tproxy {
|
||||
meta l4proto udp tproxy to [::1]:$udp_local_port
|
||||
}
|
||||
}
|
116
transparent-proxy.nft
Normal file
116
transparent-proxy.nft
Normal file
|
@ -0,0 +1,116 @@
|
|||
define tcp_host = @empty_ipv4
|
||||
define udp_host = @empty_ipv4
|
||||
define tcp_proxy_ifnames = @empty_str
|
||||
define udp_proxy_ifnames = @empty_str
|
||||
define tcp_server_port = 443
|
||||
define udp_server_port = 443
|
||||
define tcp_local_port = 1080
|
||||
define udp_local_port = 1080
|
||||
|
||||
## DO NOT CHANGE THIS LINE
|
||||
# need "ss_bp.slice", "ss_bp_tcp.slice", "ss_bp_udp.slice", "ss_fw.slice", "ss_fw_tcp.slice", "ss_fw_udp.slice"
|
||||
|
||||
add table ip transparent_proxy
|
||||
delete table ip transparent_proxy
|
||||
table ip transparent_proxy {
|
||||
set empty_ipv4 {
|
||||
type ipv4_addr
|
||||
flags constant
|
||||
}
|
||||
set empty_str {
|
||||
typeof iifname
|
||||
flags constant
|
||||
}
|
||||
set chnroute {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
|
||||
elements = {
|
||||
0.0.0.0/8,
|
||||
10.0.0.0/8,
|
||||
100.64.0.0/10,
|
||||
127.0.0.0/8,
|
||||
169.254.0.0/16,
|
||||
172.16.0.0/12,
|
||||
192.0.0.0/24,
|
||||
192.0.2.0/24,
|
||||
192.88.99.0/24,
|
||||
192.168.0.0/16,
|
||||
198.18.0.0/15,
|
||||
198.51.100.0/24,
|
||||
203.0.113.0/24,
|
||||
224.0.0.0/4,
|
||||
240.0.0.0/4,
|
||||
255.255.255.255,
|
||||
}
|
||||
}
|
||||
|
||||
# tcp part
|
||||
|
||||
chain nat_prerouting {
|
||||
type nat hook prerouting priority dstnat
|
||||
policy accept
|
||||
|
||||
ip protocol tcp iifname $tcp_proxy_ifnames jump tcp_pre_redirect
|
||||
}
|
||||
chain nat_output {
|
||||
type nat hook output priority -100
|
||||
policy accept
|
||||
|
||||
ip protocol tcp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_tcp.slice" } accept
|
||||
ip protocol tcp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_tcp.slice" } goto tcp_redirect
|
||||
ip protocol tcp jump tcp_pre_redirect
|
||||
}
|
||||
chain tcp_pre_redirect {
|
||||
ip protocol tcp ip daddr $tcp_host tcp dport != $tcp_server_port goto tcp_redirect
|
||||
ip protocol tcp ip daddr $tcp_host accept
|
||||
ip protocol tcp ip daddr != @chnroute goto tcp_redirect
|
||||
}
|
||||
chain tcp_redirect {
|
||||
ip protocol tcp redirect to :$tcp_local_port
|
||||
}
|
||||
|
||||
# udp part
|
||||
|
||||
chain mangle_prerouting {
|
||||
type filter hook prerouting priority mangle
|
||||
policy accept
|
||||
|
||||
ip protocol udp iif lo meta mark 0xdeaf goto udp_tproxy
|
||||
ip protocol udp iifname $udp_proxy_ifnames ip daddr != @chnroute goto udp_forward_conditional_tproxy
|
||||
}
|
||||
|
||||
chain mangle_output {
|
||||
type route hook output priority mangle
|
||||
policy accept
|
||||
|
||||
ip protocol udp socket cgroupv2 level 1 { "ss_bp.slice", "ss_bp_udp.slice" } accept
|
||||
ip protocol udp socket cgroupv2 level 1 { "ss_fw.slice", "ss_fw_udp.slice" } goto udp_output_mark
|
||||
ip protocol udp ip daddr $udp_host udp dport != $udp_server_port goto udp_output_mark
|
||||
ip protocol udp ip daddr $udp_host accept
|
||||
ip protocol udp ip daddr != @chnroute goto udp_output_mark
|
||||
}
|
||||
chain udp_output_mark {
|
||||
ip protocol udp mark set 0xdeaf
|
||||
}
|
||||
chain udp_forward_conditional_tproxy {
|
||||
ip protocol udp ip daddr $udp_host udp dport != $udp_server_port meta mark set 0x0000deaf goto udp_tproxy
|
||||
ip protocol udp ip daddr $udp_host accept
|
||||
ip protocol udp ip daddr != @chnroute meta mark set 0x0000deaf goto udp_tproxy
|
||||
}
|
||||
chain udp_tproxy {
|
||||
ip protocol udp tproxy to 127.0.0.1:$udp_local_port
|
||||
}
|
||||
}
|
||||
|
||||
add table ip6 output_deny
|
||||
delete table ip6 output_deny
|
||||
table ip6 output_deny {
|
||||
chain output {
|
||||
type filter hook output priority filter
|
||||
policy accept
|
||||
|
||||
ip6 daddr != { ::/127, ::ffff:0:0/96, ::ffff:0:0:0/96, 64:ff9b::/96, 64:ff9b:1::/48, 100::/64, 2001:0000::/32, 2001:20::/28, 2001:db8::/32, fc00::/7, fe80::/64, ff00::/8 } reject
|
||||
}
|
||||
}
|
44
update_list.sh
Executable file
44
update_list.sh
Executable file
|
@ -0,0 +1,44 @@
|
|||
#!/bin/bash
|
||||
set -e -o pipefail
|
||||
|
||||
tdir=$(mktemp -d -p /tmp update_list.XXXX)
|
||||
echo $tdir |grep -Eq '^/tmp/update_list' || exit 1
|
||||
cd $tdir
|
||||
|
||||
CHNROUTE=/etc/dnsmasq.d/chinadns_chnroute.txt
|
||||
CHNROUTE6=/etc/dnsmasq.d/chinadns_chnroute6.txt
|
||||
DNSMASQ=/etc/dnsmasq.d/dnsmasq_gfwlist.conf
|
||||
DNS_IP=127.0.0.1
|
||||
DNS_PORT=5453
|
||||
CURL_OPT='--user-agent curl/8.1.2'
|
||||
|
||||
#CHNROUTE
|
||||
FILE_CHNROUTE=$(basename $CHNROUTE)
|
||||
FILE_CHNROUTE6=$(basename $CHNROUTE6)
|
||||
curl $CURL_OPT https://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest -o delegated-apnic-latest.txt
|
||||
cat delegated-apnic-latest.txt | grep ipv4 | grep CN | awk -F\| '{printf("%s/%d\n", $4, 32-log($5)/log(2))}' >> $FILE_CHNROUTE
|
||||
cat delegated-apnic-latest.txt | grep ipv6 | grep CN | awk -F\| '{printf("%s/%d\n", $4, $5)}' >> $FILE_CHNROUTE6
|
||||
rm delegated-apnic-latest.txt
|
||||
|
||||
#GFWLIST
|
||||
FILE_DNSMASQ=$(basename $DNSMASQ)
|
||||
git clone https://github.com/felixonmars/dnsmasq-china-list.git --depth=1
|
||||
pushd dnsmasq-china-list
|
||||
make SERVER='#' dnsmasq
|
||||
cat accelerated-domains.china.dnsmasq.conf google.china.dnsmasq.conf apple.china.dnsmasq.conf > $FILE_DNSMASQ
|
||||
|
||||
curl $CURL_OPT https://publicsuffix.org/list/public_suffix_list.dat |python3 -c "
|
||||
r=[l.split('/')[1] for l in open('${FILE_DNSMASQ}').read().split('\n') if l.strip()]
|
||||
d=sorted(list({l.strip().split('.')[-1].encode('idna').decode('utf-8') for l in open(0) if l.strip() and not l.startswith('//')}))
|
||||
d=['server=/'+i+'/${DNS_IP}#${DNS_PORT}' for i in d if i not in r]
|
||||
print('\n')
|
||||
print('\n'.join(d))
|
||||
" >> $FILE_DNSMASQ
|
||||
|
||||
mv -f ../$FILE_CHNROUTE $CHNROUTE
|
||||
mv -f ../$FILE_CHNROUTE6 $CHNROUTE6
|
||||
mv -f ./$FILE_DNSMASQ $DNSMASQ
|
||||
popd
|
||||
|
||||
cd /
|
||||
rm -rf $tdir
|
Loading…
Reference in a new issue