NFT_ALLOWED_HOSTS_CHAIN="allowed_hosts"
NFT_BLLIST_CHAIN="blacklist"
NFT_DNSMASQ_TIMEOUT_UPDATE_CHAIN="dnsmasq_timeout_update"
NFT_MARK_CHAIN="mark_chain"
NFT_LOCAL_CLIENTS_CHAIN="local_clients"
NFT_FPROXY_CHAIN="fproxy_chain"
NFT_ACTION_FILTER_CHAIN="action_filter"
NFT_ACTION_NAT_CHAIN="action_nat"
NFT_ACTION_NAT_LOCAL_CHAIN="action_nat_local"

case "$ALLOWED_HOSTS_MODE" in
    "1")
        NFT_ALLOWED_HOSTS_PATTERN="ip saddr @${NFTSET_ALLOWED_HOSTS} jump ${NFT_BLLIST_CHAIN}"
    ;;
    "2")
        NFT_ALLOWED_HOSTS_PATTERN="ip saddr != @${NFTSET_ALLOWED_HOSTS} jump ${NFT_BLLIST_CHAIN}"
    ;;
    *)
        NFT_ALLOWED_HOSTS_PATTERN="jump ${NFT_BLLIST_CHAIN}"
    ;;
esac

NftCmdWrapper() {
    local _i=0 _attempts=10 _return_code=1
    while [ $_i -lt $_attempts ]
    do
        if $*; then
            _return_code=$?
            break
        fi
        _i=$(($_i + 1))
    done
    return $_return_code
}

NftRouteDelete() {
    local _route_table_id=$1
    $IP_CMD route flush table "$_route_table_id"
    $IP_CMD rule del table "$_route_table_id"
}

NftRouteAdd() {
    local _vpn_ip _type="$1" _route_table_id=$2 _pkts_mark=$3 _if_vpn="$4" _vpn_gw_ip="$5"
    if [ "$_type" = "lo" ]; then
        echo 0 > "/proc/sys/net/ipv4/conf/lo/rp_filter"
        $IP_CMD rule add fwmark "$_pkts_mark" table "$_route_table_id" priority "$LO_RULE_PRIO"
        $IP_CMD route add local default dev lo table "$_route_table_id"

        if [ $DEBUG -ge 1 ]; then
            echo "  nft_functions.NftRouteAdd: ${IP_CMD} rule add fwmark ${_pkts_mark} table ${_route_table_id} priority ${LO_RULE_PRIO}" >&2
            MakeLogRecord "debug" "nft_functions.NftRouteAdd: ${IP_CMD} rule add fwmark ${_pkts_mark} table ${_route_table_id} priority ${LO_RULE_PRIO}"
            echo "  nft_functions.NftRouteAdd: ${IP_CMD} route add local default dev lo table ${_route_table_id}" >&2
            MakeLogRecord "debug" "nft_functions.NftRouteAdd: ${IP_CMD} route add local default dev lo table ${_route_table_id}"
        fi
    else
        if [ -n "$_vpn_gw_ip" ]; then
            _vpn_ip="$_vpn_gw_ip"
        else
            _vpn_ip=$($IP_CMD addr list dev "$_if_vpn" 2> /dev/null | $AWK_CMD '/inet/{f=($3 == "peer") ? 4 : 2; sub("/[0-9]{1,2}$", "", $f); print $f; exit}')
        fi
        if [ -n "$_vpn_ip" -a "$_type" = "vpn" ]; then
            echo 0 > "/proc/sys/net/ipv4/conf/${_if_vpn}/rp_filter"
            NftRouteDelete "$_route_table_id" 2> /dev/null
            $IP_CMD rule add fwmark "$_pkts_mark" table "$_route_table_id" priority "$VPN_RULE_PRIO"
            $IP_CMD route add default via "$_vpn_ip" table "$_route_table_id" metric 100
            if [ $? -ne 0 ]; then
                echo "  Error! An error occurred while adding the route. Routing table id=${_route_table_id}, VPN gateway IP=${_vpn_ip}" >&2
                MakeLogRecord "err" "Error! An error occurred while adding the route. Routing table id=${_route_table_id}, VPN gateway IP=${_vpn_ip}"
            fi
            $IP_CMD route add blackhole default table "$_route_table_id" metric 200

            if [ $DEBUG -ge 1 ]; then
                echo "  nft_functions.NftRouteAdd: ${IP_CMD} rule add fwmark ${_pkts_mark} table ${_route_table_id} priority ${VPN_RULE_PRIO}" >&2
                MakeLogRecord "debug" "nft_functions.NftRouteAdd: ${IP_CMD} rule add fwmark ${_pkts_mark} table ${_route_table_id} priority ${VPN_RULE_PRIO}"
                echo "  nft_functions.NftRouteAdd: ${IP_CMD} route add default via ${_vpn_ip} table ${_route_table_id}" >&2
                MakeLogRecord "debug" "nft_functions.NftRouteAdd: ${IP_CMD} route add default via ${_vpn_ip} table ${_route_table_id}"
                echo "  nft_functions.NftRouteAdd: ${IP_CMD} route add blackhole default table ${_route_table_id} metric 200" >&2
                MakeLogRecord "debug" "nft_functions.NftRouteAdd: ${IP_CMD} route add blackhole default table ${_route_table_id} metric 200"
            fi
        fi
    fi
}

NftRouteStatus() {
    local _route_table_id=$1
    $IP_CMD route show table $_route_table_id 2> /dev/null | $AWK_CMD 'BEGIN {code=1} /^(default|local)/ {code=0} END {exit code}' && return 0
    return 1
}

NftAddBaseChains() {
    local _chain_prio_first=$1 _chain_prio_local=$2 _chain_prio_fproxy=$3
    $NFT_CMD add chain $NFT_TABLE "$NFT_LOCAL_CLIENTS_CHAIN" { type route hook output priority ${_chain_prio_local}\; policy accept\; }
    $NFT_CMD add chain $NFT_TABLE "$NFT_BLLIST_CHAIN"
    $NFT_CMD add chain $NFT_TABLE "$NFT_FPROXY_CHAIN" { type filter hook prerouting priority ${_chain_prio_fproxy}\; policy accept\; }
    $NFT_CMD add chain $NFT_TABLE "$NFT_ALLOWED_HOSTS_CHAIN" { type filter hook prerouting priority ${_chain_prio_first}\; policy accept\; }
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_FPROXY_CHAIN" meta iif lo return
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_FPROXY_CHAIN" ip daddr "@${NFTSET_FPROXY_LOCAL}" return
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ALLOWED_HOSTS_CHAIN" meta iif lo return
    if [ "$IGNORE_LOCAL_IP" = "1" ]; then
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ALLOWED_HOSTS_CHAIN" ip daddr "@${NFTSET_LOCAL_IP}" return
    fi
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ALLOWED_HOSTS_CHAIN" "$NFT_ALLOWED_HOSTS_PATTERN"
    if [ "$BYPASS_MODE" = "1" ]; then
        for _set in "$NFTSET_BYPASS_IP" "$NFTSET_BYPASS_FQDN"
        do
            NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_BLLIST_CHAIN" ip daddr "@${_set}" counter accept
        done
    fi
}

NftAddLocalClientsRule() {
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_LOCAL_CLIENTS_CHAIN" jump "$NFT_BLLIST_CHAIN"
}

NftDeleteBaseChains() {
    $NFT_CMD delete chain $NFT_TABLE "$NFT_ALLOWED_HOSTS_CHAIN"
    $NFT_CMD delete chain $NFT_TABLE "$NFT_LOCAL_CLIENTS_CHAIN"
    $NFT_CMD delete chain $NFT_TABLE "$NFT_BLLIST_CHAIN"
    $NFT_CMD delete chain $NFT_TABLE "$NFT_FPROXY_CHAIN"
}

NftAddActionChains() {
    local _chain_prio_action=$1
    $NFT_CMD add chain $NFT_TABLE "$NFT_ACTION_FILTER_CHAIN" { type filter hook prerouting priority ${_chain_prio_action}\; policy accept\; }
    $NFT_CMD add chain $NFT_TABLE "$NFT_ACTION_NAT_CHAIN" { type nat hook prerouting priority ${_chain_prio_action}\; policy accept\; }
    $NFT_CMD add chain $NFT_TABLE "$NFT_ACTION_NAT_LOCAL_CHAIN" { type nat hook output priority ${_chain_prio_action}\; policy accept\; }
}

NftDeleteActionChains() {
    $NFT_CMD delete chain $NFT_TABLE "$NFT_ACTION_FILTER_CHAIN"
    $NFT_CMD delete chain $NFT_TABLE "$NFT_ACTION_NAT_CHAIN"
    $NFT_CMD delete chain $NFT_TABLE "$NFT_ACTION_NAT_LOCAL_CHAIN"
}

NftInstanceAdd() {
    local _i _inst _first_chain_type _t_proxy_statement _chain_action_type _set

    for _i in "_name" "_pkts_mark" "_proxy_mode" "_tor_trans_port" "_route_table_id" "_if_vpn" "_t_proxy_type" "_t_proxy_port_tcp" "_t_proxy_port_udp" "_t_proxy_allow_udp" "_enable_bllist_proxy" "_enable_fproxy" "_vpn_gw_ip"
    do
        eval "local $_i=$1"
        shift
    done

    _inst="$_name"
    if [ "$_name" = " " ]; then
        _name=""
    else
        _name=".${_name}"
    fi

    if [ $DEBUG -ge 1 ]; then
        echo "  nft_functions.NftInstanceAdd.args: _name=${_name} _pkts_mark=${_pkts_mark} _proxy_mode=${_proxy_mode} _tor_trans_port=${_tor_trans_port} _route_table_id=${_route_table_id} _if_vpn=${_if_vpn} _t_proxy_type=${_t_proxy_type} _t_proxy_port_tcp=${_t_proxy_port_tcp} _t_proxy_port_udp=${_t_proxy_port_udp} _t_proxy_allow_udp=${_t_proxy_allow_udp} _enable_bllist_proxy=${_enable_bllist_proxy} _enable_fproxy=${_enable_fproxy} _vpn_gw_ip=${_vpn_gw_ip}" >&2
        MakeLogRecord "debug" "nft_functions.NftInstanceAdd.args: _name=${_name} _pkts_mark=${_pkts_mark} _proxy_mode=${_proxy_mode} _tor_trans_port=${_tor_trans_port} _route_table_id=${_route_table_id} _if_vpn=${_if_vpn} _t_proxy_type=${_t_proxy_type} _t_proxy_port_tcp=${_t_proxy_port_tcp} _t_proxy_port_udp=${_t_proxy_port_udp} _t_proxy_allow_udp=${_t_proxy_allow_udp} _enable_bllist_proxy=${_enable_bllist_proxy} _enable_fproxy=${_enable_fproxy} _vpn_gw_ip=${_vpn_gw_ip}"
    fi

    if [ "$NFTSET_DNSMASQ_TIMEOUT_UPDATE" = "1" ]; then
        _nft_dnsmasq_rule_target="${NFT_DNSMASQ_TIMEOUT_UPDATE_CHAIN}${_name}"
    else
        _nft_dnsmasq_rule_target="${NFT_MARK_CHAIN}${_name}"
    fi

    $NFT_CMD add chain $NFT_TABLE "${NFT_MARK_CHAIN}${_name}"
    $NFT_CMD add chain $NFT_TABLE "${NFT_DNSMASQ_TIMEOUT_UPDATE_CHAIN}${_name}"
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "${NFT_DNSMASQ_TIMEOUT_UPDATE_CHAIN}${_name}" ct state new set update ip daddr "@${NFTSET_DNSMASQ}${_name}"
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "${NFT_DNSMASQ_TIMEOUT_UPDATE_CHAIN}${_name}" jump "${NFT_MARK_CHAIN}${_name}"

    if [ "$_proxy_mode" = "3" ]; then
        if [ "$_t_proxy_type" = "1" ]; then
            NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_FILTER_CHAIN" meta l4proto tcp meta mark $_pkts_mark tproxy to ":${_t_proxy_port_tcp}" comment \""$_inst"\"
            if [ "$_t_proxy_allow_udp" = "1" ]; then
                NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_FILTER_CHAIN" meta l4proto udp meta mark $_pkts_mark tproxy to ":${_t_proxy_port_udp}" comment \""$_inst"\"
            fi
        else
            NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_NAT_CHAIN" meta l4proto tcp meta mark $_pkts_mark redirect to ":${_t_proxy_port_tcp}" comment \""$_inst"\"
            NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_NAT_LOCAL_CHAIN" meta l4proto tcp meta mark $_pkts_mark redirect to ":${_t_proxy_port_tcp}" comment \""$_inst"\"
            if [ "$_t_proxy_allow_udp" = "1" ]; then
                NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_NAT_CHAIN" meta l4proto udp meta mark $_pkts_mark redirect to ":${_t_proxy_port_udp}" comment \""$_inst"\"
                NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_NAT_LOCAL_CHAIN" meta l4proto udp meta mark $_pkts_mark redirect to ":${_t_proxy_port_udp}" comment \""$_inst"\"
            fi
        fi
    elif [ "$_proxy_mode" != "2" ]; then
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_NAT_CHAIN" meta l4proto tcp meta mark $_pkts_mark redirect to ":${_tor_trans_port}" comment \""$_inst"\"
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_ACTION_NAT_LOCAL_CHAIN" meta l4proto tcp meta mark $_pkts_mark redirect to ":${_tor_trans_port}" comment \""$_inst"\"
    fi

    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "${NFT_MARK_CHAIN}${_name}" mark set $_pkts_mark
    if [ "$_proxy_mode" != "2" -a "$_proxy_mode" != "3" ]; then
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_BLLIST_CHAIN" ip daddr "@${NFTSET_ONION}${_name}" counter goto "${NFT_MARK_CHAIN}${_name}" comment \""$_inst"\"
    fi
    if [ "$_enable_fproxy" = "1" ]; then
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_FPROXY_CHAIN" ip saddr "@${NFTSET_FPROXY}${_name}" counter goto "${NFT_MARK_CHAIN}${_name}" comment \""$_inst"\"
    fi

    for _set in "${NFTSET_CIDR}${_name}" "${NFTSET_IP}${_name}"
    do
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_BLLIST_CHAIN" ip daddr "@${_set}" counter goto "${NFT_MARK_CHAIN}${_name}" comment \""$_inst"\"
    done
    NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_BLLIST_CHAIN" ip daddr "@${NFTSET_DNSMASQ}${_name}" counter goto "$_nft_dnsmasq_rule_target" comment \""$_inst"\"

    if [ "$_proxy_mode" = "2" ]; then
        NftRouteAdd vpn $_route_table_id $_pkts_mark "$_if_vpn" "$_vpn_gw_ip"
    elif  [ "$_proxy_mode" = "3" -a "$_t_proxy_type" = "1" ]; then
        NftRouteAdd lo $_route_table_id $_pkts_mark
    fi

    if [ "$_enable_bllist_proxy" = "1" ]; then
        NftCmdWrapper $NFT_CMD add rule $NFT_TABLE "$NFT_LOCAL_CLIENTS_CHAIN" ip daddr "@${NFTSET_BLLIST_PROXY}${_name}" counter goto "${NFT_MARK_CHAIN}${_name}" comment \""$_inst"\"
    fi
}

NftInstanceDelete() {
    local _name="$1"
    if [ -z "$_name" -o "$_name" = " " ]; then
        _name=""
    else
        _name=".${_name}"
    fi
    $NFT_CMD delete chain $NFT_TABLE "${NFT_DNSMASQ_TIMEOUT_UPDATE_CHAIN}${_name}"
    $NFT_CMD delete chain $NFT_TABLE "${NFT_MARK_CHAIN}${_name}"
}

NftListBllistChain() {
    $NFT_CMD -t list chain $NFT_TABLE "$NFT_BLLIST_CHAIN"
}

NftListBllistChainJson() {
    $NFT_CMD -t -j list chain $NFT_TABLE "$NFT_BLLIST_CHAIN"
}

NftReturnStatus() {
    $NFT_CMD -c add rule $NFT_TABLE "$NFT_ALLOWED_HOSTS_CHAIN" continue &> /dev/null
    return $?
}
