Gateways

The VoIP Server supports an extensible gateway plugin architecture for routing calls to and from external networks. Gateways are loaded as shared-library plugins (.so) at runtime and route calls based on dialed number prefixes.

Gateway Architecture

graph LR subgraph "Internal Network (Port 5060)" C1[Crew Station 1] C2[Crew Station 2] end SRV[VoIP Server] subgraph "Trunk Side (Port 5080)" GW1[Twilio Plugin] GW2[5G Plugin] GW3[RF Radio Plugin] end subgraph "External" PSTN[PSTN / Mobile] CELL[5G Network] RADIO[HF/VHF Radio] end C1 <-->|SIP| SRV C2 <-->|SIP| SRV SRV <-->|Trunk SIP| GW1 SRV <-->|Trunk SIP| GW2 SRV <-->|Trunk SIP| GW3 GW1 <--> PSTN GW2 <--> CELL GW3 <--> RADIO

How It Works

  1. Gateway plugins are discovered from a configurable directory (pluginDir)
  2. Each plugin declares the dial prefixes it handles (e.g. +, 00 for PSTN)
  3. When a user dials an external number, the server performs longest-prefix match
  4. The matching gateway plugin constructs and sends the outbound INVITE on the trunk port (5080)
  5. Inbound calls from the trunk are routed to internal users via IP ACL validation

Configuration

Gateways are configured in voip-settings.json under the "gateways" section:

{
    "server": {
        "sipPort": 5060,
        "trunkPort": 5080
    },
    "gateways": {
        "pluginDir": "/usr/lib/gva-voip-server/gateways",
        "routes": [
            {
                "plugin": "libgw-twilio.so",
                "prefixes": ["+", "00"],
                "config": {
                    "trunkHost": "your-trunk.pstn.twilio.com",
                    "trunkPort": 5060,
                    "transport": "udp",
                    "username": "your-credential-list-username",
                    "password": "your-credential-list-password",
                    "callerIdNumber": "+441234567890",
                    "allowedIps": [
                        "54.172.60.0/23",
                        "54.244.51.0/24",
                        "34.203.250.0/23"
                    ],
                    "codecs": ["PCMU", "PCMA"]
                }
            }
        ]
    }
}

CLI Options

Option Description
--gateway-dir=<path> Override gateway plugin directory
--trunk-port=<port> Override trunk-side SIP port (default: 5080)

Writing Custom Gateway Plugins

New gateways can be created by implementing the IGatewayPlugin interface:

class MyGatewayPlugin : public QObject, public IGatewayPlugin {
    Q_OBJECT
    Q_INTERFACES(gva::voip::IGatewayPlugin)
    Q_PLUGIN_METADATA(IID IGatewayPlugin_IID FILE "my-gateway.json")

public:
    QString name() const override { return "my-gateway"; }
    QString version() const override { return "1.0.0"; }
    QString description() const override { return "My custom gateway"; }

    bool initialize(const QJsonObject& config,
                    const QString& serverAddress,
                    quint16 trunkPort) override;
    void shutdown() override;

    QStringList supportedPrefixes() const override;
    bool canRouteCall(const QString& dialedNumber) const override;
    GatewayCallResult initiateOutboundCall(const GatewayCallRequest& request) override;
    // ... other interface methods
};

Plugin Categories

  • SIP Trunk (e.g. Twilio, Vonage) — PSTN/mobile via cloud provider
  • 5G/4G Cellular — Direct cellular modem integration
  • RF Radio (HF, VHF, UHF, SATCOM) — Military radio bearer gateways

Plugin Lifecycle

  1. QPluginLoader loads the .so from the gateway directory
  2. GatewayManager calls initialize(config) with the plugin's JSON config block
  3. GatewayManager queries supportedPrefixes() to build the dial plan routing table
  4. Calls are routed via initiateOutboundCall() (outbound) or handleInboundInvite() (inbound)
  5. shutdown() is called on server stop

Plugin Status

Each gateway reports its operational status:

Status Description
Ready Plugin loaded, trunk reachable, ready for calls
Connecting Plugin initialising or trunk registration in progress
Degraded Partially operational (e.g. some trunks unreachable)
Offline Plugin failed or trunk down — do not route

See Also