aboutsummaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/a-link/sccp-split-by-con.lua170
-rw-r--r--contrib/bsc-test/README1
-rw-r--r--contrib/bsc-test/all_dial8
-rwxr-xr-xcontrib/bsc-test/dial.sh11
-rwxr-xr-xcontrib/bsc-test/drop-oml.sh6
-rwxr-xr-xcontrib/bsc-test/drop.sh8
-rw-r--r--contrib/bsc-test/hangup4
-rwxr-xr-xcontrib/bsc-test/msc.sh8
-rwxr-xr-xcontrib/bsc_control.py120
-rwxr-xr-xcontrib/bt.py33
-rwxr-xr-xcontrib/convert_to_enum.py37
-rwxr-xr-xcontrib/ctrl2sse.py147
-rwxr-xr-xcontrib/gprs/gb-proxy-unblock-bug.py58
-rw-r--r--contrib/gprs/gprs-bssgp-histogram.lua78
-rw-r--r--contrib/gprs/gprs-buffer-count.lua80
-rw-r--r--contrib/gprs/gprs-split-trace-by-tlli.lua46
-rw-r--r--contrib/gprs/gprs-verify-nu.lua59
-rw-r--r--contrib/hlr-remove-old.sql18
-rwxr-xr-xcontrib/hlrsync/hlrsync.py125
-rwxr-xr-xcontrib/ipa.py278
-rwxr-xr-xcontrib/jenkins.sh1
-rwxr-xr-xcontrib/mgcp_server.py60
-rw-r--r--contrib/nat/test_regexp.c30
-rw-r--r--contrib/nat/ussd_example.py65
-rwxr-xr-xcontrib/rtp/gen_rtp_header.erl420
-rw-r--r--contrib/rtp/rtp_replay.st21
-rw-r--r--contrib/rtp/rtp_replay_shared.st118
-rw-r--r--contrib/rtp/rtp_replay_sip.st87
-rw-r--r--contrib/rtp/timestamp_rtp.lua28
-rw-r--r--contrib/sms/fill-hlr.st66
-rw-r--r--contrib/sms/hlr-query.st10
-rw-r--r--contrib/sms/sqlite-probe.tap.d5
-rwxr-xr-xcontrib/soap.py188
-rw-r--r--contrib/systemd/osmo-bsc-mgcp.service11
-rw-r--r--contrib/systemd/osmo-bsc.service12
-rw-r--r--contrib/systemd/osmo-gbproxy.service12
-rw-r--r--contrib/systemd/osmo-nitb.service11
-rw-r--r--contrib/systemd/osmo-sgsn.service11
-rw-r--r--contrib/testconv/Makefile16
-rw-r--r--contrib/testconv/testconv_main.c133
-rwxr-xr-xcontrib/twisted_ipa.py384
41 files changed, 2983 insertions, 1 deletions
diff --git a/contrib/a-link/sccp-split-by-con.lua b/contrib/a-link/sccp-split-by-con.lua
new file mode 100644
index 00000000..f5d5502a
--- /dev/null
+++ b/contrib/a-link/sccp-split-by-con.lua
@@ -0,0 +1,170 @@
+-- Split trace based on SCCP Source
+-- There are still bugs to find... bugs bugs bugs... hmm
+do
+ local function init_listener()
+ print("CREATED LISTENER")
+ local tap = Listener.new("ip", "sccp && (ip.src == 172.16.1.81 || ip.dst == 172.16.1.81)")
+ local sccp_type_field = Field.new("sccp.message_type")
+ local sccp_src_field = Field.new("sccp.slr")
+ local sccp_dst_field = Field.new("sccp.dlr")
+ local msg_type_field = Field.new("gsm_a.dtap_msg_mm_type")
+ local lu_rej_field = Field.new("gsm_a.dtap.rej_cause")
+ local ip_src_field = Field.new("ip.src")
+ local ip_dst_field = Field.new("ip.dst")
+
+ --
+ local bssmap_msgtype_field = Field.new("gsm_a.bssmap_msgtype")
+ -- assignment failure 0x03
+ --
+
+ --
+ local dtap_cause_field = Field.new("gsm_a_dtap.cause")
+ local dtap_cc_field = Field.new("gsm_a.dtap_msg_cc_type")
+
+ local connections = {}
+
+ function check_failure(con)
+ check_lu_reject(con)
+ check_disconnect(con)
+ check_failures(con)
+ end
+
+ -- cipher mode reject
+ function check_failures(con)
+ local msgtype = bssmap_msgtype_field()
+ if not msgtype then
+ return
+ end
+
+ msgtype = tonumber(msgtype)
+ if msgtype == 89 then
+ print("Cipher mode reject")
+ con[4] = true
+ elseif msgtype == 0x03 then
+ print("Assignment failure")
+ con[4] = true
+ elseif msgtype == 0x22 then
+ print("Clear Request... RF failure?")
+ con[4] = true
+ end
+ end
+
+ -- check if a DISCONNECT is normal
+ function check_disconnect(con)
+ local msg_type = dtap_cc_field()
+ if not msg_type then
+ return
+ end
+
+ if tonumber(msg_type) ~= 0x25 then
+ return
+ end
+
+ local cause = dtap_cause_field()
+ if not cause then
+ return
+ end
+
+ cause = tonumber(cause)
+ if cause ~= 0x10 then
+ print("DISCONNECT != Normal")
+ con[4] = true
+ end
+ end
+
+ -- check if we have a LU Reject
+ function check_lu_reject(con)
+ local msg_type = msg_type_field()
+ if not msg_type then
+ return
+ end
+
+ msg_type = tonumber(tostring(msg_type))
+ if msg_type == 0x04 then
+ print("LU REJECT with " .. tostring(lu_rej_field()))
+ con[4] = true
+ end
+ end
+
+ function tap.packet(pinfo,tvb,ip)
+ local ip_src = tostring(ip_src_field())
+ local ip_dst = tostring(ip_dst_field())
+ local sccp_type = tonumber(tostring(sccp_type_field()))
+ local sccp_src = sccp_src_field()
+ local sccp_dst = sccp_dst_field()
+
+ local con
+
+ if sccp_type == 0x01 then
+ elseif sccp_type == 0x2 then
+ local src = string.format("%s-%s", ip_src, tostring(sccp_src))
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+ local datestring = os.date("%Y%m%d%H%M%S")
+ local pcap_name = string.format("alink_trace_%s-%s_%s.pcap", src, dst, datestring)
+ local dumper = Dumper.new_for_current(pcap_name)
+
+ local con = { ip_src, tostring(sccp_src), tostring(sccp_dst), false, dumper, pcap_name }
+
+ dumper:dump_current()
+ connections[src] = con
+ connections[dst] = con
+ elseif sccp_type == 0x4 then
+ -- close a connection... remove it from the list
+ local src = string.format("%s-%s", ip_src, tostring(sccp_src))
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+
+ local con = connections[src]
+ if not con then
+ return
+ end
+
+ con[5]:dump_current()
+ con[5]:flush()
+
+ -- this causes a crash on unpacted wireshark
+ con[5]:close()
+
+ -- the connection had a failure
+ if con[4] == true then
+ local datestring = os.date("%Y%m%d%H%M%S")
+ local new_name = string.format("alink_failure_%s_%s-%s.pcap", datestring, con[2], con[3])
+ os.rename(con[6], new_name)
+ else
+ os.remove(con[6])
+ end
+
+
+ -- clear the old connection
+ connections[src] = nil
+ connections[dst] = nil
+
+ elseif sccp_type == 0x5 then
+ -- not handled yet... we should verify stuff here...
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+ local con = connections[dst]
+ if not con then
+ return
+ end
+ con[5]:dump_current()
+ elseif sccp_type == 0x6 then
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+ local con = connections[dst]
+ if not con then
+ print("DON'T KNOW THIS CONNECTION for " .. ip_dst)
+ return
+ end
+ con[5]:dump_current()
+ check_failure(con)
+ end
+
+ end
+ function tap.draw()
+ print("DRAW")
+ end
+ function tap.reset()
+ print("RESET")
+ end
+ end
+
+ init_listener()
+end
diff --git a/contrib/bsc-test/README b/contrib/bsc-test/README
new file mode 100644
index 00000000..adb222e2
--- /dev/null
+++ b/contrib/bsc-test/README
@@ -0,0 +1 @@
+Some crazy scripts call testing... and MSC link failure simulation
diff --git a/contrib/bsc-test/all_dial b/contrib/bsc-test/all_dial
new file mode 100644
index 00000000..96e5f00b
--- /dev/null
+++ b/contrib/bsc-test/all_dial
@@ -0,0 +1,8 @@
+ABORT BUSY
+ABORT 'NO CARRIER'
+ABORT 'OK'
+
+'' AT
+SAY "Dialing a number\n"
+'OK' ATD05660066;
+
diff --git a/contrib/bsc-test/dial.sh b/contrib/bsc-test/dial.sh
new file mode 100755
index 00000000..e5e19f63
--- /dev/null
+++ b/contrib/bsc-test/dial.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+# Evil dial script..
+
+while true;
+do
+ chat -v -f all_dial < /dev/ttyACM0 > /dev/ttyACM0
+ sleep 5s
+ chat -v -f hangup < /dev/ttyACM0 > /dev/ttyACM0
+ sleep 2s
+done
+
diff --git a/contrib/bsc-test/drop-oml.sh b/contrib/bsc-test/drop-oml.sh
new file mode 100755
index 00000000..84eead7b
--- /dev/null
+++ b/contrib/bsc-test/drop-oml.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+sleep 3
+echo "enable"
+sleep 1
+echo "drop bts connection 0 oml"
+sleep 1
diff --git a/contrib/bsc-test/drop.sh b/contrib/bsc-test/drop.sh
new file mode 100755
index 00000000..c7b66ba7
--- /dev/null
+++ b/contrib/bsc-test/drop.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+while true;
+do
+ echo "Going to drop the OML connection"
+ ./drop-oml.sh | telnet 127.0.0.1 4242
+ sleep 58m
+done
diff --git a/contrib/bsc-test/hangup b/contrib/bsc-test/hangup
new file mode 100644
index 00000000..cad6870f
--- /dev/null
+++ b/contrib/bsc-test/hangup
@@ -0,0 +1,4 @@
+TIMEOUT 10
+'' ^Z
+SAY "Waiting for hangup confirm\n"
+'' ATH;
diff --git a/contrib/bsc-test/msc.sh b/contrib/bsc-test/msc.sh
new file mode 100755
index 00000000..bec011d4
--- /dev/null
+++ b/contrib/bsc-test/msc.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+while true;
+do
+ echo "Kill the osmo-bsc"
+ /usr/bin/kill -s SIGUSR2 `pidof osmo-bsc`
+ sleep 58s
+done
diff --git a/contrib/bsc_control.py b/contrib/bsc_control.py
new file mode 100755
index 00000000..c1b09ce7
--- /dev/null
+++ b/contrib/bsc_control.py
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+# -*- mode: python-mode; py-indent-tabs-mode: nil -*-
+"""
+/*
+ * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+"""
+
+from optparse import OptionParser
+from ipa import Ctrl
+import socket
+
+verbose = False
+
+def connect(host, port):
+ if verbose:
+ print "Connecting to host %s:%i" % (host, port)
+
+ sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sck.setblocking(1)
+ sck.connect((host, port))
+ return sck
+
+def do_set_get(sck, var, value = None):
+ (r, c) = Ctrl().cmd(var, value)
+ sck.send(c)
+ answer = Ctrl().rem_header(sck.recv(4096))
+ return (answer,) + Ctrl().verify(answer, r, var, value)
+
+def set_var(sck, var, val):
+ (a, _, _) = do_set_get(sck, var, val)
+ return a
+
+def get_var(sck, var):
+ (_, _, v) = do_set_get(sck, var)
+ return v
+
+def _leftovers(sck, fl):
+ """
+ Read outstanding data if any according to flags
+ """
+ try:
+ data = sck.recv(1024, fl)
+ except socket.error as (s_errno, strerror):
+ 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
+
+if __name__ == '__main__':
+ parser = OptionParser("Usage: %prog [options] var [value]")
+ parser.add_option("-d", "--host", dest="host",
+ help="connect to HOST", metavar="HOST")
+ parser.add_option("-p", "--port", dest="port", type="int",
+ help="use PORT", metavar="PORT", default=4249)
+ parser.add_option("-g", "--get", action="store_true",
+ dest="cmd_get", help="perform GET operation")
+ parser.add_option("-s", "--set", action="store_true",
+ dest="cmd_set", help="perform SET operation")
+ parser.add_option("-v", "--verbose", action="store_true",
+ dest="verbose", help="be verbose", default=False)
+ parser.add_option("-m", "--monitor", action="store_true",
+ dest="monitor", help="monitor the connection for traps", default=False)
+
+ (options, args) = parser.parse_args()
+
+ verbose = options.verbose
+
+ if options.cmd_set and options.cmd_get:
+ parser.error("Get and set options are mutually exclusive!")
+
+ if not (options.cmd_get or options.cmd_set or options.monitor):
+ parser.error("One of -m, -g, or -s must be set")
+
+ if not (options.host):
+ parser.error("Destination host and port required!")
+
+ sock = connect(options.host, options.port)
+
+ if options.cmd_set:
+ if len(args) < 2:
+ parser.error("Set requires var and value arguments")
+ _leftovers(sock, socket.MSG_DONTWAIT)
+ print "Got message:", set_var(sock, args[0], ' '.join(args[1:]))
+
+ if options.cmd_get:
+ if len(args) != 1:
+ parser.error("Get requires the var argument")
+ _leftovers(sock, socket.MSG_DONTWAIT)
+ (a, _, _) = do_set_get(sock, args[0])
+ print "Got message:", a
+
+ if options.monitor:
+ while True:
+ if not _leftovers(sock, 0):
+ print "Connection is gone."
+ break
+ sock.close()
diff --git a/contrib/bt.py b/contrib/bt.py
new file mode 100755
index 00000000..1b111efc
--- /dev/null
+++ b/contrib/bt.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import os
+
+f = open("unbalanced")
+lines = []
+for line in f:
+ lines.append(line)
+
+filenames = {}
+
+output = []
+for line in lines:
+ if "[0x" in line:
+ start = line.find("[")
+ end = line.find("]")
+ addr = line[start+1:end]
+ try:
+ file = filenames[addr]
+ except KeyError:
+ r = os.popen("addr2line -fs -e ./bsc_hack %s" % addr)
+ all = r.read().replace("\n", ",")
+ file = all
+ filenames[addr] = file
+
+ line = line.replace(addr, file)
+ output.append(line)
+
+g = open("unbalanced.2", "w")
+g.write("".join(output))
+
+
+
diff --git a/contrib/convert_to_enum.py b/contrib/convert_to_enum.py
new file mode 100755
index 00000000..bcd6f2ce
--- /dev/null
+++ b/contrib/convert_to_enum.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+#
+# Convert ETSI documents to an enum
+#
+
+import re, sys
+
+def convert(string):
+ string = string.strip().replace(" ", "").rjust(8, "0")
+ var = 0
+ offset = 7
+ for char in string:
+ assert offset >= 0
+ var = var | (int(char) << offset)
+ offset = offset - 1
+
+ return var
+
+def string(name):
+ name = name.replace(" ", "_")
+ name = name.replace('"', "")
+ name = name.replace('/', '_')
+ name = name.replace('(', '_')
+ name = name.replace(')', '_')
+ return "%s_%s" % (sys.argv[2], name.upper())
+
+file = open(sys.argv[1])
+
+
+for line in file:
+ m = re.match(r"[ \t]*(?P<value>[01 ]+)[ ]+(?P<name>[a-zA-Z /0-9()]+)", line[:-1])
+
+ if m:
+ print "\t%s\t\t= %d," % (string(m.groupdict()["name"]), convert(m.groupdict()["value"]))
+ else:
+ print line[:-1]
diff --git a/contrib/ctrl2sse.py b/contrib/ctrl2sse.py
new file mode 100755
index 00000000..8b630ecf
--- /dev/null
+++ b/contrib/ctrl2sse.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python2
+
+mod_license = '''
+/*
+ * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+'''
+
+import sys, argparse, random, logging, tornado.ioloop, tornado.web, tornado.tcpclient, tornado.httpclient, eventsource, bsc_control
+from eventsource import listener, request
+
+'''
+N. B: this is not an example of building proper REST API or building secure web application.
+It's only purpose is to illustrate conversion of Osmocom's Control Interface to web-friendly API.
+Exposing this to Internet while connected to production network might lead to all sorts of mischief and mayhem
+from NSA' TAO breaking into your network to zombie apocalypse. Do NOT do that.
+'''
+
+token = None
+stream = None
+url = None
+
+'''
+Returns json according to following schema - see http://json-schema.org/documentation.html for details:
+{
+ "title": "Ctrl Schema",
+ "type": "object",
+ "properties": {
+ "variable": {
+ "type": "string"
+ },
+ "varlue": {
+ "type": "string"
+ }
+ },
+ "required": ["interface", "variable", "value"]
+}
+Example validation from command-line:
+json validate --schema-file=schema.json --document-file=data.json
+The interface is represented as string because it might look different for IPv4 vs v6.
+'''
+
+def read_header(data):
+ t_length = bsc_control.ipa_ctrl_header(data)
+ if (t_length):
+ stream.read_bytes(t_length - 1, callback = read_trap)
+ else:
+ print >> sys.stderr, "protocol error: length missing in %s!" % data
+
+@tornado.gen.coroutine
+def read_trap(data):
+ (t, z, v, p) = data.split()
+ if (t != 'TRAP' or int(z) != 0):
+ print >> sys.stderr, "protocol error: TRAP != %s or 0! = %d" % (t, int(z))
+ else:
+ yield tornado.httpclient.AsyncHTTPClient().fetch(tornado.httpclient.HTTPRequest(url = "%s/%s/%s" % (url, "ping", token),
+ method = 'POST',
+ headers = {'Content-Type': 'application/json'},
+ body = tornado.escape.json_encode({ 'variable' : v, 'value' : p })))
+ stream.read_bytes(4, callback = read_header)
+
+@tornado.gen.coroutine
+def trap_setup(host, port, target_host, target_port, tk):
+ global stream
+ global url
+ global token
+ token = tk
+ url = "http://%s:%s/sse" % (host, port)
+ stream = yield tornado.tcpclient.TCPClient().connect(target_host, target_port)
+ stream.read_bytes(4, callback = read_header)
+
+def get_v(s, v):
+ return { 'variable' : v, 'value' : bsc_control.get_var(s, tornado.escape.native_str(v)) }
+
+class CtrlHandler(tornado.web.RequestHandler):
+ def initialize(self):
+ self.skt = bsc_control.connect(self.settings['ctrl_host'], self.settings['ctrl_port'])
+
+ def get(self, v):
+ self.write(get_v(self.skt, v))
+
+ def post(self):
+ self.write(get_v(self.skt, self.get_argument("variable")))
+
+class SetCtrl(CtrlHandler):
+ def get(self, var, val):
+ bsc_control.set_var(self.skt, tornado.escape.native_str(var), tornado.escape.native_str(val))
+ super(SetCtrl, self).get(tornado.escape.native_str(var))
+
+ def post(self):
+ bsc_control.set_var(self.skt, tornado.escape.native_str(self.get_argument("variable")), tornado.escape.native_str(self.get_argument("value")))
+ super(SetCtrl, self).post()
+
+class Slash(tornado.web.RequestHandler):
+ def get(self):
+ self.write('<html><head><title>%s</title></head><body>Using Tornado framework v%s'
+ '<form action="/get" method="POST">'
+ '<input type="text" name="variable">'
+ '<input type="submit" value="GET">'
+ '</form>'
+ '<form action="/set" method="POST">'
+ '<input type="text" name="variable">'
+ '<input type="text" name="value">'
+ '<input type="submit" value="SET">'
+ '</form>'
+ '</body></html>' % ("Osmocom Control Interface Proxy", tornado.version))
+
+if __name__ == '__main__':
+ p = argparse.ArgumentParser(description='Osmocom Control Interface proxy.')
+ p.add_argument('-c', '--control-port', type = int, default = 4252, help = "Target Control Interface port")
+ p.add_argument('-a', '--control-host', default = 'localhost', help = "Target Control Interface adress")
+ p.add_argument('-b', '--host', default = 'localhost', help = "Adress to bind proxy's web interface")
+ p.add_argument('-p', '--port', type = int, default = 6969, help = "Port to bind proxy's web interface")
+ p.add_argument('-d', '--debug', action='store_true', help = "Activate debugging (default off)")
+ p.add_argument('-t', '--token', default = 'osmocom', help = "Token to be used by SSE client in URL e. g. http://127.0.0.1:8888/poll/osmocom where 'osmocom' is default token value")
+ p.add_argument('-k', '--keepalive', type = int, default = 5000, help = "Timeout betwwen keepalive messages, in milliseconds, defaults to 5000")
+ args = p.parse_args()
+ random.seed()
+ tornado.netutil.Resolver.configure('tornado.netutil.ThreadedResolver') # Use non-blocking resolver
+ logging.basicConfig()
+ application = tornado.web.Application([
+ (r"/", Slash),
+ (r"/get", CtrlHandler),
+ (r"/get/(.*)", CtrlHandler),
+ (r"/set", SetCtrl),
+ (r"/set/(.*)/(.*)", SetCtrl),
+ (r"/sse/(.*)/(.*)", listener.EventSourceHandler, dict(event_class = listener.JSONIdEvent, keepalive = args.keepalive)),
+ ], debug = args.debug, ctrl_host = args.control_host, ctrl_port = args.control_port)
+ application.listen(address = args.host, port = args.port)
+ trap_setup(args.host, args.port, application.settings['ctrl_host'], application.settings['ctrl_port'], args.token)
+ tornado.ioloop.IOLoop.instance().start()
diff --git a/contrib/gprs/gb-proxy-unblock-bug.py b/contrib/gprs/gb-proxy-unblock-bug.py
new file mode 100755
index 00000000..0cd4b871
--- /dev/null
+++ b/contrib/gprs/gb-proxy-unblock-bug.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+"""
+demonstrate a unblock bug on the GB Proxy..
+"""
+
+bts_ns_reset = "\x02\x00\x81\x01\x01\x82\x1f\xe7\x04\x82\x1f\xe7"
+ns_reset_ack = "\x03\x01\x82\x1f\xe7\x04\x82\x1f\xe7"
+
+bts_ns_unblock = "\x06"
+ns_unblock_ack = "\x07"
+
+bts_bvc_reset_0 = "\x00\x00\x00\x00\x22\x04\x82\x00\x00\x07\x81\x03\x3b\x81\x02"
+ns_bvc_reset_0_ack = "\x00\x00\x00\x00\x23\x04\x82\x00\x00"
+
+bts_bvc_reset_8167 = "\x00\x00\x00\x00\x22\x04\x82\x1f\xe7\x07\x81\x08\x08\x88\x72\xf4\x80\x10\x1c\x00\x9c\x40"
+
+
+import socket
+socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+socket.bind(("0.0.0.0", 0))
+socket.setblocking(1)
+
+
+import sys
+port = int(sys.argv[1])
+print "Sending data to port: %d" % port
+
+def send_and_receive(packet):
+ socket.sendto(packet, ("127.0.0.1", port))
+
+ try:
+ data, addr = socket.recvfrom(4096)
+ except socket.error, e:
+ print "ERROR", e
+ import sys
+ sys.exit(0)
+ return data
+
+#send stuff once
+
+to_send = [
+ (bts_ns_reset, ns_reset_ack, "reset ack"),
+ (bts_ns_unblock, ns_unblock_ack, "unblock ack"),
+ (bts_bvc_reset_0, ns_bvc_reset_0_ack, "BVCI=0 reset ack"),
+]
+
+
+for (out, inp, type) in to_send:
+ res = send_and_receive(out)
+ if res != inp:
+ print "Failed to get the %s" % type
+ sys.exit(-1)
+
+import time
+time.sleep(3)
+res = send_and_receive(bts_bvc_reset_8167)
+print "Sent all messages... check wireshark for the last response"
diff --git a/contrib/gprs/gprs-bssgp-histogram.lua b/contrib/gprs/gprs-bssgp-histogram.lua
new file mode 100644
index 00000000..b1ab5df7
--- /dev/null
+++ b/contrib/gprs/gprs-bssgp-histogram.lua
@@ -0,0 +1,78 @@
+-- Simple LUA script to print the size of BSSGP messages over their type...
+
+do
+ local ip_bucket = {}
+
+ local pdu_types = {}
+ pdu_types[ 6] = "PAGING"
+ pdu_types[11] = "SUSPEND"
+ pdu_types[12] = "SUSPEND-ACK"
+ pdu_types[32] = "BVC-BLOCK"
+ pdu_types[33] = "BVC-BLOCK-ACK"
+ pdu_types[34] = "BVC-RESET"
+ pdu_types[35] = "BVC-RESET-ACK"
+ pdu_types[36] = "UNBLOCK"
+ pdu_types[37] = "UNBLOCK-ACK"
+ pdu_types[38] = "FLOW-CONTROL-BVC"
+ pdu_types[39] = "FLOW-CONTROL-BVC-ACK"
+ pdu_types[40] = "FLOW-CONTROL-MS"
+ pdu_types[41] = "FLOW-CONTROL-MS-ACK"
+ pdu_types[44] = "LLC-DISCARDED"
+
+ local function init_listener()
+ -- handle the port as NS over IP
+ local udp_port_table = DissectorTable.get("udp.port")
+ local gprs_ns_dis = Dissector.get("gprs_ns")
+ udp_port_table:add(23000,gprs_ns_dis)
+
+ -- bssgp filters
+ local bssgp_pdu_get = Field.new("bssgp.pdu_type")
+ local udp_length_get = Field.new("udp.length")
+
+ local tap = Listener.new("ip", "udp.port == 23000")
+ function tap.packet(pinfo,tvb,ip)
+ local pdu = bssgp_pdu_get()
+ local len = udp_length_get()
+
+ -- only handle bssgp, but we also want the IP frame
+ if not pdu then
+ return
+ end
+
+ pdu = tostring(pdu)
+ if tonumber(pdu) == 0 or tonumber(pdu) == 1 then
+ return
+ end
+
+ local ip_src = tostring(ip.ip_src)
+ local bssgp_histo = ip_bucket[ip_src]
+ if not bssgp_histo then
+ bssgp_histo = {}
+ ip_bucket[ip_src] = bssgp_histo
+ end
+
+ local key = pdu
+ local bucket = bssgp_histo[key]
+ if not bucket then
+ bucket = {}
+ bssgp_histo[key] = bucket
+ end
+
+ table.insert(bucket, tostring(len))
+ print("IP: " .. ip_src .. " PDU: " .. pdu_types[tonumber(pdu)] .. " Length: " .. tostring(len))
+ end
+
+ function tap.draw()
+ -- well... this will not be called...
+-- for ip,bssgp_histo in pairs(dumpers) do
+-- print("IP " .. ip)
+-- end
+ end
+
+ function tap.reset()
+ -- well... this will not be called...
+ end
+ end
+
+ init_listener()
+end
diff --git a/contrib/gprs/gprs-buffer-count.lua b/contrib/gprs/gprs-buffer-count.lua
new file mode 100644
index 00000000..ca8864ad
--- /dev/null
+++ b/contrib/gprs/gprs-buffer-count.lua
@@ -0,0 +1,80 @@
+-- I count the buffer space needed for LLC PDUs in the worse case and print it
+
+do
+ local function init_listener()
+ -- handle the port as NS over IP
+ local udp_port_table = DissectorTable.get("udp.port")
+ local gprs_ns_dis = Dissector.get("gprs_ns")
+ udp_port_table:add(23000,gprs_ns_dis)
+
+ -- bssgp filters
+ local bssgp_pdu_get = Field.new("bssgp.pdu_type")
+ local bssgp_delay_get = Field.new("bssgp.delay_val")
+ local llcgprs_get = Field.new("llcgprs")
+ local pdus = nil
+
+ print("START...")
+
+ local tap = Listener.new("ip", "udp.port == 23000 && bssgp.pdu_type == 0")
+ function tap.packet(pinfo,tvb,ip)
+ local pdu = bssgp_pdu_get()
+ local len = llcgprs_get().len
+ local delay = bssgp_delay_get()
+
+ -- only handle bssgp, but we also want the IP frame
+ if not pdu then
+ return
+ end
+
+ if tonumber(tostring(delay)) == 65535 then
+ pdus = { next = pdus,
+ len = len,
+ expires = -1 }
+ else
+ local off = tonumber(tostring(delay)) / 100.0
+ pdus = { next = pdus,
+ len = len,
+ expires = pinfo.rel_ts + off }
+ end
+ local now_time = tonumber(tostring(pinfo.rel_ts))
+ local now_size = 0
+ local l = pdus
+ local prev = nil
+ local count = 0
+ while l do
+ if now_time < l.expires or l.expires == -1 then
+ now_size = now_size + l.len
+ prev = l
+ l = l.next
+ count = count + 1
+ else
+ -- delete things
+ if prev == nil then
+ pdus = nil
+ l = nil
+ else
+ prev.next = l.next
+ l = l.next
+ end
+ end
+ end
+-- print("TOTAL: " .. now_time .. " PDU_SIZE: " .. now_size)
+ print(now_time .. " " .. now_size / 1024.0 .. " " .. count)
+-- print("NOW: " .. tostring(pinfo.rel_ts) .. " Delay: " .. tostring(delay) .. " Length: " .. tostring(len))
+ end
+
+ function tap.draw()
+ -- well... this will not be called...
+-- for ip,bssgp_histo in pairs(dumpers) do
+-- print("IP " .. ip)
+-- end
+ print("END")
+ end
+
+ function tap.reset()
+ -- well... this will not be called...
+ end
+ end
+
+ init_listener()
+end
diff --git a/contrib/gprs/gprs-split-trace-by-tlli.lua b/contrib/gprs/gprs-split-trace-by-tlli.lua
new file mode 100644
index 00000000..018c377c
--- /dev/null
+++ b/contrib/gprs/gprs-split-trace-by-tlli.lua
@@ -0,0 +1,46 @@
+-- Create a file named by_ip/''ip_addess''.cap with all ip traffic of each ip host. (works for tshark only)
+-- Dump files are created for both source and destination hosts
+do
+ local dir = "by_tlli"
+ local dumpers = {}
+ local function init_listener()
+ local udp_port_table = DissectorTable.get("udp.port")
+ local gprs_ns_dis = Dissector.get("gprs_ns")
+ udp_port_table:add(23000,gprs_ns_dis)
+
+ local field_tlli = Field.new("bssgp.tlli")
+ local tap = Listener.new("ip", "udp.port == 23000")
+
+ -- we will be called once for every IP Header.
+ -- If there's more than one IP header in a given packet we'll dump the packet once per every header
+ function tap.packet(pinfo,tvb,ip)
+ local tlli = field_tlli()
+ if not tlli then
+ return
+ end
+
+ local tlli_str = tostring(tlli)
+ tlli_dmp = dumpers[tlli_str]
+ if not tlli_dmp then
+ local tlli_hex = string.format("0x%x", tonumber(tlli_str))
+ print("Creating dump for TLLI " .. tlli_hex)
+ tlli_dmp = Dumper.new_for_current(dir .. "/" .. tlli_hex .. ".pcap")
+ dumpers[tlli_str] = tlli_dmp
+ end
+ tlli_dmp:dump_current()
+ tlli_dmp:flush()
+ end
+ function tap.draw()
+ for tlli,dumper in pairs(dumpers) do
+ dumper:flush()
+ end
+ end
+ function tap.reset()
+ for tlli,dumper in pairs(dumpers) do
+ dumper:close()
+ end
+ dumpers = {}
+ end
+ end
+ init_listener()
+end
diff --git a/contrib/gprs/gprs-verify-nu.lua b/contrib/gprs/gprs-verify-nu.lua
new file mode 100644
index 00000000..e44fdd16
--- /dev/null
+++ b/contrib/gprs/gprs-verify-nu.lua
@@ -0,0 +1,59 @@
+-- This script verifies that the N(U) is increasing...
+--
+do
+ local nu_state_src = {}
+
+ local function init_listener()
+ -- handle the port as NS over IP
+ local udp_port_table = DissectorTable.get("udp.port")
+ local gprs_ns_dis = Dissector.get("gprs_ns")
+ udp_port_table:add(23000,gprs_ns_dis)
+
+ -- we want to look here...
+ local llc_sapi_get = Field.new("llcgprs.sapib")
+ local llc_nu_get = Field.new("llcgprs.nu")
+ local bssgp_tlli_get = Field.new("bssgp.tlli")
+
+ local tap = Listener.new("ip", "udp.port == 23000")
+ function tap.packet(pinfo,tvb,ip)
+ local llc_sapi = llc_sapi_get()
+ local llc_nu = llc_nu_get()
+ local bssgp_tlli = bssgp_tlli_get()
+
+ if not llc_sapi or not llc_nu or not bssgp_tlli then
+ return
+ end
+
+ local ip_src = tostring(ip.ip_src)
+ local bssgp_tlli = tostring(bssgp_tlli)
+ local llc_nu = tostring(llc_nu)
+ local llc_sapi = tostring(llc_sapi)
+
+ local src_key = ip_src .. "-" .. bssgp_tlli .. "-" .. llc_sapi
+ local last_nu = nu_state_src[src_key]
+ if not last_nu then
+ -- print("Establishing mapping for " .. src_key)
+ nu_state_src[src_key] = llc_nu
+ return
+ end
+
+ local function tohex(number)
+ return string.format("0x%x", tonumber(number))
+ end
+
+ nu_state_src[src_key] = llc_nu
+ if tonumber(last_nu) + 1 ~= tonumber(llc_nu) then
+ print("JUMP in N(U) on TLLI " .. tohex(bssgp_tlli) .. " and SAPI: " .. llc_sapi .. " src: " .. ip_src)
+ print("\t last: " .. last_nu .. " now: " .. llc_nu)
+ end
+ end
+
+ function tap.draw()
+ end
+
+ function tap.reset()
+ end
+ end
+ init_listener()
+end
+
diff --git a/contrib/hlr-remove-old.sql b/contrib/hlr-remove-old.sql
new file mode 100644
index 00000000..626a331e
--- /dev/null
+++ b/contrib/hlr-remove-old.sql
@@ -0,0 +1,18 @@
+-- Remove old data from the database
+DELETE FROM Subscriber
+ WHERE id != 1 AND datetime('now', '-10 days') > updated AND authorized != 1;
+DELETE FROM Equipment
+ WHERE datetime('now', '-10 days') > updated;
+DELETE FROM EquipmentWatch
+ WHERE datetime('now', '-10 days') > updated;
+DELETE FROM SMS
+ WHERE datetime('now', '-10 days') > created;
+DELETE FROM VLR
+ WHERE datetime('now', '-10 days') > updated;
+DELETE FROM ApduBlobs
+ WHERE datetime('now', '-10 days') > created;
+DELETE FROM Counters
+ WHERE datetime('now', '-10 days') > timestamp;
+DELETE FROM RateCounters
+ WHERE datetime('now', '-10 days') > timestamp;
+VACUUM;
diff --git a/contrib/hlrsync/hlrsync.py b/contrib/hlrsync/hlrsync.py
new file mode 100755
index 00000000..e4a49555
--- /dev/null
+++ b/contrib/hlrsync/hlrsync.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python2.5
+
+from __future__ import with_statement
+
+from pysqlite2 import dbapi2 as sqlite3
+import sys
+
+hlr = sqlite3.connect(sys.argv[1])
+web = sqlite3.connect(sys.argv[2])
+
+# switch to autocommit
+hlr.isolation_level = None
+web.isolation_level = None
+
+hlr.row_factory = sqlite3.Row
+web.row_factory = sqlite3.Row
+
+with hlr:
+ hlr_subscrs = hlr.execute("""
+ SELECT * FROM Subscriber
+ """).fetchall()
+ hlr_tokens = hlr.execute("""
+ SELECT * FROM AuthToken
+ """).fetchall()
+
+with web:
+ web_tokens = web.execute("""
+ SELECT * FROM reg_tokens
+ """).fetchall()
+ web_sms = web.execute("""
+ SELECT * FROM sms_queue
+ """).fetchall()
+
+# index by subscr id
+hlr_subscrs_by_id = {}
+hlr_subscrs_by_ext = {}
+hlr_tokens_by_subscr_id = {}
+for x in hlr_subscrs:
+ hlr_subscrs_by_id[x['id']] = x
+ hlr_subscrs_by_ext[x['extension']] = x
+del hlr_subscrs
+for x in hlr_tokens:
+ hlr_tokens_by_subscr_id[x['subscriber_id']] = x
+del hlr_tokens
+
+web_tokens_by_subscr_id = {}
+for x in web_tokens:
+ web_tokens_by_subscr_id[x['subscriber_id']] = x
+del web_tokens
+
+# remove leftover web_tokens and correct inconsistent fields
+with web:
+ for x in web_tokens_by_subscr_id.values():
+ subscr = hlr_subscrs_by_id.get(x['subscriber_id'], None)
+ if subscr is None:
+ web.execute("""
+ DELETE FROM reg_tokens WHERE subscriber_id = ?
+ """, (x['subscriber_id'],))
+ del web_tokens_by_subscr_id[x['subscriber_id']]
+ continue
+ if str(x['imsi']) != str(subscr['imsi']) or \
+ x['extension'] != subscr['extension'] or \
+ x['tmsi'] != subscr['tmsi'] or \
+ x['lac'] != subscr['lac']:
+ web.execute("""
+ UPDATE reg_tokens
+ SET imsi = ?, extension = ?, tmsi = ?, lac = ?
+ WHERE subscriber_id = ?
+ """, (str(subscr['imsi']), subscr['extension'],
+ subscr['tmsi'], subscr['lac'], x['subscriber_id']))
+
+# add missing web_tokens
+with web:
+ for x in hlr_tokens_by_subscr_id.values():
+ subscr = hlr_subscrs_by_id.get(x['subscriber_id'], None)
+ if subscr is None:
+ hlr.execute("""
+ DELETE FROM AuthToken WHERE subscriber_id = ?
+ """, (x['subscriber_id'],))
+ del hlr_tokens_by_subscr_id[x['subscriber_id']]
+ continue
+ webtoken = web_tokens_by_subscr_id.get(x['subscriber_id'], None)
+ if webtoken is None:
+ web.execute("""
+ INSERT INTO reg_tokens
+ (subscriber_id, extension, reg_completed, name, email, lac, imsi, token, tmsi)
+ VALUES
+ (?, ?, 0, ?, '', ?, ?, ?, ?)
+ """, (x['subscriber_id'], subscr['extension'], subscr['name'],
+ subscr['lac'], str(subscr['imsi']), x['token'], subscr['tmsi']))
+
+# authorize subscribers
+with hlr:
+ for x in web_tokens_by_subscr_id.values():
+ subscr = hlr_subscrs_by_id.get(x['subscriber_id'], None)
+ if x['reg_completed'] and not subscr['authorized']:
+ hlr.execute("""
+ UPDATE Subscriber
+ SET authorized = 1
+ WHERE id = ?
+ """, (x['subscriber_id'],))
+
+# Sync SMS from web to hlr
+with hlr:
+ for sms in web_sms:
+ subscr = hlr_subscrs_by_ext.get(sms['receiver_ext'])
+ if subscr is None:
+ print '%s not found' % sms['receiver_ext']
+ continue
+ hlr.execute("""
+ INSERT INTO SMS
+ (created, sender_id, receiver_id, reply_path_req, status_rep_req, protocol_id, data_coding_scheme, ud_hdr_ind, text)
+ VALUES
+ (?, 1, ?, 0, 0, 0, 0, 0, ?)
+ """, (sms['created'], subscr['id'], sms['text']))
+with web:
+ for sms in web_sms:
+ web.execute("""
+ DELETE FROM sms_queue WHERE id = ?
+ """, (sms['id'],))
+
+
+hlr.close()
+web.close()
+
diff --git a/contrib/ipa.py b/contrib/ipa.py
new file mode 100755
index 00000000..71cbf45a
--- /dev/null
+++ b/contrib/ipa.py
@@ -0,0 +1,278 @@
+#!/usr/bin/python3
+# -*- mode: python-mode; py-indent-tabs-mode: nil -*-
+"""
+/*
+ * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+"""
+
+import struct, random, sys
+
+class IPA(object):
+ """
+ Stateless IPA protocol multiplexer: add/remove/parse (extended) header
+ """
+ version = "0.0.5"
+ TCP_PORT_OML = 3002
+ TCP_PORT_RSL = 3003
+ # OpenBSC extensions: OSMO, MGCP_OLD
+ PROTO = dict(RSL=0x00, CCM=0xFE, SCCP=0xFD, OML=0xFF, OSMO=0xEE, MGCP_OLD=0xFC)
+ # ...OML Router Control, GSUP GPRS extension, Osmocom Authn Protocol
+ EXT = dict(CTRL=0, MGCP=1, LAC=2, SMSC=3, ORC=4, GSUP=5, OAP=6)
+ # OpenBSC extension: SCCP_OLD
+ MSGT = dict(PING=0x00, PONG=0x01, ID_GET=0x04, ID_RESP=0x05, ID_ACK=0x06, SCCP_OLD=0xFF)
+ _IDTAG = dict(SERNR=0, UNITNAME=1, LOCATION=2, TYPE=3, EQUIPVERS=4, SWVERSION=5, IPADDR=6, MACADDR=7, UNIT=8)
+ CTRL_GET = 'GET'
+ CTRL_SET = 'SET'
+ CTRL_REP = 'REPLY'
+ CTRL_ERR = 'ERR'
+ CTRL_TRAP = 'TRAP'
+
+ def _l(self, d, p):
+ """
+ Reverse dictionary lookup: return key for a given value
+ """
+ if p is None:
+ return 'UNKNOWN'
+ return list(d.keys())[list(d.values()).index(p)]
+
+ def _tag(self, t, v):
+ """
+ Create TAG as TLV data
+ """
+ return struct.pack(">HB", len(v) + 1, t) + v
+
+ def proto(self, p):
+ """
+ Lookup protocol name
+ """
+ return self._l(self.PROTO, p)
+
+ def ext(self, p):
+ """
+ Lookup protocol extension name
+ """
+ return self._l(self.EXT, p)
+
+ def msgt(self, p):
+ """
+ Lookup message type name
+ """
+ return self._l(self.MSGT, p)
+
+ def idtag(self, p):
+ """
+ Lookup ID tag name
+ """
+ return self._l(self._IDTAG, p)
+
+ def ext_name(self, proto, exten):
+ """
+ Return proper extension byte name depending on the protocol used
+ """
+ if self.PROTO['CCM'] == proto:
+ return self.msgt(exten)
+ if self.PROTO['OSMO'] == proto:
+ return self.ext(exten)
+ return None
+
+ def add_header(self, data, proto, ext=None):
+ """
+ Add IPA header (with extension if necessary), data must be represented as bytes
+ """
+ if ext is None:
+ return struct.pack(">HB", len(data) + 1, proto) + data
+ return struct.pack(">HBB", len(data) + 1, proto, ext) + data
+
+ def del_header(self, data):
+ """
+ Strip IPA protocol header correctly removing extension if present
+ Returns data length, IPA protocol, extension (or None if not defined for a give protocol) and the data without header
+ """
+ if not len(data):
+ return None, None, None, None
+ (dlen, proto) = struct.unpack('>HB', data[:3])
+ if self.PROTO['OSMO'] == proto or self.PROTO['CCM'] == proto: # there's extension which we have to unpack
+ return struct.unpack('>HBB', data[:4]) + (data[4:], ) # length, protocol, extension, data
+ return dlen, proto, None, data[3:] # length, protocol, _, data
+
+ def split_combined(self, data):
+ """
+ Split the data which contains multiple concatenated IPA messages into tuple (first, rest) where rest contains remaining messages, first is the single IPA message
+ """
+ (length, _, _, _) = self.del_header(data)
+ return data[:(length + 3)], data[(length + 3):]
+
+ def tag_serial(self, data):
+ """
+ Make TAG for serial number
+ """
+ return self._tag(self._IDTAG['SERNR'], data)
+
+ def tag_name(self, data):
+ """
+ Make TAG for unit name
+ """
+ return self._tag(self._IDTAG['UNITNAME'], data)
+
+ def tag_loc(self, data):
+ """
+ Make TAG for location
+ """
+ return self._tag(self._IDTAG['LOCATION'], data)
+
+ def tag_type(self, data):
+ """
+ Make TAG for unit type
+ """
+ return self._tag(self._IDTAG['TYPE'], data)
+
+ def tag_equip(self, data):
+ """
+ Make TAG for equipment version
+ """
+ return self._tag(self._IDTAG['EQUIPVERS'], data)
+
+ def tag_sw(self, data):
+ """
+ Make TAG for software version
+ """
+ return self._tag(self._IDTAG['SWVERSION'], data)
+
+ def tag_ip(self, data):
+ """
+ Make TAG for IP address
+ """
+ return self._tag(self._IDTAG['IPADDR'], data)
+
+ def tag_mac(self, data):
+ """
+ Make TAG for MAC address
+ """
+ return self._tag(self._IDTAG['MACADDR'], data)
+
+ def tag_unit(self, data):
+ """
+ Make TAG for unit ID
+ """
+ return self._tag(self._IDTAG['UNIT'], data)
+
+ def identity(self, unit=b'', mac=b'', location=b'', utype=b'', equip=b'', sw=b'', name=b'', serial=b''):
+ """
+ Make IPA IDENTITY tag list, by default returns empty concatenated bytes of tag list
+ """
+ return self.tag_unit(unit) + self.tag_mac(mac) + self.tag_loc(location) + self.tag_type(utype) + self.tag_equip(equip) + self.tag_sw(sw) + self.tag_name(name) + self.tag_serial(serial)
+
+ def ping(self):
+ """
+ Make PING message
+ """
+ return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PING'])
+
+ def pong(self):
+ """
+ Make PONG message
+ """
+ return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PONG'])
+
+ def id_ack(self):
+ """
+ Make ID_ACK CCM message
+ """
+ return self.add_header(b'', self.PROTO['CCM'], self.MSGT['ID_ACK'])
+
+ def id_get(self):
+ """
+ Make ID_GET CCM message
+ """
+ return self.add_header(self.identity(), self.PROTO['CCM'], self.MSGT['ID_GET'])
+
+ def id_resp(self, data):
+ """
+ Make ID_RESP CCM message
+ """
+ return self.add_header(data, self.PROTO['CCM'], self.MSGT['ID_RESP'])
+
+class Ctrl(IPA):
+ """
+ Osmocom CTRL protocol implemented on top of IPA multiplexer
+ """
+ def __init__(self):
+ random.seed()
+
+ def add_header(self, data):
+ """
+ Add CTRL header
+ """
+ return super(Ctrl, self).add_header(data.encode('utf-8'), IPA.PROTO['OSMO'], IPA.EXT['CTRL'])
+
+ def rem_header(self, data):
+ """
+ Remove CTRL header, check for appropriate protocol and extension
+ """
+ (_, proto, ext, d) = super(Ctrl, self).del_header(data)
+ if self.PROTO['OSMO'] != proto or self.EXT['CTRL'] != ext:
+ return None
+ return d
+
+ def parse(self, data, op=None):
+ """
+ Parse Ctrl string returning (var, value) pair
+ var could be None in case of ERROR message
+ value could be None in case of GET message
+ """
+ (s, i, v) = data.split(' ', 2)
+ if s == self.CTRL_ERR:
+ return None, v
+ if s == self.CTRL_GET:
+ return v, None
+ (s, i, var, val) = data.split(' ', 3)
+ if s == self.CTRL_TRAP and i != '0':
+ return None, '%s with non-zero id %s' % (s, i)
+ if op is not None and i != op:
+ if s == self.CTRL_GET + '_' + self.CTRL_REP or s == self.CTRL_SET + '_' + self.CTRL_REP:
+ return None, '%s with unexpected id %s' % (s, i)
+ return var, val
+
+ def trap(self, var, val):
+ """
+ Make TRAP message with given (vak, val) pair
+ """
+ return self.add_header("%s 0 %s %s" % (self.CTRL_TRAP, var, val))
+
+ def cmd(self, var, val=None):
+ """
+ Make SET/GET command message: returns (r, m) tuple where r is random operation id and m is assembled message
+ """
+ r = random.randint(1, sys.maxsize)
+ if val is not None:
+ return r, self.add_header("%s %s %s %s" % (self.CTRL_SET, r, var, val))
+ return r, self.add_header("%s %s %s" % (self.CTRL_GET, r, var))
+
+ def verify(self, reply, r, var, val=None):
+ """
+ Verify reply to SET/GET command: returns (b, v) tuple where v is True/False verification result and v is the variable value
+ """
+ (k, v) = self.parse(reply)
+ if k != var or (val is not None and v != val):
+ return False, v
+ return True, v
+
+if __name__ == '__main__':
+ print("IPA multiplexer v%s loaded." % IPA.version)
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
index 068ee34e..b315b977 100755
--- a/contrib/jenkins.sh
+++ b/contrib/jenkins.sh
@@ -43,7 +43,6 @@ echo
set -x
cd "$base"
-cd openbsc
autoreconf --install --force
./configure --enable-osmo-bsc --enable-nat $SMPP $MGCP $IU --enable-vty-tests --enable-external-tests
$MAKE $PARALLEL_MAKE
diff --git a/contrib/mgcp_server.py b/contrib/mgcp_server.py
new file mode 100755
index 00000000..05c489db
--- /dev/null
+++ b/contrib/mgcp_server.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Simple server for mgcp... send audit, receive response..
+
+import socket, time
+
+MGCP_GATEWAY_PORT = 2427
+MGCP_CALLAGENT_PORT = 2727
+
+rsip_resp = """200 321321332\r\n"""
+audit_packet = """AUEP %d 13@mgw MGCP 1.0\r\n"""
+crcx_packet = """CRCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nL: p:20, a:GSM-EFR, nt:IN\r\nM: recvonly\r\n"""
+dlcx_packet = """DLCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nI: %d\r\n"""
+mdcx_packet = """MDCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nI: %d\r\nL: p:20, a:GSM-EFR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 258696477 0 IN IP4 172.16.1.107\r\ns=-\r\nc=IN IP4 172.16.1.107\r\nt=0 0\r\nm=audio 6666 RTP/AVP 127\r\na=rtpmap:127 GSM-EFR/8000/1\r\na=ptime:20\r\na=recvonly\r\nm=image 4402 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n"""
+
+def hexdump(src, length=8):
+ """Recipe is from http://code.activestate.com/recipes/142812/"""
+ result = []
+ digits = 4 if isinstance(src, unicode) else 2
+ for i in xrange(0, len(src), length):
+ s = src[i:i+length]
+ hexa = b' '.join(["%0*X" % (digits, ord(x)) for x in s])
+ text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])
+ result.append( b"%04X %-*s %s" % (i, length*(digits + 1), hexa, text) )
+ return b'\n'.join(result)
+
+server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+server_socket.bind(("127.0.0.1", MGCP_CALLAGENT_PORT))
+server_socket.setblocking(1)
+
+last_ci = 1
+def send_and_receive(packet):
+ global last_ci
+ server_socket.sendto(packet, ("127.0.0.1", MGCP_GATEWAY_PORT))
+ try:
+ data, addr = server_socket.recvfrom(4096)
+
+ # attempt to store the CI of the response
+ list = data.split("\n")
+ for item in list:
+ if item.startswith("I: "):
+ last_ci = int(item[3:])
+
+ print hexdump(data), addr
+ except socket.error, e:
+ print e
+ pass
+
+def generate_tid():
+ import random
+ return random.randint(0, 65123)
+
+
+
+while True:
+ send_and_receive(audit_packet % generate_tid())
+ send_and_receive(crcx_packet % generate_tid() )
+ send_and_receive(mdcx_packet % (generate_tid(), last_ci))
+ send_and_receive(dlcx_packet % (generate_tid(), last_ci))
+
+ time.sleep(3)
diff --git a/contrib/nat/test_regexp.c b/contrib/nat/test_regexp.c
new file mode 100644
index 00000000..808a703c
--- /dev/null
+++ b/contrib/nat/test_regexp.c
@@ -0,0 +1,30 @@
+/* make test_regexp */
+#include <sys/types.h>
+#include <regex.h>
+#include <stdio.h>
+
+
+int main(int argc, char **argv)
+{
+ regex_t reg;
+ regmatch_t matches[2];
+
+ if (argc != 4) {
+ printf("Invoke with: test_regexp REGEXP REPLACE NR\n");
+ return -1;
+ }
+
+ if (regcomp(&reg, argv[1], REG_EXTENDED) != 0) {
+ fprintf(stderr, "Regexp '%s' is not valid.\n", argv[1]);
+ return -1;
+ }
+
+ if (regexec(&reg, argv[3], 2, matches, 0) == 0 && matches[1].rm_eo != -1)
+ printf("New Number: %s%s\n", argv[2], &argv[3][matches[1].rm_so]);
+ else
+ printf("No match.\n");
+
+ regfree(&reg);
+
+ return 0;
+}
diff --git a/contrib/nat/ussd_example.py b/contrib/nat/ussd_example.py
new file mode 100644
index 00000000..8f7a58d3
--- /dev/null
+++ b/contrib/nat/ussd_example.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python2.7
+
+"""
+AGPLv3+ 2016 Copyright Holger Hans Peter Freyther
+
+Example of how to connect to the USSD side-channel and how to respond
+with a fixed message.
+"""
+
+import socket
+import struct
+
+ussdSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ussdSocket.connect(('127.0.0.1', 5001))
+
+def send_dt1(dstref, data):
+ dlen = struct.pack('B', len(data)).encode('hex')
+ hex = '06' + dstref.encode('hex') + '00' + '01' + dlen + data.encode('hex')
+ pdata = hex.decode('hex')
+ out = struct.pack('>HB', len(pdata), 0xfd) + pdata
+ ussdSocket.send(out)
+
+def send_rel(srcref, dstref):
+ hex = '04' + dstref.encode('hex') + srcref.encode('hex') + '000100'
+ pdata = hex.decode('hex')
+ out = struct.pack('>HB', len(pdata), 0xfd) + pdata
+ ussdSocket.send(out)
+
+def recv_one():
+ plen = ussdSocket.recv(3)
+ (plen,ptype) = struct.unpack(">HB", plen)
+ data = ussdSocket.recv(plen)
+
+ return ptype, data
+
+# Assume this is the ID request
+data = ussdSocket.recv(4)
+ussdSocket.send("\x00\x08\xfe\x05\x00" + "\x05\x01" + "ussd")
+# ^len ^len of tag ... and ignore
+
+# Expect a fake message. see struct ipac_msgt_sccp_state
+ptype, data = recv_one()
+print("%d %s" % (ptype, data.encode('hex')))
+(srcref, dstref, transid, invokeid) = struct.unpack("<3s3sBB", data[1:9])
+print("New transID %d invoke %d" % (transid, invokeid))
+
+# Expect a the invocation.. todo.. extract invoke id
+ptype, data = recv_one()
+print("%d %s" % (ptype, data.encode('hex')))
+
+# Reply with BSSAP + GSM 04.08 + MAP portion
+# 00 == invoke id 0f == DCS
+res = "01002a9b2a0802e1901c22a220020100301b02013b301604010f041155e7d2f9bc3a41412894991c06a9c9a713"
+send_dt1(dstref, res.decode('hex'))
+
+clear = "000420040109"
+send_dt1(dstref, clear.decode('hex'))
+
+# should be the clear complete
+send_rel(srcref, dstref)
+
+# Give it some time to handle connection shutdown properly
+print("Gracefully sleeping")
+import time
+time.sleep(3)
diff --git a/contrib/rtp/gen_rtp_header.erl b/contrib/rtp/gen_rtp_header.erl
new file mode 100755
index 00000000..47839c1c
--- /dev/null
+++ b/contrib/rtp/gen_rtp_header.erl
@@ -0,0 +1,420 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! -smp disable
+-module(gen_rtp_header).
+
+% -mode(compile).
+
+-define(VERSION, "0.1").
+
+-export([main/1]).
+
+-record(rtp_packet,
+ {
+ version = 2,
+ padding = 0,
+ marker = 0,
+ payload_type = 0,
+ seqno = 0,
+ timestamp = 0,
+ ssrc = 0,
+ csrcs = [],
+ extension = <<>>,
+ payload = <<>>,
+ realtime
+ }).
+
+
+main(Args) ->
+ DefaultOpts = [{format, state},
+ {ssrc, 16#11223344},
+ {rate, 8000},
+ {pt, 98}],
+ {PosArgs, Opts} = getopts_checked(Args, DefaultOpts),
+ log(debug, fun (Dev) ->
+ io:format(Dev, "Initial options:~n", []),
+ dump_opts(Dev, Opts),
+ io:format(Dev, "~s: ~p~n", ["Args", PosArgs])
+ end, [], Opts),
+ main(PosArgs, Opts).
+
+main([First | RemArgs], Opts) ->
+ try
+ F = list_to_integer(First),
+ Format = proplists:get_value(format, Opts, state),
+ PayloadData = proplists:get_value(payload, Opts, undef),
+ InFile = proplists:get_value(file, Opts, undef),
+
+ Payload = case {PayloadData, InFile} of
+ {undef, undef} ->
+ % use default value
+ #rtp_packet{}#rtp_packet.payload;
+ {P, undef} -> P;
+ {_, File} ->
+ log(info, "Loading file '~s'~n", [File], Opts),
+ {ok, InDev} = file:open(File, [read]),
+ DS = [ Pl#rtp_packet.payload || {_T, Pl} <- read_packets(InDev, Opts)],
+ file:close(InDev),
+ log(debug, "File '~s' closed, ~w packets read.~n", [File, length(DS)], Opts),
+ DS
+ end,
+ Dev = standard_io,
+ write_packet_pre(Dev, Format),
+ do_groups(Dev, Payload, F, RemArgs, Opts),
+ write_packet_post(Dev, Format),
+ 0
+ catch
+ _:_ ->
+ log(debug, "~p~n", [hd(erlang:get_stacktrace())], Opts),
+ usage(),
+ halt(1)
+ end
+ ;
+
+main(_, _Opts) ->
+ usage(),
+ halt(1).
+
+%%% group (count + offset) handling %%%
+
+do_groups(_Dev, _Pl, _F, [], _Opts) ->
+ ok;
+
+do_groups(Dev, Pl, F, [L], Opts) ->
+ do_groups(Dev, Pl, F, [L, 0], Opts);
+
+do_groups(Dev, Pl, First, [L, O | Args], Opts) ->
+ Ssrc = proplists:get_value(ssrc, Opts, #rtp_packet.ssrc),
+ PT = proplists:get_value(pt, Opts, #rtp_packet.payload_type),
+ Len = list_to_num(L),
+ Offs = list_to_num(O),
+ log(info, "Starting group: Ssrc=~.16B, PT=~B, First=~B, Len=~B, Offs=~B~n",
+ [Ssrc, PT, First, Len, Offs], Opts),
+ Pkg = #rtp_packet{ssrc = Ssrc, payload_type = PT},
+ Pl2 = write_packets(Dev, Pl, Pkg, First, Len, Offs, Opts),
+ {Args2, Opts2} = getopts_checked(Args, Opts),
+ log(debug, fun (Io) ->
+ io:format(Io, "Changed options:~n", []),
+ dump_opts(Io, Opts2 -- Opts)
+ end, [], Opts),
+ do_groups(Dev, Pl2, First+Len, Args2, Opts2).
+
+%%% error handling helpers %%%
+
+getopts_checked(Args, Opts) ->
+ try
+ getopts(Args, Opts)
+ catch
+ C:R ->
+ log(error, "~s~n",
+ [explain_error(C, R, erlang:get_stacktrace(), Opts)], Opts),
+ usage(),
+ halt(1)
+ end.
+
+explain_error(error, badarg, [{erlang,list_to_integer,[S,B]} | _ ], _Opts) ->
+ io_lib:format("Invalid number '~s' (base ~B)", [S, B]);
+explain_error(error, badarg, [{erlang,list_to_integer,[S]} | _ ], _Opts) ->
+ io_lib:format("Invalid decimal number '~s'", [S]);
+explain_error(C, R, [Hd | _ ], _Opts) ->
+ io_lib:format("~p, ~p:~p", [Hd, C, R]);
+explain_error(_, _, [], _Opts) ->
+ "".
+
+%%% usage and options %%%
+
+myname() ->
+ filename:basename(escript:script_name()).
+
+usage(Text) ->
+ io:format(standard_error, "~s: ~s~n", [myname(), Text]),
+ usage().
+
+usage() ->
+ io:format(standard_error,
+ "Usage: ~s [Options] Start Count1 Offs1 [[Options] Count2 Offs2 ...]~n",
+ [myname()]).
+
+show_version() ->
+ io:format(standard_io,
+ "~s ~s~n", [myname(), ?VERSION]).
+
+show_help() ->
+ io:format(standard_io,
+ "Usage: ~s [Options] Start Count1 Offs1 [[Options] Count2 Offs2 ...]~n~n" ++
+ "Options:~n" ++
+ " -h, --help this text~n" ++
+ " --version show version info~n" ++
+ " -i, --file=FILE reads payload from file (state format by default)~n" ++
+ " -f, --frame-size=N read payload as binary frames of size N instead~n" ++
+ " -p, --payload=HEX set constant payload~n" ++
+ " --verbose=N set verbosity~n" ++
+ " -v increase verbosity~n" ++
+ " --format=state use state format for output (default)~n" ++
+ " -C, --format=c use simple C lines for output~n" ++
+ " --format=carray use a C array for output~n" ++
+ " -s, --ssrc=SSRC set the SSRC~n" ++
+ " -t, --type=N set the payload type~n" ++
+ " -r, --rate=N set the RTP rate [8000]~n" ++
+ " -D, --duration=N set the packet duration in RTP time units [160]~n" ++
+ " -d, --delay=FLOAT add offset to playout timestamp~n" ++
+ "~n" ++
+ "Arguments:~n" ++
+ " Start initial packet (sequence) number~n" ++
+ " Count number of packets~n" ++
+ " Offs timestamp offset (in RTP units)~n" ++
+ "", [myname()]).
+
+getopts([ "--file=" ++ File | R], Opts) ->
+ getopts(R, [{file, File} | Opts]);
+getopts([ "-i" ++ T | R], Opts) ->
+ getopts_alias_arg("--file", T, R, Opts);
+getopts([ "--frame-size=" ++ N | R], Opts) ->
+ Size = list_to_integer(N),
+ getopts(R, [{frame_size, Size}, {in_format, bin} | Opts]);
+getopts([ "-f" ++ T | R], Opts) ->
+ getopts_alias_arg("--frame-size", T, R, Opts);
+getopts([ "--duration=" ++ N | R], Opts) ->
+ Duration = list_to_integer(N),
+ getopts(R, [{duration, Duration} | Opts]);
+getopts([ "-D" ++ T | R], Opts) ->
+ getopts_alias_arg("--duration", T, R, Opts);
+getopts([ "--rate=" ++ N | R], Opts) ->
+ Rate = list_to_integer(N),
+ getopts(R, [{rate, Rate} | Opts]);
+getopts([ "-r" ++ T | R], Opts) ->
+ getopts_alias_arg("--rate", T, R, Opts);
+getopts([ "--version" | _], _Opts) ->
+ show_version(),
+ halt(0);
+getopts([ "--help" | _], _Opts) ->
+ show_help(),
+ halt(0);
+getopts([ "-h" ++ T | R], Opts) ->
+ getopts_alias_no_arg("--help", T, R, Opts);
+getopts([ "--verbose=" ++ V | R], Opts) ->
+ Verbose = list_to_integer(V),
+ getopts(R, [{verbose, Verbose} | Opts]);
+getopts([ "-v" ++ T | R], Opts) ->
+ Verbose = proplists:get_value(verbose, Opts, 0),
+ getopts_short_no_arg(T, R, [ {verbose, Verbose+1} | Opts]);
+getopts([ "--format=state" | R], Opts) ->
+ getopts(R, [{format, state} | Opts]);
+getopts([ "--format=c" | R], Opts) ->
+ getopts(R, [{format, c} | Opts]);
+getopts([ "-C" ++ T | R], Opts) ->
+ getopts_alias_no_arg("--format=c", T, R, Opts);
+getopts([ "--format=carray" | R], Opts) ->
+ getopts(R, [{format, carray} | Opts]);
+getopts([ "--payload=" ++ Hex | R], Opts) ->
+ getopts(R, [{payload, hex_to_bin(Hex)} | Opts]);
+getopts([ "--ssrc=" ++ Num | R], Opts) ->
+ getopts(R, [{ssrc, list_to_num(Num)} | Opts]);
+getopts([ "-s" ++ T | R], Opts) ->
+ getopts_alias_arg("--ssrc", T, R, Opts);
+getopts([ "--type=" ++ Num | R], Opts) ->
+ getopts(R, [{pt, list_to_num(Num)} | Opts]);
+getopts([ "-t" ++ T | R], Opts) ->
+ getopts_alias_arg("--type", T, R, Opts);
+getopts([ "--delay=" ++ Num | R], Opts) ->
+ getopts(R, [{delay, list_to_float(Num)} | Opts]);
+getopts([ "-d" ++ T | R], Opts) ->
+ getopts_alias_arg("--delay", T, R, Opts);
+
+% parsing helpers
+getopts([ "--" | R], Opts) ->
+ {R, normalize_opts(Opts)};
+getopts([ O = "--" ++ _ | _], _Opts) ->
+ usage("Invalid option: " ++ O),
+ halt(1);
+getopts([ [ $-, C | _] | _], _Opts) when C < $0; C > $9 ->
+ usage("Invalid option: -" ++ [C]),
+ halt(1);
+
+getopts(R, Opts) ->
+ {R, normalize_opts(Opts)}.
+
+getopts_short_no_arg([], R, Opts) -> getopts(R, Opts);
+getopts_short_no_arg(T, R, Opts) -> getopts([ "-" ++ T | R], Opts).
+
+getopts_alias_no_arg(A, [], R, Opts) -> getopts([A | R], Opts);
+getopts_alias_no_arg(A, T, R, Opts) -> getopts([A, "-" ++ T | R], Opts).
+
+getopts_alias_arg(A, [], [T | R], Opts) -> getopts([A ++ "=" ++ T | R], Opts);
+getopts_alias_arg(A, T, R, Opts) -> getopts([A ++ "=" ++ T | R], Opts).
+
+normalize_opts(Opts) ->
+ [ proplists:lookup(E, Opts) || E <- proplists:get_keys(Opts) ].
+
+%%% conversions %%%
+
+bin_to_hex(Bin) -> [hd(integer_to_list(N,16)) || <<N:4>> <= Bin].
+hex_to_bin(Hex) -> << <<(list_to_integer([Nib],16)):4>> || Nib <- Hex>>.
+
+list_to_num("-" ++ Str) -> -list_to_num(Str);
+list_to_num("0x" ++ Str) -> list_to_integer(Str, 16);
+list_to_num("0b" ++ Str) -> list_to_integer(Str, 2);
+list_to_num(Str = [ $0 | _ ]) -> list_to_integer(Str, 8);
+list_to_num(Str) -> list_to_integer(Str, 10).
+
+%%% dumping data %%%
+
+dump_opts(Dev, Opts) ->
+ dump_opts2(Dev, Opts, proplists:get_keys(Opts)).
+
+dump_opts2(Dev, Opts, [OptName | R]) ->
+ io:format(Dev, " ~-10s: ~p~n",
+ [OptName, proplists:get_value(OptName, Opts)]),
+ dump_opts2(Dev, Opts, R);
+dump_opts2(_Dev, _Opts, []) -> ok.
+
+%%% logging %%%
+
+log(L, Fmt, Args, Opts) when is_list(Opts) ->
+ log(L, Fmt, Args, proplists:get_value(verbose, Opts, 0), Opts).
+
+log(debug, Fmt, Args, V, Opts) when V > 2 -> log2("DEBUG", Fmt, Args, Opts);
+log(info, Fmt, Args, V, Opts) when V > 1 -> log2("INFO", Fmt, Args, Opts);
+log(notice, Fmt, Args, V, Opts) when V > 0 -> log2("NOTICE", Fmt, Args, Opts);
+log(warn, Fmt, Args, _V, Opts) -> log2("WARNING", Fmt, Args, Opts);
+log(error, Fmt, Args, _V, Opts) -> log2("ERROR", Fmt, Args, Opts);
+
+log(Lvl, Fmt, Args, V, Opts) when V >= Lvl -> log2("", Fmt, Args, Opts);
+
+log(_, _, _, _i, _) -> ok.
+
+log2(Type, Fmt, Args, _Opts) when is_list(Fmt) ->
+ io:format(standard_error, "~s: " ++ Fmt, [Type | Args]);
+log2("", Fmt, Args, _Opts) when is_list(Fmt) ->
+ io:format(standard_error, Fmt, Args);
+log2(_Type, Fun, _Args, _Opts) when is_function(Fun, 1) ->
+ Fun(standard_error).
+
+%%% RTP packets %%%
+
+make_rtp_packet(P = #rtp_packet{version = 2}) ->
+ << (P#rtp_packet.version):2,
+ 0:1, % P
+ 0:1, % X
+ 0:4, % CC
+ (P#rtp_packet.marker):1,
+ (P#rtp_packet.payload_type):7,
+ (P#rtp_packet.seqno):16,
+ (P#rtp_packet.timestamp):32,
+ (P#rtp_packet.ssrc):32,
+ (P#rtp_packet.payload)/bytes
+ >>.
+
+parse_rtp_packet(
+ << 2:2, % Version 2
+ 0:1, % P (not supported yet)
+ 0:1, % X (not supported yet)
+ 0:4, % CC (not supported yet)
+ M:1,
+ PT:7,
+ SeqNo: 16,
+ TS:32,
+ Ssrc:32,
+ Payload/bytes >>) ->
+ #rtp_packet{
+ version = 0,
+ marker = M,
+ payload_type = PT,
+ seqno = SeqNo,
+ timestamp = TS,
+ ssrc = Ssrc,
+ payload = Payload}.
+
+%%% payload generation %%%
+
+next_payload(F) when is_function(F) ->
+ {F(), F};
+next_payload({F, D}) when is_function(F) ->
+ {P, D2} = F(D),
+ {P, {F, D2}};
+next_payload([P | R]) ->
+ {P, R};
+next_payload([]) ->
+ undef;
+next_payload(Bin = <<_/bytes>>) ->
+ {Bin, Bin}.
+
+%%% real writing work %%%
+
+write_packets(_Dev, DS, _P, _F, 0, _O, _Opts) ->
+ DS;
+write_packets(Dev, DataSource, P = #rtp_packet{}, F, L, O, Opts) ->
+ Format = proplists:get_value(format, Opts, state),
+ Ptime = proplists:get_value(duration, Opts, 160),
+ Delay = proplists:get_value(delay, Opts, 0),
+ Rate = proplists:get_value(rate, Opts, 8000),
+ case next_payload(DataSource) of
+ {Payload, DataSource2} ->
+ write_packet(Dev, Ptime * F / Rate + Delay,
+ P#rtp_packet{seqno = F, timestamp = F*Ptime+O,
+ payload = Payload},
+ Format),
+ write_packets(Dev, DataSource2, P, F+1, L-1, O, Opts);
+ Other -> Other
+ end.
+
+write_packet(Dev, Time, P = #rtp_packet{}, Format) ->
+ Bin = make_rtp_packet(P),
+
+ write_packet_line(Dev, Time, P, Bin, Format).
+
+write_packet_pre(Dev, carray) ->
+ io:format(Dev,
+ "struct {float t; int len; char *data;} packets[] = {~n", []);
+
+write_packet_pre(_Dev, _) -> ok.
+
+write_packet_post(Dev, carray) ->
+ io:format(Dev, "};~n", []);
+
+write_packet_post(_Dev, _) -> ok.
+
+write_packet_line(Dev, Time, _P, Bin, state) ->
+ io:format(Dev, "~f ~s~n", [Time, bin_to_hex(Bin)]);
+
+write_packet_line(Dev, Time, #rtp_packet{seqno = N, timestamp = TS}, Bin, c) ->
+ ByteList = [ [ $0, $x | integer_to_list(Byte, 16) ] || <<Byte:8>> <= Bin ],
+ ByteStr = string:join(ByteList, ", "),
+ io:format(Dev, "/* time=~f, SeqNo=~B, TS=~B */ {~s}~n", [Time, N, TS, ByteStr]);
+
+write_packet_line(Dev, Time, #rtp_packet{seqno = N, timestamp = TS}, Bin, carray) ->
+ io:format(Dev, " /* RTP: SeqNo=~B, TS=~B */~n", [N, TS]),
+ io:format(Dev, " {~f, ~B, \"", [Time, size(Bin)]),
+ [ io:format(Dev, "\\x~2.16.0B", [Byte]) || <<Byte:8>> <= Bin ],
+ io:format(Dev, "\"},~n", []).
+
+%%% real reading work %%%
+
+read_packets(Dev, Opts) ->
+ Format = proplists:get_value(in_format, Opts, state),
+
+ read_packets(Dev, Opts, Format).
+
+read_packets(Dev, Opts, Format) ->
+ case read_packet(Dev, Opts, Format) of
+ eof -> [];
+ Tuple -> [Tuple | read_packets(Dev, Opts, Format)]
+ end.
+
+read_packet(Dev, Opts, bin) ->
+ Size = proplists:get_value(frame_size, Opts),
+ case file:read(Dev, Size) of
+ {ok, Data} -> {0, #rtp_packet{payload = iolist_to_binary(Data)}};
+ eof -> eof
+ end;
+read_packet(Dev, _Opts, Format) ->
+ case read_packet_line(Dev, Format) of
+ {Time, Bin} -> {Time, parse_rtp_packet(Bin)};
+ eof -> eof
+ end.
+
+read_packet_line(Dev, state) ->
+ case io:fread(Dev, "", "~f ~s") of
+ {ok, [Time, Hex]} -> {Time, hex_to_bin(Hex)};
+ eof -> eof
+ end.
diff --git a/contrib/rtp/rtp_replay.st b/contrib/rtp/rtp_replay.st
new file mode 100644
index 00000000..e26d0738
--- /dev/null
+++ b/contrib/rtp/rtp_replay.st
@@ -0,0 +1,21 @@
+"
+Simple UDP replay from the state files
+"
+
+PackageLoader fileInPackage: #Sockets.
+FileStream fileIn: 'rtp_replay_shared.st'.
+
+
+Eval [
+ | replay file host dport |
+
+ file := Smalltalk arguments at: 1 ifAbsent: [ 'rtpstream.state' ].
+ host := Smalltalk arguments at: 2 ifAbsent: [ '127.0.0.1' ].
+ dport := (Smalltalk arguments at: 3 ifAbsent: [ '4000' ]) asInteger.
+ sport := (Smalltalk arguments at: 4 ifAbsent: [ '0' ]) asInteger.
+
+ replay := RTPReplay on: file fromPort: sport.
+
+ Transcript nextPutAll: 'Going to stream now'; nl.
+ replay streamAudio: host port: dport.
+]
diff --git a/contrib/rtp/rtp_replay_shared.st b/contrib/rtp/rtp_replay_shared.st
new file mode 100644
index 00000000..7b68c0f5
--- /dev/null
+++ b/contrib/rtp/rtp_replay_shared.st
@@ -0,0 +1,118 @@
+"
+Simple UDP replay from the state files
+"
+
+PackageLoader fileInPackage: #Sockets.
+
+Object subclass: SDPUtils [
+ "Look into using PetitParser."
+ SDPUtils class >> findPort: aSDP [
+ aSDP linesDo: [:line |
+ (line startsWith: 'm=audio ') ifTrue: [
+ | stream |
+ stream := line readStream
+ skip: 'm=audio ' size;
+ yourself.
+ ^ Number readFrom: stream.
+ ]
+ ].
+
+ ^ self error: 'Not found'.
+ ]
+
+ SDPUtils class >> findHost: aSDP [
+ aSDP linesDo: [:line |
+ (line startsWith: 'c=IN IP4 ') ifTrue: [
+ | stream |
+ ^ stream := line readStream
+ skip: 'c=IN IP4 ' size;
+ upToEnd.
+ ]
+ ].
+
+ ^ self error: 'Not found'.
+ ]
+]
+
+Object subclass: RTPReplay [
+ | filename socket |
+ RTPReplay class >> on: aFile [
+ ^ self new
+ initialize;
+ file: aFile; yourself
+ ]
+
+ RTPReplay class >> on: aFile fromPort: aPort [
+ ^ self new
+ initialize: aPort;
+ file: aFile; yourself
+ ]
+
+ initialize [
+ self initialize: 0.
+ ]
+
+ initialize: aPort [
+ socket := Sockets.DatagramSocket local: '0.0.0.0' port: aPort.
+ ]
+
+ file: aFile [
+ filename := aFile
+ ]
+
+ localPort [
+ ^ socket port
+ ]
+
+ streamAudio: aHost port: aPort [
+ | file last_time last_image udp_send dest |
+
+ last_time := nil.
+ last_image := nil.
+ file := FileStream open: filename mode: #read.
+
+ "Send the payload"
+ dest := Sockets.SocketAddress byName: aHost.
+ udp_send := [:payload | | datagram |
+ datagram := Sockets.Datagram data: payload contents address: dest port: aPort.
+ socket nextPut: datagram
+ ].
+
+ [file atEnd] whileFalse: [
+ | lineStream time data now_image |
+ lineStream := file nextLine readStream.
+
+ "Read the time, skip the blank, parse the data"
+ time := Number readFrom: lineStream.
+ lineStream skip: 1.
+
+ data := WriteStream on: (ByteArray new: 30).
+ [lineStream atEnd] whileFalse: [
+ | hex |
+ hex := lineStream next: 2.
+ data nextPut: (Number readFrom: hex readStream radix: 16).
+ ].
+
+ last_time isNil
+ ifTrue: [
+ "First time, send it right now"
+ last_time := time.
+ last_image := Time millisecondClockValue.
+ udp_send value: data.
+ ]
+ ifFalse: [
+ | wait_image new_image_time |
+
+ "How long to wait?"
+ wait_image := last_image + ((time - last_time) * 1000).
+ [ wait_image > Time millisecondClockValue ]
+ whileTrue: [Processor yield].
+
+ udp_send value: data.
+ last_time := time.
+ last_image := wait_image.
+ ]
+ ]
+ ]
+]
+
diff --git a/contrib/rtp/rtp_replay_sip.st b/contrib/rtp/rtp_replay_sip.st
new file mode 100644
index 00000000..5f844df1
--- /dev/null
+++ b/contrib/rtp/rtp_replay_sip.st
@@ -0,0 +1,87 @@
+"""
+Create a SIP connection and then stream...
+"""
+
+PackageLoader
+ fileInPackage: #OsmoSIP.
+
+"Load for the replay code"
+FileStream fileIn: 'rtp_replay_shared.st'.
+
+
+Osmo.SIPCall subclass: StreamCall [
+ | sem stream |
+
+ createCall: aSDP [
+ | sdp |
+ stream := RTPReplay on: 'rtp_ssrc6976010.240.240.1_to_10.240.240.50.state'.
+ sdp := aSDP % {stream localPort}.
+ ^ super createCall: sdp.
+ ]
+
+ sem: aSemaphore [
+ sem := aSemaphore
+ ]
+
+ sessionNew [
+ | host port |
+ Transcript nextPutAll: 'The call has started'; nl.
+ Transcript nextPutAll: sdp_result; nl.
+
+ host := SDPUtils findHost: sdp_result.
+ port := SDPUtils findPort: sdp_result.
+
+ [
+ stream streamAudio: host port: port.
+ Transcript nextPutAll: 'Streaming has finished.'; nl.
+ ] fork.
+ ]
+
+ sessionFailed [
+ sem signal
+ ]
+
+ sessionEnd [
+ sem signal
+ ]
+]
+
+Eval [
+ | transport agent call sem sdp_fr sdp_amr |
+
+
+ sdp_fr := (WriteStream on: String new)
+ nextPutAll: 'v=0'; cr; nl;
+ nextPutAll: 'o=twinkle 1739517580 1043400482 IN IP4 127.0.0.1'; cr; nl;
+ nextPutAll: 's=-'; cr; nl;
+ nextPutAll: 'c=IN IP4 127.0.0.1'; cr; nl;
+ nextPutAll: 't=0 0'; cr; nl;
+ nextPutAll: 'm=audio %1 RTP/AVP 0 101'; cr; nl;
+ nextPutAll: 'a=rtpmap:0 PCMU/8000'; cr; nl;
+ nextPutAll: 'a=rtpmap:101 telephone-event/8000'; cr; nl;
+ nextPutAll: 'a=fmtp:101 0-15'; cr; nl;
+ nextPutAll: 'a=ptime:20'; cr; nl;
+ contents.
+
+ sem := Semaphore new.
+ transport := Osmo.SIPUdpTransport
+ startOn: '0.0.0.0' port: 5066.
+ agent := Osmo.SIPUserAgent createOn: transport.
+ transport start.
+
+ call := (StreamCall
+ fromUser: 'sip:1000@sip.zecke.osmocom.org'
+ host: '127.0.0.1'
+ port: 5060
+ to: 'sip:123456@127.0.0.1'
+ on: agent)
+ sem: sem; yourself.
+
+ call createCall: sdp_fr.
+
+
+ "Wait for the stream to have ended"
+ sem wait.
+
+ (Delay forSeconds: 4) wait.
+]
diff --git a/contrib/rtp/timestamp_rtp.lua b/contrib/rtp/timestamp_rtp.lua
new file mode 100644
index 00000000..c18a06be
--- /dev/null
+++ b/contrib/rtp/timestamp_rtp.lua
@@ -0,0 +1,28 @@
+print("Ni hao")
+
+
+do
+ local tap = Listener.new("ip", "rtp")
+ local rtp_ssrc = Field.new("rtp.ssrc")
+ local frame_time = Field.new("frame.time_relative")
+ local rtp = Field.new("rtp")
+
+ function tap.packet(pinfo, tvb, ip)
+ local ip_src, ip_dst = tostring(ip.ip_src), tostring(ip.ip_dst)
+ local rtp_data = rtp()
+ local filename = "rtp_ssrc" .. rtp_ssrc() "_src_" .. ip_src .. "_to_" .. ip_dst .. ".state"
+ local f = io.open(filename, "a")
+
+ f:write(tostring(frame_time()) .. " ")
+ f:write(tostring(rtp_data.value))
+ f:write("\n")
+ f:close()
+ end
+
+ function tap.draw()
+ print("DRAW")
+ end
+ function tap.reset()
+ print("RESET")
+ end
+end
diff --git a/contrib/sms/fill-hlr.st b/contrib/sms/fill-hlr.st
new file mode 100644
index 00000000..da0643ec
--- /dev/null
+++ b/contrib/sms/fill-hlr.st
@@ -0,0 +1,66 @@
+"I create output for some simple SQL statements for the HLR db"
+
+
+Eval [
+
+"Create tables if they don't exist"
+Transcript show: 'CREATE TABLE SMS (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ created TIMESTAMP NOT NULL,
+ sent TIMESTAMP,
+ sender_id INTEGER NOT NULL,
+ receiver_id INTEGER NOT NULL,
+ deliver_attempts INTEGER NOT NULL DEFAULT 0,
+ valid_until TIMESTAMP,
+ reply_path_req INTEGER NOT NULL,
+ status_rep_req INTEGER NOT NULL,
+ protocol_id INTEGER NOT NULL,
+ data_coding_scheme INTEGER NOT NULL,
+ ud_hdr_ind INTEGER NOT NULL,
+ dest_addr TEXT,
+ user_data BLOB,
+ header BLOB,
+ text TEXT);'; nl;
+ show: 'CREATE TABLE Subscriber (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ created TIMESTAMP NOT NULL,
+ updated TIMESTAMP NOT NULL,
+ imsi NUMERIC UNIQUE NOT NULL,
+ name TEXT,
+ extension TEXT UNIQUE,
+ authorized INTEGER NOT NULL DEFAULT 0,
+ tmsi TEXT UNIQUE,
+ lac INTEGER NOT NULL DEFAULT 0);'; nl.
+
+"Create some dummy subscribers"
+num_sub := 1000.
+num_sms := 30.
+lac := 1.
+
+Transcript show: 'BEGIN;'; nl.
+
+1 to: num_sub do: [:each |
+ Transcript show: 'INSERT INTO Subscriber
+ (imsi, created, updated, authorized, lac, extension)
+ VALUES
+ (%1, datetime(''now''), datetime(''now''), 1, %2, %3);' %
+ {(274090000000000 + each). lac. each}; nl.
+].
+
+1 to: num_sms do: [:sms |
+ 1 to: num_sub do: [:sub |
+ Transcript show: 'INSERT INTO SMS
+ (created, sender_id, receiver_id, valid_until,
+ reply_path_req, status_rep_req, protocol_id,
+ data_coding_scheme, ud_hdr_ind, dest_addr,
+ text) VALUES
+ (datetime(''now''), 1, %1, ''2222-2-2'',
+ 0, 0, 0,
+ 0, 0, ''123456'',
+ ''abc'');' % {sub}; nl.
+ ]
+].
+
+Transcript show: 'COMMIT;'; nl.
+
+]
diff --git a/contrib/sms/hlr-query.st b/contrib/sms/hlr-query.st
new file mode 100644
index 00000000..bd3f97a4
--- /dev/null
+++ b/contrib/sms/hlr-query.st
@@ -0,0 +1,10 @@
+"Query for one SMS"
+
+Eval [
+1 to: 100 do: [:each |
+ Transcript show: 'SELECT SMS.* FROM SMS
+ JOIN Subscriber ON SMS.receiver_id = Subscriber.id
+ WHERE SMS.id >= 1 AND SMS.sent IS NULL AND Subscriber.lac > 0
+ ORDER BY SMS.id LIMIT 1;'; nl.
+].
+]
diff --git a/contrib/sms/sqlite-probe.tap.d b/contrib/sms/sqlite-probe.tap.d
new file mode 100644
index 00000000..e75cdfcf
--- /dev/null
+++ b/contrib/sms/sqlite-probe.tap.d
@@ -0,0 +1,5 @@
+probe process("/usr/lib/libsqlite3.so.0.8.6").function("sqlite3_get_table")
+{
+ a = user_string($zSql);
+ printf("sqlite3_get_table called '%s'\n", a);
+}
diff --git a/contrib/soap.py b/contrib/soap.py
new file mode 100755
index 00000000..4d0a023f
--- /dev/null
+++ b/contrib/soap.py
@@ -0,0 +1,188 @@
+#!/usr/bin/python3
+# -*- mode: python-mode; py-indent-tabs-mode: nil -*-
+"""
+/*
+ * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+"""
+
+__version__ = "v0.7" # bump this on every non-trivial change
+
+from twisted.internet import defer, reactor
+from twisted_ipa import CTRL, IPAFactory, __version__ as twisted_ipa_version
+from ipa import Ctrl
+from treq import post, collect
+from suds.client import Client
+from functools import partial
+from distutils.version import StrictVersion as V # FIXME: use NormalizedVersion from PEP-386 when available
+import argparse, datetime, signal, sys, os, logging, logging.handlers
+
+# we don't support older versions of TwistedIPA module
+assert V(twisted_ipa_version) > V('0.4')
+
+# keys from OpenBSC openbsc/src/libbsc/bsc_rf_ctrl.c, values SOAP-specific
+oper = { 'inoperational' : 0, 'operational' : 1 }
+admin = { 'locked' : 0, 'unlocked' : 1 }
+policy = { 'off' : 0, 'on' : 1, 'grace' : 2, 'unknown' : 3 }
+
+# keys from OpenBSC openbsc/src/libbsc/bsc_vty.c
+fix = { 'invalid' : 0, 'fix2d' : 1, 'fix3d' : 1 } # SOAP server treats it as boolean but expects int
+
+
+def handle_reply(p, f, log, r):
+ """
+ Reply handler: takes function p to process raw SOAP server reply r, function f to run for each command and verbosity flag v
+ """
+ repl = p(r) # result is expected to have both commands[] array and error string (could be None)
+ bsc_id = repl.commands[0].split()[0].split('.')[3] # we expect 1st command to have net.0.bsc.666.bts.2.trx.1 location prefix format
+ log.info("Received SOAP response for BSC %s with %d commands, error status: %s" % (bsc_id, len(repl.commands), repl.error))
+ log.debug("BSC %s commands: %s" % (bsc_id, repl.commands))
+ for t in repl.commands: # Process OpenBscCommands format from .wsdl
+ (_, m) = Ctrl().cmd(*t.split())
+ f(m)
+
+
+class Trap(CTRL):
+ """
+ TRAP handler (agnostic to factory's client object)
+ """
+ def ctrl_TRAP(self, data, op_id, v):
+ """
+ Parse CTRL TRAP and dispatch to appropriate handler after normalization
+ """
+ (l, r) = v.split()
+ loc = l.split('.')
+ t_type = loc[-1]
+ p = partial(lambda a, i: a[i] if len(a) > i else None, loc) # parse helper
+ method = getattr(self, 'handle_' + t_type.replace('-', ''), lambda: "Unhandled %s trap" % t_type)
+ method(p(1), p(3), p(5), p(7), r) # we expect net.0.bsc.666.bts.2.trx.1 format for trap prefix
+
+ def ctrl_SET_REPLY(self, data, _, v):
+ """
+ Debug log for replies to our commands
+ """
+ self.factory.log.debug('SET REPLY %s' % v)
+
+ def ctrl_ERROR(self, data, op_id, v):
+ """
+ We want to know if smth went wrong
+ """
+ self.factory.log.debug('CTRL ERROR [%s] %s' % (op_id, v))
+
+ def connectionMade(self):
+ """
+ Logging wrapper, calling super() is necessary not to break reconnection logic
+ """
+ self.factory.log.info("Connected to CTRL@%s:%d" % (self.factory.host, self.factory.port))
+ super(CTRL, self).connectionMade()
+
+ @defer.inlineCallbacks
+ def handle_locationstate(self, net, bsc, bts, trx, data):
+ """
+ Handle location-state TRAP: parse trap content, build SOAP context and use treq's routines to post it while setting up async handlers
+ """
+ (ts, fx, lat, lon, height, opr, adm, pol, mcc, mnc) = data.split(',')
+ tstamp = datetime.datetime.fromtimestamp(float(ts)).isoformat()
+ self.factory.log.debug('location-state@%s.%s.%s.%s (%s) [%s/%s] => %s' % (net, bsc, bts, trx, tstamp, mcc, mnc, data))
+ ctx = self.factory.client.registerSiteLocation(bsc, float(lon), float(lat), fix.get(fx, 0), tstamp, oper.get(opr, 2), admin.get(adm, 2), policy.get(pol, 3))
+ d = post(self.factory.location, ctx.envelope)
+ d.addCallback(collect, partial(handle_reply, ctx.process_reply, self.transport.write, self.factory.log)) # treq's collect helper is handy to get all reply content at once using closure on ctx
+ d.addErrback(lambda e, bsc: self.factory.log.critical("HTTP POST error %s while trying to register BSC %s" % (e, bsc)), bsc) # handle HTTP errors
+ # Ensure that we run only limited number of requests in parallel:
+ yield self.factory.semaphore.acquire()
+ yield d # we end up here only if semaphore is available which means it's ok to fire the request without exceeding the limit
+ self.factory.semaphore.release()
+
+ def handle_notificationrejectionv1(self, net, bsc, bts, trx, data):
+ """
+ Handle notification-rejection-v1 TRAP: just an example to show how more message types can be handled
+ """
+ self.factory.log.debug('notification-rejection-v1@bsc-id %s => %s' % (bsc, data))
+
+
+class TrapFactory(IPAFactory):
+ """
+ Store SOAP client object so TRAP handler can use it for requests
+ """
+ location = None
+ log = None
+ semaphore = None
+ client = None
+ host = None
+ port = None
+ def __init__(self, host, port, proto, semaphore, log, wsdl=None, location=None):
+ self.host = host # for logging only,
+ self.port = port # seems to be no way to get it from ReconnectingClientFactory
+ self.log = log
+ self.semaphore = semaphore
+ soap = Client(wsdl, location=location, nosend=True) # make async SOAP client
+ self.location = location.encode() if location else soap.wsdl.services[0].ports[0].location # necessary for dispatching HTTP POST via treq
+ self.client = soap.service
+ level = self.log.getEffectiveLevel()
+ self.log.setLevel(logging.WARNING) # we do not need excessive debug from lower levels
+ super(TrapFactory, self).__init__(proto, self.log)
+ self.log.setLevel(level)
+ self.log.debug("Using IPA %s, SUDS client: %s" % (Ctrl.version, soap))
+
+
+def reloader(path, script, log, dbg1, dbg2, signum, _):
+ """
+ Signal handler: we have to use execl() because twisted's reactor is not restartable due to some bug in twisted implementation
+ """
+ log.info("Received Signal %d - restarting..." % signum)
+ if signum == signal.SIGUSR1 and dbg1 not in sys.argv and dbg2 not in sys.argv:
+ sys.argv.append(dbg1) # enforce debug
+ if signum == signal.SIGUSR2 and (dbg1 in sys.argv or dbg2 in sys.argv): # disable debug
+ if dbg1 in sys.argv:
+ sys.argv.remove(dbg1)
+ if dbg2 in sys.argv:
+ sys.argv.remove(dbg2)
+ os.execl(path, script, *sys.argv[1:])
+
+
+if __name__ == '__main__':
+ p = argparse.ArgumentParser(description='Proxy between given SOAP service and Osmocom CTRL protocol.')
+ p.add_argument('-v', '--version', action='version', version=("%(prog)s " + __version__))
+ p.add_argument('-p', '--port', type=int, default=4250, help="Port to use for CTRL interface, defaults to 4250")
+ p.add_argument('-c', '--ctrl', default='localhost', help="Adress to use for CTRL interface, defaults to localhost")
+ p.add_argument('-w', '--wsdl', required=True, help="WSDL URL for SOAP")
+ p.add_argument('-n', '--num', type=int, default=5, help="Max number of concurrent HTTP requests to SOAP server")
+ p.add_argument('-d', '--debug', action='store_true', help="Enable debug log")
+ p.add_argument('-o', '--output', action='store_true', help="Log to STDOUT in addition to SYSLOG")
+ p.add_argument('-l', '--location', help="Override location found in WSDL file (don't use unless you know what you're doing)")
+ args = p.parse_args()
+
+ log = logging.getLogger('CTRL2SOAP')
+ if args.debug:
+ log.setLevel(logging.DEBUG)
+ else:
+ log.setLevel(logging.INFO)
+ log.addHandler(logging.handlers.SysLogHandler('/dev/log'))
+ if args.output:
+ log.addHandler(logging.StreamHandler(sys.stdout))
+
+ reboot = partial(reloader, os.path.abspath(__file__), os.path.basename(__file__), log, '-d', '--debug') # keep in sync with add_argument() call above
+ signal.signal(signal.SIGHUP, reboot)
+ signal.signal(signal.SIGQUIT, reboot)
+ signal.signal(signal.SIGUSR1, reboot) # restart and enabled debug output
+ signal.signal(signal.SIGUSR2, reboot) # restart and disable debug output
+
+ log.info("SOAP proxy %s starting with PID %d ..." % (__version__, os.getpid()))
+ reactor.connectTCP(args.ctrl, args.port, TrapFactory(args.ctrl, args.port, Trap, defer.DeferredSemaphore(args.num), log, args.wsdl, args.location))
+ reactor.run()
diff --git a/contrib/systemd/osmo-bsc-mgcp.service b/contrib/systemd/osmo-bsc-mgcp.service
new file mode 100644
index 00000000..c040e607
--- /dev/null
+++ b/contrib/systemd/osmo-bsc-mgcp.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=OpenBSC MGCP
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-bsc_mgcp -s -c /etc/osmocom/osmo-bsc-mgcp.cfg
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/osmo-bsc.service b/contrib/systemd/osmo-bsc.service
new file mode 100644
index 00000000..4047fef4
--- /dev/null
+++ b/contrib/systemd/osmo-bsc.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=OpenBSC BSC
+Wants=osmo-bsc-mgcp.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-bsc -c /etc/osmocom/osmo-bsc.cfg -s
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/osmo-gbproxy.service b/contrib/systemd/osmo-gbproxy.service
new file mode 100644
index 00000000..a0b7829d
--- /dev/null
+++ b/contrib/systemd/osmo-gbproxy.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Osmocom Gb proxy
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/osmo-gbproxy -c /etc/osmocom/osmo-gbproxy.cfg
+Restart=always
+RestartSec=2
+RestartPreventExitStatus=1
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/osmo-nitb.service b/contrib/systemd/osmo-nitb.service
new file mode 100644
index 00000000..377497ee
--- /dev/null
+++ b/contrib/systemd/osmo-nitb.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=OpenBSC Network In the Box (NITB)
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-nitb -s -C -c /etc/osmocom/osmo-nitb.cfg -l /var/lib/osmocom/hlr.sqlite3
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/osmo-sgsn.service b/contrib/systemd/osmo-sgsn.service
new file mode 100644
index 00000000..674d7865
--- /dev/null
+++ b/contrib/systemd/osmo-sgsn.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=OpenBSC SGSN
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-sgsn -c /etc/osmocom/osmo-sgsn.cfg
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/testconv/Makefile b/contrib/testconv/Makefile
new file mode 100644
index 00000000..bb856f75
--- /dev/null
+++ b/contrib/testconv/Makefile
@@ -0,0 +1,16 @@
+
+OBJS = testconv_main.o
+
+CC = gcc
+CFLAGS = -O0 -ggdb -Wall
+LDFLAGS =
+CPPFLAGS = -I../.. -I../../include $(shell pkg-config --cflags libosmocore) $(shell pkg-config --cflags libbcg729)
+LIBS = ../../src/libmgcp/libmgcp.a ../../src/libcommon/libcommon.a $(shell pkg-config --libs libosmocore) $(shell pkg-config --libs libbcg729) -lgsm -lrt
+
+testconv: $(OBJS)
+ $(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
+
+testconv_main.o: testconv_main.c
+
+$(OBJS):
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
diff --git a/contrib/testconv/testconv_main.c b/contrib/testconv/testconv_main.c
new file mode 100644
index 00000000..6c95c554
--- /dev/null
+++ b/contrib/testconv/testconv_main.c
@@ -0,0 +1,133 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include "bscconfig.h"
+#ifndef BUILD_MGCP_TRANSCODING
+#error "Requires MGCP transcoding enabled (see --enable-mgcp-transcoding)"
+#endif
+
+#include "openbsc/mgcp_transcode.h"
+
+static int audio_name_to_type(const char *name)
+{
+ if (!strcasecmp(name, "gsm"))
+ return 3;
+#ifdef HAVE_BCG729
+ else if (!strcasecmp(name, "g729"))
+ return 18;
+#endif
+ else if (!strcasecmp(name, "pcma"))
+ return 8;
+ else if (!strcasecmp(name, "l16"))
+ return 11;
+ return -1;
+}
+
+int mgcp_get_trans_frame_size(void *state_, int nsamples, int dst);
+
+int main(int argc, char **argv)
+{
+ char buf[4096] = {0x80, 0};
+ int cc, rc;
+ struct mgcp_rtp_end *dst_end;
+ struct mgcp_rtp_end *src_end;
+ struct mgcp_trunk_config tcfg = {{0}};
+ struct mgcp_endpoint endp = {0};
+ struct mgcp_process_rtp_state *state;
+ int in_size;
+ int in_samples = 160;
+ int out_samples = 0;
+ uint32_t ts = 0;
+ uint16_t seq = 0;
+
+ osmo_init_logging(&log_info);
+
+ tcfg.endpoints = &endp;
+ tcfg.number_endpoints = 1;
+ endp.tcfg = &tcfg;
+ mgcp_initialize_endp(&endp);
+
+ dst_end = &endp.bts_end;
+ src_end = &endp.net_end;
+
+ if (argc <= 2)
+ errx(1, "Usage: {gsm|g729|pcma|l16} {gsm|g729|pcma|l16} [SPP]");
+
+ if ((src_end->codec.payload_type = audio_name_to_type(argv[1])) == -1)
+ errx(1, "invalid input format '%s'", argv[1]);
+ if ((dst_end->codec.payload_type = audio_name_to_type(argv[2])) == -1)
+ errx(1, "invalid output format '%s'", argv[2]);
+ if (argc > 3)
+ out_samples = atoi(argv[3]);
+
+ if (out_samples) {
+ dst_end->codec.frame_duration_den = dst_end->codec.rate;
+ dst_end->codec.frame_duration_num = out_samples;
+ dst_end->frames_per_packet = 1;
+ }
+
+ rc = mgcp_transcoding_setup(&endp, dst_end, src_end);
+ if (rc < 0)
+ errx(1, "setup failed: %s", strerror(-rc));
+
+ state = dst_end->rtp_process_data;
+ OSMO_ASSERT(state != NULL);
+
+ in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0);
+ OSMO_ASSERT(sizeof(buf) >= in_size + 12);
+
+ buf[1] = src_end->codec.payload_type;
+ *(uint16_t*)(buf+2) = htons(1);
+ *(uint32_t*)(buf+4) = htonl(0);
+ *(uint32_t*)(buf+8) = htonl(0xaabbccdd);
+
+ while ((cc = read(0, buf + 12, in_size))) {
+ int cont;
+ int len;
+
+ if (cc != in_size)
+ err(1, "read");
+
+ *(uint16_t*)(buf+2) = htonl(seq);
+ *(uint32_t*)(buf+4) = htonl(ts);
+
+ seq += 1;
+ ts += in_samples;
+
+ cc += 12; /* include RTP header */
+
+ len = cc;
+
+ do {
+ cont = mgcp_transcoding_process_rtp(&endp, dst_end,
+ buf, &len, sizeof(buf));
+ if (cont == -EAGAIN) {
+ fprintf(stderr, "Got EAGAIN\n");
+ break;
+ }
+
+ if (cont < 0)
+ errx(1, "processing failed: %s", strerror(-cont));
+
+ len -= 12; /* ignore RTP header */
+
+ if (write(1, buf + 12, len) != len)
+ err(1, "write");
+
+ len = cont;
+ } while (len > 0);
+ }
+ return 0;
+}
+
diff --git a/contrib/twisted_ipa.py b/contrib/twisted_ipa.py
new file mode 100755
index 00000000..e6d7b1a1
--- /dev/null
+++ b/contrib/twisted_ipa.py
@@ -0,0 +1,384 @@
+#!/usr/bin/python3
+# -*- mode: python-mode; py-indent-tabs-mode: nil -*-
+"""
+/*
+ * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+"""
+
+__version__ = "0.6" # bump this on every non-trivial change
+
+from ipa import Ctrl, IPA
+from twisted.internet.protocol import ReconnectingClientFactory
+from twisted.internet import reactor
+from twisted.protocols import basic
+import argparse, logging
+
+class IPACommon(basic.Int16StringReceiver):
+ """
+ Generic IPA protocol handler: include some routines for simpler subprotocols.
+ It's not intended as full implementation of all subprotocols, rather common ground and example code.
+ """
+ def dbg(self, line):
+ """
+ Debug print helper
+ """
+ self.factory.log.debug(line)
+
+ def osmo_CTRL(self, data):
+ """
+ OSMO CTRL protocol
+ Placeholder, see corresponding derived class
+ """
+ pass
+
+ def osmo_MGCP(self, data):
+ """
+ OSMO MGCP extension
+ """
+ self.dbg('OSMO MGCP received %s' % data)
+
+ def osmo_LAC(self, data):
+ """
+ OSMO LAC extension
+ """
+ self.dbg('OSMO LAC received %s' % data)
+
+ def osmo_SMSC(self, data):
+ """
+ OSMO SMSC extension
+ """
+ self.dbg('OSMO SMSC received %s' % data)
+
+ def osmo_ORC(self, data):
+ """
+ OSMO ORC extension
+ """
+ self.dbg('OSMO ORC received %s' % data)
+
+ def osmo_GSUP(self, data):
+ """
+ OSMO GSUP extension
+ """
+ self.dbg('OSMO GSUP received %s' % data)
+
+ def osmo_OAP(self, data):
+ """
+ OSMO OAP extension
+ """
+ self.dbg('OSMO OAP received %s' % data)
+
+ def osmo_UNKNOWN(self, data):
+ """
+ OSMO defaul extension handler
+ """
+ self.dbg('OSMO unknown extension received %s' % data)
+
+ def handle_RSL(self, data, proto, extension):
+ """
+ RSL protocol handler
+ """
+ self.dbg('IPA RSL received message with extension %s' % extension)
+
+ def handle_CCM(self, data, proto, msgt):
+ """
+ CCM (IPA Connection Management)
+ Placeholder, see corresponding derived class
+ """
+ pass
+
+ def handle_SCCP(self, data, proto, extension):
+ """
+ SCCP protocol handler
+ """
+ self.dbg('IPA SCCP received message with extension %s' % extension)
+
+ def handle_OML(self, data, proto, extension):
+ """
+ OML protocol handler
+ """
+ self.dbg('IPA OML received message with extension %s' % extension)
+
+ def handle_OSMO(self, data, proto, extension):
+ """
+ Dispatcher point for OSMO subprotocols based on extension name, lambda default should never happen
+ """
+ method = getattr(self, 'osmo_' + IPA().ext(extension), lambda: "extension dispatch failure")
+ method(data)
+
+ def handle_MGCP(self, data, proto, extension):
+ """
+ MGCP protocol handler
+ """
+ self.dbg('IPA MGCP received message with attribute %s' % extension)
+
+ def handle_UNKNOWN(self, data, proto, extension):
+ """
+ Default protocol handler
+ """
+ self.dbg('IPA received message for %s (%s) protocol with attribute %s' % (IPA().proto(proto), proto, extension))
+
+ def process_chunk(self, data):
+ """
+ Generic message dispatcher for IPA (sub)protocols based on protocol name, lambda default should never happen
+ """
+ (_, proto, extension, content) = IPA().del_header(data)
+ if content is not None:
+ self.dbg('IPA received %s::%s [%d/%d] %s' % (IPA().proto(proto), IPA().ext_name(proto, extension), len(data), len(content), content))
+ method = getattr(self, 'handle_' + IPA().proto(proto), lambda: "protocol dispatch failure")
+ method(content, proto, extension)
+
+ def dataReceived(self, data):
+ """
+ Override for dataReceived from Int16StringReceiver because of inherently incompatible interpretation of length
+ If default handler is used than we would always get off-by-1 error (Int16StringReceiver use equivalent of l + 2)
+ """
+ if len(data):
+ (head, tail) = IPA().split_combined(data)
+ self.process_chunk(head)
+ self.dataReceived(tail)
+
+ def connectionMade(self):
+ """
+ We have to resetDelay() here to drop internal state to default values to make reconnection logic work
+ Make sure to call this via super() if overriding to keep reconnection logic intact
+ """
+ addr = self.transport.getPeer()
+ self.dbg('IPA connected to %s:%d peer' % (addr.host, addr.port))
+ self.factory.resetDelay()
+
+
+class CCM(IPACommon):
+ """
+ Implementation of CCM protocol for IPA multiplex
+ """
+ def ack(self):
+ self.transport.write(IPA().id_ack())
+
+ def ping(self):
+ self.transport.write(IPA().ping())
+
+ def pong(self):
+ self.transport.write(IPA().pong())
+
+ def handle_CCM(self, data, proto, msgt):
+ """
+ CCM (IPA Connection Management)
+ Only basic logic necessary for tests is implemented (ping-pong, id ack etc)
+ """
+ if msgt == IPA.MSGT['ID_GET']:
+ self.transport.getHandle().sendall(IPA().id_resp(self.factory.ccm_id))
+ # if we call
+ # self.transport.write(IPA().id_resp(self.factory.test_id))
+ # instead, than we would have to also call
+ # reactor.callLater(1, self.ack)
+ # instead of self.ack()
+ # otherwise the writes will be glued together - hence the necessity for ugly hack with 1s timeout
+ # Note: this still might work depending on the IPA implementation details on the other side
+ self.ack()
+ # schedule PING in 4s
+ reactor.callLater(4, self.ping)
+ if msgt == IPA.MSGT['PING']:
+ self.pong()
+
+
+class CTRL(IPACommon):
+ """
+ Implementation of Osmocom control protocol for IPA multiplex
+ """
+ def ctrl_SET(self, data, op_id, v):
+ """
+ Handle CTRL SET command
+ """
+ self.dbg('CTRL SET [%s] %s' % (op_id, v))
+
+ def ctrl_SET_REPLY(self, data, op_id, v):
+ """
+ Handle CTRL SET reply
+ """
+ self.dbg('CTRL SET REPLY [%s] %s' % (op_id, v))
+
+ def ctrl_GET(self, data, op_id, v):
+ """
+ Handle CTRL GET command
+ """
+ self.dbg('CTRL GET [%s] %s' % (op_id, v))
+
+ def ctrl_GET_REPLY(self, data, op_id, v):
+ """
+ Handle CTRL GET reply
+ """
+ self.dbg('CTRL GET REPLY [%s] %s' % (op_id, v))
+
+ def ctrl_TRAP(self, data, op_id, v):
+ """
+ Handle CTRL TRAP command
+ """
+ self.dbg('CTRL TRAP [%s] %s' % (op_id, v))
+
+ def ctrl_ERROR(self, data, op_id, v):
+ """
+ Handle CTRL ERROR reply
+ """
+ self.dbg('CTRL ERROR [%s] %s' % (op_id, v))
+
+ def osmo_CTRL(self, data):
+ """
+ OSMO CTRL message dispatcher, lambda default should never happen
+ For basic tests only, appropriate handling routines should be replaced: see CtrlServer for example
+ """
+ self.dbg('OSMO CTRL received %s::%s' % Ctrl().parse(data.decode('utf-8')))
+ (cmd, op_id, v) = data.decode('utf-8').split(' ', 2)
+ method = getattr(self, 'ctrl_' + cmd, lambda: "CTRL unknown command")
+ method(data, op_id, v)
+
+
+class IPAServer(CCM):
+ """
+ Test implementation of IPA server
+ Demonstrate CCM opearation by overriding necessary bits from CCM
+ """
+ def connectionMade(self):
+ """
+ Keep reconnection logic working by calling routine from CCM
+ Initiate CCM upon connection
+ """
+ addr = self.transport.getPeer()
+ self.factory.log.info('IPA server: connection from %s:%d client' % (addr.host, addr.port))
+ super(IPAServer, self).connectionMade()
+ self.transport.write(IPA().id_get())
+
+
+class CtrlServer(CTRL):
+ """
+ Test implementation of CTRL server
+ Demonstarte CTRL handling by overriding simpler routines from CTRL
+ """
+ def connectionMade(self):
+ """
+ Keep reconnection logic working by calling routine from CTRL
+ Send TRAP upon connection
+ Note: we can't use sendString() because of it's incompatibility with IPA interpretation of length prefix
+ """
+ addr = self.transport.getPeer()
+ self.factory.log.info('CTRL server: connection from %s:%d client' % (addr.host, addr.port))
+ super(CtrlServer, self).connectionMade()
+ self.transport.write(Ctrl().trap('LOL', 'what'))
+ self.transport.write(Ctrl().trap('rulez', 'XXX'))
+
+ def reply(self, r):
+ self.transport.write(Ctrl().add_header(r))
+
+ def ctrl_SET(self, data, op_id, v):
+ """
+ CTRL SET command: always succeed
+ """
+ self.dbg('SET [%s] %s' % (op_id, v))
+ self.reply('SET_REPLY %s %s' % (op_id, v))
+
+ def ctrl_GET(self, data, op_id, v):
+ """
+ CTRL GET command: always fail
+ """
+ self.dbg('GET [%s] %s' % (op_id, v))
+ self.reply('ERROR %s No variable found' % op_id)
+
+
+class IPAFactory(ReconnectingClientFactory):
+ """
+ Generic IPA Client Factory which can be used to store state for various subprotocols and manage connections
+ Note: so far we do not really need separate Factory for acting as a server due to protocol simplicity
+ """
+ protocol = IPACommon
+ log = None
+ ccm_id = IPA().identity(unit=b'1515/0/1', mac=b'b0:0b:fa:ce:de:ad:be:ef', utype=b'sysmoBTS', name=b'StingRay', location=b'hell', sw=IPA.version.encode('utf-8'))
+
+ def __init__(self, proto=None, log=None, ccm_id=None):
+ if proto:
+ self.protocol = proto
+ if ccm_id:
+ self.ccm_id = ccm_id
+ if log:
+ self.log = log
+ else:
+ self.log = logging.getLogger('IPAFactory')
+ self.log.setLevel(logging.CRITICAL)
+ self.log.addHandler(logging.NullHandler)
+
+ def clientConnectionFailed(self, connector, reason):
+ """
+ Only necessary for as debugging aid - if we can somehow set parent's class noisy attribute then we can omit this method
+ """
+ self.log.warning('IPAFactory connection failed: %s' % reason.getErrorMessage())
+ ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
+
+ def clientConnectionLost(self, connector, reason):
+ """
+ Only necessary for as debugging aid - if we can somehow set parent's class noisy attribute then we can omit this method
+ """
+ self.log.warning('IPAFactory connection lost: %s' % reason.getErrorMessage())
+ ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
+
+
+if __name__ == '__main__':
+ p = argparse.ArgumentParser("Twisted IPA (module v%s) app" % IPA.version)
+ p.add_argument('-v', '--version', action='version', version="%(prog)s v" + __version__)
+ p.add_argument('-p', '--port', type=int, default=4250, help="Port to use for CTRL interface")
+ p.add_argument('-d', '--host', default='localhost', help="Adress to use for CTRL interface")
+ cs = p.add_mutually_exclusive_group()
+ cs.add_argument("-c", "--client", action='store_true', help="asume client role")
+ cs.add_argument("-s", "--server", action='store_true', help="asume server role")
+ ic = p.add_mutually_exclusive_group()
+ ic.add_argument("--ipa", action='store_true', help="use IPA protocol")
+ ic.add_argument("--ctrl", action='store_true', help="use CTRL protocol")
+ args = p.parse_args()
+ test = False
+
+ log = logging.getLogger('TwistedIPA')
+ log.setLevel(logging.DEBUG)
+ log.addHandler(logging.StreamHandler(sys.stdout))
+
+ if args.ctrl:
+ if args.client:
+ # Start osmo-bsc to receive TRAP messages when osmo-bts-* connects to it
+ print('CTRL client, connecting to %s:%d' % (args.host, args.port))
+ reactor.connectTCP(args.host, args.port, IPAFactory(CTRL, log))
+ test = True
+ if args.server:
+ # Use bsc_control.py to issue set/get commands
+ print('CTRL server, listening on port %d' % args.port)
+ reactor.listenTCP(args.port, IPAFactory(CtrlServer, log))
+ test = True
+ if args.ipa:
+ if args.client:
+ # Start osmo-nitb which would initiate A-bis/IP session
+ print('IPA client, connecting to %s ports %d and %d' % (args.host, IPA.TCP_PORT_OML, IPA.TCP_PORT_RSL))
+ reactor.connectTCP(args.host, IPA.TCP_PORT_OML, IPAFactory(CCM, log))
+ reactor.connectTCP(args.host, IPA.TCP_PORT_RSL, IPAFactory(CCM, log))
+ test = True
+ if args.server:
+ # Start osmo-bts-* which would attempt to connect to us
+ print('IPA server, listening on ports %d and %d' % (IPA.TCP_PORT_OML, IPA.TCP_PORT_RSL))
+ reactor.listenTCP(IPA.TCP_PORT_RSL, IPAFactory(IPAServer, log))
+ reactor.listenTCP(IPA.TCP_PORT_OML, IPAFactory(IPAServer, log))
+ test = True
+ if test:
+ reactor.run()
+ else:
+ print("Please specify which protocol in which role you'd like to test.")