Change in mncc-python[master]: mncc_mt_loadgen.py: Program for MT call load testing with rtpsource

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

laforge gerrit-no-reply at lists.osmocom.org
Sun Mar 8 22:48:45 UTC 2020


laforge has submitted this change. ( https://gerrit.osmocom.org/c/mncc-python/+/17403 )

Change subject: mncc_mt_loadgen.py: Program for MT call load testing with rtpsource
......................................................................

mncc_mt_loadgen.py: Program for MT call load testing with rtpsource

This program (derived from mncc_test.py) is used to start MT calls
via the MNCC interface of OsmoMSC.  Every calls RTP is connected
to the new 'rtpsource' process, which generates a realistic RTP
flow in terms of number of packets (20ms interval) and payload size.

Change-Id: I9e5d799aaeeff5188dc97061f0d6e1873d9bf653
---
A ctrl.py
A mncc_mt_loadgen.py
2 files changed, 258 insertions(+), 0 deletions(-)

Approvals:
  laforge: Looks good to me, approved; Verified



diff --git a/ctrl.py b/ctrl.py
new file mode 100644
index 0000000..6f51684
--- /dev/null
+++ b/ctrl.py
@@ -0,0 +1,70 @@
+# Simple class for synchronous/blocking interface with a remote
+# Osmocom program via the CTRL interface
+
+# (C) 2020 by Harald Welte <laforge at gnumonks.org>
+#
+# Licensed under GNU General Public License, Version 2 or at your
+# option, any later version.
+
+from osmopy.osmo_ipa import Ctrl
+import socket
+
+class OsmoCtrlSimple:
+    '''Simple class for synchronous/blocking interface with a remote
+       Osmocom program via the CTRL interface'''
+    remote_host = "127.0.0.1"
+    remote_port = 0
+    sock = None
+
+    def __init__(self, host=None, port=None):
+        self.remote_host = host
+        self.remote_port = port
+
+    def connect(self, host=None, port=None):
+        if host:
+            self.remote_host = host
+        if port:
+            self.remote_port = port
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.setblocking(1)
+        self.sock.connect((self.remote_host, self.remote_port))
+        return self.sock
+
+    def disconnect(self):
+        self.sock.close()
+        self.sock = None
+
+    def _leftovers(self, fl):
+        """
+        Read outstanding data if any according to flags
+        """
+        try:
+                data = self.sock.recv(1024, fl)
+        except socket.error as _:
+                return False
+        if len(data) != 0:
+                tail = data
+                while True:
+                        (head, tail) = Ctrl().split_combined(tail)
+                        print("Got message:", Ctrl().rem_header(head))
+                        if len(tail) == 0:
+                                break
+                return True
+        return False
+
+    def do_set_get(self, var, value = None):
+        self._leftovers(socket.MSG_DONTWAIT)
+        (r, c) = Ctrl().cmd(var, value)
+        self.sock.send(c)
+        while True:
+                ret = self.sock.recv(4096)
+                # handle multiple messages, ignore TRAPs
+                ret = Ctrl().skip_traps(ret)
+                if ret != None:
+                    (i, k, v) = Ctrl().parse(ret)
+                    break;
+        return (Ctrl().rem_header(ret),) + Ctrl().verify(ret, r, var, value)
+
+    def set_var(self, var, val):
+        (a, _, _) = self.do_set_get(var, val)
+        return a
diff --git a/mncc_mt_loadgen.py b/mncc_mt_loadgen.py
new file mode 100755
index 0000000..a7daabf
--- /dev/null
+++ b/mncc_mt_loadgen.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python2
+
+# Python testing tool for establishing mobile-terminated calls using
+# the OsmoMSC MNCC interface.  It wants to communicate via CTRL with a
+# 'rtpsoucre' program which will take care of generating RtP flows
+# for the MT calls that are established.
+#
+# (C) 2015, 2020 by Harald Welte <laforge at gnumonks.org>
+#
+# Licensed under GNU General Public License, Version 2 or at your
+# option, any later version.
+
+RTPSOURCE_CTRL_IP = "127.0.0.1"
+RTPSOURCE_CTRL_PORT = 11111
+
+
+from gsm_call_fsm import GsmCallFsm, GSM48
+from mncc_sock import MnccSocket
+from thread import start_new_thread
+from ctrl import OsmoCtrlSimple
+import pykka
+import logging as log
+import signal, sys, time
+import readline, code
+import socket
+import struct
+import mncc
+from mncc_sock import mncc_rtp_msg
+
+def int2ipstr(addr):
+    '''Convert from an integer to a strinf-formatted IPv4 address'''
+    return socket.inet_ntoa(struct.pack("!I", addr))
+
+def ipstr2int(addr):
+    '''Convert from a string-formatted IPv4 address to integer'''
+    return struct.unpack("!I", socket.inet_aton(addr))[0]
+
+
+class MnccActor(pykka.ThreadingActor):
+    '''MnccActor provides an interface for GsmCallFsm to send MNCC messages'''
+    def __init__(self, mncc_sock):
+        super(MnccActor, self).__init__()
+        self.mncc_sock = mncc_sock
+
+    def on_receive(self, message):
+        if message['type'] == 'send':
+            msg = message['msg']
+            log.debug('MnccActor TxMNCC %s' % msg)
+            mncc_sock.send(msg)
+        else:
+            raise Exception('mncc', 'MnccActor Received unhandled %s' % message)
+
+def mncc_rx_thread(mncc_sock):
+    '''MNCC receive thread, broadcasting received MNCC packets to GsmCallFsm'''
+    while 1:
+        msg = mncc_sock.recv()
+        if msg == None:
+            raise Exception('mncc', 'MnccSocket disconnected')
+
+        if msg.is_frame():
+            log.warning("Dropping traffic frame: %s" % msg)
+            continue
+
+        log.debug("MnccActor RxMNCC %s, broadcasting to Call FSMs" % msg)
+        # we simply broadcast to all calls
+        pykka.ActorRegistry.broadcast({'type': 'mncc', 'msg': msg}, GsmCallFsm)
+
+
+class RtpSourceCtrlActor(pykka.ThreadingActor):
+    '''Actor for talking with the rtpsource program via an osmocom CTRL interface'''
+    def __init__(self, ctrl_host, ctrl_port=11111):
+        super(RtpSourceCtrlActor, self).__init__()
+        self.ctrl = OsmoCtrlSimple(ctrl_host, ctrl_port)
+        self.ctrl.connect()
+
+    def _set_var(self, var, val):
+        (res, var, val) = self.ctrl.do_set_get(var, val)
+        log.debug('RtpSourceCtrlActor result=%s var=%s val=%s' % (res, var, val))
+        return (res, var, val)
+
+    def on_receive(self, message):
+        if message['type'] == 'rtp_create':
+            (res, var, val) = self._set_var('rtp_create', message['cname'])
+            v = val.split(',') # input looks like '1,127.23.23.23,37723'
+            return {'cname': v[0], 'remote_host': v[1], 'remote_port': int(v[2])}
+        elif message['type'] == 'rtp_connect':
+            val = '%s,%s,%u,%u' % (message['cname'], message['remote_host'],
+                                   message['remote_port'], message['payload_type'])
+            (res, var, val) = self._set_var('rtp_connect', val)
+            return res
+        elif message['type'] == 'rtp_delete':
+            (res, var, val) = self._set_var('rtp_delete', message['cname'])
+            return res
+        else:
+            raise Exception('ctrl', 'RtpSourceCtrlActor Received unhandled %s' % message)
+
+
+class MTCallRtpsource(pykka.ThreadingActor):
+    '''Actor to start a network-initiated MT (mobile terminated) call to a given MSISDN,
+       connecting the RTP flow to an extenal rtpsource program'''
+    def __init__(self, mncc_act, ctrl_act, codecs_permitted = GSM48.AllCodecs):
+        super(MTCallRtpsource, self).__init__()
+        self.mncc_act = mncc_act
+        self.ctrl_act = ctrl_act
+        self.codecs_permitted = codecs_permitted
+        self.call = GsmCallFsm.start(self.mncc_act, self.actor_ref, True, self.codecs_permitted)
+        self.callref = self.call.ask({'type':'get_callref'})
+        self.state = 'NULL'
+        self.rtp_msc = None
+
+    def start_call(self, msisdn_called, msisdn_calling):
+        '''Start a MT call from given [external] calling party to given mobile party MSISDN'''
+        self.msisdn_called = msisdn_called
+        self.msisdn_calling = msisdn_calling
+        # allocate a RTP connection @ rtpsource
+        r = self.ctrl_act.ask({'type':'rtp_create', 'cname':self.callref})
+        self.ext_rtp_host = r['remote_host']
+        self.ext_rtp_port = r['remote_port']
+        # start the MNCC call FSM
+        self.call.tell({'type':'start_mt_call',
+                        'calling':self.msisdn_calling, 'called':self.msisdn_called})
+
+    def on_stop(self):
+        # Attempt to do a graceful shutdown by deleting the RTP connection from rtpsource
+        self.ctrl_act.ask({'type':'rtp_delete', 'cname':self.callref})
+        self.call.stop()
+
+    def on_failure(self, exception_type, exception_value, traceback):
+        # on_stop() is not called in case of failure; fix that
+        self.on_stop()
+
+    def on_receive(self, message):
+        if message['type'] == 'rtp_create_ind':
+            # MSC+MGW informs us of the PLMN side IP/port
+            mncc_rtp_ind = message['rtp']
+            # tell external rtpsourc to connect to this address
+            r = self.ctrl_act.ask({'type':'rtp_connect', 'cname':self.callref,
+                                   'remote_host': int2ipstr(mncc_rtp_ind.ip),
+                                   'remote_port': mncc_rtp_ind.port,
+                                   'payload_type':3})
+            # tell MSC to connect to ip/port @ rtpsource
+            mncc_rtp_conn = mncc_rtp_msg(msg_type=mncc.MNCC_RTP_CONNECT, callref=self.callref,
+                                         ip=ipstr2int(self.ext_rtp_host), port=self.ext_rtp_port)
+            self.mncc_act.tell({'type': 'send', 'msg': mncc_rtp_conn})
+        elif message['type'] == 'call_state_change':
+            if message['new_state'] == 'NULL':
+                # Call FSM has reached the NULL state again (call terminated) 
+                # on_stop() will clean up the RTP connection at rtpsource
+                self.stop()
+
+
+# capture SIGINT (Ctrl+C) and stop all actors
+def sigint_handler(signum, frame):
+    pykka.ActorRegistry.stop_all()
+    sys.exit(0)
+
+log.basicConfig(level = log.DEBUG,
+    format = "%(levelname)s %(filename)s:%(lineno)d %(message)s")
+
+signal.signal(signal.SIGINT, sigint_handler)
+
+# start the MnccSocket and associated pykka actor + rx thread
+mncc_sock = MnccSocket()
+mncc_act = MnccActor.start(mncc_sock)
+start_new_thread(mncc_rx_thread, (mncc_sock,))
+
+# connect via CTRL to rtpsource
+rtpctrl_act = RtpSourceCtrlActor.start(RTPSOURCE_CTRL_IP, RTPSOURCE_CTRL_PORT)
+
+# convenience wrapper
+def mt_call(msisdn_called, msisdn_calling = '123456789', codecs = GSM48.AllCodecs):
+    call_conn = MTCallRtpsource.start(mncc_act, rtpctrl_act, codecs).proxy()
+    call_conn.start_call(msisdn_called, msisdn_calling)
+    return call_conn
+
+# start one call automatically
+mt_call('90001')
+
+# start a shell to enable the user to add more calls as needed
+vars = globals().copy()
+vars.update(locals())
+shell = code.InteractiveConsole(vars)
+try:
+    shell.interact()
+except SystemExit:
+    pass
+# clan up after user leaves interactive shell
+sigint_handler(signal.SIGINT, None)

-- 
To view, visit https://gerrit.osmocom.org/c/mncc-python/+/17403
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: mncc-python
Gerrit-Branch: master
Gerrit-Change-Id: I9e5d799aaeeff5188dc97061f0d6e1873d9bf653
Gerrit-Change-Number: 17403
Gerrit-PatchSet: 3
Gerrit-Owner: laforge <laforge at osmocom.org>
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200308/2329ee45/attachment.htm>


More information about the gerrit-log mailing list