O que curl --interface tun0
faz? De acordo com man: Perform an operation using a specified interface. You can enter interface name, IP address or host name.
Parece estar usando SO_BINDTODEVICE para conseguir isso. Aqui está o que me deixa confuso: pelo que entendi, quando um programa quer enviar um pacote para um destino específico, cabe à tabela de roteamento decidir qual interface usar como origem. Esta opção parece substituir isso de alguma forma. Então o que acontece se não houver uma rota (na tabela de roteamento), que usaria essa interface e alcançaria esse destino?
Caso um (um túnel que foi criado pela sing-box):
ip a:
tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 9000 qdisc fq state UNKNOWN group default qlen 500
link/none
inet 172.18.0.1/30 brd 172.18.0.3 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::717d:757a:23db:789b/64 scope link stable-privacy
valid_lft forever preferred_lft forever
ip route:
172.18.0.0/30 dev tun0 proto kernel scope link src 172.18.0.1
curl example.com --interface tun0
faz o que deve fazer (como, no entanto?) e solicita example.com por meio de tun0.
Caso dois (interface wireguard):
ip a:
wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.230.116.1/24 scope global wg0
valid_lft forever preferred_lft forever
ip route:
10.230.116.0/24 dev wg0 proto kernel scope link src 10.230.116.1
O resultado é:connect to 142.44.215.161 port 80 failed: No route to host
Como ele sabe que, de fato, não há rota até lá?
Entendo que esses túneis são operados por programas de espaço do usuário (ou kernelspace). Isso é como um FuseFS onde esses programas podem simplesmente retornar "não, não permitido" quando não gostam de um IP de destino (ou "sim, posso fazer isso de qualquer maneira", mesmo que, de acordo com a tabela de roteamento, eles não devam enviar para lá)? Isso significa que a tabela de roteamento é completamente ignorada quando SO_BINDTODEVICE é usado? E se eu usasse eth0 em vez de tun0? É uma interface física, ela usaria a tabela de roteamento para descobrir se é possível rotear esse pacote? Finalmente, é possível fazer curl example.com --interface 172.18.0.1
. Surpreendentemente, isso não é equivalente a --interface tun0
, e não tem sucesso. Por que isso?
A opção
--interface <name>
chamasetsockopt(SO_BINDTODEVICE)
o que parece ter os seguintes efeitos no Linux:Uma busca de rota ainda é realizada – considerando apenas rotas por meio dessa interface – e se houver uma correspondência, a rota é usada normalmente. Por exemplo, se você tiver duas rotas padrão (aquela via eth0 tendo a métrica mais baixa e sendo a geralmente preferida), então
--interface wlan0
resultará na rota padrão via wlan0 sendo usada.Se não houver uma rota correspondente, no entanto, uma rota on-link
0.0.0.0/0 dev wlan0
é assumida (por exemplo , sem um gateway). Isso tem resultados diferentes para interfaces que usam endereçamento de camada de link (L2, camada MAC) vs interfaces que não usam:Ethernet e Wi-Fi (e
tap
interfaces de software) são interfaces de acesso múltiplo que exigem um endereço MAC para entregar um pacote em algum lugar (ele não irá "apenas para um gateway" sozinho), então as rotas especificam um "gateway" para direcionar os pacotes para o endereço MAC desse gateway.Nesse caso, quando a rota on-link é assumida, o host tentará fazer uma solicitação ARP para resolver o endereço IP de destino para um endereço MAC e, na maioria das vezes, nunca receberá uma resposta.
(A menos que seja uma rede muito oldschool, onde o gateway executa Proxy-ARP em nome de tudo – os gateways Cisco costumavam ter o proxy ARP habilitado por um longo tempo – e nesse caso o gateway responderá em nome do endereço IP da WAN e a conexão acontecerá. Mas isso é extremamente raro hoje em dia.)
Em contraste,
tun
interfaces (assim como interfaceswg
eppp
) são interfaces ponto a ponto que têm apenas um destino possível e, portanto, não usam ARP.Nesse caso, não há diferença entre uma rota on-link e uma rota de gateway (já que não há endereçamento MAC) – os pacotes IP são simplesmente empurrados pela interface e vão "para a outra extremidade" – e as comunicações sempre funcionarão.
(No entanto, a lógica interna do WireGuard ainda pode rejeitar o pacote com um código de erro se não houver um mapeamento "AllowedIPs" para um peer ou se o endereço externo desse peer não for conhecido.)
Desculpe, não analisei o código-fonte do kernel dessa vez e não verifiquei se meu palpite no passo 1 estava correto.
Por outro lado, se você usar
--interface <ip>
, algo completamente diferente acontece: o soquete não é vinculado a nenhuma interface, mas sim ao endereço IP local usando abind()
chamada.Os efeitos disso variam muito entre os sistemas operacionais, mas no Linux isso não influencia o roteamento em nada – mesmo que o pacote seja enviado do endereço da interface A, ele ainda pode ser roteado pela interface B ou C.
(É o "modelo de host fraco" e tem suas vantagens em certas situações, mas não é o mais comum atualmente, em que as duas interfaces são conectadas a redes totalmente diferentes. Pense mais em um antigo host Unix cujas diferentes interfaces eram meramente caminhos diferentes pela mesma rede ARPANET.)
Então, para
--interface <ip>
funcionar como você espera, um sistema Linux praticamente precisa de regras de roteamento de política que selecionam uma tabela de roteamento totalmente diferente dependendo do endereço de origem. Por exemplo, alguns softwares de VPN adicionam uma (acredito que o wg-quick do WireGuard geralmente faz isso), para que os soquetes vinculados pelo endereço IP wg0 usem uma tabela de roteamento distinta que tenha apenas rotas por wg0, e assim por diante. Elas são definidas viaip rule
.Os túneis do espaço do usuário podem apenas descartar o pacote ou podem emitir um pacote de erro ICMP (falso) em nome de um gateway (imaginário), mas não podem rejeitar retroativamente um pacote que já foi roteado e "enviado" por meio de sua interface (a leitura do descritor de arquivo do espaço do usuário constitui aceitação).
wg0
tem menos limitações, devido a ser manipulado pela lógica do kernel, e pode responder com seus próprios códigos de erro. Nem todos eles são passados como estão para soquetes de alto nível (TCP); apenas coisas comoping
usar sendmsg() de nível "IP bruto" parecem ser capazes de vê-lo, enquanto TCP connect() vê códigos de erro diferentes – de fato resultando em "Nenhuma rota para o host" quando o núcleo do WireGuard retorna EDESTADDRREQ "Endereço de destino necessário" ou similar.