[ -n "$ZAPRET_NFT_TABLE" ] || ZAPRET_NFT_TABLE=zapret

# required for : nft -f -
create_dev_stdin

nft_create_table()
{
	nft add table inet $ZAPRET_NFT_TABLE
}
nft_del_table()
{
	nft delete table inet $ZAPRET_NFT_TABLE 2>/dev/null
}
nft_list_table()
{
	nft -t list table inet $ZAPRET_NFT_TABLE
}

nft_create_set()
{
	# $1 - set name
	# $2 - params
	nft create set inet $ZAPRET_NFT_TABLE $1 "{ $2 }" 2>/dev/null
}
nft_del_set()
{
	# $1 - set name
	nft delete set inet $ZAPRET_NFT_TABLE $1
}
nft_flush_set()
{
	# $1 - set name
	nft flush set inet $ZAPRET_NFT_TABLE $1
}
nft_set_exists()
{
	# $1 - set name
	nft -t list set inet $ZAPRET_NFT_TABLE $1 2>/dev/null >/dev/null
}

nft_del_all_chains_from_table()
{
	# $1 - table_name with or without family

	# delete all chains with possible references to each other
	# cannot just delete all in the list because of references
	# avoid infinite loops
	local chains deleted=1 error=1
	while [ -n "$deleted" -a -n "$error" ]; do
		chains=$(nft -t list table $1 2>/dev/null | sed -nre "s/^[ 	]*chain ([^ ]+) \{/\1/p" | xargs)
		[ -n "$chains" ] || break
		deleted=
		error=
		for chain in $chains; do
			if nft delete chain $1 $chain 2>/dev/null; then
				deleted=1
			else
				error=1
			fi
		done
	done
}

nft_create_chains()
{
	# NOTE : postrouting hook has priority 101 to hook packets already processed by nat
	# NOTE : this is the only way to implement ipfrag for forwarded packets if nat is present
	# NOTE : to avoid retracking and sport changing use notrack for nfqws generated packets
	# NOTE : this also prevents defragmentation of ipv6 fragmented frames in kernels 4.16+
cat << EOF | nft -f -
	add chain inet $ZAPRET_NFT_TABLE dnat_output { type nat hook output priority -101; }
	flush chain inet $ZAPRET_NFT_TABLE dnat_output
	add chain inet $ZAPRET_NFT_TABLE dnat_pre { type nat hook prerouting priority -101; }
	flush chain inet $ZAPRET_NFT_TABLE dnat_pre
	add chain inet $ZAPRET_NFT_TABLE forward { type filter hook forward priority -1; }
	flush chain inet $ZAPRET_NFT_TABLE forward
	add chain inet $ZAPRET_NFT_TABLE input { type filter hook input priority -1; }
	flush chain inet $ZAPRET_NFT_TABLE input
	add chain inet $ZAPRET_NFT_TABLE flow_offload
	flush chain inet $ZAPRET_NFT_TABLE flow_offload
	add chain inet $ZAPRET_NFT_TABLE localnet_protect
	flush chain inet $ZAPRET_NFT_TABLE localnet_protect
	add rule inet  $ZAPRET_NFT_TABLE localnet_protect ip daddr $TPWS_LOCALHOST4 return comment "route_localnet allow access to tpws"
	add rule inet  $ZAPRET_NFT_TABLE localnet_protect ip daddr 127.0.0.0/8 drop comment "route_localnet remote access protection"
	add rule inet  $ZAPRET_NFT_TABLE input iif != lo jump localnet_protect
	add chain inet $ZAPRET_NFT_TABLE postrouting { type filter hook postrouting priority 101; }
	flush chain inet $ZAPRET_NFT_TABLE postrouting
	add chain inet $ZAPRET_NFT_TABLE predefrag { type filter hook output priority -401; }
	flush chain inet $ZAPRET_NFT_TABLE predefrag 
	add rule inet $ZAPRET_NFT_TABLE predefrag mark and $DESYNC_MARK !=0 notrack comment "do not track nfqws generated packets to avoid nat tampering and defragmentation"
	add set inet $ZAPRET_NFT_TABLE lanif { type ifname; }
	add set inet $ZAPRET_NFT_TABLE wanif { type ifname; }
	add set inet $ZAPRET_NFT_TABLE wanif6 { type ifname; }
	add map inet $ZAPRET_NFT_TABLE link_local { type ifname : ipv6_addr; }
EOF
}
nft_del_chains()
{
	# do not delete all chains because of additional user hooks
	# they must be inside zapret table to use nfsets

cat << EOF | nft -f - 2>/dev/null
	delete chain inet $ZAPRET_NFT_TABLE dnat_output
	delete chain inet $ZAPRET_NFT_TABLE dnat_pre
	delete chain inet $ZAPRET_NFT_TABLE forward
	delete chain inet $ZAPRET_NFT_TABLE input
	delete chain inet $ZAPRET_NFT_TABLE postrouting
	delete chain inet $ZAPRET_NFT_TABLE predefrag
	delete chain inet $ZAPRET_NFT_TABLE flow_offload
	delete chain inet $ZAPRET_NFT_TABLE localnet_protect
EOF
}
nft_del_flowtable()
{
	nft delete flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null
}
nft_create_or_update_flowtable()
{
	# $1 = flags ('offload' for hw offload)
	# $2,$3,$4,... - interfaces
	# can be called multiple times to add interfaces. interfaces can only be added , not removed
	local flags=$1 devices makelist
	shift
	# warning ! nft versions at least up to 1.0.1 do not allow interface names starting with digit in flowtable and do not allow quoting
	# warning ! openwrt fixes this in post-21.x snapshots with special nft patch
	# warning ! in traditional linux distros nft is unpatched and will fail with quoted interface definitions if unfixed
	[ -n "$flags" ] && flags="flags $flags;"
	for makelist in make_quoted_comma_list make_comma_list; do
		$makelist devices "$@"
		[ -n "$devices" ] && devices="devices={$devices};"
		nft add flowtable inet $ZAPRET_NFT_TABLE ft "{ hook ingress priority -1; $flags $devices }" && break
	done
}
nft_flush_ifsets()
{
cat << EOF | nft -f -  2>/dev/null
	flush set inet $ZAPRET_NFT_TABLE lanif
	flush set inet $ZAPRET_NFT_TABLE wanif
	flush set inet $ZAPRET_NFT_TABLE wanif6
	flush map inet $ZAPRET_NFT_TABLE link_local
EOF
}
nft_flush_link_local()
{
	nft flush map inet $ZAPRET_NFT_TABLE link_local 2>/dev/null
}
nft_list_ifsets()
{
	nft list set inet $ZAPRET_NFT_TABLE lanif
	nft list set inet $ZAPRET_NFT_TABLE wanif
	nft list set inet $ZAPRET_NFT_TABLE wanif6
	nft list map inet $ZAPRET_NFT_TABLE link_local
	nft list flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null
}

nft_create_firewall()
{
	nft_create_table
	nft_del_flowtable
	nft_flush_link_local
	nft_create_chains
}
nft_del_firewall()
{
	nft_del_chains
	nft_del_flowtable
	nft_flush_link_local
	# leave ifsets and ipsets because they may be used by custom rules
}

nft_add_rule()
{
	# $1 - chain
	# $2,$3,... - rule(s)
	local chain="$1"
	shift
	nft add rule inet $ZAPRET_NFT_TABLE $chain "$@"
}
nft_add_set_element()
{
	# $1 - set or map name
	# $2 - element
	[ -z "$2" ] || nft add element inet $ZAPRET_NFT_TABLE $1 "{ $2 }"
}
nft_add_set_elements()
{
	# $1 - set or map name
	# $2,$3,... - element(s)
	local set="$1" elements
	shift
	make_comma_list elements "$@"
	nft_add_set_element $set "$elements"
}
nft_reverse_nfqws_rule()
{
	echo "$@" | sed -e 's/oifname /iifname /g' -e 's/dport /sport /g' -e 's/daddr /saddr /g' -e 's/ct original /ct reply /g' -e "s/mark and $DESYNC_MARK == 0//g"
}
nft_clean_nfqws_rule()
{
	echo "$@" | sed -e "s/mark and $DESYNC_MARK == 0//g"
}
nft_add_nfqws_flow_exempt_rule()
{
	# $1 - rule (must be all filters in one var)
	nft_add_rule flow_offload $(nft_clean_nfqws_rule $1) return comment \"direct flow offloading exemption\"
	# do not need this because of oifname @wanif/@wanif6 filter in forward chain
	#nft_add_rule flow_offload $(nft_reverse_nfqws_rule $1) return comment \"reverse flow offloading exemption\"
}

nft_hw_offload_supported()
{
	# $1,$2,... - interface names
	local devices res=1
	make_quoted_comma_list devices "$@"
	[ -n "$devices" ] && devices="devices={$devices};"
	nft add table ${ZAPRET_NFT_TABLE}_test && nft add flowtable ${ZAPRET_NFT_TABLE}_test ft "{ flags offload; $devices }" 2>/dev/null && res=0
	nft delete table ${ZAPRET_NFT_TABLE}_test 2>/dev/null
	return $res
}

nft_apply_flow_offloading()
{
	# ft can be absent
	nft_add_rule flow_offload meta l4proto "{ tcp, udp }" flow add @ft 2>/dev/null && {
		# allow only outgoing packets to initiate flow offload
		nft_add_rule forward oifname @wanif jump flow_offload
		nft_add_rule forward oifname @wanif6 jump flow_offload
	}
}



nft_filter_apply_port_target()
{
	# $1 - var name of nftables filter
	local f
	if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then
		f="tcp dport {80,443}"
	elif [ "$MODE_HTTPS" = "1" ]; then
		f="tcp dport 443"
	elif [ "$MODE_HTTP" = "1" ]; then
		f="tcp dport 80"
	else
		echo WARNING !!! HTTP and HTTPS are both disabled
	fi
	eval $1="\"\$$1 $f\""
}
nft_filter_apply_ipset_target4()
{
	# $1 - var name of ipv4 nftables filter
	if [ "$MODE_FILTER" = "ipset" ]; then
		eval $1="\"\$$1 ip daddr @zapret\""
	fi
}
nft_filter_apply_ipset_target6()
{
	# $1 - var name of ipv6 nftables filter
	if [ "$MODE_FILTER" = "ipset" ]; then
		eval $1="\"\$$1 ip6 daddr @zapret6\""
	fi
}
nft_filter_apply_ipset_target()
{
	# $1 - var name of ipv4 nftables filter
	# $2 - var name of ipv6 nftables filter
	nft_filter_apply_ipset_target4 $1
	nft_filter_apply_ipset_target6 $2
}


nft_script_add_ifset_element()
{
	# $1 - set name
	# $2 - space separated elements
	local elements
	[ -n "$2" ] && {
		make_quoted_comma_list elements $2
		script="${script}
add element inet $ZAPRET_NFT_TABLE $1 { $elements }"
	}
}
nft_fill_ifsets()
{
	# $1 - space separated lan interface names
	# $2 - space separated wan interface names
	# $3 - space separated wan6 interface names

	local script i j ALLDEVS devs

	# if large sets exist nft works very ineffectively
	# looks like it analyzes the whole table blob to find required data pieces
	# calling all in one shot helps not to waste cpu time many times

	script="flush set inet $ZAPRET_NFT_TABLE wanif
flush set inet $ZAPRET_NFT_TABLE wanif6
flush set inet $ZAPRET_NFT_TABLE lanif"

	[ "$DISABLE_IPV4" = "1" ] || nft_script_add_ifset_element wanif "$2"
	[ "$DISABLE_IPV6" = "1" ] || nft_script_add_ifset_element wanif6 "$3"
	nft_script_add_ifset_element lanif "$1"

	echo "$script" | nft -f -

	case "$FLOWOFFLOAD" in
		software)
			ALLDEVS=$(unique $1 $2 $3)
			# unbound flowtable may cause error in older nft version
			nft_create_or_update_flowtable '' $ALLDEVS 2>/dev/null
			;;
		hardware)
			ALLDEVS=$(unique $1 $2 $3)
			# first create unbound flowtable. may cause error in older nft version
			nft_create_or_update_flowtable 'offload' 2>/dev/null
			# then add elements. some of them can cause error because unsupported
			for i in $ALLDEVS; do
				if nft_hw_offload_supported $i; then
					nft_create_or_update_flowtable 'offload' $i
				else
					# bridge members must be added instead of the bridge itself
					devs=$(resolve_lower_devices $i)
					[ -n "$devs" ] && nft_hw_offload_supported $devs && {
						for j in $devs; do
							nft_create_or_update_flowtable 'offload' $j
						done
					}
				fi
			done
			;;
	esac
}

nft_only()
{
	linux_fwtype

	case "$FWTYPE" in
		nftables)
			"$@"
			;;
	esac
}


nft_print_op()
{
	echo "Adding nftables ipv$3 rule for $2 : $1"
}
_nft_fw_tpws4()
{
	# $1 - filter ipv4
	# $2 - tpws port
	# $3 - not-empty if wan interface filtering required

	[ "$DISABLE_IPV4" = "1" ] || {
		local filter="$1" port="$2"
		nft_print_op "$filter" "tpws (port $2)" 4
		nft_add_rule dnat_output skuid != $WS_USER ${3:+oifname @wanif }$filter ip daddr != @nozapret dnat ip to $TPWS_LOCALHOST4:$port
		nft_add_rule dnat_pre iifname @lanif $filter ip daddr != @nozapret dnat ip to $TPWS_LOCALHOST4:$port
		prepare_route_localnet
	}
}
_nft_fw_tpws6()
{
	# $1 - filter ipv6
	# $2 - tpws port
	# $3 - lan interface names space separated
	# $4 - not-empty if wan interface filtering required

	[ "$DISABLE_IPV6" = "1" ] || {
		local filter="$1" port="$2" DNAT6 i
		nft_print_op "$filter" "tpws (port $port)" 6
		nft_add_rule dnat_output skuid != $WS_USER ${4:+oifname @wanif6 }$filter ip6 daddr != @nozapret6 dnat ip6 to [::1]:$port
		[ -n "$3" ] && {
			nft_add_rule dnat_pre $filter ip6 daddr != @nozapret6 dnat ip6 to iifname map @link_local:$port
			for i in $3; do
				_dnat6_target $i DNAT6
				# can be multiple tpws processes on different ports
				[ -n "$DNAT6" -a "$DNAT6" != '-' ] && nft_add_set_element link_local "$i : $DNAT6"
			done
		}
	}
}
nft_fw_tpws()
{
	# $1 - filter ipv4
	# $2 - filter ipv6
	# $3 - tpws port

	nft_fw_tpws4 "$1" $3
	nft_fw_tpws6 "$2" $3
}

_nft_fw_nfqws_post4()
{
	# $1 - filter ipv4
	# $2 - queue number
	# $3 - not-empty if wan interface filtering required

	[ "$DISABLE_IPV4" = "1" ] || {
		local filter="$1" port="$2" rule
		nft_print_op "$filter" "nfqws postrouting (qnum $port)" 4
		rule="${3:+oifname @wanif }$filter ip daddr != @nozapret"
		nft_add_rule postrouting $rule queue num $port bypass
		nft_add_nfqws_flow_exempt_rule "$rule"
	}
}
_nft_fw_nfqws_post6()
{
	# $1 - filter ipv6
	# $2 - queue number
	# $3 - not-empty if wan interface filtering required

	[ "$DISABLE_IPV6" = "1" ] || {
		local filter="$1" port="$2" rule
		nft_print_op "$filter" "nfqws postrouting (qnum $port)" 6
		rule="${3:+oifname @wanif6 }$filter ip6 daddr != @nozapret6"
		nft_add_rule postrouting $rule queue num $port bypass
		nft_add_nfqws_flow_exempt_rule "$rule"
	}
}
nft_fw_nfqws_post()
{
	# $1 - filter ipv4
	# $2 - filter ipv6
	# $3 - queue number

	nft_fw_nfqws_post4 "$1" $3
	nft_fw_nfqws_post6 "$2" $3
}


zapret_reload_ifsets()
{
	nft_only nft_create_table ; nft_fill_ifsets_overload
	return 0
}
zapret_list_ifsets()
{
	nft_only nft_list_ifsets
	return 0
}
zapret_list_table()
{
	nft_only nft_list_table
	return 0
}

zapret_apply_firewall_rules_nft()
{
	local mode="${MODE_OVERRIDE:-$MODE}"

	local first_packet_only="ct original packets 1-4"
	local desync="mark and $DESYNC_MARK == 0"
	local f4 f6 qn qns qn6 qns6

	case "$mode" in
		tpws)
			if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then
				echo both http and https are disabled. not applying redirection.
			else
				nft_filter_apply_port_target f4
				f6=$f4
				nft_filter_apply_ipset_target f4 f6
				nft_fw_tpws "$f4" "$f6" $TPPORT
			fi
			;;
		nfqws)
			# quite complex but we need to minimize nfqws processes to save RAM
			get_nfqws_qnums qn qns qn6 qns6
			if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn" ] && [ "$qn" = "$qns" ]; then
				nft_filter_apply_port_target f4
				f4="$f4 $first_packet_only"
				nft_filter_apply_ipset_target4 f4
				nft_fw_nfqws_post4 "$f4 $desync" $qn
			else
				if [ -n "$qn" ]; then
					f4="tcp dport 80"
					[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packet_only"
					nft_filter_apply_ipset_target4 f4
					nft_fw_nfqws_post4 "$f4 $desync" $qn
				fi
				if [ -n "$qns" ]; then
					f4="tcp dport 443 $first_packet_only"
					nft_filter_apply_ipset_target4 f4
					nft_fw_nfqws_post4 "$f4 $desync" $qns
				fi
			fi
			if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn6" ] && [ "$qn6" = "$qns6" ]; then
				nft_filter_apply_port_target f6
				f6="$f6 $first_packet_only"
				nft_filter_apply_ipset_target6 f6
				nft_fw_nfqws_post6 "$f6 $desync" $qn6
			else
				if [ -n "$qn6" ]; then
					f6="tcp dport 80"
					[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f6="$f6 $first_packet_only"
					nft_filter_apply_ipset_target6 f6
					nft_fw_nfqws_post6 "$f6 $desync" $qn6
				fi
				if [ -n "$qns6" ]; then
					f6="tcp dport 443 $first_packet_only"
					nft_filter_apply_ipset_target6 f6
					nft_fw_nfqws_post6 "$f6 $desync" $qns6
				fi
			fi
			;;
		custom)
	    		existf zapret_custom_firewall_nft && zapret_custom_firewall_nft
			;;
	esac
}

zapret_apply_firewall_nft()
{
	echo Applying nftables

	local mode="${MODE_OVERRIDE:-$MODE}"

	[ "$mode" = "tpws-socks" ] && return 0

	create_ipset no-update
	nft_create_firewall
	nft_fill_ifsets_overload

	zapret_apply_firewall_rules_nft

	[ "$FLOWOFFLOAD" = 'software' -o "$FLOWOFFLOAD" = 'hardware' ] && nft_apply_flow_offloading

	return 0
}
zapret_unapply_firewall_nft()
{
	echo Clearing nftables

	unprepare_route_localnet
	nft_del_firewall
	return 0
}
zapret_do_firewall_nft()
{
	# $1 - 1 - add, 0 - del

	if [ "$1" = 0 ] ; then
		zapret_unapply_firewall_nft
	else
		zapret_apply_firewall_nft
	fi

	return 0
}
