From 2134fa0aa4bddab98790fe0199e748408a3c80a3 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 31 Aug 2026 11:50:51 -0300 Subject: [PATCH] =?UTF-8?q?fix(freeswitch):=203=20achados=20reais=20de=20N?= =?UTF-8?q?AT=20entre=20ramais=20no=20mesmo=20escrit=C3=B3rio=20=E2=80=94?= =?UTF-8?q?=20registro,=20=C3=A1udio=20e=20chamada=20morrendo=20em=2032s?= =?UTF-8?q?=20(PHASE=2068)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8 --- TODO.md | 68 ++++++++++++++++ docker-compose.yml | 75 +++++++++++------- docs/NETWORK_ARCHITECTURE.md | 56 +++++++++----- infrastructure/freeswitch/Dockerfile | 77 +++++++++++++++++++ .../overrides/autoload_configs/acl.conf.xml | 53 +++++++++++++ .../autoload_configs/xml_curl.conf.xml | 8 +- 6 files changed, 290 insertions(+), 47 deletions(-) diff --git a/TODO.md b/TODO.md index f525cce..b5cda1e 100644 --- a/TODO.md +++ b/TODO.md @@ -2456,6 +2456,74 @@ rota de entrada") os dois persistiram com o tipo certo, sobrevivendo a um reload completo da página. +## PHASE 68 — Ramal registrado mas chamada não completava / sem áudio +(reportado pelo usuário testando com 2 telefones IP reais, 1502 e 1503) +- [x] **Achado 1 (chamada não completava, 503 instantâneo)**: os dois + ramais registravam com o Contact apontando pro MESMO IP público + compartilhado desta rede (esta VM nunca tem IP público próprio — + é sempre RFC1918 atrás do NAT do escritório). Ao originar uma + chamada, o FreeSWITCH tentava mandar o INVITE de volta pra esse + IP público — hairpin NAT clássico, o roteador do escritório não + sabe refletir esse tráfego de volta pra dentro. Corrigido com + `NDLB-received-in-nat-reg-contact` (Dockerfile) — o Contact salvo + passa a ser o IP/porta REALMENTE observados no pacote (a rede + interna), não o que o telefone auto-relata. +- [x] `network_mode: host` no serviço `freeswitch` (pedido do usuário, + "sempre é problema NAT do Docker") — tira a camada de NAT/bridge + do Docker do meio. Quebra em cascata corrigida: FreeSWITCH sai da + rede bridge do compose, então (a) não resolve mais os OUTROS + containers pelo nome de serviço — `fs-config` virou + `http://127.0.0.1:8080/` (porta publicada só em loopback, + xml_curl.conf.xml) —, e (b) os workers que chamavam ESL via nome + `freeswitch` (fs-events/fs-config/predictive-dialer) passam a usar + `host.docker.internal` (+ `extra_hosts: host-gateway`, 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. +- [x] **Achado 2 (chamada completava, mas sem áudio)**: com o registro + corrigido, a ligação passou a completar, mas sem RTP (confirmado + pelo usuário com `sngrep`). `local-network-acl="localnet.auto"` + (padrão 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 de novo (mesmo + hairpin, agora na mídia). Corrigido com uma ACL própria + (`b2bcall_lan`, cobre todo RFC1918 — nunca alcançável de verdade + "de fora" nesta VM) referenciada em `local-network-acl`. +- [x] **Achado 3 (chamada completava e tinha áudio, mas morria sozinha + em exatos 32s)**: mesmo com áudio já corrigido, toda chamada + morria sozinha depois de exatos 32 segundos — o Timer H do + RFC 3261 (64×T1, prazo padrão de qualquer UAS esperando o ACK de + um 2xx). Só descoberto de verdade com captura de pacote real + (`tcpdump`, instalado nesta sessão — não estava no host): o + `200 OK` que o FreeSWITCH manda pro ramal que RECEBEU a chamada + ainda tinha `Contact: sip:...@191.240.175.55:5060` (o IP público + de novo, MESMO com o SDP já mostrando o IP certo) — o telefone + nunca manda o ACK de volta (não tem como chegar por ali), o + FreeSWITCH retransmite o 200 OK sozinho (0.5s/1s/2s/4s/4s...) até + desistir e derrubar a chamada. `apply-nat-acl`/`local-network-acl` + (achados 1/2) não bastam aqui — são checagens independentes, e + trocar `apply-nat-acl` pra escopo `b2bcall_lan` (testado) não + mudou nada nesse Contact. O fix de verdade: `ext-rtp-ip`/ + `ext-sip-ip` (STUN, resolvem pro IP público) REMOVIDOS do profile + `internal` (Dockerfile) — sem endereço "externo" nenhum + configurado, o FreeSWITCH não tem mais como escolher errado, + cai sempre em `rtp-ip`/`sip-ip` (10.10.32.138). Único profile + afetado; `external` (reservado pra um trunk PSTN real, nunca + usado ainda) mantém os dois params intactos. +- [x] Diagnosticado ao vivo lendo `/var/log/freeswitch/freeswitch.log` + em tempo real durante as tentativas de chamada do usuário (nunca + supondo causa sem ver o log) e, pro achado 3, uma captura de + pacote real via `tcpdump` — cada achado confirmado por evidência + exata (`NORMAL_TEMPORARY_FAILURE`/503 instantâneo pro achado 1; + endereço do `c=`/RTP-IP pro achado 2; cabeçalho `Contact:` do + `200 OK` capturado byte a byte pro achado 3). Confirmado resolvido + pelo usuário com chamadas reais nos dois sentidos (1502→1503 e + 1503→1502), áudio bidirecional e chamada sobrevivendo bem além + dos 32s que travavam toda tentativa antes do achado 3. + --- ## Riscos conhecidos diff --git a/docker-compose.yml b/docker-compose.yml index 02c4a47..7cd6a8d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,11 +49,18 @@ services: FS_CONFIG_USER: ${FS_CONFIG_USER} FS_CONFIG_PASSWORD: ${FS_CONFIG_PASSWORD} REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379 - ESL_HOST: freeswitch + # host.docker.internal (PHASE 68): freeswitch virou network_mode: + # host, saiu da rede bridge do compose — o nome de serviço + # "freeswitch" não resolve mais pra ele. host.docker.internal + # aponta pro HOST (onde o ESL agora escuta direto), exige o + # extra_hosts abaixo em Linux (Docker 20.10+). + ESL_HOST: host.docker.internal ESL_PORT: "8021" ESL_PASSWORD: ${ESL_PASSWORD} SOFIA_EXTERNAL_GATEWAYS_DIR: /gateways CALLCENTER_QUEUES_DIR: /callcenter-queues + extra_hosts: + - "host.docker.internal:host-gateway" volumes: # Compartilhado com o FreeSWITCH (agente.md secao 41-42): fs-config # escreve os XML de gateway aqui, o profile "external" ja inclui @@ -63,7 +70,12 @@ services: # escreve um XML de fila por arquivo aqui, incluido via # X-PRE-PROCESS no nosso callcenter.conf.xml (mesmo padrao acima). - freeswitch_callcenter_queues:/callcenter-queues - # Sem porta publicada: so o FreeSWITCH (mesma rede do compose) chama isto. + # Publicado só em loopback (PHASE 68): o FreeSWITCH virou + # network_mode: host e chama isto via 127.0.0.1 agora (ver + # xml_curl.conf.xml) — antes não precisava de porta nenhuma porque os + # dois estavam na mesma rede bridge do compose. + ports: + - "127.0.0.1:8080:8080" healthcheck: test: ["CMD", "node", "-e", "fetch('http://localhost:8080/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"] interval: 10s @@ -99,28 +111,31 @@ services: # uma chamada ativa seria latência/confiabilidade desnecessárias # pra um prompt de poucos segundos. - ./data/ivr-prompts:/ivr-prompts - # Event Socket (8021) NUNCA publicado pra REDE — só em loopback do - # próprio host (agente.md secao 22: "NÃO pública... acesso somente - # pela aplicação autorizada"). Achado real testando as telas de - # Infraestrutura (platform): o comentário original aqui dizia que - # elas SEMPRE falhariam nesta VM porque "apps/api roda fora do - # Docker" — isso estava incompleto. `apps/api` (systemd, no host) - # JÁ conseguia alcançar o IP do container na rede bridge sem nenhuma - # porta publicada (`docker inspect` + `/dev/tcp` confirmam: o HOST - # sempre alcança a rede bridge do Docker, só outras MÁQUINAS é que - # não). O bloqueio real era `ESL_HOST=freeswitch` — um nome DNS que - # só existe dentro da rede interna do Docker, nunca no resolver do - # host. `127.0.0.1:8021:8021` resolve isso sem abrir a porta pra - # ninguém além do próprio host — mesma garantia de segurança de - # antes, só que a aplicação autorizada agora alcança de verdade. - # SIP (5060) e RTP (16384-16584, range fixo no Dockerfile) publicados - # pra rede a partir da PHASE 54 — necessário pra registrar um - # softphone/telefone de fora da rede Docker. - ports: - - "127.0.0.1:8021:8021" - - "5060:5060/udp" - - "5060:5060/tcp" - - "16384-16584:16384-16584/udp" + # network_mode: host (PHASE 68) — achado real reportado pelo usuário: + # dois ramais reais (telefones IP), atrás do MESMO roteador/NAT desta + # rede (esta VM nunca tem IP público próprio — é sempre RFC1918 por + # trás do NAT do escritório), ligando um pro outro. Com bridge+NAT do + # Docker no meio, o SIP/RTP entre eles precisava atravessar uma camada + # de tradução de endereço A MAIS antes mesmo de chegar no roteador do + # escritório — combinado com o hairpin NAT do roteador (dois clientes + # atrás do mesmo NAT se enxergando só pelo IP público), a chamada + # falhava em 0ms (503, nunca chegava a sair pela rede de verdade). + # Tirar o Docker NAT do meio (host networking) elimina essa camada + # extra; o fix real de hairpin em si é separado — ver comentário sobre + # `NDLB-received-in-nat-reg-contact` no Dockerfile. + # + # Sem `ports:` aqui (inválido com network_mode: host — o container usa + # as portas do HOST diretamente). Segurança do Event Socket (8021, + # agente.md secao 22) deixa de depender de publicar só em loopback + # (não existe mais essa camada) e passa a depender inteiramente da + # ACL `apply-inbound-acl="b2bcall_internal"` já configurada em + # event_socket.conf.xml (127.0.0.0/8 + 172.16.0.0/12, o range do + # bridge do Docker) — qualquer outra origem continua rejeitada mesmo + # com a porta agora "aberta" nas interfaces do host. Os workers que + # antes alcançavam via nome de serviço `freeswitch` na rede do compose + # (fs-events/fs-config/predictive-dialer) precisam agora de + # `host.docker.internal` — ver `extra_hosts` em cada um deles abaixo. + network_mode: host healthcheck: test: ["CMD-SHELL", "fs_cli -p \"$$ESL_PASSWORD\" -x status | grep -q 'is ready'"] interval: 10s @@ -139,7 +154,9 @@ services: - redis - postgres environment: - ESL_HOST: freeswitch + # host.docker.internal, não "freeswitch" (PHASE 68) — ver comentário + # equivalente em fs-config acima. + ESL_HOST: host.docker.internal ESL_PORT: "8021" ESL_PASSWORD: ${ESL_PASSWORD} APP_DATABASE_URL: postgresql://${POSTGRES_APP_USER}:${POSTGRES_APP_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public @@ -159,6 +176,8 @@ services: S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-} S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-} S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-} + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - ./data/recordings-spool:/recordings - ./data/object-storage-local:/data/object-storage @@ -174,7 +193,9 @@ services: - redis - postgres environment: - ESL_HOST: freeswitch + # host.docker.internal, não "freeswitch" (PHASE 68) — ver comentário + # equivalente em fs-config acima. + ESL_HOST: host.docker.internal ESL_PORT: "8021" ESL_PASSWORD: ${ESL_PASSWORD} APP_DATABASE_URL: postgresql://${POSTGRES_APP_USER}:${POSTGRES_APP_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public @@ -188,6 +209,8 @@ services: # — quem de fato grava e' o processo FreeSWITCH, não este worker; # não precisa do volume montado aqui, só saber o path Docker-interno. RECORDINGS_SPOOL_DIR: /recordings + extra_hosts: + - "host.docker.internal:host-gateway" ai-worker: build: diff --git a/docs/NETWORK_ARCHITECTURE.md b/docs/NETWORK_ARCHITECTURE.md index afae72d..206f4f9 100644 --- a/docs/NETWORK_ARCHITECTURE.md +++ b/docs/NETWORK_ARCHITECTURE.md @@ -1,31 +1,47 @@ # Arquitetura de Rede -## Estado atual (fase FreeSWITCH inicial) +## Estado atual (PHASE 68 — `network_mode: host`) -`freeswitch` roda na rede padrão do Docker Compose (bridge, `b2bcall_default`), -igual a `postgres` e `redis`. Nenhuma porta é publicada no host — nem 8021 -(ESL), nem SIP (5060/5080), nem RTP. Isso é intencional: ainda não existe -nenhum tronco SIP real nem ramal externo, então não há motivo pra expor nada. +`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. -`apps/api` roda hoje **direto no host** (fora do Docker), então usa -`APP_DATABASE_URL`/`REDIS_URL` apontando pra `localhost` nas portas publicadas -pelo Postgres/Redis. Ela não consegue (nem precisa, ainda) alcançar o -FreeSWITCH. +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. -## Decisão pendente: `network_mode` do FreeSWITCH (agente.md secao 19) +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. -Quando existir um tronco SIP real (fase Trunks), será preciso decidir entre: +`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). -- **`network_mode: host`**: mais simples pra SIP/RTP (sem NAT entre o - container e a rede), mas perde isolamento de rede do Docker. -- **macvlan/ipvlan**: dá ao FreeSWITCH um IP próprio na rede física, sem expor - outros serviços do host: mais trabalho de configurar, melhor isolamento. +## Decisão tomada: `network_mode` do FreeSWITCH (agente.md secao 19) -Não decidido ainda — só vira relevante quando houver um carrier/SBC real pra -conectar (secao 17: "FreeSWITCH não deverá depender de IP SIP público" — a -topologia esperada é `Internet → OpenSIPS → rede SIP privada → FreeSWITCH`, -então o FreeSWITCH em si tende a ficar em rede privada mesmo, o que favorece -manter bridge/macvlan em vez de host). +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 diff --git a/infrastructure/freeswitch/Dockerfile b/infrastructure/freeswitch/Dockerfile index 9b0d717..eb8cefa 100644 --- a/infrastructure/freeswitch/Dockerfile +++ b/infrastructure/freeswitch/Dockerfile @@ -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###' \ + /etc/freeswitch/sip_profiles/internal.xml \ + && grep -q '' /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###' \ + /etc/freeswitch/sip_profiles/internal.xml \ + && grep -q '' /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###' \ + /etc/freeswitch/sip_profiles/internal.xml \ + && grep -q '' /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 '//d' \ + -e '//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 diff --git a/infrastructure/freeswitch/overrides/autoload_configs/acl.conf.xml b/infrastructure/freeswitch/overrides/autoload_configs/acl.conf.xml index 3f2c7b7..d7d9eff 100644 --- a/infrastructure/freeswitch/overrides/autoload_configs/acl.conf.xml +++ b/infrastructure/freeswitch/overrides/autoload_configs/acl.conf.xml @@ -10,5 +10,58 @@ + + + + + + + + + + + + + + diff --git a/infrastructure/freeswitch/overrides/autoload_configs/xml_curl.conf.xml b/infrastructure/freeswitch/overrides/autoload_configs/xml_curl.conf.xml index e114c60..2f18104 100644 --- a/infrastructure/freeswitch/overrides/autoload_configs/xml_curl.conf.xml +++ b/infrastructure/freeswitch/overrides/autoload_configs/xml_curl.conf.xml @@ -6,7 +6,13 @@ vanilla continua valendo como fallback quando "not found". Credenciais substituidas em runtime pelo entrypoint.sh — nunca ficam de verdade na imagem (mesmo padrao do ESL_PASSWORD). --> - + +