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:
@@ -78,6 +78,83 @@ RUN sed -i \
|
||||
/etc/freeswitch/sip_profiles/internal.xml \
|
||||
&& ! grep -q "force-register-domain\|force-subscription-domain\|force-register-db-domain" /etc/freeswitch/sip_profiles/internal.xml
|
||||
|
||||
# Hairpin NAT entre dois ramais atrás do MESMO roteador (PHASE 68, achado
|
||||
# real reportado pelo usuário: "1502 liga pra 1503 e não completa" —
|
||||
# ambos registrados, mas a chamada falhava em 0ms com 503). Confirmado
|
||||
# lendo `sofia status profile internal reg`: os dois ramais registram com
|
||||
# Contact apontando pro MESMO IP público desta rede (a VM nunca tem IP
|
||||
# público próprio — secao "essa máquina nunca terá IP público" — telefone
|
||||
# e servidor share o mesmo NAT do escritório). `apply-nat-acl` (nat.auto,
|
||||
# já testado funcionando) SÓ detecta que o cliente está atrás de NAT; sem
|
||||
# `NDLB-received-in-nat-reg-contact`, o Contact salvo continua sendo o que
|
||||
# o telefone auto-relatou (o IP público da rede) em vez do IP realmente
|
||||
# observado no pacote UDP (a rede interna, ex.: 192.168.40.165) — originar
|
||||
# uma chamada nova pro Contact salvo então tenta mandar o INVITE de volta
|
||||
# pro IP público, que o roteador do escritório não sabe refletir de volta
|
||||
# pra dentro (hairpin NAT clássico). Ligando este parâmetro, o Contact
|
||||
# salvo passa a ser o IP/porta REALMENTE observados — a chamada entre dois
|
||||
# ramais no mesmo escritório passa a rotear direto pela rede interna,
|
||||
# nunca saindo (e voltando) pelo roteador.
|
||||
RUN sed -i \
|
||||
-e 's#<!-- *<param name="NDLB-received-in-nat-reg-contact" value="true"/> *-->#<param name="NDLB-received-in-nat-reg-contact" value="true"/>#' \
|
||||
/etc/freeswitch/sip_profiles/internal.xml \
|
||||
&& grep -q '<param name="NDLB-received-in-nat-reg-contact" value="true"/>' /etc/freeswitch/sip_profiles/internal.xml
|
||||
|
||||
# Continuação do achado acima: registro corrigido, mas a CHAMADA
|
||||
# completava sem áudio. `local-network-acl="localnet.auto"` (vanilla) só
|
||||
# cobre a subnet da própria interface do FreeSWITCH — os ramais reais
|
||||
# ficam em OUTRAS subnets do mesmo escritório, então eram tratados como
|
||||
# "de fora" e o SDP trocava o RTP/SIP anunciado pelo IP público
|
||||
# compartilhado (mesmo hairpin de antes, agora na mídia). `b2bcall_lan`
|
||||
# (acl.conf.xml, cobre todo RFC1918 — nunca alcançável de verdade "de
|
||||
# fora" nesta VM, que nunca tem IP público próprio) resolve isso.
|
||||
RUN sed -i \
|
||||
-e 's#<param name="local-network-acl" value="localnet.auto"/>#<param name="local-network-acl" value="b2bcall_lan"/>#' \
|
||||
/etc/freeswitch/sip_profiles/internal.xml \
|
||||
&& grep -q '<param name="local-network-acl" value="b2bcall_lan"/>' /etc/freeswitch/sip_profiles/internal.xml
|
||||
|
||||
# Terceira camada do mesmo achado: com áudio corrigido, a chamada ainda
|
||||
# morria sozinha em exatos 32s (Timer H do RFC 3261) — o 200 OK que o
|
||||
# FreeSWITCH manda pro ramal que RECEBEU a chamada tinha
|
||||
# `Contact: sip:...@191.240.175.55:5060` (IP público de novo), então o
|
||||
# telefone nunca manda o ACK de volta (não tem como voltar por ali) e o
|
||||
# FreeSWITCH fica retransmitindo o 200 OK sozinho até desistir. Achado
|
||||
# via captura de pacote real (tcpdump), não suposição.
|
||||
#
|
||||
# `b2bcall_external_nat` (acl.conf.xml, sentido INVERTIDO de
|
||||
# `b2bcall_lan`: só marca como NAT'd quem está de verdade fora de todo
|
||||
# RFC1918 conhecido) escopa `apply-nat-acl` corretamente pra esta
|
||||
# implantação — mantido por ser mais correto e não quebrar o fix de
|
||||
# registro acima (testado: `NDLB-received-in-nat-reg-contact` não
|
||||
# depende de `apply-nat-acl` bater, continua funcionando mesmo sem o
|
||||
# `fs_nat=yes` marcado). MAS isto sozinho não resolveu o Contact do
|
||||
# 200 OK — confirmado com uma segunda captura de pacote depois desta
|
||||
# mudança, o Contact continuou com o IP público.
|
||||
RUN sed -i \
|
||||
-e 's#<param name="apply-nat-acl" value="nat.auto"/>#<param name="apply-nat-acl" value="b2bcall_external_nat"/>#' \
|
||||
/etc/freeswitch/sip_profiles/internal.xml \
|
||||
&& grep -q '<param name="apply-nat-acl" value="b2bcall_external_nat"/>' /etc/freeswitch/sip_profiles/internal.xml
|
||||
|
||||
# O fix de verdade pro Contact: `ext-rtp-ip`/`ext-sip-ip` (STUN,
|
||||
# `$${external_sip_ip}` = 191.240.175.55) fazem FreeSWITCH sempre ter
|
||||
# um endereço "público" pronto pra usar como identidade própria em
|
||||
# respostas SIP — independente de qualquer ACL de NAT, é só o valor
|
||||
# que o profile usa quando "se apresentando" externamente. Esta VM
|
||||
# NUNCA tem IP público próprio de verdade (confirmado pelo usuário) —
|
||||
# pro profile "internal" (só ramais registrados deste escritório,
|
||||
# nunca um trunk/SBC de verdade), esse endereço "externo" nunca é
|
||||
# correto, só existe pra causar exatamente este bug. Removendo os 2
|
||||
# params, o profile cai automaticamente pra `rtp-ip`/`sip-ip`
|
||||
# (10.10.32.138) pra tudo, sem exceção — não tem mais endereço "errado"
|
||||
# pro FreeSWITCH escolher. O profile "external" (reservado pra um
|
||||
# trunk PSTN real, nunca usado ainda nesta implantação) mantém
|
||||
# `ext-rtp-ip`/`ext-sip-ip` intactos — só o "internal" precisava disso.
|
||||
RUN sed -i \
|
||||
-e '/<param name="ext-rtp-ip" value="\$\${external_rtp_ip}"\/>/d' \
|
||||
-e '/<param name="ext-sip-ip" value="\$\${external_sip_ip}"\/>/d' \
|
||||
/etc/freeswitch/sip_profiles/internal.xml \
|
||||
&& ! grep -q 'ext-rtp-ip\|ext-sip-ip' /etc/freeswitch/sip_profiles/internal.xml
|
||||
|
||||
# RTP num range fixo e pequeno (PHASE 54, docs/EXTENSIONS.md) — o range
|
||||
# vanilla default (16384-32768, ~16k portas) é inviável de publicar uma a
|
||||
# uma no host; achado ao tentar registrar um softphone de fora da rede
|
||||
|
||||
Reference in New Issue
Block a user