VPNSmith
self-host-vpnINFO

Split-tunnel VPN con tablas de rutas (ip-rule + ip-route) 2026

Split-tunnel Linux por fwmark + ip rule: aplicaciones enrutadas vía VPN vs ISP directo. Casos Netflix bypass + trabajo VPN. Scripts de setup y buenas prácticas.

Por Eric Gerard · Fondateur · VPNSmith — Spécialiste self-host VPN & VPS GDPR7 min de lecturaFoto vía Unsplash

Quieres un VPN para tu trabajo (acceso al LAN corporativo vía túnel) y al mismo tiempo Netflix viendo tu IP española para servirte el catálogo ES. O quieres que Steam descargue en directo (1 Gbps ISP) mientras el resto va por VPN. Solución: split-tunnel por tablas de rutas Linux, más potente que el split-tunnel de AllowedIPs WireGuard.

Esta guía implementa el patrón fwmark + ip rule + ip route que permite enrutar ciertas aplicaciones por el VPN y otras directamente, sin tocar el túnel WireGuard.

El concepto en 4 pasos

  1. Marcar el tráfico de una app con iptables -j MARK (por cgroup, user, o destino).
  2. Crear una tabla de rutas alternativa para el tráfico marcado (ej: tabla 200).
  3. Añadir una regla ip-rule que dice "tráfico con mark X → usa tabla 200".
  4. Poblar la tabla 200 con una ruta por defecto distinta (wg0 o eth0 directo).

Resultado: el kernel inspecciona cada paquete saliente y decide tabla 200 (VPN) o tabla principal (ISP directo).

Setup base

Pre-requisitos: un túnel WireGuard configurado y activo. Ver la guía setup Contabo WireGuard si partes de cero.

Verifica primero que iproute2 está instalado (debería en cualquier distro reciente):

ip -V
# iproute2-6.x.x

Caso A — Excluir Steam del VPN (descargas directo ISP)

Steam se identifica por cgroup al arrancarlo desde systemd. Enrutamos su tráfico vía interfaz ISP eth0 en lugar de wg0.

Paso 1 — Crear cgroup para Steam

sudo mkdir -p /sys/fs/cgroup/net_cls/steam
sudo bash -c 'echo 0x42 > /sys/fs/cgroup/net_cls/steam/net_cls.classid'

Paso 2 — Marcar paquetes salientes desde ese cgroup

sudo iptables -t mangle -A OUTPUT -m cgroup --cgroup 0x42 -j MARK --set-mark 200

Paso 3 — Crear tabla de rutas 200 con ruta vía ISP

echo "200 isp_direct" | sudo tee -a /etc/iproute2/rt_tables

ISP_GW=$(ip route | awk '/default/ && /eth0/ {print $3}')
sudo ip route add default via $ISP_GW dev eth0 table isp_direct

sudo ip rule add fwmark 200 table isp_direct

Paso 4 — Lanzar Steam en ese cgroup

cgexec -g net_cls:steam /usr/games/steam

O vía systemd .slice para setups Flatpak / Steam Snap.

Steam ahora descarga directo ISP (hasta 1 Gbps fibra) mientras el resto de tu máquina sigue por el túnel WireGuard.

Caso B — Enrutar Netflix vía ISP para catálogo ES

Quieres que Netflix vea tu IP española. Enfoque por IP destino ya que Netflix usa múltiples CDN.

Paso 1 — Obtener rangos BGP Netflix

Netflix publica sus prefijos: AS2906. Los sacas con whois -h whois.radb.net -- '-i origin AS2906' o lista mantenida:

curl -s https://api.bgpview.io/asn/2906/prefixes | jq -r '.data.ipv4_prefixes[].prefix' > /tmp/netflix-ranges.txt

Paso 2 — Marcar tráfico hacia esos rangos

while read range; do
  sudo iptables -t mangle -A OUTPUT -d "$range" -j MARK --set-mark 200
done < /tmp/netflix-ranges.txt

Paso 3 — Usar misma tabla isp_direct (ya configurada en caso A).

En el próximo netflix.com, DNS resuelve, paquete TCP sale con mark 200, kernel envía vía eth0 directo, Netflix ve tu IP española.

Caso C — Inverso: forzar solo Slack/Zoom vía VPN

Trabajas desde el extranjero, quieres tráfico laboral (Slack, Zoom, GitHub) por el VPN corporativo, resto directo.

Setup simétrico:

  • Mark por defecto = directo
  • Mark apps trabajo = vía VPN
echo "201 work_vpn" | sudo tee -a /etc/iproute2/rt_tables
sudo ip route add default dev wg0 table work_vpn
sudo ip rule add fwmark 201 table work_vpn

# Marker para Slack (por destino)
for d in $(dig +short slack.com slack-edge.com app.slack.com); do
  sudo iptables -t mangle -A OUTPUT -d "$d" -j MARK --set-mark 201
done

# Zoom (rangos publicados por Zoom en docs admin)
sudo iptables -t mangle -A OUTPUT -d 3.7.35.0/25 -j MARK --set-mark 201
sudo iptables -t mangle -A OUTPUT -d 3.21.137.128/25 -j MARK --set-mark 201

Importante: la ruta por defecto de la tabla principal debe apuntar a eth0 (ISP directo), no wg0. Verifica con ip route show table main.

Práctica: script split-tunnel.sh completo

Para no reescribir comandos en cada boot, script idempotente lanzado vía systemd:

#!/usr/bin/env bash
# /usr/local/bin/split-tunnel.sh
set -euo pipefail

ISP_IF="eth0"
VPN_IF="wg0"

ISP_GW=$(ip route | awk -v iface="$ISP_IF" '/default/ && $0 ~ iface {print $3; exit}')

grep -q '^200 isp_direct' /etc/iproute2/rt_tables || echo "200 isp_direct" >> /etc/iproute2/rt_tables
grep -q '^201 work_vpn'   /etc/iproute2/rt_tables || echo "201 work_vpn" >> /etc/iproute2/rt_tables

ip route show table isp_direct | grep -q default || ip route add default via "$ISP_GW" dev "$ISP_IF" table isp_direct
ip route show table work_vpn   | grep -q default || ip route add default dev "$VPN_IF" table work_vpn

ip rule show | grep -q "fwmark 0xc8 lookup isp_direct" || ip rule add fwmark 200 table isp_direct
ip rule show | grep -q "fwmark 0xc9 lookup work_vpn"   || ip rule add fwmark 201 table work_vpn

mkdir -p /sys/fs/cgroup/net_cls/steam
echo 0x42 > /sys/fs/cgroup/net_cls/steam/net_cls.classid
iptables -t mangle -C OUTPUT -m cgroup --cgroup 0x42 -j MARK --set-mark 200 2>/dev/null \
  || iptables -t mangle -A OUTPUT -m cgroup --cgroup 0x42 -j MARK --set-mark 200

echo "Split-tunnel rules applied."

Servicio systemd:

# /etc/systemd/system/split-tunnel.service
[Unit]
Description=Split-tunnel routing rules
After=wg-quick@wg0.service
Wants=wg-quick@wg0.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/split-tunnel.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now split-tunnel.service

Caso D — Split por usuario Linux

Quieres que todo el tráfico del usuario kid (cuenta de tu hijo) pase por VPN con filtro parental, y tu cuenta principal eric quede directa.

sudo iptables -t mangle -A OUTPUT -m owner --uid-owner kid -j MARK --set-mark 201
# Mark 201 enrutado vía VPN (configurado arriba)

Todo curl, firefox, apt lanzado bajo UID kid sale por wg0. Práctico también para browser de prueba (Chromium en otro user) que siempre ve una IP VPN distinta.

Diagnóstico y debug

Ver reglas activas

ip rule show
ip route show table isp_direct
ip route show table work_vpn

Trazar el routing de un paquete

ip route get 1.1.1.1 mark 200
# Debe mostrar "via 192.168.1.1 dev eth0 table isp_direct"

ip route get 1.1.1.1 mark 201
# Debe mostrar "dev wg0 table work_vpn"

Ver el mark en tcpdump

sudo tcpdump -i any -nn -e 'iptables' 2>/dev/null
sudo nft monitor trace

¿El mark no se aplica?

Verifica el orden de chains iptables. La tabla mangle chain OUTPUT se llama antes que el routing. Si tu regla aparece bien en iptables -t mangle -L OUTPUT -v -n pero el mark no pega, suele ser cgroup no heredado (lanza vía cgexec explícito).

Límites y gotchas

IPv6

Todos los comandos son IPv4 only. Para IPv6, duplica con:

sudo ip6tables -t mangle -A OUTPUT ...
sudo ip -6 rule add fwmark 200 table isp_direct
sudo ip -6 route add default via FE80::1 dev eth0 table isp_direct

Si usas también nuestra guía kill switch, recuerda que las reglas OUTPUT DROP del killswitch pueden bloquear el tráfico split-tunnel directo. Solución: exceptuar marks 200/201 del DROP general.

DNS leak en split-tunnel

Si splits por IP destino, el DNS sigue resuelto por tu DNS principal. Si ese DNS está dentro del túnel, Netflix puede resolver a IP UK cuando querías ES. Solución: configurar DNS directo para dominios split (dnsmasq con server-specific resolvers).

Kernel cachea rutas

Tras modificar una ip rule, ejecuta:

sudo ip route flush cache

Si no, conexiones ya abiertas mantienen su routing antiguo.

Compatibilidad Docker / containers

Docker manipula iptables agresivamente. Si cohabitan split-tunnel + Docker, espera reglas pisadas en restart Docker. Solución: lanza split-tunnel.service con After=docker.service o usa nftables.

Veredicto

Split-tunnel por fwmark + ip rule es la solución más flexible en Linux. Puedes enrutar por destino, usuario, cgroup, proceso. Coste: ~10 líneas de config y servicio systemd. Muy superior al split-tunnel AllowedIPs WireGuard que solo filtra por IP destino.

Setup recomendado casa:

  • VPN por defecto en todo
  • Steam, Netflix, Time Machine → marcados 200 → directo ISP
  • Kill switch netfilter con excepción mark 200

Setup recomendado trabajo remoto:

  • Directo ISP por defecto
  • Slack, Zoom, GitHub Enterprise, jumphost → marcados 201 → vía VPN corp

Y para la base: guía WireGuard Contabo, plantillas listas para pegar, y monitoring Prometheus + Grafana para seguir qué sale por qué interfaz en tiempo real.

★ Datacenter Nuremberg GDPR · ✓ IPv4 dédiée incluse · 200+ Mbps garantis

Probar Contabo30 jours satisfait ou remboursé