Problema
Estou enfrentando um comportamento bastante estranho, em que uma rota de gateway padrão aparentemente não relacionada está causando efeitos colaterais inesperados. Consegui replicar esse problema com um exemplo mínimo. O objetivo aqui é principalmente educacional e me deparei com isso enquanto experimentava um cenário mais complexo. Resumindo, estou conseguindo me conectar a um servidor web em 192.168.0.3 quando acredito que não deveria.
Meu laptop está conectado à minha rede doméstica usando WiFi ( rede 192.168.0.0/24 ). A tabela de roteamento está listada abaixo:
kevin@kevin-UX305LA:~$ ip route
default via 192.168.0.1 dev wlp2s0 proto dhcp metric 600
169.254.0.0/16 dev wlp2s0 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
192.168.0.0/24 dev wlp2s0 proto kernel scope link src 192.168.0.210 metric 600
Ambos curl 192.168.0.3
e curl --interface wlp2s0 192.168.0.3
atualmente funcionam e dão a seguinte resposta:
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.25.2</center>
</body>
</html>
Agora, prossigo removendo todas as rotas relacionadas à rede 192.168.0.0/24 de forma que as rotas restantes sejam:
kevin@kevin-UX305LA:~$ sudo ip route del default via 192.168.0.1
kevin@kevin-UX305LA:~$ sudo ip route del 192.168.0.0/24
kevin@kevin-UX305LA:~$ ip route
169.254.0.0/16 dev wlp2s0 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
Além disso, ip route get
com e sem ligação a uma interface são mostrados abaixo:
kevin@kevin-UX305LA:~$ ip route get 192.168.0.3
RTNETLINK answers: Network is unreachable
kevin@kevin-UX305LA:~$ ip route get oif wlp2s0 192.168.0.3
192.168.0.3 dev wlp2s0 src 192.168.0.210 uid 1000
cache
Correr curl 192.168.0.3
dá curl: (7) Couldn't connect to server
e correr curl --interface wlp2s0 192.168.0.3
não dá nada (o curl está bloqueado). Aqui está um trecho do que o strace mostra:
setsockopt(5, SOL_SOCKET, SO_BINDTODEVICE, "wlp2s0\0", 7) = 0
connect(5, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("192.168.0.3")}, 16) = -1 EINPROGRESS (Operation now in progress)
Tudo bem e o que eu esperava, ou seja, o laptop não deveria atingir 192.168.0.3 .
Agora aqui está a parte estranha. Se eu adicionar um gateway padrão fictício (digamos, a interface docker0 por meio de um endereço aleatório), de forma que minha tabela de roteamento seja a seguinte:
kevin@kevin-UX305LA:~$ sudo ip route add default via 172.17.0.2
kevin@kevin-UX305LA:~$ ip route
default via 172.17.0.2 dev docker0 linkdown
169.254.0.0/16 dev wlp2s0 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
A execução curl 192.168.0.3
falha, mas a execução curl --interface wlp2s0 192.168.0.3
é bem-sucedida com a resposta HTML anterior.
kevin@kevin-UX305LA:~$ curl 192.168.0.3
curl: (7) Failed to connect to 192.168.0.3 port 80 after 3068 ms: No route to host
kevin@kevin-UX305LA:~$ ip route get 192.168.0.3
192.168.0.3 via 172.17.0.2 dev docker0 src 172.17.0.1 uid 1000
cache
kevin@kevin-UX305LA:~$ curl --interface wlp2s0 192.168.0.3
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.25.2</center>
</body>
</html>
kevin@kevin-UX305LA:~$ ip route get oif wlp2s0 192.168.0.3
192.168.0.3 dev wlp2s0 src 192.168.0.210 uid 1000
cache
Pesquisei sobre SO_BINDTODEVICE
e descobri que ainda deveria estar obedecendo a tabela de roteamento. Por que a adição de um gateway padrão por meio de uma interface diferente por meio de um endereço aleatório é curl --interface wlp2s0 192.168.0.3
bem-sucedida?
Detalhes do Endereço
O comando a seguir lista os endereços associados às interfaces. Verifiquei que após cada comando executado acima, o resultado deste comando é sempre o mesmo.
kevin@kevin-UX305LA:~$ ip -br addr
lo UNKNOWN 127.0.0.1/8 ::1/128
wlp2s0 UP 192.168.0.210/24 fe80::7a03:3420:b8b0:4db7/64
docker0 DOWN 172.17.0.1/16
Em resumo , 192.168.0.210 é este laptop, 192.168.0.3 é outra máquina na rede que hospeda um servidor web e 192.168.0.1 é o gateway padrão (meu roteador).
Ambiente
Estou executando o Linux Mint. Aqui estão algumas informações.
kevin@kevin-UX305LA:~$ uname -a
Linux kevin-UX305LA 5.15.0-84-generic #93-Ubuntu SMP Tue Sep 5 17:16:10 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
kevin@kevin-UX305LA:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Linuxmint
Description: Linux Mint 21.2
Release: 21.2
Codename: victoria
Ao vincular à interface, a menos que rotas adequadas sejam encontradas nesta interface e depois reutilizadas, uma rota padrão implícita sem gateway será adicionada nesta interface ao resolver o resultado do roteamento . Portanto, isso só importa quando não existe essa rota padrão presente.
Nota: a falta de gateway pode causar problemas com interfaces da camada 2 (por exemplo: tentar alcançar 8.8.8.8 enviaria uma solicitação ARP para 8.8.8.8. Não causaria problemas com interfaces da camada 3, como túneis IP que não precisa de um gateway. Como nesta questão o destino está na LAN de qualquer maneira e, portanto, não requer um gateway, isso também não causa problemas.
O efeito dos
curl
comandos pode ser verificado consultando o kernel para obter o resultado da rota, como abaixo, usandoip route get
:que resolve bem para chegar a 192.168.0.3.
Para evitar isso, pode-se adicionar uma regra de política que selecionará uma rota alternativa somente quando vinculada à interface com o
oif
seletor. Normalmente isso seria usado para fornecer uma rota com um gateway adequado para resolver o problema descrito acima com falta de gateway, por exemplo assim:Mas para este caso preciso, o objetivo é evitar que tal tipo de rota exista, o que é mais difícil, pois
oif wlp2s0
mantém apenas rotas que contêmdev wlp2s0
: simplesmente adicionar um buraco negro ou uma rota inacessível não pode incluir tambémdev wlp2s0
, então será sempre ignorado. Em vez disso, isso requer uma rota falsa que fará com que o resultado final falhe. Escolher seu próprio endereço como gateway é o mesmo que não escolher nenhum gateway: também não funcionará. Portanto, isso exige a escolha de um endereço arbitrário na LAN que com certeza não existe (para evitar que ele faça um redirecionamento ICMP caso também seja um roteador). Vamos supor que 192.168.0.4 não exista e estará reservado para este uso:que agora obtém:
que acionará uma solicitação ARP para 192.168.0.4 e falhará ~ 3 segundos depois (o atraso máximo padrão do ARP para tal tentativa) com um
EHOSTUNREACH
(Sem rota para o host):Observe que o endereço IP arbitrário nem precisa estar dentro de 192.168.0.0/24. Isso teria funcionado da mesma forma, desde que a solicitação ARP falhasse no final:
Nota: o mesmo truque não funcionaria com uma interface de camada 3 (por exemplo: WireGuard ou OpenVPN no modo tun), pois não há conceito de gateway nela.
ATUALIZAR
Resolvendo o problema do OP
Não abordei antes o problema que o OP tinha: a conectividade estava falhando em vez de ter sucesso, e adicionar uma rota padrão em outro lugar fez a tentativa funcionar.
A razão é que por enquanto apenas metade da comunicação foi verificada: do host para o servidor, que poderia emitir um pacote com sucesso, e não retornar o tráfego do servidor para o host que não foi considerado.
Também foi descoberto (através do chat) que o OP está usando estas
rp_filter
configurações:que define
wlp2s0
as interfaces no modo Loose Reverse Path Forwarding definido no RFC 3704 .Reverse Path Forwarding (RPF) verifica o caminho reverso : o tráfego de retorno do servidor para o host é compatível com o caminho "normal", ou seja, a rota do host para o servidor.
Portanto, quando há uma rota padrão (que é o cenário mais comum), o Loose RPF sempre é bem-sucedido. Sem a presença de uma rota padrão nem de qualquer rota para o destino, ele falhará. Forçar uma interface sempre funciona para o tráfego de saída, mas isso não altera o tráfego reverso recebido do servidor para o host. A recepção desse tráfego de entrada é independente do fato de o tráfego de saída ter sido forçado para uma interface e não pode ser responsável por tal caso. Novamente, isso pode ser verificado
ip route get
quando não há rota padrão em nenhum lugar. Começando pelo caso do OP sem regra adicional:Invalid cross-device link
é o erro escolhido pela pilha de rede para informar que o RPF falhou: tal caminho é inválido, ou seja, qual deve ser o tráfego de retorno do servidor. Ter o padrão implícito selecionado ao vincular a uma interface não afeta o tráfego de entrada , portanto não altera este resultado: tempo limite (mas não devido ao ARP, portanto não limitado a 3s).A mesma verificação com
rp_filter=0
:funciona bem, pois a verificação não é mais feita.
Caso contrário, voltando para
rp_filter=2
, fazendo o seguinte:também faz funcionar.
Apenas adicionar uma rota errada para 192.168.0.3 faz com que ele passe no algoritmo para Loose RPF. É claro que adicionar uma rota padrão teria o mesmo efeito: enquanto houver uma rota para 192.168.0.3, o Loose RPF será aprovado.
Portanto, desative a
rp_filter
verificação completamente como feito acima ou tenha qualquer rota para 192.168.0.3 onde quer que esteja, incluindo uma rota padrão usando outra interface.