GRE (Generic Routing Encapsulation) es un protocolo de túnel sin estado que encapsula paquetes IP dentro de otros paquetes IP. Es muy sencillo, lo soporta prácticamente cualquier router o firewall y se usa para unir redes entre sedes, transportar protocolos de enrutamiento como OSPF o BGP y conectar con equipos de red de terceros. En este tutorial crearás un túnel GRE entre dos servidores Ubuntu 24.04, enrutarás una red a través de él, ajustarás la MTU, lo harás persistente con Netplan y, al final, lo cifrarás con IPsec.

Requisitos previos

  • Dos servidores con Ubuntu 24.04 LTS con IP públicas fijas, por ejemplo VPS de CubePath.
  • Un usuario no root con privilegios sudo en ambos.
  • Que el protocolo IP 47 (GRE) pueda circular entre los dos servidores. GRE no es TCP ni UDP, así que algunos firewalls y NAT lo bloquean aunque tengan los puertos abiertos.

El tutorial usa estos valores de ejemplo:

Host AHost B
IP pública203.0.113.10203.0.113.20
IP del túnel10.10.0.1/3010.10.0.2/30
Red local detrás del host10.0.1.0/2410.0.2.0/24

Paso 1: Cargar el módulo y abrir el firewall

Carga el módulo GRE del kernel en ambos hosts:

sudo modprobe ip_gre
lsmod | grep gre
ip_gre                 32768  0
ip_tunnel              36864  1 ip_gre
gre                    12288  1 ip_gre

Al cargarse, el módulo crea una interfaz llamada gre0. Es un dispositivo interno del kernel que recibe el GRE que no corresponde a ningún túnel, así que no la uses: este tutorial llama gre1 al túnel.

Si usas UFW, permite el protocolo GRE solo desde el otro host. En el host A:

sudo ufw allow proto gre from 203.0.113.20 to any

En el host B:

sudo ufw allow proto gre from 203.0.113.10 to any

Paso 2: Crear el túnel

En el host A, crea la interfaz del túnel indicando los dos extremos. ttl 255 evita que el TTL del paquete interior se copie al exterior, lo que rompería el túnel si el paquete original llega con un TTL bajo:

sudo ip tunnel add gre1 mode gre local 203.0.113.10 remote 203.0.113.20 ttl 255
sudo ip link set gre1 mtu 1476 up
sudo ip addr add 10.10.0.1/30 dev gre1

En el host B, con los extremos intercambiados:

sudo ip tunnel add gre1 mode gre local 203.0.113.20 remote 203.0.113.10 ttl 255
sudo ip link set gre1 mtu 1476 up
sudo ip addr add 10.10.0.2/30 dev gre1

La MTU de 1476 resulta de restar a los 1500 bytes de Ethernet la cabecera IP exterior (20 bytes) y la cabecera GRE básica (4 bytes).

Comprueba el túnel desde el host A:

ip tunnel show gre1
ping -c 3 10.10.0.2
gre1: gre/ip remote 203.0.113.20 local 203.0.113.10 ttl 255
64 bytes from 10.10.0.2: icmp_seq=1 ttl=64 time=1.21 ms
64 bytes from 10.10.0.2: icmp_seq=2 ttl=64 time=1.08 ms

Para ver el encapsulado, captura en la interfaz pública del host B (sustituye eth0 por la tuya) mientras haces ping:

sudo tcpdump -ni eth0 proto gre
IP 203.0.113.10 > 203.0.113.20: GREv0, length 88: IP 10.10.0.1 > 10.10.0.2: ICMP echo request, id 4, seq 1, length 64

Paso 3: Enrutar las redes locales por el túnel

El túnel sirve para que las redes que hay detrás de cada host se alcancen entre sí. Añade en cada host la ruta hacia la red del otro lado a través de la IP del túnel remoto.

En el host A:

sudo ip route add 10.0.2.0/24 via 10.10.0.2 dev gre1

En el host B:

sudo ip route add 10.0.1.0/24 via 10.10.0.1 dev gre1

Para que los hosts reenvíen tráfico entre su red local y el túnel, activa el reenvío IP de forma persistente en ambos:

echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/60-ip-forward.conf
sudo sysctl --system

Si UFW está activo, su política por defecto descarta el tráfico reenviado. Permite el reenvío entre el túnel y la interfaz de la red local (en este ejemplo eth1) en ambos sentidos:

sudo ufw route allow in on gre1 out on eth1
sudo ufw route allow in on eth1 out on gre1

Las máquinas de la red 10.0.1.0/24 también deben saber que 10.0.2.0/24 se alcanza a través del host A, ya sea porque el host A es su puerta de enlace o porque les añades una ruta estática. Comprueba el camino desde una máquina de la red A:

ip route get 10.0.2.10
ping -c 3 10.0.2.10

Paso 4: Evitar problemas de MTU con MSS clamping

Las máquinas de las redes locales usan MTU 1500 y no saben que el túnel admite menos. Si el ICMP que avisa de la fragmentación se filtra en algún punto, las conexiones TCP se quedan colgadas al transferir paquetes grandes. La solución habitual es ajustar el MSS de TCP en el host que hace de router, para que las conexiones negocien un tamaño que quepa en el túnel.

Ubuntu 24.04 usa nftables como backend de firewall, así que la forma más limpia es una tabla nftables propia, independiente de las reglas de UFW. Instala la herramienta nft (normalmente ya está presente) y crea el archivo de reglas en ambos hosts:

sudo apt install nftables
sudo mkdir -p /etc/nftables.d
sudo nano /etc/nftables.d/gre-mss.nft

Las dos primeras líneas declaran y borran la tabla, de modo que el archivo se puede cargar varias veces sin duplicar reglas:

table inet gre_mss
delete table inet gre_mss

table inet gre_mss {
    chain forward {
        type filter hook forward priority mangle; policy accept;
        oifname "gre1" tcp flags syn tcp option maxseg size set rt mtu
        iifname "gre1" tcp flags syn tcp option maxseg size set rt mtu
    }
}

Carga la tabla y comprueba que existe:

sudo nft -f /etc/nftables.d/gre-mss.nft
sudo nft list table inet gre_mss

No actives el servicio nftables para cargarla en el arranque: su archivo /etc/nftables.conf empieza con flush ruleset y borraría las reglas de UFW. En su lugar, crea una unidad de systemd que cargue solo este archivo:

sudo nano /etc/systemd/system/gre-mss.service
[Unit]
Description=MSS clamping para el tunel GRE
After=network-pre.target ufw.service

[Service]
Type=oneshot
ExecStart=/usr/sbin/nft -f /etc/nftables.d/gre-mss.nft
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Actívala y comprueba su estado:

sudo systemctl daemon-reload
sudo systemctl enable --now gre-mss.service
systemctl status gre-mss.service

Comprueba que un paquete del tamaño máximo cruza el túnel sin fragmentar (1448 bytes de datos más 28 de cabeceras ICMP e IP suman 1476):

ping -c 3 -M do -s 1448 10.10.0.2

Paso 5: Hacer el túnel persistente con Netplan

El túnel, su dirección y las rutas desaparecen al reiniciar. Netplan, el gestor de red de Ubuntu, puede declarar túneles GRE. Borra primero la interfaz temporal para que Netplan la cree:

sudo ip link del gre1

Crea un archivo de Netplan en el host A:

sudo nano /etc/netplan/60-gre1.yaml
network:
  version: 2
  tunnels:
    gre1:
      mode: gre
      local: 203.0.113.10
      remote: 203.0.113.20
      mtu: 1476
      addresses:
        - 10.10.0.1/30
      routes:
        - to: 10.0.2.0/24
          via: 10.10.0.2

Netplan exige que sus archivos no sean legibles por otros usuarios:

sudo chmod 600 /etc/netplan/60-gre1.yaml

Aplica la configuración. netplan try revierte los cambios automáticamente si pierdes la conexión y no confirmas en 120 segundos:

sudo netplan try

Pulsa Intro para confirmar y comprueba el resultado:

networkctl status gre1
ip route show dev gre1
10.0.2.0/24 via 10.10.0.2 proto static
10.10.0.0/30 proto kernel scope link src 10.10.0.1

Repite en el host B con local, remote, la dirección y la ruta intercambiados. Reinicia uno de los servidores y confirma que el ping entre 10.10.0.1 y 10.10.0.2 sigue funcionando.

Paso 6: Cifrar el túnel con IPsec

IPsec en modo transporte cifra los paquetes GRE entre los dos hosts sin cambiar nada del túnel: GRE sigue funcionando igual, pero sus paquetes viajan dentro de ESP. Este paso usa strongSwan con su interfaz moderna, swanctl.

Instala strongSwan en ambos hosts:

sudo apt update
sudo apt install charon-systemd strongswan-swanctl

Genera una clave precompartida larga y aleatoria. Usarás la misma en los dos hosts:

openssl rand -base64 32

Crea la configuración en el host A:

sudo nano /etc/swanctl/conf.d/gre.conf
connections {
    gre-ipsec {
        version = 2
        local_addrs = 203.0.113.10
        remote_addrs = 203.0.113.20
        proposals = aes256gcm16-prfsha384-ecp384
        dpd_delay = 30s
        local {
            auth = psk
            id = 203.0.113.10
        }
        remote {
            auth = psk
            id = 203.0.113.20
        }
        children {
            gre {
                mode = transport
                local_ts = dynamic[gre]
                remote_ts = dynamic[gre]
                esp_proposals = aes256gcm16-ecp384
                start_action = trap
                dpd_action = restart
            }
        }
    }
}

secrets {
    ike-gre {
        id-a = 203.0.113.10
        id-b = 203.0.113.20
        secret = "your_psk"
    }
}

Sustituye your_psk por la clave generada. En el host B, usa el mismo archivo intercambiando local_addrs/remote_addrs y los id de local y remote. local_ts = dynamic[gre] limita la protección al protocolo GRE entre las dos IP, y start_action = trap negocia la conexión automáticamente en cuanto hay tráfico GRE.

Restringe los permisos, ya que contiene la clave:

sudo chmod 600 /etc/swanctl/conf.d/gre.conf

Permite IKE y ESP desde el otro host en el firewall. En el host A:

sudo ufw allow from 203.0.113.20 to any port 500,4500 proto udp
sudo ufw allow proto esp from 203.0.113.20 to any

ESP añade entre 50 y 60 bytes por paquete, así que reduce la MTU del túnel a 1400 en ambos hosts cambiando mtu: 1476 por mtu: 1400 en /etc/netplan/60-gre1.yaml y aplicándolo con sudo netplan apply.

Carga la configuración y reinicia el servicio en ambos hosts:

sudo systemctl restart strongswan
sudo swanctl --load-all

Genera tráfico por el túnel y comprueba la asociación de seguridad:

ping -c 3 10.10.0.2
sudo swanctl --list-sas
gre-ipsec: #1, ESTABLISHED, IKEv2, ...
  local  '203.0.113.10' @ 203.0.113.10[500]
  remote '203.0.113.20' @ 203.0.113.20[500]
  gre: #1, reqid 1, INSTALLED, TRANSPORT, ESP:AES_GCM_16-256
    local  203.0.113.10/32[gre]
    remote 203.0.113.20/32[gre]

Una captura en la interfaz pública ya no debe mostrar GRE en claro, sino paquetes ESP:

sudo tcpdump -ni eth0 host 203.0.113.20 and not port 22
IP 203.0.113.10 > 203.0.113.20: ESP(spi=0xc1a2b3c4,seq=0x5), length 132

Solución de problemas

El túnel está UP pero el ping a la IP remota del túnel no responde. Captura con sudo tcpdump -ni eth0 proto gre en ambos hosts. Si los paquetes salen de un lado y no llegan al otro, algo en el camino bloquea el protocolo 47: el firewall del destino, un grupo de seguridad del proveedor o un NAT. GRE no atraviesa NAT de forma fiable; si uno de los extremos está detrás de NAT, usa WireGuard.

El ping entre túneles funciona pero no entre las redes locales. Comprueba que sysctl net.ipv4.ip_forward devuelve 1 en ambos hosts, que existen las rutas en los dos sentidos (ip route get desde cada lado) y que la cadena FORWARD no descarta el tráfico (sudo iptables -L FORWARD -v -n).

Las conexiones SSH o HTTP a través del túnel se cuelgan tras conectar. Es un problema de MTU. Revisa que la tabla gre_mss está cargada y que la MTU de gre1 es 1476 (o 1400 con IPsec).

swanctl --list-sas no muestra nada. Consulta el registro con sudo journalctl -u strongswan -n 50. AUTHENTICATION_FAILED indica que la clave o los id no coinciden; NO_PROPOSAL_CHOSEN, que los proposals son distintos en cada lado.

Conclusión

Tienes un túnel GRE persistente entre dos servidores Ubuntu 24.04 que une dos redes, con la MTU y el MSS ajustados para evitar conexiones colgadas y, opcionalmente, cifrado con IPsec en modo transporte. GRE sigue siendo la opción adecuada cuando el otro extremo es un router o firewall que no soporta WireGuard, o cuando necesitas transportar multicast para protocolos de enrutamiento. Como siguientes pasos puedes ejecutar OSPF o BGP sobre el túnel con FRRouting para anunciar las redes de forma dinámica, o añadir un segundo túnel hacia otra sede.