455 lines
14 KiB
Plaintext
Executable File
455 lines
14 KiB
Plaintext
Executable File
#!/usr/bin/utpl -S
|
|
|
|
{# Thanks to homeproxy -#}
|
|
{%-
|
|
import { readfile } from 'fs';
|
|
import { cursor } from 'uci';
|
|
import { isEmpty, yqRead } from '/etc/fchomo/scripts/fchomo.uc';
|
|
|
|
const fw4 = require('fw4');
|
|
|
|
function array_to_nftarr(array) {
|
|
if (type(array) !== 'array')
|
|
return null;
|
|
|
|
return `{ ${join(', ', uniq(array))} }`;
|
|
}
|
|
|
|
function resolve_ipv6(str) {
|
|
if (isEmpty(str))
|
|
return null;
|
|
|
|
let ipv6 = fw4.parse_subnet(str)?.[0];
|
|
if (!ipv6 || ipv6.family !== 6)
|
|
return null;
|
|
|
|
if (ipv6.bits > -1)
|
|
return `${ipv6.addr}/${ipv6.bits}`;
|
|
else
|
|
return `& ${ipv6.mask} == ${ipv6.addr}`;
|
|
}
|
|
|
|
function resolve_mark(str) {
|
|
if (isEmpty(str))
|
|
return null;
|
|
|
|
let mark = fw4.parse_mark(str);
|
|
if (isEmpty(mark))
|
|
return null;
|
|
|
|
if (mark.mask === 0xffffffff)
|
|
return fw4.hex(mark.mark);
|
|
else if (mark.mark === 0)
|
|
return `mark and ${fw4.hex(~mark.mask & 0xffffffff)}`;
|
|
else if (mark.mark === mark.mask)
|
|
return `mark or ${fw4.hex(mark.mark)}`;
|
|
else if (mark.mask === 0)
|
|
return `mark xor ${fw4.hex(mark.mark)}`;
|
|
else
|
|
return `mark and ${fw4.hex(~mark.mask & 0xffffffff)} xor ${fw4.hex(mark.mark)}`;
|
|
}
|
|
|
|
/* Misc config */
|
|
const resources_dir = '/etc/fchomo/resources';
|
|
|
|
/* UCI config start */
|
|
const cfgname = 'fchomo';
|
|
const uci = cursor();
|
|
uci.load(cfgname);
|
|
|
|
const common_tcpport = uci.get(cfgname, 'config', 'common_tcpport') || '20-21,22,53,80,110,143,443,465,853,873,993,995,8080,8443,9418',
|
|
common_udpport = uci.get(cfgname, 'config', 'common_udpport') || '20-21,22,53,80,110,143,443,853,993,995,8080,8443,9418',
|
|
stun_port = uci.get(cfgname, 'config', 'stun_port') || '3478,19302',
|
|
tun_name = uci.get(cfgname, 'config', 'tun_name') || 'hmtun0',
|
|
self_mark = uci.get(cfgname, 'config', 'self_mark') || '200',
|
|
tproxy_mark = resolve_mark(uci.get(cfgname, 'config', 'tproxy_mark') || '201'),
|
|
tun_mark = resolve_mark(uci.get(cfgname, 'config', 'tun_mark') || '202');
|
|
|
|
const redir_port = uci.get(cfgname, 'inbound', 'redir_port') || '7891',
|
|
tproxy_port = uci.get(cfgname, 'inbound', 'tproxy_port') || '7892',
|
|
tunnel_port = uci.get(cfgname, 'inbound', 'tunnel_port') || '7893',
|
|
proxy_mode = uci.get(cfgname, 'inbound', 'proxy_mode') || 'redir_tproxy';
|
|
|
|
const global_ipv6 = uci.get(cfgname, 'global', 'ipv6') || '1',
|
|
dns_ipv6 = uci.get(cfgname, 'dns', 'ipv6') || '1',
|
|
dns_port = uci.get(cfgname, 'dns', 'dns_port') || '7853';
|
|
|
|
const dnsmasq_hijacked = uci.get('dhcp', '@dnsmasq[0]', 'dns_redirect') || '0',
|
|
dnsmasq_port = uci.get('dhcp', '@dnsmasq[0]', 'port') || '53';
|
|
|
|
let client_enabled, routing_tcpport, routing_udpport, routing_mode, routing_domain;
|
|
client_enabled = uci.get(cfgname, 'routing', 'client_enabled') || '0',
|
|
routing_tcpport = uci.get(cfgname, 'routing', 'routing_tcpport') || null;
|
|
routing_udpport = uci.get(cfgname, 'routing', 'routing_udpport') || null;
|
|
routing_mode = uci.get(cfgname, 'routing', 'routing_mode') || null;
|
|
routing_domain = uci.get(cfgname, 'routing', 'routing_domain') || '0';
|
|
|
|
if (routing_tcpport === 'common')
|
|
routing_tcpport = common_tcpport;
|
|
else if (routing_tcpport === 'common_stun')
|
|
routing_tcpport = `${common_tcpport},${stun_port}`;
|
|
|
|
if (routing_udpport === 'common')
|
|
routing_udpport = common_udpport;
|
|
else if (routing_udpport === 'common_stun')
|
|
routing_udpport = `${common_udpport},${stun_port}`;
|
|
|
|
if (!routing_mode)
|
|
routing_domain = '0';
|
|
|
|
const proxy_router = uci.get(cfgname, 'routing', 'proxy_router') || '1';
|
|
const control_options = [
|
|
"listen_interfaces", "lan_filter",
|
|
"lan_direct_mac_addrs", "lan_direct_ipv4_ips", "lan_direct_ipv6_ips",
|
|
"lan_proxy_mac_addrs", "lan_proxy_ipv4_ips", "lan_proxy_ipv6_ips"
|
|
];
|
|
const control_info = {};
|
|
|
|
for (let i in control_options)
|
|
control_info[i] = uci.get(cfgname, 'routing', i);
|
|
|
|
control_info.wan_direct_ipv4_ips = json(trim(yqRead('-oj', '.IPCIDR', resources_dir + '/direct_list.yaml')) || '[]');
|
|
control_info.wan_direct_ipv6_ips = json(trim(yqRead('-oj', '.IPCIDR6', resources_dir + '/direct_list.yaml')) || '[]');
|
|
control_info.wan_proxy_ipv4_ips = json(trim(yqRead('-oj', '.IPCIDR', resources_dir + '/proxy_list.yaml')) || '[]');
|
|
control_info.wan_proxy_ipv6_ips = json(trim(yqRead('-oj', '.IPCIDR6', resources_dir + '/proxy_list.yaml')) || '[]');
|
|
/* UCI config end */
|
|
-%}
|
|
|
|
{# Common function START #}
|
|
{%- function render_acl_src(inchain, outchain): %}
|
|
chain {{ inchain }} {
|
|
{% if (control_info.listen_interfaces): %}
|
|
meta iifname != {{ array_to_nftarr(split(join(' ', control_info.listen_interfaces) + ' lo', ' ')) }} counter return
|
|
{% endif %}
|
|
meta mark {{ self_mark }} counter return
|
|
|
|
{% if (control_info.lan_filter === 'white_list'): %}
|
|
{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
|
|
ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto {{ outchain }}
|
|
{% endif %}
|
|
{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
|
|
ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto {{ outchain }}
|
|
{% endfor %}
|
|
{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
|
|
ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto {{ outchain }}
|
|
{% endif %}
|
|
{% elif (control_info.lan_filter === 'black_list'): %}
|
|
{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
|
|
ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
|
|
{% endif %}
|
|
{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
|
|
ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
|
|
{% endfor %}
|
|
{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
|
|
ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
|
|
{% endif %}
|
|
{% endif /* lan_filter */ %}
|
|
|
|
{% if (control_info.lan_filter !== 'white_list'): %}
|
|
counter goto {{ outchain }}
|
|
{% endif %}
|
|
}
|
|
{% endfunction %}
|
|
|
|
{%- function render_acl_dst(inchain, outchain): %}
|
|
chain {{ inchain }} {
|
|
meta mark {{ self_mark }} counter return
|
|
fib daddr type { local } counter return
|
|
ct direction reply counter return
|
|
|
|
ip daddr @inet4_local_addr counter return
|
|
{% if (global_ipv6 === '1'): %}
|
|
ip6 daddr @inet6_local_addr counter return
|
|
{% endif %}
|
|
|
|
ip daddr @inet4_wan_proxy_addr counter goto {{ outchain }}
|
|
{% if (global_ipv6 === '1'): %}
|
|
ip6 daddr @inet6_wan_proxy_addr counter goto {{ outchain }}
|
|
{% endif %}
|
|
|
|
ip daddr @inet4_wan_direct_addr counter return
|
|
{% if (global_ipv6 === '1'): %}
|
|
ip6 daddr @inet6_wan_direct_addr counter return
|
|
{% endif %}
|
|
|
|
{% if (routing_mode === 'routing_gfw'): %}
|
|
ip daddr != @inet4_gfw_list_addr counter return
|
|
{% if (global_ipv6 === '1'): %}
|
|
ip6 daddr != @inet6_gfw_list_addr counter return
|
|
{% endif %}
|
|
{% elif (routing_mode === 'bypass_cn'): %}
|
|
ip daddr @inet4_china_list_addr counter return
|
|
{% if (global_ipv6 === '1'): %}
|
|
ip6 daddr @inet6_china_list_addr counter return
|
|
{% endif %}
|
|
{% endif /* routing_mode */ %}
|
|
|
|
counter goto {{ outchain }}
|
|
}
|
|
{% endfunction %}
|
|
|
|
{%- function render_acl_dport(inchain, outchain, l4proto): %}
|
|
chain {{ inchain }} {
|
|
{#- DNS hijack #}
|
|
meta l4proto { tcp, udp } th dport 53 counter goto {{ outchain }} comment "!{{ cfgname }}: DNS hijack"
|
|
|
|
{% if ((l4proto === 'tcp' || !l4proto) && routing_tcpport): %}
|
|
tcp dport != @tcp_routing_port counter return
|
|
{% endif %}
|
|
{% if ((l4proto === 'udp' || !l4proto) && routing_udpport): %}
|
|
udp dport != @udp_routing_port counter return
|
|
{% endif %}
|
|
|
|
counter goto {{ outchain }}
|
|
}
|
|
{% endfunction %}
|
|
{# Common function END -#}
|
|
|
|
table inet fchomo {
|
|
{#- Reserved addresses #}
|
|
set inet4_local_addr {
|
|
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,
|
|
{# 172.25.26.0/30, https://github.com/muink/openwrt-alwaysonline.git -#}
|
|
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/32
|
|
}
|
|
}
|
|
|
|
{% if (global_ipv6 === '1'): %}
|
|
set inet6_local_addr {
|
|
type ipv6_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = {
|
|
::/128,
|
|
::1/128,
|
|
::ffff:0:0/96,
|
|
::ffff:0:0:0/96,
|
|
64:ff9b::/96,
|
|
64:ff9b:1::/48,
|
|
100::/64,
|
|
2001::/32,
|
|
2001:10::/28,
|
|
2001:20::/28,
|
|
2001:db8::/32,
|
|
2002::/16,
|
|
3fff::/20,
|
|
5f00::/16,
|
|
fc00::/7,
|
|
{# fdfe:aead:2526::0/126, https://github.com/muink/openwrt-alwaysonline.git -#}
|
|
fe80::/10,
|
|
ff00::/8
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
{#- Custom Direct list #}
|
|
set inet4_wan_direct_addr {
|
|
type ipv4_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', control_info.wan_direct_ipv4_ips) }} }
|
|
}
|
|
|
|
{% if (global_ipv6 === '1'): %}
|
|
set inet6_wan_direct_addr {
|
|
type ipv6_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', control_info.wan_direct_ipv6_ips) }} }
|
|
}
|
|
{% endif %}
|
|
|
|
{#- Custom Proxy list #}
|
|
set inet4_wan_proxy_addr {
|
|
type ipv4_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', control_info.wan_proxy_ipv4_ips) }} }
|
|
}
|
|
|
|
{% if (global_ipv6 === '1'): %}
|
|
set inet6_wan_proxy_addr {
|
|
type ipv6_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', control_info.wan_proxy_ipv6_ips) }} }
|
|
}
|
|
{% endif %}
|
|
|
|
{#- Routing mode #}
|
|
{% if (match(routing_mode, /bypass_cn/)): %}
|
|
set inet4_china_list_addr {
|
|
type ipv4_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', split(trim(readfile(resources_dir + '/china_ip4.txt')), /[\r\n]/)) }} }
|
|
}
|
|
|
|
{% if (global_ipv6 === '1'): %}
|
|
set inet6_china_list_addr {
|
|
type ipv6_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', split(trim(readfile(resources_dir + '/china_ip6.txt')), /[\r\n]/)) }} }
|
|
}
|
|
{% endif %}
|
|
{% elif (match(routing_mode, /routing_gfw/)): %}
|
|
set inet4_gfw_list_addr {
|
|
type ipv4_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = {}
|
|
}
|
|
|
|
{% if (global_ipv6 === '1'): %}
|
|
set inet6_gfw_list_addr {
|
|
type ipv6_addr
|
|
flags interval
|
|
auto-merge
|
|
elements = {}
|
|
}
|
|
{% endif %}
|
|
{% endif /* routing_mode */ %}
|
|
|
|
{#- Routing port #}
|
|
{% if (routing_tcpport): %}
|
|
set tcp_routing_port {
|
|
type inet_service
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', split(routing_tcpport, ',')) }} }
|
|
}
|
|
{% endif %}
|
|
|
|
{% if (routing_udpport): %}
|
|
set udp_routing_port {
|
|
type inet_service
|
|
flags interval
|
|
auto-merge
|
|
elements = { {{ join(', ', split(routing_udpport, ',')) }} }
|
|
}
|
|
{% endif %}
|
|
|
|
{# Main entrypoint START #}
|
|
{# https://en.wikipedia.org/wiki/Netfilter#/media/File:Netfilter-packet-flow.svg #}
|
|
chain dstnat {
|
|
type nat hook prerouting priority dstnat + 5; policy accept;
|
|
{#- DNS hijack #}
|
|
{% if (dnsmasq_hijacked !== '1'): %}
|
|
{% if (control_info.listen_interfaces): %}
|
|
meta iifname {{ array_to_nftarr(control_info.listen_interfaces) }}
|
|
{% endif %}
|
|
meta iifname != lo meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { tcp, udp } th dport 53 counter redirect to :{{ dnsmasq_port }} comment "!{{ cfgname }}: DNS hijack (subnet)"
|
|
{% endif /* dnsmasq_hijacked */ %}
|
|
{#- TCP redirect entrypoint #}
|
|
{% if (match(proxy_mode, /redir/)): %}
|
|
meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump redir_acl_src
|
|
{% endif %}
|
|
}
|
|
|
|
chain prerouting {
|
|
type filter hook prerouting priority 5; policy accept;
|
|
{#- DNS hijack #}
|
|
fib daddr type local meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { tcp, udp } th dport { {{ join(', ', [dnsmasq_port, dns_port, tunnel_port]) }} } counter accept comment "!{{ cfgname }}: DNS hijack (bypass local dnsserver)"
|
|
{#- UDP tproxy entrypoint #}
|
|
{% if (match(proxy_mode, /tproxy/)): %}
|
|
meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump tproxy_acl_src
|
|
{#- TUN entrypoint #}
|
|
{% elif (match(proxy_mode, /tun/)): %}
|
|
iifname {{ tun_name }} counter accept
|
|
meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump tun_acl_src
|
|
{% endif %}
|
|
}
|
|
|
|
{% if (proxy_router === '1'): %}
|
|
chain mangle_output {
|
|
type route hook output priority 0; policy accept;
|
|
{#- UDP tproxy entrypoint #}
|
|
{% if (match(proxy_mode, /tproxy/)): %}
|
|
meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump tproxy_acl_dst_reroute
|
|
{#- TUN entrypoint #}
|
|
{% elif (match(proxy_mode, /tun/)): %}
|
|
iifname {{ tun_name }} counter accept
|
|
meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump tun_acl_dst
|
|
{% endif %}
|
|
}
|
|
|
|
{#- TCP redirect entrypoint #}
|
|
{% if (match(proxy_mode, /redir/)): %}
|
|
chain nat_output {
|
|
type nat hook output priority 0; policy accept;
|
|
meta nfproto { {{ (global_ipv6 === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump redir_acl_dst
|
|
}
|
|
{% endif %}
|
|
{% endif /* proxy_router */ %}
|
|
{# Main entrypoint END #}
|
|
|
|
{# TCP redirect START #}
|
|
{% if (match(proxy_mode, /redir/)): %}
|
|
{{ render_acl_src('redir_acl_src', 'redir_acl_dst') }}
|
|
{{ render_acl_dst('redir_acl_dst', 'redir_acl_dport') }}
|
|
{{ render_acl_dport('redir_acl_dport', 'redir_gate', 'tcp') }}
|
|
chain redir_gate {
|
|
{#- DNS hijack #}
|
|
tcp dport 53 counter redirect to :{{ tunnel_port }} comment "!{{ cfgname }}: DNS hijack (router tcp)"
|
|
|
|
meta l4proto tcp counter redirect to :{{ redir_port }}
|
|
}
|
|
{% endif /* proxy_mode */ %}
|
|
{# TCP redirect END #}
|
|
|
|
{# UDP tproxy START #}
|
|
{% if (match(proxy_mode, /tproxy/)): %}
|
|
{{ render_acl_src('tproxy_acl_src', 'tproxy_acl_dst') }}
|
|
{{ render_acl_dst('tproxy_acl_dst', 'tproxy_acl_dport') }}
|
|
{{ render_acl_dport('tproxy_acl_dport', 'tproxy_gate', 'udp') }}
|
|
chain tproxy_gate {
|
|
meta l4proto udp meta mark set {{ tproxy_mark }} tproxy ip to :{{ tproxy_port }} counter accept
|
|
{% if (global_ipv6 === '1'): %}
|
|
meta l4proto udp meta mark set {{ tproxy_mark }} tproxy ip6 to :{{ tproxy_port }} counter accept
|
|
{% endif %}
|
|
}
|
|
|
|
{% if (proxy_router === '1'): %}
|
|
{{ render_acl_dst('tproxy_acl_dst_reroute', 'tproxy_acl_dport_reroute') }}
|
|
{{ render_acl_dport('tproxy_acl_dport_reroute', 'tproxy_mark', 'udp') }}
|
|
chain tproxy_mark {
|
|
{#- DNS hijack (router udp) #}
|
|
{# tproxy_mark --> route_table_id --reroute-to--> lo --> prerouting #}
|
|
|
|
meta l4proto udp meta mark set {{ tproxy_mark }} counter accept
|
|
}
|
|
{% endif /* proxy_router */ %}
|
|
{% endif /* proxy_mode */ %}
|
|
{# UDP tproxy END #}
|
|
|
|
{# TUN START #}
|
|
{% if (match(proxy_mode, /tun/)): %}
|
|
{{ render_acl_src('tun_acl_src', 'tun_acl_dst') }}
|
|
{{ render_acl_dst('tun_acl_dst', 'tun_acl_dport') }}
|
|
{{ render_acl_dport('tun_acl_dport', 'tun_mark', (proxy_mode === 'tun') ? '' : 'udp') }}
|
|
chain tun_mark {
|
|
meta mark set {{ tun_mark }} counter accept
|
|
}
|
|
{% endif /* proxy_mode */ %}
|
|
{# TUN END #}
|
|
}
|