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
81 lines
4.5 KiB
Markdown
81 lines
4.5 KiB
Markdown
# Arquitetura de Rede
|
|
|
|
## Estado atual (PHASE 68 — `network_mode: host`)
|
|
|
|
`freeswitch` roda em `network_mode: host` — decisão tomada na PHASE 68 (era
|
|
"pendente" antes, ver seção abaixo), pedida pelo usuário depois de dois
|
|
ramais reais no mesmo escritório não conseguirem se ligar. `postgres`/
|
|
`redis`/`fs-config` continuam na rede bridge padrão do compose
|
|
(`b2bcall_default`); só o FreeSWITCH saiu dela.
|
|
|
|
Com host networking, o FreeSWITCH usa a stack de rede do PRÓPRIO host
|
|
diretamente — sem NAT/bridge do Docker no meio, `local_ip_v4` resolve pro IP
|
|
real da interface (`10.10.32.138` nesta VM), SIP/ESL escutam direto nas
|
|
interfaces do host. Isso elimina uma camada de tradução de endereço que
|
|
existia antes de qualquer roteador físico entrar em cena — ver PHASE 68 no
|
|
TODO.md pro relato completo dos 3 achados (registro, áudio, e a chamada
|
|
morrendo sozinha em 32s) que motivaram e validaram essa mudança.
|
|
|
|
Efeitos em cascata (o FreeSWITCH sai da rede bridge do compose):
|
|
- Não resolve mais os outros containers pelo nome de serviço — `fs-config`
|
|
agora é alcançado via `http://127.0.0.1:8080/` (porta publicada só em
|
|
loopback, `xml_curl.conf.xml`).
|
|
- Os workers que chamavam ESL via nome `freeswitch`
|
|
(fs-events/fs-config/predictive-dialer) usam `host.docker.internal` (+
|
|
`extra_hosts: host-gateway`, exige Docker 20.10+).
|
|
- Segurança do Event Socket deixa de depender de "publicar só em loopback"
|
|
(não existe mais essa camada) e passa a depender inteiramente da ACL
|
|
`b2bcall_internal` já configurada (`apply-inbound-acl`) — confirmado que
|
|
continua rejeitando qualquer origem fora de loopback/rede do Docker.
|
|
|
|
`apps/api` continua rodando **direto no host** (fora do Docker) — nada muda
|
|
pra ela, `ESL_HOST=127.0.0.1` já funcionava e continua funcionando (agora de
|
|
forma ainda mais direta, sem a camada de publish-port do Docker no meio).
|
|
|
|
## Decisão tomada: `network_mode` do FreeSWITCH (agente.md secao 19)
|
|
|
|
Historicamente listada como pendente (macvlan/ipvlan vs. host), resolvida na
|
|
PHASE 68 a favor de **`network_mode: host`** — não por causa de um trunk
|
|
PSTN real (a topologia `Internet → OpenSIPS → rede SIP privada →
|
|
FreeSWITCH` da secao 17 continua valendo pra isso), mas por um problema
|
|
concreto de NAT entre dois ramais registrados no mesmo escritório (ver
|
|
achado abaixo). `macvlan`/`ipvlan` continuam como alternativas válidas se um
|
|
dia o isolamento de rede do host virar prioridade maior que a simplicidade
|
|
atual — não implementadas, não descartadas.
|
|
|
|
## Achado real (PHASE 65): VM atrás de roteador, ramal externo sem áudio
|
|
|
|
Esta VM de laboratório está atrás de um roteador (NAT) — o IP da interface de
|
|
rede (`ens18`) é privado (`10.10.32.x`), o roteador é quem faz NAT pro IP
|
|
público real. Reportado pelo usuário: um softphone externo registrava com
|
|
sucesso, mas sem áudio.
|
|
|
|
Diagnóstico (contadores de pacotes do `iptables`, não suposição): 10 pacotes
|
|
chegaram em `5060/udp` (SIP), **zero** pacotes chegaram em qualquer porta da
|
|
faixa de RTP (`16384-16584/udp`). O REGISTER funciona porque a resposta
|
|
trafega de volta pelo mesmo "buraco" NAT que o próprio pacote do cliente abriu
|
|
— mas RTP usa portas completamente diferentes, como um fluxo NOVO. Sem uma
|
|
regra de **port forward no roteador** pra essa faixa (apontando pro IP
|
|
privado desta VM), esses pacotes nunca chegam até o Docker/FreeSWITCH.
|
|
|
|
Do lado desta VM estava tudo certo: `docker-compose.yml` publica a faixa de
|
|
RTP pra rede (não só loopback), `Ext-RTP-IP`/`Ext-SIP-IP` resolvem certo via
|
|
STUN (`sofia status profile internal` mostra o IP público real), e a
|
|
detecção de NAT do próprio FreeSWITCH (`apply-nat-acl value="nat.auto"`,
|
|
`nat.auto` é uma ACL auto-gerada pelo core do FreeSWITCH — RFC1918 exceto a
|
|
própria rede local do container, não precisa existir em `acl.conf.xml`)
|
|
funciona corretamente (testado direto via `fs_cli -x "acl <ip> nat.auto"`).
|
|
|
|
**Não tem fix de código pra isso** — é infraestrutura de rede fora do
|
|
controle desta aplicação. Checklist pra quem for expor telefonia real atrás
|
|
de um roteador: encaminhar UDP `5060` e toda a faixa RTP (`16384-16584`,
|
|
Dockerfile do FreeSWITCH) pro IP privado da VM, sem tradução de porta.
|
|
|
|
## Quando `apps/api` virar container
|
|
|
|
Hoje ela roda no host por conveniência de desenvolvimento. Quando virar o
|
|
serviço Docker `b2bcall-api` (agente.md secao 14), as connection strings
|
|
precisam trocar de `localhost` pros hostnames internos do compose
|
|
(`postgres`, `redis`, `freeswitch`) — ver nota em docs/AUTHENTICATION.md sobre
|
|
essa pegadinha.
|