#!/bin/sh /etc/rc.common
# FIPS outbound LAN gateway — procd init script for OpenWrt
#
# Not enabled by default. Enable with:
#   service fips-gateway enable
#   service fips-gateway start
#
# Requires: fips daemon running, gateway section in /etc/fips/fips.yaml,
# IPv6 forwarding enabled.

USE_PROCD=1
START=96
STOP=09

PROG=/usr/bin/fips-gateway
CONFIG=/etc/fips/fips.yaml

# Port the gateway DNS listens on (must match dns.listen in fips.yaml).
GW_DNS_PORT=5353
# Port the FIPS daemon DNS listens on.
DAEMON_DNS_PORT=5354

# Global-scope IPv6 prefix assigned to br-lan so Android/Chrome clients
# believe they have full IPv6 and actually send AAAA queries.
# Uses RFC 5180 (benchmarking) range — not routed on the public internet.
GLOBAL_PREFIX="2001:2:f1b5::1/64"

start_service() {
	# Apply gateway sysctls (proxy_ndp, IPv6 forwarding).
	sysctl -p /etc/sysctl.d/fips-gateway.conf 2>/dev/null || true

	# Load conntrack module for /proc/net/nf_conntrack.
	modprobe nf_conntrack 2>/dev/null || true

	# Redirect dnsmasq .fips forwarding from daemon (5354) to gateway (5353)
	# so LAN clients get virtual IPs instead of raw mesh addresses.
	# Done early and synchronously so dnsmasq is ready before the gateway
	# starts accepting DNS queries.
	dnsmasq_swap_fips_upstream "$GW_DNS_PORT"
	sleep 1

	# Add a global-scope IPv6 prefix to br-lan so Android/Chrome clients
	# send AAAA queries (they suppress AAAA when only ULA addresses exist).
	# Also set ra_default=2 so odhcpd advertises a default route even
	# without upstream IPv6 — needed for clients to accept the prefix.
	gateway_add_global_prefix

	# Advertise the virtual IP pool via Router Advertisement so LAN clients
	# learn a route to the pool automatically.
	gateway_add_ra_route

	procd_open_instance
	procd_set_param command "$PROG" --config "$CONFIG"
	procd_set_param respawn 3600 5 5
	procd_set_param stdout 1
	procd_set_param stderr 1
	procd_close_instance
}

stop_service() {
	# Restore dnsmasq .fips forwarding back to the daemon.
	dnsmasq_swap_fips_upstream "$DAEMON_DNS_PORT"

	# Remove the RA route for the virtual IP pool.
	gateway_remove_ra_route

	# Remove the global prefix and ra_default override.
	gateway_remove_global_prefix
}

reload_service() {
	restart
}

# Extract the gateway pool CIDR from fips.yaml.
# Looks for "pool:" indented under the top-level "gateway:" block.
gateway_pool_cidr() {
	awk '/^gateway:/{found=1; next} found && /^[^ ]/{found=0} found && /pool:/{gsub(/.*pool:[[:space:]]*/, ""); gsub(/["'"'"']/, ""); print; exit}' "$CONFIG"
}

# Add an RA-advertised route for the virtual IP pool so LAN clients
# automatically learn how to reach virtual IPs.
gateway_add_ra_route() {
	local pool
	pool="$(gateway_pool_cidr)"
	[ -z "$pool" ] && return 0

	# Add a kernel route on br-lan (needed for RA to advertise it).
	ip -6 route replace "$pool" dev br-lan proto static 2>/dev/null || true

	# Add a UCI route6 entry for odhcpd to include in Router Advertisements.
	# Remove any stale entry first.
	gateway_remove_ra_route_uci
	uci add dhcp route6 >/dev/null
	uci set dhcp.@route6[-1].interface='lan'
	uci set dhcp.@route6[-1].target="$pool"
	uci commit dhcp
	/etc/init.d/odhcpd restart 2>/dev/null || true
}

# Remove the RA route.
gateway_remove_ra_route() {
	local pool
	pool="$(gateway_pool_cidr)"
	[ -z "$pool" ] && return 0

	ip -6 route del "$pool" dev br-lan proto static 2>/dev/null || true
	gateway_remove_ra_route_uci
	/etc/init.d/odhcpd restart 2>/dev/null || true
}

# Remove any existing UCI route6 entries for the pool.
gateway_remove_ra_route_uci() {
	local i=0
	while uci -q get "dhcp.@route6[$i]" >/dev/null 2>&1; do
		if [ "$(uci -q get "dhcp.@route6[$i].target")" = "$(gateway_pool_cidr)" ]; then
			uci delete "dhcp.@route6[$i]"
			uci commit dhcp
			return 0
		fi
		i=$((i + 1))
	done
}

# Add a global-scope IPv6 prefix to br-lan and enable RA default route.
gateway_add_global_prefix() {
	# Add the global prefix via UCI (idempotent — remove first).
	local current
	current="$(uci -q get network.lan.ip6addr 2>/dev/null || echo "")"
	if [ "$current" != "$GLOBAL_PREFIX" ]; then
		uci set network.lan.ip6addr="$GLOBAL_PREFIX"
		uci commit network
	fi

	# Force odhcpd to advertise a default route even without upstream IPv6.
	local ra_default
	ra_default="$(uci -q get dhcp.lan.ra_default 2>/dev/null || echo "")"
	if [ "$ra_default" != "2" ]; then
		uci set dhcp.lan.ra_default='2'
		uci commit dhcp
	fi

	# Apply network change immediately (odhcpd restarted by gateway_add_ra_route).
	/etc/init.d/network reload 2>/dev/null || true
}

# Remove the global prefix and ra_default override.
gateway_remove_global_prefix() {
	local current
	current="$(uci -q get network.lan.ip6addr 2>/dev/null || echo "")"
	if [ "$current" = "$GLOBAL_PREFIX" ]; then
		uci delete network.lan.ip6addr
		uci commit network
		/etc/init.d/network reload 2>/dev/null || true
	fi

	uci -q delete dhcp.lan.ra_default 2>/dev/null || true
	uci commit dhcp
	/etc/init.d/odhcpd restart 2>/dev/null || true
}

# Swap the dnsmasq .fips forwarding port via UCI and restart dnsmasq.
# $1 = target port number
dnsmasq_swap_fips_upstream() {
	local port="$1"

	# Remove both possible entries, then add the correct one.
	uci -q del_list dhcp.@dnsmasq[0].server="/fips/127.0.0.1#${DAEMON_DNS_PORT}" 2>/dev/null
	uci -q del_list dhcp.@dnsmasq[0].server="/fips/127.0.0.1#${GW_DNS_PORT}" 2>/dev/null
	# Also handle IPv6 loopback variants.
	uci -q del_list dhcp.@dnsmasq[0].server="/fips/::1#${DAEMON_DNS_PORT}" 2>/dev/null
	uci -q del_list dhcp.@dnsmasq[0].server="/fips/::1#${GW_DNS_PORT}" 2>/dev/null

	uci add_list dhcp.@dnsmasq[0].server="/fips/::1#${port}"
	uci commit dhcp

	# Update the drop-in config file as well (belt-and-suspenders).
	if [ -f /etc/dnsmasq.d/fips.conf ]; then
		sed -i "s|^server=/fips/.*|server=/fips/::1#${port}|" /etc/dnsmasq.d/fips.conf
	fi

	# Restart dnsmasq to pick up the change.
	/etc/init.d/dnsmasq restart 2>/dev/null || true
}
