Remove unneeded .py scripts

The ipa.py has been moved to osmo-python-tests as osmo_ipa - use it for
vty and ctrl tests instead of local copy. The soap.py and twisted_ipa.py
are not MSC-specific: leftovers from repository split which are now
available in osmo-python-tests as well.

Change-Id: Ia3ab77846c9beae7eca32a81079a4a9bfa4dcc75
changes/91/4991/4
Maxim Suraev 5 years ago committed by Harald Welte
parent b8acdcdf2e
commit 60383a1f7f
  1. 1
      Makefile.am
  2. 1
      configure.ac
  3. 1
      contrib/Makefile.am
  4. 278
      contrib/ipa.py
  5. 188
      contrib/soap.py
  6. 384
      contrib/twisted_ipa.py
  7. 6
      tests/ctrl_test_runner.py
  8. 6
      tests/vty_test_runner.py

@ -13,7 +13,6 @@ SUBDIRS = \
include \
src \
tests \
contrib \
$(NULL)
BUILT_SOURCES = $(top_srcdir)/.version

@ -196,5 +196,4 @@ AC_OUTPUT(
tests/msc_vlr/Makefile
doc/Makefile
doc/examples/Makefile
contrib/Makefile
Makefile)

@ -1 +0,0 @@
EXTRA_DIST = ipa.py

@ -1,278 +0,0 @@
#!/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)

@ -1,188 +0,0 @@
#!/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()

@ -1,384 +0,0 @@
#!/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.")

@ -29,11 +29,7 @@ import struct
import osmopy.obscvty as obscvty
import osmopy.osmoutil as osmoutil
# add $top_srcdir/contrib to find ipa.py
sys.path.append(os.path.join(sys.path[0], '..', 'contrib'))
from ipa import Ctrl, IPA
from osmopy.osmo_ipa import Ctrl, IPA
# to be able to find $top_srcdir/doc/...
confpath = os.path.join(sys.path[0], '..')

@ -23,11 +23,7 @@ import subprocess
import osmopy.obscvty as obscvty
import osmopy.osmoutil as osmoutil
# add $top_srcdir/contrib to find ipa.py
sys.path.append(os.path.join(sys.path[0], '..', 'contrib'))
from ipa import IPA
from osmopy.osmo_ipa import IPA
# to be able to find $top_srcdir/doc/...
confpath = os.path.join(sys.path[0], '..')

Loading…
Cancel
Save