#!/usr/bin/utpl

{%-
	'use strict';

	import { readfile } from 'fs';
	import { cursor } from 'uci';
	import { connect } from 'ubus';
	import { get_paths, uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups } from '/etc/momo/ucode/include.uc';

	const fw4 = require('fw4');

	const paths = get_paths();

	const cgroups_version = get_cgroups_version();

	const users = get_users();
	const groups = get_groups();
	const cgroups = get_cgroups();

	const uci = cursor();
	const ubus = connect();

	const profile = json(readfile(paths.run_profile_path));

	uci.load('momo');

	let redir_port;
	let tproxy_port;
	let tun_device;
	let dns_port;
	let fake_ip_range;
	let fake_ip6_range;

	const redirect_inbound_tag = uci.get('momo', 'core', 'redirect_inbound_tag');
	const tproxy_inbound_tag = uci.get('momo', 'core', 'tproxy_inbound_tag');
	const tun_inbound_tag = uci.get('momo', 'core', 'tun_inbound_tag');
	const dns_inbound_tag = uci.get('momo', 'core', 'dns_inbound_tag');
	const fake_ip_dns_server_tag = uci.get('momo', 'core', 'fake_ip_dns_server_tag');

	let profile_array;
	profile_array = filter(profile['inbounds'], (x) => x['tag'] == redirect_inbound_tag);
	if (length(profile_array) > 0) {
		redir_port = profile_array[0]['listen_port'];
	}
	profile_array = filter(profile['inbounds'], (x) => x['tag'] == tproxy_inbound_tag);
	if (length(profile_array) > 0) {
		tproxy_port = profile_array[0]['listen_port'];
	}
	profile_array = filter(profile['inbounds'], (x) => x['tag'] == tun_inbound_tag);
	if (length(profile_array) > 0) {
		tun_device = profile_array[0]['interface_name'];
	}
	profile_array = filter(profile['inbounds'], (x) => x['tag'] == dns_inbound_tag);
	if (length(profile_array) > 0) {
		dns_port = profile_array[0]['listen_port'];
	}
	profile_array = filter(profile['dns']['servers'], (x) => x['tag'] == fake_ip_dns_server_tag);
	if (length(profile_array) > 0) {
		fake_ip_range = profile_array[0]['inet4_range'];
		fake_ip6_range = profile_array[0]['inet6_range'];
	}

	const ipv4_dns_hijack = uci_bool(uci.get('momo', 'proxy', 'ipv4_dns_hijack'));
	const ipv6_dns_hijack = uci_bool(uci.get('momo', 'proxy', 'ipv6_dns_hijack'));
	const ipv4_proxy = uci_bool(uci.get('momo', 'proxy', 'ipv4_proxy'));
	const ipv6_proxy = uci_bool(uci.get('momo', 'proxy', 'ipv6_proxy'));
	const fake_ip_ping_hijack = uci_bool(uci.get('momo', 'proxy', 'fake_ip_ping_hijack'));
	const tcp_mode = uci.get('momo', 'proxy', 'tcp_mode');
	const udp_mode = uci.get('momo', 'proxy', 'udp_mode');

	const router_proxy = uci_bool(uci.get('momo', 'proxy', 'router_proxy'));
	const router_access_control = [];
	uci.foreach('momo', 'router_access_control', (access_control) => {
		access_control['enabled'] = uci_bool(access_control['enabled']);
		access_control['user'] = filter(uci_array(access_control['user']), (x) => index(users, x) >= 0);
		access_control['group'] = filter(uci_array(access_control['group']), (x) => index(groups, x) >= 0);
		access_control['cgroup'] = filter(uci_array(access_control['cgroup']), (x) => index(cgroups, x) >= 0);
		access_control['proxy'] = uci_bool(access_control['proxy']);
		access_control['dns'] = uci_bool(access_control['dns']);
		push(router_access_control, access_control);
	});

	const lan_proxy = uci_bool(uci.get('momo', 'proxy', 'lan_proxy'));
	const lan_inbound_interface = uci_array(uci.get('momo', 'proxy', 'lan_inbound_interface'));
	const lan_inbound_device = [];
	for (let interface in lan_inbound_interface) {
		const status = ubus.call('network.interface', 'status', { 'interface': interface });
		const device = status?.l3_device ?? status?.device ?? '';
		if (device != '') {
			push(lan_inbound_device, device);
		}
	}
	const lan_access_control = [];
	uci.foreach('momo', 'lan_access_control', (access_control) => {
		access_control['enabled'] = uci_bool(access_control['enabled']);
		access_control['ip'] = uci_array(access_control['ip']);
		access_control['ip6'] = uci_array(access_control['ip6']);
		access_control['mac'] = uci_array(access_control['mac']);
		access_control['proxy'] = uci_bool(access_control['proxy']);
		access_control['dns'] = uci_bool(access_control['dns']);
		push(lan_access_control, access_control);
	});

	const reserved_ip = uci_array(uci.get('momo', 'proxy', 'reserved_ip'));
	const reserved_ip6 = uci_array(uci.get('momo', 'proxy', 'reserved_ip6'));
	const bypass_china_mainland_ip = uci_bool(uci.get('momo', 'proxy', 'bypass_china_mainland_ip'));
	const bypass_china_mainland_ip6 = uci_bool(uci.get('momo', 'proxy', 'bypass_china_mainland_ip6'));
	const proxy_tcp_dport = split((uci.get('momo', 'proxy', 'proxy_tcp_dport') ?? '0-65535'), ' ');
	const proxy_udp_dport = split((uci.get('momo', 'proxy', 'proxy_udp_dport') ?? '0-65535'), ' ');
	const bypass_dscp = uci_array(uci.get('momo', 'proxy', 'bypass_dscp'));
	const bypass_fwmark = [];
	for (let fwmark in uci_array(uci.get('momo', 'proxy', 'bypass_fwmark'))) {
		let mark;
		let mask = '0xFFFFFFFF';
		if (index(fwmark, '/') >= 0) {
			const parts = split(fwmark, '/');
			mark = parts[0];
			mask = parts[1];
		} else {
			mark = fwmark;
		}
		push(bypass_fwmark, { mark: mark, mask: mask });
	}

	const cgroup_id = uci.get('momo', 'routing', 'cgroup_id') ?? '0x07250725';
	const cgroup_name = uci.get('momo', 'routing', 'cgroup_name') ?? 'momo';
	const tproxy_fw_mark = uci.get('momo', 'routing', 'tproxy_fw_mark') ?? '0x80';
	const tproxy_fw_mask = uci.get('momo', 'routing', 'tproxy_fw_mask') ?? '0xFF';
	const tproxy_fw_umask = fw4.hex(~tproxy_fw_mask & 0xFFFFFFFF);
	const tun_fw_mark = uci.get('momo', 'routing', 'tun_fw_mark') ?? '0x81';
	const tun_fw_mask = uci.get('momo', 'routing', 'tun_fw_mask') ?? '0xFF';
	const tun_fw_umask = fw4.hex(~tun_fw_mask & 0xFFFFFFFF);

	const dns_hijack_nfproto = [];
	if (ipv4_dns_hijack) {
		push(dns_hijack_nfproto, 'ipv4');
	}
	if (ipv6_dns_hijack) {
		push(dns_hijack_nfproto, 'ipv6');
	}

	const proxy_nfproto = [];
	if (ipv4_proxy) {
		push(proxy_nfproto, 'ipv4');
	}
	if (ipv6_proxy) {
		push(proxy_nfproto, 'ipv6');
	}

	const proxy_dport = [];
	for (let port in proxy_tcp_dport) {
		push(proxy_dport, `tcp . ${port}`);
	}
	for (let port in proxy_udp_dport) {
		push(proxy_dport, `udp . ${port}`);
	}
-%}

table inet momo {
	{% if (length(dns_hijack_nfproto) > 0): %}
	set dns_hijack_nfproto {
		type nf_proto
		flags interval
		elements = {
			{{ join(', ', dns_hijack_nfproto) }}
		}
	}
	{% endif %}

	set proxy_nfproto {
		type nf_proto
		flags interval
		{% if (length(proxy_nfproto) > 0): %}
		elements = {
			{{ join(', ', proxy_nfproto) }}
		}
		{% endif %}
	}

	set reserved_ip {
		type ipv4_addr
		flags interval
		auto-merge
		{% if (length(reserved_ip) > 0): %}
		elements = {
			{{ join(', ', reserved_ip) }}
		}
		{% endif %}
	}

	set reserved_ip6 {
		type ipv6_addr
		flags interval
		auto-merge
		{% if (length(reserved_ip6) > 0): %}
		elements = {
			{{ join(', ', reserved_ip6) }}
		}
		{% endif %}
	}

	set lan_inbound_device {
		type ifname
		flags interval
		auto-merge
		{% if (length(lan_inbound_device) > 0): %}
		elements = {
			{{ join(', ', map(lan_inbound_device, (x) => `"${x}"`)) }}
		}
		{% endif %}
	}

	set china_ip {
		type ipv4_addr
		flags interval
	}

	set china_ip6 {
		type ipv6_addr
		flags interval
	}

	set proxy_dport {
		type inet_proto . inet_service
		flags interval
		auto-merge
		{% if (length(proxy_dport) > 0): %}
		elements = {
			{{ join(', ', proxy_dport) }}
		}
		{% endif %}
	}

	set bypass_dscp {
		type dscp
		flags interval
		auto-merge
		{% if (length(bypass_dscp) > 0): %}
		elements = {
			{{ join(', ', bypass_dscp) }}
		}
		{% endif %}
	}

	{% if (router_proxy): %}
	{% if (length(dns_hijack_nfproto) > 0): %}
	chain router_dns_hijack {
		{% for (let access_control in router_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
		meta l4proto { tcp, udp } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% else %}
		{% if (length(access_control['user']) > 0): %}
		meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (length(access_control['group']) > 0): %}
		meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
		{% for (let cgroup in access_control['cgroup']): %}
		meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
		
		{% endfor %}
		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}

	{% if (tcp_mode == 'redirect'): %}
	chain router_redirect {
		{% for (let access_control in router_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
		meta l4proto tcp counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}

		{% else %}
		{% if (length(access_control['user']) > 0): %}
		meta l4proto tcp meta skuid { {{ join(', ', access_control['user']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (length(access_control['group']) > 0): %}
		meta l4proto tcp meta skgid { {{ join(', ', access_control['group']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
		{% for (let cgroup in access_control['cgroup']): %}
		meta l4proto tcp socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
		
		{% endfor %}
		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}

	{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
	chain router_tproxy {
		{% for (let access_control in router_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
		meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}

		{% else %}
		{% if (length(access_control['user']) > 0): %}
		meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% if (length(access_control['group']) > 0): %}
		meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
		{% for (let cgroup in access_control['cgroup']): %}
		meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
		
		{% endfor %}
		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}

	{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
	chain router_tun {
		{% for (let access_control in router_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
		meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}

		{% else %}
		{% if (length(access_control['user']) > 0): %}
		meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% if (length(access_control['group']) > 0): %}
		meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
		{% for (let cgroup in access_control['cgroup']): %}
		meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
		
		{% endfor %}
		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}
	{% endif %}

	{% if (lan_proxy): %}
	{% if (length(dns_hijack_nfproto) > 0): %}
	chain lan_dns_hijack {
		{% for (let access_control in lan_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
		meta l4proto { tcp, udp } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% else %}
		{% if (length(access_control['ip']) > 0): %}
		meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (length(access_control['ip6']) > 0): %}
		meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (length(access_control['mac']) > 0): %}
		meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}

		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}

	{% if (tcp_mode == 'redirect'): %}
	chain lan_redirect {
		{% for (let access_control in lan_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
		meta l4proto tcp counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} counter return {% endif %}

		{% else %}
		{% if (length(access_control['ip']) > 0): %}
		meta l4proto tcp ip saddr { {{ join(', ', access_control['ip']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (length(access_control['ip6']) > 0): %}
		meta l4proto tcp ip6 saddr { {{ join(', ', access_control['ip6']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}

		{% endif %}
		{% if (length(access_control['mac']) > 0): %}
		meta l4proto tcp ether saddr { {{ join(', ', access_control['mac']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}

		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}

	{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
	chain lan_tproxy {
		{% for (let access_control in lan_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
		meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}

		{% else %}
		{% if (length(access_control['ip']) > 0): %}
		meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy ip to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% if (length(access_control['ip6']) > 0): %}
		meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy ip6 to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% if (length(access_control['mac']) > 0): %}
		meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}

		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}

	{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
	chain lan_tun {
		{% for (let access_control in lan_access_control): %}
		{% if (access_control['enabled']): %}
		{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
		meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}

		{% else %}
		{% if (length(access_control['ip']) > 0): %}
		meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}

		{% endif %}
		{% if (length(access_control['ip6']) > 0): %}
		meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}

		{% endif %}
		{% if (length(access_control['mac']) > 0): %}
		meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}

		{% endif %}
		{% endif %}
		{% endif %}
		{% endfor %}
	}
	{% endif %}
	{% endif %}

	{% if (router_proxy): %}
	chain nat_output {
		type nat hook output priority filter; policy accept;
		{% if (cgroups_version == 1): %}
		meta cgroup {{ cgroup_id }} counter return
		{% elif (cgroups_version == 2): %}
		socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return
		{% endif %}
		{% if (length(dns_hijack_nfproto) > 0): %}
		meta nfproto @dns_hijack_nfproto jump router_dns_hijack
		{% endif %}
		{% if (tcp_mode == 'redirect'): %}
		fib daddr type { local, broadcast, anycast, multicast } counter return
		ct direction reply counter return
		ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		ip daddr @china_ip counter return
		ip6 daddr @china_ip6 counter return
		meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		{% for (let fwmark in bypass_fwmark): %}
		meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
		{% endfor %}
		meta nfproto @proxy_nfproto jump router_redirect
		{% endif %}
		{% if (fake_ip_ping_hijack): %}
		{% if (fake_ip_range): %}
		icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect
		{% endif %}
		{% if (fake_ip6_range): %}
		icmpv6 type echo-request ip6 daddr {{ fake_ip6_range }} counter redirect
		{% endif %}
		{% endif %}
	}

	chain mangle_output {
		type route hook output priority mangle; policy accept;
		{% if (cgroups_version == 1): %}
		meta cgroup {{ cgroup_id }} counter return
		{% elif (cgroups_version == 2): %}
		socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return
		{% endif %}
		fib daddr type { local, broadcast, anycast, multicast } counter return
		ct direction reply counter return
		ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		ip daddr @china_ip counter return
		ip6 daddr @china_ip6 counter return
		meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		{% for (let fwmark in bypass_fwmark): %}
		meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
		{% endfor %}
		{% if (length(dns_hijack_nfproto) > 0): %}
		meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return
		{% endif %}
		{% if (tcp_mode == 'tproxy'): %}
		meta nfproto @proxy_nfproto meta l4proto tcp jump router_tproxy
		{% elif (tcp_mode == 'tun'): %}
		meta nfproto @proxy_nfproto meta l4proto tcp jump router_tun
		{% endif %}
		{% if (udp_mode == 'tproxy'): %}
		meta nfproto @proxy_nfproto meta l4proto udp jump router_tproxy
		{% elif (udp_mode == 'tun'): %}
		meta nfproto @proxy_nfproto meta l4proto udp jump router_tun
		{% endif %}
	}

	chain mangle_prerouting_router {
		type filter hook prerouting priority mangle - 1; policy accept;
		{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
		iifname lo meta l4proto { tcp, udp } meta mark & {{ tproxy_fw_mask }} == {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept
		{% endif %}
		{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
		iifname "{{ tun_device }}" meta l4proto { icmp, tcp, udp } counter accept
		{% endif %}
	}
	{% endif %}

	{% if (lan_proxy): %}
	chain dstnat {
		type nat hook prerouting priority dstnat + 1; policy accept;
		{% if (length(dns_hijack_nfproto) > 0): %}
		iifname @lan_inbound_device meta nfproto @dns_hijack_nfproto jump lan_dns_hijack
		{% endif %}
		{% if (tcp_mode == 'redirect'): %}
		fib daddr type { local, broadcast, anycast, multicast } counter return
		ct direction reply counter return
		ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		ip daddr @china_ip counter return
		ip6 daddr @china_ip6 counter return
		meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		{% for (let fwmark in bypass_fwmark): %}
		meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
		{% endfor %}
		iifname @lan_inbound_device meta nfproto @proxy_nfproto jump lan_redirect
		{% endif %}
		{% if (fake_ip_ping_hijack): %}
		{% if (fake_ip_range): %}
		icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect
		{% endif %}
		{% if (fake_ip6_range): %}
		icmpv6 type echo-request ip6 daddr {{ fake_ip6_range }} counter redirect
		{% endif %}
		{% endif %}
	}

	chain mangle_prerouting_lan {
		type filter hook prerouting priority mangle; policy accept;
		fib daddr type { local, broadcast, anycast, multicast } counter return
		ct direction reply counter return
		ip daddr @reserved_ip {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		ip6 daddr @reserved_ip6 {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		ip daddr @china_ip counter return
		ip6 daddr @china_ip6 counter return
		meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta nfproto ipv6 meta l4proto . th dport != @proxy_dport {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
		meta l4proto { tcp, udp } ip6 dscp @bypass_dscp {% if (fake_ip6_range): %} ip6 daddr != {{ fake_ip6_range }} {% endif %} counter return
		{% for (let fwmark in bypass_fwmark): %}
		meta mark & {{ fwmark.mask }} == {{ fwmark.mark }} counter return
		{% endfor %}
		{% if (length(dns_hijack_nfproto) > 0): %}
		meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return
		{% endif %}
		{% if (tcp_mode == 'tproxy'): %}
		iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tproxy
		{% elif (tcp_mode == 'tun'): %}
		iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tun
		{% endif %}
		{% if (udp_mode == 'tproxy'): %}
		iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto udp jump lan_tproxy
		{% elif (udp_mode == 'tun'): %}
		iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto udp jump lan_tun
		{% endif %}
	}
	{% endif %}
}

{% if (bypass_china_mainland_ip): %}
include "/etc/momo/firewall/geoip_cn.nft"
{% endif %}
{% if (bypass_china_mainland_ip6): %}
include "/etc/momo/firewall/geoip6_cn.nft"
{% endif %}