#!/usr/bin/utpl

{%-
'use strict';

import { readfile } from 'fs';
import { cursor } from 'uci';
import { isEmpty } from '/etc/homeproxy/scripts/homeproxy.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/homeproxy/resources';

/* UCI config start */
const cfgname = 'homeproxy';
const uci = cursor();
uci.load(cfgname);

const routing_mode = uci.get(cfgname, 'config', 'routing_mode') || 'bypass_mainland_china';
let outbound_node, outbound_udp_node, china_dns_server, bypass_cn_traffic;

if (routing_mode !== 'custom') {
	outbound_node = uci.get(cfgname, 'config', 'main_node') || 'nil';
	outbound_udp_node = uci.get(cfgname, 'config', 'main_udp_node') || 'nil';
	china_dns_server = uci.get(cfgname, 'config', 'china_dns_server');
} else {
	outbound_node = uci.get(cfgname, 'routing', 'default_outbound') || 'nil';
	bypass_cn_traffic = uci.get(cfgname, 'routing', 'bypass_cn_traffic') || '0';
}

let routing_port = uci.get(cfgname, 'config', 'routing_port');
if (routing_port === 'common')
	routing_port = uci.get(cfgname, 'infra', 'common_port') || '22,53,80,143,443,465,587,853,873,993,995,8080,8443,9418';

const proxy_mode = uci.get(cfgname, 'config', 'proxy_mode') || 'redirect_tproxy',
      ipv6_support = uci.get(cfgname, 'config', 'ipv6_support') || '0';

let self_mark, redirect_port,
    tproxy_port, tproxy_mark,
    tun_name, tun_mark;

if (match(proxy_mode, /redirect/)) {
	self_mark = uci.get(cfgname, 'infra', 'self_mark') || '100';
	redirect_port = uci.get(cfgname, 'infra', 'redirect_port') || '5331';
}
if (match(proxy_mode, /tproxy/))
	if (outbound_udp_node !== 'nil' || routing_mode === 'custom') {
		tproxy_port = uci.get(cfgname, 'infra', 'tproxy_port') || '5332';
		tproxy_mark = resolve_mark(uci.get(cfgname, 'infra', 'tproxy_mark') || '101');
	}
if (match(proxy_mode, /tun/)) {
	tun_name = uci.get(cfgname, 'infra', 'tun_name') || 'singtun0';
	tun_mark = resolve_mark(uci.get(cfgname, 'infra', 'tun_mark') || '102');
}

const control_options = [
	"listen_interfaces", "lan_proxy_mode",
	"lan_direct_mac_addrs", "lan_direct_ipv4_ips", "lan_direct_ipv6_ips",
	"lan_proxy_mac_addrs", "lan_proxy_ipv4_ips", "lan_proxy_ipv6_ips",
	"lan_gaming_mode_mac_addrs", "lan_gaming_mode_ipv4_ips", "lan_gaming_mode_ipv6_ips",
	"lan_global_proxy_mac_addrs", "lan_global_proxy_ipv4_ips", "lan_global_proxy_ipv6_ips",
	"wan_proxy_ipv4_ips", "wan_proxy_ipv6_ips",
	"wan_direct_ipv4_ips", "wan_direct_ipv6_ips"
];
const control_info = {};

for (let i in control_options)
	control_info[i] = uci.get(cfgname, 'control', i);

const dns_hijacked = uci.get('dhcp', '@dnsmasq[0]', 'dns_redirect') || '0',
      dns_port = uci.get('dhcp', '@dnsmasq[0]', 'port') || '53';
/* UCI config end */
-%}

{# Reserved addresses -#}
set homeproxy_local_addr_v4 {
	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.31.196.0/24,
		192.52.193.0/24,
		192.88.99.0/24,
		192.168.0.0/16,
		192.175.48.0/24,
		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
	}
}
{% if (ipv6_support === '1'): %}
set homeproxy_local_addr_v6 {
	type ipv6_addr
	flags interval
	auto-merge
	elements = {
		::/128,
		::1/128,
		::ffff:0:0/96,
		100::/64,
		64:ff9b::/96,
		2001::/32,
		2001:10::/28,
		2001:20::/28,
		2001:db8::/28,
		2002::/16,
		fc00::/7,
		fe80::/10,
		ff00::/8
	}
}
{% endif %}

{% if (routing_mode === 'gfwlist'): %}
set homeproxy_gfw_list_v4 {
	type ipv4_addr
	flags interval
	auto-merge
}
{% if (ipv6_support === '1'): %}
set homeproxy_gfw_list_v6 {
	type ipv6_addr
	flags interval
	auto-merge
}
{% endif /* ipv6_support */ %}
{% elif (match(routing_mode, /mainland_china/) || bypass_cn_traffic === '1'): %}
set homeproxy_mainland_addr_v4 {
	type ipv4_addr
	flags interval
	auto-merge
	elements = {
		{% for (let cnip4 in split(trim(readfile(resources_dir + '/china_ip4.txt')), /[\r\n]/)): %}
		{{ cnip4 }},
		{% endfor %}
	}
}
{% if ((ipv6_support === '1') || china_dns_server): %}
set homeproxy_mainland_addr_v6 {
	type ipv6_addr
	flags interval
	auto-merge
	elements = {
		{% for (let cnip6 in split(trim(readfile(resources_dir + '/china_ip6.txt')), /[\r\n]/)): %}
		{{ cnip6 }},
		{% endfor %}
	}
}
{% endif /* ipv6_support */ %}
{% endif /* routing_mode */ %}

{# WAN ACL addresses #}
set homeproxy_wan_proxy_addr_v4 {
	type ipv4_addr
	flags interval
	auto-merge
{% if (control_info.wan_proxy_ipv4_ips): %}
	elements = { {{ join(', ', control_info.wan_proxy_ipv4_ips) }} }
{% endif %}
}

{% if (ipv6_support === '1'): %}
set homeproxy_wan_proxy_addr_v6 {
	type ipv6_addr
	flags interval
	auto-merge
{% if (control_info.wan_proxy_ipv6_ips): %}
	elements = { {{ join(', ', control_info.wan_proxy_ipv6_ips) }} }
{% endif /* wan_proxy_ipv6_ips*/ %}
}
{% endif /* ipv6_support */ %}

set homeproxy_wan_direct_addr_v4 {
	type ipv4_addr
	flags interval
	auto-merge
{% if (control_info.wan_direct_ipv4_ips): %}
	elements = { {{ join(', ', control_info.wan_direct_ipv4_ips) }} }
{% endif %}
}

{% if (ipv6_support === '1'): %}
set homeproxy_wan_direct_addr_v6 {
	type ipv6_addr
	flags interval
	auto-merge
{% if (control_info.wan_direct_ipv6_ips): %}
	elements = { {{ join(', ', control_info.wan_direct_ipv6_ips) }} }
{% endif /* wan_direct_ipv6_ips */ %}
}
{% endif /* ipv6_support */ %}

{% if (routing_port): %}
set homeproxy_routing_port {
	type inet_service
	flags interval
	auto-merge
	elements = { {{ join(', ', split(routing_port, ',')) }} }
}
{% endif %}

{# DNS hijack & TCP redirect #}
chain dstnat {
{% if (dns_hijacked !== '1'): %}
	{% if (control_info.listen_interfaces): %}
	meta iifname {{ array_to_nftarr(control_info.listen_interfaces) }}
	{%- endif /* listen_interfaces */ %}
	meta nfproto { ipv4, ipv6 } udp dport 53 counter redirect to :{{ dns_port }} comment "!{{ cfgname }}: DNS hijack"
{% endif /* dns_hijacked */ %}
{% if (match(proxy_mode, /redirect/)): %}
	meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac
{% endif /* proxy_mode */ %}
}

{# TCP redirect #}
{% if (match(proxy_mode, /redirect/)): %}
chain homeproxy_redirect_proxy {
	meta l4proto tcp counter redirect to :{{ redirect_port }}
}

chain homeproxy_redirect_proxy_port {
	{% if (routing_port): %}
	tcp dport != @homeproxy_routing_port counter return
	{% endif %}
	goto homeproxy_redirect_proxy
}

chain homeproxy_redirect_lanac {
	{% if (control_info.listen_interfaces): %}
	meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return
	{% endif %}
	meta mark {{ self_mark }} counter return

	{% if (control_info.lan_proxy_mode === 'listed_only'): %}
	{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_redirect
	{% endif /* lan_proxy_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect
	{% endfor /* lan_proxy_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_redirect
	{% endif /* lan_proxy_mac_addrs */ %}
	{% elif (control_info.lan_proxy_mode === 'except_listed'): %}
	{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
	{% endif /* lan_direct_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
	{% endfor /* lan_direct_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
	{% endif /* lan_direct_mac_addrs */ %}
	{% endif /* lan_proxy_mode */ %}

	{% if (control_info.lan_proxy_mode !== 'listed_only'): %}
	counter goto homeproxy_redirect
	{% endif %}
}

chain homeproxy_redirect {
	meta mark {{ self_mark }} counter return

	ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_redirect_proxy_port
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_redirect_proxy_port
	{% endif %}

	ip daddr @homeproxy_local_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_local_addr_v6 counter return
	{% endif %}

	{% if (routing_mode !== 'custom'): %}
	{% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_redirect_proxy_port
	{% endif /* lan_global_proxy_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect_proxy_port
	{% endfor /* lan_global_proxy_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_redirect_proxy_port
	{% endif /* lan_global_proxy_mac_addrs */ %}
	{% endif /* routing_mode */ %}

	ip daddr @homeproxy_wan_direct_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_direct_addr_v6 counter return
	{% endif /* ipv6_support */ %}

	{% if (routing_mode === 'gfwlist'): %}
	ip daddr != @homeproxy_gfw_list_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_gfw_list_v6 counter return
	{% endif /* ipv6_support */ %}
	{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
	ip daddr @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% elif (routing_mode === 'proxy_mainland_china'): %}
	ip daddr != @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% endif /* routing_mode */ %}

	{% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter goto homeproxy_redirect_proxy
	{% endif /* lan_gaming_mode_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect_proxy
	{% endfor /* lan_gaming_mode_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter goto homeproxy_redirect_proxy
	{% endif /* lan_gaming_mode_mac_addrs */ %}

	counter goto homeproxy_redirect_proxy_port
}

chain homeproxy_output_redir {
	type nat hook output priority filter -105; policy accept
	meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect
}
{% endif %}

{# UDP tproxy #}
{% if (match(proxy_mode, /tproxy/) && (outbound_udp_node !== 'nil' || routing_mode === 'custom')): %}
chain homeproxy_mangle_tproxy {
	meta l4proto udp meta mark set {{ tproxy_mark }} tproxy ip to 127.0.0.1:{{ tproxy_port }} counter accept
	{% if (ipv6_support === '1'): %}
	meta l4proto udp meta mark set {{ tproxy_mark }} tproxy ip6 to [::1]:{{ tproxy_port }} counter accept
	{% endif %}
}

chain homeproxy_mangle_tproxy_port {
	{% if (routing_port): %}
	udp dport != @homeproxy_routing_port counter return
	{% endif %}
	goto homeproxy_mangle_tproxy
}

chain homeproxy_mangle_mark {
	{% if (routing_port): %}
	udp dport != @homeproxy_routing_port counter return
	{% endif %}
	meta l4proto udp meta mark set {{ tproxy_mark }} counter accept
}

chain homeproxy_mangle_lanac {
	{% if (control_info.listen_interfaces): %}
	meta iifname != {{ array_to_nftarr(uniq([...control_info.listen_interfaces, ...['lo']])) }} counter return
	{% endif %}
	meta iifname != lo udp dport 53 counter return
	meta mark {{ self_mark }} counter return

	{% if (control_info.lan_proxy_mode === 'listed_only'): %}
	{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_mangle_prerouting
	{% endif /* lan_proxy_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_prerouting
	{% endfor /* lan_proxy_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_mangle_prerouting
	{% endif /* lan_proxy_mac_addrs */ %}
	{% elif (control_info.lan_proxy_mode === 'except_listed'): %}
	{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
	{% endif /* lan_direct_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
	{% endfor /* lan_direct_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
	{% endif /* lan_direct_mac_addrs */ %}
	{% endif /* lan_proxy_mode */ %}

	{% if (control_info.lan_proxy_mode !== 'listed_only'): %}
	counter goto homeproxy_mangle_prerouting
	{% endif %}
}

chain homeproxy_mangle_prerouting {
	ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_tproxy_port
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_tproxy_port
	{% endif %}

	ip daddr @homeproxy_local_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_local_addr_v6 counter return
	{% endif %}

	{% if (routing_mode !== 'custom'): %}
	{% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tproxy_port
	{% endif /* lan_global_proxy_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tproxy_port
	{% endfor /* lan_global_proxy_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_mangle_tproxy_port
	{% endif /* lan_global_proxy_mac_addrs */ %}
	{% endif /* routing_mode */ %}

	ip daddr @homeproxy_wan_direct_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_direct_addr_v6 counter return
	{% endif /* ipv6_support */ %}

	{% if (routing_mode === 'gfwlist'): %}
	ip daddr != @homeproxy_gfw_list_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_gfw_list_v6 counter return
	{% endif /* ipv6_support */ %}
	udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
	{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
	ip daddr @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% if (routing_mode !== 'custom'): %}
	udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
	{% endif /* routing_mode */ %}
	{% elif (routing_mode === 'proxy_mainland_china'): %}
	ip daddr != @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% endif /* routing_mode */ %}

	{% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter goto homeproxy_mangle_tproxy
	{% endif /* lan_gaming_mode_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tproxy
	{% endfor /* lan_gaming_mode_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter goto homeproxy_mangle_tproxy
	{% endif /* lan_gaming_mode_mac_addrs */ %}

	counter goto homeproxy_mangle_tproxy_port
}

chain homeproxy_mangle_output {
	meta mark {{ self_mark }} counter return

	ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_mark
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_mark
	{% endif %}

	ip daddr @homeproxy_local_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_local_addr_v6 counter return
	{% endif %}

	ip daddr @homeproxy_wan_direct_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_direct_addr_v6 counter return
	{% endif /* ipv6_support */ %}

	{% if (routing_mode === 'gfwlist'): %}
	ip daddr != @homeproxy_gfw_list_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_gfw_list_v6 counter return
	{% endif /* ipv6_support */ %}
	{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
	ip daddr @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% elif (routing_mode === 'proxy_mainland_china'): %}
	ip daddr != @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% endif /* routing_mode */ %}

	counter goto homeproxy_mangle_mark
}

chain mangle_prerouting {
	meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump homeproxy_mangle_lanac
}

chain mangle_output {
	meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump homeproxy_mangle_output
}
{% endif %}

{# TUN #}
{% if (match(proxy_mode, /tun/)): %}
chain homeproxy_mangle_lanac {
	iifname {{ tun_name }} counter return
	udp dport 53 counter return

	{% if (control_info.listen_interfaces): %}
	meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return
	{% endif %}

	{% if (control_info.lan_proxy_mode === 'listed_only'): %}
	{% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tun
	{% endif /* lan_proxy_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tun
	{% endfor /* lan_proxy_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_mangle_tun
	{% endif /* lan_proxy_mac_addrs */ %}
	{% elif (control_info.lan_proxy_mode === 'except_listed'): %}
	{% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return
	{% endif /* lan_direct_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_direct_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter return
	{% endfor /* lan_direct_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_direct_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return
	{% endif /* lan_direct_mac_addrs */ %}
	{% endif /* lan_proxy_mode */ %}

	{% if (control_info.lan_proxy_mode !== 'listed_only'): %}
	counter goto homeproxy_mangle_tun
	{% endif %}
}

chain homeproxy_mangle_tun_mark {
	{% if (routing_port): %}
	{% if (proxy_mode === 'tun'): %}
	tcp dport != @homeproxy_routing_port counter return
	{% endif /* proxy_mode */ %}
	udp dport != @homeproxy_routing_port counter return
	{% endif /* routing_port */ %}

	counter meta mark set {{ tun_mark }}
}

chain homeproxy_mangle_tun {
	iifname {{ tun_name }} counter return

	ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_tun_mark
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_tun_mark
	{% endif %}

	ip daddr @homeproxy_local_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_local_addr_v6 counter return
	{% endif %}

	{% if (routing_mode !== 'custom'): %}
	{% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tun_mark
	{% endif /* lan_global_proxy_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tun_mark
	{% endfor /* lan_global_proxy_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_mangle_tun_mark
	{% endif /* lan_global_proxy_mac_addrs */ %}
	{% endif /* routing_mode */ %}

	{% if (control_info.wan_direct_ipv4_ips): %}
	ip daddr {{ array_to_nftarr(control_info.wan_direct_ipv4_ips) }} counter return
	{% endif /* wan_direct_ipv4_ips */ %}
	{% if (control_info.wan_direct_ipv6_ips): %}
	ip6 daddr {{ array_to_nftarr(control_info.wan_direct_ipv6_ips) }} counter return
	{% endif /* wan_direct_ipv6_ips */ %}

	{% if (routing_mode === 'gfwlist'): %}
	ip daddr != @homeproxy_gfw_list_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_gfw_list_v6 counter return
	{% endif /* ipv6_support */ %}
	udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
	{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
	ip daddr @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% if (routing_mode !== 'custom'): %}
	udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC"
	{% endif /* routing_mode */ %}
	{% elif (routing_mode === 'proxy_mainland_china'): %}
	ip daddr != @homeproxy_mainland_addr_v4 counter return
	{% if (ipv6_support === '1'): %}
	ip6 daddr != @homeproxy_mainland_addr_v6 counter return
	{% endif /* ipv6_support */ %}
	{% endif /* routing_mode */ %}

	{% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %}
	ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter meta mark set {{ tun_mark }}
	{% endif /* lan_gaming_mode_ipv4_ips */ %}
	{% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %}
	ip6 saddr {{ resolve_ipv6(ipv6) }} counter meta mark set {{ tun_mark }}
	{% endfor /* lan_gaming_mode_ipv6_ips */ %}
	{% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %}
	ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter meta mark set {{ tun_mark }}
	{% endif /* lan_gaming_mode_mac_addrs */ %}

	counter goto homeproxy_mangle_tun_mark
}

chain mangle_prerouting {
	meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump homeproxy_mangle_lanac
}

chain mangle_output {
	meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump homeproxy_mangle_tun
}
{% endif %}
