fix(freeswitch): 3 achados reais de NAT entre ramais no mesmo escritório — registro, áudio e chamada morrendo em 32s (PHASE 68)

Usuário reportou que 1502 ligando pra 1503 (dois telefones IP reais,
ambos registrados) não completava a chamada. Três bugs em camadas
diferentes, cada um só confirmado lendo o log/capturando pacote real —
nunca por suposição:

1. Registro: os dois ramais registravam com Contact apontando pro MESMO
   IP público compartilhado desta rede (a VM nunca tem IP público
   próprio, é sempre RFC1918 atrás do NAT do escritório) — originar uma
   chamada tentava mandar o INVITE de volta pra esse IP público, hairpin
   NAT clássico, falha instantânea (503). Fix: NDLB-received-in-nat-reg-
   contact (Contact salvo vira o IP realmente observado no pacote).

   De quebra, aplicado network_mode: host no serviço freeswitch (pedido
   explícito do usuário) — tira o Docker NAT/bridge do meio. Quebra em
   cascata corrigida: fs-config vira alcançável só via 127.0.0.1:8080
   (não mais nome de serviço), workers ESL (fs-events/predictive-dialer)
   passam a usar host.docker.internal.

2. Áudio: com o registro corrigido, a chamada completava mas sem RTP —
   local-network-acl="localnet.auto" só cobria a subnet da própria
   interface do FreeSWITCH, tratando ramais de OUTRAS subnets do mesmo
   escritório como "de fora" e trocando o SDP pelo IP público de novo.
   Fix: ACL própria (b2bcall_lan, cobre todo RFC1918) referenciada em
   local-network-acl.

3. Chamada morrendo sozinha em exatos 32s (Timer H do RFC 3261): mesmo
   com áudio ok, o 200 OK que o FreeSWITCH manda pro ramal que recebeu a
   chamada ainda tinha Contact com o IP público — o telefone nunca manda
   o ACK de volta, FreeSWITCH retransmite sozinho até desistir. Só
   confirmado com tcpdump (instalado nesta sessão) capturando o pacote
   byte a byte. Fix: ext-rtp-ip/ext-sip-ip (STUN, sempre resolvem pro IP
   público) removidos do profile "internal" — sem endereço "externo"
   configurado, o FreeSWITCH nunca mais tem como escolher errado.

Confirmado resolvido pelo usuário com chamadas reais nos dois sentidos,
áudio bidirecional, sobrevivendo bem além dos 32s que travavam antes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-31 11:50:51 -03:00
parent 432cd55adb
commit 2134fa0aa4
6 changed files with 290 additions and 47 deletions

View File

@@ -10,5 +10,58 @@
<node type="allow" cidr="::1/128"/>
<node type="allow" cidr="172.16.0.0/12"/>
</list>
<!-- PHASE 68 — achado real: depois de corrigir o registro (NDLB-
received-in-nat-reg-contact, Dockerfile), a chamada completava
mas sem áudio. Causa: `local-network-acl="localnet.auto"`
(padrão vanilla) só cobre a subnet da PRÓPRIA interface do
FreeSWITCH (10.10.32.0/24, com network_mode: host) — os ramais
de verdade estão em OUTRAS subnets do mesmo escritório
(192.168.40.0/24, 192.168.44.0/24), então o FreeSWITCH os
tratava como "fora da rede local" e trocava o RTP/SIP anunciado
no SDP pelo IP público compartilhado (Ext-RTP-IP/Ext-SIP-IP,
191.240.175.55) — o mesmo hairpin NAT de antes, só que na
mídia em vez do registro. Esta VM nunca tem IP público próprio
(é sempre RFC1918 atrás do NAT do escritório), então qualquer
cliente que realmente CONSEGUE alcançar este servidor já é,
por definição, parte dessa rede privada — cobrir todo o RFC1918
aqui é seguro pra esta implantação (nunca é "de fora" de
verdade) e evita ter que listar cada subnet/VLAN do escritório
uma por uma. -->
<list name="b2bcall_lan" default="deny">
<node type="allow" cidr="10.0.0.0/8"/>
<node type="allow" cidr="172.16.0.0/12"/>
<node type="allow" cidr="192.168.0.0/16"/>
</list>
<!-- PHASE 68 (continuação) — achado real: com áudio já corrigido, o
200 OK que o FreeSWITCH manda pra um ramal como 1503 ainda tinha
`Contact: sip:1502@191.240.175.55:5060` (o IP público de novo) —
o telefone nunca manda o ACK de volta (não é dele, não sabe
voltar), o FreeSWITCH fica retransmitindo o 200 OK sozinho
(0.5s/1s/2s/4s/4s...) até desistir em exatos 32s (Timer H do
RFC 3261) e derrubar a chamada. Causa: o Contact que o
FreeSWITCH usa pra SE IDENTIFICAR numa resposta SIP é decidido
por `apply-nat-acl` (nat.auto), não por `local-network-acl` —
são duas checagens INDEPENDENTES (uma pro SDP/mídia, outra pro
cabeçalho SIP). `nat.auto` (RFC1918 exceto a própria subnet do
FreeSWITCH) continua marcando ramais de OUTRAS subnets do
escritório como "precisa de IP externo" nesta camada, mesmo já
não afetando mais o SDP. Não dá pra simplesmente trocar
`apply-nat-acl` pra `b2bcall_lan` (mesmo sentido de match de
`local-network-acl`) — isso pararia de marcar esses ramais como
NAT'd, e SEM essa marcação o `NDLB-received-in-nat-reg-contact`
(Dockerfile, o fix do REGISTRO) para de disparar, voltando o
bug original. Por isso esta é uma ACL NOVA e SEPARADA, com o
sentido INVERTIDO de `b2bcall_lan`: "NAT'd de verdade" só quem
está FORA de todo RFC1918 conhecido desta implantação — nunca
alcançável de verdade nesta VM (nunca tem IP público próprio),
mas mantém o comportamento correto se um dia existir uma
conexão genuinamente externa (trunk real via IP público). -->
<list name="b2bcall_external_nat" default="allow">
<node type="deny" cidr="10.0.0.0/8"/>
<node type="deny" cidr="172.16.0.0/12"/>
<node type="deny" cidr="192.168.0.0/16"/>
</list>
</network-lists>
</configuration>