#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=network-common
# Backward-compatible optional environment path and read-only hooks-only mode.
SKIP_DOCKER=0
for argument in "$@"; do
  case "$argument" in
    --skip-docker-routes) SKIP_DOCKER=1 ;;
    /*) export CFPP_NETWORK_ENV_FILE="$argument" ;;
    *) echo "usage: network-check [ENV_PATH] [--skip-docker-routes]" >&2; exit 2 ;;
  esac
done
source "$SCRIPT_DIR/network-common"

[[ ${CFPP_ARMED:-0} == 1 ]] || fail "network changes are not armed"
preflight_policy_table
for pair in "mangle:$CFPP_PRE_CHAIN" "mangle:$CFPP_OUT_CHAIN" "nat:$CFPP_NAT_CHAIN" "filter:$CFPP_INPUT_CHAIN"; do
  preflight_owned_chain "${pair%%:*}" "${pair#*:}"
done

iptables -t mangle -C PREROUTING -m comment --comment "$CFPP_COMMENT_PREFIX:hook-pre" -j "$CFPP_PRE_CHAIN" 2>/dev/null || fail "PREROUTING hook is missing"
iptables -t mangle -C "$CFPP_PRE_CHAIN" -p tcp -m mark --mark "$CFPP_MARK_MASK" -m comment --comment "$CFPP_COMMENT_PREFIX:save" -j CONNMARK --save-mark 2>/dev/null || fail "CONNMARK save rule is missing"
iptables -t mangle -C OUTPUT -m comment --comment "$CFPP_COMMENT_PREFIX:hook-out" -j "$CFPP_OUT_CHAIN" 2>/dev/null || fail "OUTPUT hook is missing"
iptables -t mangle -C "$CFPP_OUT_CHAIN" -p tcp -m connmark --mark "$CFPP_MARK_MASK" -m comment --comment "$CFPP_COMMENT_PREFIX:restore" -j CONNMARK --restore-mark 2>/dev/null || fail "CONNMARK restore rule is missing"
iptables -t nat -C PREROUTING -m comment --comment "$CFPP_COMMENT_PREFIX:hook-nat" -j "$CFPP_NAT_CHAIN" 2>/dev/null || fail "inbound redirect hook is missing"
[[ $(iptables -t nat -L PREROUTING -n --line-numbers | awk '$1 == "1" {print $2; exit}') == "$CFPP_NAT_CHAIN" ]] || fail "Cloudflare redirect hook must precede Docker DNAT"
for interface in "${CFPP_INGRESS_DEVICES[@]}"; do
  iptables -t nat -C "$CFPP_NAT_CHAIN" -i "$interface" -p tcp -m multiport --dports "$CFPP_CAPTURE_PORTS" -m set --match-set "$CFPP_TRUSTED_V4_SET" src -m set --match-set "$CFPP_DESTINATION_V4_SET" dst -m comment --comment "$CFPP_COMMENT_PREFIX:redirect" -j DNAT --to-destination "$CFPP_INGRESS_ADDRESS:$CFPP_INGRESS_PORT" 2>/dev/null || fail "trusted ingress DNAT missing for $interface"
done
iptables -t filter -C INPUT -m comment --comment "$CFPP_COMMENT_PREFIX:hook-input" -j "$CFPP_INPUT_CHAIN" 2>/dev/null || fail "trusted INPUT hook is missing"
[[ $(iptables -t filter -L INPUT -n --line-numbers | awk '$1 == "1" {print $2; exit}') == "$CFPP_INPUT_CHAIN" ]] || fail "trusted INPUT hook is not first"
for interface in "${CFPP_INGRESS_DEVICES[@]}"; do
  iptables -t filter -C "$CFPP_INPUT_CHAIN" -i "$interface" -d "$CFPP_INGRESS_CIDR" -p tcp --dport "$CFPP_INGRESS_PORT" -m set --match-set "$CFPP_TRUSTED_V4_SET" src -m comment --comment "$CFPP_COMMENT_PREFIX:trusted-input" -j ACCEPT 2>/dev/null || fail "trusted INPUT missing for $interface"
done
ipset list -n | grep -Fxq "$CFPP_TRUSTED_V4_SET" || fail "trusted IPv4 ipset is missing"
[[ $(ipset list "$CFPP_TRUSTED_V4_SET" | awk -F: '/^Number of entries:/ {gsub(/ /, "", $2); print $2}') =~ ^[1-9][0-9]*$ ]] || fail "trusted IPv4 ipset is empty"
ipset list -n | grep -Fxq "$CFPP_DESTINATION_V4_SET" || fail "destination IPv4 ipset is missing"
[[ $(ipset list "$CFPP_DESTINATION_V4_SET" | awk -F: '/^Number of entries:/ {gsub(/ /, "", $2); print $2}') =~ ^[1-9][0-9]*$ ]] || fail "destination IPv4 ipset is empty"
ipset test "$CFPP_DESTINATION_V4_SET" "$CFPP_INGRESS_ADDRESS" >/dev/null 2>&1 || fail "destination IPv4 ipset does not contain ingress address"
iptables -t mangle -C "$CFPP_PRE_CHAIN" -p tcp -m connmark --mark "$CFPP_MARK_MASK" -m comment --comment "$CFPP_COMMENT_PREFIX:docker-reply-restore" -j CONNMARK --restore-mark 2>/dev/null || fail "Docker reply mark restore is missing"
iptables -t mangle -C "$CFPP_OUT_CHAIN" -p tcp -m mark --mark "$CFPP_MARK_MASK" -m comment --comment "$CFPP_COMMENT_PREFIX:docker-output-save" -j CONNMARK --save-mark 2>/dev/null || fail "Docker output mark save is missing"
if (( SKIP_DOCKER == 0 )); then check_docker_routes; fi
rule_is_installed || fail "policy rule is missing"
route_is_installed || fail "local route is missing"
[[ $(<"$CFPP_ROUTE_LOCALNET_PATH") == 1 ]] || fail "route_localnet is not enabled on $CFPP_ROUTE_LOCALNET_INTERFACE"

echo "cf-pp-gateway transparent network rules verified"
