Un network namespace es una copia independiente de la pila de red de Linux: tiene sus propias interfaces, direcciones, tabla de rutas, reglas de firewall y tabla ARP. Es el mecanismo que Docker, Podman y Kubernetes usan para dar a cada contenedor su propia red. En este tutorial construirás a mano, en Ubuntu 24.04, lo mismo que hace un motor de contenedores: dos namespaces conectados a un bridge, con salida a Internet mediante NAT, resolución DNS y un firewall propio dentro de cada uno.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. Sirve cualquier servidor de pruebas: todo lo que crees aquí se elimina con los comandos del paso 7.
- Un usuario no root con privilegios
sudo. - Los paquetes
iproute2eiptables, que vienen instalados por defecto en Ubuntu 24.04.
Si el servidor ya ejecuta Docker, evita la subred 172.17.0.0/16. Este tutorial usa 172.20.0.0/24.
Paso 1: Crear un namespace y explorar su aislamiento
Crea un namespace llamado ns1:
sudo ip netns add ns1
Lista los namespaces con nombre:
ip netns list
ns1
ip netns exec ejecuta un comando dentro del namespace. Mira qué interfaces tiene:
sudo ip netns exec ns1 ip -br link
lo DOWN 00:00:00:00:00:00 <LOOPBACK>
Solo existe un loopback, y además está apagado. No ve ninguna interfaz del host ni tiene rutas. Levanta el loopback, porque muchos programas lo necesitan aunque solo hablen consigo mismos:
sudo ip netns exec ns1 ip link set lo up
Crea también el segundo namespace, que usarás en el paso 3:
sudo ip netns add ns2
sudo ip netns exec ns2 ip link set lo up
Paso 2: Conectar el host y un namespace con un par veth
Un par veth son dos interfaces virtuales unidas como por un cable: lo que entra por una sale por la otra. Crea un par y mueve un extremo dentro de ns1:
sudo ip link add veth-test type veth peer name veth-test-ns
sudo ip link set veth-test-ns netns ns1
Asigna una dirección a cada extremo y levántalos:
sudo ip addr add 10.99.0.1/30 dev veth-test
sudo ip link set veth-test up
sudo ip netns exec ns1 ip addr add 10.99.0.2/30 dev veth-test-ns
sudo ip netns exec ns1 ip link set veth-test-ns up
Comprueba la conectividad desde el host:
ping -c 2 10.99.0.2
64 bytes from 10.99.0.2: icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from 10.99.0.2: icmp_seq=2 ttl=64 time=0.039 ms
Esto funciona para un namespace, pero con muchos necesitarías una subred por par. Borra este enlace de prueba antes de continuar; al borrar un extremo, el otro desaparece también:
sudo ip link del veth-test
Paso 3: Conectar varios namespaces con un bridge
Un bridge Linux actúa como un switch virtual. Cada namespace se conecta a él con su propio par veth, y todos comparten la misma subred. Es exactamente lo que hace Docker con docker0.
Crea el bridge y dale la IP que servirá de puerta de enlace a los namespaces:
sudo ip link add br-ns type bridge
sudo ip addr add 172.20.0.1/24 dev br-ns
sudo ip link set br-ns up
Conecta ns1. El extremo veth-ns1 se queda en el host y se enchufa al bridge; eth0 va dentro del namespace (dentro puede llamarse igual que una interfaz del host, porque es otra pila de red):
sudo ip link add veth-ns1 type veth peer name eth0 netns ns1
sudo ip link set veth-ns1 master br-ns up
sudo ip netns exec ns1 ip addr add 172.20.0.10/24 dev eth0
sudo ip netns exec ns1 ip link set eth0 up
sudo ip netns exec ns1 ip route add default via 172.20.0.1
Conecta ns2 de la misma forma:
sudo ip link add veth-ns2 type veth peer name eth0 netns ns2
sudo ip link set veth-ns2 master br-ns up
sudo ip netns exec ns2 ip addr add 172.20.0.20/24 dev eth0
sudo ip netns exec ns2 ip link set eth0 up
sudo ip netns exec ns2 ip route add default via 172.20.0.1
Comprueba que los dos namespaces se ven entre sí y alcanzan el host:
sudo ip netns exec ns1 ping -c 2 172.20.0.20
sudo ip netns exec ns2 ping -c 2 172.20.0.1
Consulta los puertos conectados al bridge:
bridge link show master br-ns
8: veth-ns1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br-ns state forwarding priority 32 cost 2
10: veth-ns2@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br-ns state forwarding priority 32 cost 2
Paso 4: Dar salida a Internet con NAT
Los namespaces tienen ruta por defecto hacia el host, pero el host no reenvía paquetes y, aunque lo hiciera, las IP 172.20.0.x no son enrutables en Internet. Necesitas activar el reenvío IP y enmascarar (NAT) el tráfico que sale hacia fuera.
Identifica la interfaz pública del host:
ip route show default
default via 203.0.113.1 dev eth0 proto static
Activa el reenvío IP de forma persistente:
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/60-ip-forward.conf
sudo sysctl --system
Añade la regla de NAT para la subred de los namespaces cuando sale por eth0 (sustituye el nombre por el de tu interfaz):
sudo iptables -t nat -A POSTROUTING -s 172.20.0.0/24 -o eth0 -j MASQUERADE
Si UFW está activo, su política por defecto descarta el tráfico reenviado. Permite el reenvío del bridge hacia la interfaz pública:
sudo ufw route allow in on br-ns out on eth0
Notasi Docker está instalado, también fija la política de la cadena
FORWARDenDROP. En ese caso añade la regla en la cadena que Docker reserva para el usuario:sudo iptables -I DOCKER-USER -i br-ns -o eth0 -j ACCEPTysudo iptables -I DOCKER-USER -i eth0 -o br-ns -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT.
Comprueba la salida desde ns1:
sudo ip netns exec ns1 ping -c 2 1.1.1.1
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=1.42 ms
Paso 5: Configurar el DNS de cada namespace
La conectividad ya funciona, pero la resolución de nombres falla. En Ubuntu 24.04, /etc/resolv.conf apunta a 127.0.0.53, el resolvedor local de systemd-resolved, que escucha en el loopback del host y no es accesible desde otro namespace:
sudo ip netns exec ns1 getent hosts ubuntu.com
El comando no devuelve nada. ip netns exec monta automáticamente los archivos de /etc/netns/<nombre>/ sobre los de /etc para el comando que ejecuta, así que puedes dar a cada namespace su propio resolv.conf:
sudo mkdir -p /etc/netns/ns1 /etc/netns/ns2
echo 'nameserver 1.1.1.1' | sudo tee /etc/netns/ns1/resolv.conf /etc/netns/ns2/resolv.conf
Vuelve a probar:
sudo ip netns exec ns1 getent hosts ubuntu.com
185.125.190.20 ubuntu.com
Paso 6: Aplicar un firewall propio dentro de un namespace
Las reglas de iptables también son independientes por namespace. Las que añadas dentro de ns1 no afectan al host ni a ns2. Como ejemplo, arranca un servidor web de prueba dentro de ns1 en segundo plano:
sudo ip netns exec ns1 python3 -m http.server 8080 --bind 172.20.0.10 &
Desde ns2 y desde el host responde:
curl -sI http://172.20.0.10:8080 | head -n 1
HTTP/1.0 200 OK
Ahora aplica en ns1 una política restrictiva: se acepta el tráfico de loopback y las respuestas a conexiones propias, y el puerto 8080 solo desde el host (172.20.0.1). Todo lo demás se descarta.
sudo ip netns exec ns1 iptables -A INPUT -i lo -j ACCEPT
sudo ip netns exec ns1 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo ip netns exec ns1 iptables -A INPUT -p tcp -s 172.20.0.1 --dport 8080 -j ACCEPT
sudo ip netns exec ns1 iptables -P INPUT DROP
Desde el host sigue funcionando:
curl -sI http://172.20.0.10:8080 | head -n 1
Desde ns2 ahora se agota el tiempo de espera:
sudo ip netns exec ns2 curl -sI -m 3 http://172.20.0.10:8080
echo $?
28
El código de salida 28 de curl indica tiempo de espera agotado. Revisa las reglas y sus contadores dentro de ns1:
sudo ip netns exec ns1 iptables -L INPUT -v -n
Comprueba que el host no tiene ninguna de esas reglas:
sudo iptables -L INPUT -n | grep 8080
No debe devolver nada. Detén el servidor de prueba cuando termines:
kill %1
Paso 7: Inspeccionar y limpiar
Para saber a qué namespace pertenece un proceso, compara el enlace de /proc/<pid>/ns/net con el del host. ip netns identify hace esa comparación por ti con los namespaces con nombre:
sudo ip netns exec ns1 sleep 300 &
sudo ip netns identify "$(pgrep -n -x sleep)"
ns1
Los namespaces que crea Docker no aparecen en ip netns list porque no se registran en /run/netns. Para entrar en la red de un contenedor usa nsenter con el PID de su proceso principal:
sudo nsenter --target "$(docker inspect -f '{{.State.Pid}}' nombre_contenedor)" --net ip -br addr
Nada de lo creado en este tutorial sobrevive a un reinicio salvo el reenvío IP del paso 4. Para eliminarlo antes, borra los namespaces (sus extremos veth desaparecen con ellos), el bridge, la regla de NAT y los archivos DNS:
sudo ip netns del ns1
sudo ip netns del ns2
sudo ip link del br-ns
sudo iptables -t nat -D POSTROUTING -s 172.20.0.0/24 -o eth0 -j MASQUERADE
sudo rm -r /etc/netns/ns1 /etc/netns/ns2
Si añadiste la regla de UFW, bórrala con sudo ufw route delete allow in on br-ns out on eth0.
Solución de problemas
RTNETLINK answers: File exists. La interfaz, la dirección o la ruta ya existe. Consulta el estado actual con sudo ip netns exec ns1 ip addr o ip route antes de volver a crearla.
El namespace llega al host pero no a Internet. Comprueba en este orden: sysctl net.ipv4.ip_forward debe devolver 1, la regla MASQUERADE debe usar el nombre correcto de la interfaz de salida (sudo iptables -t nat -L POSTROUTING -v -n muestra si sus contadores suben) y la cadena FORWARD no debe descartar el tráfico (sudo iptables -L FORWARD -v -n).
Ping funciona pero los nombres no se resuelven. Falta /etc/netns/<nombre>/resolv.conf o el namespace no se creó con ip netns: el montaje automático solo lo hace ip netns exec.
Conclusión
Has construido a mano la red de un motor de contenedores: namespaces aislados, pares veth, un bridge como switch, NAT para salir a Internet, DNS por namespace y un firewall independiente dentro de cada uno. Con esta base puedes depurar la red de Docker o Kubernetes entrando en sus namespaces con nsenter, ejecutar servicios con systemd dentro de un namespace mediante la opción NetworkNamespacePath= o conectar namespaces de distintos servidores llevando el bridge a una red overlay VXLAN.
