#!/usr/bin/env bash # # Host routing script for cistech-tunnel # Routes TARGET_IP through the VPN container # set -euo pipefail ACTION="${1:-start}" # Fixed configuration (we assigned these) CONTAINER_IP="172.30.0.10" BRIDGE_NAME="br-cistech-vpn" TARGET_IP="${TARGET_IP:-10.3.1.0}" TARGET_SUBNET="10.3.1.0/24" LAN_SUBNET="192.168.0.0/23" LAN_INTERFACES="eth0 eth1 wlan0" LOG_FILE="/var/log/cistech-routing.log" log() { local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [cistech-routing] $*" echo "$msg" | tee -a "$LOG_FILE" >&2 } get_lan_interface() { ip route show default | awk '/default/ {for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1 } remove_routes() { log "Removing stale routes for $TARGET_SUBNET..." # Remove any existing route to TARGET_SUBNET ip route del "$TARGET_SUBNET" 2>/dev/null || true log "Stale routes removed" } apply_routes() { local lan_if lan_if="$(get_lan_interface)" log "Applying host routing rules..." log " Container IP: $CONTAINER_IP" log " Bridge: $BRIDGE_NAME" log " Target Subnet: $TARGET_SUBNET" log " LAN interface: ${lan_if:-unknown}" # Enable IP forwarding echo 1 > /proc/sys/net/ipv4/ip_forward log "IP forwarding enabled" # Add route to TARGET_SUBNET via container ip route replace "$TARGET_SUBNET" via "$CONTAINER_IP" dev "$BRIDGE_NAME" log "Route added: $TARGET_SUBNET via $CONTAINER_IP dev $BRIDGE_NAME" # Allow forwarding in DOCKER-USER chain for all LAN interfaces for lan_if in $LAN_INTERFACES; do # Check if interface exists if ip link show "$lan_if" &>/dev/null; then # Allow traffic from LAN to container for TARGET_SUBNET iptables -C DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_SUBNET" -j ACCEPT 2>/dev/null || \ iptables -I DOCKER-USER 1 -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_SUBNET" -j ACCEPT # Allow return traffic iptables -C DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_SUBNET" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ iptables -I DOCKER-USER 1 -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_SUBNET" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT log "DOCKER-USER iptables rules added for $lan_if <-> $BRIDGE_NAME" fi done # Masquerade traffic from LAN subnet to VPN bridge (so return traffic routes correctly) # Use nft since iptables-nft backend doesn't support iptables -t nat commands if ! nft list chain ip nat POSTROUTING 2>/dev/null | grep -q "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade"; then nft add rule ip nat POSTROUTING ip saddr "$LAN_SUBNET" oifname "$BRIDGE_NAME" counter masquerade log "NAT masquerade rule added for $LAN_SUBNET -> $BRIDGE_NAME" else log "NAT masquerade rule already exists for $LAN_SUBNET -> $BRIDGE_NAME" fi log "OK: Host routing applied - $TARGET_SUBNET via $CONTAINER_IP ($BRIDGE_NAME)" } remove_all() { log "Removing all routing rules..." # Remove route ip route del "$TARGET_SUBNET" via "$CONTAINER_IP" dev "$BRIDGE_NAME" 2>/dev/null || true # Remove iptables rules for all LAN interfaces for lan_if in $LAN_INTERFACES; do iptables -D DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_SUBNET" -j ACCEPT 2>/dev/null || true iptables -D DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_SUBNET" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true done # Remove masquerade rule (using nft) local handle="" handle=$(nft -a list chain ip nat POSTROUTING 2>/dev/null | grep "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade" | grep -oP 'handle \K\d+' | head -1 || true) if [ -n "$handle" ]; then nft delete rule ip nat POSTROUTING handle "$handle" 2>/dev/null || true fi log "All routing rules removed" } case "$ACTION" in 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