#!/usr/bin/env bash
#
# onx-firewall-conn-limits-apply — DDoS/SYN-flood/per-service rate limit kuralları.
#
# Input (stdin JSON):
#   {
#     "enabled": "1"|"0",
#     "max_conn_per_ip": 50,
#     "syn_flood_threshold": 200,
#     "syn_flood_burst": 50,
#     "ssh_rate_limit": 4,
#     "ssh_rate_window_sec": 30,
#     "http_rate_limit_per_sec": 100,
#     "icmp_rate_limit": 10,
#     "block_invalid_packets": "1"|"0",
#     "block_fragmented_packets": "1"|"0"
#   }
#
# Output: {"ok":true,"chain":"ratelimit","rules_count":N}

set -euo pipefail

readonly TABLE="inet onox"
readonly CHAIN="ratelimit"
readonly LOG_TAG="onox-ratelimit"

input="$(cat)"

# Helper: JSON'dan integer çek, default ver
get_int() { echo "$input" | jq -r ".$1 // $2" | tr -d '\n'; }
get_str() { echo "$input" | jq -r ".$1 // \"$2\"" | tr -d '\n'; }

enabled="$(get_str enabled 0)"
max_conn="$(get_int max_conn_per_ip 50)"
syn_thresh="$(get_int syn_flood_threshold 200)"
syn_burst="$(get_int syn_flood_burst 50)"
ssh_limit="$(get_int ssh_rate_limit 4)"
ssh_window="$(get_int ssh_rate_window_sec 30)"
http_limit="$(get_int http_rate_limit_per_sec 100)"
icmp_limit="$(get_int icmp_rate_limit 10)"
block_invalid="$(get_str block_invalid_packets 1)"
block_frag="$(get_str block_fragmented_packets 1)"

# ── Validate ranges ────────────────────────────────────────────────────────
for v in max_conn syn_thresh syn_burst ssh_limit ssh_window http_limit icmp_limit; do
    val="${!v}"
    if ! [[ "$val" =~ ^[0-9]+$ ]] || (( val < 1 || val > 100000 )); then
        jq -nc --arg k "$v" --arg val "$val" '{ok:false,error:"out of range",key:$k,value:$val}' >&2
        exit 1
    fi
done

# ── Ensure base infra ─────────────────────────────────────────────────────
nft list table inet onox &>/dev/null || nft add table inet onox

# Chain'i sıfırla — idempotent re-apply
if nft list chain inet onox "$CHAIN" &>/dev/null; then
    nft flush chain inet onox "$CHAIN"
else
    nft "add chain inet onox $CHAIN { type filter hook input priority -5; }"
fi

rules_count=0

if [[ "$enabled" != "1" ]]; then
    logger -t "$LOG_TAG" "Rate limits disabled — chain emptied"
    jq -nc --arg chain "$CHAIN" '{ok:true,chain:$chain,rules_count:0,enabled:false}'
    exit 0
fi

# ── Build ruleset in single -f file (atomic) ──────────────────────────────
cmdfile="$(mktemp)"
trap 'rm -f "$cmdfile"' EXIT

{
    # 1. Invalid state drop (kernel conntrack)
    if [[ "$block_invalid" == "1" ]]; then
        echo "add rule inet onox $CHAIN ct state invalid counter drop comment \"onox-invalid\""
        rules_count=$((rules_count+1))
    fi

    # 2. Fragmented packets — IP frag offset != 0
    if [[ "$block_frag" == "1" ]]; then
        echo "add rule inet onox $CHAIN ip frag-off & 0x1fff != 0 counter drop comment \"onox-frag\""
        rules_count=$((rules_count+1))
    fi

    # 3. SYN flood — accept'liyiz rate'i geçenleri drop
    echo "add rule inet onox $CHAIN tcp flags syn / fin,syn,rst,ack limit rate $syn_thresh/second burst $syn_burst packets counter accept comment \"onox-syn-ok\""
    echo "add rule inet onox $CHAIN tcp flags syn / fin,syn,rst,ack counter drop comment \"onox-syn-flood\""

    # 4. Per-IP concurrent conn limit (conntrack)
    echo "add rule inet onox $CHAIN meta nfproto ipv4 ct count over $max_conn counter drop comment \"onox-conn-limit-v4\""
    echo "add rule inet onox $CHAIN meta nfproto ipv6 ct count over $max_conn counter drop comment \"onox-conn-limit-v6\""

    # 5. SSH rate (per-source IP via dynamic meter)
    echo "add rule inet onox $CHAIN tcp dport 22 ct state new meter ssh-meter { ip saddr limit rate $ssh_limit/${ssh_window}second } counter accept comment \"onox-ssh-rate\""
    echo "add rule inet onox $CHAIN tcp dport 22 ct state new counter drop comment \"onox-ssh-excess\""

    # 6. HTTP/HTTPS rate (per-source IP via dynamic meter)
    echo "add rule inet onox $CHAIN tcp dport { 80, 443 } meter http-meter { ip saddr limit rate $http_limit/second burst $((http_limit*2)) packets } counter accept comment \"onox-http-rate\""

    # 7. ICMP rate limit (ping flood)
    echo "add rule inet onox $CHAIN icmp type echo-request limit rate $icmp_limit/second counter accept comment \"onox-icmp-rate\""
    echo "add rule inet onox $CHAIN icmp type echo-request counter drop comment \"onox-icmp-excess\""
    echo "add rule inet onox $CHAIN icmpv6 type echo-request limit rate $icmp_limit/second counter accept comment \"onox-icmp6-rate\""
    echo "add rule inet onox $CHAIN icmpv6 type echo-request counter drop comment \"onox-icmp6-excess\""

    rules_count=$((rules_count+10))
} > "$cmdfile"

if ! nft -f "$cmdfile" 2>/tmp/onox-rl-err-$$; then
    err="$(cat /tmp/onox-rl-err-$$ 2>/dev/null || echo unknown)"
    rm -f /tmp/onox-rl-err-$$
    # Rollback: chain'i temizle
    nft flush chain inet onox "$CHAIN" 2>/dev/null || true
    jq -nc --arg err "$err" '{ok:false,error:"nft apply failed",nft_err:$err}' >&2
    exit 4
fi
rm -f /tmp/onox-rl-err-$$

logger -t "$LOG_TAG" "Applied $rules_count rate-limit rules"

jq -nc --arg chain "$CHAIN" --argjson rules_count "$rules_count" \
    '{ok:true,chain:$chain,rules_count:$rules_count,enabled:true}'
