<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmo-hlr/+/16204">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved
  pespin: Looks good to me, but someone else must approve
  keith: Looks good to me, but someone else must approve

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

<div style="display:none"> Gerrit-Project: osmo-hlr </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I26e8dd8d9a08187fccb3e74ee91366bc24f6c608 </div>
<div style="display:none"> Gerrit-Change-Number: 16204 </div>
<div style="display:none"> Gerrit-PatchSet: 24 </div>
<div style="display:none"> Gerrit-Owner: neels <nhofmeyr@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: keith <keith@rhizomatica.org> </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-Reviewer: neels <nhofmeyr@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: osmith <osmith@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>