diff --git a/backend/db/migrations/0046_telephony_trunk_nat.sql b/backend/db/migrations/0046_telephony_trunk_nat.sql new file mode 100644 index 0000000..19f17b5 --- /dev/null +++ b/backend/db/migrations/0046_telephony_trunk_nat.sql @@ -0,0 +1,3 @@ +ALTER TABLE "telephony_trunks" ADD COLUMN IF NOT EXISTS "external_signaling_address" text; +ALTER TABLE "telephony_trunks" ADD COLUMN IF NOT EXISTS "external_media_address" text; +ALTER TABLE "telephony_trunks" ADD COLUMN IF NOT EXISTS "local_networks" text; diff --git a/backend/db/migrations/meta/_journal.json b/backend/db/migrations/meta/_journal.json index 95fe709..68522a1 100644 --- a/backend/db/migrations/meta/_journal.json +++ b/backend/db/migrations/meta/_journal.json @@ -323,6 +323,13 @@ "when": 1780164000000, "tag": "0045_telephony_trunks", "breakpoints": true + }, + { + "idx": 46, + "version": "7", + "when": 1780167600000, + "tag": "0046_telephony_trunk_nat", + "breakpoints": true } ] } diff --git a/backend/db/schema/telephony_trunks.ts b/backend/db/schema/telephony_trunks.ts index 4287c19..c20f5bf 100644 --- a/backend/db/schema/telephony_trunks.ts +++ b/backend/db/schema/telephony_trunks.ts @@ -30,6 +30,9 @@ export const telephonyTrunks = pgTable( callerId: text("caller_id"), inboundExtension: text("inbound_extension").notNull().default("1001"), outboundPrefix: text("outbound_prefix").notNull().default("0"), + externalSignalingAddress: text("external_signaling_address"), + externalMediaAddress: text("external_media_address"), + localNetworks: text("local_networks"), createdAt: timestamp("created_at", { withTimezone: true }) .notNull() diff --git a/backend/src/routes/telephony.ts b/backend/src/routes/telephony.ts index 7ce2014..0c6258e 100644 --- a/backend/src/routes/telephony.ts +++ b/backend/src/routes/telephony.ts @@ -75,6 +75,9 @@ const sanitizeTrunk = (trunk: any) => ({ callerId: trunk?.callerId || "", inboundExtension: trunk?.inboundExtension || "1001", outboundPrefix: trunk?.outboundPrefix || "0", + externalSignalingAddress: trunk?.externalSignalingAddress || "", + externalMediaAddress: trunk?.externalMediaAddress || "", + localNetworks: trunk?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8", }) const envExternalTelephonyConfig = () => { @@ -157,6 +160,7 @@ const renderTelekomPjsipConfig = (trunk: any) => { const authUser = asteriskValue(trunk.authUser) || sipUser const password = asteriskValue(trunk.password) const callerId = asteriskValue(trunk.callerId) || sipUser + const externalMediaAddress = asteriskValue(trunk.externalMediaAddress || trunk.externalSignalingAddress) return [ "; Von FEDEO generiert. Änderungen im Container können überschrieben werden.", @@ -181,6 +185,7 @@ const renderTelekomPjsipConfig = (trunk: any) => { `from_user=${sipUser}`, `from_domain=${registrar}`, `callerid=Telekom <${callerId}>`, + ...(externalMediaAddress ? [`media_address=${externalMediaAddress}`] : []), "direct_media=no", "force_rport=yes", "rewrite_contact=yes", @@ -249,6 +254,35 @@ const renderTelekomExtensionsConfig = (trunk: any) => { ].join("\n") } +const renderTelekomTransportConfig = (trunk: any) => { + const externalSignalingAddress = asteriskValue(trunk?.externalSignalingAddress) + const externalMediaAddress = asteriskValue(trunk?.externalMediaAddress || trunk?.externalSignalingAddress) + const localNetworks = asteriskValue(trunk?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8") + .split(/[\s,;]+/) + .map((entry) => entry.trim()) + .filter(Boolean) + + if (!externalSignalingAddress && !externalMediaAddress) { + return [ + "; Von FEDEO generiert.", + "; Kein externes Asterisk-NAT-Rewrite konfiguriert.", + "", + ].join("\n") + } + + return [ + "; Von FEDEO generiert. Änderungen am Transport benötigen einen Asterisk-Neustart.", + "[transport-udp](+)", + ...(externalSignalingAddress ? [ + `external_signaling_address=${externalSignalingAddress}`, + "external_signaling_port=5060", + ] : []), + ...(externalMediaAddress ? [`external_media_address=${externalMediaAddress}`] : []), + ...localNetworks.map((network) => `local_net=${network}`), + "", + ].join("\n") +} + const writeAsteriskTrunkConfig = async (trunk: any) => { const targetDir = asteriskGeneratedDir() await fs.mkdir(targetDir, { recursive: true }) @@ -262,6 +296,10 @@ const writeAsteriskTrunkConfig = async (trunk: any) => { name: "extensions.telekom.conf", content: renderTelekomExtensionsConfig(trunk), }, + { + name: "pjsip.transport.conf", + content: renderTelekomTransportConfig(trunk), + }, ] await Promise.all(files.map(async (file) => { @@ -364,10 +402,13 @@ export default async function telephonyRoutes(server: FastifyInstance) { return { enabled: trunk.enabled, provider: trunk.provider, - inboundExtension: trunk.inboundExtension, - outboundPrefix: trunk.outboundPrefix, - registrar: trunk.registrar, - sipUserConfigured: Boolean(trunk.sipUser), + inboundExtension: trunk.inboundExtension, + outboundPrefix: trunk.outboundPrefix, + registrar: trunk.registrar, + externalSignalingAddress: trunk.externalSignalingAddress, + externalMediaAddress: trunk.externalMediaAddress, + localNetworks: trunk.localNetworks, + sipUserConfigured: Boolean(trunk.sipUser), authUserConfigured: Boolean(trunk.authUser), passwordConfigured: Boolean(trunk.password), callerIdConfigured: Boolean(trunk.callerId), @@ -463,6 +504,9 @@ export default async function telephonyRoutes(server: FastifyInstance) { callerId: bodyString(body, "callerId"), inboundExtension: bodyString(body, "inboundExtension") || "1001", outboundPrefix: bodyString(body, "outboundPrefix") || "0", + externalSignalingAddress: bodyString(body, "externalSignalingAddress"), + externalMediaAddress: bodyString(body, "externalMediaAddress"), + localNetworks: bodyString(body, "localNetworks") || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8", password: clearPassword ? null : (password || existing?.password || null), updatedAt: now, updatedBy: req.user.user_id, diff --git a/docs/telekom-telefonie.md b/docs/telekom-telefonie.md index 128f068..8ebd23f 100644 --- a/docs/telekom-telefonie.md +++ b/docs/telekom-telefonie.md @@ -22,11 +22,16 @@ TELEPHONY_TELEKOM_AUTH_USER=# Telefonie Setup** im Bereich **Externe Telefonie**. +Wenn du die öffentliche Signaling- oder Medien-Adresse änderst, muss Asterisk anschließend neu gestartet werden, weil PJSIP-Transporte diese Werte nicht vollständig im laufenden Betrieb neu laden. + ## `.env` Fallback Die `.env`-Werte bleiben nur als lokaler Fallback für den Asterisk-Teststack erhalten, falls keine mandantenbezogene Konfiguration gepflegt ist. diff --git a/frontend/pages/settings/tenant.vue b/frontend/pages/settings/tenant.vue index 4e18858..eb68351 100644 --- a/frontend/pages/settings/tenant.vue +++ b/frontend/pages/settings/tenant.vue @@ -145,7 +145,10 @@ const telephonyTrunkForm = reactive({ clearPassword: false, callerId: "", inboundExtension: "1001", - outboundPrefix: "0" + outboundPrefix: "0", + externalSignalingAddress: "", + externalMediaAddress: "", + localNetworks: "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8" }) const setupPage = async () => { @@ -223,6 +226,9 @@ const loadTelephonyTrunk = async () => { telephonyTrunkForm.callerId = res?.callerId || "" telephonyTrunkForm.inboundExtension = res?.inboundExtension || "1001" telephonyTrunkForm.outboundPrefix = res?.outboundPrefix || "0" + telephonyTrunkForm.externalSignalingAddress = res?.externalSignalingAddress || "" + telephonyTrunkForm.externalMediaAddress = res?.externalMediaAddress || "" + telephonyTrunkForm.localNetworks = res?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8" } catch (error) { toast.add({ title: "Telefonie-Trunk konnte nicht geladen werden", color: "error" }) } finally { @@ -254,7 +260,10 @@ const saveTelephonyTrunk = async () => { clearPassword: telephonyTrunkForm.clearPassword, callerId: telephonyTrunkForm.callerId, inboundExtension: telephonyTrunkForm.inboundExtension, - outboundPrefix: telephonyTrunkForm.outboundPrefix + outboundPrefix: telephonyTrunkForm.outboundPrefix, + externalSignalingAddress: telephonyTrunkForm.externalSignalingAddress, + externalMediaAddress: telephonyTrunkForm.externalMediaAddress, + localNetworks: telephonyTrunkForm.localNetworks } }) @@ -523,6 +532,15 @@ onMounted(() => { + + + + + + + + +
diff --git a/telephony/asterisk/pjsip.conf b/telephony/asterisk/pjsip.conf index bc91196..8d28170 100644 --- a/telephony/asterisk/pjsip.conf +++ b/telephony/asterisk/pjsip.conf @@ -6,6 +6,7 @@ user_agent=FEDEO Local Asterisk type=transport protocol=udp bind=0.0.0.0:5060 +#tryinclude generated/pjsip.transport.conf [transport-ws] type=transport