feat(routing): add rule-based SIP routing for inbound and outbound calls with dashboard route management

This commit is contained in:
2026-04-10 08:22:12 +00:00
parent f3e1c96872
commit fd3a408cc2
13 changed files with 893 additions and 114 deletions

View File

@@ -20,7 +20,7 @@ import { Buffer } from 'node:buffer';
import { SipMessage } from './sip/index.ts';
import type { IEndpoint } from './sip/index.ts';
import { loadConfig, getProviderForOutbound } from './config.ts';
import { loadConfig, resolveOutboundRoute } from './config.ts';
import type { IAppConfig, IProviderConfig } from './config.ts';
import {
initProviderStates,
@@ -151,8 +151,8 @@ sock.on('message', (data: Buffer, rinfo: dgram.RemoteInfo) => {
logPacket(`UP->DN RAW (unparsed) from ${rinfo.address}:${rinfo.port}`, data);
} else {
// From device, forward to default provider.
const dp = getProviderForOutbound(appConfig);
if (dp) sock.send(data, dp.outboundProxy.port, dp.outboundProxy.address);
const dp = resolveOutboundRoute(appConfig, '');
if (dp) sock.send(data, dp.provider.outboundProxy.port, dp.provider.outboundProxy.address);
}
return;
}
@@ -189,11 +189,22 @@ sock.on('message', (data: Buffer, rinfo: dgram.RemoteInfo) => {
// 5. New outbound call from device (passthrough).
if (!ps && msg.isRequest && msg.method === 'INVITE') {
logPacket(`[new outbound] INVITE from ${rinfo.address}:${rinfo.port}`, data);
const provider = getProviderForOutbound(appConfig);
if (provider) {
const provState = providerStates.get(provider.id);
const dialedNumber = SipMessage.extractUri(msg.requestUri || '') || '';
const routeResult = resolveOutboundRoute(
appConfig,
dialedNumber,
undefined,
(pid) => !!providerStates.get(pid)?.registeredAor,
);
if (routeResult) {
const provState = providerStates.get(routeResult.provider.id);
if (provState) {
callManager.handlePassthroughOutbound(msg, { address: rinfo.address, port: rinfo.port }, provider, provState);
// Apply number transformation to the INVITE if needed.
if (routeResult.transformedNumber !== dialedNumber) {
const newUri = msg.requestUri?.replace(dialedNumber, routeResult.transformedNumber);
if (newUri) msg.setRequestUri(newUri);
}
callManager.handlePassthroughOutbound(msg, { address: rinfo.address, port: rinfo.port }, routeResult.provider, provState);
}
}
return;
@@ -212,8 +223,8 @@ sock.on('message', (data: Buffer, rinfo: dgram.RemoteInfo) => {
} else {
// From device -> forward to provider.
logPacket(`[fallback outbound] from ${rinfo.address}:${rinfo.port}`, data);
const provider = getProviderForOutbound(appConfig);
if (provider) sock.send(msg.serialize(), provider.outboundProxy.port, provider.outboundProxy.address);
const fallback = resolveOutboundRoute(appConfig, '');
if (fallback) sock.send(msg.serialize(), fallback.provider.outboundProxy.port, fallback.provider.outboundProxy.address);
}
} catch (e: any) {
log(`[err] ${e?.stack || e}`);