#!/bin/sh
PATH=/usr/sbin:/usr/bin:/sbin:/bin
# SOCKS5 method negotiation checks the local Mihomo listener only.
# No CONNECT request, external destination, DNS lookup or credentials are sent.
check_mihomo() {
    reply=$(printf '\005\001\000' | timeout 3 nc 192.168.32.168 7890 2>/dev/null | hexdump -v -e '1/1 "%02x"')
    [ "$reply" = 0500 ]
}
# Read-only manual probe; do not touch routes, leases or service state.
if [ "${1:-}" = --check ]; then
    check_mihomo
    exit $?
fi
state=direct
good=0
bad=0
withdraw() {
    printf '%s\n' 'flush set inet rpi_gateway active' 'flush set inet rpi_gateway local_dns' | nft -f - 2>/dev/null
    if [ "$state" = proxy ]; then
        logger -t rpi-gateway 'Local Mihomo SOCKS check failed; LAN now use direct routing and normal DNS'
        for proto in udp tcp; do
            conntrack -D -f ipv4 -p "$proto" --dport 53 --reply-src 192.168.32.168 >/dev/null 2>&1
            conntrack -D -f ipv6 -p "$proto" --dport 53 --reply-src fda2:a01e:2cb2::168 >/dev/null 2>&1
        done
    fi
    state=direct
    echo direct > /var/run/rpi-gateway.state
}
trap 'withdraw; exit 0' TERM INT
withdraw
while :; do
    if ! nft list table inet rpi_gateway >/dev/null 2>&1; then
        nft -f /etc/rpi-gateway.nft || { sleep 5; continue; }
        state=direct; good=0
    fi
    if check_mihomo; then
        good=$((good+1)); bad=0
        if [ "$good" -ge 2 ]; then
            if printf '%s\n' 'flush set inet rpi_gateway active' 'flush set inet rpi_gateway local_dns' \
                'add element inet rpi_gateway active { "br-lan" timeout 120s }' \
                'add element inet rpi_gateway local_dns { 192.168.32.1 timeout 120s }' | nft -f -; then
                [ "$state" = proxy ] || logger -t rpi-gateway 'Local Mihomo SOCKS listener healthy; LAN IPv4/IPv6 proxy routing and DNS forwarding enabled'
                state=proxy
                echo proxy > /var/run/rpi-gateway.state
            fi
        fi
    else
        good=0; bad=$((bad+1))
        [ "$bad" -lt 2 ] || withdraw
    fi
    sleep 30
done
