Aller au contenu principal

SMSc - Application du Centre de Services SMS

Application du Centre SMS construite avec Phoenix/Elixir, fournissant une file de messages centralisée et une API REST pour le routage et la livraison des SMS.

Documentation​

Documentation de Base​

Documentation de Conformité​

Performance​

Vue d'ensemble de l'Architecture​

Le cœur de SMS_C fournit une file de messages indépendante du protocole et une API REST. Les frontaux SMSC externes (SMPP, IMS, SS7/MAP) se connectent en tant que passerelles autonomes qui communiquent avec le cœur via l'API REST.

Flux de Messages​

Flux de Messages Sortants (MT - Mobile Terminated)​

Flux de Messages Entrants (MO - Mobile Originated)​

Fonctionnalités Clés​

1. Conception Indépendante du Protocole​

  • Le cœur de SMS_C gère la persistance des messages, le routage et l'API
  • Les frontaux externes (SMPP, IMS, SS7/MAP) gèrent la communication spécifique au protocole
  • Tous les frontaux communiquent via une API REST unifiée
  • Ajoutez de nouveaux protocoles sans changer le cœur
  • Chacun peut être mis à l'échelle indépendamment

2. Routage des Messages​

  • Moteur de routage dynamique avec configuration à l'exécution
  • Routage basé sur préfixe (numéros appelants/appelés)
  • Filtrage par type de SMSC et source (IMS/Circuit Switched/SMPP)
  • Routage basé sur la priorité avec équilibrage de charge basé sur le poids
  • Routage de réponse automatique et capacités de suppression de message
  • Contrôle de facturation par route
  • Interface Web pour la gestion des routes
  • Mises à jour de route en temps réel sans interruption de service

📖 Voir Guide de Routage SMS pour une documentation complète

3. Logique de Réessai avec Backoff Exponentiel​

  • Réessai automatique en cas d'échec de livraison
  • Backoff exponentiel : 1min, 2min, 4min, 8min, etc.
  • Tentatives de réessai maximum configurables
  • Gestion de l'expiration des messages
  • Suivi des réessais par message

Guide des Opérations​

Points d'Accès :

  • API REST : https://localhost:8443 (ou http://localhost:8080 sans TLS)
  • Panneau de Contrôle : https://localhost:8086
  • Documentation API (Swagger UI) : https://localhost:8443/api/docs

Démarrer les Frontaux Externes : Chaque frontal de protocole est une application autonome. Voir la documentation individuelle des frontaux pour les instructions de démarrage.

Configuration​

Toute la configuration est gérée directement dans config/runtime.exs. Aucune variable d'environnement n'est utilisée.

Configuration de Base​

Aucune variable d'environnement de configuration de l'application de base n'est actuellement utilisée. Les ports de serveur et les noms d'hôte sont configurés dans config/runtime.exs :

  • Serveur API : Port 8443 (HTTPS), écoutant sur 0.0.0.0
  • Panneau de Contrôle : Port 80 (HTTP), écoutant sur 0.0.0.0

Configuration de la Base de Données​

Les paramètres de la base de données sont configurés dans config/runtime.exs :

  • Nom d'utilisateur : omnitouch
  • Mot de passe : omnitouch2024
  • Nom d'hôte : localhost
  • Port : 3306
  • Nom de la base de données : smsc_new
  • Taille du pool : 1

Configuration de Cluster​

Les paramètres de cluster sont configurés dans config/runtime.exs :

  • Nœuds de cluster : "" (vide - pas de clustering par défaut)
  • Requête de cluster DNS : nil

Configuration de la File de Messages​

Les paramètres de la file de messages sont configurés dans config/runtime.exs :

  • Temps de lettre morte : 1440 minutes (24 heures avant l'expiration du message)

Intégration de Facturation​

Les paramètres de facturation sont configurés dans config/runtime.exs :

  • URL : http://localhost:2080/jsonrpc
  • Locataire : mnc001.mcc001.3gppnetwork.org
  • Destination : 55512341234
  • Source : 00101900000257
  • Sujet : 00101900000257
  • Compte : 00101900000257

Configuration de Routage SMS​

Le système de routage SMS utilise des routes dynamiques, soutenues par une base de données, qui peuvent être gérées via l'interface Web ou le fichier de configuration. Les routes sont chargées depuis config/runtime.exs lors du premier démarrage.

Exemple de Configuration :

config :sms_c, :sms_routes, [
%{
called_prefix: "+44",
dest_smsc: "InternationalGW",
weight: 100,
priority: 100,
description: "SMS International UK",
enabled: true
},
%{
called_prefix: "1900",
dest_smsc: "PremiumGW",
charged: :yes,
priority: 50,
description: "Numéros Premium US",
enabled: true
}
]

Fonctionnalités :

  • Correspondance basée sur préfixe (numéros appelants/appelés)
  • Filtrage par SMSC source et type
  • Routage basé sur priorité et poids
  • Capacités de réponse automatique et de suppression
  • Contrôle de facturation par route
  • Gestion à l'exécution via l'interface Web à /routing

📖 Voir Guide de Routage SMS pour une documentation complète, des exemples et une référence API.

Points de Terminaison de l'API REST​

Opérations de la File de Messages​

Soumettre SMS (Créer un Message)​

POST /api/messages
Content-Type: application/json

{
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Bonjour, le monde !",
"source_smsc": "web-app",
"dest_smsc": "smpp-provider", # Optionnel - le moteur de routage assigne si nul
"tp_dcs_character_set": "gsm7", # Optionnel : gsm7, 8bit, latin1, ucs2
"tp_dcs_coding_group": "general_data_coding",
"expires": "2025-10-17T10:30:00Z" # Optionnel - par défaut 24h à partir de maintenant
}

Champs Obligatoires :

  • destination_msisdn - Numéro de téléphone de destination
  • message_body - Contenu du texte du message
  • source_msisdn - Numéro de téléphone source
  • source_smsc - Identifiant du système source

Champs Optionnels :

  • dest_smsc - Passerelle de destination (le moteur de routage assigne si non fourni)
  • source_imsi, dest_imsi - Identifiants IMSI
  • tp_dcs_character_set - Encodage des caractères (gsm7, 8bit, latin1, ucs2)
  • tp_dcs_coding_group - Groupe de codage DCS
  • tp_dcs_compressed - Indicateur de compression (booléen)
  • tp_dcs_has_message_class - Indicateur de classe de message (booléen)
  • tp_dcs_message_class - Valeur de la classe de message
  • tp_user_data_header - En-tête de données utilisateur (map)
  • message_part_number, message_parts - Champs de message multipart
  • expires - Horodatage d'expiration (par défaut 24 heures)
  • deliver_after - Horodatage de livraison différée
  • deadletter, raw_data_flag, raw_sip_flag - Indicateurs booléens

Réponse :

{
"status": "success",
"data": {
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"dest_smsc": "smpp-provider",
"message_body": "Bonjour, le monde !",
"deliver_time": null,
"delivery_attempts": 0,
"expires": "2025-10-17T10:30:00Z",
"inserted_at": "2025-10-16T10:30:00Z"
}
}

Obtenir des Messages pour SMSC​

GET /api/messages/get_by_smsc?smsc=my-smsc-name

Retourne tous les messages non livrés où :

  • destination_smsc est nul OU correspond au nom SMSC fourni
  • Le message n'est pas expiré
  • Prêt à être envoyé (deliver_after est nul ou dans le passé)

Réponse :

{
"status": "success",
"data": [
{
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Bonjour",
"destination_smsc": "my-smsc-name",
"delivery_attempts": 0
}
]
}

Lister les Messages avec Filtrage SMSC Optionnel​

# Lister tous les messages dans la file
GET /api/messages

# Lister les messages pour un SMSC spécifique (avec filtrage d'en-tête)
GET /api/messages
SMSc: my-smsc-name

Sans En-tête SMSc : Retourne tous les messages dans la file, quel que soit l'état de livraison ou l'expiration.

Avec En-tête SMSc : Retourne les messages non livrés où :

  • dest_smsc correspond à la valeur de l'en-tête OU dest_smsc est nul
  • deliver_time est nul (pas encore livré)
  • deliver_after est nul ou avant/égal au temps actuel (prêt à livrer)
  • expires est après le temps actuel (non expiré)
  • Ordonné par temps d'insertion (le plus ancien en premier)

Remarque : L'approche d'en-tête SMSc permet aux frontaux externes de demander leurs messages en utilisant le même modèle de point de terminaison, l'en-tête contrôlant le comportement de filtrage.

Réponse :

[
{
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Bonjour, le monde !",
"dest_smsc": "my-smsc-name",
"deliver_time": null,
"delivery_attempts": 0,
"expires": "2025-10-17T10:30:00Z",
"inserted_at": "2025-10-16T10:30:00Z"
}
]

Obtenir un Message Unique​

GET /api/messages/{id}

Mettre à Jour un Message​

PATCH /api/messages/{id}
Content-Type: application/json

{
"status": "delivered",
"delivered_at": "2025-10-16T10:30:00Z"
}

Supprimer un SMS​

DELETE /api/messages/{id}

Gérer l'Échec de Livraison (Incrémenter le Compteur de Réessai)​

Lorsqu'une livraison de message échoue temporairement, incrémentez le compteur de tentatives de livraison et planifiez un réessai avec un backoff exponentiel.

Méthode 1 : Utiliser PUT (Recommandé)

# Simple et sémantique - PUT indique la mise à jour de l'état de livraison
PUT /api/messages/{id}

Méthode 2 : Utiliser un Point de Terminaison Explicite

# Point de terminaison explicite alternatif
POST /api/messages/{id}/increment_delivery_attempt

Les deux méthodes incrémentent delivery_attempts et définissent un délai de backoff exponentiel via deliver_after :

TentativeFormule de BackoffDélaiTemps Total
1ère2^1 minutes2 min2 min
2ème2^2 minutes4 min6 min
3ème2^3 minutes8 min14 min
4ème2^4 minutes16 min30 min
5ème2^5 minutes32 min1h 2min
6ème2^6 minutes64 min2h 6min

Réponse :

{
"id": 123,
"delivery_attempts": 1,
"deliver_after": "2025-10-20T19:05:00Z",
"deliver_time": null,
"expires": "2025-10-21T19:03:00Z",
...
}

Remarque : Les messages avec un deliver_after futur sont automatiquement filtrés des requêtes GET jusqu'à ce que la période de backoff expire.

Mettre à Jour un Message (Mise à Jour Partielle)​

Pour mettre à jour des champs spécifiques d'un message (comportement inchangé) :

PATCH /api/messages/{id}
Content-Type: application/json

{
"dest_smsc": "passerelle-mise-à-jour",
"status": "delivered"
}

Important : PUT et PATCH se comportent différemment :

  • PUT → Incrémente les tentatives de livraison avec backoff (aucun corps requis)
  • PATCH → Effectue des mises à jour de champs partiels (corps requis)

Suivi de la Santé des Frontaux​

Le cœur de SMS_C suit la santé et la disponibilité des frontaux externes grâce à un système d'enregistrement. Cela permet de surveiller le temps de disponibilité des frontaux, de détecter les pannes et de maintenir des données historiques de disponibilité.

Remarque : L'enregistrement des frontaux n'est PAS utilisé pour la livraison ou le routage des messages. Les messages sont routés en fonction du champ dest_smsc. Le système d'enregistrement existe uniquement pour la surveillance de la santé et la visibilité opérationnelle.

Comment Fonctionne l'Enregistrement des Frontaux​

Chaque frontal externe (passerelle SMPP, IMS, SS7/MAP) envoie périodiquement un enregistrement de cœur battant au cœur de SMS_C :

  1. Intervalle de Battement : Les frontaux doivent s'enregistrer toutes les 30-60 secondes
  2. Temps d'Expiration : Les enregistrements expirent après 90 secondes sans mise à jour
  3. Gestion Automatique des États :
    • Les nouveaux frontaux créent un nouvel enregistrement
    • Les frontaux actifs existants mettent à jour leur enregistrement (prolonge l'expiration)
    • Les frontaux expirés qui reviennent en ligne créent une nouvelle période d'enregistrement

Points de Terminaison d'Enregistrement des Frontaux​

Enregistrer/Mise à Jour un Frontal (Cœur Battant)​
POST /api/frontends
Content-Type: application/json

{
"frontend_name": "smpp-gateway-1",
"frontend_type": "SMPP",
"ip_address": "10.0.1.5",
"hostname": "smpp-gw-01",
"uptime_seconds": 3600,
"configuration": "{\"port\": 2775, \"system_id\": \"smpp_user\"}"
}

Champs Obligatoires :

  • frontend_name - Identifiant unique pour l'instance de frontal
  • frontend_type - Type de frontal (SMPP, IMS, MAP, etc.)

Champs Optionnels :

  • ip_address - Adresse IP du frontal (détectée automatiquement à partir de la source de la requête si non fournie)
  • hostname - Nom d'hôte du serveur frontal
  • uptime_seconds - Secondes depuis le démarrage du frontal
  • configuration - Chaîne JSON avec la configuration spécifique au frontal

Remarque : Si ip_address n'est pas fourni, le cœur de SMS_C utilisera automatiquement l'IP source de la requête HTTP. Cela fonctionne avec des connexions directes et des requêtes proxy (via l'en-tête X-Forwarded-For).

Réponse :

{
"id": 42,
"frontend_name": "smpp-gateway-1",
"frontend_type": "SMPP",
"ip_address": "10.0.1.5",
"hostname": "smpp-gw-01",
"uptime_seconds": 3600,
"status": "active",
"last_seen_at": "2025-10-20T10:30:00Z",
"expires_at": "2025-10-20T10:31:30Z",
"inserted_at": "2025-10-20T10:00:00Z"
}
Lister Tous les Enregistrements de Frontaux​
GET /api/frontends

Retourne tous les enregistrements de frontaux (actifs et expirés), ordonnés par la plus récente activité.

Lister Uniquement les Frontaux Actifs​
GET /api/frontends/active

Retourne uniquement les frontaux actuellement actifs (non expirés).

Obtenir des Statistiques de Frontal​
GET /api/frontends/stats

Retourne des statistiques récapitulatives :

{
"active": 5,
"expired": 12,
"unique_frontends": 8
}
Obtenir l'Historique des Frontaux​
GET /api/frontends/history/{frontend_name}

Retourne tous les enregistrements historiques pour un frontal spécifique, utile pour analyser les modèles de disponibilité/indisponibilité.

Exemple :

GET /api/frontends/history/smpp-gateway-1
Obtenir un Enregistrement Spécifique​
GET /api/frontends/{id}

Mise en Œuvre dans les Frontaux Externes​

Les frontaux externes doivent mettre en œuvre une tâche en arrière-plan qui envoie des cœurs battants :

Exemple (pseudocode) :

import time
import requests

def send_heartbeat():
"""Envoyer un cœur battant toutes les 30 secondes"""
while True:
try:
data = {
"frontend_name": "my-smpp-gateway",
"frontend_type": "SMPP",
"ip_address": get_local_ip(),
"hostname": get_hostname(),
"uptime_seconds": get_uptime()
}

response = requests.post(
"https://smsc-core:8443/api/frontends",
json=data,
timeout=5
)

if response.status_code in [200, 201]:
logger.debug("Cœur battant envoyé avec succès")
else:
logger.error(f"Échec du cœur battant : {response.status_code}")

except Exception as e:
logger.error(f"Erreur de cœur battant : {e}")

time.sleep(30) # Envoyer toutes les 30 secondes

# Démarrer le cœur battant dans un thread en arrière-plan
threading.Thread(target=send_heartbeat, daemon=True).start()

Surveillance de la Santé des Frontaux​

Panneau de Contrôle - L'interface Web à https://localhost:8086 affiche :

  • Les frontaux actuellement actifs
  • Horodatage de la dernière vue pour chaque frontal
  • Suivi du temps de disponibilité
  • Disponibilité historique

Requêtes API :

# Obtenir tous les frontaux actifs
curl https://localhost:8443/api/frontends/active

# Vérifier si un frontal spécifique est en ligne
curl https://localhost:8443/api/frontends/history/smpp-gateway-1 | jq '.[0].status'

# Obtenir des statistiques de santé
curl https://localhost:8443/api/frontends/stats

Autres Points de Terminaison​

Statut​

GET /api/status

Lieux​

GET /api/locations
POST /api/locations
GET /api/locations/{id}
PATCH /api/locations/{id}
DELETE /api/locations/{id}

Événements SS7​

GET /api/ss7_events
POST /api/ss7_events
GET /api/ss7_events/{id}
PATCH /api/ss7_events/{id}
DELETE /api/ss7_events/{id}

File de Messages MMS​

GET /api/mms_message_queues
POST /api/mms_message_queues
GET /api/mms_message_queues/{id}
PATCH /api/mms_message_queues/{id}
DELETE /api/mms_message_queues/{id}

Performance​

Le cœur de SMS_C offre un débit exceptionnel en utilisant Mnesia pour le stockage de messages en mémoire avec archivage automatique vers SQL pour la rétention à long terme des CDR.

Résultats des Benchmarks​

Mesuré sur Intel i7-8650U @ 1.90GHz (8 cœurs) :

Performance d'Insertion de Message :

  • insert_message (avec routage) : 1 750 msg/sec (latence moyenne de 0,58 ms)
  • insert_message (simple) : 1 750 msg/sec (latence moyenne de 0,57 ms)
  • Capacité d'environ 150 millions de messages par jour

Performance de Requête :

  • get_messages_for_smsc : 800 msg/sec (1,25 ms en moyenne)
  • list_message_queues : Accès rapide en mémoire
  • Utilisation de la mémoire : 62 Ko par opération d'insertion

Architecture​

Stratégie de Stockage :

  • Messages Actifs : Stockés dans Mnesia (en mémoire + disque) pour un accès ultra-rapide
  • Archive de Messages : Archivés automatiquement vers SQL pour le stockage à long terme des CDR
  • Rétention : Période de rétention configurable (par défaut : 24 heures)
  • Pas de goulet d'étranglement SQL : Toutes les opérations de message actif contournent SQL

Configuration​

Le stockage et la rétention des messages sont configurés dans config/runtime.exs :

config :sms_c,
message_retention_hours: 24, # Archiver les messages de plus de 24 heures
batch_insert_batch_size: 100, # Taille du lot CDR pour l'archivage SQL
batch_insert_flush_interval_ms: 100 # Intervalle de vidage CDR

Surveillance​

Panneau de Contrôle - Interface Web à https://localhost:8086

  • Voir la file de messages
  • Soumettre des messages de test
  • Gérer le routage SMS (voir Guide de Routage)
  • Simuler des décisions de routage
  • Voir les ressources système
  • Suivre les statistiques des travailleurs de lot

Statistiques des Travailleurs de Lot :

# Obtenir les statistiques actuelles des travailleurs de lot
SmsC.Messaging.BatchInsertWorker.stats()

Retourne :

%{
total_enqueued: 10000,
total_flushed: 9900,
current_queue_size: 100,
last_flush_duration_ms: 45
}

Logs - Les logs de l'application sont écrits sur stdout

# Voir les logs en temps réel
tail -f log/dev.log

Dépannage​

Port Déjà Utilisé​

# Trouver le processus utilisant le port
lsof -i :4000

# Tuer le processus
kill -9 <PID>

Frontend Externe Ne Se Connecte Pas​

Symptômes : Messages bloqués dans la file, les logs du frontal montrent des erreurs de connexion

Vérifiez :

  • Vérifiez que API_BASE_URL est correctement défini dans le frontal
  • Vérifiez que le cœur SMS_C fonctionne et est accessible
  • Passez en revue les règles réseau/pare-feu
  • Vérifiez la configuration du frontal

Solution :

# Tester la connectivité API depuis le frontal
curl http://localhost:4000/api/status

# Redémarrer le frontal
export API_BASE_URL="http://localhost:4000"
# Démarrer l'application frontal

Messages Non Livrés​

Symptômes : Les messages restent non livrés, les tentatives de réessai s'incrémentent

Vérifiez :

  1. Logs du frontal pour les erreurs d'envoi
  2. Connectivité réseau externe
  3. Configuration du frontal (identifiants, adresses)
  4. Compatibilité du format de message

Voir les messages échoués :

# Obtenir les messages avec tentatives de réessai
curl https://localhost:8443/api/messages | jq '.data[] | select(.delivery_attempts > 0)'

Latence Élevée des Messages​

Symptômes : Les messages prennent plus de temps que prévu, backlog dans la file

Vérifiez :

  1. Intervalle de sondage du frontal (peut nécessiter une réduction pour un sondage plus fréquent)
  2. Performance de la base de données
  3. Latence réseau vers les systèmes externes

Surveiller la profondeur de la file :

watch -n 5 'curl -s https://localhost:8443/api/messages | jq ".data | length"'