#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## Copyright (C) 2014 - 2015 Jason Mehring <nrgaway@gmail.com>
## Copyright (C) 2024 - 2024 Benjamin Grande M. S. <ben.grande.b@gmail.com>
## See the file COPYING for copying conditions.

set -eu -o pipefail -o errtrace

# shellcheck source=../../../../../kicksecure/helper-scripts/usr/libexec/helper-scripts/strings.bsh
source /usr/libexec/helper-scripts/strings.bsh

## Function to use shellcheck on all the variables inside the function.
# shellcheck disable=SC2034
common_variables_defaults_pre(){
  firewall_dir="/var/lib/whonix-firewall"
  firewall_script="${firewall_dir}/firewall.nft"
  [ -n "${nftables_cmd:-}" ] || nftables_cmd="write_nft_script"
  [ -n "${INTERNAL_OPEN_PORTS:-}" ] || INTERNAL_OPEN_PORTS=""
  source_only=""
  dry_run=""
  info_enabled=""
  firewall_mode_cli=""
}
common_variables_defaults_pre

common_variables_defaults_post(){

  ## Legacy.
  ## Whonix-Gateway firewall does not support TUNNEL_FIREWALL_ENABLE=true yet.
  ## It only supports VPN_FIREWALL="1".
  ## In case someone confused this setting, i.e. using TUNNEL_FIREWALL_ENABLE=true
  ## since this is how it is done on Whonix-Workstation, then gracefully enable
  ## VPN_FIREWALL="1" to prevent users shooting their own feet.
  [ -n "${TUNNEL_FIREWALL_ENABLE:-}" ] || TUNNEL_FIREWALL_ENABLE=""
  [ -n "${VPN_FIREWALL:-}" ] || VPN_FIREWALL=""
  if [ "${TUNNEL_FIREWALL_ENABLE}" = "true" ]; then
    VPN_FIREWALL="1"
  fi
  if [ "${VPN_FIREWALL}" = "1" ]; then
    TUNNEL_FIREWALL_ENABLE="true"
  fi

  [ -n "${CLEARNET_USER:-}" ] || CLEARNET_USER="$(id -u clearnet)" || true
  [ -n "${NOTUNNEL_USER:-}" ] || NOTUNNEL_USER="$(id -u notunnel)" || true
  [ -n "${ROOT_USER:-}" ] || ROOT_USER="$(id -u root)"
  [ -n "${SDWDATE_USER:-}" ] || SDWDATE_USER="$(id -u sdwdate)" || true
  [ -n "${SYSTEMCHECK_USER:-}" ] || SYSTEMCHECK_USER="$(id -u systemcheck)" || true
  [ -n "${TOR_USER:-}" ] || TOR_USER="$(id -u debian-tor)" || true # distro specific
  [ -n "${TUNNEL_USER:-}" ] || TUNNEL_USER="$(id -u tunnel)" || true
  [ -n "${UPDATESPROXYCHECK_USER:-}" ] || UPDATESPROXYCHECK_USER="$(id -u updatesproxycheck)" || true
  [ -n "${USER_USER:-}" ] || USER_USER="$(id -u user)" || true # user choice

  [ -n "${CONTROL_PORT_FILTER_PROXY_PORT:-}" ] || CONTROL_PORT_FILTER_PROXY_PORT="9051"
  [ -n "${EXTERNAL_OPEN_ALL:-}" ] || EXTERNAL_OPEN_ALL=""
  [ -n "${EXTERNAL_OPEN_PORTS:-}" ] || EXTERNAL_OPEN_PORTS=""
  [ -n "${EXTERNAL_UDP_OPEN_PORTS:-}" ] || EXTERNAL_UDP_OPEN_PORTS=""
  [ -n "${EXT_IF:-}" ] || EXT_IF="eth0"
  [ -n "${NO_REJECT_INVALID_OUTGOING_PACKAGES:-}" ] || NO_REJECT_INVALID_OUTGOING_PACKAGES=""
  [ -n "${VPN_INTERFACE:-}" ] || VPN_INTERFACE="tun0"
  [ -n "${firewall_allow_udp:-}" ] || firewall_allow_udp=""
  [ -n "${firewall_mode:-}" ] || firewall_mode="full"
  [ -n "${outgoing_allow_ip_list:-}" ] || outgoing_allow_ip_list=""

  ## Socks Ports for per application circuits.
  ## SOCKS_PORT_TOR_DEFAULT: 9050
  INTERNAL_OPEN_PORTS+=" 9050 "
  ## SOCKS_PORT_TB: 9100
  ## SOCKS_PORT_IRC: 9101
  ## SOCKS_PORT_TORBIRDY: 9102
  ## SOCKS_PORT_IM: 9103
  ## SOCKS_PORT_APT_GET: 9104
  ## SOCKS_PORT_GPG: 9105
  ## SOCKS_PORT_SSH: 9106
  ## SOCKS_PORT_GIT: 9107
  ## SOCKS_PORT_SDWDATE: 9108
  ## SOCKS_PORT_WGET: 9108
  ## SOCKS_PORT_SYSTEMCHECK: 9110
  ## SOCKS_PORT_BITCOIN: 9111
  INTERNAL_OPEN_PORTS+=" $(seq 9100 9111) "
  ## 9112
  ## 9113
  ## SOCKS_PORT_WHONIX_NEWS: 9114
  ## SOCKS_PORT_TBB_DOWNLOAD: 9115
  INTERNAL_OPEN_PORTS+=" $(seq 9114 9115) "
  ## 9116
  ## SOCKS_PORT_CURL: 9117
  ## SOCKS_PORT_RSS: 9118
  INTERNAL_OPEN_PORTS+=" $(seq 9117 9118) "
  ## 9119
  ## 9120
  ## 9121
  ## SOCKS_PORT_KDE: 9122
  ## SOCKS_PORT_GNOME: 9123
  ## SOCKS_PORT_APTITUDE: 9124
  ## SOCKS_PORT_YUM: 9125
  INTERNAL_OPEN_PORTS+=" $(seq 9122 9125) "
  ## SOCKS_PORT_TBB_DEFAULT: 9150
  INTERNAL_OPEN_PORTS+=" 9150 "

  ## For Gateway testing purposes only.
  ## To test if prerouting redirection rules for socksified interfere with
  ## transparent torification.
  ## https://forums.whonix.org/t/gateway-prerouting-rules-for-socks-ports-may-interfere-with-trans-port-traffic/18941
  ## SOCKS_PORT_HTTP
  #INTERNAL_OPEN_PORTS+=" 80 "
  ## SOCKS_PORT_SSL
  #INTERNAL_OPEN_PORTS+=" 443 "
}

print_variables() {
  if [ ! "${info_enabled}" = "1" ]; then
    return 0
  fi
  local item
  # shellcheck disable=SC2154
  for item in ${variable_list}; do
    check_variable_name "${item}" || continue
    value="${!item}"
    output_cmd "INFO: ${item}=$(output_trim "${value}")"
  done
}

error_handler() {
  echo "${0##*/} ##################################################"
  echo "${0##*/} ERROR: Whonix firewall script failed!"
  echo "${0##*/} ##################################################"
  exit 1
}
trap "error_handler" ERR

usage() {
  echo "usage: ${0##*/} [options]
 -s, --source              only source the script
 -n, --dry-run             dry-run, do not load new script
 -i, --info                print informational messages
 -m, --mode full|timesync-fail-closed
                           override firewall mode for this run
 -h, --help                print this help message"
}

# shellcheck disable=SC2034
get_options(){
  while true; do
    case ${1-} in
      -s | --source)
        source_only=1
        shift 1
        ;;
      -n | --dry-run)
        dry_run=1
        shift 1
        ;;
      -i | --info)
        info_enabled=1
        shift 1
        ;;
      -m | --mode)
        if [ "${2-}" = "full" ] || [ "${2-}" = "timesync-fail-closed" ]; then
          firewall_mode_cli="${2}"
          shift 2
        else
          printf '%s\n' "ERROR: --mode requires 'full' or 'timesync-fail-closed'" >&2
          usage
          exit 1
        fi
        ;;
      "") break ;;
      -h | --help | *)
        usage
        exit 1
        ;;
    esac
  done
}

init() {
  output_cmd "OK: Initialize Whonix firewall procedures..."
  if [ "${dry_run}" = "1" ]; then
    output_cmd "OK: Firewall will not be loaded because dry-run is set"
  fi
  mkdir --parents "${firewall_dir}"
  rm -f "${firewall_script}"
}

source_config_folder() {
  shopt -s nullglob
  local i
  for i in \
    /etc/whonix_firewall.d/*.conf \
    /usr/local/etc/whonix_firewall.d/*.conf; do
    bash_n_exit_code="0"
    bash_n_output="$(bash -n "$i" 2>&1)" || {
      bash_n_exit_code="$?"
      true
    }
    if [ ! "$bash_n_exit_code" = "0" ]; then
      output_cmd "ERROR: Invalid config file: $i
   bash_n_exit_code: $bash_n_exit_code
   bash_n_output:
   $bash_n_output" >&2
      exit 1
    fi
    # shellcheck disable=SC1090
    source "$i"
  done
  shopt -u nullglob
}

write_nft_script() {
  echo "$@" | tee -a "${firewall_script}" >/dev/null
}

nft_script_header() {
  $nftables_cmd "#!/usr/sbin/nft -f"
}

nft_check() {
  ## Check is broken when modifying policy of existing chain.
  if [ "${dry_run}" = "1" ]; then
    return 0
  fi
  nft --check --file "${firewall_script}"
}

nft_load() {
  if [ "${dry_run}" = "1" ]; then
    return 0
  fi
  nft --file "${firewall_script}"
}

status_files() {
  if [ "${dry_run}" = "1" ]; then
    return 0
  fi
  mkdir --parents /run/whonix_firewall
  if [ -e /run/whonix_firewall/first_run_current_boot.status ]; then
    touch /run/whonix_firewall/consecutive_run.status
    return 0
  fi
  touch /run/whonix_firewall/first_run_current_boot.status
}

end() {
  if [ "${dry_run}" = "1" ]; then
    output_cmd "OK: Firewall will not be loaded because dry-run is set"
    exit 0
  fi
  output_cmd "OK: Whonix firewall loaded."
  exit 0
}

date_cmd() {
  date -u +"%Y-%m-%d %T"
}

output_cmd() {
  echo "$(date_cmd) - ${0##*/} - $*"
}

output_trim() {
  echo "${1}" | tr -s "\n" " " | sed -e "s/^ //" -e "s/ $//"
}

firewall_mode_detection() {
  ## firewall_mode: user configuration setting.
  ##   "full" (default) - Full network access.
  ##   "timesync-fail-closed" - Restrict network until sdwdate succeeds.
  ##
  ## firewall_mode_cli: --mode CLI override. Takes precedence over config.
  ##
  ## firewall_mode_detected: internal runtime state determined here.
  ##   All firewall rules check this variable, not firewall_mode.

  ## CLI --mode takes precedence over config file setting.
  if [ -n "${firewall_mode_cli}" ]; then
    firewall_mode="${firewall_mode_cli}"
  fi

  if [ "${firewall_mode}" = "timesync-fail-closed" ]; then
    ## User opted in to timesync-fail-closed mode.
    ## Detect actual runtime state based on sdwdate status files.
    if [ -e /run/sdwdate/first_success ]; then
      firewall_mode_detected=full
      output_cmd "OK: (/run/sdwdate/first_success exists.)"
    elif [ -e /run/sdwdate/success ]; then
      firewall_mode_detected=full
      output_cmd "OK: (/run/sdwdate/success exists.)"
    else
      ## sdwdate has not yet succeeded.
      firewall_mode_detected=timesync-fail-closed
    fi
  elif [ "${firewall_mode}" = "full" ]; then
    firewall_mode_detected=full
  else
    output_cmd "ERROR: firewall_mode must be 'full' or 'timesync-fail-closed', got '${firewall_mode}'."
    error_handler
  fi

  if [ "$firewall_mode_detected" = "timesync-fail-closed" ]; then
    output_cmd "OK: Running in timesync-fail-closed mode."
    output_cmd "OK: (Only local Tor control port connections and torified sdwdate allowed.)"
  else
    output_cmd "OK: Running in full mode."
    output_cmd "OK: (Full torified network access allowed.)"
  fi
}

nft_reject_invalid_outgoing_packages() {
  ## Drop invalid outgoing packages,
  ## unless NO_REJECT_INVALID_OUTGOING_PACKAGES is set to 1.
  if [ ! "$NO_REJECT_INVALID_OUTGOING_PACKAGES" = "1" ]; then
    ## https://lists.torproject.org/pipermail/tor-talk/2014-March/032507.html
    $nftables_cmd add rule inet filter output ct state invalid counter reject
    #$nftables_cmd add rule inet filter output oifname != "lo" ip saddr != 127.0.0.1 ip daddr != 127.0.0.1 tcp flags "&" "(fin|ack)" == "fin|ack" counter reject

    ## DROP INVALID SYN PACKETS
    $nftables_cmd add rule inet filter output tcp flags "&" "(fin|syn|rst|psh|ack|urg)" == "fin|syn|rst|ack" counter reject
    $nftables_cmd add rule inet filter output tcp flags "&" "(fin|syn)" == "fin|syn" counter reject
    $nftables_cmd add rule inet filter output tcp flags "&" "(syn|rst)" == "syn|rst" counter reject

    ## DROP PACKETS WITH INCOMING FRAGMENTS. THIS ATTACK ONCE RESULTED IN KERNEL PANICS
    ## TODO: IPv6?
    $nftables_cmd add rule inet filter output ip frag-off "&" 0x1fff != 0 counter reject

    ## DROP INCOMING MALFORMED XMAS PACKETS
    $nftables_cmd add rule inet filter output tcp flags "&" "(fin|syn|rst|psh|ack|urg)" == "fin|syn|rst|psh|ack|urg" counter reject

    ## DROP INCOMING MALFORMED NULL PACKETS
    $nftables_cmd add rule inet filter output tcp flags "&" "(fin|syn|rst|psh|ack|urg)" == 0x0 counter reject
  fi
}

nft_drop_invalid_incoming_packages() {
  ## DROP INVALID
  $nftables_cmd add rule inet filter input ct state invalid counter drop

  ## DROP INVALID SYN PACKETS
  $nftables_cmd add rule inet filter input tcp flags "&" "(fin|syn|rst|psh|ack|urg)" "==" "fin|syn|rst|ack" counter drop
  $nftables_cmd add rule inet filter input tcp flags "&" "(fin|syn)" == "fin|syn" counter drop
  $nftables_cmd add rule inet filter input tcp flags "&" "(syn|rst)" == "syn|rst" counter drop

  ## DROP PACKETS WITH INCOMING FRAGMENTS. THIS ATTACK ONCE RESULTED IN KERNEL PANICS
  $nftables_cmd add rule inet filter input ip frag-off "&" 0x1fff != 0 counter drop
  ## TODO: Useful for IPv6?

  ## DROP INCOMING MALFORMED XMAS PACKETS
  $nftables_cmd add rule inet filter input tcp flags "&" "(fin|syn|rst|psh|ack|urg)" "==" "fin|syn|rst|psh|ack|urg" counter drop

  ## DROP INCOMING MALFORMED NULL PACKETS
  $nftables_cmd add rule inet filter input tcp flags "&" "(fin|syn|rst|psh|ack|urg)" "==" 0x0 counter drop
}

nft_input_rules_gateway() {
  ## Traffic on the loopback interface is accepted.
  $nftables_cmd add rule inet filter input iifname "lo" counter accept

  ## Established incoming connections are always accepted.
  ## Optionally, allow Related incoming connections when
  ## GATEWAY_ALLOW_INCOMING_RELATED_STATE mode is enabled.
  if [ "$GATEWAY_ALLOW_INCOMING_RELATED_STATE" = "1" ]; then
    $nftables_cmd add rule inet filter input ct state related,established counter accept
  else
    $nftables_cmd add rule inet filter input ct state established counter accept
  fi

  ## Allow fragmentation-needed ICMP packets to avoid MTU problems
  ## when Whonix-Gateway is connected to a link that has smaller
  ## MTU than 1500 assumed by Whonix-Gateway
  if [ "$GATEWAY_ALLOW_INCOMING_ICMP_FRAG_NEEDED" = "1" ]; then
    $nftables_cmd add rule inet filter input icmp type destination-unreachable icmp code frag-needed ct state related counter accept
    ## ICMPv6 Packet Too Big is the IPv6 equivalent of ICMPv4 frag-needed.
    ## Required for IPv6 Path MTU Discovery.
    $nftables_cmd add rule inet filter input icmpv6 type packet-too-big ct state related counter accept
  fi

  # Accept ICMPv6 neighbor discovery.
  $nftables_cmd add rule inet filter input icmpv6 type "{ nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect }" counter accept

  ## Drop all incoming ICMP traffic by default.
  ## All incoming connections are dropped by default anyway, but should a user
  ## allow incoming ports (such as for incoming SSH or FlashProxy), ICMP should
  ## still be dropped to filter for example ICMP time stamp requests.
  if [ ! "$GATEWAY_ALLOW_INCOMING_ICMP" = "1" ]; then
    $nftables_cmd add rule inet filter input ip protocol icmp counter drop
    $nftables_cmd add rule inet filter input ip6 nexthdr icmpv6 counter drop
  fi

  ## Allow all incoming connections on the virtual VPN network interface,
  ## when VPN_FIREWALL mode is enabled.
  ## DISABLED BY DEFAULT.
  if [ "$VPN_FIREWALL" = "1" ]; then
    $nftables_cmd add rule inet filter input iifname "$VPN_INTERFACE" counter accept
  fi

  local ext_if_item

  for ext_if_item in $EXT_IF; do
    ## Allow incoming SSH connections on the external interface.
    ## DISABLED BY DEFAULT. For testing/debugging only.
    if [ "$GATEWAY_ALLOW_INCOMING_SSH" = "1" ]; then
      $nftables_cmd add rule inet filter input iifname "$ext_if_item" tcp dport 22 counter accept
    fi

    ## Allow incoming Flash Proxy connections on the external interface.
    ## This has NOTHING to do with Adobe Flash.
    ## DISABLED BY DEFAULT.
    if [ "$GATEWAY_ALLOW_INCOMING_FLASHPROXY" = "1" ]; then
      $nftables_cmd add rule inet filter input iifname "$ext_if_item" tcp dport "$FLASHPROXY_PORT" counter accept
    fi

    local local_port_to_open
    if [ "${info_enabled}" = "1" ]; then
      output_cmd "INFO: Opening External TCP port(s): $(output_trim "${EXTERNAL_OPEN_PORTS:-"NONE"}")"
    fi
    for local_port_to_open in $EXTERNAL_OPEN_PORTS; do
      $nftables_cmd add rule inet filter input iifname "$ext_if_item" tcp dport "$local_port_to_open" counter accept
    done

    local local_udp_port_to_open
    if [ "${info_enabled}" = "1" ]; then
      output_cmd "INFO: Opening External UDP port(s): $(output_trim "${EXTERNAL_UDP_OPEN_PORTS:-"NONE"}")"
    fi
    for local_udp_port_to_open in $EXTERNAL_UDP_OPEN_PORTS; do
      $nftables_cmd add rule inet filter input udp dport "$local_udp_port_to_open" counter accept
    done

    if [ "$EXTERNAL_OPEN_ALL" = "true" ]; then
      if [ "${info_enabled}" = "1" ]; then
        output_cmd "INFO: EXTERNAL_OPEN_ALL='true', opening all External ports"
      fi
      $nftables_cmd add rule inet filter input counter accept
    fi
  done

  if [ "$firewall_mode_detected" = "timesync-fail-closed" ]; then
    true "timesync-fail-closed mode, skipping rest of function ${FUNCNAME[0]}"
    return 0
  fi

  for ext_if_item in $EXT_IF; do
    ## Allow incoming DIRPORT connections for an optional Tor relay.
    ## DISABLED BY DEFAULT.
    if [ "$GATEWAY_ALLOW_INCOMING_DIR_PORT" = "1" ]; then
      $nftables_cmd add rule inet filter input iifname "$ext_if_item" tcp dport "$DIR_PORT" counter accept
    fi

    ## Allow incoming ORPORT connections for an optional Tor relay.
    ## DISABLED BY DEFAULT.
    if [ "$GATEWAY_ALLOW_INCOMING_OR_PORT" = "1" ]; then
      $nftables_cmd add rule inet filter input iifname "$ext_if_item" tcp dport "$OR_PORT" counter accept
    fi

    ## Custom Open Ports on external interface
    ## - untested, should work
    ## - Replace 22,9050,9051,9150,9151 with any ports you like to be open, example: 9050,9051
    ##   or just 9050
    #$nftables_cmd add rule inet filter input iifname "$ext_if_item" th dport { 22, 9050, 9051, 9150, 9151 } counter accept

    ## OPTIONAL Allow incoming OpenVPN connections on the external interface.
    #$nftables_cmd add rule inet filter input iifname "$ext_if_item" tcp dport 1194 counter accept
  done

  local int_tif_item
  local int_if_item

  for int_tif_item in $INT_TIF; do
    if [ "$WORKSTATION_TRANSPARENT_DNS" = "1" ]; then
      ## Allow DNS traffic to DnsPort.
      $nftables_cmd add rule inet filter input iifname "$int_tif_item" udp dport "$DNS_PORT_WORKSTATION" counter accept
    fi
  done

  for int_if_item in $INT_IF; do
    if [ "$WORKSTATION_TRANSPARENT_TCP" = "1" ]; then
      ## Allow TCP traffic TransPort.
      $nftables_cmd add rule inet filter input iifname "$int_if_item" tcp dport "$TRANS_PORT_WORKSTATION" counter accept
    fi
  done

  for int_tif_item in $INT_TIF; do
    ## Allow TCP traffic to Control Port Filter Proxy.
    if [ "$CONTROL_PORT_FILTER_PROXY_ENABLE" = "1" ]; then
      $nftables_cmd add rule inet filter input iifname "$int_tif_item" tcp dport "$CONTROL_PORT_FILTER_PROXY_PORT" counter accept
    fi

    ## Allow socksified applications.
    if [ "$WORKSTATION_ALLOW_SOCKSIFIED" = "1" ]; then
      if [ "${info_enabled}" = "1" ]; then
        output_cmd "INFO: WORKSTATION_ALLOW_SOCKSIFIED='1', Socks Ports will be reachable from the Workstation"
        output_cmd "INFO: opening Internal TCP port(s): $(output_trim "${INTERNAL_OPEN_PORTS}")"
      fi
      local socks_port_item
      for socks_port_item in $INTERNAL_OPEN_PORTS; do
        true "socks_port_item: $socks_port_item"
        $nftables_cmd add rule inet filter input iifname "$int_tif_item" tcp dport "$socks_port_item" counter accept
      done

      ## Accept ports 9152-9229 prepared for user custom applications.
      ## See /usr/share/tor/tor-service-defaults-torrc for more comments.
      if [ "${info_enabled}" = "1" ]; then
        output_cmd "INFO: opening TCP port(s) 9152:9229 for user custom applications"
      fi
      $nftables_cmd add rule inet filter input iifname "$int_tif_item" tcp dport 9152-9229 counter accept
    fi
  done

  for int_if_item in $INT_IF; do
    ## Redirect Control Port Filter Proxy to Control Port Filter Proxy port.
    if [ "$CONTROL_PORT_FILTER_PROXY_ENABLE" = "1" ]; then
      local workstation_dest_socksified_item
      for workstation_dest_socksified_item in $WORKSTATION_DEST_SOCKSIFIED; do
        $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" ip daddr "$workstation_dest_socksified_item" tcp dport "$CONTROL_PORT_FILTER_PROXY_PORT" counter redirect to :"$CONTROL_PORT_FILTER_PROXY_PORT"
      done
      local workstation_dest_socksified_ipv6_item
      for workstation_dest_socksified_ipv6_item in $WORKSTATION_DEST_SOCKSIFIED_IPV6; do
        $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" ip6 daddr "$workstation_dest_socksified_ipv6_item" tcp dport "$CONTROL_PORT_FILTER_PROXY_PORT" counter redirect to :"$CONTROL_PORT_FILTER_PROXY_PORT"
        ## TODO: IPv6 test
      done
    fi

    if [ "$WORKSTATION_ALLOW_SOCKSIFIED" = "1" ]; then
      local socks_port_item
      for socks_port_item in $INTERNAL_OPEN_PORTS; do
        local workstation_dest_socksified_item
        for workstation_dest_socksified_item in $WORKSTATION_DEST_SOCKSIFIED; do
          true "socks_port_item: $socks_port_item"
          ## Redirect Browser/IRC/TorBirdy, etc. to SocksPort.
          $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" ip daddr "$workstation_dest_socksified_item" tcp dport "$socks_port_item" counter redirect to :"$socks_port_item"
        done
        local workstation_dest_socksified_ipv6_item
        for workstation_dest_socksified_ipv6_item in $WORKSTATION_DEST_SOCKSIFIED_IPV6; do
          true "socks_port_item: $socks_port_item"
          $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" ip6 daddr "$workstation_dest_socksified_ipv6_item" tcp dport "$socks_port_item" counter redirect to :"$socks_port_item"
          ## TODO: IPv6 test
        done
      done
      ## Redirect ports 9152-9229 prepared for user custom applications.
      local workstation_dest_socksified_item
      for workstation_dest_socksified_item in $WORKSTATION_DEST_SOCKSIFIED; do
        $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" ip daddr "$workstation_dest_socksified_item" tcp dport 9152-9229 counter redirect
      done
      local workstation_dest_socksified_ipv6_item
      for workstation_dest_socksified_ipv6_item in $WORKSTATION_DEST_SOCKSIFIED_IPV6; do
        $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" ip6 daddr "$workstation_dest_socksified_ipv6_item" tcp dport 9152-9229 counter redirect
        ## TODO: IPv6 test
      done
    else
      output_cmd "INFO: WORKSTATION_ALLOW_SOCKSIFIED='${WORKSTATION_ALLOW_SOCKSIFIED}', Socks Ports will not be opened"
    fi

    if [ "$WORKSTATION_TRANSPARENT_DNS" = "1" ]; then
      ## Redirect remaining DNS traffic to DNS_PORT_WORKSTATION.
      ## Only user installed applications not configured to use a SocksPort are affected.
      $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" udp dport 53 counter redirect to :"$DNS_PORT_WORKSTATION"
    fi

    if [ "$WORKSTATION_TRANSPARENT_TCP" = "1" ]; then
      ## Catch all remaining TCP and redirect to TransPort.
      ## Only user installed applications not configured to use a SocksPort are affected.
      $nftables_cmd add rule inet nat prerouting iifname "$int_if_item" tcp flags "&" "(fin|syn|rst|ack)" == syn counter redirect to :"$TRANS_PORT_WORKSTATION"

      ## Optionally restrict TransPort.
      ## Replace above rule with a more restrictive one, e.g.:
      #$nftables_cmd add rule inet nat prerouting iifname "$int_if_item" tcp dport { 80,443} tcp flags "&" "(fin|syn|rst|ack)" == syn counter redirect to :"$TRANS_PORT_WORKSTATION"
    fi
  done
}

nft_input_defaults_gateway() {
  ## Log.
  #$nftables_cmd add rule inet filter input counter log prefix \"Whonix_blocked_input4:\"

  ## Reject anything not explicitly allowed above.
  ## Drop is better than reject here, because we do not want to reveal it's a Whonix-Gateway.
  ## (In case someone running Whonix-Gateway on bare metal.)
  $nftables_cmd add rule inet filter input counter drop
}

nft_forward_gateway() {
  ## Log.
  #$nftables_cmd add rule inet filter forward counter log prefix \"Whonix_blocked_forward4:\"

  ## Reject everything.
  $nftables_cmd add rule inet filter forward counter reject
}

qubes_gateway() {
  if [ "$firewall_mode_detected" = "timesync-fail-closed" ]; then
    true "timesync-fail-closed mode, skipping rest of function ${FUNCNAME[0]}"
    return 0
  fi

  if [ -e /run/qubes/this-is-netvm ] || [ -e /run/qubes/this-is-proxyvm ]; then
    ## The same for squid from qubes-updates-cache, which runs as user vm-updates.
    if getent passwd vm-updates > /dev/null; then
      ## IPv4: 'ip protocol' matches the IPv4 header protocol field.
      ## IPv6: 'ip6 nexthdr' matches the IPv6 next header field.
      ## ('ip6 protocol' is not valid nftables syntax.)
      $nftables_cmd add rule inet nat output ip protocol udp skuid vm-updates ct state new counter dnat to "127.0.0.1:${DNS_PORT_GATEWAY}"
      $nftables_cmd add rule inet nat output ip6 nexthdr udp skuid vm-updates ct state new counter dnat to "[::1]:${DNS_PORT_GATEWAY}"
      $nftables_cmd add rule inet nat output ip protocol tcp skuid vm-updates ct state new counter dnat to "127.0.0.1:${TRANS_PORT_GATEWAY}"
      $nftables_cmd add rule inet nat output ip6 nexthdr tcp skuid vm-updates ct state new counter dnat to "[::1]:${TRANS_PORT_GATEWAY}"
      ## TODO: IPv6: test
    fi
  fi
}
