Ir al contenido

Automaticación de rutas dinámicas con NetworkManager y dispatcher.d

·5 mins
Linux Redes Automatización Debian NetworkManager Dispatcher.d VPN Tailscale Rutas Dinámicas Linux
Jordi Fàbregas
Autor
Jordi Fàbregas
Administrador de Sistemas y Redes. Entusiasta del software libre.
Tabla de contenido

Introducción
#

En muchos entornos de trabajo es común disponer de varias redes y de conexión remota mediante VPN. En ciertos contextos, tenemos que decidir si queremos conectar por VPN o enrutar directamente por la red local.

Por ejemplo, si estamos en las instalaciones físicas de la empresa, conviene conectarse directamente a la red interna, optimizando el rendimiento. En cambio, si queremos conectarnos desde fuera de las instalaciones, debemos usar una VPN para mayor seguridad de las conexiones.

El problema, resulta tedioso hacer estos cambios a mano y no todos los usuarios van a saber la topologia exacta de la red, ni enrutar con órdenes como

ip route [IP] via [GATEWAY] dev [INTERFACE]

Automatizar este proceso no solo ahorra tiempo, sino que evita errores de configuración y garantiza que la seguridad y el rendimiento se gestionen correctamente según la ubicación.

NetworkManager y dispatcher.d
#

Podríamos pensar que con triggers de systemd podríamos configurar estos eventos, y de hecho es posible, pero existe un servicio presente en la mayoría de distribuciones Linux modernas que facilita enormemente la administración de redes: NetworkManager.

NetworkManager se ocupa de levantar y bajar interfaces, recordar contraseñas WiFi, gestionar perfiles de red y facilitar la integración de servicios de red. También permite ejecutar acciones personalizadas cuando ocurre un evento de red, como conectarse a una Wi-Fi o desconectarse de ella. Estos hooks se implementan mediante scripts en el directorio:

/etc/NetworkManager/dispatcher.d/

Cada vez que una interfaz cambia de estado (por ejemplo, up o down), NetworkManager ejecuta los scripts allí presentes y les pasa dos parámetros de entrada: el nombre de la interfaz y su estado.

INTERFACE="$1"
STATUS="$2"

De este modo, colocando un fichero en dispatcher.d se ejecutará automáticamente cuando cambie el estado de la red. Esto permite añadir reglas como:

  • Montar automáticamente un recurso de red al conectarse a la Wi-Fi de la empresa o de casa.

  • Cambiar dinámicamente rutas y gateways según el SSID al que estemos conectados.

  • Activar o desactivar la VPN de manera automática según la red.

Script de ejemplo
#

En nuestro ejemplo, el gateway 192.168.1.50 es un router que conecta nuestra red local (192.168.1.0/24) con otra subred interna de la empresa (172.16.1.0/24).

Cuando estamos físicamente conectados a la oficina o a nuestra Wi-Fi de confianza, queremos que el tráfico destinado a esa subred interna no pase por la VPN, sino directamente a través de este gateway.

En cambio, cuando no reconoce la red wifi, activa el VPN y enruta el tráfico hacia 172.16.1.0/24mediante el mismo.

ADVERTENCIA: Este script es un ejemplo de cómo usar dispatcher.d para enrutar tráfico a través de una VPN en un caso específico. Está pensado solo como referencia para usuarios avanzados. No se garantiza que funcione en tu red y no debe ejecutarse tal cual.

#!/bin/bash

# Script for use with NetworkManager's dispatcher.d
#
# When connected to a trusted local Wi-Fi (defined in LOCAL_SSIDS), this script
# disables the Tailscale VPN and adds a route to the target network via the local gateway.
#
# When connected to any other Wi-Fi, it enables the Tailscale VPN and removes
# the local route to ensure traffic goes through the VPN.
#
# Deployment:
# Place this script in /etc/NetworkManager/dispatcher.d/
# Ensure it is owned by root and has executable permissions.


INTERFACE="$1" # Passed by NetworkManager (interface name)
STATUS="$2" # Passed by NetworkManager (connection status)

## CONFIGURATION ##
TARGET_IP="172.16.1.0/24" # IP with or without CIDR
LOCAL_GATEWAY="192.168.1.50"
LOCAL_INTERFACE="wlp2s0"  # Wireless interface name

# Trusted Wifi networks (Tailscale will be disabled for these)
LOCAL_SSIDS=("Work_SSID_1" "Work_SSID_2" "Work_SSID_3" "Work_SSID_4")

## FUNCTIONS ##
check_root() {
  # Verify script is running as root
  if [ $(id -u) = 0 ]; then
    return 0  # OK  
  else
    echo "This script needs to be executed as root" >&2
    logger -t net-dispatcher "This script needs to be executed as root"
    exit 1  # ERROR
  fi
}

check_package() {
  # Check if required package is installed (Debian/Ubuntu)
  local pkg="$1"
  if dpkg -s "$pkg" &> /dev/null; then
    return 0
  else
    echo "Package $1 not found, aborting..." >&2
    exit 1
  fi
}

send_notification() {

    # Detectar el usuario gráfico en seat0
    local user=$(loginctl list-sessions | awk '$4 == "seat0" {print $3}')
    [[ -z "$user" ]] && { logger -t net-dispatcher "No se detectó usuario gráfico"; return 1; }

    local uid=$(id -u "$user")
    local bus="unix:path=/run/user/$uid/bus"

    # Ejecutar notify-send como usuario gráfico con el bus correcto
    sudo -H -u "$user" env DBUS_SESSION_BUS_ADDRESS="$bus" \
        notify-send "$@"
}

## MAIN SCRIPT ##

# If the interface is down or not $LOCAL_INTERFACE
if [[ "$STATUS" != "up" || "$INTERFACE" != "$LOCAL_INTERFACE" ]]; then
    exit 0
fi

# Verify requirements
check_root
check_package wireless-tools # iwgetid
check_package libnotify-bin # notify-send
check_package tailscale
check_package util-linux # logger
check_package iproute2 # ip route

# Detect current WiFi network
CURRENT_SSID=$(iwgetid -r)

logger -t net-dispatcher "Interface $LOCAL_INTERFACE is $STATUS"

# Check if connected to trusted network
ssid_found=false
for ssid in "${LOCAL_SSIDS[@]}"; do
    if [[ "$ssid" == "$CURRENT_SSID" ]]; then
        ssid_found=true
        logger -t net-dispatcher "Local SSID found: $CURRENT_SSID"
        break
    fi
done

# Remove the route for safety before adding it
ip route del "$TARGET_IP" 2>/dev/null || true

if [[ "$ssid_found" == true ]]; then
    # Trusted network - use local routing
    send_notification --app-name="NetworkManager" -t 5000 -i network-wireless "Welcome Home" "Routing over LAN"

    logger -t net-dispatcher "Routing via LAN"
    tailscale down
    ip route add "$TARGET_IP" via "$LOCAL_GATEWAY" dev "$LOCAL_INTERFACE"

else
    send_notification --app-name="NetworkManager" -t 5000 -i network-vpn "Untrusted network" "Routing over VPN"

    logger -t net-dispatcher "Local SSID not found: $CURRENT_SSID"

    logger -t net-dispatcher "Routing via Tailscale"
    tailscale up --accept-routes
fi