Tenho um sistema debian 10 razoavelmente padrão configurado como um roteador (echo 1 > /proc/sys/net/ipv4/ip_forward) com uma interface WAN (=enp11s0) e uma interface DMZ (=enp10s0). A interface WAN tem alguns endereços IP públicos vinculados a ela, adicionados com, por exemplo
ip addr add 81.2.3.4/25 brd + dev enp11s0
ip addr add 81.2.3.5/25 brd + dev enp11s0
ip addr add 81.2.3.6/25 brd + dev enp11s0
ip addr add 81.2.3.7/25 brd + dev enp11s0
A interface DMZ tem um ip local atribuído, 10.2.10.10. Os nomes dos servidores, zz, zz-dmz etc abaixo são todos declarados em /etc/hosts.
Configurei o NFT para fazer DNAT no gancho de pré-roteamento:
flush ruleset
define DMZ = enp10s0
define WAN = enp11s0
define WAN_NET = 81.2.3.0/25
define wan2dmz_map = {
www : www-dmz,
zz : zz-dmz,
dns0 : dns0-dmz,
dns1 : dns1-dmz,
drift78 : drift78-dmz
}
define dmz2wan_map = {
www-dmz : www,
zz-dmz : zz,
dns0-dmz : dns0,
dns1-dmz : dns1,
drift78-dmz : drift78
}
table ip fail2ban {
chain input {
type filter hook forward priority 0;
}
}
table ip global {
map ip_mapWD {
type ipv4_addr : ipv4_addr
elements = $wan2dmz_map
}
map ip_mapDW {
type ipv4_addr : ipv4_addr
elements = $dmz2wan_map
}
# Accepted WAN ports
chain SRV_ACCEPT {
ip daddr www-dmz tcp dport {http,https} counter accept
ip daddr zz-dmz tcp dport {http,https,smtp,pop3,imap2,submission,imaps,465} accept
ip daddr drift78-dmz accept
ip daddr dns0-dmz tcp dport 53 accept
ip daddr dns1-dmz tcp dport 53 accept
}
chain input {
type filter hook input priority 0; policy drop;
iif {lo,$DMZ} accept
ct state established,related accept
ct state invalid drop
ct status dnat accept
ip saddr 127.0.0.0/8 drop
ip protocol icmp icmp type echo-request limit rate over 1/second burst 5 packets drop
ip frag-off & 0x1fff != 0 counter drop
tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop
ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, router-solicitation, router-advertisement, tr-problem } accept
}
chain output {
type filter hook output priority 0;
}
chain forward {
type filter hook forward priority 0; policy drop;
iif {lo, $DMZ} accept
ct state established,related accept
ct state invalid drop
ct status dnat accept
ip protocol icmp icmp type echo-request limit rate over 1/second burst 5 packets drop
ip frag-off & 0x1fff != 0 counter drop
tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop
ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, router-solicitation, router-advertisement, tr-problem } accept
# Accept selected dest. ip/ports from WAN
iif $WAN jump SRV_ACCEPT
# VPN
ip saddr 10.8.0.0/8 iifname tun0 accept
# Log denied on WAN
iif $WAN log prefix "[NFT] WAN: "
}
chain prerouting {
type nat hook prerouting priority -100; policy accept;
# Hairpin
iif {lo,$DMZ} fib daddr type local dnat to ip daddr map @ip_mapWD
# From WAN, change dest. to DMZ
iif $WAN ip daddr $WAN_NET counter dnat to ip daddr map @ip_mapWD
}
chain postrouting {
type nat hook postrouting priority 100;
oif {tun0,$DMZ} counter masquerade;
}
}
(zz é um ip público para o servidor zz, e zz-dmz é um ip privado na rede 10.2.10.x).
Isso funciona: eu posso alcançar zz-dmz do lado da WAN sondando zz. Na verdade, ainda não tentei o hairpin.
Agora o problema: Serviços locais na máquina debian 10 (por exemplo, exim4) procuram o ip público ("zz") como mx para seu domínio e tentam se conectar ao seu ip público, mas os pacotes não são roteados novamente para zz-dmz. Em vez disso, eles ficam presos na interface lo, saltando do endereço ip primário na interface WAN para o ip público "zz" que (obviamente) não tem serviço respondendo localmente. Isso pode ser visto fazendo login no roteador debian e digitando
root@opax:~/firewall# telnet zz 25
Trying 81.2.3.4...
telnet: Unable to connect to remote host: Connection refused
Tcpdump mostra o mais compacto em lo. Eu pensei que a regra
iif {lo,$DMZ} fib daddr type local dnat to ip daddr map @ip_mapWD
substituiria o IP de destino no lo também e os redirecionaria para $DMZ?
O que estou perdendo?