contrib/dgsm/ add example esme and dialplan
Add example scripts for the distributed GSM network: esme_dgsm.py: connect to the SMPP port of OsmoMSC A and forward SMS to the SMPP port of OsmoMSC B. The IP and port of OsmoMSC B is retrieved by the receiver's MSISDN using osmo-mslookup-client. contrib/dgsm/freeswitch_dialplan_dgsm.py: resolve the destination SIP servers of calls with osmo-mslookup-client and bridge the calls accordingly. For a detailed overview of the D-GSM and mslookup related files, please see the elaborate comment at the top of mslookup.c (already added in an earlier patch). Related: OS#4254 Related: OS#4255 Change-Id: I26e8dd8d9a08187fccb3e74ee91366bc24f6c608
This commit is contained in:
parent
52ef60fe96
commit
e53a34a7e1
|
@ -1,4 +1,6 @@
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
|
esme_dgsm.py \
|
||||||
|
freeswitch_dialplan_dgsm.py \
|
||||||
osmo-mslookup-pipe.py \
|
osmo-mslookup-pipe.py \
|
||||||
osmo-mslookup-socket.py \
|
osmo-mslookup-socket.py \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
Copyright 2019 sysmocom s.f.m.c GmbH <info@sysmocom.de>
|
||||||
|
|
||||||
|
WARNING: this is just a proof-of-concept implementation, it blocks for every
|
||||||
|
received SMPP request and is not suitable for servicing more than one request
|
||||||
|
at a time.
|
||||||
|
|
||||||
|
Based on esme.py from RCCN (license changed with permission from author):
|
||||||
|
https://github.com/Rhizomatica/rccn/blob/master/rccn/esme.py
|
||||||
|
Copyright 2017 keith <keith@rhizomatica.org>
|
||||||
|
|
||||||
|
Forward SMS to the receiver's SMSC, as determined with mslookup.
|
||||||
|
Requires smpplip (pip3 install --user smpplib) and osmo-mslookup-client.
|
||||||
|
|
||||||
|
Example SMPP configuration for osmo-msc.cfg:
|
||||||
|
smpp
|
||||||
|
local-tcp-ip 127.0.0.1 2775
|
||||||
|
policy closed
|
||||||
|
smpp-first
|
||||||
|
# outgoing to esme_dgsm.py
|
||||||
|
esme OSMPP
|
||||||
|
no alert-notifications
|
||||||
|
password foo
|
||||||
|
default-route
|
||||||
|
# incoming from esme_dgsm.py
|
||||||
|
esme ISMPP
|
||||||
|
no alert-notifications
|
||||||
|
password foo
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import smpplib
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def can_handle_pdu(pdu):
|
||||||
|
if not isinstance(pdu, smpplib.command.DeliverSM):
|
||||||
|
logging.info('PDU is not a DeliverSM, ignoring')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if int(pdu.dest_addr_ton) == smpplib.consts.SMPP_TON_INTL:
|
||||||
|
logging.info("Unable to handle SMS for %s: SMPP_TON_INTL" %
|
||||||
|
(pdu.destination_addr))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def query_mslookup(service_type, id, id_type='msisdn'):
|
||||||
|
query_str = '%s.%s.%s' % (service_type, id, id_type)
|
||||||
|
logging.info('mslookup: ' + query_str)
|
||||||
|
|
||||||
|
result_line = subprocess.check_output(['osmo-mslookup-client', query_str,
|
||||||
|
'-f', 'json'])
|
||||||
|
if isinstance(result_line, bytes):
|
||||||
|
result_line = result_line.decode('ascii')
|
||||||
|
|
||||||
|
logging.info('mslookup result: ' + result_line.rstrip())
|
||||||
|
return json.loads(result_line)
|
||||||
|
|
||||||
|
|
||||||
|
def tx_sms(dst_host, dst_port, source, destination, registered_delivery,
|
||||||
|
unicode_text):
|
||||||
|
smpp_client = smpplib.client.Client(dst_host, dst_port, 90)
|
||||||
|
smpp_client.connect()
|
||||||
|
smpp_client.bind_transceiver(system_id=args.dst_id, password=args.dst_pass)
|
||||||
|
logging.info('Connected to destination SMSC (%s@%s:%s)' % (args.dst_id,
|
||||||
|
dst_host, dst_port))
|
||||||
|
|
||||||
|
pdu = smpp_client.send_message(
|
||||||
|
source_addr_ton=smpplib.consts.SMPP_TON_ALNUM,
|
||||||
|
source_addr_npi=smpplib.consts.SMPP_NPI_UNK,
|
||||||
|
source_addr=source.decode(),
|
||||||
|
dest_addr_ton=smpplib.consts.SMPP_TON_SBSCR,
|
||||||
|
dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
|
||||||
|
destination_addr=destination.decode(),
|
||||||
|
short_message=unicode_text,
|
||||||
|
registered_delivery=registered_delivery,
|
||||||
|
)
|
||||||
|
|
||||||
|
smpp_client.unbind()
|
||||||
|
smpp_client.disconnect()
|
||||||
|
del pdu
|
||||||
|
del smpp_client
|
||||||
|
|
||||||
|
|
||||||
|
def rx_deliver_sm(pdu):
|
||||||
|
if not can_handle_pdu(pdu):
|
||||||
|
return smpplib.consts.SMPP_ESME_RSYSERR
|
||||||
|
|
||||||
|
msisdn = pdu.destination_addr.decode()
|
||||||
|
logging.info("Incoming SMS for: " + msisdn)
|
||||||
|
|
||||||
|
if args.sleep:
|
||||||
|
logging.info("Sleeping for %i seconds" % (args.sleep))
|
||||||
|
time.sleep(args.sleep)
|
||||||
|
logging.info("Sleep done")
|
||||||
|
|
||||||
|
result = query_mslookup("smpp.sms", msisdn)
|
||||||
|
if 'v4' not in result or not result['v4']:
|
||||||
|
logging.info('No IPv4 result from mslookup! This example only'
|
||||||
|
' makes use of IPv4, dropping.')
|
||||||
|
return smpplib.consts.SMPP_ESME_RSYSERR
|
||||||
|
|
||||||
|
dst_host, dst_port = result['v4']
|
||||||
|
tx_sms(dst_host, dst_port, pdu.source_addr,
|
||||||
|
pdu.destination_addr, int(pdu.registered_delivery),
|
||||||
|
pdu.short_message)
|
||||||
|
|
||||||
|
return smpplib.consts.SMPP_ESME_ROK
|
||||||
|
|
||||||
|
|
||||||
|
def smpp_bind():
|
||||||
|
client = smpplib.client.Client(args.src_host, args.src_port, 90)
|
||||||
|
client.set_message_received_handler(rx_deliver_sm)
|
||||||
|
client.connect()
|
||||||
|
client.bind_transceiver(system_id=args.src_id, password=args.src_pass)
|
||||||
|
logging.info('Connected to source SMSC (%s@%s:%s)' % (args.src_id,
|
||||||
|
args.src_host, args.src_port))
|
||||||
|
logging.info('Waiting for SMS...')
|
||||||
|
client.listen()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global args
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--src-host', default='127.0.0.1',
|
||||||
|
help='source SMSC (OsmoMSC) host (default: 127.0.0.1)')
|
||||||
|
parser.add_argument('--src-port', default=2775, type=int,
|
||||||
|
help='source SMSC (OsmoMSC) port (default: 2775)')
|
||||||
|
parser.add_argument('--src-id', default='OSMPP',
|
||||||
|
help='source system id, as configured in osmo-msc.cfg'
|
||||||
|
' (default: OSMPP)')
|
||||||
|
parser.add_argument('--src-pass', default='foo',
|
||||||
|
help='source system password, as configured in'
|
||||||
|
' osmo-msc.cfg (default: foo)')
|
||||||
|
parser.add_argument('--dst-id', default='ISMPP',
|
||||||
|
help='destination system id, as configured in'
|
||||||
|
' osmo-msc.cfg (default: ISMPP)')
|
||||||
|
parser.add_argument('--dst-pass', default='foo',
|
||||||
|
help='destination system password, as configured in'
|
||||||
|
' osmo-msc.cfg (default: foo)')
|
||||||
|
parser.add_argument('--sleep', default=0, type=float,
|
||||||
|
help='sleep time in seconds before forwarding an SMS,'
|
||||||
|
' to test multithreading (default: 0)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format='[%(asctime)s]'
|
||||||
|
' (%(threadName)s) %(message)s', datefmt="%H:%M:%S")
|
||||||
|
smpp_bind()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
Copyright 2019 sysmocom s.f.m.c GmbH <info@sysmocom.de>
|
||||||
|
|
||||||
|
This is a freeswitch dialplan implementation, see:
|
||||||
|
https://freeswitch.org/confluence/display/FREESWITCH/mod_python
|
||||||
|
|
||||||
|
Find the right SIP server with mslookup (depending on the destination number)
|
||||||
|
and bridge calls accordingly.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def query_mslookup(service_type, id, id_type='msisdn'):
|
||||||
|
query_str = '%s.%s.%s' % (service_type, id, id_type)
|
||||||
|
print('[dialplan-dgsm] mslookup: ' + query_str)
|
||||||
|
|
||||||
|
result_line = subprocess.check_output([
|
||||||
|
'osmo-mslookup-client', query_str, '-f', 'json'])
|
||||||
|
if isinstance(result_line, bytes):
|
||||||
|
result_line = result_line.decode('ascii')
|
||||||
|
|
||||||
|
print('[dialplan-dgsm] mslookup result: ' + result_line)
|
||||||
|
return json.loads(result_line)
|
||||||
|
|
||||||
|
|
||||||
|
def handler(session, args):
|
||||||
|
""" Handle calls: bridge to the SIP server found with mslookup. """
|
||||||
|
print('[dialplan-dgsm] call handler')
|
||||||
|
msisdn = session.getVariable('destination_number')
|
||||||
|
|
||||||
|
# Run osmo-mslookup-client binary. We have also tried to directly call the
|
||||||
|
# C functions with ctypes but this has lead to hard-to-debug segfaults.
|
||||||
|
try:
|
||||||
|
result = query_mslookup("sip.voice", msisdn)
|
||||||
|
|
||||||
|
# This example only makes use of IPv4
|
||||||
|
if not result['v4']:
|
||||||
|
print('[dialplan-dgsm] no IPv4 result from mslookup')
|
||||||
|
session.hangup('UNALLOCATED_NUMBER')
|
||||||
|
return
|
||||||
|
|
||||||
|
sip_ip, sip_port = result['v4']
|
||||||
|
dial_str = 'sofia/internal/sip:{}@{}:{}'.format(
|
||||||
|
msisdn, sip_ip, sip_port)
|
||||||
|
print('[dialplan-dgsm] dial_str: ' + str(dial_str))
|
||||||
|
|
||||||
|
session.execute('bridge', dial_str)
|
||||||
|
except:
|
||||||
|
print('[dialplan-dgsm]: exception during call handler')
|
||||||
|
session.hangup('UNALLOCATED_NUMBER')
|
||||||
|
|
||||||
|
|
||||||
|
def fsapi(session, stream, env, args):
|
||||||
|
""" Freeswitch refuses to load the module without this. """
|
||||||
|
stream.write(env.serialize())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('id', type=int)
|
||||||
|
parser.add_argument('-i', '--id-type', default='msisdn',
|
||||||
|
help='default: "msisdn"')
|
||||||
|
parser.add_argument('-s', '--service', default='sip.voice',
|
||||||
|
help='default: "sip.voice"')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
result = query_mslookup(args.service, args.id, args.id_type)
|
||||||
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue