Pular para o conteúdo
, , , , ,

Redes no Firecracker: Configurando TAP, NAT e Internet para seu Nano-Lambda [pt.3]

Aprenda a configurar TAP, NAT e iptables no Firecracker. Dê internet às suas microVMs para criar um validador de URLs real, mantendo isolamento total ao bloquear o acesso à rede local.

Avatar de DK
DKTrabalha com Linux e Unix a mais de 23 anos e possui as certificações LPI 3, RHCE, AIX e VIO.

08 dez, 2025
9 min de leitura

Série Firecracker:


No artigo anterior, construímos um nano-Lambda que executa funções Python em microVMs isoladas. Gerou QR Codes lindos. Mas tinha uma limitação: a VM era uma ilha. Sem rede, sem internet, sem comunicação com o mundo exterior.

Hoje vamos resolver isso. Vamos dar internet pro nosso nano-Lambda.

E pra provar que funciona de verdade, vamos construir algo útil: um validador de URLs. Você passa uma lista de sites, a microVM acorda, dispara requests em paralelo, e retorna um JSON com o status de cada um. Simples, prático, e impossível de fazer sem rede.

Por que rede em microVMs é diferente?

Quando você roda um container Docker, a rede “simplesmente funciona”. Docker cria bridges, configura NAT, gerencia IPs… tudo automático. Você nem pensa nisso.

Com Firecracker, você pensa. Muito.

Firecracker é minimalista até na rede. Ele suporta um único tipo de dispositivo de rede: virtio-net. E ele espera que você, configure tudo do lado do host. O Firecracker só conecta a VM a uma interface TAP que você criou.

Isso parece trabalhoso (e é), mas tem um lado bom: você entende exatamente o que tá acontecendo. Sem mágica, sem camadas escondidas. E isso é ótimo pra segurança — você controla cada pacote que entra e sai.

O que vamos construir

Nosso objetivo final:

  1. Criar uma interface TAP no host
  2. Configurar NAT pra VM acessar a internet
  3. Configurar DNS dentro da VM
  4. Atualizar o nano-Lambda pra suportar rede
  5. Criar um validador de URLs que funciona de verdade

Bônus: vamos bloquear acesso da VM à rede local, permitindo apenas internet pública. Isolamento de verdade.

Conceitos rápidos: TAP, Bridge e NAT

Antes de meter a mão na massa, um glossário rápido:

TAP: É uma interface de rede virtual. Pensa nela como um “cabo de rede imaginário”. De um lado, a microVM acha que tá conectada numa placa de rede real. Do outro lado, o host Linux vê uma interface de rede normal que pode rotear, filtrar, fazer NAT…

Bridge: Conecta várias interfaces de rede como se fossem um switch. Útil quando você quer que várias VMs conversem entre si.

NAT (Network Address Translation): Permite que a VM use o IP do host pra acessar a internet. A VM tem um IP privado (tipo 172.16.0.2), mas quando ela acessa google.com, o pacote sai com o IP público do host.

Pra nosso caso, vamos usar a configuração mais simples: TAP + NAT. Uma interface TAP conectada diretamente ao host, com masquerading pra internet.

Diagrama de rede do Firecracker mostrando o fluxo da MicroVM para internet e bloqueio da rede local

O diagrama mostra exatamente o que vamos construir: a App Python dentro da MicroVM se comunica via eth0 (172.16.0.2), que conecta na interface tap0 do host. O firewall faz NAT pra internet pública, mas bloqueia acesso à rede local (192.168.x.x). Isolamento de verdade.

Nota sobre firewalls: Neste artigo uso firewalld (padrão no Fedora). Se você usa Ubuntu, o sistema usa iptables. Os scripts do repositório detectam automaticamente qual firewall está ativo e usam os comandos corretos. Se preferir seguir com iptables, os conceitos são os mesmos — apenas a sintaxe muda.

Passo 1: Criando a interface TAP

Primeiro, precisamos criar uma interface TAP. Isso é feito com o comando ip tuntap:

# Cria a interface TAP
sudo ip tuntap add dev tap0 mode tap

# Atribui um IP pro lado do host
sudo ip addr add 172.16.0.1/24 dev tap0

# Sobe a interface
sudo ip link set tap0 up

Pronto. Agora temos uma interface tap0 com IP 172.16.0.1. A VM vai usar 172.16.0.2.

Pra verificar:

ip addr show tap0

Deve mostrar algo como:

tap0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 ...
    inet 172.16.0.1/24 scope global tap0

Passo 2: Configurando NAT no host

Agora precisamos que o host faça NAT — traduza os pacotes da VM pro IP do host quando eles saem pra internet.

# Habilita IP forwarding (permite que o host roteie pacotes)
sudo sysctl -w net.ipv4.ip_forward=1

# Adiciona a interface TAP à zona trusted (permite todo tráfego dela)
sudo firewall-cmd --zone=trusted --add-interface=tap0

# Habilita masquerading (NAT) pra saída
sudo firewall-cmd --add-masquerade

Simples assim. O firewalld cuida do resto. A zona trusted permite todo tráfego da TAP, e o masquerade faz o NAT automático.

Dica: Pra descobrir sua interface de saída, use ip route | grep default. A interface aparece depois de “dev”.

Bloqueando acesso à rede local (segurança)

Aqui tá o ouro do isolamento. Queremos que a VM acesse a internet pública, mas NÃO a sua rede local. Imagina se alguém mandar um código malicioso que tenta escanear sua rede interna?

# Bloqueia acesso a redes privadas usando rich rules
sudo firewall-cmd --zone=trusted --add-rich-rule='rule family=ipv4 source address=172.16.0.0/24 destination address=10.0.0.0/8 drop'
sudo firewall-cmd --zone=trusted --add-rich-rule='rule family=ipv4 source address=172.16.0.0/24 destination address=192.168.0.0/16 drop'

Agora a VM pode acessar google.com, mas não pode acessar 192.168.1.1 (seu roteador) ou qualquer máquina na sua rede.

Versão com iptables (Ubuntu) Se você usa Ubuntu ou prefere iptables: “`bash # Habilita IP forwarding sudo sysctl -w net.ipv4.ip_forward=1 # Configura masquerading (NAT) – substitua eth0 pela sua interface sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # Permite tráfego da TAP pro mundo sudo iptables -A FORWARD -i tap0 -o eth0 -j ACCEPT sudo iptables -A FORWARD -i eth0 -o tap0 -m state –state RELATED,ESTABLISHED -j ACCEPT # Bloqueia acesso a redes privadas sudo iptables -I FORWARD -i tap0 -d 10.0.0.0/8 -j DROP sudo iptables -I FORWARD -i tap0 -d 172.16.0.0/12 -j DROP sudo iptables -I FORWARD -i tap0 -d 192.168.0.0/16 -j DROP # Mas permite a própria subnet da VM sudo iptables -I FORWARD -i tap0 -d 172.16.0.0/24 -j ACCEPT “`

Passo 3: Configurando o Firecracker pra usar a TAP

Quando iniciar o Firecracker, precisamos dizer pra ele usar a interface TAP. Isso é feito via API:

curl --unix-socket /tmp/firecracker.socket -X PUT \
  "http://localhost/network-interfaces/eth0" \
  -H "Content-Type: application/json" \
  -d '{
    "iface_id": "eth0",
    "guest_mac": "AA:FC:00:00:00:01",
    "host_dev_name": "tap0"
  }'

Isso conecta a interface eth0 dentro da VM à interface tap0 do host.

Passo 4: Configurando a rede dentro da VM

Agora a parte que pega muita gente: configurar a rede DENTRO da microVM. O Firecracker só conecta o “cabo”. Quem configura IP, gateway e DNS é o sistema dentro da VM.

Precisamos atualizar nosso rootfs pra configurar a rede no boot. Primeiro, vamos montar a imagem:

# Cria um diretório temporário para montar
sudo mkdir -p /mnt/rootfs

# Monta a imagem ext4
sudo mount rootfs-python.ext4 /mnt/rootfs

# Agora você pode editar os arquivos dentro de /mnt/rootfs

Com o rootfs montado, vamos adicionar as configurações de rede:

# Configura interface de rede estática
sudo cat > /mnt/rootfs/etc/network/interfaces << 'EOF'
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 172.16.0.2
    netmask 255.255.255.0
    gateway 172.16.0.1
EOF

# DNS - crucial pra resolver nomes
sudo cat > /mnt/rootfs/etc/resolv.conf << 'EOF'
nameserver 8.8.8.8
nameserver 1.1.1.1
EOF

O truque aqui é o /etc/resolv.conf. Sem ele, ping google.com falha mesmo com a rede funcionando. A VM precisa saber onde perguntar “qual o IP de google.com?”.

Atualizando o script de boot

Também precisamos garantir que a interface suba antes de rodar nossa função. Atualize o /mnt/rootfs/run-function.sh:

#!/bin/sh
echo "Configurando rede"

# Sobe a interface de rede
ip link set eth0 up
ip addr add 172.16.0.2/24 dev eth0
ip route add default via 172.16.0.1

echo "Testando conectividade..."
ping -c 1 8.8.8.8 > /dev/null 2>&1 && echo "Internet OK" || echo "Sem internet"

echo ""
echo "nano-Lambda executando..."
echo ""

if [ -f /functions/handler.py ]; then
    cd /functions
    python3 handler.py
    RETVAL=$?
    echo ""
    echo "Execução finalizada (exit: $RETVAL)"
else
    echo "ERRO: handler.py não encontrado"
fi

echo ""
sync
sleep 1
poweroff -f

Depois de fazer todas as alterações, desmonte o rootfs:

# Desmonta o rootfs
sudo umount /mnt/rootfs

Passo 5: Atualizando o build-rootfs.sh

Vamos atualizar o script de build do rootfs pra incluir as configurações de rede:

# Adiciona pacotes de rede
apk add --root /rootfs --no-cache \
    python3 \
    py3-requests \
    py3-urllib3 \
    ca-certificates

O ca-certificates é importante pra HTTPS funcionar. Sem ele, requests.get('https://google.com') vai reclamar de certificado inválido.

Importante: Se você já tem um rootfs-python.ext4 do artigo anterior, apague-o antes de rodar o build novamente. O rootfs antigo não tem as dependências de rede (requests, ca-certificates). Sem isso, a função vai falhar com ModuleNotFoundError.

rm -f rootfs-python.ext4
sudo ./build-rootfs-network.sh

Passo 6: O Validador de URLs

Agora a função que vai provar que tudo funciona. Salve como exemplo-validador/handler.py:

#!/usr/bin/env python3
"""
Função nano-Lambda: Validador de URLs

Lê uma lista de URLs de /functions/input.txt e retorna o status HTTP de cada uma.
Usa requests em paralelo pra performance.
"""

import json
import sys
import ssl
import socket
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed

try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    import urllib.request
    import urllib.error
    HAS_REQUESTS = False

def check_url_requests(url):
    """Verifica URL usando requests."""
    try:
        resp = requests.head(url, timeout=10, allow_redirects=True)
        return {
            "url": url,
            "status": resp.status_code,
            "ok": resp.ok
        }
    except requests.exceptions.SSLError as e:
        return {"url": url, "status": "SSL_ERROR", "ok": False, "error": str(e)}
    except requests.exceptions.ConnectionError:
        return {"url": url, "status": "CONNECTION_ERROR", "ok": False}
    except requests.exceptions.Timeout:
        return {"url": url, "status": "TIMEOUT", "ok": False}
    except Exception as e:
        return {"url": url, "status": "ERROR", "ok": False, "error": str(e)}

def check_url_urllib(url):
    """Verifica URL usando urllib (fallback)."""
    try:
        req = urllib.request.Request(url, method='HEAD')
        with urllib.request.urlopen(req, timeout=10) as resp:
            return {
                "url": url,
                "status": resp.getcode(),
                "ok": 200 <= resp.getcode() < 400
            }
    except urllib.error.HTTPError as e:
        return {"url": url, "status": e.code, "ok": False}
    except urllib.error.URLError as e:
        return {"url": url, "status": "URL_ERROR", "ok": False, "error": str(e.reason)}
    except Exception as e:
        return {"url": url, "status": "ERROR", "ok": False, "error": str(e)}

def check_ssl_expiry(url):
    """Verifica se o certificado SSL vai expirar em 30 dias."""
    try:
        hostname = url.replace("https://", "").replace("http://", "").split("/")[0]
        context = ssl.create_default_context()

        with socket.create_connection((hostname, 443), timeout=10) as sock:
            with context.wrap_socket(sock, server_hostname=hostname) as ssock:
                cert = ssock.getpeercert()

        not_after = cert['notAfter']
        expire_date = datetime.strptime(not_after, '%b %d %H:%M:%S %Y %Z')
        days_left = (expire_date - datetime.now()).days

        return {
            "expires": not_after,
            "days_left": days_left,
            "warning": days_left < 30
        }
    except Exception as e:
        return {"error": str(e)}

def main():
    try:
        with open('/functions/input.txt', 'r') as f:
            content = f.read().strip()
    except FileNotFoundError:
        print("ERRO: /functions/input.txt não encontrado")
        sys.exit(1)

    # Parseia URLs (uma por linha ou separadas por vírgula)
    urls = [u.strip() for u in content.replace(',', '\n').split('\n') if u.strip()]

    if not urls:
        print("ERRO: Nenhuma URL fornecida")
        sys.exit(1)

    print(f"Validando {len(urls)} URLs...")

    # Escolhe função de verificação
    check_func = check_url_requests if HAS_REQUESTS else check_url_urllib

    results = []
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = {executor.submit(check_func, url): url for url in urls}

        for future in as_completed(futures):
            result = future.result()

            if result["url"].startswith("https://") and result.get("ok"):
                result["ssl"] = check_ssl_expiry(result["url"])

            results.append(result)
            print(f"  {result['url']}: {result['status']}")

    report = {
        "timestamp": datetime.now().isoformat(),
        "total": len(urls),
        "ok": sum(1 for r in results if r.get("ok")),
        "failed": sum(1 for r in results if not r.get("ok")),
        "results": results
    }

    print("")
    print("JSON_RESULT_START")
    print(json.dumps(report, indent=2))
    print("JSON_RESULT_END")

if __name__ == '__main__':
    main()

Essa função:

  1. Lê URLs do input (uma por linha ou separadas por vírgula)
  2. Verifica cada uma em paralelo usando concurrent.futures
  3. Pra URLs HTTPS, também verifica se o certificado SSL expira em menos de 30 dias
  4. Retorna um JSON estruturado com os resultados

Passo 7: Atualizando o nano-lambda.py

Precisamos atualizar o nano-Lambda pra configurar a rede antes de iniciar a VM.

Cuidado com duplicação de regras: Se você rodar o script 10 vezes, não queremos 10 regras idênticas poluindo o firewall. A solução é verificar se a interface já existe antes de configurar:

def setup_network(self):
    """Configura a interface TAP e NAT."""
    # Evita duplicar regras se já estiver configurado
    if os.path.exists("/sys/class/net/tap0"):
        print("[*] Rede já configurada, pulando setup...")
        return

    print("[*] Configurando rede...")

    subprocess.run(
        ["ip", "tuntap", "add", "dev", "tap0", "mode", "tap"],
        check=True
    )

    subprocess.run(
        ["ip", "addr", "add", "172.16.0.1/24", "dev", "tap0"],
        check=True
    )

    subprocess.run(["ip", "link", "set", "tap0", "up"], check=True)

    subprocess.run(
        ["sysctl", "-w", "net.ipv4.ip_forward=1"],
        check=True, capture_output=True
    )

    # Configura firewall (firewalld no Fedora)
    subprocess.run([
        "firewall-cmd", "--zone=trusted", "--add-interface=tap0"
    ], check=False)

    subprocess.run([
        "firewall-cmd", "--add-masquerade"
    ], check=False)

def configure_vm(self):
    """Configura a microVM via API."""

    print("[*] Configurando rede da VM...")
    self._call_api("PUT", "/network-interfaces/eth0", {
        "iface_id": "eth0",
        "guest_mac": "AA:FC:00:00:00:01",
        "host_dev_name": "tap0"
    })

Nota: O script completo no repositório detecta automaticamente se o sistema usa firewalld ou iptables e configura corretamente. Se você usa Ubuntu, o script vai usar iptables sem precisar mudar nada.

Testando!

Com tudo configurado:

# Cria arquivo de input com URLs pra testar
cat > urls.txt << 'EOF'
https://google.com
https://fogonacaixadagua.com.br
https://github.com
https://site-que-nao-existe.xyz
EOF

# Executa o validador
sudo python3 nano-lambda.py exemplo-validador/handler.py "$(cat urls.txt)"

Se tudo funcionar, você verá:

==================================================
nano-Lambda: Executando função em microVM isolada
==================================================
Função: exemplo-validador/handler.py
Input: https://google.com
https://fogonacaixadagua.com.br
...

[*] Configurando rede...
[*] Copiando rootfs template...
[*] Iniciando Firecracker...
[*] Configurando kernel...
[*] Configurando rootfs...
[*] Configurando rede da VM...
[*] Iniciando microVM...
[*] Aguardando execução...

Validando 4 URLs...
  https://google.com: 200
  https://fogonacaixadagua.com.br: 200
  https://github.com: 200
  https://site-que-nao-existe.xyz: CONNECTION_ERROR

==================================================
Resultado:
==================================================
{
  "timestamp": "2024-12-08T10:30:00",
  "total": 4,
  "ok": 3,
  "failed": 1,
  "results": [...]
}

O que aprendemos

Configurar rede no Firecracker é mais trabalhoso que no Docker, mas você ganha:

  1. Controle total: Você sabe exatamente o que entra e sai
  2. Isolamento real: A VM pode acessar internet mas não sua rede local
  3. Segurança: Código malicioso não consegue escanear sua rede interna

Esse é o tipo de isolamento que faz sentido pra executar código não confiável. A VM pode baixar o que quiser da internet, mas tá completamente bloqueada de acessar qualquer coisa na sua rede.

Troubleshooting

VM não consegue pingar nada:

  • Verifique se net.ipv4.ip_forward=1 está ativo: sysctl net.ipv4.ip_forward
  • Verifique se masquerading está ativo: firewall-cmd --query-masquerade (ou iptables -t nat -L -n no Ubuntu)
  • Verifique se a TAP está na zona trusted: firewall-cmd --zone=trusted --list-interfaces

DNS não funciona (ping IP funciona, ping domínio não):

  • Verifique /etc/resolv.conf dentro da VM
  • Tente com DNS diferente: nameserver 8.8.8.8
  • Em redes corporativas ou VPNs, a porta 53 pode ser bloqueada pra fora. Use o DNS da sua rede local

HTTPS falha com erro de certificado:

  • Verifique se ca-certificates está instalado no rootfs
  • Tente update-ca-certificates dentro da VM

Próximos passos

Agora seu nano-Lambda tem acesso à internet e pode fazer coisas úteis de verdade. Algumas ideias:

  • Web scraper isolado: Baixa páginas, extrai dados, retorna JSON
  • Verificador de APIs: Testa endpoints e mede latência
  • Downloader seguro: Baixa arquivos suspeitos em ambiente isolado

No próximo artigo, vamos explorar snapshots e restore — a tecnologia que permite o Lambda da AWS iniciar em milissegundos. Spoiler: envolve tirar “fotos” da memória da VM.

Até lá!


Este artigo faz parte da série “Firecracker: MicroVMs na Prática”. Todo o código está disponível em GitHub.

Avatar de DK

Comentários

Deixe um comentário

Seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Ir para