diff --git a/apps/rego-tunnel/shared/host-routing.sh b/apps/rego-tunnel/shared/host-routing.sh index 58dba73..7450eda 100644 --- a/apps/rego-tunnel/shared/host-routing.sh +++ b/apps/rego-tunnel/shared/host-routing.sh @@ -1,203 +1,107 @@ #!/usr/bin/env bash +# +# Host routing script for rego-tunnel +# Routes TARGET_IP through the VPN container +# set -euo pipefail ACTION="${1:-start}" -APP_ENV="/etc/runtipi/app-data/runtipi/rego-tunnel/app.env" -if [[ -f "$APP_ENV" ]]; then - # shellcheck disable=SC1090 - source "$APP_ENV" -fi - -CONTAINER_NAME="${CONTAINER_NAME:-rego-tunnel_runtipi-rego-tunnel-1}" -# Prefer the user-config network (stable host bridge name) if it exists. -NET_NAME="${NET_NAME:-}" -NET_NAME_FALLBACK="${NET_NAME_FALLBACK:-rego-tunnel_runtipi_network}" -NET_NAME_PREFERRED="${NET_NAME_PREFERRED:-rego-tunnel_runtipi_vpn_static}" +# Fixed configuration (we assigned these) +CONTAINER_IP="172.31.0.10" +BRIDGE_NAME="br-rego-vpn" TARGET_IP="${TARGET_IP:-10.35.33.230}" -HOST_BRIDGE_ALIAS="${HOST_BRIDGE_ALIAS:-br-rego-tunnel}" -VM_SUBNET="${VM_SUBNET:-100.100.0.0}" - -TARGET_CIDR="$TARGET_IP/32" -VM_SUBNET_CIDR="${VM_SUBNET_CIDR:-${VM_SUBNET}/24}" +LOG_FILE="/var/log/rego-routing.log" log() { - echo "[rego-routing] $*" >&2 + local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [rego-routing] $*" + echo "$msg" | tee -a "$LOG_FILE" >&2 } -remove_stale_nft_redirects() { - # Some previous setups may have installed a transparent proxy (REDSOCKS) - # redirecting TCP destined for TARGET_IP to a local port (e.g. 12345). - # That breaks the "single target IP via container" gateway model. - # - # We only remove rules that match our TARGET_IP in the ip nat/REDSOCKS chain. - if ! command -v nft >/dev/null 2>&1; then - return 0 - fi - - local handles - handles="$(nft -a list chain ip nat REDSOCKS 2>/dev/null | awk -v ip="$TARGET_IP" '/ip daddr/ && $0 ~ ip && /redirect to :/ {for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)}' || true)" - if [[ -z "$handles" ]]; then - return 0 - fi - - while read -r h; do - [[ -n "$h" ]] || continue - nft delete rule ip nat REDSOCKS handle "$h" 2>/dev/null || true - done <<< "$handles" +get_lan_interface() { + ip route show default | awk '/default/ {for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1 } -remove_stale_host_routes() { - # The VM's internal subnet (default 100.100.0.0/24) should live only inside - # the rego-tunnel container/VM. If the host has a route for it (left over from - # older scripts), it can cause confusing routing. - ip -4 route del "$VM_SUBNET_CIDR" 2>/dev/null || true +remove_routes() { + log "Removing stale routes for $TARGET_IP..." + + # Remove any existing route to TARGET_IP + ip route del "$TARGET_IP" 2>/dev/null || true + ip route del "$TARGET_IP/32" 2>/dev/null || true + + log "Stale routes removed" } -get_lan_if() { - # Avoid early-exit pipelines (pipefail + SIGPIPE) by not exiting awk early. - ip route show default 0.0.0.0/0 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1)}}' -} +apply_routes() { + local lan_if + lan_if="$(get_lan_interface)" -choose_net_name() { - if [[ -n "${NET_NAME:-}" ]]; then - echo "$NET_NAME" - return 0 - fi + log "Applying host routing rules..." + log " Container IP: $CONTAINER_IP" + log " Bridge: $BRIDGE_NAME" + log " Target IP: $TARGET_IP" + log " LAN interface: ${lan_if:-unknown}" - if docker network inspect "$NET_NAME_PREFERRED" >/dev/null 2>&1; then - echo "$NET_NAME_PREFERRED" - return 0 - fi + # Enable IP forwarding + echo 1 > /proc/sys/net/ipv4/ip_forward + log "IP forwarding enabled" - echo "$NET_NAME_FALLBACK" -} + # Add route to TARGET_IP via container + ip route replace "$TARGET_IP/32" via "$CONTAINER_IP" dev "$BRIDGE_NAME" + log "Route added: $TARGET_IP via $CONTAINER_IP dev $BRIDGE_NAME" -get_bridge_and_container_ip() { - local chosen net_id bridge_opt bridge cip - chosen="$(choose_net_name)" + # Allow forwarding in DOCKER-USER chain (if LAN interface detected) + if [[ -n "$lan_if" ]]; then + # Allow traffic from LAN to container for TARGET_IP + iptables -C DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_IP" -j ACCEPT 2>/dev/null || \ + iptables -I DOCKER-USER 1 -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_IP" -j ACCEPT - net_id="$(docker network inspect -f '{{.Id}}' "$chosen" 2>/dev/null || true)" - if [[ -z "$net_id" ]]; then - return 1 - fi + # Allow return traffic + iptables -C DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_IP" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -I DOCKER-USER 1 -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_IP" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - # If Docker network specifies an explicit bridge name, use it. - bridge_opt="$(docker network inspect -f '{{index .Options "com.docker.network.bridge.name"}}' "$chosen" 2>/dev/null || true)" - if [[ -n "$bridge_opt" && "$bridge_opt" != "" ]]; then - bridge="$bridge_opt" - else - bridge="br-${net_id:0:12}" - fi - - cip="$(docker inspect -f "{{(index .NetworkSettings.Networks \"$chosen\").IPAddress}}" "$CONTAINER_NAME" 2>/dev/null || true)" - if [[ -z "$cip" ]]; then - return 1 - fi - - echo "$bridge $cip" -} - -set_bridge_alias() { - local bridge="$1" - - # Docker creates the bridge name (br-); renaming it breaks Docker. - # Setting an alias is safe and improves readability in `ip link` output. - if [[ -n "${HOST_BRIDGE_ALIAS:-}" ]]; then - ip link set dev "$bridge" alias "$HOST_BRIDGE_ALIAS" 2>/dev/null || true - fi -} - -container_apply() { - docker exec "$CONTAINER_NAME" sh -lc 'set -e -BR="${BRIDGE_NAME:-br-rego-vpn}" -VM="${VM_NET_IP:-100.100.0.2}" -TARGET="${TARGET_IP:-10.35.33.230}" - -echo 1 > /proc/sys/net/ipv4/ip_forward -ip route replace "$TARGET/32" via "$VM" dev "$BR" 2>/dev/null || ip route replace "$TARGET" via "$VM" dev "$BR" 2>/dev/null || true -iptables -C FORWARD -d "$TARGET" -j ACCEPT 2>/dev/null || iptables -A FORWARD -d "$TARGET" -j ACCEPT -iptables -C FORWARD -s "$TARGET" -j ACCEPT 2>/dev/null || iptables -A FORWARD -s "$TARGET" -j ACCEPT -iptables -t nat -C POSTROUTING -o "$BR" -d "$TARGET" -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o "$BR" -d "$TARGET" -j MASQUERADE -' -} - -container_remove() { - docker exec "$CONTAINER_NAME" sh -lc 'set -e -BR="${BRIDGE_NAME:-br-rego-vpn}" -VM="${VM_NET_IP:-100.100.0.2}" -TARGET="${TARGET_IP:-10.35.33.230}" - -ip route del "$TARGET/32" via "$VM" dev "$BR" 2>/dev/null || true -iptables -t nat -D POSTROUTING -o "$BR" -d "$TARGET" -j MASQUERADE 2>/dev/null || true -' || true -} - -apply_host_rules() { - local lan_if bridge cip - - remove_stale_nft_redirects - remove_stale_host_routes - - lan_if="$(get_lan_if)" - if [[ -z "$lan_if" ]]; then - log "WARN: could not detect LAN default interface; skipping host iptables" - fi - - read -r bridge cip < <(get_bridge_and_container_ip) - set_bridge_alias "$bridge" - - echo 1 > /proc/sys/net/ipv4/ip_forward - - ip route replace "$TARGET_CIDR" via "$cip" dev "$bridge" - - if [[ -n "${lan_if:-}" ]]; then - iptables -C DOCKER-USER -i "$lan_if" -o "$bridge" -d "$TARGET_CIDR" -j ACCEPT 2>/dev/null || \ - iptables -I DOCKER-USER 1 -i "$lan_if" -o "$bridge" -d "$TARGET_CIDR" -j ACCEPT - - iptables -C DOCKER-USER -i "$bridge" -o "$lan_if" -s "$TARGET_CIDR" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ - iptables -I DOCKER-USER 1 -i "$bridge" -o "$lan_if" -s "$TARGET_CIDR" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - fi - - container_apply - - log "OK: routing $TARGET_CIDR via $cip ($bridge)" -} - -remove_host_rules() { - local lan_if bridge cip - - lan_if="$(get_lan_if)" - if read -r bridge cip < <(get_bridge_and_container_ip); then - ip route del "$TARGET_CIDR" via "$cip" dev "$bridge" 2>/dev/null || true - - if [[ -n "${lan_if:-}" ]]; then - iptables -D DOCKER-USER -i "$lan_if" -o "$bridge" -d "$TARGET_CIDR" -j ACCEPT 2>/dev/null || true - iptables -D DOCKER-USER -i "$bridge" -o "$lan_if" -s "$TARGET_CIDR" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + log "DOCKER-USER iptables rules added for $lan_if <-> $BRIDGE_NAME" + else + log "WARN: Could not detect LAN interface, skipping DOCKER-USER rules" fi - fi - container_remove + log "OK: Host routing applied - $TARGET_IP via $CONTAINER_IP ($BRIDGE_NAME)" +} + +remove_all() { + local lan_if + lan_if="$(get_lan_interface)" + + log "Removing all routing rules..." + + # Remove route + ip route del "$TARGET_IP/32" via "$CONTAINER_IP" dev "$BRIDGE_NAME" 2>/dev/null || true + + # Remove iptables rules + if [[ -n "$lan_if" ]]; then + iptables -D DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_IP" -j ACCEPT 2>/dev/null || true + iptables -D DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_IP" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + fi + + log "All routing rules removed" } case "$ACTION" in - start) - # Wait briefly for docker + container/network to be ready. - for _ in {1..30}; do - if get_bridge_and_container_ip >/dev/null 2>&1; then - apply_host_rules - exit 0 - fi - sleep 2 - done - log "ERROR: container/network not ready; no routing applied" - exit 0 - ;; - stop) - remove_host_rules - ;; - *) - echo "Usage: $0 {start|stop}" >&2 - exit 2 - ;; + start) + remove_routes + apply_routes + ;; + stop) + remove_all + ;; + restart) + remove_all + sleep 1 + remove_routes + apply_routes + ;; + *) + echo "Usage: $0 {start|stop|restart}" >&2 + exit 2 + ;; esac