Un seul démon pour toute la chaîne.
Strowger remplace la pile de téléphonie hétéroclite — serveur SIP, pont média, scripts de liaison — par un processus Rust unique gérant la signalisation SIP, le flux média RTP (G.711) et la session IA en temps réel. Un binaire, un fichier de config, une API HTTP. Propulsé par le moteur sipvox.
- Autonome. Utilisable par n'importe quel projet via son API HTTP. Zéro couplage avec le reste de votre infrastructure.
- Maîtrise du média. Strowger gère l'intégralité du chemin RTP de bout en bout — rien à câbler entre les machines, aucune couche intermédiaire à surveiller. Toute une classe de bugs de plomberie disparaît par conception.
- Souverain. Les CDR, enregistrements, transcriptions et la signalisation ne quittent jamais votre infrastructure. L'audio de la conversation est transmis à un seul tiers : le fournisseur de modèle que vous choisissez, avec vos propres clés et votre DPA.
- Expertise audio intégrée (tampon de lecture, latence, barge-in, watchdog) — des mois de débogage en production sont ici des contraintes de conception, et non des problèmes à redécouvrir.
Un espace de travail Cargo composé de crates réutilisables — sip, media,
ai, call, httpd — assemblées par le
démon sipvoxd. Chaque crate est utilisable individuellement comme bibliothèque.
# Compilation
cargo build --release
# Lancement (dev : une clé API vide désactive l'auth)
SIPVOX_CONFIG=/etc/sipvox/config.toml ./target/release/sipvoxd
# Test de santé
curl -s http://127.0.0.1:7060/health
Un seul fichier TOML (chemin via SIPVOX_CONFIG), les secrets sont interpolés depuis
l'environnement pour que le fichier reste versionnable :
[daemon]
http_host = "127.0.0.1"
http_port = 7060
public_ip = "203.0.113.10" # IP publique du serveur (pas de STUN en v1)
data_dir = "/var/lib/sipvox" # SQLite, enregistrements
rtp_port_range = "22000-22999"
[ai.gemini]
api_key = "${SIPVOX_GEMINI_API_KEY}"
model = "gemini-3.1-flash-live-preview"
playout_ms = 200
[[trunk]]
name = "ovh-prod"
registrar = "sip-domain.example"
outbound_proxy = "oaXXXXXX-ovh-1.sip-proxy.example"
username = "0033XXXXXXXXX"
auth_username = "0033XXXXXXXXX"
password = "${SIPVOX_OVH_PASSWORD}"
number = "+33XXXXXXXXX"
transport = "udp"
register = true # false = trunk avec auth par IP (pas de REGISTER)
[[inbound_route]]
trunk = "ovh-prod"
did = "*184163651" # +E164 exact | *suffixe | * fourre-tout
handler = { type = "webhook", url = "http://127.0.0.1:9100/api/voice/inbound?token=…" }
Chaque INVITE entrant est autorisé et configuré par un unique webhook vers votre service
(voir la référence API pour le mapping HTTP→SIP et le corps de la réponse 200). La
réponse est séquencée pour que l'appelant n'entende jamais de blanc, et un backend IA hors ligne ne
répond jamais — zéro facturation. Il n'y a pas de logique de rappel locale : votre service identifie l'
appelant dans le webhook et reconnaît un rappel de son côté.
Ce sont des exigences non négociables, apprises en production :
- Tampon de lecture à l'émission ≥ 200 ms par défaut (voix saccadée = sous-alimentation du TTS).
- Full-duplex avec barge-in ; seuil par défaut conservateur (500 ms est trop sensible).
- Watchdog de silence : une légère "relance" après N secondes sans audio de l'IA.
- TTFT mesuré et journalisé par appel ; alerte au-delà de 1,5 s (référence : 0,83 s).
- Taille du prompt journalisée — la latence augmente avec la taille du prompt.
- Plage RTP dédiée, jamais partagée — une collision ressemble à un "non-réponse" massif.
- Codecs : PCMA + PCMU, trames de 20 ms ; le verrouillage RTP symétrique maintient le flux média entrant sans contournements fragiles. Enregistrement bidirectionnel natif → WAV mixé + par canal.
Un trait VoiceBackend isole le modèle. Le backend v1 est Gemini Live
avec ses réglages optimisés (VAD/barge-in configurable, watchdog de silence, full-duplex,
mesure du TTFT). OpenAI Realtime est le backend prévu pour la v2. Les modèles sont utilisés
avec vos propres clés, facturés au coût réel — sans marge cachée. Les outils invoqués par l'IA
pendant un appel sont relayés via HTTP à votre service (proxy_url) et leurs
résultats agrégés dans le callback final.
- API HTTP : liaison loopback +
X-API-Key. Jamais exposée publiquement sans votre reverse proxy. - SIP : les INVITE/OPTIONS entrants hors liste blanche (IP du registrar/proxy résolues + CIDR de config) sont rejetés silencieusement et comptabilisés. Le REGISTER entrant est toujours rejeté (Strowger n'est pas un registrar).
- Secrets : droits
0600env/config uniquement. Jamais dans les logs, jamais dans l'API, jamais dans les callbacks (l'auth SIP est masquée). - Enregistrements : opt-in par appel, répertoire
0700, rétention automatique.
tracing avec call_id comme champ structuré partout ;
métriques Prometheus (enregistrements par état, appels par statut/direction, TTFT de l'IA,
taille du prompt, sous-alimentations du tampon, pertes RTP, rejets anti-scan, latence des outils proxy) ; un fichier de log par
appel avec la chronologie SIP complète + événements IA + appels d'outils.
cargo build --release
sudo ./deploy/install.sh target/release/sipvoxd
# puis éditez /etc/sipvox/config.toml et /etc/sipvox/sipvox.env (secrets)
sudo systemctl enable --now sipvox
journalctl -u sipvox -f
L'arrêt (SIGTERM) est progressif : l'API se ferme, puis le worker d'appel, suivi d'un
désenregistrement propre des trunks. Les secrets résident dans /etc/sipvox/sipvox.env
(0640 root:sipvox), interpolés dans config.toml via
${VAR} — jamais dans le dépôt ni dans l'unité systemd.
Strowger est sous licence par canal simultané — un canal = un appel simultané.
Le binaire s'active en ligne auprès d'un serveur de licence et renouvelle un bail signé
(Ed25519) ; la durée du bail est la tolérance hors ligne (7 jours pour les licences datées,
30 pour les perpétuelles). Les capacités (max_concurrent_calls, trunks, enregistrement, IA)
sont des valeurs lues depuis le bail vérifié — pas des options à activer. À l'expiration du bail, seuls
les nouveaux appels sont refusés ; les appels en cours se terminent toujours normalement, et
/health passe en état degraded avec un motif clair.
Sans aucune licence, Strowger fonctionne en mode libre-service gratuit — limité à 1 trunk, 1 canal et une durée d'appel maximale d'une minute, avec documentation communautaire et support IA par email. Suffisant pour le relier à votre trunk et l'entendre répondre ; activez une licence pour lever les limites. Voir les tarifs et le téléchargement.