Merge history from osmo-gsm-manuals.git

Change-Id: I298828d47ce86c13301f5ab245934fbcf8d8d2d3
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..89bf7df
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+debian/*.log
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..ee67509
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,3 @@
+[gerrit]
+host=gerrit.osmocom.org
+project=openbsc
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..cda4057
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,12 @@
+Harald Welte <laforge@gnumonks.org>
+Harald Welte <laforge@gnumonks.org> <laflocal@hanuman.gnumonks.org>
+Harald Welte <laforge@gnumonks.org> <laflocal@goeller.de.gnumonks.org>
+Holger Hans Peter Freyther <holger@moiji-mobile.com> <zecke@selfish.org>
+Holger Hans Peter Freyther <holger@moiji-mobile.com> <ich@tamarin.(none)>
+Holger Hans Peter Freyther <holgre@moiji-mobile.com> <holger@freyther.de>
+Andreas Eversberg <jolly@eversberg.eu>
+Andreas Eversberg <jolly@eversberg.eu> <Andreas.Eversberg@versatel.de>
+Andreas Eversberg <jolly@eversberg.eu> <root@nuedel.(none)>
+Pablo Neira Ayuso <pablo@soleta.eu> <pablo@gnumonks.org>
+Max Suraev <msuraev@sysmocom.de>
+Tom Tsou <tom.tsou@ettus.com> <tom@tsou.cc>
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
new file mode 100755
index 0000000..5bab5bf
--- /dev/null
+++ b/contrib/jenkins.sh
@@ -0,0 +1,62 @@
+#!/bin/sh -ex
+
+osmo-clean-workspace.sh
+
+artifact_deps() {
+
+	x="$($1 libosmocore)"
+	x="${x}_$($1 libosmo-abis)"
+	x="${x}_$($1 libosmo-netif)"
+	x="${x}_$($1 libosmo-sccp "$sccp_branch")"
+	x="${x}_$($1 libsmpp34)"
+	x="${x}_$($1 openggsn)"
+
+	if [ "x$IU" = "x--enable-iu" ]; then
+		x="${x}_$($1 libasn1c)"
+		x="${x}_$($1 osmo-iuh "$osmo_iuh_branch")"
+	fi
+
+	echo "${x}.tar.gz"
+}
+
+build_deps() {
+
+	osmo-build-dep.sh libosmocore master ac_cv_path_DOXYGEN=false
+	verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
+	osmo-build-dep.sh libosmo-abis
+	osmo-build-dep.sh libosmo-netif
+	osmo-build-dep.sh libosmo-sccp "$sccp_branch"
+	PARALLEL_MAKE=-j1 osmo-build-dep.sh libsmpp34
+	osmo-build-dep.sh openggsn
+
+	if [ "x$IU" = "x--enable-iu" ]; then
+		osmo-build-dep.sh libasn1c
+		osmo-build-dep.sh osmo-iuh "$osmo_iuh_branch"
+	fi
+}
+
+build_project() {
+
+	cd "$base/openbsc"
+
+	autoreconf --install --force
+
+	./configure "$SMPP" "$MGCP" "$IU" \
+		--enable-osmo-bsc \
+		--enable-nat  \
+		--enable-vty-tests \
+		--enable-external-tests
+
+	"$MAKE" $PARALLEL_MAKE
+	"$MAKE" check || cat-testlogs.sh
+	"$MAKE" distcheck || cat-testlogs.sh
+}
+
+if [ "x$IU" = "x--enable-iu" ]; then
+        sccp_branch="old_sua"
+        osmo_iuh_branch="old_sua"
+fi
+
+. osmo-build.sh
+
+osmo-clean-workspace.sh
diff --git a/debian/autoreconf b/debian/autoreconf
new file mode 100644
index 0000000..9a3a67f
--- /dev/null
+++ b/debian/autoreconf
@@ -0,0 +1 @@
+openbsc
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..a157b2e
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,1096 @@
+openbsc (1.1.0) unstable; urgency=medium
+
+  [ Neels Hofmeyr ]
+  * debian/rules: show testsuite.log when tests are failing
+  * vty: skip installing cmds now always installed by default
+  * vty: skip installing cmds now always installed by default
+  * sms.db: silence libdbi warnings on out-of-range index
+  * fix build: gprs_ra_id_by_bts(): ensure to init all values
+  * backport support for 3-digit MNC with leading zeros
+  * Migrate from OpenSSL to osmo_get_rand_id()
+
+  [ Harald Welte ]
+  * osmo-bsc: Print NOTICE message on unimplemented BSSMAP UDT
+  * osmo-bsc-sccplite: Implement incoming RESET procedure
+  * mgcp_transcoding_test: Add LIBOSMOABIS_CFLAGS
+  * sysinfo: Fix regression causing missing L2 Pseudo-Length in SI5/SI6
+    (Closes: #3059)
+
+  [ Pau Espin Pedrol ]
+  * Use type bool for boolean fields in gsm48_si_ro_info
+  * vty: Add cmd to configure 3g Early Classmark Sending
+  * mgcp_protocol: Don't print osmux stats if it is off
+  * bsc: Improve handling of paging_request return value
+  * bsc: paging: Fix losing paging messages for BTS
+  * libbsc: set_net_mcc_mnc_apply: Fix memleak on parsing incorrect mcc mnc
+  * bsc_nat: ctrl: fix memleak on reply receival
+  * bsc_nat: forward_to_bsc: remove one level of indentation
+  * bsc_nat: forward_to_bsc: Fix memleak on send failure
+  * bsc_nat: Drop redundant ccon ptr in bsc_cmd_list
+  * bsc_nat: ctrl: Fix crash on receveing bsc reply
+  * nat: Add jitter buffer on the uplink receiver
+  * smpp_smsc_conf: Fix heap-use-after-free
+  * chan_alloc.c: Fix log var formatting issues
+  * mgcp: switch to new osmux output APIs
+  * debian/changelog: change last release for unreleased to unstable
+
+  [ Max ]
+  * Fix tests after rate_ctr change
+
+  [ Vadim Yanitskiy ]
+  * libmsc: add support for both comp128v2 and comp128v3
+  * gsm_04_80.h: use '#pragma once' instead of includes
+  * gsm_04_80.h: cosmetic: whitespace fix
+  * src/libmsc/ussd.c: drop useless forward declaration
+
+  [ Stefan Sperling ]
+  * Make "waiting indicator" of IMMEDIATE ASSIGN REJECT dynamic.
+  * bts chan_load: ignore unusable BTS
+  * Add stat items for the BTS's channel load average and T3122.
+  * Add support for Access Control Class ramping.
+  * fix a format string error in bts_update_t3122_chan_load()
+  * fix initialization of acc ramping
+  * only log actual access control class ramping changes
+  * ensure that acc_ramp_init() is only called once
+  * trigger acc ramping based on trx rf-locked state
+  * rename helper functions in the acc ramp code to avoid confusion
+  * trigger acc ramping on state-changed-event reports
+  * only trigger acc ramping if trx 0 is usable and unlocked
+  * fix handling of state changes in acc ramping
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de>  Fri, 04 May 2018 12:45:08 +0200
+
+openbsc (1.0.0) unstable; urgency=medium
+
+  [ Holger Hans Peter Freyther ]
+  * Revert "gprs: Use RAND_bytes for p-tmsi"
+  * sgsn: Fix pattern for too long msisdn
+  * osmux: Allow to listen to a specific address
+  * sgsn: Fix up the VTY doc strings
+  * stats/mgcp: Initialize the statistics for MGCP as well
+  * gbproxy: Count more GSM 04.08 messages
+  * gtp: Fix Makefile.am so maybe distcheck is now going to work
+  * gtphub: Fix the VTY prompt to make the tests move forward
+  * bsc/vty: Provide a hint of available input
+  * gtphub: Fix compilation using gcc5
+  * dahdi: The driver has moved to libosmo-abis
+  * gtphub: Make the two setter static as well
+  * db: Avoid undefined behavior when copying cm2/cm3 from the db
+  * gtphub: Fix use after free on failure
+  * gsm0408: Provide unique strings for the gsm 04.08 message
+  * msc: Remove oversimplified todo entry and add a comment
+  * meas: Do not retry to close the database
+  * mgcp: Fix compiler warnings on size_t on AMD64
+  * abis: Send the message without enforcing to wait for a response
+  * bsc: Add code to send ip.access reboot command to nanoBTS
+  * bsc: Add parameter to restart a bts
+  * subscr: Add testcase creating an already created subscriber
+  * subscr: Make db_create_subscriber fail on duplicates
+  * db: If creating a subscriber in the db fails, return NULL
+  * ctrl: Extend ctrl command to optionally handle alg+ki
+  * jenkins: Add the build script from jenkins here
+  * nat/vty: Don't assume one can magically add IPv4 addresses to lo
+  * nat/vty: Use different port for the mock MSC
+  * nat/vty: Convert into str for the VTY command
+  * nat/vty: And move to a different port..
+  * nat/vty: Remove second assumption about lo and binding
+  * nat/vty: Fix construct not working with python 2.6
+  * nat/vty: Do not print token update statement
+  * nat/ussd: Add an example of the USSD gateway side-channel
+  * debian: Make upgrading from debian SID easier
+  * filter/nat: Fix the context for the imsi assignment
+  * bsc: Create minimal SI6 rest octets
+  * ci: Attempt to disable doxygen warnings of dependencies
+  * lchan: Release channel in case of late activation ack
+  * ussd: Add band-aid for interrogationSS
+  * debian: Install header and source file to different directory
+  * debian: Require libgtp-dev after the SO version bump
+  * sgsn: Fix deeply flawed copying logic for PDP context activation
+  * sgsn: Fill the cch_pdp with a value coming from the tlv structure
+  * sgsn: Convert cch_pdp to host order for libgtp
+
+  [ Jacob Erlbeck ]
+  * sgsn/test: Add and call cleanup_test function
+  * sgsn/test: Really parse received DL LLC messages
+  * stats: Enable stats for sgsn, gbproxy, nitb, bsc, nat
+  * stats: Set class_id in rate_ctr group descriptions
+  * sgsn: Remove tlli_foreign2local
+  * sgsn/test: Add bssgp_raid parameter to send_0408_message
+  * sgsn: Make ra_id_equals available as gprs_ra_id_equals
+  * sgsn: Only look at TLLIs in sgsn_mm_ctx_by_tlli
+  * sgsn: Change handling of missing mmctx in gsm48_rx_gmm_ra_upd_req
+  * sgsn/test: Add test case test_gmm_routing_areas
+  * sgsn: Re-add searching for MM ctx based on TLLI / P-TMSI matches
+
+  [ Daniel Willmann ]
+  * gprs: Use RAND_bytes for p-tmsi
+  * gbproxy/test: Add and call cleanup_test function
+  * gprs/gb_proxy: Use RAND_bytes for gbproxy TLLI/TMSI allocation
+  * rename enum gprs_mm_state to gprs_gmm_state
+  * rename enum gprs_mm_state to gprs_gmm_state
+  * create_pdp_conf(): factor out PDP context accept dispatch as send_act_pdp_cont_acc()
+  * sgsn_mm_ctx_cleanup_free(): clean up LLME iff present (Gb, not Iu)
+  * gprs: more conditionals for Gb specific actions
+  * configure.ac: add --enable-iu with deps asn1c, ranap, sigtran
+  * add DRANAP debug constant
+  * add DSUA debug constant
+  * osmux: Add negotiation state so race conditions can't disable osmux
+  * gprs_gmm: Fix bit mask when determining update/attach type
+  * IuPS: track msg->dst aka ue_conn_ctx, comment
+  * IuPS: osmo-sgsn: add core IuPS impl, call iu_init()
+  * IuPS: redirect Iu in various places, link Iu in sgsn-test
+  * IuPS: add Iu response to create_pdp_conf()
+  * IuPS: add Iu response to delete_pdp_conf()
+  * IuPS: dev hack: init hardcoded Ki on ATT REQ
+  * IuPS: send Security Mode Command, track the new_key flag.
+  * IuPS: add GMM Service Request rx and tx
+  * IuPS: RA UPD: make sure to authorize, for Iu Integrity Protection
+  * IuPS: sgsn_mm_ctx: add enum gprs_pmm_state field, track PMM state
+  * IuPS: GMM Attach: reset MM ctx pending_req
+  * IuPS: Introduce function to change PMM state
+  * IuPS: Change GTP-U endpoint to SGSN in PMM_IDLE and page UE when data arrives
+  * gbproxy: Check whether gbproxy_update_link_state_after() deletes the link_info
+  * test/gbproxy: Test for possible memory corruption when link_info is freed
+
+  [ Harald Welte ]
+  * SGSN: Fix typo in VTY license statement.
+  * meas_db.c: fix compiler warnings
+  * Depend on libgtp >= 0.92, as previous versions don't have gtpie.h installed
+  * oap and gtphub tests must only be compiled of LIBGTP is present!
+  * Fix TSC/BSIC handling bug and remove bts->tsc
+  * vty: Print NCC/BCC and not just integer value of BSIC
+  * Fix compilation with no libc-ares present on the system
+  * tests/smpp: Fix linking order
+  * gsm_data_shared: compute/sprintf the lchan name only once
+  * remove old obsolete linux kernel and wireshark patches
+  * AUTHORS: Add Jacob and Neels
+  * bring the README into the 21st century
+  * mncc.c: Convert mncc_names[] to 'struct value_string'
+  * mncc: introduce 'struct gsm_mncc_bridge' for MNCC_BRIDGE
+  * indicate the GSM 04.08 channel mode in 'show lchan'
+  * add DOT graph showing NITB data structures and their references
+  * rename ipaccess-find into abisip-find
+  * Revert "move to hex TMSI representation"
+  * Start to use struct osmo_auth_vector from gsm_auth_tuple
+  * move gsm_04_08_gprs.h to libosmocore
+  * use new libosmocore gsm_23_003.h for IMEI/IMSI length
+  * Rename gprs_gsup_* to osmo_gsup_*
+  * rename gprs_shift_*() to osmo_shift_*()
+  * move osmo_shift_* / osmo_match_shift_* to libosmogsm
+  * gsup_messages: Add UMTS AKA related encoding/decoding support
+  * osmo_gsup_messge.[ch] documentation update (doxygen)
+  * move utils.h functions to libosmocore
+  * Move osmo_gsup_messages.[ch] to libosmocore
+  * oap_message.h: Remove dependency to openbsc include
+  * OAP: use osmo_oap_ prefix for OAP, rather than plain oap_
+  * OAP: Various coding style fixes
+  * osmo_oap_decode(): Use common argument ordering
+  * sgsn/GSUP: Support MAP-style nested LU/ISD
+  * Add human-readable name of  SGSN_AUTH_AUTHENTICATE
+  * sgsn_test: Adapt test case to now-existing InsertSubscriberData
+  * subscr_name(): Handle case for subscr == NULL
+  * rtp_proxy.c: Ensure msgb_alloc is large enough for largest AMR frame
+  * prepare sgsn_mm_ctx for Gb and Iu mode (UMTS)
+  * rename gsm0408_gprs_rcvmsg() to gsm0408_gprs_rcvmsg_gb()
+  * gprs_gmm.c: Make TLLI handling specific to Gb interface
+  * gprs_gmm.c: Perform LLME operations only if we have one
+  * remove old copy of documentation that now is in osmo-gsm-manuals.git
+  * add example config for sysmobts
+  * add .mailmap file for mapping git author name/mail in shortlog
+  * osmo-nitb: generate backtrace on SIGABRT
+  * COSMETIC: 'if' is not a function, so there is space before '('
+  * mncc_builtin: Properly reject DTMF
+  * WIP: OM2000: Full state machine implementation using osmo_fsm
+  * OM2000: Fix state machien for CF/TRXC on START Result
+  * SGSN: Use dummy all-zero MSISDN value in PDP Context Act on GTP
+  * RBS2000: re-establish any lost signalling links
+  * talloc_cxt: Fix compiler warning / missing #include
+  * bs11_config: remove compiler waring about unused variable
+  * RBS2000: Avoid segfault if ts->lapd instance doesn't exist
+  * RBS2000: Ensure the is-connection-list command is only used on RBS2000
+  * Support configuration of CON MO Groups/Paths from VTY
+  * OM2000: CON MO: Allow larger range for CCP and CI values
+  * OM2000: Add three IEs to TCH activation about which we have no clue
+  * OM2000: Fix missing dynamic TCH initialization
+  * abis_nm: Fix possible not-null-terminated buffer
+  * abis_nm: Fix non-null terminated buffer
+  * libmsc/db: avoid subscr->name without terminating NULL char
+  * Fix possible non-null-terminated buffer
+  * ipaccess-config: Handle setsockopt return value
+  * ipaccess-proxy: Check setsockopt() return value
+  * abis_nm: ceck fseek() return code in is_last_line()
+  * bsc_msc.c: Check setsockopt() return value
+  * sgsn_test: Fix missing = in == type check
+  * abisip-find: check bsc_fd_register() result
+  * bsc_ctrl: Ensure we don't pass NULL string into strtok_r()
+  * mgcp_protocol: Ensure we don't call strtok_r with NULL data
+  * cfg_bts_si2quater_neigh_add(): Don't call strerror() on negative value
+  * gsm0408_test.c: Don't pass negative value to strerror()
+  * channel_mode_from_lchan(): Add missing break statement
+  * add gsup_test_client program
+  * move OAP messages implementations to libosmocore
+  * bsc_vty: Fix missing break statements in switch()
+  * sgsn: Add GTP information to "show pdp-context"
+  * remove unused struct members of 'struct sgsn_pdp_ctx'
+  * Implement VTY configuration to control Early Classmark Sending
+  * OM2000: use assoc_so *only* for TS objects
+  * VTY: Print 3G auth tuples, not just 2G auth tuples
+  * OM2000: Add FIXME comments for missing resolving of RX/TX MO!
+  * OM2000: Change the order of MO initialization
+  * OM2000: Send ALTCRQ for SuperChannel after receiving IS Enable Req Ack
+  * RBS2000: Add the P-GSL Timer IE to RSL CHAN ACT for PDCH
+  * RBS2000 RSL: Support for sending RSL PAGING CMD for GPRS
+  * pcu_sock: Don't re-implement core functionality like gsm_bts_trx_num()
+  * pcu_sock: get rid of magic numbers and use ARRAY_SIZE() for array iteration
+  * pcu_sock: Forward paging request from PCU via RSL to BTS
+  * pcu_sock: Send non-NULL hLayer1 to PCU
+  * Use new e1inp_signal_names from libosmo-abis to print input signal names
+  * costmetic: Document gsm48_multirate_config() + const-ify input
+  * bsc_api.c: Documentation for handle_mr_config()
+  * bsc_vty: Factor vty_get_ts() out of pdch_act_cmd()
+  * bsc_vty.c: Further simplify vty_get_ts()
+  * bsc_vty.c: Add command for manual [de]actiovation of logical channels
+  * bsc_vty: Add command to manually issue IPAC MDCX
+  * libbsc: Create pcu-socket only as specified in config file
+  * don't re-implement osmo_talloc_replace_string()
+  * Add vty command "radio-link-timeout infinite" for uplink rx testing
+  * jenkins.sh: Proper error message if local environment isn't set up
+  * Support for TS 04.14 conformance test commands
+  * Add VTY commands for experimentation with TS 04.14 commands
+  * bsc_api: Fix copy+paste error in printing name of RR STATUS PDU
+  * libbsc: Add VTY command to re-send the SYSTEM INFORMATION to BTS
+  * check for missing result of rate_ctr_group_alloc()
+  * Fix regression causing loss of static system-information messages
+    (Closes: #2367)
+  * RSL: Allow disabling of BCCH/SACCH filling for given SI type
+  * gsm_bts_trx_set_system_infos(): Disable non-existing SI
+  * gtphob: check for missing result of rate_ctr_group_alloc()
+  * bsc_vty: Add VTY command to test CTRL TRAP feature
+  * GSM timers: User reasonable defaults; don't save if equal default
+  * bsc_vty: Don't allow timers of zero (0)
+  * remove code disabling T3109 if configured to 0
+  * SGSN: Fix RAN information exposed on GTP during PDP CTX CREATE
+  * Fix gsm_pchan2chan_nr() to use RSL_CHAN_OSMO_PDCH
+  * sgsn_vty: Don't assume pdp->lib is always valid
+  * Migrate from gprs_apn_to_str() to libosmocore osmo_apn_to_str()
+  * increase libsmpp34 version requirement to 1.12
+  * NITB: remove 'help' output about '-a' option that is removed for ages
+  * Treat SIGTERM just like SIGINT in our programs
+  * Use verify_value_string_arrays_are_terminated from osmo-ci
+  * libbsc: document arguments of generate_bcch_chan_list()
+  * Make sure BA-IND in all SI2xxx is '0' and in all SI5xxx is '1'
+  * gsm0408_test: Verify that BA-IND is 0 in SI2xxx and 1 in SI5xxx
+  * Fix nanobts_omlattr unit test
+  * nanobts_omlattra_test: Initialize logging before executing tests
+  * osmo-bsc: Initialize logging before initializing rate_ctr
+  * remove sgsn, gbproxy and gtphub from openbsc.git
+  * rename osmo-bsc to osmo-bsc-sccplite
+  * Bump version: 0.15.1 → 1.0.0
+
+  [ Neels Hofmeyr ]
+  * libcommon: soak up three static functions.
+  * oap: implement initial OAP API.
+  * oap: add oap testsuite skeleton
+  * oap: add OAP API test
+  * gsup/oap: add OAP to GSUP client.
+  * oap: add OAP config to VTY
+  * sgsn_vty.c: fix typo
+  * Add GTP hub initial code base.
+  * gtphub: add first bits of GRX ares
+  * gtphub: fix Echo behavior: respond directly.
+  * gtphub: add/fix IMSI and APN IE error handling
+  * gtphub: make test code reusable for future tests.
+  * gtphub: fix User plane decoding, add unit test.
+  * gtphub: fix handling of sender from nonstandard port.
+  * gtphub: Add logging for ares queries.
+  * gtphub: fix numerous segfaults, and other stupidities.
+  * gtphub: don't always try to do GGSN resolution.
+  * gtphub: add logging labels to bind struct.
+  * gtphub: review log levels, add level arg to LOG().
+  * gtphub: cosmetic: break long lines.
+  * fix build: remove obsolete gtphub EXTRA_DIST.
+  * gtphub: fix gtphub_read() semantics.
+  * gtphub: fix fatal log msg for SGSN proxy.
+  * gtphub: fix three oversights (thanks to coverity).
+  * gtphub: fix echo reply to SGSNs: wrong fd. Add test.
+  * gtphub: lose obsolete comment.
+  * gtphub: fix ares segfault: add missing initialization.
+  * gtphub: add enable_gtphub_test to atlocal.in.
+  * gtphub: remove another obsolete comment.
+  * gtphub_test: build only when c-ares and gtp are present.
+  * gtphub: add to debian build
+  * gtphub: add to osmoappdesc.py
+  * gtphub: fix number map range for TEIs.
+  * gtphub: nr_map: add min,max and wrap.
+  * gtphub: cosmetic: rename a file.
+  * gtphub: review some logging.
+  * gtphub: ares vty and init
+  * gtphub: ensure cleanup of peer addresses.
+  * gtphub: add explicit cleanup handles.
+  * gtphub: cosmetic: clarify bind pointer naming.
+  * gtphub: add first rate counters
+  * gtphub: first vty show commands.
+  * gtphub: cosmetic: fix an argument name.
+  * gtphub: move timestamp into packet struct.
+  * gtphub: cosmetic/prepare: add nr_map_refresh().
+  * gtphub: add assertion to ensure expiry ordering.
+  * gtphub: cosmetic/prepare: rename expiry queues.
+  * gtphub: track tunnels explicitly.
+  * gtphub: avoid segfault for incomplete tunnels.
+  * gtphub: fix a conditional for log output
+  * gtphub: complain about excess cmdline args.
+  * gtphub: implement restart counter properly.
+  * gtphub: cosmetic: for_each_side,_plane macros.
+  * gtphub: use a single TEI pool across planes.
+  * gtphub: make sure mapped TEIs aren't occupied (incomplete).
+  * gtphub: refactor: use side_idx everywhere.
+  * gtphub: vty: add missing SGSN-proxy output.
+  * gtphub: add two stubs for Delete PDP msgs
+  * gtphub: fix some style complaints from cppcheck
+  * gtphub: be more fatal about not finding an unused TEI.
+  * gtphub: fix segfault when empty config.
+  * gtphub: remove obsolete todo comment
+  * gtphub: handle Delete PDP Context.
+  * gtphub_test: add some cleanup asserts.
+  * gtphub_test: add test for Del PDP from GGSN side.
+  * gtphub: cosmetic
+  * gtphub: monitor GSNs' restart counters.
+  * gtphub: cosmetic: early continue for less indent
+  * gtphub: fix: add a missing NULL check
+  * gtphub: add test for reused TEI.
+  * gtphub: Add a debug log
+  * gtphub: add restart counter test
+  * gtphub: fix use after free.
+  * gtphub: wrap gtphub_write() for test suite.
+  * gtphub: fix restart cleanup peer matching.
+  * gtphub: cosmetic
+  * gtphub: improve logging for invalid packet
+  * gtphub: check TEI presence in Create PDP Ctx Response
+  * tests: fix condition to run sgsn, oap, gtphub tests
+  * gtphub: Del PDP: replace unnecessary lookup with asserts.
+  * gtphub: tweak an error log message
+  * gtphub: prepare: keep tunnel ref in gtp_packet_desc.
+  * gtphub: implement sgsn_use_sender for NAT.
+  * gtphub_test: tweak test_user_data(): no seq routing.
+  * gtphub: add test for SGSN behind NAT
+  * gtphub: tweak startup log for sgsn_use_sender
+  * gtphub: add gtphub-example.txt
+  * gtphub: enhance gtphub-example.txt
+  * gtphub: fix missing newline in log output
+  * gtphub: logging: have one newline per log.
+  * gtphub: tweak logging.
+  * gtphub: be strict about unknown cmdline args
+  * gtphub: add more detailed I/O rate counters.
+  * gtphub: log: add side str to msg for reused TEI
+  * gtphub_test: add test_parallel_context_creation()
+  * gtphub: log: add TEI reuse msg, fix another TEI msg.
+  * gtphub: fix: when checking TEIs, skip 0.
+  * gtphub: simplify/fix: one TEI mapping per tunnel.
+  * gtphub: log most common message type names.
+  * gtphub: improve handling of restarted peer.
+  * gtphub: add VTY show for peers and peer stats.
+  * gtphub: log: limit length of hex dumps.
+  * osmoappdesc.py: fix wrong index introduced by gtphub.
+  * gtphub VTY test: use only 127.0.0.1.
+  * gtphub VTY: fix doc strings for show cmds
+  * gtphub VTY: add newlines to some VTY docs' final lines
+  * fix bsc_vty out: timeslot indented too deeply.
+  * remove src/libgb/Makefile.am
+  * Add README.vty-tests
+  * openbsc/README: some fixes, add CSCN and Iu*
+  * gtphub: include ports.h instead of redefining OSMO_VTY_PORT_GTPHUB
+  * gtphub: tweak default logging level
+  * osmo-bsc: fix checking wrong pointer for ctrl setup success
+  * minor fixes in bsc_vty.c and bsc_nat.c
+  * bsc_nat: fail if VTY telnet port cannot be bound, clarify comment
+  * ipaccess_rcvmsg: fix returncode, add partial write warning
+  * gsm340_rx_tpdu: comment-out two unused vars
+  * enable telnet VTY bind address config for various programs
+  * osmo-nitb: add -M to pass specific MNCC socket path
+  * osmo-nitb: cosmetic: rename to rf_ctrl_path, following mncc_sock_path
+  * osmo-nitb: be strict about cmdline args
+  * enable ctrl bind config for various programs
+  * smpp: refactor initialization, add bind address
+  * bsc_test.c: fix tz.override val and note a FIXME
+  * osmo_bsc_filter.c: add fixme note
+  * minor comment
+  * use sqlite3_close() instead of sqlite3_close_v2()
+  * check return value of sqlite3_close(), retry up to three times.
+  * db: evaluate return value of sqlite3_finalize(), tweak log output.
+  * channel_test: don't segfault if paging fails
+  * 04.08: apply new bitmask functions, fix bitmask use
+  * 04.08: apply new transaction id inline functions
+  * bsc_scan_msc_msg: check protocol discriminator
+  * fix confusing typo in constant (THAN -> THEN)
+  * osmo-bsc: fix compiler warning: store struct in vty->index
+  * Add MM Auth test; add auth_action_str() function
+  * MM Auth test: add two tests for AUTH_THEN_CIPH
+  * MM Auth test: add test to re-use existing auth
+  * MM Auth: introduce AUTH_ERROR constant.
+  * MM Auth: return AUTH_NOT_AVAIL instead of hardcoded zero
+  * Fix MM Auth: disallow key_seq mismatch
+  * Fix MM Auth: zero-initialize auth tuple before first use
+  * gtphub_unmap_header_tei(): don't dereference unmapped_tei arg if not present (CID #57687)
+  * bsc_nat: forward_sccp_to_msc(): assert con presence (CID #57872)
+  * gbproxy_test: assert msg allocation (CID #57873)
+  * drop unneccessary duplicate linking: osmo-bsc
+  * drop unneccessary duplicate/unused linking: ipaccess
+  * drop unneccessary duplicate linking: osmo-nitb
+  * drop unneccessary duplicate linking: tests/gsm0408
+  * gsm04_08_clear_request(): release loc with arg release=0
+  * tweak db debug log: log TMSI as hex
+  * gprs_gmm.c: Don't try to de-reference NULL mmctx
+  * comment tweak for bsc_handover_start()
+  * debug log: cosmetic fixes
+  * gprs_gmm.c: don't transmit NOTEXIST when mmctx is NULL
+  * lchan_alloc(): on alloc failure, report original type
+  * dyn PDCH: allow allocating TCH/F on TCH/F_PDCH slots
+  * dyn PDCH: send PDCH ACT for each TCH/F_PDCH on TS Enable
+  * dyn PDCH: TS flags: rename one, add three, as enum
+  * dyn PDCH: fix: clear PDCH flags on TS reconnect (e.g. BTS re-connect)
+  * dyn PDCH: track pending PDCH de-/activation
+  * dyn PDCH: add lchan sanity checks in PDCH DE/ACT ACK
+  * dyn PDCH: set lchan->state after PDCH DEACT / before PDCH ACT
+  * bsc_version.c: update copyright date, add contributor
+  * configure: require libgsm for --enable-mgcp-transcoding
+  * rm dup: use channel type names from libosmocore
+  * typo in sgsn_test
+  * dyn PDCH: enable PDCH only after release due to error
+  * vty: show lchan summary: also show lchan->state
+  * debug log: log all lchan state transitions
+  * dyn PDCH: cosmetic: clarify lchan rel with assertion and comment
+  * err log: tweak dyn pdch ack error logging
+  * jenkins.sh: remove code dup
+  * jenkins.sh: add --enable-iu matrix build
+  * cosmetic: dyn_pdch_init(): flatten if-logic, add comments
+  * dyn pdch: don't PDCH ACT if gprs mode is none
+  * fix ctrl test: dyn TS: use new GSM_PCHAN_TCH_F_TCH_H_PDCH
+  * fix: create_pdp_conf(): unset reject_cause after unknown ran_type
+  * comments: clarify some dynamic TS comments
+  * cosmetic: rsl_rx_rf_chan_rel_ack(): use local ts var for brevity
+  * cosmetic: act lchan type: use constant instead of 0x00
+  * cosmetic: rsl_rx_chan_act_ack(): use local lchan var in 14 instances
+  * dyn TS: add ts->dyn state
+  * gsm_data_shared: add gsm_ts_and_pchan_name() for dyn ts logging
+  * code dup: join [rsl_]lchan_lookup() from libbsc and osmo-bts
+  * error log: abis_rsl.c: log errors in channel_mode_from_lchan()
+  * log lchan_alloc() result
+  * debug log: fix line endings for abis_rsl_rx_rll logging
+  * gsm_ts2chan_nr(): add assertions for lchan_nr
+  * cosmetic: dyn_pdch_init(): debug log: use new gsm_ts_and_pchan_name()
+  * error log: rsl_chan_activate_lchan: log channel mode error
+  * prepare dyn TS: act lchan: fetch the channel mode a bit later
+  * dyn TS: rename lchan->dyn_pdch to lchan->dyn
+  * dyn TS: gsm_lchan2chan_nr(): decouple from ts->pchan
+  * dyn TS: rsl *2chan_nr(): handle TCH/F_TCH/H_PDCH
+  * dyn TS: verify_chan_comb(): handle new dyn TS NM_CHANC_*
+  * dyn TS: rsl_lchan_lookup(): add dyn PCHAN
+  * dyn TS: enhance channel allocator for dynamic TS
+  * dyn TS: chan act: set chan_nr according to dyn pchan type
+  * dyn TS: implement pchan switchover logic
+  * dyn TS: split dyn_pdch_init() for new dyn type and rename
+  * dyn TS: Rename bsc_dyn_pdch.c to bsc_dyn_ts.c
+  * dyn TS: OS#1778 workaround: disable TCH/F on dyn TS for nitb
+  * gsm_pchan2chan_nr: disable a chan_nr assert in BTS, to not break octphy
+  * gsm_pchan2chan_nr(): fix uninitialized cbits
+  * comment: gsm48_gmm_sendmsg(): add spec reference on encryptable
+  * dyn TS: bts_chan_load: use correct nr of subslots for dyn ts
+  * chan_alloc.c: use ts_subslots() instead of subslots_per_pchan[]
+  * move ts_sublots() to gsm_data_shared.c, it will be used by osmo-bts
+  * dyn TS: move check whether to switch to PDCH to separate function
+  * dyn TS: fix error recovery: switch to PDCH after lchan error state
+  * dyn TS: clearly use lchan[0], fixing minor confusion
+  * dyn TS: fix: properly run an lchan activation timeout
+  * dyn TS: fix OS#1798: on late RF CHAN REL ACK, activate PDCH
+  * dyn TS: debug log 'switchover complete' only when there was a switchover
+  * dyn TS: debug log: if still in use, also log lchan type and state
+  * log: improve for rsl_lchan_mark_broken()
+  * log: rsl notice: tiny tweak for readability
+  * add libiu
+  * cosmetic: gprs_sgsn.c: move pdp.h include to top
+  * IuPS: add VTY config for asn_debug
+  * log: abis_rsl: don't log 'error' when there is no error
+  * log causing rx event for lchan_lookup errors
+  * properly #include <openbsc/gsm_data.h> from gsm_subscriber.h
+  * remove unused bsc_copyright from bsc_vty.c
+  * vty l3 help: fix typo 'comamnds'; fix english s/his//
+  * utils/Makefile.am: remove unused LIBOSMOVTY_CFLAGS
+  * cosmetic: various comment, whitespace tweaks
+  * Sanity fixes for gsm0408_dispatch(): rc, assertions
+  * debug log for sms: fix/add
+  * cosmetic: transaction.h: 1 comment typo, 1 whitespace
+  * cosmetic fixes in libcommon/talloc_ctx.c
+  * mscsplit: bsc_init: don't pass telnet dummy conn
+  * mscsplit: move subscriber conns list into struct gsm_network
+  * mscsplit: gsm_network_init(): add explicit root talloc ctx
+  * mscsplit: talloc_ctx_init(): decouple from global tall_bsc_ctx
+  * mscsplit: bsc_vty_init(): decouple from global bsc_gsmnet
+  * mscsplit: abis vty: decouple from global bsc_gsmnet variable
+  * mscsplit: add gsm_network backpointer to gsm_subscriber_connection
+  * mscsplit: directly access gsm_network backpointer from gsm_subscriber_connection
+  * cosmetic: vty_test_runner.py: add comment for vim auto settings
+  * vty_test_runner.py: raise exception when MSC socket connection fails
+  * log VTY telnet bind only once
+  * cosmetic: comment typo on e1_config.c
+  * channel test: prepare to add another test function
+  * channel_test: test nr of subslots for dyn pchan, with error
+  * Revert "bts: extend bts_chan_load to allow counting tch only"
+  * dyn TS: fix: ts_subslots() for TCH/F_PDCH in PDCH mode
+  * dyn TS: fix: abis_om2000: also handle dyn TS as TCH
+  * dyn TS: fix: e1_config.c: switch(pchan) for dyn TS
+  * cosmetic: bs11: also use ts_is_tch()
+  * vty_test_runner.py: fix nat_msc_test(): socket attach: reduce timeout, retry
+  * fix: send SNDCP XID only on GERAN Gb contexts
+  * log CTRL bind only once
+  * vty_test_runner.py: make unittest print all output by default
+  * vty_test_runner.py: fix indents to use spaces, fix vim comment
+  * build: be robust against install-sh files above the root dir
+  * configure: check for pkg-config presence
+  * Revert "bsc: count the usage of codec by setting the lchan active"
+  * abis_rsl_rx_dchan(): guard against lchan_lookup() returning NULL
+  * gsm_trx_name(): don't break if trx is NULL
+  * jenkins.sh: use osmo-build-dep.sh, log test failures
+  * ipaccess-config: initialize root talloc ctx with name
+  * bs11_config: initialize bs11 tall ctx, use instead of bsc ctx
+  * meas_pcap2db.c: remove unused include of msgb.h
+  * gtphub_test.c: remove unused include of msgb.h
+  * msgb talloc ctx: initialize in all main() scopes
+  * msgb ctx: use new msgb_talloc_ctx_init(), don't access talloc_msgb_ctx
+  * info log: iu: add line break to and tweak rx RAB Ass Resp log
+  * log: count_codecs(): drop logging of non-TCH lchan types
+  * bsc_vty: include dyn TS info in vty show lchan
+  * fix use after free in bsc_config_free
+  * OM2000: for TS conf of dyn TS, always send TCH/F chan comb
+  * OM2000: disallow ip.access style TCH/F_PDCH pchan type
+  * Add empty libcommon-cs
+  * define mncc_recv_cb_t to avoid code dup
+  * move to libcommon-cs: net init 1: rename to bsc_network_init
+  * move to libcommon-cs: net init 2: move bsc_network_init decl to new .h
+  * move to libcommon-cs: net init 3: actual move
+  * move to libcommon-cs: gsm48_create_mm_serv_rej(), gsm48_create_loc_upd_rej()
+  * IuPS: properly update ra_id on GMM Attach Request
+  * factor out gen of USSD notify and release complete to libosmocore
+  * factor out & introduce struct gsm_encr, in common_cs.h
+  * sms_next_rp_msg_ref(): use direct pointer to next_rp_ref counter
+  * tests: drop unused libmsc, unneeded duplicate libbsc linking
+  * split bsc_bootstrap_network() in alloc and config
+  * global gsm_network: move allocation further up
+  * move to libcommon-cs: global vty gsm_network pointer
+  * move to libcommon-cs: network VTY that isn't BSC-specific
+  * bsc vty: rename show_net_cmd to bsc_show_net_cmd
+  * reinvent connection_for_subscr() and move to libmsc
+  * Move timezone settings up to network level
+  * move to libcommon-cs: net timezone VTY config
+  * split subscr_con_allocate()/_free() in bsc_ and msc_
+  * osmo-nitb: exit when MNCC socket init failed
+  * gsm_subscriber_connection: mark BSC specific items
+  * abis_om2k: fix typo that declared non-existent struct gsm_bts_trx_s
+  * lchan release in error state: SACCH deact only for SACCH pchans
+  * cosmetic: chan_alloc: use switch instead of if-cascade
+  * Fix TCH/F_PDCH: no need to check ts subslots for PDCH
+  * Revert "Support Deactivate PDP Context Request from network"
+  * build: bump required libosmocore, libosmogsm to 0.9.4, for GSUP
+  * comments: gsup client: rename to Generic, adjust copyright and authors
+  * gprs_gsup_client*: remove the gprs_ prefix
+  * rename gprs_gsup_client.h to gsup_client.h
+  * gsup client, gsup_test_client: move logging to DLGSUP category
+  * oap: rename public API from oap_ to oap_client_
+  * rename oap.h to oap_client.h
+  * move gprs/oap.c to libcommon/oap_client.c
+  * move grps_gsup_client.c to libcommon/gsup_client.c
+  * oap_test.c: rename to avoid clash with libosmocore oap_test.c
+  * oap_client_test: print test descr to stderr, check stderr
+  * oap_client: move logging to DLOAP logging category
+  * oap_client_test: show bug: disabled state does not reject message
+  * oap_client: reject all messages in disabled/uninitialized state
+  * oap_client: make use of OAP optional: disable for NULL config
+  * fix: missing terminator in two value_string arrays
+  * gtphub: fix possible NULL deref: don't print NULL tunnel
+  * build: osmo-nitb: fix missing LIBCRYPTO_FLAGS
+  * build: remove obsolete $LIBCRYPT in two places
+  * gitignore: gsup_test_client binary
+  * use new OSMO_VALUE_STRING
+  * fix 'osmo-nitb --version' segfault
+  * Revert "Turn some warnings into errors"
+  * gprs subscr: fix: intended strcmp(), but is strcpy()
+  * undup: gtphub_test: use libosmocore's llist_count()
+  * fix strncpy() invocation in vty_interface_layer3.c and 3 tests
+  * cosmetic: use osmo_strlcpy() everywhere
+  * various comment / whitespace tweaks (libmsc, gprs, libcommon-cs)
+  * osmo_bsc_grace.h: use '<>' include, not '""'
+  * paging.h: use '<>' include, not '""'
+  * compiler warning: bsc_vty: remove two unused vars
+  * gsm0408_rcv_cc: guard against NULL subscriber
+  * vty tests: attempt to get at sporadic 'Broken Pipe' error
+  * vty tests: more attempts to resolve sporadic 'Broken Pipe' error
+  * vty tests: testBSCreload: ipa_handle_small: ensure rx of 4 bytes
+  * gsm48_tx_mm_auth_req(): support UMTS AUTN
+  * Revert "vty tests: more attempts to resolve sporadic 'Broken Pipe' error"
+  * gsm_04_08: implement parsing of UMTS Auth responses
+  * vty tests: more attempts to resolve 'Broken Pipe' error
+  * vty tests: more attempts to resolve 'Broken Pipe' error (3)
+  * vty tests: more attempts to resolve 'Broken Pipe' error (2)
+  * remove compiler warning: unused rc in vty_interface_layer3
+  * subscr_update_expire_lu(): fix (obscure) segfault
+  * logging: use central filter and ctx consts from libosmocore
+  * debug.h/c: remove unused cruft / cosmetic tweaks
+  * logging fixup: shorter names for LOGGING_FILTER_* and LOGGING_CTX_*
+  * cosmetic: remove unused scall_signal_data.subscr
+  * vty_test_runner.py: fix socket leak
+  * vty: fix subscr ref count leak in 'subscriber name' cmd
+  * cosmetic: gsm_data.h, README: rename CSCN to MSC
+  * cosmetic: clarify BSC's remote MSC data vs. OsmoMSC
+  * cosmetic: rename osmo_msc_data.h to bsc_msc_data.h
+  * cosmetic: rename struct osmo_msc_data to bsc_msc_data
+  * add struct gprs_subscr, separating gprs from gsm_subscriber
+  * vty_rest_runner.py: remove debug monitoring for TCP sockets
+  * python tests: remove process 'Launch' message, now at osmoutil
+  * smpp_test_runner.py: fix socket leak
+  * SGSN: Integrate support for UMTS AKA
+  * SGSN VTY: make missing GSUP server address+port fatal
+  * vty test: nat_msc_test: setsockopt REUSE to avoid TIME_WAIT problems
+  * ctrl_test_runner: speed up more than 10 fold by sleeping less
+  * fix: gprs_gmm, gprs_llc_vty: two unterminated value_string arrays
+  * subscriber conn: add indicator for originating RAN
+  * vty tests: allow picking specific tests to run by name
+  * vty tests: close msc socket after nat_msc_test
+  * add struct bsc_subscr, separating libbsc from gsm_subscriber
+  * python tests: vty and smpp: speed up >10 times
+  * oap tests: fix after SQN scheme changes from libosmocore
+  * jenkins: add value_string termination check
+  * bsc_/gprs_subscriber: fix: use osmo_strlcpy() to safely copy IMSI
+  * python tests: allow running from separate build dir
+  * LU counters: count completion and failure, not messages sent
+  * jenkins.sh: Iu: use libosmo-sccp,-netif master
+  * build: iu: use libosmo-sccp tag 'old_sua'
+  * fix VTY parsing: subscriber-create-on-demand random
+  * fix subscriber random extension allocation range
+  * fix '/include/openbsc ' to have no trailing space
+  * ipaccess-config: properly create swload
+  * jenkins: fix build of --enable-iu: use osmo-iuh tag 'old_sua'
+  * cosmetic: vty for timers: remove obsolete range check
+  * vty: add 'default' keyword to timer config
+  * timer vty: also print the default value in cmd doc
+  * Revert "factor out & introduce struct gsm_encr, in common_cs.h"
+  * fix vty tests: vty no longer goes to parent node implicitly
+  * jenkins: use osmo-clean-workspace.sh before and after build
+
+  [ Pablo Neira Ayuso ]
+  * mgcp_osmux: available circuit IDs from 0 to 255, not from 0 to 128
+  * libmsc: use GSM411_RP_CAUSE_MO_NUM_UNASSIGNED as return value
+  * libmsc: send RP-ACK to MS after ESME sends SMPP DELIVER-SM-RESP
+  * src: use osmo_timer_setup()
+  * gsm_04_11: get rid of unused parameter in sms_route_mt_sms()
+  * libmsc: do not leak pending SMPP command object on error path
+  * libmsc: remove dead code in sms_route_mt_sms()
+  * libmsc: remove duplicate lines in deliver_to_esme()
+  * libmsc: remove 'deferred' parameter in sms_route_mt_sms()
+  * libmsc: move gsm340_rx_sms_submit() to sms_route_mt_sms()
+  * libmsc: set registered_delivery field in SMPP 3.4 DELIVER_SM messages
+  * libmsc: report status report request flag from SMPP SUBMIT_SM
+  * libmsc: missing bit shift in status report flag when stored in sms object
+  * utils: smpp_mirror: set registered_delivery field in SMPP SUBMIT_SM
+  * utils: smpp_mirror: temporarily munch SMPP delivery receipts
+  * utils: smpp_mirror: reflect message reference TLV
+  * libmsc: add support for SMPP delivery receipts
+  * libmsc: update database to accomodate SMS status-report fields
+  * utils: smpp_mirror: bounce Delivery Receipts as Delivery Acknowledgments
+  * libmsc: handle delivery ack via SMPP SUBMIT SM / send GSM 03.40 status report
+  * libmsc: support GSM 03.40 status report for nitb
+  * libmsc: gsm340_gen_oa_sub() may return negative value
+  * libmsc: use smpp34_tlv_for_each() to avoid suboptimal TLV handling
+  * libmsc: use new smpp34 esm_class definitions
+  * libmsc: use SMPP34_DELIVERY_RECEIPT_* in libsmpp34
+  * libmsc: annotate esme route in the sms object from deliver_to_esme()
+  * libmsc: sms_route_mt_sms() may return uninitialized return value
+
+  [ Martin Hauke ]
+  * Fix no-return-in-nonvoid-function meas_vis.c
+  * Fix Warning: openbsc implicit-pointer-decl meas_udp2db.c:50
+
+  [ Alexander Huemer ]
+  * tests/oap: depend on libgtp
+  * tests/abis: fix format specifiers
+  * gprs: use libgtp cflags
+  * Consistenly format variables in */Makefile.am files
+  * Build fixes
+  * Add missing _CFLAGS and _LIBS
+
+  [ Ruben Undheim ]
+  * Patch to make openbsc find libsmpp34
+  * Fix some typos in stdout output
+  * deb: unbreak nightly builds
+
+  [ Vadim Yanitskiy ]
+  * move to hex TMSI representation
+  * db.c: implemented incremental migration
+  * mncc_sock: use osmo_sock_unix_init() from libosmocore
+  * VTY: add the dyn_ts_allow_tch_f option
+
+  [ Max ]
+  * Ignore vty test byproducts
+  * Fix segfault with broken config
+  * NAT: vty command to display number of BSCs
+  * NAT: extend debug output for ipaccess errors
+  * Remove trivial wrapper function
+  * NAT: allow allocating BSC in arbitrary order
+  * vty_test_runner: update ipa sending code
+  * NAT: move BSC config into separate file
+  * NAT: reload BSCs config dynamically
+  * Add SI2quater support to SI3
+  * Refactor SI-related code
+  * Add basic SI2quater support
+  * Fix documentation for command parameters
+  * Fix earfcn deletion
+  * Cleanup shared data structure
+  * Add basic UARFCN support
+  * Add vty check for max si2quater size
+  * Add extra debug output with channel mode and type
+  * Add missing include
+  * Fix comment typo
+  * Adjust si2quater ranges
+  * Disconnect calls with incompatible channel types / modes
+  * Ignore extended test leftovers
+  * Cleanup db test
+  * Move DTX settings to BTS
+  * Use proper measurement for handover
+  * Make extending subscriber creation easier
+  * Fix copy-paste error in SI6
+  * Make si2q scheduling optional
+  * Store last used FN for TCH
+  * Add regexp authorization policy for IMSI
+  * Add warning for unsupported DTX configurations
+  * Add DTXd indicator to gsm_lchan
+  * DTX: add data necessary for scheduling
+  * Add talkspurt indicator for gsm_lchan
+  * Fix SIGABRT on wrong AMR payload
+  * Make random extension range configurable
+  * Fix vty tests with subscriber deletion
+  * SGSN: force GSUP CN domain to PS
+  * SGSN: add vty config for choosing GPRS encryption
+  * SGSN: move TLLI unassignment into separate function
+  * Make random MSISDN assignment optional
+  * SGSN: prevent starting with inconsistent config
+  * SGSN: use unique AUTH REQ reference
+  * SGSN: split GEA key management from TLLI
+  * SGSN: add preliminary support for GPRS encryption
+  * SGSN: encrypt/decrypt only necessary frames
+  * SGSN: move cipher application to separate function
+  * Fix default subscriber regexp
+  * Improve code re-use
+  * Use random operation id
+  * Add python functions to get/set ctrl variables
+  * Add web proxy for control interface
+  * Modify SI 13 field for control_ack_type
+  * DTX: extend SID cache
+  * DTX: add flag for AMR HR P*
+  * DTX DL: use FSM for AMR
+  * Log use of incompatible BS-AG-BLKS-RES value
+  * Replace magic number with define
+  * DTX DL: Add FACCH cache
+  * Decrease count_codecs logging verbosity
+  * abisip-find: use protocol constant
+  * Replace duplicated code with macro call
+  * Add IPA multiplex
+  * Use IPA module for vty tests
+  * Add twisted-based IPA multiplex
+  * bsc_control.py: style corrections
+  * bsc_control.py: use ipa.py module
+  * bsc_control.py: remove unused -i option
+  * Improve GPRS logging
+  * Integrate Debian packaging changes
+  * Cosmetic fixes around SI generation
+  * Turn some warnings into errors
+  * Log expected SRES on GPRS AUTH REJECT
+  * Turn some compiler warnings into errors
+  * Print subcriber when skipping auth
+  * Improve OML failure report
+  * bsc_control.py: fix blocking
+  * Prevent segfault in range encoding
+  * SI2q: add support for multiple UARFCNs
+  * CTRL: remove boilerplate
+  * Remove dependency to autoconf-archive
+  * Attempt to fix nightly builds
+  * vty: remove ignored logging parameters
+  * Expand chan allocation logging
+  * Remove duplicating define
+  * Add support for extended SI2q parameters
+  * Handle DSD from HLR
+  * Fix potential segfault in sgsn_libgtp.c
+  * Add MS time. offset to gsm_lchan
+  * examples: remove logging level * everything
+  * Don't drop OML links for Get Attributes NACK
+  * twisted_ipa.py: bump version properly
+  * twisted_ipa.py: make debug logging more robust
+  * Add simple CTRL2SOAP proxy
+  * Handle PCU version received via OML alert
+  * python: fix Null logger
+  * abis: log known ACKs and unknown messages
+  * gsm_bts: add version and variant details
+  * Fix MS TO measurement representation
+  * Remove libs from openbsc.pc
+  * gbproxy: add example .service
+  * deb: install openbsc.pc
+  * Save PCU version reported by BTS
+  * Prepare for extended SI2quater support
+  * Add gsm_bts_type_variant to gsm_bts struct
+  * Make pcap dependency optional
+  * Fix vty warnings for GEA0
+  * ctrl: remove boilerplate code
+  * deb: install python scripts from contrib/
+  * Gb: use textual representation for parse log
+  * Use ipa.py for ctrl tests
+  * Use libosmocore for SW Description parsing
+  * Make BTS type and variant converters shareable
+  * Add forgotten items to .gitignore
+  * Use define for limit on number of BTS features
+  * Prepare for BTS attribute reporting via OML
+  * Restructure SI2quater generation
+  * SI2quater: fix EARFCN inclusion check
+  * Get basic BTS attributes
+  * Remove errorneous include
+  * Adjust BTS model feature check
+  * Add remote BTS feature storage and helpers
+  * Get TRX attributes
+  * Request and parse BTS feature list via OML
+  * OML: fix potential OOB memory access
+  * Move SI-related defines
+  * Update SI data structures and generation
+  * bsc_init: Forget which SIs are valid for the trx
+  * Fix BTS features length check
+  * Enable optional static builds
+  * Fix BTS attribute requests
+  * gbproxy: log unhandled BSSGP PDU as text
+  * gbproxy: log signal value as text
+  * Remove common_cs.h from .deb
+  * Use release helper from libosmocore
+  * SI13: drop PBCCH-related bits
+  * CTRL: cleanup write-only command functions
+  * Show OML link uptime in vty
+  * Make TRX rf locking more visible
+
+  [ Alexander Couzens ]
+  * add .gitreview
+  * gprs: use new uint8_t * for kv in gprs_cipher_run()
+  * libbsc: skip channel state LCHAN_S_INACTIVE while handover
+  * gprs_gmm: remove duplicated start of T3395
+  * bs11_config: add brackets to fix warning in argument parsing
+  * sgsn: add statistics counter for GPRS and PDP packets
+  * sgsn: add statistics counter for LLC packets
+  * libbsc/libmsc: convert old osmo counter into rate_ctrgs
+  * libmsc: add missing count of sms no receiver when using smpp_first
+  * libmsc/bsc: split rate counters into bsc and msc group
+  * bsc/netinit: correct mistyped rate counter
+  * gprs/gsm0408_gprs_force_reattach_oldmsg: check llme before use
+  * gprs/gprs_llc: fix null pointer deref in gprs_llc_rcvmsg
+  * sms: change rp err cause of smpp_try_deliver errors
+  * bts: extend bts_chan_load to allow counting tch only
+  * bsc: count the usage of codec by setting the lchan active
+  * msc: add counters to track call attempts/active/success/failed
+  * gprs/gprs_llc: count UI frames over SAPI in the statistics
+  * fix mistypes, spaces and tabs
+  * bsc: count the usage of codec by setting the lchan active
+  * gprs/sgsn: rename gprs->mm_state -> gmm_state
+  * gprs/sgsn: rename sgsn_mm_ctx_alloc() -> sgsn_mm_ctx_alloc_gb()
+  * gprs/gprs_mm: add value_strings for PMM & MM states
+  * gprs/sgsn_mm_ctx_alloc(): initialize MM state to IDLE
+  * gprs/sgsn_vty: fix typo in comment
+  * libmsc/update_db_revision_3(): free memleaking db result
+  * unixsocket: start sabm for UNIXSOCKET
+  * gprs_sgsn.c: initialize ptmsi with 0xdeadbeef
+  * libbsc: add chreq type for CHREQ_T_PDCH_ONE_PHASE & CHREQ_T_PDCH_TWO_PHASE
+  * abis_om2k: protect MO FSMs by NULL check
+  * libbsc: add rsl_ericsson_imm_assign_cmd() which reply with a confirmation message
+  * pcu_sock: set flag PCU_IF_FLAG_SYSMO by setting pcu_direct = 1
+  * pcu_sock: pcu_tx_info_ind allow to use TRX not starting from 0
+  * pcu_sock: implement direct tlli on AGCH
+  * abis_rsl: fix off-by-one length check when parsing ericson tlli field
+  * abis_rsl: use msgb_pull to parse tlli from msg
+
+  [ Andreas Eversberg ]
+  * dyn PDCH: Fix free slot search for chan_alloc_reverse == true
+  * dyn PDCH: Automatically deactivate/activate PDCH on TCH/F+PDCH channel
+
+  [ Dieter Spaar ]
+  * SGSN: fix FCS calculation for encrypted frames
+
+  [ bhargava ]
+  * Modify SI 13 field to support 11 bit RACH
+
+  [ Philipp ]
+  * Adding LLC-XID encoder / decoder and unit test
+  * Moving grs_sndcp.h header file to include
+  * Adding LLC-XID related modifications in LLC
+  * SNDCP: add SNDCP-XID encoder/decoder and unit test
+  * RFC1144: add slhc code from linux kernel
+  * RFC1144: integration and unit-test
+  * SNDCP: add RFC1144 header compression functionality
+  * V.42bis: add sourcecode from IAXmodem (SPANDSP)
+  * V.42bis: integration and unit test
+  * SNDCP: add V.42bis data compression functionality
+  * SLHC: Improving slhc (RFC1144) testcase
+  * SGSN: Fixing build config
+  * sndcp: Fixups for sndcp layer based on coverity-scan suggestions
+  * OML: Improve OML attribute handling
+  * gsm0408: Adding log output for 3g specific RR messages
+  * Cosmetic: Add missing switch to usage help
+  * OM2000: Add fault report parsing
+  * om2000: added support for ericssons sabm negotiation
+  * OM2000: Throw error when MO can not be enabled
+  * SNDCP: Fixup based on Coverity Scan suggestion
+  * rbs2000: Add missing bts feature definitions
+  * OM2000: Fixup based on Coverity Scan suggestion
+  * rsl: support for ericssons propritary SI13 format
+  * sndcp: fixup for coverity scan defect CID 149097
+  * LLC: Fixup element order in LLC-XID
+  * sndcp: Allow empty SNDCP-XID indications
+  * cosmetic: Rename phone to ms
+
+  [ root ]
+  * om2000: Add support for querying RBS capabilities
+
+  [ Pravin Kumarvel ]
+  * Correct Logging macro for pdpctx_timer_start
+  * Add support for pdpctx_timer_stop
+  * Support Deactivate PDP Context Request from network
+
+  [ Keith ]
+  * Pass actual smpp_avail_status through to smpp in alert_all_esme()
+  * Add VTY command to immediately expire user (set expire_lu to now)
+  * meas_json: fix NEIGH: missing array braces
+  * libmsc: Map SMPP command status to GSM 04.11 cause
+  * LIBMSC: Place correct dst address in status report
+  * LIBMSC: Use sms->text, not sms->user_data to construct report body
+  * libmsc: Use actual delivery time in delivery reports.
+  * libmsc: Log Rx DELIVER-SM RESP before calling gsm411_send_rp_ack
+
+  [ Philipp Maier ]
+  * compression: Fix nullpointer deref
+  * gprs: Fix compiler warning about uninitalized cause code
+  * om2000: add VTY command to delete CON groups
+  * XID: resend xid with pdp-ctx-ack messages
+  * silent_call: remove unfinished fuzzer interface
+  * sgsn: fix problem with leading-zero-IMSIs
+  * cosmetic: add copyright header to bsc_control.py
+  * cosmetic: Add commandline option to display version
+  * gprs: fix T3186 encoding in Sysinfo 13
+  * gsm_data_shared: add value strings for gsm_chreq
+  * libbsc: add debug log message to S_L_INP_* callbacks
+  * pcu_sock: add basic pcu interface support
+  * pcu_sock: Forward imm.ass PCU originated messages
+  * pcu_sock: Fix broken paging command
+
+  [ Minh-Quang Nguyen ]
+  * rsl: Fix dropping of LAPDm UA message.
+
+  [ Alexander Chemeris ]
+  * utils: 'meas_json' utility to convert measurement feed into a JSON feed.
+  * libmsc: Fix VTY output for handover counters.
+  * libcommon: Fix log output for bts>0.
+
+  [ Pau Espin Pedrol ]
+  * nat: Use equal func in bsc_sccp
+  * nat: Fix initial buffer size parameter for getline
+  * smpp_smsc.c: Log on sending deliver_sm message
+  * libmsc: Remove comment not applying anymore
+  * mgcp_osmux: Remove unused parameter
+  * mgcp: Fix uplink activation of Osmux stream
+  * sgsn_test: Fix wrong definition of wrap func
+  * smpp: Fix compilation warning
+  * libmsc: Fix wrong handling of user_message_reference parameter
+  * bsc_api: Fix NULL secondary_lchan access in handle_ass_fail
+  * libbsc: Use correct printf formatting for uint64_t
+
+  [ Benoit Bolsee ]
+  * smpp: fix return cause
+  * 04.08: find a portable way to obtain the timezone offset
+  * transaction: reject calls from unidentified subscribers
+
+  [ Ivan Kluchnikov ]
+  * handover_logic: set correct link to bts for subscriber_connection in case of moving this connection to another bts
+  * handover_decision: Fix condition for power budget handover attempt
+
+  [ André Boddenberg ]
+  * Make use of osmo-build.sh to use dependency artifacts for builds.
+
+ -- Harald Welte <laforge@gnumonks.org>  Sat, 28 Oct 2017 21:17:34 +0200
+
+openbsc (0.15.1) UNRELEASED; urgency=medium
+
+  * Move forward toward a new release.
+  * Prevent SGSN starting with 'auth-policy remote' when no 'gsup remote-*' are configured.
+    Note: such configs are broken without extra workarounds anyway.
+
+  [ Harald Welte ]
+  * Rename osmo-bsc to osmo-bsc-sccplite to avoid clashes with new 3GPP AoIP
+    osmo-bsc.git code
+
+ -- Holger Hans Peter Freyther <holger@moiji-mobile.com>  Tue, 24 May 2016 23:14:31 +0200
+
+openbsc (0.14.0) unstable; urgency=low
+
+  * New upstream tag and additional patches.
+
+ -- Holger Hans Peter Freyther <holger@freyther.de>  Sat, 14 Mar 2015 20:33:25 +0100
+
+openbsc (0.12.0+git26-7) unstable; urgency=low
+
+  * 64bit fix for the MGCP rewriting
+
+ -- Holger Hans Peter Freyther <holger@freyther.de>  Wed, 07 Nov 2012 11:39:34 +0100
+
+openbsc (0.12.0+git26-6) precise; urgency=low
+
+  * Added init script for osmocom-sgsn. 
+
+ -- Eric Butler <eric@codebutler.com>  Fri, 24 Aug 2012 21:04:32 -0700
+
+openbsc (0.12.0+git26-5) precise; urgency=low
+
+  * Don't enable MNCC sock by default.
+  * Automatically create important directories.
+  * Fix init script 'stop' command.
+
+ -- Eric Butler <eric@codebutler.com>  Fri, 24 Aug 2012 20:56:33 -0700
+
+openbsc (0.12.0+git26-4) precise; urgency=low
+
+  * Specify HLR path and enable RTP proxy.
+
+ -- Eric Butler <eric@codebutler.com>  Mon, 20 Aug 2012 00:21:07 -0700
+
+openbsc (0.12.0+git26-3) precise; urgency=low
+
+  * Fix init script.
+
+ -- Eric Butler <eric@codebutler.com>  Sun, 19 Aug 2012 16:05:44 -0700
+
+openbsc (0.12.0+git26-2) precise; urgency=low
+
+  * Fix libdbi package dependency.
+
+ -- Eric Butler <eric@codebutler.com>  Wed, 15 Aug 2012 00:35:37 -0700
+
+openbsc (0.12.0+git26-1) precise; urgency=low
+
+  * Fix version issue.
+
+ -- Eric Butler <eric@codebutler.com>  Tue, 14 Aug 2012 21:00:51 -0700
+
+openbsc (0.12.0+git26) precise; urgency=low
+
+  * Updated ubuntu package.
+
+ -- Eric Butler <eric@codebutler.com>  Tue, 14 Aug 2012 17:36:51 -0700
+
+openbsc (0.9.13.115.eb113-1) natty; urgency=low
+
+  * New upstream release
+
+ -- Harald Welte <laforge@gnumonks.org>  Wed, 11 May 2011 18:41:24 +0000
+
+openbsc (0.9.4-1) unstable; urgency=low
+
+  * Initial release
+
+ -- Harald Welte <laforge@gnumonks.org>  Tue, 24 Aug 2010 13:34:24 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..0e9bb0c
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,137 @@
+Source: openbsc
+Maintainer: Harald Welte <laforge@gnumonks.org>
+Section: net
+Priority: optional
+Build-Depends: debhelper (>= 9),
+               autotools-dev,
+               autoconf-archive,
+               pkg-config,
+               libosmocore-dev,
+               libosmo-sccp-dev,
+               libdbi0-dev,
+               dh-autoreconf,
+               dh-systemd (>= 1.5),
+               libosmo-abis-dev,
+               libosmo-netif-dev,
+               libdbd-sqlite3,
+               libpcap-dev,
+               libsmpp34-dev
+Standards-Version: 3.9.8
+Vcs-Git: git://bs11-abis.gnumonks.org/openbsc.git
+Vcs-Browser: http://openbsc.osmocom.org/trac/browser
+Homepage: https://projects.osmocom.org/projects/openbsc
+
+Package: osmocom-bsc-sccplite
+Architecture: any
+Depends: ${shlibs:Depends},
+         ${misc:Depends}
+Description: GSM Base Station Controller
+ This is the BSC-only version of OpenBSC. It requires a Mobile Switching Center
+ (MSC) to operate.
+ .
+ You might rather prefer to use osmocom-nitb which is considered a
+ "GSM Network-in-a-Box" and does not depend on a MSC.
+
+Package: osmocom-nitb
+Architecture: any
+Depends: ${shlibs:Depends},
+         ${misc:Depends},
+         libdbd-sqlite3
+Description: GSM Network-in-a-Box, implements BSC, MSC, SMSC, HLR, VLR
+ This is the Network-in-a-Box version of OpenBSC. It has all the GSM network
+ components bundled together. When using osmocom-nitb, there is no need for a
+ Mobile Switching Center (MSC) which is needed when using osmocom-bsc-sccplite.
+
+Package: osmocom-ipaccess-utils
+Architecture: any
+Depends: ${shlibs:Depends},
+         ${misc:Depends}
+Description: Command line utilities for ip.access nanoBTS
+ This package contains utilities that are specific for nanoBTS when being used
+ together with OpenBSC. It contains mainly three tools: ipaccess-find,
+ ipaccess-config and ipaccess-proxy.
+
+Package: osmocom-bs11-utils
+Architecture: any
+Depends: ${shlibs:Depends},
+         ${misc:Depends}
+Description: Command line utilities for Siemens BS-11 BTS
+ There is a tool in this package for configuring the Siemens BS-11 BTS.
+ Additionally, it contains one tool for making use of an ISDN-card and the
+ public telephone network as frequency standard for the E1 line.
+
+Package: osmocom-bsc-nat
+Architecture: any
+Depends: ${shlibs:Depends},
+         ${misc:Depends}
+Recommends: osmocom-bsc-sccplite
+Description: Osmocom Base Station Controller Network Address Translation
+ This NAT is useful for masquerading multiple BSCs behind one. It listens
+ for incoming BSCs on port 5000 and connects to a specified Mobile Switching
+ Center (MSC).
+ .
+ This package is part of OpenBSC and closely related to osmocom-bsc-sccplite.
+
+Package: osmo-bsc-mgcp
+Architecture: any
+Depends: ${shlibs:Depends},
+         ${misc:Depends}
+Description: OsmoBSC-MGCP: Osmocom's Legacy Media Gateway.
+
+Package: openbsc-dev
+Architecture: all
+Depends: ${misc:Depends}
+Description: Header file needed by tools tightly integrated
+ Some other programs depend on gsm_data_shared.h and gsm_data_shared.c
+ from OpenBSC. This package installs these files to your file system so
+ that the other packages can build-depend on this package.
+ .
+ The directory structure is copied after the structure in the repository
+ and the header and .c file are installed into /usr/src/osmocom/openbsc/.
+
+Package: osmocom-bsc-sccplite-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmocom-bsc-sccplite (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the OpenBSC BSC
+ Make debugging possible
+
+Package: osmocom-nitb-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmocom-nitb (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the OpenBSC NITB
+ Make debugging possible
+
+Package: osmocom-ipaccess-utils-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmocom-ipaccess-utils (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the OpenBSC ip.access utils
+ Make debugging possible
+
+Package: osmocom-bs11-utils-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmocom-bs11-utils (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the OpenBSC BS11 utils
+ Make debugging possible
+
+Package: osmocom-bsc-nat-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmocom-bsc-nat (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the OpenBSC Network Address Translation
+ Make debugging possible
+
+Package: osmo-bsc-mgcp-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Debug symbols for the  Osmocom's Legacy Media Gateway.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..1e4dee1
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,137 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: OpenBSC
+Source: http://openbsc.osmocom.org/
+
+Files: *
+Copyright: 2008-2015        Harald Welte <laforge@gnumonks.org>
+           2008-2015        Holger Hans Peter Freyther <zecke@selfish.org>
+           2009-2015        On-Waves
+           2008             Jan Luebbe <jluebbe@debian.org>
+           2008,2010-2011   Daniel Willmann <daniel@totalueberwachung.de>
+           2009,2011,2013   Andreas Eversberg <Andreas.Eversberg@versatel.de>
+           2009,2011        Dieter Spaar <spaar@mirider.augusta.de>
+           2009             Mike Haben <michael.haben@btinternet.com>
+           2010             Sylvain Munaut <246tnt@gmail.com>
+           2012-2013        Pablo Neira Ayuso <pablo@gnumonks.org>
+           2013-2015        Sysmocom s.f.m.c. GmbH  (Jacob Erlbeck)
+           2014             Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+License: AGPL-3+
+Comment: Contributions by Stefan Schmidt <stefan@datenfreihafen.org> as well
+
+Files: wireshark/0001-abis_oml.patch
+       wireshark/0002-ericsson_rbs2409.patch
+       wireshark/0003-lucent-hnb.patch
+       wireshark/0005-rsl-hsl.patch
+Copyright: 1998         Gerald Combs <gerald@wireshark.org>
+           2007,2011    Anders Broman <anders.broman@ericsson.com>
+           2009         Holger Hans Peter Freyther <zecke@selfish.org>
+           2009-2011    Harald Welte <laforge@gnumonks.org>
+License: GPL-2+
+
+Files: openbsc/include/mISDNif.h
+Copyright: 2008         Karsten Keil <kkeil@novell.com>
+License: LGPL-2.1
+
+Files: openbsc/src/libmgcp/g711common.h
+Copyright: 2009         Abramo Bagnara <abramo@alsa-project.org>
+License: GPL-2+
+
+Files: openbsc/git-version-gen
+Copyright: 2007-2010    Free Software Foundation
+License: GPL-3+
+
+Files: openbsc/osmoappdesc.py
+       openbsc/tests/smpp_test_runner.py
+       openbsc/tests/ctrl_test_runner.py
+       openbsc/tests/vty_test_runner.py
+Copyright: 2013         Katerina Barone-Adesi <kat.obsc@gmail.com>
+           2013         Jacob Erlbeck <jerlbeck@sysmocom.de>
+           2013-2014    Holger Hans Peter Freyther <zecke@selfish.org>
+License: GPL-3+
+
+Files: openbsc/src/libbsc/bsc_ctrl_lookup.c
+Copyright: 2010-2011    Daniel Willmann <daniel@totalueberwachung.de>
+           2010-2011    On-Waves
+License: GPL-2+
+
+Files: openbsc/src/libmsc/mncc_sock.c
+       openbsc/src/libmsc/mncc_builtin.c
+Copyright: 2008-2010    Harald Welte <laforge@gnumonks.org>
+           2009         Andreas Eversberg <Andreas.Eversberg@versatel.de>
+           2012         Holger Hans Peter Freyther <zecke@selfish.org>
+License: GPL-2+
+
+Files: debian/*
+Copyright: 2012-2015    Holger Hans Peter Freyther <zecke@selfish.org>
+           2016         Ruben Undheim <ruben.undheim@gmail.com>
+License: GPL-2+
+
+
+License: AGPL-3+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+License: GPL-2+
+ This package 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 2 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, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General Public
+ License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+
+License: GPL-3+
+ This package 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, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General Public
+ License version 3 can be found in "/usr/share/common-licenses/GPL-3".
+
+
+License: LGPL-2.1
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; version
+ 2.1 of the License.
+ .
+ This library 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
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU Lesser General
+ Public License version 2.1 can be found in
+ "/usr/share/common-licenses/LGPL-2.1".
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..cd545c2
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+openbsc/README
diff --git a/debian/openbsc-dev.install b/debian/openbsc-dev.install
new file mode 100644
index 0000000..b669b0f
--- /dev/null
+++ b/debian/openbsc-dev.install
@@ -0,0 +1,3 @@
+openbsc/include/openbsc/gsm_data_shared.h usr/src/osmocom/openbsc/openbsc/include/openbsc/
+openbsc/src/libcommon/gsm_data_shared.c usr/src/osmocom/openbsc/openbsc/src/libcommon/
+usr/lib/*/pkgconfig/openbsc.pc
diff --git a/debian/osmo-bsc-mgcp.install b/debian/osmo-bsc-mgcp.install
new file mode 100644
index 0000000..8f9b74f
--- /dev/null
+++ b/debian/osmo-bsc-mgcp.install
@@ -0,0 +1,3 @@
+etc/osmocom/osmo-bsc-mgcp.cfg
+lib/systemd/system/osmo-bsc-mgcp.service
+usr/bin/osmo-bsc_mgcp
diff --git a/debian/osmocom-bs11-utils.install b/debian/osmocom-bs11-utils.install
new file mode 100644
index 0000000..757a854
--- /dev/null
+++ b/debian/osmocom-bs11-utils.install
@@ -0,0 +1,2 @@
+/usr/bin/bs11_config
+/usr/bin/isdnsync
diff --git a/debian/osmocom-bsc-nat.init b/debian/osmocom-bsc-nat.init
new file mode 100755
index 0000000..984a7ce
--- /dev/null
+++ b/debian/osmocom-bsc-nat.init
@@ -0,0 +1,153 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          osmocom-bsc-nat
+# Required-Start:    $network $local_fs
+# Required-Stop:
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Osmocom GSM network-in-a-box
+# Description:       A minimal implementation of the GSM Base Station Controller,
+#                    Mobile Switching Center, Home Location regster and all other
+#                    components to run a self-contained GSM network.
+### END INIT INFO
+
+# Author: Harald Welte <laforge@gnumonks.org>
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+NAME=osmo-bsc_nat                      # Introduce the short server's name here
+DESC="Osmocom GSM BSC Multiplexer (NAT)" # Introduce a short description here
+DAEMON=/usr/bin/osmo-bsc_nat           # Introduce the server's location here
+SCRIPTNAME=/etc/init.d/osmocom-bsc-nat
+CONFIG_FILE=/etc/osmocom/osmocom-bsc-nat.cfg
+
+# Exit if the package is not installed
+[ -x $DAEMON ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/osmocom-bsc-nat ] && . /etc/default/osmocom-bsc-nat
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+DAEMON_ARGS="-D -c $CONFIG_FILE"
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \
+		|| return 1
+	start-stop-daemon --start --quiet --exec $DAEMON -- \
+		$DAEMON_ARGS \
+		|| return 2
+	# Add code here, if necessary, that waits for the process to be ready
+	# to handle requests from services started subsequently which depend
+	# on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --name $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Wait for children to finish too if this is a daemon that forks
+	# and if the daemon is only ever run from this initscript.
+	# If the above conditions are not satisfied then add some other code
+	# that waits for the process to drop all resources that could be
+	# needed by services started subsequently.  A last resort is to
+	# sleep for some time.
+	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+	[ "$?" = 2 ] && return 2
+	return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+	#
+	# If the daemon can reload its configuration without
+	# restarting (for example, when it is sent a SIGHUP),
+	# then implement that here.
+	#
+	start-stop-daemon --stop --signal 1 --quiet $PIDFILE --name $NAME
+	return 0
+}
+
+case "$1" in
+  start)
+    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+    do_start
+    case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+  ;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
diff --git a/debian/osmocom-bsc-nat.install b/debian/osmocom-bsc-nat.install
new file mode 100644
index 0000000..ba134d1
--- /dev/null
+++ b/debian/osmocom-bsc-nat.install
@@ -0,0 +1,3 @@
+/etc/osmocom/osmo-bsc-nat.cfg
+/lib/systemd/system/osmo-bsc-nat.service
+/usr/bin/osmo-bsc_nat
diff --git a/debian/osmocom-bsc-sccplite.examples b/debian/osmocom-bsc-sccplite.examples
new file mode 100644
index 0000000..545709b
--- /dev/null
+++ b/debian/osmocom-bsc-sccplite.examples
@@ -0,0 +1,2 @@
+openbsc/doc/examples/osmo-bsc_mgcp
+openbsc/doc/examples/osmo-bsc-sccplite
diff --git a/debian/osmocom-bsc-sccplite.install b/debian/osmocom-bsc-sccplite.install
new file mode 100644
index 0000000..872333e
--- /dev/null
+++ b/debian/osmocom-bsc-sccplite.install
@@ -0,0 +1,3 @@
+/etc/osmocom/osmo-bsc-sccplite.cfg
+/lib/systemd/system/osmo-bsc-sccplite.service
+/usr/bin/osmo-bsc-sccplite
diff --git a/debian/osmocom-ipaccess-utils.install b/debian/osmocom-ipaccess-utils.install
new file mode 100644
index 0000000..de13c18
--- /dev/null
+++ b/debian/osmocom-ipaccess-utils.install
@@ -0,0 +1,3 @@
+/usr/bin/ipaccess-config
+/usr/bin/abisip-find
+/usr/bin/ipaccess-proxy
diff --git a/debian/osmocom-nitb.default b/debian/osmocom-nitb.default
new file mode 100644
index 0000000..ef76a5f
--- /dev/null
+++ b/debian/osmocom-nitb.default
@@ -0,0 +1,8 @@
+CONFIG_FILE="/etc/osmocom/osmo-nitb.cfg"
+HLR_FILE="/var/lib/osmocom/hlr.sqlite3"
+
+DAEMON_ARGS="-P"
+
+# Uncomment if using LCR+Asterisk
+# DAEMON_ARGS="-m -P"
+
diff --git a/debian/osmocom-nitb.dirs b/debian/osmocom-nitb.dirs
new file mode 100644
index 0000000..efbca2b
--- /dev/null
+++ b/debian/osmocom-nitb.dirs
@@ -0,0 +1,3 @@
+/etc/osmocom
+/var/log/osmocom
+/var/lib/osmocom
diff --git a/debian/osmocom-nitb.examples b/debian/osmocom-nitb.examples
new file mode 100644
index 0000000..c098d5c
--- /dev/null
+++ b/debian/osmocom-nitb.examples
@@ -0,0 +1 @@
+openbsc/doc/examples/osmo-nitb
diff --git a/debian/osmocom-nitb.init b/debian/osmocom-nitb.init
new file mode 100755
index 0000000..0747446
--- /dev/null
+++ b/debian/osmocom-nitb.init
@@ -0,0 +1,152 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          osmo-nitb
+# Required-Start:    $network $local_fs
+# Required-Stop:
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Osmocom GSM network-in-a-box
+# Description:       A minimal implementation of the GSM Base Station Controller,
+#                    Mobile Switching Center, Home Location regster and all other
+#                    components to run a self-contained GSM network.
+### END INIT INFO
+
+# Author: Harald Welte <laforge@gnumonks.org>
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+NAME=osmo-nitb                      # Introduce the short server's name here
+DESC="Osmocom GSM Network-in-a-Box" # Introduce a short description here
+DAEMON=/usr/bin/osmo-nitb           # Introduce the server's location here
+SCRIPTNAME=/etc/init.d/osmocom-nitb
+
+# Exit if the package is not installed
+[ -x $DAEMON ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/osmocom-nitb ] && . /etc/default/osmocom-nitb
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+DAEMON_ARGS="$DAEMON_ARGS -D -c $CONFIG_FILE -l $HLR_FILE"
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \
+		|| return 1
+	start-stop-daemon --start --quiet --exec $DAEMON -- \
+		$DAEMON_ARGS \
+		|| return 2
+	# Add code here, if necessary, that waits for the process to be ready
+	# to handle requests from services started subsequently which depend
+	# on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --name $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Wait for children to finish too if this is a daemon that forks
+	# and if the daemon is only ever run from this initscript.
+	# If the above conditions are not satisfied then add some other code
+	# that waits for the process to drop all resources that could be
+	# needed by services started subsequently.  A last resort is to
+	# sleep for some time.
+	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+	[ "$?" = 2 ] && return 2
+	return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+	#
+	# If the daemon can reload its configuration without
+	# restarting (for example, when it is sent a SIGHUP),
+	# then implement that here.
+	#
+	start-stop-daemon --stop --signal 1 --quiet $PIDFILE --name $NAME
+	return 0
+}
+
+case "$1" in
+  start)
+    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+    do_start
+    case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+  ;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
diff --git a/debian/osmocom-nitb.install b/debian/osmocom-nitb.install
new file mode 100644
index 0000000..87ed7e2
--- /dev/null
+++ b/debian/osmocom-nitb.install
@@ -0,0 +1,4 @@
+/etc/osmocom/osmo-nitb.cfg
+/lib/systemd/system/osmo-nitb.service
+/usr/bin/osmo-nitb
+openbsc/contrib/*.py usr/bin/
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..dc609f9
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,31 @@
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+DEBIAN  := $(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2)
+DEBVERS := $(shell echo '$(DEBIAN)' | cut -d- -f1)
+VERSION := $(shell echo '$(DEBVERS)' | sed -e 's/[+-].*//' -e 's/~//g')
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+%:
+	dh $@ --sourcedirectory=openbsc --with=systemd --with autoreconf
+
+# This is needed for debian stable (squeeze)
+override_dh_autoreconf:
+	cd openbsc && autoreconf --install --force
+
+override_dh_strip:
+	dh_strip -posmocom-bsc-sccplite --dbg-package=osmocom-bsc-sccplite-dbg
+	dh_strip -posmocom-nitb --dbg-package=osmocom-nitb-dbg
+	dh_strip -posmocom-ipaccess-utils --dbg-package=osmocom-ipaccess-utils-dbg
+	dh_strip -posmocom-bs11-utils --dbg-package=osmocom-bs11-utils-dbg
+	dh_strip -posmocom-bsc-nat --dbg-package=osmocom-bsc-nat-dbg
+
+override_dh_auto_configure:
+	dh_auto_configure --sourcedirectory=openbsc -- --enable-nat --enable-osmo-bsc --enable-smpp --with-systemdsystemunitdir=/lib/systemd/system
+
+# Print test results in case of a failure
+override_dh_auto_test:
+	dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/openbsc/.gitignore b/openbsc/.gitignore
new file mode 100644
index 0000000..a1695c6
--- /dev/null
+++ b/openbsc/.gitignore
@@ -0,0 +1,89 @@
+*.o
+*.lo
+*.a
+.deps
+Makefile
+Makefile.in
+bscconfig.h
+bscconfig.h.in
+openbsc.pc
+src/osmo-nitb/osmo-nitb
+src/osmo-bsc_mgcp/osmo-bsc_mgcp
+src/osmo-bsc/osmo-bsc-sccplite
+src/utils/meas_vis
+src/utils/meas_json
+src/utils/osmo-meas-pcap2db
+src/utils/osmo-meas-udp2db
+src/utils/smpp_mirror
+*.*~
+*.sw?
+.libs
+*.pyc
+*.gcda
+*.gcno
+
+#configure
+aclocal.m4
+autom4te.cache/
+config.log
+config.status
+config.guess
+config.sub
+configure
+compile
+depcomp
+install-sh
+missing
+stamp-h1
+libtool
+ltmain.sh
+m4/*.m4
+
+# git-version-gen magic
+.tarball-version
+.version
+
+
+# apps and app data
+hlr.sqlite3
+src/utils/bs11_config
+src/ipaccess/ipaccess-config
+src/ipaccess/abisip-find
+src/ipaccess/ipaccess-firmware
+src/ipaccess/ipaccess-proxy
+src/utils/isdnsync
+src/nat/bsc_nat
+src/osmo-bsc_nat/osmo-bsc_nat
+src/libcommon/gsup_test_client
+
+#tests
+tests/testsuite.dir
+tests/bsc-nat/bsc_nat_test
+tests/bsc-nat-trie/bsc_nat_trie_test
+tests/channel/channel_test
+tests/db/db_test
+tests/debug/debug_test
+tests/gsm0408/gsm0408_test
+tests/mgcp/mgcp_test
+tests/sccp/sccp_test
+tests/sms/sms_test
+tests/timer/timer_test
+tests/abis/abis_test
+tests/si/si_test
+tests/smpp/smpp_test
+tests/bsc/bsc_test
+tests/trau/trau_test
+tests/mgcp/mgcp_transcoding_test
+tests/subscr/subscr_test
+tests/subscr/bsc_subscr_test
+tests/mm_auth/mm_auth_test
+tests/nanobts_omlattr/nanobts_omlattr_test
+
+tests/atconfig
+tests/atlocal
+tests/package.m4
+tests/testsuite
+tests/testsuite.log
+
+src/openbsc.cfg*
+writtenconfig/
diff --git a/openbsc/AUTHORS b/openbsc/AUTHORS
new file mode 100644
index 0000000..91af515
--- /dev/null
+++ b/openbsc/AUTHORS
@@ -0,0 +1,9 @@
+Harald Welte <laforge@gnumonks.org>
+Holger Freyther <zecke@selfish.org>
+Jan Luebbe <jluebbe@debian.org>
+Stefan Schmidt <stefan@datenfreihafen.org>
+Daniel Willmann <daniel@totalueberwachung.de>
+Andreas Eversberg <Andreas.Eversberg@versatel.de>
+Sylvain Munaut <246tnt@gmail.com>
+Jacob Erlbeck <jerlbeck@sysmocom.de>
+Neels Hofmeyr <nhofmeyr@sysmocom.de>
diff --git a/openbsc/COPYING b/openbsc/COPYING
new file mode 100644
index 0000000..dba13ed
--- /dev/null
+++ b/openbsc/COPYING
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/openbsc/Makefile.am b/openbsc/Makefile.am
new file mode 100644
index 0000000..792dcf2
--- /dev/null
+++ b/openbsc/Makefile.am
@@ -0,0 +1,33 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+## FIXME: automake >= 1.13 or autoconf >= 2.70 provide better suited AC_CONFIG_MACRO_DIRS for configure.ac
+## remove line below when OE toolchain is updated to version which include those
+ACLOCAL_AMFLAGS = -I m4
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+SUBDIRS = \
+	doc \
+	include \
+	src \
+	tests \
+	contrib \
+	$(NULL)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = openbsc.pc
+
+BUILT_SOURCES = $(top_srcdir)/.version
+EXTRA_DIST = git-version-gen osmoappdesc.py .version
+
+DISTCHECK_CONFIGURE_FLAGS = \
+	--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+@RELMAKE@
+
+$(top_srcdir)/.version:
+	echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+	echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/openbsc/README b/openbsc/README
new file mode 100644
index 0000000..d01b2cf
--- /dev/null
+++ b/openbsc/README
@@ -0,0 +1,39 @@
+About OpenBSC
+=============
+
+OpenBSC started as a minimalistic all-in-one implementation of the GSM Network,
+with particular emphasis on the functionality typically provided by the BSC,
+MSC, HLR, VLR and SMSC.  Today it is a growing suite of libraries and programs,
+implementing protocol stacks and functional elements, including
+
+ * OsmoBSC - a pure GSM BSC, speaking Abis/IP to the BTS and A/IP to the MSC
+ * OsmoBSC-MGCP - MGCP helper to the OsmoBSC software
+ * OsmoNITB - a BSC+MSC+VLR+HLR+SMSC "Network in the box".
+ * OsmoMSC - a voice CN with A/IP and IuCS/IP towards the BSC and/or HNB-GW
+ * OsmoSGSN - a GPRS SGSN with Gb/IP and IuPS/IP towards the PCU and/or HNB-GW
+ * Osmo-GbProxy - a Proxy to aggregate many Gb links as one Gb link to the SGSN
+ * OsmoBSCNAT - a gateway aggregating many A links as one A link to the MSC
+ * OsmoGTPHUB - a hub aggregating many GTP links (between SGSN and GGSN)
+ * ipaccess-utils - some tools to discover + configure ip.access nanoBTS
+ * bs11_config - a tool to configure the Siemens BS-11 microBTS
+
+Various interfaces towards the BTS are supported, among which are:
+
+ * Classic A-bis over E1 using a mISDN based E1 interface. In other
+   words, you can connect existing GSM Base Transceiver Station (BTS)
+   through E1 to OpenBSC.  So far, we have made it work with the Siemens BS-11,
+   various Ericsson RBS2xxx BTS models and the Nokia MetroSite.
+
+ * A-bis over IP as used by the ip.access nanoBTS product family as well as
+   the Open Source OsmoBTS software (by the same authors as OpenBSC).  OsmoBTS
+   in turn supports various transceiver hardware, including the sysmoBTS
+   product family, as well as SDR transceivers supported by OsmoTRX, such as
+   the UmTRX or USRP boardss.
+
+ * IuCS and IuPS over IP towards an HNB-GW (see osmo-iuh) for UMTS (3G)
+   voice and data links.
+
+Find OpenBSC online at
+http://openbsc.osmocom.org/
+
+	Harald Welte <laforge@gnumonks.org>
diff --git a/openbsc/README.vty-tests b/openbsc/README.vty-tests
new file mode 100644
index 0000000..ba1b87c
--- /dev/null
+++ b/openbsc/README.vty-tests
@@ -0,0 +1,11 @@
+To run the configuration parsing and output (VTY) test suite, first install
+
+  git://git.osmocom.org/python/osmo-python-tests
+
+and pass the following configure options here:
+
+  ./configure --enable-vty-tests --enable-external-tests
+
+The VTY tests are then included in the standard check target:
+
+  make check
diff --git a/openbsc/configure.ac b/openbsc/configure.ac
new file mode 100644
index 0000000..75dd5d3
--- /dev/null
+++ b/openbsc/configure.ac
@@ -0,0 +1,283 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([openbsc],
+	m4_esyscmd([./git-version-gen .tarball-version]),
+	[openbsc@lists.osmocom.org])
+
+dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
+AC_CONFIG_AUX_DIR([.])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl include release helper
+RELMAKE='-include osmo-release.mk'
+AC_SUBST([RELMAKE])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT
+
+dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
+AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
+if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
+        AC_MSG_WARN([You need to install pkg-config])
+fi
+PKG_PROG_PKG_CONFIG([0.20])
+
+dnl check for AX_CHECK_COMPILE_FLAG
+m4_ifdef([AX_CHECK_COMPILE_FLAG], [], [
+	AC_MSG_ERROR([Please install autoconf-archive; re-run 'autoreconf -fi' for it to take effect.])
+	])
+
+dnl checks for libraries
+AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""])
+AC_SUBST(LIBRARY_DL)
+
+
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0)
+PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0)
+PKG_CHECK_MODULES(LIBOSMOGB, libosmogb >= 0.11.0)
+PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.0)
+PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.2.0)
+
+# Enabke/disable the NAT?
+AC_ARG_ENABLE([nat], [AS_HELP_STRING([--enable-nat], [Build the BSC NAT. Requires SCCP])],
+    [osmo_ac_build_nat="$enableval"],[osmo_ac_build_nat="no"])
+if test "$osmo_ac_build_nat" = "yes" ; then
+        PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.9.0)
+fi
+AM_CONDITIONAL(BUILD_NAT, test "x$osmo_ac_build_nat" = "xyes")
+AC_SUBST(osmo_ac_build_nat)
+
+# Enable/disable the BSC?
+AC_ARG_ENABLE([osmo-bsc], [AS_HELP_STRING([--enable-osmo-bsc], [Build the Osmo BSC])],
+    [osmo_ac_build_bsc="$enableval"],[osmo_ac_build_bsc="no"])
+if test "$osmo_ac_build_bsc" = "yes" ; then
+    PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.9.0)
+fi
+AM_CONDITIONAL(BUILD_BSC, test "x$osmo_ac_build_bsc" = "xyes")
+AC_SUBST(osmo_ac_build_bsc)
+
+# Enable/disable smpp support in the nitb?
+AC_ARG_ENABLE([smpp], [AS_HELP_STRING([--enable-smpp], [Build the SMPP interface])],
+    [osmo_ac_build_smpp="$enableval"],[osmo_ac_build_smpp="no"])
+if test "$osmo_ac_build_smpp" = "yes" ; then
+    PKG_CHECK_MODULES(LIBSMPP34, libsmpp34 >= 1.13.0)
+    AC_DEFINE(BUILD_SMPP, 1, [Define if we want to build SMPP])
+fi
+AM_CONDITIONAL(BUILD_SMPP, test "x$osmo_ac_build_smpp" = "xyes")
+AC_SUBST(osmo_ac_build_smpp)
+
+# Enable/disable transcoding within osmo-bsc_mgcp?
+AC_ARG_ENABLE([mgcp-transcoding], [AS_HELP_STRING([--enable-mgcp-transcoding], [Build the MGCP gateway with internal transcoding enabled.])],
+    [osmo_ac_mgcp_transcoding="$enableval"],[osmo_ac_mgcp_transcoding="no"])
+AC_ARG_WITH([g729], [AS_HELP_STRING([--with-g729], [Enable G.729 encoding/decoding.])], [osmo_ac_with_g729="$withval"],[osmo_ac_with_g729="no"])
+
+if test "$osmo_ac_mgcp_transcoding" = "yes" ; then
+    AC_CHECK_HEADERS([gsm.h gsm/gsm.h], [osmo_ac_found_gsm_headers=yes])
+    if test "$osmo_ac_found_gsm_headers" != "yes" ; then
+	AC_MSG_ERROR([Unable to find the libgsm headers])
+    fi
+    AC_SUBST(HAVE_GSM_H)
+    AC_SUBST(HAVE_GSM_GSM_H)
+
+    AC_SEARCH_LIBS([gsm_create], [gsm], [LIBRARY_GSM="$LIBS";LIBS=""], [AC_MSG_ERROR([--enable-mgcp-transcoding: cannot find usable libgsm])])
+    AC_SUBST(LIBRARY_GSM)
+    if test "$osmo_ac_with_g729" = "yes" ; then
+	PKG_CHECK_MODULES(LIBBCG729, libbcg729 >= 0.1, [AC_DEFINE([HAVE_BCG729], [1], [Use bgc729 decoder/encoder])])
+    fi
+    AC_DEFINE(BUILD_MGCP_TRANSCODING, 1, [Define if we want to build the MGCP gateway with transcoding support])
+fi
+AM_CONDITIONAL(BUILD_MGCP_TRANSCODING, test "x$osmo_ac_mgcp_transcoding" = "xyes")
+AC_SUBST(osmo_ac_mgcp_transcoding)
+
+# Enable/disable 3G aka IuPS + IuCS support?
+AC_ARG_ENABLE([iu], [AS_HELP_STRING([--enable-iu], [Build 3G support, aka IuPS and IuCS interfaces])],
+    [osmo_ac_iu="$enableval"],[osmo_ac_iu="no"])
+if test "x$osmo_ac_iu" = "xyes" ; then
+    PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.31)
+    PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 0.3.0)
+    PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 0.9.0)
+    AC_DEFINE(BUILD_IU, 1, [Define if we want to build IuPS and IuCS interfaces support])
+fi
+AM_CONDITIONAL(BUILD_IU, test "x$osmo_ac_iu" = "xyes")
+AC_SUBST(osmo_ac_iu)
+
+
+dnl checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS(dbi/dbd.h,,AC_MSG_ERROR(DBI library is not installed))
+
+found_pcap=yes
+AC_CHECK_HEADERS(pcap/pcap.h,,found_pcap=no)
+AM_CONDITIONAL(HAVE_PCAP, test "$found_pcap" = yes)
+
+found_cdk=yes
+AC_CHECK_HEADERS(cdk/cdk.h,,found_cdk=no)
+AM_CONDITIONAL(HAVE_LIBCDK, test "$found_cdk" = yes)
+
+found_sqlite3=yes
+PKG_CHECK_MODULES(SQLITE3, sqlite3, ,found_sqlite3=no)
+AM_CONDITIONAL(HAVE_SQLITE3, test "$found_sqlite3" = yes)
+AC_SUBST(found_sqlite3)
+
+
+dnl Checks for typedefs, structures and compiler characteristics
+
+AC_ARG_ENABLE(sanitize,
+	[AS_HELP_STRING(
+		[--enable-sanitize],
+		[Compile with address sanitizer enabled],
+	)],
+	[sanitize=$enableval], [sanitize="no"])
+if test x"$sanitize" = x"yes"
+then
+	CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
+	CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
+fi
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
+      [ AC_MSG_RESULT([yes])
+        SYMBOL_VISIBILITY="-fvisibility=hidden"],
+        AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+AX_CHECK_COMPILE_FLAG([-Werror=implicit], [CFLAGS="$CFLAGS -Werror=implicit"])
+AX_CHECK_COMPILE_FLAG([-Werror=maybe-uninitialized], [CFLAGS="$CFLAGS -Werror=maybe-uninitialized"])
+AX_CHECK_COMPILE_FLAG([-Werror=memset-transposed-args], [CFLAGS="$CFLAGS -Werror=memset-transposed-args"])
+AX_CHECK_COMPILE_FLAG([-Werror=null-dereference], [CFLAGS="$CFLAGS -Werror=null-dereference"])
+AX_CHECK_COMPILE_FLAG([-Werror=sizeof-array-argument], [CFLAGS="$CFLAGS -Werror=sizeof-array-argument"])
+AX_CHECK_COMPILE_FLAG([-Werror=sizeof-pointer-memaccess], [CFLAGS="$CFLAGS -Werror=sizeof-pointer-memaccess"])
+
+# Coverage build taken from WebKit's configure.in
+AC_MSG_CHECKING([whether to enable code coverage support])
+AC_ARG_ENABLE(coverage,
+              AC_HELP_STRING([--enable-coverage],
+                             [enable code coverage support [default=no]]),
+              [],[enable_coverage="no"])
+AC_MSG_RESULT([$enable_coverage])
+if test "$enable_coverage" = "yes"; then
+   COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs"
+   COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs"
+   AC_SUBST([COVERAGE_CFLAGS])
+   AC_SUBST([COVERAGE_LDFLAGS])
+fi
+
+AC_DEFUN([CHECK_TM_INCLUDES_TM_GMTOFF], [
+  AC_CACHE_CHECK(
+    [whether struct tm has tm_gmtoff member],
+    osmo_cv_tm_includes_tm_gmtoff,
+    [AC_LINK_IFELSE([
+      AC_LANG_PROGRAM([
+        #include <time.h>
+      ], [
+        time_t t = time(NULL);
+        struct tm* lt = localtime(&t);
+        int off = lt->tm_gmtoff;
+      ])
+    ],
+    osmo_cv_tm_includes_tm_gmtoff=yes,
+    osmo_cv_tm_includes_tm_gmtoff=no
+    )]
+  )
+  if test "x$osmo_cv_tm_includes_tm_gmtoff" = xyes; then
+    AC_DEFINE(HAVE_TM_GMTOFF_IN_TM, 1,
+              [Define if struct tm has tm_gmtoff member.])
+  fi
+])
+
+CHECK_TM_INCLUDES_TM_GMTOFF
+
+AC_ARG_ENABLE([vty_tests],
+		AC_HELP_STRING([--enable-vty-tests],
+				[Include the VTY/CTRL tests in make check (deprecated)
+				[default=no]]),
+		[enable_ext_tests="$enableval"],[enable_ext_tests="no"])
+AC_ARG_ENABLE([external_tests],
+		AC_HELP_STRING([--enable-external-tests],
+				[Include the VTY/CTRL tests in make check [default=no]]),
+		[enable_ext_tests="$enableval"],[enable_ext_tests="no"])
+if test "x$enable_ext_tests" = "xyes" ; then
+	AM_PATH_PYTHON
+	AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes)
+	 if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
+		AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
+	fi
+fi
+AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
+AC_MSG_RESULT([$enable_ext_tests])
+AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
+
+# https://www.freedesktop.org/software/systemd/man/daemon.html
+AC_ARG_WITH([systemdsystemunitdir],
+     [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
+     [with_systemdsystemunitdir=auto])
+AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
+     def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
+
+     AS_IF([test "x$def_systemdsystemunitdir" = "x"],
+   [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
+    [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
+    with_systemdsystemunitdir=no],
+   [with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
+      [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
+AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
+
+AC_MSG_RESULT([CFLAGS="$CFLAGS"])
+AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
+
+dnl Generate the output
+AM_CONFIG_HEADER(bscconfig.h)
+
+AC_OUTPUT(
+    openbsc.pc
+    include/openbsc/Makefile
+    include/Makefile
+    src/Makefile
+    src/libtrau/Makefile
+    src/libbsc/Makefile
+    src/libmsc/Makefile
+    src/libmgcp/Makefile
+    src/libcommon/Makefile
+    src/libfilter/Makefile
+    src/libiu/Makefile
+    src/libcommon-cs/Makefile
+    src/osmo-nitb/Makefile
+    src/osmo-bsc/Makefile
+    src/osmo-bsc_nat/Makefile
+    src/osmo-bsc_mgcp/Makefile
+    src/ipaccess/Makefile
+    src/utils/Makefile
+    tests/Makefile
+    tests/atlocal
+    tests/gsm0408/Makefile
+    tests/db/Makefile
+    tests/channel/Makefile
+    tests/bsc/Makefile
+    tests/bsc-nat/Makefile
+    tests/bsc-nat-trie/Makefile
+    tests/mgcp/Makefile
+    tests/abis/Makefile
+    tests/smpp/Makefile
+    tests/trau/Makefile
+    tests/subscr/Makefile
+    tests/mm_auth/Makefile
+    tests/nanobts_omlattr/Makefile
+    doc/Makefile
+    doc/examples/Makefile
+    contrib/Makefile
+    contrib/systemd/Makefile
+    Makefile)
diff --git a/openbsc/contrib/Makefile.am b/openbsc/contrib/Makefile.am
new file mode 100644
index 0000000..3439c97
--- /dev/null
+++ b/openbsc/contrib/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = systemd
diff --git a/openbsc/contrib/a-link/sccp-split-by-con.lua b/openbsc/contrib/a-link/sccp-split-by-con.lua
new file mode 100644
index 0000000..f5d5502
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/bsc-test/README b/openbsc/contrib/bsc-test/README
new file mode 100644
index 0000000..adb222e
--- /dev/null
+++ b/openbsc/contrib/bsc-test/README
@@ -0,0 +1 @@
+Some crazy scripts call testing... and MSC link failure simulation
diff --git a/openbsc/contrib/bsc-test/all_dial b/openbsc/contrib/bsc-test/all_dial
new file mode 100644
index 0000000..96e5f00
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/bsc-test/dial.sh b/openbsc/contrib/bsc-test/dial.sh
new file mode 100755
index 0000000..e5e19f6
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/bsc-test/drop-oml.sh b/openbsc/contrib/bsc-test/drop-oml.sh
new file mode 100755
index 0000000..84eead7
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/bsc-test/drop.sh b/openbsc/contrib/bsc-test/drop.sh
new file mode 100755
index 0000000..c7b66ba
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/bsc-test/hangup b/openbsc/contrib/bsc-test/hangup
new file mode 100644
index 0000000..cad6870
--- /dev/null
+++ b/openbsc/contrib/bsc-test/hangup
@@ -0,0 +1,4 @@
+TIMEOUT 10
+'' ^Z
+SAY "Waiting for hangup confirm\n"
+'' ATH;
diff --git a/openbsc/contrib/bsc-test/msc.sh b/openbsc/contrib/bsc-test/msc.sh
new file mode 100755
index 0000000..766603d
--- /dev/null
+++ b/openbsc/contrib/bsc-test/msc.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+while true;
+do
+	echo "Kill the osmo-bsc-sccplite"
+	/usr/bin/kill -s SIGUSR2 `pidof osmo-bsc-sccplite`
+	sleep 58s
+done
diff --git a/openbsc/contrib/bsc_control.py b/openbsc/contrib/bsc_control.py
new file mode 100755
index 0000000..c1b09ce
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/bt.py b/openbsc/contrib/bt.py
new file mode 100755
index 0000000..1b111ef
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/convert_to_enum.py b/openbsc/contrib/convert_to_enum.py
new file mode 100755
index 0000000..bcd6f2c
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/ctrl2sse.py b/openbsc/contrib/ctrl2sse.py
new file mode 100755
index 0000000..8b630ec
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/hlr-remove-old.sql b/openbsc/contrib/hlr-remove-old.sql
new file mode 100644
index 0000000..626a331
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/hlrsync/hlrsync.py b/openbsc/contrib/hlrsync/hlrsync.py
new file mode 100755
index 0000000..e4a4955
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/ipa.py b/openbsc/contrib/ipa.py
new file mode 100755
index 0000000..71cbf45
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/mgcp_server.py b/openbsc/contrib/mgcp_server.py
new file mode 100755
index 0000000..05c489d
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/nat/test_regexp.c b/openbsc/contrib/nat/test_regexp.c
new file mode 100644
index 0000000..808a703
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/nat/ussd_example.py b/openbsc/contrib/nat/ussd_example.py
new file mode 100644
index 0000000..8f7a58d
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/rtp/gen_rtp_header.erl b/openbsc/contrib/rtp/gen_rtp_header.erl
new file mode 100755
index 0000000..47839c1
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/rtp/rtp_replay.st b/openbsc/contrib/rtp/rtp_replay.st
new file mode 100644
index 0000000..e26d073
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/rtp/rtp_replay_shared.st b/openbsc/contrib/rtp/rtp_replay_shared.st
new file mode 100644
index 0000000..7b68c0f
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/rtp/rtp_replay_sip.st b/openbsc/contrib/rtp/rtp_replay_sip.st
new file mode 100644
index 0000000..5f844df
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/rtp/timestamp_rtp.lua b/openbsc/contrib/rtp/timestamp_rtp.lua
new file mode 100644
index 0000000..c18a06b
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/sms/fill-hlr.st b/openbsc/contrib/sms/fill-hlr.st
new file mode 100644
index 0000000..da0643e
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/sms/hlr-query.st b/openbsc/contrib/sms/hlr-query.st
new file mode 100644
index 0000000..bd3f97a
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/sms/sqlite-probe.tap.d b/openbsc/contrib/sms/sqlite-probe.tap.d
new file mode 100644
index 0000000..e75cdfc
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/soap.py b/openbsc/contrib/soap.py
new file mode 100755
index 0000000..4d0a023
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/systemd/Makefile.am b/openbsc/contrib/systemd/Makefile.am
new file mode 100644
index 0000000..69f973e
--- /dev/null
+++ b/openbsc/contrib/systemd/Makefile.am
@@ -0,0 +1,14 @@
+if HAVE_SYSTEMD
+SYSTEMD_SERVICES = osmo-nitb.service osmo-bsc-mgcp.service
+
+if BUILD_NAT
+SYSTEMD_SERVICES += osmo-bsc-nat.service
+endif
+
+if BUILD_BSC
+SYSTEMD_SERVICES += osmo-bsc-sccplite.service
+endif
+
+EXTRA_DIST = $(SYSTEMD_SERVICES)
+systemdsystemunit_DATA = $(SYSTEMD_SERVICES)
+endif # HAVE_SYSTEMD
diff --git a/openbsc/contrib/systemd/osmo-bsc-mgcp.service b/openbsc/contrib/systemd/osmo-bsc-mgcp.service
new file mode 100644
index 0000000..c040e60
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/systemd/osmo-bsc-nat.service b/openbsc/contrib/systemd/osmo-bsc-nat.service
new file mode 100644
index 0000000..245fc6b
--- /dev/null
+++ b/openbsc/contrib/systemd/osmo-bsc-nat.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Osmocom GSM BSC Multiplexer (NAT)
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-bsc_nat -c /etc/osmocom/osmo-bsc-nat.cfg
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/openbsc/contrib/systemd/osmo-bsc-sccplite.service b/openbsc/contrib/systemd/osmo-bsc-sccplite.service
new file mode 100644
index 0000000..3edd35c
--- /dev/null
+++ b/openbsc/contrib/systemd/osmo-bsc-sccplite.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=OpenBSC BSC (legacy, with SCCPLite)
+Wants=osmo-bsc-mgcp.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-bsc-sccplite -c /etc/osmocom/osmo-bsc-sccplite.cfg -s
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/openbsc/contrib/systemd/osmo-nitb.service b/openbsc/contrib/systemd/osmo-nitb.service
new file mode 100644
index 0000000..377497e
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/testconv/Makefile b/openbsc/contrib/testconv/Makefile
new file mode 100644
index 0000000..bb856f7
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/testconv/testconv_main.c b/openbsc/contrib/testconv/testconv_main.c
new file mode 100644
index 0000000..6c95c55
--- /dev/null
+++ b/openbsc/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/openbsc/contrib/twisted_ipa.py b/openbsc/contrib/twisted_ipa.py
new file mode 100755
index 0000000..e6d7b1a
--- /dev/null
+++ b/openbsc/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.")
diff --git a/openbsc/doc/BS11-OML.txt b/openbsc/doc/BS11-OML.txt
new file mode 100644
index 0000000..e5c3299
--- /dev/null
+++ b/openbsc/doc/BS11-OML.txt
@@ -0,0 +1,31 @@
+The Siemens BS-11 supports the following additional GSM 12.21 OML operations:
+
+
+CREATE OBJECT
+
+abis_om_fom_hdr.obj_class can be 
+A3:
+A5: ALCO, BBSIG, CCLK, GPSU, LI, PA
+A8: EnvaBTSE
+A9: BPORT
+
+the abis_om_obj_inst.trx_nr field indicates the index of object, whereas the
+abis_om_fom_hdr.bts_nr indicates the type of the object.
+
+enum abis_bs11_objtype {
+	BS11_OBJ_ALCO		= 0x01,
+	BS11_OBJ_BBSIG		= 0x02,	/* obj_class: 0,1 */
+	BS11_OBJ_TRX1		= 0x03,	/* only DEACTIVATE TRX1 */
+	BS11_OBJ_CCLK		= 0x04,
+	BS11_OBJ_GPSU		= 0x06,
+	BS11_OBJ_LI		= 0x07,
+	BS11_OBJ_PA		= 0x09,	/* obj_class: 0, 1*/
+};
+
+In case of CREATE ENVABTSE, the abis_om_obj_inst.trx_nr indicates the EnvaBTSEx
+number.
+
+In case of A9 (CREAETE BPORT), the abis_om_obj_inst.bts_nr indicates which BPORT
+shall be used.
+
+
diff --git a/openbsc/doc/Makefile.am b/openbsc/doc/Makefile.am
new file mode 100644
index 0000000..5a23107
--- /dev/null
+++ b/openbsc/doc/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = \
+	examples \
+	$(NULL)
diff --git a/openbsc/doc/call-routing.txt b/openbsc/doc/call-routing.txt
new file mode 100644
index 0000000..3402f9e
--- /dev/null
+++ b/openbsc/doc/call-routing.txt
@@ -0,0 +1,25 @@
+Call routing in OpenBSC
+
+Flow of events:
+
+ # MO call initiated by MS, CHANNEL RQD, IMMEDIATE ASSIGN
+ # MS sends CC SETUP message, we assume already on TCH/H FACCH
+ # OpenBSC does a subscriber lookup based on the target extension
+  * If a subscriber is found:
+   # send CALL PROCEEDING message to MO
+   # page the MT subscriber and ask itI to ask for TCH/H
+   # once paging completes, we have the TCH/H for the MT end
+   # send SETUP to MT
+   # receive CALL CONFIRMED from MT
+   # set-up the TRAU mux mapping between the E1 subslots for both TCH/H
+   # receive ALERTING from MT, route ALERTING to MO
+   # receive CONNECT from MT, confirm to MT with CONNECT_ACK
+   # send a CONNECT message to MO, receive CONNECT_ACK from MO
+ * If subscriber is not found:
+  # send RELEASE COMPLETE with apropriate cause to MO (1: unalloacated 3: no route)
+  
+
+
+Thoughts about RR/MM:
+
+* we allocate RR/MM entities on demand, when we need them
diff --git a/openbsc/doc/channel_release.txt b/openbsc/doc/channel_release.txt
new file mode 100644
index 0000000..c9cdfeb
--- /dev/null
+++ b/openbsc/doc/channel_release.txt
@@ -0,0 +1,95 @@
+
+GSM 04.08 7.1.7 / 9.1.7		RR CHANNEL RELESE
+
+RSL 08.58 3.4 / ?		RLL Link Release Request
+
+RSL 08.58 4.6 / 8.4.5		DEACTivate SACCH
+	* Deactivate SACCH according to Channel Release Proc 04.08
+	* to be sent after RR CHANNEL RELEASE is sent to MS
+
+RSL 08.58 4.7 / 8.4.14		RF CHANnel RELease
+	* tells the BTS to release a radio channel
+	* "when an activated radio channel is no longer needed"
+	* BTS responds with RF CHANnel RELease ACKnowledge
+
+
+GSM 04.08 3.4.13: RR connection release procedure
+
+* network sends RR CHANNEL RELEASE to MS on the DCCH
+  * start T3109
+  * deactivate SACCH
+* MS disconnects main signalling link (by sending DISC)
+  * all other data links are disconnected by local end link release
+* network receives DISC (BTS sends RLL REL IND to BSC)
+  * stop T3109
+  * start T3111 
+* when T3111 times out, the network can reuse the channls
+* if T3109 times out, the network deactivates the channels 
+  and can reuse them
+  * this probably means simply RF CHANnel RELease
+
+
+== Implementation in OpenBSC ==
+
+There are two possible reasons a gsm_subscriber_connection
+will be released. One is a network failure, the other is
+the completion of an operation/transaction.
+
+=== Failure ===
+The BSC API will call the gsm_04_08.c:gsm0408_clear_request callback
+and the MSC part will release all transactions, operations and such
+and the channels will be released as error case.
+
+=== Success ===
+Every time an 'operation' or 'transaction' is finished msc_release_connection
+will be called and it will determine if the gsm_subscriber_connection can
+be released.
+
+In case it can be released bsc_api.c:gsm0808_clear will be called
+which will release all lchan's associated with the connection. For the
+primary channel a SACH Deactivate will be send with the release
+reason NORMAL RELEASE.
+
+
+bsc_api.c:gsm0808_clear
+	* Release a channel used for handover
+	* Release the primary lchan with normal release, SACH deactivate
+
+chan_alloc.c:lchan_release(chan, sacch_deactivate, reason)
+	* Start the release procedure. It is working in steps with callbacks
+	  coming from the abis_rsl.c code.
+	* Release all SAPI's > 0 as local end (The BTS should send a
+	  REL_CONF a message)
+	* Send SACH Deactivate on SAPI=0 if required.
+	* Start T3109 (stop it when the main signalling link is disconnected)
+	  or when the channel released. On timeout start the error handling.
+	* abis_rsl.c schedules the RSL_MT_RF_CHAN_REL once all SAPI's are
+	  released and after T3111 has timed out or there is an error.
+
+RX of RELease INDication:
+        * Calls internal rsl_handle_release which might release the RF.
+
+RX of RELease CONFirmation:
+        * Calls internal rsl_handle_release which might release the RF.
+
+* RX of RF_CHAN_REL_ACK
+	* call lchan_free()
+
+
+=== Integration with SMS ===
+
+* RX of CP_ERROR or unimplemented MT
+	* trigger trans_free() which will msc_release_connection()
+
+* CP TC1* expired while waiting for CP-ACK
+	* trigger trans_free() which will msc_release_connection()
+
+* RX of RP_ERROR
+	* trigger trans_free() which will msc_release_connection()
+	
+* TX of CP-ACK in MT DELIVER
+	* trigger trans_free() which will msc_release_connection()
+
+* RX of CP-ACK in MO SUBMIT
+	* trigger trans_free() which will msc_release_connection()
+	
diff --git a/openbsc/doc/e1-data-model.txt b/openbsc/doc/e1-data-model.txt
new file mode 100644
index 0000000..509004f
--- /dev/null
+++ b/openbsc/doc/e1-data-model.txt
@@ -0,0 +1,172 @@
+E1 related data model
+
+This data model describes the physical relationship of the individual
+parts in the network, it is not the logical/protocol side of the GSM
+network.
+
+A BTS is connected to the BSC by some physical link.  It could be an actual
+E1 link, but it could also be abis-over-IP with a mixture of TCP and RTP/UDP.
+
+To further complicate the fact, multiple BTS can share one such pysical
+link.  On a single E1 line, we can easily accomodate up to three BTS with
+two TRX each.
+
+Thus, it is best for OpenBSC to have some kind of abstraction layer.  The BSC's
+view of a BTS connected to it.  We call this 'bts_link'.  A bts_link can be
+* all the TCP and UDP streams of a Abis-over-IP BTS
+* a set of E1 timeslots for OML, RSL and TRAU connections on a E1 link
+* a serial line exclusively used for OML messages (T-Link)
+
+A bts_link can be registered with the OpenBSC core at runtime.
+
+struct trx_link {
+	struct gsm_bts_trx *trx;
+};
+
+struct bts_link {
+	struct gsm_bts *bts;
+	struct trx_link trx_links[NUM_TRX];
+};
+
+Interface from stack to input core:
+======================================================================
+int abis_rsl_sendmsg(struct msgb *msg);
+	send a message through a RSL link to the TRX specified by the caller in
+	msg->trx.
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+	receive a message from a RSL link from the TRX specified by the
+	caller in msg->trx.
+
+int abis_nm_sendmsg(struct msgb *msg);
+	send a message through a OML link to the BTS specified by the caller in
+	msg->trx->bts.  The caller can just use bts->c0 to get the first TRX
+	in a BTS. (OML messages are not really sent to a TRX but to the BTS)
+
+int abis_nm_rcvmsg(struct msgb *msg);
+	receive a message from a OML link from the BTS specified by the caller
+	in msg->trx->bts.  The caller can just use bts->c0 to get the first
+	TRX in a BTS.
+
+int abis_link_event(int event, void *data);
+	signal some event (such as layer 1 connect/disconnect) from the
+	input core to the stack.
+
+int subch_demux_in(mx, const uint8_t *data, int len);
+	receive 'len' bytes from a given E1 timeslot (TRAU frames)
+
+int subchan_mux_out(mx, uint8_t *data, int len);
+	obtain 'len' bytes of output data to be sent on E1 timeslot
+
+Intrface by Input Core for Input Plugins
+======================================================================
+
+int btslink_register_plugin();
+
+
+Configuration for the E1 input module
+======================================================================
+
+BTS
+	BTS number
+	number of TRX
+	OML link
+		E1 line number
+		timeslot number
+		[subslot number]
+		SAPI
+		TEI
+	for each TRX
+		RSL link
+			E1 line number
+			timeslot number
+			[subslot number]
+			SAPI
+			TEI
+		for each TS
+			E1 line number
+			timeslot number
+			subslot number
+
+
+E1 input module data model
+======================================================================
+
+
+enum e1inp_sign_type {
+	E1INP_SIGN_NONE,
+	E1INP_SIGN_OML,
+	E1INP_SIGN_RSL,
+};
+
+struct e1inp_sign_link {
+	/* list of signalling links */
+	struct llist_head list;
+
+	enum e1inp_sign_type type;
+
+	/* trx for msg->trx of received msgs */	
+	struct gsm_bts_trx *trx;
+
+	/* msgb queue of to-be-transmitted msgs */
+	struct llist_head tx_list;
+
+	/* SAPI and TEI on the E1 TS */
+	uint8_t sapi;
+	uint8_t tei;
+}
+
+enum e1inp_ts_type {
+	E1INP_TS_TYPE_NONE,
+	E1INP_TS_TYPE_SIGN,
+	E1INP_TS_TYPE_TRAU,
+};
+
+/* A timeslot in the E1 interface */
+struct e1inp_ts {
+	enum e1inp_ts_type type;
+	struct e1inp_line *line;
+	union {
+		struct {
+			struct llist_head sign_links;
+		} sign;
+		struct {
+			/* subchannel demuxer for frames from E1 */
+			struct subch_demux demux;
+			/* subchannel muxer for frames to E1 */
+			struct subch_mux mux;
+		} trau;
+	};
+	union {
+		struct {
+			/* mISDN driver has one fd for each ts */
+			struct osmo_fd;
+		} misdn;
+	} driver;
+};
+
+struct e1inp_line {
+	unsigned int num;
+	char *name;
+
+	struct e1inp_ts ts[NR_E1_TS];
+
+	char *e1inp_driver;
+	void *driver_data;
+};
+
+/* Call from the Stack: configuration of this TS has changed */
+int e1inp_update_ts(struct e1inp_ts *ts);
+
+/* Receive a packet from the E1 driver */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+		uint8_t tei, uint8_t sapi);
+
+/* Send a packet, callback function in the driver */
+int e1driver_tx_ts(struct e1inp_ts *ts, struct msgb *msg)
+
+
+struct e1inp_driver {
+	const char *name;
+	int (*want_write)(struct e1inp_ts *ts);
+};
diff --git a/openbsc/doc/examples/Makefile.am b/openbsc/doc/examples/Makefile.am
new file mode 100644
index 0000000..2b8336f
--- /dev/null
+++ b/openbsc/doc/examples/Makefile.am
@@ -0,0 +1,40 @@
+
+OSMOCONF_FILES = \
+	osmo-nitb/sysmobts/osmo-nitb.cfg \
+	osmo-bsc_mgcp/osmo-bsc-mgcp.cfg
+
+if BUILD_NAT
+OSMOCONF_FILES += osmo-bsc_nat/osmo-bsc-nat.cfg
+endif
+
+if BUILD_BSC
+OSMOCONF_FILES += osmo-bsc-sccplite/osmo-bsc-sccplite.cfg
+endif
+
+osmoconfdir = $(sysconfdir)/osmocom
+osmoconf_DATA = $(OSMOCONF_FILES)
+
+EXTRA_DIST = $(OSMOCONF_FILES)
+
+CFG_FILES = find $(srcdir) -name '*.cfg*' | sed -e 's,^$(srcdir),,'
+
+dist-hook:
+	for f in $$($(CFG_FILES)); do \
+		j="$(distdir)/$$f" && \
+		mkdir -p "$$(dirname $$j)" && \
+		$(INSTALL_DATA) $(srcdir)/$$f $$j; \
+	done
+
+install-data-hook:
+	for f in $$($(CFG_FILES)); do \
+		j="$(DESTDIR)$(docdir)/examples/$$f" && \
+		mkdir -p "$$(dirname $$j)" && \
+		$(INSTALL_DATA) $(srcdir)/$$f $$j; \
+	done
+
+uninstall-hook:
+	@$(PRE_UNINSTALL)
+	for f in $$($(CFG_FILES)); do \
+		j="$(DESTDIR)$(docdir)/examples/$$f" && \
+		$(RM) $$j; \
+	done
diff --git a/openbsc/doc/examples/osmo-bsc-sccplite/osmo-bsc-sccplite.cfg b/openbsc/doc/examples/osmo-bsc-sccplite/osmo-bsc-sccplite.cfg
new file mode 100644
index 0000000..ab23a76
--- /dev/null
+++ b/openbsc/doc/examples/osmo-bsc-sccplite/osmo-bsc-sccplite.cfg
@@ -0,0 +1,94 @@
+!
+! OsmoBSC (0.9.14+gitr1+3d331c0062bb0c9694dbd4d1eab7adc58138c3ae) configuration saved from vty
+!!
+password foo
+!
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver ipa
+network
+ network country code 1
+ mobile network code 1
+ short name OsmoBSC
+ long name OsmoBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 1
+ paging any use tch 0
+ rrlp mode none
+ mm info 1
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ subscriber-keep-in-ram 0
+ bts 0
+  type nanobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  dtx uplink force
+  dtx downlink
+  ip.access unit_id 0 0
+  oml ip.access stream_id 255 line 0
+  neighbor-list mode manual-si5
+  neighbor-list add arfcn 100
+  neighbor-list add arfcn 200
+  si5 neighbor-list add arfcn 10
+  si5 neighbor-list add arfcn 20
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 871
+   nominal power 23
+   max_power_red 20
+   rsl e1 tei 0
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    hopping enabled 0
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 0
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 0
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 0
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 0
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 0
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 0
+msc
+ ip.access rtp-base 4000
+ timeout-ping 20
+ timeout-pong 5
+ dest 192.168.100.11 6666 0
+ access-list-name msc-list
+ no access-list-name
+bsc
+ no access-list-name
+ access-list-name bsc-list
diff --git a/openbsc/doc/examples/osmo-bsc_mgcp/osmo-bsc-mgcp.cfg b/openbsc/doc/examples/osmo-bsc_mgcp/osmo-bsc-mgcp.cfg
new file mode 100644
index 0000000..67f288e
--- /dev/null
+++ b/openbsc/doc/examples/osmo-bsc_mgcp/osmo-bsc-mgcp.cfg
@@ -0,0 +1,19 @@
+!
+! MGCP configuration hand edited
+!   !
+password foo
+!
+line vty
+ no login
+!
+mgcp
+  !local ip 213.167.134.14
+  !bts ip 172.16.252.43
+  !bind ip 127.0.0.1
+  bind port 2427
+  rtp base 4000
+  rtp force-ptime 20
+  sdp audio payload number 98
+  sdp audio payload name AMR/8000
+  number endpoints 31
+  no rtcp-omit
diff --git a/openbsc/doc/examples/osmo-bsc_nat/black-list.cfg b/openbsc/doc/examples/osmo-bsc_nat/black-list.cfg
new file mode 100644
index 0000000..d36179d
--- /dev/null
+++ b/openbsc/doc/examples/osmo-bsc_nat/black-list.cfg
@@ -0,0 +1 @@
+678012512671923:6:6:
diff --git a/openbsc/doc/examples/osmo-bsc_nat/bscs.config b/openbsc/doc/examples/osmo-bsc_nat/bscs.config
new file mode 100644
index 0000000..176debe
--- /dev/null
+++ b/openbsc/doc/examples/osmo-bsc_nat/bscs.config
@@ -0,0 +1,13 @@
+nat
+ bsc 0
+  token lol
+  location_area_code 1234
+  description bsc
+  max-endpoints 32
+  paging forbidden 0
+ bsc 1
+  token wat
+  location_area_code 5678
+  description bsc
+  max-endpoints 32
+  paging forbidden 0
diff --git a/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc-nat.cfg b/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc-nat.cfg
new file mode 100644
index 0000000..6b48e97
--- /dev/null
+++ b/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc-nat.cfg
@@ -0,0 +1,66 @@
+!
+! OsmoBSCNAT (0.12.0.266-2daa9) configuration saved from vty
+!!
+!
+log stderr
+  logging filter all 1
+  logging color 1
+  logging timestamp 0
+  logging level all debug
+  logging level rll notice
+  logging level cc notice
+  logging level mm notice
+  logging level rr notice
+  logging level rsl notice
+  logging level nm info
+  logging level mncc notice
+  logging level pag notice
+  logging level meas notice
+  logging level sccp notice
+  logging level msc notice
+  logging level mgcp notice
+  logging level ho notice
+  logging level db notice
+  logging level ref notice
+  logging level gprs debug
+  logging level ns info
+  logging level bssgp debug
+  logging level llc debug
+  logging level sndcp debug
+  logging level nat notice
+  logging level ctrl notice
+  logging level smpp debug
+  logging level lglobal notice
+  logging level llapd notice
+  logging level linp notice
+  logging level lmux notice
+  logging level lmi notice
+  logging level lmib notice
+  logging level lsms notice
+!
+line vty
+ no login
+!
+mgcp
+  bind ip 0.0.0.0
+  bind port 2427
+  rtp bts-base 4000
+  rtp net-base 16000
+  rtp ip-dscp 0
+  no rtcp-omit
+  sdp audio-payload number 126
+  sdp audio-payload name AMR/8000
+  loop 0
+  number endpoints 1
+  call-agent ip 127.0.0.1
+  rtp transcoder-base 0
+  transcoder-remote-base 4000
+nat
+ msc ip 127.0.0.1
+ msc port 5000
+ timeout auth 2
+ timeout ping 20
+ timeout pong 5
+ ip-dscp 0
+ bscs-config-file bscs.config
+ access-list bla imsi-allow ^11$
diff --git a/openbsc/doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx-hopping.cfg b/openbsc/doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx-hopping.cfg
new file mode 100644
index 0000000..dbb9cff
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx-hopping.cfg
@@ -0,0 +1,153 @@
+!
+! OpenBSC (0.9.0.845-57c4) configuration saved from vty
+!!
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver misdn
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 1
+ rrlp mode none
+ mm info 0
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator descending
+  rach tx integer 9
+  rach max transmission 7
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 121
+   nominal power 24
+   max_power_red 12
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config CCCH
+    hopping enabled 0
+    e1 line 0 timeslot 1 sub-slot full
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 2 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 2 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 2 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 3
+  trx 1
+   rf_locked 0
+   arfcn 119
+   nominal power 24
+   max_power_red 12
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 2
+   timeslot 0
+    phys_chan_config SDCCH8
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 4 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 4 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 4 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 5 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 5 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 5 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 1
+    hopping sequence-number 0
+    hopping maio 0
+    hopping arfcn add 117
+    hopping arfcn add 119
+    e1 line 0 timeslot 5 sub-slot 3
diff --git a/openbsc/doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx.cfg b/openbsc/doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx.cfg
new file mode 100644
index 0000000..02ff4b8
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx.cfg
@@ -0,0 +1,82 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver misdn
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 1
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 121
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    e1 line 0 timeslot 1 sub-slot full
+   timeslot 1
+    phys_chan_config SDCCH8
+    e1 line 0 timeslot 2 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 3
+  trx 1
+   arfcn 123
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 2
+   timeslot 0
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 3
diff --git a/openbsc/doc/examples/osmo-nitb/bs11/openbsc-2bts-2trx.cfg b/openbsc/doc/examples/osmo-nitb/bs11/openbsc-2bts-2trx.cfg
new file mode 100644
index 0000000..47b8d46
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/bs11/openbsc-2bts-2trx.cfg
@@ -0,0 +1,146 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 1
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 121
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    e1 line 0 timeslot 1 sub-slot full
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 3
+  trx 1
+   arfcn 123
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 2
+   timeslot 0
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 4 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 5 sub-slot 3
+ bts 1
+  type bs11
+  band GSM900
+  location_area_code 2
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 1 timeslot 6 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 122
+   max_power_red 0
+   rsl e1 line 1 timeslot 6 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    e1 line 1 timeslot 7 sub-slot 0
+   timeslot 1
+    phys_chan_config SDCCH8
+    e1 line 1 timeslot 7 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 7 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 7 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 8 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 8 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 8 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 8 sub-slot 3
+  trx 1
+   arfcn 124
+   max_power_red 0
+   rsl e1 line 1 timeslot 6 sub-slot full
+   rsl e1 tei 2
+   timeslot 0
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 9 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 9 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 9 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 9 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 10 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 10 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 10 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 1 timeslot 10 sub-slot 3
diff --git a/openbsc/doc/examples/osmo-nitb/bs11/osmo-nitb.cfg b/openbsc/doc/examples/osmo-nitb/bs11/osmo-nitb.cfg
new file mode 100644
index 0000000..bcb0b98
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/bs11/osmo-nitb.cfg
@@ -0,0 +1,54 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver misdn
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 1
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 121
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    e1 line 0 timeslot 1 sub-slot full
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 2 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 3 sub-slot 3
+
diff --git a/openbsc/doc/examples/osmo-nitb/nanobts/openbsc-multitrx.cfg b/openbsc/doc/examples/osmo-nitb/nanobts/openbsc-multitrx.cfg
new file mode 100644
index 0000000..99f2653
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/nanobts/openbsc-multitrx.cfg
@@ -0,0 +1,88 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver ipa
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 1
+ rrlp mode none
+ mm info 0
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ bts 0
+  type nanobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1800 0
+  oml ip.access stream_id 255 line 0
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 871
+   nominal power 23
+   max_power_red 0
+   rsl e1 tei 0
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+   timeslot 1
+    phys_chan_config SDCCH8
+   timeslot 2
+    phys_chan_config TCH/F
+   timeslot 3
+    phys_chan_config TCH/F
+   timeslot 4
+    phys_chan_config TCH/F
+   timeslot 5
+    phys_chan_config TCH/F
+   timeslot 6
+    phys_chan_config TCH/F
+   timeslot 7
+    phys_chan_config TCH/F
+  trx 1
+   rf_locked 0
+   arfcn 873
+   nominal power 23
+   max_power_red 0
+   rsl e1 tei 0
+   timeslot 0
+    phys_chan_config TCH/F
+   timeslot 1
+    phys_chan_config SDCCH8
+   timeslot 2
+    phys_chan_config TCH/F
+   timeslot 3
+    phys_chan_config TCH/F
+   timeslot 4
+    phys_chan_config TCH/F
+   timeslot 5
+    phys_chan_config TCH/F
+   timeslot 6
+    phys_chan_config TCH/F
+   timeslot 7
+    phys_chan_config TCH/F
diff --git a/openbsc/doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg b/openbsc/doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg
new file mode 100644
index 0000000..c5e7be8
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg
@@ -0,0 +1,66 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver ipa
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 1
+ rrlp mode none
+ mm info 1
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ bts 0
+  type nanobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1801 0
+  oml ip.access stream_id 255 line 0
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 514
+   nominal power 23
+   max_power_red 20
+   rsl e1 tei 0
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+   timeslot 1
+    phys_chan_config SDCCH8
+   timeslot 2
+    phys_chan_config TCH/F
+   timeslot 3
+    phys_chan_config TCH/F
+   timeslot 4
+    phys_chan_config TCH/F
+   timeslot 5
+    phys_chan_config TCH/F
+   timeslot 6
+    phys_chan_config TCH/F
+   timeslot 7
+    phys_chan_config TCH/F
diff --git a/openbsc/doc/examples/osmo-nitb/nokia/openbsc_nokia_3trx.cfg b/openbsc/doc/examples/osmo-nitb/nokia/openbsc_nokia_3trx.cfg
new file mode 100644
index 0000000..1906991
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/nokia/openbsc_nokia_3trx.cfg
@@ -0,0 +1,115 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver misdn
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ bts 0
+  type nokia_site
+  band GSM1800
+  cell_identity 1
+  location_area_code 1
+  base_station_id_code 63
+  training_sequence_code 7
+  
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 1
+  
+  trx 0
+   arfcn 866
+   max_power_red 24
+   rsl e1 line 0 timeslot 2 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    e1 line 0 timeslot 6 sub-slot full
+   timeslot 1
+    phys_chan_config SDCCH8
+    e1 line 0 timeslot 6 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 6 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 6 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 7 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 7 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 7 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 7 sub-slot 3
+
+  trx 1
+   arfcn 870
+   max_power_red 24
+   rsl e1 line 0 timeslot 3 sub-slot full
+   rsl e1 tei 2
+   timeslot 0
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 8 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 8 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 8 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 8 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 9 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 9 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 9 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 9 sub-slot 3
+
+  trx 2
+   arfcn 874
+   max_power_red 24
+   rsl e1 line 0 timeslot 4 sub-slot full
+   rsl e1 tei 3
+   timeslot 0
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 10 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 10 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 10 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 10 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 11 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 11 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 11 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    e1 line 0 timeslot 11 sub-slot 3
diff --git a/openbsc/doc/examples/osmo-nitb/rbs2308/osmo-nitb.cfg b/openbsc/doc/examples/osmo-nitb/rbs2308/osmo-nitb.cfg
new file mode 100644
index 0000000..d783796
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/rbs2308/osmo-nitb.cfg
@@ -0,0 +1,208 @@
+!
+! OpenBSC (0.9.11.308-62d46) configuration saved from vty
+!!
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 262
+ mobile network code 42
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 0
+ paging any use tch 0
+ rrlp mode none
+ mm info 0
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ subscriber-keep-in-ram 0
+ bts 0
+  type rbs2000
+  band GSM900
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator descending
+  rach tx integer 9
+  rach max transmission 7
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 62
+  neighbor-list mode automatic
+  gprs mode none
+  is-connection-list add 4 512 12
+  is-connection-list add 16 524 12
+  is-connection-list add 28 536 12
+  is-connection-list add 40 548 12
+  trx 0
+   rf_locked 0
+   arfcn 55
+   nominal power 24
+   max_power_red 12
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 0
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+    hopping enabled 0
+    e1 line 0 timeslot 1 sub-slot full
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 2 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 2 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 2 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 3 sub-slot 3
+  trx 1
+   rf_locked 0
+   arfcn 57
+   nominal power 24
+   max_power_red 12
+   rsl e1 line 0 timeslot 4 sub-slot full
+   rsl e1 tei 1
+   timeslot 0
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 5 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 5 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 5 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 5 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 6 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 6 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 6 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 6 sub-slot 3
+  trx 2
+   rf_locked 0
+   arfcn 59
+   nominal power 24
+   max_power_red 12
+   rsl e1 line 0 timeslot 7 sub-slot full
+   rsl e1 tei 2
+   timeslot 0
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 8 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 8 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 8 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 8 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 9 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 9 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 9 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 9 sub-slot 3
+  trx 3
+   rf_locked 0
+   arfcn 61
+   nominal power 24
+   max_power_red 12
+   rsl e1 line 0 timeslot 10 sub-slot full
+   rsl e1 tei 3
+   timeslot 0
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 11 sub-slot 0
+   timeslot 1
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 11 sub-slot 1
+   timeslot 2
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 11 sub-slot 2
+   timeslot 3
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 11 sub-slot 3
+   timeslot 4
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 12 sub-slot 0
+   timeslot 5
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 12 sub-slot 1
+   timeslot 6
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 12 sub-slot 2
+   timeslot 7
+    phys_chan_config TCH/F
+    hopping enabled 0
+    e1 line 0 timeslot 12 sub-slot 3
+
+e1_input
+ e1_line 0 driver dahdi
diff --git a/openbsc/doc/examples/osmo-nitb/sysmobts/osmo-nitb.cfg b/openbsc/doc/examples/osmo-nitb/sysmobts/osmo-nitb.cfg
new file mode 100644
index 0000000..7cd5d1f
--- /dev/null
+++ b/openbsc/doc/examples/osmo-nitb/sysmobts/osmo-nitb.cfg
@@ -0,0 +1,66 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver ipa
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 1
+ rrlp mode none
+ mm info 1
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ bts 0
+  type sysmobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1801 0
+  oml ip.access stream_id 255 line 0
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 514
+   nominal power 23
+   max_power_red 20
+   rsl e1 tei 0
+   timeslot 0
+    phys_chan_config CCCH+SDCCH4
+   timeslot 1
+    phys_chan_config SDCCH8
+   timeslot 2
+    phys_chan_config TCH/F
+   timeslot 3
+    phys_chan_config TCH/F
+   timeslot 4
+    phys_chan_config TCH/F
+   timeslot 5
+    phys_chan_config TCH/F
+   timeslot 6
+    phys_chan_config TCH/F
+   timeslot 7
+    phys_chan_config TCH/F
diff --git a/openbsc/doc/gsm-hopping.txt b/openbsc/doc/gsm-hopping.txt
new file mode 100644
index 0000000..c964963
--- /dev/null
+++ b/openbsc/doc/gsm-hopping.txt
@@ -0,0 +1,54 @@
+according to GSM 05.02:
+
+general parameters from CCCH:
+* CA cell allocation of ARFCN's (System Information / BCCH)
+* FN: TDMA frame number (t1,t2,t3') in SCH
+
+specific parameters from channel assignment:
+* MA: mobile allocation, defines set of ARFCN's, up to 64
+* MAIO: index
+* HSN: hopping sequence generator number (0..64)
+
+
+hopping sequence generation (6.2.3):
+
+uint8_t rntable[114] = {
+	 48,  98,  63,   1,  36,  95,  78, 102,  94,  73,
+	  0,  64,  25,  81,  76,  59, 124,  23, 104, 100,
+	101,  47, 118,  85,  18,  56,  96,  86,  54,   2,
+	 80,  34, 127,  13,   6,  89,  57, 103,  12,  74,
+	 55, 111,  75,  38, 109,  71, 112,  29,  11,  88,
+	 87,  19,   3,  68, 110,  26,  33,  31,   8,  45,
+	 82,  58,  40, 107,  32,   5, 106,  92,  62,  67,
+	 77, 108, 122,  37,  60,  66, 121,  42,  51, 126,
+	117, 114,   4,  90,  43,  52,  53, 113, 120,  72,
+	 16,  49,   7,  79, 119,  61,  22,  84,   9,  97,
+	125,  99,  17, 123
+};
+
+/* mai=0 represents lowest ARFCN in the MA */
+
+
+uint8_t hopping_mai(uint8_t hsn, uint32_t fn, uint8_t maio,
+		     uint8_t t1, uint8_t t2, uint8_t t3_)
+{
+	uint8_t mai;
+
+	if (hsn == 0) /* cyclic hopping */
+		mai = (fn + maio) % n;
+	else {
+		uint32_t m, m_, t_, s;
+
+		m = t2 + rntable[(hsn xor (t1 % 64)) + t3];
+		m_ = m % (2^NBIN);
+		t_ = t3 % (2^NBIN);
+		if (m_ < n then)
+			s = m_;
+		else
+			s = (m_ + t_) % n;
+		mai = (s + maio) % n;
+	}
+
+	return mai;
+}
+
diff --git a/openbsc/doc/handover.txt b/openbsc/doc/handover.txt
new file mode 100644
index 0000000..ac19e87
--- /dev/null
+++ b/openbsc/doc/handover.txt
@@ -0,0 +1,89 @@
+Ideas about a handover algorithm
+======================================================================
+
+This is mostly based on the results presented in Chapter 8 of "Performance
+Enhancements in a Frequency Hopping GSM Network" by Thomas Toftegaard Nielsen
+and Joeroen Wigard. 
+
+
+=== Reasons for performing handover ===
+
+Section 2.1.1: Handover used in their CAPACITY simulation:
+
+1) Interference Handover
+
+Average RXLEV is satisfactory high, but average RXQUAL too low indicates
+interference to the channel.  Handover should be made.
+
+2) Bad Quality
+
+Averaged RXQUAL is lower than a threshold
+
+3) Low Level / Signal Strength
+
+Average RXLEV is lower than a threshold
+
+4) Distance Handover
+
+MS is too far away from a cell (measured by TA)
+
+5) Power budget / Better Cell
+
+RX Level of neighbor cell is at least "HO Margin dB" dB better than the
+current serving cell.
+
+=== Ideal parameters for HO algorithm ===
+
+Chapter 8, Section 2.2, Table 24:
+
+Window RXLEV averaging:		10 SACCH frames (no weighting)
+Window RXQUAL averaging:	1 SACCH frame (no averaging)
+Level Threashold:		1 of the last 1 AV-RXLEV values < -110dBm
+Quality Threshold:		3 of the last 4 AV-RXQUAL values >= 5
+Interference Threshold:		1 of the last AV-RXLEV > -85 dBm &
+				3 of the last 4 AV-RXQUAL values >= 5
+Power Budget:			Level of neighbor cell > 3 dB better
+Power Budget Interval:		Every 6 SACCH frames (6 seconds ?!?)
+Distance Handover:		Disabled
+Evaluation rule 1:		RXLEV of the candidate cell a tleast -104 dBm
+Evaluation rule 2:		Level of candidate cell > 3dB better own cell
+Timer Successful HO:		5 SACCH frames
+Timer Unsuccessful HO:		1 SACCH frame
+
+In a non-frequency hopping case, RXQUAL threshold can be decreased to
+RXLEV >= 4
+
+When frequency hopping is enabled, the following additional parameters
+should be introduced:
+
+* No intra-cell handover
+* Use a HO Margin of 2dB
+
+=== Handover Channel Reservation ===
+
+In loaded network, each cell should reserve some channels for handovers,
+rather than using all of them for new call establishment.  This reduces the
+need to drop calls due to failing handovers, at the expense of failing new call
+attempts.
+
+=== Dynamic HO Margin ===
+
+The handover margin (hysteresis) should depend on the RXQUAL. Optimal results
+were achieved with the following settings:
+* RXQUAL <= 4: 9 dB
+* RXQUAL == 5: 6 dB
+* RXQUAL >= 6: 1 dB
+
+
+
+== Actual Handover on a protocol level ==
+
+After the BSC has decided a handover shall be done, it has to
+
+# allocate a channel at the new BTS
+# allocate a handover reference
+# activate the channel on the BTS side using RSL CHANNEL ACTIVATION,
+  indicating the HO reference
+# BTS responds with CHAN ACT ACK, including GSM frame number
+# BSC sends 04.08 HO CMD to MS using old BTS
+
diff --git a/openbsc/doc/ipa-sccp.txt b/openbsc/doc/ipa-sccp.txt
new file mode 100644
index 0000000..5d6719e
--- /dev/null
+++ b/openbsc/doc/ipa-sccp.txt
@@ -0,0 +1,94 @@
+
+IPA SCCP message flow in the BSC
+
+February, 2013		Holger Hans Peter Freyther
+
+CONTENTS
+
+1. SCCP inside the IPA header
+2. Supported SCCP message types
+3. Receiving SCCP messages
+4. Sending SCCP messages
+
+
+1. SCCP inside the IPA header
+
+Many Soft-MSCs implement something that is called SCCP/lite. This means
+that SCCP messages are transported inside a small multiplexing protocol
+over TCP/IP. This is an alternative to a full SIGTRAN implementation.
+
+The multiplexing protocol is the same as used with the sysmoBTS and the
+ip.access nanoBTS. It is a three byte header with two bytes for the length
+in network byte order and one byte for the type. The type to be used for
+SCCP is 0xFD.
+
+	struct ipa_header {
+		uint16_t length_in_network_order;
+		uint8_t  type;
+	} __attribute__((packed));
+
+
+
+2. Supported SCCP message types
+
+To implement GSM 08.08 only a subset of SCCP messages need to be implemented.
+For transporting paging and reset messages SCCP UDT messages are used. For
+the connections with a Mobile Station (MS) a SCCP connection is opened. This
+means that the SCCP CR, SCCP CC, SCCP CREF, SCCP RLC, SCCP RLSD, SCCP DT1
+and SCCP IT messages are supported.
+
+
+3. Receiving SCCP UDT messages
+
+This is an illustration of the flow of messages. The IPA multiplexing protocol
+is used for various protocols. This means there is a central place where the
+multiplexing stream terminates. The stream is terminated in the osmo_bsc_msc.c
+file and the ipaccess_a_fd_cb method. For SCCP messages the SCCP dispatching
+sccp_system_incoming method is called. This function is implemented in the
+libosmo-sccp library.
+
+To receive UDT messages osmo_bsc_sccp.c:osmo_bsc_sccp_init is using the
+sccp_set_read function to register a callback for UDT messages. The callback
+is msc_sccp_read and it is calling bsc_handle_udt that is implemented in the
+osmo_bsc_bssap.c. This function will handle the GSM 08.08 BSSAP messages.
+Currently only the reset acknowledge and the paging messages are handled.
+
+The BSC currently does not accept incoming SCCP messages and is only opening
+SCCP connections to the MSC. When opening a connection the callbacks for state
+changes (connection confirmed, released, release complete) are set and a routine
+for handling incoming data. This registration is done in the osmo_bsc_sccp.c
+file and the bsc_create_new_connection method. The name of the callback is
+msc_outgoing_sccp_data and this will call bsc_handle_dt1 that is implemented
+in the osmo_bsc_bssap.c file. This will forward the messages to the right
+Mobile Station (MS).
+
+
+4. Sending SCCP messages
+
+There are three parts to sending that will be explained below. The first part
+is to send an entire SCCP frame (which includes the GSM 08.08 data) to the
+MSC. This is done by first registering the low level sending. sccp_system_init
+is called with the function that is responsible for sending a message. The
+msc_sccp_write_ipa will call the msc_queue_write function with the data and
+the right MSC connection. Below the msc_queue_write the IPA header will be
+prepended to the msg and then send to the MSC.
+
+The BSC supports multiple different A-link connections, the decision to pick
+the right MSC is done in this method. It is either done via the SCCP connection
+or the ctx pointer.
+
+When the BSC is starting a BSS RESET message will be sent to the MSC. The reset
+is created in osmo_bsc_msc.c:initialize_if_needed and sccp_write is called with
+the GSM 08.08 data and the connection to use. The libosmo-sccp library will
+embed it into a SCCP UDT message and call the msc_sccp_write_ipa method.
+
+When a new SCCP connection is to be created the bsc_create_new_connection
+in the osmo_bsc_sccp.c file. The sccp_connection_socket method will create
+the context for a SCCP connection. The state and data callback will be used
+to be notified about data and changes. Once the connection is configured the
+bsc_open_connection will be called that will ask the libosmo-sccp library to
+create a SCCP CR message using the sccp_connection_connect method. For active
+connections the sccp_connection_write method will be called.
+
+
+
diff --git a/openbsc/doc/oml-interface.txt b/openbsc/doc/oml-interface.txt
new file mode 100644
index 0000000..02bead7
--- /dev/null
+++ b/openbsc/doc/oml-interface.txt
@@ -0,0 +1,22 @@
+oml interface design notes
+
+problems:
+
+* there is no way how to tag a command sent to the BTS, with the response
+  having the same tag to identify the originator of the command
+* therefore, we can have e.g. both the BSC and the OML interface send a
+  SET ATTRIBUTE message, where the responses would end up at the wrong
+  query.
+* The BTS has 10s to ACK/NACK a command. We do not run any timers.
+
+the only possible solutions i can imagine:
+* have some kind of exclusive locking, where the OML interface gets blocked
+  from the BSC and is exclusively assigned to the OML console until all commands
+  of the OML console have terminated.  This can either be done explicitly
+  dynamically or on demand
+
+* use the OML interface synchronously, i.e. always wait for the response from
+  the BTS before
+
+* unilateral / unsolicited messages need to be broadcasted to both the BSC and
+  the OML console
diff --git a/openbsc/doc/osmo-nitb-data_structures.dot b/openbsc/doc/osmo-nitb-data_structures.dot
new file mode 100644
index 0000000..81955e8
--- /dev/null
+++ b/openbsc/doc/osmo-nitb-data_structures.dot
@@ -0,0 +1,33 @@
+digraph G {
+	net [label="gsm_network"]
+	bts [label="gsm_bts"]
+	trx [label="gsm_bts_trx"]
+	ts [label="gsm_bts_trx_ts"]
+	lchan [label="gsm_lchan"]
+	sub [label="gsm_subscriber"]
+	subcon [label="gsm_subscriber_conn"]
+	sccpcon [label="osmo_bsc_sccp_con"]
+	subgrp [label="gsm_subscriber_group"]
+
+	net -> bts
+	bts -> trx
+	trx -> ts
+	ts -> lchan
+
+	lchan -> ts
+	ts -> trx
+	trx -> bts
+	bts -> net
+
+	lchan -> subcon
+
+	subcon -> sub
+	subcon -> sccpcon
+	subcon -> lchan
+	subcon -> lchan [label="ho_lchan"]
+	subcon -> bts
+	subcon -> lchan [label="secondary_lchan"]
+
+	sub -> subgrp
+	subgrp -> net
+}
diff --git a/openbsc/doc/paging.txt b/openbsc/doc/paging.txt
new file mode 100644
index 0000000..c597c22
--- /dev/null
+++ b/openbsc/doc/paging.txt
@@ -0,0 +1,48 @@
+
+GSM Paging implementation in OpenBSC
+
+== Code structure ==
+
+The code is implemented in the libbsc/paging.c file. The external
+interface is documented/specified in the include/openbsc/paging.h
+header file. The code is used by the NITB and BSC application.
+
+
+== Implementation ==
+
+Paging can be initiated in two ways. The standard way is to page by
+LAC. Each BTS has its own list/queue of outstanding paging operation.
+When a subscriber is paged one "struct paging_request" per BTS will
+be allocated and added to the tail of the list. The BTS is supposed
+to be configured to not repeat the paging.
+
+A paging_request will remain in the queue until a paging response or at
+the expiry of the T3113. Every 500 milliseconds a RSL paging command is
+send to the BTS. The 500 milliseconds is a throttling to not crash the
+ip.access nanoBTS. Once one paging_request has been handled it will be
+put at the end of the queue/list and the available slots for the BTS
+will be decreased.
+
+The available slots will be updated based on the paging load information
+element of the CCCH Load indication. If no paging slots are considered
+to be available and no load indication is sent a timer is started. The
+current timeout is 500 milliseconds and at the expiry of the timer the
+available slots will be set to 20.
+
+OpenBSC has the " paging free <-1-1024>" configuration option. In case
+there are less free channels than required no paging request will be
+sent to the BTS. Instead it will be attempted to send the paging request
+at the next timeout (500 milliseconds).
+
+== Limitation ==
+
+The paging throughput could be higher but this has lead to crashes on the
+ip.access nanoBTS in the past.
+
+== Configuration ==
+
+=== ip.access nanoBTS ===
+
+The current CCCH Load indication threshold is 10% and the period is 1 second.
+The code can be found inside the src/libbsc/bts_ipaccess_nanobts.c inside the
+nanobts_attr_bts array.
diff --git a/openbsc/git-version-gen b/openbsc/git-version-gen
new file mode 100755
index 0000000..42cf3d2
--- /dev/null
+++ b/openbsc/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#	echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#	echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+    1) ;;
+    *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || exit 1
+    case $v in
+	*$nl*) v= ;; # reject multi-line output
+	[0-9]*) ;;
+	*) v= ;;
+    esac
+    test -z "$v" \
+	&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+    : # use $v
+elif
+       v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+	  || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && case $v in
+	 [0-9]*) ;;
+	 v[0-9]*) ;;
+	 *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+    case $v in
+	*-*-*) : git describe is okay three part flavor ;;
+	*-*)
+	    : git describe is older two part flavor
+	    # Recreate the number of commits and rewrite such that the
+	    # result is the same as if we were using the newer version
+	    # of git describe.
+	    vtag=`echo "$v" | sed 's/-.*//'`
+	    numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+	    v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+	    ;;
+    esac
+
+    # Change the first '-' to a '.', so version-comparing tools work properly.
+    # Remove the "g" in git describe's output string, to save a byte.
+    v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+    v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+    '') ;;
+    *) # Append the suffix only if there isn't one already.
+	case $v in
+	  *-dirty) ;;
+	  *) v="$v-dirty" ;;
+	esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/openbsc/include/Makefile.am b/openbsc/include/Makefile.am
new file mode 100644
index 0000000..3234e62
--- /dev/null
+++ b/openbsc/include/Makefile.am
@@ -0,0 +1,8 @@
+SUBDIRS = \
+	openbsc \
+	$(NULL)
+
+noinst_HEADERS = \
+	mISDNif.h \
+	compat_af_isdn.h \
+	$(NULL)
diff --git a/openbsc/include/compat_af_isdn.h b/openbsc/include/compat_af_isdn.h
new file mode 100644
index 0000000..56cbfb3
--- /dev/null
+++ b/openbsc/include/compat_af_isdn.h
@@ -0,0 +1,39 @@
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+#undef AF_ISDN
+#undef PF_ISDN
+
+extern	int	AF_ISDN;
+#define PF_ISDN	AF_ISDN
+
+int	AF_ISDN;
+
+#endif
+
+extern void init_af_isdn(void);
+
+#ifdef AF_COMPATIBILITY_FUNC
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+void init_af_isdn(void)
+{
+	int	s;
+
+	/* test for new value */
+	AF_ISDN = 34;
+	s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (s >= 0) {
+		close(s);
+		return;
+	}
+	AF_ISDN = 27;
+	s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (s >= 0) {
+		close(s);
+		return;
+	}
+}
+#else
+void init_af_isdn(void)
+{
+}
+#endif
+#endif
diff --git a/openbsc/include/mISDNif.h b/openbsc/include/mISDNif.h
new file mode 100644
index 0000000..8e065d2
--- /dev/null
+++ b/openbsc/include/mISDNif.h
@@ -0,0 +1,387 @@
+/*
+ *
+ * Author	Karsten Keil <kkeil@novell.com>
+ *
+ * Copyright 2008  by Karsten Keil <kkeil@novell.com>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
+ *
+ */
+
+#ifndef mISDNIF_H
+#define mISDNIF_H
+
+#include <stdarg.h>
+#ifdef linux
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#else
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#endif
+
+/*
+ * ABI Version 32 bit
+ *
+ * <8 bit> Major version
+ *		- changed if any interface become backwards incompatible
+ *
+ * <8 bit> Minor version
+ *              - changed if any interface is extended but backwards compatible
+ *
+ * <16 bit> Release number
+ *              - should be incremented on every checkin
+ */
+#define	MISDN_MAJOR_VERSION	1
+#define	MISDN_MINOR_VERSION	1
+#define MISDN_RELEASE		20
+
+/* primitives for information exchange
+ * generell format
+ * <16  bit  0 >
+ * <8  bit command>
+ *    BIT 8 = 1 LAYER private
+ *    BIT 7 = 1 answer
+ *    BIT 6 = 1 DATA
+ * <8  bit target layer mask>
+ *
+ * Layer = 00 is reserved for general commands
+   Layer = 01  L2 -> HW
+   Layer = 02  HW -> L2
+   Layer = 04  L3 -> L2
+   Layer = 08  L2 -> L3
+ * Layer = FF is reserved for broadcast commands
+ */
+
+#define MISDN_CMDMASK		0xff00
+#define MISDN_LAYERMASK		0x00ff
+
+/* generell commands */
+#define OPEN_CHANNEL		0x0100
+#define CLOSE_CHANNEL		0x0200
+#define CONTROL_CHANNEL		0x0300
+#define CHECK_DATA		0x0400
+
+/* layer 2 -> layer 1 */
+#define PH_ACTIVATE_REQ		0x0101
+#define PH_DEACTIVATE_REQ	0x0201
+#define PH_DATA_REQ		0x2001
+#define MPH_ACTIVATE_REQ	0x0501
+#define MPH_DEACTIVATE_REQ	0x0601
+#define MPH_INFORMATION_REQ	0x0701
+#define PH_CONTROL_REQ		0x0801
+
+/* layer 1 -> layer 2 */
+#define PH_ACTIVATE_IND		0x0102
+#define PH_ACTIVATE_CNF		0x4102
+#define PH_DEACTIVATE_IND	0x0202
+#define PH_DEACTIVATE_CNF	0x4202
+#define PH_DATA_IND		0x2002
+#define PH_DATA_E_IND		0x3002
+#define MPH_ACTIVATE_IND	0x0502
+#define MPH_DEACTIVATE_IND	0x0602
+#define MPH_INFORMATION_IND	0x0702
+#define PH_DATA_CNF		0x6002
+#define PH_CONTROL_IND		0x0802
+#define PH_CONTROL_CNF		0x4802
+
+/* layer 3 -> layer 2 */
+#define DL_ESTABLISH_REQ	0x1004
+#define DL_RELEASE_REQ		0x1104
+#define DL_DATA_REQ		0x3004
+#define DL_UNITDATA_REQ		0x3104
+#define DL_INFORMATION_REQ	0x0004
+
+/* layer 2 -> layer 3 */
+#define DL_ESTABLISH_IND	0x1008
+#define DL_ESTABLISH_CNF	0x5008
+#define DL_RELEASE_IND		0x1108
+#define DL_RELEASE_CNF		0x5108
+#define DL_DATA_IND		0x3008
+#define DL_UNITDATA_IND		0x3108
+#define DL_INFORMATION_IND	0x0008
+
+/* intern layer 2 managment */
+#define MDL_ASSIGN_REQ		0x1804
+#define MDL_ASSIGN_IND		0x1904
+#define MDL_REMOVE_REQ		0x1A04
+#define MDL_REMOVE_IND		0x1B04
+#define MDL_STATUS_UP_IND	0x1C04
+#define MDL_STATUS_DOWN_IND	0x1D04
+#define MDL_STATUS_UI_IND	0x1E04
+#define MDL_ERROR_IND		0x1F04
+#define MDL_ERROR_RSP		0x5F04
+
+/* DL_INFORMATION_IND types */
+#define DL_INFO_L2_CONNECT	0x0001
+#define DL_INFO_L2_REMOVED	0x0002
+
+/* PH_CONTROL types */
+/* TOUCH TONE IS 0x20XX  XX "0"..."9", "A","B","C","D","*","#" */
+#define DTMF_TONE_VAL		0x2000
+#define DTMF_TONE_MASK		0x007F
+#define DTMF_TONE_START		0x2100
+#define DTMF_TONE_STOP		0x2200
+#define DTMF_HFC_COEF		0x4000
+#define DSP_CONF_JOIN		0x2403
+#define DSP_CONF_SPLIT		0x2404
+#define DSP_RECEIVE_OFF		0x2405
+#define DSP_RECEIVE_ON		0x2406
+#define DSP_ECHO_ON		0x2407
+#define DSP_ECHO_OFF		0x2408
+#define DSP_MIX_ON		0x2409
+#define DSP_MIX_OFF		0x240a
+#define DSP_DELAY		0x240b
+#define DSP_JITTER		0x240c
+#define DSP_TXDATA_ON		0x240d
+#define DSP_TXDATA_OFF		0x240e
+#define DSP_TX_DEJITTER		0x240f
+#define DSP_TX_DEJ_OFF		0x2410
+#define DSP_TONE_PATT_ON	0x2411
+#define DSP_TONE_PATT_OFF	0x2412
+#define DSP_VOL_CHANGE_TX	0x2413
+#define DSP_VOL_CHANGE_RX	0x2414
+#define DSP_BF_ENABLE_KEY	0x2415
+#define DSP_BF_DISABLE		0x2416
+#define DSP_BF_ACCEPT		0x2416
+#define DSP_BF_REJECT		0x2417
+#define DSP_PIPELINE_CFG	0x2418
+#define HFC_VOL_CHANGE_TX	0x2601
+#define HFC_VOL_CHANGE_RX	0x2602
+#define HFC_SPL_LOOP_ON		0x2603
+#define HFC_SPL_LOOP_OFF	0x2604
+
+/* DSP_TONE_PATT_ON parameter */
+#define TONE_OFF			0x0000
+#define TONE_GERMAN_DIALTONE		0x0001
+#define TONE_GERMAN_OLDDIALTONE		0x0002
+#define TONE_AMERICAN_DIALTONE		0x0003
+#define TONE_GERMAN_DIALPBX		0x0004
+#define TONE_GERMAN_OLDDIALPBX		0x0005
+#define TONE_AMERICAN_DIALPBX		0x0006
+#define TONE_GERMAN_RINGING		0x0007
+#define TONE_GERMAN_OLDRINGING		0x0008
+#define TONE_AMERICAN_RINGPBX		0x000b
+#define TONE_GERMAN_RINGPBX		0x000c
+#define TONE_GERMAN_OLDRINGPBX		0x000d
+#define TONE_AMERICAN_RINGING		0x000e
+#define TONE_GERMAN_BUSY		0x000f
+#define TONE_GERMAN_OLDBUSY		0x0010
+#define TONE_AMERICAN_BUSY		0x0011
+#define TONE_GERMAN_HANGUP		0x0012
+#define TONE_GERMAN_OLDHANGUP		0x0013
+#define TONE_AMERICAN_HANGUP		0x0014
+#define TONE_SPECIAL_INFO		0x0015
+#define TONE_GERMAN_GASSENBESETZT	0x0016
+#define TONE_GERMAN_AUFSCHALTTON	0x0016
+
+/* MPH_INFORMATION_IND */
+#define L1_SIGNAL_LOS_OFF	0x0010
+#define L1_SIGNAL_LOS_ON	0x0011
+#define L1_SIGNAL_AIS_OFF	0x0012
+#define L1_SIGNAL_AIS_ON	0x0013
+#define L1_SIGNAL_RDI_OFF	0x0014
+#define L1_SIGNAL_RDI_ON	0x0015
+#define L1_SIGNAL_SLIP_RX	0x0020
+#define L1_SIGNAL_SLIP_TX	0x0021
+
+/*
+ * protocol ids
+ * D channel 1-31
+ * B channel 33 - 63
+ */
+
+#define ISDN_P_NONE		0
+#define ISDN_P_BASE		0
+#define ISDN_P_TE_S0		0x01
+#define ISDN_P_NT_S0  		0x02
+#define ISDN_P_TE_E1		0x03
+#define ISDN_P_NT_E1  		0x04
+#define ISDN_P_TE_UP0		0x05
+#define ISDN_P_NT_UP0		0x06
+
+#define IS_ISDN_P_TE(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_TE_E1) || \
+				(p == ISDN_P_TE_UP0) || (p == ISDN_P_LAPD_TE))
+#define IS_ISDN_P_NT(p) ((p == ISDN_P_NT_S0) || (p == ISDN_P_NT_E1) || \
+				(p == ISDN_P_NT_UP0) || (p == ISDN_P_LAPD_NT))
+#define IS_ISDN_P_S0(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_NT_S0))
+#define IS_ISDN_P_E1(p) ((p == ISDN_P_TE_E1) || (p == ISDN_P_NT_E1))
+#define IS_ISDN_P_UP0(p) ((p == ISDN_P_TE_UP0) || (p == ISDN_P_NT_UP0))
+
+
+#define ISDN_P_LAPD_TE		0x10
+#define	ISDN_P_LAPD_NT		0x11
+
+#define ISDN_P_B_MASK		0x1f
+#define ISDN_P_B_START		0x20
+
+#define ISDN_P_B_RAW		0x21
+#define ISDN_P_B_HDLC		0x22
+#define ISDN_P_B_X75SLP		0x23
+#define ISDN_P_B_L2DTMF		0x24
+#define ISDN_P_B_L2DSP		0x25
+#define ISDN_P_B_L2DSPHDLC	0x26
+
+#define OPTION_L2_PMX		1
+#define OPTION_L2_PTP		2
+#define OPTION_L2_FIXEDTEI	3
+#define OPTION_L2_CLEANUP	4
+
+/* should be in sync with linux/kobject.h:KOBJ_NAME_LEN */
+#define MISDN_MAX_IDLEN		20
+
+struct mISDNhead {
+	unsigned int	prim;
+	unsigned int	id;
+}  __attribute__((packed));
+
+#define MISDN_HEADER_LEN	sizeof(struct mISDNhead)
+#define MAX_DATA_SIZE		2048
+#define MAX_DATA_MEM		(MAX_DATA_SIZE + MISDN_HEADER_LEN)
+#define MAX_DFRAME_LEN		260
+
+#define MISDN_ID_ADDR_MASK	0xFFFF
+#define MISDN_ID_TEI_MASK	0xFF00
+#define MISDN_ID_SAPI_MASK	0x00FF
+#define MISDN_ID_TEI_ANY	0x7F00
+
+#define MISDN_ID_ANY		0xFFFF
+#define MISDN_ID_NONE		0xFFFE
+
+#define GROUP_TEI		127
+#define TEI_SAPI		63
+#define CTRL_SAPI		0
+
+#define MISDN_MAX_CHANNEL	127
+#define MISDN_CHMAP_SIZE	((MISDN_MAX_CHANNEL + 1) >> 3)
+
+#define SOL_MISDN	0
+
+struct sockaddr_mISDN {
+	sa_family_t    family;
+	unsigned char	dev;
+	unsigned char	channel;
+	unsigned char	sapi;
+	unsigned char	tei;
+};
+
+struct mISDNversion {
+	unsigned char	major;
+	unsigned char	minor;
+	unsigned short	release;
+};
+
+#define MAX_DEVICE_ID 63
+
+struct mISDN_devinfo {
+	u_int			id;
+	u_int			Dprotocols;
+	u_int			Bprotocols;
+	u_int			protocol;
+	u_char			channelmap[MISDN_CHMAP_SIZE];
+	u_int			nrbchan;
+	char			name[MISDN_MAX_IDLEN];
+};
+
+struct mISDN_devrename {
+	u_int			id;
+	char			name[MISDN_MAX_IDLEN];
+};
+
+struct ph_info_ch {
+	int32_t 		protocol;
+	int64_t			Flags;
+};
+
+struct ph_info_dch {
+	struct ph_info_ch	ch;
+	int16_t			state;
+	int16_t			num_bch;
+};
+
+struct ph_info {
+	struct ph_info_dch	dch;
+	struct ph_info_ch 	bch[];
+};
+
+/* timer device ioctl */
+#define IMADDTIMER	_IOR('I', 64, int)
+#define IMDELTIMER	_IOR('I', 65, int)
+/* socket ioctls */
+#define	IMGETVERSION	_IOR('I', 66, int)
+#define	IMGETCOUNT	_IOR('I', 67, int)
+#define IMGETDEVINFO	_IOR('I', 68, int)
+#define IMCTRLREQ	_IOR('I', 69, int)
+#define IMCLEAR_L2	_IOR('I', 70, int)
+#define IMSETDEVNAME	_IOR('I', 71, struct mISDN_devrename)
+
+static inline int
+test_channelmap(u_int nr, u_char *map)
+{
+	if (nr <= MISDN_MAX_CHANNEL)
+		return map[nr >> 3] & (1 << (nr & 7));
+	else
+		return 0;
+}
+
+static inline void
+set_channelmap(u_int nr, u_char *map)
+{
+	map[nr >> 3] |= (1 << (nr & 7));
+}
+
+static inline void
+clear_channelmap(u_int nr, u_char *map)
+{
+	map[nr >> 3] &= ~(1 << (nr & 7));
+}
+
+/* CONTROL_CHANNEL parameters */
+#define MISDN_CTRL_GETOP		0x0000
+#define MISDN_CTRL_LOOP			0x0001
+#define MISDN_CTRL_CONNECT		0x0002
+#define MISDN_CTRL_DISCONNECT		0x0004
+#define MISDN_CTRL_PCMCONNECT		0x0010
+#define MISDN_CTRL_PCMDISCONNECT	0x0020
+#define MISDN_CTRL_SETPEER		0x0040
+#define MISDN_CTRL_UNSETPEER		0x0080
+#define MISDN_CTRL_RX_OFF		0x0100
+#define MISDN_CTRL_FILL_EMPTY		0x0200
+#define MISDN_CTRL_GETPEER		0x0400
+#define MISDN_CTRL_HW_FEATURES_OP	0x2000
+#define MISDN_CTRL_HW_FEATURES		0x2001
+#define MISDN_CTRL_HFC_OP		0x4000
+#define MISDN_CTRL_HFC_PCM_CONN		0x4001
+#define MISDN_CTRL_HFC_PCM_DISC		0x4002
+#define MISDN_CTRL_HFC_CONF_JOIN	0x4003
+#define MISDN_CTRL_HFC_CONF_SPLIT	0x4004
+#define MISDN_CTRL_HFC_RECEIVE_OFF	0x4005
+#define MISDN_CTRL_HFC_RECEIVE_ON	0x4006
+#define MISDN_CTRL_HFC_ECHOCAN_ON 	0x4007
+#define MISDN_CTRL_HFC_ECHOCAN_OFF 	0x4008
+
+
+/* socket options */
+#define MISDN_TIME_STAMP		0x0001
+
+struct mISDN_ctrl_req {
+	int		op;
+	int		channel;
+	int		p1;
+	int		p2;
+};
+
+/* muxer options */
+#define MISDN_OPT_ALL		1
+#define MISDN_OPT_TEIMGR	2
+
+#endif /* mISDNIF_H */
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
new file mode 100644
index 0000000..1fa30d5
--- /dev/null
+++ b/openbsc/include/openbsc/Makefile.am
@@ -0,0 +1,83 @@
+noinst_HEADERS = \
+	abis_nm.h \
+	abis_om2000.h \
+	abis_rsl.h \
+	acc_ramp.h \
+	arfcn_range_encode.h \
+	auth.h \
+	bsc_msc.h \
+	bsc_msg_filter.h \
+	bsc_nat.h \
+	bsc_nat_callstats.h \
+	bsc_nat_sccp.h \
+	bsc_rll.h \
+	bsc_subscriber.h \
+	bss.h \
+	bts_ipaccess_nanobts_omlattr.h \
+	chan_alloc.h \
+	common_bsc.h \
+	common_cs.h \
+	ctrl.h \
+	db.h \
+	debug.h \
+	e1_config.h \
+	gsm_04_08.h \
+	gsm_04_11.h \
+	gsm_04_14.h \
+	gsm_04_80.h \
+	gsm_data.h \
+	gsm_data_shared.h \
+	gsm_subscriber.h \
+	gsup_client.h \
+	handover.h \
+	handover_decision.h \
+	ipaccess.h \
+	iu.h \
+	meas_feed.h \
+	meas_rep.h \
+	mgcp.h \
+	mgcp_internal.h \
+	mgcp_transcode.h \
+	misdn.h \
+	mncc.h \
+	mncc_int.h \
+	nat_rewrite_trie.h \
+	network_listen.h \
+	oap_client.h \
+	openbscdefines.h \
+	osmo_bsc.h \
+	osmo_bsc_grace.h \
+	osmo_bsc_rf.h \
+	osmo_msc.h \
+	bsc_msc_data.h \
+	osmux.h \
+	paging.h \
+	pcu_if.h \
+	pcuif_proto.h \
+	rest_octets.h \
+	rrlp.h \
+	rs232.h \
+	rtp_proxy.h \
+	signal.h \
+	silent_call.h \
+	smpp.h \
+	sms_queue.h \
+	socket.h \
+	system_information.h \
+	token_auth.h \
+	transaction.h \
+	trau_mux.h \
+	trau_upqueue.h \
+	ussd.h \
+	vty.h \
+	$(NULL)
+
+openbsc_HEADERS = \
+	bsc_api.h \
+	gsm_04_08.h \
+	meas_rep.h \
+	$(NULL)
+
+# DO NOT add a newline and '$(NULL)' to this line. That would add a trailing
+# space to the directory installed: $prefix/include/'openbsc '
+openbscdir = $(includedir)/openbsc
diff --git a/openbsc/include/openbsc/abis_nm.h b/openbsc/include/openbsc/abis_nm.h
new file mode 100644
index 0000000..d7ab7d5
--- /dev/null
+++ b/openbsc/include/openbsc/abis_nm.h
@@ -0,0 +1,173 @@
+/* GSM Network Management messages on the A-bis interface 
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _NM_H
+#define _NM_H
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <openbsc/gsm_data.h>
+
+/* max number of attributes represented as 3GPP TS 52.021 §9.4.62 SW Description array */
+#define MAX_BTS_ATTR 5
+
+/* The BCCH info from an ip.access test, in host byte order
+ * and already parsed... */
+struct ipac_bcch_info {
+	struct llist_head list;
+
+	uint16_t info_type;
+	uint8_t freq_qual;
+	uint16_t arfcn;
+	uint8_t rx_lev;
+	uint8_t rx_qual;
+	int16_t freq_err;
+	uint16_t frame_offset;
+	uint32_t frame_nr_offset;
+	uint8_t bsic;
+	struct osmo_cell_global_id cgi;
+	uint8_t ba_list_si2[16];
+	uint8_t ba_list_si2bis[16];
+	uint8_t ba_list_si2ter[16];
+	uint8_t ca_list_si1[16];
+};
+
+/* PUBLIC */
+
+struct msgb;
+
+struct abis_nm_cfg {
+	/* callback for unidirectional reports */
+	int (*report_cb)(struct msgb *,
+			 struct abis_om_fom_hdr *);
+	/* callback for software activate requests from BTS */
+	int (*sw_act_req)(struct msgb *);
+};
+
+extern int abis_nm_rcvmsg(struct msgb *msg);
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len);
+int abis_nm_rx(struct msgb *msg);
+int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2);
+int abis_nm_chg_adm_state(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0,
+			  uint8_t i1, uint8_t i2, enum abis_nm_adm_state adm_state);
+int abis_nm_establish_tei(struct gsm_bts *bts, uint8_t trx_nr,
+			  uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot,
+			  uint8_t tei);
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+			   uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot);
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+			   uint8_t e1_port, uint8_t e1_timeslot,
+			   uint8_t e1_subslot);
+int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class,
+		     uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+		     const uint8_t *attr, uint8_t attr_len);
+int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len);
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len);
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb);
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1,
+			uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len);
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *msg);
+int abis_nm_event_reports(struct gsm_bts *bts, int on);
+int abis_nm_reset_resource(struct gsm_bts *bts);
+int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
+			  uint8_t win_size, int forced,
+			  gsm_cbfn *cbfn, void *cb_data);
+int abis_nm_software_load_status(struct gsm_bts *bts);
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+			      gsm_cbfn *cbfn, void *cb_data);
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0,
+			    uint8_t e1_port1, uint8_t ts1);
+
+int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class,
+			 uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+			 uint8_t test_nr, uint8_t auton_report, struct msgb *msg);
+
+/* Siemens / BS-11 specific */
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts);
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin);
+int abis_nm_bs11_create_object(struct gsm_bts *bts, enum abis_bs11_objtype type,
+			  uint8_t idx, uint8_t attr_len, const uint8_t *attr);
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx);
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx);
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, uint8_t idx);
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx);
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, uint8_t e1_port,
+			  uint8_t e1_timeslot, uint8_t e1_subslot, uint8_t tei);
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts);
+int abis_nm_bs11_get_serno(struct gsm_bts *bts);
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level);
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx);
+int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on);
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password);
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked);
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts);
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value);
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts);
+int abis_nm_bs11_get_state(struct gsm_bts *bts);
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+			  uint8_t win_size, int forced, gsm_cbfn *cbfn);
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts);
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport);
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg);
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect);
+int abis_nm_bs11_restart(struct gsm_bts *bts);
+
+/* ip.access nanoBTS specific commands */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type,
+			 uint8_t obj_class, uint8_t bts_nr,
+			 uint8_t trx_nr, uint8_t ts_nr,
+			 uint8_t *attr, int attr_len);
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr,
+				int attr_len);
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx);
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, uint8_t obj_class,
+				uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+				uint8_t *attr, uint8_t attr_len);
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, 
+				 uint32_t ip, uint16_t port, uint8_t stream);
+void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts);
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf);
+const char *ipacc_testres_name(uint8_t res);
+
+/* Functions calling into other code parts */
+int nm_is_running(struct gsm_nm_state *s);
+
+int abis_nm_vty_init(void);
+
+void abis_nm_clear_queue(struct gsm_bts *bts);
+
+int _abis_nm_sendmsg(struct msgb *msg);
+
+void abis_nm_queue_send_next(struct gsm_bts *bts);	/* for bs11_config. */
+
+int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw, const size_t len);
+
+/* Helper functions for updating attributes */
+int abis_nm_update_max_power_red(struct gsm_bts_trx *trx);
+
+#endif /* _NM_H */
diff --git a/openbsc/include/openbsc/abis_om2000.h b/openbsc/include/openbsc/abis_om2000.h
new file mode 100644
index 0000000..b093a03
--- /dev/null
+++ b/openbsc/include/openbsc/abis_om2000.h
@@ -0,0 +1,129 @@
+#ifndef OPENBSC_ABIS_OM2K_H
+#define OPENBSC_ABIS_OM2K_H
+/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface
+ * implemented based on protocol trace analysis, no formal documentation */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+enum abis_om2k_mo_cls {
+	OM2K_MO_CLS_TRXC			= 0x01,
+	OM2K_MO_CLS_TS				= 0x03,
+	OM2K_MO_CLS_TF				= 0x04,
+	OM2K_MO_CLS_IS				= 0x05,
+	OM2K_MO_CLS_CON				= 0x06,
+	OM2K_MO_CLS_DP				= 0x07,
+	OM2K_MO_CLS_CF				= 0x0a,
+	OM2K_MO_CLS_TX				= 0x0b,
+	OM2K_MO_CLS_RX				= 0x0c,
+};
+
+enum om2k_mo_state {
+	OM2K_MO_S_RESET = 0,
+	OM2K_MO_S_STARTED,
+	OM2K_MO_S_ENABLED,
+	OM2K_MO_S_DISABLED,
+};
+
+/* on-wire format for IS conn group */
+struct om2k_is_conn_grp {
+	uint16_t icp1;
+	uint16_t icp2;
+	uint8_t cont_idx;
+} __attribute__ ((packed));
+
+/* internal data formant for IS conn group */
+struct is_conn_group {
+	struct llist_head list;
+	uint16_t icp1;
+	uint16_t icp2;
+	uint8_t ci;
+};
+
+/* on-wire format for CON Path */
+struct om2k_con_path {
+	uint16_t ccp;
+	uint8_t ci;
+	uint8_t tag;
+	uint8_t tei;
+} __attribute__ ((packed));
+
+/* internal data format for CON group */
+struct con_group {
+	/* links list of CON groups in BTS */
+	struct llist_head list;
+	struct gsm_bts *bts;
+	/* CON Group ID */
+	uint8_t cg;
+	/* list of CON paths in this group */
+	struct llist_head paths;
+};
+
+/* internal data format for CON path */
+struct con_path {
+	/* links with con_group.paths */
+	struct llist_head list;
+	/* CON Connection Point */
+	uint16_t ccp;
+	/* Contiguity Index */
+	uint8_t ci;
+	/* Tag */
+	uint8_t tag;
+	/* TEI */
+	uint8_t tei;
+};
+
+extern const struct abis_om2k_mo om2k_mo_cf;
+extern const struct abis_om2k_mo om2k_mo_is;
+extern const struct abis_om2k_mo om2k_mo_con;
+extern const struct abis_om2k_mo om2k_mo_tf;
+
+extern const struct value_string om2k_mo_class_short_vals[];
+
+int abis_om2k_rcvmsg(struct msgb *msg);
+
+extern const struct abis_om2k_mo om2k_mo_cf;
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			 uint8_t operational);
+int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts);
+int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts);
+int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx);
+int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx);
+int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts);
+
+struct osmo_fsm_inst *om2k_bts_fsm_start(struct gsm_bts *bts);
+void abis_om2k_bts_init(struct gsm_bts *bts);
+void abis_om2k_trx_init(struct gsm_bts_trx *trx);
+
+int abis_om2k_vty_init(void);
+
+struct vty;
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts);
+
+#endif /* OPENBCS_ABIS_OM2K_H */
diff --git a/openbsc/include/openbsc/abis_rsl.h b/openbsc/include/openbsc/abis_rsl.h
new file mode 100644
index 0000000..400e09f
--- /dev/null
+++ b/openbsc/include/openbsc/abis_rsl.h
@@ -0,0 +1,118 @@
+/* GSM Radio Signalling Link messages on the A-bis interface 
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _RSL_H
+#define _RSL_H
+
+#include <stdbool.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/core/msgb.h>
+
+struct gsm_bts;
+struct gsm_lchan;
+struct gsm_subscriber;
+struct gsm_bts_trx_ts;
+
+#define GSM48_LEN2PLEN(a)	(((a) << 2) | 1)
+
+int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len);
+int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
+		      const uint8_t *data, int len);
+int rsl_chan_activate(struct gsm_bts_trx *trx, uint8_t chan_nr,
+		      uint8_t act_type,
+		      struct rsl_ie_chan_mode *chan_mode,
+		      struct rsl_ie_chan_ident *chan_ident,
+		      uint8_t bs_power, uint8_t ms_power,
+		      uint8_t ta);
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, uint8_t act_type,
+			    uint8_t ho_ref);
+int rsl_chan_mode_modify_req(struct gsm_lchan *ts);
+int rsl_encryption_cmd(struct msgb *msg);
+int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len,
+		   uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs);
+int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val);
+
+int rsl_data_request(struct msgb *msg, uint8_t link_id);
+int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id);
+int rsl_relase_request(struct gsm_lchan *lchan, uint8_t link_id);
+
+/* Ericcson vendor specific RSL extensions */
+int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, uint8_t *val);
+
+/* Siemens vendor-specific RSL extensions */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci);
+
+/* ip.access specfic RSL extensions */
+int rsl_ipacc_crcx(struct gsm_lchan *lchan);
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, uint32_t ip,
+		   uint16_t port, uint8_t rtp_payload2);
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan);
+int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act);
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+
+uint64_t str_to_imsi(const char *imsi_str);
+int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id,
+			enum rsl_rel_mode release_mode);
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int);
+int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *broken);
+
+/* to be provided by external code */
+int rsl_deact_sacch(struct gsm_lchan *lchan);
+
+/* BCCH related code */
+int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf);
+int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf);
+
+int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type,
+			  const uint8_t *data, int len);
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db);
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm);
+
+/* SMSCB functionality */
+int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
+		       struct rsl_ie_cb_cmd_type cb_command,
+		       const uint8_t *data, int len);
+
+/* some Nokia specific stuff */
+int rsl_nokia_si_begin(struct gsm_bts_trx *trx);
+int rsl_nokia_si_end(struct gsm_bts_trx *trx);
+
+/* required for Nokia BTS power control */
+int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction);
+
+
+int rsl_release_sapis_from(struct gsm_lchan *lchan, int start,
+				enum rsl_rel_mode release_mode);
+int rsl_start_t3109(struct gsm_lchan *lchan);
+
+int rsl_direct_rf_release(struct gsm_lchan *lchan);
+
+void dyn_ts_init(struct gsm_bts_trx_ts *ts);
+int dyn_ts_switchover_start(struct gsm_bts_trx_ts *ts,
+			    enum gsm_phys_chan_config to_pchan);
+
+#endif /* RSL_MT_H */
+
diff --git a/openbsc/include/openbsc/acc_ramp.h b/openbsc/include/openbsc/acc_ramp.h
new file mode 100644
index 0000000..efb12b0
--- /dev/null
+++ b/openbsc/include/openbsc/acc_ramp.h
@@ -0,0 +1,161 @@
+/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+/*!
+ * Access control class (ACC) ramping is used to slowly make the cell available to
+ * an increasing number of MS. This avoids overload at startup time in cases where
+ * a lot of MS would discover the new cell and try to connect to it all at once.
+ */
+
+#define ACC_RAMP_STEP_SIZE_MIN 1 /* allow at most 1 new ACC per ramp step */
+#define ACC_RAMP_STEP_SIZE_DEFAULT ACC_RAMP_STEP_SIZE_MIN
+#define ACC_RAMP_STEP_SIZE_MAX 10 /* allow all ACC in one step (effectively disables ramping) */
+
+#define ACC_RAMP_STEP_INTERVAL_MIN 30	/* 30 seconds */
+#define ACC_RAMP_STEP_INTERVAL_MAX 600	/* 10 minutes */
+
+/*!
+ * Data structure used to manage ACC ramping. Please avoid setting or reading fields
+ * in this structure directly. Use the accessor functions below instead.
+ */
+struct acc_ramp {
+	struct gsm_bts *bts; /*!< backpointer to BTS using this ACC ramp */
+
+	bool acc_ramping_enabled; /*!< whether ACC ramping is enabled */
+
+	/*!
+	 * Bitmask which keeps track of access control classes that are currently denied
+	 * access. The function acc_ramp_apply() uses this mask to modulate bits from
+	 * octets 2 and 3 in RACH Control Parameters (see 3GPP 44.018 10.5.2.29).
+	 * Ramping is only concerned with ACCs 0-9. While any of the bits 0-9 is set,
+	 * the corresponding ACC is barred.
+	 * ACCs 11-15 should always be allowed, and ACC 10 denies emergency calls for
+	 * all ACCs from 0-9 inclusive; these ACCs are ignored in this implementation.
+	 */
+	uint16_t barred_accs;
+
+	/*!
+	 * This controls the maximum number of ACCs to allow per ramping step (1 - 10).
+	 * The compile-time default value is ACC_RAMP_STEP_SIZE_DEFAULT.
+	 * This value can be changed by VTY configuration.
+	 * A value of ACC_RAMP_STEP_SIZE_MAX effectively disables ramping.
+	 */
+	unsigned int step_size;
+
+	/*!
+	 * Ramping step interval in seconds.
+	 * This value depends on the current BTS channel load average, unless
+	 * it has been overriden by VTY configuration.
+	 */
+	unsigned int step_interval_sec;
+	bool step_interval_is_fixed;
+	struct osmo_timer_list step_timer;
+};
+
+/*!
+ * Enable or disable ACC ramping.
+ * When enabled, ramping begins once acc_ramp_start() is called.
+ * When disabled, an ACC ramping process in progress will continue
+ * unless acc_ramp_abort() is called as well.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline void acc_ramp_set_enabled(struct acc_ramp *acc_ramp, bool enable)
+{
+	acc_ramp->acc_ramping_enabled = enable;
+}
+
+/*!
+ * Return true if ACC ramping is currently enabled, else false.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline bool acc_ramp_is_enabled(struct acc_ramp *acc_ramp)
+{
+	return acc_ramp->acc_ramping_enabled;
+}
+
+/*!
+ * Return the current ACC ramp step size.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline unsigned int acc_ramp_get_step_size(struct acc_ramp *acc_ramp)
+{
+	return acc_ramp->step_size;
+}
+
+/*!
+ * Return the current ACC ramp step interval (in seconds)
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline unsigned int acc_ramp_get_step_interval(struct acc_ramp *acc_ramp)
+{
+	return acc_ramp->step_interval_sec;
+}
+
+/*!
+ * If the step interval is dynamic, return true, else return false.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline bool acc_ramp_step_interval_is_dynamic(struct acc_ramp *acc_ramp)
+{
+	return !(acc_ramp->step_interval_is_fixed);
+}
+
+/*!
+ * Return bitmasks which correspond to access control classes that are currently
+ * denied access. Ramping is only concerned with those bits which control access
+ * for ACCs 0-9, and any of the other bits will always be set to zero in these masks, i.e.
+ * it is safe to OR these bitmasks with the corresponding fields in struct gsm48_rach_control.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline uint8_t acc_ramp_get_barred_t2(struct acc_ramp *acc_ramp)
+{
+	return ((acc_ramp->barred_accs >> 8) & 0x03);
+};
+static inline uint8_t acc_ramp_get_barred_t3(struct acc_ramp *acc_ramp)
+{
+	return (acc_ramp->barred_accs & 0xff);
+}
+
+/*!
+ * Potentially mark certain Access Control Classes (ACCs) as barred in accordance to ACC ramping.
+ * \param[in] rach_control RACH control parameters in which barred ACCs will be configured.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline void acc_ramp_apply(struct gsm48_rach_control *rach_control, struct acc_ramp *acc_ramp)
+{
+	rach_control->t2 |= acc_ramp_get_barred_t2(acc_ramp);
+	rach_control->t3 |= acc_ramp_get_barred_t3(acc_ramp);
+}
+
+void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts);
+int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size);
+int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval);
+void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp);
+void acc_ramp_trigger(struct acc_ramp *acc_ramp);
+void acc_ramp_abort(struct acc_ramp *acc_ramp);
diff --git a/openbsc/include/openbsc/arfcn_range_encode.h b/openbsc/include/openbsc/arfcn_range_encode.h
new file mode 100644
index 0000000..7ec710c
--- /dev/null
+++ b/openbsc/include/openbsc/arfcn_range_encode.h
@@ -0,0 +1,26 @@
+#ifndef ARFCN_RANGE_ENCODE_H
+#define ARFCN_RANGE_ENCODE_H
+
+#include <stdint.h>
+
+enum gsm48_range {
+	ARFCN_RANGE_INVALID	= -1,
+	ARFCN_RANGE_128		= 127,
+	ARFCN_RANGE_256		= 255,
+	ARFCN_RANGE_512		= 511,
+	ARFCN_RANGE_1024	= 1023,
+};
+
+#define RANGE_ENC_MAX_ARFCNS	29
+
+int range_enc_determine_range(const int *arfcns, int size, int *f0_out);
+int range_enc_arfcns(enum gsm48_range rng, const int *arfcns, int sze, int *out, int idx);
+int range_enc_find_index(enum gsm48_range rng, const int *arfcns, int size);
+int range_enc_filter_arfcns(int *arfcns, const int sze, const int f0, int *f0_included);
+
+int range_enc_range128(uint8_t *chan_list, int f0, int *w);
+int range_enc_range256(uint8_t *chan_list, int f0, int *w);
+int range_enc_range512(uint8_t *chan_list, int f0, int *w);
+int range_enc_range1024(uint8_t *chan_list, int f0, int f0_incl, int *w);
+
+#endif
diff --git a/openbsc/include/openbsc/auth.h b/openbsc/include/openbsc/auth.h
new file mode 100644
index 0000000..6181131
--- /dev/null
+++ b/openbsc/include/openbsc/auth.h
@@ -0,0 +1,26 @@
+#ifndef _AUTH_H
+#define _AUTH_H
+
+#include <osmocom/core/utils.h>
+
+struct gsm_auth_tuple;
+struct gsm_subscriber;
+
+enum auth_action {
+	AUTH_ERROR		= -1,	/* Internal error */
+	AUTH_NOT_AVAIL		= 0,	/* No auth tuple available */
+	AUTH_DO_AUTH_THEN_CIPH	= 1,	/* Firsth authenticate, then cipher */
+	AUTH_DO_CIPH		= 2,	/* Only ciphering */
+	AUTH_DO_AUTH		= 3,	/* Only authentication, no ciphering */
+};
+
+extern const struct value_string auth_action_names[];
+static inline const char *auth_action_str(enum auth_action a)
+{
+	return get_value_string(auth_action_names, a);
+}
+
+int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple,
+                              struct gsm_subscriber *subscr, int key_seq);
+
+#endif /* _AUTH_H */
diff --git a/openbsc/include/openbsc/bsc_api.h b/openbsc/include/openbsc/bsc_api.h
new file mode 100644
index 0000000..3a93119
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_api.h
@@ -0,0 +1,55 @@
+/* GSM 08.08 like API for OpenBSC */
+
+#ifndef OPENBSC_BSC_API_H
+#define OPENBSC_BSC_API_H
+
+#include "gsm_data.h"
+
+#define BSC_API_CONN_POL_ACCEPT	0
+#define BSC_API_CONN_POL_REJECT	1
+
+struct bsc_api {
+	/*! \brief BTS->MSC: tell MSC a SAPI was not established */
+	void (*sapi_n_reject)(struct gsm_subscriber_connection *conn, int dlci);
+	/*! \brief MS->MSC: Tell MSC that ciphering has been enabled */
+	void (*cipher_mode_compl)(struct gsm_subscriber_connection *conn,
+				  struct msgb *msg, uint8_t chosen_encr);
+	/*! \brief MS->MSC: New MM context with L3 payload */
+	int (*compl_l3)(struct gsm_subscriber_connection *conn,
+			struct msgb *msg, uint16_t chosen_channel); 
+	/*! \brief MS->BSC/MSC: Um L3 message */
+	void (*dtap)(struct gsm_subscriber_connection *conn, uint8_t link_id,
+			struct msgb *msg);
+	/*! \brief BSC->MSC: Assignment of lchan successful */
+	void (*assign_compl)(struct gsm_subscriber_connection *conn,
+			  uint8_t rr_cause, uint8_t chosen_channel,
+			  uint8_t encr_alg_id, uint8_t speech_mode);
+	/*! \brief BSC->MSC: Assignment of lchan failed */
+	void (*assign_fail)(struct gsm_subscriber_connection *conn,
+			 uint8_t cause, uint8_t *rr_cause);
+	/*! \brief BSC->MSC: RR conn has been cleared */
+	int (*clear_request)(struct gsm_subscriber_connection *conn,
+			      uint32_t cause);
+	/*! \brief BSC->MSC: Classmark Update */
+	void (*classmark_chg)(struct gsm_subscriber_connection *conn,
+			      const uint8_t *cm2, uint8_t cm2_len,
+			      const uint8_t *cm3, uint8_t cm3_len);
+
+	/**
+	 * Configure the multirate setting on this channel. If it is
+	 * not implemented AMR5.9 will be used.
+	 */
+	void (*mr_config)(struct gsm_subscriber_connection *conn,
+				struct gsm_lchan *lchan, int full_rate);
+};
+
+int bsc_api_init(struct gsm_network *network, struct bsc_api *api);
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id, int allow_sacch);
+int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate);
+int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
+			const uint8_t *key, int len, int include_imeisv);
+int gsm0808_page(struct gsm_bts *bts, unsigned int page_group,
+		 unsigned int mi_len, uint8_t *mi, int chan_type);
+int gsm0808_clear(struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/openbsc/include/openbsc/bsc_msc.h b/openbsc/include/openbsc/bsc_msc.h
new file mode 100644
index 0000000..39258d3
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_msc.h
@@ -0,0 +1,65 @@
+/* Routines to talk to the MSC using the IPA Protocol */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_MSC_H
+#define BSC_MSC_H
+
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+#include <netinet/in.h>
+
+struct bsc_msc_dest {
+	struct llist_head list;
+
+	char *ip;
+	int port;
+	int dscp;
+};
+
+
+struct bsc_msc_connection {
+	struct osmo_wqueue write_queue;
+	int is_connected;
+	int is_authenticated;
+	int first_contact;
+
+	struct llist_head *dests;
+
+	const char *name;
+
+	void (*connection_loss) (struct bsc_msc_connection *);
+	void (*connected) (struct bsc_msc_connection *);
+	struct osmo_timer_list reconnect_timer;
+	struct osmo_timer_list timeout_timer;
+
+	struct msgb *pending_msg;
+};
+
+struct bsc_msc_connection *bsc_msc_create(void *ctx, struct llist_head *dest);
+int bsc_msc_connect(struct bsc_msc_connection *);
+void bsc_msc_schedule_connect(struct bsc_msc_connection *);
+
+void bsc_msc_lost(struct bsc_msc_connection *);
+
+struct msgb *bsc_msc_id_get_resp(int fixed, const char *token, const uint8_t *res, int len);
+
+#endif
diff --git a/openbsc/include/openbsc/bsc_msc_data.h b/openbsc/include/openbsc/bsc_msc_data.h
new file mode 100644
index 0000000..1b1ffc2
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_msc_data.h
@@ -0,0 +1,143 @@
+/*
+ * Data for the true BSC
+ *
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2015 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * NOTE: This is about a *remote* MSC for OsmoBSC and is not part of libmsc.
+ */
+
+#ifndef _OSMO_MSC_DATA_H
+#define _OSMO_MSC_DATA_H
+
+#include "bsc_msc.h"
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm23003.h>
+
+#include <regex.h>
+
+struct osmo_bsc_rf;
+struct gsm_network;
+
+struct gsm_audio_support {
+        uint8_t hr  : 1,
+                ver : 7;
+};
+
+enum {
+	MSC_CON_TYPE_NORMAL,
+	MSC_CON_TYPE_LOCAL,
+};
+
+/*! /brief Information on a remote MSC for libbsc.
+ */
+struct bsc_msc_data {
+	struct llist_head entry;
+
+	/* Back pointer */
+	struct gsm_network *network;
+
+	int allow_emerg;
+	int type;
+
+	/* local call routing */
+	char *local_pref;
+	regex_t local_pref_reg;
+
+
+	/* Connection data */
+	char *bsc_token;
+	uint8_t bsc_key[16];
+	uint8_t bsc_key_present;
+
+	int ping_timeout;
+	int pong_timeout;
+	struct osmo_timer_list ping_timer;
+	struct osmo_timer_list pong_timer;
+	int advanced_ping;
+	struct bsc_msc_connection *msc_con;
+	struct osmo_plmn_id core_plmn;
+	int core_lac;
+	int core_ci;
+	int rtp_base;
+
+	/* audio codecs */
+	struct gsm48_multi_rate_conf amr_conf;
+	struct gsm_audio_support **audio_support;
+	int audio_length;
+
+	/* destinations */
+	struct llist_head dests;
+
+	/* ussd welcome text */
+	char *ussd_welcome_txt;
+
+	/* mgcp agent */
+	struct osmo_wqueue mgcp_agent;
+
+	int nr;
+
+	/* ussd msc connection lost text */
+	char *ussd_msc_lost_txt;
+
+	/* ussd text when MSC has entered the grace period */
+	char *ussd_grace_txt;
+
+	char *acc_lst_name;
+};
+
+/*
+ * Per BSC data.
+ */
+struct osmo_bsc_data {
+	struct gsm_network *network;
+
+	/* msc configuration */
+	struct llist_head mscs;
+
+	/* rf ctl related bits */
+	char *mid_call_txt;
+	int mid_call_timeout;
+	char *rf_ctrl_name;
+	struct osmo_bsc_rf *rf_ctrl;
+	int auto_off_timeout;
+
+	/* ussd text when there is no MSC available */
+	char *ussd_no_msc_txt;
+
+	char *acc_lst_name;
+};
+
+
+int osmo_bsc_msc_init(struct bsc_msc_data *msc);
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet);
+int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto);
+int msc_queue_write_with_ping(struct bsc_msc_connection *, struct msgb *msg, int proto);
+
+int osmo_bsc_audio_init(struct gsm_network *network);
+
+struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *, int);
+struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *, int);
+
+void bsc_notify_and_close_conns(struct bsc_msc_connection *msc_con);
+
+#endif
diff --git a/openbsc/include/openbsc/bsc_msg_filter.h b/openbsc/include/openbsc/bsc_msg_filter.h
new file mode 100644
index 0000000..22b8447
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_msg_filter.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/msgfile.h>
+#include <osmocom/core/linuxrbtree.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <regex.h>
+
+struct vty;
+struct gsm48_hdr;
+
+struct bsc_filter_reject_cause {
+	int lu_reject_cause;
+	int cm_reject_cause;
+};
+
+struct bsc_filter_barr_entry {
+	struct rb_node node;
+
+	char *imsi;
+	int cm_reject_cause;
+	int lu_reject_cause;
+};
+
+enum bsc_filter_acc_ctr {
+	ACC_LIST_LOCAL_FILTER,
+	ACC_LIST_GLOBAL_FILTER,
+};
+
+struct bsc_msg_acc_lst {
+	struct llist_head list;
+
+	/* counter */
+	struct rate_ctr_group *stats;
+
+	/* the name of the list */
+	const char *name;
+	struct llist_head fltr_list;
+};
+
+struct bsc_msg_acc_lst_entry {
+	struct llist_head list;
+
+	/* the filter */
+	char *imsi_allow;
+	regex_t imsi_allow_re;
+	char *imsi_deny;
+	regex_t imsi_deny_re;
+
+	/* reject reasons for the access lists */
+	int cm_reject_cause;
+	int lu_reject_cause;
+};
+
+enum {
+	FLT_CON_TYPE_NONE,
+	FLT_CON_TYPE_LU,
+	FLT_CON_TYPE_CM_SERV_REQ,
+	FLT_CON_TYPE_PAG_RESP,
+	FLT_CON_TYPE_SSA,
+	FLT_CON_TYPE_LOCAL_REJECT,
+	FLT_CON_TYPE_OTHER,
+};
+
+
+struct bsc_filter_state {
+	char *imsi;
+	int imsi_checked;
+	int con_type;
+};
+
+struct bsc_filter_request {
+	void *ctx;
+	struct rb_root *black_list;
+	struct llist_head *access_lists;
+	const char *local_lst_name;
+	const char *global_lst_name;
+	int bsc_nr;
+};
+
+
+int bsc_filter_barr_adapt(void *ctx, struct rb_root *rbtree, const struct osmo_config_list *);
+int bsc_filter_barr_find(struct rb_root *root, const char *imsi, int *cm, int *lu);
+
+/**
+ * Content filtering.
+ */
+int bsc_msg_filter_initial(struct gsm48_hdr *hdr, size_t size,
+			struct bsc_filter_request *req,
+			int *con_type, char **imsi,
+			struct bsc_filter_reject_cause *cause);
+int bsc_msg_filter_data(struct gsm48_hdr *hdr, size_t size,
+			struct bsc_filter_request *req,
+			struct bsc_filter_state *state,
+			struct bsc_filter_reject_cause *cause);
+
+/* IMSI allow/deny handling */
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_find(struct llist_head *lst, const char *name);
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_get(void *ctx, struct llist_head *lst, const char *name);
+void bsc_msg_acc_lst_delete(struct bsc_msg_acc_lst *lst);
+
+struct bsc_msg_acc_lst_entry *bsc_msg_acc_lst_entry_create(struct bsc_msg_acc_lst *);
+int bsc_msg_acc_lst_check_allow(struct bsc_msg_acc_lst *lst, const char *imsi);
+
+void bsc_msg_acc_lst_vty_init(void *ctx, struct llist_head *lst, int node);
+void bsc_msg_acc_lst_write(struct vty *vty);
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
new file mode 100644
index 0000000..3eba70d
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -0,0 +1,470 @@
+/*
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_NAT_H
+#define BSC_NAT_H
+
+#include "mgcp.h"
+#include "bsc_msg_filter.h"
+
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/msgfile.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <regex.h>
+#include <stdbool.h>
+
+#define DIR_BSC 1
+#define DIR_MSC 2
+
+#define PAGIN_GROUP_UNASSIGNED -1
+
+struct sccp_source_reference;
+struct nat_sccp_connection;
+struct bsc_nat_parsed;
+struct bsc_nat;
+struct bsc_nat_ussd_con;
+struct nat_rewrite_rule;
+
+/*
+ * Is this terminated to the MSC, to the local machine (release
+ * handling for IMSI filtering) or to a USSD provider?
+ */
+enum {
+	NAT_CON_END_MSC,
+	NAT_CON_END_LOCAL,
+	NAT_CON_END_USSD,
+};
+
+/*
+ * Pending command entry
+ */
+struct bsc_cmd_list {
+	struct llist_head list_entry;
+
+	struct osmo_timer_list timeout;
+
+	/* The NATed ID used on the bsc_con*/
+	int nat_id;
+
+	/* The command from the control connection */
+	struct ctrl_cmd *cmd;
+};
+
+/*
+ * Per BSC data structure
+ */
+struct bsc_connection {
+	struct llist_head list_entry;
+
+	/* do we know anything about this BSC? */
+	int authenticated;
+	uint8_t last_rand[16];
+
+	/* the fd we use to communicate */
+	struct osmo_wqueue write_queue;
+
+	/* incoming message buffer */
+	struct msgb *pending_msg;
+
+	/* the BSS associated */
+	struct bsc_config *cfg;
+
+	/* a timeout node */
+	struct osmo_timer_list id_timeout;
+
+	/* pong timeout */
+	struct osmo_timer_list ping_timeout;
+	struct osmo_timer_list pong_timeout;
+
+	/* mgcp related code */
+	char *_endpoint_status;
+	int number_multiplexes;
+	int max_endpoints;
+	int last_endpoint;
+	int next_transaction;
+	uint32_t pending_dlcx_count;
+	struct llist_head pending_dlcx;
+
+	/* track the pending commands for this BSC */
+	struct llist_head cmd_pending;
+	int last_id;
+
+	/* a back pointer */
+	struct bsc_nat *nat;
+};
+
+/**
+ * Stats per BSC
+ */
+struct bsc_config_stats {
+	struct rate_ctr_group *ctrg;
+};
+
+enum bsc_cfg_ctr {
+	BCFG_CTR_SCCP_CONN,
+	BCFG_CTR_SCCP_CALLS,
+	BCFG_CTR_NET_RECONN,
+	BCFG_CTR_DROPPED_SCCP,
+	BCFG_CTR_DROPPED_CALLS,
+	BCFG_CTR_REJECTED_CR,
+	BCFG_CTR_REJECTED_MSG,
+	BCFG_CTR_ILL_PACKET,
+	BCFG_CTR_CON_TYPE_LU,
+	BCFG_CTR_CON_CMSERV_RQ,
+	BCFG_CTR_CON_PAG_RESP,
+	BCFG_CTR_CON_SSA,
+	BCFG_CTR_CON_OTHER,
+};
+
+/**
+ * One BSC entry in the config
+ */
+struct bsc_config {
+	struct llist_head entry;
+
+	uint8_t key[16];
+	uint8_t key_present;
+	char *token;
+	int nr;
+
+	char *description;
+
+	/* imsi white and blacklist */
+	char *acc_lst_name;
+
+	int forbid_paging;
+	int paging_group;
+
+	/* audio handling */
+	int max_endpoints;
+
+	/* used internally for reload handling */
+	bool remove;
+	bool token_updated;
+
+	/* backpointer */
+	struct bsc_nat *nat;
+
+	struct bsc_config_stats stats;
+
+	struct llist_head lac_list;
+
+	/* Osmux is enabled/disabled per BSC */
+	int osmux;
+
+	/* Use a jitterbuffer on the bts-side receiver */
+	bool bts_use_jibuf;
+	/* Minimum and maximum buffer size for the jitter buffer, in ms */
+	uint32_t bts_jitter_delay_min;
+	uint32_t bts_jitter_delay_max;
+	/* Enabled if explicitly configured through VTY: */
+	bool bts_use_jibuf_override;
+	bool bts_jitter_delay_min_override;
+	bool bts_jitter_delay_max_override;
+};
+
+struct bsc_lac_entry {
+	struct llist_head entry;
+	uint16_t lac;
+};
+
+struct bsc_nat_paging_group {
+	struct llist_head entry;
+
+	/* list of lac entries */
+	struct llist_head lists;
+	int nr;
+};
+
+/**
+ * BSCs point of view of endpoints
+ */
+struct bsc_endpoint {
+	/* the operation that is carried out */
+	int transaction_state;
+	/* the pending transaction id */
+	char *transaction_id;
+	/* the bsc we are talking to */
+	struct bsc_connection *bsc;
+};
+
+/**
+ * Statistic for the nat.
+ */
+struct bsc_nat_statistics {
+	struct {
+		struct osmo_counter *conn;
+		struct osmo_counter *calls;
+	} sccp;
+
+	struct {
+		struct osmo_counter *reconn;
+                struct osmo_counter *auth_fail;
+	} bsc;
+
+	struct {
+		struct osmo_counter *reconn;
+	} msc;
+
+	struct {
+		struct osmo_counter *reconn;
+	} ussd;
+};
+
+/**
+ * the structure of the "nat" network
+ */
+struct bsc_nat {
+	/* active SCCP connections that need patching */
+	struct llist_head sccp_connections;
+
+	/* active BSC connections that need patching */
+	struct llist_head bsc_connections;
+
+	/* access lists */
+	struct llist_head access_lists;
+
+	/* paging groups */
+	struct llist_head paging_groups;
+
+	/* known BSC's */
+	struct llist_head bsc_configs;
+	int num_bsc;
+	int bsc_ip_dscp;
+
+	/* MGCP config */
+	struct mgcp_config *mgcp_cfg;
+	uint8_t mgcp_msg[4096];
+	int mgcp_length;
+	int mgcp_ipa;
+	int sdp_ensure_amr_mode_set;
+	int paging_bss_forward;
+
+	/* msc things */
+	struct llist_head dests;
+	struct bsc_msc_dest *main_dest;
+	struct bsc_msc_connection *msc_con;
+	char *token;
+
+	/* timeouts */
+	int auth_timeout;
+	int ping_timeout;
+	int pong_timeout;
+
+	struct bsc_endpoint *bsc_endpoints;
+
+	/* path to file with BSC config */
+	char *include_file;
+	char *include_base;
+	char *resolved_path;
+
+	/* filter */
+	char *acc_lst_name;
+
+	/* Barring of subscribers with a rb tree */
+	struct rb_root imsi_black_list;
+	char *imsi_black_list_fn;
+
+	/* number rewriting */
+	char *num_rewr_name;
+	struct llist_head num_rewr;
+	char *num_rewr_post_name;
+	struct llist_head num_rewr_post;
+
+	char *smsc_rewr_name;
+	struct llist_head smsc_rewr;
+	char *tpdest_match_name;
+	struct llist_head tpdest_match;
+	char *sms_clear_tp_srr_name;
+	struct llist_head sms_clear_tp_srr;
+	char *sms_num_rewr_name;
+	struct llist_head sms_num_rewr;
+
+	/* more rewriting */
+	char *num_rewr_trie_name;
+	struct nat_rewrite *num_rewr_trie;
+
+	/* USSD messages  we want to match */
+	char *ussd_lst_name;
+	char *ussd_query;
+	regex_t ussd_query_re;
+	char *ussd_token;
+	char *ussd_local;
+	struct osmo_fd ussd_listen;
+	struct bsc_nat_ussd_con *ussd_con;
+
+	/* for maintainenance */
+	int blocked;
+
+	/* statistics */
+	struct bsc_nat_statistics stats;
+
+	/* control interface */
+	struct ctrl_handle *ctrl;
+};
+
+struct bsc_nat_ussd_con {
+	struct osmo_wqueue queue;
+	struct bsc_nat *nat;
+	int authorized;
+
+	struct msgb *pending_msg;
+
+	struct osmo_timer_list auth_timeout;
+};
+
+/* create and init the structures */
+struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token,
+				    unsigned int number);
+struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num);
+struct bsc_config *bsc_config_by_token(struct bsc_nat *nat, const char *token, int len);
+void bsc_config_free(struct bsc_config *);
+void bsc_config_add_lac(struct bsc_config *cfg, int lac);
+void bsc_config_del_lac(struct bsc_config *cfg, int lac);
+int bsc_config_handles_lac(struct bsc_config *cfg, int lac);
+
+struct bsc_nat *bsc_nat_alloc(void);
+struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat);
+void bsc_nat_set_msc_ip(struct bsc_nat *bsc, const char *ip);
+
+void sccp_connection_destroy(struct nat_sccp_connection *);
+void bsc_close_connection(struct bsc_connection *);
+
+const char *bsc_con_type_to_string(int type);
+
+/**
+ * parse the given message into the above structure
+ */
+struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg);
+
+/**
+ * filter based on IP Access header in both directions
+ */
+int bsc_nat_filter_ipa(int direction, struct msgb *msg, struct bsc_nat_parsed *parsed);
+int bsc_nat_vty_init(struct bsc_nat *nat);
+int bsc_nat_find_paging(struct msgb *msg, const uint8_t **,int *len);
+
+/**
+ * SCCP patching and handling
+ */
+struct nat_sccp_connection *create_sccp_src_ref(struct bsc_connection *bsc, struct bsc_nat_parsed *parsed);
+int update_sccp_src_ref(struct nat_sccp_connection *sccp, struct bsc_nat_parsed *parsed);
+void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed);
+struct nat_sccp_connection *patch_sccp_src_ref_to_bsc(struct msgb *, struct bsc_nat_parsed *, struct bsc_nat *);
+struct nat_sccp_connection *patch_sccp_src_ref_to_msc(struct msgb *, struct bsc_nat_parsed *, struct bsc_connection *);
+struct nat_sccp_connection *bsc_nat_find_con_by_bsc(struct bsc_nat *, struct sccp_source_reference *);
+
+/**
+ * MGCP/Audio handling
+ */
+int bsc_mgcp_nr_multiplexes(int max_endpoints);
+int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length);
+int bsc_mgcp_assign_patch(struct nat_sccp_connection *, struct msgb *msg);
+void bsc_mgcp_init(struct nat_sccp_connection *);
+void bsc_mgcp_dlcx(struct nat_sccp_connection *);
+void bsc_mgcp_free_endpoints(struct bsc_nat *nat);
+int bsc_mgcp_nat_init(struct bsc_nat *nat);
+
+struct nat_sccp_connection *bsc_mgcp_find_con(struct bsc_nat *, int endpoint_number);
+struct msgb *bsc_mgcp_rewrite(char *input, int length, int endp, const char *ip,
+			      int port, int osmux, int *first_payload_type, int mode_set);
+void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg);
+
+void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc);
+int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60]);
+uint32_t bsc_mgcp_extract_ci(const char *resp);
+
+
+int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int id);
+int bsc_do_write(struct osmo_wqueue *queue, struct msgb *msg, int id);
+int bsc_write_msg(struct osmo_wqueue *queue, struct msgb *msg);
+int bsc_write_cb(struct osmo_fd *bfd, struct msgb *msg);
+
+int bsc_nat_msc_is_connected(struct bsc_nat *nat);
+
+int bsc_conn_type_to_ctr(struct nat_sccp_connection *conn);
+
+struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed, struct msgb *msg, uint32_t *len);
+
+/** USSD filtering */
+int bsc_ussd_init(struct bsc_nat *nat);
+int bsc_ussd_check(struct nat_sccp_connection *con, struct bsc_nat_parsed *parsed, struct msgb *msg);
+int bsc_ussd_close_connections(struct bsc_nat *nat);
+
+struct msgb *bsc_nat_rewrite_msg(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *, const char *imsi);
+
+/** paging group handling */
+struct bsc_nat_paging_group *bsc_nat_paging_group_num(struct bsc_nat *nat, int group);
+struct bsc_nat_paging_group *bsc_nat_paging_group_create(struct bsc_nat *nat, int group);
+void bsc_nat_paging_group_delete(struct bsc_nat_paging_group *);
+void bsc_nat_paging_group_add_lac(struct bsc_nat_paging_group *grp, int lac);
+void bsc_nat_paging_group_del_lac(struct bsc_nat_paging_group *grp, int lac);
+
+/**
+ * Number rewriting support below
+ */
+struct bsc_nat_num_rewr_entry {
+	struct llist_head list;
+
+	regex_t msisdn_reg;
+	regex_t num_reg;
+
+	char *replace;
+	uint8_t is_prefix_lookup;
+};
+
+void bsc_nat_num_rewr_entry_adapt(void *ctx, struct llist_head *head, const struct osmo_config_list *);
+
+void bsc_nat_send_mgcp_to_msc(struct bsc_nat *bsc_nat, struct msgb *msg);
+void bsc_nat_handle_mgcp(struct bsc_nat *bsc, struct msgb *msg);
+
+struct ctrl_handle *bsc_nat_controlif_setup(struct bsc_nat *nat,
+					    const char *bind_addr, int port);
+void bsc_nat_ctrl_del_pending(struct bsc_cmd_list *pending);
+int bsc_nat_handle_ctrlif_msg(struct bsc_connection *bsc, struct msgb *msg);
+
+int bsc_nat_extract_lac(struct bsc_connection *bsc, struct nat_sccp_connection *con,
+				struct bsc_nat_parsed *parsed, struct msgb *msg);
+
+int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg,
+			struct bsc_nat_parsed *, int *con_type, char **imsi,
+			struct bsc_filter_reject_cause *cause);
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+			struct nat_sccp_connection *con, struct bsc_nat_parsed *parsed,
+			struct bsc_filter_reject_cause *cause);
+
+/**
+ * CTRL interface helper
+ */
+void bsc_nat_inform_reject(struct bsc_connection *bsc, const char *imsi);
+
+/*
+ * Use for testing
+ */
+void bsc_nat_free(struct bsc_nat *nat);
+
+#endif
diff --git a/openbsc/include/openbsc/bsc_nat_callstats.h b/openbsc/include/openbsc/bsc_nat_callstats.h
new file mode 100644
index 0000000..64f9bfc
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_nat_callstats.h
@@ -0,0 +1,55 @@
+/*
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_NAT_CALLSTATS_H
+#define BSC_NAT_CALLSTATS_H
+
+#include <osmocom/core/linuxlist.h>
+
+#include <osmocom/sccp/sccp_types.h>
+
+struct bsc_nat_call_stats {
+	struct llist_head entry;
+
+	struct sccp_source_reference remote_ref;
+	struct sccp_source_reference src_ref; /* as seen by the MSC */
+
+	/* mgcp options */
+	uint32_t ci;
+	int bts_rtp_port;
+	int net_rtp_port;
+	struct in_addr bts_addr;
+	struct in_addr net_addr;
+
+
+	/* as witnessed by the NAT */
+	uint32_t net_ps;
+	uint32_t net_os;
+	uint32_t bts_pr;
+	uint32_t bts_or;
+	uint32_t bts_expected;
+	uint32_t bts_jitter;
+	int      bts_loss;
+
+	uint32_t trans_id;
+	int msc_endpoint;
+};
+
+#endif
diff --git a/openbsc/include/openbsc/bsc_nat_sccp.h b/openbsc/include/openbsc/bsc_nat_sccp.h
new file mode 100644
index 0000000..0824664
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_nat_sccp.h
@@ -0,0 +1,105 @@
+/* NAT utilities using SCCP types */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_NAT_SCCP_H
+#define BSC_NAT_SCCP_H
+
+#include "bsc_msg_filter.h"
+
+#include <osmocom/sccp/sccp_types.h>
+
+/*
+ * For the NAT we will need to analyze and later patch
+ * the received message. This would require us to parse
+ * the IPA and SCCP header twice. Instead of doing this
+ * we will have one analyze structure and have the patching
+ * and filter operate on the same structure.
+ */
+struct bsc_nat_parsed {
+	/* ip access prototype */
+	int ipa_proto;
+
+	/* source local reference */
+	struct sccp_source_reference *src_local_ref;
+
+	/* destination local reference */
+	struct sccp_source_reference *dest_local_ref;
+
+	/* original value */
+	struct sccp_source_reference original_dest_ref;
+
+	/* called ssn number */
+	int called_ssn;
+
+	/* calling ssn number */
+	int calling_ssn;
+
+	/* sccp message type */
+	int sccp_type;
+
+	/* bssap type, e.g. 0 for BSS Management */
+	int bssap;
+
+	/* the gsm0808 message type */
+	int gsm_type;
+};
+
+/*
+ * Per SCCP source local reference patch table. It needs to
+ * be updated on new SCCP connections, connection confirm and reject,
+ * and on the loss of the BSC connection.
+ */
+struct nat_sccp_connection {
+	struct llist_head list_entry;
+
+	struct bsc_connection *bsc;
+	struct bsc_msc_connection *msc_con;
+
+	struct sccp_source_reference real_ref;
+	struct sccp_source_reference patched_ref;
+	struct sccp_source_reference remote_ref;
+	int has_remote_ref;
+
+	/* status */
+	int con_local;
+	int authorized;
+
+	struct bsc_filter_state filter_state;
+
+	uint16_t lac;
+	uint16_t ci;
+
+	/* remember which Transactions we run over the bypass */
+	char ussd_ti[8];
+
+	/*
+	 * audio handling. Remember if we have ever send a CRCX,
+	 * remember the endpoint used by the MSC and BSC.
+	 */
+	int msc_endp;
+	int bsc_endp;
+
+	/* timeout handling */
+	struct timespec creation_time;
+};
+
+
+#endif
diff --git a/openbsc/include/openbsc/bsc_rll.h b/openbsc/include/openbsc/bsc_rll.h
new file mode 100644
index 0000000..729ba60
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_rll.h
@@ -0,0 +1,19 @@
+#ifndef _BSC_RLL_H
+#define _BSC_RLL_H
+
+#include <openbsc/gsm_data.h>
+
+enum bsc_rllr_ind {
+	BSC_RLLR_IND_EST_CONF,
+	BSC_RLLR_IND_REL_IND,
+	BSC_RLLR_IND_ERR_IND,
+	BSC_RLLR_IND_TIMEOUT,
+};
+
+int rll_establish(struct gsm_lchan *lchan, uint8_t link_id,
+		  void (*cb)(struct gsm_lchan *, uint8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data);
+void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type);
+
+#endif /* _BSC_RLL_H */
diff --git a/openbsc/include/openbsc/bsc_subscriber.h b/openbsc/include/openbsc/bsc_subscriber.h
new file mode 100644
index 0000000..324734f
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_subscriber.h
@@ -0,0 +1,43 @@
+/* GSM subscriber details for use in BSC land */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+
+struct log_target;
+
+struct bsc_subscr {
+	struct llist_head entry;
+	int use_count;
+
+	char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+	uint32_t tmsi;
+	uint16_t lac;
+};
+
+const char *bsc_subscr_name(struct bsc_subscr *bsub);
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list,
+						     const char *imsi);
+struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list,
+						     uint32_t tmsi);
+
+struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
+					   const char *imsi);
+struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
+					   uint32_t tmsi);
+
+void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi);
+
+struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub,
+				   const char *file, int line);
+struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub,
+				   const char *file, int line);
+#define bsc_subscr_get(bsub) _bsc_subscr_get(bsub, __BASE_FILE__, __LINE__)
+#define bsc_subscr_put(bsub) _bsc_subscr_put(bsub, __BASE_FILE__, __LINE__)
+
+void log_set_filter_bsc_subscr(struct log_target *target,
+			       struct bsc_subscr *bsub);
diff --git a/openbsc/include/openbsc/bss.h b/openbsc/include/openbsc/bss.h
new file mode 100644
index 0000000..9f16bf7
--- /dev/null
+++ b/openbsc/include/openbsc/bss.h
@@ -0,0 +1,20 @@
+#ifndef _BSS_H_
+#define _BSS_H_
+
+#include <openbsc/gsm_data.h>
+
+struct msgb;
+
+/* start and stop network */
+extern int bsc_network_alloc(mncc_recv_cb_t mncc_recv);
+extern int bsc_network_configure(const char *cfg_file);
+extern int bsc_shutdown_net(struct gsm_network *net);
+
+/* register all supported BTS */
+extern int bts_init(void);
+extern int bts_model_bs11_init(void);
+extern int bts_model_rbs2k_init(void);
+extern int bts_model_nanobts_init(void);
+extern int bts_model_nokia_site_init(void);
+extern int bts_model_sysmobts_init(void);
+#endif
diff --git a/openbsc/include/openbsc/bts_ipaccess_nanobts_omlattr.h b/openbsc/include/openbsc/bts_ipaccess_nanobts_omlattr.h
new file mode 100644
index 0000000..bc7860b
--- /dev/null
+++ b/openbsc/include/openbsc/bts_ipaccess_nanobts_omlattr.h
@@ -0,0 +1,32 @@
+/* OML attribute table generator for ipaccess nanobts */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/msgb.h>
+
+struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts,
+				    struct gsm_bts_trx *trx);
diff --git a/openbsc/include/openbsc/chan_alloc.h b/openbsc/include/openbsc/chan_alloc.h
new file mode 100644
index 0000000..0ba3f96
--- /dev/null
+++ b/openbsc/include/openbsc/chan_alloc.h
@@ -0,0 +1,50 @@
+/* Management functions to allocate/release struct gsm_lchan */
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef _CHAN_ALLOC_H
+#define _CHAN_ALLOC_H
+
+#include "gsm_data.h"
+
+struct gsm_subscriber_connection;
+
+/* Find an allocated channel for a specified subscriber */
+struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr);
+
+/* Allocate a logical channel (SDCCH, TCH, ...) */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, int allow_bigger);
+
+/* Free a logical channel (SDCCH, TCH, ...) */
+void lchan_free(struct gsm_lchan *lchan);
+void lchan_reset(struct gsm_lchan *lchan);
+
+/* Release the given lchan */
+int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode release_mode);
+
+struct pchan_load {
+	struct load_counter pchan[_GSM_PCHAN_MAX];
+};
+
+void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts);
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net);
+void bts_update_t3122_chan_load(struct gsm_bts *bts);
+
+int trx_is_usable(struct gsm_bts_trx *trx);
+
+#endif /* _CHAN_ALLOC_H */
diff --git a/openbsc/include/openbsc/common_bsc.h b/openbsc/include/openbsc/common_bsc.h
new file mode 100644
index 0000000..7960383
--- /dev/null
+++ b/openbsc/include/openbsc/common_bsc.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <stdint.h>
+#include <openbsc/common_cs.h>
+
+struct gsm_network *bsc_network_init(void *ctx,
+				     uint16_t country_code,
+				     uint16_t network_code,
+				     mncc_recv_cb_t mncc_recv);
diff --git a/openbsc/include/openbsc/common_cs.h b/openbsc/include/openbsc/common_cs.h
new file mode 100644
index 0000000..4282064
--- /dev/null
+++ b/openbsc/include/openbsc/common_cs.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <stdint.h>
+
+struct msgb;
+struct gsm_network;
+
+typedef int (*mncc_recv_cb_t)(struct gsm_network *, struct msgb *);
+
+struct vty;
+
+struct gsm_network *gsm_network_init(void *ctx,
+				     uint16_t country_code,
+				     uint16_t network_code,
+				     mncc_recv_cb_t mncc_recv);
+
+int common_cs_vty_init(struct gsm_network *network,
+                 int (* config_write_net )(struct vty *));
+struct gsm_network *gsmnet_from_vty(struct vty *v);
diff --git a/openbsc/include/openbsc/ctrl.h b/openbsc/include/openbsc/ctrl.h
new file mode 100644
index 0000000..c5ac210
--- /dev/null
+++ b/openbsc/include/openbsc/ctrl.h
@@ -0,0 +1,4 @@
+#pragma once
+
+struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net,
+					const char *bind_addr, uint16_t port);
diff --git a/openbsc/include/openbsc/db.h b/openbsc/include/openbsc/db.h
new file mode 100644
index 0000000..bb90705
--- /dev/null
+++ b/openbsc/include/openbsc/db.h
@@ -0,0 +1,86 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _DB_H
+#define _DB_H
+
+#include <stdbool.h>
+
+#include "gsm_subscriber.h"
+
+struct gsm_equipment;
+struct gsm_network;
+struct gsm_auth_info;
+struct gsm_auth_tuple;
+struct gsm_sms;
+struct gsm_subscriber;
+
+/* one time initialisation */
+int db_init(const char *name);
+int db_prepare(void);
+int db_fini(void);
+
+/* subscriber management */
+struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
+					    uint64_t smax, bool alloc_exten);
+struct gsm_subscriber *db_get_subscriber(enum gsm_subscriber_field field,
+					 const char *subscr);
+int db_sync_subscriber(struct gsm_subscriber *subscriber);
+int db_subscriber_expire(void *priv, void (*callback)(void *priv, long long unsigned int id));
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber);
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber, uint64_t smin,
+			      uint64_t smax);
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, uint32_t* token);
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char *imei);
+int db_subscriber_delete(struct gsm_subscriber *subscriber);
+int db_sync_equipment(struct gsm_equipment *equip);
+int db_subscriber_update(struct gsm_subscriber *subscriber);
+int db_subscriber_list_active(void (*list_cb)(struct gsm_subscriber*,void*), void*);
+
+/* auth info */
+int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                               struct gsm_subscriber *subscr);
+int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                                struct gsm_subscriber *subscr);
+int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                    struct gsm_subscriber *subscr);
+int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                     struct gsm_subscriber *subscr);
+
+/* SMS store-and-forward */
+int db_sms_store(struct gsm_sms *sms);
+struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id);
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id);
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, unsigned long long min_subscr_id, unsigned int failed);
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr);
+int db_sms_mark_delivered(struct gsm_sms *sms);
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms);
+
+/* APDU blob storage */
+int db_apdu_blob_store(struct gsm_subscriber *subscr, 
+			uint8_t apdu_id_flags, uint8_t len,
+			uint8_t *apdu);
+
+/* Statistics counter storage */
+struct osmo_counter;
+int db_store_counter(struct osmo_counter *ctr);
+struct rate_ctr_group;
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg);
+
+#endif /* _DB_H */
diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h
new file mode 100644
index 0000000..8a4247b
--- /dev/null
+++ b/openbsc/include/openbsc/debug.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <stdio.h>
+#include <osmocom/core/linuxlist.h>
+
+#define DEBUG
+#include <osmocom/core/logging.h>
+
+/* Debug Areas of the code */
+enum {
+	DRLL,
+	DCC,
+	DMM,
+	DRR,
+	DRSL,
+	DNM,
+	DMNCC,
+	DPAG,
+	DMEAS,
+	DSCCP,
+	DMSC,
+	DMGCP,
+	DHO,
+	DDB,
+	DREF,
+	DGPRS,
+	DNS,
+	DBSSGP,
+	DLLC,
+	DSNDCP,
+	DSLHC,
+	DNAT,
+	DCTRL,
+	DSMPP,
+	DFILTER,
+	DGTPHUB,
+	DRANAP,
+	DSUA,
+	DV42BIS,
+	DPCU,
+	Debug_LastEntry,
+};
+
+struct gsm_subscriber;
+
+void log_set_filter_vlr_subscr(struct log_target *target,
+			       struct gsm_subscriber *vlr_subscr);
+
+extern const struct log_info log_info;
diff --git a/openbsc/include/openbsc/e1_config.h b/openbsc/include/openbsc/e1_config.h
new file mode 100644
index 0000000..538c0b0
--- /dev/null
+++ b/openbsc/include/openbsc/e1_config.h
@@ -0,0 +1,11 @@
+#ifndef _E1_CONFIG_H
+#define _E1_CONFIG_H
+
+#include <openbsc/gsm_data_shared.h>
+
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts);
+int e1_reconfig_trx(struct gsm_bts_trx *trx);
+int e1_reconfig_bts(struct gsm_bts *bts);
+
+#endif /* _E1_CONFIG_H */
+
diff --git a/openbsc/include/openbsc/gsm_04_08.h b/openbsc/include/openbsc/gsm_04_08.h
new file mode 100644
index 0000000..a8b2de9
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_08.h
@@ -0,0 +1,85 @@
+#ifndef _GSM_04_08_H
+#define _GSM_04_08_H
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <openbsc/meas_rep.h>
+
+struct msgb;
+struct gsm_bts;
+struct gsm_subscriber;
+struct gsm_network;
+struct gsm_trans;
+struct gsm_subscriber_connection;
+struct amr_multirate_conf;
+struct amr_mode;
+struct bsc_subscr;
+
+#define GSM48_ALLOC_SIZE	2048
+#define GSM48_ALLOC_HEADROOM	256
+
+static inline struct msgb *gsm48_msgb_alloc_name(const char *name)
+{
+	return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
+				   name);
+}
+
+/* config options controlling the behaviour of the lower leves */
+void gsm0408_allow_everyone(int allow);
+void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
+void gsm0408_clear_all_trans(struct gsm_network *net, int protocol);
+int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id);
+int gsm0408_new_conn(struct gsm_subscriber_connection *conn);
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *bts, uint8_t ra);
+/* don't use "enum gsm_chreq_reason_t" to avoid circular dependency */
+int get_reason_by_chreq(uint8_t ra, int neci);
+void gsm_net_update_ctype(struct gsm_network *net);
+
+int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn);
+int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, uint8_t *rand,
+			 uint8_t *autn, int key_seq);
+int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn);
+int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn);
+int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+				enum gsm48_reject_value value);
+int gsm48_send_rr_release(struct gsm_lchan *lchan);
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv);
+int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, uint8_t apdu_id,
+			   uint8_t apdu_len, const uint8_t *apdu);
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_class);
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+		      uint8_t power_command, uint8_t ho_ref);
+
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg);
+
+/* convert a ASCII phone number to call-control BCD */
+int encode_bcd_number(uint8_t *bcd_lv, uint8_t max_len,
+		      int h_len, const char *input);
+int decode_bcd_number(char *output, int output_len, const uint8_t *bcd_lv,
+		      int h_len);
+
+int send_siemens_mrpci(struct gsm_lchan *lchan, uint8_t *classmark2_lv);
+int gsm48_extract_mi(uint8_t *classmark2, int length, char *mi_string, uint8_t *mi_type);
+int gsm48_paging_extract_mi(struct gsm48_pag_resp *pag, int length, char *mi_string, uint8_t *mi_type);
+int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg, struct bsc_subscr *bsub);
+
+int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t lchan_mode);
+int gsm48_rx_rr_modif_ack(struct msgb *msg);
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg);
+
+struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value);
+struct msgb *gsm48_create_loc_upd_rej(uint8_t cause);
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+			   const struct gsm_lchan *lchan);
+
+void release_security_operation(struct gsm_subscriber_connection *conn);
+void allocate_security_operation(struct gsm_subscriber_connection *conn);
+
+int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes);
+
+#endif
diff --git a/openbsc/include/openbsc/gsm_04_11.h b/openbsc/include/openbsc/gsm_04_11.h
new file mode 100644
index 0000000..017c887
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_11.h
@@ -0,0 +1,47 @@
+#ifndef _GSM_04_11_H
+#define _GSM_04_11_H
+
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#define UM_SAPI_SMS 3	/* See GSM 04.05/04.06 */
+
+/* SMS deliver PDU */
+struct sms_deliver {
+	uint8_t mti:2;		/* message type indicator */
+	uint8_t mms:1;		/* more messages to send */
+	uint8_t rp:1;		/* reply path */
+	uint8_t udhi:1;	/* user data header indicator */
+	uint8_t sri:1;		/* status report indication */
+	uint8_t *orig_addr;	/* originating address */
+	uint8_t pid;		/* protocol identifier */
+	uint8_t dcs;		/* data coding scheme */
+				/* service centre time stamp */
+	uint8_t ud_len;	/* user data length */
+	uint8_t *user_data;	/* user data */
+
+	uint8_t msg_ref;	/* message reference */
+	uint8_t *smsc;
+};
+
+struct msgb;
+
+int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+struct gsm_sms *sms_alloc(void);
+void sms_free(struct gsm_sms *sms);
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, struct gsm_subscriber *sender, int dcs, const char *text);
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans);
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms);
+int gsm411_send_sms(struct gsm_subscriber_connection *conn,
+		    struct gsm_sms *sms);
+void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn);
+
+uint8_t sms_next_rp_msg_ref(uint8_t *next_rp_ref);
+
+int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref);
+int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref,
+			 uint8_t cause);
+
+#endif
diff --git a/openbsc/include/openbsc/gsm_04_14.h b/openbsc/include/openbsc/gsm_04_14.h
new file mode 100644
index 0000000..3cdbe04
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_14.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_04_14.h>
+
+int gsm0414_tx_close_tch_loop_cmd(struct gsm_subscriber_connection *conn,
+				  enum gsm414_tch_loop_mode loop_mode);
+int gsm0414_tx_open_loop_cmd(struct gsm_subscriber_connection *conn);
+int gsm0414_tx_act_emmi_cmd(struct gsm_subscriber_connection *conn);
+int gsm0414_tx_test_interface(struct gsm_subscriber_connection *conn,
+			      uint8_t tested_devs);
+int gsm0414_tx_reset_ms_pos_store(struct gsm_subscriber_connection *conn,
+				  uint8_t technology);
+
+int gsm0414_rcv_test(struct gsm_subscriber_connection *conn,
+		     struct msgb *msg);
diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h
new file mode 100644
index 0000000..ce1b5c2
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_80.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_04_80.h>
+#include <osmocom/gsm/gsm0480.h>
+
+struct gsm_subscriber_connection;
+
+int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
+			       const struct msgb *in_msg, const char* response_text,
+			       const struct ss_request *req);
+int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
+			     const struct msgb *msg,
+			     const struct ss_request *request);
+
+int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level,
+			 const char *text);
+int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn);
+
+int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level,
+			 const char *text);
+int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn);
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
new file mode 100644
index 0000000..f3d85ac
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -0,0 +1,626 @@
+#ifndef _GSM_DATA_H
+#define _GSM_DATA_H
+
+#include <stdint.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/gsm/gsm23003.h>
+
+#include <osmocom/crypt/auth.h>
+
+#include <openbsc/rest_octets.h>
+#include <openbsc/common_cs.h>
+
+/** annotations for msgb ownership */
+#define __uses
+
+#define OBSC_NM_W_ACK_CB(__msgb) (__msgb)->cb[3]
+
+struct mncc_sock_state;
+struct gsm_subscriber_group;
+struct bsc_subscr;
+
+#define OBSC_LINKID_CB(__msgb)	(__msgb)->cb[3]
+
+#define tmsi_from_string(str) strtoul(str, NULL, 10)
+
+/* 3-bit long values */
+#define EARFCN_PRIO_INVALID 8
+#define EARFCN_MEAS_BW_INVALID 8
+/* 5-bit long values */
+#define EARFCN_QRXLV_INVALID 32
+#define EARFCN_THRESH_LOW_INVALID 32
+
+enum gsm_security_event {
+	GSM_SECURITY_NOAVAIL,
+	GSM_SECURITY_AUTH_FAILED,
+	GSM_SECURITY_SUCCEEDED,
+	GSM_SECURITY_ALREADY,
+};
+
+struct msgb;
+typedef int gsm_cbfn(unsigned int hooknum,
+		     unsigned int event,
+		     struct msgb *msg,
+		     void *data, void *param);
+
+/* Real authentication information containing Ki */
+enum gsm_auth_algo {
+	AUTH_ALGO_NONE,
+	AUTH_ALGO_XOR,
+	AUTH_ALGO_COMP128v1,
+	AUTH_ALGO_COMP128v2,
+	AUTH_ALGO_COMP128v3,
+};
+
+struct gsm_auth_info {
+	enum gsm_auth_algo auth_algo;
+	unsigned int a3a8_ki_len;
+	uint8_t a3a8_ki[16];
+};
+
+struct gsm_auth_tuple {
+	int use_count;
+	int key_seq;
+	struct osmo_auth_vector vec;
+};
+#define GSM_KEY_SEQ_INVAL	7	/* GSM 04.08 - 10.5.1.2 */
+
+/*
+ * LOCATION UPDATING REQUEST state
+ *
+ * Our current operation is:
+ *	- Get imei/tmsi
+ *	- Accept/Reject according to global policy
+ */
+struct gsm_loc_updating_operation {
+        struct osmo_timer_list updating_timer;
+	unsigned int waiting_for_imsi : 1;
+	unsigned int waiting_for_imei : 1;
+	unsigned int key_seq : 4;
+};
+
+/*
+ * AUTHENTICATION/CIPHERING state
+ */
+struct gsm_security_operation {
+	struct gsm_auth_tuple atuple;
+	gsm_cbfn *cb;
+	void *cb_data;
+};
+
+/*
+ * A dummy to keep a connection up for at least
+ * a couple of seconds to work around MSC issues.
+ */
+struct gsm_anchor_operation {
+	struct osmo_timer_list timeout;
+};
+
+/* Maximum number of neighbor cells whose average we track */
+#define MAX_NEIGH_MEAS		10
+/* Maximum size of the averaging window for neighbor cells */
+#define MAX_WIN_NEIGH_AVG	10
+
+/* processed neighbor measurements for one cell */
+struct neigh_meas_proc {
+	uint16_t arfcn;
+	uint8_t bsic;
+	uint8_t rxlev[MAX_WIN_NEIGH_AVG];
+	unsigned int rxlev_cnt;
+	uint8_t last_seen_nr;
+};
+
+enum ran_type {
+       RAN_UNKNOWN,
+       RAN_GERAN_A,	/* 2G / A-interface */
+       RAN_UTRAN_IU,	/* 3G / Iu-interface (IuCS or IuPS) */
+};
+
+/* active radio connection of a mobile subscriber */
+struct gsm_subscriber_connection {
+	struct llist_head entry;
+
+	/* To whom we are allocated at the moment */
+	struct gsm_subscriber *subscr;
+
+	/* libbsc subscriber information */
+	struct bsc_subscr *bsub;
+
+	/* LU expiration handling */
+	uint8_t expire_timer_stopped;
+	/* SMS helpers for libmsc */
+	uint8_t next_rp_ref;
+
+	/*
+	 * Operations that have a state and might be pending
+	 */
+	struct gsm_loc_updating_operation *loc_operation;
+	struct gsm_security_operation *sec_operation;
+	struct gsm_anchor_operation *anch_operation;
+
+	/* Are we part of a special "silent" call */
+	int silent_call;
+
+	/* MNCC rtp bridge markers */
+	int mncc_rtp_bridge;
+	int mncc_rtp_create_pending;
+	int mncc_rtp_connect_pending;
+
+	/* bsc structures */
+	struct osmo_bsc_sccp_con *sccp_con; /* BSC */
+
+	/* back pointers */
+	struct gsm_network *network;
+
+	int in_release;
+	struct gsm_lchan *lchan; /* BSC */
+	struct gsm_lchan *ho_lchan; /* BSC */
+	struct gsm_bts *bts; /* BSC */
+
+	/* for assignment handling */
+	struct osmo_timer_list T10; /* BSC */
+	struct gsm_lchan *secondary_lchan; /* BSC */
+
+	/* connected via 2G or 3G? */
+	enum ran_type via_ran;
+};
+
+
+#define ROLE_BSC
+#include "gsm_data_shared.h"
+
+enum {
+	BTS_STAT_CHAN_LOAD_AVERAGE,
+	BTS_STAT_T3122,
+};
+
+enum {
+	BSC_CTR_CHREQ_TOTAL,
+	BSC_CTR_CHREQ_NO_CHANNEL,
+	BSC_CTR_HANDOVER_ATTEMPTED,
+	BSC_CTR_HANDOVER_NO_CHANNEL,
+	BSC_CTR_HANDOVER_TIMEOUT,
+	BSC_CTR_HANDOVER_COMPLETED,
+	BSC_CTR_HANDOVER_FAILED,
+	BSC_CTR_PAGING_ATTEMPTED,
+	BSC_CTR_PAGING_DETACHED,
+	BSC_CTR_PAGING_COMPLETED,
+	BSC_CTR_PAGING_EXPIRED,
+	BSC_CTR_CHAN_RF_FAIL,
+	BSC_CTR_CHAN_RLL_ERR,
+	BSC_CTR_BTS_OML_FAIL,
+	BSC_CTR_BTS_RSL_FAIL,
+	BSC_CTR_CODEC_AMR_F,
+	BSC_CTR_CODEC_AMR_H,
+	BSC_CTR_CODEC_EFR,
+	BSC_CTR_CODEC_V1_FR,
+	BSC_CTR_CODEC_V1_HR,
+};
+
+static const struct rate_ctr_desc bsc_ctr_description[] = {
+	[BSC_CTR_CHREQ_TOTAL] = 		{"chreq:total", "Received channel requests."},
+	[BSC_CTR_CHREQ_NO_CHANNEL] = 		{"chreq:no_channel", "Sent to MS no channel available."},
+	[BSC_CTR_HANDOVER_ATTEMPTED] = 		{"handover:attempted", "Received handover attempts."},
+	[BSC_CTR_HANDOVER_NO_CHANNEL] = 	{"handover:no_channel", "Sent no channel available responses."},
+	[BSC_CTR_HANDOVER_TIMEOUT] = 		{"handover:timeout", "Count the amount of timeouts of timer T3103."},
+	[BSC_CTR_HANDOVER_COMPLETED] = 		{"handover:completed", "Received handover completed."},
+	[BSC_CTR_HANDOVER_FAILED] = 		{"handover:failed", "Receive HO FAIL messages."},
+	[BSC_CTR_PAGING_ATTEMPTED] = 		{"paging:attempted", "Paging attempts for a MS."},
+	[BSC_CTR_PAGING_DETACHED] = 		{"paging:detached", "Counts the amount of paging attempts which couldn't sent out any paging request because no responsible bts found."},
+	[BSC_CTR_PAGING_COMPLETED] = 		{"paging:completed", "Paging successful completed."},
+	[BSC_CTR_PAGING_EXPIRED] = 		{"paging:expired", "Paging Request expired because of timeout T3113."},
+	[BSC_CTR_CHAN_RF_FAIL] = 		{"chan:rf_fail", "Received a RF failure indication from BTS."},
+	[BSC_CTR_CHAN_RLL_ERR] = 		{"chan:rll_err", "Received a RLL failure with T200 cause from BTS."},
+	[BSC_CTR_BTS_OML_FAIL] = 		{"bts:oml_fail", "Received a TEI down on a OML link."},
+	[BSC_CTR_BTS_RSL_FAIL] = 		{"bts:rsl_fail", "Received a TEI down on a OML link."},
+	[BSC_CTR_CODEC_AMR_F] =			{"bts:codec_amr_f", "Count the usage of AMR/F codec by channel mode requested."},
+	[BSC_CTR_CODEC_AMR_H] =			{"bts:codec_amr_h", "Count the usage of AMR/H codec by channel mode requested."},
+	[BSC_CTR_CODEC_EFR] = 			{"bts:codec_efr", "Count the usage of EFR codec by channel mode requested."},
+	[BSC_CTR_CODEC_V1_FR] =			{"bts:codec_fr", "Count the usage of FR codec by channel mode requested."},
+	[BSC_CTR_CODEC_V1_HR] =			{"bts:codec_hr", "Count the usage of HR codec by channel mode requested."},
+};
+
+enum {
+	MSC_CTR_LOC_UPDATE_TYPE_ATTACH,
+	MSC_CTR_LOC_UPDATE_TYPE_NORMAL,
+	MSC_CTR_LOC_UPDATE_TYPE_PERIODIC,
+	MSC_CTR_LOC_UPDATE_TYPE_DETACH,
+	MSC_CTR_LOC_UPDATE_FAILED,
+	MSC_CTR_LOC_UPDATE_COMPLETED,
+	MSC_CTR_SMS_SUBMITTED,
+	MSC_CTR_SMS_NO_RECEIVER,
+	MSC_CTR_SMS_DELIVERED,
+	MSC_CTR_SMS_RP_ERR_MEM,
+	MSC_CTR_SMS_RP_ERR_OTHER,
+	MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR,
+	MSC_CTR_CALL_MO_SETUP,
+	MSC_CTR_CALL_MO_CONNECT_ACK,
+	MSC_CTR_CALL_MT_SETUP,
+	MSC_CTR_CALL_MT_CONNECT,
+	MSC_CTR_CALL_ACTIVE,
+	MSC_CTR_CALL_COMPLETE,
+	MSC_CTR_CALL_INCOMPLETE,
+};
+
+static const struct rate_ctr_desc msc_ctr_description[] = {
+	[MSC_CTR_LOC_UPDATE_TYPE_ATTACH] = 		{"loc_update_type:attach", "Received location update imsi attach requests."},
+	[MSC_CTR_LOC_UPDATE_TYPE_NORMAL] = 		{"loc_update_type:normal", "Received location update normal requests."},
+	[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC] = 		{"loc_update_type:periodic", "Received location update periodic requests."},
+	[MSC_CTR_LOC_UPDATE_TYPE_DETACH] = 		{"loc_update_type:detach", "Received location update detach indication."},
+	[MSC_CTR_LOC_UPDATE_FAILED] = 		{"loc_update_resp:failed", "Rejected location updates."},
+	[MSC_CTR_LOC_UPDATE_COMPLETED] = 	{"loc_update_resp:completed", "Successful location updates."},
+	[MSC_CTR_SMS_SUBMITTED] = 		{"sms:submitted", "Received a RPDU from a MS (MO)."},
+	[MSC_CTR_SMS_NO_RECEIVER] = 		{"sms:no_receiver", "Counts SMS which couldn't routed because no receiver found."},
+	[MSC_CTR_SMS_DELIVERED] = 		{"sms:delivered", "Global SMS Deliver attempts."},
+	[MSC_CTR_SMS_RP_ERR_MEM] = 		{"sms:rp_err_mem", "CAUSE_MT_MEM_EXCEEDED errors of MS responses on a sms deliver attempt."},
+	[MSC_CTR_SMS_RP_ERR_OTHER] = 		{"sms:rp_err_other", "Other error of MS responses on a sms delive attempt."},
+	[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR] =	{"sms:deliver_unknown_error", "Unknown error occured during sms delivery."},
+	/* FIXME: count also sms delivered */
+	[MSC_CTR_CALL_MO_SETUP] = 		{"call:mo_setup", "Received setup requests from a MS to init a MO call."},
+	[MSC_CTR_CALL_MO_CONNECT_ACK] = 	{"call:mo_connect_ack", "Received a connect ack from MS of a MO call. Call is now succesful connected up."},
+	[MSC_CTR_CALL_MT_SETUP] = 		{"call:mt_setup", "Sent setup requests to the MS (MT)."},
+	[MSC_CTR_CALL_MT_CONNECT] = 		{"call:mt_connect", "Sent a connect to the MS (MT)."},
+	[MSC_CTR_CALL_ACTIVE] =			{"call:active", "Count total amount of calls that ever reached active state."},
+	[MSC_CTR_CALL_COMPLETE] = 		{"call:complete", "Count total amount of calls which got terminated by disconnect req or ind after reaching active state."},
+	[MSC_CTR_CALL_INCOMPLETE] = 		{"call:incomplete", "Count total amount of call which got terminated by any other reason after reaching active state."},
+};
+
+
+static const struct rate_ctr_group_desc bsc_ctrg_desc = {
+	"bsc",
+	"base station controller",
+	OSMO_STATS_CLASS_GLOBAL,
+	ARRAY_SIZE(bsc_ctr_description),
+	bsc_ctr_description,
+};
+
+static const struct rate_ctr_group_desc msc_ctrg_desc = {
+	"msc",
+	"mobile switching center",
+	OSMO_STATS_CLASS_GLOBAL,
+	ARRAY_SIZE(msc_ctr_description),
+	msc_ctr_description,
+};
+
+enum gsm_auth_policy {
+	GSM_AUTH_POLICY_CLOSED, /* only subscribers authorized in DB */
+	GSM_AUTH_POLICY_ACCEPT_ALL, /* accept everyone, even if not authorized in DB */
+	GSM_AUTH_POLICY_TOKEN, /* accept first, send token per sms, then revoke authorization */
+	GSM_AUTH_POLICY_REGEXP, /* accept IMSIs matching given regexp */
+};
+
+#define GSM_T3101_DEFAULT 10	/* s */
+#define GSM_T3103_DEFAULT 5	/* s */
+#define GSM_T3105_DEFAULT 100	/* ms */
+#define GSM_T3107_DEFAULT 5	/* s */
+#define GSM_T3109_DEFAULT 19	/* s, must be 2s + radio_link_timeout*0.48 */
+#define GSM_T3111_DEFAULT 2	/* s */
+#define GSM_T3113_DEFAULT 60
+#define GSM_T3115_DEFAULT 10
+#define GSM_T3117_DEFAULT 10
+#define GSM_T3119_DEFAULT 10
+#define GSM_T3122_DEFAULT 10
+#define GSM_T3141_DEFAULT 10
+
+struct gsm_tz {
+	int override; /* if 0, use system's time zone instead. */
+	int hr; /* hour */
+	int mn; /* minute */
+	int dst; /* daylight savings */
+};
+
+struct gsm_network {
+	struct osmo_plmn_id plmn;
+
+	char *name_long;
+	char *name_short;
+	enum gsm_auth_policy auth_policy;
+	regex_t authorized_regexp;
+	char *authorized_reg_str;
+	enum gsm48_reject_value reject_cause;
+	int a5_encryption;
+	int neci;
+	int send_mm_info;
+	struct {
+		int active;
+		/* Window RXLEV averaging */
+		unsigned int win_rxlev_avg;	/* number of SACCH frames */
+		/* Window RXQUAL averaging */
+		unsigned int win_rxqual_avg;	/* number of SACCH frames */
+		/* Window RXLEV neighbouring cells averaging */
+		unsigned int win_rxlev_avg_neigh; /* number of SACCH frames */
+
+		/* how often should we check for power budget HO */
+		unsigned int pwr_interval;	/* SACCH frames */
+		/* how much better does a neighbor cell have to be ? */
+		unsigned int pwr_hysteresis;	/* dBm */
+		/* maximum distacne before we try a handover */
+		unsigned int max_distance;	/* TA values */
+	} handover;
+
+	struct rate_ctr_group *bsc_ctrs;
+	struct rate_ctr_group *msc_ctrs;
+	struct osmo_counter *active_calls;
+
+	/* layer 4 */
+	struct mncc_sock_state *mncc_state;
+	mncc_recv_cb_t mncc_recv;
+	struct llist_head upqueue;
+	struct llist_head trans_list;
+	struct bsc_api *bsc_api;
+
+	unsigned int num_bts;
+	struct llist_head bts_list;
+
+	/* timer values */
+	int T3101;
+	int T3103;
+	int T3105;
+	int T3107;
+	int T3109;
+	int T3111;
+	int T3113;
+	int T3115;
+	int T3117;
+	int T3119;
+	int T3122;
+	int T3141;
+
+	/* timer to expire old location updates */
+	struct osmo_timer_list subscr_expire_timer;
+
+	/* Timer for periodic channel load measurements to maintain each BTS's T3122. */
+	struct osmo_timer_list t3122_chan_load_timer;
+
+	/* Radio Resource Location Protocol (TS 04.31) */
+	struct {
+		enum rrlp_mode mode;
+	} rrlp;
+
+	enum gsm_chan_t ctype_by_chreq[18];
+
+	/* Use a TCH for handling requests of type paging any */
+	int pag_any_tch;
+
+	/* MSC data in case we are a true BSC */
+	struct osmo_bsc_data *bsc_data;
+
+	/* subscriber related features */
+	bool auto_create_subscr;
+	bool auto_assign_exten;
+	uint64_t ext_min;
+	uint64_t ext_max;
+	struct gsm_subscriber_group *subscr_group;
+	struct gsm_sms_queue *sms_queue;
+
+	/* nitb related control */
+	int avoid_tmsi;
+
+	/* control interface */
+	struct ctrl_handle *ctrl;
+
+	/* Allow or disallow TCH/F on dynamic TCH/F_TCH/H_PDCH; OS#1778 */
+	bool dyn_ts_allow_tch_f;
+
+	/* all active subscriber connections. */
+	struct llist_head subscr_conns;
+
+	/* if override is nonzero, this timezone data is used for all MM
+	 * contexts. */
+	/* TODO: in OsmoNITB, tz-override used to be BTS-specific. To enable
+	 * BTS|RNC specific timezone overrides for multi-tz networks in
+	 * OsmoMSC, this should be tied to the location area code (LAC). */
+	struct gsm_tz tz;
+
+	/* List of all struct bsc_subscr used in libbsc. This llist_head is
+	 * allocated so that the llist_head pointer itself can serve as a
+	 * talloc context (useful to not have to pass the entire gsm_network
+	 * struct to the bsc_subscr_* API, and for bsc_susbscr unit tests to
+	 * not require gsm_data.h). In an MSC-without-BSC environment, this
+	 * pointer is NULL to indicate absence of a bsc_subscribers list. */
+	struct llist_head *bsc_subscribers;
+};
+
+static inline const struct osmo_location_area_id *bts_lai(struct gsm_bts *bts)
+{
+	static struct osmo_location_area_id lai;
+	lai = (struct osmo_location_area_id){
+		.plmn = bts->network->plmn,
+		.lac = bts->location_area_code,
+	};
+	return &lai;
+}
+
+struct osmo_esme;
+
+enum gsm_sms_source_id {
+	SMS_SOURCE_UNKNOWN = 0,
+	SMS_SOURCE_MS,		/* received from MS */
+	SMS_SOURCE_VTY,		/* received from VTY */
+	SMS_SOURCE_SMPP,	/* received via SMPP */
+};
+
+#define SMS_HDR_SIZE	128
+#define SMS_TEXT_SIZE	256
+
+struct gsm_sms_addr {
+	uint8_t ton;
+	uint8_t npi;
+	char addr[21+1];
+};
+
+struct gsm_sms {
+	unsigned long long id;
+	struct gsm_subscriber *receiver;
+	struct gsm_sms_addr src, dst;
+	enum gsm_sms_source_id source;
+
+	struct {
+		uint8_t transaction_id;
+		uint32_t msg_ref;
+	} gsm411;
+
+	struct {
+		struct osmo_esme *esme;
+		uint32_t sequence_nr;
+		int transaction_mode;
+		char msg_id[16];
+	} smpp;
+
+	unsigned long validity_minutes;
+	time_t created;
+	bool is_report;
+	uint8_t reply_path_req;
+	uint8_t status_rep_req;
+	uint8_t ud_hdr_ind;
+	uint8_t protocol_id;
+	uint8_t data_coding_scheme;
+	uint8_t msg_ref;
+	uint8_t user_data_len;
+	uint8_t user_data[SMS_TEXT_SIZE];
+
+	char text[SMS_TEXT_SIZE];
+};
+
+extern void talloc_ctx_init(void *ctx_root);
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type);
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+				 uint16_t arfcn, uint8_t bsic);
+
+enum gsm_bts_type parse_btstype(const char *arg);
+const char *btstype2str(enum gsm_bts_type type);
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+				struct gsm_bts *start_bts);
+
+extern void *tall_bsc_ctx;
+extern int ipacc_rtp_direct;
+
+/* this actaully refers to the IPA transport, not the BTS model */
+static inline int is_ipaccess_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static inline int is_sysmobts_v2(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_OSMOBTS:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static inline int is_siemens_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static inline int is_nokia_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NOKIA_SITE:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static inline int is_e1_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+	case GSM_BTS_TYPE_RBS2000:
+	case GSM_BTS_TYPE_NOKIA_SITE:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg);
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy);
+
+enum rrlp_mode rrlp_mode_parse(const char *arg);
+const char *rrlp_mode_name(enum rrlp_mode mode);
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid);
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode);
+int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode);
+
+void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts);
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts);
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan);
+
+int gsm_btsmodel_set_feature(struct gsm_bts_model *model, enum gsm_bts_features feat);
+int gsm_bts_model_register(struct gsm_bts_model *model);
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lchan);
+void bsc_subscr_con_free(struct gsm_subscriber_connection *conn);
+
+struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network);
+void msc_subscr_con_free(struct gsm_subscriber_connection *conn);
+
+struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net,
+					enum gsm_bts_type type,
+					uint8_t bsic);
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr,
+		   uint8_t e1_ts, uint8_t e1_ts_ss);
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason);
+bool gsm_btsmodel_has_feature(struct gsm_bts_model *model, enum gsm_bts_features feat);
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr);
+int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx);
+int gsm_bts_set_system_infos(struct gsm_bts *bts);
+
+/* generic E1 line operations for all ISDN-based BTS. */
+extern struct e1inp_line_ops bts_isdn_e1inp_line_ops;
+
+extern const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE+1];
+extern const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1];
+
+/* control interface handling */
+int bsc_base_ctrl_cmds_install(void);
+int msc_ctrl_cmds_install(void);
+
+/* dependency handling */
+void bts_depend_mark(struct gsm_bts *bts, int dep);
+void bts_depend_clear(struct gsm_bts *bts, int dep);
+int bts_depend_check(struct gsm_bts *bts);
+int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other);
+
+int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts);
+void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value);
+
+#endif /* _GSM_DATA_H */
diff --git a/openbsc/include/openbsc/gsm_data_shared.h b/openbsc/include/openbsc/gsm_data_shared.h
new file mode 100644
index 0000000..e616c12
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_data_shared.h
@@ -0,0 +1,1023 @@
+#ifndef _GSM_DATA_SHAREDH
+#define _GSM_DATA_SHAREDH
+
+#include <regex.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/rxlev_stat.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/gsm/meas_rep.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/abis/e1_input.h>
+
+#ifndef ROLE_BSC
+#include <osmocom/gsm/lapdm.h>
+#endif
+
+#include <openbsc/acc_ramp.h>
+
+/* 16 is the max. number of SI2quater messages according to 3GPP TS 44.018 Table 10.5.2.33b.1:
+   4-bit index is used (2#1111 = 10#15) */
+#define SI2Q_MAX_NUM 16
+/* length in bits (for single SI2quater message) */
+#define SI2Q_MAX_LEN 160
+#define SI2Q_MIN_LEN 18
+
+struct osmo_bsc_data;
+
+struct osmo_bsc_sccp_con;
+struct gsm_sms_queue;
+
+/* RRLP mode of operation */
+enum rrlp_mode {
+	RRLP_MODE_NONE,
+	RRLP_MODE_MS_BASED,
+	RRLP_MODE_MS_PREF,
+	RRLP_MODE_ASS_PREF,
+};
+
+/* Channel Request reason */
+enum gsm_chreq_reason_t {
+	GSM_CHREQ_REASON_EMERG,
+	GSM_CHREQ_REASON_PAG,
+	GSM_CHREQ_REASON_CALL,
+	GSM_CHREQ_REASON_LOCATION_UPD,
+	GSM_CHREQ_REASON_OTHER,
+	GSM_CHREQ_REASON_PDCH,
+};
+
+/* lchans 0..3 are SDCCH in combined channel configuration,
+   use 4 as magic number for BCCH hack - see osmo-bts-../oml.c:opstart_compl() */
+#define CCCH_LCHAN 4
+
+#define TRX_NR_TS	8
+#define TS_MAX_LCHAN	8
+
+#define HARDCODED_ARFCN 123
+#define HARDCODED_BSIC	0x3f	/* NCC = 7 / BCC = 7 */
+
+/* for multi-drop config */
+#define HARDCODED_BTS0_TS	1
+#define HARDCODED_BTS1_TS	6
+#define HARDCODED_BTS2_TS	11
+
+#define MAX_VERSION_LENGTH 64
+
+#define MAX_BTS_FEATURES 128
+
+enum gsm_hooks {
+	GSM_HOOK_NM_SWLOAD,
+	GSM_HOOK_RR_PAGING,
+	GSM_HOOK_RR_SECURITY,
+};
+
+enum gsm_paging_event {
+	GSM_PAGING_SUCCEEDED,
+	GSM_PAGING_EXPIRED,
+	GSM_PAGING_OOM,
+	GSM_PAGING_BUSY,
+};
+
+enum bts_gprs_mode {
+	BTS_GPRS_NONE = 0,
+	BTS_GPRS_GPRS = 1,
+	BTS_GPRS_EGPRS = 2,
+};
+
+struct gsm_lchan;
+struct gsm_subscriber;
+struct gsm_mncc;
+struct osmo_rtp_socket;
+struct rtp_socket;
+struct bsc_api;
+
+/* Network Management State */
+struct gsm_nm_state {
+	uint8_t operational;
+	uint8_t administrative;
+	uint8_t availability;
+};
+
+struct gsm_abis_mo {
+	uint8_t obj_class;
+	uint8_t procedure_pending;
+	struct abis_om_obj_inst obj_inst;
+	const char *name;
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed *nm_attr;
+	struct gsm_bts *bts;
+};
+
+/* Ericsson OM2000 Managed Object */
+struct abis_om2k_mo {
+	uint8_t class;
+	uint8_t bts;
+	uint8_t assoc_so;
+	uint8_t inst;
+} __attribute__ ((packed));
+
+struct om2k_mo {
+	struct abis_om2k_mo addr;
+	struct osmo_fsm_inst *fsm;
+};
+
+#define MAX_A5_KEY_LEN	(128/8)
+#define A38_XOR_MIN_KEY_LEN	12
+#define A38_XOR_MAX_KEY_LEN	16
+#define A38_COMP128_KEY_LEN	16
+#define RSL_ENC_ALG_A5(x)	(x+1)
+#define MAX_EARFCN_LIST 32
+
+/* is the data link established? who established it? */
+#define LCHAN_SAPI_UNUSED	0
+#define LCHAN_SAPI_MS		1
+#define LCHAN_SAPI_NET		2
+#define LCHAN_SAPI_REL		3
+
+/* state of a logical channel */
+enum gsm_lchan_state {
+	LCHAN_S_NONE,		/* channel is not active */
+	LCHAN_S_ACT_REQ,	/* channel activation requested */
+	LCHAN_S_ACTIVE,		/* channel is active and operational */
+	LCHAN_S_REL_REQ,	/* channel release has been requested */
+	LCHAN_S_REL_ERR,	/* channel is in an error state */
+	LCHAN_S_BROKEN,		/* channel is somehow unusable */
+	LCHAN_S_INACTIVE,	/* channel is set inactive */
+};
+
+/* BTS ONLY */
+#define MAX_NUM_UL_MEAS	104
+#define LC_UL_M_F_L1_VALID	(1 << 0)
+#define LC_UL_M_F_RES_VALID	(1 << 1)
+
+struct bts_ul_meas {
+	/* BER in units of 0.01%: 10.000 == 100% ber, 0 == 0% ber */
+	uint16_t ber10k;
+	/* timing advance offset (in quarter bits) */
+	int16_t ta_offs_qbits;
+	/* C/I ratio in dB */
+	float c_i;
+	/* flags */
+	uint8_t is_sub:1;
+	/* RSSI in dBm * -1 */
+	uint8_t inv_rssi;
+};
+
+struct bts_codec_conf {
+	uint8_t hr;
+	uint8_t efr;
+	uint8_t amr;
+};
+
+struct amr_mode {
+	uint8_t mode;
+	uint8_t threshold;
+	uint8_t hysteresis;
+};
+
+struct amr_multirate_conf {
+	uint8_t gsm48_ie[2];
+	struct amr_mode ms_mode[4];
+	struct amr_mode bts_mode[4];
+	uint8_t num_modes;
+};
+/* /BTS ONLY */
+
+enum lchan_csd_mode {
+	LCHAN_CSD_M_NT,
+	LCHAN_CSD_M_T_1200_75,
+	LCHAN_CSD_M_T_600,
+	LCHAN_CSD_M_T_1200,
+	LCHAN_CSD_M_T_2400,
+	LCHAN_CSD_M_T_9600,
+	LCHAN_CSD_M_T_14400,
+	LCHAN_CSD_M_T_29000,
+	LCHAN_CSD_M_T_32000,
+};
+
+/* State of the SAPIs in the lchan */
+enum lchan_sapi_state {
+	LCHAN_SAPI_S_NONE,
+	LCHAN_SAPI_S_REQ,
+	LCHAN_SAPI_S_ASSIGNED,
+	LCHAN_SAPI_S_REL,
+	LCHAN_SAPI_S_ERROR,
+};
+
+struct gsm_lchan {
+	/* The TS that we're part of */
+	struct gsm_bts_trx_ts *ts;
+	/* The logical subslot number in the TS */
+	uint8_t nr;
+	/* The logical channel type */
+	enum gsm_chan_t type;
+	/* RSL channel mode */
+	enum rsl_cmod_spd rsl_cmode;
+	/* If TCH, traffic channel mode */
+	enum gsm48_chan_mode tch_mode;
+	enum lchan_csd_mode csd_mode;
+	/* State */
+	enum gsm_lchan_state state;
+	const char *broken_reason;
+	/* Power levels for MS and BTS */
+	uint8_t bs_power;
+	uint8_t ms_power;
+	/* Encryption information */
+	struct {
+		uint8_t alg_id;
+		uint8_t key_len;
+		uint8_t key[MAX_A5_KEY_LEN];
+	} encr;
+
+	/* AMR bits */
+	uint8_t mr_ms_lv[7];
+	uint8_t mr_bts_lv[7];
+
+	/* Established data link layer services */
+	uint8_t sapis[8];
+	int sacch_deact;
+
+	struct {
+		uint32_t bound_ip;
+		uint32_t connect_ip;
+		uint16_t bound_port;
+		uint16_t connect_port;
+		uint16_t conn_id;
+		uint8_t rtp_payload;
+		uint8_t rtp_payload2;
+		uint8_t speech_mode;
+#ifdef ROLE_BSC
+		struct rtp_socket *rtp_socket;
+#else
+		struct osmo_rtp_socket *rtp_socket;
+#endif
+	} abis_ip;
+
+	uint8_t rqd_ta;
+
+	char *name;
+
+#ifdef ROLE_BSC
+	struct osmo_timer_list T3101;
+	struct osmo_timer_list T3109;
+	struct osmo_timer_list T3111;
+	struct osmo_timer_list error_timer;
+	struct osmo_timer_list act_timer;
+	struct osmo_timer_list rel_work;
+	uint8_t error_cause;
+
+	/* table of neighbor cell measurements */
+	struct neigh_meas_proc neigh_meas[MAX_NEIGH_MEAS];
+
+	/* cache of last measurement reports on this lchan */
+	struct gsm_meas_rep meas_rep[6];
+	int meas_rep_idx;
+
+	/* GSM Random Access data */
+	struct gsm48_req_ref *rqd_ref;
+
+	struct gsm_subscriber_connection *conn;
+
+	struct {
+		/* channel activation type and handover ref */
+		uint8_t act_type;
+		uint8_t ho_ref;
+		struct gsm48_req_ref *rqd_ref;
+		uint8_t rqd_ta;
+	} dyn;
+#else
+	/* Number of different GsmL1_Sapi_t used in osmo_bts_sysmo is 23.
+	 * Currently we don't share these headers so this is a magic number. */
+	struct llist_head sapi_cmds;
+	uint8_t sapis_dl[23];
+	uint8_t sapis_ul[23];
+	struct lapdm_channel lapdm_ch;
+	struct llist_head dl_tch_queue;
+	struct {
+		/* bitmask of all SI that are present/valid in si_buf */
+		uint32_t valid;
+		uint32_t last;
+		/* buffers where we put the pre-computed SI:
+		   SI2Q_MAX_NUM is the max number of SI2quater messages (see 3GPP TS 44.018) */
+		sysinfo_buf_t buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM];
+	} si;
+	struct {
+		uint8_t flags;
+		/* RSL measurment result number, 0 at lchan_act */
+		uint8_t res_nr;
+		/* current Tx power level of the BTS */
+		uint8_t bts_tx_pwr;
+		/* number of measurements stored in array below */
+		uint8_t num_ul_meas;
+		struct bts_ul_meas uplink[MAX_NUM_UL_MEAS];
+		/* last L1 header from the MS */
+		uint8_t l1_info[2];
+		struct gsm_meas_rep_unidir ul_res;
+	} meas;
+	struct {
+		struct amr_multirate_conf amr_mr;
+		struct {
+			struct osmo_fsm_inst *dl_amr_fsm;
+			/* TCH cache */
+			uint8_t cache[20];
+			/* FACCH cache */
+			uint8_t facch[GSM_MACBLOCK_LEN];
+			uint8_t len;
+			uint32_t fn;
+			bool is_update;
+			/* set for each SID frame to detect talkspurt for codecs
+			   without explicit ONSET event */
+			bool ul_sid;
+			/* indicates if DTXd was active during DL measurement
+			   period */
+			bool dl_active;
+		} dtx;
+		uint8_t last_cmr;
+		uint32_t last_fn;
+	} tch;
+
+	/* 3GPP TS 48.058 § 9.3.37: [0; 255] ok, -1 means invalid*/
+	int16_t ms_t_offs;
+	/* 3GPP TS 45.010 § 1.2 round trip propagation delay (in symbols) or -1 */
+	int16_t p_offs;
+
+	/* BTS-side ciphering state (rx only, bi-directional, ...) */
+	uint8_t ciph_state;
+	uint8_t ciph_ns;
+	uint8_t loopback;
+	struct {
+		uint8_t active;
+		uint8_t ref;
+		/* T3105: PHYS INF retransmission */
+		struct osmo_timer_list t3105;
+		/* counts up to Ny1 */
+		unsigned int phys_info_count;
+	} ho;
+	/* S counter for link loss */
+	int s;
+	/* Kind of the release/activation. E.g. RSL or PCU */
+	int rel_act_kind;
+	/* RTP header Marker bit to indicate beginning of speech after pause  */
+	bool rtp_tx_marker;
+	/* power handling */
+	struct {
+		uint8_t current;
+		uint8_t fixed;
+	} ms_power_ctrl;
+
+	struct msgb *pending_rel_ind_msg;
+#endif
+};
+
+enum {
+	TS_F_PDCH_ACTIVE =		0x1000,
+	TS_F_PDCH_ACT_PENDING =		0x2000,
+	TS_F_PDCH_DEACT_PENDING =	0x4000,
+	TS_F_PDCH_PENDING_MASK =	0x6000 /*<
+			TS_F_PDCH_ACT_PENDING | TS_F_PDCH_DEACT_PENDING */
+} gsm_bts_trx_ts_flags;
+
+/* One Timeslot in a TRX */
+struct gsm_bts_trx_ts {
+	struct gsm_bts_trx *trx;
+	/* number of this timeslot at the TRX */
+	uint8_t nr;
+
+	enum gsm_phys_chan_config pchan;
+
+	struct {
+		enum gsm_phys_chan_config pchan_is;
+		enum gsm_phys_chan_config pchan_want;
+		struct msgb *pending_chan_activ;
+	} dyn;
+
+	unsigned int flags;
+	struct gsm_abis_mo mo;
+	struct tlv_parsed nm_attr;
+	uint8_t nm_chan_comb;
+	int tsc;		/* -1 == use BTS TSC */
+
+	struct {
+		/* Parameters below are configured by VTY */
+		int enabled;
+		uint8_t maio;
+		uint8_t hsn;
+		struct bitvec arfcns;
+		uint8_t arfcns_data[1024/8];
+		/* This is the pre-computed MA for channel assignments */
+		struct bitvec ma;
+		uint8_t ma_len;	/* part of ma_data that is used */
+		uint8_t ma_data[8];	/* 10.5.2.21: max 8 bytes value part */
+	} hopping;
+
+	/* To which E1 subslot are we connected */
+	struct gsm_e1_subslot e1_link;
+
+	union {
+		struct {
+			struct om2k_mo om2k_mo;
+		} rbs2000;
+	};
+
+	struct gsm_lchan lchan[TS_MAX_LCHAN];
+};
+
+/* One TRX in a BTS */
+struct gsm_bts_trx {
+	/* list header in bts->trx_list */
+	struct llist_head list;
+
+	struct gsm_bts *bts;
+	/* number of this TRX in the BTS */
+	uint8_t nr;
+	/* human readable name / description */
+	char *description;
+	/* how do we talk RSL with this TRX? */
+	struct gsm_e1_subslot rsl_e1_link;
+	uint8_t rsl_tei;
+	struct e1inp_sign_link *rsl_link;
+
+	/* Some BTS (specifically Ericsson RBS) have a per-TRX OML Link */
+	struct e1inp_sign_link *oml_link;
+
+	struct gsm_abis_mo mo;
+	struct tlv_parsed nm_attr;
+	struct {
+		struct gsm_abis_mo mo;
+	} bb_transc;
+
+	uint16_t arfcn;
+	int nominal_power;		/* in dBm */
+	unsigned int max_power_red;	/* in actual dB */
+
+#ifndef ROLE_BSC
+	struct trx_power_params power_params;
+	int ms_power_control;
+
+	struct {
+		void *l1h;
+	} role_bts;
+#endif
+
+	union {
+		struct {
+			struct {
+				struct gsm_abis_mo mo;
+			} bbsig;
+			struct {
+				struct gsm_abis_mo mo;
+			} pa;
+		} bs11;
+		struct {
+			unsigned int test_state;
+			uint8_t test_nr;
+			struct rxlev_stats rxlev_stat;
+		} ipaccess;
+		struct {
+			struct {
+				struct om2k_mo om2k_mo;
+			} trxc;
+			struct {
+				struct om2k_mo om2k_mo;
+			} rx;
+			struct {
+				struct om2k_mo om2k_mo;
+			} tx;
+		} rbs2000;
+	};
+	struct gsm_bts_trx_ts ts[TRX_NR_TS];
+};
+
+#define GSM_BTS_SI2Q(bts, i)   (struct gsm48_system_information_type_2quater *)((bts)->si_buf[SYSINFO_TYPE_2quater][i])
+#define GSM_BTS_HAS_SI(bts, i) ((bts)->si_valid & (1 << i))
+#define GSM_BTS_SI(bts, i)     (void *)((bts)->si_buf[i][0])
+#define GSM_LCHAN_SI(lchan, i) (void *)((lchan)->si.buf[i][0])
+
+enum gsm_bts_type {
+	GSM_BTS_TYPE_UNKNOWN,
+	GSM_BTS_TYPE_BS11,
+	GSM_BTS_TYPE_NANOBTS,
+	GSM_BTS_TYPE_RBS2000,
+	GSM_BTS_TYPE_NOKIA_SITE,
+	GSM_BTS_TYPE_OSMOBTS,
+	_NUM_GSM_BTS_TYPE
+};
+
+enum gsm_bts_type_variant {
+	BTS_UNKNOWN,
+	BTS_OSMO_LITECELL15,
+	BTS_OSMO_OCTPHY,
+	BTS_OSMO_SYSMO,
+	BTS_OSMO_TRX,
+	_NUM_BTS_VARIANT
+};
+
+/* Used by OML layer for BTS Attribute reporting */
+enum bts_attribute {
+	BTS_TYPE_VARIANT,
+	BTS_SUB_MODEL,
+	TRX_PHY_VERSION,
+};
+
+struct vty;
+
+struct gsm_bts_model {
+	struct llist_head list;
+
+	enum gsm_bts_type type;
+	enum gsm_bts_type_variant variant;
+	const char *name;
+
+	bool started;
+	int (*start)(struct gsm_network *net);
+	int (*oml_rcvmsg)(struct msgb *msg);
+
+	void (*e1line_bind_ops)(struct e1inp_line *line);
+
+	void (*config_write_bts)(struct vty *vty, struct gsm_bts *bts);
+	void (*config_write_trx)(struct vty *vty, struct gsm_bts_trx *trx);
+	void (*config_write_ts)(struct vty *vty, struct gsm_bts_trx_ts *ts);
+
+	struct tlv_definition nm_att_tlvdef;
+
+	/* features of a given BTS model set via gsm_bts_model_register() locally */
+	struct bitvec features;
+	uint8_t _features_data[MAX_BTS_FEATURES/8];
+};
+
+/* N. B: always add new features to the end of the list (right before _NUM_BTS_FEAT) to avoid breaking compatibility
+   with BTS compiled against earlier version of this header */
+enum gsm_bts_features {
+	BTS_FEAT_HSCSD,
+	BTS_FEAT_GPRS,
+	BTS_FEAT_EGPRS,
+	BTS_FEAT_ECSD,
+	BTS_FEAT_HOPPING,
+	BTS_FEAT_MULTI_TSC,
+	BTS_FEAT_OML_ALERTS,
+	BTS_FEAT_AGCH_PCH_PROP,
+	BTS_FEAT_CBCH,
+	_NUM_BTS_FEAT
+};
+
+extern const struct value_string gsm_bts_features_descs[];
+
+/*
+ * This keeps track of the paging status of one BTS. It
+ * includes a number of pending requests, a back pointer
+ * to the gsm_bts, a timer and some more state.
+ */
+struct gsm_bts_paging_state {
+	/* pending requests */
+	struct llist_head pending_requests;
+	struct gsm_bts *bts;
+
+	struct osmo_timer_list work_timer;
+	struct osmo_timer_list credit_timer;
+
+	/* free chans needed */
+	int free_chans_need;
+
+	/* load */
+	uint16_t available_slots;
+};
+
+struct gsm_envabtse {
+	struct gsm_abis_mo mo;
+};
+
+struct gsm_bts_gprs_nsvc {
+	struct gsm_bts *bts;
+	/* data read via VTY config file, to configure the BTS
+	 * via OML from BSC */
+	int id;
+	uint16_t nsvci;
+	uint16_t local_port;	/* on the BTS */
+	uint16_t remote_port;	/* on the SGSN */
+	uint32_t remote_ip;	/* on the SGSN */
+
+	struct gsm_abis_mo mo;
+};
+
+enum gprs_rlc_par {
+	RLC_T3142,
+	RLC_T3169,
+	RLC_T3191,
+	RLC_T3193,
+	RLC_T3195,
+	RLC_N3101,
+	RLC_N3103,
+	RLC_N3105,
+	CV_COUNTDOWN,
+	T_DL_TBF_EXT,	/* ms */
+	T_UL_TBF_EXT,	/* ms */
+	_NUM_RLC_PAR
+};
+
+enum gprs_cs {
+	GPRS_CS1,
+	GPRS_CS2,
+	GPRS_CS3,
+	GPRS_CS4,
+	GPRS_MCS1,
+	GPRS_MCS2,
+	GPRS_MCS3,
+	GPRS_MCS4,
+	GPRS_MCS5,
+	GPRS_MCS6,
+	GPRS_MCS7,
+	GPRS_MCS8,
+	GPRS_MCS9,
+	_NUM_GRPS_CS
+};
+
+struct gprs_rlc_cfg {
+	uint16_t parameter[_NUM_RLC_PAR];
+	struct {
+		uint16_t repeat_time; /* ms */
+		uint8_t repeat_count;
+	} paging;
+	uint32_t cs_mask; /* bitmask of gprs_cs */
+	uint8_t initial_cs;
+	uint8_t initial_mcs;
+};
+
+
+enum neigh_list_manual_mode {
+	NL_MODE_AUTOMATIC = 0,
+	NL_MODE_MANUAL = 1,
+	NL_MODE_MANUAL_SI5SEP = 2, /* SI2 and SI5 have separate neighbor lists */
+};
+
+enum bts_loc_fix {
+	BTS_LOC_FIX_INVALID = 0,
+	BTS_LOC_FIX_2D = 1,
+	BTS_LOC_FIX_3D = 2,
+};
+
+extern const struct value_string bts_loc_fix_names[];
+
+struct bts_location {
+	struct llist_head list;
+	time_t tstamp;
+	enum bts_loc_fix valid;
+	double lat;
+	double lon;
+	double height;
+};
+
+/* Channel load counter */
+struct load_counter {
+	unsigned int total;
+	unsigned int used;
+};
+
+/* One BTS */
+struct gsm_bts {
+	/* list header in net->bts_list */
+	struct llist_head list;
+
+	/* Geographical location of the BTS */
+	struct llist_head loc_list;
+
+	/* number of ths BTS in network */
+	uint8_t nr;
+	/* human readable name / description */
+	char *description;
+	/* Cell Identity */
+	uint16_t cell_identity;
+	/* location area code of this BTS */
+	uint16_t location_area_code;
+	/* Base Station Identification Code (BSIC), lower 3 bits is BCC,
+	 * which is used as TSC for the CCCH */
+	uint8_t bsic;
+	/* type of BTS */
+	enum gsm_bts_type type;
+	enum gsm_bts_type_variant variant;
+	struct gsm_bts_model *model;
+	enum gsm_band band;
+	char version[MAX_VERSION_LENGTH];
+	char sub_model[MAX_VERSION_LENGTH];
+
+	/* features of a given BTS set/reported via OML */
+	struct bitvec features;
+	uint8_t _features_data[MAX_BTS_FEATURES/8];
+
+	/* Connected PCU version (if any) */
+	char pcu_version[MAX_VERSION_LENGTH];
+
+	/* maximum Tx power that the MS is permitted to use in this cell */
+	int ms_max_power;
+
+	/* how do we talk OML with this TRX? */
+	struct gsm_e1_subslot oml_e1_link;
+	uint8_t oml_tei;
+	struct e1inp_sign_link *oml_link;
+	/* Timer to use for deferred drop of OML link, see \ref ipaccess_drop_oml_deferred */
+	struct osmo_timer_list oml_drop_link_timer;
+	/* when OML link was established */
+	time_t uptime;
+
+	/* Abis network management O&M handle */
+	struct abis_nm_h *nmh;
+
+	struct gsm_abis_mo mo;
+
+	/* number of this BTS on given E1 link */
+	uint8_t bts_nr;
+
+	/* DTX features of this BTS */
+	enum gsm48_dtx_mode dtxu;
+	bool dtxd;
+
+	/* paging state and control */
+	struct gsm_bts_paging_state paging;
+
+	/* CCCH is on C0 */
+	struct gsm_bts_trx *c0;
+
+	struct {
+		struct gsm_abis_mo mo;
+	} site_mgr;
+
+	/* bitmask of all SI that are present/valid in si_buf */
+	uint32_t si_valid;
+	/* 3GPP TS 44.018 Table 10.5.2.33b.1 INDEX and COUNT for SI2quater */
+	uint8_t si2q_index; /* distinguish individual SI2quater messages */
+	uint8_t si2q_count; /* si2q_index for the last (highest indexed) individual SI2quater message */
+	/* buffers where we put the pre-computed SI */
+	sysinfo_buf_t si_buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM];
+	/* offsets used while generating SI2quater */
+	size_t e_offset;
+	size_t u_offset;
+
+	/* ip.accesss Unit ID's have Site/BTS/TRX layout */
+	union {
+		struct {
+			uint16_t site_id;
+			uint16_t bts_id;
+			uint32_t flags;
+			uint32_t rsl_ip;
+		} ip_access;
+		struct {
+			struct {
+				struct gsm_abis_mo mo;
+			} cclk;
+			struct {
+				struct gsm_abis_mo mo;
+			} rack;
+			struct gsm_envabtse envabtse[4];
+		} bs11;
+		struct {
+			struct {
+				struct om2k_mo om2k_mo;
+				struct gsm_abis_mo mo;
+				struct llist_head conn_groups;
+			} cf;
+			struct {
+				struct om2k_mo om2k_mo;
+				struct gsm_abis_mo mo;
+				struct llist_head conn_groups;
+			} is;
+			struct {
+				struct om2k_mo om2k_mo;
+				struct gsm_abis_mo mo;
+				struct llist_head conn_groups;
+			} con;
+			struct {
+				struct om2k_mo om2k_mo;
+				struct gsm_abis_mo mo;
+			} dp;
+			struct {
+				struct om2k_mo om2k_mo;
+				struct gsm_abis_mo mo;
+			} tf;
+			uint32_t use_superchannel:1;
+		} rbs2000;
+		struct {
+			uint8_t bts_type;
+			unsigned int configured:1,
+				skip_reset:1,
+				no_loc_rel_cnf:1,
+				bts_reset_timer_cnf,
+				did_reset:1,
+				wait_reset:1;
+			struct osmo_timer_list reset_timer;
+		} nokia;
+	};
+
+	/* Not entirely sure how ip.access specific this is */
+	struct {
+		uint8_t supports_egprs_11bit_rach;
+		enum bts_gprs_mode mode;
+		struct {
+			struct gsm_abis_mo mo;
+			uint16_t nsei;
+			uint8_t timer[7];
+		} nse;
+		struct {
+			struct gsm_abis_mo mo;
+			uint16_t bvci;
+			uint8_t timer[11];
+			struct gprs_rlc_cfg rlc_cfg;
+		} cell;
+		struct gsm_bts_gprs_nsvc nsvc[2];
+		uint8_t rac;
+		uint8_t net_ctrl_ord;
+		bool ctrl_ack_type_use_block;
+	} gprs;
+
+	/* RACH NM values */
+	int rach_b_thresh;
+	int rach_ldavg_slots;
+
+	/* transceivers */
+	int num_trx;
+	struct llist_head trx_list;
+
+	/* SI related items */
+	int force_combined_si;
+	int bcch_change_mark;
+
+	/* access control class ramping */
+	struct acc_ramp acc_ramp;
+
+#ifdef ROLE_BSC
+	/* Abis NM queue */
+	struct llist_head abis_queue;
+	int abis_nm_pend;
+
+	struct gsm_network *network;
+
+	/* should the channel allocator allocate channels from high TRX to TRX0,
+	 * rather than starting from TRX0 and go upwards? */
+	int chan_alloc_reverse;
+
+	enum neigh_list_manual_mode neigh_list_manual_mode;
+	/* parameters from which we build SYSTEM INFORMATION */
+	struct {
+		struct gsm48_rach_control rach_control;
+		uint8_t ncc_permitted;
+		struct gsm48_cell_sel_par cell_sel_par;
+		struct gsm48_si_selection_params cell_ro_sel_par; /* rest octet */
+		struct gsm48_cell_options cell_options;
+		struct gsm48_control_channel_descr chan_desc;
+		struct bitvec neigh_list;
+		struct bitvec cell_alloc;
+		struct bitvec si5_neigh_list;
+		struct osmo_earfcn_si2q si2quater_neigh_list;
+		size_t uarfcn_length; /* index for uarfcn and scramble lists */
+		struct {
+			/* bitmask large enough for all possible ARFCN's */
+			uint8_t neigh_list[1024/8];
+			uint8_t cell_alloc[1024/8];
+			/* If the user wants a different neighbor list in SI5 than in SI2 */
+			uint8_t si5_neigh_list[1024/8];
+			uint8_t meas_bw_list[MAX_EARFCN_LIST];
+			uint16_t earfcn_list[MAX_EARFCN_LIST];
+			uint16_t uarfcn_list[MAX_EARFCN_LIST];
+			uint16_t scramble_list[MAX_EARFCN_LIST];
+		} data;
+	} si_common;
+	bool early_classmark_allowed;
+	bool early_classmark_allowed_3g;
+	/* for testing only: Have an infinitely long radio link timeout */
+	bool infinite_radio_link_timeout;
+
+	/* do we use static (user-defined) system information messages? (bitmask) */
+	uint32_t si_mode_static;
+
+	/* exclude the BTS from the global RF Lock handling */
+	int excl_from_rf_lock;
+
+	/* supported codecs beside FR */
+	struct bts_codec_conf codec;
+
+	/* BTS dependencies bit field */
+	uint32_t depends_on[256/(8*4)];
+
+	/* full and half rate multirate config */
+	struct amr_multirate_conf mr_full;
+	struct amr_multirate_conf mr_half;
+
+	/* PCU socket state */
+	char *pcu_sock_path;
+	struct pcu_sock_state *pcu_state;
+
+	struct osmo_stat_item_group *bts_statg;
+
+	/* BTS-specific overrides for timer values from struct gsm_network. */
+	uint8_t T3122;	/* ASSIGMENT REJECT wait indication */
+
+	/* Periodic channel load measurements are used to maintain T3122. */
+	struct load_counter chan_load_samples[7];
+	int chan_load_samples_idx;
+	uint8_t chan_load_avg; /* current channel load average in percent (0 - 100). */
+
+#endif /* ROLE_BSC */
+	void *role;
+};
+
+
+struct gsm_bts *gsm_bts_alloc(void *talloc_ctx, uint8_t bts_num);
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num);
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts);
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num);
+
+enum gsm_bts_type str2btstype(const char *arg);
+const char *btstype2str(enum gsm_bts_type type);
+
+enum bts_attribute str2btsattr(const char *s);
+const char *btsatttr2str(enum bts_attribute v);
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg);
+const char *btsvariant2str(enum gsm_bts_type_variant v);
+
+extern const struct value_string gsm_chreq_descs[];
+const struct value_string gsm_pchant_names[13];
+const struct value_string gsm_pchant_descs[13];
+const char *gsm_pchan_name(enum gsm_phys_chan_config c);
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name);
+const char *gsm_lchant_name(enum gsm_chan_t c);
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c);
+char *gsm_trx_name(const struct gsm_bts_trx *trx);
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts);
+char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts);
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan);
+const char *gsm_lchans_name(enum gsm_lchan_state s);
+
+static inline char *gsm_lchan_name(const struct gsm_lchan *lchan)
+{
+	return lchan->name;
+}
+
+static inline int gsm_bts_set_feature(struct gsm_bts *bts, enum gsm_bts_features feat)
+{
+	OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+	return bitvec_set_bit_pos(&bts->features, feat, 1);
+}
+
+static inline bool gsm_bts_has_feature(const struct gsm_bts *bts, enum gsm_bts_features feat)
+{
+	OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+	return bitvec_get_bit_pos(&bts->features, feat);
+}
+
+void gsm_abis_mo_reset(struct gsm_abis_mo *mo);
+
+struct gsm_abis_mo *
+gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+	    const struct abis_om_obj_inst *obj_inst);
+
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+		 const struct abis_om_obj_inst *obj_inst);
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+	     const struct abis_om_obj_inst *obj_inst);
+
+/* reset the state of all MO in the BTS */
+void gsm_bts_mo_reset(struct gsm_bts *bts);
+
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+			  uint8_t ts_nr, uint8_t lchan_nr);
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan);
+uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan,
+				   enum gsm_phys_chan_config as_pchan);
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts);
+
+/*
+ * help with parsing regexps
+ */
+int gsm_parse_reg(void *ctx, regex_t *reg, char **str,
+		int argc, const char **argv) __attribute__ ((warn_unused_result));
+
+static inline uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts)
+{
+	if (ts->tsc != -1)
+		return ts->tsc;
+	else
+		return ts->trx->bts->bsic & 7;
+}
+
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+				   int *rc);
+
+enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts);
+uint8_t ts_subslots(struct gsm_bts_trx_ts *ts);
+bool ts_is_tch(struct gsm_bts_trx_ts *ts);
+
+#endif
diff --git a/openbsc/include/openbsc/gsm_subscriber.h b/openbsc/include/openbsc/gsm_subscriber.h
new file mode 100644
index 0000000..df3eee6
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_subscriber.h
@@ -0,0 +1,125 @@
+#ifndef _GSM_SUBSCR_H
+#define _GSM_SUBSCR_H
+
+#include <stdbool.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+
+#include <openbsc/gsm_data.h>
+
+#define GSM_NAME_LENGTH 160
+
+#define GSM_EXTENSION_LENGTH 15 /* MSISDN can only be 15 digits length */
+#define GSM_MIN_EXTEN 20000
+#define GSM_MAX_EXTEN 49999
+
+#define GSM_SUBSCRIBER_FIRST_CONTACT	0x00000001
+/* gprs_sgsn.h defines additional flags including and above bit 16 (0x10000) */
+
+#define GSM_SUBSCRIBER_NO_EXPIRATION	0x0
+
+struct vty;
+
+struct subscr_request;
+
+struct gsm_subscriber_group {
+	struct gsm_network *net;
+
+	int keep_subscr;
+};
+
+struct gsm_equipment {
+	long long unsigned int id;
+	char imei[GSM23003_IMEISV_NUM_DIGITS+1];
+	char name[GSM_NAME_LENGTH];
+
+	struct gsm48_classmark1 classmark1;
+	uint8_t classmark2_len;
+	uint8_t classmark2[3];
+	uint8_t classmark3_len;
+	uint8_t classmark3[14];
+};
+
+struct gsm_subscriber {
+	struct gsm_subscriber_group *group;
+	long long unsigned int id;
+	char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+	uint32_t tmsi;
+	uint16_t lac;
+	char name[GSM_NAME_LENGTH];
+	char extension[GSM_EXTENSION_LENGTH];
+	int authorized;
+	time_t expire_lu;
+
+	/* Don't delete subscribers even if group->keep_subscr is not set */
+	int keep_in_ram;
+
+	/* Temporary field which is not stored in the DB/HLR */
+	uint32_t flags;
+
+	/* Every user can only have one equipment in use at any given
+	 * point in time */
+	struct gsm_equipment equipment;
+
+	/* for internal management */
+	int use_count;
+	struct llist_head entry;
+
+	/* pending requests */
+	int is_paging;
+	struct llist_head requests;
+};
+
+enum gsm_subscriber_field {
+	GSM_SUBSCRIBER_IMSI,
+	GSM_SUBSCRIBER_TMSI,
+	GSM_SUBSCRIBER_EXTENSION,
+	GSM_SUBSCRIBER_ID,
+};
+
+enum gsm_subscriber_update_reason {
+	GSM_SUBSCRIBER_UPDATE_ATTACHED,
+	GSM_SUBSCRIBER_UPDATE_DETACHED,
+	GSM_SUBSCRIBER_UPDATE_EQUIPMENT,
+};
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr);
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr);
+struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp,
+						const char *imsi);
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_subscriber_group *sgrp,
+					  uint32_t tmsi);
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_subscriber_group *sgrp,
+					  const char *imsi);
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_subscriber_group *sgrp,
+					       const char *ext);
+struct gsm_subscriber *subscr_get_by_id(struct gsm_subscriber_group *sgrp,
+					unsigned long long id);
+struct gsm_subscriber *subscr_get_or_create(struct gsm_subscriber_group *sgrp,
+					const char *imsi);
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason);
+struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_subscriber_group *sgrp,
+					     uint32_t tmsi);
+struct gsm_subscriber *subscr_active_by_imsi(struct gsm_subscriber_group *sgrp,
+					     const char *imsi);
+
+char *subscr_name(struct gsm_subscriber *subscr);
+
+int subscr_purge_inactive(struct gsm_subscriber_group *sgrp);
+void subscr_update_from_db(struct gsm_subscriber *subscr);
+void subscr_expire(struct gsm_subscriber_group *sgrp);
+int subscr_update_expire_lu(struct gsm_subscriber *subscr, struct gsm_bts *bts);
+
+/*
+ * Paging handling with authentication
+ */
+struct subscr_request *subscr_request_channel(struct gsm_subscriber *subscr,
+                        int type, gsm_cbfn *cbfn, void *param);
+void subscr_remove_request(struct subscr_request *req);
+
+/* internal */
+struct gsm_subscriber *subscr_alloc(void);
+extern struct llist_head active_subscribers;
+
+#endif /* _GSM_SUBSCR_H */
diff --git a/openbsc/include/openbsc/gsup_client.h b/openbsc/include/openbsc/gsup_client.h
new file mode 100644
index 0000000..a113225
--- /dev/null
+++ b/openbsc/include/openbsc/gsup_client.h
@@ -0,0 +1,60 @@
+/* GPRS Subscriber Update Protocol client */
+
+/* (C) 2014 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Jacob Erlbeck
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <osmocom/core/timer.h>
+
+#include <openbsc/oap_client.h>
+
+#define GSUP_CLIENT_RECONNECT_INTERVAL 10
+#define GSUP_CLIENT_PING_INTERVAL 20
+
+struct msgb;
+struct ipa_client_conn;
+struct gsup_client;
+
+/* Expects message in msg->l2h */
+typedef int (*gsup_client_read_cb_t)(struct gsup_client *gsupc,
+				     struct msgb *msg);
+
+struct gsup_client {
+	struct ipa_client_conn *link;
+	gsup_client_read_cb_t read_cb;
+	void *data;
+
+	struct oap_client_state oap_state;
+
+	struct osmo_timer_list ping_timer;
+	struct osmo_timer_list connect_timer;
+	int is_connected;
+	int got_ipa_pong;
+};
+
+struct gsup_client *gsup_client_create(const char *ip_addr,
+				       unsigned int tcp_port,
+				       gsup_client_read_cb_t read_cb,
+				       struct oap_client_config *oap_config);
+
+void gsup_client_destroy(struct gsup_client *gsupc);
+int gsup_client_send(struct gsup_client *gsupc, struct msgb *msg);
+struct msgb *gsup_client_msgb_alloc(void);
+
diff --git a/openbsc/include/openbsc/handover.h b/openbsc/include/openbsc/handover.h
new file mode 100644
index 0000000..3fe71a2
--- /dev/null
+++ b/openbsc/include/openbsc/handover.h
@@ -0,0 +1,14 @@
+#ifndef _HANDOVER_H
+#define _HANDOVER_H
+
+struct gsm_subscriber_connection;
+
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts);
+
+/* clear any operation for this connection */
+void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan);
+
+/* Return the old lchan or NULL. This is meant for audio handling */
+struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan);
+
+#endif /* _HANDOVER_H */
diff --git a/openbsc/include/openbsc/handover_decision.h b/openbsc/include/openbsc/handover_decision.h
new file mode 100644
index 0000000..81078b0
--- /dev/null
+++ b/openbsc/include/openbsc/handover_decision.h
@@ -0,0 +1,7 @@
+#ifndef _HANDOVER_DECISION_H
+#define _HANDOVER_DECISION_H
+
+void on_dso_load_ho_dec(void);
+
+#endif /* _HANDOVER_DECISION_H */
+
diff --git a/openbsc/include/openbsc/ipaccess.h b/openbsc/include/openbsc/ipaccess.h
new file mode 100644
index 0000000..1b5a485
--- /dev/null
+++ b/openbsc/include/openbsc/ipaccess.h
@@ -0,0 +1,53 @@
+#ifndef _IPACCESS_H
+#define _IPACCESS_H
+
+#include <osmocom/abis/e1_input.h>
+#include "gsm_subscriber.h"
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+
+struct ipac_msgt_sccp_state {
+	uint8_t	src_ref[3];
+	uint8_t	dst_ref[3];
+	uint8_t trans_id;
+	uint8_t invoke_id;
+	char	imsi[GSM23003_IMSI_MAX_DIGITS+1];
+	uint8_t data[0];
+} __attribute__((packed));
+
+/*
+ * @add_remove 0 for remove, 1 for add, 3 to asK
+ * @nr_lacs Number of extra lacs inside this package
+ * @lac One lac entry
+ */
+struct ipac_ext_lac_cmd {
+	uint8_t add_remove;
+	uint8_t nr_extra_lacs;
+	uint16_t lac;
+	uint8_t data[0];
+} __attribute__((packed));
+
+void ipaccess_drop_oml(struct gsm_bts *bts);
+void ipaccess_drop_oml_deferred(struct gsm_bts *bts);
+void ipaccess_drop_rsl(struct gsm_bts_trx *trx);
+
+struct sdp_header_item {
+	struct sdp_header_entry header_entry;
+	struct llist_head entry;
+	off_t absolute_offset;
+};
+
+struct sdp_header {
+	struct sdp_firmware firmware_info;
+
+	/* for more_magic a list of sdp_header_entry_list */
+	struct llist_head header_list;
+
+	/* the entry of the sdp_header */
+	struct llist_head entry;
+};
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned base_offset, struct llist_head *list);
+
+#endif /* _IPACCESS_H */
diff --git a/openbsc/include/openbsc/iu.h b/openbsc/include/openbsc/iu.h
new file mode 100644
index 0000000..f973ac1
--- /dev/null
+++ b/openbsc/include/openbsc/iu.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <stdbool.h>
+
+struct sgsn_pdp_ctx;
+struct msgb;
+struct gprs_ra_id;
+
+struct RANAP_RAB_SetupOrModifiedItemIEs_s;
+struct RANAP_GlobalRNC_ID;
+
+struct ue_conn_ctx {
+	struct llist_head list;
+	struct osmo_sccp_link *link;
+	uint32_t conn_id;
+	int integrity_active;
+	struct gprs_ra_id ra_id;
+};
+
+enum iu_event_type {
+	IU_EVENT_RAB_ASSIGN,
+	IU_EVENT_SECURITY_MODE_COMPLETE,
+	IU_EVENT_IU_RELEASE, /* An actual Iu Release message was received */
+	IU_EVENT_LINK_INVALIDATED, /* A SUA link was lost or closed down */
+	/* FIXME: maybe IU_EVENT_IU_RELEASE and IU_EVENT_LINK_INVALIDATED
+	 * should be combined to one generic event that simply means the
+	 * ue_conn_ctx should no longer be used, for whatever reason. */
+};
+
+extern const struct value_string iu_event_type_names[];
+static inline const char *iu_event_type_str(enum iu_event_type e)
+{
+	return get_value_string(iu_event_type_names, e);
+}
+
+/* Implementations of iu_recv_cb_t shall find the ue_conn_ctx in msg->dst. */
+typedef int (* iu_recv_cb_t )(struct msgb *msg, struct gprs_ra_id *ra_id,
+			      /* TODO "gprs_" in generic CS+PS domain ^ */
+			      uint16_t *sai);
+
+typedef int (* iu_event_cb_t )(struct ue_conn_ctx *ue_ctx,
+			       enum iu_event_type type, void *data);
+
+typedef int (* iu_rab_ass_resp_cb_t )(struct ue_conn_ctx *ue_ctx, uint8_t rab_id,
+		struct RANAP_RAB_SetupOrModifiedItemIEs_s *setup_ies);
+
+int iu_init(void *ctx, const char *listen_addr, uint16_t listen_port,
+	    iu_recv_cb_t iu_recv_cb, iu_event_cb_t iu_event_cb);
+
+void iu_link_del(struct osmo_sccp_link *link);
+
+int iu_tx(struct msgb *msg, uint8_t sapi);
+
+int iu_page_cs(const char *imsi, const uint32_t *tmsi, uint16_t lac);
+int iu_page_ps(const char *imsi, const uint32_t *ptmsi, uint16_t lac, uint8_t rac);
+
+int iu_rab_act(struct ue_conn_ctx *ue_ctx, struct msgb *msg);
+int iu_rab_deact(struct ue_conn_ctx *ue_ctx, uint8_t rab_id);
+int iu_tx_sec_mode_cmd(struct ue_conn_ctx *uectx, struct gsm_auth_tuple *tp,
+		       int send_ck, int new_key);
+
+void iu_vty_init(int *asn_debug_p);
diff --git a/openbsc/include/openbsc/meas_feed.h b/openbsc/include/openbsc/meas_feed.h
new file mode 100644
index 0000000..f77ee07
--- /dev/null
+++ b/openbsc/include/openbsc/meas_feed.h
@@ -0,0 +1,41 @@
+#ifndef _OPENBSC_MEAS_FEED_H
+#define _OPENBSC_MEAS_FEED_H
+
+#include <stdint.h>
+
+#include <openbsc/meas_rep.h>
+
+struct meas_feed_hdr {
+	uint8_t msg_type;
+	uint8_t reserved;
+	uint16_t version;
+};
+
+struct meas_feed_meas {
+	struct meas_feed_hdr hdr;
+	char imsi[15+1];
+	char name[31+1];
+	char scenario[31+1];
+	struct gsm_meas_rep mr;
+	/* The logical channel type, enum gsm_chan_t */
+	uint8_t lchan_type;
+	/* The physical channel type, enum gsm_phys_chan_config */
+	uint8_t pchan_type;
+	/* number of ths BTS in network */
+	uint8_t bts_nr;
+	/* number of this TRX in the BTS */
+	uint8_t trx_nr;
+	/* number of this timeslot at the TRX */
+	uint8_t ts_nr;
+	/* The logical subslot number in the TS */
+	uint8_t ss_nr;
+};
+
+enum meas_feed_msgtype {
+	MEAS_FEED_MEAS		= 0,
+};
+
+#define MEAS_FEED_VERSION	1
+
+
+#endif
diff --git a/openbsc/include/openbsc/meas_rep.h b/openbsc/include/openbsc/meas_rep.h
new file mode 100644
index 0000000..b0c03f0
--- /dev/null
+++ b/openbsc/include/openbsc/meas_rep.h
@@ -0,0 +1,67 @@
+#ifndef _MEAS_REP_H
+#define _MEAS_REP_H
+
+#include <stdint.h>
+
+#include <osmocom/gsm/meas_rep.h>
+
+#define MRC_F_PROCESSED	0x0001
+
+/* extracted from a L3 measurement report IE */
+struct gsm_meas_rep_cell {
+	uint8_t rxlev;
+	uint8_t bsic;
+	uint8_t neigh_idx;
+	uint16_t arfcn;
+	unsigned int flags;
+};
+
+#define MEAS_REP_F_UL_DTX	0x01
+#define MEAS_REP_F_DL_VALID	0x02
+#define MEAS_REP_F_BA1		0x04
+#define MEAS_REP_F_DL_DTX	0x08
+#define MEAS_REP_F_MS_TO	0x10
+#define MEAS_REP_F_MS_L1	0x20
+#define MEAS_REP_F_FPC		0x40
+
+/* parsed uplink and downlink measurement result */
+struct gsm_meas_rep {
+	/* back-pointer to the logical channel */
+	struct gsm_lchan *lchan;
+
+	/* number of the measurement report */
+	uint8_t nr;
+	/* flags, see MEAS_REP_F_* */
+	unsigned int flags;
+
+	/* uplink and downlink rxlev, rxqual; full and sub */
+	struct gsm_meas_rep_unidir ul;
+	struct gsm_meas_rep_unidir dl;
+
+	uint8_t bs_power;
+	/* according to 3GPP TS 48.058 § MS Timing Offset [-63; 192] */
+	int16_t ms_timing_offset;
+	struct {
+		int8_t pwr;	/* MS power in dBm */
+		uint8_t ta;	/* MS timing advance */
+	} ms_l1;
+
+	/* neighbor measurement reports for up to 6 cells */
+	int num_cell;
+	struct gsm_meas_rep_cell cell[6];
+};
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+		     enum meas_rep_field field, unsigned int num);
+
+/* Check if N out of M last values for FIELD are >= bd */
+int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
+			enum meas_rep_field field,
+			unsigned int n, unsigned int m, int be);
+
+unsigned int calc_initial_idx(unsigned int array_size,
+			      unsigned int meas_rep_idx,
+			      unsigned int num_values);
+
+#endif /* _MEAS_REP_H */
diff --git a/openbsc/include/openbsc/mgcp.h b/openbsc/include/openbsc/mgcp.h
new file mode 100644
index 0000000..4d1c5ef
--- /dev/null
+++ b/openbsc/include/openbsc/mgcp.h
@@ -0,0 +1,291 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_MGCP_H
+#define OPENBSC_MGCP_H
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+#include "debug.h"
+
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#define RTP_PORT_DEFAULT 4000
+#define RTP_PORT_NET_DEFAULT 16000
+
+/**
+ * Calculate the RTP audio port for the given multiplex
+ * and the direction. This allows a semi static endpoint
+ * to port calculation removing the need for the BSC
+ * and the MediaGateway to communicate.
+ *
+ * Port usage explained:
+ *       base + (multiplex * 2) + 0 == local port to wait for network packets
+ *       base + (multiplex * 2) + 1 == local port for rtcp
+ *
+ * The above port will receive packets from the BTS that need
+ * to be patched and forwarded to the network.
+ * The above port will receive packets from the network that
+ * need to be patched and forwarded to the BTS.
+ *
+ * We assume to have a static BTS IP address so we can differentiate
+ * network and BTS.
+ *
+ */
+static inline int rtp_calculate_port(int multiplex, int base)
+{
+	return base + (multiplex * 2);
+}
+
+
+/*
+ * Handling of MGCP Endpoints and the MGCP Config
+ */
+struct mgcp_endpoint;
+struct mgcp_config;
+struct mgcp_trunk_config;
+struct mgcp_rtp_end;
+
+#define MGCP_ENDP_CRCX 1
+#define MGCP_ENDP_DLCX 2
+#define MGCP_ENDP_MDCX 3
+
+/*
+ * what to do with the msg?
+ *	- continue as usual?
+ *	- reject and send a failure code?
+ *	- defer? do not send anything
+ */
+#define MGCP_POLICY_CONT	4
+#define MGCP_POLICY_REJECT	5
+#define MGCP_POLICY_DEFER	6
+
+typedef int (*mgcp_realloc)(struct mgcp_trunk_config *cfg, int endpoint);
+typedef int (*mgcp_change)(struct mgcp_trunk_config *cfg, int endpoint, int state);
+typedef int (*mgcp_policy)(struct mgcp_trunk_config *cfg, int endpoint, int state, const char *transactio_id);
+typedef int (*mgcp_reset)(struct mgcp_trunk_config *cfg);
+typedef int (*mgcp_rqnt)(struct mgcp_endpoint *endp, char tone);
+
+/**
+ * Return:
+ *   <  0 in case no audio was processed
+ *   >= 0 in case audio was processed. The remaining payload
+ *   length will be returned.
+ */
+typedef int (*mgcp_processing)(struct mgcp_endpoint *endp,
+			       struct mgcp_rtp_end *dst_end,
+			       char *data, int *len, int buf_size);
+typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp,
+				     struct mgcp_rtp_end *dst_end,
+				     struct mgcp_rtp_end *src_end);
+
+typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp,
+				int *payload_type,
+				const char**subtype_name,
+				const char**fmtp_extra);
+
+#define PORT_ALLOC_STATIC	0
+#define PORT_ALLOC_DYNAMIC	1
+
+/**
+ * This holds information on how to allocate ports
+ */
+struct mgcp_port_range {
+	int mode;
+
+	/* addr or NULL to fall-back to default */
+	char *bind_addr;
+
+	/* pre-allocated from a base? */
+	int base_port;
+
+	/* dynamically allocated */
+	int range_start;
+	int range_end;
+	int last_port;
+};
+
+#define MGCP_KEEPALIVE_ONCE (-1)
+
+struct mgcp_trunk_config {
+	struct llist_head entry;
+
+	struct mgcp_config *cfg;
+
+	int trunk_nr;
+	int trunk_type;
+
+	char *audio_fmtp_extra;
+	char *audio_name;
+	int audio_payload;
+	int audio_send_ptime;
+	int audio_send_name;
+	int audio_loop;
+
+	int no_audio_transcoding;
+
+	int omit_rtcp;
+	int keepalive_interval;
+
+	/* RTP patching */
+	int force_constant_ssrc; /* 0: don't, 1: once */
+	int force_aligned_timing;
+
+	/* spec handling */
+	int force_realloc;
+
+	/* timer */
+	struct osmo_timer_list keepalive_timer;
+
+	unsigned int number_endpoints;
+	struct mgcp_endpoint *endpoints;
+};
+
+enum mgcp_role {
+	MGCP_BSC = 0,
+	MGCP_BSC_NAT,
+};
+
+struct mgcp_config {
+	int source_port;
+	char *local_ip;
+	char *source_addr;
+	char *bts_ip;
+	char *call_agent_addr;
+
+	struct in_addr bts_in;
+
+	/* transcoder handling */
+	char *transcoder_ip;
+	struct in_addr transcoder_in;
+	int transcoder_remote_base;
+
+	/* RTP processing */
+	mgcp_processing rtp_processing_cb;
+	mgcp_processing_setup setup_rtp_processing_cb;
+
+	mgcp_get_format get_net_downlink_format_cb;
+
+	struct osmo_wqueue gw_fd;
+
+	struct mgcp_port_range bts_ports;
+	struct mgcp_port_range net_ports;
+	struct mgcp_port_range transcoder_ports;
+	int endp_dscp;
+
+	int bts_force_ptime;
+
+	mgcp_change change_cb;
+	mgcp_policy policy_cb;
+	mgcp_reset reset_cb;
+	mgcp_realloc realloc_cb;
+	mgcp_rqnt rqnt_cb;
+	void *data;
+
+	uint32_t last_call_id;
+
+	/* trunk handling */
+	struct mgcp_trunk_config trunk;
+	struct llist_head trunks;
+
+	/* only used for start with a static configuration */
+	int last_net_port;
+	int last_bts_port;
+
+	enum mgcp_role role;
+
+	/* osmux translator: 0 means disabled, 1 means enabled */
+	int osmux;
+	/* addr to bind the server to */
+	char *osmux_addr;
+	/* The BSC-NAT may ask for enabling osmux on demand. This tells us if
+	 * the osmux socket is already initialized.
+	 */
+	int osmux_init;
+	/* osmux batch factor: from 1 to 4 maximum */
+	int osmux_batch;
+	/* osmux batch size (in bytes) */
+	int osmux_batch_size;
+	/* osmux port */
+	uint16_t osmux_port;
+	/* Pad circuit with dummy messages until we see the first voice
+	 * message.
+	 */
+	uint16_t osmux_dummy;
+
+	/* Use a jitterbuffer on the bts-side receiver */
+	bool bts_use_jibuf;
+	/* Minimum and maximum buffer size for the jitter buffer, in ms */
+	uint32_t bts_jitter_delay_min;
+	uint32_t bts_jitter_delay_max;
+};
+
+/* config management */
+struct mgcp_config *mgcp_config_alloc(void);
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg,
+		      enum mgcp_role role);
+int mgcp_vty_init(void);
+int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg);
+void mgcp_release_endp(struct mgcp_endpoint *endp);
+void mgcp_initialize_endp(struct mgcp_endpoint *endp);
+int mgcp_reset_transcoder(struct mgcp_config *cfg);
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *stats, size_t size);
+int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter);
+
+void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval);
+
+/*
+ * format helper functions
+ */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg);
+
+/* adc helper */
+static inline int mgcp_timeslot_to_endpoint(int multiplex, int timeslot)
+{
+	if (timeslot == 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Timeslot should not be 0\n");
+		timeslot = 255;
+	}
+
+	return timeslot + (32 * multiplex);
+}
+
+static inline void mgcp_endpoint_to_timeslot(int endpoint, int *multiplex, int *timeslot)
+{
+	*multiplex = endpoint / 32;
+	*timeslot = endpoint % 32;
+}
+
+int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint);
+int mgcp_send_reset_all(struct mgcp_config *cfg);
+
+
+int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port);
+int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, struct sockaddr_in *addr, char *buf, int rc);
+int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len);
+
+#endif
diff --git a/openbsc/include/openbsc/mgcp_internal.h b/openbsc/include/openbsc/mgcp_internal.h
new file mode 100644
index 0000000..cd6365f
--- /dev/null
+++ b/openbsc/include/openbsc/mgcp_internal.h
@@ -0,0 +1,356 @@
+/* MGCP Private Data */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <string.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/netif/jibuf.h>
+
+#define CI_UNUSED 0
+
+enum mgcp_connection_mode {
+	MGCP_CONN_NONE = 0,
+	MGCP_CONN_RECV_ONLY = 1,
+	MGCP_CONN_SEND_ONLY = 2,
+	MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY,
+	MGCP_CONN_LOOPBACK  = 4 | MGCP_CONN_RECV_SEND,
+};
+
+enum mgcp_trunk_type {
+	MGCP_TRUNK_VIRTUAL,
+	MGCP_TRUNK_E1,
+};
+
+struct mgcp_rtp_stream_state {
+	uint32_t ssrc;
+	uint16_t last_seq;
+	uint32_t last_timestamp;
+	uint32_t err_ts_counter;
+	int32_t last_tsdelta;
+	uint32_t last_arrival_time;
+};
+
+struct mgcp_rtp_state {
+	int initialized;
+	int patch_ssrc;
+
+	uint32_t orig_ssrc;
+
+	int seq_offset;
+
+	int32_t  timestamp_offset;
+	uint32_t packet_duration;
+
+	struct mgcp_rtp_stream_state in_stream;
+	struct mgcp_rtp_stream_state out_stream;
+
+	/* jitter and packet loss calculation */
+	int stats_initialized;
+	uint16_t stats_base_seq;
+	uint16_t stats_max_seq;
+	uint32_t stats_ssrc;
+	uint32_t stats_jitter;
+	int32_t stats_transit;
+	int stats_cycles;
+};
+
+struct mgcp_rtp_codec {
+	uint32_t rate;
+	int channels;
+	uint32_t frame_duration_num;
+	uint32_t frame_duration_den;
+
+	int payload_type;
+	char *audio_name;
+	char *subtype_name;
+};
+
+struct mgcp_rtp_end {
+	/* statistics */
+	unsigned int packets;
+	unsigned int octets;
+	unsigned int dropped_packets;
+	struct in_addr addr;
+
+	/* in network byte order */
+	int rtp_port, rtcp_port;
+
+	/* audio codec information */
+	struct mgcp_rtp_codec codec;
+	struct mgcp_rtp_codec alt_codec; /* TODO/XXX: make it generic */
+
+	/* per endpoint data */
+	int  frames_per_packet;
+	uint32_t packet_duration_ms;
+	char *fmtp_extra;
+	int output_enabled;
+	int force_output_ptime;
+
+	/* RTP patching */
+	int force_constant_ssrc; /* -1: always, 0: don't, 1: once */
+	int force_aligned_timing;
+	void *rtp_process_data;
+
+	/*
+	 * Each end has a socket...
+	 */
+	struct osmo_fd rtp;
+	struct osmo_fd rtcp;
+
+	int local_port;
+	int local_alloc;
+};
+
+enum {
+	MGCP_TAP_BTS_IN,
+	MGCP_TAP_BTS_OUT,
+	MGCP_TAP_NET_IN,
+	MGCP_TAP_NET_OUT,
+
+	/* last element */
+	MGCP_TAP_COUNT
+};
+
+struct mgcp_rtp_tap {
+	int enabled;
+	struct sockaddr_in forward;
+};
+
+struct mgcp_lco {
+	char *string;
+	char *codec;
+	int pkt_period_min; /* time in ms */
+	int pkt_period_max; /* time in ms */
+};
+
+enum mgcp_type {
+	MGCP_RTP_DEFAULT	= 0,
+	MGCP_RTP_TRANSCODED,
+	MGCP_OSMUX_BSC,
+	MGCP_OSMUX_BSC_NAT,
+};
+
+#include <openbsc/osmux.h>
+
+struct mgcp_endpoint {
+	int allocated;
+	uint32_t ci;
+	char *callid;
+	struct mgcp_lco local_options;
+	int conn_mode;
+	int orig_mode;
+
+	/* backpointer */
+	struct mgcp_config *cfg;
+	struct mgcp_trunk_config *tcfg;
+
+	/* port status for bts/net */
+	struct mgcp_rtp_end bts_end;
+	struct mgcp_rtp_end net_end;
+
+	/*
+	 * For transcoding we will send from the local_port
+	 * of trans_bts and it will arrive at trans_net from
+	 * where we will forward it to the network.
+	 */
+	struct mgcp_rtp_end trans_bts;
+	struct mgcp_rtp_end trans_net;
+	enum mgcp_type type;
+
+	/* sequence bits */
+	struct mgcp_rtp_state net_state;
+	struct mgcp_rtp_state bts_state;
+
+	/* fields for re-transmission */
+	char *last_trans;
+	char *last_response;
+
+	/* tap for the endpoint */
+	struct mgcp_rtp_tap taps[MGCP_TAP_COUNT];
+
+	struct {
+		/* Osmux state: disabled, activating, active */
+		enum osmux_state state;
+		/* Allocated Osmux circuit ID for this endpoint */
+		int allocated_cid;
+		/* Used Osmux circuit ID for this endpoint */
+		uint8_t cid;
+		/* handle to batch messages */
+		struct osmux_in_handle *in;
+		/* handle to unbatch messages */
+		struct osmux_out_handle out;
+		/* statistics */
+		struct {
+			uint32_t chunks;
+			uint32_t octets;
+		} stats;
+	} osmux;
+
+	/* Jitter buffer */
+	struct osmo_jibuf* bts_jb;
+	/* Use a jitterbuffer on the bts-side receiver */
+	bool bts_use_jibuf;
+	/* Minimum and maximum buffer size for the jitter buffer, in ms */
+	uint32_t bts_jitter_delay_min;
+	uint32_t bts_jitter_delay_max;
+};
+
+#define for_each_line(line, save)			\
+	for (line = strline_r(NULL, &save); line;\
+	     line = strline_r(NULL, &save))
+
+static inline char *strline_r(char *str, char **saveptr)
+{
+	char *result;
+
+	if (str)
+		*saveptr = str;
+
+	result = *saveptr;
+
+	if (*saveptr != NULL) {
+		*saveptr = strpbrk(*saveptr, "\r\n");
+
+		if (*saveptr != NULL) {
+			char *eos = *saveptr;
+
+			if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n')
+				(*saveptr)++;
+			(*saveptr)++;
+			if ((*saveptr)[0] == '\0')
+				*saveptr = NULL;
+
+			*eos = '\0';
+		}
+	}
+
+	return result;
+}
+
+
+
+#define ENDPOINT_NUMBER(endp) abs((int)(endp - endp->tcfg->endpoints))
+
+/**
+ * Internal structure while parsing a request
+ */
+struct mgcp_parse_data {
+	struct mgcp_config *cfg;
+	struct mgcp_endpoint *endp;
+	char *trans;
+	char *save;
+	int found;
+};
+
+int mgcp_send_dummy(struct mgcp_endpoint *endp);
+int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port);
+int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port);
+int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *enp, int rtp_port);
+int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *enp, int rtp_port);
+int mgcp_free_rtp_port(struct mgcp_rtp_end *end);
+
+/* For transcoding we need to manage an in and an output that are connected */
+static inline int endp_back_channel(int endpoint)
+{
+	return endpoint + 60;
+}
+
+struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index);
+struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index);
+
+void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change,
+			 struct mgcp_rtp_end *rtp);
+uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp,
+				  struct mgcp_rtp_end *rtp);
+
+void mgcp_state_calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *,
+			uint32_t *expected, int *loss);
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *);
+
+/* payload processing default functions */
+int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end,
+				char *data, int *len, int buf_size);
+
+int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp,
+				      struct mgcp_rtp_end *dst_end,
+				      struct mgcp_rtp_end *src_end);
+
+void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**subtype_name,
+					  const char**fmtp_extra);
+
+/* internal RTP Annex A counting */
+void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
+			const uint16_t seq, const int32_t transit,
+			const uint32_t ssrc);
+
+int mgcp_set_ip_tos(int fd, int tos);
+
+enum {
+	MGCP_DEST_NET = 0,
+	MGCP_DEST_BTS,
+};
+
+
+#define MGCP_DUMMY_LOAD 0x23
+
+
+/**
+ * SDP related information
+ */
+/* Assume audio frame length of 20ms */
+#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20
+#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000
+#define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20
+#define DEFAULT_RTP_AUDIO_DEFAULT_RATE  8000
+#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1
+
+#define PTYPE_UNDEFINED (-1)
+int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p);
+int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec,
+			int payload_type, const char *audio_name);
+
+
+/**
+ * Internal network related
+ */
+static inline const char *mgcp_net_src_addr(struct mgcp_endpoint *endp)
+{
+	if (endp->cfg->net_ports.bind_addr)
+		return endp->cfg->net_ports.bind_addr;
+	return endp->cfg->source_addr;
+}
+
+static inline const char *mgcp_bts_src_addr(struct mgcp_endpoint *endp)
+{
+	if (endp->cfg->bts_ports.bind_addr)
+		return endp->cfg->bts_ports.bind_addr;
+	return endp->cfg->source_addr;
+}
+
+/**
+ * Internal jitter buffer related
+ */
+void mgcp_dejitter_udp_send(struct msgb *msg, void *data);
diff --git a/openbsc/include/openbsc/mgcp_transcode.h b/openbsc/include/openbsc/mgcp_transcode.h
new file mode 100644
index 0000000..6892deb
--- /dev/null
+++ b/openbsc/include/openbsc/mgcp_transcode.h
@@ -0,0 +1,94 @@
+/*
+ * (C) 2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef OPENBSC_MGCP_TRANSCODE_H
+#define OPENBSC_MGCP_TRANSCODE_H
+
+#include "bscconfig.h"
+
+#if HAVE_GSM_H
+#include <gsm.h>
+#elif HAVE_GSM_GSM_H
+#include <gsm/gsm.h>
+#endif
+#ifdef HAVE_BCG729
+#include <bcg729/decoder.h>
+#include <bcg729/encoder.h>
+#endif
+
+enum audio_format {
+	AF_INVALID,
+	AF_S16,
+	AF_L16,
+	AF_GSM,
+	AF_G729,
+	AF_PCMA,
+	AF_PCMU
+};
+
+
+struct mgcp_process_rtp_state {
+	/* decoding */
+	enum audio_format src_fmt;
+	union {
+		gsm gsm_handle;
+#ifdef HAVE_BCG729
+		bcg729DecoderChannelContextStruct *g729_dec;
+#endif
+	} src;
+	size_t src_frame_size;
+	size_t src_samples_per_frame;
+
+	/* processing */
+
+	/* encoding */
+	enum audio_format dst_fmt;
+	union {
+		gsm gsm_handle;
+#ifdef HAVE_BCG729
+		bcg729EncoderChannelContextStruct *g729_enc;
+#endif
+	} dst;
+	size_t dst_frame_size;
+	size_t dst_samples_per_frame;
+	int dst_packet_duration;
+
+	int is_running;
+	uint16_t next_seq;
+	uint32_t next_time;
+	int16_t samples[10*160];
+	size_t sample_cnt;
+	size_t sample_offs;
+};
+
+
+int mgcp_transcoding_setup(struct mgcp_endpoint *endp,
+			   struct mgcp_rtp_end *dst_end,
+			   struct mgcp_rtp_end *src_end);
+
+void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**audio_name,
+					  const char**fmtp_extra);
+
+int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp,
+				 struct mgcp_rtp_end *dst_end,
+				 char *data, int *len, int buf_size);
+
+int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst);
+#endif /* OPENBSC_MGCP_TRANSCODE_H */
diff --git a/openbsc/include/openbsc/misdn.h b/openbsc/include/openbsc/misdn.h
new file mode 100644
index 0000000..9851ad3
--- /dev/null
+++ b/openbsc/include/openbsc/misdn.h
@@ -0,0 +1,27 @@
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MISDN_H
+#define MISDN_H
+
+#include <osmocom/abis/e1_input.h>
+
+int mi_setup(int cardnr,  struct e1inp_line *line, int release_l2);
+int mi_e1_line_update(struct e1inp_line *line);
+
+#endif
diff --git a/openbsc/include/openbsc/mncc.h b/openbsc/include/openbsc/mncc.h
new file mode 100644
index 0000000..49f0c8b
--- /dev/null
+++ b/openbsc/include/openbsc/mncc.h
@@ -0,0 +1,219 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface 
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _MNCC_H
+#define _MNCC_H
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/mncc.h>
+
+#include <stdint.h>
+
+struct gsm_network;
+struct msgb;
+
+
+/* One end of a call */
+struct gsm_call {
+	struct llist_head entry;
+
+	/* network handle */
+	void *net;
+
+	/* the 'local' transaction */
+	uint32_t callref;
+	/* the 'remote' transaction */
+	uint32_t remote_ref;
+};
+
+#define MNCC_SETUP_REQ		0x0101
+#define MNCC_SETUP_IND		0x0102
+#define MNCC_SETUP_RSP		0x0103
+#define MNCC_SETUP_CNF		0x0104
+#define MNCC_SETUP_COMPL_REQ	0x0105
+#define MNCC_SETUP_COMPL_IND	0x0106
+/* MNCC_REJ_* is perfomed via MNCC_REL_* */
+#define MNCC_CALL_CONF_IND	0x0107
+#define MNCC_CALL_PROC_REQ	0x0108
+#define MNCC_PROGRESS_REQ	0x0109
+#define MNCC_ALERT_REQ		0x010a
+#define MNCC_ALERT_IND		0x010b
+#define MNCC_NOTIFY_REQ		0x010c
+#define MNCC_NOTIFY_IND		0x010d
+#define MNCC_DISC_REQ		0x010e
+#define MNCC_DISC_IND		0x010f
+#define MNCC_REL_REQ		0x0110
+#define MNCC_REL_IND		0x0111
+#define MNCC_REL_CNF		0x0112
+#define MNCC_FACILITY_REQ	0x0113
+#define MNCC_FACILITY_IND	0x0114
+#define MNCC_START_DTMF_IND	0x0115
+#define MNCC_START_DTMF_RSP	0x0116
+#define MNCC_START_DTMF_REJ	0x0117
+#define MNCC_STOP_DTMF_IND	0x0118
+#define MNCC_STOP_DTMF_RSP	0x0119
+#define MNCC_MODIFY_REQ		0x011a
+#define MNCC_MODIFY_IND		0x011b
+#define MNCC_MODIFY_RSP		0x011c
+#define MNCC_MODIFY_CNF		0x011d
+#define MNCC_MODIFY_REJ		0x011e
+#define MNCC_HOLD_IND		0x011f
+#define MNCC_HOLD_CNF		0x0120
+#define MNCC_HOLD_REJ		0x0121
+#define MNCC_RETRIEVE_IND	0x0122
+#define MNCC_RETRIEVE_CNF	0x0123
+#define MNCC_RETRIEVE_REJ	0x0124
+#define MNCC_USERINFO_REQ	0x0125
+#define MNCC_USERINFO_IND	0x0126
+#define MNCC_REJ_REQ		0x0127
+#define MNCC_REJ_IND		0x0128
+
+#define MNCC_BRIDGE		0x0200
+#define MNCC_FRAME_RECV		0x0201
+#define MNCC_FRAME_DROP		0x0202
+#define MNCC_LCHAN_MODIFY	0x0203
+#define MNCC_RTP_CREATE		0x0204
+#define MNCC_RTP_CONNECT	0x0205
+#define MNCC_RTP_FREE		0x0206
+
+#define GSM_TCHF_FRAME		0x0300
+#define GSM_TCHF_FRAME_EFR	0x0301
+#define GSM_TCHH_FRAME		0x0302
+#define GSM_TCH_FRAME_AMR	0x0303
+#define GSM_BAD_FRAME		0x03ff
+
+#define MNCC_SOCKET_HELLO	0x0400
+
+#define GSM_MAX_FACILITY	128
+#define GSM_MAX_SSVERSION	128
+#define GSM_MAX_USERUSER	128
+
+#define	MNCC_F_BEARER_CAP	0x0001
+#define MNCC_F_CALLED		0x0002
+#define MNCC_F_CALLING		0x0004
+#define MNCC_F_REDIRECTING	0x0008
+#define MNCC_F_CONNECTED	0x0010
+#define MNCC_F_CAUSE		0x0020
+#define MNCC_F_USERUSER		0x0040
+#define MNCC_F_PROGRESS		0x0080
+#define MNCC_F_EMERGENCY	0x0100
+#define MNCC_F_FACILITY		0x0200
+#define MNCC_F_SSVERSION	0x0400
+#define MNCC_F_CCCAP		0x0800
+#define MNCC_F_KEYPAD		0x1000
+#define MNCC_F_SIGNAL		0x2000
+
+struct gsm_mncc {
+	/* context based information */
+	uint32_t	msg_type;
+	uint32_t	callref;
+
+	/* which fields are present */
+	uint32_t	fields;
+
+	/* data derived informations (MNCC_F_ based) */
+	struct gsm_mncc_bearer_cap	bearer_cap;
+	struct gsm_mncc_number		called;
+	struct gsm_mncc_number		calling;
+	struct gsm_mncc_number		redirecting;
+	struct gsm_mncc_number		connected;
+	struct gsm_mncc_cause		cause;
+	struct gsm_mncc_progress	progress;
+	struct gsm_mncc_useruser	useruser;
+	struct gsm_mncc_facility	facility;
+	struct gsm_mncc_cccap		cccap;
+	struct gsm_mncc_ssversion	ssversion;
+	struct	{
+		int		sup;
+		int		inv;
+	} clir;
+	int		signal;
+
+	/* data derived information, not MNCC_F based */
+	int		keypad;
+	int		more;
+	int		notify; /* 0..127 */
+	int		emergency;
+	char		imsi[16];
+
+	unsigned char	lchan_type;
+	unsigned char	lchan_mode;
+};
+
+struct gsm_data_frame {
+	uint32_t	msg_type;
+	uint32_t	callref;
+	unsigned char	data[0];
+};
+
+#define MNCC_SOCK_VERSION	5
+struct gsm_mncc_hello {
+	uint32_t	msg_type;
+	uint32_t	version;
+
+	/* send the sizes of the structs */
+	uint32_t	mncc_size;
+	uint32_t	data_frame_size;
+
+	/* send some offsets */
+	uint32_t	called_offset;
+	uint32_t	signal_offset;
+	uint32_t	emergency_offset;
+	uint32_t	lchan_type_offset;
+};
+
+struct gsm_mncc_rtp {
+	uint32_t	msg_type;
+	uint32_t	callref;
+	uint32_t	ip;
+	uint16_t	port;
+	uint32_t	payload_type;
+	uint32_t	payload_msg_type;
+};
+
+struct gsm_mncc_bridge {
+	uint32_t	msg_type;
+	uint32_t	callref[2];
+};
+
+const char *get_mncc_name(int value);
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg);
+
+/* input from CC code into mncc_builtin */
+int int_mncc_recv(struct gsm_network *net, struct msgb *msg);
+
+/* input from CC code into mncc_sock */
+int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg);
+
+int mncc_sock_init(struct gsm_network *net, const char *sock_path);
+
+#define mncc_is_data_frame(msg_type) \
+	(msg_type == GSM_TCHF_FRAME \
+		|| msg_type == GSM_TCHF_FRAME_EFR \
+		|| msg_type == GSM_TCHH_FRAME \
+		|| msg_type == GSM_TCH_FRAME_AMR \
+		|| msg_type == GSM_BAD_FRAME)
+
+
+#endif
diff --git a/openbsc/include/openbsc/mncc_int.h b/openbsc/include/openbsc/mncc_int.h
new file mode 100644
index 0000000..213ce14
--- /dev/null
+++ b/openbsc/include/openbsc/mncc_int.h
@@ -0,0 +1,14 @@
+#ifndef _MNCC_INT_H
+#define _MNCC_INT_H
+
+#include <stdint.h>
+
+struct mncc_int {
+	uint8_t def_codec[2];
+};
+
+extern struct mncc_int mncc_int;
+
+uint8_t mncc_codec_for_mode(int lchan_type);
+
+#endif
diff --git a/openbsc/include/openbsc/nat_rewrite_trie.h b/openbsc/include/openbsc/nat_rewrite_trie.h
new file mode 100644
index 0000000..0571099
--- /dev/null
+++ b/openbsc/include/openbsc/nat_rewrite_trie.h
@@ -0,0 +1,47 @@
+/*
+ * (C) 2013 by On-Waves
+ * (C) 2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef NAT_REWRITE_FILE_H
+#define NAT_REWRITE_FILE_H
+
+#include <osmocom/core/linuxrbtree.h>
+
+struct vty;
+
+struct nat_rewrite_rule {
+	/* For digits 0-9 and + */
+	struct nat_rewrite_rule *rules[11];
+
+	char empty;
+	char prefix[14];
+	char rewrite[6];
+};
+
+struct nat_rewrite {
+	struct nat_rewrite_rule rule;
+	size_t prefixes;
+};
+
+
+struct nat_rewrite *nat_rewrite_parse(void *ctx, const char *filename);
+struct nat_rewrite_rule *nat_rewrite_lookup(struct nat_rewrite *, const char *prefix);
+void nat_rewrite_dump(struct nat_rewrite *rewr);
+void nat_rewrite_dump_vty(struct vty *vty, struct nat_rewrite *rewr);
+
+#endif
diff --git a/openbsc/include/openbsc/network_listen.h b/openbsc/include/openbsc/network_listen.h
new file mode 100644
index 0000000..67d1f4e
--- /dev/null
+++ b/openbsc/include/openbsc/network_listen.h
@@ -0,0 +1,16 @@
+#ifndef _OPENBSC_NWL_H
+#define _OPENBSC_NWL_H
+
+#include <stdint.h>
+#include <openbsc/gsm_data.h>
+
+void ipac_nwl_init(void);
+
+/* Start a NWL test.  It will raise the S_IPAC_TEST_COMPLETE signal. */
+int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr,
+			const uint8_t *phys_conf, unsigned int phys_conf_len);
+
+int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev,
+			     uint16_t max_num_arfcns);
+
+#endif /* _OPENBSC_NWL_H */
diff --git a/openbsc/include/openbsc/oap_client.h b/openbsc/include/openbsc/oap_client.h
new file mode 100644
index 0000000..80c86d5
--- /dev/null
+++ b/openbsc/include/openbsc/oap_client.h
@@ -0,0 +1,82 @@
+/* Osmocom Authentication Protocol API */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct msgb;
+struct osmo_oap_message;
+
+/* This is the config part for vty. It is essentially copied in
+ * oap_client_state, where values are copied over once the config is
+ * considered valid. */
+struct oap_client_config {
+	uint16_t client_id;
+	int secret_k_present;
+	uint8_t secret_k[16];
+	int secret_opc_present;
+	uint8_t secret_opc[16];
+};
+
+/* The runtime state of the OAP client. client_id and the secrets are in fact
+ * duplicated from oap_client_config, so that a separate validation of the
+ * config data is possible, and so that only a struct oap_client_state* is
+ * passed around. */
+struct oap_client_state {
+	enum {
+		OAP_UNINITIALIZED = 0,	/* just allocated. */
+		OAP_DISABLED,		/* disabled by config. */
+		OAP_INITIALIZED,	/* enabled, config is valid. */
+		OAP_REQUESTED_CHALLENGE,
+		OAP_SENT_CHALLENGE_RESULT,
+		OAP_REGISTERED
+	} state;
+	uint16_t client_id;
+	uint8_t secret_k[16];
+	uint8_t secret_opc[16];
+	int registration_failures;
+};
+
+/* From config, initialize state. Return 0 on success. */
+int oap_client_init(struct oap_client_config *config,
+		    struct oap_client_state *state);
+
+/* Construct an OAP registration message and return in *msg_tx. Use
+ * state->client_id and update state->state.
+ * Return 0 on success, or a negative value on error.
+ * If an error is returned, *msg_tx is guaranteed to be NULL. */
+int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx);
+
+/* Decode and act on a received OAP message msg_rx. Update state->state.  If a
+ * non-NULL pointer is returned in *msg_tx, that msgb should be sent to the OAP
+ * server (and freed) by the caller. The received msg_rx is not freed.
+ * Return 0 on success, or a negative value on error.
+ * If an error is returned, *msg_tx is guaranteed to be NULL. */
+int oap_client_handle(struct oap_client_state *state,
+		      const struct msgb *msg_rx, struct msgb **msg_tx);
+
+/* Allocate a msgb and in it, return the encoded oap_client_msg. Return
+ * NULL on error. (Like oap_client_encode(), but also allocates a msgb.)
+ * About the name: the idea is do_something(oap_client_encoded(my_struct))
+ */
+struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_client_msg);
diff --git a/openbsc/include/openbsc/openbscdefines.h b/openbsc/include/openbsc/openbscdefines.h
new file mode 100644
index 0000000..c6ac153
--- /dev/null
+++ b/openbsc/include/openbsc/openbscdefines.h
@@ -0,0 +1,34 @@
+/* 
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSCDEFINES_H
+#define OPENBSCDEFINES_H
+
+#ifdef BUILDING_ON_WINDOWS
+    #ifdef BUILDING_OPENBSC
+        #define BSC_API __declspec(dllexport)
+    #else
+        #define BSC_API __declspec(dllimport)
+    #endif
+#else
+    #define BSC_API __attribute__((visibility("default")))
+#endif
+
+#endif
diff --git a/openbsc/include/openbsc/osmo_bsc.h b/openbsc/include/openbsc/osmo_bsc.h
new file mode 100644
index 0000000..9e688fd
--- /dev/null
+++ b/openbsc/include/openbsc/osmo_bsc.h
@@ -0,0 +1,71 @@
+/* OpenBSC BSC code */
+
+#ifndef OSMO_BSC_H
+#define OSMO_BSC_H
+
+#include "bsc_api.h"
+#include "bsc_msg_filter.h"
+
+#define BSS_SEND_USSD 1
+
+enum bsc_con {
+	BSC_CON_SUCCESS,
+	BSC_CON_REJECT_NO_LINK,
+	BSC_CON_REJECT_RF_GRACE,
+	BSC_CON_NO_MEM,
+};
+
+struct sccp_connection;
+struct bsc_msc_data;
+struct bsc_msc_connection;
+
+struct osmo_bsc_sccp_con {
+	struct llist_head entry;
+
+	int ciphering_handled;
+
+	/* for audio handling */
+	uint16_t cic;
+	int rtp_port;
+
+	/* for advanced ping/pong */
+	int send_ping;
+
+	/* SCCP connection realted */
+	struct sccp_connection *sccp;
+	struct bsc_msc_data *msc;
+	struct osmo_timer_list sccp_it_timeout;
+	struct osmo_timer_list sccp_cc_timeout;
+
+	struct llist_head sccp_queue;
+	unsigned int sccp_queue_size;
+
+	struct gsm_subscriber_connection *conn;
+	uint8_t new_subscriber;
+
+	struct bsc_filter_state filter_state;
+};
+
+struct bsc_api *osmo_bsc_api();
+
+int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg);
+int bsc_open_connection(struct osmo_bsc_sccp_con *sccp, struct msgb *msg);
+enum bsc_con bsc_create_new_connection(struct gsm_subscriber_connection *conn,
+				       struct bsc_msc_data *msc, int send_ping);
+int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp);
+
+struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, struct msgb *);
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg);
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg);
+int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn);
+
+int bsc_handle_udt(struct bsc_msc_data *msc, struct msgb *msg, unsigned int length);
+int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn, struct msgb *msg, unsigned int len);
+
+int bsc_ctrl_cmds_install();
+
+void bsc_gen_location_state_trap(struct gsm_bts *bts);
+
+struct llist_head *bsc_access_lists(void);
+
+#endif
diff --git a/openbsc/include/openbsc/osmo_bsc_grace.h b/openbsc/include/openbsc/osmo_bsc_grace.h
new file mode 100644
index 0000000..5a81cd1
--- /dev/null
+++ b/openbsc/include/openbsc/osmo_bsc_grace.h
@@ -0,0 +1,35 @@
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OSMO_BSC_GRACE_H
+#define OSMO_BSC_GRACE_H
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+
+struct bsc_msc_data;
+
+int bsc_grace_allow_new_connection(struct gsm_network *net, struct gsm_bts *bts);
+int bsc_grace_paging_request(enum signal_rf rf_policy,
+			     struct bsc_subscr *subscr,
+			     int chan_needed,
+			     struct bsc_msc_data *msc);
+
+#endif
diff --git a/openbsc/include/openbsc/osmo_bsc_rf.h b/openbsc/include/openbsc/osmo_bsc_rf.h
new file mode 100644
index 0000000..19ccd08
--- /dev/null
+++ b/openbsc/include/openbsc/osmo_bsc_rf.h
@@ -0,0 +1,66 @@
+#ifndef OSMO_BSC_RF
+#define OSMO_BSC_RF
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+enum osmo_bsc_rf_opstate {
+	OSMO_BSC_RF_OPSTATE_INOPERATIONAL,
+	OSMO_BSC_RF_OPSTATE_OPERATIONAL,
+};
+
+enum osmo_bsc_rf_adminstate {
+	OSMO_BSC_RF_ADMINSTATE_UNLOCKED,
+	OSMO_BSC_RF_ADMINSTATE_LOCKED,
+};
+
+enum osmo_bsc_rf_policy {
+	OSMO_BSC_RF_POLICY_OFF,
+	OSMO_BSC_RF_POLICY_ON,
+	OSMO_BSC_RF_POLICY_GRACE,
+	OSMO_BSC_RF_POLICY_UNKNOWN,
+};
+
+
+struct gsm_network;
+
+struct osmo_bsc_rf {
+	/* the value of signal.h */
+	int policy;
+	struct osmo_fd listen;
+	struct gsm_network *gsm_network;
+
+	const char *last_state_command;
+
+	char *last_rf_lock_ctrl_command;
+
+	/* delay the command */
+	char last_request;
+	struct osmo_timer_list delay_cmd;
+
+	/* verify that RF is up as it should be */
+	struct osmo_timer_list rf_check;
+
+	/* some handling for the automatic grace switch */
+	struct osmo_timer_list grace_timeout;
+
+	/* auto RF switch-off due lack of MSC connection */
+	struct osmo_timer_list auto_off_timer;
+};
+
+struct osmo_bsc_rf_conn {
+	struct osmo_wqueue queue;
+	struct osmo_bsc_rf *rf;
+};
+
+const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate);
+const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate);
+const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy);
+enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts);
+enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts);
+enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts);
+struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net);
+void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd);
+
+#endif
diff --git a/openbsc/include/openbsc/osmo_msc.h b/openbsc/include/openbsc/osmo_msc.h
new file mode 100644
index 0000000..beb3f5e
--- /dev/null
+++ b/openbsc/include/openbsc/osmo_msc.h
@@ -0,0 +1,11 @@
+/* Routines for the MSC handling */
+
+#ifndef OSMO_MSC_H
+#define OSMO_MSC_H
+
+#include "bsc_api.h"
+
+struct bsc_api *msc_bsc_api();
+void msc_release_connection(struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/openbsc/include/openbsc/osmux.h b/openbsc/include/openbsc/osmux.h
new file mode 100644
index 0000000..6386125
--- /dev/null
+++ b/openbsc/include/openbsc/osmux.h
@@ -0,0 +1,41 @@
+#ifndef _OPENBSC_OSMUX_H_
+#define _OPENBSC_OSMUX_H_
+
+#include <osmocom/netif/osmux.h>
+
+#define OSMUX_PORT	1984
+
+enum {
+	OSMUX_ROLE_BSC = 0,
+	OSMUX_ROLE_BSC_NAT,
+};
+
+int osmux_init(int role, struct mgcp_config *cfg);
+int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port);
+void osmux_disable_endpoint(struct mgcp_endpoint *endp);
+void osmux_allocate_cid(struct mgcp_endpoint *endp);
+void osmux_release_cid(struct mgcp_endpoint *endp);
+
+int osmux_xfrm_to_rtp(struct mgcp_endpoint *endp, int type, char *buf, int rc);
+int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp);
+
+int osmux_send_dummy(struct mgcp_endpoint *endp);
+
+int osmux_get_cid(void);
+void osmux_put_cid(uint8_t osmux_cid);
+int osmux_used_cid(void);
+
+enum osmux_state {
+	OSMUX_STATE_DISABLED = 0, /* Osmux not being currently used by endp */
+	OSMUX_STATE_NEGOTIATING,  /* Osmux was locally requested in MGCP CRCX */
+	OSMUX_STATE_ACTIVATING,   /* Osmux was accepted in MGCP CRCX ACK. It can now be enabled by \ref osmux_enable_endpoint. */
+	OSMUX_STATE_ENABLED,	  /* Osmux was initialized by \ref osmux_enable_endpoint and can process frames */
+};
+
+enum osmux_usage {
+	OSMUX_USAGE_OFF = 0,
+	OSMUX_USAGE_ON = 1,
+	OSMUX_USAGE_ONLY = 2,
+};
+
+#endif
diff --git a/openbsc/include/openbsc/paging.h b/openbsc/include/openbsc/paging.h
new file mode 100644
index 0000000..7dd8500
--- /dev/null
+++ b/openbsc/include/openbsc/paging.h
@@ -0,0 +1,77 @@
+/* Paging helper and manager.... */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef PAGING_H
+#define PAGING_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_subscriber.h>
+
+/**
+ * A pending paging request
+ */
+struct gsm_paging_request {
+	/* list_head for list of all paging requests */
+	struct llist_head entry;
+	/* the subscriber which we're paging. Later gsm_paging_request
+	 * should probably become a part of the bsc_subsrc struct? */
+	struct bsc_subscr *bsub;
+	/* back-pointer to the BTS on which we are paging */
+	struct gsm_bts *bts;
+	/* what kind of channel type do we ask the MS to establish */
+	int chan_type;
+
+	/* Timer 3113: how long do we try to page? */
+	struct osmo_timer_list T3113;
+
+	/* How often did we ask the BTS to page? */
+	int attempts;
+
+	/* callback to be called in case paging completes */
+	gsm_cbfn *cbfn;
+	void *cbfn_param;
+};
+
+/* schedule paging request */
+int paging_request(struct gsm_network *network, struct bsc_subscr *bsub,
+		   int type, gsm_cbfn *cbfn, void *data);
+int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub,
+		       int type, gsm_cbfn *cbfn, void *data);
+
+/* stop paging requests */
+void paging_request_stop(struct llist_head *bts_list,
+			 struct gsm_bts *_bts, struct bsc_subscr *bsub,
+			 struct gsm_subscriber_connection *conn,
+			 struct msgb *msg);
+
+/* update paging load */
+void paging_update_buffer_space(struct gsm_bts *bts, uint16_t);
+
+/* pending paging requests */
+unsigned int paging_pending_requests_nr(struct gsm_bts *bts);
+
+void *paging_get_data(struct gsm_bts *bts, struct bsc_subscr *bsub);
+
+#endif
diff --git a/openbsc/include/openbsc/pcu_if.h b/openbsc/include/openbsc/pcu_if.h
new file mode 100644
index 0000000..1f398b4
--- /dev/null
+++ b/openbsc/include/openbsc/pcu_if.h
@@ -0,0 +1,35 @@
+#ifndef _PCU_IF_H
+#define _PCU_IF_H
+
+#include <osmocom/gsm/l1sap.h>
+
+extern int pcu_direct;
+
+struct pcu_sock_state {
+	struct gsm_network *net;
+	struct osmo_fd listen_bfd;	/* fd for listen socket */
+	struct osmo_fd conn_bfd;	/* fd for connection to lcr */
+	struct llist_head upqueue;	/* queue for sending messages */
+};
+
+/* PCU relevant information has changed; Inform PCU (if connected) */
+void pcu_info_update(struct gsm_bts *bts);
+
+/* Forward rach indication to PCU */
+int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
+	uint8_t is_11bit, enum ph_burst_type burst_type);
+
+/* Confirm the sending of an immediate assignment to the pcu */
+int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli);
+
+
+/* Confirm the sending of an immediate assignment to the pcu */
+int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli);
+
+/* Open connection to PCU */
+int pcu_sock_init(const char *path, struct gsm_bts *bts);
+
+/* Close connection to PCU */
+void pcu_sock_exit(struct gsm_bts *bts);
+
+#endif /* _PCU_IF_H */
diff --git a/openbsc/include/openbsc/pcuif_proto.h b/openbsc/include/openbsc/pcuif_proto.h
new file mode 100644
index 0000000..2e3f782
--- /dev/null
+++ b/openbsc/include/openbsc/pcuif_proto.h
@@ -0,0 +1,178 @@
+#ifndef _PCUIF_PROTO_H
+#define _PCUIF_PROTO_H
+
+#define PCU_IF_VERSION		0x09
+
+/* msg_type */
+#define PCU_IF_MSG_DATA_REQ	0x00	/* send data to given channel */
+#define PCU_IF_MSG_DATA_CNF	0x01	/* confirm (e.g. transmission on PCH) */
+#define PCU_IF_MSG_DATA_IND	0x02	/* receive data from given channel */
+#define PCU_IF_MSG_DATA_CNF_DT	0x11	/* confirm (with direct tlli) */
+#define PCU_IF_MSG_RTS_REQ	0x10	/* ready to send request */
+#define PCU_IF_MSG_RACH_IND	0x22	/* receive RACH */
+#define PCU_IF_MSG_INFO_IND	0x32	/* retrieve BTS info */
+#define PCU_IF_MSG_ACT_REQ	0x40	/* activate/deactivate PDCH */
+#define PCU_IF_MSG_TIME_IND	0x52	/* GSM time indication */
+#define PCU_IF_MSG_PAG_REQ	0x60	/* paging request */
+
+/* sapi */
+#define PCU_IF_SAPI_RACH	0x01	/* channel request on CCCH */
+#define PCU_IF_SAPI_AGCH	0x02	/* assignment on AGCH */
+#define PCU_IF_SAPI_PCH		0x03	/* paging/assignment on PCH */
+#define PCU_IF_SAPI_BCCH	0x04	/* SI on BCCH */
+#define PCU_IF_SAPI_PDTCH	0x05	/* packet data/control/ccch block */
+#define PCU_IF_SAPI_PRACH	0x06	/* packet random access channel */
+#define PCU_IF_SAPI_PTCCH	0x07	/* packet TA control channel */
+#define PCU_IF_SAPI_AGCH_DT	0x08	/* assignment on AGCH but with additional TLLI */
+
+/* flags */
+#define PCU_IF_FLAG_ACTIVE	(1 << 0)/* BTS is active */
+#define PCU_IF_FLAG_SYSMO	(1 << 1)/* access PDCH of sysmoBTS directly */
+#define PCU_IF_FLAG_CS1		(1 << 16)
+#define PCU_IF_FLAG_CS2		(1 << 17)
+#define PCU_IF_FLAG_CS3		(1 << 18)
+#define PCU_IF_FLAG_CS4		(1 << 19)
+#define PCU_IF_FLAG_MCS1	(1 << 20)
+#define PCU_IF_FLAG_MCS2	(1 << 21)
+#define PCU_IF_FLAG_MCS3	(1 << 22)
+#define PCU_IF_FLAG_MCS4	(1 << 23)
+#define PCU_IF_FLAG_MCS5	(1 << 24)
+#define PCU_IF_FLAG_MCS6	(1 << 25)
+#define PCU_IF_FLAG_MCS7	(1 << 26)
+#define PCU_IF_FLAG_MCS8	(1 << 27)
+#define PCU_IF_FLAG_MCS9	(1 << 28)
+
+struct gsm_pcu_if_data {
+	uint8_t		sapi;
+	uint8_t		len;
+	uint8_t		data[162];
+	uint32_t	fn;
+	uint16_t	arfcn;
+	uint8_t		trx_nr;
+	uint8_t		ts_nr;
+	uint8_t		block_nr;
+	int8_t		rssi;
+	uint16_t	ber10k;		/*!< \brief BER in units of 0.01% */
+	int16_t		ta_offs_qbits;	/* !< \brief Burst TA Offset in quarter bits */
+	int16_t		lqual_cb;	/* !< \brief Link quality in centiBel */
+} __attribute__ ((packed));
+
+/* data confirmation with direct tlli (instead of raw mac block with tlli) */
+struct gsm_pcu_if_data_cnf_dt {
+	uint8_t		sapi;
+	uint32_t	tlli;
+	uint32_t	fn;
+	uint16_t	arfcn;
+	uint8_t		trx_nr;
+	uint8_t		ts_nr;
+	uint8_t		block_nr;
+	int8_t		rssi;
+	uint16_t ber10k;	/*!< \brief BER in units of 0.01% */
+	int16_t ta_offs_qbits;	/* !< \brief Burst TA Offset in quarter bits */
+	int16_t lqual_cb;	/* !< \brief Link quality in centiBel */
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_rts_req {
+	uint8_t		sapi;
+	uint8_t		spare[3];
+	uint32_t	fn;
+	uint16_t	arfcn;
+	uint8_t		trx_nr;
+	uint8_t		ts_nr;
+	uint8_t		block_nr;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_rach_ind {
+	uint8_t		sapi;
+	uint16_t	ra;
+	int16_t		qta;
+	uint32_t	fn;
+	uint16_t	arfcn;
+	uint8_t		is_11bit;
+	uint8_t 	burst_type;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_info_trx {
+	uint16_t	arfcn;
+	uint8_t		pdch_mask;		/* PDCH channels per TS */
+	uint8_t		spare;
+	uint8_t		tsc[8];			/* TSC per channel */
+	uint32_t	hlayer1;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_info_ind {
+	uint32_t	version;
+	uint32_t	flags;
+	struct gsm_pcu_if_info_trx trx[8];	/* TRX infos per BTS */
+	uint8_t		bsic;
+	/* RAI */
+	uint16_t	mcc, mnc;
+	uint8_t		mnc_3_digits;
+	uint16_t	lac, rac;
+	/* NSE */
+	uint16_t	nsei;
+	uint8_t		nse_timer[7];
+	uint8_t		cell_timer[11];
+	/* cell */
+	uint16_t	cell_id;
+	uint16_t	repeat_time;
+	uint8_t		repeat_count;
+	uint16_t	bvci;
+	uint8_t		t3142;
+	uint8_t		t3169;
+	uint8_t		t3191;
+	uint8_t		t3193_10ms;
+	uint8_t		t3195;
+	uint8_t		n3101;
+	uint8_t		n3103;
+	uint8_t		n3105;
+	uint8_t		cv_countdown;
+	uint16_t	dl_tbf_ext;
+	uint16_t	ul_tbf_ext;
+	uint8_t		initial_cs;
+	uint8_t		initial_mcs;
+	/* NSVC */
+	uint16_t	nsvci[2];
+	uint16_t	local_port[2];
+	uint16_t	remote_port[2];
+	uint32_t	remote_ip[2];
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_act_req {
+	uint8_t		activate;
+	uint8_t		trx_nr;
+	uint8_t		ts_nr;
+	uint8_t		spare;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_time_ind {
+	uint32_t	fn;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_pag_req {
+	uint8_t		sapi;
+	uint8_t		chan_needed;
+	uint8_t		identity_lv[9];
+} __attribute__ ((packed));
+
+struct gsm_pcu_if {
+	/* context based information */
+	uint8_t		msg_type;	/* message type */
+	uint8_t		bts_nr;		/* bts number */
+	uint8_t		spare[2];
+
+	union {
+		struct gsm_pcu_if_data		data_req;
+		struct gsm_pcu_if_data		data_cnf;
+		struct gsm_pcu_if_data_cnf_dt	data_cnf_dt;
+		struct gsm_pcu_if_data		data_ind;
+		struct gsm_pcu_if_rts_req	rts_req;
+		struct gsm_pcu_if_rach_ind	rach_ind;
+		struct gsm_pcu_if_info_ind	info_ind;
+		struct gsm_pcu_if_act_req	act_req;
+		struct gsm_pcu_if_time_ind	time_ind;
+		struct gsm_pcu_if_pag_req	pag_req;
+	} u;
+} __attribute__ ((packed));
+
+#endif /* _PCUIF_PROTO_H */
diff --git a/openbsc/include/openbsc/rest_octets.h b/openbsc/include/openbsc/rest_octets.h
new file mode 100644
index 0000000..0ae65f3
--- /dev/null
+++ b/openbsc/include/openbsc/rest_octets.h
@@ -0,0 +1,119 @@
+#ifndef _REST_OCTETS_H
+#define _REST_OCTETS_H
+
+#include <stdbool.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocom/gsm/sysinfo.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net);
+int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts);
+int rest_octets_si6(uint8_t *data, bool is1800_net);
+
+struct gsm48_si_selection_params {
+	uint16_t penalty_time:5,
+		  temp_offs:3,
+		  cell_resel_off:6,
+		  cbq:1,
+		  present:1;
+};
+
+struct gsm48_si_power_offset {
+	uint8_t power_offset:2,
+		 present:1;
+};
+
+struct gsm48_si3_gprs_ind {
+	uint8_t si13_position:1,
+		 ra_colour:3,
+		 present:1;
+};
+
+struct gsm48_lsa_params {
+	uint32_t prio_thr:3,
+		 lsa_offset:3,
+		 mcc:12,
+		 mnc:12;
+	unsigned int present;
+};
+
+struct gsm48_si_ro_info {
+	struct gsm48_si_selection_params selection_params;
+	struct gsm48_si_power_offset power_offset;
+	bool si2ter_indicator;
+	bool early_cm_ctrl;
+	struct {
+		uint8_t where:3,
+			 present:1;
+	} scheduling;
+	struct gsm48_si3_gprs_ind gprs_ind;
+	/* SI 3 specific */
+	bool early_cm_restrict_3g;
+	bool si2quater_indicator;
+	/* SI 4 specific */
+	struct gsm48_lsa_params lsa_params;
+	uint16_t cell_id;
+	uint8_t break_ind;	/* do we have SI7 + SI8 ? */
+};
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(uint8_t *data, const struct gsm48_si_ro_info *si3);
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len);
+
+/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
+enum gprs_nmo {
+	GPRS_NMO_I	= 0,	/* CS pagin on GPRS paging or traffic channel */
+	GPRS_NMO_II	= 1,	/* all paging on CCCH */
+	GPRS_NMO_III	= 2,	/* no paging coordination */
+};
+
+/* TS 04.60 12.24 */
+struct gprs_cell_options {
+	enum gprs_nmo nmo;
+	/* T3168: wait for packet uplink assignment message */
+	uint32_t t3168;	/* in milliseconds */
+	/* T3192: wait for release of the TBF after reception of the final block */
+	uint32_t t3192;	/* in milliseconds */
+	uint32_t drx_timer_max;/* in seconds */
+	uint32_t bs_cv_max;
+	uint8_t  supports_egprs_11bit_rach;
+	bool ctrl_ack_type_use_block; /* use PACKET CONTROL ACKNOWLEDGMENT */
+
+	uint8_t ext_info_present;
+	struct {
+		uint8_t egprs_supported;
+			uint8_t use_egprs_p_ch_req;
+			uint8_t bep_period;
+		uint8_t pfc_supported;
+		uint8_t dtm_supported;
+		uint8_t bss_paging_coordination;
+	} ext_info;
+};
+
+/* TS 04.60 Table 12.9.2 */
+struct gprs_power_ctrl_pars {
+	uint8_t alpha;
+	uint8_t t_avg_w;
+	uint8_t t_avg_t;
+	uint8_t pc_meas_chan;
+	uint8_t n_avg_i;
+};
+
+struct gsm48_si13_info {
+	struct gprs_cell_options cell_opts;
+	struct gprs_power_ctrl_pars pwr_ctrl_pars;
+	uint8_t bcch_change_mark;
+	uint8_t si_change_field;
+	uint8_t rac;
+	uint8_t spgc_ccch_sup;
+	uint8_t net_ctrl_ord;
+	uint8_t prio_acc_thr;
+};
+
+/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13);
+
+#endif /* _REST_OCTETS_H */
diff --git a/openbsc/include/openbsc/rrlp.h b/openbsc/include/openbsc/rrlp.h
new file mode 100644
index 0000000..c89402a
--- /dev/null
+++ b/openbsc/include/openbsc/rrlp.h
@@ -0,0 +1,7 @@
+#ifndef _RRLP_H
+#define _RRLP_H
+
+void on_dso_load_rrlp(void);
+
+#endif /* _RRLP_H */
+
diff --git a/openbsc/include/openbsc/rs232.h b/openbsc/include/openbsc/rs232.h
new file mode 100644
index 0000000..61187ca
--- /dev/null
+++ b/openbsc/include/openbsc/rs232.h
@@ -0,0 +1,9 @@
+#ifndef _RS232_H
+#define _RS232_H
+
+int rs232_setup(const char *serial_port, unsigned int delay_ms,
+		struct gsm_bts *bts);
+
+int handle_serial_msg(struct msgb *msg);
+
+#endif /* _RS232_H */
diff --git a/openbsc/include/openbsc/rtp_proxy.h b/openbsc/include/openbsc/rtp_proxy.h
new file mode 100644
index 0000000..52ffefd
--- /dev/null
+++ b/openbsc/include/openbsc/rtp_proxy.h
@@ -0,0 +1,95 @@
+#ifndef _RTP_PROXY_H
+#define _RTP_PROXY_H
+
+/* RTP proxy handling for ip.access nanoBTS */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/select.h>
+
+#include <openbsc/mncc.h>
+
+#define RTP_PT_GSM_FULL 3
+#define RTP_PT_GSM_HALF 96
+#define RTP_PT_GSM_EFR 97
+#define RTP_PT_AMR 98
+#define RTP_LEN_GSM_FULL 33
+#define RTP_LEN_GSM_HALF 15
+#define RTP_LEN_GSM_EFR 31
+#define RTP_GSM_DURATION 160
+
+enum rtp_rx_action {
+	RTP_NONE,
+	RTP_PROXY,
+	RTP_RECV_UPSTREAM,
+};
+
+enum rtp_tx_action {
+	RTP_SEND_NONE,
+	RTP_SEND_DOWNSTREAM,
+};
+
+struct rtp_sub_socket {
+	struct sockaddr_in sin_local;
+	struct sockaddr_in sin_remote;
+
+	struct osmo_fd bfd;
+	/* linked list of to-be-transmitted msgb's */
+	struct llist_head tx_queue;
+};
+
+struct rtp_socket {
+	struct llist_head list;
+
+	struct rtp_sub_socket rtp;
+	struct rtp_sub_socket rtcp;
+
+	/* what should we do on receive? */
+	enum rtp_rx_action rx_action;
+	union {
+		struct {
+			struct rtp_socket *other_sock;
+		} proxy;
+		struct {
+			struct gsm_network *net;
+			uint32_t callref;
+		} receive;
+	};
+	enum rtp_tx_action tx_action;
+	struct {
+		uint16_t sequence;
+		uint32_t timestamp;
+		uint32_t ssrc;
+		struct timeval last_tv;
+	} transmit;
+};
+
+struct rtp_socket *rtp_socket_create(void);
+int rtp_socket_bind(struct rtp_socket *rs, uint32_t ip);
+int rtp_socket_connect(struct rtp_socket *rs, uint32_t ip, uint16_t port);
+int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other);
+int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net, uint32_t callref);
+int rtp_socket_free(struct rtp_socket *rs);
+int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame);
+
+#endif /* _RTP_PROXY_H */
diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h
new file mode 100644
index 0000000..9749250
--- /dev/null
+++ b/openbsc/include/openbsc/signal.h
@@ -0,0 +1,243 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2009-2010, 2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_SIGNAL_H
+#define OPENBSC_SIGNAL_H
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <openbsc/gsm_data.h>
+
+#include <osmocom/core/signal.h>
+
+/*
+ * Signalling subsystems
+ */
+enum signal_subsystems {
+	SS_PAGING,
+	SS_SMS,
+	SS_ABISIP,
+	SS_NM,
+	SS_LCHAN,
+	SS_SUBSCR,
+	SS_SCALL,
+	SS_CHALLOC,
+	SS_IPAC_NWL,
+	SS_RF,
+	SS_MSC,
+	SS_HO,
+	SS_CCCH
+};
+
+/* SS_PAGING signals */
+enum signal_paging {
+	S_PAGING_SUCCEEDED,
+	S_PAGING_EXPIRED,
+};
+
+/* SS_SMS signals */
+enum signal_sms {
+	S_SMS_SUBMITTED,	/* A SMS has been successfully submitted to us */
+	S_SMS_DELIVERED,	/* A SMS has been successfully delivered to a MS */
+	S_SMS_SMMA,		/* A MS tells us it has more space available */
+	S_SMS_MEM_EXCEEDED,	/* A MS tells us it has no more space available */
+	S_SMS_UNKNOWN_ERROR,	/* A MS tells us it has an error */
+};
+
+/* SS_ABISIP signals */
+enum signal_abisip {
+	S_ABISIP_CRCX_ACK,
+	S_ABISIP_MDCX_ACK,
+	S_ABISIP_DLCX_IND,
+};
+
+/* SS_NM signals */
+enum signal_nm {
+	S_NM_SW_ACTIV_REP,	/* GSM 12.21 software activated report */
+	S_NM_FAIL_REP,		/* GSM 12.21 failure event report */
+	S_NM_NACK,		/* GSM 12.21 various NM_MT_*_NACK happened */
+	S_NM_IPACC_NACK,	/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_NACK happened */
+	S_NM_IPACC_ACK,		/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_ACK happened */
+	S_NM_IPACC_RESTART_ACK, /* nanoBTS has send a restart ack */
+	S_NM_IPACC_RESTART_NACK,/* nanoBTS has send a restart ack */
+	S_NM_TEST_REP,		/* GSM 12.21 Test Report */
+	S_NM_STATECHG_OPER,	/* Operational State changed*/
+	S_NM_STATECHG_ADM,	/* Administrative State changed */
+	S_NM_OM2K_CONF_RES,	/* OM2K Configuration Result */
+};
+
+/* SS_LCHAN signals */
+enum signal_lchan {
+	/*
+	 * The lchan got freed with an use_count != 0 and error
+	 * recovery needs to be carried out from within the
+	 * signal handler.
+	 */
+	S_LCHAN_UNEXPECTED_RELEASE,
+	S_LCHAN_ACTIVATE_ACK,		/* 08.58 Channel Activate ACK */
+	S_LCHAN_ACTIVATE_NACK,		/* 08.58 Channel Activate NACK */
+	S_LCHAN_HANDOVER_COMPL,		/* 04.08 Handover Completed */
+	S_LCHAN_HANDOVER_FAIL,		/* 04.08 Handover Failed */
+	S_LCHAN_HANDOVER_DETECT,	/* 08.58 Handover Detect */
+	S_LCHAN_MEAS_REP,		/* 08.58 Measurement Report */
+};
+
+/* SS_CHALLOC signals */
+enum signal_challoc {
+	S_CHALLOC_ALLOC_FAIL,	/* allocation of lchan has failed */
+	S_CHALLOC_FREED,	/* lchan has been successfully freed */
+};
+
+/* SS_SUBSCR signals */
+enum signal_subscr {
+	S_SUBSCR_ATTACHED,
+	S_SUBSCR_DETACHED,
+	S_SUBSCR_IDENTITY,		/* we've received some identity information */
+};
+
+/* SS_SCALL signals */
+enum signal_scall {
+	S_SCALL_SUCCESS,
+	S_SCALL_EXPIRED,
+	S_SCALL_DETACHED,
+};
+
+/* SS_IPAC_NWL signals */
+enum signal_ipaccess {
+	S_IPAC_NWL_COMPLETE,
+};
+
+enum signal_global {
+	S_GLOBAL_BTS_CLOSE_OM,
+};
+
+/* SS_RF signals */
+enum signal_rf {
+	S_RF_OFF,
+	S_RF_ON,
+	S_RF_GRACE,
+};
+
+struct gsm_subscriber;
+
+struct paging_signal_data {
+	struct gsm_subscriber *subscr;
+	struct gsm_bts *bts;
+
+	int paging_result;
+
+	/* NULL in case the paging didn't work */
+	struct gsm_subscriber_connection *conn;
+};
+
+struct scall_signal_data {
+	struct gsm_subscriber_connection *conn;
+	void *data;
+};
+
+struct ipacc_ack_signal_data {
+	struct gsm_bts_trx *trx;
+	uint8_t msg_type;
+};
+
+struct abis_om2k_mo;
+
+struct nm_statechg_signal_data {
+	struct gsm_bts *bts;
+	uint8_t obj_class;
+	void *obj;
+	struct gsm_nm_state *old_state;
+	struct gsm_nm_state *new_state;
+
+	/* This pointer is vaold for TS 12.21 MO */
+	struct abis_om_obj_inst *obj_inst;
+	/* This pointer is vaold for RBS2000 MO */
+	struct abis_om2k_mo *om2k_mo;
+};
+
+struct nm_om2k_signal_data {
+	struct gsm_bts *bts;
+	void *obj;
+	struct abis_om2k_mo *om2k_mo;
+
+	uint8_t accordance_ind;
+};
+
+struct nm_nack_signal_data {
+	struct msgb *msg;
+	struct gsm_bts *bts;
+	uint8_t mt;
+};
+
+struct challoc_signal_data {
+	struct gsm_bts *bts;
+	struct gsm_lchan *lchan;
+	enum gsm_chan_t type;
+};
+
+struct rf_signal_data {
+	struct gsm_network *net;
+};
+
+struct sms_signal_data {
+	/* The transaction where this occured */
+	struct gsm_trans *trans;
+	/* Can be NULL for SMMA */
+	struct gsm_sms *sms;
+	/* int paging result. Only the ones with > 0 */
+	int paging_result;
+};
+
+struct lchan_signal_data {
+	/* The lchan the signal happened on */
+	struct gsm_lchan *lchan;
+	/* Measurement reports on this lchan */
+	struct gsm_meas_rep *mr;
+};
+
+/* MSC signals */
+enum signal_msc {
+	S_MSC_LOST,
+	S_MSC_CONNECTED,
+	S_MSC_AUTHENTICATED,
+};
+
+struct bsc_msc_data;
+struct msc_signal_data {
+	struct bsc_msc_data *data;
+};
+
+/* SS_CCCH signals */
+enum signal_ccch {
+	S_CCCH_PAGING_LOAD,
+	S_CCCH_RACH_LOAD,
+};
+
+struct ccch_signal_data {
+	struct gsm_bts *bts;
+	uint16_t pg_buf_space;
+	uint16_t rach_slot_count;
+	uint16_t rach_busy_count;
+	uint16_t rach_access_count;
+};
+
+#endif
diff --git a/openbsc/include/openbsc/silent_call.h b/openbsc/include/openbsc/silent_call.h
new file mode 100644
index 0000000..619a543
--- /dev/null
+++ b/openbsc/include/openbsc/silent_call.h
@@ -0,0 +1,15 @@
+#ifndef _SILENT_CALL_H
+#define _SILENT_CALL_H
+
+struct gsm_subscriber_connection;
+
+extern int gsm_silent_call_start(struct gsm_subscriber *subscr,
+                                 void *data, int type);
+extern int gsm_silent_call_stop(struct gsm_subscriber *subscr);
+
+#if 0
+extern int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg);
+extern int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg);
+#endif
+
+#endif /* _SILENT_CALL_H */
diff --git a/openbsc/include/openbsc/smpp.h b/openbsc/include/openbsc/smpp.h
new file mode 100644
index 0000000..bcdac8f
--- /dev/null
+++ b/openbsc/include/openbsc/smpp.h
@@ -0,0 +1,4 @@
+#pragma once
+
+int smpp_openbsc_alloc_init(void *ctx);
+int smpp_openbsc_start(struct gsm_network *net);
diff --git a/openbsc/include/openbsc/sms_queue.h b/openbsc/include/openbsc/sms_queue.h
new file mode 100644
index 0000000..2a8bd58
--- /dev/null
+++ b/openbsc/include/openbsc/sms_queue.h
@@ -0,0 +1,17 @@
+#ifndef SMS_QUEUE_H
+#define SMS_QUEUE_H
+
+struct gsm_network;
+struct gsm_sms_queue;
+struct vty;
+
+int sms_queue_start(struct gsm_network *, int in_flight);
+int sms_queue_trigger(struct gsm_sms_queue *);
+
+/* vty helper functions */
+int sms_queue_stats(struct gsm_sms_queue *, struct vty* vty);
+int sms_queue_set_max_pending(struct gsm_sms_queue *, int max);
+int sms_queue_set_max_failure(struct gsm_sms_queue *, int fail);
+int sms_queue_clear(struct gsm_sms_queue *);
+
+#endif
diff --git a/openbsc/include/openbsc/socket.h b/openbsc/include/openbsc/socket.h
new file mode 100644
index 0000000..0fd85f1
--- /dev/null
+++ b/openbsc/include/openbsc/socket.h
@@ -0,0 +1,14 @@
+#ifndef _BSC_SOCKET_H
+#define _BSC_SOCKET_H
+
+#include <osmocom/core/select.h>
+
+#ifndef IPPROTO_GRE
+#define IPPROTO_GRE 47
+#endif
+
+int make_sock(struct osmo_fd *bfd, int proto,
+	      uint32_t ip, uint16_t port, int priv_nr,
+	      int (*cb)(struct osmo_fd *fd, unsigned int what), void *data);
+
+#endif /* _BSC_SOCKET_H */
diff --git a/openbsc/include/openbsc/system_information.h b/openbsc/include/openbsc/system_information.h
new file mode 100644
index 0000000..71bea26
--- /dev/null
+++ b/openbsc/include/openbsc/system_information.h
@@ -0,0 +1,22 @@
+#ifndef _SYSTEM_INFO_H
+#define _SYSTEM_INFO_H
+
+#include <osmocom/gsm/sysinfo.h>
+
+#include <openbsc/arfcn_range_encode.h>
+
+struct gsm_bts;
+
+int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type type);
+size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e);
+unsigned range1024_p(unsigned n);
+unsigned range512_q(unsigned m);
+int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
+		 int f0, uint8_t *chan_list);
+uint8_t si2q_num(struct gsm_bts *bts);
+int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint8_t thresh_lo, uint8_t prio,
+		   uint8_t qrx, uint8_t meas_bw);
+int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble);
+int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble,
+		   bool diversity);
+#endif
diff --git a/openbsc/include/openbsc/token_auth.h b/openbsc/include/openbsc/token_auth.h
new file mode 100644
index 0000000..47dc7aa
--- /dev/null
+++ b/openbsc/include/openbsc/token_auth.h
@@ -0,0 +1,7 @@
+#ifndef _TOKEN_AUTH_H
+#define _TOKEN_AUTH_H
+
+void on_dso_load_token(void);
+
+#endif /* _TOKEN_AUTH_H */
+
diff --git a/openbsc/include/openbsc/transaction.h b/openbsc/include/openbsc/transaction.h
new file mode 100644
index 0000000..9a87d04
--- /dev/null
+++ b/openbsc/include/openbsc/transaction.h
@@ -0,0 +1,79 @@
+#ifndef _TRANSACT_H
+#define _TRANSACT_H
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/mncc.h>
+#include <osmocom/gsm/gsm0411_smc.h>
+#include <osmocom/gsm/gsm0411_smr.h>
+
+/* One transaction */
+struct gsm_trans {
+	/* Entry in list of all transactions */
+	struct llist_head entry;
+
+	/* Back pointer to the network struct */
+	struct gsm_network *net;
+
+	/* The protocol within which we live */
+	uint8_t protocol;
+
+	/* The current transaction ID */
+	uint8_t transaction_id;
+
+	/* To whom we belong, unique identifier of remote MM entity */
+	struct gsm_subscriber *subscr;
+
+	/* The associated connection we are using to transmit messages */
+	struct gsm_subscriber_connection *conn;
+
+	/* reference from MNCC or other application */
+	uint32_t callref;
+
+	/* if traffic channel receive was requested */
+	int tch_recv;
+
+	/* is thats one paging? */
+	struct subscr_request *paging_request;
+
+	union {
+		struct {
+
+			/* current call state */
+			int state;
+
+			/* current timer and message queue */
+			int Tcurrent;		/* current CC timer */
+			int T308_second;	/* used to send release again */
+			struct osmo_timer_list timer;
+			struct gsm_mncc msg;	/* stores setup/disconnect/release message */
+		} cc;
+		struct {
+			struct gsm411_smc_inst smc_inst;
+			struct gsm411_smr_inst smr_inst;
+
+			struct gsm_sms *sms;
+		} sms;
+	};
+};
+
+
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn,
+				   uint8_t proto, uint8_t trans_id);
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					uint32_t callref);
+
+struct gsm_trans *trans_alloc(struct gsm_network *net,
+			      struct gsm_subscriber *subscr,
+			      uint8_t protocol, uint8_t trans_id,
+			      uint32_t callref);
+void trans_free(struct gsm_trans *trans);
+
+int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr,
+			  uint8_t protocol, uint8_t ti_flag);
+int trans_has_conn(const struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/openbsc/include/openbsc/trau_mux.h b/openbsc/include/openbsc/trau_mux.h
new file mode 100644
index 0000000..75c359b
--- /dev/null
+++ b/openbsc/include/openbsc/trau_mux.h
@@ -0,0 +1,70 @@
+/* Simple TRAU frame reflector to route voice calls */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* The "TRAU mux map" defines which particular 16kbit sub-slot (in which E1
+ * timeslot on which E1 interface) should be directly muxed to which other 
+ * sub-slot.  Entries in the mux map are always bi-directional. 
+ *
+ * The idea of all this is to directly switch voice channels in the BSC
+ * from one phone to another.  We do this right now since we don't support
+ * any external interface for voice channels, and in the future as an
+ * optimization to routing them externally.
+ */
+
+#include <stdint.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mncc.h>
+
+struct decoded_trau_frame;
+
+/* map a TRAU mux map entry */
+int trau_mux_map(const struct gsm_e1_subslot *src,
+		 const struct gsm_e1_subslot *dst);
+int trau_mux_map_lchan(const struct gsm_lchan *src,	
+			const struct gsm_lchan *dst);
+
+/* unmap a TRAU mux map entry */
+int trau_mux_unmap(const struct gsm_e1_subslot *ss, uint32_t callref);
+
+/* we get called by subchan_demux */
+int trau_mux_input(struct gsm_e1_subslot *src_e1_ss,
+		   const uint8_t *trau_bits, int num_bits);
+
+/* add a trau receiver */
+int trau_recv_lchan(struct gsm_lchan *lchan, uint32_t callref);
+
+/* send trau from application */
+int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame);
+
+/* switch trau muxer to new lchan */
+int switch_trau_mux(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan);
+
+/* callback invoked if we receive TRAU frames */
+int subch_cb(struct subch_demux *dmx, int ch, uint8_t *data, int len, void *_priv);
+
+/* TRAU frame transcoding */
+struct msgb *trau_decode_fr(uint32_t callref,
+	const struct decoded_trau_frame *tf);
+struct msgb *trau_decode_efr(uint32_t callref,
+	const struct decoded_trau_frame *tf);
+void trau_encode_fr(struct decoded_trau_frame *tf,
+	const unsigned char *data);
+void trau_encode_efr(struct decoded_trau_frame *tf,
+	const unsigned char *data);
diff --git a/openbsc/include/openbsc/trau_upqueue.h b/openbsc/include/openbsc/trau_upqueue.h
new file mode 100644
index 0000000..ecc7658
--- /dev/null
+++ b/openbsc/include/openbsc/trau_upqueue.h
@@ -0,0 +1,7 @@
+#ifndef _TRAU_UPQUEUE_H
+#define _TRAU_UPQUEUE_H
+
+void trau_tx_to_mncc(struct gsm_network *net, struct msgb *msg);
+
+#endif /* _TRAU_UPQUEUE_H */
+
diff --git a/openbsc/include/openbsc/ussd.h b/openbsc/include/openbsc/ussd.h
new file mode 100644
index 0000000..2665468
--- /dev/null
+++ b/openbsc/include/openbsc/ussd.h
@@ -0,0 +1,10 @@
+#ifndef _USSD_H
+#define _USSD_H
+
+/* Handler function for mobile-originated USSD messages */
+
+#include <osmocom/core/msgb.h>
+
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+#endif
diff --git a/openbsc/include/openbsc/vty.h b/openbsc/include/openbsc/vty.h
new file mode 100644
index 0000000..60b7d2d
--- /dev/null
+++ b/openbsc/include/openbsc/vty.h
@@ -0,0 +1,51 @@
+#ifndef OPENBSC_VTY_H
+#define OPENBSC_VTY_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/command.h>
+
+struct gsm_network;
+struct vty;
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *);
+
+struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base);
+
+extern struct cmd_element cfg_description_cmd;
+extern struct cmd_element cfg_no_description_cmd;
+
+enum bsc_vty_node {
+	GSMNET_NODE = _LAST_OSMOVTY_NODE + 1,
+	BTS_NODE,
+	TRX_NODE,
+	TS_NODE,
+	SUBSCR_NODE,
+	MGCP_NODE,
+	GBPROXY_NODE,
+	SGSN_NODE,
+	OML_NODE,
+	NAT_NODE,
+	NAT_BSC_NODE,
+	MSC_NODE,
+	OM2K_NODE,
+	OM2K_CON_GROUP_NODE,
+	TRUNK_NODE,
+	PGROUP_NODE,
+	MNCC_INT_NODE,
+	NITB_NODE,
+	BSC_NODE,
+	SMPP_NODE,
+	SMPP_ESME_NODE,
+	GTPHUB_NODE,
+};
+
+extern int bsc_vty_is_config_node(struct vty *vty, int node);
+
+struct log_info;
+int bsc_vty_init(struct gsm_network *network);
+int bsc_vty_init_extra(void);
+
+struct gsm_network *gsmnet_from_vty(struct vty *vty);
+
+#endif
diff --git a/openbsc/m4/README b/openbsc/m4/README
new file mode 100644
index 0000000..92eb30b
--- /dev/null
+++ b/openbsc/m4/README
@@ -0,0 +1,3 @@
+We want to avoid creating too many external build-time dependencies
+like this one to autoconf-archive.  This directory provides a local
+copy of required m4 rules.
diff --git a/openbsc/m4/ax_check_compile_flag.m4 b/openbsc/m4/ax_check_compile_flag.m4
new file mode 100644
index 0000000..ca36397
--- /dev/null
+++ b/openbsc/m4/ax_check_compile_flag.m4
@@ -0,0 +1,74 @@
+# ===========================================================================
+#   http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+#   Check whether the given FLAG works with the current language's compiler
+#   or gives an error.  (Warnings, however, are ignored)
+#
+#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+#   success/failure.
+#
+#   If EXTRA-FLAGS is defined, it is added to the current language's default
+#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with
+#   the flags: "CFLAGS EXTRA-FLAGS FLAG".  This can for example be used to
+#   force the compiler to issue an error when a bad flag is given.
+#
+#   INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+#   Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+#   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, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 4
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+  _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+  AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+    [AS_VAR_SET(CACHEVAR,[yes])],
+    [AS_VAR_SET(CACHEVAR,[no])])
+  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+  [m4_default([$2], :)],
+  [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/openbsc/openbsc.pc.in b/openbsc/openbsc.pc.in
new file mode 100644
index 0000000..17e265d
--- /dev/null
+++ b/openbsc/openbsc.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/
+
+Name: OpenBSC
+Description: OpenBSC base station controller
+Requires:
+Version: @VERSION@
+Cflags: -I${includedir}
diff --git a/openbsc/osmoappdesc.py b/openbsc/osmoappdesc.py
new file mode 100644
index 0000000..5b01e4c
--- /dev/null
+++ b/openbsc/osmoappdesc.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# 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, see <http://www.gnu.org/licenses/>
+
+
+# Most systems won't be able to use these, so they're separated out
+nitb_e1_configs = [
+    "doc/examples/osmo-nitb/bs11/openbsc-2bts-2trx.cfg",
+    "doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx-hopping.cfg",
+    "doc/examples/osmo-nitb/bs11/openbsc-1bts-2trx.cfg",
+    "doc/examples/osmo-nitb/bs11/osmo-nitb.cfg",
+    "doc/examples/osmo-nitb/nokia/openbsc_nokia_3trx.cfg",
+    "doc/examples/osmo-nitb/nanobts/openbsc-multitrx.cfg",
+    "doc/examples/osmo-nitb/rbs2308/osmo-nitb.cfg"
+]
+
+
+app_configs = {
+    "osmo-bsc-sccplite": ["doc/examples/osmo-bsc-sccplite/osmo-bsc-sccplite.cfg"],
+    "nat": ["doc/examples/osmo-bsc_nat/osmo-bsc-nat.cfg"],
+    "mgcp": ["doc/examples/osmo-bsc_mgcp/osmo-bsc-mgcp.cfg"],
+    "nitb": ["doc/examples/osmo-nitb/nanobts/openbsc-multitrx.cfg",
+             "doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg"],
+}
+
+
+apps = [(4242, "src/osmo-bsc/osmo-bsc-sccplite", "OsmoBSC", "osmo-bsc-sccplite"),
+        (4244, "src/osmo-bsc_nat/osmo-bsc_nat",  "OsmoBSCNAT", "nat"),
+        (4243, "src/osmo-bsc_mgcp/osmo-bsc_mgcp", "OpenBSC MGCP", "mgcp"),
+        (4242, "src/osmo-nitb/osmo-nitb", "OpenBSC", "nitb"),
+        ]
+
+vty_command = ["./src/osmo-nitb/osmo-nitb", "-c",
+               "doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg"]
+
+vty_app = apps[3] # reference apps[] entry for osmo-nitb
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
new file mode 100644
index 0000000..e579ea0
--- /dev/null
+++ b/openbsc/src/Makefile.am
@@ -0,0 +1,59 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+# Libraries
+SUBDIRS = \
+	libcommon \
+	libmgcp \
+	libbsc \
+	libmsc \
+	libtrau \
+	libfilter \
+	libcommon-cs \
+	$(NULL)
+
+# Conditional Libraries
+if BUILD_IU
+SUBDIRS += \
+	libiu \
+	$(NULL)
+endif
+
+# Programs
+SUBDIRS += \
+	osmo-nitb \
+	osmo-bsc_mgcp \
+	utils \
+	ipaccess \
+	$(NULL)
+
+# Conditional Programs
+if BUILD_NAT
+SUBDIRS += \
+	osmo-bsc_nat \
+	$(NULL)
+endif
+
+if BUILD_BSC
+SUBDIRS += \
+	osmo-bsc \
+	$(NULL)
+endif
diff --git a/openbsc/src/ipaccess/Makefile.am b/openbsc/src/ipaccess/Makefile.am
new file mode 100644
index 0000000..4dfe247
--- /dev/null
+++ b/openbsc/src/ipaccess/Makefile.am
@@ -0,0 +1,66 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+OSMO_LIBS = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(NULL)
+
+bin_PROGRAMS = \
+	abisip-find \
+	ipaccess-config \
+	ipaccess-proxy \
+	$(NULL)
+
+abisip_find_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(OSMO_LIBS) \
+	$(NULL)
+
+abisip_find_SOURCES = \
+	abisip-find.c \
+	$(NULL)
+
+ipaccess_config_SOURCES = \
+	ipaccess-config.c \
+	ipaccess-firmware.c \
+	network_listen.c \
+	$(NULL)
+
+# FIXME: resolve the bogus dependencies patched around here:
+ipaccess_config_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(OSMO_LIBS) \
+	$(NULL)
+
+ipaccess_proxy_SOURCES = \
+	ipaccess-proxy.c \
+	$(NULL)
+
+ipaccess_proxy_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(OSMO_LIBS) \
+	$(NULL)
diff --git a/openbsc/src/ipaccess/abisip-find.c b/openbsc/src/ipaccess/abisip-find.c
new file mode 100644
index 0000000..21d9f22
--- /dev/null
+++ b/openbsc/src/ipaccess/abisip-find.c
@@ -0,0 +1,216 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/ipa.h>
+#include <openbsc/gsm_data.h>
+
+static int udp_sock(const char *ifname)
+{
+	int fd, rc, bc = 1;
+	struct sockaddr_in sa;
+
+	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (fd < 0)
+		return fd;
+
+	if (ifname) {
+#ifdef __FreeBSD__
+		rc = setsockopt(fd, SOL_SOCKET, IP_RECVIF, ifname,
+				strlen(ifname));
+#else
+		rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
+				strlen(ifname));
+#endif
+		if (rc < 0)
+			goto err;
+	}
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	sa.sin_addr.s_addr = INADDR_ANY;
+
+	rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+	if (rc < 0)
+		goto err;
+
+	rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
+	if (rc < 0)
+		goto err;
+
+#if 0
+	/* we cannot bind, since the response packets don't come from
+	 * the broadcast address */
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	inet_aton("255.255.255.255", &sa.sin_addr);
+
+	rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
+	if (rc < 0)
+		goto err;
+#endif
+	return fd;
+
+err:
+	close(fd);
+	return rc;
+}
+
+const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00,
+				IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_IPADDR,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+
+static int bcast_find(int fd)
+{
+	struct sockaddr_in sa;
+
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	inet_aton("255.255.255.255", &sa.sin_addr);
+
+	return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa));
+}
+
+static int parse_response(unsigned char *buf, int len)
+{
+	uint8_t t_len;
+	uint8_t t_tag;
+	uint8_t *cur = buf;
+
+	while (cur < buf + len) {
+		t_len = *cur++;
+		t_tag = *cur++;
+		
+		printf("%s='%s'  ", ipa_ccm_idtag_name(t_tag), cur);
+
+		cur += t_len;
+	}
+	printf("\n");
+	return 0;
+}
+
+static int read_response(int fd)
+{
+	unsigned char buf[255];
+	struct sockaddr_in sa;
+	int len;
+	socklen_t sa_len = sizeof(sa);
+
+	len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len);
+	if (len < 0)
+		return len;
+
+	/* 2 bytes length, 1 byte protocol */
+	if (buf[2] != IPAC_PROTO_IPACCESS)
+		return 0;
+
+	if (buf[4] != IPAC_MSGT_ID_RESP)
+		return 0;
+
+	return parse_response(buf+6, len-6);
+}
+
+static int bfd_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+	if (flags & BSC_FD_READ)
+		return read_response(bfd->fd);
+	if (flags & BSC_FD_WRITE) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return bcast_find(bfd->fd);
+	}
+	return 0;
+}
+
+static struct osmo_timer_list timer;
+
+static void timer_cb(void *_data)
+{
+	struct osmo_fd *bfd = _data;
+
+	bfd->when |= BSC_FD_WRITE;
+
+	osmo_timer_schedule(&timer, 5, 0);
+}
+
+int main(int argc, char **argv)
+{
+	struct osmo_fd bfd;
+	char *ifname = NULL;
+	int rc;
+
+	printf("abisip-find (C) 2009 by Harald Welte\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	if (argc < 2) {
+		fprintf(stdout, "you might need to specify the outgoing\n"
+			" network interface, e.g. ``%s eth0''\n", argv[0]);
+	} else {
+		ifname = argv[1];
+	}
+
+	bfd.cb = bfd_cb;
+	bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd.fd = udp_sock(ifname);
+	if (bfd.fd < 0) {
+		perror("Cannot create local socket for broadcast udp");
+		exit(1);
+	}
+
+	rc = osmo_fd_register(&bfd);
+	if (rc < 0) {
+		fprintf(stderr, "Cannot register FD\n");
+		exit(1);
+	}
+
+	osmo_timer_setup(&timer, timer_cb, &bfd);
+	osmo_timer_schedule(&timer, 5, 0);
+
+	printf("Trying to find ip.access BTS by broadcast UDP...\n");
+
+	while (1) {
+		rc = osmo_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
diff --git a/openbsc/src/ipaccess/ipaccess-config.c b/openbsc/src/ipaccess/ipaccess-config.c
new file mode 100644
index 0000000..6822c06
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-config.c
@@ -0,0 +1,1019 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/common_bsc.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/network_listen.h>
+#include <osmocom/abis/ipaccess.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/network_listen.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/abis/abis.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+struct gsm_network *bsc_gsmnet;
+
+static int net_listen_testnr;
+static int restart;
+static char *prim_oml_ip;
+static char *bts_ip_addr, *bts_ip_mask, *bts_ip_gw;
+static char *unit_id;
+static uint16_t nv_flags;
+static uint16_t nv_mask;
+static char *software = NULL;
+static int sw_load_state = 0;
+static int oml_state = 0;
+static int dump_files = 0;
+static char *firmware_analysis = NULL;
+static int found_trx = 0;
+static int loop_tests = 0;
+
+static void *tall_ctx_config = NULL;
+static struct abis_nm_sw_desc *sw_load1 = NULL;
+static struct abis_nm_sw_desc *sw_load2 = NULL;
+
+/*
+static uint8_t prim_oml_attr[] = { 0x95, 0x00, 7, 0x88, 192, 168, 100, 11, 0x00, 0x00 };
+static uint8_t unit_id_attr[] = { 0x91, 0x00, 9, '2', '3', '4', '2', '/' , '0', '/', '0', 0x00 };
+*/
+
+extern int ipaccess_fd_cb(struct osmo_fd *bfd, unsigned int what);
+extern struct e1inp_line_ops ipaccess_e1inp_line_ops;
+
+/* Actively connect to a BTS.  Currently used by ipaccess-config.c */
+static int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
+{
+	struct e1inp_ts *e1i_ts = &line->ts[0];
+	struct osmo_fd *bfd = &e1i_ts->driver.ipaccess.fd;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd->data = line;
+	bfd->priv_nr = E1INP_SIGN_OML;
+
+	if (bfd->fd < 0) {
+		LOGP(DLINP, LOGL_ERROR, "could not create TCP socket.\n");
+		return -EIO;
+	}
+
+	ret = setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "could not set socket option\n");
+		close(bfd->fd);
+		return -EIO;
+	}
+
+	ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "could not connect socket\n");
+		close(bfd->fd);
+		return ret;
+	}
+
+	ret = osmo_fd_register(bfd);
+	if (ret < 0) {
+		close(bfd->fd);
+		return ret;
+	}
+	return ret;
+	//return e1inp_line_register(line);
+}
+
+/* configure pseudo E1 line in ip.access style and connect to BTS */
+static int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts, *rsl_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return -ENOMEM;
+
+	line->driver = e1inp_driver_find("ipa");
+	if (!line->driver) {
+		fprintf(stderr, "cannot `ipa' driver, giving up.\n");
+		return -EINVAL;
+	}
+	line->ops = &ipaccess_e1inp_line_ops;
+
+	/* create E1 timeslots for signalling and TRAU frames */
+	e1inp_ts_config_sign(&line->ts[1-1], line);
+	e1inp_ts_config_sign(&line->ts[2-1], line);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[1-1];
+	rsl_ts = &line->ts[2-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, 0xff, 0);
+	rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL,
+					  bts->c0, 0, 0);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	bts->c0->rsl_link = rsl_link;
+
+	/* default port at BTS for incoming connections is 3006 */
+	if (sin->sin_port == 0)
+		sin->sin_port = htons(3006);
+
+	return ipaccess_connect(line, sin);
+}
+
+/*
+ * Callback function for NACK on the OML NM
+ *
+ * Currently we send the config requests but don't check the
+ * result. The nanoBTS will send us a NACK when we did something the
+ * BTS didn't like.
+ */
+static int ipacc_msg_nack(uint8_t mt)
+{
+	fprintf(stderr, "Failure to set attribute. This seems fatal\n");
+	exit(-1);
+	return 0;
+}
+
+static void check_restart_or_exit(struct gsm_bts_trx *trx)
+{
+	if (restart) {
+		abis_nm_ipaccess_restart(trx);
+	} else {
+		exit(0);
+	}
+}
+
+static int ipacc_msg_ack(uint8_t mt, struct gsm_bts_trx *trx)
+{
+	if (sw_load_state == 1) {
+		fprintf(stderr, "The new software is activaed.\n");
+		check_restart_or_exit(trx);
+	} else if (oml_state == 1) {
+		fprintf(stderr, "Set the NV Attributes.\n");
+		check_restart_or_exit(trx);
+	}
+
+	return 0;
+}
+
+static const uint8_t phys_conf_min[] = { 0x02 };
+
+static uint16_t build_physconf(uint8_t *physconf_buf, const struct rxlev_stats *st)
+{
+	uint16_t *whitelist = (uint16_t *) (physconf_buf + 4);
+	int num_arfcn;
+	unsigned int arfcnlist_size;
+
+	/* Create whitelist from rxlevels */
+	physconf_buf[0] = phys_conf_min[0];
+	physconf_buf[1] = NM_IPAC_EIE_ARFCN_WHITE;
+	num_arfcn = ipac_rxlevstat2whitelist(whitelist, st, 0, 100);
+	arfcnlist_size = num_arfcn * 2;
+	*((uint16_t *) (physconf_buf+2)) = htons(arfcnlist_size);
+	DEBUGP(DNM, "physconf_buf (%s)\n", osmo_hexdump(physconf_buf, arfcnlist_size+4));
+	return arfcnlist_size+4;
+}
+
+static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts_trx *trx;
+	uint8_t physconf_buf[2*NUM_ARFCNS+16];
+	uint16_t physconf_len;
+
+	switch (signal) {
+	case S_IPAC_NWL_COMPLETE:
+		trx = signal_data;
+		DEBUGP(DNM, "received S_IPAC_NWL_COMPLETE signal\n");
+		switch (trx->ipaccess.test_nr) {
+		case NM_IPACC_TESTNO_CHAN_USAGE:
+			/* Dump RxLev results */
+			//rxlev_stat_dump(&trx->ipaccess.rxlev_stat);
+			/* Create whitelist from results */
+			physconf_len = build_physconf(physconf_buf,
+						      &trx->ipaccess.rxlev_stat);
+			/* Start next test abbout BCCH channel usage */
+			ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_CHAN_USAGE,
+					    physconf_buf, physconf_len);
+			break;
+		case NM_IPACC_TESTNO_BCCH_CHAN_USAGE:
+			/* Dump BCCH RxLev results */
+			//rxlev_stat_dump(&trx->ipaccess.rxlev_stat);
+			/* Create whitelist from results */
+			physconf_len = build_physconf(physconf_buf,
+						      &trx->ipaccess.rxlev_stat);
+			/* Start next test about BCCH info */
+			ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_INFO,
+					    physconf_buf, physconf_len);
+			break;
+		case NM_IPACC_TESTNO_BCCH_INFO:
+			/* re-start full process with CHAN_USAGE */
+			if (loop_tests) {
+				DEBUGP(DNM, "starting next test cycle\n");
+				ipac_nwl_test_start(trx, net_listen_testnr, phys_conf_min,
+						    sizeof(phys_conf_min));
+			} else {
+				exit(0);
+			}
+			break;
+		}
+		break;
+	}
+	return 0;
+}
+
+static int nm_state_event(int evt, uint8_t obj_class, void *obj,
+			  struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+			  struct abis_om_obj_inst *obj_inst);
+
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	struct ipacc_ack_signal_data *ipacc_data;
+	struct nm_statechg_signal_data *nsd;
+
+	switch (signal) {
+	case S_NM_IPACC_NACK:
+		ipacc_data = signal_data;
+		return ipacc_msg_nack(ipacc_data->msg_type);
+	case S_NM_IPACC_ACK:
+		ipacc_data = signal_data;
+		return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->trx);
+	case S_NM_IPACC_RESTART_ACK:
+		printf("The BTS has acked the restart. Exiting.\n");
+		exit(0);
+		break;
+	case S_NM_IPACC_RESTART_NACK:
+		printf("The BTS has nacked the restart. Exiting.\n");
+		exit(0);
+		break;
+	case S_NM_STATECHG_OPER:
+	case S_NM_STATECHG_ADM:
+		nsd = signal_data;
+		nm_state_event(signal, nsd->obj_class, nsd->obj, nsd->old_state,
+				nsd->new_state, nsd->obj_inst);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* callback function passed to the ABIS OML code */
+static int percent;
+static int percent_old;
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *_msg,
+		       void *data, void *param)
+{
+	struct msgb *msg;
+	struct gsm_bts_trx *trx;
+
+	if (hook != GSM_HOOK_NM_SWLOAD)
+		return 0;
+
+	trx = (struct gsm_bts_trx *) data;
+
+	switch (event) {
+	case NM_MT_LOAD_INIT_ACK:
+		fprintf(stdout, "Software Load Initiate ACK\n");
+		break;
+	case NM_MT_LOAD_INIT_NACK:
+		fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+		exit(5);
+		break;
+	case NM_MT_LOAD_END_ACK:
+		fprintf(stderr, "LOAD END ACK...");
+		/* now make it the default */
+		sw_load_state = 1;
+
+		msg = msgb_alloc(1024, "sw: nvattr");
+		msg->l2h = msgb_put(msg, 3);
+		msg->l3h = &msg->l2h[3];
+
+		/* activate software */
+		if (sw_load1)
+			abis_nm_put_sw_desc(msg, sw_load1, true);
+
+		if (sw_load2)
+			abis_nm_put_sw_desc(msg, sw_load2, true);
+
+		/* fill in the data */
+		msg->l2h[0] = NM_ATT_IPACC_CUR_SW_CFG;
+		msg->l2h[1] = msgb_l3len(msg) >> 8;
+		msg->l2h[2] = msgb_l3len(msg) & 0xff;
+		printf("Foo l2h: %p l3h: %p... length l2: %u  l3: %u\n", msg->l2h, msg->l3h, msgb_l2len(msg), msgb_l3len(msg));
+		abis_nm_ipaccess_set_nvattr(trx, msg->l2h, msgb_l2len(msg));
+		msgb_free(msg);
+		break;
+	case NM_MT_LOAD_END_NACK:
+		fprintf(stderr, "ERROR: Software Load End NACK\n");
+		exit(3);
+		break;
+	case NM_MT_ACTIVATE_SW_NACK:
+		fprintf(stderr, "ERROR: Activate Software NACK\n");
+		exit(4);
+		break;
+	case NM_MT_ACTIVATE_SW_ACK:
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+		percent = abis_nm_software_load_status(trx->bts);
+		if (percent > percent_old)
+			printf("Software Download Progress: %d%%\n", percent);
+		percent_old = percent;
+		break;
+	case NM_MT_LOAD_ABORT:
+		fprintf(stderr, "ERROR: Load aborted by the BTS.\n");
+		exit(6);
+		break;
+	}
+	return 0;
+}
+
+static void nv_put_ip_if_cfg(struct msgb *nmsg, uint32_t ip, uint32_t mask)
+{
+	msgb_put_u8(nmsg, NM_ATT_IPACC_IP_IF_CFG);
+
+	msgb_put_u32(nmsg, ip);
+	msgb_put_u32(nmsg, mask);
+}
+
+static void nv_put_gw_cfg(struct msgb *nmsg, uint32_t addr, uint32_t mask, uint32_t gw)
+{
+	msgb_put_u8(nmsg, NM_ATT_IPACC_IP_GW_CFG);
+	msgb_put_u32(nmsg, addr);
+	msgb_put_u32(nmsg, mask);
+	msgb_put_u32(nmsg, gw);
+}
+
+static void nv_put_unit_id(struct msgb *nmsg, const char *unit_id)
+{
+	msgb_tl16v_put(nmsg, NM_ATT_IPACC_UNIT_ID, strlen(unit_id)+1,
+			(const uint8_t *)unit_id);
+}
+
+static void nv_put_prim_oml(struct msgb *nmsg, uint32_t ip, uint16_t port)
+{
+	int len;
+
+	/* 0x88 + IP + port */
+	len = 1 + sizeof(ip) + sizeof(port);
+
+	msgb_put_u8(nmsg, NM_ATT_IPACC_PRIM_OML_CFG_LIST);
+	msgb_put_u16(nmsg, len);
+
+	msgb_put_u8(nmsg, 0x88);
+
+	/* IP address */
+	msgb_put_u32(nmsg, ip);
+
+	/* port number */
+	msgb_put_u16(nmsg, port);
+}
+
+static void nv_put_flags(struct msgb *nmsg, uint16_t nv_flags, uint16_t nv_mask)
+{
+	msgb_put_u8(nmsg, NM_ATT_IPACC_NV_FLAGS);
+	msgb_put_u16(nmsg, sizeof(nv_flags) + sizeof(nv_mask));
+	msgb_put_u8(nmsg, nv_flags & 0xff);
+	msgb_put_u8(nmsg, nv_mask & 0xff);
+	msgb_put_u8(nmsg, nv_flags >> 8);
+	msgb_put_u8(nmsg, nv_mask >> 8);
+}
+
+/* human-readable test names for the ip.access tests */
+static const struct value_string ipa_test_strs[] = {
+	{ 64, "ccch-usage" },
+	{ 65, "bcch-usage" },
+	{ 66, "freq-sync" },
+	{ 67, "rtp-usage" },
+	{ 68, "rtp-perf" },
+	{ 69, "gprs-ccch" },
+	{ 70, "pccch-usage" },
+	{ 71, "gprs-usage" },
+	{ 72, "esta-mf" },
+	{ 73, "uplink-mf" },
+	{ 74, "dolink-mf" },
+	{ 75, "tbf-details" },
+	{ 76, "tbf-usage" },
+	{ 77, "llc-data" },
+	{ 78, "pdch-usage" },
+	{ 79, "power-control" },
+	{ 80, "link-adaption" },
+	{ 81, "tch-usage" },
+	{ 82, "amr-mf" },
+	{ 83, "rtp-multiplex-perf" },
+	{ 84, "rtp-multiplex-usage" },
+	{ 85, "srtp-multiplex-usage" },
+	{ 86, "abis-traffic" },
+	{ 89, "gprs-multiplex-perf" },
+	{ 90, "gprs-multiplex-usage" },
+	{ 0, NULL },
+};
+
+/* human-readable names for the ip.access nanoBTS NVRAM Flags */
+static const struct value_string ipa_nvflag_strs[] = {
+	{ 0x0001, "static-ip" },
+	{ 0x0002, "static-gw" },
+	{ 0x0004, "no-dhcp-vsi" },
+	{ 0x0008, "dhcp-enabled" },
+	{ 0x0040, "led-disabled" },
+	{ 0x0100, "secondary-oml-enabled" },
+	{ 0x0200, "diag-enabled" },
+	{ 0x0400, "cli-enabled" },
+	{ 0x0800, "http-enabled" },
+	{ 0x1000, "post-enabled" },
+	{ 0x2000, "snmp-enabled" },
+	{ 0, NULL }
+};
+
+/* set the flags in flags/mask according to a string-identified flag and 'enable' */
+static int ipa_nvflag_set(uint16_t *flags, uint16_t *mask, const char *name, int en)
+{
+	int rc;
+	rc = get_string_value(ipa_nvflag_strs, name);
+	if (rc < 0)
+		return rc;
+
+	*mask |= rc;
+	if (en)
+		*flags |= rc;
+	else
+		*flags &= ~rc;
+
+	return 0;
+}
+
+static void bootstrap_om(struct gsm_bts_trx *trx)
+{
+	struct msgb *nmsg = msgb_alloc(1024, "nested msgb");
+	int need_to_set_attr = 0;
+	int len;
+
+	printf("OML link established using TRX %d\n", trx->nr);
+
+	if (unit_id) {
+		len = strlen(unit_id);
+		if (len > nmsg->data_len-10)
+			goto out_err;
+		printf("setting Unit ID to '%s'\n", unit_id);
+		nv_put_unit_id(nmsg, unit_id);
+		need_to_set_attr = 1;
+	}
+	if (prim_oml_ip) {
+		struct in_addr ia;
+
+		if (!inet_aton(prim_oml_ip, &ia)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				prim_oml_ip);
+			goto out_err;
+		}
+
+		printf("setting primary OML link IP to '%s'\n", inet_ntoa(ia));
+		nv_put_prim_oml(nmsg, ntohl(ia.s_addr), 0);
+		need_to_set_attr = 1;
+	}
+	if (nv_mask) {
+		printf("setting NV Flags/Mask to 0x%04x/0x%04x\n",
+			nv_flags, nv_mask);
+		nv_put_flags(nmsg, nv_flags, nv_mask);
+		need_to_set_attr = 1;
+	}
+	if (bts_ip_addr && bts_ip_mask) {
+		struct in_addr ia_addr, ia_mask;
+
+		if (!inet_aton(bts_ip_addr, &ia_addr)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				bts_ip_addr);
+			goto out_err;
+		}
+
+		if (!inet_aton(bts_ip_mask, &ia_mask)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				bts_ip_mask);
+			goto out_err;
+		}
+
+		printf("setting static IP Address/Mask\n");
+		nv_put_ip_if_cfg(nmsg, ntohl(ia_addr.s_addr), ntohl(ia_mask.s_addr));
+		need_to_set_attr = 1;
+	}
+	if (bts_ip_gw) {
+		struct in_addr ia_gw;
+
+		if (!inet_aton(bts_ip_gw, &ia_gw)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				bts_ip_gw);
+			goto out_err;
+		}
+
+		printf("setting static IP Gateway\n");
+		/* we only set the default gateway with zero addr/mask */
+		nv_put_gw_cfg(nmsg, 0, 0, ntohl(ia_gw.s_addr));
+		need_to_set_attr = 1;
+	}
+
+	if (need_to_set_attr) {
+		abis_nm_ipaccess_set_nvattr(trx, nmsg->head, nmsg->len);
+		oml_state = 1;
+	}
+
+	if (restart && !prim_oml_ip && !software) {
+		printf("restarting BTS\n");
+		abis_nm_ipaccess_restart(trx);
+	}
+
+out_err:
+	msgb_free(nmsg);
+}
+
+static int nm_state_event(int evt, uint8_t obj_class, void *obj,
+			  struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+			  struct abis_om_obj_inst *obj_inst)
+{
+	if (obj_class == NM_OC_BASEB_TRANSC) {
+		if (!found_trx && obj_inst->trx_nr != 0xff) {
+			struct gsm_bts_trx *trx = container_of(obj, struct gsm_bts_trx, bb_transc);
+			bootstrap_om(trx);
+			found_trx = 1;
+		}
+	} else if (evt == S_NM_STATECHG_OPER &&
+	    obj_class == NM_OC_RADIO_CARRIER &&
+	    new_state->availability == 3) {
+		struct gsm_bts_trx *trx = obj;
+
+		if (net_listen_testnr)
+			ipac_nwl_test_start(trx, net_listen_testnr,
+					    phys_conf_min, sizeof(phys_conf_min));
+		else if (software) {
+			int rc;
+			printf("Attempting software upload with '%s'\n", software);
+			rc = abis_nm_software_load(trx->bts, trx->nr, software, 19, 0, swload_cbfn, trx);
+			if (rc < 0) {
+				fprintf(stderr, "Failed to start software load\n");
+				exit(-3);
+			}
+		}
+	}
+	return 0;
+}
+
+static struct abis_nm_sw_desc *create_swload(struct sdp_header *header)
+{
+	struct abis_nm_sw_desc *load;
+
+	load = talloc_zero(tall_ctx_config, struct abis_nm_sw_desc);
+
+	osmo_strlcpy((char *)load->file_id, header->firmware_info.sw_part,
+		     sizeof(load->file_id));
+	load->file_id_len = strlen((char*)load->file_id) + 1;
+
+	osmo_strlcpy((char *)load->file_version, header->firmware_info.version,
+		     sizeof(load->file_version));
+	load->file_version_len = strlen((char*)load->file_version) + 1;
+
+	return load;
+}
+
+static int find_sw_load_params(const char *filename)
+{
+	struct stat stat;
+	struct sdp_header *header;
+	struct llist_head *entry;
+	int fd;
+	void *tall_firm_ctx = 0;
+
+	entry = talloc_zero(tall_firm_ctx, struct llist_head);
+	INIT_LLIST_HEAD(entry);
+
+	fd = open(filename, O_RDONLY);
+	if (!fd) {
+		perror("nada");
+		return -1;
+	}
+
+	/* verify the file */
+	if (fstat(fd, &stat) == -1) {
+		perror("Can not stat the file");
+		close(fd);
+		return -1;
+	}
+
+	ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+	if (close(fd) != 0) {
+		perror("Close failed.\n");
+		return -1;
+	}
+
+	/* try to find what we are looking for */
+	llist_for_each_entry(header, entry, entry) {
+		if (ntohs(header->firmware_info.more_more_magic) == 0x1000) {
+			sw_load1 = create_swload(header);
+		} else if (ntohs(header->firmware_info.more_more_magic) == 0x2001) {
+			sw_load2 = create_swload(header);
+		}
+	}
+
+	if (!sw_load1 || !sw_load2) {
+		fprintf(stderr, "Did not find data.\n");
+		talloc_free(tall_firm_ctx);
+		return -1;
+        }
+
+	talloc_free(tall_firm_ctx);
+	return 0;
+}
+
+static void dump_entry(struct sdp_header_item *sub_entry, int part, int fd)
+{
+	int out_fd;
+	int copied;
+	char filename[4096];
+	off_t target;
+
+	if (!dump_files)
+		return;
+
+	if (sub_entry->header_entry.something1 == 0)
+		return;
+
+	snprintf(filename, sizeof(filename), "part.%d", part++);
+	out_fd = open(filename, O_WRONLY | O_CREAT, 0660);
+	if (out_fd < 0) {
+		perror("Can not dump firmware");
+		return;
+	}
+
+	target = sub_entry->absolute_offset + ntohl(sub_entry->header_entry.start) + 4;
+	if (lseek(fd, target, SEEK_SET) != target) {
+		perror("seek failed");
+		close(out_fd);
+		return;
+	}
+
+	for (copied = 0; copied < ntohl(sub_entry->header_entry.length); ++copied) {
+		char c;
+		if (read(fd, &c, sizeof(c)) != sizeof(c)) {
+			perror("copy failed");
+			break;
+		}
+
+		if (write(out_fd, &c, sizeof(c)) != sizeof(c)) {
+			perror("write failed");
+			break;
+		}
+	}
+
+	close(out_fd);
+}
+
+static void analyze_firmware(const char *filename)
+{
+	struct stat stat;
+	struct sdp_header *header;
+	struct sdp_header_item *sub_entry;
+	struct llist_head *entry;
+	int fd;
+	void *tall_firm_ctx = 0;
+	int part = 0;
+
+	entry = talloc_zero(tall_firm_ctx, struct llist_head);
+	INIT_LLIST_HEAD(entry);
+
+	printf("Opening possible firmware '%s'\n", filename);
+	fd = open(filename, O_RDONLY);
+	if (!fd) {
+		perror("nada");
+		return;
+	}
+
+	/* verify the file */
+	if (fstat(fd, &stat) == -1) {
+		perror("Can not stat the file");
+		close(fd);
+		return;
+	}
+
+	ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+
+	llist_for_each_entry(header, entry, entry) {
+		printf("Printing header information:\n");
+		printf("more_more_magic: 0x%x\n", ntohs(header->firmware_info.more_more_magic));
+		printf("header_length: %u\n", ntohl(header->firmware_info.header_length));
+		printf("file_length: %u\n", ntohl(header->firmware_info.file_length));
+		printf("sw_part: %.20s\n", header->firmware_info.sw_part);
+		printf("text1: %.64s\n", header->firmware_info.text1);
+		printf("time: %.12s\n", header->firmware_info.time);
+		printf("date: %.14s\n", header->firmware_info.date);
+		printf("text2: %.10s\n", header->firmware_info.text2);
+		printf("version: %.20s\n", header->firmware_info.version);
+		printf("subitems...\n");
+
+		llist_for_each_entry(sub_entry, &header->header_list, entry) {
+			printf("\tsomething1: %u\n", sub_entry->header_entry.something1);
+			printf("\ttext1: %.64s\n", sub_entry->header_entry.text1);
+			printf("\ttime: %.12s\n", sub_entry->header_entry.time);
+			printf("\tdate: %.14s\n", sub_entry->header_entry.date);
+			printf("\ttext2: %.10s\n", sub_entry->header_entry.text2);
+			printf("\tversion: %.20s\n", sub_entry->header_entry.version);
+			printf("\tlength: %u\n", ntohl(sub_entry->header_entry.length));
+			printf("\taddr1: 0x%x\n", ntohl(sub_entry->header_entry.addr1));
+			printf("\taddr2: 0x%x\n", ntohl(sub_entry->header_entry.addr2));
+			printf("\tstart: 0x%x\n", ntohl(sub_entry->header_entry.start));
+			printf("\tabs. offset: 0x%lx\n", sub_entry->absolute_offset);
+			printf("\n\n");
+
+			dump_entry(sub_entry, part++, fd);
+		}
+		printf("\n\n");
+	}
+
+	if (close(fd) != 0) {
+		perror("Close failed.\n");
+		return;
+	}
+
+	talloc_free(tall_firm_ctx);
+}
+
+static void print_usage(void)
+{
+	printf("Usage: ipaccess-config IP_OF_BTS\n");
+}
+
+static void print_help(void)
+{
+#if 0
+	printf("Commands for reading from the BTS:\n");
+	printf("  -D --dump\t\t\tDump the BTS configuration\n");
+	printf("\n");
+#endif
+	printf("Commands for writing to the BTS:\n");
+	printf("  -u --unit-id UNIT_ID\t\tSet the Unit ID of the BTS\n");
+	printf("  -o --oml-ip IP\t\tSet primary OML IP (IP of your BSC)\n");
+	printf("  -i --ip-address IP/MASK\tSet static IP address + netmask of BTS\n");
+	printf("  -g --ip-gateway IP\t\tSet static IP gateway of BTS\n");
+	printf("  -r --restart\t\t\tRestart the BTS (after other operations)\n");
+	printf("  -n --nvram-flags FLAGS/MASK\tSet NVRAM attributes\n");
+	printf("  -S --nvattr-set FLAG\tSet one additional NVRAM attribute\n");
+	printf("  -U --nvattr-unset FLAG\tSet one additional NVRAM attribute\n");
+	printf("  -l --listen TESTNR\t\tPerform specified test number\n");
+	printf("  -L --Listen TEST_NAME\t\tPerform specified test\n");
+	printf("  -s --stream-id ID\t\tSet the IPA Stream Identifier for OML\n");
+	printf("  -d --software FIRMWARE\tDownload firmware into BTS\n");
+	printf("\n");
+	printf("Miscellaneous commands:\n");
+	printf("  -h --help\t\t\tthis text\n");
+	printf("  -H --HELP\t\t\tPrint parameter details.\n");
+	printf("  -f --firmware FIRMWARE\tProvide firmware information\n");
+	printf("  -w --write-firmware\t\tThis will dump the firmware parts to the filesystem. Use with -f.\n");
+	printf("  -p --loop\t\t\tLoop the tests executed with the --listen command.\n");
+}
+
+static void print_value_string(const struct value_string *val, int size)
+{
+	int i;
+
+	for (i = 0; i < size - 1; ++i) {
+		char sep = val[i + 1].str == NULL ? '.' : ',';
+		printf("%s%c ", val[i].str, sep);
+	}
+	printf("\n");
+}
+
+static void print_options(void)
+{
+
+	printf("Options for NVRAM (-S,-U):\n  ");
+	print_value_string(&ipa_nvflag_strs[0], ARRAY_SIZE(ipa_nvflag_strs));
+
+	printf("Options for Tests (-L):\n ");
+	print_value_string(&ipa_test_strs[0], ARRAY_SIZE(ipa_test_strs));
+}
+
+extern void bts_model_nanobts_init();
+
+int main(int argc, char **argv)
+{
+	struct gsm_bts *bts;
+	struct sockaddr_in sin;
+	int rc, option_index = 0, stream_id = 0xff;
+
+	tall_ctx_config = talloc_named_const(NULL, 0, "ipaccess-config");
+	msgb_talloc_ctx_init(tall_ctx_config, 0);
+
+	osmo_init_logging(&log_info);
+	log_parse_category_mask(osmo_stderr_target, "DNM,0");
+	bts_model_nanobts_init();
+
+	printf("ipaccess-config (C) 2009-2010 by Harald Welte and others\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	while (1) {
+		int c;
+		unsigned long ul;
+		char *slash;
+		static struct option long_options[] = {
+			{ "unit-id", 1, 0, 'u' },
+			{ "oml-ip", 1, 0, 'o' },
+			{ "ip-address", 1, 0, 'i' },
+			{ "ip-gateway", 1, 0, 'g' },
+			{ "restart", 0, 0, 'r' },
+			{ "nvram-flags", 1, 0, 'n' },
+			{ "nvattr-set", 1, 0, 'S' },
+			{ "nvattr-unset", 1, 0, 'U' },
+			{ "help", 0, 0, 'h' },
+			{ "HELP", 0, 0, 'H' },
+			{ "listen", 1, 0, 'l' },
+			{ "Listen", 1, 0, 'L' },
+			{ "stream-id", 1, 0, 's' },
+			{ "software", 1, 0, 'd' },
+			{ "firmware", 1, 0, 'f' },
+			{ "write-firmware", 0, 0, 'w' },
+			{ "disable-color", 0, 0, 'c'},
+			{ "loop", 0, 0, 'p' },
+			{ 0, 0, 0, 0 },
+		};
+
+		c = getopt_long(argc, argv, "u:o:i:g:rn:S:U:l:L:hs:d:f:wcpH", long_options,
+				&option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'u':
+			unit_id = optarg;
+			break;
+		case 'o':
+			prim_oml_ip = optarg;
+			break;
+		case 'i':
+			slash = strchr(optarg, '/');
+			if (!slash)
+				exit(2);
+			bts_ip_addr = optarg;
+			*slash = 0;
+			bts_ip_mask = slash+1;
+			break;
+		case 'g':
+			bts_ip_gw = optarg;
+			break;
+		case 'r':
+			restart = 1;
+			break;
+		case 'n':
+			slash = strchr(optarg, '/');
+			if (!slash)
+				exit(2);
+			ul = strtoul(optarg, NULL, 16);
+			nv_flags = ul & 0xffff;
+			ul = strtoul(slash+1, NULL, 16);
+			nv_mask = ul & 0xffff;
+			break;
+		case 'S':
+			if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 1) < 0)
+				exit(2);
+			break;
+		case 'U':
+			if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 0) < 0)
+				exit(2);
+			break;
+		case 'l':
+			net_listen_testnr = atoi(optarg);
+			break;
+		case 'L':
+			net_listen_testnr = get_string_value(ipa_test_strs,
+							     optarg);
+			if (net_listen_testnr < 0) {
+				fprintf(stderr,
+					"The test '%s' is not known. Use -H to"
+					" see available tests.\n", optarg);
+				exit(2);
+			}
+			break;
+		case 's':
+			stream_id = atoi(optarg);
+			break;
+		case 'd':
+			software = strdup(optarg);
+			if (find_sw_load_params(optarg) != 0)
+				exit(0);
+			break;
+		case 'f':
+			firmware_analysis = optarg;
+			break;
+		case 'w':
+			dump_files = 1;
+			break;
+		case 'c':
+			log_set_use_color(osmo_stderr_target, 0);
+			break;
+		case 'p':
+			loop_tests = 1;
+			break;
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 'H':
+			print_options();
+			exit(0);
+		}
+	};
+
+	if (firmware_analysis)
+		analyze_firmware(firmware_analysis);
+
+	if (optind >= argc) {
+		/* only warn if we have not done anything else */
+		if (!firmware_analysis)
+			fprintf(stderr, "you have to specify the IP address of the BTS. Use --help for more information\n");
+		exit(2);
+	}
+	libosmo_abis_init(tall_ctx_config);
+
+	bsc_gsmnet = bsc_network_init(tall_bsc_ctx, 1, 1, NULL);
+	if (!bsc_gsmnet)
+		exit(1);
+
+	bts = gsm_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_NANOBTS,
+				     HARDCODED_BSIC);
+	/* ip.access supports up to 4 chained TRX */
+	gsm_bts_trx_alloc(bts);
+	gsm_bts_trx_alloc(bts);
+	gsm_bts_trx_alloc(bts);
+	bts->oml_tei = stream_id;
+	
+	osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+	osmo_signal_register_handler(SS_IPAC_NWL, nwl_sig_cb, NULL);
+
+	ipac_nwl_init();
+
+	printf("Trying to connect to ip.access BTS ...\n");
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(argv[optind], &sin.sin_addr);
+	rc = ia_config_connect(bts, &sin);
+	if (rc < 0) {
+		perror("Error connecting to the BTS");
+		exit(1);
+	}
+	
+	bts->oml_link->ts->sign.delay = 10;
+	bts->c0->rsl_link->ts->sign.delay = 10;
+	while (1) {
+		rc = osmo_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
diff --git a/openbsc/src/ipaccess/ipaccess-firmware.c b/openbsc/src/ipaccess/ipaccess-firmware.c
new file mode 100644
index 0000000..5f55bb5
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-firmware.c
@@ -0,0 +1,135 @@
+/* Routines for parsing an ipacces SDP firmware file */
+
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <osmocom/core/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PART_LENGTH 138
+
+osmo_static_assert(sizeof(struct sdp_header_entry) == 138, right_entry);
+osmo_static_assert(sizeof(struct sdp_firmware) == 158, _right_header_length);
+
+/* more magic, the second "int" in the header */
+static char more_magic[] = { 0x10, 0x02 };
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned int base_offset, struct llist_head *list)
+{
+	struct sdp_firmware *firmware_header = 0;
+	struct sdp_header *header;
+	char buf[4096];
+	int rc, i;
+	uint16_t table_size;
+	uint16_t table_offset;
+	off_t table_start;
+
+
+	rc = read(fd, buf, sizeof(*firmware_header));
+	if (rc < 0) {
+		perror("Can not read header start.");
+		return -1;
+	}
+
+	firmware_header = (struct sdp_firmware *) &buf[0];
+	if (strncmp(firmware_header->magic, " SDP", 4) != 0) {
+		fprintf(stderr, "Wrong magic.\n");
+		return -1;
+	}
+
+	if (memcmp(firmware_header->more_magic, more_magic, 2) != 0) {
+		fprintf(stderr, "Wrong more magic. Got: 0x%x 0x%x vs. 0x%x 0x%x\n",
+			firmware_header->more_magic[0] & 0xff, firmware_header->more_magic[1] & 0xff,
+			more_magic[0], more_magic[1]);
+		return -1;
+	}
+
+
+	if (ntohl(firmware_header->file_length) != st_size) {
+		fprintf(stderr, "The filesize and the header do not match.\n");
+		return -1;
+	}
+
+	/* add the firmware */
+	header = talloc_zero(list, struct sdp_header);
+	header->firmware_info = *firmware_header;
+	INIT_LLIST_HEAD(&header->header_list);
+	llist_add(&header->entry, list);
+
+	table_offset = ntohs(firmware_header->table_offset);
+	table_start = lseek(fd, table_offset, SEEK_CUR);
+	if (table_start == -1) {
+		fprintf(stderr, "Failed to seek to the rel position: 0x%x\n", table_offset);
+		return -1;
+	}
+
+	if (read(fd, &table_size, sizeof(table_size)) != sizeof(table_size)) {
+		fprintf(stderr, "The table size could not be read.\n");
+		return -1;
+	}
+
+	table_size = ntohs(table_size);
+
+	if (table_size % PART_LENGTH != 0) {
+		fprintf(stderr, "The part length seems to be wrong: 0x%x\n", table_size);
+		return -1;
+	}
+
+	/* look into each firmware now */
+	for (i = 0; i < table_size / PART_LENGTH; ++i) {
+		struct sdp_header_entry entry;
+		struct sdp_header_item *header_entry;
+		unsigned int offset = table_start + 2;
+		offset += i * 138;
+
+		if (lseek(fd, offset, SEEK_SET) != offset) {
+			fprintf(stderr, "Can not seek to the offset: %u.\n", offset);
+			return -1;
+		}
+
+		rc = read(fd, &entry, sizeof(entry));
+		if (rc != sizeof(entry)) {
+			fprintf(stderr, "Can not read the header entry.\n");
+			return -1;
+		}
+
+		header_entry = talloc_zero(header,  struct sdp_header_item);
+		header_entry->header_entry = entry;
+		header_entry->absolute_offset = base_offset;
+		llist_add(&header_entry->entry, &header->header_list);
+
+		/* now we need to find the SDP file... */
+		offset = ntohl(entry.start) + 4 + base_offset;
+		if (lseek(fd, offset, SEEK_SET) != offset) {
+			perror("can't seek to sdp");
+			return -1;
+		}
+
+
+		ipaccess_analyze_file(fd, ntohl(entry.length), offset, list);
+	}
+
+	return 0;
+}
+
diff --git a/openbsc/src/ipaccess/ipaccess-proxy.c b/openbsc/src/ipaccess/ipaccess-proxy.c
new file mode 100644
index 0000000..d367442
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-proxy.c
@@ -0,0 +1,1226 @@
+/* OpenBSC Abis/IP proxy ip.access nanoBTS */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/abis/ipa.h>
+#include <osmocom/abis/ipaccess.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+#include <osmocom/core/talloc.h>
+
+/* one instance of an ip.access protocol proxy */
+struct ipa_proxy {
+	/* socket where we listen for incoming OML from BTS */
+	struct osmo_fd oml_listen_fd;
+	/* socket where we listen for incoming RSL from BTS */
+	struct osmo_fd rsl_listen_fd;
+	/* list of BTS's (struct ipa_bts_conn */
+	struct llist_head bts_list;
+	/* the BSC reconnect timer */
+	struct osmo_timer_list reconn_timer;
+	/* global GPRS NS data */
+	struct in_addr gprs_addr;
+	struct in_addr listen_addr;
+};
+
+/* global pointer to the proxy structure */
+static struct ipa_proxy *ipp;
+
+struct ipa_proxy_conn {
+	struct osmo_fd fd;
+	struct llist_head tx_queue;
+	struct ipa_bts_conn *bts_conn;
+};
+#define MAX_TRX 4
+
+/* represents a particular BTS in our proxy */
+struct ipa_bts_conn {
+	/* list of BTS's (ipa_proxy->bts_list) */
+	struct llist_head list;
+	/* back pointer to the proxy which we belong to */
+	struct ipa_proxy *ipp;
+	/* the unit ID as determined by CCM */
+	struct {
+		uint16_t site_id;
+		uint16_t bts_id;
+	} unit_id;
+
+	/* incoming connections from BTS */
+	struct ipa_proxy_conn *oml_conn;
+	struct ipa_proxy_conn *rsl_conn[MAX_TRX];
+
+	/* outgoing connections to BSC */
+	struct ipa_proxy_conn *bsc_oml_conn;
+	struct ipa_proxy_conn *bsc_rsl_conn[MAX_TRX];
+
+	/* UDP sockets for BTS and BSC injection */
+	struct osmo_fd udp_bts_fd;
+	struct osmo_fd udp_bsc_fd;
+
+	/* NS data */
+	struct in_addr bts_addr;
+	struct osmo_fd gprs_ns_fd;
+	int gprs_local_port;
+	uint16_t gprs_orig_port;
+	uint32_t gprs_orig_ip;
+
+	char *id_tags[256];
+	uint8_t *id_resp;
+	unsigned int id_resp_len;
+};
+
+enum ipp_fd_type {
+	OML_FROM_BTS = 1,
+	RSL_FROM_BTS = 2,
+	OML_TO_BSC = 3,
+	RSL_TO_BSC = 4,
+	UDP_TO_BTS = 5,
+	UDP_TO_BSC = 6,
+};
+
+/* some of the code against we link from OpenBSC needs this */
+void *tall_bsc_ctx;
+
+static char *listen_ipaddr;
+static char *bsc_ipaddr;
+static char *gprs_ns_ipaddr;
+
+static int gprs_ns_cb(struct osmo_fd *bfd, unsigned int what);
+
+#define PROXY_ALLOC_SIZE	1200
+
+static struct ipa_bts_conn *find_bts_by_unitid(struct ipa_proxy *ipp,
+						uint16_t site_id,
+						uint16_t bts_id)
+{
+	struct ipa_bts_conn *ipbc;
+
+	llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+		if (ipbc->unit_id.site_id == site_id &&
+		    ipbc->unit_id.bts_id == bts_id)
+			return ipbc;
+	}
+
+	return NULL;
+}
+
+struct ipa_proxy_conn *alloc_conn(void)
+{
+	struct ipa_proxy_conn *ipc;
+
+	ipc = talloc_zero(tall_bsc_ctx, struct ipa_proxy_conn);
+	if (!ipc)
+		return NULL;
+
+	INIT_LLIST_HEAD(&ipc->tx_queue);
+
+	return ipc;
+}
+
+static int store_idtags(struct ipa_bts_conn *ipbc, struct tlv_parsed *tlvp)
+{
+	unsigned int i, len;
+
+	for (i = 0; i <= 0xff; i++) {
+		if (!TLVP_PRESENT(tlvp, i))
+			continue;
+
+		len = TLVP_LEN(tlvp, i);
+#if 0
+		if (!ipbc->id_tags[i])
+			ipbc->id_tags[i] = talloc_size(tall_bsc_ctx, len);
+		else
+#endif
+			ipbc->id_tags[i] = talloc_realloc_size(ipbc,
+							  ipbc->id_tags[i], len);
+		if (!ipbc->id_tags[i])
+			return -ENOMEM;
+
+		memset(ipbc->id_tags[i], 0, len);
+		//memcpy(ipbc->id_tags[i], TLVP_VAL(tlvp, i), len);
+	}
+	return 0;
+}
+
+
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data);
+
+#define logp_ipbc_uid(ss, lvl, ipbc, trx_id) _logp_ipbc_uid(ss, lvl, __FILE__, __LINE__, ipbc, trx_id)
+
+static void _logp_ipbc_uid(unsigned int ss, unsigned int lvl, char *file, int line,
+			   struct ipa_bts_conn *ipbc, uint8_t trx_id)
+{
+	if (ipbc)
+		logp2(ss, lvl, file, line, 0, "(%u/%u/%u) ", ipbc->unit_id.site_id,
+		     ipbc->unit_id.bts_id, trx_id);
+	else
+		logp2(ss, lvl, file, line, 0, "unknown ");
+}
+
+static int handle_udp_read(struct osmo_fd *bfd)
+{
+	struct ipa_bts_conn *ipbc = bfd->data;
+	struct ipa_proxy_conn *other_conn = NULL;
+	struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP UDP");
+	struct ipaccess_head *hh;
+	int ret;
+
+	/* with UDP sockets, we cannot read partial packets but have to read
+	 * all of it in one go */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, msg->data_len, 0);
+	if (ret < 0) {
+		if (errno != EAGAIN)
+			LOGP(DLINP, LOGL_ERROR, "recv error  %s\n", strerror(errno));
+		msgb_free(msg);
+		return ret;
+	}
+	if (ret == 0) {
+		DEBUGP(DLINP, "UDP peer disappeared, dead socket\n");
+		osmo_fd_unregister(bfd);
+		close(bfd->fd);
+		bfd->fd = -1;
+		msgb_free(msg);
+		return -EIO;
+	}
+	if (ret < sizeof(*hh)) {
+		DEBUGP(DLINP, "could not even read header!?!\n");
+		msgb_free(msg);
+		return -EIO;
+	}
+	msgb_put(msg, ret);
+	msg->l2h = msg->data + sizeof(*hh);
+	DEBUGP(DLMI, "UDP RX: %s\n", osmo_hexdump(msg->data, msg->len));
+
+	if (hh->len != msg->len - sizeof(*hh)) {
+		DEBUGP(DLINP, "length (%u/%u) disagrees with header(%u)\n",
+			msg->len, msg->len - 3, hh->len);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	switch (bfd->priv_nr & 0xff) {
+	case UDP_TO_BTS:
+		/* injection towards BTS */
+		switch (hh->proto) {
+		case IPAC_PROTO_RSL:
+			/* FIXME: what to do about TRX > 0 */
+			other_conn = ipbc->rsl_conn[0];
+			break;
+		default:
+			DEBUGP(DLINP, "Unknown protocol 0x%02x, sending to "
+				"OML FD\n", hh->proto);
+			/* fall through */
+		case IPAC_PROTO_IPACCESS:
+		case IPAC_PROTO_OML:
+			other_conn = ipbc->oml_conn;
+			break;
+		}
+		break;
+	case UDP_TO_BSC:
+		/* injection towards BSC */
+		switch (hh->proto) {
+		case IPAC_PROTO_RSL:
+			/* FIXME: what to do about TRX > 0 */
+			other_conn = ipbc->bsc_rsl_conn[0];
+			break;
+		default:
+			DEBUGP(DLINP, "Unknown protocol 0x%02x, sending to "
+				"OML FD\n", hh->proto);
+			/* fall through */
+		case IPAC_PROTO_IPACCESS:
+		case IPAC_PROTO_OML:
+			other_conn = ipbc->bsc_oml_conn;
+			break;
+		}
+		break;
+	default:
+		DEBUGP(DLINP, "Unknown filedescriptor priv_nr=%04x\n", bfd->priv_nr);
+		break;
+	}
+
+	if (other_conn) {
+		/* enqueue the message for TX on the respective FD */
+		msgb_enqueue(&other_conn->tx_queue, msg);
+		other_conn->fd.when |= BSC_FD_WRITE;
+	} else
+		msgb_free(msg);
+
+	return 0;
+}
+
+static int handle_udp_write(struct osmo_fd *bfd)
+{
+	/* not implemented yet */
+	bfd->when &= ~BSC_FD_WRITE;
+
+	return -EIO;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int udp_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_udp_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_udp_write(bfd);
+
+	return rc;
+}
+
+
+static int ipbc_alloc_connect(struct ipa_proxy_conn *ipc, struct osmo_fd *bfd,
+			      uint16_t site_id, uint16_t bts_id,
+			      uint16_t trx_id, struct tlv_parsed *tlvp,
+			      struct msgb *msg)
+{
+	struct ipa_bts_conn *ipbc;
+	uint16_t udp_port;
+	int ret = 0;
+	struct sockaddr_in sin;
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+	DEBUGP(DLINP, "(%u/%u/%u) New BTS connection: ",
+		site_id, bts_id, trx_id);
+
+	/* OML needs to be established before RSL */
+	if ((bfd->priv_nr & 0xff) != OML_FROM_BTS) {
+		DEBUGPC(DLINP, "Not a OML connection ?!?\n");
+		return -EIO;
+	}
+
+	/* allocate new BTS connection data structure */
+	ipbc = talloc_zero(tall_bsc_ctx, struct ipa_bts_conn);
+	if (!ipbc) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	DEBUGPC(DLINP, "Created BTS Conn data structure\n");
+	ipbc->ipp = ipp;
+	ipbc->unit_id.site_id = site_id;
+	ipbc->unit_id.bts_id = bts_id;
+	ipbc->oml_conn = ipc;
+	ipc->bts_conn = ipbc;
+
+	/* store the content of the ID TAGS for later reference */
+	store_idtags(ipbc, tlvp);
+	ipbc->id_resp_len = msg->len;
+	ipbc->id_resp = talloc_size(tall_bsc_ctx, ipbc->id_resp_len);
+	memcpy(ipbc->id_resp, msg->data, ipbc->id_resp_len);
+
+	/* Create OML TCP connection towards BSC */
+	sin.sin_port = htons(IPA_TCP_PORT_OML);
+	ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+	if (!ipbc->bsc_oml_conn) {
+		ret = -EIO;
+		goto err_bsc_conn;
+	}
+
+	DEBUGP(DLINP, "(%u/%u/%u) OML Connected to BSC\n",
+		site_id, bts_id, trx_id);
+
+	/* Create UDP socket for BTS packet injection */
+	udp_port = 10000 + (site_id % 1000)*100 + (bts_id % 100);
+	ret = make_sock(&ipbc->udp_bts_fd, IPPROTO_UDP, INADDR_ANY, udp_port,
+			UDP_TO_BTS, udp_fd_cb, ipbc);
+	if (ret < 0)
+		goto err_udp_bts;
+	DEBUGP(DLINP, "(%u/%u/%u) Created UDP socket for injection "
+		"towards BTS at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+	/* Create UDP socket for BSC packet injection */
+	udp_port = 20000 + (site_id % 1000)*100 + (bts_id % 100);
+	ret = make_sock(&ipbc->udp_bsc_fd, IPPROTO_UDP, INADDR_ANY, udp_port,
+			UDP_TO_BSC, udp_fd_cb, ipbc);
+	if (ret < 0)
+		goto err_udp_bsc;
+	DEBUGP(DLINP, "(%u/%u/%u) Created UDP socket for injection "
+		"towards BSC at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+
+	/* GPRS NS related code */
+	if (gprs_ns_ipaddr) {
+		struct sockaddr_in sock;
+		socklen_t len = sizeof(sock);
+		struct in_addr addr;
+		uint32_t ip;
+
+		inet_aton(listen_ipaddr, &addr);
+		ip = ntohl(addr.s_addr); /* make_sock() needs host byte order */
+		ret = make_sock(&ipbc->gprs_ns_fd, IPPROTO_UDP, ip, 0, 0,
+				gprs_ns_cb, ipbc);
+		if (ret < 0) {
+			LOGP(DLINP, LOGL_ERROR, "Creating the GPRS socket failed.\n");
+			goto err_udp_bsc;
+		}
+
+		ret = getsockname(ipbc->gprs_ns_fd.fd, (struct sockaddr* ) &sock, &len);
+		ipbc->gprs_local_port = ntohs(sock.sin_port);
+		LOGP(DLINP, LOGL_NOTICE,
+			"Created GPRS NS Socket. Listening on: %s:%d\n",
+			inet_ntoa(sock.sin_addr), ipbc->gprs_local_port);
+
+		ret = getpeername(bfd->fd, (struct sockaddr* ) &sock, &len);
+		ipbc->bts_addr = sock.sin_addr;
+	}
+
+	llist_add(&ipbc->list, &ipp->bts_list);
+
+	return 0;
+
+err_udp_bsc:
+	osmo_fd_unregister(&ipbc->udp_bts_fd);
+err_udp_bts:
+	osmo_fd_unregister(&ipbc->bsc_oml_conn->fd);
+	close(ipbc->bsc_oml_conn->fd.fd);
+	talloc_free(ipbc->bsc_oml_conn);
+	ipbc->bsc_oml_conn = NULL;
+err_bsc_conn:
+	talloc_free(ipbc->id_resp);
+	talloc_free(ipbc);
+#if 0
+	osmo_fd_unregister(bfd);
+	close(bfd->fd);
+	talloc_free(bfd);
+#endif
+err_out:
+	return ret;
+}
+
+static int ipaccess_rcvmsg(struct ipa_proxy_conn *ipc, struct msgb *msg,
+			   struct osmo_fd *bfd)
+{
+	struct tlv_parsed tlvp;
+	uint8_t msg_type = *(msg->l2h);
+	struct ipaccess_unit unit_data;
+	struct ipa_bts_conn *ipbc;
+	int ret = 0;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = ipa_ccm_send_pong(bfd->fd);
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DLMI, "PONG!\n");
+		break;
+	case IPAC_MSGT_ID_RESP:
+		DEBUGP(DLMI, "ID_RESP ");
+		/* parse tags, search for Unit ID */
+		ipa_ccm_idtag_parse(&tlvp, (uint8_t *)msg->l2h + 2,
+				     msgb_l2len(msg)-2);
+		DEBUGP(DLMI, "\n");
+
+		if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) {
+			LOGP(DLINP, LOGL_ERROR, "No Unit ID in ID RESPONSE !?!\n");
+			return -EIO;
+		}
+
+		/* lookup BTS, create sign_link, ... */
+		memset(&unit_data, 0, sizeof(unit_data));
+		ipa_parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT),
+				      &unit_data);
+		ipbc = find_bts_by_unitid(ipp, unit_data.site_id, unit_data.bts_id);
+		if (!ipbc) {
+			/* We have not found an ipbc (per-bts proxy instance)
+			 * for this BTS yet.  The first connection of a new BTS must
+			 * be a OML connection.  We allocate the associated data structures,
+			 * and try to connect to the remote end */
+
+			return ipbc_alloc_connect(ipc, bfd, unit_data.site_id,
+						  unit_data.bts_id,
+						  unit_data.trx_id, &tlvp, msg);
+			/* if this fails, the caller will clean up bfd */
+		} else {
+			struct sockaddr_in sin;
+			memset(&sin, 0, sizeof(sin));
+			sin.sin_family = AF_INET;
+			inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+			DEBUGP(DLINP, "Identified BTS %u/%u/%u\n",
+				unit_data.site_id, unit_data.bts_id, unit_data.trx_id);
+
+			if ((bfd->priv_nr & 0xff) != RSL_FROM_BTS) {
+				LOGP(DLINP, LOGL_ERROR, "Second OML connection from "
+				     "same BTS ?!?\n");
+				return 0;
+			}
+
+			if (unit_data.trx_id >= MAX_TRX) {
+				LOGP(DLINP, LOGL_ERROR, "We don't support more "
+				     "than %u TRX\n", MAX_TRX);
+				return -EINVAL;
+			}
+
+			ipc->bts_conn = ipbc;
+			/* store TRX number in higher 8 bit of the bfd private number */
+			bfd->priv_nr |= unit_data.trx_id << 8;
+			ipbc->rsl_conn[unit_data.trx_id] = ipc;
+
+			/* Create RSL TCP connection towards BSC */
+			sin.sin_port = htons(IPA_TCP_PORT_RSL);
+			ipbc->bsc_rsl_conn[unit_data.trx_id] =
+				connect_bsc(&sin, RSL_TO_BSC | (unit_data.trx_id << 8), ipbc);
+			if (!ipbc->bsc_oml_conn)
+				return -EIO;
+			DEBUGP(DLINP, "(%u/%u/%u) Connected RSL to BSC\n",
+				unit_data.site_id, unit_data.bts_id, unit_data.trx_id);
+		}
+		break;
+	case IPAC_MSGT_ID_GET:
+		DEBUGP(DLMI, "ID_GET\n");
+		if ((bfd->priv_nr & 0xff) != OML_TO_BSC &&
+		    (bfd->priv_nr & 0xff) != RSL_TO_BSC) {
+			DEBUGP(DLINP, "IDentity REQuest from BTS ?!?\n");
+			return -EIO;
+		}
+		ipbc = ipc->bts_conn;
+		if (!ipbc) {
+			DEBUGP(DLINP, "ID_GET from BSC before we have ID_RESP from BTS\n");
+			return -EIO;
+		}
+		ret = write(bfd->fd, ipbc->id_resp, ipbc->id_resp_len);
+		if (ret != ipbc->id_resp_len) {
+			LOGP(DLINP, LOGL_ERROR, "Partial write: %d of %d\n",
+			     ret, ipbc->id_resp_len);
+			return -EIO;
+		}
+		ret = 0;
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DLMI, "ID_ACK? -> ACK!\n");
+		ret = ipa_ccm_send_id_ack(bfd->fd);
+		break;
+	default:
+		LOGP(DLMI, LOGL_ERROR, "Unhandled IPA type; %d\n", msg_type);
+		return 1;
+		break;
+	}
+	return ret;
+}
+
+struct msgb *ipaccess_proxy_read_msg(struct osmo_fd *bfd, int *error)
+{
+	struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP");
+	struct ipaccess_head *hh;
+	int len, ret = 0;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	/* first read our 3-byte header */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, 3, 0);
+	if (ret < 0) {
+		if (errno != EAGAIN)
+			LOGP(DLINP, LOGL_ERROR, "recv error: %s\n", strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msgb_put(msg, ret);
+
+	/* then read te length as specified in header */
+	msg->l2h = msg->data + sizeof(*hh);
+	len = ntohs(hh->len);
+	ret = recv(bfd->fd, msg->l2h, len, 0);
+	if (ret < len) {
+		LOGP(DLINP, LOGL_ERROR, "short read!\n");
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+static struct ipa_proxy_conn *ipc_by_priv_nr(struct ipa_bts_conn *ipbc,
+					     unsigned int priv_nr)
+{
+	struct ipa_proxy_conn *bsc_conn;
+	unsigned int trx_id = priv_nr >> 8;
+
+	switch (priv_nr & 0xff) {
+	case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+		bsc_conn = ipbc->bsc_oml_conn;
+		break;
+	case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+		bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+		break;
+	case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+		bsc_conn = ipbc->oml_conn;
+		break;
+	case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+		bsc_conn = ipbc->rsl_conn[trx_id];
+		break;
+	default:
+		bsc_conn = NULL;
+		break;
+	}
+	return bsc_conn;
+}
+
+static void reconn_tmr_cb(void *data)
+{
+	struct ipa_proxy *ipp = data;
+	struct ipa_bts_conn *ipbc;
+	struct sockaddr_in sin;
+	int i;
+
+	DEBUGP(DLINP, "Running reconnect timer\n");
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+	llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+		/* if OML to BSC is dead, try to restore it */
+		if (ipbc->oml_conn && !ipbc->bsc_oml_conn) {
+			sin.sin_port = htons(IPA_TCP_PORT_OML);
+			logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, 0);
+			LOGPC(DLINP, LOGL_NOTICE, "OML Trying to reconnect\n");
+			ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+			if (!ipbc->bsc_oml_conn)
+				goto reschedule;
+			logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, 0);
+			LOGPC(DLINP, LOGL_NOTICE, "OML Reconnected\n");
+		}
+		/* if we (still) don't have a OML connection, skip RSL */
+		if (!ipbc->oml_conn || !ipbc->bsc_oml_conn)
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(ipbc->rsl_conn); i++) {
+			unsigned int priv_nr;
+			/* don't establish RSL links which we don't have */
+			if (!ipbc->rsl_conn[i])
+				continue;
+			if (ipbc->bsc_rsl_conn[i])
+				continue;
+			priv_nr = ipbc->rsl_conn[i]->fd.priv_nr;
+			priv_nr &= ~0xff;
+			priv_nr |= RSL_TO_BSC;
+			sin.sin_port = htons(IPA_TCP_PORT_RSL);
+			logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+			LOGPC(DLINP, LOGL_NOTICE, "RSL Trying to reconnect\n");
+			ipbc->bsc_rsl_conn[i] = connect_bsc(&sin, priv_nr, ipbc);
+			if (!ipbc->bsc_rsl_conn[i])
+				goto reschedule;
+			logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+			LOGPC(DLINP, LOGL_NOTICE, "RSL Reconnected\n");
+		}
+	}
+	return;
+
+reschedule:
+	osmo_timer_schedule(&ipp->reconn_timer, 5, 0);
+}
+
+static void handle_dead_socket(struct osmo_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;		/* local conn */
+	struct ipa_proxy_conn *bsc_conn;		/* remote conn */
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	unsigned int trx_id = bfd->priv_nr >> 8;
+	struct msgb *msg, *msg2;
+
+	osmo_fd_unregister(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* FIXME: clear tx_queue, remove all references, etc. */
+	llist_for_each_entry_safe(msg, msg2, &ipc->tx_queue, list)
+		msgb_free(msg);
+
+	switch (bfd->priv_nr & 0xff) {
+	case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+		/* The BTS started a connection with us but we got no
+		 * IPAC_MSGT_ID_RESP message yet, in that scenario we did not
+		 * allocate the ipa_bts_conn structure. */
+		if (ipbc == NULL)
+			break;
+		ipbc->oml_conn = NULL;
+		bsc_conn = ipbc->bsc_oml_conn;
+		/* close the connection to the BSC */
+		osmo_fd_unregister(&bsc_conn->fd);
+		close(bsc_conn->fd.fd);
+		llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+			msgb_free(msg);
+		talloc_free(bsc_conn);
+		ipbc->bsc_oml_conn = NULL;
+		/* FIXME: do we need to delete the entire ipbc ? */
+		break;
+	case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+		ipbc->rsl_conn[trx_id] = NULL;
+		bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+		/* close the connection to the BSC */
+		osmo_fd_unregister(&bsc_conn->fd);
+		close(bsc_conn->fd.fd);
+		llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+			msgb_free(msg);
+		talloc_free(bsc_conn);
+		ipbc->bsc_rsl_conn[trx_id] = NULL;
+		break;
+	case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+		ipbc->bsc_oml_conn = NULL;
+		bsc_conn = ipbc->oml_conn;
+		/* start reconnect timer */
+		osmo_timer_schedule(&ipp->reconn_timer, 5, 0);
+		break;
+	case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+		ipbc->bsc_rsl_conn[trx_id] = NULL;
+		bsc_conn = ipbc->rsl_conn[trx_id];
+		/* start reconnect timer */
+		osmo_timer_schedule(&ipp->reconn_timer, 5, 0);
+		break;
+	default:
+		bsc_conn = NULL;
+		break;
+	}
+
+	talloc_free(ipc);
+}
+
+static void patch_gprs_msg(struct ipa_bts_conn *ipbc, int priv_nr, struct msgb *msg)
+{
+	uint8_t *nsvci;
+
+	if ((priv_nr & 0xff) != OML_FROM_BTS && (priv_nr & 0xff) != OML_TO_BSC)
+		return;
+
+	if (msgb_l2len(msg) != 39)
+		return;
+
+	/*
+	 * Check if this is a IPA Set Attribute or IPA Set Attribute ACK
+	 * and if the FOM Class is GPRS NSVC0 and then we will patch it.
+	 *
+	 * The patch assumes the message looks like the one from the trace
+	 * but we only match messages with a specific size anyway... So
+	 * this hack should work just fine.
+	 */
+
+	if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 &&
+	    msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 &&
+	    msg->l2h[18] == 0xf5 && msg->l2h[19] == 0xf2) {
+		nsvci = &msg->l2h[23];
+		ipbc->gprs_orig_port =  *(uint16_t *)(nsvci+8);
+		ipbc->gprs_orig_ip = *(uint32_t *)(nsvci+10);
+		*(uint16_t *)(nsvci+8) = htons(ipbc->gprs_local_port);
+		*(uint32_t *)(nsvci+10) = ipbc->ipp->listen_addr.s_addr;
+	} else if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 &&
+	    msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 &&
+	    msg->l2h[18] == 0xf6 && msg->l2h[19] == 0xf2) {
+		nsvci = &msg->l2h[23];
+		*(uint16_t *)(nsvci+8) = ipbc->gprs_orig_port;
+		*(uint32_t *)(nsvci+10) = ipbc->gprs_orig_ip;
+	}
+}
+
+static int handle_tcp_read(struct osmo_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	struct ipa_proxy_conn *bsc_conn;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+	int ret = 0;
+	char *btsbsc;
+
+	if ((bfd->priv_nr & 0xff) <= 2)
+		btsbsc = "BTS";
+	else
+		btsbsc = "BSC";
+
+	msg = ipaccess_proxy_read_msg(bfd, &ret);
+	if (!msg) {
+		if (ret == 0) {
+			logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+			LOGPC(DLINP, LOGL_NOTICE, "%s disappeared, "
+			     "dead socket\n", btsbsc);
+			handle_dead_socket(bfd);
+		}
+		return ret;
+	}
+
+	msgb_put(msg, ret);
+	logp_ipbc_uid(DLMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+	DEBUGPC(DLMI, "RX<-%s: %s\n", btsbsc, osmo_hexdump(msg->data, msg->len));
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ret = ipaccess_rcvmsg(ipc, msg, bfd);
+		if (ret < 0) {
+			osmo_fd_unregister(bfd);
+			close(bfd->fd);
+			bfd->fd = -1;
+			talloc_free(bfd);
+			msgb_free(msg);
+			return ret;
+		} else if (ret == 0) {
+			/* we do not forward parts of the CCM protocol
+			 * through the proxy but rather terminate it ourselves. */
+			msgb_free(msg);
+			return ret;
+		}
+	}
+
+	if (!ipbc) {
+		LOGP(DLINP, LOGL_ERROR,
+		     "received %s packet but no ipc->bts_conn?!?\n", btsbsc);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	bsc_conn = ipc_by_priv_nr(ipbc, bfd->priv_nr);
+	if (bsc_conn) {
+		if (gprs_ns_ipaddr)
+			patch_gprs_msg(ipbc, bfd->priv_nr, msg);
+		/* enqueue packet towards BSC */
+		msgb_enqueue(&bsc_conn->tx_queue, msg);
+		/* mark respective filedescriptor as 'we want to write' */
+		bsc_conn->fd.when |= BSC_FD_WRITE;
+	} else {
+		logp_ipbc_uid(DLINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8);
+		LOGPC(DLINP, LOGL_INFO, "Dropping packet from %s, "
+		     "since remote connection is dead\n", btsbsc);
+		msgb_free(msg);
+	}
+
+	return ret;
+}
+
+/* a TCP socket is ready to be written to */
+static int handle_tcp_write(struct osmo_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	struct llist_head *lh;
+	struct msgb *msg;
+	char *btsbsc;
+	int ret;
+
+	if ((bfd->priv_nr & 0xff) <= 2)
+		btsbsc = "BTS";
+	else
+		btsbsc = "BSC";
+
+
+	/* get the next msg for this timeslot */
+	if (llist_empty(&ipc->tx_queue)) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+	lh = ipc->tx_queue.next;
+	llist_del(lh);
+	msg = llist_entry(lh, struct msgb, list);
+
+	logp_ipbc_uid(DLMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+	DEBUGPC(DLMI, "TX %04x: %s\n", bfd->priv_nr,
+		osmo_hexdump(msg->data, msg->len));
+
+	ret = send(bfd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	if (ret == 0) {
+		logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+		LOGP(DLINP, LOGL_NOTICE, "%s disappeared, dead socket\n", btsbsc);
+		handle_dead_socket(bfd);
+	}
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int proxy_ipaccess_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ) {
+		rc = handle_tcp_read(bfd);
+		if (rc < 0)
+			return rc;
+	}
+	if (what & BSC_FD_WRITE)
+		rc = handle_tcp_write(bfd);
+
+	return rc;
+}
+
+/* callback of the listening filedescriptor */
+static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	struct ipa_proxy_conn *ipc;
+	struct osmo_fd *bfd;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		perror("accept");
+		return ret;
+	}
+	DEBUGP(DLINP, "accept()ed new %s link from %s\n",
+		(listen_bfd->priv_nr & 0xff) == OML_FROM_BTS ? "OML" : "RSL",
+		inet_ntoa(sa.sin_addr));
+
+	ipc = alloc_conn();
+	if (!ipc) {
+		close(ret);
+		return -ENOMEM;
+	}
+
+	bfd = &ipc->fd;
+	bfd->fd = ret;
+	bfd->data = ipc;
+	bfd->priv_nr = listen_bfd->priv_nr;
+	bfd->cb = proxy_ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = osmo_fd_register(bfd);
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(ipc);
+		return ret;
+	}
+
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = ipa_ccm_send_id_req(bfd->fd);
+
+	return 0;
+}
+
+static void send_ns(int fd, const char *buf, int size, struct in_addr ip, int port)
+{
+	int ret;
+	struct sockaddr_in addr;
+	socklen_t len = sizeof(addr);
+	memset(&addr, 0, sizeof(addr));
+
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	addr.sin_addr = ip;
+
+	ret = sendto(fd, buf, size, 0, (struct sockaddr *) &addr, len);
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to forward GPRS message.\n");
+	}
+}
+
+static int gprs_ns_cb(struct osmo_fd *bfd, unsigned int what)
+{
+	struct ipa_bts_conn *bts;
+	char buf[4096];
+	int ret;
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+
+	/* 1. get the data... */
+	ret = recvfrom(bfd->fd, buf, sizeof(buf), 0, (struct sockaddr *) &sock, &len);
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to recv GPRS NS msg: %s.\n", strerror(errno));
+		return -1;
+	}
+
+	bts = bfd->data;
+
+	/* 2. figure out where to send it to */
+	if (memcmp(&sock.sin_addr, &ipp->gprs_addr, sizeof(sock.sin_addr)) == 0) {
+		LOGP(DLINP, LOGL_DEBUG, "GPRS NS msg from network.\n");
+		send_ns(bfd->fd, buf, ret, bts->bts_addr, 23000);
+	} else if (memcmp(&sock.sin_addr, &bts->bts_addr, sizeof(sock.sin_addr)) == 0) {
+		LOGP(DLINP, LOGL_DEBUG, "GPRS NS msg from BTS.\n");
+		send_ns(bfd->fd, buf, ret, ipp->gprs_addr, 23000);
+	} else {
+		LOGP(DLINP, LOGL_ERROR, "Unknown GPRS source: %s\n", inet_ntoa(sock.sin_addr));
+	}
+
+	return 0;
+}
+
+/* Actively connect to a BSC.  */
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data)
+{
+	struct ipa_proxy_conn *ipc;
+	struct osmo_fd *bfd;
+	int ret, on = 1;
+
+	ipc = alloc_conn();
+	if (!ipc)
+		return NULL;
+
+	ipc->bts_conn = data;
+
+	bfd = &ipc->fd;
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd->data = ipc;
+	bfd->priv_nr = priv_nr;
+
+	if (bfd->fd < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Could not create socket: %s\n",
+			strerror(errno));
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	ret = setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Could not set socket option\n");
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Could not connect socket: %s\n",
+		     inet_ntoa(sa->sin_addr));
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	/* pre-fill tx_queue with identity request */
+	ret = osmo_fd_register(bfd);
+	if (ret < 0) {
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	return ipc;
+}
+
+static int ipaccess_proxy_setup(void)
+{
+	int ret;
+
+	ipp = talloc_zero(tall_bsc_ctx, struct ipa_proxy);
+	if (!ipp)
+		return -ENOMEM;
+	INIT_LLIST_HEAD(&ipp->bts_list);
+	osmo_timer_setup(&ipp->reconn_timer, reconn_tmr_cb, ipp);
+
+	/* Listen for OML connections */
+	ret = make_sock(&ipp->oml_listen_fd, IPPROTO_TCP, INADDR_ANY,
+			IPA_TCP_PORT_OML, OML_FROM_BTS, listen_fd_cb, NULL);
+	if (ret < 0)
+		return ret;
+
+	/* Listen for RSL connections */
+	ret = make_sock(&ipp->rsl_listen_fd, IPPROTO_TCP, INADDR_ANY,
+			IPA_TCP_PORT_RSL, RSL_FROM_BTS, listen_fd_cb, NULL);
+
+	if (ret < 0)
+		return ret;
+
+	/* Connect the GPRS NS Socket */
+	if (gprs_ns_ipaddr) {
+		inet_aton(gprs_ns_ipaddr, &ipp->gprs_addr);
+		inet_aton(listen_ipaddr, &ipp->listen_addr);
+	}
+
+	return ret;
+}
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+static void print_help(void)
+{
+	printf(" ipaccess-proxy is a proxy BTS.\n");
+	printf(" -h --help. This help text.\n");
+	printf(" -l --listen IP. The ip to listen to.\n");
+	printf(" -b --bsc IP. The BSC IP address.\n");
+	printf(" -g --gprs IP. Take GPRS NS from that IP.\n");
+	printf("\n");
+	printf(" -s --disable-color. Disable the color inside the logging message.\n");
+	printf(" -e --log-level number. Set the global loglevel.\n");
+	printf(" -T --timestamp. Prefix every log message with a timestamp.\n");
+	printf(" -V --version. Print the version of OpenBSC.\n");
+}
+
+static void print_usage(void)
+{
+	printf("Usage: ipaccess-proxy [options]\n");
+}
+
+enum {
+	IPA_PROXY_OPT_LISTEN_NONE	= 0,
+	IPA_PROXY_OPT_LISTEN_IP		= (1 << 0),
+	IPA_PROXY_OPT_BSC_IP		= (1 << 1),
+};
+
+static void handle_options(int argc, char** argv)
+{
+	int options_mask = 0;
+
+	/* disable explicit missing arguments error output from getopt_long */
+	opterr = 0;
+
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"log-level", 1, 0, 'e'},
+			{"listen", 1, 0, 'l'},
+			{"bsc", 1, 0, 'b'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hsTe:l:b:g:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 'l':
+			listen_ipaddr = optarg;
+			options_mask |= IPA_PROXY_OPT_LISTEN_IP;
+			break;
+		case 'b':
+			bsc_ipaddr = optarg;
+			options_mask |= IPA_PROXY_OPT_BSC_IP;
+			break;
+		case 'g':
+			gprs_ns_ipaddr = optarg;
+			break;
+		case 's':
+			log_set_use_color(osmo_stderr_target, 0);
+			break;
+		case 'T':
+			log_set_print_timestamp(osmo_stderr_target, 1);
+			break;
+		case 'e':
+			log_set_log_level(osmo_stderr_target, atoi(optarg));
+			break;
+		case '?':
+			if (optopt) {
+				printf("ERROR: missing mandatory argument "
+				       "for `%s' option\n", argv[optind-1]);
+			} else {
+				printf("ERROR: unknown option `%s'\n",
+					argv[optind-1]);
+			}
+			print_usage();
+			print_help();
+			exit(EXIT_FAILURE);
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+	if ((options_mask & (IPA_PROXY_OPT_LISTEN_IP | IPA_PROXY_OPT_BSC_IP))
+		 != (IPA_PROXY_OPT_LISTEN_IP | IPA_PROXY_OPT_BSC_IP)) {
+		printf("ERROR: You have to specify `--listen' and `--bsc' "
+		       "options at least.\n");
+		print_usage();
+		print_help();
+		exit(EXIT_FAILURE);
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "ipaccess-proxy");
+	msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+	osmo_init_logging(&log_info);
+	log_parse_category_mask(osmo_stderr_target, "DLINP:DLMI");
+
+	handle_options(argc, argv);
+
+	rc = ipaccess_proxy_setup();
+	if (rc < 0)
+		exit(1);
+
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	osmo_init_ignore_signals();
+
+	while (1) {
+		osmo_select_main(0);
+	}
+}
diff --git a/openbsc/src/ipaccess/network_listen.c b/openbsc/src/ipaccess/network_listen.c
new file mode 100644
index 0000000..43d82a9
--- /dev/null
+++ b/openbsc/src/ipaccess/network_listen.c
@@ -0,0 +1,257 @@
+/* ip.access nanoBTS network listen mode */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/rxlev_stat.h>
+#include <osmocom/gsm/gsm48_ie.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/e1_input.h>
+
+#define WHITELIST_MAX_SIZE ((NUM_ARFCNS*2)+2+1)
+
+int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev,
+			     uint16_t max_num_arfcns)
+{
+	int i;
+	unsigned int num_arfcn = 0;
+
+	for (i = NUM_RXLEVS-1; i >= min_rxlev; i--) {
+		int16_t arfcn = -1;
+
+		while ((arfcn = rxlev_stat_get_next(st, i, arfcn)) >= 0) {
+			*buf++ = htons(arfcn);
+			num_arfcn++;
+
+		}
+
+		if (num_arfcn > max_num_arfcns)
+			break;
+	}
+
+	return num_arfcn;
+}
+
+enum ipac_test_state {
+	IPAC_TEST_S_IDLE,
+	IPAC_TEST_S_RQD,
+	IPAC_TEST_S_EXEC,
+	IPAC_TEST_S_PARTIAL,
+};
+
+int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr,
+			const uint8_t *phys_conf, unsigned int phys_conf_len)
+{
+	struct msgb *msg;
+
+	if (trx->ipaccess.test_state != IPAC_TEST_S_IDLE) {
+		fprintf(stderr, "Cannot start test in state %u\n", trx->ipaccess.test_state);
+		return -EINVAL;
+	}
+
+	switch (testnr) {
+	case NM_IPACC_TESTNO_CHAN_USAGE:
+	case NM_IPACC_TESTNO_BCCH_CHAN_USAGE:
+		rxlev_stat_reset(&trx->ipaccess.rxlev_stat);
+		break;
+	}
+
+	msg = msgb_alloc_headroom(phys_conf_len+256, 128, "OML");
+
+	if (phys_conf && phys_conf_len) {
+		uint8_t *payload;
+		/* first put the phys conf header */
+		msgb_tv16_put(msg, NM_ATT_PHYS_CONF, phys_conf_len);
+		payload = msgb_put(msg, phys_conf_len);
+		memcpy(payload, phys_conf, phys_conf_len);
+	}
+
+	abis_nm_perform_test(trx->bts, NM_OC_RADIO_CARRIER, 0, trx->nr, 0xff,
+			     testnr, 1, msg);
+	trx->ipaccess.test_nr = testnr;
+
+	/* FIXME: start safety timer until when test is supposed to complete */
+
+	return 0;
+}
+
+static uint16_t last_arfcn;
+static struct gsm_sysinfo_freq nwl_si_freq[1024];
+#define FREQ_TYPE_NCELL_2	0x04 /* sub channel of SI 2 */
+#define FREQ_TYPE_NCELL_2bis	0x08 /* sub channel of SI 2bis */
+#define FREQ_TYPE_NCELL_2ter	0x10 /* sub channel of SI 2ter */
+
+struct ipacc_ferr_elem {
+	int16_t freq_err;
+	uint8_t freq_qual;
+	uint8_t arfcn;
+} __attribute__((packed));
+
+struct ipacc_cusage_elem {
+	uint16_t arfcn:10,
+		  rxlev:6;
+} __attribute__ ((packed));
+
+static int test_rep(void *_msg)
+{
+	struct msgb *msg = _msg;
+	struct abis_om_fom_hdr *foh = msgb_l3(msg);
+	uint16_t test_rep_len, ferr_list_len;
+	struct ipacc_ferr_elem *ife;
+	struct ipac_bcch_info binfo;
+	struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+	int i, rc;
+
+	DEBUGP(DNM, "TEST REPORT: ");
+
+	if (foh->data[0] != NM_ATT_TEST_NO ||
+	    foh->data[2] != NM_ATT_TEST_REPORT)
+		return -EINVAL;
+
+	DEBUGPC(DNM, "test_no=0x%02x ", foh->data[1]);
+	/* data[2] == NM_ATT_TEST_REPORT */
+	/* data[3..4]: test_rep_len */
+	memcpy(&test_rep_len, &foh->data[3], sizeof(uint16_t));
+	test_rep_len = ntohs(test_rep_len);
+	/* data[5]: ip.access test result */
+	DEBUGPC(DNM, "tst_res=%s\n", ipacc_testres_name(foh->data[5]));
+
+	/* data[6]: ip.access nested IE. 3 == freq_err_list */
+	switch (foh->data[6]) {
+	case NM_IPAC_EIE_FREQ_ERR_LIST:
+		/* data[7..8]: length of ferr_list */
+		memcpy(&ferr_list_len, &foh->data[7], sizeof(uint16_t));
+		ferr_list_len = ntohs(ferr_list_len);
+
+		/* data[9...]: frequency error list elements */
+		for (i = 0; i < ferr_list_len; i+= sizeof(*ife)) {
+			ife = (struct ipacc_ferr_elem *) (foh->data + 9 + i);
+			DEBUGP(DNM, "==> ARFCN %4u, Frequency Error %6hd\n",
+			ife->arfcn, ntohs(ife->freq_err));
+		}
+		break;
+	case NM_IPAC_EIE_CHAN_USE_LIST:
+		/* data[7..8]: length of ferr_list */
+		memcpy(&ferr_list_len, &foh->data[7], sizeof(uint16_t));
+		ferr_list_len = ntohs(ferr_list_len);
+
+		/* data[9...]: channel usage list elements */
+		for (i = 0; i < ferr_list_len; i+= 2) {
+			uint16_t *cu_ptr = (uint16_t *)(foh->data + 9 + i);
+			uint16_t cu = ntohs(*cu_ptr);
+			uint16_t arfcn = cu & 0x3ff;
+			uint8_t rxlev = cu >> 10;
+			DEBUGP(DNM, "==> ARFCN %4u, RxLev %2u\n", arfcn, rxlev);
+			rxlev_stat_input(&sign_link->trx->ipaccess.rxlev_stat,
+					 arfcn, rxlev);
+		}
+		break;
+	case NM_IPAC_EIE_BCCH_INFO_TYPE:
+		break;
+	case NM_IPAC_EIE_BCCH_INFO:
+		rc = ipac_parse_bcch_info(&binfo, foh->data+6);
+		if (rc < 0) {
+			DEBUGP(DNM, "BCCH Info parsing failed\n");
+			break;
+		}
+		DEBUGP(DNM, "==> ARFCN %u, RxLev %2u, RxQual %2u: %s, LAC %d CI %d BSIC %u\n",
+			binfo.arfcn, binfo.rx_lev, binfo.rx_qual,
+			osmo_plmn_name(&binfo.cgi.lai.plmn),
+			binfo.cgi.lai.lac, binfo.cgi.cell_identity, binfo.bsic);
+
+		if (binfo.arfcn != last_arfcn) {
+			/* report is on a new arfcn, need to clear channel list */
+			memset(nwl_si_freq, 0, sizeof(nwl_si_freq));
+			last_arfcn = binfo.arfcn;
+		}
+		if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2) {
+			DEBUGP(DNM, "BA SI2: %s\n", osmo_hexdump(binfo.ba_list_si2, sizeof(binfo.ba_list_si2)));
+			gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2, sizeof(binfo.ba_list_si2),
+						0x8c, FREQ_TYPE_NCELL_2);
+		}
+		if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+			DEBUGP(DNM, "BA SI2bis: %s\n", osmo_hexdump(binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis)));
+			gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis),
+						0x8e, FREQ_TYPE_NCELL_2bis);
+		}
+		if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+			DEBUGP(DNM, "BA SI2ter: %s\n", osmo_hexdump(binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter)));
+			gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter),
+						0x8e, FREQ_TYPE_NCELL_2ter);
+		}
+		for (i = 0; i < ARRAY_SIZE(nwl_si_freq); i++) {
+			if (nwl_si_freq[i].mask)
+				DEBUGP(DNM, "Neighbor Cell on ARFCN %u\n", i);
+		}
+		break;
+	default:
+		break;
+	}
+
+	switch (foh->data[5]) {
+	case NM_IPACC_TESTRES_SUCCESS:
+	case NM_IPACC_TESTRES_STOPPED:
+	case NM_IPACC_TESTRES_TIMEOUT:
+	case NM_IPACC_TESTRES_NO_CHANS:
+		sign_link->trx->ipaccess.test_state = IPAC_TEST_S_IDLE;
+		/* Send signal to notify higher layers of test completion */
+		DEBUGP(DNM, "dispatching S_IPAC_NWL_COMPLETE signal\n");
+		osmo_signal_dispatch(SS_IPAC_NWL, S_IPAC_NWL_COMPLETE,
+					sign_link->trx);
+		break;
+	case NM_IPACC_TESTRES_PARTIAL:
+		sign_link->trx->ipaccess.test_state = IPAC_TEST_S_PARTIAL;
+		break;
+	}
+
+	return 0;
+}
+
+static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	switch (signal) {
+	case S_NM_TEST_REP:
+		return test_rep(signal_data);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+void ipac_nwl_init(void)
+{
+	osmo_signal_register_handler(SS_NM, nwl_sig_cb, NULL);
+}
diff --git a/openbsc/src/libbsc/Makefile.am b/openbsc/src/libbsc/Makefile.am
new file mode 100644
index 0000000..666e8b3
--- /dev/null
+++ b/openbsc/src/libbsc/Makefile.am
@@ -0,0 +1,58 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libbsc.a \
+	$(NULL)
+
+libbsc_a_SOURCES = \
+	abis_nm.c \
+	abis_nm_vty.c \
+	abis_om2000.c \
+	abis_om2000_vty.c \
+	abis_rsl.c \
+	acc_ramp.c \
+	bsc_rll.c \
+	bsc_subscriber.c \
+	paging.c \
+	bts_ericsson_rbs2000.c \
+	bts_ipaccess_nanobts.c \
+	bts_siemens_bs11.c \
+	bts_nokia_site.c \
+	bts_unknown.c \
+	bts_sysmobts.c \
+	chan_alloc.c \
+	handover_decision.c \
+	handover_logic.c \
+	meas_rep.c \
+	pcu_sock.c \
+	rest_octets.c \
+	system_information.c \
+	e1_config.c \
+	bsc_api.c \
+	bsc_msc.c bsc_vty.c \
+	gsm_04_08_utils.c \
+	gsm_04_80_utils.c \
+	bsc_init.c \
+	bts_init.c \
+	bsc_rf_ctrl.c \
+	arfcn_range_encode.c \
+	bsc_ctrl_commands.c \
+	bsc_ctrl_lookup.c \
+	net_init.c \
+	bsc_dyn_ts.c \
+	bts_ipaccess_nanobts_omlattr.c \
+	$(NULL)
+
diff --git a/openbsc/src/libbsc/abis_nm.c b/openbsc/src/libbsc/abis_nm.c
new file mode 100644
index 0000000..922c2d5
--- /dev/null
+++ b/openbsc/src/libbsc/abis_nm.c
@@ -0,0 +1,2922 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/misdn.h>
+#include <openbsc/signal.h>
+#include <osmocom/abis/e1_input.h>
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+#define IPACC_SEGMENT_SIZE	245
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len)
+{
+	if (!bts->model)
+		return -EIO;
+	return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0);
+}
+
+static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size)
+{
+	int i;
+
+	for (i = 0; i < size; i++) {
+		if (arr[i] == mt)
+			return 1;
+	}
+
+	return 0;
+}
+
+#if 0
+/* is this msgtype the usual ACK/NACK type ? */
+static int is_ack_nack(enum abis_nm_msgtype mt)
+{
+	return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack));
+}
+#endif
+
+/* is this msgtype a report ? */
+static int is_report(enum abis_nm_msgtype mt)
+{
+	return is_in_arr(mt, abis_nm_reports, ARRAY_SIZE(abis_nm_reports));
+}
+
+#define MT_ACK(x)	(x+1)
+#define MT_NACK(x)	(x+2)
+
+static void fill_om_hdr(struct abis_om_hdr *oh, uint8_t len)
+{
+	oh->mdisc = ABIS_OM_MDISC_FOM;
+	oh->placement = ABIS_OM_PLACEMENT_ONLY;
+	oh->sequence = 0;
+	oh->length = len;
+}
+
+static struct abis_om_fom_hdr *fill_om_fom_hdr(struct abis_om_hdr *oh, uint8_t len,
+			    uint8_t msg_type, uint8_t obj_class,
+			    uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr)
+{
+	struct abis_om_fom_hdr *foh =
+			(struct abis_om_fom_hdr *) oh->data;
+
+	fill_om_hdr(oh, len+sizeof(*foh));
+	foh->msg_type = msg_type;
+	foh->obj_class = obj_class;
+	foh->obj_inst.bts_nr = bts_nr;
+	foh->obj_inst.trx_nr = trx_nr;
+	foh->obj_inst.ts_nr = ts_nr;
+	return foh;
+}
+
+static struct msgb *nm_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OML");
+}
+
+int _abis_nm_sendmsg(struct msgb *msg)
+{
+	msg->l2h = msg->data;
+
+	if (!msg->dst) {
+		LOGP(DNM, LOGL_ERROR, "%s: msg->dst == NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	return abis_sendmsg(msg);
+}
+
+/* Send a OML NM Message from BSC to BTS */
+static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg)
+{
+	msg->dst = bts->oml_link;
+
+	/* queue OML messages */
+	if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) {
+		bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg);
+		return _abis_nm_sendmsg(msg);
+	} else {
+		msgb_enqueue(&bts->abis_queue, msg);
+		return 0;
+	}
+
+}
+
+int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	OBSC_NM_W_ACK_CB(msg) = 1;
+	return abis_nm_queue_msg(bts, msg);
+}
+
+static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg)
+{
+	OBSC_NM_W_ACK_CB(msg) = 0;
+	return abis_nm_queue_msg(bts, msg);
+}
+
+static int abis_nm_rcvmsg_sw(struct msgb *mb);
+
+int nm_is_running(struct gsm_nm_state *s) {
+	return (s->operational == NM_OPSTATE_ENABLED) && (
+		(s->availability == NM_AVSTATE_OK) ||
+		(s->availability == 0xff)
+	);
+}
+
+/* Update the administrative state of a given object in our in-memory data
+ * structures and send an event to the higher layer */
+static int update_admstate(struct gsm_bts *bts, uint8_t obj_class,
+			   struct abis_om_obj_inst *obj_inst, uint8_t adm_state)
+{
+	struct gsm_nm_state *nm_state, new_state;
+	struct nm_statechg_signal_data nsd;
+
+	memset(&nsd, 0, sizeof(nsd));
+
+	nsd.obj = gsm_objclass2obj(bts, obj_class, obj_inst);
+	if (!nsd.obj)
+		return -EINVAL;
+	nm_state = gsm_objclass2nmstate(bts, obj_class, obj_inst);
+	if (!nm_state)
+		return -1;
+
+	new_state = *nm_state;
+	new_state.administrative = adm_state;
+
+	nsd.bts = bts;
+	nsd.obj_class = obj_class;
+	nsd.old_state = nm_state;
+	nsd.new_state = &new_state;
+	nsd.obj_inst = obj_inst;
+	osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd);
+
+	nm_state->administrative = adm_state;
+
+	return 0;
+}
+
+static int abis_nm_rx_statechg_rep(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct gsm_bts *bts = sign_link->trx->bts;
+	struct tlv_parsed tp;
+	struct gsm_nm_state *nm_state, new_state;
+
+	DEBUGPC(DNM, "STATE CHG: ");
+
+	memset(&new_state, 0, sizeof(new_state));
+
+	nm_state = gsm_objclass2nmstate(bts, foh->obj_class, &foh->obj_inst);
+	if (!nm_state) {
+		DEBUGPC(DNM, "unknown object class\n");
+		return -EINVAL;
+	}
+
+	new_state = *nm_state;
+	
+	abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+	if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) {
+		new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE);
+		DEBUGPC(DNM, "OP_STATE=%s ",
+			abis_nm_opstate_name(new_state.operational));
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) {
+		if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0)
+			new_state.availability = 0xff;
+		else
+			new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS);
+		DEBUGPC(DNM, "AVAIL=%s(%02x) ",
+			abis_nm_avail_name(new_state.availability),
+			new_state.availability);
+	} else
+		new_state.availability = 0xff;
+	if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
+		new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+		DEBUGPC(DNM, "ADM=%2s ",
+			get_value_string(abis_nm_adm_state_names,
+					 new_state.administrative));
+	}
+	DEBUGPC(DNM, "\n");
+
+	if ((new_state.administrative != 0 && nm_state->administrative == 0) ||
+	    new_state.operational != nm_state->operational ||
+	    new_state.availability != nm_state->availability) {
+		/* Update the operational state of a given object in our in-memory data
+ 		* structures and send an event to the higher layer */
+		struct nm_statechg_signal_data nsd;
+		nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+		nsd.obj_class = foh->obj_class;
+		nsd.old_state = nm_state;
+		nsd.new_state = &new_state;
+		nsd.obj_inst = &foh->obj_inst;
+		nsd.bts = bts;
+		osmo_signal_dispatch(SS_NM, S_NM_STATECHG_OPER, &nsd);
+		nm_state->operational = new_state.operational;
+		nm_state->availability = new_state.availability;
+		if (nm_state->administrative == 0)
+			nm_state->administrative = new_state.administrative;
+	}
+#if 0
+	if (op_state == 1) {
+		/* try to enable objects that are disabled */
+		abis_nm_opstart(bts, foh->obj_class,
+				foh->obj_inst.bts_nr,
+				foh->obj_inst.trx_nr,
+				foh->obj_inst.ts_nr);
+	}
+#endif
+	return 0;
+}
+
+static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type,
+				    const char *severity, const uint8_t *p_val,
+				    const char *text)
+{
+	enum abis_nm_pcause_type pcause = p_val[0];
+	enum abis_mm_event_causes cause = osmo_load16be(p_val + 1);
+
+	LOGPC(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr);
+	if (type)
+		LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type);
+	if (severity)
+		LOGPC(DNM, LOGL_ERROR, "Severity=%s, ", severity);
+
+	LOGPC(DNM, LOGL_ERROR, "Probable cause=%s: ",
+	      get_value_string(abis_nm_pcause_type_names, pcause));
+
+	if (pcause == NM_PCAUSE_T_MANUF)
+		LOGPC(DNM, LOGL_ERROR, "%s, ",
+		      get_value_string(abis_mm_event_cause_names, cause));
+	else
+		LOGPC(DNM, LOGL_ERROR, "%02X %02X ", p_val[1], p_val[2]);
+
+	if (text) {
+		LOGPC(DNM, LOGL_ERROR, "Additional Text=%s. ", text);
+	}
+
+	LOGPC(DNM, LOGL_ERROR, "\n");
+}
+
+static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_val, const char *type,
+					  const char *severity, const char *text)
+{
+	enum abis_mm_event_causes cause = osmo_load16be(p_val + 1);
+
+	switch (cause) {
+	case OSMO_EVT_PCU_VERS:
+		if (text) {
+			LOGPC(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text);
+			osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version));
+		} else {
+			LOGPC(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr);
+			bts->pcu_version[0] = '\0';
+		}
+		break;
+	default:
+		log_oml_fail_rep(bts, type, severity, p_val, text);
+	};
+}
+
+static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct tlv_parsed tp;
+	int rc = 0;
+	const uint8_t *p_val = NULL;
+	char *p_text = NULL;
+	const char *e_type = NULL, *severity = NULL;
+
+	abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data,
+			  oh->length-sizeof(*foh));
+
+	if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) {
+		p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT);
+		p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val,
+					TLVP_LEN(&tp, NM_ATT_ADD_TEXT));
+	}
+
+	if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
+		e_type = abis_nm_event_type_name(*TLVP_VAL(&tp,
+							   NM_ATT_EVENT_TYPE));
+
+	if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
+		severity = abis_nm_severity_name(*TLVP_VAL(&tp,
+							   NM_ATT_SEVERITY));
+
+	if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) {
+		p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE);
+
+		switch (p_val[0]) {
+		case NM_PCAUSE_T_MANUF:
+			handle_manufact_report(bts, p_val, e_type, severity,
+					       p_text);
+			break;
+		default:
+			log_oml_fail_rep(bts, e_type, severity, p_val, p_text);
+		};
+	} else {
+		LOGPC(DNM, LOGL_ERROR, "BTS%u: Failure Event Report without "
+		      "Probable Cause?!\n", bts->nr);
+		rc = -EINVAL;
+	}
+
+	if (p_text)
+		talloc_free(p_text);
+
+	return rc;
+}
+
+static int abis_nm_rcvmsg_report(struct msgb *mb, struct gsm_bts *bts)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	uint8_t mt = foh->msg_type;
+
+	abis_nm_debugp_foh(DNM, foh);
+
+	//nmh->cfg->report_cb(mb, foh);
+
+	switch (mt) {
+	case NM_MT_STATECHG_EVENT_REP:
+		return abis_nm_rx_statechg_rep(mb);
+		break;
+	case NM_MT_SW_ACTIVATED_REP:
+		DEBUGPC(DNM, "Software Activated Report\n");
+		osmo_signal_dispatch(SS_NM, S_NM_SW_ACTIV_REP, mb);
+		break;
+	case NM_MT_FAILURE_EVENT_REP:
+		rx_fail_evt_rep(mb, bts);
+		osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, mb);
+		break;
+	case NM_MT_TEST_REP:
+		DEBUGPC(DNM, "Test Report\n");
+		osmo_signal_dispatch(SS_NM, S_NM_TEST_REP, mb);
+		break;
+	default:
+		DEBUGPC(DNM, "reporting NM MT 0x%02x\n", mt);
+		break;
+		
+	};
+
+	return 0;
+}
+
+/* Activate the specified software into the BTS */
+static int ipacc_sw_activate(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1,
+			     uint8_t i2, const struct abis_nm_sw_desc *sw_desc)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint16_t len = abis_nm_sw_desc_len(sw_desc, true);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2);
+	abis_nm_put_sw_desc(msg, sw_desc, true);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw_descr,
+			     const size_t size)
+{
+	int res = 0;
+	int i;
+
+	for (i = 1; i < size; ++i) {
+		if (memcmp(sw_descr[res].file_version, sw_descr[i].file_version,
+			   OSMO_MIN(sw_descr[i].file_version_len,
+				    sw_descr[res].file_version_len)) < 0) {
+			res = i;
+		}
+	}
+
+	return res;
+}
+
+static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id, uint8_t *val, uint8_t len)
+{
+	switch (id) {
+	case BTS_TYPE_VARIANT:
+		LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val);
+		break;
+	case BTS_SUB_MODEL:
+		LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val);
+		break;
+	default:
+		return false;
+	}
+	return true;
+}
+
+/* Parse Attribute Response Info - return pointer to the actual content */
+static inline uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, uint8_t *ari, uint16_t ari_len, uint16_t *out_len)
+{
+	uint8_t num_unreported = ari[0], i;
+
+	DEBUGP(DNM, "BTS%u Get Attributes Response Info: %u bytes total with %u unreported attributes\n",
+	       bts_nr, ari_len, num_unreported);
+
+	/* +1 because we have to account for number of unreported attributes, prefixing the list: */
+	for (i = 0; i < num_unreported; i++)
+		LOGP(DNM, LOGL_ERROR, "BTS%u Attribute %s is unreported\n",
+		     bts_nr, get_value_string(abis_nm_att_names, ari[i + 1]));
+
+	/* the data starts right after the list of unreported attributes + space for length of that list */
+	*out_len = ari_len - (num_unreported + 2);
+
+	return ari + num_unreported + 1; /* we have to account for 1st byte with number of unreported attributes */
+}
+
+/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.30 Manufacturer Id */
+static inline uint8_t *parse_attr_resp_info_manuf_id(struct gsm_bts *bts, uint8_t *data, uint16_t *data_len)
+{
+	struct tlv_parsed tp;
+	uint16_t m_id_len = 0;
+	uint8_t adjust = 0, i;
+
+	abis_nm_tlv_parse(&tp, bts, data, *data_len);
+	if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_ID, 2)) {
+		m_id_len = TLVP_LEN(&tp, NM_ATT_MANUF_ID);
+
+		/* log potential BTS feature vector overflow */
+		if (m_id_len > sizeof(bts->_features_data))
+			LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n",
+			     bts->nr, MAX_BTS_FEATURES/8);
+
+		/* check that max. expected BTS attribute is above given feature vector length */
+		if (m_id_len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT))
+			LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: reported unexpectedly long (%u bytes) "
+			     "feature vector - most likely it was compiled against newer BSC headers. "
+			     "Consider upgrading your BSC to later version.\n",
+			     bts->nr, m_id_len);
+
+		memcpy(bts->_features_data, TLVP_VAL(&tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data));
+		adjust = m_id_len + 3; /* adjust for parsed TL16V struct */
+
+		for (i = 0; i < _NUM_BTS_FEAT; i++)
+			if (gsm_bts_has_feature(bts, i) != gsm_btsmodel_has_feature(bts->model, i))
+				LOGP(DNM, LOGL_NOTICE, "BTS%u feature '%s' reported via OML does not match statically "
+				     "set feature: %u != %u. Please fix.\n", bts->nr,
+				     get_value_string(gsm_bts_features_descs, i),
+				     gsm_bts_has_feature(bts, i), gsm_btsmodel_has_feature(bts->model, i));
+	}
+
+	*data_len -= adjust;
+
+	return data + adjust;
+}
+
+/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */
+static inline uint8_t *parse_attr_resp_info_manuf_state(const struct gsm_bts_trx *trx, uint8_t *data, uint16_t *data_len)
+{
+	struct tlv_parsed tp;
+	const uint8_t *power;
+	uint8_t adjust = 0;
+
+	if (!trx) /* this attribute does not make sense on BTS level, only on TRX level */
+		return data;
+
+	abis_nm_tlv_parse(&tp, trx->bts, data, *data_len);
+	if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_STATE, 1)) {
+		power = TLVP_VAL(&tp, NM_ATT_MANUF_STATE);
+		LOGP(DNM, LOGL_NOTICE, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *power);
+		adjust = 2; /* adjust for parsed TV struct */
+	}
+
+	*data_len -= adjust;
+
+	return data + adjust;
+}
+
+/* Handle 3GPP TS 52.021 §9.4.64 Get Attribute Response Info */
+static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct gsm_bts *bts = trx ? trx->bts : sign_link->trx->bts;
+	struct tlv_parsed tp;
+	uint8_t *data, i;
+	uint16_t data_len;
+	int rc;
+	struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
+
+	abis_nm_debugp_foh(DNM, foh);
+
+	DEBUGPC(DNM, "Get Attributes Response for BTS%u\n", bts->nr);
+
+	abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+	if (!TLVP_PRES_LEN(&tp, NM_ATT_GET_ARI, 1)) {
+		LOGP(DNM, LOGL_ERROR, "BTS%u: Get Attributes Response without Response Info?!\n", bts->nr);
+		return -EINVAL;
+	}
+
+	data = parse_attr_resp_info_unreported(bts->nr, TLVP_VAL(&tp, NM_ATT_GET_ARI), TLVP_LEN(&tp, NM_ATT_GET_ARI),
+					       &data_len);
+
+	data = parse_attr_resp_info_manuf_state(trx, data, &data_len);
+	data = parse_attr_resp_info_manuf_id(bts, data, &data_len);
+
+	/* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */
+	rc = abis_nm_get_sw_conf(data, data_len, &sw_descr[0], ARRAY_SIZE(sw_descr));
+	if (rc > 0) {
+		for (i = 0; i < rc; i++) {
+			if (!handle_attr(bts, str2btsattr((const char *)sw_descr[i].file_id),
+					 sw_descr[i].file_version, sw_descr[i].file_version_len))
+				LOGP(DNM, LOGL_NOTICE, "BTS%u: ARI reported sw[%d/%d]: %s is %s\n",
+				     bts->nr, i, rc, sw_descr[i].file_id, sw_descr[i].file_version);
+		}
+	} else
+		LOGP(DNM, LOGL_ERROR, "BTS%u: failed to parse SW-Config part of Get Attribute Response Info: %s\n",
+		     bts->nr, strerror(-rc));
+
+	return 0;
+}
+
+/* 3GPP TS 52.021 §6.2.5 */
+static int abis_nm_rx_sw_act_req(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct tlv_parsed tp;
+	const uint8_t *sw_config;
+	int ret, sw_config_len, len;
+	struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
+
+	abis_nm_debugp_foh(DNM, foh);
+
+	DEBUGPC(DNM, "SW Activate Request: ");
+
+	DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n");
+
+	ret = abis_nm_sw_act_req_ack(sign_link->trx->bts, foh->obj_class,
+				      foh->obj_inst.bts_nr,
+				      foh->obj_inst.trx_nr,
+				      foh->obj_inst.ts_nr, 0,
+				      foh->data, oh->length-sizeof(*foh));
+	if (ret != 0) {
+		LOGP(DNM, LOGL_ERROR,
+			"Sending SW ActReq ACK failed: %d\n", ret);
+		return ret;
+	}
+
+	abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+	sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG);
+	sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG);
+	if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) {
+		LOGP(DNM, LOGL_ERROR,
+			"SW config not found! Can't continue.\n");
+		return -EINVAL;
+	} else {
+		DEBUGP(DNM, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len));
+	}
+
+	/* Parse up to two sw descriptions from the data */
+	len = abis_nm_get_sw_conf(sw_config, sw_config_len, &sw_descr[0],
+				  ARRAY_SIZE(sw_descr));
+	if (len <= 0) {
+		LOGP(DNM, LOGL_ERROR, "Failed to parse SW Config.\n");
+		return -EINVAL;
+	}
+
+	ret = abis_nm_select_newest_sw(&sw_descr[0], len);
+	DEBUGP(DNM, "Selected sw description %d of %d\n", ret, len);
+
+	return ipacc_sw_activate(sign_link->trx->bts, foh->obj_class,
+				 foh->obj_inst.bts_nr,
+				 foh->obj_inst.trx_nr,
+				 foh->obj_inst.ts_nr,
+				 &sw_descr[ret]);
+}
+
+/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */
+static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct tlv_parsed tp;
+	uint8_t adm_state;
+
+	abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+	if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE))
+		return -EINVAL;
+
+	adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+
+	return update_admstate(sign_link->trx->bts, foh->obj_class, &foh->obj_inst, adm_state);
+}
+
+static int abis_nm_rx_lmt_event(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct tlv_parsed tp;
+
+	DEBUGP(DNM, "LMT Event ");
+	abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) {
+		uint8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION);
+		DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF");
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) {
+		uint8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV);
+		DEBUGPC(DNM, "Level=%u ", level);
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) {
+		char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME);
+		DEBUGPC(DNM, "Username=%s ", name);
+	}
+	DEBUGPC(DNM, "\n");
+	/* FIXME: parse LMT LOGON TIME */
+	return 0;
+}
+
+void abis_nm_queue_send_next(struct gsm_bts *bts)
+{
+	int wait = 0;
+	struct msgb *msg;
+	/* the queue is empty */
+	while (!llist_empty(&bts->abis_queue)) {
+		msg = msgb_dequeue(&bts->abis_queue);
+		wait = OBSC_NM_W_ACK_CB(msg);
+		_abis_nm_sendmsg(msg);
+
+		if (wait)
+			break;
+	}
+
+	bts->abis_nm_pend = wait;
+}
+
+/* Receive a OML NM Message from BTS */
+static int abis_nm_rcvmsg_fom(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	uint8_t mt = foh->msg_type;
+	/* sign_link might get deleted via osmo_signal_dispatch -> save bts */
+	struct gsm_bts *bts = sign_link->trx->bts;
+	int ret = 0;
+
+	/* check for unsolicited message */
+	if (is_report(mt))
+		return abis_nm_rcvmsg_report(mb, bts);
+
+	if (is_in_arr(mt, abis_nm_sw_load_msgs, ARRAY_SIZE(abis_nm_sw_load_msgs)))
+		return abis_nm_rcvmsg_sw(mb);
+
+	if (is_in_arr(mt, abis_nm_nacks, ARRAY_SIZE(abis_nm_nacks))) {
+		struct nm_nack_signal_data nack_data;
+		struct tlv_parsed tp;
+
+		abis_nm_debugp_foh(DNM, foh);
+
+		DEBUGPC(DNM, "%s NACK ", abis_nm_nack_name(mt));
+
+		abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			DEBUGPC(DNM, "CAUSE=%s\n",
+				abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\n");
+
+		nack_data.msg = mb;
+		nack_data.mt = mt;
+		nack_data.bts = bts;
+		osmo_signal_dispatch(SS_NM, S_NM_NACK, &nack_data);
+		abis_nm_queue_send_next(bts);
+		return 0;
+	}
+#if 0
+	/* check if last message is to be acked */
+	if (is_ack_nack(nmh->last_msgtype)) {
+		if (mt == MT_ACK(nmh->last_msgtype)) {
+			DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type);
+			/* we got our ACK, continue sending the next msg */
+		} else if (mt == MT_NACK(nmh->last_msgtype)) {
+			/* we got a NACK, signal this to the caller */
+			DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type);
+			/* FIXME: somehow signal this to the caller */
+		} else {
+			/* really strange things happen */
+			return -EINVAL;
+		}
+	}
+#endif
+
+	switch (mt) {
+	case NM_MT_CHG_ADM_STATE_ACK:
+		ret = abis_nm_rx_chg_adm_state_ack(mb);
+		break;
+	case NM_MT_SW_ACT_REQ:
+		ret = abis_nm_rx_sw_act_req(mb);
+		break;
+	case NM_MT_BS11_LMT_SESSION:
+		ret = abis_nm_rx_lmt_event(mb);
+		break;
+	case NM_MT_OPSTART_ACK:
+		abis_nm_debugp_foh(DNM, foh);
+		DEBUGPC(DNM, "Opstart ACK\n");
+		break;
+	case NM_MT_SET_CHAN_ATTR_ACK:
+		abis_nm_debugp_foh(DNM, foh);
+		DEBUGPC(DNM, "Set Channel Attributes ACK\n");
+		break;
+	case NM_MT_SET_RADIO_ATTR_ACK:
+		abis_nm_debugp_foh(DNM, foh);
+		DEBUGPC(DNM, "Set Radio Carrier Attributes ACK\n");
+		break;
+	case NM_MT_CONN_MDROP_LINK_ACK:
+		abis_nm_debugp_foh(DNM, foh);
+		DEBUGPC(DNM, "CONN MDROP LINK ACK\n");
+		break;
+	case NM_MT_IPACC_RESTART_ACK:
+		osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_ACK, NULL);
+		break;
+	case NM_MT_IPACC_RESTART_NACK:
+		osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
+		break;
+	case NM_MT_SET_BTS_ATTR_ACK:
+		break;
+	case NM_MT_GET_ATTR_RESP:
+		ret = abis_nm_rx_get_attr_resp(mb, gsm_bts_trx_num(bts, (foh)->obj_inst.trx_nr));
+		break;
+	default:
+		abis_nm_debugp_foh(DNM, foh);
+		LOGPC(DNM, LOGL_ERROR, "Unhandled message %s\n",
+		      get_value_string(abis_nm_msgtype_names, mt));
+	}
+
+	abis_nm_queue_send_next(bts);
+	return ret;
+}
+
+static int abis_nm_rx_ipacc(struct msgb *mb);
+
+static int abis_nm_rcvmsg_manuf(struct msgb *mb)
+{
+	int rc;
+	struct e1inp_sign_link *sign_link = mb->dst;
+	int bts_type = sign_link->trx->bts->type;
+
+	switch (bts_type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		rc = abis_nm_rx_ipacc(mb);
+		abis_nm_queue_send_next(sign_link->trx->bts);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this "
+		     "BTS type (%u)\n", bts_type);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+/* High-Level API */
+/* Entry-point where L2 OML from BTS enters the NM code */
+int abis_nm_rcvmsg(struct msgb *msg)
+{
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	int rc = 0;
+
+	/* Various consistency checks */
+	if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+			oh->placement);
+		if (oh->placement != ABIS_OM_PLACEMENT_FIRST) {
+			rc = -EINVAL;
+			goto err;
+		}
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+			oh->sequence);
+		rc = -EINVAL;
+		goto err;
+	}
+#if 0
+	unsigned int l2_len = msg->tail - (uint8_t *)msgb_l2(msg);
+	unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr);
+	if (oh->length + hlen > l2_len) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n",
+			oh->length + sizeof(*oh), l2_len);
+		return -EINVAL;
+	}
+	if (oh->length + hlen < l2_len)
+		LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len);
+#endif
+	msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+	switch (oh->mdisc) {
+	case ABIS_OM_MDISC_FOM:
+		rc = abis_nm_rcvmsg_fom(msg);
+		break;
+	case ABIS_OM_MDISC_MANUF:
+		rc = abis_nm_rcvmsg_manuf(msg);
+		break;
+	case ABIS_OM_MDISC_MMI:
+	case ABIS_OM_MDISC_TRAU:
+		LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n",
+			oh->mdisc);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n",
+			oh->mdisc);
+		rc = -EINVAL;
+		break;
+	}
+err:
+	msgb_free(msg);
+	return rc;
+}
+
+#if 0
+/* initialized all resources */
+struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg)
+{
+	struct abis_nm_h *nmh;
+
+	nmh = malloc(sizeof(*nmh));
+	if (!nmh)
+		return NULL;
+
+	nmh->cfg = cfg;
+
+	return nmh;
+}
+
+/* free all resources */
+void abis_nm_fini(struct abis_nm_h *nmh)
+{
+	free(nmh);
+}
+#endif
+
+/* Here we are trying to define a high-level API that can be used by
+ * the actual BSC implementation.  However, the architecture is currently
+ * still under design.  Ideally the calls to this API would be synchronous,
+ * while the underlying stack behind the APi runs in a traditional select
+ * based state machine.
+ */
+
+/* 6.2 Software Load: */
+enum sw_state {
+	SW_STATE_NONE,
+	SW_STATE_WAIT_INITACK,
+	SW_STATE_WAIT_SEGACK,
+	SW_STATE_WAIT_ENDACK,
+	SW_STATE_WAIT_ACTACK,
+	SW_STATE_ERROR,
+};
+
+struct abis_nm_sw {
+	struct gsm_bts *bts;
+	int trx_nr;
+	gsm_cbfn *cbfn;
+	void *cb_data;
+	int forced;
+
+	/* this will become part of the SW LOAD INITIATE */
+	uint8_t obj_class;
+	uint8_t obj_instance[3];
+
+	uint8_t file_id[255];
+	uint8_t file_id_len;
+
+	uint8_t file_version[255];
+	uint8_t file_version_len;
+
+	uint8_t window_size;
+	uint8_t seg_in_window;
+
+	int fd;
+	FILE *stream;
+	enum sw_state state;
+	int last_seg;
+};
+
+static struct abis_nm_sw g_sw;
+
+static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg)
+{
+	if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) {
+		msgb_v_put(msg, NM_ATT_SW_DESCR);
+		msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+		msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+			       sw->file_version);
+	} else if (sw->bts->type == GSM_BTS_TYPE_BS11) {
+		msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+		msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+			     sw->file_version);
+	} else {
+		LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n");
+	}
+}
+
+/* 6.2.1 / 8.3.1: Load Data Initiate */
+static int sw_load_init(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t len = 3*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	sw_add_file_id_and_ver(sw, msg);
+	msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size);
+	
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+static int is_last_line(FILE *stream)
+{
+	char next_seg_buf[256];
+	long pos;
+
+	/* check if we're sending the last line */
+	pos = ftell(stream);
+
+	/* Did ftell fail? Then we are at the end for sure */
+	if (pos < 0)
+		return 1;
+
+	if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) {
+		int rc = fseek(stream, pos, SEEK_SET);
+		if (rc < 0)
+			return rc;
+		return 1;
+	}
+
+	fseek(stream, pos, SEEK_SET);
+	return 0;
+}
+
+/* 6.2.2 / 8.3.2 Load Data Segment */
+static int sw_load_segment(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	char seg_buf[256];
+	char *line_buf = seg_buf+2;
+	unsigned char *tlv;
+	int len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) {
+			perror("fgets reading segment");
+			return -EINVAL;
+		}
+		seg_buf[0] = 0x00;
+
+		/* check if we're sending the last line */
+		sw->last_seg = is_last_line(sw->stream);
+		if (sw->last_seg)
+			seg_buf[1] = 0;
+		else
+			seg_buf[1] = 1 + sw->seg_in_window++;
+
+		len = strlen(line_buf) + 2;
+		tlv = msgb_put(msg, TLV_GROSS_LEN(len));
+		tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (uint8_t *)seg_buf);
+		/* BS11 wants CR + LF in excess of the TLV length !?! */
+		tlv[1] -= 2;
+
+		/* we only now know the exact length for the OM hdr */
+		len = strlen(line_buf)+2;
+		break;
+	case GSM_BTS_TYPE_NANOBTS: {
+		osmo_static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough);
+		len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE);
+		if (len < 0) {
+			perror("read failed");
+			return -EINVAL;
+		}
+
+		if (len != IPACC_SEGMENT_SIZE)
+			sw->last_seg = 1;
+
+		++sw->seg_in_window;
+		msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const uint8_t *) seg_buf);
+		len += 3;
+		break;
+	}
+	default:
+		LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n");
+		/* FIXME: Other BTS types */
+		return -1;
+	}
+
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	return abis_nm_sendmsg_direct(sw->bts, msg);
+}
+
+/* 6.2.4 / 8.3.4 Load Data End */
+static int sw_load_end(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	sw_add_file_id_and_ver(sw, msg);
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+/* Activate the specified software into the BTS */
+static int sw_activate(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	/* FIXME: this is BS11 specific format */
+	msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+	msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+		     sw->file_version);
+
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[4];
+	unsigned int header_length;
+	unsigned int file_length;
+} __attribute__ ((packed));
+
+static int parse_sdp_header(struct abis_nm_sw *sw)
+{
+	struct sdp_firmware firmware_header;
+	int rc;
+	struct stat stat;
+
+	rc = read(sw->fd, &firmware_header, sizeof(firmware_header));
+	if (rc != sizeof(firmware_header)) {
+		LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n");
+		return -1;
+	}
+
+	if (strncmp(firmware_header.magic, " SDP", 4) != 0) {
+		LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n");
+		return -1;
+	}
+
+	if (firmware_header.more_magic[0] != 0x10 ||
+	    firmware_header.more_magic[1] != 0x02 ||
+	    firmware_header.more_magic[2] != 0x00 ||
+	    firmware_header.more_magic[3] != 0x00) {
+		LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n");
+		return -1;
+	}
+
+
+	if (fstat(sw->fd, &stat) == -1) {
+		LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n");
+		return -1;
+	}
+
+	if (ntohl(firmware_header.file_length) != stat.st_size) {
+		LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n");
+		return -1;
+	}
+
+	/* go back to the start as we checked the whole filesize.. */
+	lseek(sw->fd, 0l, SEEK_SET);
+	LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood.\n"
+			       "There might be checksums in the file that are not\n"
+			       "verified and incomplete firmware might be flashed.\n"
+			       "There is absolutely no WARRANTY that flashing will\n"
+			       "work.\n");
+	return 0;
+}
+
+static int sw_open_file(struct abis_nm_sw *sw, const char *fname)
+{
+	char file_id[12+1];
+	char file_version[80+1];
+	int rc;
+
+	sw->fd = open(fname, O_RDONLY);
+	if (sw->fd < 0)
+		return sw->fd;
+
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		sw->stream = fdopen(sw->fd, "r");
+		if (!sw->stream) {
+			perror("fdopen");
+			return -1;
+		}
+		/* read first line and parse file ID and VERSION */
+		rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n",
+			    file_id, file_version);
+		if (rc != 2) {
+			perror("parsing header line of software file");
+			return -1;
+		}
+		strcpy((char *)sw->file_id, file_id);
+		sw->file_id_len = strlen(file_id);
+		strcpy((char *)sw->file_version, file_version);
+		sw->file_version_len = strlen(file_version);
+		/* rewind to start of file */
+		rewind(sw->stream);
+		break;	
+	case GSM_BTS_TYPE_NANOBTS:
+		/* TODO: extract that from the filename or content */
+		rc = parse_sdp_header(sw);
+		if (rc < 0) {
+			fprintf(stderr, "Could not parse the ipaccess SDP header\n");
+			return -1;
+		}
+
+		strcpy((char *)sw->file_id, "id");
+		sw->file_id_len = 3;
+		strcpy((char *)sw->file_version, "version");
+		sw->file_version_len = 8;
+		break;
+	default:
+		/* We don't know how to treat them yet */
+		close(sw->fd);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+	
+static void sw_close_file(struct abis_nm_sw *sw)
+{
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		fclose(sw->stream);
+		break;
+	default:
+		close(sw->fd);
+		break;
+	}
+}
+
+/* Fill the window */
+static int sw_fill_window(struct abis_nm_sw *sw)
+{
+	int rc;
+
+	while (sw->seg_in_window < sw->window_size) {
+		rc = sw_load_segment(sw);
+		if (rc < 0)
+			return rc;
+		if (sw->last_seg)
+			break;
+	}
+	return 0;
+}
+
+/* callback function from abis_nm_rcvmsg() handler */
+static int abis_nm_rcvmsg_sw(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	int rc = -1;
+	struct abis_nm_sw *sw = &g_sw;
+	enum sw_state old_state = sw->state;
+	
+	//DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type);
+
+	switch (sw->state) {
+	case SW_STATE_WAIT_INITACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_INIT_ACK:
+			/* fill window with segments */
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_INIT_ACK, mb,
+					 sw->cb_data, NULL);
+			rc = sw_fill_window(sw);
+			sw->state = SW_STATE_WAIT_SEGACK;
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		case NM_MT_LOAD_INIT_NACK:
+			if (sw->forced) {
+				DEBUGP(DNM, "FORCED: Ignoring Software Load "
+					"Init NACK\n");
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_INIT_ACK, mb,
+						 sw->cb_data, NULL);
+				rc = sw_fill_window(sw);
+				sw->state = SW_STATE_WAIT_SEGACK;
+			} else {
+				DEBUGP(DNM, "Software Load Init NACK\n");
+				/* FIXME: cause */
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_INIT_NACK, mb,
+						 sw->cb_data, NULL);
+				sw->state = SW_STATE_ERROR;
+			}
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		}
+		break;
+	case SW_STATE_WAIT_SEGACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_SEG_ACK:
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_SEG_ACK, mb,
+					 sw->cb_data, NULL);
+			sw->seg_in_window = 0;
+			if (!sw->last_seg) {
+				/* fill window with more segments */
+				rc = sw_fill_window(sw);
+				sw->state = SW_STATE_WAIT_SEGACK;
+			} else {
+				/* end the transfer */
+				sw->state = SW_STATE_WAIT_ENDACK;
+				rc = sw_load_end(sw);
+			}
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		case NM_MT_LOAD_ABORT:
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_ABORT, mb,
+					 sw->cb_data, NULL);
+			break;
+		}
+		break;
+	case SW_STATE_WAIT_ENDACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_END_ACK:
+			sw_close_file(sw);
+			DEBUGP(DNM, "Software Load End (BTS %u)\n",
+				sw->bts->nr);
+			sw->state = SW_STATE_NONE;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_END_ACK, mb,
+					 sw->cb_data, NULL);
+			rc = 0;
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		case NM_MT_LOAD_END_NACK:
+			if (sw->forced) {
+				DEBUGP(DNM, "FORCED: Ignoring Software Load"
+					"End NACK\n");
+				sw->state = SW_STATE_NONE;
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_END_ACK, mb,
+						 sw->cb_data, NULL);
+			} else {
+				DEBUGP(DNM, "Software Load End NACK\n");
+				/* FIXME: cause */
+				sw->state = SW_STATE_ERROR;
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_END_NACK, mb,
+						 sw->cb_data, NULL);
+			}
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		}
+	case SW_STATE_WAIT_ACTACK:
+		switch (foh->msg_type) {
+		case NM_MT_ACTIVATE_SW_ACK:
+			/* we're done */
+			DEBUGP(DNM, "Activate Software DONE!\n");
+			sw->state = SW_STATE_NONE;
+			rc = 0;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_ACTIVATE_SW_ACK, mb,
+					 sw->cb_data, NULL);
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		case NM_MT_ACTIVATE_SW_NACK:
+			DEBUGP(DNM, "Activate Software NACK\n");
+			/* FIXME: cause */
+			sw->state = SW_STATE_ERROR;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_ACTIVATE_SW_NACK, mb,
+					 sw->cb_data, NULL);
+			abis_nm_queue_send_next(sign_link->trx->bts);
+			break;
+		}
+	case SW_STATE_NONE:
+		switch (foh->msg_type) {
+		case NM_MT_ACTIVATE_SW_ACK:
+			rc = 0;
+			break;
+		}
+		break;
+	case SW_STATE_ERROR:
+		break;
+	}
+
+	if (rc)
+		DEBUGP(DNM, "unexpected NM MT 0x%02x in state %u -> %u\n",
+			foh->msg_type, old_state, sw->state);
+
+	return rc;
+}
+
+/* Load the specified software into the BTS */
+int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
+			  uint8_t win_size, int forced,
+			  gsm_cbfn *cbfn, void *cb_data)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	int rc;
+
+	DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n",
+		bts->nr, fname);
+
+	if (sw->state != SW_STATE_NONE)
+		return -EBUSY;
+
+	sw->bts = bts;
+	sw->trx_nr = trx_nr;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		sw->obj_class = NM_OC_SITE_MANAGER;
+		sw->obj_instance[0] = 0xff;
+		sw->obj_instance[1] = 0xff;
+		sw->obj_instance[2] = 0xff;
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		sw->obj_class = NM_OC_BASEB_TRANSC;
+		sw->obj_instance[0] = sw->bts->nr;
+		sw->obj_instance[1] = sw->trx_nr;
+		sw->obj_instance[2] = 0xff;
+		break;
+	case GSM_BTS_TYPE_UNKNOWN:
+	default:
+		LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
+		return -1;
+		break;
+	}
+	sw->window_size = win_size;
+	sw->state = SW_STATE_WAIT_INITACK;
+	sw->cbfn = cbfn;
+	sw->cb_data = cb_data;
+	sw->forced = forced;
+
+	rc = sw_open_file(sw, fname);
+	if (rc < 0) {
+		sw->state = SW_STATE_NONE;
+		return rc;
+	}
+
+	return sw_load_init(sw);
+}
+
+int abis_nm_software_load_status(struct gsm_bts *bts)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	struct stat st;
+	int rc, percent;
+
+	rc = fstat(sw->fd, &st);
+	if (rc < 0) {
+		perror("ERROR during stat");
+		return rc;
+	}
+
+	if (sw->stream)
+		percent = (ftell(sw->stream) * 100) / st.st_size;
+	else
+		percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size;
+	return percent;
+}
+
+/* Activate the specified software into the BTS */
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+			      gsm_cbfn *cbfn, void *cb_data)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	int rc;
+
+	DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n",
+		bts->nr, fname);
+
+	if (sw->state != SW_STATE_NONE)
+		return -EBUSY;
+
+	sw->bts = bts;
+	sw->obj_class = NM_OC_SITE_MANAGER;
+	sw->obj_instance[0] = 0xff;
+	sw->obj_instance[1] = 0xff;
+	sw->obj_instance[2] = 0xff;
+	sw->state = SW_STATE_WAIT_ACTACK;
+	sw->cbfn = cbfn;
+	sw->cb_data = cb_data;
+
+	/* Open the file in order to fill some sw struct members */
+	rc = sw_open_file(sw, fname);
+	if (rc < 0) {
+		sw->state = SW_STATE_NONE;
+		return rc;
+	}
+	sw_close_file(sw);
+
+	return sw_activate(sw);
+}
+
+static void fill_nm_channel(struct abis_nm_channel *ch, uint8_t bts_port,
+		       uint8_t ts_nr, uint8_t subslot_nr)
+{
+	ch->attrib = NM_ATT_ABIS_CHANNEL;
+	ch->bts_port = bts_port;
+	ch->timeslot = ts_nr;
+	ch->subslot = subslot_nr;	
+}
+
+int abis_nm_establish_tei(struct gsm_bts *bts, uint8_t trx_nr,
+			  uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot,
+			  uint8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	uint8_t len = sizeof(*ch) + 2;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER,
+			bts->bts_nr, trx_nr, 0xff);
+	
+	msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+			   uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot)
+{
+	struct gsm_bts *bts = trx->bts;
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN,
+			NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff);
+	
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+			   struct abis_nm_abis_channel *chan)
+{
+}
+#endif
+
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+			   uint8_t e1_port, uint8_t e1_timeslot,
+			   uint8_t e1_subslot)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF,
+			NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n",
+		gsm_ts_name(ts),
+		e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+			   struct abis_nm_abis_channel *chan,
+			   uint8_t subchan)
+{
+}
+#endif
+
+/* 3GPP TS 52.021 § 8.11.1 */
+int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+		     const uint8_t *attr, uint8_t attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+
+	if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
+		LOGPC(DNM, LOGL_NOTICE, "Getting attributes from BTS%d type %s is not supported.\n",
+		      bts->nr, btstype2str(bts->type));
+		return -EINVAL;
+	}
+
+	DEBUGP(DNM, "Get Attr (bts=%d)\n", bts->nr);
+
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_GET_ATTR, obj_class,
+			bts_nr, trx_nr, ts_nr);
+	msgb_tl16v_put(msg, NM_ATT_LIST_REQ_ATTR, attr_len, attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.6.1 */
+int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t *cur;
+
+	DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.6.2 */
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t *cur;
+
+	DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER,
+			trx->bts->bts_nr, trx->nr, 0xff);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_update_max_power_red(struct gsm_bts_trx *trx)
+{
+	uint8_t attr[] = { NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2 };
+	return abis_nm_set_radio_attr(trx, attr, ARRAY_SIZE(attr));
+}
+
+static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
+			const char **reason)
+{
+	int i;
+
+	*reason = "Reason unknown";
+
+	/* As it turns out, the BS-11 has some very peculiar restrictions
+	 * on the channel combinations it allows */
+	switch (ts->trx->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		switch (chan_comb) {
+		case NM_CHANC_TCHHalf:
+		case NM_CHANC_TCHHalf2:
+		case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+			/* not supported */
+			*reason = "TCH/H is not supported.";
+			return -EINVAL;
+		case NM_CHANC_SDCCH:
+			/* only one SDCCH/8 per TRX */
+			for (i = 0; i < TRX_NR_TS; i++) {
+				if (i == ts->nr)
+					continue;
+				if (ts->trx->ts[i].nm_chan_comb ==
+				    NM_CHANC_SDCCH) {
+					*reason = "Only one SDCCH/8 per TRX allowed.";
+					return -EINVAL;
+				}
+			}
+			/* not allowed for TS0 of BCCH-TRX */
+			if (ts->trx == ts->trx->bts->c0 &&
+			    ts->nr == 0) {
+				*reason = "SDCCH/8 must be on TS0.";
+				return -EINVAL;
+			}
+
+			/* not on the same TRX that has a BCCH+SDCCH4
+			 * combination */
+			if (ts->trx != ts->trx->bts->c0 &&
+			    (ts->trx->ts[0].nm_chan_comb == 5 ||
+			     ts->trx->ts[0].nm_chan_comb == 8)) {
+				*reason = "SDCCH/8 and BCCH must be on the same TRX.";
+				return -EINVAL;
+			}
+			break;
+		case NM_CHANC_mainBCCH:
+		case NM_CHANC_BCCHComb:
+			/* allowed only for TS0 of C0 */
+			if (ts->trx != ts->trx->bts->c0 || ts->nr != 0) {
+				*reason = "Main BCCH must be on TS0.";
+				return -EINVAL;
+			}
+			break;
+		case NM_CHANC_BCCH:
+			/* allowed only for TS 2/4/6 of C0 */
+			if (ts->trx != ts->trx->bts->c0) {
+				*reason = "BCCH must be on C0.";
+				return -EINVAL;
+			}
+			if (ts->nr != 2 && ts->nr != 4 && ts->nr != 6) {
+				*reason = "BCCH must be on TS 2/4/6.";
+				return -EINVAL;
+			}
+			break;
+		case 8: /* this is not like 08.58, but in fact
+			 * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */
+			/* FIXME: only one CBCH allowed per cell */
+			break;
+		}
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		switch (ts->nr) {
+		case 0:
+			if (ts->trx->nr == 0) {
+				/* only on TRX0 */
+				switch (chan_comb) {
+				case NM_CHANC_BCCH:
+				case NM_CHANC_mainBCCH:
+				case NM_CHANC_BCCHComb:
+					return 0;
+					break;
+				default:
+					*reason = "TS0 of TRX0 must carry a BCCH.";
+					return -EINVAL;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					*reason = "TS0 must carry a TCH/F or TCH/H.";
+					return -EINVAL;
+				}
+			}
+			break;
+		case 1:
+			if (ts->trx->nr == 0) {
+				switch (chan_comb) {
+				case NM_CHANC_SDCCH_CBCH:
+					if (ts->trx->ts[0].nm_chan_comb ==
+					    NM_CHANC_mainBCCH)
+						return 0;
+					*reason = "TS0 must be the main BCCH for CBCH.";
+					return -EINVAL;
+				case NM_CHANC_SDCCH:
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_PDCH:
+				case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+					return 0;
+				default:
+					*reason = "TS1 must carry a CBCH, SDCCH or TCH.";
+					return -EINVAL;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_SDCCH:
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					*reason = "TS1 must carry a SDCCH or TCH.";
+					return -EINVAL;
+				}
+			}
+			break;
+		case 2:
+		case 3:
+		case 4:
+		case 5:
+		case 6:
+		case 7:
+			switch (chan_comb) {
+			case NM_CHANC_TCHFull:
+			case NM_CHANC_TCHHalf:
+			case NM_CHANC_IPAC_TCHFull_TCHHalf:
+				return 0;
+			case NM_CHANC_IPAC_PDCH:
+			case NM_CHANC_IPAC_TCHFull_PDCH:
+			case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+				if (ts->trx->nr == 0)
+					return 0;
+				else {
+					*reason = "PDCH must be on TRX0.";
+					return -EINVAL;
+				}
+			}
+			break;
+		}
+		*reason = "Unknown combination";
+		return -EINVAL;
+	case GSM_BTS_TYPE_OSMOBTS:
+		/* no known restrictions */
+		return 0;
+	default:
+		/* unknown BTS type */
+		return 0;
+	}
+	return 0;
+}
+
+/* Chapter 8.6.3 */
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	uint8_t zero = 0x00;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t len = 2 + 2;
+	const char *reason = NULL;
+
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		len += 4 + 2 + 2 + 3;
+
+	DEBUGP(DNM, "Set Chan Attr %s\n", gsm_ts_name(ts));
+	if (verify_chan_comb(ts, chan_comb, &reason) < 0) {
+		msgb_free(msg);
+		LOGP(DNM, LOGL_ERROR,
+			"Invalid Channel Combination %d on %s. Reason: %s\n",
+			chan_comb, gsm_ts_name(ts), reason);
+		return -EINVAL;
+	}
+	ts->nm_chan_comb = chan_comb;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR,
+			NM_OC_CHANNEL, bts->bts_nr,
+			ts->trx->nr, ts->nr);
+	msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb);
+	if (ts->hopping.enabled) {
+		unsigned int i;
+		uint8_t *len;
+
+		msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn);
+		msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio);
+
+		/* build the ARFCN list */
+		msgb_put_u8(msg, NM_ATT_ARFCN_LIST);
+		len = msgb_put(msg, 1);
+		*len = 0;
+		for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+			if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) {
+				msgb_put_u16(msg, i);
+				/* At least BS-11 wants a TLV16 here */
+				if (bts->type == GSM_BTS_TYPE_BS11)
+					*len += 1;
+				else
+					*len += sizeof(uint16_t);
+			}
+		}
+	}
+	msgb_tv_put(msg, NM_ATT_TSC, gsm_ts_tsc(ts));	/* training sequence */
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		msgb_tlv_put(msg, 0x59, 1, &zero);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1,
+			uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t msgtype = NM_MT_SW_ACT_REQ_ACK;
+	uint8_t len = att_len;
+
+	if (nack) {
+		len += 2;
+		msgtype = NM_MT_SW_ACT_REQ_NACK;
+	}
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3);
+
+	if (attr) {
+		uint8_t *ptr = msgb_put(msg, att_len);
+		memcpy(ptr, attr, att_len);
+	}
+	if (nack)
+		msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP);
+
+	return abis_nm_sendmsg_direct(bts, msg);
+}
+
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *rawmsg)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	uint8_t *data;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+	fill_om_hdr(oh, len);
+	data = msgb_put(msg, len);
+	memcpy(data, rawmsg, len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Siemens specific commands */
+static int __simple_cmd(struct gsm_bts *bts, uint8_t msg_type)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.9.2 */
+int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2)
+{
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	foh = fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2);
+
+	abis_nm_debugp_foh(DNM, foh);
+	DEBUGPC(DNM, "Sending OPSTART\n");
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.8.5 */
+int abis_nm_chg_adm_state(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0,
+			  uint8_t i1, uint8_t i2, enum abis_nm_adm_state adm_state)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2);
+	msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0,
+			    uint8_t e1_port1, uint8_t ts1)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t *attr;
+
+	DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n",
+		e1_port0, ts0, e1_port1, ts1);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK,
+			NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00);
+
+	attr = msgb_put(msg, 3);
+	attr[0] = NM_ATT_MDROP_LINK;
+	attr[1] = e1_port0;
+	attr[2] = ts0;
+
+	attr = msgb_put(msg, 3);
+	attr[0] = NM_ATT_MDROP_NEXT;
+	attr[1] = e1_port1;
+	attr[2] = ts1;
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.7.1 */
+int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class,
+			 uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+			 uint8_t test_nr, uint8_t auton_report, struct msgb *msg)
+{
+	struct abis_om_hdr *oh;
+
+	DEBUGP(DNM, "PEFORM TEST %s\n", abis_nm_test_name(test_nr));
+
+	if (!msg)
+		msg = nm_msgb_alloc();
+
+	msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report);
+	msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr);
+	oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST,
+			obj_class, bts_nr, trx_nr, ts_nr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_event_reports(struct gsm_bts *bts, int on)
+{
+	if (on == 0)
+		return __simple_cmd(bts, NM_MT_STOP_EVENT_REP);
+	else
+		return __simple_cmd(bts, NM_MT_REST_EVENT_REP);
+}
+
+/* Siemens (or BS-11) specific commands */
+
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect)
+{
+	if (reconnect == 0)
+		return __simple_cmd(bts, NM_MT_BS11_DISCONNECT);
+	else
+		return __simple_cmd(bts, NM_MT_BS11_RECONNECT);
+}
+
+int abis_nm_bs11_restart(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_RESTART);
+}
+
+
+struct bs11_date_time {
+	uint16_t	year;
+	uint8_t	month;
+	uint8_t	day;
+	uint8_t	hour;
+	uint8_t	min;
+	uint8_t	sec;
+} __attribute__((packed));
+
+
+void get_bs11_date_time(struct bs11_date_time *aet)
+{
+	time_t t;
+	struct tm *tm;
+
+	t = time(NULL);
+	tm = localtime(&t);
+	aet->sec = tm->tm_sec;
+	aet->min = tm->tm_min;
+	aet->hour = tm->tm_hour;
+	aet->day = tm->tm_mday;
+	aet->month = tm->tm_mon;
+	aet->year = htons(1900 + tm->tm_year);
+}
+
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE);
+}
+
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin)
+{
+	if (begin)
+		return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX);
+	else
+		return __simple_cmd(bts, NM_MT_BS11_END_DB_TX);
+}
+
+int abis_nm_bs11_create_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, uint8_t idx,
+				uint8_t attr_len, const uint8_t *attr)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t *cur;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ,
+			NM_OC_BS11, type, 0, idx);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, uint8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ,
+			NM_OC_BS11, type, 0, idx);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t zero = 0x00;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ,
+			NM_OC_BS11_ENVABTSE, 0, idx, 0xff);
+	msgb_tlv_put(msg, 0x99, 1, &zero);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT,
+			idx, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT,
+			idx, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+static const uint8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL };
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* like abis_nm_conn_terr_traf + set_tei */
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, uint8_t e1_port,
+			  uint8_t e1_timeslot, uint8_t e1_subslot,
+			  uint8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR,
+			NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+	msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR,
+			NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+	msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t attr = NM_ATT_BS11_TXPWR;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t attr[] = { NM_ATT_BS11_PLL_MODE };
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY,
+			    NM_ATT_BS11_CCLK_TYPE };
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+	return abis_nm_sendmsg(bts, msg);
+
+}
+
+//static const uint8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 };
+
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on)
+{
+	return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on);
+}
+
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on)
+{
+	return abis_nm_bs11_logon(bts, 0x03, "FIELD  ", on);
+}
+
+int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time bdt;
+
+	get_bs11_date_time(&bdt);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	if (on) {
+		uint8_t len = 3*2 + sizeof(bdt)
+				+ 1 + strlen(name);
+		fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON,
+				NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME,
+			     sizeof(bdt), (uint8_t *) &bdt);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV,
+			     1, &level);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME,
+			     strlen(name), (uint8_t *)name);
+	} else {
+		fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF,
+				NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+	}
+	
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+
+	if (strlen(password) != 10)
+		return -EINVAL;
+
+ 	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR,
+			NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const uint8_t *)password);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+	uint8_t tlv_value;
+	
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+			BS11_OBJ_LI, 0x00, 0x00);
+
+	if (locked)
+		tlv_value = BS11_LI_PLL_LOCKED;
+	else
+		tlv_value = BS11_LI_PLL_STANDALONE;
+	
+	msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value);
+	
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Set the calibration value of the PLL (work value/set value)
+ * It depends on the login which one is changed */
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+	uint8_t tlv_value[2];
+
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+			BS11_OBJ_TRX1, 0x00, 0x00);
+
+	tlv_value[0] = value>>8;
+	tlv_value[1] = value&0xff;
+
+	msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_state(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_GET_STATE);
+}
+
+/* BS11 SWL */
+
+void *tall_fle_ctx;
+
+struct abis_nm_bs11_sw {
+	struct gsm_bts *bts;
+	char swl_fname[PATH_MAX];
+	uint8_t win_size;
+	int forced;
+	struct llist_head file_list;
+	gsm_cbfn *user_cb;	/* specified by the user */
+};
+static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw;
+
+struct file_list_entry {
+	struct llist_head list;
+	char fname[PATH_MAX];
+};
+
+struct file_list_entry *fl_dequeue(struct llist_head *queue)
+{
+	struct llist_head *lh;
+
+	if (llist_empty(queue))
+		return NULL;
+
+	lh = queue->next;
+	llist_del(lh);
+	
+	return llist_entry(lh, struct file_list_entry, list);
+}
+
+static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw)
+{
+	char linebuf[255];
+	struct llist_head *lh, *lh2;
+	FILE *swl;
+	int rc = 0;
+
+	swl = fopen(bs11_sw->swl_fname, "r");
+	if (!swl)
+		return -ENODEV;
+
+	/* zero the stale file list, if any */
+	llist_for_each_safe(lh, lh2, &bs11_sw->file_list) {
+		llist_del(lh);
+		talloc_free(lh);
+	}
+
+	while (fgets(linebuf, sizeof(linebuf), swl)) {
+		char file_id[12+1];
+		char file_version[80+1];
+		struct file_list_entry *fle;
+		static char dir[PATH_MAX];
+
+		if (strlen(linebuf) < 4)
+			continue;
+	
+		rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version);
+		if (rc < 0) {
+			perror("ERR parsing SWL file");
+			rc = -EINVAL;
+			goto out;
+		}
+		if (rc < 2)
+			continue;
+
+		fle = talloc_zero(tall_fle_ctx, struct file_list_entry);
+		if (!fle) {
+			rc = -ENOMEM;
+			goto out;
+		}
+
+		/* construct new filename */
+		osmo_strlcpy(dir, bs11_sw->swl_fname, sizeof(dir));
+		strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1);
+		strcat(fle->fname, "/");
+		strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname));
+		
+		llist_add_tail(&fle->list, &bs11_sw->file_list);
+	}
+
+out:
+	fclose(swl);
+	return rc;
+}
+
+/* bs11 swload specific callback, passed to abis_nm core swload */
+static int bs11_swload_cbfn(unsigned int hook, unsigned int event,
+			    struct msgb *msg, void *data, void *param)
+{
+	struct abis_nm_bs11_sw *bs11_sw = data;
+	struct file_list_entry *fle;
+	int rc = 0;
+
+	switch (event) {
+	case NM_MT_LOAD_END_ACK:
+		fle = fl_dequeue(&bs11_sw->file_list);
+		if (fle) {
+			/* start download the next file of our file list */
+			rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname,
+						   bs11_sw->win_size,
+						   bs11_sw->forced,
+						   &bs11_swload_cbfn, bs11_sw);
+			talloc_free(fle);
+		} else {
+			/* activate the SWL */
+			rc = abis_nm_software_activate(bs11_sw->bts,
+							bs11_sw->swl_fname,
+							bs11_swload_cbfn,
+							bs11_sw);
+		}
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+	case NM_MT_LOAD_END_NACK:
+	case NM_MT_LOAD_INIT_ACK:
+	case NM_MT_LOAD_INIT_NACK:
+	case NM_MT_ACTIVATE_SW_NACK:
+	case NM_MT_ACTIVATE_SW_ACK:
+	default:
+		/* fallthrough to the user callback */
+		if (bs11_sw->user_cb)
+			rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL);
+		break;
+	}
+
+	return rc;
+}
+
+/* Siemens provides a SWL file that is a mere listing of all the other
+ * files that are part of a software release.  We need to upload first
+ * the list file, and then each file that is listed in the list file */
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+			  uint8_t win_size, int forced, gsm_cbfn *cbfn)
+{
+	struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw;
+	struct file_list_entry *fle;
+	int rc = 0;
+
+	INIT_LLIST_HEAD(&bs11_sw->file_list);
+	bs11_sw->bts = bts;
+	bs11_sw->win_size = win_size;
+	bs11_sw->user_cb = cbfn;
+	bs11_sw->forced = forced;
+
+	osmo_strlcpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname));
+	rc = bs11_read_swl_file(bs11_sw);
+	if (rc < 0)
+		return rc;
+
+	/* dequeue next item in file list */
+	fle = fl_dequeue(&bs11_sw->file_list);
+	if (!fle)
+		return -EINVAL;
+
+	/* start download the next file of our file list */
+	rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced,
+				   bs11_swload_cbfn, bs11_sw);
+	talloc_free(fle);
+	return rc;
+}
+
+#if 0
+static uint8_t req_attr_btse[] = {
+	NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION,
+	NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV,
+	NM_ATT_BS11_LMT_USER_NAME,
+
+	0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME,
+
+	NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY,
+
+	NM_ATT_BS11_SW_LOAD_STORED };
+
+static uint8_t req_attr_btsm[] = {
+	NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME,
+	NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID,
+	NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG,
+	NM_ATT_SW_DESCR, NM_ATT_GET_ARI };
+#endif
+	
+static uint8_t req_attr[] = {
+	NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE,
+	0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO,
+	0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL };
+
+int abis_nm_bs11_get_serno(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	/* SiemensHW CCTRL object */
+	fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11,
+			0x03, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time aet;
+
+	get_bs11_date_time(&aet);
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	/* SiemensHW CCTRL object */
+	fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+	msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (uint8_t *) &aet);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	uint8_t attr = NM_ATT_BS11_LINE_CFG;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11_BPORT, bport, 0xff, 0x02);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time aet;
+
+	get_bs11_date_time(&aet);
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT,
+			bport, 0xff, 0x02);
+	msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* ip.access nanoBTS specific commands */
+static const char ipaccess_magic[] = "com.ipaccess";
+
+
+static int abis_nm_rx_ipacc(struct msgb *msg)
+{
+	struct in_addr addr;
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	struct abis_om_fom_hdr *foh;
+	uint8_t idstrlen = oh->data[0];
+	struct tlv_parsed tp;
+	struct ipacc_ack_signal_data signal;
+	struct e1inp_sign_link *sign_link = msg->dst;
+
+	if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) {
+		LOGP(DNM, LOGL_ERROR, "id string is not com.ipaccess !?!\n");
+		return -EINVAL;
+	}
+
+	foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen);
+	abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	abis_nm_debugp_foh(DNM, foh);
+
+	DEBUGPC(DNM, "IPACCESS(0x%02x): ", foh->msg_type);
+
+	switch (foh->msg_type) {
+	case NM_MT_IPACC_RSL_CONNECT_ACK:
+		DEBUGPC(DNM, "RSL CONNECT ACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) {
+			memcpy(&addr,
+				TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr));
+
+			DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr));
+		}
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT))
+			DEBUGPC(DNM, "PORT=%u ",
+				ntohs(*((uint16_t *)
+					TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT))));
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID))
+			DEBUGPC(DNM, "STREAM=0x%02x ",
+					*TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID));
+		DEBUGPC(DNM, "\n");
+		break;
+	case NM_MT_IPACC_RSL_CONNECT_NACK:
+		LOGP(DNM, LOGL_ERROR, "RSL CONNECT NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		DEBUGPC(DNM, "SET NVATTR ACK\n");
+		/* FIXME: decode and show the actual attributes */
+		break;
+	case NM_MT_IPACC_SET_NVATTR_NACK:
+		LOGP(DNM, LOGL_ERROR, "SET NVATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_GET_NVATTR_ACK:
+		DEBUGPC(DNM, "GET NVATTR ACK\n");
+		/* FIXME: decode and show the actual attributes */
+		break;
+	case NM_MT_IPACC_GET_NVATTR_NACK:
+		LOGPC(DNM, LOGL_ERROR, "GET NVATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_SET_ATTR_ACK:
+		DEBUGPC(DNM, "SET ATTR ACK\n");
+		break;
+	case NM_MT_IPACC_SET_ATTR_NACK:
+		LOGPC(DNM, LOGL_ERROR, "SET ATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	default:
+		DEBUGPC(DNM, "unknown\n");
+		break;
+	}
+
+	/* signal handling */
+	switch  (foh->msg_type) {
+	case NM_MT_IPACC_RSL_CONNECT_NACK:
+	case NM_MT_IPACC_SET_NVATTR_NACK:
+	case NM_MT_IPACC_GET_NVATTR_NACK:
+		signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
+		signal.msg_type = foh->msg_type;
+		osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal);
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
+		signal.msg_type = foh->msg_type;
+		osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* send an ip-access manufacturer specific message */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type,
+			 uint8_t obj_class, uint8_t bts_nr,
+			 uint8_t trx_nr, uint8_t ts_nr,
+			 uint8_t *attr, int attr_len)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	uint8_t *data;
+
+	/* construct the 12.21 OM header, observe the erroneous length */
+	oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+	fill_om_hdr(oh, sizeof(*foh) + attr_len);
+	oh->mdisc = ABIS_OM_MDISC_MANUF;
+
+	/* add the ip.access magic */
+	data = msgb_put(msg, sizeof(ipaccess_magic)+1);
+	*data++ = sizeof(ipaccess_magic);
+	memcpy(data, ipaccess_magic, sizeof(ipaccess_magic));
+
+	/* fill the 12.21 FOM header */
+	foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh));
+	foh->msg_type = msg_type;
+	foh->obj_class = obj_class;
+	foh->obj_inst.bts_nr = bts_nr;
+	foh->obj_inst.trx_nr = trx_nr;
+	foh->obj_inst.ts_nr = ts_nr;
+
+	if (attr && attr_len) {
+		data = msgb_put(msg, attr_len);
+		memcpy(data, attr, attr_len);
+	}
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* set some attributes in NVRAM */
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr,
+				int attr_len)
+{
+	return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR,
+				    NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr,
+				    attr_len);
+}
+
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx,
+				 uint32_t ip, uint16_t port, uint8_t stream)
+{
+	struct in_addr ia;
+	uint8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0,
+			    NM_ATT_IPACC_DST_IP_PORT, 0, 0,
+			    NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 };
+
+	int attr_len = sizeof(attr);
+
+	ia.s_addr = htonl(ip);
+	attr[1] = stream;
+	attr[3] = port >> 8;
+	attr[4] = port & 0xff;
+	*(uint32_t *)(attr+6) = ia.s_addr;
+
+	/* if ip == 0, we use the default IP */
+	if (ip == 0)
+		attr_len -= 5;
+
+	LOGP(DNM, LOGL_INFO, "(bts=%d,trx=%d) IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+		trx->bts->nr, trx->nr, inet_ntoa(ia), port, stream);
+
+	return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT,
+				    NM_OC_BASEB_TRANSC, trx->bts->bts_nr,
+				    trx->nr, 0xff, attr, attr_len);
+}
+
+/* restart / reboot an ip.access nanoBTS */
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_IPACC_RESTART, NM_OC_BASEB_TRANSC,
+			trx->bts->nr, trx->nr, 0xff);
+
+	return abis_nm_sendmsg_direct(trx->bts, msg);
+}
+
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, uint8_t obj_class,
+				uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+				uint8_t *attr, uint8_t attr_len)
+{
+	return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR,
+				    obj_class, bts_nr, trx_nr, ts_nr,
+				     attr, attr_len);
+}
+
+void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts)
+{
+	struct gsm48_ra_id *_buf = (struct gsm48_ra_id*)buf;
+	uint16_t ci = htons(bts->cell_identity);
+	/* we simply reuse the GSM48 function and write the Cell ID over the position where the RAC
+	 * starts */
+	gsm48_ra_id_by_bts(_buf, bts);
+	memcpy(&_buf->rac, &ci, sizeof(ci));
+}
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason)
+{
+	uint8_t new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+
+
+	if (!trx->bts || !trx->bts->oml_link) {
+		/* Set initial state which will be sent when BTS connects. */
+		trx->mo.nm_state.administrative = new_state;
+		return;
+	}
+
+	LOGP(DNM, LOGL_NOTICE, "(bts=%d,trx=%d) Requesting administrative state change %s -> %s [%s]\n",
+	     trx->bts->nr, trx->nr,
+	     get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative),
+	     get_value_string(abis_nm_adm_state_names, new_state), reason);
+
+	abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
+			      trx->bts->bts_nr, trx->nr, 0xff,
+			      new_state);
+}
+
+static const struct value_string ipacc_testres_names[] = {
+	{ NM_IPACC_TESTRES_SUCCESS,	"SUCCESS" },
+	{ NM_IPACC_TESTRES_TIMEOUT,	"TIMEOUT" },
+	{ NM_IPACC_TESTRES_NO_CHANS,	"NO CHANNELS" },
+	{ NM_IPACC_TESTRES_PARTIAL,	"PARTIAL" },
+	{ NM_IPACC_TESTRES_STOPPED,	"STOPPED" },
+	{ 0,				NULL }
+};
+
+const char *ipacc_testres_name(uint8_t res)
+{
+	return get_value_string(ipacc_testres_names, res);
+}
+
+void ipac_parse_cgi(struct osmo_cell_global_id *cid, const uint8_t *buf)
+{
+	osmo_plmn_from_bcd(buf, &cid->lai.plmn);
+	cid->lai.lac = ntohs(*((uint16_t *)&buf[3]));
+	cid->cell_identity = ntohs(*((uint16_t *)&buf[5]));
+}
+
+/* parse BCCH information IEI from wire format to struct ipac_bcch_info */
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf)
+{
+	uint8_t *cur = buf;
+	uint16_t len __attribute__((unused));
+
+	memset(binf, 0, sizeof(*binf));
+
+	if (cur[0] != NM_IPAC_EIE_BCCH_INFO)
+		return -EINVAL;
+	cur++;
+
+	len = ntohs(*(uint16_t *)cur);
+	cur += 2;
+
+	binf->info_type = ntohs(*(uint16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+		binf->freq_qual = *cur >> 2;
+
+	binf->arfcn = (*cur++ & 3) << 8;
+	binf->arfcn |= *cur++;
+
+	if (binf->info_type & IPAC_BINF_RXLEV)
+		binf->rx_lev = *cur & 0x3f;
+	cur++;
+
+	if (binf->info_type & IPAC_BINF_RXQUAL)
+		binf->rx_qual = *cur & 0x7;
+	cur++;
+
+	if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+		binf->freq_err = ntohs(*(uint16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_OFFSET)
+		binf->frame_offset = ntohs(*(uint16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET)
+		binf->frame_nr_offset = ntohl(*(uint32_t *)cur);
+	cur += 4;
+
+#if 0
+	/* Somehow this is not set correctly */
+	if (binf->info_type & IPAC_BINF_BSIC)
+#endif
+		binf->bsic = *cur & 0x3f;
+	cur++;
+
+	ipac_parse_cgi(&binf->cgi, cur);
+	cur += 7;
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) {
+		memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2));
+		cur += sizeof(binf->ba_list_si2);
+	}
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+		memcpy(binf->ba_list_si2bis, cur,
+			sizeof(binf->ba_list_si2bis));
+		cur += sizeof(binf->ba_list_si2bis);
+	}
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+		memcpy(binf->ba_list_si2ter, cur,
+			sizeof(binf->ba_list_si2ter));
+		cur += sizeof(binf->ba_list_si2ter);
+	}
+
+	return 0;
+}
+
+void abis_nm_clear_queue(struct gsm_bts *bts)
+{
+	struct msgb *msg;
+
+	while (!llist_empty(&bts->abis_queue)) {
+		msg = msgb_dequeue(&bts->abis_queue);
+		msgb_free(msg);
+	}
+
+	bts->abis_nm_pend = 0;
+}
diff --git a/openbsc/src/libbsc/abis_nm_ipaccess.c b/openbsc/src/libbsc/abis_nm_ipaccess.c
new file mode 100644
index 0000000..b822538
--- /dev/null
+++ b/openbsc/src/libbsc/abis_nm_ipaccess.c
@@ -0,0 +1,87 @@
+/* GSM Network Management (OML) messages on the A-bis interface 
+ * Extensions for the ip.access A-bis over IP protocol*/
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* A list of all the 'embedded' attributes of ip.access */
+enum ipa_embedded_att {
+	IPA_ATT_ARFCN_WHITELIST		= 0x01,
+	IPA_ATT_ARFCN_BLACKLIST		= 0x02,
+	IPA_ATT_FREQ_ERR_LIST		= 0x03,
+	IPA_ATT_CHAN_USAGE_LIST		= 0x04,
+	IPA_ATT_BCCH_INF_TYPE		= 0x05,
+	IPA_ATT_BCCH_INF		= 0x06,
+	IPA_ATT_CONFIG			= 0x07,
+	IPA_ATT_RESULT_DETAILS		= 0x08,
+	IPA_ATT_RXLEV_THRESH		= 0x09,
+	IPA_ATT_FREQ_SYNC_OPT		= 0x0a,
+	IPA_ATT_MAC_ADDR		= 0x0b,
+	IPA_ATT_HW_SW_COMPAT_NR		= 0x0c,
+	IPA_ATT_MANUF_SER_NR		= 0x0d,
+	IPA_ATT_OEM_ID			= 0x0e,
+	IPA_ATT_DATETIME_MANUF		= 0x0f,
+	IPA_ATT_DATETIME_CALIB		= 0x10,
+	IPA_ATT_BEACON_INF		= 0x11,
+	IPA_ATT_FREQ_ERR		= 0x12,
+	IPA_ATT_SNMP_COMM_STRING	= 0x13,
+	IPA_ATT_SNMP_TRAP_ADDR		= 0x14,
+	IPA_ATT_SNMP_TRAP_PORT		= 0x15,
+	IPA_ATT_SNMP_MAN_ADDR		= 0x16,
+	IPA_ATT_SNMP_SYS_CONTACT	= 0x17,
+	IPA_ATT_FACTORY_ID		= 0x18,
+	IPA_ATT_FACTORY_SERIAL		= 0x19,
+	IPA_ATT_LOGGED_EVT_IND		= 0x1a,
+	IPA_ATT_LOCAL_ADD_TEXT		= 0x1b,
+	IPA_ATT_FREQ_BANDS		= 0x1c,
+	IPA_ATT_MAX_TA			= 0x1d,
+	IPA_ATT_CIPH_ALG		= 0x1e,
+	IPA_ATT_CHAN_TYPES		= 0x1f,
+	IPA_ATT_CHAN_MODES		= 0x20,
+	IPA_ATT_GPRS_CODING_SCHEMES	= 0x21,
+	IPA_ATT_RTP_FEATURES		= 0x22,
+	IPA_ATT_RSL_FEATURES		= 0x23,
+	IPA_ATT_BTS_HW_CLASS		= 0x24,
+	IPA_ATT_BTS_ID			= 0x25,
+	IPA_ATT_BCAST_L2_MSG		= 0x26,
+};
+
+/* append an ip.access channel list to the given msgb */
+static int ipa_chan_list_append(struct msgb *msg, uint8_t ie,
+				uint16_t *arfcns, int arfcn_count)
+{
+	int i;
+	uint8_t *u8;
+	uint16_t *u16;
+
+	/* tag */
+	u8 = msgb_push(msg, 1);
+	*u8 = ie;
+
+	/* length in octets */
+	u16 = msgb_push(msg, 2);
+	*u16 = htons(arfcn_count * 2);
+
+	for (i = 0; i < arfcn_count; i++) {
+		u16 = msgb_push(msg, 2);
+		*u16 = htons(arfcns[i]);
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/libbsc/abis_nm_vty.c b/openbsc/src/libbsc/abis_nm_vty.c
new file mode 100644
index 0000000..4dae212
--- /dev/null
+++ b/openbsc/src/libbsc/abis_nm_vty.c
@@ -0,0 +1,190 @@
+/* VTY interface for A-bis OML (Netowrk Management) */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/gsm/abis_nm.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct cmd_node oml_node = {
+	OML_NODE,
+	"%s(oml)# ",
+	1,
+};
+
+struct oml_node_state {
+	struct gsm_bts *bts;
+	uint8_t obj_class;
+	uint8_t obj_inst[3];
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+/* FIXME: auto-generate those strings from the value_string lists */
+#define NM_OBJCLASS_VTY "(site-manager|bts|radio-carrier|baseband-transceiver|channel|adjc|handover|power-contorl|btse|rack|test|envabtse|bport|gprs-nse|gprs-cell|gprs-nsvc|siemenshw)"
+#define NM_OBJCLASS_VTY_HELP	"Site Manager Object\n"			\
+				"BTS Object\n"				\
+				"Radio Carrier Object\n"		\
+				"Baseband Transceiver Object\n"		\
+				"Channel (Timeslot) Object\n"		\
+				"Adjacent Object (Siemens)\n"		\
+				"Handover Object (Siemens)\n"		\
+				"Power Control Object (Siemens)\n"	\
+				"BTSE Object (Siemens)\n"		\
+				"Rack Object (Siemens)\n"		\
+				"Test Object (Siemens)\n"		\
+				"ENVABTSE Object (Siemens)\n"		\
+				"BPORT Object (Siemens)\n"		\
+				"GPRS NSE Object (ip.access/osmo-bts)\n"	\
+				"GPRS Cell Object (ip.acecss/osmo-bts)\n"	\
+				"GPRS NSVC Object (ip.acecss/osmo-bts)\n"	\
+				"SIEMENSHW Object (Siemens)\n"
+
+
+DEFUN(oml_class_inst, oml_class_inst_cmd,
+	"bts <0-255> oml class " NM_OBJCLASS_VTY
+					" instance <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OML managed objects\n"
+	"Object Class\n" 	NM_OBJCLASS_VTY_HELP
+	"Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]);
+	oms->obj_inst[0] = atoi(argv[2]);
+	oms->obj_inst[1] = atoi(argv[3]);
+	oms->obj_inst[2] = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OML_NODE;
+
+	return CMD_SUCCESS;
+
+}
+
+DEFUN(oml_classnum_inst, oml_classnum_inst_cmd,
+	"bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OML managed objects\n"
+	"Object Class\n" "Object Class\n"
+	"Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->obj_class = atoi(argv[1]);
+	oms->obj_inst[0] = atoi(argv[2]);
+	oms->obj_inst[1] = atoi(argv[3]);
+	oms->obj_inst[2] = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OML_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd,
+	"change-adm-state (locked|unlocked|shutdown|null)",
+	"Change the Administrative State\n"
+	"Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n")
+{
+	struct oml_node_state *oms = vty->index;
+	enum abis_nm_adm_state state;
+
+	state = get_string_value(abis_nm_adm_state_names, argv[0]);
+
+	abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0],
+			      oms->obj_inst[1], oms->obj_inst[2], state);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_opstart, oml_opstart_cmd,
+	"opstart", "Send an OPSTART message to the object")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0],
+			oms->obj_inst[1], oms->obj_inst[2]);
+
+	return CMD_SUCCESS;
+}
+
+int abis_nm_vty_init(void)
+{
+	install_element(ENABLE_NODE, &oml_class_inst_cmd);
+	install_element(ENABLE_NODE, &oml_classnum_inst_cmd);
+	install_node(&oml_node, dummy_config_write);
+
+	install_element(OML_NODE, &oml_chg_adm_state_cmd);
+	install_element(OML_NODE, &oml_opstart_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/libbsc/abis_om2000.c b/openbsc/src/libbsc/abis_om2000.c
new file mode 100644
index 0000000..82a14b2
--- /dev/null
+++ b/openbsc/src/libbsc/abis_om2000.c
@@ -0,0 +1,2776 @@
+/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface
+ * implemented based on protocol trace analysis, no formal documentation */
+
+/* (C) 2010-2011,2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/signal.h>
+#include <osmocom/abis/e1_input.h>
+
+/* FIXME: move to libosmocore */
+struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm,
+						   struct osmo_fsm_inst *parent,
+						   uint32_t parent_term_event,
+						   const char *id)
+{
+	struct osmo_fsm_inst *fi;
+
+	fi = osmo_fsm_inst_alloc(fsm, parent, NULL, parent->log_level,
+				 id ? id : parent->id);
+	if (!fi) {
+		/* indicate immediate termination to caller */
+		osmo_fsm_inst_dispatch(parent, parent_term_event, NULL);
+		return NULL;
+	}
+
+	LOGPFSM(fi, "is child of %s\n", osmo_fsm_inst_name(parent));
+
+	fi->proc.parent = parent;
+	fi->proc.parent_term_event = parent_term_event;
+	llist_add(&fi->proc.child, &parent->proc.children);
+
+	return fi;
+}
+
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+
+#define OM2K_TIMEOUT	10
+#define TRX_FSM_TIMEOUT	60
+#define BTS_FSM_TIMEOUT	60
+
+/* use following functions from abis_nm.c:
+	* om2k_msgb_alloc()
+	* abis_om2k_sendmsg()
+ */
+
+struct abis_om2k_hdr {
+	struct abis_om_hdr om;
+	uint16_t msg_type;
+	struct abis_om2k_mo mo;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+enum abis_om2k_msgtype {
+	OM2K_MSGT_ABORT_SP_CMD			= 0x0000,
+	OM2K_MSGT_ABORT_SP_COMPL		= 0x0002,
+	OM2K_MSGT_ALARM_REP_ACK			= 0x0004,
+	OM2K_MSGT_ALARM_REP_NACK		= 0x0005,
+	OM2K_MSGT_ALARM_REP			= 0x0006,
+	OM2K_MSGT_ALARM_STATUS_REQ		= 0x0008,
+	OM2K_MSGT_ALARM_STATUS_REQ_ACK		= 0x000a,
+	OM2K_MSGT_ALARM_STATUS_REQ_REJ		= 0x000b,
+	OM2K_MSGT_ALARM_STATUS_RES_ACK		= 0x000c,
+	OM2K_MSGT_ALARM_STATUS_RES_NACK		= 0x000d,
+	OM2K_MSGT_ALARM_STATUS_RES		= 0x000e,
+	OM2K_MSGT_CAL_TIME_RESP			= 0x0010,
+	OM2K_MSGT_CAL_TIME_REJ			= 0x0011,
+	OM2K_MSGT_CAL_TIME_REQ			= 0x0012,
+
+	OM2K_MSGT_CON_CONF_REQ			= 0x0014,
+	OM2K_MSGT_CON_CONF_REQ_ACK		= 0x0016,
+	OM2K_MSGT_CON_CONF_REQ_REJ		= 0x0017,
+	OM2K_MSGT_CON_CONF_RES_ACK		= 0x0018,
+	OM2K_MSGT_CON_CONF_RES_NACK		= 0x0019,
+	OM2K_MSGT_CON_CONF_RES			= 0x001a,
+
+	OM2K_MSGT_CONNECT_CMD			= 0x001c,
+	OM2K_MSGT_CONNECT_COMPL			= 0x001e,
+	OM2K_MSGT_CONNECT_REJ			= 0x001f,
+
+	OM2K_MSGT_DISABLE_REQ			= 0x0028,
+	OM2K_MSGT_DISABLE_REQ_ACK		= 0x002a,
+	OM2K_MSGT_DISABLE_REQ_REJ		= 0x002b,
+	OM2K_MSGT_DISABLE_RES_ACK		= 0x002c,
+	OM2K_MSGT_DISABLE_RES_NACK		= 0x002d,
+	OM2K_MSGT_DISABLE_RES			= 0x002e,
+	OM2K_MSGT_DISCONNECT_CMD		= 0x0030,
+	OM2K_MSGT_DISCONNECT_COMPL		= 0x0032,
+	OM2K_MSGT_DISCONNECT_REJ		= 0x0033,
+	OM2K_MSGT_ENABLE_REQ			= 0x0034,
+	OM2K_MSGT_ENABLE_REQ_ACK		= 0x0036,
+	OM2K_MSGT_ENABLE_REQ_REJ		= 0x0037,
+	OM2K_MSGT_ENABLE_RES_ACK		= 0x0038,
+	OM2K_MSGT_ENABLE_RES_NACK		= 0x0039,
+	OM2K_MSGT_ENABLE_RES			= 0x003a,
+
+	OM2K_MSGT_FAULT_REP_ACK			= 0x0040,
+	OM2K_MSGT_FAULT_REP_NACK		= 0x0041,
+	OM2K_MSGT_FAULT_REP			= 0x0042,
+
+	OM2K_MSGT_IS_CONF_REQ			= 0x0060,
+	OM2K_MSGT_IS_CONF_REQ_ACK		= 0x0062,
+	OM2K_MSGT_IS_CONF_REQ_REJ		= 0x0063,
+	OM2K_MSGT_IS_CONF_RES_ACK		= 0x0064,
+	OM2K_MSGT_IS_CONF_RES_NACK		= 0x0065,
+	OM2K_MSGT_IS_CONF_RES			= 0x0066,
+
+	OM2K_MSGT_OP_INFO			= 0x0074,
+	OM2K_MSGT_OP_INFO_ACK			= 0x0076,
+	OM2K_MSGT_OP_INFO_REJ			= 0x0077,
+	OM2K_MSGT_RESET_CMD		 	= 0x0078,
+	OM2K_MSGT_RESET_COMPL			= 0x007a,
+	OM2K_MSGT_RESET_REJ			= 0x007b,
+	OM2K_MSGT_RX_CONF_REQ			= 0x007c,
+	OM2K_MSGT_RX_CONF_REQ_ACK		= 0x007e,
+	OM2K_MSGT_RX_CONF_REQ_REJ		= 0x007f,
+	OM2K_MSGT_RX_CONF_RES_ACK		= 0x0080,
+	OM2K_MSGT_RX_CONF_RES_NACK		= 0x0081,
+	OM2K_MSGT_RX_CONF_RES			= 0x0082,
+	OM2K_MSGT_START_REQ			= 0x0084,
+	OM2K_MSGT_START_REQ_ACK			= 0x0086,
+	OM2K_MSGT_START_REQ_REJ			= 0x0087,
+	OM2K_MSGT_START_RES_ACK			= 0x0088,
+	OM2K_MSGT_START_RES_NACK		= 0x0089,
+	OM2K_MSGT_START_RES			= 0x008a,
+	OM2K_MSGT_STATUS_REQ			= 0x008c,
+	OM2K_MSGT_STATUS_RESP			= 0x008e,
+	OM2K_MSGT_STATUS_REJ			= 0x008f,
+
+	OM2K_MSGT_TEST_REQ			= 0x0094,
+	OM2K_MSGT_TEST_REQ_ACK			= 0x0096,
+	OM2K_MSGT_TEST_REQ_REJ			= 0x0097,
+	OM2K_MSGT_TEST_RES_ACK			= 0x0098,
+	OM2K_MSGT_TEST_RES_NACK			= 0x0099,
+	OM2K_MSGT_TEST_RES			= 0x009a,
+
+	OM2K_MSGT_TF_CONF_REQ			= 0x00a0,
+	OM2K_MSGT_TF_CONF_REQ_ACK		= 0x00a2,
+	OM2K_MSGT_TF_CONF_REQ_REJ		= 0x00a3,
+	OM2K_MSGT_TF_CONF_RES_ACK		= 0x00a4,
+	OM2K_MSGT_TF_CONF_RES_NACK		= 0x00a5,
+	OM2K_MSGT_TF_CONF_RES			= 0x00a6,
+	OM2K_MSGT_TS_CONF_REQ			= 0x00a8,
+	OM2K_MSGT_TS_CONF_REQ_ACK		= 0x00aa,
+	OM2K_MSGT_TS_CONF_REQ_REJ		= 0x00ab,
+	OM2K_MSGT_TS_CONF_RES_ACK		= 0x00ac,
+	OM2K_MSGT_TS_CONF_RES_NACK		= 0x00ad,
+	OM2K_MSGT_TS_CONF_RES			= 0x00ae,
+	OM2K_MSGT_TX_CONF_REQ			= 0x00b0,
+	OM2K_MSGT_TX_CONF_REQ_ACK		= 0x00b2,
+	OM2K_MSGT_TX_CONF_REQ_REJ		= 0x00b3,
+	OM2K_MSGT_TX_CONF_RES_ACK		= 0x00b4,
+	OM2K_MSGT_TX_CONF_RES_NACK		= 0x00b5,
+	OM2K_MSGT_TX_CONF_RES			= 0x00b6,
+
+	OM2K_MSGT_CAPA_REQ			= 0x00e8,
+	OM2K_MSGT_CAPA_REQ_ACK			= 0x00ea,
+	OM2K_MSGT_CAPA_REQ_REJ			= 0x00eb,
+	OM2K_MSGT_CAPA_RES			= 0x00ee,
+	OM2K_MSGT_CAPA_RES_ACK			= 0x00ec,
+	OM2K_MSGT_CAPA_RES_NACK			= 0x00ed,
+
+	OM2K_MSGT_NEGOT_REQ_ACK			= 0x0104,
+	OM2K_MSGT_NEGOT_REQ_NACK		= 0x0105,
+	OM2K_MSGT_NEGOT_REQ			= 0x0106,
+};
+
+enum abis_om2k_dei {
+	OM2K_DEI_ACCORDANCE_IND			= 0x00,
+	OM2K_DEI_BCC				= 0x06,
+	OM2K_DEI_BS_AG_BKS_RES			= 0x07,
+	OM2K_DEI_BSIC				= 0x09,
+	OM2K_DEI_BA_PA_MFRMS			= 0x0a,
+	OM2K_DEI_CBCH_INDICATOR			= 0x0b,
+	OM2K_DEI_CCCH_OPTIONS			= 0x0c,
+	OM2K_DEI_CAL_TIME			= 0x0d,
+	OM2K_DEI_COMBINATION			= 0x0f,
+	OM2K_DEI_CON_CONN_LIST			= 0x10,
+	OM2K_DEI_DRX_DEV_MAX			= 0x12,
+	OM2K_DEI_END_LIST_NR			= 0x13,
+	OM2K_DEI_EXT_COND_MAP_1			= 0x14,
+	OM2K_DEI_EXT_COND_MAP_2			= 0x15,
+	OM2K_DEI_FILLING_MARKER			= 0x1c,
+	OM2K_DEI_FN_OFFSET			= 0x1d,
+	OM2K_DEI_FREQ_LIST			= 0x1e,
+	OM2K_DEI_FREQ_SPEC_RX			= 0x1f,
+	OM2K_DEI_FREQ_SPEC_TX			= 0x20,
+	OM2K_DEI_HSN				= 0x21,
+	OM2K_DEI_ICM_INDICATOR			= 0x22,
+	OM2K_DEI_INT_FAULT_MAP_1A		= 0x23,
+	OM2K_DEI_INT_FAULT_MAP_1B		= 0x24,
+	OM2K_DEI_INT_FAULT_MAP_2A		= 0x25,
+	OM2K_DEI_INT_FAULT_MAP_2A_EXT		= 0x26,
+	OM2K_DEI_IS_CONN_LIST			= 0x27,
+	OM2K_DEI_LIST_NR			= 0x28,
+	OM2K_DEI_LOCAL_ACCESS			= 0x2a,
+	OM2K_DEI_MAIO				= 0x2b,
+	OM2K_DEI_MO_STATE			= 0x2c,
+	OM2K_DEI_NY1				= 0x2d,
+	OM2K_DEI_OP_INFO			= 0x2e,
+	OM2K_DEI_POWER				= 0x2f,
+	OM2K_DEI_REASON_CODE			= 0x32,
+	OM2K_DEI_RX_DIVERSITY			= 0x33,
+	OM2K_DEI_REPL_UNIT_MAP			= 0x34,
+	OM2K_DEI_RESULT_CODE			= 0x35,
+	OM2K_DEI_T3105				= 0x38,
+	OM2K_DEI_TF_MODE			= 0x3a,
+	OM2K_DEI_TS_NR				= 0x3c,
+	OM2K_DEI_TSC				= 0x3d,
+	OM2K_DEI_BTS_VERSION			= 0x40,
+	OM2K_DEI_OML_IWD_VERSION		= 0x41,
+	OM2K_DEI_RSL_IWD_VERSION		= 0x42,
+	OM2K_DEI_OML_FUNC_MAP_1			= 0x43,
+	OM2K_DEI_OML_FUNC_MAP_2			= 0x44,
+	OM2K_DEI_RSL_FUNC_MAP_1			= 0x45,
+	OM2K_DEI_RSL_FUNC_MAP_2			= 0x46,
+	OM2K_DEI_EXT_RANGE			= 0x47,
+	OM2K_DEI_REQ_IND			= 0x48,
+	OM2K_DEI_REPL_UNIT_MAP_EXT		= 0x50,
+	OM2K_DEI_ICM_BOUND_PARAMS		= 0x74,
+	OM2K_DEI_LSC				= 0x79,
+	OM2K_DEI_LSC_FILT_TIME			= 0x7a,
+	OM2K_DEI_CALL_SUPV_TIME			= 0x7b,
+	OM2K_DEI_ICM_CHAN_RATE			= 0x7e,
+	OM2K_DEI_HW_INFO_SIG			= 0x84,
+	OM2K_DEI_TF_SYNC_SRC			= 0x86,
+	OM2K_DEI_TTA				= 0x87,
+	OM2K_DEI_CAPA_SIG			= 0x8a,
+	OM2K_DEI_NEGOT_REC1			= 0x90,
+	OM2K_DEI_NEGOT_REC2			= 0x91,
+	OM2K_DEI_ENCR_ALG			= 0x92,
+	OM2K_DEI_INTERF_REJ_COMB		= 0x94,
+	OM2K_DEI_FS_OFFSET			= 0x98,
+	OM2K_DEI_EXT_COND_MAP_2_EXT		= 0x9c,
+	OM2K_DEI_TSS_MO_STATE			= 0x9d,
+};
+
+const struct tlv_definition om2k_att_tlvdef = {
+	.def = {
+		[OM2K_DEI_ACCORDANCE_IND] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_BCC] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_BS_AG_BKS_RES] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_BSIC] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_BA_PA_MFRMS] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_CBCH_INDICATOR] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_INT_FAULT_MAP_1A] =	{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_INT_FAULT_MAP_1B] =	{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_INT_FAULT_MAP_2A] =	{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_INT_FAULT_MAP_2A_EXT]={ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_CCCH_OPTIONS] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_CAL_TIME] =		{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_COMBINATION] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_CON_CONN_LIST] =	{ TLV_TYPE_TLV },
+		[OM2K_DEI_DRX_DEV_MAX] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_END_LIST_NR] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_EXT_COND_MAP_1] =	{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_EXT_COND_MAP_2] =	{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_FILLING_MARKER] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_FN_OFFSET] =		{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_FREQ_LIST] =		{ TLV_TYPE_TLV },
+		[OM2K_DEI_FREQ_SPEC_RX] =	{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_FREQ_SPEC_TX] =	{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_HSN] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_ICM_INDICATOR] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_IS_CONN_LIST] =	{ TLV_TYPE_TLV },
+		[OM2K_DEI_LIST_NR] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_LOCAL_ACCESS] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_MAIO] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_MO_STATE] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_NY1] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_OP_INFO] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_POWER] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_REASON_CODE] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_RX_DIVERSITY] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_RESULT_CODE] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_T3105] = 		{ TLV_TYPE_TV },
+		[OM2K_DEI_TF_MODE] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_TS_NR] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_TSC] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_BTS_VERSION] =	{ TLV_TYPE_FIXED, 12 },
+		[OM2K_DEI_OML_IWD_VERSION] =	{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_RSL_IWD_VERSION] =	{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_OML_FUNC_MAP_1] =	{ TLV_TYPE_TLV },
+		[OM2K_DEI_OML_FUNC_MAP_2] =	{ TLV_TYPE_TLV },
+		[OM2K_DEI_RSL_FUNC_MAP_1] =	{ TLV_TYPE_TLV },
+		[OM2K_DEI_RSL_FUNC_MAP_2] =	{ TLV_TYPE_TLV },
+		[OM2K_DEI_EXT_RANGE] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_REQ_IND] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_REPL_UNIT_MAP] =	{ TLV_TYPE_FIXED, 6 },
+		[OM2K_DEI_REPL_UNIT_MAP_EXT] =	{TLV_TYPE_FIXED, 6},
+		[OM2K_DEI_ICM_BOUND_PARAMS] =	{ TLV_TYPE_FIXED, 5 },
+		[OM2K_DEI_LSC] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_LSC_FILT_TIME] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_CALL_SUPV_TIME] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_ICM_CHAN_RATE] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_HW_INFO_SIG] =	{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_TF_SYNC_SRC] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_TTA] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_CAPA_SIG] =		{ TLV_TYPE_FIXED, 2 },
+		[OM2K_DEI_NEGOT_REC1] =		{ TLV_TYPE_TLV },
+		[OM2K_DEI_NEGOT_REC2] =		{ TLV_TYPE_TLV },
+		[OM2K_DEI_ENCR_ALG] =		{ TLV_TYPE_TV },
+		[OM2K_DEI_INTERF_REJ_COMB] =	{ TLV_TYPE_TV },
+		[OM2K_DEI_FS_OFFSET] =		{ TLV_TYPE_FIXED, 5 },
+		[OM2K_DEI_EXT_COND_MAP_2_EXT] = { TLV_TYPE_FIXED, 4 },
+		[OM2K_DEI_TSS_MO_STATE] = 	{ TLV_TYPE_FIXED, 4 },
+	},
+};
+
+static const struct value_string om2k_msgcode_vals[] = {
+	{ 0x0000, "Abort SP Command" },
+	{ 0x0002, "Abort SP Complete" },
+	{ 0x0004, "Alarm Report ACK" },
+	{ 0x0005, "Alarm Report NACK" },
+	{ 0x0006, "Alarm Report" },
+	{ 0x0008, "Alarm Status Request" },
+	{ 0x000a, "Alarm Status Request Accept" },
+	{ 0x000b, "Alarm Status Request Reject" },
+	{ 0x000c, "Alarm Status Result ACK" },
+	{ 0x000d, "Alarm Status Result NACK" },
+	{ 0x000e, "Alarm Status Result" },
+	{ 0x0010, "Calendar Time Response" },
+	{ 0x0011, "Calendar Time Reject" },
+	{ 0x0012, "Calendar Time Request" },
+	{ 0x0014, "CON Configuration Request" },
+	{ 0x0016, "CON Configuration Request Accept" },
+	{ 0x0017, "CON Configuration Request Reject" },
+	{ 0x0018, "CON Configuration Result ACK" },
+	{ 0x0019, "CON Configuration Result NACK" },
+	{ 0x001a, "CON Configuration Result" },
+	{ 0x001c, "Connect Command" },
+	{ 0x001e, "Connect Complete" },
+	{ 0x001f, "Connect Reject" },
+	{ 0x0028, "Disable Request" },
+	{ 0x002a, "Disable Request Accept" },
+	{ 0x002b, "Disable Request Reject" },
+	{ 0x002c, "Disable Result ACK" },
+	{ 0x002d, "Disable Result NACK" },
+	{ 0x002e, "Disable Result" },
+	{ 0x0030, "Disconnect Command" },
+	{ 0x0032, "Disconnect Complete" },
+	{ 0x0033, "Disconnect Reject" },
+	{ 0x0034, "Enable Request" },
+	{ 0x0036, "Enable Request Accept" },
+	{ 0x0037, "Enable Request Reject" },
+	{ 0x0038, "Enable Result ACK" },
+	{ 0x0039, "Enable Result NACK" },
+	{ 0x003a, "Enable Result" },
+	{ 0x003c, "Escape Downlink Normal" },
+	{ 0x003d, "Escape Downlink NACK" },
+	{ 0x003e, "Escape Uplink Normal" },
+	{ 0x003f, "Escape Uplink NACK" },
+	{ 0x0040, "Fault Report ACK" },
+	{ 0x0041, "Fault Report NACK" },
+	{ 0x0042, "Fault Report" },
+	{ 0x0044, "File Package End Command" },
+	{ 0x0046, "File Package End Result" },
+	{ 0x0047, "File Package End Reject" },
+	{ 0x0048, "File Relation Request" },
+	{ 0x004a, "File Relation Response" },
+	{ 0x004b, "File Relation Request Reject" },
+	{ 0x004c, "File Segment Transfer" },
+	{ 0x004e, "File Segment Transfer Complete" },
+	{ 0x004f, "File Segment Transfer Reject" },
+	{ 0x0050, "HW Information Request" },
+	{ 0x0052, "HW Information Request Accept" },
+	{ 0x0053, "HW Information Request Reject" },
+	{ 0x0054, "HW Information Result ACK" },
+	{ 0x0055, "HW Information Result NACK" },
+	{ 0x0056, "HW Information Result" },
+	{ 0x0060, "IS Configuration Request" },
+	{ 0x0062, "IS Configuration Request Accept" },
+	{ 0x0063, "IS Configuration Request Reject" },
+	{ 0x0064, "IS Configuration Result ACK" },
+	{ 0x0065, "IS Configuration Result NACK" },
+	{ 0x0066, "IS Configuration Result" },
+	{ 0x0068, "Load Data End" },
+	{ 0x006a, "Load Data End Result" },
+	{ 0x006b, "Load Data End Reject" },
+	{ 0x006c, "Load Data Init" },
+	{ 0x006e, "Load Data Init Accept" },
+	{ 0x006f, "Load Data Init Reject" },
+	{ 0x0070, "Loop Control Command" },
+	{ 0x0072, "Loop Control Complete" },
+	{ 0x0073, "Loop Control Reject" },
+	{ 0x0074, "Operational Information" },
+	{ 0x0076, "Operational Information Accept" },
+	{ 0x0077, "Operational Information Reject" },
+	{ 0x0078, "Reset Command" },
+	{ 0x007a, "Reset Complete" },
+	{ 0x007b, "Reset Reject" },
+	{ 0x007c, "RX Configuration Request" },
+	{ 0x007e, "RX Configuration Request Accept" },
+	{ 0x007f, "RX Configuration Request Reject" },
+	{ 0x0080, "RX Configuration Result ACK" },
+	{ 0x0081, "RX Configuration Result NACK" },
+	{ 0x0082, "RX Configuration Result" },
+	{ 0x0084, "Start Request" },
+	{ 0x0086, "Start Request Accept" },
+	{ 0x0087, "Start Request Reject" },
+	{ 0x0088, "Start Result ACK" },
+	{ 0x0089, "Start Result NACK" },
+	{ 0x008a, "Start Result" },
+	{ 0x008c, "Status Request" },
+	{ 0x008e, "Status Response" },
+	{ 0x008f, "Status Reject" },
+	{ 0x0094, "Test Request" },
+	{ 0x0096, "Test Request Accept" },
+	{ 0x0097, "Test Request Reject" },
+	{ 0x0098, "Test Result ACK" },
+	{ 0x0099, "Test Result NACK" },
+	{ 0x009a, "Test Result" },
+	{ 0x00a0, "TF Configuration Request" },
+	{ 0x00a2, "TF Configuration Request Accept" },
+	{ 0x00a3, "TF Configuration Request Reject" },
+	{ 0x00a4, "TF Configuration Result ACK" },
+	{ 0x00a5, "TF Configuration Result NACK" },
+	{ 0x00a6, "TF Configuration Result" },
+	{ 0x00a8, "TS Configuration Request" },
+	{ 0x00aa, "TS Configuration Request Accept" },
+	{ 0x00ab, "TS Configuration Request Reject" },
+	{ 0x00ac, "TS Configuration Result ACK" },
+	{ 0x00ad, "TS Configuration Result NACK" },
+	{ 0x00ae, "TS Configuration Result" },
+	{ 0x00b0, "TX Configuration Request" },
+	{ 0x00b2, "TX Configuration Request Accept" },
+	{ 0x00b3, "TX Configuration Request Reject" },
+	{ 0x00b4, "TX Configuration Result ACK" },
+	{ 0x00b5, "TX Configuration Result NACK" },
+	{ 0x00b6, "TX Configuration Result" },
+	{ 0x00bc, "DIP Alarm Report ACK" },
+	{ 0x00bd, "DIP Alarm Report NACK" },
+	{ 0x00be, "DIP Alarm Report" },
+	{ 0x00c0, "DIP Alarm Status Request" },
+	{ 0x00c2, "DIP Alarm Status Response" },
+	{ 0x00c3, "DIP Alarm Status Reject" },
+	{ 0x00c4, "DIP Quality Report I ACK" },
+	{ 0x00c5, "DIP Quality Report I NACK" },
+	{ 0x00c6, "DIP Quality Report I" },
+	{ 0x00c8, "DIP Quality Report II ACK" },
+	{ 0x00c9, "DIP Quality Report II NACK" },
+	{ 0x00ca, "DIP Quality Report II" },
+	{ 0x00dc, "DP Configuration Request" },
+	{ 0x00de, "DP Configuration Request Accept" },
+	{ 0x00df, "DP Configuration Request Reject" },
+	{ 0x00e0, "DP Configuration Result ACK" },
+	{ 0x00e1, "DP Configuration Result NACK" },
+	{ 0x00e2, "DP Configuration Result" },
+	{ 0x00e4, "Capabilities HW Info Report ACK" },
+	{ 0x00e5, "Capabilities HW Info Report NACK" },
+	{ 0x00e6, "Capabilities HW Info Report" },
+	{ 0x00e8, "Capabilities Request" },
+	{ 0x00ea, "Capabilities Request Accept" },
+	{ 0x00eb, "Capabilities Request Reject" },
+	{ 0x00ec, "Capabilities Result ACK" },
+	{ 0x00ed, "Capabilities Result NACK" },
+	{ 0x00ee, "Capabilities Result" },
+	{ 0x00f0, "FM Configuration Request" },
+	{ 0x00f2, "FM Configuration Request Accept" },
+	{ 0x00f3, "FM Configuration Request Reject" },
+	{ 0x00f4, "FM Configuration Result ACK" },
+	{ 0x00f5, "FM Configuration Result NACK" },
+	{ 0x00f6, "FM Configuration Result" },
+	{ 0x00f8, "FM Report Request" },
+	{ 0x00fa, "FM Report Response" },
+	{ 0x00fb, "FM Report Reject" },
+	{ 0x00fc, "FM Start Command" },
+	{ 0x00fe, "FM Start Complete" },
+	{ 0x00ff, "FM Start Reject" },
+	{ 0x0100, "FM Stop Command" },
+	{ 0x0102, "FM Stop Complete" },
+	{ 0x0103, "FM Stop Reject" },
+	{ 0x0104, "Negotiation Request ACK" },
+	{ 0x0105, "Negotiation Request NACK" },
+	{ 0x0106, "Negotiation Request" },
+	{ 0x0108, "BTS Initiated Request ACK" },
+	{ 0x0109, "BTS Initiated Request NACK" },
+	{ 0x010a, "BTS Initiated Request" },
+	{ 0x010c, "Radio Channels Release Command" },
+	{ 0x010e, "Radio Channels Release Complete" },
+	{ 0x010f, "Radio Channels Release Reject" },
+	{ 0x0118, "Feature Control Command" },
+	{ 0x011a, "Feature Control Complete" },
+	{ 0x011b, "Feature Control Reject" },
+
+	{ 0, NULL }
+};
+
+/* TS 12.21 Section 9.4: Attributes */
+static const struct value_string om2k_attr_vals[] = {
+	{ 0x00, "Accordance indication" },
+	{ 0x01, "Alarm Id" },
+	{ 0x02, "Alarm Data" },
+	{ 0x03, "Alarm Severity" },
+	{ 0x04, "Alarm Status" },
+	{ 0x05, "Alarm Status Type" },
+	{ 0x06, "BCC" },
+	{ 0x07, "BS_AG_BKS_RES" },
+	{ 0x09, "BSIC" },
+	{ 0x0a, "BA_PA_MFRMS" },
+	{ 0x0b, "CBCH Indicator" },
+	{ 0x0c, "CCCH Options" },
+	{ 0x0d, "Calendar Time" },
+	{ 0x0f, "Channel Combination" },
+	{ 0x10, "CON Connection List" },
+	{ 0x11, "Data End Indication" },
+	{ 0x12, "DRX_DEV_MAX" },
+	{ 0x13, "End List Number" },
+	{ 0x14, "External Condition Map Class 1" },
+	{ 0x15, "External Condition Map Class 2" },
+	{ 0x16, "File Relation Indication" },
+	{ 0x17, "File Revision" },
+	{ 0x18, "File Segment Data" },
+	{ 0x19, "File Segment Length" },
+	{ 0x1a, "File Segment Sequence Number" },
+	{ 0x1b, "File Size" },
+	{ 0x1c, "Filling Marker" },
+	{ 0x1d, "FN Offset" },
+	{ 0x1e, "Frequency List" },
+	{ 0x1f, "Frequency Specifier RX" },
+	{ 0x20, "Frequency Specifier TX" },
+	{ 0x21, "HSN" },
+	{ 0x22, "ICM Indicator" },
+	{ 0x23, "Internal Fault Map Class 1A" },
+	{ 0x24, "Internal Fault Map Class 1B" },
+	{ 0x25, "Internal Fault Map Class 2A" },
+	{ 0x26, "Internal Fault Map Class 2A Extension" },
+	{ 0x27, "IS Connection List" },
+	{ 0x28, "List Number" },
+	{ 0x29, "File Package State Indication" },
+	{ 0x2a, "Local Access State" },
+	{ 0x2b, "MAIO" },
+	{ 0x2c, "MO State" },
+	{ 0x2d, "Ny1" },
+	{ 0x2e, "Operational Information" },
+	{ 0x2f, "Power" },
+	{ 0x30, "RU Position Data" },
+	{ 0x31, "Protocol Error" },
+	{ 0x32, "Reason Code" },
+	{ 0x33, "Receiver Diversity" },
+	{ 0x34, "Replacement Unit Map" },
+	{ 0x35, "Result Code" },
+	{ 0x36, "RU Revision Data" },
+	{ 0x38, "T3105" },
+	{ 0x39, "Test Loop Setting" },
+	{ 0x3a, "TF Mode" },
+	{ 0x3b, "TF Compensation Value" },
+	{ 0x3c, "Time Slot Number" },
+	{ 0x3d, "TSC" },
+	{ 0x3e, "RU Logical Id" },
+	{ 0x3f, "RU Serial Number Data" },
+	{ 0x40, "BTS Version" },
+	{ 0x41, "OML IWD Version" },
+	{ 0x42, "RWL IWD Version" },
+	{ 0x43, "OML Function Map 1" },
+	{ 0x44, "OML Function Map 2" },
+	{ 0x45, "RSL Function Map 1" },
+	{ 0x46, "RSL Function Map 2" },
+	{ 0x47, "Extended Range Indicator" },
+	{ 0x48, "Request Indicators" },
+	{ 0x49, "DIP Alarm Condition Map" },
+	{ 0x4a, "ES Incoming" },
+	{ 0x4b, "ES Outgoing" },
+	{ 0x4e, "SES Incoming" },
+	{ 0x4f, "SES Outgoing" },
+	{ 0x50, "Replacement Unit Map Extension" },
+	{ 0x52, "UAS Incoming" },
+	{ 0x53, "UAS Outgoing" },
+	{ 0x58, "DF Incoming" },
+	{ 0x5a, "DF Outgoing" },
+	{ 0x5c, "SF" },
+	{ 0x60, "S Bits Setting" },
+	{ 0x61, "CRC-4 Use Option" },
+	{ 0x62, "T Parameter" },
+	{ 0x63, "N Parameter" },
+	{ 0x64, "N1 Parameter" },
+	{ 0x65, "N3 Parameter" },
+	{ 0x66, "N4 Parameter" },
+	{ 0x67, "P Parameter" },
+	{ 0x68, "Q Parameter" },
+	{ 0x69, "BI_Q1" },
+	{ 0x6a, "BI_Q2" },
+	{ 0x74, "ICM Boundary Parameters" },
+	{ 0x77, "AFT" },
+	{ 0x78, "AFT RAI" },
+	{ 0x79, "Link Supervision Control" },
+	{ 0x7a, "Link Supervision Filtering Time" },
+	{ 0x7b, "Call Supervision Time" },
+	{ 0x7c, "Interval Length UAS Incoming" },
+	{ 0x7d, "Interval Length UAS Outgoing" },
+	{ 0x7e, "ICM Channel Rate" },
+	{ 0x7f, "Attribute Identifier" },
+	{ 0x80, "FM Frequency List" },
+	{ 0x81, "FM Frequency Report" },
+	{ 0x82, "FM Percentile" },
+	{ 0x83, "FM Clear Indication" },
+	{ 0x84, "HW Info Signature" },
+	{ 0x85, "MO Record" },
+	{ 0x86, "TF Synchronisation Source" },
+	{ 0x87, "TTA" },
+	{ 0x88, "End Segment Number" },
+	{ 0x89, "Segment Number" },
+	{ 0x8a, "Capabilities Signature" },
+	{ 0x8c, "File Relation List" },
+	{ 0x90, "Negotiation Record I" },
+	{ 0x91, "Negotiation Record II" },
+	{ 0x92, "Encryption Algorithm" },
+	{ 0x94, "Interference Rejection Combining" },
+	{ 0x95, "Dedication Information" },
+	{ 0x97, "Feature Code" },
+	{ 0x98, "FS Offset" },
+	{ 0x99, "ESB Timeslot" },
+	{ 0x9a, "Master TG Instance" },
+	{ 0x9b, "Master TX Chain Delay" },
+	{ 0x9c, "External Condition Class 2 Extension" },
+	{ 0x9d, "TSs MO State" },
+	{ 0, NULL }
+};
+
+const struct value_string om2k_mo_class_short_vals[] = {
+	{ 0x01, "TRXC" },
+	{ 0x03, "TS" },
+	{ 0x04, "TF" },
+	{ 0x05, "IS" },
+	{ 0x06, "CON" },
+	{ 0x07, "DP" },
+	{ 0x0a, "CF" },
+	{ 0x0b, "TX" },
+	{ 0x0c, "RX" },
+	{ 0, NULL }
+};
+
+const struct value_string om2k_result_strings[] = {
+	{ 0x02, "Wrong state or out of sequence" },
+	{ 0x03, "File error" },
+	{ 0x04, "Fault, unspecified" },
+	{ 0x05, "Tuning fault" },
+	{ 0x06, "Protocol error" },
+	{ 0x07, "MO not connected" },
+	{ 0x08, "Parameter error" },
+	{ 0x09, "Optional function not supported" },
+	{ 0x0a, "Local access state LOCALLY DISCONNECTED" },
+	{ 0, NULL }
+};
+
+const struct value_string om2k_accordance_strings[] = {
+	{ 0x00, "Data according to request" },
+	{ 0x01, "Data not according to request" },
+	{ 0x02, "Inconsistent MO data" },
+	{ 0x03, "Capability constraint violation" },
+	{ 0, NULL }
+};
+
+const struct value_string om2k_mostate_vals[] = {
+	{ 0x00, "RESET" },
+	{ 0x01, "STARTED" },
+	{ 0x02, "ENABLED" },
+	{ 0x03, "DISABLED" },
+	{ 0, NULL }
+};
+
+/* entire decoded OM2K message (header + parsed TLV) */
+struct om2k_decoded_msg {
+	struct abis_om2k_hdr o2h;
+	uint16_t msg_type;
+	struct tlv_parsed tp;
+};
+
+/* resolve the OM2000 Managed Object by BTS + MO Address */
+static struct om2k_mo *
+get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo)
+{
+	struct om2k_mo *mo = NULL;
+	struct gsm_bts_trx *trx;
+
+	switch (abis_mo->class) {
+	case OM2K_MO_CLS_CF:
+		mo = &bts->rbs2000.cf.om2k_mo;
+		break;
+	case OM2K_MO_CLS_CON:
+		mo = &bts->rbs2000.con.om2k_mo;
+		break;
+	case OM2K_MO_CLS_IS:
+		mo = &bts->rbs2000.is.om2k_mo;
+		break;
+	case OM2K_MO_CLS_TF:
+		mo = &bts->rbs2000.tf.om2k_mo;
+		break;
+
+	case OM2K_MO_CLS_TRXC:
+		trx = gsm_bts_trx_num(bts, abis_mo->inst);
+		if (!trx)
+			return NULL;
+		mo = &trx->rbs2000.trxc.om2k_mo;
+		break;
+	case OM2K_MO_CLS_TX:
+		trx = gsm_bts_trx_num(bts, abis_mo->inst);
+		if (!trx)
+			return NULL;
+		mo = &trx->rbs2000.tx.om2k_mo;
+		break;
+	case OM2K_MO_CLS_RX:
+		trx = gsm_bts_trx_num(bts, abis_mo->inst);
+		if (!trx)
+			return NULL;
+		mo = &trx->rbs2000.rx.om2k_mo;
+		break;
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_num(bts, abis_mo->assoc_so);
+		if (!trx)
+			return NULL;
+		if (abis_mo->inst >= ARRAY_SIZE(trx->ts))
+			return NULL;
+		mo = &trx->ts[abis_mo->inst].rbs2000.om2k_mo;
+		break;
+	default:
+		return NULL;
+	};
+
+	return mo;
+}
+
+static struct msgb *om2k_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OM2000");
+}
+
+static int abis_om2k_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len)
+{
+	return tlv_parse(tp, &om2k_att_tlvdef, buf, len, 0, 0);
+}
+
+static int abis_om2k_msg_tlv_parse(struct tlv_parsed *tp, struct abis_om2k_hdr *oh)
+{
+	return abis_om2k_tlv_parse(tp, oh->data, oh->om.length - 6);
+}
+
+/* decode/parse the message */
+static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	odm->msg_type = ntohs(o2h->msg_type);
+	odm->o2h = *o2h;
+	return abis_om2k_msg_tlv_parse(&odm->tp, o2h);
+}
+
+static char *om2k_mo_name(const struct abis_om2k_mo *mo)
+{
+	static char mo_buf[64];
+
+	memset(mo_buf, 0, sizeof(mo_buf));
+	snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x",
+		 get_value_string(om2k_mo_class_short_vals, mo->class),
+		 mo->bts, mo->assoc_so, mo->inst);
+	return mo_buf;
+}
+
+/* resolve the gsm_nm_state data structure for a given MO */
+static struct gsm_nm_state *
+mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_nm_state *nm_state = NULL;
+
+	switch (mo->class) {
+	case OM2K_MO_CLS_TRXC:
+		trx = gsm_bts_trx_num(bts, mo->inst);
+		if (!trx)
+			return NULL;
+		nm_state = &trx->mo.nm_state;
+		break;
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		if (mo->inst >= ARRAY_SIZE(trx->ts))
+			return NULL;
+		nm_state = &trx->ts[mo->inst].mo.nm_state;
+		break;
+	case OM2K_MO_CLS_TF:
+		nm_state = &bts->rbs2000.tf.mo.nm_state;
+		break;
+	case OM2K_MO_CLS_IS:
+		nm_state = &bts->rbs2000.is.mo.nm_state;
+		break;
+	case OM2K_MO_CLS_CON:
+		nm_state = &bts->rbs2000.con.mo.nm_state;
+		break;
+	case OM2K_MO_CLS_DP:
+		nm_state = &bts->rbs2000.con.mo.nm_state;
+		break;
+	case OM2K_MO_CLS_CF:
+		nm_state = &bts->mo.nm_state;
+		break;
+	case OM2K_MO_CLS_TX:
+		trx = gsm_bts_trx_num(bts, mo->inst);
+		if (!trx)
+			return NULL;
+		/* FIXME */
+		break;
+	case OM2K_MO_CLS_RX:
+		trx = gsm_bts_trx_num(bts, mo->inst);
+		if (!trx)
+			return NULL;
+		/* FIXME */
+		break;
+	}
+
+	return nm_state;
+}
+
+static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo)
+{
+	struct gsm_bts_trx *trx;
+
+	switch (mo->class) {
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_RX:
+	case OM2K_MO_CLS_TRXC:
+		return gsm_bts_trx_num(bts, mo->inst);
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_num(bts, mo->assoc_so);
+		if (!trx)
+			return NULL;
+		if (mo->inst >= ARRAY_SIZE(trx->ts))
+			return NULL;
+		return &trx->ts[mo->inst];
+	case OM2K_MO_CLS_TF:
+	case OM2K_MO_CLS_IS:
+	case OM2K_MO_CLS_CON:
+	case OM2K_MO_CLS_DP:
+	case OM2K_MO_CLS_CF:
+		return bts;
+	}
+
+	return NULL;
+}
+
+static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo,
+			    uint8_t mo_state)
+{
+	struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
+	struct gsm_nm_state new_state;
+	struct nm_statechg_signal_data nsd;
+
+	if (!nm_state)
+		return;
+
+	new_state = *nm_state;
+	/* NOTICE: 12.21 Availability state values != OM2000 */
+	new_state.availability = mo_state;
+
+	memset(&nsd, 0, sizeof(nsd));
+
+	nsd.bts = bts;
+	nsd.obj = mo2obj(bts, mo);
+	nsd.old_state = nm_state;
+	nsd.new_state = &new_state;
+	nsd.om2k_mo = mo;
+
+	osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd);
+
+	nm_state->availability = new_state.availability;
+}
+
+static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			    uint8_t op_state)
+{
+	struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
+	struct gsm_nm_state new_state;
+
+	if (!nm_state)
+		return;
+
+	new_state = *nm_state;
+	switch (op_state) {
+	case 1:
+		new_state.operational = NM_OPSTATE_ENABLED;
+		break;
+	case 0:
+		new_state.operational = NM_OPSTATE_DISABLED;
+		break;
+	default:
+		new_state.operational = NM_OPSTATE_NULL;
+		break;
+	}
+
+	nm_state->operational = new_state.operational;
+}
+
+static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h;
+	struct gsm_bts_trx *trx;
+
+	msg->l2h = msg->data;
+	o2h = (struct abis_om2k_hdr *) msg->l2h;
+
+	/* Compute the length in the OML header */
+	o2h->om.length = 6 + msgb_l2len(msg)-sizeof(*o2h);
+
+	switch (o2h->mo.class) {
+	case OM2K_MO_CLS_TRXC:
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_RX:
+		/* Route through per-TRX OML Link to the appropriate TRX */
+		trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst);
+		if (!trx) {
+			LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
+				"non-existing TRX\n", om2k_mo_name(&o2h->mo));
+			return -ENODEV;
+		}
+		msg->dst = trx->oml_link;
+		break;
+	case OM2K_MO_CLS_TS:
+		/* Route through per-TRX OML Link to the appropriate TRX */
+		trx = gsm_bts_trx_by_nr(bts, o2h->mo.assoc_so);
+		if (!trx) {
+			LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
+				"non-existing TRX\n", om2k_mo_name(&o2h->mo));
+			return -ENODEV;
+		}
+		msg->dst = trx->oml_link;
+		break;
+	default:
+		/* Route through the IXU/DXU OML Link */
+		msg->dst = bts->oml_link;
+		break;
+	}
+
+	return _abis_nm_sendmsg(msg);
+}
+
+static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo,
+			  uint16_t msg_type)
+{
+	o2h->om.mdisc = ABIS_OM_MDISC_FOM;
+	o2h->om.placement = ABIS_OM_PLACEMENT_ONLY;
+	o2h->om.sequence = 0;
+	/* We fill o2h->om.length later during om2k_sendmsg() */
+	o2h->msg_type = htons(msg_type);
+	memcpy(&o2h->mo, mo, sizeof(o2h->mo));
+}
+
+static int abis_om2k_cal_time_resp(struct gsm_bts *bts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	time_t tm_t;
+	struct tm *tm;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &bts->rbs2000.cf.om2k_mo.addr,
+			OM2K_MSGT_CAL_TIME_RESP);
+
+	tm_t = time(NULL);
+	tm = localtime(&tm_t);
+
+	msgb_put_u8(msg, OM2K_DEI_CAL_TIME);
+	msgb_put_u8(msg, tm->tm_year % 100);
+	msgb_put_u8(msg, tm->tm_mon + 1);
+	msgb_put_u8(msg, tm->tm_mday);
+	msgb_put_u8(msg, tm->tm_hour);
+	msgb_put_u8(msg, tm->tm_min);
+	msgb_put_u8(msg, tm->tm_sec);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+				uint8_t msg_type)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, mo, msg_type);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+		get_value_string(om2k_msgcode_vals, msg_type));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD);
+}
+
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ);
+}
+
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ);
+}
+
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD);
+}
+
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD);
+}
+
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ);
+}
+
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ);
+}
+
+int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISABLE_REQ);
+}
+
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			 uint8_t operational)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO);
+
+	msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO));
+
+	/* we update the state here... and send the signal at ACK */
+	update_op_state(bts, mo, operational);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CAPA_REQ);
+}
+
+static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1,
+				  uint16_t icp2, uint8_t cont_idx)
+{
+	grp->icp1 = htons(icp1);
+	grp->icp2 = htons(icp2);
+	grp->cont_idx = cont_idx;
+}
+
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct is_conn_group *grp;
+	unsigned int num_grps = 0, i = 0;
+	struct om2k_is_conn_grp *cg;
+
+	/* count number of groups in linked list */
+	llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+		num_grps++;
+
+	if (!num_grps)
+		return -EINVAL;
+
+	/* allocate buffer for oml group array */
+	cg = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps);
+
+	/* fill array with data from linked list */
+	llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+		om2k_fill_is_conn_grp(&cg[i++], grp->icp1, grp->icp2, grp->ci);
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &bts->rbs2000.is.om2k_mo.addr,
+			OM2K_MSGT_IS_CONF_REQ);
+
+	msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1);
+	msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1);
+
+	msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST,
+		     num_grps * sizeof(*cg), (uint8_t *)cg);
+
+	talloc_free(cg);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n",
+		om2k_mo_name(&bts->rbs2000.is.om2k_mo.addr),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_IS_CONF_REQ));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_con_conf_req(struct gsm_bts *bts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct con_group *grp;
+	unsigned int num_grps = 0;
+
+	/* count number of groups in linked list */
+	llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list)
+		num_grps++;
+
+	if (!num_grps)
+		return -EINVAL;
+
+	/* first build the value part of the OM2K_DEI_CON_CONN_LIST DEI */
+	msgb_put_u8(msg, num_grps);
+	llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) {
+		struct con_path *cp;
+		unsigned int num_paths = 0;
+		llist_for_each_entry(cp, &grp->paths, list)
+			num_paths++;
+		msgb_put_u8(msg, num_paths);
+		llist_for_each_entry(cp, &grp->paths, list) {
+			struct om2k_con_path *om2k_cp;
+			om2k_cp = (struct om2k_con_path *) msgb_put(msg, sizeof(*om2k_cp));
+			om2k_cp->ccp = htons(cp->ccp);
+			om2k_cp->ci = cp->ci;
+			om2k_cp->tag = cp->tag;
+			om2k_cp->tei = cp->tei;
+		}
+	}
+	msgb_push_u8(msg, msgb_length(msg));
+	msgb_push_u8(msg, OM2K_DEI_CON_CONN_LIST);
+
+	/* pre-pend the list number DEIs */
+	msgb_tv_push(msg, OM2K_DEI_END_LIST_NR, 1);
+	msgb_tv_push(msg, OM2K_DEI_LIST_NR, 1);
+
+	/* pre-pend the OM2K header */
+	o2k = (struct abis_om2k_hdr *) msgb_push(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &bts->rbs2000.con.om2k_mo.addr,
+			OM2K_MSGT_CON_CONF_REQ);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n",
+		om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_CON_CONF_REQ));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+static void om2k_trx_to_mo(struct abis_om2k_mo *mo,
+			   const struct gsm_bts_trx *trx,
+			   enum abis_om2k_mo_cls cls)
+{
+	mo->class = cls;
+	mo->bts = 0;
+	mo->inst = trx->nr;
+	mo->assoc_so = 255;
+}
+
+static void om2k_ts_to_mo(struct abis_om2k_mo *mo,
+			  const struct gsm_bts_trx_ts *ts)
+{
+	mo->class = OM2K_MO_CLS_TS;
+	mo->bts = 0;
+	mo->inst = ts->nr;
+	mo->assoc_so = ts->trx->nr;
+}
+
+/* Configure a Receiver MO */
+int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct abis_om2k_mo mo;
+
+	om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX);
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ);
+
+	msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn);
+	msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */
+
+	return abis_om2k_sendmsg(trx->bts, msg);
+}
+
+/* Configure a Transmitter MO */
+int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct abis_om2k_mo mo;
+
+	om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX);
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ);
+
+	msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn);
+	msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red);
+	msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0);	/* Filling enabled */
+	msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7);
+	/* Dedication Information is optional */
+
+	return abis_om2k_sendmsg(trx->bts, msg);
+}
+
+enum abis_om2k_tf_mode {
+	OM2K_TF_MODE_MASTER	= 0x00,
+	OM2K_TF_MODE_STANDALONE	= 0x01,
+	OM2K_TF_MODE_SLAVE	= 0x02,
+	OM2K_TF_MODE_UNDEFINED	= 0xff,
+};
+
+static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff };
+
+int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &bts->rbs2000.tf.om2k_mo.addr,
+			OM2K_MSGT_TF_CONF_REQ);
+
+	msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE);
+	msgb_tv_put(msg, OM2K_DEI_TF_SYNC_SRC, 0x00);
+	msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET,
+			  sizeof(fs_offset_undef), fs_offset_undef);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n",
+		om2k_mo_name(&bts->rbs2000.tf.om2k_mo.addr),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_TF_CONF_REQ));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+static uint8_t pchan2comb(enum gsm_phys_chan_config pchan)
+{
+	switch (pchan) {
+	case GSM_PCHAN_CCCH:
+		return 4;
+	case GSM_PCHAN_CCCH_SDCCH4:
+		return 5;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+		return 3;
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+	case GSM_PCHAN_PDCH:
+	case GSM_PCHAN_TCH_F_PDCH:
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		return 8;
+	default:
+		return 0;
+	}
+}
+
+static uint8_t ts2comb(struct gsm_bts_trx_ts *ts)
+{
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F_PDCH:
+		LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use"
+		     " with OM2000, use %s instead\n",
+		     gsm_ts_and_pchan_name(ts),
+		     gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH),
+		     gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH));
+		/* If we allowed initialization of TCH/F_PDCH, it would fail
+		 * when we try to send the ip.access specific RSL PDCH Act
+		 * message for it. Rather fail completely right now: */
+		return 0;
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		return pchan2comb(GSM_PCHAN_TCH_F);
+	default:
+		return pchan2comb(ts->pchan);
+	}
+}
+
+static int put_freq_list(uint8_t *buf, uint16_t arfcn)
+{
+	buf[0] = 0x00; /* TX/RX address */
+	buf[1] = (arfcn >> 8);
+	buf[2] = (arfcn & 0xff);
+
+	return 3;
+}
+
+/* Compute a frequency list in OM2000 fomrmat */
+static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts)
+{
+	uint8_t *cur = list;
+	int len;
+
+	if (ts->hopping.enabled) {
+		unsigned int i;
+		for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+			if (bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+				cur += put_freq_list(cur, i);
+		}
+	} else
+		cur += put_freq_list(cur, ts->trx->arfcn);
+
+	len = cur - list;
+
+	return len;
+}
+
+const uint8_t icm_bound_params[] = { 0x02, 0x06, 0x0c, 0x16, 0x06 };
+
+int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct abis_om2k_mo mo;
+	uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */
+	int freq_list_len;
+
+	om2k_ts_to_mo(&mo, ts);
+
+	memset(freq_list, 0, sizeof(freq_list));
+	freq_list_len = om2k_gen_freq_list(freq_list, ts);
+	if (freq_list_len < 0)
+		return freq_list_len;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ);
+
+	msgb_tv_put(msg, OM2K_DEI_COMBINATION, ts2comb(ts));
+	msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr);
+	msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list);
+	msgb_tv_put(msg, OM2K_DEI_HSN, ts->hopping.hsn);
+	msgb_tv_put(msg, OM2K_DEI_MAIO, ts->hopping.maio);
+	msgb_tv_put(msg, OM2K_DEI_BSIC, ts->trx->bts->bsic);
+	msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */
+	msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0);
+	msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */
+	/* Optional: Interference Rejection Combining */
+	msgb_tv_put(msg, OM2K_DEI_INTERF_REJ_COMB, 0x00);
+	switch (ts->pchan) {
+	case GSM_PCHAN_CCCH:
+		msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06);
+		msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01);
+		msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05);
+		/* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */
+		msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01);
+		break;
+	case GSM_PCHAN_CCCH_SDCCH4:
+		msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10);
+		msgb_tv_put(msg, OM2K_DEI_NY1, 35);
+		msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06);
+		msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0);
+		msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
+		msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01);
+		msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0);
+		msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05);
+		/* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */
+		msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01);
+		msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
+				  sizeof(icm_bound_params), icm_bound_params);
+		break;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+		msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10);
+		msgb_tv_put(msg, OM2K_DEI_NY1, 35);
+		msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0);
+		msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
+		/* Disable RF RESOURCE INDICATION on idle channels */
+		msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0);
+		msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
+				  sizeof(icm_bound_params), icm_bound_params);
+		break;
+	default:
+		msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10);
+		msgb_tv_put(msg, OM2K_DEI_NY1, 35);
+		msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
+		/* Disable RF RESOURCE INDICATION on idle channels */
+		msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0);
+		msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
+				  sizeof(icm_bound_params), icm_bound_params);
+		msgb_tv_put(msg, OM2K_DEI_TTA, 10); /* Timer for Time Alignment */
+		if (ts->pchan == GSM_PCHAN_TCH_H)
+			msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 1); /* TCH/H */
+		else
+			msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 0); /* TCH/F */
+		msgb_tv_put(msg, OM2K_DEI_LSC, 1); /* enabled */
+		msgb_tv_put(msg, OM2K_DEI_LSC_FILT_TIME, 10);	/* units of 100ms */
+		msgb_tv_put(msg, OM2K_DEI_CALL_SUPV_TIME, 8);
+		msgb_tv_put(msg, OM2K_DEI_ENCR_ALG, 0x00);
+		/* Not sure what those below mean */
+		msgb_tv_put(msg, 0x9e, 0x00);
+		msgb_tv_put(msg, 0x9f, 0x37);
+		msgb_tv_put(msg, 0xa0, 0x01);
+		break;
+	}
+
+	DEBUGP(DNM, "Tx MO=%s %s\n",
+		om2k_mo_name(&mo),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ));
+
+	return abis_om2k_sendmsg(ts->trx->bts, msg);
+}
+
+
+/***********************************************************************
+ * OM2000 Managed Object (MO) FSM
+ ***********************************************************************/
+
+#define S(x)	(1 << (x))
+
+enum om2k_event_name {
+	OM2K_MO_EVT_START,
+	OM2K_MO_EVT_RX_CONN_COMPL,
+	OM2K_MO_EVT_RX_RESET_COMPL,
+	OM2K_MO_EVT_RX_START_REQ_ACCEPT,
+	OM2K_MO_EVT_RX_START_RES,
+	OM2K_MO_EVT_RX_CFG_REQ_ACCEPT,
+	OM2K_MO_EVT_RX_CFG_RES,
+	OM2K_MO_EVT_RX_ENA_REQ_ACCEPT,
+	OM2K_MO_EVT_RX_ENA_RES,
+	OM2K_MO_EVT_RX_OPINFO_ACC,
+};
+
+static const struct value_string om2k_event_names[] = {
+	{ OM2K_MO_EVT_START,			"START" },
+	{ OM2K_MO_EVT_RX_CONN_COMPL,		"RX-CONN-COMPL" },
+	{ OM2K_MO_EVT_RX_RESET_COMPL,		"RX-RESET-COMPL" },
+	{ OM2K_MO_EVT_RX_START_REQ_ACCEPT,	"RX-RESET-REQ-ACCEPT" },
+	{ OM2K_MO_EVT_RX_START_RES,		"RX-START-RESULT" },
+	{ OM2K_MO_EVT_RX_CFG_REQ_ACCEPT,	"RX-CFG-REQ-ACCEPT" },
+	{ OM2K_MO_EVT_RX_CFG_RES,		"RX-CFG-RESULT" },
+	{ OM2K_MO_EVT_RX_ENA_REQ_ACCEPT,	"RX-ENABLE-REQ-ACCEPT" },
+	{ OM2K_MO_EVT_RX_ENA_RES,		"RX-ENABLE-RESULT" },
+	{ OM2K_MO_EVT_RX_OPINFO_ACC,		"RX-OPINFO-ACCEPT" },
+	{ 0, NULL }
+};
+
+enum om2k_mo_fsm_state {
+	OM2K_ST_INIT,
+	OM2K_ST_WAIT_CONN_COMPL,
+	OM2K_ST_WAIT_RES_COMPL,
+	OM2K_ST_WAIT_START_ACCEPT,
+	OM2K_ST_WAIT_START_RES,
+	OM2K_ST_WAIT_CFG_ACCEPT,
+	OM2K_ST_WAIT_CFG_RES,
+	OM2K_ST_WAIT_ENABLE_ACCEPT,
+	OM2K_ST_WAIT_ENABLE_RES,
+	OM2K_ST_WAIT_OPINFO_ACCEPT,
+	OM2K_ST_DONE,
+	OM2K_ST_ERROR,
+};
+
+struct om2k_mo_fsm_priv {
+	struct gsm_bts_trx *trx;
+	struct om2k_mo *mo;
+	uint8_t ts_nr;
+};
+
+static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+	OSMO_ASSERT(event == OM2K_MO_EVT_START);
+
+	switch (omfp->mo->addr.class) {
+	case OM2K_MO_CLS_CF:
+		/* no Connect required, is always connected */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr);
+		break;
+	case OM2K_MO_CLS_TRXC:
+		/* no Connect required, start with Reset */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr);
+		break;
+	default:
+		/* start with Connect */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CONN_COMPL,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr);
+		break;
+	}
+}
+
+static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+	switch (omfp->mo->addr.class) {
+#if 0
+	case OM2K_MO_CLS_TF:
+		/* skip the reset, hope that helps */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr);
+		break;
+#endif
+	default:
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr);
+		break;
+	}
+}
+
+static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT,
+				OM2K_TIMEOUT, 0);
+	abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr);
+}
+
+static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_decoded_msg *omd = data;
+
+	switch (omd->msg_type) {
+	case OM2K_MSGT_START_REQ_ACK:
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_RES,
+					OM2K_TIMEOUT, 0);
+		break;
+	case OM2K_MSGT_START_REQ_REJ:
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+		break;
+	}
+}
+
+static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+	struct gsm_bts_trx_ts *ts;
+
+	switch (omfp->mo->addr.class) {
+	case OM2K_MO_CLS_CF:
+	case OM2K_MO_CLS_TRXC:
+		/* Transition directly to Operational Info */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT,
+				OM2K_TIMEOUT, 0);
+		abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1);
+		return;
+	case OM2K_MO_CLS_DP:
+		/* Transition directoy to WAIT_ENABLE_ACCEPT */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
+		return;
+#if 0
+	case OM2K_MO_CLS_TF:
+		/* skip the config, hope that helps speeding things up */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
+					OM2K_TIMEOUT, 0);
+		abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
+		return;
+#endif
+	}
+
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_ACCEPT,
+				OM2K_TIMEOUT, 0);
+	switch (omfp->mo->addr.class) {
+	case OM2K_MO_CLS_TF:
+		abis_om2k_tx_tf_conf_req(omfp->trx->bts);
+		break;
+	case OM2K_MO_CLS_IS:
+		abis_om2k_tx_is_conf_req(omfp->trx->bts);
+		break;
+	case OM2K_MO_CLS_CON:
+		abis_om2k_tx_con_conf_req(omfp->trx->bts);
+		break;
+	case OM2K_MO_CLS_TX:
+		abis_om2k_tx_tx_conf_req(omfp->trx);
+		break;
+	case OM2K_MO_CLS_RX:
+		abis_om2k_tx_rx_conf_req(omfp->trx);
+		break;
+	case OM2K_MO_CLS_TS:
+		ts = mo2obj(omfp->trx->bts, &omfp->mo->addr);
+		abis_om2k_tx_ts_conf_req(ts);
+		break;
+	}
+}
+
+static void om2k_mo_st_wait_cfg_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+	uint32_t timeout = OM2K_TIMEOUT;
+
+	if (omfp->mo->addr.class == OM2K_MO_CLS_TF)
+		timeout = 600;
+
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_RES, timeout, 0);
+}
+
+static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+	struct om2k_decoded_msg *omd = data;
+	uint8_t accordance;
+
+	if (!TLVP_PRESENT(&omd->tp, OM2K_DEI_ACCORDANCE_IND)) {
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+		return;
+	}
+	accordance = *TLVP_VAL(&omd->tp, OM2K_DEI_ACCORDANCE_IND);
+
+	if (accordance != 0) {
+		/* accordance not OK */
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+		return;
+	}
+
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
+				OM2K_TIMEOUT, 0);
+	abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
+}
+
+static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+	struct om2k_decoded_msg *omd = data;
+
+	switch (omd->msg_type) {
+	case OM2K_MSGT_ENABLE_REQ_REJ:
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+		break;
+	case OM2K_MSGT_ENABLE_REQ_ACK:
+		if (omfp->mo->addr.class == OM2K_MO_CLS_IS &&
+		    omfp->trx->bts->rbs2000.use_superchannel)
+			e1inp_ericsson_set_altc(omfp->trx->bts->oml_link->ts->line, 1);
+		osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_RES,
+					OM2K_TIMEOUT, 0);
+	}
+}
+
+static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+	//struct om2k_decoded_msg *omd = data;
+	/* TODO: check if state is actually enabled now? */
+
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT,
+				OM2K_TIMEOUT, 0);
+	abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1);
+}
+
+static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+	/* if we have just received opinfo accept for the timeslot,
+	 * start dynamic TCH switching procedures */
+	if (omfp->mo->addr.class == OM2K_MO_CLS_TS) {
+		struct gsm_bts_trx_ts *ts;
+		ts = mo2obj(omfp->trx->bts, &omfp->mo->addr);
+		dyn_ts_init(ts);
+	}
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_DONE, 0, 0);
+}
+
+static void om2k_mo_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+	omfp->mo->fsm = NULL;
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void om2k_mo_s_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+	omfp->mo->fsm = NULL;
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+}
+
+static const struct osmo_fsm_state om2k_is_states[] = {
+	[OM2K_ST_INIT] = {
+		.name = "INIT",
+		.in_event_mask = S(OM2K_MO_EVT_START),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_CONN_COMPL) |
+				  S(OM2K_ST_WAIT_START_ACCEPT) |
+				  S(OM2K_ST_WAIT_RES_COMPL),
+		.action = om2k_mo_st_init,
+	},
+	[OM2K_ST_WAIT_CONN_COMPL] = {
+		.name = "WAIT-CONN-COMPL",
+		.in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_START_ACCEPT) |
+				  S(OM2K_ST_WAIT_RES_COMPL),
+		.action = om2k_mo_st_wait_conn_compl,
+	},
+	[OM2K_ST_WAIT_RES_COMPL] = {
+		.name = "WAIT-RES-COMPL",
+		.in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_START_ACCEPT),
+		.action = om2k_mo_st_wait_res_compl,
+	},
+	[OM2K_ST_WAIT_START_ACCEPT] = {
+		.name = "WAIT-START-ACCEPT",
+		.in_event_mask = S(OM2K_MO_EVT_RX_START_REQ_ACCEPT),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_START_RES),
+		.action =om2k_mo_st_wait_start_accept,
+	},
+	[OM2K_ST_WAIT_START_RES] = {
+		.name = "WAIT-START-RES",
+		.in_event_mask = S(OM2K_MO_EVT_RX_START_RES),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_CFG_ACCEPT) |
+				  S(OM2K_ST_WAIT_OPINFO_ACCEPT),
+		.action = om2k_mo_st_wait_start_res,
+	},
+	[OM2K_ST_WAIT_CFG_ACCEPT] = {
+		.name = "WAIT-CFG-ACCEPT",
+		.in_event_mask = S(OM2K_MO_EVT_RX_CFG_REQ_ACCEPT),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_CFG_RES),
+		.action = om2k_mo_st_wait_cfg_accept,
+	},
+	[OM2K_ST_WAIT_CFG_RES] = {
+		.name = "WAIT-CFG-RES",
+		.in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_ENABLE_ACCEPT),
+		.action = om2k_mo_st_wait_cfg_res,
+	},
+	[OM2K_ST_WAIT_ENABLE_ACCEPT] = {
+		.name = "WAIT-ENABLE-ACCEPT",
+		.in_event_mask = S(OM2K_MO_EVT_RX_ENA_REQ_ACCEPT),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_ENABLE_RES),
+		.action = om2k_mo_st_wait_enable_accept,
+	},
+	[OM2K_ST_WAIT_ENABLE_RES] = {
+		.name = "WAIT-ENABLE-RES",
+		.in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR) |
+				  S(OM2K_ST_WAIT_OPINFO_ACCEPT),
+		.action = om2k_mo_st_wait_enable_res,
+	},
+	[OM2K_ST_WAIT_OPINFO_ACCEPT] = {
+		.name = "WAIT-OPINFO-ACCEPT",
+		.in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC),
+		.out_state_mask = S(OM2K_ST_DONE) |
+				  S(OM2K_ST_ERROR),
+		.action = om2k_mo_st_wait_opinfo_accept,
+	},
+	[OM2K_ST_DONE] = {
+		.name = "DONE",
+		.in_event_mask = 0,
+		.out_state_mask = 0,
+		.onenter = om2k_mo_s_done_onenter,
+	},
+	[OM2K_ST_ERROR] = {
+		.name = "ERROR",
+		.in_event_mask = 0,
+		.out_state_mask = 0,
+		.onenter = om2k_mo_s_error_onenter,
+	},
+
+};
+
+static int om2k_mo_timer_cb(struct osmo_fsm_inst *fi)
+{
+	osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+	return 0;
+}
+
+static struct osmo_fsm om2k_mo_fsm = {
+	.name = "OM2000-MO",
+	.states = om2k_is_states,
+	.num_states = ARRAY_SIZE(om2k_is_states),
+	.log_subsys = DNM,
+	.event_names = om2k_event_names,
+	.timer_cb = om2k_mo_timer_cb,
+};
+
+struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent,
+					uint32_t term_event,
+					struct gsm_bts_trx *trx, struct om2k_mo *mo)
+{
+	struct osmo_fsm_inst *fi;
+	struct om2k_mo_fsm_priv *omfp;
+	char idbuf[64];
+
+	snprintf(idbuf, sizeof(idbuf), "%s-%s", parent->id,
+		 om2k_mo_name(&mo->addr));
+
+	fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent,
+					  term_event, idbuf);
+	if (!fi)
+		return NULL;
+
+	mo->fsm = fi;
+	omfp = talloc_zero(fi, struct om2k_mo_fsm_priv);
+	omfp->mo = mo;
+	omfp->trx = trx;
+	fi->priv = omfp;
+
+	osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL);
+
+	return fi;
+}
+
+int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo,
+			struct om2k_decoded_msg *odm)
+{
+	switch (odm->msg_type) {
+	case OM2K_MSGT_CONNECT_COMPL:
+	case OM2K_MSGT_CONNECT_REJ:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_CONN_COMPL, odm);
+		break;
+
+	case OM2K_MSGT_RESET_COMPL:
+	case OM2K_MSGT_RESET_REJ:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_RESET_COMPL, odm);
+		break;
+
+	case OM2K_MSGT_START_REQ_ACK:
+	case OM2K_MSGT_START_REQ_REJ:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_START_REQ_ACCEPT, odm);
+		break;
+
+	case OM2K_MSGT_START_RES:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_START_RES, odm);
+		break;
+
+	case OM2K_MSGT_CON_CONF_REQ_ACK:
+	case OM2K_MSGT_IS_CONF_REQ_ACK:
+	case OM2K_MSGT_RX_CONF_REQ_ACK:
+	case OM2K_MSGT_TF_CONF_REQ_ACK:
+	case OM2K_MSGT_TS_CONF_REQ_ACK:
+	case OM2K_MSGT_TX_CONF_REQ_ACK:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm);
+		break;
+
+	case OM2K_MSGT_CON_CONF_RES:
+	case OM2K_MSGT_IS_CONF_RES:
+	case OM2K_MSGT_RX_CONF_RES:
+	case OM2K_MSGT_TF_CONF_RES:
+	case OM2K_MSGT_TS_CONF_RES:
+	case OM2K_MSGT_TX_CONF_RES:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_CFG_RES, odm);
+		break;
+
+	case OM2K_MSGT_ENABLE_REQ_ACK:
+	case OM2K_MSGT_ENABLE_REQ_REJ:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, odm);
+		break;
+	case OM2K_MSGT_ENABLE_RES:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_ENA_RES, odm);
+		break;
+
+	case OM2K_MSGT_OP_INFO_ACK:
+	case OM2K_MSGT_OP_INFO_REJ:
+		osmo_fsm_inst_dispatch(mo->fsm,
+				OM2K_MO_EVT_RX_OPINFO_ACC, odm);
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+/***********************************************************************
+ * OM2000 TRX Finite State Machine, initializes TRXC and all siblings
+ ***********************************************************************/
+
+enum om2k_trx_event {
+	OM2K_TRX_EVT_START,
+	OM2K_TRX_EVT_TRXC_DONE,
+	OM2K_TRX_EVT_TX_DONE,
+	OM2K_TRX_EVT_RX_DONE,
+	OM2K_TRX_EVT_TS_DONE,
+	OM2K_TRX_EVT_STOP,
+};
+
+static struct value_string om2k_trx_events[] = {
+	{ OM2K_TRX_EVT_START,		"START" },
+	{ OM2K_TRX_EVT_TRXC_DONE,	"TRXC-DONE" },
+	{ OM2K_TRX_EVT_TX_DONE,		"TX-DONE" },
+	{ OM2K_TRX_EVT_RX_DONE,		"RX-DONE" },
+	{ OM2K_TRX_EVT_TS_DONE,		"TS-DONE" },
+	{ OM2K_TRX_EVT_STOP,		"STOP" },
+	{ 0, NULL }
+};
+
+enum om2k_trx_state {
+	 OM2K_TRX_S_INIT,
+	 OM2K_TRX_S_WAIT_TRXC,
+	 OM2K_TRX_S_WAIT_TX,
+	 OM2K_TRX_S_WAIT_RX,
+	 OM2K_TRX_S_WAIT_TS,
+	 OM2K_TRX_S_DONE,
+	 OM2K_TRX_S_ERROR
+};
+
+struct om2k_trx_fsm_priv {
+	struct gsm_bts_trx *trx;
+	uint8_t next_ts_nr;
+};
+
+static void om2k_trx_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_trx_fsm_priv *otfp = fi->priv;
+
+	/* First initialize TRXC */
+	osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TRXC,
+				TRX_FSM_TIMEOUT, 0);
+	om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TRXC_DONE, otfp->trx,
+			  &otfp->trx->rbs2000.trxc.om2k_mo);
+}
+
+static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_trx_fsm_priv *otfp = fi->priv;
+
+	/* Initialize TX after TRXC */
+	osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TX,
+				TRX_FSM_TIMEOUT, 0);
+	om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TX_DONE, otfp->trx,
+			  &otfp->trx->rbs2000.tx.om2k_mo);
+}
+
+static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_trx_fsm_priv *otfp = fi->priv;
+
+	/* Initialize RX after TX */
+	osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_RX,
+				TRX_FSM_TIMEOUT, 0);
+	om2k_mo_fsm_start(fi, OM2K_TRX_EVT_RX_DONE, otfp->trx,
+			  &otfp->trx->rbs2000.rx.om2k_mo);
+}
+
+static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_trx_fsm_priv *otfp = fi->priv;
+	struct gsm_bts_trx_ts *ts;
+
+	/* Initialize Timeslots after TX */
+	osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS,
+				TRX_FSM_TIMEOUT, 0);
+	otfp->next_ts_nr = 0;
+	ts = &otfp->trx->ts[otfp->next_ts_nr++];
+	om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
+			  &ts->rbs2000.om2k_mo);
+}
+
+static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_trx_fsm_priv *otfp = fi->priv;
+	struct gsm_bts_trx_ts *ts;
+
+	if (otfp->next_ts_nr < 8) {
+		/* iterate to the next timeslot */
+		ts = &otfp->trx->ts[otfp->next_ts_nr++];
+		om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
+				  &ts->rbs2000.om2k_mo);
+	} else {
+		/* only after all 8 TS */
+		osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0);
+	}
+}
+
+static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct om2k_trx_fsm_priv *otfp = fi->priv;
+	gsm_bts_trx_set_system_infos(otfp->trx);
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static const struct osmo_fsm_state om2k_trx_states[] = {
+	[OM2K_TRX_S_INIT] = {
+		.in_event_mask = S(OM2K_TRX_EVT_START),
+		.out_state_mask = S(OM2K_TRX_S_WAIT_TRXC),
+		.name = "INIT",
+		.action = om2k_trx_s_init,
+	},
+	[OM2K_TRX_S_WAIT_TRXC] = {
+		.in_event_mask = S(OM2K_TRX_EVT_TRXC_DONE),
+		.out_state_mask = S(OM2K_TRX_S_ERROR) |
+				  S(OM2K_TRX_S_WAIT_TX),
+		.name = "WAIT-TRXC",
+		.action = om2k_trx_s_wait_trxc,
+	},
+	[OM2K_TRX_S_WAIT_TX] = {
+		.in_event_mask = S(OM2K_TRX_EVT_TX_DONE),
+		.out_state_mask = S(OM2K_TRX_S_ERROR) |
+				  S(OM2K_TRX_S_WAIT_RX),
+		.name = "WAIT-TX",
+		.action = om2k_trx_s_wait_tx,
+	},
+	[OM2K_TRX_S_WAIT_RX] = {
+		.in_event_mask = S(OM2K_TRX_EVT_RX_DONE),
+		.out_state_mask = S(OM2K_TRX_S_ERROR) |
+				  S(OM2K_TRX_S_WAIT_TS),
+		.name = "WAIT-RX",
+		.action = om2k_trx_s_wait_rx,
+	},
+	[OM2K_TRX_S_WAIT_TS] = {
+		.in_event_mask = S(OM2K_TRX_EVT_TS_DONE),
+		.out_state_mask = S(OM2K_TRX_S_ERROR) |
+				  S(OM2K_TRX_S_DONE),
+		.name = "WAIT-TS",
+		.action = om2k_trx_s_wait_ts,
+	},
+	[OM2K_TRX_S_DONE] = {
+		.name = "DONE",
+		.onenter = om2k_trx_s_done_onenter,
+	},
+	[OM2K_TRX_S_ERROR] = {
+		.name = "ERROR",
+	},
+};
+
+static int om2k_trx_timer_cb(struct osmo_fsm_inst *fi)
+{
+	osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_ERROR, 0, 0);
+	return 0;
+}
+
+static struct osmo_fsm om2k_trx_fsm = {
+	.name = "OM2000-TRX",
+	.states = om2k_trx_states,
+	.num_states = ARRAY_SIZE(om2k_trx_states),
+	.log_subsys = DNM,
+	.event_names = om2k_trx_events,
+	.timer_cb = om2k_trx_timer_cb,
+};
+
+struct osmo_fsm_inst *om2k_trx_fsm_start(struct osmo_fsm_inst *parent,
+					 struct gsm_bts_trx *trx,
+					 uint32_t term_event)
+{
+	struct osmo_fsm_inst *fi;
+	struct om2k_trx_fsm_priv *otfp;
+	char idbuf[32];
+
+	snprintf(idbuf, sizeof(idbuf), "%u/%u", trx->bts->nr, trx->nr);
+
+	fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, term_event,
+					  idbuf);
+	if (!fi)
+		return NULL;
+
+	otfp = talloc_zero(fi, struct om2k_trx_fsm_priv);
+	otfp->trx = trx;
+	fi->priv = otfp;
+
+	osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL);
+
+	return fi;
+}
+
+
+/***********************************************************************
+ * OM2000 BTS Finite State Machine, initializes CF and all siblings
+ ***********************************************************************/
+
+enum om2k_bts_event {
+	OM2K_BTS_EVT_START,
+	OM2K_BTS_EVT_CF_DONE,
+	OM2K_BTS_EVT_IS_DONE,
+	OM2K_BTS_EVT_CON_DONE,
+	OM2K_BTS_EVT_TF_DONE,
+	OM2K_BTS_EVT_TRX_DONE,
+	OM2K_BTS_EVT_STOP,
+};
+
+static const struct value_string om2k_bts_events[] = {
+	{ OM2K_BTS_EVT_START,		"START" },
+	{ OM2K_BTS_EVT_CF_DONE,		"CF-DONE" },
+	{ OM2K_BTS_EVT_IS_DONE,		"IS-DONE" },
+	{ OM2K_BTS_EVT_CON_DONE,	"CON-DONE" },
+	{ OM2K_BTS_EVT_TF_DONE,		"TF-DONE" },
+	{ OM2K_BTS_EVT_TRX_DONE,	"TRX-DONE" },
+	{ OM2K_BTS_EVT_STOP,		"STOP" },
+	{ 0, NULL }
+};
+
+enum om2k_bts_state {
+	OM2K_BTS_S_INIT,
+	OM2K_BTS_S_WAIT_CF,
+	OM2K_BTS_S_WAIT_IS,
+	OM2K_BTS_S_WAIT_CON,
+	OM2K_BTS_S_WAIT_TF,
+	OM2K_BTS_S_WAIT_TRX,
+	OM2K_BTS_S_DONE,
+	OM2K_BTS_S_ERROR,
+};
+
+struct om2k_bts_fsm_priv {
+	struct gsm_bts *bts;
+	uint8_t next_trx_nr;
+};
+
+static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_bts_fsm_priv *obfp = fi->priv;
+	struct gsm_bts *bts = obfp->bts;
+
+	OSMO_ASSERT(event == OM2K_BTS_EVT_START);
+	osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CF,
+				BTS_FSM_TIMEOUT, 0);
+	om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CF_DONE, bts->c0,
+			  &bts->rbs2000.cf.om2k_mo);
+}
+
+static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_bts_fsm_priv *obfp = fi->priv;
+	struct gsm_bts *bts = obfp->bts;
+
+	OSMO_ASSERT(event == OM2K_BTS_EVT_CF_DONE);
+	/* TF can take a long time to initialize, wait for 10min */
+	osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TF, 600, 0);
+	om2k_mo_fsm_start(fi, OM2K_BTS_EVT_TF_DONE, bts->c0,
+			  &bts->rbs2000.tf.om2k_mo);
+}
+
+static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_bts_fsm_priv *obfp = fi->priv;
+	struct gsm_bts *bts = obfp->bts;
+
+	OSMO_ASSERT(event == OM2K_BTS_EVT_TF_DONE);
+
+	osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CON,
+				BTS_FSM_TIMEOUT, 0);
+	om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CON_DONE, bts->c0,
+			  &bts->rbs2000.con.om2k_mo);
+}
+
+static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_bts_fsm_priv *obfp = fi->priv;
+	struct gsm_bts *bts = obfp->bts;
+
+	OSMO_ASSERT(event == OM2K_BTS_EVT_CON_DONE);
+
+	osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS,
+				BTS_FSM_TIMEOUT, 0);
+	om2k_mo_fsm_start(fi, OM2K_BTS_EVT_IS_DONE, bts->c0,
+			  &bts->rbs2000.is.om2k_mo);
+}
+
+static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_bts_fsm_priv *obfp = fi->priv;
+	struct gsm_bts_trx *trx;
+
+	OSMO_ASSERT(event == OM2K_BTS_EVT_IS_DONE);
+
+	osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX,
+				BTS_FSM_TIMEOUT, 0);
+	obfp->next_trx_nr = 0;
+	trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++);
+	om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE);
+}
+
+static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct om2k_bts_fsm_priv *obfp = fi->priv;
+
+	OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_DONE);
+
+	if (obfp->next_trx_nr < obfp->bts->num_trx) {
+		struct gsm_bts_trx *trx;
+		trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++);
+		om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE);
+	} else {
+		osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0);
+	}
+}
+
+static void om2k_bts_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static const struct osmo_fsm_state om2k_bts_states[] = {
+	[OM2K_BTS_S_INIT] = {
+		.in_event_mask = S(OM2K_BTS_EVT_START),
+		.out_state_mask = S(OM2K_BTS_S_WAIT_CF),
+		.name = "INIT",
+		.action = om2k_bts_s_init,
+	},
+	[OM2K_BTS_S_WAIT_CF] = {
+		.in_event_mask = S(OM2K_BTS_EVT_CF_DONE),
+		.out_state_mask = S(OM2K_BTS_S_ERROR) |
+				  S(OM2K_BTS_S_WAIT_TF),
+		.name = "WAIT-CF",
+		.action = om2k_bts_s_wait_cf,
+	},
+	[OM2K_BTS_S_WAIT_TF] = {
+		.in_event_mask = S(OM2K_BTS_EVT_TF_DONE),
+		.out_state_mask = S(OM2K_BTS_S_ERROR) |
+				  S(OM2K_BTS_S_WAIT_CON),
+		.name = "WAIT-TF",
+		.action = om2k_bts_s_wait_tf,
+	},
+	[OM2K_BTS_S_WAIT_CON] = {
+		.in_event_mask = S(OM2K_BTS_EVT_CON_DONE),
+		.out_state_mask = S(OM2K_BTS_S_ERROR) |
+				  S(OM2K_BTS_S_WAIT_IS),
+		.name = "WAIT-CON",
+		.action = om2k_bts_s_wait_con,
+	},
+	[OM2K_BTS_S_WAIT_IS] = {
+		.in_event_mask = S(OM2K_BTS_EVT_IS_DONE),
+		.out_state_mask = S(OM2K_BTS_S_ERROR) |
+				  S(OM2K_BTS_S_WAIT_TRX),
+		.name = "WAIT-IS",
+		.action = om2k_bts_s_wait_is,
+	},
+	[OM2K_BTS_S_WAIT_TRX] = {
+		.in_event_mask = S(OM2K_BTS_EVT_TRX_DONE),
+		.out_state_mask = S(OM2K_BTS_S_ERROR) |
+				  S(OM2K_BTS_S_DONE),
+		.name = "WAIT-TRX",
+		.action = om2k_bts_s_wait_trx,
+	},
+	[OM2K_BTS_S_DONE] = {
+		.name = "DONE",
+		.onenter = om2k_bts_s_done_onenter,
+	},
+	[OM2K_BTS_S_ERROR] = {
+		.name = "ERROR",
+	},
+};
+
+static int om2k_bts_timer_cb(struct osmo_fsm_inst *fi)
+{
+	osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0);
+	return 0;
+}
+
+static struct osmo_fsm om2k_bts_fsm = {
+	.name = "OM2000-BTS",
+	.states = om2k_bts_states,
+	.num_states = ARRAY_SIZE(om2k_bts_states),
+	.log_subsys = DNM,
+	.event_names = om2k_bts_events,
+	.timer_cb = om2k_bts_timer_cb,
+};
+
+struct osmo_fsm_inst *
+om2k_bts_fsm_start(struct gsm_bts *bts)
+{
+	struct osmo_fsm_inst *fi;
+	struct om2k_bts_fsm_priv *obfp;
+	char idbuf[16];
+
+	snprintf(idbuf, sizeof(idbuf), "%u", bts->nr);
+
+	fi = osmo_fsm_inst_alloc(&om2k_bts_fsm, bts, NULL,
+				 LOGL_DEBUG, idbuf);
+	if (!fi)
+		return NULL;
+	fi->priv = obfp = talloc_zero(fi, struct om2k_bts_fsm_priv);
+	obfp->bts = bts;
+
+	osmo_fsm_inst_dispatch(fi, OM2K_BTS_EVT_START, NULL);
+
+	return fi;
+}
+
+
+/***********************************************************************
+ * OM2000 Negotiation
+ ***********************************************************************/
+
+static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+				      uint8_t *data, unsigned int len)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK);
+
+	msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+struct iwd_version {
+	uint8_t gen_char[3+1];
+	uint8_t rev_char[3+1];
+};
+
+struct iwd_type {
+	uint8_t num_vers;
+	struct iwd_version v[8];
+};
+
+static int om2k_rx_negot_req(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	struct iwd_type iwd_types[16];
+	uint8_t num_iwd_types = o2h->data[2];
+	uint8_t *cur = o2h->data+3;
+	unsigned int i, v;
+
+	uint8_t out_buf[1024];
+	uint8_t *out_cur = out_buf+1;
+	uint8_t out_num_types = 0;
+
+	memset(iwd_types, 0, sizeof(iwd_types));
+
+	/* Parse the RBS-supported IWD versions into iwd_types array */
+	for (i = 0; i < num_iwd_types; i++) {
+		uint8_t num_versions = *cur++;
+		uint8_t iwd_type = *cur++;
+
+		iwd_types[iwd_type].num_vers = num_versions;
+
+		for (v = 0; v < num_versions; v++) {
+			struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v];
+
+			memcpy(iwd_v->gen_char, cur, 3);
+			cur += 3;
+			memcpy(iwd_v->rev_char, cur, 3);
+			cur += 3;
+
+			DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type,
+				iwd_v->gen_char, iwd_v->rev_char);
+		}
+	}
+
+	/* Select the last version for each IWD type */
+	for (i = 0; i < ARRAY_SIZE(iwd_types); i++) {
+		struct iwd_type *type = &iwd_types[i];
+		struct iwd_version *last_v;
+
+		if (type->num_vers == 0)
+			continue;
+
+		out_num_types++;
+
+		last_v = &type->v[type->num_vers-1];
+
+		*out_cur++ = i;
+		memcpy(out_cur, last_v->gen_char, 3);
+		out_cur += 3;
+		memcpy(out_cur, last_v->rev_char, 3);
+		out_cur += 3;
+	}
+
+	out_buf[0] = out_num_types;
+
+	return abis_om2k_tx_negot_req_ack(sign_link->trx->bts, &o2h->mo, out_buf, out_cur - out_buf);
+}
+
+
+/***********************************************************************
+ * OM2000 Receive Message Handler
+ ***********************************************************************/
+
+static int om2k_rx_nack(struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	uint16_t msg_type = ntohs(o2h->msg_type);
+	struct tlv_parsed tp;
+
+	LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s", om2k_mo_name(&o2h->mo),
+		get_value_string(om2k_msgcode_vals, msg_type));
+
+	abis_om2k_msg_tlv_parse(&tp, o2h);
+	if (TLVP_PRESENT(&tp, OM2K_DEI_REASON_CODE))
+		LOGPC(DNM, LOGL_ERROR, ", Reason 0x%02x",
+			*TLVP_VAL(&tp, OM2K_DEI_REASON_CODE));
+
+	if (TLVP_PRESENT(&tp, OM2K_DEI_RESULT_CODE))
+		LOGPC(DNM, LOGL_ERROR, ", Result %s",
+			get_value_string(om2k_result_strings,
+					 *TLVP_VAL(&tp, OM2K_DEI_RESULT_CODE)));
+	LOGPC(DNM, LOGL_ERROR, "\n");
+
+	return 0;
+}
+
+static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm)
+{
+	uint8_t mo_state;
+
+	if (!TLVP_PRESENT(&odm->tp, OM2K_DEI_MO_STATE))
+		return -EIO;
+	mo_state = *TLVP_VAL(&odm->tp, OM2K_DEI_MO_STATE);
+
+	LOGP(DNM, LOGL_DEBUG, "Rx MO=%s %s, MO State: %s\n",
+		om2k_mo_name(&odm->o2h.mo),
+		get_value_string(om2k_msgcode_vals, odm->msg_type),
+		get_value_string(om2k_mostate_vals, mo_state));
+
+	/* Throw error message in case we see an enable rsponse that does
+	 * not yield an enabled mo-state */
+	if (odm->msg_type == OM2K_MSGT_ENABLE_RES
+	    && mo_state != OM2K_MO_S_ENABLED) {
+		LOGP(DNM, LOGL_ERROR,
+		     "Rx MO=%s %s Failed to enable MO State!\n",
+		     om2k_mo_name(&odm->o2h.mo),
+		     get_value_string(om2k_msgcode_vals, odm->msg_type));
+	}
+
+	update_mo_state(bts, &odm->o2h.mo, mo_state);
+
+	return 0;
+}
+
+/* Display fault report bits (helper function of display_fault_maps()) */
+static bool display_fault_bits(const uint8_t *vect, uint16_t len,
+			       uint8_t dei, const struct abis_om2k_mo *mo)
+{
+	uint16_t i;
+	int k;
+	bool faults_present = false;
+	int first = 1;
+	char string[255];
+
+	/* Check if errors are present at all */
+	for (i = 0; i < len; i++)
+		if (vect[i])
+			faults_present = true;
+	if (!faults_present)
+		return false;
+
+	sprintf(string, "Fault Report: %s (",
+		get_value_string(om2k_attr_vals, dei));
+
+	for (i = 0; i < len; i++) {
+		for (k = 0; k < 8; k++) {
+			if ((vect[i] >> k) & 1) {
+				if (!first)
+					sprintf(string + strlen(string), ",");
+				sprintf(string + strlen(string), "%d", k + i*8);
+				first = 0;
+			}
+		}
+	}
+
+	sprintf(string + strlen(string), ")\n");
+	DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string);
+
+	return true;
+}
+
+/* Display fault report maps */
+static void display_fault_maps(const uint8_t *src, unsigned int src_len,
+			       const struct abis_om2k_mo *mo)
+{
+	uint8_t tag;
+	uint16_t tag_len;
+	const uint8_t *val;
+	int src_pos = 0;
+	int rc;
+	int tlv_count = 0;
+	uint16_t msg_code;
+	bool faults_present = false;
+
+	/* Chop off header */
+	src+=4;
+	src_len-=4;
+
+	/* Check message type */
+	msg_code = (*src & 0xff) << 8;
+	src++;
+	src_len--;
+	msg_code |= (*src & 0xff);
+	src++;
+	src_len--;
+	if (msg_code != OM2K_MSGT_FAULT_REP) {
+		LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault report: invalid message code!\n",
+		     om2k_mo_name(mo));
+		return;
+	}
+
+	/* Chop off mo-interface */
+	src += 4;
+	src_len -= 4;
+
+	/* Iterate over each TLV element */
+	while (1) {
+
+		/* Bail if an the maximum number of TLV fields
+		 * have been parsed */
+		if (tlv_count >= 11) {
+			LOGP(DNM, LOGL_ERROR,
+			     "Rx MO=%s Fault Report: too many tlv elements!\n",
+			     om2k_mo_name(mo));
+			return;
+		}
+
+		/* Parse TLV field */
+		rc = tlv_parse_one(&tag, &tag_len, &val, &om2k_att_tlvdef,
+				   src + src_pos, src_len - src_pos);
+		if (rc > 0)
+			src_pos += rc;
+		else {
+			LOGP(DNM, LOGL_ERROR,
+			     "Rx MO=%s Fault Report: invalid tlv element!\n",
+			     om2k_mo_name(mo));
+			return;
+		}
+
+		switch (tag) {
+		case OM2K_DEI_INT_FAULT_MAP_1A:
+		case OM2K_DEI_INT_FAULT_MAP_1B:
+		case OM2K_DEI_INT_FAULT_MAP_2A:
+		case OM2K_DEI_EXT_COND_MAP_1:
+		case OM2K_DEI_EXT_COND_MAP_2:
+		case OM2K_DEI_REPL_UNIT_MAP:
+		case OM2K_DEI_INT_FAULT_MAP_2A_EXT:
+		case OM2K_DEI_EXT_COND_MAP_2_EXT:
+		case OM2K_DEI_REPL_UNIT_MAP_EXT:
+			faults_present |= display_fault_bits(val, tag_len,
+							     tag, mo);
+			break;
+		}
+
+		/* Stop when no further TLV elements can be expected */
+		if (src_len - src_pos < 2)
+			break;
+
+		tlv_count++;
+	}
+
+	if (!faults_present) {
+		DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n",
+		       om2k_mo_name(mo));
+	}
+}
+
+int abis_om2k_rcvmsg(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+	struct gsm_bts *bts = sign_link->trx->bts;
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	struct abis_om_hdr *oh = &o2h->om;
+	uint16_t msg_type = ntohs(o2h->msg_type);
+	struct om2k_decoded_msg odm;
+	struct om2k_mo *mo;
+	int rc = 0;
+
+	/* Various consistency checks */
+	if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+			oh->placement);
+		if (oh->placement != ABIS_OM_PLACEMENT_FIRST)
+			return -EINVAL;
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+			oh->sequence);
+		return -EINVAL;
+	}
+
+	msg->l3h = (unsigned char *)o2h + sizeof(*o2h);
+
+	if (oh->mdisc != ABIS_OM_MDISC_FOM) {
+		LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n",
+			oh->mdisc);
+		return -EINVAL;
+	}
+
+	DEBUGP(DNM, "Rx MO=%s %s (%s)\n", om2k_mo_name(&o2h->mo),
+		get_value_string(om2k_msgcode_vals, msg_type),
+		osmo_hexdump(msg->l2h, msgb_l2len(msg)));
+
+	om2k_decode_msg(&odm, msg);
+
+	process_mo_state(bts, &odm);
+
+	switch (msg_type) {
+	case OM2K_MSGT_CAL_TIME_REQ:
+		rc = abis_om2k_cal_time_resp(bts);
+		break;
+	case OM2K_MSGT_FAULT_REP:
+		display_fault_maps(msg->l2h, msgb_l2len(msg), &o2h->mo);
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_FAULT_REP_ACK);
+		break;
+	case OM2K_MSGT_NEGOT_REQ:
+		rc = om2k_rx_negot_req(msg);
+		break;
+	case OM2K_MSGT_START_RES:
+		/* common processing here */
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_RES_ACK);
+		/* below we dispatch into MO */
+		break;
+	case OM2K_MSGT_IS_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_CON_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_TX_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_RX_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_TS_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_TF_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_ENABLE_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK);
+		break;
+	case OM2K_MSGT_DISABLE_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK);
+		break;
+	case OM2K_MSGT_TEST_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK);
+		break;
+	case OM2K_MSGT_CAPA_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_RES_ACK);
+		break;
+	/* ERrors */
+	case OM2K_MSGT_START_REQ_REJ:
+	case OM2K_MSGT_CONNECT_REJ:
+	case OM2K_MSGT_OP_INFO_REJ:
+	case OM2K_MSGT_DISCONNECT_REJ:
+	case OM2K_MSGT_TEST_REQ_REJ:
+	case OM2K_MSGT_CON_CONF_REQ_REJ:
+	case OM2K_MSGT_IS_CONF_REQ_REJ:
+	case OM2K_MSGT_TX_CONF_REQ_REJ:
+	case OM2K_MSGT_RX_CONF_REQ_REJ:
+	case OM2K_MSGT_TS_CONF_REQ_REJ:
+	case OM2K_MSGT_TF_CONF_REQ_REJ:
+	case OM2K_MSGT_ENABLE_REQ_REJ:
+	case OM2K_MSGT_ALARM_STATUS_REQ_REJ:
+	case OM2K_MSGT_DISABLE_REQ_REJ:
+		rc = om2k_rx_nack(msg);
+		break;
+	}
+
+	/* Resolve the MO for this message */
+	mo = get_om2k_mo(bts, &o2h->mo);
+	if (!mo) {
+		LOGP(DNM, LOGL_ERROR, "Couldn't resolve MO for OM2K msg "
+		     "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type),
+		     msgb_hexdump(msg));
+		return 0;
+	}
+	if (!mo->fsm) {
+		LOGP(DNM, LOGL_ERROR, "MO object should not generate any message. fsm == NULL "
+		     "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type),
+		     msgb_hexdump(msg));
+		return 0;
+	}
+
+	/* Dispatch message to that MO */
+	om2k_mo_fsm_recvmsg(bts, mo, &odm);
+
+	msgb_free(msg);
+	return rc;
+}
+
+static void om2k_mo_init(struct om2k_mo *mo, uint8_t class,
+			 uint8_t bts_nr, uint8_t assoc_so, uint8_t inst)
+{
+	mo->addr.class = class;
+	mo->addr.bts = bts_nr;
+	mo->addr.assoc_so = assoc_so;
+	mo->addr.inst = inst;
+}
+
+/* initialize the OM2K_MO members of gsm_bts_trx and its timeslots */
+void abis_om2k_trx_init(struct gsm_bts_trx *trx)
+{
+	struct gsm_bts *bts = trx->bts;
+	unsigned int i;
+
+	OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000);
+
+	om2k_mo_init(&trx->rbs2000.trxc.om2k_mo, OM2K_MO_CLS_TRXC,
+		     bts->nr, 255, trx->nr);
+	om2k_mo_init(&trx->rbs2000.tx.om2k_mo, OM2K_MO_CLS_TX,
+		     bts->nr, 255, trx->nr);
+	om2k_mo_init(&trx->rbs2000.rx.om2k_mo, OM2K_MO_CLS_RX,
+		     bts->nr, 255, trx->nr);
+
+	for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+		om2k_mo_init(&trx->ts[i].rbs2000.om2k_mo, OM2K_MO_CLS_TS,
+				bts->nr, trx->nr, i);
+	}
+}
+
+/* initialize the OM2K_MO members of gsm_bts */
+void abis_om2k_bts_init(struct gsm_bts *bts)
+{
+	OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000);
+
+	om2k_mo_init(&bts->rbs2000.cf.om2k_mo, OM2K_MO_CLS_CF,
+			bts->nr, 0xFF, 0);
+	om2k_mo_init(&bts->rbs2000.is.om2k_mo, OM2K_MO_CLS_IS,
+			bts->nr, 0xFF, 0);
+	om2k_mo_init(&bts->rbs2000.con.om2k_mo, OM2K_MO_CLS_CON,
+			bts->nr, 0xFF, 0);
+	om2k_mo_init(&bts->rbs2000.dp.om2k_mo, OM2K_MO_CLS_DP,
+			bts->nr, 0xFF, 0);
+	om2k_mo_init(&bts->rbs2000.tf.om2k_mo, OM2K_MO_CLS_TF,
+			bts->nr, 0xFF, 0);
+}
+
+static __attribute__((constructor)) void abis_om2k_init(void)
+{
+	osmo_fsm_register(&om2k_mo_fsm);
+	osmo_fsm_register(&om2k_bts_fsm);
+	osmo_fsm_register(&om2k_trx_fsm);
+}
diff --git a/openbsc/src/libbsc/abis_om2000_vty.c b/openbsc/src/libbsc/abis_om2000_vty.c
new file mode 100644
index 0000000..11dd131
--- /dev/null
+++ b/openbsc/src/libbsc/abis_om2000_vty.c
@@ -0,0 +1,607 @@
+/* VTY interface for A-bis OM2000 */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct cmd_node om2k_node = {
+	OM2K_NODE,
+	"%s(om2k)# ",
+	1,
+};
+
+static struct cmd_node om2k_con_group_node = {
+	OM2K_CON_GROUP_NODE,
+	"%s(om2k-con-group)# ",
+	1,
+};
+
+struct con_group;
+
+struct oml_node_state {
+	struct gsm_bts *bts;
+	struct abis_om2k_mo mo;
+	struct con_group *cg;
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+/* FIXME: auto-generate those strings from the value_string lists */
+#define OM2K_OBJCLASS_VTY "(trxc|ts|tf|is|con|dp|cf|tx|rx)"
+#define OM2K_OBJCLASS_VTY_HELP 	"TRX Controller\n"	\
+				"Timeslot\n"		\
+				"Timing Function\n"	\
+				"Interface Switch\n"	\
+				"Abis Concentrator\n"	\
+				"Digital Path\n"	\
+				"Central Function\n"	\
+				"Transmitter\n"		\
+				"Receiver\n"
+
+DEFUN(om2k_class_inst, om2k_class_inst_cmd,
+	"bts <0-255> om2000 class " OM2K_OBJCLASS_VTY
+					" <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OM2000 managed objects\n"
+	"Object Class\n" 	OM2K_OBJCLASS_VTY_HELP
+	"BTS Number\n" "Associated SO Instance\n" "Instance Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (bts->type != GSM_BTS_TYPE_RBS2000) {
+		vty_out(vty, "%% BTS %d not an Ericsson RBS%s",
+			bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]);
+	oms->mo.bts = atoi(argv[2]);
+	oms->mo.assoc_so = atoi(argv[3]);
+	oms->mo.inst = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OM2K_NODE;
+
+	return CMD_SUCCESS;
+
+}
+
+DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd,
+	"bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OML managed objects\n"
+	"Object Class\n" "Object Class\n"
+	"BTS Number\n" "Associated SO Instance\n" "Instance Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->mo.class = atoi(argv[1]);
+	oms->mo.bts = atoi(argv[2]);
+	oms->mo.assoc_so = atoi(argv[3]);
+	oms->mo.inst = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OM2K_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_reset, om2k_reset_cmd,
+	"reset-command",
+	"Reset the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_reset_cmd(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_start, om2k_start_cmd,
+	"start-request",
+	"Start the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_start_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_status, om2k_status_cmd,
+	"status-request",
+	"Get the MO Status\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_status_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_connect, om2k_connect_cmd,
+	"connect-command",
+	"Connect the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_connect_cmd(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_disconnect, om2k_disconnect_cmd,
+	"disconnect-command",
+	"Disconnect the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_enable, om2k_enable_cmd,
+	"enable-request",
+	"Enable the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_enable_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_disable, om2k_disable_cmd,
+	"disable-request",
+	"Disable the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_disable_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_op_info, om2k_op_info_cmd,
+	"operational-info <0-1>",
+	"Set operational information\n"
+	"Set operational info to 0 or 1\n")
+{
+	struct oml_node_state *oms = vty->index;
+	int oper = atoi(argv[0]);
+
+	abis_om2k_tx_op_info(oms->bts, &oms->mo, oper);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_test, om2k_test_cmd,
+	"test-request",
+	"Test the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_test_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_cap_req, om2k_cap_req_cmd,
+	"capabilities-request",
+	"Request MO capabilities\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_cap_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+static struct con_group *con_group_find_or_create(struct gsm_bts *bts, uint8_t cg)
+{
+	struct con_group *ent;
+
+	llist_for_each_entry(ent, &bts->rbs2000.con.conn_groups, list) {
+		if (ent->cg == cg)
+			return ent;
+	}
+
+	ent = talloc_zero(bts, struct con_group);
+	ent->bts = bts;
+	ent->cg = cg;
+	INIT_LLIST_HEAD(&ent->paths);
+	llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups);
+
+	return ent;
+}
+
+static int con_group_del(struct gsm_bts *bts, uint8_t cg_id)
+{
+	struct con_group *cg, *cg2;
+
+	llist_for_each_entry_safe(cg, cg2, &bts->rbs2000.con.conn_groups, list) {
+		if (cg->cg == cg_id) {
+			llist_del(&cg->list);
+			talloc_free(cg);
+			return 0;
+		};
+	}
+	return -ENOENT;
+}
+
+static void con_group_add_path(struct con_group *cg, uint16_t ccp,
+				uint8_t ci, uint8_t tag, uint8_t tei)
+{
+	struct con_path *cp = talloc_zero(cg, struct con_path);
+
+	cp->ccp = ccp;
+	cp->ci = ci;
+	cp->tag = tag;
+	cp->tei = tei;
+	llist_add(&cp->list, &cg->paths);
+}
+
+static int con_group_del_path(struct con_group *cg, uint16_t ccp,
+				uint8_t ci, uint8_t tag, uint8_t tei)
+{
+	struct con_path *cp, *cp2;
+	llist_for_each_entry_safe(cp, cp2, &cg->paths, list) {
+		if (cp->ccp == ccp && cp->ci == ci && cp->tag == tag &&
+		    cp->tei == tei) {
+			llist_del(&cp->list);
+			talloc_free(cp);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd,
+	"con-connection-group <1-31>",
+	"Configure a CON (Concentrator) Connection Group\n"
+	"CON Connection Group Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct con_group *cg;
+	uint8_t cgid = atoi(argv[0]);
+
+	if (bts->type != GSM_BTS_TYPE_RBS2000) {
+		vty_out(vty, "%% CON MO only exists in RBS2000%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	cg = con_group_find_or_create(bts, cgid);
+	if (!cg) {
+		vty_out(vty, "%% Cannot create CON Group%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->node = OM2K_CON_GROUP_NODE;
+	vty->index = cg;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(del_om2k_con_group, del_om2k_con_group_cmd,
+	"del-connection-group <1-31>",
+	"Delete a CON (Concentrator) Connection Group\n"
+	"CON Connection Group Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc;
+	uint8_t cgid = atoi(argv[0]);
+
+	if (bts->type != GSM_BTS_TYPE_RBS2000) {
+		vty_out(vty, "%% CON MO only exists in RBS2000%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = con_group_del(bts, cgid);
+	if (rc != 0) {
+		vty_out(vty, "%% Cannot delete CON Group%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define CON_PATH_HELP	"CON Path (In/Out)\n"				\
+			"Add CON Path to Concentration Group\n"		\
+			"Delete CON Path from Concentration Group\n"	\
+			"CON Conection Point\n"				\
+			"Contiguity Index\n"				\
+
+DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd,
+	"con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>",
+	CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n")
+{
+	struct con_group *cg = vty->index;
+	uint16_t ccp = atoi(argv[1]);
+	uint8_t ci = atoi(argv[2]);
+	uint8_t tei = atoi(argv[3]);
+
+	if (!strcmp(argv[0], "add"))
+		con_group_add_path(cg, ccp, ci, 0, tei);
+	else {
+		if (con_group_del_path(cg, ccp, ci, 0, tei) < 0) {
+			vty_out(vty, "%% No matching CON Path%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd,
+	"con-path (add|del) <0-2047> <0-255> concentrated <1-16>",
+	CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n")
+{
+	struct con_group *cg = vty->index;
+	uint16_t ccp = atoi(argv[1]);
+	uint8_t ci = atoi(argv[2]);
+	uint8_t tag = atoi(argv[3]);
+
+	if (!strcmp(argv[0], "add"))
+		con_group_add_path(cg, ccp, ci, tag, 0xff);
+	else {
+		if (con_group_del_path(cg, ccp, ci, tag, 0xff) < 0) {
+			vty_out(vty, "%% No matching CON list entry%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd,
+	"abis-lower-transport (single-timeslot|super-channel)",
+	"Configure thee Abis Lower Transport\n"
+	"Single Timeslot (classic Abis)\n"
+	"SuperChannel (Packet Abis)\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct con_group *cg;
+
+	if (bts->type != GSM_BTS_TYPE_RBS2000) {
+		vty_out(vty, "%% Command only works for RBS2000%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "super-channel"))
+		bts->rbs2000.use_superchannel = 1;
+	else
+		bts->rbs2000.use_superchannel = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd,
+	"is-connection-list (add|del) <0-2047> <0-2047> <0-255>",
+	"Interface Switch Connection List\n"
+	"Add to IS list\n" "Delete from IS list\n"
+	"ICP1\n" "ICP2\n" "Contiguity Index\n")
+{
+	struct gsm_bts *bts = vty->index;
+	uint16_t icp1 = atoi(argv[1]);
+	uint16_t icp2 = atoi(argv[2]);
+	uint8_t ci = atoi(argv[3]);
+	struct is_conn_group *grp, *grp2;
+
+	if (bts->type != GSM_BTS_TYPE_RBS2000) {
+		vty_out(vty, "%% IS MO only exists in RBS2000%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "add")) {
+		grp = talloc_zero(bts, struct is_conn_group);
+		grp->icp1 = icp1;
+		grp->icp2 = icp2;
+		grp->ci = ci;
+		llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups);
+	} else {
+		llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) {
+			if (grp->icp1 == icp1 && grp->icp2 == icp2
+			    && grp->ci == ci) {
+				llist_del(&grp->list);
+				talloc_free(grp);
+				return CMD_SUCCESS;
+			}
+		}
+		vty_out(vty, "%% No matching IS Conn Group found!%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(om2k_conf_req, om2k_conf_req_cmd,
+	"configuration-request",
+	"Send the configuration request for current MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	struct gsm_bts_trx *trx = NULL;
+	struct gsm_bts_trx_ts *ts = NULL;
+
+	switch (oms->mo.class) {
+	case OM2K_MO_CLS_IS:
+		abis_om2k_tx_is_conf_req(bts);
+		break;
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so);
+		if (!trx) {
+			vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
+				oms->mo.assoc_so, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) {
+			vty_out(vty, "%% Timeslot %u out of range%s",
+				oms->mo.inst, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &trx->ts[oms->mo.inst];
+		abis_om2k_tx_ts_conf_req(ts);
+		break;
+	case OM2K_MO_CLS_RX:
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_TRXC:
+		trx = gsm_bts_trx_by_nr(bts, oms->mo.inst);
+		if (!trx) {
+			vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
+				oms->mo.inst, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		switch (oms->mo.class) {
+		case OM2K_MO_CLS_RX:
+			abis_om2k_tx_rx_conf_req(trx);
+			break;
+		case OM2K_MO_CLS_TX:
+			abis_om2k_tx_tx_conf_req(trx);
+			break;
+		default:
+			break;
+		}
+		break;
+	case OM2K_MO_CLS_TF:
+		abis_om2k_tx_tf_conf_req(bts);
+		break;
+	default:
+		vty_out(vty, "%% Don't know how to configure MO%s",
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void dump_con_group(struct vty *vty, struct con_group *cg)
+{
+	struct con_path *cp;
+
+	llist_for_each_entry(cp, &cg->paths, list) {
+		vty_out(vty, "   con-path add %u %u ", cp->ccp, cp->ci);
+		if (cp->tei == 0xff) {
+			vty_out(vty, "concentrated %u%s", cp->tag,
+				VTY_NEWLINE);
+		} else {
+			vty_out(vty, "deconcentrated %u%s", cp->tei,
+				VTY_NEWLINE);
+		}
+	}
+}
+
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	struct is_conn_group *igrp;
+	struct con_group *cgrp;
+
+	llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list)
+		vty_out(vty, "  is-connection-list add %u %u %u%s",
+			igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE);
+
+	llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) {
+		vty_out(vty, "  con-connection-group %u%s", cgrp->cg,
+			VTY_NEWLINE);
+		dump_con_group(vty, cgrp);
+	}
+	if (bts->rbs2000.use_superchannel)
+		vty_out(vty, "  abis-lower-transport super-channel%s",
+			VTY_NEWLINE);
+}
+
+int abis_om2k_vty_init(void)
+{
+	install_element(ENABLE_NODE, &om2k_class_inst_cmd);
+	install_element(ENABLE_NODE, &om2k_classnum_inst_cmd);
+	install_node(&om2k_node, dummy_config_write);
+
+	install_element(OM2K_NODE, &om2k_reset_cmd);
+	install_element(OM2K_NODE, &om2k_start_cmd);
+	install_element(OM2K_NODE, &om2k_status_cmd);
+	install_element(OM2K_NODE, &om2k_connect_cmd);
+	install_element(OM2K_NODE, &om2k_disconnect_cmd);
+	install_element(OM2K_NODE, &om2k_enable_cmd);
+	install_element(OM2K_NODE, &om2k_disable_cmd);
+	install_element(OM2K_NODE, &om2k_op_info_cmd);
+	install_element(OM2K_NODE, &om2k_test_cmd);
+	install_element(OM2K_NODE, &om2k_cap_req_cmd);
+	install_element(OM2K_NODE, &om2k_conf_req_cmd);
+
+	install_node(&om2k_con_group_node, dummy_config_write);
+	install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_dec_cmd);
+	install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_conc_cmd);
+
+	install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd);
+	install_element(BTS_NODE, &cfg_bts_alt_mode_cmd);
+	install_element(BTS_NODE, &cfg_om2k_con_group_cmd);
+	install_element(BTS_NODE, &del_om2k_con_group_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/libbsc/abis_rsl.c b/openbsc/src/libbsc/abis_rsl.c
new file mode 100644
index 0000000..5a508b2
--- /dev/null
+++ b/openbsc/src/libbsc/abis_rsl.c
@@ -0,0 +1,2929 @@
+/* GSM Radio Signalling Link messages on the A-bis interface
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/debug.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/gsm_subscriber.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/pcu_if.h>
+
+#define RSL_ALLOC_SIZE		1024
+#define RSL_ALLOC_HEADROOM	128
+
+enum sacch_deact {
+	SACCH_NONE,
+	SACCH_DEACTIVATE,
+};
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan);
+static void error_timeout_cb(void *data);
+static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts);
+static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc);
+static void dyn_ts_switchover_complete(struct gsm_lchan *lchan);
+
+static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan,
+			      struct gsm_meas_rep *resp)
+{
+	struct lchan_signal_data sig;
+	sig.lchan = lchan;
+	sig.mr = resp;
+	osmo_signal_dispatch(SS_LCHAN, sig_no, &sig);
+}
+
+static void do_lchan_free(struct gsm_lchan *lchan)
+{
+	/* We start the error timer to make the channel available again */
+	if (lchan->state == LCHAN_S_REL_ERR) {
+		osmo_timer_setup(&lchan->error_timer, error_timeout_cb, lchan);
+		osmo_timer_schedule(&lchan->error_timer,
+				   lchan->ts->trx->bts->network->T3111 + 2, 0);
+	} else {
+		rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+	}
+	lchan_free(lchan);
+}
+
+static void count_codecs(struct gsm_bts *bts, struct gsm_lchan *lchan)
+{
+	OSMO_ASSERT(bts);
+
+	if (lchan->type == GSM_LCHAN_TCH_H) {
+		switch (lchan->tch_mode) {
+		case GSM48_CMODE_SPEECH_AMR:
+			rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_AMR_H]);
+			break;
+		case GSM48_CMODE_SPEECH_V1:
+			rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_V1_HR]);
+			break;
+		default:
+			break;
+		}
+	} else if (lchan->type == GSM_LCHAN_TCH_F) {
+		switch (lchan->tch_mode) {
+		case GSM48_CMODE_SPEECH_AMR:
+			rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_AMR_F]);
+			break;
+		case GSM48_CMODE_SPEECH_V1:
+			rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_V1_FR]);
+			break;
+		case GSM48_CMODE_SPEECH_EFR:
+			rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_EFR]);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static uint8_t mdisc_by_msgtype(uint8_t msg_type)
+{
+	/* mask off the transparent bit ? */
+	msg_type &= 0xfe;
+
+	if ((msg_type & 0xf0) == 0x00)
+		return ABIS_RSL_MDISC_RLL;
+	if ((msg_type & 0xf0) == 0x10) {
+		if (msg_type >= 0x19 && msg_type <= 0x22)
+			return ABIS_RSL_MDISC_TRX;
+		else
+			return ABIS_RSL_MDISC_COM_CHAN;
+	}
+	if ((msg_type & 0xe0) == 0x20)
+		return ABIS_RSL_MDISC_DED_CHAN;
+	
+	return ABIS_RSL_MDISC_LOC;
+}
+
+static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh,
+				  uint8_t msg_type)
+{
+	dh->c.msg_discr = mdisc_by_msgtype(msg_type);
+	dh->c.msg_type = msg_type;
+	dh->ie_chan = RSL_IE_CHAN_NR;
+}
+
+/* call rsl_lchan_lookup and set the log context */
+static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+				      const char *log_name)
+{
+	int rc;
+	struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc);
+
+	if (!lchan) {
+		LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n",
+		     log_name, chan_nr);
+		return NULL;
+	}
+
+	if (rc < 0)
+		LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n",
+		     gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr);
+
+	if (lchan->conn)
+		log_set_context(LOG_CTX_VLR_SUBSCR, lchan->conn->subscr);
+
+	return lchan;
+}
+
+/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */
+uint64_t str_to_imsi(const char *imsi_str)
+{
+	uint64_t ret;
+
+	ret = strtoull(imsi_str, NULL, 10);
+
+	return ret;
+}
+
+static struct msgb *rsl_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM,
+				   "RSL");
+}
+
+static void pad_macblock(uint8_t *out, const uint8_t *in, int len)
+{
+	memcpy(out, in, len);
+
+	if (len < GSM_MACBLOCK_LEN)
+		memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len);
+}
+
+/* Chapter 9.3.7: Encryption Information */
+static int build_encr_info(uint8_t *out, struct gsm_lchan *lchan)
+{
+	*out++ = lchan->encr.alg_id & 0xff;
+	if (lchan->encr.key_len)
+		memcpy(out, lchan->encr.key, lchan->encr.key_len);
+	return lchan->encr.key_len + 1;
+}
+
+static void print_rsl_cause(int lvl, const uint8_t *cause_v, uint8_t cause_len)
+{
+	int i;
+
+	LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ",
+		cause_v[0], rsl_err_name(cause_v[0]));
+	for (i = 1; i < cause_len-1; i++)
+		LOGPC(DRSL, lvl, "%02x ", cause_v[i]);
+}
+
+static void lchan_act_tmr_cb(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	rsl_lchan_mark_broken(lchan, "activation timeout");
+	lchan_free(lchan);
+}
+
+static void lchan_deact_tmr_cb(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	rsl_lchan_mark_broken(lchan, "de-activation timeout");
+	lchan_free(lchan);
+}
+
+
+/* Send a BCCH_INFO message as per Chapter 8.5.1 */
+int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	const struct gsm_bts *bts = trx->bts;
+	struct msgb *msg = rsl_msgb_alloc();
+	uint8_t type = osmo_sitype2rsl(si_type);
+
+	if (bts->c0 != trx)
+		LOGP(DRR, LOGL_ERROR, "Attempting to set BCCH SI%s on wrong BTS%u/TRX%u\n",
+		     get_value_string(osmo_sitype_strs, si_type), bts->nr, trx->nr);
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh);
+	init_dchan_hdr(dh, RSL_MT_BCCH_INFO);
+	dh->chan_nr = RSL_CHAN_BCCH;
+
+	if (trx->bts->type == GSM_BTS_TYPE_RBS2000
+	    && type == RSL_SYSTEM_INFO_13) {
+		/* Ericsson proprietary encoding of SI13 */
+		msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, RSL_ERIC_SYSTEM_INFO_13);
+		if (data)
+			msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+		msgb_tv_put(msg, RSL_IE_ERIC_BCCH_MAPPING, 0x00);
+	} else {
+		/* Normal encoding */
+		msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+		if (data)
+			msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+	}
+
+	msg->dst = trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
+		      const uint8_t *data, int len)
+{
+	struct abis_rsl_common_hdr *ch;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+	ch->msg_discr = ABIS_RSL_MDISC_TRX;
+	ch->msg_type = RSL_MT_SACCH_FILL;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	if (data)
+		msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+	msg->dst = trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type,
+			  const uint8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+	uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	if (data)
+		msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+	db = abs(db);
+	if (db > 30)
+		return -EINVAL;
+
+	msg = rsl_msgb_alloc();
+
+	lchan->bs_power = db/2;
+	if (fpc)
+		lchan->bs_power |= 0x10;
+	
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+	int ctl_lvl;
+
+	ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm);
+	if (ctl_lvl < 0)
+		return ctl_lvl;
+
+	msg = rsl_msgb_alloc();
+
+	lchan->ms_power = ctl_lvl;
+
+	if (fpc)
+		lchan->ms_power |= 0x20;
+	
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
+				   struct gsm_lchan *lchan)
+{
+	memset(cm, 0, sizeof(*cm));
+
+	/* FIXME: what to do with data calls ? */
+	cm->dtx_dtu = 0;
+	if (lchan->ts->trx->bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
+		cm->dtx_dtu |= RSL_CMOD_DTXu;
+	if (lchan->ts->trx->bts->dtxd)
+		cm->dtx_dtu |= RSL_CMOD_DTXd;
+
+	/* set TCH Speech/Data */
+	cm->spd_ind = lchan->rsl_cmode;
+
+	if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN &&
+	    lchan->tch_mode != GSM48_CMODE_SIGN)
+		LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, "
+			"but tch_mode != signalling\n");
+
+	switch (lchan->type) {
+	case GSM_LCHAN_SDCCH:
+		cm->chan_rt = RSL_CMOD_CRT_SDCCH;
+		break;
+	case GSM_LCHAN_TCH_F:
+		cm->chan_rt = RSL_CMOD_CRT_TCH_Bm;
+		break;
+	case GSM_LCHAN_TCH_H:
+		cm->chan_rt = RSL_CMOD_CRT_TCH_Lm;
+		break;
+	case GSM_LCHAN_NONE:
+	case GSM_LCHAN_UNKNOWN:
+	default:
+		LOGP(DRSL, LOGL_ERROR,
+		     "unsupported activation lchan->type %u %s\n",
+		     lchan->type, gsm_lchant_name(lchan->type));
+		return -EINVAL;
+	}
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SIGN:
+		cm->chan_rate = 0;
+		break;
+	case GSM48_CMODE_SPEECH_V1:
+		cm->chan_rate = RSL_CMOD_SP_GSM1;
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		cm->chan_rate = RSL_CMOD_SP_GSM2;
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		cm->chan_rate = RSL_CMOD_SP_GSM3;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_6k0:
+		switch (lchan->csd_mode) {
+		case LCHAN_CSD_M_NT:
+			/* non-transparent CSD with RLP */
+			switch (lchan->tch_mode) {
+			case GSM48_CMODE_DATA_14k5:
+				cm->chan_rate = RSL_CMOD_SP_NT_14k5;
+				break;
+			case GSM48_CMODE_DATA_12k0:
+				cm->chan_rate = RSL_CMOD_SP_NT_12k0;
+				break;
+			case GSM48_CMODE_DATA_6k0:
+				cm->chan_rate = RSL_CMOD_SP_NT_6k0;
+				break;
+			default:
+				LOGP(DRSL, LOGL_ERROR,
+				     "unsupported lchan->tch_mode %u\n",
+				     lchan->tch_mode);
+				return -EINVAL;
+			}
+			break;
+			/* transparent data services below */
+		case LCHAN_CSD_M_T_1200_75:
+			cm->chan_rate = RSL_CMOD_CSD_T_1200_75;
+			break;
+		case LCHAN_CSD_M_T_600:
+			cm->chan_rate = RSL_CMOD_CSD_T_600;
+			break;
+		case LCHAN_CSD_M_T_1200:
+			cm->chan_rate = RSL_CMOD_CSD_T_1200;
+			break;
+		case LCHAN_CSD_M_T_2400:
+			cm->chan_rate = RSL_CMOD_CSD_T_2400;
+			break;
+		case LCHAN_CSD_M_T_9600:
+			cm->chan_rate = RSL_CMOD_CSD_T_9600;
+			break;
+		case LCHAN_CSD_M_T_14400:
+			cm->chan_rate = RSL_CMOD_CSD_T_14400;
+			break;
+		case LCHAN_CSD_M_T_29000:
+			cm->chan_rate = RSL_CMOD_CSD_T_29000;
+			break;
+		case LCHAN_CSD_M_T_32000:
+			cm->chan_rate = RSL_CMOD_CSD_T_32000;
+			break;
+		default:
+			LOGP(DRSL, LOGL_ERROR,
+			     "unsupported lchan->csd_mode %u\n",
+			     lchan->csd_mode);
+			return -EINVAL;
+		}
+		break;
+	default:
+		LOGP(DRSL, LOGL_ERROR,
+		     "unsupported lchan->tch_mode %u\n",
+		     lchan->tch_mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg)
+{
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0],
+			     lchan->mr_bts_lv + 1);
+}
+
+static enum gsm_phys_chan_config pchan_for_lchant(enum gsm_chan_t type)
+{
+	switch (type) {
+	case GSM_LCHAN_TCH_F:
+		return GSM_PCHAN_TCH_F;
+	case GSM_LCHAN_TCH_H:
+		return GSM_PCHAN_TCH_H;
+	case GSM_LCHAN_NONE:
+	case GSM_LCHAN_PDTCH:
+		/* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only
+		 * used in osmo-bts. Maybe set PDTCH and drop the NONE case
+		 * here. */
+		return GSM_PCHAN_PDCH;
+	default:
+		return GSM_PCHAN_UNKNOWN;
+	}
+}
+
+/*! Tx simplified channel activation message for non-standard PDCH type. */
+static int rsl_chan_activate_lchan_as_pdch(struct gsm_lchan *lchan)
+{
+	struct msgb *msg;
+	struct abis_rsl_dchan_hdr *dh;
+
+	/* This might be called after release of the second lchan of a TCH/H,
+	 * but PDCH activation must always happen on the first lchan. Make sure
+	 * the calling code passes the correct lchan. */
+	OSMO_ASSERT(lchan == lchan->ts->lchan);
+
+	rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+	dh->chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH);
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, RSL_ACT_OSMO_PDCH);
+
+	if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000 &&
+	    lchan->ts->trx->bts->rbs2000.use_superchannel) {
+		const uint8_t eric_pgsl_tmr[] = { 30, 1 };
+		msgb_tv_fixed_put(msg, RSL_IE_ERIC_PGSL_TIMERS,
+				  sizeof(eric_pgsl_tmr), eric_pgsl_tmr);
+	}
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.1 */
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, uint8_t act_type,
+			    uint8_t ho_ref)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+	uint8_t *len;
+	uint8_t ta;
+
+	struct rsl_ie_chan_mode cm;
+	struct gsm48_chan_desc cd;
+
+	/* If a TCH_F/PDCH TS is in PDCH mode, deactivate PDCH first. */
+	if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+	    && (lchan->ts->flags & TS_F_PDCH_ACTIVE)) {
+		/* store activation type and handover reference */
+		lchan->dyn.act_type = act_type;
+		lchan->dyn.ho_ref = ho_ref;
+		return rsl_ipacc_pdch_activate(lchan->ts, 0);
+	}
+
+	/*
+	 * If necessary, release PDCH on dynamic TS. Note that sending a
+	 * release here is only necessary when in PDCH mode; for TCH types, an
+	 * RSL RF Chan Release is initiated by the BTS when a voice call ends,
+	 * so when we reach this, it will already be released. If a dyn TS is
+	 * in PDCH mode, it is still active and we need to initiate a release
+	 * from the BSC side here.
+	 *
+	 * If pchan_is != pchan_want, the PDCH has already been taken down and
+	 * the switchover now needs to enable the TCH lchan.
+	 *
+	 * To switch a dyn TS between TCH/H and TCH/F, it is sufficient to send
+	 * a chan activ with the new lchan type, because it will already be
+	 * released.
+	 */
+	if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+	    && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH
+	    && lchan->ts->dyn.pchan_is == lchan->ts->dyn.pchan_want) {
+		enum gsm_phys_chan_config pchan_want;
+		pchan_want = pchan_for_lchant(lchan->type);
+		if (lchan->ts->dyn.pchan_is != pchan_want) {
+			/*
+			 * Make sure to record on lchan[0] so that we'll find
+			 * it after the PDCH release.
+			 */
+			struct gsm_lchan *lchan0 = lchan->ts->lchan;
+			lchan0->dyn.act_type = act_type,
+			lchan0->dyn.ho_ref = ho_ref;
+			lchan0->dyn.rqd_ref = lchan->rqd_ref;
+			lchan0->dyn.rqd_ta = lchan->rqd_ta;
+			lchan->rqd_ref = NULL;
+			lchan->rqd_ta = 0;
+			DEBUGP(DRSL, "%s saved rqd_ref=%p ta=%u\n",
+			       gsm_lchan_name(lchan0), lchan0->rqd_ref,
+			       lchan0->rqd_ta);
+			return dyn_ts_switchover_start(lchan->ts, pchan_want);
+		}
+	}
+
+	DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n",
+	       gsm_ts_and_pchan_name(lchan->ts),
+	       rsl_act_type_name(act_type));
+
+	if (act_type == RSL_ACT_OSMO_PDCH) {
+		if (lchan->ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+			LOGP(DRSL, LOGL_ERROR,
+			     "%s PDCH channel activation only allowed on %s\n",
+			     gsm_ts_and_pchan_name(lchan->ts),
+			     gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH));
+			return -EINVAL;
+		}
+		return rsl_chan_activate_lchan_as_pdch(lchan);
+	}
+
+	if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+	    && lchan->ts->dyn.pchan_want == GSM_PCHAN_PDCH) {
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s Expected PDCH activation kind\n",
+		     gsm_ts_and_pchan_name(lchan->ts));
+		return -EINVAL;
+	}
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0) {
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s Cannot find channel mode from lchan type\n",
+		     gsm_ts_and_pchan_name(lchan->ts));
+		return rc;
+	}
+
+	rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+	ta = lchan->rqd_ta;
+
+	/* BS11 requires TA shifted by 2 bits */
+	if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11)
+		ta <<= 2;
+
+	memset(&cd, 0, sizeof(cd));
+	gsm48_lchan2chan_desc(&cd, lchan);
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+
+	if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+		dh->chan_nr = gsm_lchan_as_pchan2chan_nr(
+					lchan, lchan->ts->dyn.pchan_want);
+	else
+		dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (uint8_t *) &cm);
+
+	/*
+	 * The Channel Identification is needed for Phase1 phones
+	 * and it contains the GSM48 Channel Description and the
+	 * Mobile Allocation. The GSM 08.58 asks for the Mobile
+	 * Allocation to have a length of zero. We are using the
+	 * msgb_l3len to calculate the length of both messages.
+	 */
+	msgb_v_put(msg, RSL_IE_CHAN_IDENT);
+	len = msgb_put(msg, 1);
+	msgb_tv_fixed_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd);
+
+	if (lchan->ts->hopping.enabled)
+		msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len,
+			     lchan->ts->hopping.ma_data);
+	else
+		msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL);
+
+	/* update the calculated size */
+	msg->l3h = len + 1;
+	*len = msgb_l3len(msg);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		uint8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	switch (act_type) {
+	case RSL_ACT_INTER_ASYNC:
+	case RSL_ACT_INTER_SYNC:
+		msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref);
+		break;
+	default:
+		break;
+	}
+
+	msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+	msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+	msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+	mr_config_for_bts(lchan, msg);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.9: Modify channel mode on BTS side */
+int rsl_chan_mode_modify_req(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+	struct rsl_ie_chan_mode cm;
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0)
+		return rc;
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ);
+	dh->chan_nr = chan_nr;
+
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (uint8_t *) &cm);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		uint8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	mr_config_for_bts(lchan, msg);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.6: Send the encryption command with given L3 info */
+int rsl_encryption_cmd(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct gsm_lchan *lchan = msg->lchan;
+	uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+	uint8_t encr_info[MAX_A5_KEY_LEN+2];
+	uint8_t l3_len = msg->len;
+	int rc;
+
+	/* First push the L3 IE tag and length */
+	msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
+
+	/* then the link identifier (SAPI0, main sign link) */
+	msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0);
+
+	/* then encryption information */
+	rc = build_encr_info(encr_info, lchan);
+	if (rc <= 0)
+		return rc;
+	msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+
+	/* and finally the DCHAN header */
+	dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_ENCR_CMD);
+	dh->chan_nr = chan_nr;
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */
+int rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH);
+	dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan));
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static bool dyn_ts_should_switch_to_pdch(struct gsm_bts_trx_ts *ts)
+{
+	int ss;
+
+	if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+		return false;
+
+	if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE)
+		return false;
+
+	/* Already in PDCH mode? */
+	if (ts->dyn.pchan_is == GSM_PCHAN_PDCH)
+		return false;
+
+	/* See if all lchans are released. */
+	for (ss = 0; ss < ts_subslots(ts); ss++) {
+		struct gsm_lchan *lc = &ts->lchan[ss];
+		if (lc->state != LCHAN_S_NONE) {
+			DEBUGP(DRSL, "%s lchan %u still in use"
+			       " (type=%s,state=%s)\n",
+			       gsm_ts_and_pchan_name(ts), lc->nr,
+			       gsm_lchant_name(lc->type),
+			       gsm_lchans_name(lc->state));
+			/* An lchan is still used. */
+			return false;
+		}
+	}
+
+	/* All channels are released, go to PDCH mode. */
+	DEBUGP(DRSL, "%s back to PDCH\n",
+	       gsm_ts_and_pchan_name(ts));
+	return true;
+}
+
+static void error_timeout_cb(void *data)
+{
+	struct gsm_lchan *lchan = data;
+	if (lchan->state != LCHAN_S_REL_ERR) {
+		LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n",
+		     gsm_lchan_name(lchan), lchan->state);
+		return;
+	}
+
+	/* go back to the none state */
+	LOGP(DRSL, LOGL_INFO, "%s is back in operation.\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+
+	/* Put PDCH channel back into PDCH mode, if GPRS is enabled */
+	if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+	    && lchan->ts->trx->bts->gprs.mode != BTS_GPRS_NONE)
+		rsl_ipacc_pdch_activate(lchan->ts, 1);
+
+	if (dyn_ts_should_switch_to_pdch(lchan->ts))
+		dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH);
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan);
+
+/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */
+static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error,
+				enum sacch_deact deact_sacch)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	/* Stop timers that should lead to a channel release */
+	osmo_timer_del(&lchan->T3109);
+
+	if (lchan->state == LCHAN_S_REL_ERR) {
+		LOGP(DRSL, LOGL_NOTICE, "%s is in error state, not sending release.\n",
+		     gsm_lchan_name(lchan));
+		return -1;
+	}
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL);
+	dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	if (error)
+		DEBUGP(DRSL, "%s RF Channel Release due to error: %d\n",
+		       gsm_lchan_name(lchan), error);
+	else
+		DEBUGP(DRSL, "%s RF Channel Release\n", gsm_lchan_name(lchan));
+
+	if (error) {
+		/*
+		 * FIXME: GSM 04.08 gives us two options for the abnormal
+		 * chanel release. This can be either like in the non-existent
+		 * sub-lcuase 3.5.1 or for the main signalling link deactivate
+		 * the SACCH, start timer T3109 and consider the channel as
+		 * released.
+		 *
+		 * This code is doing the later for all raido links and not
+		 * only the main link. Right now all SAPIs are released on the
+		 * local end, the SACCH will be de-activated and right now the
+		 * T3111 will be started. First T3109 should be started and then
+		 * the T3111.
+		 *
+		 * TODO: Move this out of the function.
+		 */
+
+		/*
+		 * sacch de-activate and "local end release"
+		 */
+		if (deact_sacch == SACCH_DEACTIVATE)
+			rsl_deact_sacch(lchan);
+		rsl_release_sapis_from(lchan, 0, RSL_REL_LOCAL_END);
+
+		/*
+		 * TODO: start T3109 now.
+		 */
+		rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR);
+	}
+
+	/* Start another timer or assume the BTS sends a ACK/NACK? */
+	osmo_timer_setup(&lchan->act_timer, lchan_deact_tmr_cb, lchan);
+	osmo_timer_schedule(&lchan->act_timer, 4, 0);
+
+	rc =  abis_rsl_sendmsg(msg);
+
+	/* BTS will respond by RF CHAN REL ACK */
+	return rc;
+}
+
+/*
+ * Special handling for channel releases in the error case.
+ */
+static int rsl_rf_chan_release_err(struct gsm_lchan *lchan)
+{
+	enum sacch_deact sacch_deact;
+	if (lchan->state != LCHAN_S_ACTIVE)
+		return 0;
+	switch (ts_pchan(lchan->ts)) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+	case GSM_PCHAN_CCCH_SDCCH4:
+	case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+	case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+		sacch_deact = SACCH_DEACTIVATE;
+		break;
+	default:
+		sacch_deact = SACCH_NONE;
+		break;
+	}
+	return rsl_rf_chan_release(lchan, 1, sacch_deact);
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan)
+{
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+
+	DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan));
+
+	/* Stop all pending timers */
+	osmo_timer_del(&lchan->act_timer);
+	osmo_timer_del(&lchan->T3111);
+
+	/*
+	 * The BTS didn't respond within the timeout to our channel
+	 * release request and we have marked the channel as broken.
+	 * Now we do receive an ACK and let's be conservative. If it
+	 * is a sysmoBTS we know that only one RF Channel Release ACK
+	 * will be sent. So let's "repair" the channel.
+	 */
+	if (lchan->state == LCHAN_S_BROKEN) {
+		int do_free = is_sysmobts_v2(ts->trx->bts);
+		LOGP(DRSL, LOGL_NOTICE,
+			"%s CHAN REL ACK for broken channel. %s.\n",
+			gsm_lchan_name(lchan),
+			do_free ? "Releasing it" : "Keeping it broken");
+		if (do_free)
+			do_lchan_free(lchan);
+		if (dyn_ts_should_switch_to_pdch(lchan->ts))
+			dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH);
+		return 0;
+	}
+
+	if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR)
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n",
+			gsm_lchan_name(lchan),
+			gsm_lchans_name(lchan->state));
+
+	do_lchan_free(lchan);
+
+	/*
+	 * Check Osmocom RSL CHAN ACT style dynamic TCH/F_TCH/H_PDCH TS for pending
+	 * transitions in these cases:
+	 *
+	 * a) after PDCH was released due to switchover request, activate TCH.
+	 *    BSC initiated this switchover, so dyn.pchan_is != pchan_want and
+	 *    lchan->type has been set to the desired GSM_LCHAN_*.
+	 *
+	 * b) Voice call ended and a TCH is released. If the TS is now unused,
+	 *    switch to PDCH. Here still dyn.pchan_is == dyn.pchan_want because
+	 *    we're only just notified and may decide to switch to PDCH now.
+	 */
+	if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+		DEBUGP(DRSL, "%s Rx RSL Channel Release ack for lchan %u\n",
+		       gsm_ts_and_pchan_name(ts), lchan->nr);
+
+		/* (a) */
+		if (ts->dyn.pchan_is != ts->dyn.pchan_want)
+			return dyn_ts_switchover_continue(ts);
+		
+		/* (b) */
+		if (dyn_ts_should_switch_to_pdch(ts))
+			return dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH);
+	}
+
+	/*
+	 * Put a dynamic TCH/F_PDCH channel back to PDCH mode iff it was
+	 * released successfully. If in error, the PDCH ACT will follow after
+	 * T3111 in error_timeout_cb().
+	 *
+	 * Any state other than LCHAN_S_REL_ERR became LCHAN_S_NONE after above
+	 * do_lchan_free(). Assert this, because that's what ensures a PDCH ACT
+	 * on a TCH/F_PDCH TS in all cases.
+	 *
+	 * If GPRS is disabled, always skip the PDCH ACT.
+	 */
+	OSMO_ASSERT(lchan->state == LCHAN_S_NONE
+		    || lchan->state == LCHAN_S_REL_ERR);
+	if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE)
+		return 0;
+	if (ts->pchan == GSM_PCHAN_TCH_F_PDCH
+	    && lchan->state == LCHAN_S_NONE)
+		return rsl_ipacc_pdch_activate(ts, 1);
+	return 0;
+}
+
+int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len,
+		   uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_PAGING_CMD);
+	dh->chan_nr = RSL_CHAN_PCH_AGCH;
+
+	msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group);
+	msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2);
+	msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed);
+
+	/* Ericsson wants to have this IE in case a paging message
+	 * relates to packet paging */
+	if (bts->type == GSM_BTS_TYPE_RBS2000 && is_gprs)
+		msgb_tv_put(msg, RSL_IE_ERIC_PACKET_PAG_IND, 0);
+
+	msg->dst = bts->c0->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int imsi_str2bcd(uint8_t *bcd_out, const char *str_in)
+{
+	int i, len = strlen(str_in);
+
+	for (i = 0; i < len; i++) {
+		int num = str_in[i] - 0x30;
+		if (num < 0 || num > 9)
+			return -1;
+		if (i % 2 == 0)
+			bcd_out[i/2] = num;
+		else
+			bcd_out[i/2] |= (num << 4);
+	}
+
+	return 0;
+}
+
+/* Chapter 8.5.6 */
+struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t *val)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	uint8_t buf[GSM_MACBLOCK_LEN];
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD);
+	dh->chan_nr = RSL_CHAN_PCH_AGCH;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val);
+		break;
+	default:
+		/* If phase 2, construct a FULL_IMM_ASS_INFO */
+		pad_macblock(buf, val, len);
+		msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, GSM_MACBLOCK_LEN,
+			     buf);
+		break;
+	}
+
+	msg->dst = bts->c0->rsl_link;
+	return msg;
+}
+
+/* Chapter 8.5.6 */
+int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val)
+{
+	struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val);
+	if (!msg)
+		return 1;
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.5.6 */
+int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, uint8_t *val)
+{
+	struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val);
+	if (!msg)
+		return 1;
+
+	/* ericsson can handle a reference at the end of the message which is used in
+	 * the confirm message. The confirm message is only sent if the trailer is present */
+	msgb_put_u8(msg, RSL_IE_ERIC_MOBILE_ID);
+	msgb_put_u32(msg, tlli);
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Send Siemens specific MS RF Power Capability Indication */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI);
+	dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	dh->chan_nr = gsm_lchan2chan_nr(lchan);
+	msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(uint8_t *)mrpci);
+
+	DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n",
+		gsm_lchan_name(lchan), *(uint8_t *)mrpci);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+
+/* Send "DATA REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_data_request(struct msgb *msg, uint8_t link_id)
+{
+	if (msg->lchan == NULL) {
+		LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n");
+		return -EINVAL;
+	}
+
+	rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan),
+			link_id, 1);
+
+	msg->dst = msg->lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Send "ESTABLISH REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id)
+{
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan),
+			     link_id, 0);
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n",
+		gsm_lchan_name(lchan), link_id);
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static void rsl_handle_release(struct gsm_lchan *lchan);
+
+/* Special work handler to handle missing RSL_MT_REL_CONF message from
+ * Nokia InSite BTS */
+static void lchan_rel_work_cb(void *data)
+{
+	struct gsm_lchan *lchan = data;
+	int sapi;
+
+	for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+		if (lchan->sapis[sapi] == LCHAN_SAPI_REL)
+			lchan->sapis[sapi] = LCHAN_SAPI_UNUSED;
+	}
+	rsl_handle_release(lchan);
+}
+
+/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection.
+   This is what higher layers should call.  The BTS then responds with
+   RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE,
+   which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls
+   lchan_free() */
+int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id,
+			enum rsl_rel_mode release_mode)
+{
+
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan),
+			     link_id, 0);
+	/* 0 is normal release, 1 is local end */
+	msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode);
+
+	/* FIXME: start some timer in case we don't receive a REL ACK ? */
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n",
+		gsm_lchan_name(lchan), link_id, release_mode);
+
+	abis_rsl_sendmsg(msg);
+
+	/* Do not wait for Nokia BTS to send the confirm. */
+	if (is_nokia_bts(lchan->ts->trx->bts)
+	 && lchan->ts->trx->bts->nokia.no_loc_rel_cnf
+	 && release_mode == RSL_REL_LOCAL_END) {
+		DEBUGP(DRLL, "Scheduling release, becasuse Nokia InSite BTS does not send a RELease CONFirm.\n");
+		lchan->sapis[link_id & 0x7] = LCHAN_SAPI_REL;
+		osmo_timer_setup(&lchan->rel_work, lchan_rel_work_cb, lchan);
+		osmo_timer_schedule(&lchan->rel_work, 0, 0);
+	}
+
+	return 0;
+}
+
+int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *reason)
+{
+	LOGP(DRSL, LOGL_ERROR, "%s %s lchan broken: %s\n",
+	     gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), reason);
+	rsl_lchan_set_state(lchan, LCHAN_S_BROKEN);
+	lchan->broken_reason = reason;
+	return 0;
+}
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int state)
+{
+	DEBUGP(DRSL, "%s state %s -> %s\n",
+	       gsm_lchan_name(lchan), gsm_lchans_name(lchan->state),
+	       gsm_lchans_name(state));
+	lchan->state = state;
+	return 0;
+}
+
+/* Chapter 8.4.2: Channel Activate Acknowledge */
+static int rsl_rx_chan_act_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+
+	/* BTS has confirmed channel activation, we now need
+	 * to assign the activated channel to the MS */
+	if (rslh->ie_chan != RSL_IE_CHAN_NR)
+		return -EINVAL;
+
+	osmo_timer_del(&lchan->act_timer);
+
+	if (lchan->state == LCHAN_S_BROKEN) {
+		int do_release = is_sysmobts_v2(ts->trx->bts);
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK for broken channel. %s\n",
+			gsm_lchan_name(lchan),
+			do_release ? "Releasing it" : "Keeping it broken");
+		if (do_release) {
+			talloc_free(lchan->rqd_ref);
+			lchan->rqd_ref = NULL;
+			lchan->rqd_ta = 0;
+			rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE);
+			if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+				/*
+				 * lchan_act_tmr_cb() already called
+				 * lchan_free() and cleared the lchan->type, so
+				 * calling dyn_ts_switchover_complete() here
+				 * would not have the desired effect of
+				 * mimicking an activated lchan that we can
+				 * release. Instead hack the dyn ts state to
+				 * make sure that rsl_rx_rf_chan_rel_ack() will
+				 * switch back to PDCH, i.e. have pchan_is ==
+				 * pchan_want, both != GSM_PCHAN_PDCH:
+				 */
+				ts->dyn.pchan_is = GSM_PCHAN_NONE;
+				ts->dyn.pchan_want = GSM_PCHAN_NONE;
+			}
+			rsl_rf_chan_release(msg->lchan, 0, SACCH_NONE);
+		}
+		return 0;
+	}
+
+	if (lchan->state != LCHAN_S_ACT_REQ)
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n",
+			gsm_lchan_name(lchan),
+			gsm_lchans_name(lchan->state));
+	rsl_lchan_set_state(lchan, LCHAN_S_ACTIVE);
+
+	if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+		dyn_ts_switchover_complete(lchan);
+
+	if (lchan->rqd_ref) {
+		rsl_send_imm_assignment(lchan);
+		talloc_free(lchan->rqd_ref);
+		lchan->rqd_ref = NULL;
+		lchan->rqd_ta = 0;
+	}
+
+	send_lchan_signal(S_LCHAN_ACTIVATE_ACK, lchan, NULL);
+
+	/* Update bts attributes inside the PCU */
+	if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH ||
+	    ts->pchan == GSM_PCHAN_TCH_F_PDCH ||
+	    ts->pchan == GSM_PCHAN_PDCH)
+		pcu_info_update(ts->trx->bts);
+
+	return 0;
+}
+
+/* Chapter 8.4.3: Channel Activate NACK */
+static int rsl_rx_chan_act_nack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	osmo_timer_del(&msg->lchan->act_timer);
+
+	if (msg->lchan->state == LCHAN_S_BROKEN) {
+		LOGP(DRSL, LOGL_ERROR,
+			"%s CHANNEL ACTIVATE NACK for broken channel.\n",
+			gsm_lchan_name(msg->lchan));
+		return -1;
+	}
+
+	LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK ",
+		gsm_lchan_name(msg->lchan));
+
+	/* BTS has rejected channel activation ?!? */
+	if (dh->ie_chan != RSL_IE_CHAN_NR)
+		return -EINVAL;
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) {
+		const uint8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE);
+		print_rsl_cause(LOGL_ERROR, cause,
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+		msg->lchan->error_cause = *cause;
+		if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC) {
+			rsl_lchan_mark_broken(msg->lchan, "NACK on activation");
+		} else
+			rsl_rf_chan_release(msg->lchan, 1, SACCH_DEACTIVATE);
+
+	} else {
+		rsl_lchan_mark_broken(msg->lchan, "NACK on activation no IE");
+	}
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL);
+	return 0;
+}
+
+/* Chapter 8.4.4: Connection Failure Indication */
+static int rsl_rx_conn_fail(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING state %s ",
+	     gsm_lchan_name(msg->lchan),
+	     gsm_lchans_name(msg->lchan->state));
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE),
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+	LOGPC(DRSL, LOGL_NOTICE, "\n");
+	rate_ctr_inc(&msg->lchan->ts->trx->bts->network->bsc_ctrs->ctr[BSC_CTR_CHAN_RF_FAIL]);
+	return rsl_rf_chan_release_err(msg->lchan);
+}
+
+static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru,
+				const char *prefix)
+{
+	DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ",
+		prefix, rxlev2dbm(mru->full.rx_lev),
+		prefix, rxlev2dbm(mru->sub.rx_lev));
+	DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ",
+		prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual);
+}
+
+static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr)
+{
+	int i;
+	const char *name = "";
+
+	if (lchan && lchan->conn) {
+		if (lchan->conn->bsub)
+			name = bsc_subscr_name(lchan->conn->bsub);
+		else if (lchan->conn->subscr)
+			name = lchan->conn->subscr->imsi;
+		else
+			name = lchan->name;
+	}
+
+	DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr);
+
+	if (mr->flags & MEAS_REP_F_DL_DTX)
+		DEBUGPC(DMEAS, "DTXd ");
+
+	print_meas_rep_uni(&mr->ul, "ul");
+	DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power);
+
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset);
+
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr);
+		DEBUGPC(DMEAS, "L1_FPC=%u ",
+			mr->flags & MEAS_REP_F_FPC ? 1 : 0);
+		DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta);
+	}
+
+	if (mr->flags & MEAS_REP_F_UL_DTX)
+		DEBUGPC(DMEAS, "DTXu ");
+	if (mr->flags & MEAS_REP_F_BA1)
+		DEBUGPC(DMEAS, "BA1 ");
+	if (!(mr->flags & MEAS_REP_F_DL_VALID))
+		DEBUGPC(DMEAS, "NOT VALID ");
+	else
+		print_meas_rep_uni(&mr->dl, "dl");
+
+	DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell);
+	if (mr->num_cell == 7)
+		return;
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n",
+			mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+	}
+}
+
+static int rsl_rx_meas_res(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+	struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan);
+	uint8_t len;
+	const uint8_t *val;
+	int rc;
+
+	/* check if this channel is actually active */
+	/* FIXME: maybe this check should be way more generic/centralized */
+	if (msg->lchan->state != LCHAN_S_ACTIVE) {
+		LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n",
+			gsm_lchan_name(msg->lchan));
+		return 0;
+	}
+
+	memset(mr, 0, sizeof(*mr));
+	mr->lchan = msg->lchan;
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) ||
+	    !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) ||
+	    !TLVP_PRESENT(&tp, RSL_IE_BS_POWER))
+		return -EIO;
+
+	/* Mandatory Parts */
+	mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR);
+
+	len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS);
+	val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS);
+	if (len >= 3) {
+		if (val[0] & 0x40)
+			mr->flags |= MEAS_REP_F_DL_DTX;
+		mr->ul.full.rx_lev = val[0] & 0x3f;
+		mr->ul.sub.rx_lev = val[1] & 0x3f;
+		mr->ul.full.rx_qual = val[2]>>3 & 0x7;
+		mr->ul.sub.rx_qual = val[2] & 0x7;
+	}
+
+	mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+
+	/* Optional Parts */
+	if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) {
+		/* According to 3GPP TS 48.058 § MS Timing Offset = Timing Offset field - 63 */
+		mr->ms_timing_offset = *TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET) - 63;
+		mr->flags |= MEAS_REP_F_MS_TO;
+	}
+
+	if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) {
+		struct e1inp_sign_link *sign_link = msg->dst;
+
+		val = TLVP_VAL(&tp, RSL_IE_L1_INFO);
+		mr->flags |= MEAS_REP_F_MS_L1;
+		mr->ms_l1.pwr = ms_pwr_dbm(sign_link->trx->bts->band, val[0] >> 3);
+		if (val[0] & 0x04)
+			mr->flags |= MEAS_REP_F_FPC;
+		mr->ms_l1.ta = val[1];
+		/* BS11 and Nokia reports TA shifted by 2 bits */
+		if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11
+		 || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+			mr->ms_l1.ta >>= 2;
+	}
+	if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+		msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+		rc = gsm48_parse_meas_rep(mr, msg);
+		if (rc < 0)
+			return rc;
+	}
+
+	print_meas_rep(msg->lchan, mr);
+
+	send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr);
+
+	return 0;
+}
+
+/* Chapter 8.4.7 */
+static int rsl_rx_hando_det(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan));
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+		DEBUGPC(DRSL, "access delay = %u\n",
+			*TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY));
+	else
+		DEBUGPC(DRSL, "\n");
+
+	send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL);
+
+	return 0;
+}
+
+static bool lchan_may_change_pdch(struct gsm_lchan *lchan, bool pdch_act)
+{
+	struct gsm_bts_trx_ts *ts;
+
+	OSMO_ASSERT(lchan);
+
+	ts = lchan->ts;
+	OSMO_ASSERT(ts);
+	OSMO_ASSERT(ts->trx);
+	OSMO_ASSERT(ts->trx->bts);
+
+	if (lchan->ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+		LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK"
+		     " for channel that is no TCH/F_PDCH\n",
+		     gsm_lchan_name(lchan),
+		     gsm_pchan_name(ts->pchan),
+		     pdch_act? "ACT" : "DEACT");
+		return false;
+	}
+
+	if (lchan->state != LCHAN_S_NONE) {
+		LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK"
+		     " in unexpected state: %s\n",
+		     gsm_lchan_name(lchan),
+		     gsm_pchan_name(ts->pchan),
+		     pdch_act? "ACT" : "DEACT",
+		     gsm_lchans_name(lchan->state));
+		return false;
+	}
+	return true;
+}
+
+static int rsl_rx_pdch_act_ack(struct msgb *msg)
+{
+	if (!lchan_may_change_pdch(msg->lchan, true))
+		return -EINVAL;
+
+	msg->lchan->ts->flags |= TS_F_PDCH_ACTIVE;
+	msg->lchan->ts->flags &= ~TS_F_PDCH_ACT_PENDING;
+
+	return 0;
+}
+
+static int rsl_rx_pdch_deact_ack(struct msgb *msg)
+{
+	if (!lchan_may_change_pdch(msg->lchan, false))
+		return -EINVAL;
+
+	msg->lchan->ts->flags &= ~TS_F_PDCH_ACTIVE;
+	msg->lchan->ts->flags &= ~TS_F_PDCH_DEACT_PENDING;
+
+	rsl_chan_activate_lchan(msg->lchan, msg->lchan->dyn.act_type,
+				msg->lchan->dyn.ho_ref);
+
+	return 0;
+}
+
+static int abis_rsl_rx_dchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+	struct e1inp_sign_link *sign_link = msg->dst;
+
+	msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr,
+				  "Abis RSL rx DCHAN: ");
+	if (!msg->lchan)
+		return -1;
+	ts_name = gsm_lchan_name(msg->lchan);
+
+	switch (rslh->c.msg_type) {
+	case RSL_MT_CHAN_ACTIV_ACK:
+		DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name);
+		rc = rsl_rx_chan_act_ack(msg);
+		count_codecs(sign_link->trx->bts, msg->lchan);
+		break;
+	case RSL_MT_CHAN_ACTIV_NACK:
+		rc = rsl_rx_chan_act_nack(msg);
+		break;
+	case RSL_MT_CONN_FAIL:
+		rc = rsl_rx_conn_fail(msg);
+		break;
+	case RSL_MT_MEAS_RES:
+		rc = rsl_rx_meas_res(msg);
+		break;
+	case RSL_MT_HANDO_DET:
+		rc = rsl_rx_hando_det(msg);
+		break;
+	case RSL_MT_RF_CHAN_REL_ACK:
+		rc = rsl_rx_rf_chan_rel_ack(msg->lchan);
+		break;
+	case RSL_MT_MODE_MODIFY_ACK:
+		count_codecs(sign_link->trx->bts, msg->lchan);
+		DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name);
+		break;
+	case RSL_MT_MODE_MODIFY_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_PDCH_ACT_ACK:
+		DEBUGP(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name);
+		rc = rsl_rx_pdch_act_ack(msg);
+		break;
+	case RSL_MT_IPAC_PDCH_ACT_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_PDCH_DEACT_ACK:
+		DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name);
+		rc = rsl_rx_pdch_deact_ack(msg);
+		break;
+	case RSL_MT_IPAC_PDCH_DEACT_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name);
+		break;
+	case RSL_MT_PHY_CONTEXT_CONF:
+	case RSL_MT_PREPROC_MEAS_RES:
+	case RSL_MT_TALKER_DET:
+	case RSL_MT_LISTENER_DET:
+	case RSL_MT_REMOTE_CODEC_CONF_REP:
+	case RSL_MT_MR_CODEC_MOD_ACK:
+	case RSL_MT_MR_CODEC_MOD_NACK:
+	case RSL_MT_MR_CODEC_MOD_PER:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan "
+			"msg 0x%02x\n", ts_name, rslh->c.msg_type);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n",
+			ts_name, rslh->c.msg_type);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int rsl_rx_error_rep(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	struct tlv_parsed tp;
+	struct e1inp_sign_link *sign_link = msg->dst;
+
+	LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(sign_link->trx));
+
+	rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE),
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	return 0;
+}
+
+static int abis_rsl_rx_trx(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	struct e1inp_sign_link *sign_link = msg->dst;
+	int rc = 0;
+
+	switch (rslh->msg_type) {
+	case RSL_MT_ERROR_REPORT:
+		rc = rsl_rx_error_rep(msg);
+		break;
+	case RSL_MT_RF_RES_IND:
+		/* interference on idle channels of TRX */
+		//DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(sign_link->trx));
+		break;
+	case RSL_MT_OVERLOAD:
+		/* indicate CCCH / ACCH / processor overload */
+		LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n",
+		     gsm_trx_name(sign_link->trx));
+		break;
+	case 0x42: /* Nokia specific: SI End ACK */
+		LOGP(DRSL, LOGL_INFO, "Nokia SI End ACK\n");
+		break;
+	case 0x43: /* Nokia specific: SI End NACK */
+		LOGP(DRSL, LOGL_INFO, "Nokia SI End NACK\n");
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message "
+			"type 0x%02x\n", gsm_trx_name(sign_link->trx), rslh->msg_type);
+		return -EINVAL;
+	}
+	return rc;
+}
+
+/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */
+static void t3101_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+	LOGP(DRSL, LOGL_NOTICE,
+	     "%s T3101 expired: no response to IMMEDIATE ASSIGN\n",
+	     gsm_lchan_name(lchan));
+	rsl_rf_chan_release(lchan, 1, SACCH_DEACTIVATE);
+}
+
+/* If T3111 expires, we will send the RF Channel Request */
+static void t3111_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+	LOGP(DRSL, LOGL_NOTICE,
+	     "%s T3111 expired: releasing RF Channel\n",
+	     gsm_lchan_name(lchan));
+	rsl_rf_chan_release(lchan, 0, SACCH_NONE);
+}
+
+/* If T3109 expires the MS has not send a UA/UM do the error release */
+static void t3109_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	LOGP(DRSL, LOGL_ERROR,
+		"%s SACCH deactivation timeout.\n", gsm_lchan_name(lchan));
+	rsl_rf_chan_release(lchan, 1, SACCH_NONE);
+}
+
+/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */
+static int rsl_send_imm_ass_rej(struct gsm_bts *bts,
+				struct gsm48_req_ref *rqd_ref,
+				uint8_t wait_ind)
+{
+	uint8_t buf[GSM_MACBLOCK_LEN];
+	struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf;
+
+	/* create IMMEDIATE ASSIGN REJECT 04.08 message */
+	memset(iar, 0, sizeof(*iar));
+	iar->proto_discr = GSM48_PDISC_RR;
+	iar->msg_type = GSM48_MT_RR_IMM_ASS_REJ;
+	iar->page_mode = GSM48_PM_SAME;
+
+	/*
+	 * Set all request references and wait indications to the same value.
+	 * 3GPP TS 44.018 v4.5.0 release 4 (section 9.1.20.2) requires that
+	 * we duplicate reference and wait indication to fill the message.
+	 * The BTS will aggregate up to 4 of our ASS REJ messages if possible.
+	 */
+	memcpy(&iar->req_ref1, rqd_ref, sizeof(iar->req_ref1));
+	iar->wait_ind1 = wait_ind;
+
+	memcpy(&iar->req_ref2, rqd_ref, sizeof(iar->req_ref2));
+	iar->wait_ind2 = wait_ind;
+
+	memcpy(&iar->req_ref3, rqd_ref, sizeof(iar->req_ref3));
+	iar->wait_ind3 = wait_ind;
+
+	memcpy(&iar->req_ref4, rqd_ref, sizeof(iar->req_ref4));
+	iar->wait_ind4 = wait_ind;
+
+	/* we need to subtract 1 byte from sizeof(*iar) since ia includes the l2_plen field */
+	iar->l2_plen = GSM48_LEN2PLEN((sizeof(*iar)-1));
+
+	return rsl_imm_assign_cmd(bts, sizeof(*iar), (uint8_t *) iar);
+}
+
+/* Handle packet channel rach requests */
+static int rsl_rx_pchan_rqd(struct msgb *msg, struct gsm_bts *bts)
+{
+	struct gsm48_req_ref *rqd_ref;
+	struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
+	rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+	uint8_t ra = rqd_ref->ra;
+	uint8_t t1, t2, t3;
+	uint32_t fn;
+	uint8_t rqd_ta;
+	uint8_t is_11bit;
+
+	/* Process rach request and forward contained information to PCU */
+	if (ra == 0x7F) {
+		is_11bit = 1;
+
+		/* FIXME: Also handle 11 bit rach requests */
+		LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n",bts->nr);
+		return -EINVAL;
+	} else {
+		is_11bit = 0;
+		t1 = rqd_ref->t1;
+		t2 = rqd_ref->t2;
+		t3 = rqd_ref->t3_low | (rqd_ref->t3_high << 3);
+		fn = (51 * ((t3-t2) % 26) + t3 + 51 * 26 * t1);
+
+		rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+	}
+
+	return pcu_tx_rach_ind(bts, rqd_ta, ra, fn, is_11bit,
+			       GSM_L1_BURST_TYPE_ACCESS_0);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_chan_rqd(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct gsm_bts *bts = sign_link->trx->bts;
+	struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
+	struct gsm48_req_ref *rqd_ref;
+	enum gsm_chan_t lctype;
+	enum gsm_chreq_reason_t chreq_reason;
+	struct gsm_lchan *lchan;
+	uint8_t rqd_ta;
+	int is_lu;
+
+	uint16_t arfcn;
+	uint8_t subch;
+
+	/* parse request reference to be used in immediate assign */
+	if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE)
+		return -EINVAL;
+
+	rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+
+	/* parse access delay and use as TA */
+	if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY)
+		return -EINVAL;
+	rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+
+	/* Determine channel request cause code */
+	chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci);
+	LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n",
+	     msg->lchan->ts->trx->bts->nr,
+	     get_value_string(gsm_chreq_descs, chreq_reason),
+	     rqd_ref->ra, bts->network->neci, chreq_reason);
+
+	/* Handle PDCH related rach requests (in case of BSC-co-located-PCU */
+	if (chreq_reason == GSM_CHREQ_REASON_PDCH)
+		return rsl_rx_pchan_rqd(msg, bts);
+
+	/* determine channel type (SDCCH/TCH_F/TCH_H) based on
+	 * request reference RA */
+	lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra);
+
+	rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CHREQ_TOTAL]);
+
+	/*
+	 * We want LOCATION UPDATES to succeed and will assign a TCH
+	 * if we have no SDCCH available.
+	 */
+	is_lu = !!(chreq_reason == GSM_CHREQ_REASON_LOCATION_UPD);
+
+	/* check availability / allocate channel */
+	lchan = lchan_alloc(bts, lctype, is_lu);
+	if (!lchan) {
+		uint8_t wait_ind;
+		LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s 0x%x\n",
+		     msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra);
+		rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CHREQ_NO_CHANNEL]);
+		if (bts->T3122)
+			wait_ind = bts->T3122;
+		else if (bts->network->T3122)
+			wait_ind = bts->network->T3122 & 0xff;
+		else
+			wait_ind = GSM_T3122_DEFAULT;
+		/* The BTS will gather multiple CHAN RQD and reject up to 4 MS at the same time. */
+		rsl_send_imm_ass_rej(bts, rqd_ref, wait_ind);
+		return 0;
+	}
+
+	/*
+	 * Expecting lchan state to be NONE, except for dyn TS in PDCH mode.
+	 * Those are expected to be ACTIVE: the PDCH release will be sent from
+	 * rsl_chan_activate_lchan() below.
+	 */
+	if (lchan->state != LCHAN_S_NONE
+	    && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+		 && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH
+		 && lchan->state == LCHAN_S_ACTIVE))
+		LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel "
+		     "in state %s\n", gsm_lchan_name(lchan),
+		     gsm_lchans_name(lchan->state));
+
+	/* save the RACH data as we need it after the CHAN ACT ACK */
+	lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref);
+	if (!lchan->rqd_ref) {
+		LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n");
+		lchan_free(lchan);
+		return -ENOMEM;
+	}
+
+	memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref));
+	lchan->rqd_ta = rqd_ta;
+
+	arfcn = lchan->ts->trx->arfcn;
+	subch = lchan->nr;
+	
+	lchan->encr.alg_id = RSL_ENC_ALG_A5(0);	/* no encryption */
+	lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+	lchan->bs_power = 0; /* 0dB reduction, output power = Pn */
+	lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+	lchan->tch_mode = GSM48_CMODE_SIGN;
+
+	/* Start another timer or assume the BTS sends a ACK/NACK? */
+	osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan);
+	osmo_timer_schedule(&lchan->act_timer, 4, 0);
+
+	DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s "
+		"r=%s ra=0x%02x ta=%d\n", gsm_lchan_name(lchan), arfcn, subch,
+		gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason),
+		rqd_ref->ra, rqd_ta);
+
+	rsl_chan_activate_lchan(lchan, RSL_ACT_INTRA_IMM_ASS, 0);
+
+	return 0;
+}
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	uint8_t buf[GSM_MACBLOCK_LEN];
+	struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf;
+
+	/* create IMMEDIATE ASSIGN 04.08 messge */
+	memset(ia, 0, sizeof(*ia));
+	/* we set ia->l2_plen once we know the length of the MA below */
+	ia->proto_discr = GSM48_PDISC_RR;
+	ia->msg_type = GSM48_MT_RR_IMM_ASS;
+	ia->page_mode = GSM48_PM_SAME;
+	gsm48_lchan2chan_desc(&ia->chan_desc, lchan);
+
+	/* use request reference extracted from CHAN_RQD */
+	memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref));
+	ia->timing_advance = lchan->rqd_ta;
+	if (!lchan->ts->hopping.enabled) {
+		ia->mob_alloc_len = 0;
+	} else {
+		ia->mob_alloc_len = lchan->ts->hopping.ma_len;
+		memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len);
+	}
+	/* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */
+	ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len);
+
+	/* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */
+	osmo_timer_setup(&lchan->T3101, t3101_expired, lchan);
+	osmo_timer_schedule(&lchan->T3101, bts->network->T3101, 0);
+
+	/* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */
+	return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia);
+}
+
+/* current load on the CCCH */
+static int rsl_rx_ccch_load(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	struct ccch_signal_data sd;
+
+	sd.bts = sign_link->trx->bts;
+	sd.rach_slot_count = -1;
+	sd.rach_busy_count = -1;
+	sd.rach_access_count = -1;
+
+	switch (rslh->data[0]) {
+	case RSL_IE_PAGING_LOAD:
+		sd.pg_buf_space = rslh->data[1] << 8 | rslh->data[2];
+		if (is_ipaccess_bts(sign_link->trx->bts) && sd.pg_buf_space == 0xffff) {
+			/* paging load below configured threshold, use 50 as default */
+			sd.pg_buf_space = 50;
+		}
+		paging_update_buffer_space(sign_link->trx->bts, sd.pg_buf_space);
+		osmo_signal_dispatch(SS_CCCH, S_CCCH_PAGING_LOAD, &sd);
+		break;
+	case RSL_IE_RACH_LOAD:
+		if (msg->data_len >= 7) {
+			sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3];
+			sd.rach_busy_count = rslh->data[4] << 8 | rslh->data[5];
+			sd.rach_access_count = rslh->data[6] << 8 | rslh->data[7];
+			osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int abis_rsl_rx_cchan(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+	uint32_t tlli;
+
+	msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr,
+				  "Abis RSL rx CCHAN: ");
+
+	switch (rslh->c.msg_type) {
+	case RSL_MT_CHAN_RQD:
+		/* MS has requested a channel on the RACH */
+		rc = rsl_rx_chan_rqd(msg);
+		break;
+	case RSL_MT_CCCH_LOAD_IND:
+		/* current load on the CCCH */
+		rc = rsl_rx_ccch_load(msg);
+		break;
+	case RSL_MT_DELETE_IND:
+		/* CCCH overloaded, IMM_ASSIGN was dropped */
+	case RSL_MT_CBCH_LOAD_IND:
+		/* current load on the CBCH */
+		LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message "
+			"type 0x%02x\n", rslh->c.msg_type);
+		break;
+	case 0x10: /* Ericsson specific: Immediate Assign Sent */
+		/* FIXME: Replace the messy message parsing below
+		 * with proper TV parser */
+		LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n");
+		if(msg->len < 9)
+			LOGP(DRSL, LOGL_ERROR, "short IMM.ass sent message!\n");
+		else if(msg->data[4] != 0xf1)
+			LOGP(DRSL, LOGL_ERROR, "unsupported IMM.ass message format! (please fix)\n");
+		else {
+			msgb_pull(msg, 5); /* drop previous data to use msg_pull_u32 */
+			tlli = msgb_pull_u32(msg);
+			pcu_tx_imm_ass_sent(sign_link->trx->bts, tlli);
+		}
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type "
+			"0x%02x\n", rslh->c.msg_type);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int rsl_rx_rll_err_ind(struct msgb *msg)
+{
+	struct tlv_parsed tp;
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	uint8_t rlm_cause;
+
+	rsl_tlv_parse(&tp, rllh->data, msgb_l2len(msg) - sizeof(*rllh));
+	if (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) {
+		LOGP(DRLL, LOGL_ERROR,
+			"%s ERROR INDICATION without mandantory cause.\n",
+			gsm_lchan_name(msg->lchan));
+		return -1;
+	}
+
+	rlm_cause = *TLVP_VAL(&tp, RSL_IE_RLM_CAUSE);
+	LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s in state=%s\n",
+		gsm_lchan_name(msg->lchan),
+		rsl_rlm_cause_name(rlm_cause),
+		gsm_lchans_name(msg->lchan->state));
+
+	rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
+
+	if (rlm_cause == RLL_CAUSE_T200_EXPIRED) {
+		rate_ctr_inc(&msg->lchan->ts->trx->bts->network->bsc_ctrs->ctr[BSC_CTR_CHAN_RLL_ERR]);
+		return rsl_rf_chan_release_err(msg->lchan);
+	}
+
+	return 0;
+}
+
+static void rsl_handle_release(struct gsm_lchan *lchan)
+{
+	int sapi;
+	struct gsm_bts *bts;
+
+	/*
+	 * Maybe only one link/SAPI was releasd or the error handling
+	 * was activated. Just return now and let the other code handle
+	 * it.
+	 */
+	if (lchan->state != LCHAN_S_REL_REQ)
+		return;
+
+	for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+		if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+			continue;
+		LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n",
+		     gsm_lchan_name(lchan), sapi);
+		return;
+	}
+
+
+	/* Stop T3109 and wait for T3111 before re-using the channel */
+	osmo_timer_del(&lchan->T3109);
+	osmo_timer_setup(&lchan->T3111, t3111_expired, lchan);
+	bts = lchan->ts->trx->bts;
+	osmo_timer_schedule(&lchan->T3111, bts->network->T3111, 0);
+}
+
+/*	ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST
+	0x02, 0x06,
+	0x01, 0x20,
+	0x02, 0x00,
+	0x0b, 0x00, 0x0f, 0x05, 0x08, ... */
+
+static int abis_rsl_rx_rll(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+	uint8_t sapi = rllh->link_id & 7;
+
+	msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr,
+				  "Abis RSL rx RLL: ");
+	ts_name = gsm_lchan_name(msg->lchan);
+	DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi);
+	
+	switch (rllh->c.msg_type) {
+	case RSL_MT_DATA_IND:
+		DEBUGPC(DRLL, "DATA INDICATION\n");
+		if (msgb_l2len(msg) >
+		    sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+		    rllh->data[0] == RSL_IE_L3_INFO) {
+			msg->l3h = &rllh->data[3];
+			return gsm0408_rcvmsg(msg, rllh->link_id);
+		}
+		break;
+	case RSL_MT_EST_IND:
+		DEBUGPC(DRLL, "ESTABLISH INDICATION\n");
+		/* lchan is established, stop T3101 */
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS;
+		osmo_timer_del(&msg->lchan->T3101);
+		if (msgb_l2len(msg) >
+		    sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+		    rllh->data[0] == RSL_IE_L3_INFO) {
+			msg->l3h = &rllh->data[3];
+			return gsm0408_rcvmsg(msg, rllh->link_id);
+		}
+		break;
+	case RSL_MT_EST_CONF:
+		DEBUGPC(DRLL, "ESTABLISH CONFIRM\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET;
+		rll_indication(msg->lchan, rllh->link_id,
+				  BSC_RLLR_IND_EST_CONF);
+		break;
+	case RSL_MT_REL_IND:
+		/* BTS informs us of having received  DISC from MS */
+		DEBUGPC(DRLL, "RELEASE INDICATION\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+		rll_indication(msg->lchan, rllh->link_id,
+				  BSC_RLLR_IND_REL_IND);
+		rsl_handle_release(msg->lchan);
+		break;
+	case RSL_MT_REL_CONF:
+		/* BTS informs us of having received UA from MS,
+		 * in response to DISC that we've sent earlier */
+		DEBUGPC(DRLL, "RELEASE CONFIRMATION\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+		rsl_handle_release(msg->lchan);
+		break;
+	case RSL_MT_ERROR_IND:
+		DEBUGPC(DRLL, "ERROR INDICATION\n");
+		rc = rsl_rx_rll_err_ind(msg);
+		break;
+	case RSL_MT_UNIT_DATA_IND:
+		DEBUGPC(DRLL, "UNIT DATA INDICATION\n");
+		LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+		break;
+	default:
+		DEBUGPC(DRLL, "UNKNOWN\n");
+		LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+	}
+	return rc;
+}
+
+static uint8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan)
+{
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x00;
+		case GSM_LCHAN_TCH_H:
+			return 0x03;
+		default:
+			break;
+		}
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x01;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x02;
+		case GSM_LCHAN_TCH_H:
+			return 0x05;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for "
+		"tch_mode == 0x%02x\n", lchan->tch_mode);
+	return 0;
+}
+
+static uint8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan)
+{
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_GSM_HALF;
+		default:
+			break;
+		}
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_EFR;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_AMR;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for "
+		"tch_mode == 0x%02x\n & lchan_type == %d",
+		lchan->tch_mode, lchan->type);
+	return 0;
+}
+
+/* ip.access specific RSL extensions */
+static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv)
+{
+	struct in_addr ip;
+	uint16_t port, conn_id;
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) {
+		ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_LOCAL_IP);
+		DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip));
+		lchan->abis_ip.bound_ip = ntohl(ip.s_addr);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) {
+		port = tlvp_val16_unal(tv, RSL_IE_IPAC_LOCAL_PORT);
+		port = ntohs(port);
+		DEBUGPC(DRSL, "LOCAL_PORT=%u ", port);
+		lchan->abis_ip.bound_port = port;
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) {
+		conn_id = tlvp_val16_unal(tv, RSL_IE_IPAC_CONN_ID);
+		conn_id = ntohs(conn_id);
+		DEBUGPC(DRSL, "CON_ID=%u ", conn_id);
+		lchan->abis_ip.conn_id = conn_id;
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) {
+		lchan->abis_ip.rtp_payload2 =
+				*TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2);
+		DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ",
+			lchan->abis_ip.rtp_payload2);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) {
+		lchan->abis_ip.speech_mode =
+				*TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE);
+		DEBUGPC(DRSL, "speech_mode=0x%02x ",
+			lchan->abis_ip.speech_mode);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) {
+		ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_REMOTE_IP);
+		DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip));
+		lchan->abis_ip.connect_ip = ntohl(ip.s_addr);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) {
+		port = tlvp_val16_unal(tv, RSL_IE_IPAC_REMOTE_PORT);
+		port = ntohs(port);
+		DEBUGPC(DRSL, "REMOTE_PORT=%u ", port);
+		lchan->abis_ip.connect_port = port;
+	}
+}
+
+/*! \brief Issue IPA RSL CRCX to configure RTP on BTS side
+ *  \param[in] lchan Logical Channel for which we issue CRCX
+ */
+int rsl_ipacc_crcx(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IPAC_CRCX);
+	dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+	dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+	/* 0x1- == receive-only, 0x-1 == EFR codec */
+	lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+
+	DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n",
+		gsm_lchan_name(lchan), lchan->abis_ip.speech_mode,
+		lchan->abis_ip.rtp_payload);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/*! \brief Issue IPA RSL MDCX to configure MGW-side of RTP
+ *  \param[in] lchan Logical Channel for which we issue MDCX
+ *  \param[in] ip Remote (MGW) IP address for RTP
+ *  \param[in] port Remote (MGW) UDP port number for RTP
+ *  \param[in] rtp_payload2 Contents of RTP PAYLOAD 2 IE
+ */
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, uint32_t ip, uint16_t port,
+		   uint8_t rtp_payload2)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	uint32_t *att_ip;
+	struct in_addr ia;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IPAC_MDCX);
+	dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+	dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+	/* we need to store these now as MDCX_ACK does not return them :( */
+	lchan->abis_ip.rtp_payload2 = rtp_payload2;
+	lchan->abis_ip.connect_port = port;
+	lchan->abis_ip.connect_ip = ip;
+
+	/* 0x0- == both directions, 0x-1 == EFR codec */
+	lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d "
+		"CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan),
+		inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2,
+		lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode);
+
+	msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+	msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
+	att_ip = (uint32_t *) msgb_put(msg, sizeof(ip));
+	*att_ip = ia.s_addr;
+	msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port);
+	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+	if (rtp_payload2)
+		msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2);
+
+	msg->dst = lchan->ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* tell BTS to connect RTP stream to our local RTP socket */
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan)
+{
+	struct rtp_socket *rs = lchan->abis_ip.rtp_socket;
+	int rc;
+
+	rc = rsl_ipacc_mdcx(lchan, ntohl(rs->rtp.sin_local.sin_addr.s_addr),
+				ntohs(rs->rtp.sin_local.sin_port),
+			/* FIXME: use RTP payload of bound socket, not BTS*/
+				lchan->abis_ip.rtp_payload2);
+
+	return rc;
+}
+
+int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	uint8_t msg_type;
+
+	if (ts->flags & TS_F_PDCH_PENDING_MASK) {
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s PDCH %s requested, but a PDCH%s%s is still pending\n",
+		     gsm_ts_name(ts),
+		     act ? "ACT" : "DEACT",
+		     ts->flags & TS_F_PDCH_ACT_PENDING? " ACT" : "",
+		     ts->flags & TS_F_PDCH_DEACT_PENDING? " DEACT" : "");
+		return -EINVAL;
+	}
+
+	if (act){
+		/* Callers should heed the GPRS mode. */
+		OSMO_ASSERT(ts->trx->bts->gprs.mode != BTS_GPRS_NONE);
+		msg_type = RSL_MT_IPAC_PDCH_ACT;
+		ts->flags |= TS_F_PDCH_ACT_PENDING;
+	} else {
+		msg_type = RSL_MT_IPAC_PDCH_DEACT;
+		ts->flags |= TS_F_PDCH_DEACT_PENDING;
+	}
+	/* TODO add timeout to cancel PDCH DE/ACT */
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, msg_type);
+	dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0);
+
+	DEBUGP(DRSL, "%s IPAC PDCH %sACT\n", gsm_ts_name(ts),
+		act ? "" : "DE");
+
+	msg->dst = ts->trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	/* the BTS has acknowledged a local bind, it now tells us the IP
+	* address and port number to which it has bound the given logical
+	* channel */
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) ||
+	    !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) ||
+	    !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) {
+		LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing");
+		return -EINVAL;
+	}
+
+	ipac_parse_rtp(lchan, &tv);
+
+	osmo_signal_dispatch(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	/* the BTS has acknowledged a remote connect request and
+	 * it now tells us the IP address and port number to which it has
+	 * connected the given logical channel */
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	ipac_parse_rtp(lchan, &tv);
+	osmo_signal_dispatch(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tv, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE),
+				TLVP_LEN(&tv, RSL_IE_CAUSE));
+
+	osmo_signal_dispatch(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	char *ts_name;
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr,
+				  "Abis RSL rx IPACC: ");
+	ts_name = gsm_lchan_name(msg->lchan);
+	
+	switch (rllh->c.msg_type) {
+	case RSL_MT_IPAC_CRCX_ACK:
+		DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name);
+		rc = abis_rsl_rx_ipacc_crcx_ack(msg);
+		break;
+	case RSL_MT_IPAC_CRCX_NACK:
+		/* somehow the BTS was unable to bind the lchan to its local
+		 * port?!? */
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_MDCX_ACK:
+		/* the BTS tells us that a connect operation was successful */
+		DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name);
+		rc = abis_rsl_rx_ipacc_mdcx_ack(msg);
+		break;
+	case RSL_MT_IPAC_MDCX_NACK:
+		/* somehow the BTS was unable to connect the lchan to a remote
+		 * port */
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_DLCX_IND:
+		DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name);
+		rc = abis_rsl_rx_ipacc_dlcx_ind(msg);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n",
+			rllh->c.msg_type);
+		break;
+	}
+	DEBUGPC(DRSL, "\n");
+
+	return rc;
+}
+
+int dyn_ts_switchover_start(struct gsm_bts_trx_ts *ts,
+			    enum gsm_phys_chan_config to_pchan)
+{
+	int ss;
+	int rc = -EIO;
+
+	OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+	DEBUGP(DRSL, "%s starting switchover to %s\n",
+	       gsm_ts_and_pchan_name(ts), gsm_pchan_name(to_pchan));
+
+	if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s: Attempt to switch dynamic channel to %s,"
+		     " but is already in switchover.\n",
+		     gsm_ts_and_pchan_name(ts),
+		     gsm_pchan_name(to_pchan));
+		return ts->dyn.pchan_want == to_pchan? 0 : -EAGAIN;
+	}
+
+	if (ts->dyn.pchan_is == to_pchan) {
+		LOGP(DRSL, LOGL_INFO,
+		     "%s %s Already is in %s mode, cannot switchover.\n",
+		     gsm_ts_name(ts), gsm_pchan_name(ts->pchan),
+		     gsm_pchan_name(to_pchan));
+		return -EINVAL;
+	}
+
+	/* Paranoia: let's make sure all is indeed released. */
+	for (ss = 0; ss < ts_subslots(ts); ss++) {
+		struct gsm_lchan *lc = &ts->lchan[ss];
+		if (lc->state != LCHAN_S_NONE) {
+			LOGP(DRSL, LOGL_ERROR,
+			     "%s Attempt to switch dynamic channel to %s,"
+			     " but is not fully released.\n",
+			     gsm_ts_and_pchan_name(ts),
+			     gsm_pchan_name(to_pchan));
+			return -EAGAIN;
+		}
+	}
+
+	/* Record that we're busy switching. */
+	ts->dyn.pchan_want = to_pchan;
+
+	/*
+	 * To switch from PDCH, we need to initiate the release from the BSC
+	 * side. dyn_ts_switchover_continue() will be called from
+	 * rsl_rx_rf_chan_rel_ack(). PDCH is always on lchan[0].
+	 */
+	if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) {
+		rsl_lchan_set_state(ts->lchan, LCHAN_S_REL_REQ);
+		rc = rsl_rf_chan_release(ts->lchan, 0, SACCH_NONE);
+		if (rc) {
+			LOGP(DRSL, LOGL_ERROR,
+			     "%s RSL RF Chan Release failed\n",
+			     gsm_ts_and_pchan_name(ts));
+			return dyn_ts_switchover_failed(ts, rc);
+		}
+		return 0;
+	}
+
+	/*
+	 * To switch from TCH/F and TCH/H pchans, this has been called from
+	 * rsl_rx_rf_chan_rel_ack(), i.e. release is complete. Go ahead and
+	 * activate as new type. This will always be PDCH.
+	 */
+	return dyn_ts_switchover_continue(ts);
+}
+
+static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts)
+{
+	int rc;
+	uint8_t act_type;
+	uint8_t ho_ref;
+	int ss;
+	struct gsm_lchan *lchan;
+
+	OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+	DEBUGP(DRSL, "%s switchover: release complete,"
+	       " activating new pchan type\n",
+	       gsm_ts_and_pchan_name(ts));
+
+	if (ts->dyn.pchan_is == ts->dyn.pchan_want) {
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s Requested to switchover dynamic channel to the"
+		     " same type it is already in.\n",
+		     gsm_ts_and_pchan_name(ts));
+		return 0;
+	}
+
+	for (ss = 0; ss < ts_subslots(ts); ss++) {
+		lchan = &ts->lchan[ss];
+		if (lchan->rqd_ref) {
+			LOGP(DRSL, LOGL_ERROR,
+			     "%s During dyn TS switchover, expecting no"
+			     " Request Reference to be pending. Discarding!\n",
+			     gsm_lchan_name(lchan));
+			talloc_free(lchan->rqd_ref);
+			lchan->rqd_ref = NULL;
+		}
+	}
+
+	/*
+	 * When switching pchan modes, all lchans are unused. So always
+	 * activate whatever wants to be activated on the first lchan.  (We
+	 * wouldn't remember to use lchan[1] across e.g. a PDCH deact anyway)
+	 */
+	lchan = ts->lchan;
+	
+	/*
+	 * For TCH/x, the lchan->type has been set in lchan_alloc(), but it may
+	 * have been lost during channel release due to dynamic switchover.
+	 *
+	 * For PDCH, the lchan->type will actually remain NONE.
+	 * TODO: set GSM_LCHAN_PDTCH?
+	 */
+	switch (ts->dyn.pchan_want) {
+	case GSM_PCHAN_TCH_F:
+		lchan->type = GSM_LCHAN_TCH_F;
+		break;
+	case GSM_PCHAN_TCH_H:
+		lchan->type = GSM_LCHAN_TCH_H;
+		break;
+	case GSM_PCHAN_PDCH:
+		lchan->type = GSM_LCHAN_NONE;
+		break;
+	default:
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s Invalid target pchan for dynamic TS\n",
+		     gsm_ts_and_pchan_name(ts));
+	}
+
+	act_type = (ts->dyn.pchan_want == GSM_PCHAN_PDCH)
+		? RSL_ACT_OSMO_PDCH
+		: lchan->dyn.act_type;
+	ho_ref = (ts->dyn.pchan_want == GSM_PCHAN_PDCH)
+		? 0
+		: lchan->dyn.ho_ref;
+
+	/* Fetch the rqd_ref back from before switchover started. */
+	lchan->rqd_ref = lchan->dyn.rqd_ref;
+	lchan->rqd_ta = lchan->dyn.rqd_ta;
+	lchan->dyn.rqd_ref = NULL;
+	lchan->dyn.rqd_ta = 0;
+
+	/* During switchover, we have received a release ack, which means that
+	 * the act_timer has been stopped. Start the timer again so we mark
+	 * this channel broken if the activation ack comes too late. */
+	osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan);
+	osmo_timer_schedule(&lchan->act_timer, 4, 0);
+
+	rc = rsl_chan_activate_lchan(lchan, act_type, ho_ref);
+	if (rc) {
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s RSL Chan Activate failed\n",
+		     gsm_ts_and_pchan_name(ts));
+		return dyn_ts_switchover_failed(ts, rc);
+	}
+	return 0;
+}
+
+static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc)
+{
+	ts->dyn.pchan_want = ts->dyn.pchan_is;
+	LOGP(DRSL, LOGL_ERROR, "%s Error %d during dynamic channel switchover."
+	     " Going back to previous pchan.\n", gsm_ts_and_pchan_name(ts),
+	     rc);
+	return rc;
+}
+
+static void dyn_ts_switchover_complete(struct gsm_lchan *lchan)
+{
+	enum gsm_phys_chan_config pchan_act;
+	enum gsm_phys_chan_config pchan_was;
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+
+	OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+	pchan_act = pchan_for_lchant(lchan->type);
+	/*
+	 * Paranoia: do the types match?
+	 * In case of errors: we've received an act ack already, so what to do
+	 * about it? Logging the error should suffice for now.
+	 */
+	if (pchan_act != ts->dyn.pchan_want)
+		LOGP(DRSL, LOGL_ERROR,
+		     "%s Requested transition does not match lchan type %s\n",
+		     gsm_ts_and_pchan_name(ts),
+		     gsm_lchant_name(lchan->type));
+
+	pchan_was = ts->dyn.pchan_is;
+	ts->dyn.pchan_is = ts->dyn.pchan_want = pchan_act;
+
+	if (pchan_was != ts->dyn.pchan_is)
+		LOGP(DRSL, LOGL_INFO, "%s switchover from %s complete.\n",
+		     gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan_was));
+}
+
+/* Entry-point where L2 RSL from BTS enters */
+int abis_rsl_rcvmsg(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh;
+	int rc = 0;
+
+	if (!msg) {
+		DEBUGP(DRSL, "Empty RSL msg?..\n");
+		return -1;
+	}
+
+	if (msgb_l2len(msg) < sizeof(*rslh)) {
+		DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg));
+		msgb_free(msg);
+		return -1;
+	}
+
+	rslh = msgb_l2(msg);
+
+	switch (rslh->msg_discr & 0xfe) {
+	case ABIS_RSL_MDISC_RLL:
+		rc = abis_rsl_rx_rll(msg);
+		break;
+	case ABIS_RSL_MDISC_DED_CHAN:
+		rc = abis_rsl_rx_dchan(msg);
+		break;
+	case ABIS_RSL_MDISC_COM_CHAN:
+		rc = abis_rsl_rx_cchan(msg);
+		break;
+	case ABIS_RSL_MDISC_TRX:
+		rc = abis_rsl_rx_trx(msg);
+		break;
+	case ABIS_RSL_MDISC_LOC:
+		LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n",
+			rslh->msg_discr);
+		break;
+	case ABIS_RSL_MDISC_IPACCESS:
+		rc = abis_rsl_rx_ipacc(msg);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator "
+			"0x%02x\n", rslh->msg_discr);
+		rc = -EINVAL;
+	}
+	msgb_free(msg);
+	return rc;
+}
+
+int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
+		       struct rsl_ie_cb_cmd_type cb_command,
+		       const uint8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *cb_cmd;
+
+	cb_cmd = rsl_msgb_alloc();
+	if (!cb_cmd)
+		return -1;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD);
+	dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+	dh->chan_nr = chan_number; /* TODO: check the chan config */
+
+	msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, *(uint8_t*)&cb_command);
+	msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data);
+
+	cb_cmd->dst = bts->c0->rsl_link;
+
+	return abis_rsl_sendmsg(cb_cmd);
+}
+
+int rsl_nokia_si_begin(struct gsm_bts_trx *trx)
+{
+	struct abis_rsl_common_hdr *ch;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+	ch->msg_discr = ABIS_RSL_MDISC_TRX;
+	ch->msg_type = 0x40; /* Nokia SI Begin */
+
+	msg->dst = trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_nokia_si_end(struct gsm_bts_trx *trx)
+{
+	struct abis_rsl_common_hdr *ch;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+	ch->msg_discr = ABIS_RSL_MDISC_TRX;
+	ch->msg_type = 0x41;  /* Nokia SI End */
+
+	msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */
+
+	msg->dst = trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction)
+{
+	struct abis_rsl_common_hdr *ch;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+	ch->msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	ch->msg_type = RSL_MT_BS_POWER_CONTROL;
+
+	msgb_tv_put(msg, RSL_IE_CHAN_NR, channel);
+	msgb_tv_put(msg, RSL_IE_BS_POWER, reduction); /* reduction in 2dB steps */
+
+	msg->dst = trx->rsl_link;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/**
+ * Release all allocated SAPIs starting from @param start and
+ * release them with the given release mode. Once the release
+ * confirmation arrives it will be attempted to release the
+ * the RF channel.
+ */
+int rsl_release_sapis_from(struct gsm_lchan *lchan, int start,
+			enum rsl_rel_mode release_mode)
+{
+	int no_sapi = 1;
+	int sapi;
+
+	for (sapi = start; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+		uint8_t link_id;
+		if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+			continue;
+
+		link_id = sapi;
+		if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)
+			link_id |= 0x40;
+		rsl_release_request(lchan, link_id, release_mode);
+		no_sapi = 0;
+	}
+
+	return no_sapi;
+}
+
+int rsl_start_t3109(struct gsm_lchan *lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+
+	osmo_timer_setup(&lchan->T3109, t3109_expired, lchan);
+	osmo_timer_schedule(&lchan->T3109, bts->network->T3109, 0);
+	return 0;
+}
+
+/**
+ * \brief directly RF Channel Release the lchan
+ *
+ * When no SAPI was allocated, directly release the logical channel. This
+ * should only be called from chan_alloc.c on channel release handling. In
+ * case no SAPI was established the RF Channel can be directly released,
+ */
+int rsl_direct_rf_release(struct gsm_lchan *lchan)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(lchan->sapis); ++i) {
+		if (lchan->sapis[i] != LCHAN_SAPI_UNUSED) {
+			LOGP(DRSL, LOGL_ERROR, "%s SAPI(%d) still allocated.\n",
+				gsm_lchan_name(lchan), i);
+			return -1;
+		}
+	}
+
+	/* Now release it */
+	return rsl_rf_chan_release(lchan, 0, SACCH_NONE);
+}
diff --git a/openbsc/src/libbsc/acc_ramp.c b/openbsc/src/libbsc/acc_ramp.c
new file mode 100644
index 0000000..0c4dbac
--- /dev/null
+++ b/openbsc/src/libbsc/acc_ramp.c
@@ -0,0 +1,363 @@
+/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <strings.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/acc_ramp.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+
+/*
+ * Check if an ACC has been permanently barred for a BTS,
+ * e.g. with the 'rach access-control-class' VTY command.
+ */
+static bool acc_is_permanently_barred(struct gsm_bts *bts, unsigned int acc)
+{
+	OSMO_ASSERT(acc >= 0 && acc <= 9);
+	if (acc == 8 || acc == 9)
+		return (bts->si_common.rach_control.t2 & (1 << (acc - 8)));
+	return (bts->si_common.rach_control.t3 & (1 << (acc)));
+}
+
+static void allow_one_acc(struct acc_ramp *acc_ramp, unsigned int acc)
+{
+	OSMO_ASSERT(acc >= 0 && acc <= 9);
+	if (acc_ramp->barred_accs & (1 << acc))
+		LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: allowing Access Control Class %u\n", acc_ramp->bts->nr, acc);
+	acc_ramp->barred_accs &= ~(1 << acc);
+}
+
+static void barr_one_acc(struct acc_ramp *acc_ramp, unsigned int acc)
+{
+	OSMO_ASSERT(acc >= 0 && acc <= 9);
+	if ((acc_ramp->barred_accs & (1 << acc)) == 0)
+		LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: barring Access Control Class %u\n", acc_ramp->bts->nr, acc);
+	acc_ramp->barred_accs |= (1 << acc);
+}
+
+static void barr_all_accs(struct acc_ramp *acc_ramp)
+{
+	unsigned int acc;
+	for (acc = 0; acc < 10; acc++) {
+		if (!acc_is_permanently_barred(acc_ramp->bts, acc))
+			barr_one_acc(acc_ramp, acc);
+	}
+}
+
+static void allow_all_accs(struct acc_ramp *acc_ramp)
+{
+	unsigned int acc;
+	for (acc = 0; acc < 10; acc++) {
+		if (!acc_is_permanently_barred(acc_ramp->bts, acc))
+			allow_one_acc(acc_ramp, acc);
+	}
+}
+
+static unsigned int get_next_step_interval(struct acc_ramp *acc_ramp)
+{
+	struct gsm_bts *bts = acc_ramp->bts;
+	uint64_t load;
+
+	if (acc_ramp->step_interval_is_fixed)
+		return acc_ramp->step_interval_sec;
+
+	/* Scale the step interval to current channel load average. */
+	load = (bts->chan_load_avg << 8); /* convert to fixed-point */
+	acc_ramp->step_interval_sec = ((load * ACC_RAMP_STEP_INTERVAL_MAX) / 100) >> 8;
+	if (acc_ramp->step_interval_sec < ACC_RAMP_STEP_SIZE_MIN)
+		acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN;
+	else if (acc_ramp->step_interval_sec > ACC_RAMP_STEP_INTERVAL_MAX)
+		acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MAX;
+
+	LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: step interval set to %u seconds based on %u%% channel load average\n",
+	     bts->nr, acc_ramp->step_interval_sec, bts->chan_load_avg);
+	return acc_ramp->step_interval_sec;
+}
+
+static void do_acc_ramping_step(void *data)
+{
+	struct acc_ramp *acc_ramp = data;
+	int i;
+
+	/* Shortcut in case we only do one ramping step. */
+	if (acc_ramp->step_size == ACC_RAMP_STEP_SIZE_MAX) {
+		allow_all_accs(acc_ramp);
+		gsm_bts_set_system_infos(acc_ramp->bts);
+		return;
+	}
+
+	/* Allow 'step_size' ACCs, starting from ACC0. ACC9 will be allowed last. */
+	for (i = 0; i < acc_ramp->step_size; i++) {
+		int idx = ffs(acc_ramp_get_barred_t3(acc_ramp));
+		if (idx > 0) {
+			/* One of ACC0-ACC7 is still bared. */
+			unsigned int acc = idx - 1;
+			if (!acc_is_permanently_barred(acc_ramp->bts, acc))
+				allow_one_acc(acc_ramp, acc);
+		} else {
+			idx = ffs(acc_ramp_get_barred_t2(acc_ramp));
+			if (idx == 1 || idx == 2) {
+				/* ACC8 or ACC9 is still barred. */
+				unsigned int acc = idx - 1 + 8;
+				if (!acc_is_permanently_barred(acc_ramp->bts, acc))
+					allow_one_acc(acc_ramp, acc);
+			} else {
+				/* All ACCs are now allowed. */
+				break;
+			}
+		}
+	}
+
+	gsm_bts_set_system_infos(acc_ramp->bts);
+
+	/* If we have not allowed all ACCs yet, schedule another ramping step. */
+	if (acc_ramp_get_barred_t2(acc_ramp) != 0x00 ||
+	    acc_ramp_get_barred_t3(acc_ramp) != 0x00)
+		osmo_timer_schedule(&acc_ramp->step_timer, get_next_step_interval(acc_ramp), 0);
+}
+
+/* Implements osmo_signal_cbfn() -- trigger or abort ACC ramping upon changes RF lock state. */
+static int acc_ramp_nm_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+	struct nm_statechg_signal_data *nsd = signal_data;
+	struct acc_ramp *acc_ramp = handler_data;
+	struct gsm_bts_trx *trx = NULL;
+	bool trigger_ramping = false, abort_ramping = false;
+
+	/* Handled signals map to an Administrative State Change ACK, or a State Changed Event Report. */
+	if (signal != S_NM_STATECHG_ADM && signal != S_NM_STATECHG_OPER)
+		return 0;
+
+	if (nsd->obj_class != NM_OC_RADIO_CARRIER)
+		return 0;
+
+	trx = nsd->obj;
+
+	LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: administrative state %s -> %s\n",
+	    acc_ramp->bts->nr, trx->nr,
+	    get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative),
+	    get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative));
+	LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: operational state %s -> %s\n",
+	    acc_ramp->bts->nr, trx->nr,
+	    abis_nm_opstate_name(nsd->old_state->operational),
+	    abis_nm_opstate_name(nsd->new_state->operational));
+
+	/* We only care about state changes of the first TRX. */
+	if (trx->nr != 0)
+		return 0;
+
+	/* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */
+	if (trx->rsl_link == NULL) {
+		LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change because RSL link is down\n",
+		     acc_ramp->bts->nr, trx->nr);
+		return 0;
+	}
+
+	/* Trigger or abort ACC ramping based on the new state of this TRX. */
+	if (nsd->old_state->administrative != nsd->new_state->administrative) {
+		switch (nsd->new_state->administrative) {
+		case NM_STATE_UNLOCKED:
+			if (nsd->old_state->operational != nsd->new_state->operational) {
+				/*
+				 * Administrative and operational state have both changed.
+				 * Trigger ramping only if TRX 0 will be both enabled and unlocked.
+				 */
+				if (nsd->new_state->operational == NM_OPSTATE_ENABLED)
+					trigger_ramping = true;
+				else
+					LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
+					     "because TRX is transitioning into operational state '%s'\n",
+					     acc_ramp->bts->nr, trx->nr,
+					     abis_nm_opstate_name(nsd->new_state->operational));
+			} else {
+				/*
+				 * Operational state has not changed.
+				 * Trigger ramping only if TRX 0 is already usable.
+				 */
+				if (trx_is_usable(trx))
+					trigger_ramping = true;
+				else
+					LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
+					     "because TRX is not usable\n", acc_ramp->bts->nr, trx->nr);
+			}
+			break;
+		case NM_STATE_LOCKED:
+		case NM_STATE_SHUTDOWN:
+			abort_ramping = true;
+			break;
+		case NM_STATE_NULL:
+		default:
+			LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized administrative state '0x%x' "
+			    "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative);
+			break;
+		}
+	}
+	if (nsd->old_state->operational != nsd->new_state->operational) {
+		switch (nsd->new_state->operational) {
+		case NM_OPSTATE_ENABLED:
+			if (nsd->old_state->administrative != nsd->new_state->administrative) {
+				/*
+				 * Administrative and operational state have both changed.
+				 * Trigger ramping only if TRX 0 will be both enabled and unlocked.
+				 */
+				if (nsd->new_state->administrative == NM_STATE_UNLOCKED)
+					trigger_ramping = true;
+				else
+					LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
+					     "because TRX is transitioning into administrative state '%s'\n",
+					     acc_ramp->bts->nr, trx->nr,
+					     get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative));
+			} else {
+				/*
+				 * Administrative state has not changed.
+				 * Trigger ramping only if TRX 0 is already unlocked.
+				 */
+				if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+					trigger_ramping = true;
+				else
+					LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
+					     "because TRX is in administrative state '%s'\n",
+					     acc_ramp->bts->nr, trx->nr,
+					     get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative));
+			}
+			break;
+		case NM_OPSTATE_DISABLED:
+			abort_ramping = true;
+			break;
+		case NM_OPSTATE_NULL:
+		default:
+			LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized operational state '0x%x' "
+			     "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative);
+			break;
+		}
+	}
+
+	if (trigger_ramping)
+		acc_ramp_trigger(acc_ramp);
+	else if (abort_ramping)
+		acc_ramp_abort(acc_ramp);
+
+	return 0;
+}
+
+/*!
+ * Initialize an acc_ramp data structure.
+ * Storage for this structure must be provided by the caller.
+ *
+ * By default, ACC ramping is disabled and all ACCs are allowed.
+ *
+ * \param[in] acc_ramp Pointer to acc_ramp structure to be initialized.
+ * \param[in] bts BTS which uses this ACC ramp data structure.
+ */
+void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts)
+{
+	acc_ramp->bts = bts;
+	acc_ramp_set_enabled(acc_ramp, false);
+	acc_ramp->step_size = ACC_RAMP_STEP_SIZE_DEFAULT;
+	acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN;
+	acc_ramp->step_interval_is_fixed = false;
+	allow_all_accs(acc_ramp);
+	osmo_timer_setup(&acc_ramp->step_timer, do_acc_ramping_step, acc_ramp);
+	osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, acc_ramp);
+}
+
+/*!
+ * Change the ramping step size which controls how many ACCs will be allowed per ramping step.
+ * Returns negative on error (step_size out of range), else zero.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ * \param[in] step_size The new step size value.
+ */
+int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size)
+{
+	if (step_size < ACC_RAMP_STEP_SIZE_MIN || step_size > ACC_RAMP_STEP_SIZE_MAX)
+		return -ERANGE;
+
+	acc_ramp->step_size = step_size;
+	LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step size set to %u\n", acc_ramp->bts->nr, step_size);
+	return 0;
+}
+
+/*!
+ * Change the ramping step interval to a fixed value. Unless this function is called,
+ * the interval is automatically scaled to the BTS channel load average.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ * \param[in] step_interval The new fixed step interval in seconds.
+ */
+int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval)
+{
+	if (step_interval < ACC_RAMP_STEP_INTERVAL_MIN || step_interval > ACC_RAMP_STEP_INTERVAL_MAX)
+		return -ERANGE;
+
+	acc_ramp->step_interval_sec = step_interval;
+	acc_ramp->step_interval_is_fixed = true;
+	LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to %u seconds\n",
+	     acc_ramp->bts->nr, step_interval);
+	return 0;
+}
+
+/*!
+ * Clear a previously set fixed ramping step interval, so that the interval
+ * is again automatically scaled to the BTS channel load average.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp)
+{
+	acc_ramp->step_interval_is_fixed = false;
+	LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to 'dynamic'\n",
+	     acc_ramp->bts->nr);
+}
+
+/*!
+ * Determine if ACC ramping should be started according to configuration, and
+ * begin the ramping process if the necessary conditions are present.
+ * Perform at least one ramping step to allow 'step_size' ACCs.
+ * If 'step_size' is ACC_RAMP_STEP_SIZE_MAX, or if ACC ramping is disabled,
+ * all ACCs will be allowed immediately.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+void acc_ramp_trigger(struct acc_ramp *acc_ramp)
+{
+	/* Abort any previously running ramping process and allow all available ACCs. */
+	acc_ramp_abort(acc_ramp);
+
+	if (acc_ramp_is_enabled(acc_ramp)) {
+		/* Set all available ACCs to barred and start ramping up. */
+		barr_all_accs(acc_ramp);
+		do_acc_ramping_step(acc_ramp);
+	}
+}
+
+/*!
+ * Abort the ramping process and allow all available ACCs immediately.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+void acc_ramp_abort(struct acc_ramp *acc_ramp)
+{
+	if (osmo_timer_pending(&acc_ramp->step_timer))
+		osmo_timer_del(&acc_ramp->step_timer);
+
+	allow_all_accs(acc_ramp);
+}
diff --git a/openbsc/src/libbsc/arfcn_range_encode.c b/openbsc/src/libbsc/arfcn_range_encode.c
new file mode 100644
index 0000000..9ca4840
--- /dev/null
+++ b/openbsc/src/libbsc/arfcn_range_encode.c
@@ -0,0 +1,330 @@
+/* gsm 04.08 system information (si) encoding and decoding
+ * 3gpp ts 04.08 version 7.21.0 release 1998 / etsi ts 100 940 v7.21.0 */
+
+/*
+ * (C) 2012 Holger Hans Peter Freyther
+ * (C) 2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <openbsc/arfcn_range_encode.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <osmocom/core/utils.h>
+
+#include <errno.h>
+
+static inline int greatest_power_of_2_lesser_or_equal_to(int index)
+{
+	int power_of_2 = 1;
+
+	do {
+		power_of_2 *= 2;
+	} while (power_of_2 <= index);
+
+	/* now go back one step */
+	return power_of_2 / 2;
+}
+
+static inline int mod(int data, int range)
+{
+	int res = data % range;
+	while (res < 0)
+		res += range;
+	return res;
+}
+
+/**
+ * Determine at which index to split the ARFCNs to create an
+ * equally size partition for the given range. Return -1 if
+ * no such partition exists.
+ */
+int range_enc_find_index(enum gsm48_range range, const int *freqs, const int size)
+{
+	int i, j, n;
+
+	const int RANGE_DELTA = (range - 1) / 2;
+
+	for (i = 0; i < size; ++i) {
+		n = 0;
+		for (j = 0; j < size; ++j) {
+			if (mod(freqs[j] - freqs[i], range) <= RANGE_DELTA)
+				n += 1;
+		}
+
+		if (n - 1 == (size - 1) / 2)
+			return i;
+	}
+
+	return -1;
+}
+
+/**
+ * Range encode the ARFCN list.
+ * \param range The range to use.
+ * \param arfcns The list of ARFCNs
+ * \param size The size of the list of ARFCNs
+ * \param out Place to store the W(i) output.
+ */
+int range_enc_arfcns(enum gsm48_range range,
+		const int *arfcns, int size, int *out,
+		const int index)
+{
+	int split_at;
+	int i;
+
+	/*
+	 * The below is a GNU extension and we can remove it when
+	 * we move to a quicksort like in-situ swap with the pivot.
+	 */
+	int arfcns_left[size / 2];
+	int arfcns_right[size / 2];
+	int l_size;
+	int r_size;
+	int l_origin;
+	int r_origin;
+
+
+	/* Test the two recursion anchors and stop processing */
+	if (size == 0)
+		return 0;
+
+	if (size == 1) {
+		out[index] = 1 + arfcns[0];
+		return 0;
+	}
+
+	/* Now do the processing */
+	split_at = range_enc_find_index(range, arfcns, size);
+	if (split_at < 0)
+		return -EINVAL;
+
+	/* we now know where to split */
+	out[index] = 1 + arfcns[split_at];
+
+	/* calculate the work that needs to be done for the leafs */
+	l_origin = mod(arfcns[split_at] + ((range - 1) / 2) + 1, range);
+	r_origin = mod(arfcns[split_at] + 1, range);
+	for (i = 0, l_size = 0, r_size = 0; i < size; ++i) {
+		if (mod(arfcns[i] - l_origin, range) < range / 2)
+			arfcns_left[l_size++] = mod(arfcns[i] - l_origin, range);
+		if (mod(arfcns[i] - r_origin, range) < range / 2)
+			arfcns_right[r_size++] = mod(arfcns[i] - r_origin, range);
+	}
+
+	/*
+	 * Now recurse and we need to make this iterative... but as the
+	 * tree is balanced the stack will not be too deep.
+	 */
+	if (l_size)
+		range_enc_arfcns(range / 2, arfcns_left, l_size,
+			out, index + greatest_power_of_2_lesser_or_equal_to(index + 1));
+	if (r_size)
+		range_enc_arfcns((range - 1) / 2, arfcns_right, r_size,
+			 out, index + (2 * greatest_power_of_2_lesser_or_equal_to(index + 1)));
+	return 0;
+}
+
+/*
+ * The easiest is to use f0 == arfcns[0]. This means that under certain
+ * circumstances we can encode less ARFCNs than possible with an optimal f0.
+ *
+ * TODO: Solve the optimisation problem and pick f0 so that the max distance
+ * is the smallest. Taking into account the modulo operation. I think picking
+ * size/2 will be the optimal arfcn.
+ */
+/**
+ * This implements the range determination as described in GSM 04.08 J4. The
+ * result will be a base frequency f0 and the range to use. Note that for range
+ * 1024 encoding f0 always refers to ARFCN 0 even if it is not an element of
+ * the arfcns list.
+ *
+ * \param[in] arfcns The input frequencies, they must be sorted, lowest number first
+ * \param[in] size The length of the array
+ * \param[out] f0 The selected F0 base frequency. It might not be inside the list
+ */
+int range_enc_determine_range(const int *arfcns, const int size, int *f0)
+{
+	int max = 0;
+
+	/*
+	 * Go for the easiest. And pick arfcns[0] == f0.
+	 */
+	max = arfcns[size - 1] - arfcns[0];
+	*f0 = arfcns[0];
+
+	if (max < 128 && size <= 29)
+		return ARFCN_RANGE_128;
+	if (max < 256 && size <= 22)
+		return ARFCN_RANGE_256;
+	if (max < 512 && size <= 18)
+		return ARFCN_RANGE_512;
+	if (max < 1024 && size <= 17) {
+		*f0 = 0;
+		return ARFCN_RANGE_1024;
+	}
+
+	return ARFCN_RANGE_INVALID;
+}
+
+static void write_orig_arfcn(uint8_t *chan_list, int f0)
+{
+	chan_list[0] |= (f0 >> 9) & 1;
+	chan_list[1] = (f0 >> 1);
+	chan_list[2] = (f0 & 1) << 7;
+}
+
+static void write_all_wn(uint8_t *chan_list, int bit_offs,
+			 int *w, int w_size, int w1_len)
+{
+	int octet_offs = 0; /* offset into chan_list */
+	int wk_len = w1_len; /* encoding size in bits of w[k] */
+	int k; /* 1 based */
+	int level = 0; /* tree level, top level = 0 */
+	int lvl_left = 1; /* nodes per tree level */
+
+	/* W(2^i) to W(2^(i+1)-1) are on w1_len-i bits when present */
+
+	for (k = 1; k <= w_size; k++) {
+		int wk_left = wk_len;
+		DEBUGP(DRR,
+		       "k=%d, wk_len=%d, offs=%d:%d, level=%d, "
+		       "lvl_left=%d\n",
+		       k, wk_len, octet_offs, bit_offs, level, lvl_left);
+
+		while (wk_left > 0) {
+			int cur_bits = 8 - bit_offs;
+			int cur_mask;
+			int wk_slice;
+
+			if (cur_bits > wk_left)
+				cur_bits = wk_left;
+
+			cur_mask = ((1 << cur_bits) - 1);
+
+			DEBUGP(DRR,
+			       " wk_left=%d, cur_bits=%d, offs=%d:%d\n",
+			       wk_left, cur_bits, octet_offs, bit_offs);
+
+			/* advance */
+			wk_left -= cur_bits;
+			bit_offs += cur_bits;
+
+			/* right aligned wk data for current out octet */
+			wk_slice = (w[k-1] >> wk_left) & cur_mask;
+
+			/* cur_bits now contains the number of bits
+			 * that are to be copied from wk to the chan_list.
+			 * wk_left is set to the number of bits that must
+			 * not yet be copied.
+			 * bit_offs points after the bit area that is going to
+			 * be overwritten:
+			 *
+			 *          wk_left
+			 *             |
+			 *             v
+			 * wk: WWWWWWWWWWW
+			 *        |||||<-- wk_slice, cur_bits=5
+			 *      --WWWWW-
+			 *             ^
+			 *             |
+			 *           bit_offs
+			 */
+
+			DEBUGP(DRR,
+			       " wk=%02x, slice=%02x/%02x, cl=%02x\n",
+			       w[k-1], wk_slice, cur_mask, wk_slice << (8 - bit_offs));
+
+			chan_list[octet_offs] &= ~(cur_mask << (8 - bit_offs));
+			chan_list[octet_offs] |= wk_slice << (8 - bit_offs);
+
+			/* adjust output */
+			if (bit_offs == 8) {
+				bit_offs = 0;
+				octet_offs += 1;
+			}
+		}
+
+		/* adjust bit sizes */
+		lvl_left -= 1;
+		if (!lvl_left) {
+			/* completed tree level, advance to next */
+			level += 1;
+			lvl_left = 1 << level;
+			wk_len -= 1;
+		}
+	}
+}
+
+int range_enc_range128(uint8_t *chan_list, int f0, int *w)
+{
+	chan_list[0] = 0x8C;
+	write_orig_arfcn(chan_list, f0);
+
+	write_all_wn(&chan_list[2], 1, w, 28, 7);
+	return 0;
+}
+
+int range_enc_range256(uint8_t *chan_list, int f0, int *w)
+{
+	chan_list[0] = 0x8A;
+	write_orig_arfcn(chan_list, f0);
+
+	write_all_wn(&chan_list[2], 1, w, 21, 8);
+	return 0;
+}
+
+int range_enc_range512(uint8_t *chan_list, int f0, int *w)
+{
+	chan_list[0] = 0x88;
+	write_orig_arfcn(chan_list, f0);
+
+	write_all_wn(&chan_list[2], 1, w, 17, 9);
+	return 0;
+}
+
+int range_enc_range1024(uint8_t *chan_list, int f0, int f0_included, int *w)
+{
+	chan_list[0] = 0x80 | (f0_included << 2);
+
+	write_all_wn(&chan_list[0], 6, w, 16, 10);
+	return 0;
+}
+
+int range_enc_filter_arfcns(int *arfcns,
+			    const int size, const int f0, int *f0_included)
+{
+	int i, j = 0;
+	*f0_included = 0;
+
+	for (i = 0; i < size; ++i) {
+		/*
+		 * Appendix J.4 says the following:
+		 * All frequencies except F(0), minus F(0) + 1.
+		 * I assume we need to exclude it here.
+		 */
+		if (arfcns[i] == f0) {
+			*f0_included = 1;
+			continue;
+		}
+
+		arfcns[j++] = mod(arfcns[i] - (f0 + 1), 1024);
+	}
+
+	return j;
+}
diff --git a/openbsc/src/libbsc/bsc_api.c b/openbsc/src/libbsc/bsc_api.c
new file mode 100644
index 0000000..0c16db3
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_api.c
@@ -0,0 +1,896 @@
+/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */
+
+/* (C) 2010-2011 by Holger Hans Peter Freyther
+ * (C) 2010-2011 by On-Waves
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/handover.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/trau_mux.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/core/talloc.h>
+
+#define GSM0808_T10_VALUE    6, 0
+
+
+static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind);
+static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id);
+static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+
+/* GSM 08.08 3.2.2.33 */
+static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
+{
+	uint8_t channel_mode = 0, channel = 0;
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+	case GSM48_CMODE_SPEECH_EFR:
+	case GSM48_CMODE_SPEECH_AMR:
+		channel_mode = 0x9;
+		break;
+	case GSM48_CMODE_SIGN:
+		channel_mode = 0x8;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+		channel_mode = 0xe;
+		break;
+	case GSM48_CMODE_DATA_12k0:
+		channel_mode = 0xb;
+		break;
+	case GSM48_CMODE_DATA_6k0:
+		channel_mode = 0xc;
+		break;
+	case GSM48_CMODE_DATA_3k6:
+		channel_mode = 0xd;
+		break;
+	}
+
+	switch (lchan->type) {
+	case GSM_LCHAN_NONE:
+		channel = 0x0;
+		break;
+	case GSM_LCHAN_SDCCH:
+		channel = 0x1;
+		break;
+	case GSM_LCHAN_TCH_F:
+		channel = 0x8;
+		break;
+	case GSM_LCHAN_TCH_H:
+		channel = 0x9;
+		break;
+	case GSM_LCHAN_UNKNOWN:
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan);
+		break;
+	}
+
+	return channel_mode << 4 | channel;
+}
+
+static uint8_t chan_mode_to_speech(struct gsm_lchan *lchan)
+{
+	int mode = 0;
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		mode = 1;
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		mode = 0x11;
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		mode = 0x21;
+		break;
+	case GSM48_CMODE_SIGN:
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_6k0:
+	case GSM48_CMODE_DATA_3k6:
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Using non speech mode: %d\n", mode);
+		return 0;
+		break;
+	}
+
+	/* assume to always do AMR HR on any TCH type */
+	if (lchan->type == GSM_LCHAN_TCH_H ||
+	    lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		mode |= 0x4;
+
+        return mode;
+}
+
+static void assignment_t10_timeout(void *_conn)
+{
+	struct bsc_api *api;
+	struct gsm_subscriber_connection *conn =
+		(struct gsm_subscriber_connection *) _conn;
+
+	LOGP(DMSC, LOGL_ERROR, "Assignment T10 timeout on %p\n", conn);
+
+	/*
+	 * normal release on the secondary channel but only if the
+	 * secondary_channel has not been released by the handle_chan_nack.
+	 */
+	if (conn->secondary_lchan)
+		lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+	conn->secondary_lchan = NULL;
+
+	/* inform them about the failure */
+	api = conn->network->bsc_api;
+	api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+}
+
+/*! \brief Determine and apply AMR multi-rate configuration to lchan
+ *  Determine which AMR multi-rate configuration to use and apply it to
+ *  the lchan (so it can be communicated to BTS and MS during channel
+ *  activation.
+ *  \param[in] conn subscriber connection (used to resolve bsc_api)
+ *  \param[out] lchan logical channel to which to apply mr config
+ *  \param[in] full_rate whether to use full-rate (1) or half-rate (0) config
+ */
+static void handle_mr_config(struct gsm_subscriber_connection *conn,
+			     struct gsm_lchan *lchan, int full_rate)
+{
+	struct bsc_api *api;
+	api = conn->network->bsc_api;
+	struct amr_multirate_conf *mr;
+	struct gsm48_multi_rate_conf *mr_conf;
+
+	/* BSC api override for this method, used in OsmoBSC mode with
+	 * bsc_mr_config() to use MSC-specific/specified configuration */
+	if (api->mr_config)
+		return api->mr_config(conn, lchan, full_rate);
+
+	/* NITB case: use the BTS-specic multi-rate configuration from
+	 * the vty/configuration file */
+	if (full_rate)
+		mr = &lchan->ts->trx->bts->mr_full;
+	else
+		mr = &lchan->ts->trx->bts->mr_half;
+
+	mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+	mr_conf->ver = 1;
+
+	/* default, if no AMR codec defined */
+	if (!mr->gsm48_ie[1]) {
+		mr_conf->icmi = 1;
+		mr_conf->m5_90 = 1;
+	}
+	/* store encoded MR config IE lchan for both MS (uplink) and BTS
+	 * (downlink) directions */
+	gsm48_multirate_config(lchan->mr_ms_lv, mr, mr->ms_mode);
+	gsm48_multirate_config(lchan->mr_bts_lv, mr, mr->bts_mode);
+}
+
+/*
+ * Start a new assignment and make sure that it is completed within T10 either
+ * positively, negatively or by the timeout.
+ *
+ *  1.) allocate a new lchan
+ *  2.) copy the encryption key and other data from the
+ *      old to the new channel.
+ *  3.) RSL Channel Activate this channel and wait
+ *
+ * -> Signal handler for the LCHAN
+ *  4.) Send GSM 04.08 assignment command to the MS
+ *
+ * -> Assignment Complete/Assignment Failure
+ *  5.) Release the SDCCH, continue signalling on the new link
+ */
+static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+	struct gsm_lchan *new_lchan;
+	int chan_type;
+
+	chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
+
+	new_lchan = lchan_alloc(conn->bts, chan_type, 0);
+
+	if (!new_lchan) {
+		LOGP(DMSC, LOGL_NOTICE, "No free channel.\n");
+		return -1;
+	}
+
+	/* copy old data to the new channel */
+	memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr));
+	new_lchan->ms_power = conn->lchan->ms_power;
+	new_lchan->bs_power = conn->lchan->bs_power;
+	new_lchan->rqd_ta = conn->lchan->rqd_ta;
+
+	/* copy new data to it */
+	new_lchan->tch_mode = chan_mode;
+	new_lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+
+	/* handle AMR correctly */
+	if (chan_mode == GSM48_CMODE_SPEECH_AMR)
+		handle_mr_config(conn, new_lchan, full_rate);
+
+	if (rsl_chan_activate_lchan(new_lchan, 0x1, 0) < 0) {
+		LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+		lchan_free(new_lchan);
+		return -1;
+	}
+
+	/* remember that we have the channel */
+	conn->secondary_lchan = new_lchan;
+	new_lchan->conn = conn;
+
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+	return 0;
+}
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lchan)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *net = lchan->ts->trx->bts->network;
+
+	conn = talloc_zero(net, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	conn->network = net;
+	conn->lchan = lchan;
+	conn->bts = lchan->ts->trx->bts;
+	conn->via_ran = RAN_GERAN_A;
+	lchan->conn = conn;
+	llist_add_tail(&conn->entry, &net->subscr_conns);
+	return conn;
+}
+
+void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+
+	if (conn->subscr) {
+		subscr_put(conn->subscr);
+		conn->subscr = NULL;
+	}
+
+
+	if (conn->ho_lchan) {
+		LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n");
+		conn->ho_lchan->conn = NULL;
+	}
+
+	if (conn->lchan) {
+		LOGP(DNM, LOGL_ERROR, "The lchan should have been cleared.\n");
+		conn->lchan->conn = NULL;
+	}
+
+	if (conn->secondary_lchan) {
+		LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n");
+		conn->secondary_lchan->conn = NULL;
+	}
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+}
+
+int bsc_api_init(struct gsm_network *network, struct bsc_api *api)
+{
+	network->bsc_api = api;
+	return 0;
+}
+
+/*! \brief process incoming 08.08 DTAP from MSC (send via BTS to MS) */
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn,
+			struct msgb *msg, int link_id, int allow_sacch)
+{
+	uint8_t sapi;
+
+
+	if (!conn->lchan) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "Called submit dtap without an lchan.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	sapi = link_id & 0x7;
+	msg->lchan = conn->lchan;
+	msg->dst = msg->lchan->ts->trx->rsl_link;
+
+	/* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */
+	if (allow_sacch && sapi != 0) {
+		if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H)
+			link_id |= 0x40;
+	}
+
+	msg->l3h = msg->data;
+	/* is requested SAPI already up? */
+	if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) {
+		/* Establish L2 for additional SAPI */
+		OBSC_LINKID_CB(msg) = link_id;
+		if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) {
+			msgb_free(msg);
+			send_sapi_reject(conn, link_id);
+			return -1;
+		}
+		return 0;
+	} else {
+		/* Directly forward via RLL/RSL to BTS */
+		return rsl_data_request(msg, link_id);
+	}
+}
+
+/*
+ * \brief Check if the given channel is compatible with the mode/fullrate
+ */
+static int chan_compat_with_mode(struct gsm_lchan *lchan, int chan_mode, int full_rate)
+{
+	switch (chan_mode) {
+	case GSM48_CMODE_SIGN:
+		/* signalling is always possible */
+		return 1;
+	case GSM48_CMODE_SPEECH_V1:
+	case GSM48_CMODE_SPEECH_AMR:
+	case GSM48_CMODE_DATA_3k6:
+	case GSM48_CMODE_DATA_6k0:
+		/* these services can all run on TCH/H, but we may have
+		 * an explicit override by the 'full_rate' argument */
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 1;
+		case GSM_LCHAN_TCH_H:
+			if (full_rate)
+				return 0;
+			else
+				return 1;
+			break;
+		default:
+			return 0;
+		}
+		break;
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_SPEECH_EFR:
+		/* these services all explicitly require a TCH/F */
+		if (lchan->type == GSM_LCHAN_TCH_F)
+			return 1;
+		else
+			return 0;
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * Send a GSM08.08 Assignment Request. Right now this does not contain the
+ * audio codec type or the allowed rates for the config. It is assumed that
+ * this is for audio handling only. In case the current channel does not allow
+ * the selected mode a new one will be allocated.
+ *
+ * TODO: Add multirate configuration, make it work for more than audio.
+ */
+int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+	struct bsc_api *api;
+	api = conn->network->bsc_api;
+
+	if (!chan_compat_with_mode(conn->lchan, chan_mode, full_rate)) {
+		if (handle_new_assignment(conn, chan_mode, full_rate) != 0)
+			goto error;
+	} else {
+		if (chan_mode == GSM48_CMODE_SPEECH_AMR)
+			handle_mr_config(conn, conn->lchan, full_rate);
+
+		LOGP(DMSC, LOGL_NOTICE,
+		     "Sending %s ChanModify for speech: %s on channel %s\n",
+		     gsm_lchan_name(conn->lchan),
+		     get_value_string(gsm48_chan_mode_names, chan_mode),
+		     get_value_string(gsm_chan_t_names, conn->lchan->type));
+		gsm48_lchan_modify(conn->lchan, chan_mode);
+	}
+
+	/* we will now start the timer to complete the assignment */
+	osmo_timer_setup(&conn->T10, assignment_t10_timeout, conn);
+	osmo_timer_schedule(&conn->T10, GSM0808_T10_VALUE);
+	return 0;
+
+error:
+	api->assign_fail(conn, 0, NULL);
+	return -1;
+}
+
+int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len,
+		 uint8_t *mi, int chan_type)
+{
+	return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type, false);
+}
+
+static void handle_ass_compl(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	struct bsc_api *api = conn->network->bsc_api;
+
+	if (conn->secondary_lchan != msg->lchan) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment Compl should occur on second lchan.\n");
+		return;
+	}
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment Compl invalid: %zu\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		return;
+	}
+
+	/* switch TRAU muxer for E1 based BTS from one channel to another */
+	if (is_e1_bts(conn->bts))
+		switch_trau_mux(conn->lchan, conn->secondary_lchan);
+
+	/* swap channels */
+	osmo_timer_del(&conn->T10);
+
+	lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END);
+	conn->lchan = conn->secondary_lchan;
+	conn->secondary_lchan = NULL;
+
+	if (is_ipaccess_bts(conn->bts) && conn->lchan->tch_mode != GSM48_CMODE_SIGN)
+		rsl_ipacc_crcx(conn->lchan);
+
+	api->assign_compl(conn, gh->data[0],
+			  lchan_to_chosen_channel(conn->lchan),
+			  conn->lchan->encr.alg_id,
+			  chan_mode_to_speech(conn->lchan));
+}
+
+static void handle_ass_fail(struct gsm_subscriber_connection *conn,
+			    struct msgb *msg)
+{
+	struct bsc_api *api = conn->network->bsc_api;
+	uint8_t *rr_failure;
+	struct gsm48_hdr *gh;
+
+
+	if (conn->lchan != msg->lchan) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment failure should occur on primary lchan.\n");
+		return;
+	}
+
+	/* stop the timer and release it */
+	osmo_timer_del(&conn->T10);
+	if (conn->secondary_lchan) {
+		lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+		conn->secondary_lchan = NULL;
+	}
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "assignment failure unhandled: %zu\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		rr_failure = NULL;
+	} else {
+		rr_failure = &gh->data[0];
+	}
+
+	api->assign_fail(conn,
+			 GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE,
+			 rr_failure);
+}
+
+static void handle_classmark_chg(struct gsm_subscriber_connection *conn,
+				 struct msgb *msg)
+{
+	struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	uint8_t cm2_len, cm3_len = 0;
+	uint8_t *cm2, *cm3 = NULL;
+
+	DEBUGP(DRR, "CLASSMARK CHANGE ");
+
+	/* classmark 2 */
+	cm2_len = gh->data[0];
+	cm2 = &gh->data[1];
+	DEBUGPC(DRR, "CM2(len=%u) ", cm2_len);
+
+	if (payload_len > cm2_len + 1) {
+		/* we must have a classmark3 */
+		if (gh->data[cm2_len+1] != 0x20) {
+			DEBUGPC(DRR, "ERR CM3 TAG\n");
+			return;
+		}
+		if (cm2_len > 3) {
+			DEBUGPC(DRR, "CM2 too long!\n");
+			return;
+		}
+
+		cm3_len = gh->data[cm2_len+2];
+		cm3 = &gh->data[cm2_len+3];
+		if (cm3_len > 14) {
+			DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len);
+			return;
+		}
+		DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len);
+	}
+	api->classmark_chg(conn, cm2, cm2_len, cm3, cm3_len);
+}
+
+/* Chapter 9.1.16 Handover complete */
+static void handle_rr_ho_compl(struct msgb *msg)
+{
+	struct lchan_signal_data sig;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	sig.lchan = msg->lchan;
+	sig.mr = NULL;
+	osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig);
+	/* FIXME: release old channel */
+}
+
+/* Chapter 9.1.17 Handover Failure */
+static void handle_rr_ho_fail(struct msgb *msg)
+{
+	struct lchan_signal_data sig;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER FAILED cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	sig.lchan = msg->lchan;
+	sig.mr = NULL;
+	osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig);
+	/* FIXME: release allocated new channel */
+}
+
+
+static void dispatch_dtap(struct gsm_subscriber_connection *conn,
+			  uint8_t link_id, struct msgb *msg)
+{
+	struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+	struct gsm48_hdr *gh;
+	uint8_t pdisc;
+	uint8_t msg_type;
+	int rc;
+
+	if (msgb_l3len(msg) < sizeof(*gh)) {
+		LOGP(DMSC, LOGL_ERROR, "Message too short for a GSM48 header.\n");
+		return;
+	}
+
+	gh = msgb_l3(msg);
+	pdisc = gsm48_hdr_pdisc(gh);
+	msg_type = gsm48_hdr_msg_type(gh);
+
+	/* the idea is to handle all RR messages here, and only hand
+	 * MM/CC/SMS-CP/LCS up to the MSC.  Some messages like PAGING
+	 * RESPONSE or CM SERVICE REQUEST will not be covered here, as
+	 * they are only possible in the first L3 message of each L2
+	 * channel, i.e. 'conn' will not exist and gsm0408_rcvmsg()
+	 * will call api->compl_l3() for it */
+	switch (pdisc) {
+	case GSM48_PDISC_RR:
+		switch (msg_type) {
+		case GSM48_MT_RR_GPRS_SUSP_REQ:
+			DEBUGP(DRR, "%s\n",
+			       gsm48_rr_msg_name(GSM48_MT_RR_GPRS_SUSP_REQ));
+			break;
+		case GSM48_MT_RR_STATUS:
+			LOGP(DRR, LOGL_NOTICE, "%s (cause: %s)\n",
+			     gsm48_rr_msg_name(GSM48_MT_RR_STATUS),
+			     rr_cause_name(gh->data[0]));
+			break;
+		case GSM48_MT_RR_MEAS_REP:
+			/* This shouldn't actually end up here, as RSL treats
+			* L3 Info of 08.58 MEASUREMENT REPORT different by calling
+			* directly into gsm48_parse_meas_rep */
+			LOGP(DMEAS, LOGL_ERROR, "DIRECT GSM48 MEASUREMENT REPORT ?!? ");
+			break;
+		case GSM48_MT_RR_HANDO_COMPL:
+			handle_rr_ho_compl(msg);
+			break;
+		case GSM48_MT_RR_HANDO_FAIL:
+			handle_rr_ho_fail(msg);
+			break;
+		case GSM48_MT_RR_CIPH_M_COMPL:
+			if (api->cipher_mode_compl)
+				api->cipher_mode_compl(conn, msg,
+						conn->lchan->encr.alg_id);
+			break;
+		case GSM48_MT_RR_ASS_COMPL:
+			handle_ass_compl(conn, msg);
+			break;
+		case GSM48_MT_RR_ASS_FAIL:
+			handle_ass_fail(conn, msg);
+			break;
+		case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
+			osmo_timer_del(&conn->T10);
+			rc = gsm48_rx_rr_modif_ack(msg);
+			if (rc < 0) {
+				api->assign_fail(conn,
+						 GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+						 NULL);
+			} else if (rc >= 0) {
+				api->assign_compl(conn, 0,
+						  lchan_to_chosen_channel(conn->lchan),
+						  conn->lchan->encr.alg_id,
+						  chan_mode_to_speech(conn->lchan));
+			}
+			break;
+		case GSM48_MT_RR_CLSM_CHG:
+			handle_classmark_chg(conn, msg);
+			break;
+		case GSM48_MT_RR_APP_INFO:
+			/* Passing RR APP INFO to MSC, not quite
+			 * according to spec */
+			if (api->dtap)
+				api->dtap(conn, link_id, msg);
+			break;
+		default:
+			/* Normally, a MSC should never receive RR
+			 * messages, but we'd rather forward what we
+			 * don't know than drop it... */
+			LOGP(DRR, LOGL_NOTICE,
+			     "BSC: Passing %s 04.08 RR message to MSC\n",
+			     gsm48_rr_msg_name(msg_type));
+			if (api->dtap)
+				api->dtap(conn, link_id, msg);
+		}
+		break;
+	default:
+		if (api->dtap)
+			api->dtap(conn, link_id, msg);
+		break;
+	}
+}
+
+/*! \brief RSL has received a DATA INDICATION with L3 from MS */
+int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id)
+{
+	int rc;
+	struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+	struct gsm_lchan *lchan;
+
+	lchan = msg->lchan;
+	if (lchan->state != LCHAN_S_ACTIVE) {
+		LOGP(DRSL, LOGL_INFO, "Got data in non active state(%s), "
+		     "discarding.\n", gsm_lchans_name(lchan->state));
+		return -1;
+	}
+
+
+	if (lchan->conn) {
+		/* if we already have a connection, forward via DTAP to
+		 * MSC */
+		dispatch_dtap(lchan->conn, link_id, msg);
+	} else {
+		/* allocate a new connection */
+		rc = BSC_API_CONN_POL_REJECT;
+		lchan->conn = bsc_subscr_con_allocate(msg->lchan);
+		if (!lchan->conn) {
+			lchan_release(lchan, 1, RSL_REL_NORMAL);
+			return -1;
+		}
+
+		/* fwd via bsc_api to send COMPLETE L3 INFO to MSC */
+		rc = api->compl_l3(lchan->conn, msg, 0);
+
+		if (rc != BSC_API_CONN_POL_ACCEPT) {
+			lchan->conn->lchan = NULL;
+			bsc_subscr_con_free(lchan->conn);
+			lchan_release(lchan, 1, RSL_REL_NORMAL);
+		}
+	}
+
+	return 0;
+}
+
+/*! \brief We received a GSM 08.08 CIPHER MODE from the MSC */
+int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
+			const uint8_t *key, int len, int include_imeisv)
+{
+	if (cipher > 0 && key == NULL) {
+		LOGP(DRSL, LOGL_ERROR, "Need to have an encrytpion key.\n");
+		return -1;
+	}
+
+	if (len > MAX_A5_KEY_LEN) {
+		LOGP(DRSL, LOGL_ERROR, "The key is too long: %d\n", len);
+		return -1;
+	}
+
+	conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher);
+	if (key) {
+		conn->lchan->encr.key_len = len;
+		memcpy(conn->lchan->encr.key, key, len);
+	}
+
+	return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv);
+}
+
+/*
+ * Release all occupied RF Channels but stay around for more.
+ */
+int gsm0808_clear(struct gsm_subscriber_connection *conn)
+{
+	if (conn->ho_lchan)
+		bsc_clear_handover(conn, 1);
+
+	if (conn->secondary_lchan)
+		lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+
+	if (conn->lchan)
+		lchan_release(conn->lchan, 1, RSL_REL_NORMAL);
+
+	conn->lchan = NULL;
+	conn->secondary_lchan = NULL;
+	conn->ho_lchan = NULL;
+	conn->bts = NULL;
+
+	osmo_timer_del(&conn->T10);
+
+	return 0;
+}
+
+static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id)
+{
+	struct bsc_api *api;
+
+	if (!conn)
+		return;
+
+	api = conn->network->bsc_api;
+	if (!api || !api->sapi_n_reject)
+		return;
+
+	api->sapi_n_reject(conn, link_id);
+}
+
+static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind)
+{
+	struct msgb *msg = _data;
+
+	/*
+	 * There seems to be a small window that the RLL timer can
+	 * fire after a lchan_release call and before the S_CHALLOC_FREED
+	 * is called. Check if a conn is set before proceeding.
+	 */
+	if (!lchan->conn)
+		return;
+
+	switch (rllr_ind) {
+	case BSC_RLLR_IND_EST_CONF:
+		rsl_data_request(msg, OBSC_LINKID_CB(msg));
+		break;
+	case BSC_RLLR_IND_REL_IND:
+	case BSC_RLLR_IND_ERR_IND:
+	case BSC_RLLR_IND_TIMEOUT:
+		send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg));
+		msgb_free(msg);
+		break;
+	}
+}
+
+static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal,
+				   void *handler_data, void *signal_data)
+{
+	struct bsc_api *bsc;
+	struct gsm_lchan *lchan;
+	struct lchan_signal_data *lchan_data;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+
+	lchan_data = signal_data;
+	if (!lchan_data->lchan || !lchan_data->lchan->conn)
+		return 0;
+
+	lchan = lchan_data->lchan;
+	bsc = lchan->ts->trx->bts->network->bsc_api;
+	if (!bsc)
+		return 0;
+
+	switch (signal) {
+	case S_LCHAN_UNEXPECTED_RELEASE:
+		handle_release(lchan->conn, bsc, lchan);
+		break;
+	case S_LCHAN_ACTIVATE_ACK:
+		handle_chan_ack(lchan->conn, bsc, lchan);
+		break;
+	case S_LCHAN_ACTIVATE_NACK:
+		handle_chan_nack(lchan->conn, bsc, lchan);
+		break;
+	}
+
+	return 0;
+}
+
+static void handle_release(struct gsm_subscriber_connection *conn,
+			   struct bsc_api *bsc, struct gsm_lchan *lchan)
+{
+	int destruct = 1;
+
+	if (conn->secondary_lchan == lchan) {
+		osmo_timer_del(&conn->T10);
+		conn->secondary_lchan = NULL;
+
+		bsc->assign_fail(conn,
+				 GSM0808_CAUSE_RADIO_INTERFACE_FAILURE,
+				 NULL);
+	}
+
+	/* clear the connection now */
+	if (bsc->clear_request)
+		destruct = bsc->clear_request(conn, 0);
+
+	/* now give up all channels */
+	if (conn->lchan == lchan)
+		conn->lchan = NULL;
+	if (conn->ho_lchan == lchan) {
+		bsc_clear_handover(conn, 0);
+		conn->ho_lchan = NULL;
+	}
+	lchan->conn = NULL;
+
+	gsm0808_clear(conn);
+
+	if (destruct)
+		bsc_subscr_con_free(conn);
+}
+
+static void handle_chan_ack(struct gsm_subscriber_connection *conn,
+			    struct bsc_api *api, struct gsm_lchan *lchan)
+{
+	if (conn->secondary_lchan != lchan)
+		return;
+
+	LOGP(DMSC, LOGL_NOTICE, "Sending assignment on chan: %p\n", lchan);
+	gsm48_send_rr_ass_cmd(conn->lchan, lchan, lchan->ms_power);
+}
+
+static void handle_chan_nack(struct gsm_subscriber_connection *conn,
+			     struct bsc_api *api, struct gsm_lchan *lchan)
+{
+	if (conn->secondary_lchan != lchan)
+		return;
+
+	LOGP(DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n");
+	conn->secondary_lchan->conn = NULL;
+	conn->secondary_lchan = NULL;
+}
+
+static __attribute__((constructor)) void on_dso_load_bsc(void)
+{
+	osmo_signal_register_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL);
+}
+
diff --git a/openbsc/src/libbsc/bsc_ctrl_commands.c b/openbsc/src/libbsc/bsc_ctrl_commands.c
new file mode 100644
index 0000000..e6c49d1
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_ctrl_commands.c
@@ -0,0 +1,523 @@
+/*
+ * (C) 2013-2015 by Holger Hans Peter Freyther
+ * (C) 2013-2015 by 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 Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <errno.h>
+#include <time.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/gsm/gsm48.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+
+#define CTRL_CMD_VTY_STRING(cmdname, cmdstr, dtype, element) \
+	CTRL_HELPER_GET_STRING(cmdname, dtype, element) \
+	CTRL_HELPER_SET_STRING(cmdname, dtype, element) \
+static struct ctrl_cmd_element cmd_##cmdname = { \
+	.name = cmdstr, \
+	.get = get_##cmdname, \
+	.set = set_##cmdname, \
+	.verify = verify_vty_description_string, \
+}
+
+/**
+ * Check that there are no newlines or comments or other things
+ * that could make the VTY configuration unparsable.
+ */
+static int verify_vty_description_string(struct ctrl_cmd *cmd,
+			const char *value, void *data)
+{
+	int i;
+	const size_t len = strlen(value);
+
+	for (i = 0; i < len; ++i) {
+		switch(value[i]) {
+		case '#':
+		case '\n':
+		case '\r':
+			cmd->reply = "String includes illegal character";
+			return -1;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+CTRL_CMD_DEFINE(net_mcc, "mcc");
+static int get_net_mcc(struct ctrl_cmd *cmd, void *_data)
+{
+       struct gsm_network *net = cmd->node;
+       cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc));
+       if (!cmd->reply) {
+               cmd->reply = "OOM";
+               return CTRL_CMD_ERROR;
+       }
+       return CTRL_CMD_REPLY;
+}
+static int set_net_mcc(struct ctrl_cmd *cmd, void *_data)
+{
+       struct gsm_network *net = cmd->node;
+       uint16_t mcc;
+       if (osmo_mcc_from_str(cmd->value, &mcc))
+               return -1;
+       net->plmn.mcc = mcc;
+       return get_net_mcc(cmd, _data);
+}
+static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+       if (osmo_mcc_from_str(value, NULL))
+               return -1;
+       return 0;
+}
+
+CTRL_CMD_DEFINE(net_mnc, "mnc");
+static int get_net_mnc(struct ctrl_cmd *cmd, void *_data)
+{
+       struct gsm_network *net = cmd->node;
+       cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits));
+       if (!cmd->reply) {
+               cmd->reply = "OOM";
+               return CTRL_CMD_ERROR;
+       }
+       return CTRL_CMD_REPLY;
+}
+static int set_net_mnc(struct ctrl_cmd *cmd, void *_data)
+{
+       struct gsm_network *net = cmd->node;
+       struct osmo_plmn_id plmn = net->plmn;
+       if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) {
+               cmd->reply = "Error while decoding MNC";
+               return CTRL_CMD_ERROR;
+       }
+       net->plmn = plmn;
+       return get_net_mnc(cmd, _data);
+}
+static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+       if (osmo_mnc_from_str(value, NULL, NULL))
+               return -1;
+       return 0;
+}
+
+CTRL_CMD_VTY_STRING(net_short_name, "short-name", struct gsm_network, name_short);
+CTRL_CMD_VTY_STRING(net_long_name, "long-name", struct gsm_network, name_long);
+
+static int set_net_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (!is_ipaccess_bts(bts))
+			continue;
+
+		/*
+		 * The ip.access nanoBTS seems to be unrelaible on BSSGP
+		 * so let's us just reboot it. For the sysmoBTS we can just
+		 * restart the process as all state is gone.
+		 */
+		if (!is_sysmobts_v2(bts) && strcmp(cmd->value, "restart") == 0) {
+			struct gsm_bts_trx *trx;
+			llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+				abis_nm_ipaccess_restart(trx);
+		} else
+			ipaccess_drop_oml(bts);
+	}
+
+	cmd->reply = "Tried to drop the BTS";
+	return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration");
+
+static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d)
+{
+	char *tmp, *saveptr, *mcc, *mnc;
+	int rc = 0;
+
+	tmp = talloc_strdup(cmd, value);
+	if (!tmp)
+		return 1;
+
+	mcc = strtok_r(tmp, ",", &saveptr);
+	mnc = strtok_r(NULL, ",", &saveptr);
+
+	if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL))
+		rc = -1;
+
+	talloc_free(tmp);
+	return rc;
+}
+
+static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+	char *tmp, *saveptr, *mcc_str, *mnc_str;
+	struct osmo_plmn_id plmn;
+
+	tmp = talloc_strdup(cmd, cmd->value);
+	if (!tmp)
+		goto oom;
+
+	mcc_str = strtok_r(tmp, ",", &saveptr);
+	mnc_str = strtok_r(NULL, ",", &saveptr);
+
+	if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) {
+		cmd->reply = "Error while decoding MCC";
+		talloc_free(tmp);
+		return CTRL_CMD_ERROR;
+	}
+
+	if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) {
+		cmd->reply = "Error while decoding MNC";
+		talloc_free(tmp);
+		return CTRL_CMD_ERROR;
+	}
+
+	talloc_free(tmp);
+
+	if (!osmo_plmn_cmp(&net->plmn, &plmn)) {
+		cmd->reply = "Nothing changed";
+		return CTRL_CMD_REPLY;
+	}
+
+	net->plmn = plmn;
+
+	return set_net_apply_config(cmd, data);
+
+oom:
+	cmd->reply = "OOM";
+	return CTRL_CMD_ERROR;
+}
+CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply");
+
+/* BTS related commands below */
+CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535);
+CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535);
+
+static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_bts *bts = cmd->node;
+
+	if (!is_ipaccess_bts(bts)) {
+		cmd->reply = "BTS is not IP based";
+		return CTRL_CMD_ERROR;
+	}
+
+	ipaccess_drop_oml(bts);
+	cmd->reply = "Tried to drop the BTS";
+	return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration");
+
+static int set_bts_si(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_bts *bts = cmd->node;
+	int rc;
+
+	rc = gsm_bts_set_system_infos(bts);
+	if (rc != 0) {
+		cmd->reply = "Failed to generate SI";
+		return CTRL_CMD_ERROR;
+	}
+
+	cmd->reply = "Generated new System Information";
+	return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations");
+
+static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data)
+{
+	int i;
+	struct pchan_load pl;
+	struct gsm_bts *bts;
+	const char *space = "";
+
+	bts = cmd->node;
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+
+	cmd->reply = talloc_strdup(cmd, "");
+
+	for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) {
+		const struct load_counter *lc = &pl.pchan[i];
+
+		/* These can never have user load */
+		if (i == GSM_PCHAN_NONE)
+			continue;
+		if (i == GSM_PCHAN_CCCH)
+			continue;
+		if (i == GSM_PCHAN_PDCH)
+			continue;
+		if (i == GSM_PCHAN_UNKNOWN)
+			continue;
+
+		cmd->reply = talloc_asprintf_append(cmd->reply,
+					"%s%s,%u,%u",
+					space, gsm_pchan_name(i), lc->used, lc->total);
+		if (!cmd->reply)
+			goto error;
+		space = " ";
+	}
+
+	return CTRL_CMD_REPLY;
+
+error:
+	cmd->reply = "Memory allocation failure";
+	return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load");
+
+static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_bts *bts = cmd->node;
+
+	cmd->reply = bts->oml_link ? "connected" : "disconnected";
+	return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state");
+
+static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+	int valid;
+	enum bts_gprs_mode mode;
+	struct gsm_bts *bts = cmd->node;
+
+	mode = bts_gprs_mode_parse(value, &valid);
+	if (!valid) {
+		cmd->reply = "Mode is not known";
+		return 1;
+	}
+
+	if (!bts_gprs_mode_is_compat(bts, mode)) {
+		cmd->reply = "bts does not support this mode";
+		return 1;
+	}
+
+	return 0;
+}
+
+static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_bts *bts = cmd->node;
+
+	cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode));
+	return CTRL_CMD_REPLY;
+}
+
+static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_bts *bts = cmd->node;
+
+	bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL);
+	return get_bts_gprs_mode(cmd, data);
+}
+
+CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode");
+
+static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data)
+{
+	const char *oper, *admin, *policy;
+	struct gsm_bts *bts = cmd->node;
+
+	if (!bts) {
+		cmd->reply = "bts not found.";
+		return CTRL_CMD_ERROR;
+	}
+
+	oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+	admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+	policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+	cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy);
+	if (!cmd->reply) {
+		cmd->reply = "OOM.";
+		return CTRL_CMD_ERROR;
+	}
+
+	return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state");
+
+static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+	struct gsm_bts *bts;
+	const char *policy_name;
+
+	policy_name = osmo_bsc_rf_get_policy_name(net->bsc_data->rf_ctrl->policy);
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		struct gsm_bts_trx *trx;
+
+		/* Exclude the BTS from the global lock */
+		if (bts->excl_from_rf_lock)
+			continue;
+
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			if (trx->mo.nm_state.availability == NM_AVSTATE_OK &&
+			    trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) {
+				cmd->reply = talloc_asprintf(cmd,
+						"state=on,policy=%s,bts=%u,trx=%u",
+						policy_name, bts->nr, trx->nr);
+				return CTRL_CMD_REPLY;
+			}
+		}
+	}
+
+	cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s",
+			policy_name);
+	return CTRL_CMD_REPLY;
+}
+
+#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z"
+
+static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data)
+{
+	int locked = atoi(cmd->value);
+	struct gsm_network *net = cmd->node;
+	time_t now = time(NULL);
+	char now_buf[64];
+	struct osmo_bsc_rf *rf;
+
+	if (!net) {
+		cmd->reply = "net not found.";
+		return CTRL_CMD_ERROR;
+	}
+
+	rf = net->bsc_data->rf_ctrl;
+
+	if (!rf) {
+		cmd->reply = "RF Ctrl is not enabled in the BSC Configuration";
+		return CTRL_CMD_ERROR;
+	}
+
+	talloc_free(rf->last_rf_lock_ctrl_command);
+	strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now));
+	rf->last_rf_lock_ctrl_command =
+		talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf);
+
+	osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1');
+
+	cmd->reply = talloc_asprintf(cmd, "%u", locked);
+	if (!cmd->reply) {
+		cmd->reply = "OOM.";
+		return CTRL_CMD_ERROR;
+	}
+
+	return CTRL_CMD_REPLY;
+}
+
+static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	int locked = atoi(cmd->value);
+
+	if ((locked != 0) && (locked != 1))
+		return 1;
+
+	return 0;
+}
+CTRL_CMD_DEFINE(net_rf_lock, "rf_locked");
+
+static int get_net_bts_num(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+
+	cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts);
+	return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts");
+
+/* TRX related commands below here */
+CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red);
+static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+	int tmp = atoi(value);
+
+	if (tmp < 0 || tmp > 22) {
+		cmd->reply = "Value must be between 0 and 22";
+		return -1;
+	}
+
+	if (tmp & 1) {
+		cmd->reply = "Value must be even";
+		return -1;
+	}
+
+	return 0;
+}
+CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023);
+
+static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data)
+{
+	struct gsm_bts_trx *trx = cmd->node;
+	int old_power;
+
+	/* remember the old value, set the new one */
+	old_power = trx->max_power_red;
+	trx->max_power_red = atoi(cmd->value);
+
+	/* Maybe update the value */
+	if (old_power != trx->max_power_red) {
+		LOGP(DCTRL, LOGL_NOTICE,
+			"%s updating max_pwr_red(%d)\n",
+			gsm_trx_name(trx), trx->max_power_red);
+		abis_nm_update_max_power_red(trx);
+	}
+
+	return get_trx_max_power(cmd, _data);
+}
+CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction");
+
+int bsc_base_ctrl_cmds_install(void)
+{
+	int rc = 0;
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_short_name);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_long_name);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num);
+
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_lac);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ci);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_apply_config);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_chan_load);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_conn);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_gprs_mode);
+	rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state);
+
+	rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_max_power);
+	rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_arfcn);
+
+	return rc;
+}
diff --git a/openbsc/src/libbsc/bsc_ctrl_lookup.c b/openbsc/src/libbsc/bsc_ctrl_lookup.c
new file mode 100644
index 0000000..a8a8cf5
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_ctrl_lookup.c
@@ -0,0 +1,107 @@
+/* SNMP-like status interface. Look-up of BTS/TRX
+ *
+ * (C) 2010-2011 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2010-2011 by On-Waves
+ *
+ * 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 2 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.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+
+extern vector ctrl_node_vec;
+
+/*! \brief control interface lookup function for bsc/bts gsm_data
+ * \param[in] data Private data passed to controlif_setup()
+ * \param[in] vline Vector of the line holding the command string
+ * \param[out] node_type type (CTRL_NODE_) that was determined
+ * \param[out] node_data private dta of node that was determined
+ * \param i Current index into vline, up to which it is parsed
+ */
+static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type,
+				void **node_data, int *i)
+{
+	struct gsm_network *net = data;
+	struct gsm_bts *bts = NULL;
+	struct gsm_bts_trx *trx = NULL;
+	struct gsm_bts_trx_ts *ts = NULL;
+	char *token = vector_slot(vline, *i);
+	long num;
+
+	/* TODO: We need to make sure that the following chars are digits
+	 * and/or use strtol to check if number conversion was successful
+	 * Right now something like net.bts_stats will not work */
+	if (!strcmp(token, "bts")) {
+		if (*node_type != CTRL_NODE_ROOT || !net)
+			goto err_missing;
+		(*i)++;
+		if (!ctrl_parse_get_num(vline, *i, &num))
+			goto err_index;
+
+		bts = gsm_bts_num(net, num);
+		if (!bts)
+			goto err_missing;
+		*node_data = bts;
+		*node_type = CTRL_NODE_BTS;
+	} else if (!strcmp(token, "trx")) {
+		if (*node_type != CTRL_NODE_BTS || !*node_data)
+			goto err_missing;
+		bts = *node_data;
+		(*i)++;
+		if (!ctrl_parse_get_num(vline, *i, &num))
+			goto err_index;
+
+		trx = gsm_bts_trx_num(bts, num);
+		if (!trx)
+			goto err_missing;
+		*node_data = trx;
+		*node_type = CTRL_NODE_TRX;
+	} else if (!strcmp(token, "ts")) {
+		if (*node_type != CTRL_NODE_TRX || !*node_data)
+			goto err_missing;
+		trx = *node_data;
+		(*i)++;
+		if (!ctrl_parse_get_num(vline, *i, &num))
+			goto err_index;
+
+		if ((num >= 0) && (num < TRX_NR_TS))
+			ts = &trx->ts[num];
+		if (!ts)
+			goto err_missing;
+		*node_data = ts;
+		*node_type = CTRL_NODE_TS;
+	} else
+		return 0;
+
+	return 1;
+err_missing:
+	return -ENODEV;
+err_index:
+	return -ERANGE;
+}
+
+struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net,
+					const char *bind_addr, uint16_t port)
+{
+	return ctrl_interface_setup_dynip(net, bind_addr, port,
+					  bsc_ctrl_node_lookup);
+}
diff --git a/openbsc/src/libbsc/bsc_dyn_ts.c b/openbsc/src/libbsc/bsc_dyn_ts.c
new file mode 100644
index 0000000..e5422fc
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_dyn_ts.c
@@ -0,0 +1,77 @@
+/* Dynamic PDCH initialisation implementation shared across NM and RSL */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+
+void tchf_pdch_ts_init(struct gsm_bts_trx_ts *ts)
+{
+	int rc;
+
+	if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) {
+		LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none':"
+		     " not activating PDCH.\n",
+		     gsm_ts_and_pchan_name(ts));
+		return;
+	}
+
+	LOGP(DRSL, LOGL_DEBUG, "%s: trying to PDCH ACT\n",
+	     gsm_ts_and_pchan_name(ts));
+
+	rc = rsl_ipacc_pdch_activate(ts, 1);
+	if (rc != 0)
+		LOGP(DRSL, LOGL_ERROR, "%s %s: PDCH ACT failed\n",
+		     gsm_ts_name(ts), gsm_pchan_name(ts->pchan));
+}
+
+void tchf_tchh_pdch_ts_init(struct gsm_bts_trx_ts *ts)
+{
+	if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) {
+		LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none':"
+		     " not activating PDCH.\n",
+		     gsm_ts_and_pchan_name(ts));
+		return;
+	}
+
+	dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH);
+}
+
+void dyn_ts_init(struct gsm_bts_trx_ts *ts)
+{
+	/* Clear all TCH/F_PDCH flags */
+	ts->flags &= ~(TS_F_PDCH_PENDING_MASK | TS_F_PDCH_ACTIVE);
+
+	/* Clear TCH/F_TCH/H_PDCH state */
+	ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+	ts->dyn.pending_chan_activ = NULL;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F_PDCH:
+		tchf_pdch_ts_init(ts);
+		break;
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		tchf_tchh_pdch_ts_init(ts);
+		break;
+	default:
+		break;
+	}
+}
diff --git a/openbsc/src/libbsc/bsc_init.c b/openbsc/src/libbsc/bsc_init.c
new file mode 100644
index 0000000..218b02a
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_init.c
@@ -0,0 +1,578 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+#include <openbsc/misdn.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/ports.h>
+#include <openbsc/system_information.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/chan_alloc.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/ipaccess.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <openbsc/e1_config.h>
+#include <openbsc/common_bsc.h>
+#include <openbsc/pcu_if.h>
+#include <limits.h>
+
+/* global pointer to the gsm network data structure */
+extern struct gsm_network *bsc_gsmnet;
+
+/* Callback function for NACK on the OML NM */
+static int oml_msg_nack(struct nm_nack_signal_data *nack)
+{
+	if (nack->mt == NM_MT_GET_ATTR_NACK) {
+		LOGP(DNM, LOGL_ERROR, "BTS%u does not support Get Attributes "
+		     "OML message.\n", nack->bts->nr);
+		return 0;
+	}
+
+	if (nack->mt == NM_MT_SET_BTS_ATTR_NACK)
+		LOGP(DNM, LOGL_ERROR, "Failed to set BTS attributes. That is fatal. "
+		     "Was the bts type and frequency properly specified?\n");
+	else
+		LOGP(DNM, LOGL_ERROR, "Got %s NACK going to drop the OML links.\n",
+		     abis_nm_nack_name(nack->mt));
+
+	if (!nack->bts) {
+		LOGP(DNM, LOGL_ERROR, "Unknown bts. Can not drop it.\n");
+		return 0;
+	}
+
+	if (is_ipaccess_bts(nack->bts))
+		ipaccess_drop_oml_deferred(nack->bts);
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	struct nm_nack_signal_data *nack;
+
+	switch (signal) {
+	case S_NM_NACK:
+		nack = signal_data;
+		return oml_msg_nack(nack);
+	default:
+		break;
+	}
+	return 0;
+}
+
+int bsc_shutdown_net(struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr);
+		osmo_signal_dispatch(SS_L_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts);
+	}
+
+	return 0;
+}
+
+static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len)
+{
+	struct gsm_bts *bts = trx->bts;
+	int rc, j;
+
+	if (si_len) {
+		DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i),
+			osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN));
+	} else
+		DEBUGP(DRR, "SI%s: OFF\n", get_value_string(osmo_sitype_strs, i));
+
+	switch (i) {
+	case SYSINFO_TYPE_5:
+	case SYSINFO_TYPE_5bis:
+	case SYSINFO_TYPE_5ter:
+	case SYSINFO_TYPE_6:
+		rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i),
+				       si_len ? GSM_BTS_SI(bts, i) : NULL, si_len);
+		break;
+	case SYSINFO_TYPE_2quater:
+		if (si_len == 0) {
+			rc = rsl_bcch_info(trx, i, NULL, 0);
+			break;
+		}
+		for (j = 0; j <= bts->si2q_count; j++)
+			rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN);
+		break;
+	default:
+		rc = rsl_bcch_info(trx, i, si_len ? GSM_BTS_SI(bts, i) : NULL, si_len);
+		break;
+	}
+
+	return rc;
+}
+
+/* set all system information types for a TRX */
+int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
+{
+	int i, rc;
+	struct gsm_bts *bts = trx->bts;
+	uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n;
+	int si_len[_MAX_SYSINFO_TYPE];
+
+	bts->si_common.cell_sel_par.ms_txpwr_max_ccch =
+			ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+	bts->si_common.cell_sel_par.neci = bts->network->neci;
+
+	/* Zero/forget the state of the dynamically computed SIs, leeping the static ones */
+	bts->si_valid = bts->si_mode_static;
+
+	/* First, we determine which of the SI messages we actually need */
+
+	if (trx == bts->c0) {
+		/* 1...4 are always present on a C0 TRX */
+		gen_si[n_si++] = SYSINFO_TYPE_1;
+		gen_si[n_si++] = SYSINFO_TYPE_2;
+		gen_si[n_si++] = SYSINFO_TYPE_2bis;
+		gen_si[n_si++] = SYSINFO_TYPE_2ter;
+		gen_si[n_si++] = SYSINFO_TYPE_2quater;
+		gen_si[n_si++] = SYSINFO_TYPE_3;
+		gen_si[n_si++] = SYSINFO_TYPE_4;
+
+		/* 13 is always present on a C0 TRX of a GPRS BTS */
+		if (bts->gprs.mode != BTS_GPRS_NONE)
+			gen_si[n_si++] = SYSINFO_TYPE_13;
+	}
+
+	/* 5 and 6 are always present on every TRX */
+	gen_si[n_si++] = SYSINFO_TYPE_5;
+	gen_si[n_si++] = SYSINFO_TYPE_5bis;
+	gen_si[n_si++] = SYSINFO_TYPE_5ter;
+	gen_si[n_si++] = SYSINFO_TYPE_6;
+
+	/* Second, we generate the selected SI via RSL */
+
+	for (n = 0; n < n_si; n++) {
+		i = gen_si[n];
+		/* Only generate SI if this SI is not in "static" (user-defined) mode */
+		if (!(bts->si_mode_static & (1 << i))) {
+			/* Set SI as being valid. gsm_generate_si() might unset
+			 * it, if SI is not required. */
+			bts->si_valid |= (1 << i);
+			rc = gsm_generate_si(bts, i);
+			if (rc < 0)
+				goto err_out;
+			si_len[i] = rc;
+		} else {
+			if (i == SYSINFO_TYPE_5 || i == SYSINFO_TYPE_5bis
+			 || i == SYSINFO_TYPE_5ter)
+				si_len[i] = 18;
+			else if (i == SYSINFO_TYPE_6)
+				si_len[i] = 11;
+			else
+				si_len[i] = 23;
+		}
+	}
+
+	/* Third, we send the selected SI via RSL */
+
+	for (n = 0; n < n_si; n++) {
+		i = gen_si[n];
+		/* if we don't currently have this SI, we send a zero-length
+		 * RSL BCCH FILLING / SACCH FILLING * in order to deactivate
+		 * the SI, in case it might have previously been active */
+		if (!GSM_BTS_HAS_SI(bts, i))
+			rc = rsl_si(trx, i, 0);
+		else
+			rc = rsl_si(trx, i, si_len[i]);
+		if (rc < 0)
+			return rc;
+	}
+
+	/* Make sure the PCU is aware (in case anything GPRS related has
+	 * changed in SI */
+	pcu_info_update(bts);
+
+	return 0;
+err_out:
+	LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>, "
+	     "most likely a problem with neighbor cell list generation\n",
+	     get_value_string(osmo_sitype_strs, i), bts->nr, strerror(-rc));
+	return rc;
+}
+
+/* set all system information types for a BTS */
+int gsm_bts_set_system_infos(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	/* Generate a new ID */
+	bts->bcch_change_mark += 1;
+	bts->bcch_change_mark %= 0x7;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int rc;
+
+		rc = gsm_bts_trx_set_system_infos(trx);
+		if (rc != 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+/* Produce a MA as specified in 10.5.2.21 */
+static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
+{
+	/* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs
+	 * and the MA */
+	struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc;
+	struct bitvec *ts_arfcn = &ts->hopping.arfcns;
+	struct bitvec *ma = &ts->hopping.ma;
+	unsigned int num_cell_arfcns, bitnum, n_chan;
+	int i;
+
+	/* re-set the MA to all-zero */
+	ma->cur_bit = 0;
+	ts->hopping.ma_len = 0;
+	memset(ma->data, 0, ma->data_len);
+
+	if (!ts->hopping.enabled)
+		return 0;
+
+	/* count the number of ARFCNs in the cell channel allocation */
+	num_cell_arfcns = 0;
+	for (i = 0; i < 1024; i++) {
+		if (bitvec_get_bit_pos(cell_chan, i))
+			num_cell_arfcns++;
+	}
+
+	/* pad it to octet-aligned number of bits */
+	ts->hopping.ma_len = num_cell_arfcns / 8;
+	if (num_cell_arfcns % 8)
+		ts->hopping.ma_len++;
+
+	n_chan = 0;
+	for (i = 0; i < 1024; i++) {
+		if (!bitvec_get_bit_pos(cell_chan, i))
+			continue;
+		/* set the corresponding bit in the MA */
+		bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+		if (bitvec_get_bit_pos(ts_arfcn, i))
+			bitvec_set_bit_pos(ma, bitnum, 1);
+		else
+			bitvec_set_bit_pos(ma, bitnum, 0);
+		n_chan++;
+	}
+
+	/* ARFCN 0 is special: It is coded last in the bitmask */
+	if (bitvec_get_bit_pos(cell_chan, 0)) {
+		n_chan++;
+		/* set the corresponding bit in the MA */
+		bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+		if (bitvec_get_bit_pos(ts_arfcn, 0))
+			bitvec_set_bit_pos(ma, bitnum, 1);
+		else
+			bitvec_set_bit_pos(ma, bitnum, 0);
+	}
+
+	return 0;
+}
+
+static void bootstrap_rsl(struct gsm_bts_trx *trx)
+{
+	unsigned int i;
+
+	LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) "
+		"on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n",
+		trx->bts->nr, trx->nr, trx->arfcn,
+		osmo_plmn_name(&bsc_gsmnet->plmn),
+		trx->bts->location_area_code,
+		trx->bts->cell_identity, trx->bts->bsic);
+
+	if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
+		rsl_nokia_si_begin(trx);
+	}
+
+	/*
+	 * Trigger ACC ramping before sending system information to BTS.
+	 * This ensures that RACH control in system information is configured correctly.
+	 * TRX 0 should be usable and unlocked, otherwise starting ACC ramping is pointless.
+	 */
+	if (trx_is_usable(trx) && trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+		acc_ramp_trigger(&trx->bts->acc_ramp);
+
+	gsm_bts_trx_set_system_infos(trx);
+
+	if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
+		/* channel unspecific, power reduction in 2 dB steps */
+		rsl_bs_power_control(trx, 0xFF, trx->max_power_red / 2);
+		rsl_nokia_si_end(trx);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+		generate_ma_for_ts(&trx->ts[i]);
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+	struct gsm_bts_trx *trx = isd->trx;
+	int ts_no, lchan_no;
+	/* N. B: we rely on attribute order when parsing response in abis_nm_rx_get_attr_resp() */
+	const uint8_t bts_attr[] = { NM_ATT_MANUF_ID, NM_ATT_SW_CONFIG, };
+	const uint8_t trx_attr[] = { NM_ATT_MANUF_STATE, NM_ATT_SW_CONFIG, };
+
+	/* we should not request more attributes than we're ready to handle */
+	OSMO_ASSERT(sizeof(bts_attr) < MAX_BTS_ATTR);
+	OSMO_ASSERT(sizeof(trx_attr) < MAX_BTS_ATTR);
+
+	if (subsys != SS_L_INPUT)
+		return -EINVAL;
+
+	LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__,
+		get_value_string(e1inp_signal_names, signal));
+	switch (signal) {
+	case S_L_INP_TEI_UP:
+		if (isd->link_type == E1INP_SIGN_OML) {
+			/* TODO: this is required for the Nokia BTS, hopping is configured
+			   during OML, other MA is not set.  */
+			struct gsm_bts_trx *cur_trx;
+			/* was static in system_information.c */
+			extern int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts);
+			uint8_t ca[20];
+			/* has to be called before generate_ma_for_ts to
+			  set bts->si_common.cell_alloc */
+			generate_cell_chan_list(ca, trx->bts);
+
+			/* Request generic BTS-level attributes */
+			abis_nm_get_attr(trx->bts, NM_OC_BTS, 0xFF, 0xFF, 0xFF, bts_attr, sizeof(bts_attr));
+
+			llist_for_each_entry(cur_trx, &trx->bts->trx_list, list) {
+				int i;
+				/* Request TRX-level attributes */
+				abis_nm_get_attr(cur_trx->bts, NM_OC_BASEB_TRANSC, 0, cur_trx->nr, 0xFF,
+						 trx_attr, sizeof(trx_attr));
+				for (i = 0; i < ARRAY_SIZE(cur_trx->ts); i++)
+					generate_ma_for_ts(&cur_trx->ts[i]);
+			}
+		}
+		if (isd->link_type == E1INP_SIGN_RSL)
+			bootstrap_rsl(trx);
+		break;
+	case S_L_INP_TEI_DN:
+		LOGP(DLMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx);
+
+		if (isd->link_type == E1INP_SIGN_OML)
+			rate_ctr_inc(&trx->bts->network->bsc_ctrs->ctr[BSC_CTR_BTS_OML_FAIL]);
+		else if (isd->link_type == E1INP_SIGN_RSL) {
+			rate_ctr_inc(&trx->bts->network->bsc_ctrs->ctr[BSC_CTR_BTS_RSL_FAIL]);
+			acc_ramp_abort(&trx->bts->acc_ramp);
+		}
+
+		/*
+		 * free all allocated channels. change the nm_state so the
+		 * trx and trx_ts becomes unusable and chan_alloc.c can not
+		 * allocate from it.
+		 */
+		for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[ts_no];
+
+			for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) {
+				if (ts->lchan[lchan_no].state != LCHAN_S_NONE)
+					lchan_free(&ts->lchan[lchan_no]);
+				lchan_reset(&ts->lchan[lchan_no]);
+			}
+		}
+
+		gsm_bts_mo_reset(trx->bts);
+
+		abis_nm_clear_queue(trx->bts);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int bootstrap_bts(struct gsm_bts *bts)
+{
+	int i, n;
+
+	if (!bts->model)
+		return -EFAULT;
+
+	if (bts->model->start && !bts->model->started) {
+		int ret = bts->model->start(bts->network);
+		if (ret < 0)
+			return ret;
+
+		bts->model->started = true;
+	}
+
+	/* FIXME: What about secondary TRX of a BTS?  What about a BTS that has TRX
+	 * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */
+	switch (bts->band) {
+	case GSM_BAND_1800:
+		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
+			LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_1900:
+		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) {
+			LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_900:
+		if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
+		    bts->c0->arfcn > 1023)  {
+			LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 0-124, 955-1023.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_850:
+		if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) {
+			LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n");
+			return -EINVAL;
+		}
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n");
+		return -EINVAL;
+	}
+
+	if (bts->network->auth_policy == GSM_AUTH_POLICY_ACCEPT_ALL &&
+	    !bts->si_common.rach_control.cell_bar)
+		LOGP(DNM, LOGL_ERROR, "\nWARNING: You are running an 'accept-all' "
+			"network on a BTS that is not barred.  This "
+			"configuration is likely to interfere with production "
+			"GSM networks and should only be used in a RF "
+			"shielded environment such as a faraday cage!\n\n");
+
+	/* Control Channel Description is set from vty/config */
+
+	/* T3212 is set from vty/config */
+
+	/* Set ccch config by looking at ts config */
+	for (n=0, i=0; i<8; i++)
+		n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0;
+
+	switch (n) {
+	case 0:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
+		/* Limit reserved block to 2 on combined channel according to
+		   3GPP TS 44.018 Table 10.5.2.11.1 */
+		if (bts->si_common.chan_desc.bs_ag_blks_res > 2) {
+			LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, "
+			     "reducing BS-AG-BLKS-RES value %d -> 2\n",
+			     bts->si_common.chan_desc.bs_ag_blks_res);
+			bts->si_common.chan_desc.bs_ag_blks_res = 2;
+		}
+		break;
+	case 1:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC;
+		break;
+	case 2:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC;
+		break;
+	case 3:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC;
+		break;
+	case 4:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC;
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n");
+		return -EINVAL;
+	}
+
+	bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
+
+	bts->si_common.cell_sel_par.acs = 0;
+
+	bts->si_common.ncc_permitted = 0xff;
+
+	bts->chan_load_samples_idx = 0;
+
+	/* ACC ramping is initialized from vty/config */
+
+	/* Initialize the BTS state */
+	gsm_bts_mo_reset(bts);
+
+	return 0;
+}
+
+int bsc_network_alloc(mncc_recv_cb_t mncc_recv)
+{
+	/* initialize our data structures */
+	bsc_gsmnet = bsc_network_init(tall_bsc_ctx, 1, 1, mncc_recv);
+	if (!bsc_gsmnet)
+		return -ENOMEM;
+
+	bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC");
+	bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC");
+
+	return 0;
+}
+
+int bsc_network_configure(const char *config_file)
+{
+	struct gsm_bts *bts;
+	int rc;
+
+	rc = vty_read_config_file(config_file, NULL);
+	if (rc < 0) {
+		LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	/* start telnet after reading config for vty_get_bind_addr() */
+	rc = telnet_init_dynif(tall_bsc_ctx, bsc_gsmnet, vty_get_bind_addr(),
+			       OSMO_VTY_PORT_NITB_BSC);
+	if (rc < 0)
+		return rc;
+
+	osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+	osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
+
+	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+		rc = bootstrap_bts(bts);
+		if (rc < 0) {
+			LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n");
+			return rc;
+		}
+		rc = e1_reconfig_bts(bts);
+		if (rc < 0) {
+			LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n");
+			return rc;
+		}
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/libbsc/bsc_msc.c b/openbsc/src/libbsc/bsc_msc.c
new file mode 100644
index 0000000..82a572d
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_msc.c
@@ -0,0 +1,320 @@
+/* Routines to talk to the MSC using the IPA Protocol */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_msc.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/ipaccess.h>
+
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static void connection_loss(struct bsc_msc_connection *con)
+{
+	struct osmo_fd *fd;
+
+	fd = &con->write_queue.bfd;
+
+	if (con->pending_msg) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "MSC(%s) dropping incomplete message.\n", con->name);
+		msgb_free(con->pending_msg);
+		con->pending_msg = NULL;
+	}
+
+	close(fd->fd);
+	fd->fd = -1;
+	fd->cb = osmo_wqueue_bfd_cb;
+	fd->when = 0;
+
+	con->is_connected = 0;
+	con->first_contact = 0;
+	con->connection_loss(con);
+}
+
+static void msc_con_timeout(void *_con)
+{
+	struct bsc_msc_connection *con = _con;
+
+	LOGP(DMSC, LOGL_ERROR,
+		"MSC(%s) Connection timeout.\n", con->name);
+	bsc_msc_lost(con);
+}
+
+/* called in the case of a non blocking connect */
+static int msc_connection_connect(struct osmo_fd *fd, unsigned int what)
+{
+	int rc;
+	int val;
+	struct bsc_msc_connection *con;
+	struct osmo_wqueue *queue;
+
+	socklen_t len = sizeof(val);
+
+	queue = container_of(fd, struct osmo_wqueue, bfd);
+	con = container_of(queue, struct bsc_msc_connection, write_queue);
+
+	if ((what & BSC_FD_WRITE) == 0) {
+		LOGP(DMSC, LOGL_ERROR,
+			"MSC(%s) Callback but not writable.\n", con->name);
+		return -1;
+	}
+
+	/* From here on we will either be connected or reconnect */
+	osmo_timer_del(&con->timeout_timer);
+
+	/* check the socket state */
+	rc = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &val, &len);
+	if (rc != 0) {
+		LOGP(DMSC, LOGL_ERROR,
+			"getsockopt for the MSC(%s) socket failed.\n", con->name);
+		goto error;
+	}
+	if (val != 0) {
+		LOGP(DMSC, LOGL_ERROR,
+			"Not connected to the MSC(%s): %d\n",
+			con->name, val);
+		goto error;
+	}
+
+
+	/* go to full operation */
+	fd->cb = osmo_wqueue_bfd_cb;
+	fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+
+	con->is_connected = 1;
+	LOGP(DMSC, LOGL_NOTICE,
+		"(Re)Connected to the MSC(%s).\n", con->name);
+	if (con->connected)
+		con->connected(con);
+	return 0;
+
+error:
+	osmo_fd_unregister(fd);
+	connection_loss(con);
+	return -1;
+}
+static void setnonblocking(struct osmo_fd *fd)
+{
+	int flags;
+
+	flags = fcntl(fd->fd, F_GETFL);
+	if (flags < 0) {
+		perror("fcntl get failed");
+		close(fd->fd);
+		fd->fd = -1;
+		return;
+	}
+
+	flags |= O_NONBLOCK;
+	flags = fcntl(fd->fd, F_SETFL, flags);
+	if (flags < 0) {
+		perror("fcntl get failed");
+		close(fd->fd);
+		fd->fd = -1;
+		return;
+	}
+}
+
+int bsc_msc_connect(struct bsc_msc_connection *con)
+{
+	struct bsc_msc_dest *dest;
+	struct osmo_fd *fd;
+	struct sockaddr_in sin;
+	int on = 1, ret;
+
+	if (llist_empty(con->dests)) {
+		LOGP(DMSC, LOGL_ERROR,
+			"No MSC(%s) connections configured.\n",
+			con->name);
+		connection_loss(con);
+		return -1;
+	}
+
+	/* TODO: Why are we not using the libosmocore soecket
+	 * abstraction, or libosmo-netif? */
+
+	/* move to the next connection */
+	dest = (struct bsc_msc_dest *) con->dests->next;
+	llist_del(&dest->list);
+	llist_add_tail(&dest->list, con->dests);
+
+	LOGP(DMSC, LOGL_NOTICE,
+		"Attempting to connect MSC(%s) at %s:%d\n",
+		con->name, dest->ip, dest->port);
+
+	con->is_connected = 0;
+
+	msgb_free(con->pending_msg);
+	con->pending_msg = NULL;
+
+	fd = &con->write_queue.bfd;
+	fd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	fd->priv_nr = 1;
+
+	if (fd->fd < 0) {
+		perror("Creating TCP socket failed");
+		return fd->fd;
+	}
+
+	/* make it non blocking */
+	setnonblocking(fd);
+
+	/* set the socket priority */
+	ret = setsockopt(fd->fd, IPPROTO_IP, IP_TOS,
+			 &dest->dscp, sizeof(dest->dscp));
+	if (ret != 0)
+		LOGP(DMSC, LOGL_ERROR,
+			"Failed to set DSCP to %d on MSC(%s). %s\n",
+			dest->dscp, con->name, strerror(errno));
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(dest->port);
+	inet_aton(dest->ip, &sin.sin_addr);
+
+	ret = setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	if (ret != 0)
+		LOGP(DMSC, LOGL_ERROR,
+		     "Failed to set SO_REUSEADDR socket option\n");
+	ret = connect(fd->fd, (struct sockaddr *) &sin, sizeof(sin));
+
+	if (ret == -1 && errno == EINPROGRESS) {
+		LOGP(DMSC, LOGL_ERROR,
+			"MSC(%s) Connection in progress\n", con->name);
+		fd->when = BSC_FD_WRITE;
+		fd->cb = msc_connection_connect;
+		osmo_timer_setup(&con->timeout_timer, msc_con_timeout, con);
+		osmo_timer_schedule(&con->timeout_timer, 20, 0);
+	} else if (ret < 0) {
+		perror("Connection failed");
+		connection_loss(con);
+		return ret;
+	} else {
+		fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+		fd->cb = osmo_wqueue_bfd_cb;
+		con->is_connected = 1;
+		if (con->connected)
+			con->connected(con);
+	}
+
+	ret = osmo_fd_register(fd);
+	if (ret < 0) {
+		perror("Registering the fd failed");
+		close(fd->fd);
+		return ret;
+	}
+
+	return ret;
+}
+
+struct bsc_msc_connection *bsc_msc_create(void *ctx, struct llist_head *dests)
+{
+	struct bsc_msc_connection *con;
+
+	con = talloc_zero(NULL, struct bsc_msc_connection);
+	if (!con) {
+		LOGP(DMSC, LOGL_FATAL, "Failed to create the MSC connection.\n");
+		return NULL;
+	}
+
+	con->dests = dests;
+	con->write_queue.bfd.fd = -1;
+	con->name = "";
+	osmo_wqueue_init(&con->write_queue, 100);
+	return con;
+}
+
+void bsc_msc_lost(struct bsc_msc_connection *con)
+{
+	osmo_wqueue_clear(&con->write_queue);
+	osmo_timer_del(&con->timeout_timer);
+	osmo_timer_del(&con->reconnect_timer);
+
+	if (con->write_queue.bfd.fd >= 0)
+		osmo_fd_unregister(&con->write_queue.bfd);
+	connection_loss(con);
+}
+
+static void reconnect_msc(void *_msc)
+{
+	struct bsc_msc_connection *con = _msc;
+
+	LOGP(DMSC, LOGL_NOTICE,
+		"Attempting to reconnect to the MSC(%s).\n", con->name);
+	bsc_msc_connect(con);
+}
+
+void bsc_msc_schedule_connect(struct bsc_msc_connection *con)
+{
+	LOGP(DMSC, LOGL_NOTICE,
+		"Attempting to reconnect to the MSC(%s)\n", con->name);
+	osmo_timer_setup(&con->reconnect_timer, reconnect_msc, con);
+	osmo_timer_schedule(&con->reconnect_timer, 5, 0);
+}
+
+struct msgb *bsc_msc_id_get_resp(int fixed, const char *token, const uint8_t *res, int len)
+{
+	struct msgb *msg;
+
+	if (!token) {
+		LOGP(DMSC, LOGL_ERROR, "No token specified.\n");
+		return NULL;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "id resp");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create the message.\n");
+		return NULL;
+	}
+
+	/*
+	 * The situation is bizarre. The encoding doesn't follow the
+	 * TLV structure. It is more like a LV and old versions had
+	 * it wrong but we want new versions to old servers so we
+	 * introduce the quirk here.
+	 */
+	msg->l2h = msgb_v_put(msg, IPAC_MSGT_ID_RESP);
+	if (fixed) {
+		msgb_put_u8(msg, 0);
+		msgb_put_u8(msg, strlen(token) + 2);
+		msgb_tv_fixed_put(msg, IPAC_IDTAG_UNITNAME, strlen(token) + 1, (uint8_t *) token);
+		if (len > 0) {
+			msgb_put_u8(msg, 0);
+			msgb_put_u8(msg, len + 1);
+			msgb_tv_fixed_put(msg, 0x24, len, res);
+		}
+	} else {
+		msgb_l16tv_put(msg, strlen(token) + 1,
+			IPAC_IDTAG_UNITNAME, (uint8_t *) token);
+	}
+
+	return msg;
+}
diff --git a/openbsc/src/libbsc/bsc_rf_ctrl.c b/openbsc/src/libbsc/bsc_rf_ctrl.c
new file mode 100644
index 0000000..0e28600
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_rf_ctrl.c
@@ -0,0 +1,534 @@
+/* RF Ctl handling socket */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/ipaccess.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#define RF_CMD_QUERY '?'
+#define RF_CMD_OFF   '0'
+#define RF_CMD_ON    '1'
+#define RF_CMD_D_OFF 'd'
+#define RF_CMD_ON_G  'g'
+
+static const struct value_string opstate_names[] = {
+	{ OSMO_BSC_RF_OPSTATE_INOPERATIONAL, "inoperational" },
+	{ OSMO_BSC_RF_OPSTATE_OPERATIONAL, "operational" },
+	{ 0, NULL }
+};
+
+static const struct value_string adminstate_names[] = {
+	{ OSMO_BSC_RF_ADMINSTATE_UNLOCKED, "unlocked" },
+	{ OSMO_BSC_RF_ADMINSTATE_LOCKED, "locked" },
+	{ 0, NULL }
+};
+
+static const struct value_string policy_names[] = {
+	{ OSMO_BSC_RF_POLICY_OFF, "off" },
+	{ OSMO_BSC_RF_POLICY_ON, "on" },
+	{ OSMO_BSC_RF_POLICY_GRACE, "grace" },
+	{ OSMO_BSC_RF_POLICY_UNKNOWN, "unknown" },
+	{ 0, NULL }
+};
+
+const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate)
+{
+	return get_value_string(opstate_names, opstate);
+}
+
+const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate)
+{
+	return get_value_string(adminstate_names, adminstate);
+}
+
+const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy)
+{
+	return get_value_string(policy_names, policy);
+}
+
+enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED)
+			return OSMO_BSC_RF_OPSTATE_OPERATIONAL;
+	}
+
+	/* No trx were active, so this bts is disabled */
+	return OSMO_BSC_RF_OPSTATE_INOPERATIONAL;
+}
+
+enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+			return OSMO_BSC_RF_ADMINSTATE_UNLOCKED;
+	}
+
+	/* All trx administrative states were locked */
+	return OSMO_BSC_RF_ADMINSTATE_LOCKED;
+}
+
+enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts)
+{
+	struct osmo_bsc_data *bsc_data = bts->network->bsc_data;
+
+	if (!bsc_data)
+		return OSMO_BSC_RF_POLICY_UNKNOWN;
+
+	switch (bsc_data->rf_ctrl->policy) {
+	case S_RF_ON:
+		return OSMO_BSC_RF_POLICY_ON;
+	case S_RF_OFF:
+		return OSMO_BSC_RF_POLICY_OFF;
+	case S_RF_GRACE:
+		return OSMO_BSC_RF_POLICY_GRACE;
+	default:
+		return OSMO_BSC_RF_POLICY_UNKNOWN;
+	}
+}
+
+static int lock_each_trx(struct gsm_network *net, bool lock)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		struct gsm_bts_trx *trx;
+
+		/* Exclude the BTS from the global lock */
+		if (bts->excl_from_rf_lock) {
+			LOGP(DLINP, LOGL_DEBUG,
+				"Excluding BTS(%d) from trx lock.\n", bts->nr);
+			continue;
+		}
+
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			gsm_trx_lock_rf(trx, lock, "ctrl");
+		}
+	}
+
+	return 0;
+}
+
+static void send_resp(struct osmo_bsc_rf_conn *conn, char send)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc(10, "RF Query");
+	if (!msg) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to allocate response msg.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = send;
+
+	if (osmo_wqueue_enqueue(&conn->queue, msg) != 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the answer.\n");
+		msgb_free(msg);
+		return;
+	}
+
+	return;
+}
+
+
+/*
+ * Send a
+ *    'g' when we are in grace mode
+ *    '1' when one TRX is online,
+ *    '0' otherwise
+ */
+static void handle_query(struct osmo_bsc_rf_conn *conn)
+{
+	struct gsm_bts *bts;
+	char send = RF_CMD_OFF;
+
+	if (conn->rf->policy == S_RF_GRACE)
+		return send_resp(conn, RF_CMD_ON_G);
+
+	llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) {
+		struct gsm_bts_trx *trx;
+
+		/* Exclude the BTS from the global lock */
+		if (bts->excl_from_rf_lock) {
+			LOGP(DLINP, LOGL_DEBUG,
+				"Excluding BTS(%d) from query.\n", bts->nr);
+			continue;
+		}
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			if (trx->mo.nm_state.availability == NM_AVSTATE_OK &&
+			    trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) {
+					send = RF_CMD_ON;
+					break;
+			}
+		}
+	}
+
+	send_resp(conn, send);
+}
+
+static void rf_check_cb(void *_data)
+{
+	struct gsm_bts *bts;
+	struct osmo_bsc_rf *rf = _data;
+
+	llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) {
+		struct gsm_bts_trx *trx;
+
+		/* don't bother to check a booting or missing BTS */
+		if (!bts->oml_link || !is_ipaccess_bts(bts))
+			continue;
+
+		/* Exclude the BTS from the global lock */
+		if (bts->excl_from_rf_lock) {
+			LOGP(DLINP, LOGL_DEBUG,
+				"Excluding BTS(%d) from query.\n", bts->nr);
+			continue;
+		}
+
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			if (trx->mo.nm_state.availability != NM_AVSTATE_OK ||
+			    trx->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
+			    trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) {
+				LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n");
+				ipaccess_drop_oml(bts);
+				break;
+			}
+		}
+	}
+}
+
+static void send_signal(struct osmo_bsc_rf *rf, int val)
+{
+	struct rf_signal_data sig;
+	sig.net = rf->gsm_network;
+
+	rf->policy = val;
+	osmo_signal_dispatch(SS_RF, val, &sig);
+}
+
+static int switch_rf_off(struct osmo_bsc_rf *rf)
+{
+	lock_each_trx(rf->gsm_network, true);
+	send_signal(rf, S_RF_OFF);
+
+	return 0;
+}
+
+static void grace_timeout(void *_data)
+{
+	struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data;
+
+	LOGP(DLINP, LOGL_NOTICE, "Grace timeout. Going to disable all BTS/TRX.\n");
+	switch_rf_off(rf);
+}
+
+static int enter_grace(struct osmo_bsc_rf *rf)
+{
+	if (osmo_timer_pending(&rf->grace_timeout)) {
+		LOGP(DLINP, LOGL_NOTICE, "RF Grace timer is pending. Not restarting.\n");
+		return 0;
+	}
+
+	osmo_timer_setup(&rf->grace_timeout, grace_timeout, rf);
+	osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0);
+	LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n",
+	     rf->gsm_network->bsc_data->mid_call_timeout);
+
+	send_signal(rf, S_RF_GRACE);
+	return 0;
+}
+
+static void rf_delay_cmd_cb(void *data)
+{
+	struct osmo_bsc_rf *rf = data;
+
+	switch (rf->last_request) {
+	case RF_CMD_D_OFF:
+		rf->last_state_command = "RF Direct Off";
+		osmo_timer_del(&rf->rf_check);
+		osmo_timer_del(&rf->grace_timeout);
+		switch_rf_off(rf);
+		break;
+	case RF_CMD_ON:
+		rf->last_state_command = "RF Direct On";
+		osmo_timer_del(&rf->grace_timeout);
+		lock_each_trx(rf->gsm_network, false);
+		send_signal(rf, S_RF_ON);
+		osmo_timer_schedule(&rf->rf_check, 3, 0);
+		break;
+	case RF_CMD_OFF:
+		rf->last_state_command = "RF Scheduled Off";
+		osmo_timer_del(&rf->rf_check);
+		enter_grace(rf);
+		break;
+	}
+}
+
+static int rf_read_cmd(struct osmo_fd *fd)
+{
+	struct osmo_bsc_rf_conn *conn = fd->data;
+	char buf[1];
+	int rc;
+
+	rc = read(fd->fd, buf, sizeof(buf));
+	if (rc != sizeof(buf)) {
+		LOGP(DLINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno));
+		osmo_fd_unregister(fd);
+		close(fd->fd);
+		osmo_wqueue_clear(&conn->queue);
+		talloc_free(conn);
+		return -1;
+	}
+
+	switch (buf[0]) {
+	case RF_CMD_QUERY:
+		handle_query(conn);
+		break;
+	case RF_CMD_D_OFF:
+	case RF_CMD_ON:
+	case RF_CMD_OFF:
+		osmo_bsc_rf_schedule_lock(conn->rf, buf[0]);
+		break;
+	default:
+		conn->rf->last_state_command = "Unknown command";
+		LOGP(DLINP, LOGL_ERROR, "Unknown command %d\n", buf[0]);
+		break;
+	}
+
+	return 0;
+}
+
+static int rf_write_cmd(struct osmo_fd *fd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(fd->fd, msg->data, msg->len);
+	if (rc != msg->len) {
+		LOGP(DLINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what)
+{
+	struct osmo_bsc_rf_conn *conn;
+	struct osmo_bsc_rf *rf = bfd->data;
+	struct sockaddr_un addr;
+	socklen_t len = sizeof(addr);
+	int fd;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &addr, &len);
+	if (fd < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n",
+		     errno, strerror(errno));
+		return -1;
+	}
+
+	conn = talloc_zero(rf, struct osmo_bsc_rf_conn);
+	if (!conn) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to allocate mem.\n");
+		close(fd);
+		return -1;
+	}
+
+	osmo_wqueue_init(&conn->queue, 10);
+	conn->queue.bfd.data = conn;
+	conn->queue.bfd.fd = fd;
+	conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+	conn->queue.read_cb = rf_read_cmd;
+	conn->queue.write_cb = rf_write_cmd;
+	conn->rf = rf;
+
+	if (osmo_fd_register(&conn->queue.bfd) != 0) {
+		close(fd);
+		talloc_free(conn);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void rf_auto_off_cb(void *_timer)
+{
+	struct osmo_bsc_rf *rf = _timer;
+
+	LOGP(DLINP, LOGL_NOTICE,
+		"Going to switch off RF due lack of a MSC connection.\n");
+	osmo_bsc_rf_schedule_lock(rf, RF_CMD_D_OFF);
+}
+
+static int msc_signal_handler(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct gsm_network *net;
+	struct msc_signal_data *msc;
+	struct osmo_bsc_rf *rf;
+
+	/* check if we want to handle this signal */
+	if (subsys != SS_MSC)
+		return 0;
+
+	net = handler_data;
+	msc = signal_data;
+
+	/* check if we have the needed information */
+	if (!net->bsc_data)
+		return 0;
+	if (msc->data->type != MSC_CON_TYPE_NORMAL)
+		return 0;
+
+	rf = net->bsc_data->rf_ctrl;
+	switch (signal) {
+	case S_MSC_LOST:
+		if (net->bsc_data->auto_off_timeout < 0)
+			return 0;
+		if (osmo_timer_pending(&rf->auto_off_timer))
+			return 0;
+		osmo_timer_schedule(&rf->auto_off_timer,
+				net->bsc_data->auto_off_timeout, 0);
+		break;
+	case S_MSC_CONNECTED:
+		osmo_timer_del(&rf->auto_off_timer);
+		break;
+	}
+
+	return 0;
+}
+
+static int rf_create_socket(struct osmo_bsc_rf *rf, const char *path)
+{
+	unsigned int namelen;
+	struct sockaddr_un local;
+	struct osmo_fd *bfd;
+	int rc;
+
+	bfd = &rf->listen;
+	bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (bfd->fd < 0) {
+		LOGP(DLINP, LOGL_ERROR, "Can not create socket. %d/%s\n",
+		     errno, strerror(errno));
+		return -1;
+	}
+
+	local.sun_family = AF_UNIX;
+	osmo_strlcpy(local.sun_path, path, sizeof(local.sun_path));
+	unlink(local.sun_path);
+
+	/* we use the same magic that X11 uses in Xtranssock.c for
+	 * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+	local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+	namelen = SUN_LEN(&local);
+#else
+	namelen = strlen(local.sun_path) +
+		  offsetof(struct sockaddr_un, sun_path);
+#endif
+
+	rc = bind(bfd->fd, (struct sockaddr *) &local, namelen);
+	if (rc != 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n",
+		     local.sun_path, errno, strerror(errno));
+		close(bfd->fd);
+		return -1;
+	}
+
+	if (listen(bfd->fd, 0) != 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno));
+		close(bfd->fd);
+		return -1;
+	}
+
+	bfd->when = BSC_FD_READ;
+	bfd->cb = rf_ctrl_accept;
+	bfd->data = rf;
+
+	if (osmo_fd_register(bfd) != 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n");
+		close(bfd->fd);
+		return -1;
+	}
+
+	return 0;
+}
+
+struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net)
+{
+	struct osmo_bsc_rf *rf;
+
+	rf = talloc_zero(NULL, struct osmo_bsc_rf);
+	if (!rf) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n");
+		return NULL;
+	}
+
+	if (path && rf_create_socket(rf, path) != 0) {
+		talloc_free(rf);
+		return NULL;
+	}
+
+	rf->gsm_network = net;
+	rf->policy = S_RF_ON;
+	rf->last_state_command = "";
+	rf->last_rf_lock_ctrl_command = talloc_strdup(rf, "");
+
+	/* check the rf state */
+	osmo_timer_setup(&rf->rf_check, rf_check_cb, rf);
+
+	/* delay cmd handling */
+	osmo_timer_setup(&rf->delay_cmd, rf_delay_cmd_cb, rf);
+
+	osmo_timer_setup(&rf->auto_off_timer, rf_auto_off_cb, rf);
+
+	/* listen to RF signals */
+	osmo_signal_register_handler(SS_MSC, msc_signal_handler, net);
+
+	return rf;
+}
+
+void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd)
+{
+	rf->last_request = cmd;
+	if (!osmo_timer_pending(&rf->delay_cmd))
+		osmo_timer_schedule(&rf->delay_cmd, 1, 0);
+}
diff --git a/openbsc/src/libbsc/bsc_rll.c b/openbsc/src/libbsc/bsc_rll.c
new file mode 100644
index 0000000..bb488da
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_rll.c
@@ -0,0 +1,139 @@
+/* GSM BSC Radio Link Layer API
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+
+struct bsc_rll_req {
+	struct llist_head list;
+	struct osmo_timer_list timer;
+
+	struct gsm_lchan *lchan;
+	uint8_t link_id;
+
+	void (*cb)(struct gsm_lchan *lchan, uint8_t link_id,
+		   void *data, enum bsc_rllr_ind);
+	void *data;
+};
+
+/* we only compare C1, C2 and SAPI */
+#define LINKID_MASK	0xC7
+
+static LLIST_HEAD(bsc_rll_reqs);
+
+static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type)
+{
+	llist_del(&rllr->list);
+	rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type);
+	talloc_free(rllr);
+}
+
+static void timer_cb(void *_rllr)
+{
+	struct bsc_rll_req *rllr = _rllr;
+
+	complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT);
+}
+
+/* establish a RLL connection with given SAPI / priority */
+int rll_establish(struct gsm_lchan *lchan, uint8_t sapi,
+		  void (*cb)(struct gsm_lchan *, uint8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data)
+{
+	struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req);
+	uint8_t link_id;
+	if (!rllr)
+		return -ENOMEM;
+
+	link_id = sapi;
+
+	/* If we are a TCH and not in signalling mode, we need to
+	 * indicate that the new RLL connection is to be made on the SACCH */
+	if ((lchan->type == GSM_LCHAN_TCH_F ||
+	     lchan->type == GSM_LCHAN_TCH_H) && sapi != 0)
+		link_id |= 0x40;
+
+	rllr->lchan = lchan;
+	rllr->link_id = link_id;
+	rllr->cb = cb;
+	rllr->data = data;
+
+	llist_add(&rllr->list, &bsc_rll_reqs);
+
+	osmo_timer_setup(&rllr->timer, timer_cb, rllr);
+	osmo_timer_schedule(&rllr->timer, 7, 0);
+
+	/* send the RSL RLL ESTablish REQuest */
+	return rsl_establish_request(rllr->lchan, rllr->link_id);
+}
+
+/* Called from RSL code in case we have received an indication regarding
+ * any RLL link */
+void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type)
+{
+	struct bsc_rll_req *rllr, *rllr2;
+
+	llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+		if (rllr->lchan == lchan &&
+		    (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) {
+			osmo_timer_del(&rllr->timer);
+			complete_rllr(rllr, type);
+			return;
+		}
+	}
+}
+
+static int rll_lchan_signal(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct challoc_signal_data *challoc;
+	struct bsc_rll_req *rllr, *rllr2;
+
+	if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED)
+		return 0;
+
+	challoc = (struct challoc_signal_data *) signal_data;
+
+	llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+		if (rllr->lchan == challoc->lchan) {
+			osmo_timer_del(&rllr->timer);
+			complete_rllr(rllr, BSC_RLLR_IND_ERR_IND);
+		}
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_rll(void)
+{
+	osmo_signal_register_handler(SS_CHALLOC, rll_lchan_signal, NULL);
+}
diff --git a/openbsc/src/libbsc/bsc_subscriber.c b/openbsc/src/libbsc/bsc_subscriber.c
new file mode 100644
index 0000000..73e61e8
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_subscriber.c
@@ -0,0 +1,168 @@
+/* GSM subscriber details for use in BSC land */
+
+/*
+ * (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <talloc.h>
+#include <string.h>
+#include <limits.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/logging.h>
+
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/debug.h>
+
+static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list)
+{
+	struct bsc_subscr *bsub;
+
+	bsub = talloc_zero(list, struct bsc_subscr);
+	if (!bsub)
+		return NULL;
+
+	llist_add_tail(&bsub->entry, list);
+	bsub->use_count = 1;
+
+	return bsub;
+}
+
+struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
+					   const char *imsi)
+{
+	struct bsc_subscr *bsub;
+
+	if (!imsi || !*imsi)
+		return NULL;
+
+	llist_for_each_entry(bsub, list, entry) {
+		if (!strcmp(bsub->imsi, imsi))
+			return bsc_subscr_get(bsub);
+	}
+	return NULL;
+}
+
+struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
+					   uint32_t tmsi)
+{
+	struct bsc_subscr *bsub;
+
+	if (tmsi == GSM_RESERVED_TMSI)
+		return NULL;
+
+	llist_for_each_entry(bsub, list, entry) {
+		if (bsub->tmsi == tmsi)
+			return bsc_subscr_get(bsub);
+	}
+	return NULL;
+}
+
+void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi)
+{
+	if (!bsub)
+		return;
+	osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi));
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list,
+						     const char *imsi)
+{
+	struct bsc_subscr *bsub;
+	bsub = bsc_subscr_find_by_imsi(list, imsi);
+	if (bsub)
+		return bsub;
+	bsub = bsc_subscr_alloc(list);
+	bsc_subscr_set_imsi(bsub, imsi);
+	return bsub;
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list,
+						     uint32_t tmsi)
+{
+	struct bsc_subscr *bsub;
+	bsub = bsc_subscr_find_by_tmsi(list, tmsi);
+	if (bsub)
+		return bsub;
+	bsub = bsc_subscr_alloc(list);
+	bsub->tmsi = tmsi;
+	return bsub;
+}
+
+const char *bsc_subscr_name(struct bsc_subscr *bsub)
+{
+	static char buf[32];
+	if (!bsub)
+		return "unknown";
+	if (bsub->imsi[0])
+		snprintf(buf, sizeof(buf), "IMSI:%s", bsub->imsi);
+	else
+		snprintf(buf, sizeof(buf), "TMSI:0x%08x", bsub->tmsi);
+	return buf;
+}
+
+static void bsc_subscr_free(struct bsc_subscr *bsub)
+{
+	llist_del(&bsub->entry);
+	talloc_free(bsub);
+}
+
+struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub,
+				   const char *file, int line)
+{
+	OSMO_ASSERT(bsub->use_count < INT_MAX);
+	bsub->use_count++;
+	LOGPSRC(DREF, LOGL_DEBUG, file, line,
+		"BSC subscr %s usage increases to: %d\n",
+		bsc_subscr_name(bsub), bsub->use_count);
+	return bsub;
+}
+
+struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub,
+				   const char *file, int line)
+{
+	bsub->use_count--;
+	LOGPSRC(DREF, bsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR,
+		file, line,
+		"BSC subscr %s usage decreases to: %d\n",
+		bsc_subscr_name(bsub), bsub->use_count);
+	if (bsub->use_count <= 0)
+		bsc_subscr_free(bsub);
+	return NULL;
+}
+
+void log_set_filter_bsc_subscr(struct log_target *target,
+			       struct bsc_subscr *bsc_subscr)
+{
+	struct bsc_subscr **fsub = (void*)&target->filter_data[LOG_FLT_BSC_SUBSCR];
+
+	/* free the old data */
+	if (*fsub) {
+		bsc_subscr_put(*fsub);
+		*fsub = NULL;
+	}
+
+	if (bsc_subscr) {
+		target->filter_map |= (1 << LOG_FLT_BSC_SUBSCR);
+		*fsub = bsc_subscr_get(bsc_subscr);
+	} else
+		target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR);
+}
diff --git a/openbsc/src/libbsc/bsc_vty.c b/openbsc/src/libbsc/bsc_vty.c
new file mode 100644
index 0000000..c71ea4a
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_vty.c
@@ -0,0 +1,4515 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/ctrl/control_if.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_om2000.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/db.h>
+#include <openbsc/vty.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <openbsc/system_information.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/pcu_if.h>
+#include <openbsc/acc_ramp.h>
+
+#include <openbsc/common_cs.h>
+
+#include <inttypes.h>
+
+#include "../../bscconfig.h"
+
+
+#define LCHAN_NR_STR "Logical Channel Number\n"
+
+
+/* FIXME: this should go to some common file */
+static const struct value_string gprs_ns_timer_strs[] = {
+	{ 0, "tns-block" },
+	{ 1, "tns-block-retries" },
+	{ 2, "tns-reset" },
+	{ 3, "tns-reset-retries" },
+	{ 4, "tns-test" },
+	{ 5, "tns-alive" },
+	{ 6, "tns-alive-retries" },
+	{ 0, NULL }
+};
+
+static const struct value_string gprs_bssgp_cfg_strs[] = {
+	{ 0,	"blocking-timer" },
+	{ 1,	"blocking-retries" },
+	{ 2,	"unblocking-retries" },
+	{ 3,	"reset-timer" },
+	{ 4,	"reset-retries" },
+	{ 5,	"suspend-timer" },
+	{ 6,	"suspend-retries" },
+	{ 7,	"resume-timer" },
+	{ 8,	"resume-retries" },
+	{ 9,	"capability-update-timer" },
+	{ 10,	"capability-update-retries" },
+	{ 0,	NULL }
+};
+
+static const struct value_string bts_neigh_mode_strs[] = {
+	{ NL_MODE_AUTOMATIC, "automatic" },
+	{ NL_MODE_MANUAL, "manual" },
+	{ NL_MODE_MANUAL_SI5SEP, "manual-si5" },
+	{ 0, NULL }
+};
+
+const struct value_string bts_loc_fix_names[] = {
+	{ BTS_LOC_FIX_INVALID,	"invalid" },
+	{ BTS_LOC_FIX_2D,	"fix2d" },
+	{ BTS_LOC_FIX_3D,	"fix3d" },
+	{ 0, NULL }
+};
+
+struct cmd_node bts_node = {
+	BTS_NODE,
+	"%s(config-net-bts)# ",
+	1,
+};
+
+struct cmd_node trx_node = {
+	TRX_NODE,
+	"%s(config-net-bts-trx)# ",
+	1,
+};
+
+struct cmd_node ts_node = {
+	TS_NODE,
+	"%s(config-net-bts-trx-ts)# ",
+	1,
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+{
+	vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s",
+		abis_nm_opstate_name(nms->operational),
+		get_value_string(abis_nm_adm_state_names, nms->administrative),
+		abis_nm_avail_name(nms->availability), VTY_NEWLINE);
+}
+
+static void dump_pchan_load_vty(struct vty *vty, char *prefix,
+				const struct pchan_load *pl)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) {
+		const struct load_counter *lc = &pl->pchan[i];
+		unsigned int percent;
+
+		if (lc->total == 0)
+			continue;
+
+		percent = (lc->used * 100) / lc->total;
+
+		vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix,
+			gsm_pchan_name(i), percent, lc->used, lc->total,
+			VTY_NEWLINE);
+	}
+}
+
+static void net_dump_vty(struct vty *vty, struct gsm_network *net)
+{
+	struct pchan_load pl;
+
+	vty_out(vty, "BSC is on MCC-MNC %s and has %u BTS%s",
+		osmo_plmn_name(&net->plmn), net->num_bts, VTY_NEWLINE);
+	vty_out(vty, "  Long network name: '%s'%s",
+		net->name_long, VTY_NEWLINE);
+	vty_out(vty, "  Short network name: '%s'%s",
+		net->name_short, VTY_NEWLINE);
+	vty_out(vty, "  Authentication policy: %s",
+		gsm_auth_policy_name(net->auth_policy));
+	if (net->authorized_reg_str)
+		vty_out(vty, ", authorized regexp: %s", net->authorized_reg_str);
+	vty_out(vty, "%s", VTY_NEWLINE);
+	vty_out(vty, "  Auto create subscriber: %s%s",
+		net->auto_create_subscr ? "yes" : "no", VTY_NEWLINE);
+	vty_out(vty, "  Auto assign extension: %s%s",
+		net->auto_assign_exten ? "yes" : "no", VTY_NEWLINE);
+	vty_out(vty, "  Location updating reject cause: %u%s",
+		net->reject_cause, VTY_NEWLINE);
+	vty_out(vty, "  Encryption: A5/%u%s", net->a5_encryption,
+		VTY_NEWLINE);
+	vty_out(vty, "  NECI (TCH/H): %u%s", net->neci,
+		VTY_NEWLINE);
+	vty_out(vty, "  Use TCH for Paging any: %d%s", net->pag_any_tch,
+		VTY_NEWLINE);
+	vty_out(vty, "  RRLP Mode: %s%s", rrlp_mode_name(net->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, "  MM Info: %s%s", net->send_mm_info ? "On" : "Off",
+		VTY_NEWLINE);
+	vty_out(vty, "  Handover: %s%s", net->handover.active ? "On" : "Off",
+		VTY_NEWLINE);
+	network_chan_load(&pl, net);
+	vty_out(vty, "  Current Channel Load:%s", VTY_NEWLINE);
+	dump_pchan_load_vty(vty, "    ", &pl);
+
+	/* show rf */
+	if (net->bsc_data)
+		vty_out(vty, "  Last RF Command: %s%s",
+			net->bsc_data->rf_ctrl->last_state_command,
+			VTY_NEWLINE);
+	if (net->bsc_data)
+		vty_out(vty, "  Last RF Lock Command: %s%s",
+			net->bsc_data->rf_ctrl->last_rf_lock_ctrl_command,
+			VTY_NEWLINE);
+}
+
+DEFUN(bsc_show_net, bsc_show_net_cmd, "show network",
+	SHOW_STR "Display information about a GSM NETWORK\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	net_dump_vty(vty, net);
+
+	return CMD_SUCCESS;
+}
+
+static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l)
+{
+	struct e1inp_line *line;
+
+	if (!e1l) {
+		vty_out(vty, "   None%s", VTY_NEWLINE);
+		return;
+	}
+
+	line = e1l->ts->line;
+
+	vty_out(vty, "    E1 Line %u, Type %s: Timeslot %u, Mode %s%s",
+		line->num, line->driver->name, e1l->ts->num,
+		e1inp_signtype_name(e1l->type), VTY_NEWLINE);
+	vty_out(vty, "    E1 TEI %u, SAPI %u%s",
+		e1l->tei, e1l->sapi, VTY_NEWLINE);
+}
+
+static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct pchan_load pl;
+	unsigned long long sec;
+	struct timespec tp;
+	int rc;
+
+	vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+		"BSIC %u (NCC=%u, BCC=%u) and %u TRX%s",
+		bts->nr, btstype2str(bts->type), gsm_band_name(bts->band),
+		bts->cell_identity,
+		bts->location_area_code, bts->bsic,
+		bts->bsic >> 3, bts->bsic & 7,
+		bts->num_trx, VTY_NEWLINE);
+	vty_out(vty, "Description: %s%s",
+		bts->description ? bts->description : "(null)", VTY_NEWLINE);
+	if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH))
+		vty_out(vty, "PCU version %s connected%s", bts->pcu_version,
+			VTY_NEWLINE);
+	vty_out(vty, "MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "Minimum Rx Level for Access: %i dBm%s",
+		rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min),
+		VTY_NEWLINE);
+	vty_out(vty, "Cell Reselection Hysteresis: %u dBm%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "  Access Control Class ramping: %senabled%s",
+		acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE);
+	if (acc_ramp_is_enabled(&bts->acc_ramp)) {
+		if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp))
+			vty_out(vty, "  Access Control Class ramping step interval: %u seconds%s",
+				acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE);
+		else
+			vty_out(vty, "  Access Control Class ramping step interval: dynamic%s", VTY_NEWLINE);
+	        vty_out(vty, "  enabling %u Access Control Class%s per ramping step%s",
+			acc_ramp_get_step_size(&bts->acc_ramp),
+			acc_ramp_get_step_size(&bts->acc_ramp) > 1 ? "es" : "", VTY_NEWLINE);
+	}
+	vty_out(vty, "RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer,
+		VTY_NEWLINE);
+	vty_out(vty, "RACH Max transmissions: %u%s",
+		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+		VTY_NEWLINE);
+	if (bts->si_common.rach_control.cell_bar)
+		vty_out(vty, "  CELL IS BARRED%s", VTY_NEWLINE);
+	if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
+		vty_out(vty, "Uplink DTX: %s%s",
+			(bts->dtxu != GSM48_DTX_SHALL_BE_USED) ?
+			"enabled" : "forced", VTY_NEWLINE);
+	else
+		vty_out(vty, "Uplink DTX: not enabled%s", VTY_NEWLINE);
+	vty_out(vty, "Downlink DTX: %senabled%s", bts->dtxd ? "" : "not ",
+		VTY_NEWLINE);
+	vty_out(vty, "Channel Description Attachment: %s%s",
+		(bts->si_common.chan_desc.att) ? "yes" : "no", VTY_NEWLINE);
+	vty_out(vty, "Channel Description BS-PA-MFRMS: %u%s",
+		bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
+	vty_out(vty, "Channel Description BS-AG_BLKS-RES: %u%s",
+		bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
+	vty_out(vty, "System Information present: 0x%08x, static: 0x%08x%s",
+		bts->si_valid, bts->si_mode_static, VTY_NEWLINE);
+	vty_out(vty, "Early Classmark Sending: 2G %s, 3G %s%s%s",
+		bts->early_classmark_allowed ? "allowed" : "forbidden",
+		bts->early_classmark_allowed_3g ? "allowed" : "forbidden",
+		bts->early_classmark_allowed_3g && !bts->early_classmark_allowed ?
+		" (forbidden by 2G bit)" : "",
+		VTY_NEWLINE);
+	if (bts->pcu_sock_path)
+		vty_out(vty, "PCU Socket Path: %s%s", bts->pcu_sock_path, VTY_NEWLINE);
+	if (is_ipaccess_bts(bts))
+		vty_out(vty, "  Unit ID: %u/%u/0, OML Stream ID 0x%02x%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id,
+			bts->oml_tei, VTY_NEWLINE);
+	else if (bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+		vty_out(vty, "  Skip Reset: %d%s",
+			bts->nokia.skip_reset, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &bts->mo.nm_state);
+	vty_out(vty, "  Site Mgr NM State: ");
+	net_dump_nmstate(vty, &bts->site_mgr.mo.nm_state);
+	vty_out(vty, "  GPRS NSE: ");
+	net_dump_nmstate(vty, &bts->gprs.nse.mo.nm_state);
+	vty_out(vty, "  GPRS CELL: ");
+	net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state);
+	vty_out(vty, "  GPRS NSVC0: ");
+	net_dump_nmstate(vty, &bts->gprs.nsvc[0].mo.nm_state);
+	vty_out(vty, "  GPRS NSVC1: ");
+	net_dump_nmstate(vty, &bts->gprs.nsvc[1].mo.nm_state);
+	vty_out(vty, "  Paging: %u pending requests, %u free slots%s",
+		paging_pending_requests_nr(bts),
+		bts->paging.available_slots, VTY_NEWLINE);
+	if (is_ipaccess_bts(bts)) {
+		vty_out(vty, "  OML Link state: ");
+		if (bts->oml_link) {
+			vty_out(vty, "connected");
+			if (bts->uptime) {
+				rc = clock_gettime(CLOCK_MONOTONIC, &tp);
+				if (rc == 0) { /* monotonic clock helps to ensure that conversion below is valid */
+					sec = (unsigned long long)difftime(tp.tv_sec, bts->uptime);
+					vty_out(vty, " %llu days %llu hours %llu min. %llu sec.%s",
+						OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec),
+						sec % 60, VTY_NEWLINE);
+				}
+			}
+		} else
+			vty_out(vty, "disconnected.%s", VTY_NEWLINE);
+	} else {
+		vty_out(vty, "  E1 Signalling Link:%s", VTY_NEWLINE);
+		e1isl_dump_vty(vty, bts->oml_link);
+	}
+
+	/* FIXME: chan_desc */
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+	vty_out(vty, "  Current Channel Load:%s", VTY_NEWLINE);
+	dump_pchan_load_vty(vty, "    ", &pl);
+}
+
+DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]",
+	SHOW_STR "Display information about a BTS\n"
+		"BTS number")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	int bts_nr;
+
+	if (argc != 0) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+		return CMD_SUCCESS;
+	}
+	/* print all BTS's */
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+		bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+
+	return CMD_SUCCESS;
+}
+
+/* utility functions */
+static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
+			  const char *ts, const char *ss)
+{
+	e1_link->e1_nr = atoi(line);
+	e1_link->e1_ts = atoi(ts);
+	if (!strcmp(ss, "full"))
+		e1_link->e1_ts_ss = 255;
+	else
+		e1_link->e1_ts_ss = atoi(ss);
+}
+
+static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link,
+				 const char *prefix)
+{
+	if (!e1_link->e1_ts)
+		return;
+
+	if (e1_link->e1_ts_ss == 255)
+		vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s",
+			prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE);
+	else
+		vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s",
+			prefix, e1_link->e1_nr, e1_link->e1_ts,
+			e1_link->e1_ts_ss, VTY_NEWLINE);
+}
+
+
+static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	vty_out(vty, "   timeslot %u%s", ts->nr, VTY_NEWLINE);
+	if (ts->tsc != -1)
+		vty_out(vty, "    training_sequence_code %u%s", ts->tsc, VTY_NEWLINE);
+	if (ts->pchan != GSM_PCHAN_NONE)
+		vty_out(vty, "    phys_chan_config %s%s",
+			gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+	vty_out(vty, "    hopping enabled %u%s",
+		ts->hopping.enabled, VTY_NEWLINE);
+	if (ts->hopping.enabled) {
+		unsigned int i;
+		vty_out(vty, "    hopping sequence-number %u%s",
+			ts->hopping.hsn, VTY_NEWLINE);
+		vty_out(vty, "    hopping maio %u%s",
+			ts->hopping.maio, VTY_NEWLINE);
+		for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+			if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+				continue;
+			vty_out(vty, "    hopping arfcn add %u%s",
+				i, VTY_NEWLINE);
+		}
+	}
+	config_write_e1_link(vty, &ts->e1_link, "    ");
+
+	if (ts->trx->bts->model->config_write_ts)
+		ts->trx->bts->model->config_write_ts(vty, ts);
+}
+
+static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
+{
+	int i;
+
+	vty_out(vty, "  trx %u%s", trx->nr, VTY_NEWLINE);
+	if (trx->description)
+		vty_out(vty, "   description %s%s", trx->description,
+			VTY_NEWLINE);
+	vty_out(vty, "   rf_locked %u%s",
+		trx->mo.nm_state.administrative == NM_STATE_LOCKED ? 1 : 0,
+		VTY_NEWLINE);
+	vty_out(vty, "   arfcn %u%s", trx->arfcn, VTY_NEWLINE);
+	vty_out(vty, "   nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
+	vty_out(vty, "   max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
+	config_write_e1_link(vty, &trx->rsl_e1_link, "   rsl ");
+	vty_out(vty, "   rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE);
+
+	if (trx->bts->model->config_write_trx)
+		trx->bts->model->config_write_trx(vty, trx);
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		config_write_ts_single(vty, &trx->ts[i]);
+}
+
+static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts)
+{
+	unsigned int i;
+	vty_out(vty, "  gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
+		VTY_NEWLINE);
+	if (bts->gprs.mode == BTS_GPRS_NONE)
+		return;
+
+	vty_out(vty, "  gprs 11bit_rach_support_for_egprs %u%s",
+		bts->gprs.supports_egprs_11bit_rach, VTY_NEWLINE);
+
+	vty_out(vty, "  gprs routing area %u%s", bts->gprs.rac,
+		VTY_NEWLINE);
+	vty_out(vty, "  gprs network-control-order nc%u%s",
+		bts->gprs.net_ctrl_ord, VTY_NEWLINE);
+	if (!bts->gprs.ctrl_ack_type_use_block)
+		vty_out(vty, "  gprs control-ack-type-rach%s", VTY_NEWLINE);
+	vty_out(vty, "  gprs cell bvci %u%s", bts->gprs.cell.bvci,
+		VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++)
+		vty_out(vty, "  gprs cell timer %s %u%s",
+			get_value_string(gprs_bssgp_cfg_strs, i),
+			bts->gprs.cell.timer[i], VTY_NEWLINE);
+	vty_out(vty, "  gprs nsei %u%s", bts->gprs.nse.nsei,
+		VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++)
+		vty_out(vty, "  gprs ns timer %s %u%s",
+			get_value_string(gprs_ns_timer_strs, i),
+			bts->gprs.nse.timer[i], VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+		struct gsm_bts_gprs_nsvc *nsvc =
+					&bts->gprs.nsvc[i];
+		struct in_addr ia;
+
+		ia.s_addr = htonl(nsvc->remote_ip);
+		vty_out(vty, "  gprs nsvc %u nsvci %u%s", i,
+			nsvc->nsvci, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u local udp port %u%s", i,
+			nsvc->local_port, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u remote udp port %u%s", i,
+			nsvc->remote_port, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u remote ip %s%s", i,
+			inet_ntoa(ia), VTY_NEWLINE);
+	}
+}
+
+/* Write the model data if there is one */
+static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	if (!bts->model)
+		return;
+
+	if (bts->model->config_write_bts)
+		bts->model->config_write_bts(vty, bts);
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		config_write_trx_single(vty, trx);
+}
+
+static void write_amr_modes(struct vty *vty, const char *prefix,
+	const char *name, struct amr_mode *modes, int num)
+{
+	int i;
+
+	vty_out(vty, "  %s threshold %s", prefix, name);
+	for (i = 0; i < num - 1; i++)
+		vty_out(vty, " %d", modes[i].threshold);
+	vty_out(vty, "%s", VTY_NEWLINE);
+	vty_out(vty, "  %s hysteresis %s", prefix, name);
+	for (i = 0; i < num - 1; i++)
+		vty_out(vty, " %d", modes[i].hysteresis);
+	vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts,
+	struct amr_multirate_conf *mr, int full)
+{
+	struct gsm48_multi_rate_conf *mr_conf;
+	const char *prefix = (full) ? "amr tch-f" : "amr tch-h";
+	int i, num;
+
+	if (!(mr->gsm48_ie[1]))
+		return;
+
+	mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+
+	num = 0;
+	vty_out(vty, "  %s modes", prefix);
+	for (i = 0; i < ((full) ? 8 : 6); i++) {
+		if ((mr->gsm48_ie[1] & (1 << i))) {
+			vty_out(vty, " %d", i);
+			num++;
+		}
+	}
+	vty_out(vty, "%s", VTY_NEWLINE);
+	if (num > 4)
+		num = 4;
+	if (num > 1) {
+		write_amr_modes(vty, prefix, "ms", mr->ms_mode, num);
+		write_amr_modes(vty, prefix, "bts", mr->bts_mode, num);
+	}
+	vty_out(vty, "  %s start-mode ", prefix);
+	if (mr_conf->icmi) {
+		num = 0;
+		for (i = 0; i < ((full) ? 8 : 6) && num < 4; i++) {
+			if ((mr->gsm48_ie[1] & (1 << i)))
+				num++;
+			if (mr_conf->smod == num - 1) {
+				vty_out(vty, "%d%s", num, VTY_NEWLINE);
+				break;
+			}
+		}
+	} else
+		vty_out(vty, "auto%s", VTY_NEWLINE);
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+	int i;
+	uint8_t tmp;
+
+	vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE);
+	vty_out(vty, "  type %s%s", btstype2str(bts->type), VTY_NEWLINE);
+	if (bts->description)
+		vty_out(vty, "  description %s%s", bts->description, VTY_NEWLINE);
+	vty_out(vty, "  band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
+	vty_out(vty, "  cell_identity %u%s", bts->cell_identity, VTY_NEWLINE);
+	vty_out(vty, "  location_area_code %u%s", bts->location_area_code,
+		VTY_NEWLINE);
+	if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
+		vty_out(vty, "  dtx uplink%s%s",
+			(bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? "" : " force",
+			VTY_NEWLINE);
+	if (bts->dtxd)
+		vty_out(vty, "  dtx downlink%s", VTY_NEWLINE);
+	vty_out(vty, "  base_station_id_code %u%s", bts->bsic, VTY_NEWLINE);
+	vty_out(vty, "  ms max power %u%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "  cell reselection hysteresis %u%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "  rxlev access min %u%s",
+		bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE);
+
+	if (bts->si_common.cell_ro_sel_par.present) {
+		struct gsm48_si_selection_params *sp;
+		sp = &bts->si_common.cell_ro_sel_par;
+
+		if (sp->cbq)
+			vty_out(vty, "  cell bar qualify %u%s",
+				sp->cbq, VTY_NEWLINE);
+
+		if (sp->cell_resel_off)
+			vty_out(vty, "  cell reselection offset %u%s",
+				sp->cell_resel_off*2, VTY_NEWLINE);
+
+		if (sp->temp_offs == 7)
+			vty_out(vty, "  temporary offset infinite%s",
+				VTY_NEWLINE);
+		else if (sp->temp_offs)
+			vty_out(vty, "  temporary offset %u%s",
+				sp->temp_offs*10, VTY_NEWLINE);
+
+		if (sp->penalty_time == 31)
+			vty_out(vty, "  penalty time reserved%s",
+				VTY_NEWLINE);
+		else if (sp->penalty_time)
+			vty_out(vty, "  penalty time %u%s",
+				(sp->penalty_time*20)+20, VTY_NEWLINE);
+	}
+
+	/* Is periodic LU enabled or disabled? */
+	if (bts->si_common.chan_desc.t3212 == 0)
+		vty_out(vty, "  no periodic location update%s", VTY_NEWLINE);
+	else
+		vty_out(vty, "  periodic location update %u%s",
+			bts->si_common.chan_desc.t3212 * 6, VTY_NEWLINE);
+
+	if (gsm_bts_get_radio_link_timeout(bts) < 0)
+		vty_out(vty, "  radio-link-timeout infinite%s", VTY_NEWLINE);
+	else
+		vty_out(vty, "  radio-link-timeout %d%s",
+			gsm_bts_get_radio_link_timeout(bts), VTY_NEWLINE);
+	vty_out(vty, "  channel allocator %s%s",
+		bts->chan_alloc_reverse ? "descending" : "ascending",
+		VTY_NEWLINE);
+	vty_out(vty, "  rach tx integer %u%s",
+		bts->si_common.rach_control.tx_integer, VTY_NEWLINE);
+	vty_out(vty, "  rach max transmission %u%s",
+		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+		VTY_NEWLINE);
+
+	vty_out(vty, "  channel-descrption attach %u%s",
+		bts->si_common.chan_desc.att, VTY_NEWLINE);
+	vty_out(vty, "  channel-descrption bs-pa-mfrms %u%s",
+		bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
+	vty_out(vty, "  channel-descrption bs-ag-blks-res %u%s",
+		bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
+
+	if (bts->rach_b_thresh != -1)
+		vty_out(vty, "  rach nm busy threshold %u%s",
+			bts->rach_b_thresh, VTY_NEWLINE);
+	if (bts->rach_ldavg_slots != -1)
+		vty_out(vty, "  rach nm load average %u%s",
+			bts->rach_ldavg_slots, VTY_NEWLINE);
+	if (bts->si_common.rach_control.cell_bar)
+		vty_out(vty, "  cell barred 1%s", VTY_NEWLINE);
+	if ((bts->si_common.rach_control.t2 & 0x4) == 0)
+		vty_out(vty, "  rach emergency call allowed 1%s", VTY_NEWLINE);
+	if ((bts->si_common.rach_control.t3) != 0)
+		for (i = 0; i < 8; i++)
+			if (bts->si_common.rach_control.t3 & (0x1 << i))
+				vty_out(vty, "  rach access-control-class %d barred%s", i, VTY_NEWLINE);
+	if ((bts->si_common.rach_control.t2 & 0xfb) != 0)
+		for (i = 0; i < 8; i++)
+			if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i)))
+				vty_out(vty, "  rach access-control-class %d barred%s", i+8, VTY_NEWLINE);
+	vty_out(vty, "  %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE);
+	if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp)) {
+		vty_out(vty, "  access-control-class-ramping-step-interval %u%s",
+			acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE);
+	} else {
+		vty_out(vty, "  access-control-class-ramping-step-interval dynamic%s", VTY_NEWLINE);
+	}
+	vty_out(vty, "  access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp),
+		VTY_NEWLINE);
+	for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) {
+		if (bts->si_mode_static & (1 << i)) {
+			vty_out(vty, "  system-information %s mode static%s",
+				get_value_string(osmo_sitype_strs, i), VTY_NEWLINE);
+			vty_out(vty, "  system-information %s static %s%s",
+				get_value_string(osmo_sitype_strs, i),
+				osmo_hexdump_nospc(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN),
+				VTY_NEWLINE);
+		}
+	}
+	vty_out(vty, "  early-classmark-sending %s%s",
+		bts->early_classmark_allowed ? "allowed" : "forbidden", VTY_NEWLINE);
+	vty_out(vty, "  early-classmark-sending-3g %s%s",
+		bts->early_classmark_allowed_3g ? "allowed" : "forbidden", VTY_NEWLINE);
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		vty_out(vty, "  ip.access unit_id %u %u%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+		if (bts->ip_access.rsl_ip) {
+			struct in_addr ia;
+			ia.s_addr = htonl(bts->ip_access.rsl_ip);
+			vty_out(vty, "  ip.access rsl-ip %s%s", inet_ntoa(ia),
+				VTY_NEWLINE);
+		}
+		vty_out(vty, "  oml ip.access stream_id %u line %u%s",
+			bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE);
+		break;
+	case GSM_BTS_TYPE_NOKIA_SITE:
+		vty_out(vty, "  nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE);
+		vty_out(vty, "  nokia_site no-local-rel-conf %d%s",
+			bts->nokia.no_loc_rel_cnf, VTY_NEWLINE);
+		vty_out(vty, "  nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE);
+		/* fall through: Nokia requires "oml e1" parameters also */
+	default:
+		config_write_e1_link(vty, &bts->oml_e1_link, "  oml ");
+		vty_out(vty, "  oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
+		break;
+	}
+
+	/* if we have a limit, write it */
+	if (bts->paging.free_chans_need >= 0)
+		vty_out(vty, "  paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE);
+
+	vty_out(vty, "  neighbor-list mode %s%s",
+		get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE);
+	if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) {
+		for (i = 0; i < 1024; i++) {
+			if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i))
+				vty_out(vty, "  neighbor-list add arfcn %u%s",
+					i, VTY_NEWLINE);
+		}
+	}
+	if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
+		for (i = 0; i < 1024; i++) {
+			if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i))
+				vty_out(vty, "  si5 neighbor-list add arfcn %u%s",
+					i, VTY_NEWLINE);
+		}
+	}
+
+	for (i = 0; i < MAX_EARFCN_LIST; i++) {
+		struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+		if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
+			vty_out(vty, "  si2quater neighbor-list add earfcn %u "
+				"thresh-hi %u", e->arfcn[i], e->thresh_hi);
+
+			vty_out(vty, " thresh-lo %u",
+				e->thresh_lo_valid ? e->thresh_lo : 32);
+
+			vty_out(vty, " prio %u",
+				e->prio_valid ? e->prio : 8);
+
+			vty_out(vty, " qrxlv %u",
+				e->qrxlm_valid ? e->qrxlm : 32);
+
+			tmp = e->meas_bw[i];
+			vty_out(vty, " meas %u",
+				(tmp != OSMO_EARFCN_MEAS_INVALID) ? tmp : 8);
+
+			vty_out(vty, "%s", VTY_NEWLINE);
+		}
+	}
+
+	for (i = 0; i < bts->si_common.uarfcn_length; i++) {
+		vty_out(vty, "  si2quater neighbor-list add uarfcn %u %u %u%s",
+			bts->si_common.data.uarfcn_list[i],
+			bts->si_common.data.scramble_list[i] & ~(1 << 9),
+			(bts->si_common.data.scramble_list[i] >> 9) & 1,
+			VTY_NEWLINE);
+	}
+
+	vty_out(vty, "  codec-support fr");
+	if (bts->codec.hr)
+		vty_out(vty, " hr");
+	if (bts->codec.efr)
+		vty_out(vty, " efr");
+	if (bts->codec.amr)
+		vty_out(vty, " amr");
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	config_write_bts_amr(vty, bts, &bts->mr_full, 1);
+	config_write_bts_amr(vty, bts, &bts->mr_half, 0);
+
+	config_write_bts_gprs(vty, bts);
+
+	if (bts->excl_from_rf_lock)
+		vty_out(vty, "  rf-lock-exclude%s", VTY_NEWLINE);
+
+	vty_out(vty, "  %sforce-combined-si%s",
+		bts->force_combined_si ? "" : "no ", VTY_NEWLINE);
+
+	for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) {
+		int j;
+
+		if (bts->depends_on[i] == 0)
+			continue;
+
+		for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) {
+			int bts_nr;
+
+			if ((bts->depends_on[i] & (1<<j)) == 0)
+				continue;
+
+			bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j;
+			vty_out(vty, "  depends-on-bts %d%s", bts_nr, VTY_NEWLINE);
+		}
+	}
+	if (bts->pcu_sock_path)
+		vty_out(vty, "  pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE);
+
+	config_write_bts_model(vty, bts);
+}
+
+static int config_write_bts(struct vty *v)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(v);
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &gsmnet->bts_list, list)
+		config_write_bts_single(v, bts);
+
+	return CMD_SUCCESS;
+}
+
+/* small helper macro for conditional dumping of timer */
+#define VTY_OUT_TIMER(number)	\
+	if (gsmnet->T##number != GSM_T##number##_DEFAULT)	\
+		vty_out(vty, " timer t"#number" %u%s", gsmnet->T##number, VTY_NEWLINE)
+
+static int config_write_net(struct vty *vty)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	vty_out(vty, "network%s", VTY_NEWLINE);
+	vty_out(vty, " network country code %s%s", osmo_mcc_name(gsmnet->plmn.mcc), VTY_NEWLINE);
+	vty_out(vty, " mobile network code %s%s",
+		osmo_mnc_name(gsmnet->plmn.mnc, gsmnet->plmn.mnc_3_digits), VTY_NEWLINE);
+	vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE);
+	vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE);
+	vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE);
+	if (gsmnet->authorized_reg_str)
+		vty_out(vty, " authorized-regexp %s%s", gsmnet->authorized_reg_str, VTY_NEWLINE);
+	vty_out(vty, " location updating reject cause %u%s",
+		gsmnet->reject_cause, VTY_NEWLINE);
+	vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
+	vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE);
+	vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE);
+	vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE);
+	vty_out(vty, " handover %u%s", gsmnet->handover.active, VTY_NEWLINE);
+	vty_out(vty, " handover window rxlev averaging %u%s",
+		gsmnet->handover.win_rxlev_avg, VTY_NEWLINE);
+	vty_out(vty, " handover window rxqual averaging %u%s",
+		gsmnet->handover.win_rxqual_avg, VTY_NEWLINE);
+	vty_out(vty, " handover window rxlev neighbor averaging %u%s",
+		gsmnet->handover.win_rxlev_avg_neigh, VTY_NEWLINE);
+	vty_out(vty, " handover power budget interval %u%s",
+		gsmnet->handover.pwr_interval, VTY_NEWLINE);
+	vty_out(vty, " handover power budget hysteresis %u%s",
+		gsmnet->handover.pwr_hysteresis, VTY_NEWLINE);
+	vty_out(vty, " handover maximum distance %u%s",
+		gsmnet->handover.max_distance, VTY_NEWLINE);
+	VTY_OUT_TIMER(3101);
+	VTY_OUT_TIMER(3103);
+	VTY_OUT_TIMER(3105);
+	VTY_OUT_TIMER(3107);
+	VTY_OUT_TIMER(3109);
+	VTY_OUT_TIMER(3111);
+	VTY_OUT_TIMER(3113);
+	VTY_OUT_TIMER(3115);
+	VTY_OUT_TIMER(3117);
+	VTY_OUT_TIMER(3119);
+	VTY_OUT_TIMER(3122);
+	VTY_OUT_TIMER(3141);
+	vty_out(vty, " dyn_ts_allow_tch_f %d%s",
+		gsmnet->dyn_ts_allow_tch_f ? 1 : 0, VTY_NEWLINE);
+	vty_out(vty, " subscriber-keep-in-ram %d%s",
+		gsmnet->subscr_group->keep_subscr, VTY_NEWLINE);
+	if (gsmnet->tz.override != 0) {
+		if (gsmnet->tz.dst)
+			vty_out(vty, " timezone %d %d %d%s",
+				gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst,
+				VTY_NEWLINE);
+		else
+			vty_out(vty, " timezone %d %d%s",
+				gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+{
+	vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+		trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+	vty_out(vty, "Description: %s%s",
+		trx->description ? trx->description : "(null)", VTY_NEWLINE);
+	vty_out(vty, "  RF Nominal Power: %d dBm, reduced by %u dB, "
+		"resulting BS power: %d dBm%s",
+		trx->nominal_power, trx->max_power_red,
+		trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &trx->mo.nm_state);
+	vty_out(vty, "  Baseband Transceiver NM State: ");
+	net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state);
+	if (is_ipaccess_bts(trx->bts)) {
+		vty_out(vty, "  ip.access stream ID: 0x%02x%s",
+			trx->rsl_tei, VTY_NEWLINE);
+	} else {
+		vty_out(vty, "  E1 Signalling Link:%s", VTY_NEWLINE);
+		e1isl_dump_vty(vty, trx->rsl_link);
+	}
+}
+
+DEFUN(show_trx,
+      show_trx_cmd,
+      "show trx [<0-255>] [<0-255>]",
+	SHOW_STR "Display information about a TRX\n"
+	"BTS Number\n"
+	"TRX Number\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts = NULL;
+	struct gsm_bts_trx *trx;
+	int bts_nr, trx_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+		trx_dump_vty(vty, trx);
+		return CMD_SUCCESS;
+	}
+	if (bts) {
+		/* print all TRX in this BTS */
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			trx_dump_vty(vty, trx);
+		}
+		return CMD_SUCCESS;
+	}
+
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			trx_dump_vty(vty, trx);
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s, TSC %u",
+		ts->trx->bts->nr, ts->trx->nr, ts->nr,
+		gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts));
+	if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
+		vty_out(vty, " (%s mode)",
+			ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F");
+	vty_out(vty, "%s", VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &ts->mo.nm_state);
+	if (!is_ipaccess_bts(ts->trx->bts))
+		vty_out(vty, "  E1 Line %u, Timeslot %u, Subslot %u%s",
+			ts->e1_link.e1_nr, ts->e1_link.e1_ts,
+			ts->e1_link.e1_ts_ss, VTY_NEWLINE);
+}
+
+DEFUN(show_ts,
+      show_ts_cmd,
+      "show timeslot [<0-255>] [<0-255>] [<0-7>]",
+	SHOW_STR "Display information about a TS\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts = NULL;
+	struct gsm_bts_trx *trx = NULL;
+	struct gsm_bts_trx_ts *ts = NULL;
+	int bts_nr, trx_nr, ts_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+	}
+	if (argc >= 3) {
+		ts_nr = atoi(argv[2]);
+		if (ts_nr >= TRX_NR_TS) {
+			vty_out(vty, "%% can't find TS '%s'%s", argv[2],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		/* Fully Specified: print and exit */
+		ts = &trx->ts[ts_nr];
+		ts_dump_vty(vty, ts);
+		return CMD_SUCCESS;
+	}
+
+	if (bts && trx) {
+		/* Iterate over all TS in this TRX */
+		for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+			ts = &trx->ts[ts_nr];
+			ts_dump_vty(vty, ts);
+		}
+	} else if (bts) {
+		/* Iterate over all TRX in this BTS, TS in each TRX */
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+				ts = &trx->ts[ts_nr];
+				ts_dump_vty(vty, ts);
+			}
+		}
+	} else {
+		/* Iterate over all BTS, TRX in each BTS, TS in each TRX */
+		for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+			bts = gsm_bts_num(net, bts_nr);
+			for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+				trx = gsm_bts_trx_num(bts, trx_nr);
+				for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+					ts = &trx->ts[ts_nr];
+					ts_dump_vty(vty, ts);
+				}
+			}
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (strlen(subscr->name))
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (strlen(subscr->extension))
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub)
+{
+	if (strlen(bsub->imsi))
+		vty_out(vty, "    IMSI: %s%s", bsub->imsi, VTY_NEWLINE);
+	if (bsub->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: 0x%08x%s", bsub->tmsi,
+			VTY_NEWLINE);
+	vty_out(vty, "    Use count: %d%s", bsub->use_count, VTY_NEWLINE);
+}
+
+static void meas_rep_dump_uni_vty(struct vty *vty,
+				  struct gsm_meas_rep_unidir *mru,
+				  const char *prefix,
+				  const char *dir)
+{
+	vty_out(vty, "%s  RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ",
+		prefix, dir, rxlev2dbm(mru->full.rx_lev),
+			dir, rxlev2dbm(mru->sub.rx_lev));
+	vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s",
+		dir, mru->full.rx_qual, dir, mru->sub.rx_qual,
+		VTY_NEWLINE);
+}
+
+static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr,
+			      const char *prefix)
+{
+	vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE);
+	vty_out(vty, "%s  Flags: %s%s%s%s%s", prefix,
+			mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "",
+			mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "",
+			mr->flags & MEAS_REP_F_FPC ? "FPC " : "",
+			mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ",
+			VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		vty_out(vty, "%s  MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_L1)
+		vty_out(vty, "%s  L1 MS Power: %u dBm, Timing Advance: %u%s",
+			prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_DL_VALID)
+		meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
+	meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+}
+
+/* FIXME: move this to libosmogsm */
+static const struct value_string gsm48_cmode_names[] = {
+	{ GSM48_CMODE_SIGN,		"signalling" },
+	{ GSM48_CMODE_SPEECH_V1,	"FR or HR" },
+	{ GSM48_CMODE_SPEECH_EFR,	"EFR" },
+	{ GSM48_CMODE_SPEECH_AMR,	"AMR" },
+	{ GSM48_CMODE_DATA_14k5,	"CSD(14k5)" },
+	{ GSM48_CMODE_DATA_12k0,	"CSD(12k0)" },
+	{ GSM48_CMODE_DATA_6k0,		"CSD(6k0)" },
+	{ GSM48_CMODE_DATA_3k6,		"CSD(3k6)" },
+	{ 0, NULL }
+};
+
+/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots.
+ * Don't do anything if the ts is not dynamic. */
+static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+			vty_out(vty, " as %s",
+				gsm_pchan_name(ts->dyn.pchan_is));
+		else
+			vty_out(vty, " switching %s -> %s",
+				gsm_pchan_name(ts->dyn.pchan_is),
+				gsm_pchan_name(ts->dyn.pchan_want));
+		break;
+	case GSM_PCHAN_TCH_F_PDCH:
+		if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+			vty_out(vty, " as %s",
+				(ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+							      : "TCH/F");
+		else
+			vty_out(vty, " switching %s -> %s",
+				(ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+							      : "TCH/F",
+				(ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+								   : "TCH/F");
+		break;
+	default:
+		/* no dyn ts */
+		break;
+	}
+}
+
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+	int idx;
+
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
+	/* show dyn TS details, if applicable */
+	switch (lchan->ts->pchan) {
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		vty_out(vty, "  Osmocom Dyn TS:");
+		vty_out_dyn_ts_status(vty, lchan->ts);
+		vty_out(vty, VTY_NEWLINE);
+		break;
+	case GSM_PCHAN_TCH_F_PDCH:
+		vty_out(vty, "  IPACC Dyn PDCH TS:");
+		vty_out_dyn_ts_status(vty, lchan->ts);
+		vty_out(vty, VTY_NEWLINE);
+		break;
+	default:
+		/* no dyn ts */
+		break;
+	}
+	vty_out(vty, "  Connection: %u, State: %s%s%s%s",
+		lchan->conn ? 1: 0,
+		gsm_lchans_name(lchan->state),
+		lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "",
+		lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "",
+		VTY_NEWLINE);
+	vty_out(vty, "  BS Power: %u dBm, MS Power: %u dBm%s",
+		lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
+		- lchan->bs_power*2,
+		ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+		VTY_NEWLINE);
+	vty_out(vty, "  Channel Mode / Codec: %s%s",
+		get_value_string(gsm48_cmode_names, lchan->tch_mode),
+		VTY_NEWLINE);
+	if (lchan->conn && lchan->conn->subscr) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_vty(vty, lchan->conn->subscr);
+	} else
+		vty_out(vty, "  No Subscriber%s", VTY_NEWLINE);
+	if (is_ipaccess_bts(lchan->ts->trx->bts)) {
+		struct in_addr ia;
+		ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+		vty_out(vty, "  Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s",
+			inet_ntoa(ia), lchan->abis_ip.bound_port,
+			lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
+			VTY_NEWLINE);
+	}
+
+	/* we want to report the last measurement report */
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+			       lchan->meas_rep_idx, 1);
+	meas_rep_dump_vty(vty, &lchan->meas_rep[idx], "  ");
+}
+
+static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+	struct gsm_meas_rep *mr;
+	int idx;
+
+	/* we want to report the last measurement report */
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+			       lchan->meas_rep_idx, 1);
+	mr =  &lchan->meas_rep[idx];
+
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		gsm_pchan_name(lchan->ts->pchan));
+	vty_out_dyn_ts_status(vty, lchan->ts);
+	vty_out(vty, ", Lchan %u, Type %s, State %s - "
+		"L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
+		lchan->nr,
+		gsm_lchant_name(lchan->type), gsm_lchans_name(lchan->state),
+		mr->ms_l1.pwr,
+		rxlev2dbm(mr->dl.full.rx_lev),
+		rxlev2dbm(mr->ul.full.rx_lev),
+		VTY_NEWLINE);
+}
+
+
+static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty,
+			     void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+	int lchan_nr;
+	for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) {
+		struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+		if ((lchan->type == GSM_LCHAN_NONE) && (lchan->state == LCHAN_S_NONE))
+			continue;
+		dump_cb(vty, lchan);
+	}
+
+	return CMD_SUCCESS;
+}
+
+static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty,
+			  void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+	int ts_nr;
+
+	for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+		struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+		dump_lchan_trx_ts(ts, vty, dump_cb);
+	}
+
+	return CMD_SUCCESS;
+}
+
+static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty,
+			  void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+	int trx_nr;
+
+	for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+		struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr);
+		dump_lchan_trx(trx, vty, dump_cb);
+	}
+
+	return CMD_SUCCESS;
+}
+
+static int lchan_summary(struct vty *vty, int argc, const char **argv,
+			 void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lchan;
+	int bts_nr, trx_nr, ts_nr, lchan_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS %s%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+
+		if (argc == 1)
+			return dump_lchan_bts(bts, vty, dump_cb);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX %s%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+
+		if (argc == 2)
+			return dump_lchan_trx(trx, vty, dump_cb);
+	}
+	if (argc >= 3) {
+		ts_nr = atoi(argv[2]);
+		if (ts_nr >= TRX_NR_TS) {
+			vty_out(vty, "%% can't find TS %s%s", argv[2],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &trx->ts[ts_nr];
+
+		if (argc == 3)
+			return dump_lchan_trx_ts(ts, vty, dump_cb);
+	}
+	if (argc >= 4) {
+		lchan_nr = atoi(argv[3]);
+		if (lchan_nr >= TS_MAX_LCHAN) {
+			vty_out(vty, "%% can't find LCHAN %s%s", argv[3],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		lchan = &ts->lchan[lchan_nr];
+		dump_cb(vty, lchan);
+		return CMD_SUCCESS;
+	}
+
+
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		dump_lchan_bts(bts, vty, dump_cb);
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(show_lchan,
+      show_lchan_cmd,
+      "show lchan [<0-255>] [<0-255>] [<0-7>] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	LCHAN_NR_STR)
+
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+      show_lchan_summary_cmd,
+      "show lchan summary [<0-255>] [<0-255>] [<0-7>] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+        "Short summary\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+        LCHAN_NR_STR)
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
+static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag)
+{
+	vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE);
+	bsc_subscr_dump_vty(vty, pag->bsub);
+}
+
+static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_paging_request *pag;
+
+	if (!bts->paging.bts)
+		return;
+
+	llist_for_each_entry(pag, &bts->paging.pending_requests, entry)
+		paging_dump_vty(vty, pag);
+}
+
+DEFUN(show_paging,
+      show_paging_cmd,
+      "show paging [<0-255>]",
+	SHOW_STR "Display information about paging reuqests of a BTS\n"
+	"BTS Number\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts;
+	int bts_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS %s%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+		bts_paging_dump_vty(vty, bts);
+		
+		return CMD_SUCCESS;
+	}
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		bts_paging_dump_vty(vty, bts);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_paging_group,
+      show_paging_group_cmd,
+      "show paging-group <0-255> IMSI",
+      SHOW_STR "Display the paging group\n"
+      "BTS Number\n" "IMSI\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts;
+	unsigned int page_group;
+	int bts_nr = atoi(argv[0]);
+
+	if (bts_nr >= net->num_bts) {
+		vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(net, bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
+						str_to_imsi(argv[1]));
+	vty_out(vty, "%%Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s",
+		str_to_imsi(argv[1]), bts->nr,
+		page_group, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_neci,
+      cfg_net_neci_cmd,
+      "neci (0|1)",
+	"New Establish Cause Indication\n"
+	"Don't set the NECI bit\n" "Set the NECI bit\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->neci = atoi(argv[0]);
+	gsm_net_update_ctype(gsmnet);
+	return CMD_SUCCESS;
+}
+
+#define HANDOVER_STR	"Handover Options\n"
+
+DEFUN(cfg_net_handover, cfg_net_handover_cmd,
+      "handover (0|1)",
+	HANDOVER_STR
+	"Don't perform in-call handover\n"
+	"Perform in-call handover\n")
+{
+	int enable = atoi(argv[0]);
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	if (enable && ipacc_rtp_direct) {
+		vty_out(vty, "%% Cannot enable handover unless RTP Proxy mode "
+			"is enabled by using the -P command line option%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	gsmnet->handover.active = enable;
+
+	return CMD_SUCCESS;
+}
+
+#define HO_WIN_STR HANDOVER_STR "Measurement Window\n"
+#define HO_WIN_RXLEV_STR HO_WIN_STR "Received Level Averaging\n"
+#define HO_WIN_RXQUAL_STR HO_WIN_STR "Received Quality Averaging\n"
+#define HO_PBUDGET_STR HANDOVER_STR "Power Budget\n"
+#define HO_AVG_COUNT_STR "Amount to use for Averaging\n"
+
+DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd,
+      "handover window rxlev averaging <1-10>",
+	HO_WIN_RXLEV_STR
+	"How many RxLev measurements are used for averaging\n"
+	HO_AVG_COUNT_STR)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.win_rxlev_avg = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd,
+      "handover window rxqual averaging <1-10>",
+	HO_WIN_RXQUAL_STR
+	"How many RxQual measurements are used for averaging\n"
+	HO_AVG_COUNT_STR)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.win_rxqual_avg = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd,
+      "handover window rxlev neighbor averaging <1-10>",
+	HO_WIN_RXLEV_STR "Neighbor\n"
+	"How many RxQual measurements are used for averaging\n"
+	HO_AVG_COUNT_STR)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd,
+      "handover power budget interval <1-99>",
+	HO_PBUDGET_STR
+	"How often to check if we have a better cell (SACCH frames)\n"
+	"Interval\n" "Number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.pwr_interval = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd,
+      "handover power budget hysteresis <0-999>",
+	HO_PBUDGET_STR
+	"How many dB does a neighbor to be stronger to become a HO candidate\n"
+	"Hysteresis\n" "Number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.pwr_hysteresis = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd,
+      "handover maximum distance <0-9999>",
+	HANDOVER_STR
+	"How big is the maximum timing advance before HO is forced\n"
+	"Distance\n" "Number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.max_distance = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_pag_any_tch,
+      cfg_net_pag_any_tch_cmd,
+      "paging any use tch (0|1)",
+      "Assign a TCH when receiving a Paging Any request\n"
+      "Any Channel\n" "Use\n" "TCH\n"
+      "Do not use TCH for Paging Request Any\n"
+      "Do use TCH for Paging Request Any\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->pag_any_tch = atoi(argv[0]);
+	gsm_net_update_ctype(gsmnet);
+	return CMD_SUCCESS;
+}
+
+#define DEFAULT_TIMER(number) GSM_T##number##_DEFAULT
+/* Add another expansion so that DEFAULT_TIMER() becomes its value */
+#define EXPAND_AND_STRINGIFY(x) OSMO_STRINGIFY(x)
+
+#define DECLARE_TIMER(number, doc) \
+    DEFUN(cfg_net_T##number,					\
+      cfg_net_T##number##_cmd,					\
+      "timer t" #number  " (default|<1-65535>)",		\
+      "Configure GSM Timers\n"					\
+      doc " (default: " EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \
+      "Set to default timer value"				\
+	  " (" EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \
+      "Timer Value in seconds\n")				\
+{								\
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);	\
+	int value;						\
+	if (strcmp(argv[0], "default") == 0)			\
+		value = DEFAULT_TIMER(number);			\
+	else							\
+		value = atoi(argv[0]);				\
+								\
+	gsmnet->T##number = value;				\
+	return CMD_SUCCESS;					\
+}
+
+DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT")
+DECLARE_TIMER(3103, "Set the timeout value for HANDOVER")
+DECLARE_TIMER(3105, "Set the timer for repetition of PHYSICAL INFORMATION")
+DECLARE_TIMER(3107, "Currently not used")
+DECLARE_TIMER(3109, "Set the RSL SACCH deactivation timeout")
+DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel")
+DECLARE_TIMER(3113, "Set the time to try paging a subscriber")
+DECLARE_TIMER(3115, "Currently not used")
+DECLARE_TIMER(3117, "Currently not used")
+DECLARE_TIMER(3119, "Currently not used")
+DECLARE_TIMER(3122, "Default waiting time (seconds) after IMM ASS REJECT")
+DECLARE_TIMER(3141, "Currently not used")
+
+DEFUN_DEPRECATED(cfg_net_dtx,
+		 cfg_net_dtx_cmd,
+		 "dtx-used (0|1)",
+		 ".HIDDEN\n""Obsolete\n""Obsolete\n")
+{
+	vty_out(vty, "%% 'dtx-used' is now deprecated: use dtx * "
+		"configuration options of BTS instead%s", VTY_NEWLINE);
+       return CMD_SUCCESS;
+}
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+      cfg_bts_cmd,
+      "bts <0-255>",
+      "Select a BTS to configure\n"
+	"BTS Number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	int bts_nr = atoi(argv[0]);
+	struct gsm_bts *bts;
+
+	if (bts_nr > gsmnet->num_bts) {
+		vty_out(vty, "%% The next unused BTS number is %u%s",
+			gsmnet->num_bts, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (bts_nr == gsmnet->num_bts) {
+		/* allocate a new one */
+		bts = gsm_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN,
+					     HARDCODED_BSIC);
+		/*
+		 * Initalize bts->acc_ramp here. Else we could segfault while
+		 * processing a configuration file with ACC ramping settings.
+		 */
+		acc_ramp_init(&bts->acc_ramp, bts);
+	} else
+		bts = gsm_bts_num(gsmnet, bts_nr);
+
+	if (!bts) {
+		vty_out(vty, "%% Unable to allocate BTS %u%s",
+			gsmnet->num_bts, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->index = bts;
+	vty->index_sub = &bts->description;
+	vty->node = BTS_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_type,
+      cfg_bts_type_cmd,
+      "type TYPE", /* dynamically created */
+      "Set the BTS type\n" "Type\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc;
+
+	rc = gsm_set_bts_type(bts, str2btstype(argv[0]));
+	if (rc < 0)
+		return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_band,
+      cfg_bts_band_cmd,
+      "band BAND",
+      "Set the frequency band of this BTS\n" "Frequency band\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int band = gsm_band_parse(argv[0]);
+
+	if (band < 0) {
+		vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+			band, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->band = band;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_dtxu, cfg_bts_dtxu_cmd, "dtx uplink [force]",
+      "Configure discontinuous transmission\n"
+      "Enable Uplink DTX for this BTS\n"
+      "MS 'shall' use DTXu instead of 'may' use (might not be supported by "
+      "older phones).\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->dtxu = (argc > 0) ? GSM48_DTX_SHALL_BE_USED : GSM48_DTX_MAY_BE_USED;
+	if (!is_ipaccess_bts(bts))
+		vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration "
+			"neither supported nor tested!%s", VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_dtxu, cfg_bts_no_dtxu_cmd, "no dtx uplink",
+      NO_STR
+      "Configure discontinuous transmission\n"
+      "Disable Uplink DTX for this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_dtxd, cfg_bts_dtxd_cmd, "dtx downlink",
+      "Configure discontinuous transmission\n"
+      "Enable Downlink DTX for this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->dtxd = true;
+	if (!is_ipaccess_bts(bts))
+		vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration "
+			"neither supported nor tested!%s", VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_dtxd, cfg_bts_no_dtxd_cmd, "no dtx downlink",
+      NO_STR
+      "Configure discontinuous transmission\n"
+      "Disable Downlink DTX for this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->dtxd = false;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ci,
+      cfg_bts_ci_cmd,
+      "cell_identity <0-65535>",
+      "Set the Cell identity of this BTS\n" "Cell Identity\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int ci = atoi(argv[0]);
+
+	if (ci < 0 || ci > 0xffff) {
+		vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s",
+			ci, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->cell_identity = ci;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_lac,
+      cfg_bts_lac_cmd,
+      "location_area_code <0-65535>",
+      "Set the Location Area Code (LAC) of this BTS\n" "LAC\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int lac = atoi(argv[0]);
+
+	if (lac < 0 || lac > 0xffff) {
+		vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+		vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->location_area_code = lac;
+
+	return CMD_SUCCESS;
+}
+
+
+/* compatibility wrapper for old config files */
+DEFUN_HIDDEN(cfg_bts_tsc,
+      cfg_bts_tsc_cmd,
+      "training_sequence_code <0-7>",
+      "Set the Training Sequence Code (TSC) of this BTS\n" "TSC\n")
+{
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_bsic,
+      cfg_bts_bsic_cmd,
+      "base_station_id_code <0-63>",
+      "Set the Base Station Identity Code (BSIC) of this BTS\n"
+      "BSIC of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int bsic = atoi(argv[0]);
+
+	if (bsic < 0 || bsic > 0x3f) {
+		vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s",
+			bsic, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->bsic = bsic;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_unit_id,
+      cfg_bts_unit_id_cmd,
+      "ip.access unit_id <0-65534> <0-255>",
+      "Abis/IP specific options\n"
+      "Set the IPA BTS Unit ID\n"
+      "Unit ID (Site)\n"
+      "Unit ID (BTS)\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int site_id = atoi(argv[0]);
+	int bts_id = atoi(argv[1]);
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->ip_access.site_id = site_id;
+	bts->ip_access.bts_id = bts_id;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rsl_ip,
+      cfg_bts_rsl_ip_cmd,
+      "ip.access rsl-ip A.B.C.D",
+      "Abis/IP specific options\n"
+      "Set the IPA RSL IP Address of the BSC\n"
+      "Destination IP address for RSL connection\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct in_addr ia;
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	inet_aton(argv[0], &ia);
+	bts->ip_access.rsl_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+#define NOKIA_STR "Nokia *Site related commands\n"
+
+DEFUN(cfg_bts_nokia_site_skip_reset,
+      cfg_bts_nokia_site_skip_reset_cmd,
+      "nokia_site skip-reset (0|1)",
+      NOKIA_STR
+      "Skip the reset step during bootstrap process of this BTS\n"
+      "Do NOT skip the reset\n" "Skip the reset\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->type != GSM_BTS_TYPE_NOKIA_SITE) {
+		vty_out(vty, "%% BTS is not of Nokia *Site type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->nokia.skip_reset = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_nokia_site_no_loc_rel_cnf,
+      cfg_bts_nokia_site_no_loc_rel_cnf_cmd,
+      "nokia_site no-local-rel-conf (0|1)",
+      NOKIA_STR
+      "Do not wait for RELease CONFirm message when releasing channel locally\n"
+      "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!is_nokia_bts(bts)) {
+		vty_out(vty, "%% BTS is not of Nokia *Site type%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->nokia.no_loc_rel_cnf = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_nokia_site_bts_reset_timer_cnf,
+      cfg_bts_nokia_site_bts_reset_timer_cnf_cmd,
+      "nokia_site bts-reset-timer  <15-100>",
+      NOKIA_STR
+      "The amount of time (in sec.) between BTS_RESET is sent,\n"
+      "and the BTS is being bootstrapped.\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!is_nokia_bts(bts)) {
+		vty_out(vty, "%% BTS is not of Nokia *Site type%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->nokia.bts_reset_timer_cnf = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+#define OML_STR	"Organization & Maintenance Link\n"
+#define IPA_STR "A-bis/IP Specific Options\n"
+
+DEFUN(cfg_bts_stream_id,
+      cfg_bts_stream_id_cmd,
+      "oml ip.access stream_id <0-255> line E1_LINE",
+	OML_STR IPA_STR
+      "Set the ip.access Stream ID of the OML link of this BTS\n"
+      "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int stream_id = atoi(argv[0]), linenr = atoi(argv[1]);
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->oml_tei = stream_id;
+	/* This is used by e1inp_bind_ops callback for each BTS model. */
+	bts->oml_e1_link.e1_nr = linenr;
+
+	return CMD_SUCCESS;
+}
+
+#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n"
+
+DEFUN(cfg_bts_oml_e1,
+      cfg_bts_oml_e1_cmd,
+      "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+	OML_E1_STR
+      "E1/T1 line number to be used for OML\n"
+      "E1/T1 line number to be used for OML\n"
+      "E1/T1 timeslot to be used for OML\n"
+      "E1/T1 timeslot to be used for OML\n"
+      "E1/T1 sub-slot to be used for OML\n"
+      "Use E1/T1 sub-slot 0\n"
+      "Use E1/T1 sub-slot 1\n"
+      "Use E1/T1 sub-slot 2\n"
+      "Use E1/T1 sub-slot 3\n"
+      "Use full E1 slot 3\n"
+      )
+{
+	struct gsm_bts *bts = vty->index;
+
+	parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_oml_e1_tei,
+      cfg_bts_oml_e1_tei_cmd,
+      "oml e1 tei <0-63>",
+	OML_E1_STR
+      "Set the TEI to be used for OML\n"
+      "TEI Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->oml_tei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd,
+      "channel allocator (ascending|descending)",
+	"Channnel Allocator\n" "Channel Allocator\n"
+	"Allocate Timeslots and Transceivers in ascending order\n"
+	"Allocate Timeslots and Transceivers in descending order\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!strcmp(argv[0], "ascending"))
+		bts->chan_alloc_reverse = 0;
+	else
+		bts->chan_alloc_reverse = 1;
+
+	return CMD_SUCCESS;
+}
+
+#define RACH_STR "Random Access Control Channel\n"
+
+DEFUN(cfg_bts_rach_tx_integer,
+      cfg_bts_rach_tx_integer_cmd,
+      "rach tx integer <0-15>",
+	RACH_STR
+      "Set the raw tx integer value in RACH Control parameters IE\n"
+      "Set the raw tx integer value in RACH Control parameters IE\n"
+      "Raw tx integer value in RACH Control parameters IE\n")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_max_trans,
+      cfg_bts_rach_max_trans_cmd,
+      "rach max transmission (1|2|4|7)",
+	RACH_STR
+      "Set the maximum number of RACH burst transmissions\n"
+      "Set the maximum number of RACH burst transmissions\n"
+      "Maximum number of 1 RACH burst transmissions\n"
+      "Maximum number of 2 RACH burst transmissions\n"
+      "Maximum number of 4 RACH burst transmissions\n"
+      "Maximum number of 7 RACH burst transmissions\n")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+#define CD_STR "Channel Description\n"
+
+DEFUN(cfg_bts_chan_desc_att,
+      cfg_bts_chan_desc_att_cmd,
+      "channel-descrption attach (0|1)",
+	CD_STR
+      "Set if attachment is required\n"
+      "Attachment is NOT required\n"
+      "Attachment is required (standard)\n")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.chan_desc.att = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_chan_desc_bs_pa_mfrms,
+      cfg_bts_chan_desc_bs_pa_mfrms_cmd,
+      "channel-descrption bs-pa-mfrms <2-9>",
+	CD_STR
+      "Set number of multiframe periods for paging groups\n"
+      "Number of multiframe periods for paging groups\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int bs_pa_mfrms = atoi(argv[0]);
+
+	bts->si_common.chan_desc.bs_pa_mfrms = bs_pa_mfrms - 2;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_chan_desc_bs_ag_blks_res,
+      cfg_bts_chan_desc_bs_ag_blks_res_cmd,
+      "channel-descrption bs-ag-blks-res <0-7>",
+	CD_STR
+      "Set number of blocks reserved for access grant\n"
+      "Number of blocks reserved for access grant\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int bs_ag_blks_res = atoi(argv[0]);
+
+	bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res;
+	return CMD_SUCCESS;
+}
+
+#define NM_STR "Network Management\n"
+
+DEFUN(cfg_bts_rach_nm_b_thresh,
+      cfg_bts_rach_nm_b_thresh_cmd,
+      "rach nm busy threshold <0-255>",
+	RACH_STR NM_STR
+      "Set the NM Busy Threshold\n"
+      "Set the NM Busy Threshold\n"
+      "NM Busy Threshold in dB")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_b_thresh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_nm_ldavg,
+      cfg_bts_rach_nm_ldavg_cmd,
+      "rach nm load average <0-65535>",
+	RACH_STR NM_STR
+      "Set the NM Loadaverage Slots value\n"
+      "Set the NM Loadaverage Slots value\n"
+      "NM Loadaverage Slots value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_ldavg_slots = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd,
+      "cell barred (0|1)",
+      "Should this cell be barred from access?\n"
+      "Should this cell be barred from access?\n"
+      "Cell should NOT be barred\n"
+      "Cell should be barred\n")
+
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.rach_control.cell_bar = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd,
+      "rach emergency call allowed (0|1)",
+      RACH_STR
+      "Should this cell allow emergency calls?\n"
+      "Should this cell allow emergency calls?\n"
+      "Should this cell allow emergency calls?\n"
+      "Do NOT allow emergency calls\n"
+      "Allow emergency calls\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (atoi(argv[0]) == 0)
+		bts->si_common.rach_control.t2 |= 0x4;
+	else
+		bts->si_common.rach_control.t2 &= ~0x4;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_ac_class, cfg_bts_rach_ac_class_cmd,
+      "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)",
+      RACH_STR
+      "Set access control class\n"
+      "Access control class 0\n"
+      "Access control class 1\n"
+      "Access control class 2\n"
+      "Access control class 3\n"
+      "Access control class 4\n"
+      "Access control class 5\n"
+      "Access control class 6\n"
+      "Access control class 7\n"
+      "Access control class 8\n"
+      "Access control class 9\n"
+      "Access control class 11 for PLMN use\n"
+      "Access control class 12 for security services\n"
+      "Access control class 13 for public utilities (e.g. water/gas suppliers)\n"
+      "Access control class 14 for emergency services\n"
+      "Access control class 15 for PLMN staff\n"
+      "barred to use access control class\n"
+      "allowed to use access control class\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	uint8_t control_class;
+	uint8_t allowed = 0;
+
+	if (strcmp(argv[1], "allowed") == 0)
+		allowed = 1;
+
+	control_class = atoi(argv[0]);
+	if (control_class < 8)
+		if (allowed)
+			bts->si_common.rach_control.t3 &= ~(0x1 << control_class);
+		else
+			bts->si_common.rach_control.t3 |= (0x1 << control_class);
+	else
+		if (allowed)
+			bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8));
+		else
+			bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8));
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd,
+      "ms max power <0-40>",
+      "MS Options\n"
+      "Maximum transmit power of the MS\n"
+      "Maximum transmit power of the MS\n"
+      "Maximum transmit power of the MS in dBm")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->ms_max_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+#define CELL_STR "Cell Parameters\n"
+
+DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd,
+      "cell reselection hysteresis <0-14>",
+      CELL_STR "Cell re-selection parameters\n"
+      "Cell Re-Selection Hysteresis in dB\n"
+      "Cell Re-Selection Hysteresis in dB")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd,
+      "rxlev access min <0-63>",
+      "Minimum RxLev needed for cell access\n"
+      "Minimum RxLev needed for cell access\n"
+      "Minimum RxLev needed for cell access\n"
+      "Minimum RxLev needed for cell access (better than -110dBm)")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd,
+	"cell bar qualify (0|1)",
+	CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n"
+	"Set CBQ to 0\n" "Set CBQ to 1\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd,
+	"cell reselection offset <0-126>",
+	CELL_STR "Cell Re-Selection Parameters\n"
+	"Cell Re-Selection Offset (CRO) in dB\n"
+	"Cell Re-Selection Offset (CRO) in dB\n"
+	)
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd,
+	"temporary offset <0-60>",
+	"Cell selection temporary negative offset\n"
+	"Cell selection temporary negative offset\n"
+	"Cell selection temporary negative offset in dB")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd,
+	"temporary offset infinite",
+	"Cell selection temporary negative offset\n"
+	"Cell selection temporary negative offset\n"
+	"Sets cell selection temporary negative offset to infinity")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.temp_offs = 7;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd,
+	"penalty time <20-620>",
+	"Cell selection penalty time\n"
+	"Cell selection penalty time\n"
+	"Cell selection penalty time in seconds (by 20s increments)\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd,
+	"penalty time reserved",
+	"Cell selection penalty time\n"
+	"Cell selection penalty time\n"
+	"Set cell selection penalty time to reserved value 31, "
+		"(indicate that CELL_RESELECT_OFFSET is subtracted from C2 "
+		"and TEMPORARY_OFFSET is ignored)")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.penalty_time = 31;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_per_loc_upd, cfg_bts_per_loc_upd_cmd,
+      "periodic location update <6-1530>",
+      "Periodic Location Updating Interval\n"
+      "Periodic Location Updating Interval\n"
+      "Periodic Location Updating Interval\n"
+      "Periodic Location Updating Interval in Minutes\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.chan_desc.t3212 = atoi(argv[0]) / 6;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_per_loc_upd, cfg_bts_no_per_loc_upd_cmd,
+      "no periodic location update",
+      NO_STR
+      "Periodic Location Updating Interval\n"
+      "Periodic Location Updating Interval\n"
+      "Periodic Location Updating Interval\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.chan_desc.t3212 = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_radio_link_timeout, cfg_bts_radio_link_timeout_cmd,
+	"radio-link-timeout <4-64>",
+	"Radio link timeout criterion (BTS side)\n"
+	"Radio link timeout value (lost SACCH block)\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	gsm_bts_set_radio_link_timeout(bts, atoi(argv[0]));
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_radio_link_timeout_inf, cfg_bts_radio_link_timeout_inf_cmd,
+	"radio-link-timeout infinite",
+	"Radio link timeout criterion (BTS side)\n"
+	"Infinite Radio link timeout value (use only for BTS RF testing)\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
+		vty_out(vty, "%% infinite radio link timeout not supported by this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE);
+	gsm_bts_set_radio_link_timeout(bts, -1);
+
+	return CMD_SUCCESS;
+}
+
+#define GPRS_TEXT	"GPRS Packet Network\n"
+
+DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd,
+	"gprs cell bvci <2-65535>",
+	GPRS_TEXT
+	"GPRS Cell Settings\n"
+	"GPRS BSSGP VC Identifier\n"
+	"GPRS BSSGP VC Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.cell.bvci = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd,
+	"gprs nsei <0-65535>",
+	GPRS_TEXT
+	"GPRS NS Entity Identifier\n"
+	"GPRS NS Entity Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nse.nsei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \
+		"NSVC Logical Number\n"
+
+DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd,
+	"gprs nsvc <0-1> nsvci <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"NS Virtual Connection Identifier\n"
+	"GPRS NS VC Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].nsvci = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd,
+	"gprs nsvc <0-1> local udp port <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"GPRS NS Local UDP Port\n"
+	"GPRS NS Local UDP Port\n"
+	"GPRS NS Local UDP Port\n"
+	"GPRS NS Local UDP Port Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].local_port = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd,
+	"gprs nsvc <0-1> remote udp port <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"GPRS NS Remote UDP Port\n"
+	"GPRS NS Remote UDP Port\n"
+	"GPRS NS Remote UDP Port\n"
+	"GPRS NS Remote UDP Port Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].remote_port = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd,
+	"gprs nsvc <0-1> remote ip A.B.C.D",
+	GPRS_TEXT NSVC_TEXT
+	"GPRS NS Remote IP Address\n"
+	"GPRS NS Remote IP Address\n"
+	"GPRS NS Remote IP Address\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+	struct in_addr ia;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	inet_aton(argv[1], &ia);
+	bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pag_free, cfg_bts_pag_free_cmd,
+      "paging free <-1-1024>",
+      "Paging options\n"
+      "Only page when having a certain amount of free slots\n"
+      "amount of required free paging slots. -1 to disable\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->paging.free_chans_need = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd,
+	"gprs ns timer " NS_TIMERS " <0-255>",
+	GPRS_TEXT "Network Service\n"
+	"Network Service Timer\n"
+	NS_TIMERS_HELP "Timer Value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer))
+		return CMD_WARNING;
+
+	bts->gprs.nse.timer[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)"
+#define BSSGP_TIMERS_HELP	\
+	"Tbvc-block timeout\n"			\
+	"Tbvc-block retries\n"			\
+	"Tbvc-unblock retries\n"		\
+	"Tbvcc-reset timeout\n"			\
+	"Tbvc-reset retries\n"			\
+	"Tbvc-suspend timeout\n"		\
+	"Tbvc-suspend retries\n"		\
+	"Tbvc-resume timeout\n"			\
+	"Tbvc-resume retries\n"			\
+	"Tbvc-capa-update timeout\n"		\
+	"Tbvc-capa-update retries\n"
+
+DEFUN(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd,
+	"gprs cell timer " BSSGP_TIMERS " <0-255>",
+	GPRS_TEXT "Cell / BSSGP\n"
+	"Cell/BSSGP Timer\n"
+	BSSGP_TIMERS_HELP "Timer Value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer))
+		return CMD_WARNING;
+
+	bts->gprs.cell.timer[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd,
+	"gprs routing area <0-255>",
+	GPRS_TEXT
+	"GPRS Routing Area Code\n"
+	"GPRS Routing Area Code\n"
+	"GPRS Routing Area Code\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.rac = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_ctrl_ack, cfg_bts_gprs_ctrl_ack_cmd,
+	"gprs control-ack-type-rach", GPRS_TEXT
+	"Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
+	"four access bursts format instead of default RLC/MAC control block\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.ctrl_ack_type_use_block = false;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_bts_gprs_ctrl_ack, cfg_no_bts_gprs_ctrl_ack_cmd,
+	"no gprs control-ack-type-rach", NO_STR GPRS_TEXT
+	"Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
+	"four access bursts format instead of default RLC/MAC control block\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.ctrl_ack_type_use_block = true;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_net_ctrl_ord, cfg_bts_gprs_net_ctrl_ord_cmd,
+	"gprs network-control-order (nc0|nc1|nc2)",
+	GPRS_TEXT
+	"GPRS Network Control Order\n"
+	"MS controlled cell re-selection, no measurement reporting\n"
+	"MS controlled cell re-selection, MS sends measurement reports\n"
+	"Network controlled cell re-selection, MS sends measurement reports\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.net_ctrl_ord = atoi(argv[0] + 2);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd,
+	"gprs mode (none|gprs|egprs)",
+	GPRS_TEXT
+	"GPRS Mode for this BTS\n"
+	"GPRS Disabled on this BTS\n"
+	"GPRS Enabled on this BTS\n"
+	"EGPRS (EDGE) Enabled on this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL);
+
+	if (!bts_gprs_mode_is_compat(bts, mode)) {
+		vty_out(vty, "This BTS type does not support %s%s", argv[0],
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.mode = mode;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_11bit_rach_support_for_egprs,
+	cfg_bts_gprs_11bit_rach_support_for_egprs_cmd,
+	"gprs 11bit_rach_support_for_egprs (0|1)",
+	GPRS_TEXT "11 bit RACH options\n"
+	"Disable 11 bit RACH for EGPRS\n"
+	"Enable 11 bit RACH for EGPRS")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->gprs.supports_egprs_11bit_rach = atoi(argv[0]);
+
+	if (bts->gprs.supports_egprs_11bit_rach > 1) {
+		vty_out(vty, "Error in RACH type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if ((bts->gprs.mode == BTS_GPRS_NONE) &&
+		(bts->gprs.supports_egprs_11bit_rach == 1)) {
+		vty_out(vty, "Error:gprs mode is none and 11bit rach is"
+			" enabled%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define SI_TEXT		"System Information Messages\n"
+#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)"
+#define SI_TYPE_HELP 	"System Information Type 1\n"	\
+			"System Information Type 2\n"	\
+			"System Information Type 3\n"	\
+			"System Information Type 4\n"	\
+			"System Information Type 5\n"	\
+			"System Information Type 6\n"	\
+			"System Information Type 7\n"	\
+			"System Information Type 8\n"	\
+			"System Information Type 9\n"	\
+			"System Information Type 10\n"	\
+			"System Information Type 13\n"	\
+			"System Information Type 16\n"	\
+			"System Information Type 17\n"	\
+			"System Information Type 18\n"	\
+			"System Information Type 19\n"	\
+			"System Information Type 20\n"	\
+			"System Information Type 2bis\n"	\
+			"System Information Type 2ter\n"	\
+			"System Information Type 2quater\n"	\
+			"System Information Type 5bis\n"	\
+			"System Information Type 5ter\n"
+
+DEFUN(cfg_bts_si_mode, cfg_bts_si_mode_cmd,
+	"system-information " SI_TYPE_TEXT " mode (static|computed)",
+	SI_TEXT SI_TYPE_HELP
+	"System Information Mode\n"
+	"Static user-specified\n"
+	"Dynamic, BSC-computed\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int type;
+
+	type = get_string_value(osmo_sitype_strs, argv[0]);
+	if (type < 0) {
+		vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[1], "static"))
+		bts->si_mode_static |= (1 << type);
+	else
+		bts->si_mode_static &= ~(1 << type);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si_static, cfg_bts_si_static_cmd,
+	"system-information " SI_TYPE_TEXT " static HEXSTRING",
+	SI_TEXT SI_TYPE_HELP
+	"Static System Information filling\n"
+	"Static user-specified SI content in HEX notation\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc, type;
+
+	type = get_string_value(osmo_sitype_strs, argv[0]);
+	if (type < 0) {
+		vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!(bts->si_mode_static & (1 << type))) {
+		vty_out(vty, "SI Type %s is not configured in static mode%s",
+			get_value_string(osmo_sitype_strs, type), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Fill buffer with padding pattern */
+	memset(GSM_BTS_SI(bts, type), 0x2b, GSM_MACBLOCK_LEN);
+
+	/* Parse the user-specified SI in hex format, [partially] overwriting padding */
+	rc = osmo_hexparse(argv[1], GSM_BTS_SI(bts, type), GSM_MACBLOCK_LEN);
+	if (rc < 0 || rc > GSM_MACBLOCK_LEN) {
+		vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Mark this SI as present */
+	bts->si_valid |= (1 << type);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_early_cm, cfg_bts_early_cm_cmd,
+	"early-classmark-sending (allowed|forbidden)",
+	"Early Classmark Sending\n"
+	"Early Classmark Sending is allowed\n"
+	"Early Classmark Sending is forbidden\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!strcmp(argv[0], "allowed"))
+		bts->early_classmark_allowed = true;
+	else
+		bts->early_classmark_allowed = false;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_early_cm_3g, cfg_bts_early_cm_3g_cmd,
+	"early-classmark-sending-3g (allowed|forbidden)",
+	"3G Early Classmark Sending\n"
+	"3G Early Classmark Sending is allowed\n"
+	"3G Early Classmark Sending is forbidden\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!strcmp(argv[0], "allowed"))
+		bts->early_classmark_allowed_3g = true;
+	else
+		bts->early_classmark_allowed_3g = false;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd,
+	"neighbor-list mode (automatic|manual|manual-si5)",
+	"Neighbor List\n" "Mode of Neighbor List generation\n"
+	"Automatically from all BTS in this OpenBSC\n" "Manual\n"
+	"Manual with different lists for SI2 and SI5\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int mode = get_string_value(bts_neigh_mode_strs, argv[0]);
+
+	switch (mode) {
+	case NL_MODE_MANUAL_SI5SEP:
+	case NL_MODE_MANUAL:
+		/* make sure we clear the current list when switching to
+		 * manual mode */
+		if (bts->neigh_list_manual_mode == 0)
+			memset(&bts->si_common.data.neigh_list, 0,
+				sizeof(bts->si_common.data.neigh_list));
+		break;
+	default:
+		break;
+	}
+
+	bts->neigh_list_manual_mode = mode;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_neigh, cfg_bts_neigh_cmd,
+	"neighbor-list (add|del) arfcn <0-1023>",
+	"Neighbor List\n" "Add to manual neighbor list\n"
+	"Delete from manual neighbor list\n" "ARFCN of neighbor\n"
+	"ARFCN of neighbor\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct bitvec *bv = &bts->si_common.neigh_list;
+	uint16_t arfcn = atoi(argv[1]);
+
+	if (!bts->neigh_list_manual_mode) {
+		vty_out(vty, "%% Cannot configure neighbor list in "
+			"automatic mode%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "add"))
+		bitvec_set_bit_pos(bv, arfcn, 1);
+	else
+		bitvec_set_bit_pos(bv, arfcn, 0);
+
+	return CMD_SUCCESS;
+}
+
+/* help text should be kept in sync with EARFCN_*_INVALID defines */
+DEFUN(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd,
+      "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> "
+      "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>",
+      "SI2quater Neighbor List\n" "SI2quater Neighbor List\n"
+      "Add to manual SI2quater neighbor list\n"
+      "EARFCN of neighbor\n" "EARFCN of neighbor\n"
+      "threshold high bits\n" "threshold high bits\n"
+      "threshold low bits\n" "threshold low bits (32 means NA)\n"
+      "priority\n" "priority (8 means NA)\n"
+      "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n"
+      "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	uint16_t arfcn = atoi(argv[0]);
+	uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]),
+		prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]);
+	int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas);
+
+	switch (r) {
+	case 1:
+		vty_out(vty, "Warning: multiple threshold-high are not supported, overriding with %u%s",
+			thresh_hi, VTY_NEWLINE);
+		break;
+	case EARFCN_THRESH_LOW_INVALID:
+		vty_out(vty, "Warning: multiple threshold-low are not supported, overriding with %u%s",
+			thresh_lo, VTY_NEWLINE);
+		break;
+	case EARFCN_QRXLV_INVALID + 1:
+		vty_out(vty, "Warning: multiple QRXLEVMIN are not supported, overriding with %u%s",
+			qrx, VTY_NEWLINE);
+		break;
+	case EARFCN_PRIO_INVALID:
+		vty_out(vty, "Warning: multiple priorities are not supported, overriding with %u%s",
+			prio, VTY_NEWLINE);
+		break;
+	default:
+		if (r < 0) {
+			vty_out(vty, "Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	if (si2q_num(bts) <= SI2Q_MAX_NUM)
+		return CMD_SUCCESS;
+
+	vty_out(vty, "Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s",
+		bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE);
+	osmo_earfcn_del(e, arfcn);
+
+	return CMD_WARNING;
+}
+
+DEFUN(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd,
+	"si2quater neighbor-list del earfcn <0-65535>",
+	"SI2quater Neighbor List\n"
+	"SI2quater Neighbor List\n"
+	"Delete from SI2quater manual neighbor list\n"
+	"EARFCN of neighbor\n"
+	"EARFCN\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	uint16_t arfcn = atoi(argv[0]);
+	int r = osmo_earfcn_del(e, arfcn);
+	if (r < 0) {
+		vty_out(vty, "Unable to delete arfcn %u: %s%s", arfcn,
+			strerror(-r), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si2quater_uarfcn_add, cfg_bts_si2quater_uarfcn_add_cmd,
+      "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>",
+      "SI2quater Neighbor List\n"
+      "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n"
+      "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n"
+      "diversity bit\n")
+{
+	struct gsm_bts *bts = vty->index;
+	uint16_t arfcn = atoi(argv[0]), scramble = atoi(argv[1]);
+
+	switch(bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) {
+	case -ENOMEM:
+		vty_out(vty, "Unable to add UARFCN: max number of UARFCNs (%u) reached%s", MAX_EARFCN_LIST, VTY_NEWLINE);
+		return CMD_WARNING;
+	case -ENOSPC:
+		vty_out(vty, "Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s",
+			arfcn, scramble, VTY_NEWLINE);
+		return CMD_WARNING;
+	case -EADDRINUSE:
+		vty_out(vty, "Unable to add UARFCN: (%u, %u) is already added%s", arfcn, scramble, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si2quater_uarfcn_del, cfg_bts_si2quater_uarfcn_del_cmd,
+      "si2quater neighbor-list del uarfcn <0-16383> <0-511>",
+      "SI2quater Neighbor List\n"
+      "SI2quater Neighbor List\n"
+      "Delete from SI2quater manual neighbor list\n"
+      "UARFCN of neighbor\n"
+      "UARFCN\n"
+      "scrambling code\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts_uarfcn_del(bts, atoi(argv[0]), atoi(argv[1])) < 0) {
+		vty_out(vty, "Unable to delete uarfcn: pair not found%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd,
+	"si5 neighbor-list (add|del) arfcn <0-1023>",
+	"SI5 Neighbor List\n"
+	"SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n"
+	"Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n"
+	"ARFCN of neighbor\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct bitvec *bv = &bts->si_common.si5_neigh_list;
+	uint16_t arfcn = atoi(argv[1]);
+
+	if (!bts->neigh_list_manual_mode) {
+		vty_out(vty, "%% Cannot configure neighbor list in "
+			"automatic mode%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "add"))
+		bitvec_set_bit_pos(bv, arfcn, 1);
+	else
+		bitvec_set_bit_pos(bv, arfcn, 0);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd,
+	"pcu-socket PATH",
+	"PCU Socket Path for using OsmoPCU co-located with BSC (legacy BTS)\n"
+	"Path in the file system for the unix-domain PCU socket\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc;
+
+	osmo_talloc_replace_string(bts, &bts->pcu_sock_path, argv[0]);
+	pcu_sock_exit(bts);
+	rc = pcu_sock_init(bts->pcu_sock_path, bts);
+	if (rc < 0) {
+		vty_out(vty, "%% Error creating PCU socket `%s' for BTS %u%s",
+			bts->pcu_sock_path, bts->nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_acc_ramping,
+      cfg_bts_acc_ramping_cmd,
+      "access-control-class-ramping",
+      "Enable Access Control Class ramping\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!acc_ramp_is_enabled(&bts->acc_ramp))
+		acc_ramp_set_enabled(&bts->acc_ramp, true);
+
+	/*
+	 * ACC ramping takes effect either when the BTS reconnects RSL,
+	 * or when RF administrative state changes to 'unlocked'.
+	 */
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_acc_ramping, cfg_bts_no_acc_ramping_cmd,
+      "no access-control-class-ramping",
+      NO_STR
+      "Disable Access Control Class ramping\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (acc_ramp_is_enabled(&bts->acc_ramp)) {
+		acc_ramp_abort(&bts->acc_ramp);
+		acc_ramp_set_enabled(&bts->acc_ramp, false);
+		gsm_bts_set_system_infos(bts);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_acc_ramping_step_interval,
+      cfg_bts_acc_ramping_step_interval_cmd,
+      "access-control-class-ramping-step-interval (<"
+      OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MIN) "-"
+      OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MAX) ">|dynamic)",
+      "Configure Access Control Class ramping step interval\n"
+      "Set a fixed step interval (in seconds)\n"
+      "Use dynamic step interval based on BTS channel load\n")
+{
+	struct gsm_bts *bts = vty->index;
+	bool dynamic = (strcmp(argv[0], "dynamic") == 0);
+	int error;
+
+	if (dynamic) {
+		acc_ramp_set_step_interval_dynamic(&bts->acc_ramp);
+		return CMD_SUCCESS;
+	}
+
+	error = acc_ramp_set_step_interval(&bts->acc_ramp, atoi(argv[0]));
+	if (error != 0) {
+		if (error == -ERANGE)
+			vty_out(vty, "Unable to set ACC ramp step interval: value out of range%s", VTY_NEWLINE);
+		else
+			vty_out(vty, "Unable to set ACC ramp step interval: unknown error%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_acc_ramping_step_size,
+      cfg_bts_acc_ramping_step_size_cmd,
+      "access-control-class-ramping-step-size (<"
+      OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MIN) "-"
+      OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MAX) ">)",
+      "Configure Access Control Class ramping step size\n"
+      "Set the number of Access Control Classes to enable per ramping step\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int error;
+
+	error = acc_ramp_set_step_size(&bts->acc_ramp, atoi(argv[0]));
+	if (error != 0) {
+		if (error == -ERANGE)
+			vty_out(vty, "Unable to set ACC ramp step size: value out of range%s", VTY_NEWLINE);
+		else
+			vty_out(vty, "Unable to set ACC ramp step size: unknown error%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n"
+
+DEFUN(cfg_bts_excl_rf_lock,
+      cfg_bts_excl_rf_lock_cmd,
+      "rf-lock-exclude",
+      EXCL_RFLOCK_STR)
+{
+	struct gsm_bts *bts = vty->index;
+	bts->excl_from_rf_lock = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_excl_rf_lock,
+      cfg_bts_no_excl_rf_lock_cmd,
+      "no rf-lock-exclude",
+      NO_STR EXCL_RFLOCK_STR)
+{
+	struct gsm_bts *bts = vty->index;
+	bts->excl_from_rf_lock = 0;
+	return CMD_SUCCESS;
+}
+
+#define FORCE_COMB_SI_STR "Force the generation of a single SI (no ter/bis)\n"
+
+DEFUN(cfg_bts_force_comb_si,
+      cfg_bts_force_comb_si_cmd,
+      "force-combined-si",
+      FORCE_COMB_SI_STR)
+{
+	struct gsm_bts *bts = vty->index;
+	bts->force_combined_si = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_force_comb_si,
+      cfg_bts_no_force_comb_si_cmd,
+      "no force-combined-si",
+      NO_STR FORCE_COMB_SI_STR)
+{
+	struct gsm_bts *bts = vty->index;
+	bts->force_combined_si = 0;
+	return CMD_SUCCESS;
+}
+
+static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[])
+{
+	struct gsm_bts *bts = vty->index;
+	struct bts_codec_conf *codec = &bts->codec;
+	int i;
+
+	codec->hr = 0;
+	codec->efr = 0;
+	codec->amr = 0;
+	for (i = 0; i < argc; i++) {
+		if (!strcmp(argv[i], "hr"))
+			codec->hr = 1;
+		if (!strcmp(argv[i], "efr"))
+			codec->efr = 1;
+		if (!strcmp(argv[i], "amr"))
+			codec->amr = 1;
+	}
+}
+
+#define CODEC_PAR_STR	" (hr|efr|amr)"
+#define CODEC_HELP_STR	"Half Rate\n" \
+			"Enhanced Full Rate\nAdaptive Multirate\n"
+
+DEFUN(cfg_bts_codec0, cfg_bts_codec0_cmd,
+	"codec-support fr",
+	"Codec Support settings\nFullrate\n")
+{
+	_get_codec_from_arg(vty, 0, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec1, cfg_bts_codec1_cmd,
+	"codec-support fr" CODEC_PAR_STR,
+	"Codec Support settings\nFullrate\n"
+	CODEC_HELP_STR)
+{
+	_get_codec_from_arg(vty, 1, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec2, cfg_bts_codec2_cmd,
+	"codec-support fr" CODEC_PAR_STR CODEC_PAR_STR,
+	"Codec Support settings\nFullrate\n"
+	CODEC_HELP_STR CODEC_HELP_STR)
+{
+	_get_codec_from_arg(vty, 2, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec3, cfg_bts_codec3_cmd,
+	"codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
+	"Codec Support settings\nFullrate\n"
+	CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+{
+	_get_codec_from_arg(vty, 3, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec4, cfg_bts_codec4_cmd,
+	"codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
+	"Codec Support settings\nFullrate\n"
+	CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+{
+	_get_codec_from_arg(vty, 4, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_depends_on, cfg_bts_depends_on_cmd,
+	"depends-on-bts <0-255>",
+	"This BTS can only be started if another one is up\n" "BTS Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct gsm_bts *other_bts;
+	int dep = atoi(argv[0]);
+
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "This feature is only available for IP systems.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	other_bts = gsm_bts_num(bts->network, dep);
+	if (!other_bts || !is_ipaccess_bts(other_bts)) {
+		vty_out(vty, "This feature is only available for IP systems.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (dep >= bts->nr) {
+		vty_out(vty, "%%Need to depend on an already declared unit.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts_depend_mark(bts, dep);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd,
+	"depeneds-on-bts <0-255>",
+	NO_STR "This BTS can only be started if another one is up\n"
+	"BTS Number\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int dep = atoi(argv[0]);
+
+	bts_depend_clear(bts, dep);
+	return CMD_SUCCESS;
+}
+
+#define AMR_TEXT "Adaptive Multi Rate settings\n"
+#define AMR_MODE_TEXT "Codec modes to use with AMR codec\n"
+#define AMR_START_TEXT "Initial codec to use with AMR\n" \
+	"Automatically\nFirst codec\nSecond codec\nThird codec\nFourth codec\n"
+#define AMR_TH_TEXT "AMR threshold between codecs\nMS side\nBTS side\n"
+#define AMR_HY_TEXT "AMR hysteresis between codecs\nMS side\nBTS side\n"
+
+static void get_amr_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+	struct gsm_bts *bts = vty->index;
+	struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+	struct gsm48_multi_rate_conf *mr_conf =
+				(struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+	int i;
+
+	mr->gsm48_ie[1] = 0;
+	for (i = 0; i < argc; i++)
+		mr->gsm48_ie[1] |= 1 << atoi(argv[i]);
+	mr_conf->icmi = 0;
+}
+
+static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+	struct gsm_bts *bts = vty->index;
+	struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+	struct amr_mode *modes;
+	int i;
+
+	modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
+	for (i = 0; i < argc - 1; i++)
+		modes[i].threshold = atoi(argv[i + 1]);
+}
+
+static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+	struct gsm_bts *bts = vty->index;
+	struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+	struct amr_mode *modes;
+	int i;
+
+	modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
+	for (i = 0; i < argc - 1; i++)
+		modes[i].hysteresis = atoi(argv[i + 1]);
+}
+
+static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full)
+{
+	struct gsm_bts *bts = vty->index;
+	struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+	struct gsm48_multi_rate_conf *mr_conf =
+				(struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+	int num = 0, i;
+
+	for (i = 0; i < ((full) ? 8 : 6); i++) {
+		if ((mr->gsm48_ie[1] & (1 << i))) {
+			num++;
+		}
+	}
+
+	if (argv[0][0] == 'a' || num == 0)
+		mr_conf->icmi = 0;
+	else {
+		mr_conf->icmi = 1;
+		if (num < atoi(argv[0]))
+			mr_conf->smod = num - 1;
+		else
+			mr_conf->smod = atoi(argv[0]) - 1;
+	}
+}
+
+#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)"
+#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \
+	"10,2k\n12,2k\n"
+
+#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)"
+#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n"
+
+#define	AMR_TH_HELP_STR "Threshold between codec 1 and 2\n"
+#define	AMR_HY_HELP_STR "Hysteresis between codec 1 and 2\n"
+
+DEFUN(cfg_bts_amr_fr_modes1, cfg_bts_amr_fr_modes1_cmd,
+	"amr tch-f modes" AMR_TCHF_PAR_STR,
+	AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+	AMR_TCHF_HELP_STR)
+{
+	get_amr_from_arg(vty, 1, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_modes2, cfg_bts_amr_fr_modes2_cmd,
+	"amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+	AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+	AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+	get_amr_from_arg(vty, 2, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_modes3, cfg_bts_amr_fr_modes3_cmd,
+	"amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+	AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+	AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+	get_amr_from_arg(vty, 3, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_modes4, cfg_bts_amr_fr_modes4_cmd,
+	"amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+	AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+	AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+	get_amr_from_arg(vty, 4, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_start_mode, cfg_bts_amr_fr_start_mode_cmd,
+	"amr tch-f start-mode (auto|1|2|3|4)",
+	AMR_TEXT "Full Rate\n" AMR_START_TEXT)
+{
+	get_amr_start_from_arg(vty, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_thres1, cfg_bts_amr_fr_thres1_cmd,
+	"amr tch-f threshold (ms|bts) <0-63>",
+	AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+	AMR_TH_HELP_STR)
+{
+	get_amr_th_from_arg(vty, 2, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_thres2, cfg_bts_amr_fr_thres2_cmd,
+	"amr tch-f threshold (ms|bts) <0-63> <0-63>",
+	AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+	AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+	get_amr_th_from_arg(vty, 3, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_thres3, cfg_bts_amr_fr_thres3_cmd,
+	"amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>",
+	AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+	AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+	get_amr_th_from_arg(vty, 4, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_hyst1, cfg_bts_amr_fr_hyst1_cmd,
+	"amr tch-f hysteresis (ms|bts) <0-15>",
+	AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+	AMR_HY_HELP_STR)
+{
+	get_amr_hy_from_arg(vty, 2, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_hyst2, cfg_bts_amr_fr_hyst2_cmd,
+	"amr tch-f hysteresis (ms|bts) <0-15> <0-15>",
+	AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+	AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+	get_amr_hy_from_arg(vty, 3, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_hyst3, cfg_bts_amr_fr_hyst3_cmd,
+	"amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>",
+	AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+	AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+	get_amr_hy_from_arg(vty, 4, argv, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes1, cfg_bts_amr_hr_modes1_cmd,
+	"amr tch-h modes" AMR_TCHH_PAR_STR,
+	AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+	AMR_TCHH_HELP_STR)
+{
+	get_amr_from_arg(vty, 1, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes2, cfg_bts_amr_hr_modes2_cmd,
+	"amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+	AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+	AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+	get_amr_from_arg(vty, 2, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes3, cfg_bts_amr_hr_modes3_cmd,
+	"amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+	AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+	AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+	get_amr_from_arg(vty, 3, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes4, cfg_bts_amr_hr_modes4_cmd,
+	"amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+	AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+	AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+	get_amr_from_arg(vty, 4, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_start_mode, cfg_bts_amr_hr_start_mode_cmd,
+	"amr tch-h start-mode (auto|1|2|3|4)",
+	AMR_TEXT "Half Rate\n" AMR_START_TEXT)
+{
+	get_amr_start_from_arg(vty, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_thres1, cfg_bts_amr_hr_thres1_cmd,
+	"amr tch-h threshold (ms|bts) <0-63>",
+	AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+	AMR_TH_HELP_STR)
+{
+	get_amr_th_from_arg(vty, 2, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_thres2, cfg_bts_amr_hr_thres2_cmd,
+	"amr tch-h threshold (ms|bts) <0-63> <0-63>",
+	AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+	AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+	get_amr_th_from_arg(vty, 3, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_thres3, cfg_bts_amr_hr_thres3_cmd,
+	"amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>",
+	AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+	AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+	get_amr_th_from_arg(vty, 4, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_hyst1, cfg_bts_amr_hr_hyst1_cmd,
+	"amr tch-h hysteresis (ms|bts) <0-15>",
+	AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+	AMR_HY_HELP_STR)
+{
+	get_amr_hy_from_arg(vty, 2, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_hyst2, cfg_bts_amr_hr_hyst2_cmd,
+	"amr tch-h hysteresis (ms|bts) <0-15> <0-15>",
+	AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+	AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+	get_amr_hy_from_arg(vty, 3, argv, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_hyst3, cfg_bts_amr_hr_hyst3_cmd,
+	"amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>",
+	AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+	AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+	get_amr_hy_from_arg(vty, 4, argv, 0);
+	return CMD_SUCCESS;
+}
+
+#define TRX_TEXT "Radio Transceiver\n"
+
+/* per TRX configuration */
+DEFUN(cfg_trx,
+      cfg_trx_cmd,
+      "trx <0-255>",
+	TRX_TEXT
+      "Select a TRX to configure")
+{
+	int trx_nr = atoi(argv[0]);
+	struct gsm_bts *bts = vty->index;
+	struct gsm_bts_trx *trx;
+
+	if (trx_nr > bts->num_trx) {
+		vty_out(vty, "%% The next unused TRX number in this BTS is %u%s",
+			bts->num_trx, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (trx_nr == bts->num_trx) {
+		/* we need to allocate a new one */
+		trx = gsm_bts_trx_alloc(bts);
+	} else
+		trx = gsm_bts_trx_num(bts, trx_nr);
+
+	if (!trx)
+		return CMD_WARNING;
+
+	vty->index = trx;
+	vty->index_sub = &trx->description;
+	vty->node = TRX_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_arfcn,
+      cfg_trx_arfcn_cmd,
+      "arfcn <0-1023>",
+      "Set the ARFCN for this TRX\n"
+      "Absolute Radio Frequency Channel Number\n")
+{
+	int arfcn = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+
+	/* FIXME: check if this ARFCN is supported by this TRX */
+
+	trx->arfcn = arfcn;
+
+	/* FIXME: patch ARFCN into SYSTEM INFORMATION */
+	/* FIXME: use OML layer to update the ARFCN */
+	/* FIXME: use RSL layer to update SYSTEM INFORMATION */
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power,
+      cfg_trx_nominal_power_cmd,
+      "nominal power <0-100>",
+      "Nominal TRX RF Power in dBm\n"
+      "Nominal TRX RF Power in dBm\n"
+      "Nominal TRX RF Power in dBm\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	trx->nominal_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_max_power_red,
+      cfg_trx_max_power_red_cmd,
+      "max_power_red <0-100>",
+      "Reduction of maximum BS RF Power (relative to nominal power)\n"
+      "Reduction of maximum BS RF Power in dB\n")
+{
+	int maxpwr_r = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+	int upper_limit = 24;	/* default 12.21 max power red. */
+
+	/* FIXME: check if our BTS type supports more than 12 */
+	if (maxpwr_r < 0 || maxpwr_r > upper_limit) {
+		vty_out(vty, "%% Power %d dB is not in the valid range%s",
+			maxpwr_r, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (maxpwr_r & 1) {
+		vty_out(vty, "%% Power %d dB is not an even value%s",
+			maxpwr_r, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	trx->max_power_red = maxpwr_r;
+
+	/* FIXME: make sure we update this using OML */
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rsl_e1,
+      cfg_trx_rsl_e1_cmd,
+      "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "RSL Parameters\n"
+      "E1/T1 interface to be used for RSL\n"
+      "E1/T1 interface to be used for RSL\n"
+      "E1/T1 Line Number to be used for RSL\n"
+      "E1/T1 Timeslot to be used for RSL\n"
+      "E1/T1 Timeslot to be used for RSL\n"
+      "E1/T1 Sub-slot to be used for RSL\n"
+      "E1/T1 Sub-slot 0 is to be used for RSL\n"
+      "E1/T1 Sub-slot 1 is to be used for RSL\n"
+      "E1/T1 Sub-slot 2 is to be used for RSL\n"
+      "E1/T1 Sub-slot 3 is to be used for RSL\n"
+      "E1/T1 full timeslot is to be used for RSL\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rsl_e1_tei,
+      cfg_trx_rsl_e1_tei_cmd,
+      "rsl e1 tei <0-63>",
+      "RSL Parameters\n"
+      "Set the TEI to be used for RSL\n"
+      "Set the TEI to be used for RSL\n"
+      "TEI to be used for RSL\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	trx->rsl_tei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rf_locked,
+      cfg_trx_rf_locked_cmd,
+      "rf_locked (0|1)",
+      "Set or unset the RF Locking (Turn off RF of the TRX)\n"
+      "TRX is NOT RF locked (active)\n"
+      "TRX is RF locked (turned off)\n")
+{
+	int locked = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+
+	gsm_trx_lock_rf(trx, locked, "vty");
+	return CMD_SUCCESS;
+}
+
+/* per TS configuration */
+DEFUN(cfg_ts,
+      cfg_ts_cmd,
+      "timeslot <0-7>",
+      "Select a Timeslot to configure\n"
+      "Timeslot number\n")
+{
+	int ts_nr = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+	struct gsm_bts_trx_ts *ts;
+
+	if (ts_nr >= TRX_NR_TS) {
+		vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s",
+			TRX_NR_TS, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts = &trx->ts[ts_nr];
+
+	vty->index = ts;
+	vty->node = TS_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_pchan,
+      cfg_ts_pchan_cmd,
+      "phys_chan_config PCHAN", /* dynamically generated! */
+      "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int pchanc;
+
+	pchanc = gsm_pchan_parse(argv[0]);
+	if (pchanc < 0)
+		return CMD_WARNING;
+
+	ts->pchan = pchanc;
+
+	return CMD_SUCCESS;
+}
+
+/* used for backwards compatibility with old config files that still
+ * have uppercase pchan type names */
+DEFUN_HIDDEN(cfg_ts_pchan_compat,
+      cfg_ts_pchan_compat_cmd,
+      "phys_chan_config PCHAN",
+      "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int pchanc;
+
+	pchanc = gsm_pchan_parse(argv[0]);
+	if (pchanc < 0)
+		return CMD_WARNING;
+
+	ts->pchan = pchanc;
+
+	return CMD_SUCCESS;
+}
+
+
+
+DEFUN(cfg_ts_tsc,
+      cfg_ts_tsc_cmd,
+      "training_sequence_code <0-7>",
+      "Training Sequence Code of the Timeslot\n" "TSC\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	if (!gsm_btsmodel_has_feature(ts->trx->bts->model, BTS_FEAT_MULTI_TSC)) {
+		vty_out(vty, "%% This BTS does not support a TSC != BCC, "
+			"falling back to BCC%s", VTY_NEWLINE);
+		ts->tsc = -1;
+		return CMD_WARNING;
+	}
+
+	ts->tsc = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+#define HOPPING_STR "Configure frequency hopping\n"
+
+DEFUN(cfg_ts_hopping,
+      cfg_ts_hopping_cmd,
+      "hopping enabled (0|1)",
+	HOPPING_STR "Enable or disable frequency hopping\n"
+      "Disable frequency hopping\n" "Enable frequency hopping\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int enabled = atoi(argv[0]);
+
+	if (enabled && !gsm_btsmodel_has_feature(ts->trx->bts->model, BTS_FEAT_HOPPING)) {
+		vty_out(vty, "BTS model does not support hopping%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts->hopping.enabled = enabled;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_hsn,
+      cfg_ts_hsn_cmd,
+      "hopping sequence-number <0-63>",
+	HOPPING_STR
+      "Which hopping sequence to use for this channel\n"
+      "Hopping Sequence Number (HSN)\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	ts->hopping.hsn = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_maio,
+      cfg_ts_maio_cmd,
+      "hopping maio <0-63>",
+	HOPPING_STR
+      "Which hopping MAIO to use for this channel\n"
+      "Mobile Allocation Index Offset (MAIO)\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	ts->hopping.maio = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_arfcn_add,
+      cfg_ts_arfcn_add_cmd,
+      "hopping arfcn add <0-1023>",
+	HOPPING_STR "Configure hopping ARFCN list\n"
+      "Add an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int arfcn = atoi(argv[0]);
+
+	bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_arfcn_del,
+      cfg_ts_arfcn_del_cmd,
+      "hopping arfcn del <0-1023>",
+	HOPPING_STR "Configure hopping ARFCN list\n"
+      "Delete an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int arfcn = atoi(argv[0]);
+
+	bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_e1_subslot,
+      cfg_ts_e1_subslot_cmd,
+      "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "E1/T1 channel connected to this on-air timeslot\n"
+      "E1/T1 channel connected to this on-air timeslot\n"
+      "E1/T1 line connected to this on-air timeslot\n"
+      "E1/T1 timeslot connected to this on-air timeslot\n"
+      "E1/T1 timeslot connected to this on-air timeslot\n"
+      "E1/T1 sub-slot connected to this on-air timeslot\n"
+      "E1/T1 sub-slot 0 connected to this on-air timeslot\n"
+      "E1/T1 sub-slot 1 connected to this on-air timeslot\n"
+      "E1/T1 sub-slot 2 connected to this on-air timeslot\n"
+      "E1/T1 sub-slot 3 connected to this on-air timeslot\n"
+      "Full E1/T1 timeslot connected to this on-air timeslot\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
+{
+	vty_out(vty, "Channel Requests        : %"PRIu64" total, %"PRIu64" no channel%s",
+		net->bsc_ctrs->ctr[BSC_CTR_CHREQ_TOTAL].current,
+		net->bsc_ctrs->ctr[BSC_CTR_CHREQ_NO_CHANNEL].current,
+		VTY_NEWLINE);
+	vty_out(vty, "Channel Failures        : %"PRIu64" rf_failures, %"PRIu64" rll failures%s",
+		net->bsc_ctrs->ctr[BSC_CTR_CHAN_RF_FAIL].current,
+		net->bsc_ctrs->ctr[BSC_CTR_CHAN_RLL_ERR].current,
+		VTY_NEWLINE);
+	vty_out(vty, "Paging                  : %"PRIu64" attempted, %"PRIu64" complete, %"PRIu64" expired%s",
+		net->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED].current,
+		net->bsc_ctrs->ctr[BSC_CTR_PAGING_COMPLETED].current,
+		net->bsc_ctrs->ctr[BSC_CTR_PAGING_EXPIRED].current,
+		VTY_NEWLINE);
+	vty_out(vty, "BTS failures            : %"PRIu64" OML, %"PRIu64" RSL%s",
+		net->bsc_ctrs->ctr[BSC_CTR_BTS_OML_FAIL].current,
+		net->bsc_ctrs->ctr[BSC_CTR_BTS_RSL_FAIL].current,
+		VTY_NEWLINE);
+}
+
+DEFUN(drop_bts,
+      drop_bts_cmd,
+      "drop bts connection <0-65535> (oml|rsl)",
+      "Debug/Simulation command to drop Abis/IP BTS\n"
+      "Debug/Simulation command to drop Abis/IP BTS\n"
+      "Debug/Simulation command to drop Abis/IP BTS\n"
+      "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n")
+{
+	struct gsm_network *gsmnet;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts *bts;
+	unsigned int bts_nr;
+
+	gsmnet = gsmnet_from_vty(vty);
+
+	bts_nr = atoi(argv[0]);
+	if (bts_nr >= gsmnet->num_bts) {
+		vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+			gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+
+	/* close all connections */
+	if (strcmp(argv[1], "oml") == 0) {
+		ipaccess_drop_oml(bts);
+	} else if (strcmp(argv[1], "rsl") == 0) {
+		/* close all rsl connections */
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			ipaccess_drop_rsl(trx);
+		}
+	} else {
+		vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(restart_bts, restart_bts_cmd,
+      "restart-bts <0-65535>",
+      "Restart ip.access nanoBTS through OML\n"
+      "BTS Number\n")
+{
+	struct gsm_network *gsmnet;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts *bts;
+	unsigned int bts_nr;
+
+	gsmnet = gsmnet_from_vty(vty);
+
+	bts_nr = atoi(argv[0]);
+	if (bts_nr >= gsmnet->num_bts) {
+		vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+			gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) {
+		vty_out(vty, "This command only works for ipaccess nanoBTS.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* go from last TRX to c0 */
+	llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+		abis_nm_ipaccess_restart(trx);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(bts_resend, bts_resend_cmd,
+      "bts <0-255> resend-system-information",
+      "BTS Specific Commands\n" "BTS Number\n"
+      "Re-generate + re-send BCCH SYSTEM INFORMATION\n")
+{
+	struct gsm_network *gsmnet;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts *bts;
+	unsigned int bts_nr;
+
+	gsmnet = gsmnet_from_vty(vty);
+
+	bts_nr = atoi(argv[0]);
+	if (bts_nr >= gsmnet->num_bts) {
+		vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+			gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+		gsm_bts_trx_set_system_infos(trx);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(smscb_cmd, smscb_cmd_cmd,
+	"bts <0-255> smscb-command <1-4> HEXSTRING",
+	"BTS related commands\n" "BTS Number\n"
+	"SMS Cell Broadcast\n" "Last Valid Block\n"
+	"Hex Encoded SMSCB message (up to 88 octets)\n")
+{
+	struct gsm_bts *bts;
+	int bts_nr = atoi(argv[0]);
+	int last_block = atoi(argv[1]);
+	struct rsl_ie_cb_cmd_type cb_cmd;
+	uint8_t buf[88];
+	int rc;
+
+	bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	rc = osmo_hexparse(argv[2], buf, sizeof(buf));
+	if (rc < 0 || rc > sizeof(buf)) {
+		vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	cb_cmd.spare = 0;
+	cb_cmd.def_bcast = 0;
+	cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+
+	switch (last_block) {
+	case 1:
+		cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
+		break;
+	case 2:
+		cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
+		break;
+	case 3:
+		cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
+		break;
+	case 4:
+		cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
+		break;
+	}
+
+	rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, buf, rc);
+
+	return CMD_SUCCESS;
+}
+
+/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */
+static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str,
+					 const char *ts_str)
+{
+	int bts_nr = atoi(bts_str);
+	int trx_nr = atoi(trx_str);
+	int ts_nr = atoi(ts_str);
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+
+	bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return NULL;
+	}
+
+	trx = gsm_bts_trx_num(bts, trx_nr);
+	if (!trx) {
+		vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
+		return NULL;
+	}
+
+	ts = &trx->ts[ts_nr];
+
+	return ts;
+}
+
+DEFUN(pdch_act, pdch_act_cmd,
+	"bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)",
+	"BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+	"TRX Timeslot\n" "Timeslot Number\n" "Packet Data Channel\n"
+	"Activate Dynamic PDCH/TCH (-> PDCH mode)\n"
+	"Deactivate Dynamic PDCH/TCH (-> TCH mode)\n")
+{
+	struct gsm_bts_trx_ts *ts;
+	int activate;
+
+	ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+	if (!ts)
+		return CMD_WARNING;
+
+	if (!is_ipaccess_bts(ts->trx->bts)) {
+		vty_out(vty, "%% This command only works for ipaccess BTS%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+		vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH "
+			"mode%s", ts->nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[3], "activate"))
+		activate = 1;
+	else
+		activate = 0;
+
+	rsl_ipacc_pdch_activate(ts, activate);
+
+	return CMD_SUCCESS;
+
+}
+
+/* determine the logical channel type based on the physical channel type */
+static int lchan_type_by_pchan(enum gsm_phys_chan_config pchan)
+{
+	switch (pchan) {
+	case GSM_PCHAN_TCH_F:
+		return GSM_LCHAN_TCH_F;
+	case GSM_PCHAN_TCH_H:
+		return GSM_LCHAN_TCH_H;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+	case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+	case GSM_PCHAN_CCCH_SDCCH4:
+	case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+		return GSM_LCHAN_SDCCH;
+	default:
+		return -1;
+	}
+}
+
+/* configure the lchan for a single AMR mode (as specified) */
+static int lchan_set_single_amr_mode(struct gsm_lchan *lchan, uint8_t amr_mode)
+{
+	struct amr_multirate_conf mr;
+	struct gsm48_multi_rate_conf *mr_conf;
+	mr_conf = (struct gsm48_multi_rate_conf *) &mr.gsm48_ie;
+
+	if (amr_mode > 7)
+		return -1;
+
+	memset(&mr, 0, sizeof(mr));
+	mr_conf->ver = 1;
+	/* bit-mask of supported modes, only one bit is set. Reflects
+	 * Figure 10.5.2.47a where there are no thershold and only a
+	 * single mode */
+	mr.gsm48_ie[1] = 1 << amr_mode;
+
+	mr.ms_mode[0].mode = amr_mode;
+	mr.bts_mode[0].mode = amr_mode;
+
+	/* encode this configuration into the lchan for both uplink and
+	 * downlink direction */
+	gsm48_multirate_config(lchan->mr_ms_lv, &mr, mr.ms_mode);
+	gsm48_multirate_config(lchan->mr_bts_lv, &mr, mr.bts_mode);
+
+	return 0;
+}
+
+/* Debug/Measurement command to activate a given logical channel
+ * manually in a given mode/codec.  This is useful for receiver
+ * performance testing (FER/RBER/...) */
+DEFUN(lchan_act, lchan_act_cmd,
+	"bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> (activate|deactivate) (hr|fr|efr|amr) [<0-7>]",
+	"BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+	"TRX Timeslot\n" "Timeslot Number\n" "Sub-Slot Number\n" "Sub-Slot Number\n"
+	"Manual Channel Activation (e.g. for BER test)\n"
+	"Manual Channel Deactivation (e.g. for BER test)\n"
+	"Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "AMR Mode\n")
+{
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lchan;
+	int ss_nr = atoi(argv[3]);
+	const char *act_str = argv[4];
+	const char *codec_str = argv[5];
+	int activate;
+
+	ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+	if (!ts)
+		return CMD_WARNING;
+
+	lchan = &ts->lchan[ss_nr];
+
+	if (!strcmp(act_str, "activate"))
+		activate = 1;
+	else
+		activate = 0;
+
+	if (ss_nr >= ts_subslots(ts)) {
+		vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s",
+			ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (activate) {
+		int lchan_t;
+		if (lchan->state != LCHAN_S_NONE) {
+			vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		lchan_t = lchan_type_by_pchan(ts->pchan);
+		if (lchan_t < 0)
+			return CMD_WARNING;
+		/* configure the lchan */
+		lchan->type = lchan_t;
+		lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+		if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr"))
+			lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+		else if (!strcmp(codec_str, "efr"))
+			lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+		else if (!strcmp(codec_str, "amr")) {
+			int amr_mode;
+			if (argc < 7) {
+				vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE);
+				return CMD_WARNING;
+			}
+			amr_mode = atoi(argv[6]);
+			lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
+			lchan_set_single_amr_mode(lchan, amr_mode);
+		}
+		vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE);
+		rsl_chan_activate_lchan(lchan, RSL_ACT_TYPE_INITIAL, 0);
+		rsl_ipacc_crcx(lchan);
+	} else {
+		rsl_direct_rf_release(lchan);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(lchan_mdcx, lchan_mdcx_cmd,
+	"bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>",
+	"BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+	"TRX Timeslot\n" "Timeslot Number\n" "Sub-Slot\n" "Sub-Slot Number\n"
+	"Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n")
+{
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lchan;
+	int ss_nr = atoi(argv[3]);
+	int port = atoi(argv[5]);
+	struct in_addr ia;
+	inet_aton(argv[4], &ia);
+
+	ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+	if (!ts)
+		return CMD_WARNING;
+
+	lchan = &ts->lchan[ss_nr];
+
+	if (ss_nr >= ts_subslots(ts)) {
+		vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s",
+			ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan),
+		inet_ntoa(ia), port, VTY_NEWLINE);
+	rsl_ipacc_mdcx(lchan, ntohl(ia.s_addr), port, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(ctrl_trap, ctrl_trap_cmd,
+	"ctrl-interface generate-trap TRAP VALUE",
+	"Commands related to the CTRL Interface\n"
+	"Generate a TRAP for test purpose\n"
+	"Identity/Name of the TRAP variable\n"
+	"Value of the TRAP variable\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	ctrl_cmd_send_trap(net->ctrl, argv[0], (char *) argv[1]);
+	return CMD_SUCCESS;
+}
+
+extern int bsc_vty_init_extra(void);
+
+int bsc_vty_init(struct gsm_network *network)
+{
+	cfg_ts_pchan_cmd.string =
+		vty_cmd_string_from_valstr(tall_bsc_ctx,
+					   gsm_pchant_names,
+					   "phys_chan_config (", "|", ")",
+					   VTY_DO_LOWER);
+	cfg_ts_pchan_cmd.doc =
+		vty_cmd_string_from_valstr(tall_bsc_ctx,
+					   gsm_pchant_descs,
+					   "Physical Channel Combination\n",
+					   "\n", "", 0);
+
+	cfg_bts_type_cmd.string =
+		vty_cmd_string_from_valstr(tall_bsc_ctx,
+					   bts_type_names,
+					   "type (", "|", ")",
+					   VTY_DO_LOWER);
+	cfg_bts_type_cmd.doc =
+		vty_cmd_string_from_valstr(tall_bsc_ctx,
+					   bts_type_descs,
+					   "BTS Vendor/Type\n",
+					   "\n", "", 0);
+
+	common_cs_vty_init(network, config_write_net);
+
+	install_element_ve(&bsc_show_net_cmd);
+	install_element_ve(&show_bts_cmd);
+	install_element_ve(&show_trx_cmd);
+	install_element_ve(&show_ts_cmd);
+	install_element_ve(&show_lchan_cmd);
+	install_element_ve(&show_lchan_summary_cmd);
+
+	install_element_ve(&show_paging_cmd);
+	install_element_ve(&show_paging_group_cmd);
+
+	logging_vty_add_cmds(NULL);
+	osmo_stats_vty_add_cmds();
+
+	install_element(GSMNET_NODE, &cfg_net_neci_cmd);
+	install_element(GSMNET_NODE, &cfg_net_handover_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxqual_avg_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_neigh_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3101_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3103_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3105_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3107_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3109_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3111_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3113_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3115_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3117_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3119_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3122_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3141_cmd);
+	install_element(GSMNET_NODE, &cfg_net_dtx_cmd);
+	install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd);
+
+	install_element(GSMNET_NODE, &cfg_bts_cmd);
+	install_node(&bts_node, config_write_bts);
+	install_element(BTS_NODE, &cfg_bts_type_cmd);
+	install_element(BTS_NODE, &cfg_description_cmd);
+	install_element(BTS_NODE, &cfg_no_description_cmd);
+	install_element(BTS_NODE, &cfg_bts_band_cmd);
+	install_element(BTS_NODE, &cfg_bts_ci_cmd);
+	install_element(BTS_NODE, &cfg_bts_dtxu_cmd);
+	install_element(BTS_NODE, &cfg_bts_dtxd_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd);
+	install_element(BTS_NODE, &cfg_bts_lac_cmd);
+	install_element(BTS_NODE, &cfg_bts_tsc_cmd);
+	install_element(BTS_NODE, &cfg_bts_bsic_cmd);
+	install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+	install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd);
+	install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd);
+	install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd);
+	install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd);
+	install_element(BTS_NODE, &cfg_bts_stream_id_cmd);
+	install_element(BTS_NODE, &cfg_bts_oml_e1_cmd);
+	install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd);
+	install_element(BTS_NODE, &cfg_bts_challoc_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
+	install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd);
+	install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd);
+	install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd);
+	install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
+	install_element(BTS_NODE, &cfg_bts_per_loc_upd_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_per_loc_upd_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
+	install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd);
+	install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd);
+	install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd);
+	install_element(BTS_NODE, &cfg_bts_penalty_time_cmd);
+	install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd);
+	install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd);
+	install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd);
+	install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd);
+	install_element(BTS_NODE, &cfg_bts_pag_free_cmd);
+	install_element(BTS_NODE, &cfg_bts_si_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_si_static_cmd);
+	install_element(BTS_NODE, &cfg_bts_early_cm_cmd);
+	install_element(BTS_NODE, &cfg_bts_early_cm_3g_cmd);
+	install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_neigh_cmd);
+	install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd);
+	install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd);
+	install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd);
+	install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_add_cmd);
+	install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_del_cmd);
+	install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd);
+	install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_force_comb_si_cmd);
+	install_element(BTS_NODE, &cfg_bts_codec0_cmd);
+	install_element(BTS_NODE, &cfg_bts_codec1_cmd);
+	install_element(BTS_NODE, &cfg_bts_codec2_cmd);
+	install_element(BTS_NODE, &cfg_bts_codec3_cmd);
+	install_element(BTS_NODE, &cfg_bts_codec4_cmd);
+	install_element(BTS_NODE, &cfg_bts_depends_on_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_depends_on_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_modes1_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_modes2_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_modes3_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_modes4_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_thres1_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_thres2_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_thres3_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_hyst1_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_hyst2_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_hyst3_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_fr_start_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_modes1_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_modes2_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_modes3_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_modes4_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_thres1_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_thres2_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_thres3_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_hyst1_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_hyst2_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_hyst3_cmd);
+	install_element(BTS_NODE, &cfg_bts_amr_hr_start_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd);
+	install_element(BTS_NODE, &cfg_bts_acc_ramping_cmd);
+	install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd);
+	install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd);
+	install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd);
+
+	install_element(BTS_NODE, &cfg_trx_cmd);
+	install_node(&trx_node, dummy_config_write);
+	install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
+	install_element(TRX_NODE, &cfg_description_cmd);
+	install_element(TRX_NODE, &cfg_no_description_cmd);
+	install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+	install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
+	install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
+
+	install_element(TRX_NODE, &cfg_ts_cmd);
+	install_node(&ts_node, dummy_config_write);
+	install_element(TS_NODE, &cfg_ts_pchan_cmd);
+	install_element(TS_NODE, &cfg_ts_pchan_compat_cmd);
+	install_element(TS_NODE, &cfg_ts_tsc_cmd);
+	install_element(TS_NODE, &cfg_ts_hopping_cmd);
+	install_element(TS_NODE, &cfg_ts_hsn_cmd);
+	install_element(TS_NODE, &cfg_ts_maio_cmd);
+	install_element(TS_NODE, &cfg_ts_arfcn_add_cmd);
+	install_element(TS_NODE, &cfg_ts_arfcn_del_cmd);
+	install_element(TS_NODE, &cfg_ts_e1_subslot_cmd);
+
+	install_element(ENABLE_NODE, &drop_bts_cmd);
+	install_element(ENABLE_NODE, &restart_bts_cmd);
+	install_element(ENABLE_NODE, &bts_resend_cmd);
+	install_element(ENABLE_NODE, &pdch_act_cmd);
+	install_element(ENABLE_NODE, &lchan_act_cmd);
+	install_element(ENABLE_NODE, &lchan_mdcx_cmd);
+	install_element(ENABLE_NODE, &smscb_cmd_cmd);
+	install_element(ENABLE_NODE, &ctrl_trap_cmd);
+
+	abis_nm_vty_init();
+	abis_om2k_vty_init();
+	e1inp_vty_init();
+	osmo_fsm_vty_add_cmds();
+
+	bsc_vty_init_extra();
+
+	return 0;
+}
diff --git a/openbsc/src/libbsc/bts_ericsson_rbs2000.c b/openbsc/src/libbsc/bts_ericsson_rbs2000.c
new file mode 100644
index 0000000..99da4e7
--- /dev/null
+++ b/openbsc/src/libbsc/bts_ericsson_rbs2000.c
@@ -0,0 +1,204 @@
+/* Ericsson RBS-2xxx specific code */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/abis/lapd.h>
+
+static void bootstrap_om_bts(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+	/* FIXME: this is global init, not bootstrapping */
+	abis_om2k_bts_init(bts);
+	abis_om2k_trx_init(bts->c0);
+
+	/* TODO: Should we wait for a Failure report? */
+	om2k_bts_fsm_start(bts);
+}
+
+static void bootstrap_om_trx(struct gsm_bts_trx *trx)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n",
+	     trx->bts->nr, trx->nr);
+	/* FIXME */
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+	/* FIXME */
+	return 0;
+}
+
+
+/* Tell LAPD to start start the SAP (send SABM requests) for all signalling
+ * timeslots in this line */
+static void start_sabm_in_line(struct e1inp_line *line, int start)
+{
+	struct e1inp_sign_link *link;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
+		struct e1inp_ts *ts = &line->ts[i];
+
+		if (ts->type != E1INP_TS_TYPE_SIGN)
+			continue;
+
+		llist_for_each_entry(link, &ts->sign.sign_links, list) {
+			if (!ts->lapd)
+				continue;
+			lapd_instance_set_profile(ts->lapd,
+						  &lapd_profile_abis_ericsson);
+
+			if (start)
+				lapd_sap_start(ts->lapd, link->tei, link->sapi);
+			else
+				lapd_sap_stop(ts->lapd, link->tei, link->sapi);
+		}
+	}
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts *bts;
+
+	if (subsys != SS_L_GLOBAL)
+		return 0;
+
+	switch (signal) {
+	case S_GLOBAL_BTS_CLOSE_OM:
+		bts = signal_data;
+		if (bts->type == GSM_BTS_TYPE_RBS2000)
+			shutdown_om(signal_data);
+		break;
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+	struct e1inp_ts *e1i_ts;
+
+	if (subsys != SS_L_INPUT)
+		return 0;
+
+	LOGP(DNM, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__,
+			get_value_string(e1inp_signal_names, signal));
+	switch (signal) {
+	case S_L_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000)
+				break;
+			if (isd->tei == isd->trx->bts->oml_tei)
+				bootstrap_om_bts(isd->trx->bts);
+			else
+				bootstrap_om_trx(isd->trx);
+			break;
+		}
+		break;
+	case S_L_INP_TEI_DN:
+		if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000)
+			break;
+		LOGP(DNM, LOGL_NOTICE, "Line-%u TS-%u TEI-%u SAPI-%u: Link "
+		     "Lost for Ericsson RBS2000. Re-starting DL Establishment\n",
+		     isd->line->num, isd->ts_nr, isd->tei, isd->sapi);
+		/* Some datalink for a given TEI/SAPI went down, try to re-start it */
+		e1i_ts = &isd->line->ts[isd->ts_nr-1];
+		OSMO_ASSERT(e1i_ts->type == E1INP_TS_TYPE_SIGN);
+		lapd_sap_start(e1i_ts->lapd, isd->tei, isd->sapi);
+		break;
+	case S_L_INP_LINE_INIT:
+	case S_L_INP_LINE_NOALARM:
+		if (strcasecmp(isd->line->driver->name, "DAHDI")
+		 && strcasecmp(isd->line->driver->name, "MISDN_LAPD")
+		 && strcasecmp(isd->line->driver->name, "UNIXSOCKET"))
+			break;
+		start_sabm_in_line(isd->line, 1);
+		break;
+	case S_L_INP_LINE_ALARM:
+		if (strcasecmp(isd->line->driver->name, "DAHDI")
+		 && strcasecmp(isd->line->driver->name, "MISDN_LAPD")
+		 && strcasecmp(isd->line->driver->name, "UNIXSOCKET"))
+			break;
+		start_sabm_in_line(isd->line, 0);
+		break;
+	}
+
+	return 0;
+}
+
+static void config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	abis_om2k_config_write_bts(vty, bts);
+}
+
+static int bts_model_rbs2k_start(struct gsm_network *net);
+
+static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line)
+{
+	e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
+}
+
+static struct gsm_bts_model model_rbs2k = {
+	.type = GSM_BTS_TYPE_RBS2000,
+	.name = "rbs2000",
+	.start = bts_model_rbs2k_start,
+	.oml_rcvmsg = &abis_om2k_rcvmsg,
+	.config_write_bts = &config_write_bts,
+	.e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops,
+};
+
+static int bts_model_rbs2k_start(struct gsm_network *net)
+{
+	model_rbs2k.features.data = &model_rbs2k._features_data[0];
+	model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data);
+
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_EGPRS);
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HSCSD);
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_MULTI_TSC);
+
+	osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
+	osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
+
+	return 0;
+}
+
+int bts_model_rbs2k_init(void)
+{
+	return gsm_bts_model_register(&model_rbs2k);
+}
diff --git a/openbsc/src/libbsc/bts_init.c b/openbsc/src/libbsc/bts_init.c
new file mode 100644
index 0000000..d6b152a
--- /dev/null
+++ b/openbsc/src/libbsc/bts_init.c
@@ -0,0 +1,30 @@
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <openbsc/bss.h>
+
+int bts_init(void)
+{
+	bts_model_bs11_init();
+	bts_model_rbs2k_init();
+	bts_model_nanobts_init();
+	bts_model_nokia_site_init();
+	bts_model_sysmobts_init();
+	/* Your new BTS here. */
+	return 0;
+}
diff --git a/openbsc/src/libbsc/bts_ipaccess_nanobts.c b/openbsc/src/libbsc/bts_ipaccess_nanobts.c
new file mode 100644
index 0000000..ea83767
--- /dev/null
+++ b/openbsc/src/libbsc/bts_ipaccess_nanobts.c
@@ -0,0 +1,552 @@
+/* ip.access nanoBTS specific code */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <time.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/logging.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/bts_ipaccess_nanobts_omlattr.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static int bts_model_nanobts_start(struct gsm_network *net);
+static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line);
+
+struct gsm_bts_model bts_model_nanobts = {
+	.type = GSM_BTS_TYPE_NANOBTS,
+	.name = "nanobts",
+	.start = bts_model_nanobts_start,
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.e1line_bind_ops = bts_model_nanobts_e1line_bind_ops, 
+	.nm_att_tlvdef = {
+		.def = {
+			/* ip.access specifics */
+			[NM_ATT_IPACC_DST_IP] =		{ TLV_TYPE_FIXED, 4 },
+			[NM_ATT_IPACC_DST_IP_PORT] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_STREAM_ID] =	{ TLV_TYPE_TV, },
+			[NM_ATT_IPACC_SEC_OML_CFG] =	{ TLV_TYPE_FIXED, 6 },
+			[NM_ATT_IPACC_IP_IF_CFG] =	{ TLV_TYPE_FIXED, 8 },
+			[NM_ATT_IPACC_IP_GW_CFG] =	{ TLV_TYPE_FIXED, 12 },
+			[NM_ATT_IPACC_IN_SERV_TIME] =	{ TLV_TYPE_FIXED, 4 },
+			[NM_ATT_IPACC_LOCATION] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_PAGING_CFG] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_UNIT_ID] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_UNIT_NAME] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SNMP_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NV_FLAGS] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_FREQ_CTRL] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CUR_SW_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_TIMING_BUS] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CGI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RAC] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_OBJ_VERSION] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NSEI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_BVCI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NSVCI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NS_CFG] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_BSSGP_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NS_LINK_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_ALM_THRESH_LIST]=	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_TIB_CONTROL] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SUPP_FEATURES] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CODING_SCHEMES] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG_2] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_HEARTB_TOUT] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_UPTIME] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG_3] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SSL_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SEC_POSSIBLE] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_IML_SSL_STATE] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_REVOC_DATE] =	{ TLV_TYPE_TL16V },
+		},
+	},
+};
+
+
+/* Callback function to be called whenever we get a GSM 12.21 state change event */
+static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
+{
+	uint8_t obj_class = nsd->obj_class;
+	void *obj = nsd->obj;
+	struct gsm_nm_state *new_state = nsd->new_state;
+
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_bts_gprs_nsvc *nsvc;
+
+	struct msgb *msgb;
+
+	if (!is_ipaccess_bts(nsd->bts))
+		return 0;
+
+	/* This event-driven BTS setup is currently only required on nanoBTS */
+
+	/* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create
+	 * endless loop */
+	if (evt != S_NM_STATECHG_OPER)
+		return 0;
+
+	switch (obj_class) {
+	case NM_OC_SITE_MANAGER:
+		bts = container_of(obj, struct gsm_bts, site_mgr);
+		if ((new_state->operational == NM_OPSTATE_ENABLED &&
+		     new_state->availability == NM_AVSTATE_OK) ||
+		    (new_state->operational == NM_OPSTATE_DISABLED &&
+		     new_state->availability == NM_AVSTATE_OFF_LINE))
+			abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff);
+		break;
+	case NM_OC_BTS:
+		bts = obj;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			msgb = nanobts_attr_bts_get(bts);
+			abis_nm_set_bts_attr(bts, msgb->data, msgb->len);
+			msgb_free(msgb);
+			abis_nm_chg_adm_state(bts, obj_class,
+					      bts->bts_nr, 0xff, 0xff,
+					      NM_STATE_UNLOCKED);
+			abis_nm_opstart(bts, obj_class,
+					bts->bts_nr, 0xff, 0xff);
+		}
+		break;
+	case NM_OC_CHANNEL:
+		ts = obj;
+		trx = ts->trx;
+		if (new_state->operational == NM_OPSTATE_DISABLED &&
+		    new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			enum abis_nm_chan_comb ccomb =
+						abis_nm_chcomb4pchan(ts->pchan);
+			if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) {
+				ipaccess_drop_oml_deferred(trx->bts);
+				return -1;
+			}
+			abis_nm_chg_adm_state(trx->bts, obj_class,
+					      trx->bts->bts_nr, trx->nr, ts->nr,
+					      NM_STATE_UNLOCKED);
+			abis_nm_opstart(trx->bts, obj_class,
+					trx->bts->bts_nr, trx->nr, ts->nr);
+		}
+		if (new_state->operational == NM_OPSTATE_ENABLED
+		    && new_state->availability == NM_AVSTATE_OK)
+			dyn_ts_init(ts);
+		break;
+	case NM_OC_RADIO_CARRIER:
+		trx = obj;
+		if (new_state->operational == NM_OPSTATE_DISABLED &&
+		    new_state->availability == NM_AVSTATE_OK)
+			abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr,
+					trx->nr, 0xff);
+		break;
+	case NM_OC_GPRS_NSE:
+		bts = container_of(obj, struct gsm_bts, gprs.nse);
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			msgb = nanobts_attr_nse_get(bts);
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0xff, 0xff, msgb->data,
+						  msgb->len);
+			msgb_free(msgb);
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					0xff, 0xff);
+		}
+		break;
+	case NM_OC_GPRS_CELL:
+		bts = container_of(obj, struct gsm_bts, gprs.cell);
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			msgb = nanobts_attr_cell_get(bts);
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0, 0xff, msgb->data,
+						  msgb->len);
+			msgb_free(msgb);
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					0, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      0, 0xff, NM_STATE_UNLOCKED);
+			abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr,
+					      0xff, 0xff, NM_STATE_UNLOCKED);
+		}
+		break;
+	case NM_OC_GPRS_NSVC:
+		nsvc = obj;
+		bts = nsvc->bts;
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		/* We skip NSVC1 since we only use NSVC0 */
+		if (nsvc->id == 1)
+			break;
+		if ((new_state->availability == NM_AVSTATE_OFF_LINE) ||
+		    (new_state->availability == NM_AVSTATE_DEPENDENCY)) {
+			msgb = nanobts_attr_nscv_get(bts);
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  nsvc->id, 0xff,
+						  msgb->data, msgb->len);
+			msgb_free(msgb);
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					nsvc->id, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      nsvc->id, 0xff,
+					      NM_STATE_UNLOCKED);
+		}
+	default:
+		break;
+	}
+	return 0;
+}
+
+/* Callback function to be called every time we receive a 12.21 SW activated report */
+static int sw_activ_rep(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct e1inp_sign_link *sign_link = mb->dst;
+	struct gsm_bts *bts = sign_link->trx->bts;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+
+	if (!trx)
+		return -EINVAL;
+
+	if (!is_ipaccess_bts(trx->bts))
+		return 0;
+
+	switch (foh->obj_class) {
+	case NM_OC_BASEB_TRANSC:
+		abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+				      trx->bts->bts_nr, trx->nr, 0xff,
+				      NM_STATE_UNLOCKED);
+		abis_nm_opstart(trx->bts, foh->obj_class,
+				trx->bts->bts_nr, trx->nr, 0xff);
+		/* TRX software is active, tell it to initiate RSL Link */
+		abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip,
+					     3003, trx->rsl_tei);
+		break;
+	case NM_OC_RADIO_CARRIER: {
+		/*
+		 * Locking the radio carrier will make it go
+		 * offline again and we would come here. The
+		 * framework should determine that there was
+		 * no change and avoid recursion.
+		 *
+		 * This code is here to make sure that on start
+		 * a TRX remains locked.
+		 */
+		int rc_state = trx->mo.nm_state.administrative;
+		/* Patch ARFCN into radio attribute */
+		struct msgb *msgb = nanobts_attr_radio_get(trx->bts, trx);
+		abis_nm_set_radio_attr(trx, msgb->data, msgb->len);
+		msgb_free(msgb);
+		abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+				      trx->bts->bts_nr, trx->nr, 0xff,
+				      rc_state);
+		abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr,
+				trx->nr, 0xff);
+		break;
+		}
+	}
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	if (subsys != SS_NM)
+		return 0;
+
+	switch (signal) {
+	case S_NM_SW_ACTIV_REP:
+		return sw_activ_rep(signal_data);
+	case S_NM_STATECHG_OPER:
+	case S_NM_STATECHG_ADM:
+		return nm_statechg_event(signal, signal_data);
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int bts_model_nanobts_start(struct gsm_network *net)
+{
+	osmo_signal_unregister_handler(SS_NM, bts_ipa_nm_sig_cb, NULL);
+	osmo_signal_register_handler(SS_NM, bts_ipa_nm_sig_cb, NULL);
+	return 0;
+}
+
+int bts_model_nanobts_init(void)
+{
+	bts_model_nanobts.features.data = &bts_model_nanobts._features_data[0];
+	bts_model_nanobts.features.data_len =
+				sizeof(bts_model_nanobts._features_data);
+
+	gsm_btsmodel_set_feature(&bts_model_nanobts, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&bts_model_nanobts, BTS_FEAT_EGPRS);
+	gsm_btsmodel_set_feature(&bts_model_nanobts, BTS_FEAT_MULTI_TSC);
+
+	return gsm_bts_model_register(&bts_model_nanobts);
+}
+
+#define OML_UP         0x0001
+#define RSL_UP         0x0002
+
+static struct gsm_bts *
+find_bts_by_unitid(struct gsm_network *net, uint16_t site_id, uint16_t bts_id)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (!is_ipaccess_bts(bts))
+			continue;
+
+		if (bts->ip_access.site_id == site_id &&
+		    bts->ip_access.bts_id == bts_id)
+			return bts;
+	}
+	return NULL;
+}
+
+/* These are exported because they are used by the VTY interface. */
+void ipaccess_drop_rsl(struct gsm_bts_trx *trx)
+{
+	if (!trx->rsl_link)
+		return;
+
+	e1inp_sign_link_destroy(trx->rsl_link);
+	trx->rsl_link = NULL;
+}
+
+void ipaccess_drop_oml(struct gsm_bts *bts)
+{
+	struct gsm_bts *rdep_bts;
+	struct gsm_bts_trx *trx;
+
+	/* First of all, remove deferred drop if enabled */
+	osmo_timer_del(&bts->oml_drop_link_timer);
+
+	if (!bts->oml_link)
+		return;
+
+	e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = NULL;
+	bts->uptime = 0;
+
+	/* we have issues reconnecting RSL, drop everything. */
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		ipaccess_drop_rsl(trx);
+
+	bts->ip_access.flags = 0;
+
+	/*
+	 * Go through the list and see if we are the depndency of a BTS
+	 * and then drop the BTS. This can lead to some recursion but it
+	 * should be fine in userspace.
+	 * The oml_link is serving as recursion anchor for us and
+	 * it is set to NULL some lines above.
+	 */
+	llist_for_each_entry(rdep_bts, &bts->network->bts_list, list) {
+		if (!bts_depend_is_depedency(rdep_bts, bts))
+			continue;
+		LOGP(DLINP, LOGL_NOTICE, "Dropping BTS(%u) due BTS(%u).\n",
+			rdep_bts->nr, bts->nr);
+		ipaccess_drop_oml(rdep_bts);
+	}
+}
+
+/*! Callback for  \ref ipaccess_drop_oml_deferred_cb.
+ */
+static void ipaccess_drop_oml_deferred_cb(void *data)
+{
+	struct gsm_bts *bts = (struct gsm_bts *) data;
+	ipaccess_drop_oml(bts);
+}
+/*! Deferr \ref ipacces_drop_oml through a timer to avoid dropping structures in
+ *  current code context. This may be needed if we want to destroy the OML link
+ *  while being called from a lower layer "struct osmo_fd" cb, were it is
+ *  mandatory to return -EBADF if the osmo_fd has been destroyed. In case code
+ *  destroying an OML link is called through an osmo_signal, it becomes
+ *  impossible to return any value, thus deferring the destruction is required.
+ */
+void ipaccess_drop_oml_deferred(struct gsm_bts *bts)
+{
+	if (!osmo_timer_pending(&bts->oml_drop_link_timer) && bts->oml_link) {
+		LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Deferring Drop of OML link.\n", bts->nr);
+		osmo_timer_setup(&bts->oml_drop_link_timer, ipaccess_drop_oml_deferred_cb, bts);
+		osmo_timer_schedule(&bts->oml_drop_link_timer, 0, 0);
+	}
+}
+
+/* This function is called once the OML/RSL link becomes up. */
+static struct e1inp_sign_link *
+ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
+		      enum e1inp_sign_type type)
+{
+	struct gsm_bts *bts;
+	struct ipaccess_unit *dev = unit_data;
+	struct e1inp_sign_link *sign_link = NULL;
+	struct timespec tp;
+	int rc;
+
+	bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id);
+	if (!bts) {
+		LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for "
+			" %u/%u/%u, disconnecting\n", dev->site_id,
+			dev->bts_id, dev->trx_id);
+		return NULL;
+	}
+	DEBUGP(DLINP, "Identified BTS %u/%u/%u\n",
+			dev->site_id, dev->bts_id, dev->trx_id);
+
+	switch(type) {
+	case E1INP_SIGN_OML:
+		/* remove old OML signal link for this BTS. */
+		ipaccess_drop_oml(bts);
+
+		if (!bts_depend_check(bts)) {
+			LOGP(DLINP, LOGL_NOTICE,
+				"Dependency not full-filled for %u/%u/%u\n",
+				dev->site_id, dev->bts_id, dev->trx_id);
+			return NULL;
+		}
+
+		/* create new OML link. */
+		sign_link = bts->oml_link =
+			e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML - 1],
+						E1INP_SIGN_OML, bts->c0,
+						bts->oml_tei, 0);
+		rc = clock_gettime(CLOCK_MONOTONIC, &tp);
+		bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
+		break;
+	case E1INP_SIGN_RSL: {
+		struct e1inp_ts *ts;
+		struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, dev->trx_id);
+
+		/* no OML link set yet? give up. */
+		if (!bts->oml_link || !trx)
+			return NULL;
+
+		/* remove old RSL link for this TRX. */
+		ipaccess_drop_rsl(trx);
+
+		/* set new RSL link for this TRX. */
+		line = bts->oml_link->ts->line;
+		ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1];
+		e1inp_ts_config_sign(ts, line);
+		sign_link = trx->rsl_link =
+				e1inp_sign_link_create(ts, E1INP_SIGN_RSL,
+						       trx, trx->rsl_tei, 0);
+		trx->rsl_link->ts->sign.delay = 0;
+		break;
+	}
+	default:
+		break;
+	}
+	return sign_link;
+}
+
+static void ipaccess_sign_link_down(struct e1inp_line *line)
+{
+	/* No matter what link went down, we close both signal links. */
+	struct e1inp_ts *ts = &line->ts[E1INP_SIGN_OML-1];
+	struct e1inp_sign_link *link;
+
+	llist_for_each_entry(link, &ts->sign.sign_links, list) {
+		struct gsm_bts *bts = link->trx->bts;
+
+		ipaccess_drop_oml(bts);
+		/* Yes, we only use the first element of the list. */
+		break;
+	}
+}
+
+/* This function is called if we receive one OML/RSL message. */
+static int ipaccess_sign_link(struct msgb *msg)
+{
+	int ret = 0;
+	struct e1inp_sign_link *link = msg->dst;
+	struct e1inp_ts *e1i_ts = link->ts;
+
+	switch (link->type) {
+	case E1INP_SIGN_RSL:
+		if (!(link->trx->bts->ip_access.flags &
+					(RSL_UP << link->trx->nr))) {
+			e1inp_event(e1i_ts, S_L_INP_TEI_UP,
+					link->tei, link->sapi);
+			link->trx->bts->ip_access.flags |=
+					(RSL_UP << link->trx->nr);
+		}
+	        ret = abis_rsl_rcvmsg(msg);
+	        break;
+	case E1INP_SIGN_OML:
+		if (!(link->trx->bts->ip_access.flags & OML_UP)) {
+			e1inp_event(e1i_ts, S_L_INP_TEI_UP,
+					link->tei, link->sapi);
+			link->trx->bts->ip_access.flags |= OML_UP;
+		}
+	        ret = abis_nm_rcvmsg(msg);
+	        break;
+	default:
+		LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n",
+			link->type);
+		msgb_free(msg);
+		break;
+	}
+	return ret;
+}
+
+/* not static, ipaccess-config needs it. */
+struct e1inp_line_ops ipaccess_e1inp_line_ops = {
+	.cfg = {
+		.ipa = {
+			.addr = "0.0.0.0",
+			.role = E1INP_LINE_R_BSC,
+		},
+	},
+	.sign_link_up	= ipaccess_sign_link_up,
+	.sign_link_down	= ipaccess_sign_link_down,
+	.sign_link	= ipaccess_sign_link,
+};
+
+static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line)
+{
+        e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops);
+}
diff --git a/openbsc/src/libbsc/bts_ipaccess_nanobts_omlattr.c b/openbsc/src/libbsc/bts_ipaccess_nanobts_omlattr.c
new file mode 100644
index 0000000..473e1ca
--- /dev/null
+++ b/openbsc/src/libbsc/bts_ipaccess_nanobts_omlattr.c
@@ -0,0 +1,240 @@
+/* ip.access nanoBTS specific code, OML attribute table generator */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <osmocom/core/msgb.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+
+static void patch_16(uint8_t *data, const uint16_t val)
+{
+	memcpy(data, &val, sizeof(val));
+}
+
+static void patch_32(uint8_t *data, const uint32_t val)
+{
+	memcpy(data, &val, sizeof(val));
+}
+
+struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts)
+{
+	struct msgb *msgb;
+	uint8_t buf[256];
+	int rlt;
+	msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+	memcpy(buf, "\x55\x5b\x61\x67\x6d\x73", 6);
+	msgb_tv_fixed_put(msgb, NM_ATT_INTERF_BOUND, 6, buf);
+
+	/* interference avg. period in numbers of SACCH multifr */
+	msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06);
+
+	rlt = gsm_bts_get_radio_link_timeout(bts);
+	if (rlt == -1) {
+		/* Osmocom extension: Use infinite radio link timeout */
+		buf[0] = 0xFF;
+		buf[1] = 0x00;
+	} else {
+		/* conn fail based on SACCH error rate */
+		buf[0] = 0x01;
+		buf[1] = rlt;
+	}
+	msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf);
+
+	memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7);
+	msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf);
+
+	msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f);
+
+	/* seconds */
+	memcpy(buf, "\x00\x01\x0a", 3);
+	msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf);
+
+	/* percent */
+	msgb_tv_put(msgb, NM_ATT_CCCH_L_T, 10);
+
+	/* seconds */
+	msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1);
+
+	/* busy threshold in - dBm */
+	buf[0] = 10;
+	if (bts->rach_b_thresh != -1)
+		buf[0] = bts->rach_b_thresh & 0xff;
+	msgb_tv_put(msgb, NM_ATT_RACH_B_THRESH, buf[0]);
+
+	/* rach load averaging 1000 slots */
+	buf[0] = 0x03;
+	buf[1] = 0xe8;
+	if (bts->rach_ldavg_slots != -1) {
+		buf[0] = (bts->rach_ldavg_slots >> 8) & 0x0f;
+		buf[1] = bts->rach_ldavg_slots & 0xff;
+	}
+	msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf);
+
+	/* miliseconds */
+	msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, 128);
+
+	/* 10 retransmissions of physical config */
+	msgb_tv_put(msgb, NM_ATT_NY1, 10);
+
+	buf[0] = (bts->c0->arfcn >> 8) & 0x0f;
+	buf[1] = bts->c0->arfcn & 0xff;
+	msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf);
+
+	msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic);
+
+	abis_nm_ipaccess_cgi(buf, bts);
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_CGI, 7, buf);
+
+	return msgb;
+}
+
+struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts)
+{
+	struct msgb *msgb;
+	uint8_t buf[256];
+	msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+	/* NSEI 925 */
+	buf[0] = bts->gprs.nse.nsei >> 8;
+	buf[1] = bts->gprs.nse.nsei & 0xff;
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_NSEI, 2, buf);
+
+	/* all timers in seconds */
+	OSMO_ASSERT(ARRAY_SIZE(bts->gprs.nse.timer) < sizeof(buf));
+	memcpy(buf, bts->gprs.nse.timer, ARRAY_SIZE(bts->gprs.nse.timer));
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_CFG, 7, buf);
+
+	/* all timers in seconds */
+	buf[0] = 3;	/* blockimg timer (T1) */
+	buf[1] = 3;	/* blocking retries */
+	buf[2] = 3;	/* unblocking retries */
+	buf[3] = 3;	/* reset timer (T2) */
+	buf[4] = 3;	/* reset retries */
+	buf[5] = 10;	/* suspend timer (T3) in 100ms */
+	buf[6] = 3;	/* suspend retries */
+	buf[7] = 10;	/* resume timer (T4) in 100ms */
+	buf[8] = 3;	/* resume retries */
+	buf[9] = 10;	/* capability update timer (T5) */
+	buf[10] = 3;	/* capability update retries */
+
+	OSMO_ASSERT(ARRAY_SIZE(bts->gprs.cell.timer) < sizeof(buf));
+	memcpy(buf, bts->gprs.cell.timer, ARRAY_SIZE(bts->gprs.cell.timer));
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_BSSGP_CFG, 11, buf);
+
+	return msgb;
+}
+
+struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts)
+{
+	struct msgb *msgb;
+	uint8_t buf[256];
+	msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+	/* routing area code */
+	buf[0] = bts->gprs.rac;
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_RAC, 1, buf);
+
+	buf[0] = 5;	/* repeat time (50ms) */
+	buf[1] = 3;	/* repeat count */
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_GPRS_PAGING_CFG, 2, buf);
+
+	/* BVCI 925 */
+	buf[0] = bts->gprs.cell.bvci >> 8;
+	buf[1] = bts->gprs.cell.bvci & 0xff;
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_BVCI, 2, buf);
+
+	/* all timers in seconds, unless otherwise stated */
+	buf[0] = 20;	/* T3142 */
+	buf[1] = 5;	/* T3169 */
+	buf[2] = 5;	/* T3191 */
+	buf[3] = 160;	/* T3193 (units of 10ms) */
+	buf[4] = 5;	/* T3195 */
+	buf[5] = 10;	/* N3101 */
+	buf[6] = 4;	/* N3103 */
+	buf[7] = 8;	/* N3105 */
+	buf[8] = 15;	/* RLC CV countdown */
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG, 9, buf);
+
+	if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+		buf[0] = 0x8f;
+		buf[1] = 0xff;
+	} else {
+		buf[0] = 0x0f;
+		buf[1] = 0x00;
+	}
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_CODING_SCHEMES, 2, buf);
+
+	buf[0] = 0;	/* T downlink TBF extension (0..500, high byte) */
+	buf[1] = 250;	/* T downlink TBF extension (0..500, low byte) */
+	buf[2] = 0;	/* T uplink TBF extension (0..500, high byte) */
+	buf[3] = 250;	/* T uplink TBF extension (0..500, low byte) */
+	buf[4] = 2;	/* CS2 */
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_2, 5, buf);
+
+#if 0
+	/* EDGE model only, breaks older models.
+	 * Should inquire the BTS capabilities */
+	buf[0] = 2;		/* MCS2 */
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_3, 1, buf);
+#endif
+
+	return msgb;
+}
+
+struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts)
+{
+	struct msgb *msgb;
+	uint8_t buf[256];
+	msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+	/* 925 */
+	buf[0] = bts->gprs.nsvc[0].nsvci >> 8;
+	buf[1] = bts->gprs.nsvc[0].nsvci & 0xff;
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf);
+
+	/* remote udp port */
+	patch_16(&buf[0], htons(bts->gprs.nsvc[0].remote_port));
+	/* remote ip address */
+	patch_32(&buf[2], htonl(bts->gprs.nsvc[0].remote_ip));
+	/* local udp port */
+	patch_16(&buf[6], htons(bts->gprs.nsvc[0].local_port));
+	msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf);
+
+	return msgb;
+}
+
+struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts,
+				    struct gsm_bts_trx *trx)
+{
+	struct msgb *msgb;
+	uint8_t buf[256];
+	msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+	/* number of -2dB reduction steps / Pn */
+	msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2);
+
+	buf[0] = trx->arfcn >> 8;
+	buf[1] = trx->arfcn & 0xff;
+	msgb_tl16v_put(msgb, NM_ATT_ARFCN_LIST, 2, buf);
+
+	return msgb;
+}
diff --git a/openbsc/src/libbsc/bts_nokia_site.c b/openbsc/src/libbsc/bts_nokia_site.c
new file mode 100644
index 0000000..3ca76c0
--- /dev/null
+++ b/openbsc/src/libbsc/bts_nokia_site.c
@@ -0,0 +1,1739 @@
+/* Nokia XXXsite family specific code */
+
+/* (C) 2011 by Dieter Spaar <spaar@mirider.augusta.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+  TODO: Attention: There are some static variables used for states during
+  configuration. Those variables have to be moved to a BTS specific context,
+  otherwise there will most certainly be problems if more than one Nokia BTS
+  is used.
+*/
+
+#include <time.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/timer.h>
+
+#include <osmocom/abis/lapd.h>
+
+/* TODO: put in a separate file ? */
+
+extern int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg);
+/* was static in system_information.c */
+extern int generate_cell_chan_list(uint8_t * chan_list, struct gsm_bts *bts);
+
+static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts);
+static void reset_timer_cb(void *_bts);
+static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref);
+static int dump_elements(uint8_t * data, int len) __attribute__((unused));
+
+static void bootstrap_om_bts(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+	if (!bts->nokia.skip_reset) {
+		if (!bts->nokia.did_reset)
+			abis_nm_reset(bts, 1);
+	} else
+		bts->nokia.did_reset = 1;
+}
+
+static void bootstrap_om_trx(struct gsm_bts_trx *trx)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n",
+	     trx->bts->nr, trx->nr);
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+	/* TODO !? */
+	return 0;
+}
+
+#define SAPI_OML    62
+#define SAPI_RSL    0
+
+/*
+
+  Tell LAPD to start start the SAP (send SABM requests) for all signalling
+  timeslots in this line
+
+  Attention: this has to be adapted for mISDN
+*/
+
+static void start_sabm_in_line(struct e1inp_line *line, int start, int sapi)
+{
+	struct e1inp_sign_link *link;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
+		struct e1inp_ts *ts = &line->ts[i];
+
+		if (ts->type != E1INP_TS_TYPE_SIGN)
+			continue;
+
+		llist_for_each_entry(link, &ts->sign.sign_links, list) {
+			if (sapi != -1 && link->sapi != sapi)
+				continue;
+
+#if 0				/* debugging */
+			printf("sap start/stop (%d): %d tei=%d sapi=%d\n",
+			       start, i + 1, link->tei, link->sapi);
+#endif
+
+			if (start) {
+				ts->lapd->profile.t200_sec = 1;
+				ts->lapd->profile.t200_usec = 0;
+				lapd_sap_start(ts->lapd, link->tei,
+					       link->sapi);
+			} else
+				lapd_sap_stop(ts->lapd, link->tei,
+					      link->sapi);
+		}
+	}
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts *bts;
+
+	if (subsys != SS_L_GLOBAL)
+		return 0;
+
+	switch (signal) {
+	case S_GLOBAL_BTS_CLOSE_OM:
+		bts = signal_data;
+		if (bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+			shutdown_om(signal_data);
+		break;
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+
+	if (subsys != SS_L_INPUT)
+		return 0;
+
+	switch (signal) {
+	case S_L_INP_LINE_INIT:
+		start_sabm_in_line(isd->line, 1, SAPI_OML);	/* start only OML */
+		break;
+	case S_L_INP_TEI_DN:
+		break;
+	case S_L_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			if (isd->trx->bts->type != GSM_BTS_TYPE_NOKIA_SITE)
+				break;
+
+			if (isd->tei == isd->trx->bts->oml_tei)
+				bootstrap_om_bts(isd->trx->bts);
+			else
+				bootstrap_om_trx(isd->trx);
+			break;
+		}
+		break;
+	case S_L_INP_TEI_UNKNOWN:
+		/* We are receiving LAPD frames with one TEI that we do not
+		 * seem to know, likely that we (the BSC) stopped working
+		 * and lost our local states. However, the BTS is already
+		 * configured, we try to take over the RSL links. */
+		start_sabm_in_line(isd->line, 1, SAPI_RSL);
+		break;
+	}
+
+	return 0;
+}
+
+static void nm_statechg_evt(unsigned int signal,
+			    struct nm_statechg_signal_data *nsd)
+{
+	if (nsd->bts->type != GSM_BTS_TYPE_NOKIA_SITE)
+		return;
+}
+
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	if (subsys != SS_NM)
+		return 0;
+
+	switch (signal) {
+	case S_NM_STATECHG_OPER:
+	case S_NM_STATECHG_ADM:
+		nm_statechg_evt(signal, signal_data);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* TODO: put in a separate file ? */
+
+static const struct value_string nokia_msgt_name[] = {
+	{ 0x80, "NOKIA_BTS_CONF_DATA" },
+	{ 0x81, "NOKIA_BTS_ACK" },
+	{ 0x82, "NOKIA_BTS_OMU_STARTED" },
+	{ 0x83, "NOKIA_BTS_START_DOWNLOAD_REQ" },
+	{ 0x84, "NOKIA_BTS_MF_REQ" },
+	{ 0x85, "NOKIA_BTS_AF_REQ" },
+	{ 0x86, "NOKIA_BTS_RESET_REQ" },
+	{ 0x87, "NOKIA_reserved" },
+	{ 0x88, "NOKIA_BTS_CONF_REQ" },
+	{ 0x89, "NOKIA_BTS_TEST_REQ" },
+	{ 0x8A, "NOKIA_BTS_TEST_REPORT" },
+	{ 0x8B, "NOKIA_reserved" },
+	{ 0x8C, "NOKIA_reserved" },
+	{ 0x8D, "NOKIA_reserved" },
+	{ 0x8E, "NOKIA_BTS_CONF_COMPL" },
+	{ 0x8F, "NOKIA_reserved" },
+	{ 0x90, "NOKIA_BTS_STM_TEST_REQ" },
+	{ 0x91, "NOKIA_BTS_STM_TEST_REPORT" },
+	{ 0x92, "NOKIA_BTS_TRANSMISSION_COMMAND" },
+	{ 0x93, "NOKIA_BTS_TRANSMISSION_ANSWER" },
+	{ 0x94, "NOKIA_BTS_HW_DB_UPLOAD_REQ" },
+	{ 0x95, "NOKIA_BTS_START_HW_DB_DOWNLOAD_REQ" },
+	{ 0x96, "NOKIA_BTS_HW_DB_SAVE_REQ" },
+	{ 0x97, "NOKIA_BTS_FLASH_ERASURE_REQ" },
+	{ 0x98, "NOKIA_BTS_HW_DB_DOWNLOAD_REQ" },
+	{ 0x99, "NOKIA_BTS_PWR_SUPPLY_CONTROL" },
+	{ 0x9A, "NOKIA_BTS_ATTRIBUTE_REQ" },
+	{ 0x9B, "NOKIA_BTS_ATTRIBUTE_REPORT" },
+	{ 0x9C, "NOKIA_BTS_HW_REQ" },
+	{ 0x9D, "NOKIA_BTS_HW_REPORT" },
+	{ 0x9E, "NOKIA_BTS_RTE_TEST_REQ" },
+	{ 0x9F, "NOKIA_BTS_RTE_TEST_REPORT" },
+	{ 0xA0, "NOKIA_BTS_HW_DB_VERIFICATION_REQ" },
+	{ 0xA1, "NOKIA_BTS_CLOCK_REQ" },
+	{ 0xA2, "NOKIA_AC_CIRCUIT_REQ_NACK" },
+	{ 0xA3, "NOKIA_AC_INTERRUPTED" },
+	{ 0xA4, "NOKIA_BTS_NEW_TRE_INFO" },
+	{ 0xA5, "NOKIA_AC_BSC_CIRCUITS_ALLOCATED" },
+	{ 0xA6, "NOKIA_BTS_TRE_POLL_LIST" },
+	{ 0xA7, "NOKIA_AC_CIRCUIT_REQ" },
+	{ 0xA8, "NOKIA_BTS_BLOCK_CTRL_REQ" },
+	{ 0xA9, "NOKIA_BTS_GSM_TIME_REQ" },
+	{ 0xAA, "NOKIA_BTS_GSM_TIME" },
+	{ 0xAB, "NOKIA_BTS_OUTPUT_CONTROL" },
+	{ 0xAC, "NOKIA_BTS_STATE_CHANGED" },
+	{ 0xAD, "NOKIA_BTS_SW_SAVE_REQ" },
+	{ 0xAE, "NOKIA_BTS_ALARM" },
+	{ 0xAF, "NOKIA_BTS_CHA_ADM_STATE" },
+	{ 0xB0, "NOKIA_AC_POOL_SIZE_REPORT" },
+	{ 0xB1, "NOKIA_AC_POOL_SIZE_INQUIRY" },
+	{ 0xB2, "NOKIA_BTS_COMMISS_TEST_COMPLETED" },
+	{ 0xB3, "NOKIA_BTS_COMMISS_TEST_REQ" },
+	{ 0xB4, "NOKIA_BTS_TRANSP_BTS_TO_BSC" },
+	{ 0xB5, "NOKIA_BTS_TRANSP_BSC_TO_BTS" },
+	{ 0xB6, "NOKIA_BTS_LCS_COMMAND" },
+	{ 0xB7, "NOKIA_BTS_LCS_ANSWER" },
+	{ 0xB8, "NOKIA_BTS_LMU_FN_OFFSET_COMMAND" },
+	{ 0xB9, "NOKIA_BTS_LMU_FN_OFFSET_ANSWER" },
+	{ 0, NULL }
+};
+
+static const char *get_msg_type_name_string(uint8_t msg_type)
+{
+	return get_value_string(nokia_msgt_name, msg_type);
+}
+
+static const struct value_string nokia_element_name[] = {
+	{ 0x01, "Ny1" },
+	{ 0x02, "T3105_F" },
+	{ 0x03, "Interference band limits" },
+	{ 0x04, "Interference report timer in secs" },
+	{ 0x05, "Channel configuration per TS" },
+	{ 0x06, "BSIC" },
+	{ 0x07, "RACH report timer in secs" },
+	{ 0x08, "Hardware database status" },
+	{ 0x09, "BTS RX level" },
+	{ 0x0A, "ARFN" },
+	{ 0x0B, "STM antenna attenuation" },
+	{ 0x0C, "Cell allocation bitmap" },
+	{ 0x0D, "Radio definition per TS" },
+	{ 0x0E, "Frame number" },
+	{ 0x0F, "Antenna diversity" },
+	{ 0x10, "T3105_D" },
+	{ 0x11, "File format" },
+	{ 0x12, "Last File" },
+	{ 0x13, "BTS type" },
+	{ 0x14, "Erasure mode" },
+	{ 0x15, "Hopping mode" },
+	{ 0x16, "Floating TRX" },
+	{ 0x17, "Power supplies" },
+	{ 0x18, "Reset type" },
+	{ 0x19, "Averaging period" },
+	{ 0x1A, "RBER2" },
+	{ 0x1B, "LAC" },
+	{ 0x1C, "CI" },
+	{ 0x1D, "Failure parameters" },
+	{ 0x1E, "(RF max power reduction)" },
+	{ 0x1F, "Measured RX_SENS" },
+	{ 0x20, "Extended cell radius" },
+	{ 0x21, "reserved" },
+	{ 0x22, "Success-Failure" },
+	{ 0x23, "Ack-Nack" },
+	{ 0x24, "OMU test results" },
+	{ 0x25, "File identity" },
+	{ 0x26, "Generation and version code" },
+	{ 0x27, "SW description" },
+	{ 0x28, "BCCH LEV" },
+	{ 0x29, "Test type" },
+	{ 0x2A, "Subscriber number" },
+	{ 0x2B, "reserved" },
+	{ 0x2C, "HSN" },
+	{ 0x2D, "reserved" },
+	{ 0x2E, "MS RXLEV" },
+	{ 0x2F, "MS TXLEV" },
+	{ 0x30, "RXQUAL" },
+	{ 0x31, "RX SENS" },
+	{ 0x32, "Alarm block" },
+	{ 0x33, "Neighbouring BCCH levels" },
+	{ 0x34, "STM report type" },
+	{ 0x35, "MA" },
+	{ 0x36, "MAIO" },
+	{ 0x37, "H_FLAG" },
+	{ 0x38, "TCH_ARFN" },
+	{ 0x39, "Clock output" },
+	{ 0x3A, "Transmitted power" },
+	{ 0x3B, "Clock sync" },
+	{ 0x3C, "TMS protocol discriminator" },
+	{ 0x3D, "TMS protocol data" },
+	{ 0x3E, "FER" },
+	{ 0x3F, "SWR result" },
+	{ 0x40, "Object identity" },
+	{ 0x41, "STM RX Antenna Test" },
+	{ 0x42, "reserved" },
+	{ 0x43, "reserved" },
+	{ 0x44, "Object current state" },
+	{ 0x45, "reserved" },
+	{ 0x46, "FU channel configuration" },
+	{ 0x47, "reserved" },
+	{ 0x48, "ARFN of a CU" },
+	{ 0x49, "FU radio definition" },
+	{ 0x4A, "reserved" },
+	{ 0x4B, "Severity" },
+	{ 0x4C, "Diversity selection" },
+	{ 0x4D, "RX antenna test" },
+	{ 0x4E, "RX antenna supervision period" },
+	{ 0x4F, "RX antenna state" },
+	{ 0x50, "Sector configuration" },
+	{ 0x51, "Additional info" },
+	{ 0x52, "SWR parameters" },
+	{ 0x53, "HW inquiry mode" },
+	{ 0x54, "reserved" },
+	{ 0x55, "Availability status" },
+	{ 0x56, "reserved" },
+	{ 0x57, "EAC inputs" },
+	{ 0x58, "EAC outputs" },
+	{ 0x59, "reserved" },
+	{ 0x5A, "Position" },
+	{ 0x5B, "HW unit identity" },
+	{ 0x5C, "RF test signal attenuation" },
+	{ 0x5D, "Operational state" },
+	{ 0x5E, "Logical object identity" },
+	{ 0x5F, "reserved" },
+	{ 0x60, "BS_TXPWR_OM" },
+	{ 0x61, "Loop_Duration" },
+	{ 0x62, "LNA_Path_Selection" },
+	{ 0x63, "Serial number" },
+	{ 0x64, "HW version" },
+	{ 0x65, "Obj. identity and obj. state" },
+	{ 0x66, "reserved" },
+	{ 0x67, "EAC input definition" },
+	{ 0x68, "EAC id and text" },
+	{ 0x69, "HW unit status" },
+	{ 0x6A, "SW release version" },
+	{ 0x6B, "FW version" },
+	{ 0x6C, "Bit_Error_Ratio" },
+	{ 0x6D, "RXLEV_with_Attenuation" },
+	{ 0x6E, "RXLEV_without_Attenuation" },
+	{ 0x6F, "reserved" },
+	{ 0x70, "CU_Results" },
+	{ 0x71, "reserved" },
+	{ 0x72, "LNA_Path_Results" },
+	{ 0x73, "RTE Results" },
+	{ 0x74, "Real Time" },
+	{ 0x75, "RX diversity selection" },
+	{ 0x76, "EAC input config" },
+	{ 0x77, "Feature support" },
+	{ 0x78, "File version" },
+	{ 0x79, "Outputs" },
+	{ 0x7A, "FU parameters" },
+	{ 0x7B, "Diagnostic info" },
+	{ 0x7C, "FU BSIC" },
+	{ 0x7D, "TRX Configuration" },
+	{ 0x7E, "Download status" },
+	{ 0x7F, "RX difference limit" },
+	{ 0x80, "TRX HW capability" },
+	{ 0x81, "Common HW config" },
+	{ 0x82, "Autoconfiguration pool size" },
+	{ 0x83, "TRE diagnostic info" },
+	{ 0x84, "TRE object identity" },
+	{ 0x85, "New TRE Info" },
+	{ 0x86, "Acknowledgement period" },
+	{ 0x87, "Synchronization mode" },
+	{ 0x88, "reserved" },
+	{ 0x89, "Block Control Data" },
+	{ 0x8A, "SW load mode" },
+	{ 0x8B, "Recommended recovery action" },
+	{ 0x8C, "BSC BCF id" },
+	{ 0x8D, "Q1 baud rate" },
+	{ 0x8E, "Allocation status" },
+	{ 0x8F, "Functional entity number" },
+	{ 0x90, "Transmission delay" },
+	{ 0x91, "Loop Duration ms" },
+	{ 0x92, "Logical channel" },
+	{ 0x93, "Q1 address" },
+	{ 0x94, "Alarm detail" },
+	{ 0x95, "Cabinet type" },
+	{ 0x96, "HW unit existence" },
+	{ 0x97, "RF power parameters" },
+	{ 0x98, "Message scenario" },
+	{ 0x99, "HW unit max amount" },
+	{ 0x9A, "Master TRX" },
+	{ 0x9B, "Transparent data" },
+	{ 0x9C, "BSC topology info" },
+	{ 0x9D, "Air i/f modulation" },
+	{ 0x9E, "LCS Q1 command data" },
+	{ 0x9F, "Frame number offset" },
+	{ 0xA0, "Abis TSL" },
+	{ 0xA1, "Dynamic pool info" },
+	{ 0xA2, "LCS LLP data" },
+	{ 0xA3, "LCS Q1 answer data" },
+	{ 0xA4, "DFCA FU Radio Definition" },
+	{ 0xA5, "Antenna hopping" },
+	{ 0xA6, "Field record sequence number" },
+	{ 0xA7, "Timeslot offslot" },
+	{ 0xA8, "EPCR capability" },
+	{ 0xA9, "Connectsite optional element" },
+	{ 0xAA, "TSC" },
+	{ 0xAB, "Special TX Power Setting" },
+	{ 0xAC, "Optional sync settings" },
+	{ 0xFA, "Abis If parameters" },
+	{ 0, NULL }
+};
+
+static const char *get_element_name_string(uint16_t element)
+{
+	return get_value_string(nokia_element_name, element);
+}
+
+static const struct value_string nokia_bts_types[] = {
+	{ 0x0a, 	"MetroSite GSM 900" },
+	{ 0x0b,		"MetroSite GSM 1800" },
+	{ 0x0c,		"MetroSite GSM 1900 (PCS)" },
+	{ 0x0d,		"MetroSite GSM 900 & 1800" },
+	{ 0x0e,		"InSite GSM 900" },
+	{ 0x0f,		"InSite GSM 1800" },
+	{ 0x10,		"InSite GSM 1900" },
+	{ 0x11,		"UltraSite GSM 900" },
+	{ 0x12,		"UltraSite GSM 1800" },
+	{ 0x13,		"UltraSite GSM/US-TDMA 1900" },
+	{ 0x14,		"UltraSite GSM 900 & 1800" },
+	{ 0x16,		"UltraSite GSM/US-TDMA 850" },
+	{ 0x18,		"MetroSite GSM/US-TDMA 850" },
+	{ 0x19,		"UltraSite GSM 800/1900" },
+	{ 0, 		NULL }
+};
+
+static const char *get_bts_type_string(uint8_t type)
+{
+	return get_value_string(nokia_bts_types, type);
+}
+
+static const struct value_string nokia_severity[] = {
+	{ 0,	"indeterminate" },
+	{ 1,	"critical" },
+	{ 2,	"major" },
+	{ 3,	"minor" },
+	{ 4,	"warning" },
+	{ 0,	NULL }
+};
+
+static const char *get_severity_string(uint8_t severity)
+{
+	return get_value_string(nokia_severity, severity);
+}
+
+/* TODO: put in a separate file ? */
+
+/* some message IDs */
+
+#define NOKIA_MSG_CONF_DATA             128
+#define NOKIA_MSG_ACK                   129
+#define NOKIA_MSG_OMU_STARTED           130
+#define NOKIA_MSG_START_DOWNLOAD_REQ    131
+#define NOKIA_MSG_MF_REQ                132
+#define NOKIA_MSG_RESET_REQ             134
+#define NOKIA_MSG_CONF_REQ              136
+#define NOKIA_MSG_CONF_COMPLETE         142
+#define NOKIA_MSG_BLOCK_CTRL_REQ        168
+#define NOKIA_MSG_STATE_CHANGED         172
+#define NOKIA_MSG_ALARM                 174
+
+/* some element IDs */
+
+#define NOKIA_EI_BTS_TYPE       0x13
+#define NOKIA_EI_ACK            0x23
+#define NOKIA_EI_ADD_INFO       0x51
+#define NOKIA_EI_SEVERITY       0x4B
+#define NOKIA_EI_ALARM_DETAIL   0x94
+
+#define OM_ALLOC_SIZE       1024
+#define OM_HEADROOM_SIZE    128
+
+static uint8_t fu_config_template[] = {
+	0x7F, 0x7A, 0x39,
+	/* ID = 0x7A (FU parameters) ## constructed ## */
+	/* length = 57 */
+	/* [3] */
+
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [6] */
+	0x00, 0x07, 0x01, 0xFF,
+
+	0x41, 0x02,
+	/* ID = 0x01 (Ny1) */
+	/* length = 2 */
+	/* [12] */
+	0x00, 0x05,
+
+	0x42, 0x02,
+	/* ID = 0x02 (T3105_F) */
+	/* length = 2 */
+	/* [16] */
+	0x00, 0x28, /* FIXME: use net->T3105 */
+
+	0x50, 0x02,
+	/* ID = 0x10 (T3105_D) */
+	/* length = 2 */
+	/* [20] */
+	0x00, 0x28, /* FIXME: use net->T3105 */
+
+	0x43, 0x05,
+	/* ID = 0x03 (Interference band limits) */
+	/* length = 5 */
+	/* [24] */
+	0x0F, 0x1B, 0x27, 0x33, 0x3F,
+
+	0x44, 0x02,
+	/* ID = 0x04 (Interference report timer in secs) */
+	/* length = 2 */
+	/* [31] */
+	0x00, 0x10,
+
+	0x47, 0x01,
+	/* ID = 0x07 (RACH report timer in secs) */
+	/* length = 1 */
+	/* [35] */
+	0x1E,
+
+	0x4C, 0x10,
+	/* ID = 0x0C (Cell allocation bitmap) ####### */
+	/* length = 16 */
+	/* [38] */
+	0x8F, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+	0x59, 0x01,
+	/* ID = 0x19 (Averaging period) */
+	/* length = 1 */
+	/* [56] */
+	0x01,
+
+	0x5E, 0x01,
+	/* ID = 0x1E ((RF max power reduction)) */
+	/* length = 1 */
+	/* [59] */
+	0x00,
+
+	0x7F, 0x46, 0x11,
+	/* ID = 0x46 (FU channel configuration) ## constructed ## */
+	/* length = 17 */
+	/* [63] */
+
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [66] */
+	0x00, 0x07, 0x01, 0xFF,
+
+	0x45, 0x08,
+	/* ID = 0x05 (Channel configuration per TS) */
+	/* length = 8 */
+	/* [72] */
+	0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+
+	0x7F, 0x65, 0x0B,
+	/* ID = 0x65 (Obj. identity and obj. state) ## constructed ## */
+	/* length = 11 */
+	/* [83] */
+
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [86] */
+	0x00, 0x04, 0x01, 0xFF,
+
+	0x5F, 0x44, 0x01,
+	/* ID = 0x44 (Object current state) */
+	/* length = 1 */
+	/* [93] */
+	0x03,
+
+	0x7F, 0x7C, 0x0A,
+	/* ID = 0x7C (FU BSIC) ## constructed ## */
+	/* length = 10 */
+	/* [97] */
+
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [100] */
+	0x00, 0x07, 0x01, 0xFF,
+
+	0x46, 0x01,
+	/* ID = 0x06 (BSIC) */
+	/* length = 1 */
+	/* [106] */
+	0x00,
+
+	0x7F, 0x48, 0x0B,
+	/* ID = 0x48 (ARFN of a CU) ## constructed ## */
+	/* length = 11 */
+	/* [110] */
+
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [113] */
+	0x00, 0x08, 0x01, 0xFF,
+
+	0x4A, 0x02,
+	/* ID = 0x0A (ARFN) ####### */
+	/* length = 2 */
+	/* [119] */
+	0x03, 0x62,
+
+	0x7F, 0x49, 0x59,
+	/* ID = 0x49 (FU radio definition) ## constructed ## */
+	/* length = 89 */
+	/* [124] */
+
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [127] */
+	0x00, 0x07, 0x01, 0xFF,
+
+	0x4D, 0x50,
+	/* ID = 0x0D (Radio definition per TS) ####### */
+	/* length = 80 */
+	/* [133] */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* MA */
+	0x03, 0x62,		/* HSN, MAIO or ARFCN if no hopping */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x62,
+};
+
+/* TODO: put in a separate file ? */
+
+/*
+  build the configuration for each TRX
+*/
+
+static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id,
+			  uint8_t * fu_config, int *hopping)
+{
+	int i;
+
+	*hopping = 0;
+
+	memcpy(fu_config, fu_config_template, sizeof(fu_config_template));
+
+	/* set ID */
+
+	fu_config[6 + 2] = id;
+	fu_config[66 + 2] = id;
+	fu_config[86 + 2] = id;
+	fu_config[100 + 2] = id;
+	fu_config[113 + 2] = id;
+	fu_config[127 + 2] = id;
+
+	/* set ARFCN */
+
+	uint16_t arfcn = trx->arfcn;
+
+	fu_config[119] = arfcn >> 8;
+	fu_config[119 + 1] = arfcn & 0xFF;
+
+	for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+		struct gsm_bts_trx_ts *ts = &trx->ts[i];
+
+		if (ts->hopping.enabled) {
+			/* reverse order */
+			int j;
+			for (j = 0; j < ts->hopping.ma_len; j++)
+				fu_config[133 + (i * 10) + (7 - j)] =
+				    ts->hopping.ma_data[j];
+			fu_config[133 + 8 + (i * 10)] = ts->hopping.hsn;
+			fu_config[133 + 8 + 1 + (i * 10)] = ts->hopping.maio;
+			*hopping = 1;
+		} else {
+			fu_config[133 + 8 + (i * 10)] = arfcn >> 8;
+			fu_config[133 + 8 + 1 + (i * 10)] = arfcn & 0xFF;
+		}
+	}
+
+	/* set BSIC */
+
+	/*
+	   Attention: all TRX except the first one seem to get the TSC
+	   from the CHANNEL ACTIVATION command (in CHANNEL IDENTIFICATION,
+	   GSM 04.08 CHANNEL DESCRIPTION).
+	   There was a bug in rsl_chan_activate_lchan() setting this parameter.
+	 */
+
+	uint8_t bsic = trx->bts->bsic;
+
+	fu_config[106] = bsic;
+
+	/* set CA */
+
+	if (generate_cell_chan_list(&fu_config[38], trx->bts) != 0) {
+		fprintf(stderr, "generate_cell_chan_list failed\n");
+		return 0;
+	}
+
+	/* set channel configuration */
+
+	for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+		struct gsm_bts_trx_ts *ts = &trx->ts[i];
+		uint8_t chan_config;
+
+		/*
+		   0 = FCCH + SCH + BCCH + CCCH
+		   1 = FCCH + SCH + BCCH + CCCH + SDCCH/4 + SACCH/4
+		   2 = BCCH + CCCH (This combination is not used in any BTS)
+		   3 = FCCH + SCH + BCCH + CCCH + SDCCH/4 with SDCCH2 used as CBCH
+		   4 = SDCCH/8 + SACCH/8
+		   5 = SDCCH/8 with SDCCH2 used as CBCH
+		   6 = TCH/F + FACCH/F + SACCH/F
+		   7 = E-RACH (Talk family)
+		   9 = Dual rate (capability for TCH/F and TCH/H)
+		   10 = reserved for BTS internal use
+		   11 = PBCCH + PCCCH + PDTCH + PACCH + PTCCH (can be used in GPRS release 2).
+		   0xFF = spare TS
+		 */
+
+		if (ts->pchan == GSM_PCHAN_NONE)
+			chan_config = 0xFF;
+		else if (ts->pchan == GSM_PCHAN_CCCH)
+			chan_config = 0;
+		else if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4)
+			chan_config = 1;
+		else if (ts->pchan == GSM_PCHAN_TCH_F)
+			chan_config = 6;	/* 9 should work too */
+		else if (ts->pchan == GSM_PCHAN_TCH_H)
+			chan_config = 9;
+		else if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C)
+			chan_config = 4;
+		else if (ts->pchan == GSM_PCHAN_PDCH)
+			chan_config = 11;
+		else {
+			fprintf(stderr,
+				"unsupported channel config %d for timeslot %d\n",
+				ts->pchan, i);
+			return 0;
+		}
+
+		fu_config[72 + i] = chan_config;
+	}
+	return sizeof(fu_config_template);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t bts_config_1[] = {
+	0x4E, 0x02,
+	/* ID = 0x0E (Frame number) */
+	/* length = 2 */
+	/* [2] */
+	0xFF, 0xFF,
+
+	0x5F, 0x4E, 0x02,
+	/* ID = 0x4E (RX antenna supervision period) */
+	/* length = 2 */
+	/* [7] */
+	0xFF, 0xFF,
+
+	0x5F, 0x50, 0x02,
+	/* ID = 0x50 (Sector configuration) */
+	/* length = 2 */
+	/* [12] */
+	0x01, 0x01,
+};
+
+static uint8_t bts_config_2[] = {
+	0x55, 0x02,
+	/* ID = 0x15 (Hopping mode) */
+	/* length = 2 */
+	/* [2] */
+	0x01, 0x00,
+
+	0x5F, 0x75, 0x02,
+	/* ID = 0x75 (RX diversity selection) */
+	/* length = 2 */
+	/* [7] */
+	0x01, 0x01,
+};
+
+static uint8_t bts_config_3[] = {
+	0x5F, 0x20, 0x02,
+	/* ID = 0x20 (Extended cell radius) */
+	/* length = 2 */
+	/* [3] */
+	0x01, 0x00,
+};
+
+static uint8_t bts_config_4[] = {
+	0x5F, 0x74, 0x09,
+	/* ID = 0x74 (Real Time) */
+	/* length = 9 */
+	/* [3] year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */
+	0x07, 0xDB, 0x06, 0x02, 0x0B, 0x20, 0x0C, 0x00,
+	0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [15] */
+	0x01, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [21] */
+	0x02, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [27] */
+	0x03, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [33] */
+	0x04, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [39] */
+	0x05, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [45] */
+	0x06, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [51] */
+	0x07, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [57] */
+	0x08, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [63] */
+	0x09, 0x01, 0x00,
+
+	0x5F, 0x76, 0x03,
+	/* ID = 0x76 (EAC input config) */
+	/* length = 3 */
+	/* [69] */
+	0x0A, 0x01, 0x00,
+};
+
+static uint8_t bts_config_insite[] = {
+	0x4E, 0x02,
+	/* ID = 0x0E (Frame number) */
+	/* length = 2 */
+	/* [2] */
+	0xFF, 0xFF,
+
+	0x5F, 0x4E, 0x02,
+	/* ID = 0x4E (RX antenna supervision period) */
+	/* length = 2 */
+	/* [7] */
+	0xFF, 0xFF,
+
+	0x5F, 0x50, 0x02,
+	/* ID = 0x50 (Sector configuration) */
+	/* length = 2 */
+	/* [12] */
+	0x01, 0x01,
+
+	0x55, 0x02,
+	/* ID = 0x15 (Hopping mode) */
+	/* length = 2 */
+	/* [16] */
+	0x01, 0x00,
+
+	0x5F, 0x20, 0x02,
+	/* ID = 0x20 (Extended cell radius) */
+	/* length = 2 */
+	/* [21] */
+	0x01, 0x00,
+
+	0x5F, 0x74, 0x09,
+	/* ID = 0x74 (Real Time) */
+	/* length = 9 */
+	/* [26] */
+	0x07, 0xDB, 0x07, 0x0A, 0x0F, 0x09, 0x0B, 0x00,
+	0x00,
+};
+
+void set_real_time(uint8_t * real_time)
+{
+	time_t t;
+	struct tm *tm;
+
+	t = time(NULL);
+	tm = localtime(&t);
+
+	/* year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */
+
+	real_time[0] = (1900 + tm->tm_year) >> 8;
+	real_time[1] = (1900 + tm->tm_year) & 0xFF;
+	real_time[2] = tm->tm_mon + 1;
+	real_time[3] = tm->tm_mday;
+	real_time[4] = tm->tm_hour;
+	real_time[5] = tm->tm_min;
+	real_time[6] = tm->tm_sec;
+	real_time[7] = 0;
+	real_time[8] = 0;
+}
+
+/* TODO: put in a separate file ? */
+
+/*
+  build the configuration data
+*/
+
+static int make_bts_config(uint8_t bts_type, int n_trx, uint8_t * fu_config,
+			   int need_hopping)
+{
+	/* is it an InSite BTS ? */
+	if (bts_type == 0x0E || bts_type == 0x0F || bts_type == 0x10) {	/* TODO */
+		if (n_trx != 1) {
+			fprintf(stderr, "InSite has only one TRX\n");
+			return 0;
+		}
+		if (need_hopping != 0) {
+			fprintf(stderr, "InSite does not support hopping\n");
+			return 0;
+		}
+		memcpy(fu_config, bts_config_insite, sizeof(bts_config_insite));
+		set_real_time(&fu_config[26]);
+		return sizeof(bts_config_insite);
+	}
+
+	int len = 0;
+	int i;
+
+	memcpy(fu_config + len, bts_config_1, sizeof(bts_config_1));
+
+	/* set sector configuration */
+	fu_config[len + 12 - 1] = 1 + n_trx;	/* len */
+	for (i = 0; i < n_trx; i++)
+		fu_config[len + 12 + 1 + i] = ((i + 1) & 0xFF);
+
+	len += (sizeof(bts_config_1) + (n_trx - 1));
+
+	memcpy(fu_config + len, bts_config_2, sizeof(bts_config_2));
+	/* set hopping mode (Baseband and RF hopping work for the MetroSite) */
+	if (need_hopping)
+		fu_config[len + 2 + 1] = 1;	/* 0: no hopping, 1: Baseband hopping, 2: RF hopping */
+	len += sizeof(bts_config_2);
+
+	/* set extended cell radius for each TRX */
+	for (i = 0; i < n_trx; i++) {
+		memcpy(fu_config + len, bts_config_3, sizeof(bts_config_3));
+		fu_config[len + 3] = ((i + 1) & 0xFF);
+		len += sizeof(bts_config_3);
+	}
+
+	memcpy(fu_config + len, bts_config_4, sizeof(bts_config_4));
+	set_real_time(&fu_config[len + 3]);
+	len += sizeof(bts_config_4);
+
+	return len;
+}
+
+/* TODO: put in a separate file ? */
+
+static struct msgb *nm_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, "OML");
+}
+
+/* TODO: put in a separate file ? */
+
+struct abis_om_nokia_hdr {
+	uint8_t msg_type;
+	uint8_t spare;
+	uint16_t reference;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#define ABIS_OM_NOKIA_HDR_SIZE (sizeof(struct abis_om_hdr) + sizeof(struct abis_om_nokia_hdr))
+
+static int abis_nm_send(struct gsm_bts *bts, uint8_t msg_type, uint16_t ref,
+			uint8_t * data, int len_data)
+{
+	struct abis_om_hdr *oh;
+	struct abis_om_nokia_hdr *noh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *)msgb_put(msg,
+					    ABIS_OM_NOKIA_HDR_SIZE + len_data);
+
+	oh->mdisc = ABIS_OM_MDISC_FOM;
+	oh->placement = ABIS_OM_PLACEMENT_ONLY;
+	oh->sequence = 0;
+	oh->length = sizeof(struct abis_om_nokia_hdr) + len_data;
+
+	noh = (struct abis_om_nokia_hdr *)oh->data;
+
+	noh->msg_type = msg_type;
+	noh->spare = 0;
+	noh->reference = htons(ref);
+	memcpy(noh->data, data, len_data);
+
+	DEBUGPC(DNM, "Sending %s\n", get_msg_type_name_string(msg_type));
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t download_req[] = {
+	0x5F, 0x25, 0x0B,
+	/* ID = 0x25 (File identity) */
+	/* length = 11 */
+	/* [3] */
+	0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
+	0x2A, 0x2A, 0x2A,
+
+	0x5F, 0x78, 0x03,
+	/* ID = 0x78 (File version) */
+	/* length = 3 */
+	/* [17] */
+	0x2A, 0x2A, 0x2A,
+
+	0x5F, 0x81, 0x0A, 0x01,
+	/* ID = 0x8A (SW load mode) */
+	/* length = 1 */
+	/* [24] */
+	0x01,
+
+	0x5F, 0x81, 0x06, 0x01,
+	/* ID = 0x86 (Acknowledgement period) */
+	/* length = 1 */
+	/* [29] */
+	0x01,
+};
+
+static int abis_nm_download_req(struct gsm_bts *bts, uint16_t ref)
+{
+	uint8_t *data = download_req;
+	int len_data = sizeof(download_req);
+
+	return abis_nm_send(bts, NOKIA_MSG_START_DOWNLOAD_REQ, ref, data,
+			    len_data);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t ack[] = {
+	0x5F, 0x23, 0x01,
+	/* ID = 0x23 (Ack-Nack) */
+	/* length = 1 */
+	/* [3] */
+	0x01,
+};
+
+static int abis_nm_ack(struct gsm_bts *bts, uint16_t ref)
+{
+	uint8_t *data = ack;
+	int len_data = sizeof(ack);
+
+	return abis_nm_send(bts, NOKIA_MSG_ACK, ref, data, len_data);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t reset[] = {
+	0x5F, 0x40, 0x04,
+	/* ID = 0x40 (Object identity) */
+	/* length = 4 */
+	/* [3] */
+	0x00, 0x01, 0xFF, 0xFF,
+};
+
+static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref)
+{
+	uint8_t *data = reset;
+	int len_data = sizeof(reset);
+	LOGP(DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf);
+	return abis_nm_send(bts, NOKIA_MSG_RESET_REQ, ref, data, len_data);
+}
+
+/* TODO: put in a separate file ? */
+
+static int abis_nm_send_multi_segments(struct gsm_bts *bts, uint8_t msg_type,
+				       uint16_t ref, uint8_t * data, int len)
+{
+	int len_remain, len_to_send, max_send;
+	int seq = 0;
+	int ret;
+
+	len_remain = len;
+
+	while (len_remain) {
+		struct abis_om_hdr *oh;
+		struct abis_om_nokia_hdr *noh;
+		struct msgb *msg = nm_msgb_alloc();
+
+		if (seq == 0)
+			max_send = 256 - sizeof(struct abis_om_nokia_hdr);
+		else
+			max_send = 256;
+
+		if (len_remain > max_send) {
+			len_to_send = max_send;
+
+			if (seq == 0) {
+				/* first segment */
+				oh = (struct abis_om_hdr *)msgb_put(msg,
+								    ABIS_OM_NOKIA_HDR_SIZE
+								    +
+								    len_to_send);
+
+				oh->mdisc = ABIS_OM_MDISC_FOM;
+				oh->placement = ABIS_OM_PLACEMENT_FIRST;	/* first segment of multi-segment message */
+				oh->sequence = seq;
+				oh->length = 0;	/* 256 bytes */
+
+				noh = (struct abis_om_nokia_hdr *)oh->data;
+
+				noh->msg_type = msg_type;
+				noh->spare = 0;
+				noh->reference = htons(ref);
+				memcpy(noh->data, data, len_to_send);
+			} else {
+				/* segment in between */
+				oh = (struct abis_om_hdr *)msgb_put(msg,
+								    sizeof
+								    (struct
+								     abis_om_hdr)
+								    +
+								    len_to_send);
+
+				oh->mdisc = ABIS_OM_MDISC_FOM;
+				oh->placement = ABIS_OM_PLACEMENT_MIDDLE;	/* segment of multi-segment message */
+				oh->sequence = seq;
+				oh->length = 0;	/* 256 bytes */
+
+				memcpy(oh->data, data, len_to_send);
+			}
+		} else {
+
+			len_to_send = len_remain;
+
+			/* check if message fits in a single segment */
+
+			if (seq == 0)
+				return abis_nm_send(bts, msg_type, ref, data,
+						    len_to_send);
+
+			/* last segment */
+
+			oh = (struct abis_om_hdr *)msgb_put(msg,
+							    sizeof(struct
+								   abis_om_hdr)
+							    + len_to_send);
+
+			oh->mdisc = ABIS_OM_MDISC_FOM;
+			oh->placement = ABIS_OM_PLACEMENT_LAST;	/* last segment of multi-segment message */
+			oh->sequence = seq;
+			oh->length = len_to_send;
+
+			memcpy(oh->data, data, len_to_send);
+		}
+
+		DEBUGPC(DNM, "Sending multi-segment %d\n", seq);
+
+		ret = abis_nm_sendmsg(bts, msg);
+		if (ret < 0)
+			return ret;
+
+		nokia_abis_nm_queue_send_next(bts);
+
+		/* next segment */
+		len_remain -= len_to_send;
+		data += len_to_send;
+		seq++;
+	}
+	return ret;
+}
+
+/* TODO: put in a separate file ? */
+
+static int abis_nm_send_config(struct gsm_bts *bts, uint8_t bts_type)
+{
+	struct gsm_bts_trx *trx;
+	uint8_t config[2048];	/* TODO: might be too small if lots of TRX are used */
+	int len = 0;
+	int idx = 0;
+	int ret;
+	int hopping = 0;
+	int need_hopping = 0;
+
+	memset(config, 0, sizeof(config));
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+#if 0				/* debugging */
+		printf("TRX\n");
+		printf("  arfcn: %d\n", trx->arfcn);
+		printf("  bsic: %d\n", trx->bts->bsic);
+		uint8_t ca[20];
+		memset(ca, 0xFF, sizeof(ca));
+		ret = generate_cell_chan_list(ca, trx->bts);
+		printf("  ca (%d): %s\n", ret, osmo_hexdump(ca, sizeof(ca)));
+		int i;
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+
+			printf("  pchan %d: %d\n", i, ts->pchan);
+		}
+#endif
+		ret = make_fu_config(trx, idx + 1, config + len, &hopping);
+		need_hopping |= hopping;
+		len += ret;
+
+		idx++;
+	}
+
+	ret = make_bts_config(bts_type, idx, config + len, need_hopping);
+	len += ret;
+
+#if 0				/* debugging */
+	dump_elements(config, len);
+#endif
+
+	return abis_nm_send_multi_segments(bts, NOKIA_MSG_CONF_DATA, 1, config,
+					   len);
+}
+
+#define GET_NEXT_BYTE if(idx >= len) return 0; \
+                        ub = data[idx++];
+
+static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value,
+			int max_value)
+{
+	uint8_t ub;
+	int idx = 0;
+	int found = 0;
+	int constructed __attribute__((unused));
+	uint16_t id_value;
+
+	for (;;) {
+
+		GET_NEXT_BYTE;
+
+		/* encoding bit, construced means that other elements are contained */
+		constructed = ((ub & 0x20) ? 1 : 0);
+
+		if ((ub & 0x1F) == 0x1F) {
+			/* fixed pattern, ID follows */
+			GET_NEXT_BYTE;	/* ID */
+			id_value = ub & 0x7F;
+			if (ub & 0x80) {
+				/* extension bit */
+				GET_NEXT_BYTE;	/* ID low part */
+				id_value = (id_value << 7) | (ub & 0x7F);
+			}
+			if (id_value == id)
+				found = 1;
+		} else {
+			id_value = (ub & 0x3F);
+			if (id_value == id)
+				found = 1;
+		}
+
+		GET_NEXT_BYTE;	/* length */
+
+		if (found) {
+			/* get data */
+			uint8_t n = ub;
+			uint8_t i;
+			for (i = 0; i < n; i++) {
+				GET_NEXT_BYTE;
+				if (max_value <= 0)
+					return -1;	/* buffer too small */
+				*value = ub;
+				value++;
+				max_value--;
+			}
+			return n;	/* length */
+		} else {
+			/* skip data */
+			uint8_t n = ub;
+			uint8_t i;
+			for (i = 0; i < n; i++) {
+				GET_NEXT_BYTE;
+			}
+		}
+	}
+	return 0;		/* not found */
+}
+
+static int dump_elements(uint8_t * data, int len)
+{
+	uint8_t ub;
+	int idx = 0;
+	int constructed;
+	uint16_t id_value;
+	static char indent[100] = "";	/* TODO: move static to BTS context */
+
+	for (;;) {
+
+		GET_NEXT_BYTE;
+
+		/* encoding bit, construced means that other elements are contained */
+		constructed = ((ub & 0x20) ? 1 : 0);
+
+		if ((ub & 0x1F) == 0x1F) {
+			/* fixed pattern, ID follows */
+			GET_NEXT_BYTE;	/* ID */
+			id_value = ub & 0x7F;
+			if (ub & 0x80) {
+				/* extension bit */
+				GET_NEXT_BYTE;	/* ID low part */
+				id_value = (id_value << 7) | (ub & 0x7F);
+			}
+
+		} else {
+			id_value = (ub & 0x3F);
+		}
+
+		GET_NEXT_BYTE;	/* length */
+
+		printf("%s--ID = 0x%02X (%s) %s\n", indent, id_value,
+		       get_element_name_string(id_value),
+		       constructed ? "** constructed **" : "");
+		printf("%s  length = %d\n", indent, ub);
+		printf("%s  %s\n", indent, osmo_hexdump(data + idx, ub));
+
+		if (constructed) {
+			int indent_len = strlen(indent);
+			strcat(indent, "   ");
+
+			dump_elements(data + idx, ub);
+
+			indent[indent_len] = 0;
+		}
+		/* skip data */
+		uint8_t n = ub;
+		uint8_t i;
+		for (i = 0; i < n; i++) {
+			GET_NEXT_BYTE;
+		}
+	}
+	return 0;
+}
+
+/* TODO: put in a separate file ? */
+
+/* taken from abis_nm.c */
+
+static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts)
+{
+	int wait = 0;
+	struct msgb *msg;
+	/* the queue is empty */
+	while (!llist_empty(&bts->abis_queue)) {
+		msg = msgb_dequeue(&bts->abis_queue);
+		wait = OBSC_NM_W_ACK_CB(msg);
+		abis_sendmsg(msg);
+
+		if (wait)
+			break;
+	}
+
+	bts->abis_nm_pend = wait;
+}
+
+/* TODO: put in a separate file ? */
+
+/* timer for restarting OML after BTS reset */
+
+static void reset_timer_cb(void *_bts)
+{
+	struct gsm_bts *bts = _bts;
+	struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+	struct e1inp_line *line;
+
+	bts->nokia.wait_reset = 0;
+
+	/* OML link */
+	line = e1inp_line_find(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to "
+		     "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+		return;
+	}
+
+	start_sabm_in_line(line, 0, -1);	/* stop all first */
+	start_sabm_in_line(line, 1, SAPI_OML);	/* start only OML */
+}
+
+/* TODO: put in a separate file ? */
+
+/*
+  This is how the configuration is done:
+  - start OML link
+  - reset BTS
+  - receive ACK, wait some time and restart OML link
+  - receive OMU STARTED message, send START DOWNLOAD REQ
+  - receive CNF REQ message, send CONF DATA
+  - receive ACK, start RSL link(s)
+  ACK some other messages received from the BTS.
+
+  Probably its also possible to configure the BTS without a reset, this 
+  has not been tested yet.
+*/
+
+static int abis_nm_rcvmsg_fom(struct msgb *mb)
+{
+	struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)mb->dst;
+	struct gsm_bts *bts = sign_link->trx->bts;
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_nokia_hdr *noh = msgb_l3(mb);
+	uint8_t mt = noh->msg_type;
+	int ret = 0;
+	uint16_t ref = ntohs(noh->reference);
+	uint8_t info[256];
+	uint8_t ack = 0xFF;
+	uint8_t severity = 0xFF;
+	int str_len;
+	int len_data;
+
+	if (bts->nokia.wait_reset) {
+		LOGP(DNM, LOGL_INFO,
+		     "Ignore message while waiting for reset\n");
+		return ret;
+	}
+
+	if (oh->length < sizeof(struct abis_om_nokia_hdr)) {
+		LOGP(DNM, LOGL_ERROR, "Message too short\n");
+		return -EINVAL;
+	}
+
+	len_data = oh->length - sizeof(struct abis_om_nokia_hdr);
+	LOGP(DNM, LOGL_INFO, "(0x%02X) %s\n", mt, get_msg_type_name_string(mt));
+#if 0				/* debugging */
+	dump_elements(noh->data, len_data);
+#endif
+
+	switch (mt) {
+	case NOKIA_MSG_OMU_STARTED:
+		if (find_element(noh->data, len_data,
+				 NOKIA_EI_BTS_TYPE, &bts->nokia.bts_type,
+				 sizeof(uint8_t)) == sizeof(uint8_t))
+			LOGP(DNM, LOGL_INFO, "BTS type = %d (%s)\n",
+			     bts->nokia.bts_type,
+			     get_bts_type_string(bts->nokia.bts_type));
+		else
+			LOGP(DNM, LOGL_ERROR, "BTS type not found\n");
+		/* send START_DOWNLOAD_REQ */
+		abis_nm_download_req(bts, ref);
+		break;
+	case NOKIA_MSG_MF_REQ:
+		break;
+	case NOKIA_MSG_CONF_REQ:
+		/* send ACK */
+		abis_nm_ack(bts, ref);
+		nokia_abis_nm_queue_send_next(bts);
+		/* send CONF_DATA */
+		abis_nm_send_config(bts, bts->nokia.bts_type);
+		bts->nokia.configured = 1;
+		break;
+	case NOKIA_MSG_ACK:
+		if (find_element
+		    (noh->data, len_data, NOKIA_EI_ACK, &ack,
+		     sizeof(uint8_t)) == sizeof(uint8_t)) {
+			LOGP(DNM, LOGL_INFO, "ACK = %d\n", ack);
+			if (ack != 1) {
+				LOGP(DNM, LOGL_ERROR, "No ACK received (%d)\n",
+				     ack);
+				/* TODO: properly handle failures (NACK) */
+			}
+		} else
+			LOGP(DNM, LOGL_ERROR, "ACK not found\n");
+
+		/* TODO: the assumption for the following is that no NACK was received */
+
+		/* ACK for reset message ? */
+		if (!bts->nokia.did_reset) {
+			bts->nokia.did_reset = 1;
+
+			/* 
+			   TODO: For the InSite processing the received data is 
+			   blocked in the driver during reset.
+			   Otherwise the LAPD module might assert because the InSite
+			   sends garbage on the E1 line during reset.
+			   This is done by looking at "wait_reset" in the driver
+			   (function handle_ts1_read()) and ignoring the received data.
+			   It seems to be necessary for the MetroSite too.
+			 */
+			bts->nokia.wait_reset = 1;
+
+			osmo_timer_setup(&bts->nokia.reset_timer,
+					 reset_timer_cb, bts);
+			osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0);
+
+			struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+			struct e1inp_line *line;
+			/* OML link */
+			line = e1inp_line_find(e1_link->e1_nr);
+			if (!line) {
+				LOGP(DLINP, LOGL_ERROR,
+				     "BTS %u OML link referring to "
+				     "non-existing E1 line %u\n", bts->nr,
+				     e1_link->e1_nr);
+				return -ENOMEM;
+			}
+
+			start_sabm_in_line(line, 0, -1);	/* stop all first */
+		}
+
+		/* ACK for CONF DATA message ? */
+		if (bts->nokia.configured != 0) {
+			/* start TRX  (RSL link) */
+
+			struct gsm_e1_subslot *e1_link =
+					&sign_link->trx->rsl_e1_link;
+			struct e1inp_line *line;
+
+			bts->nokia.configured = 0;
+
+			/* RSL Link */
+			line = e1inp_line_find(e1_link->e1_nr);
+			if (!line) {
+				LOGP(DLINP, LOGL_ERROR,
+				     "TRX (%u/%u) RSL link referring "
+				     "to non-existing E1 line %u\n",
+				     sign_link->trx->bts->nr, sign_link->trx->nr,
+				     e1_link->e1_nr);
+				return -ENOMEM;
+			}
+			/* start TRX */
+			start_sabm_in_line(line, 1, SAPI_RSL);	/* start only RSL */
+		}
+		break;
+	case NOKIA_MSG_STATE_CHANGED:
+		/* send ACK */
+		abis_nm_ack(bts, ref);
+		break;
+	case NOKIA_MSG_CONF_COMPLETE:
+		/* send ACK */
+		abis_nm_ack(bts, ref);
+		break;
+	case NOKIA_MSG_BLOCK_CTRL_REQ:	/* seems to be send when something goes wrong !? */
+		/* send ACK (do we have to send an ACK ?) */
+		abis_nm_ack(bts, ref);
+		break;
+	case NOKIA_MSG_ALARM:
+		find_element(noh->data, len_data, NOKIA_EI_SEVERITY, &severity,
+			     sizeof(severity));
+		/* TODO: there might be alarms with both elements set */
+		str_len =
+		    find_element(noh->data, len_data, NOKIA_EI_ADD_INFO, info,
+				 sizeof(info));
+		if (str_len > 0) {
+			info[str_len] = 0;
+			LOGP(DNM, LOGL_INFO, "ALARM Severity %s (%d) : %s\n",
+			     get_severity_string(severity), severity, info);
+		} else {	/* nothing found, try details */
+			str_len =
+			    find_element(noh->data, len_data,
+					 NOKIA_EI_ALARM_DETAIL, info,
+					 sizeof(info));
+			if (str_len > 0) {
+				uint16_t code;
+				info[str_len] = 0;
+				code = (info[0] << 8) + info[1];
+				LOGP(DNM, LOGL_INFO,
+				     "ALARM Severity %s (%d), code 0x%X : %s\n",
+				     get_severity_string(severity), severity,
+				     code, info + 2);
+			}
+		}
+		/* send ACK */
+		abis_nm_ack(bts, ref);
+		break;
+	}
+
+	nokia_abis_nm_queue_send_next(bts);
+
+	return ret;
+}
+
+/* TODO: put in a separate file ? */
+
+int abis_nokia_rcvmsg(struct msgb *msg)
+{
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	int rc = 0;
+
+	/* Various consistency checks */
+	if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+		     oh->placement);
+		if (oh->placement != ABIS_OM_PLACEMENT_FIRST)
+			return -EINVAL;
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+		     oh->sequence);
+		return -EINVAL;
+	}
+	msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+	switch (oh->mdisc) {
+	case ABIS_OM_MDISC_FOM:
+		LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_FOM\n");
+		rc = abis_nm_rcvmsg_fom(msg);
+		break;
+	case ABIS_OM_MDISC_MANUF:
+		LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_MANUF\n");
+		break;
+	case ABIS_OM_MDISC_MMI:
+	case ABIS_OM_MDISC_TRAU:
+		LOGP(DNM, LOGL_ERROR,
+		     "unimplemented ABIS OML message discriminator 0x%x\n",
+		     oh->mdisc);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR,
+		     "unknown ABIS OML message discriminator 0x%x\n",
+		     oh->mdisc);
+		return -EINVAL;
+	}
+
+	msgb_free(msg);
+	return rc;
+}
+
+static int bts_model_nokia_site_start(struct gsm_network *net);
+
+static void bts_model_nokia_site_e1line_bind_ops(struct e1inp_line *line)
+{
+	e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
+}
+
+static struct gsm_bts_model model_nokia_site = {
+	.type = GSM_BTS_TYPE_NOKIA_SITE,
+	.name = "nokia_site",
+	.start = bts_model_nokia_site_start,
+	.oml_rcvmsg = &abis_nokia_rcvmsg,
+	.e1line_bind_ops = &bts_model_nokia_site_e1line_bind_ops,
+};
+
+static struct gsm_network *my_net;
+
+static int bts_model_nokia_site_start(struct gsm_network *net)
+{
+	model_nokia_site.features.data = &model_nokia_site._features_data[0];
+	model_nokia_site.features.data_len =
+	    sizeof(model_nokia_site._features_data);
+
+	gsm_btsmodel_set_feature(&model_nokia_site, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_nokia_site, BTS_FEAT_HSCSD);
+	gsm_btsmodel_set_feature(&model_nokia_site, BTS_FEAT_MULTI_TSC);
+
+	osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
+	osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
+	osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+
+	my_net = net;
+
+	return 0;
+}
+
+int bts_model_nokia_site_init(void)
+{
+	return gsm_bts_model_register(&model_nokia_site);
+}
diff --git a/openbsc/src/libbsc/bts_siemens_bs11.c b/openbsc/src/libbsc/bts_siemens_bs11.c
new file mode 100644
index 0000000..c083b1e
--- /dev/null
+++ b/openbsc/src/libbsc/bts_siemens_bs11.c
@@ -0,0 +1,602 @@
+/* Siemens BS-11 specific code */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/signal.h>
+
+static int bts_model_bs11_start(struct gsm_network *net);
+
+static void bts_model_bs11_e1line_bind_ops(struct e1inp_line *line)
+{
+	e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
+}
+
+static struct gsm_bts_model model_bs11 = {
+	.type = GSM_BTS_TYPE_BS11,
+	.name = "bs11",
+	.start = bts_model_bs11_start,
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.e1line_bind_ops = bts_model_bs11_e1line_bind_ops,
+	.nm_att_tlvdef = {
+		.def = {
+			[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TLV },
+			/* BS11 specifics */
+			[NM_ATT_BS11_ESN_FW_CODE_NO] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_ESN_HW_CODE_NO] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_ESN_PCB_SERIAL] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_BOOT_SW_VERS] =	{ TLV_TYPE_TLV },
+			[0xd5] =			{ TLV_TYPE_TLV },
+			[0xa8] =			{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PASSWORD] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_TXPWR] =		{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_RSSI_OFFS] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LINE_CFG] = 	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_L1_PROT_TYPE] =	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_BIT_ERR_THESH] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_BS11_DIVERSITY] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV },	
+			[NM_ATT_BS11_LMT_LOGIN_TIME] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_USER_NAME] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_BTS_STATE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_E1_STATE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PLL_MODE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PLL]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_CCLK_ACCURACY] =	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_CCLK_TYPE] =	{ TLV_TYPE_TV },
+			[0x95] =			{ TLV_TYPE_FIXED, 2 },
+		},
+	},
+};
+
+/* The following definitions are for OM and NM packets that we cannot yet
+ * generate by code but we just pass on */
+
+// BTS Site Manager, SET ATTRIBUTES
+
+/*
+  Object Class: BTS Site Manager
+  Instance 1: FF
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  sAbisExternalTime: 2007/09/08   14:36:11
+  omLAPDRelTimer: 30sec
+  shortLAPDIntTimer: 5sec
+  emergencyTimer1: 10 minutes
+  emergencyTimer2: 0 minutes
+*/
+
+unsigned char msg_1[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF,
+		NM_ATT_BS11_ABIS_EXT_TIME, 0x07,
+			0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE,
+		0x02,
+			0x00, 0x1E,
+		NM_ATT_BS11_SH_LAPD_INT_TIMER,
+			0x01, 0x05,
+		0x42, 0x02, 0x00, 0x0A,
+		0x44, 0x02, 0x00, 0x00
+};
+
+// BTS, SET BTS ATTRIBUTES
+
+/*
+  Object Class: BTS
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET BTS ATTRIBUTES
+  bsIdentityCode / BSIC:
+    PLMN_colour_code: 7h
+    BS_colour_code:   7h
+  BTS Air Timer T3105: 4  ,unit 10 ms
+  btsIsHopping: FALSE
+  periodCCCHLoadIndication: 1sec
+  thresholdCCCHLoadIndication: 0%
+  cellAllocationNumber: 00h = GSM 900
+  enableInterferenceClass: 00h =  Disabled
+  fACCHQual: 6 (FACCH stealing flags minus 1)
+  intaveParameter: 31 SACCH multiframes
+  interferenceLevelBoundaries:
+    Interference Boundary 1: 0Ah
+    Interference Boundary 2: 0Fh
+    Interference Boundary 3: 14h
+    Interference Boundary 4: 19h
+    Interference Boundary 5: 1Eh
+  mSTxPwrMax: 11
+      GSM range:     2=39dBm, 15=13dBm, stepsize 2 dBm
+      DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+      PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+                    30=33dBm, 31=32dBm
+  ny1:
+    Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20
+  powerOutputThresholds:
+    Out Power Fault Threshold:     -10 dB
+    Red Out Power Threshold:       - 6 dB
+    Excessive Out Power Threshold:   5 dB
+  rACHBusyThreshold: -127 dBm
+  rACHLoadAveragingSlots: 250 ,number of RACH burst periods
+  rfResourceIndicationPeriod: 125  SACCH multiframes
+  T200:
+    SDCCH:                044 in  5 ms
+    FACCH/Full rate:      031 in  5 ms
+    FACCH/Half rate:      041 in  5 ms
+    SACCH with TCH SAPI0: 090 in 10 ms
+    SACCH with SDCCH:     090 in 10 ms
+    SDCCH with SAPI3:     090 in  5 ms
+    SACCH with TCH SAPI3: 135 in 10 ms
+  tSync: 9000 units of 10 msec
+  tTrau: 9000 units of 10 msec
+  enableUmLoopTest: 00h =  disabled
+  enableExcessiveDistance: 00h =  Disabled
+  excessiveDistance: 64km
+  hoppingMode: 00h = baseband hopping
+  cellType: 00h =  Standard Cell
+  BCCH ARFCN / bCCHFrequency: 1
+*/
+
+static unsigned char bs11_attr_bts[] =
+{
+		NM_ATT_BSIC, HARDCODED_BSIC,
+		NM_ATT_BTS_AIR_TIMER, 0x04,
+		NM_ATT_BS11_BTSLS_HOPPING, 0x00,
+		NM_ATT_CCCH_L_I_P, 0x01,
+		NM_ATT_CCCH_L_T, 0x00,
+		NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM,
+		NM_ATT_BS11_ENA_INTERF_CLASS, 0x01,
+		NM_ATT_BS11_FACCH_QUAL, 0x06,
+		/* interference avg. period in numbers of SACCH multifr */
+		NM_ATT_INTAVE_PARAM, 0x1F,
+		NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B,
+		NM_ATT_CCCH_L_T, 0x23,
+		NM_ATT_GSM_TIME, 0x28, 0x00,
+		NM_ATT_ADM_STATE, 0x03,
+		NM_ATT_RACH_B_THRESH, 0x7F,
+		NM_ATT_LDAVG_SLOTS, 0x00, 0xFA,
+		NM_ATT_BS11_RF_RES_IND_PER, 0x7D,
+		NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87,
+		NM_ATT_BS11_TSYNC, 0x23, 0x28,
+		NM_ATT_BS11_TTRAU, 0x23, 0x28,
+		NM_ATT_TEST_DUR, 0x01, 0x00,
+		NM_ATT_OUTST_ALARM, 0x01, 0x00,
+		NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40,
+		NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00,
+		NM_ATT_BS11_PLL, 0x01, 0x00,
+		NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/,
+};
+
+// Handover Recognition, SET ATTRIBUTES
+
+/*
+Illegal Contents GSM Formatted O&M Msg
+  Object Class: Handover Recognition
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  enableDelayPowerBudgetHO: 00h = Disabled
+  enableDistanceHO: 00h =  Disabled
+  enableInternalInterCellHandover: 00h = Disabled
+  enableInternalIntraCellHandover: 00h =  Disabled
+  enablePowerBudgetHO: 00h = Disabled
+  enableRXLEVHO: 00h =  Disabled
+  enableRXQUALHO: 00h =  Disabled
+  hoAveragingDistance: 8  SACCH multiframes
+  hoAveragingLev:
+    A_LEV_HO: 8  SACCH multiframes
+    W_LEV_HO: 1  SACCH multiframes
+  hoAveragingPowerBudget:  16  SACCH multiframes
+  hoAveragingQual:
+    A_QUAL_HO: 8  SACCH multiframes
+    W_QUAL_HO: 2  SACCH multiframes
+  hoLowerThresholdLevDL: (10 - 110) dBm
+  hoLowerThresholdLevUL: (5 - 110) dBm
+  hoLowerThresholdQualDL: 06h =   6.4% < BER < 12.8%
+  hoLowerThresholdQualUL: 06h =   6.4% < BER < 12.8%
+  hoThresholdLevDLintra : (20 - 110) dBm
+  hoThresholdLevULintra: (20 - 110) dBm
+  hoThresholdMsRangeMax: 20 km
+  nCell: 06h
+  timerHORequest: 3  ,unit 2 SACCH multiframes
+*/
+
+unsigned char msg_3[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF,
+		0xD0, 0x00,		/* enableDelayPowerBudgetHO */
+		0x64, 0x00,		/* enableDistanceHO */
+		0x67, 0x00,		/* enableInternalInterCellHandover */
+		0x68, 0x00,		/* enableInternalInterCellHandover */
+		0x6A, 0x00,		/* enablePowerBudgetHO */
+		0x6C, 0x00,		/* enableRXLEVHO */
+		0x6D, 0x00,		/* enableRXQUALHO */
+		0x6F, 0x08,		/* hoAveragingDistance */
+		0x70, 0x08, 0x01,	/* hoAveragingLev */
+		0x71, 0x10, 0x10, 0x10,
+		0x72, 0x08, 0x02,	/* hoAveragingQual */
+		0x73, 0x0A,		/* hoLowerThresholdLevDL */
+		0x74, 0x05,		/* hoLowerThresholdLevUL */
+		0x75, 0x06,		/* hoLowerThresholdQualDL */
+		0x76, 0x06,		/* hoLowerThresholdQualUL */
+		0x78, 0x14,		/* hoThresholdLevDLintra */
+		0x79, 0x14,		/* hoThresholdLevULintra */
+		0x7A, 0x14,		/* hoThresholdMsRangeMax */
+		0x7D, 0x06,		/* nCell */
+		NM_ATT_BS11_TIMER_HO_REQUEST, 0x03,
+		0x20, 0x01, 0x00,
+		0x45, 0x01, 0x00,
+		0x48, 0x01, 0x00,
+		0x5A, 0x01, 0x00,
+		0x5B, 0x01, 0x05,
+		0x5E, 0x01, 0x1A,
+		0x5F, 0x01, 0x20,
+		0x9D, 0x01, 0x00,
+		0x47, 0x01, 0x00,
+		0x5C, 0x01, 0x64,
+		0x5D, 0x01, 0x1E,
+		0x97, 0x01, 0x20,
+		0xF7, 0x01, 0x3C,
+};
+
+// Power Control, SET ATTRIBUTES
+
+/*
+  Object Class: Power Control
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  enableMsPowerControl: 00h =  Disabled
+  enablePowerControlRLFW: 00h =  Disabled
+  pcAveragingLev:
+    A_LEV_PC: 4  SACCH multiframes
+    W_LEV_PC: 1  SACCH multiframes
+  pcAveragingQual:
+    A_QUAL_PC: 4  SACCH multiframes
+    W_QUAL_PC: 2  SACCH multiframes
+  pcLowerThresholdLevDL: 0Fh
+  pcLowerThresholdLevUL: 0Ah
+  pcLowerThresholdQualDL: 05h =   3.2% < BER <  6.4%
+  pcLowerThresholdQualUL: 05h =   3.2% < BER <  6.4%
+  pcRLFThreshold: 0Ch
+  pcUpperThresholdLevDL: 14h
+  pcUpperThresholdLevUL: 0Fh
+  pcUpperThresholdQualDL: 04h =   1.6% < BER <  3.2%
+  pcUpperThresholdQualUL: 04h =   1.6% < BER <  3.2%
+  powerConfirm: 2  ,unit 2 SACCH multiframes
+  powerControlInterval: 2  ,unit 2 SACCH multiframes
+  powerIncrStepSize: 02h = 4 dB
+  powerRedStepSize: 01h = 2 dB
+  radioLinkTimeoutBs: 64  SACCH multiframes
+  enableBSPowerControl: 00h =  disabled
+*/
+
+unsigned char msg_4[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF,
+		NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00,
+		NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00,
+		0x7E, 0x04, 0x01,	/* pcAveragingLev */
+		0x7F, 0x04, 0x02,	/* pcAveragingQual */
+		0x80, 0x0F,		/* pcLowerThresholdLevDL */
+		0x81, 0x0A,		/* pcLowerThresholdLevUL */
+		0x82, 0x05,		/* pcLowerThresholdQualDL */
+		0x83, 0x05,		/* pcLowerThresholdQualUL */
+		0x84, 0x0C, 		/* pcRLFThreshold */
+		0x85, 0x14, 		/* pcUpperThresholdLevDL */
+		0x86, 0x0F, 		/* pcUpperThresholdLevUL */
+		0x87, 0x04,		/* pcUpperThresholdQualDL */
+		0x88, 0x04,		/* pcUpperThresholdQualUL */
+		0x89, 0x02,		/* powerConfirm */
+		0x8A, 0x02,		/* powerConfirmInterval */
+		0x8B, 0x02,		/* powerIncrStepSize */
+		0x8C, 0x01,		/* powerRedStepSize */
+		0x8D, 0x40,		/* radioLinkTimeoutBs */
+		0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl
+};
+
+
+// Transceiver, SET TRX ATTRIBUTES (TRX 0)
+
+/*
+  Object Class: Transceiver
+  BTS relat. Number: 0
+  Tranceiver number: 0
+  Instance 3: FF
+SET TRX ATTRIBUTES
+  aRFCNList (HEX):  0001
+  txPwrMaxReduction: 00h =   30dB
+  radioMeasGran: 254  SACCH multiframes
+  radioMeasRep: 01h =  enabled
+  memberOfEmergencyConfig: 01h =  TRUE
+  trxArea: 00h = TRX doesn't belong to a concentric cell
+*/
+
+static unsigned char bs11_attr_radio[] =
+{
+		NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/,
+		NM_ATT_RF_MAXPOWR_R, 0x00,
+		NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05,
+		NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01,
+		NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01,
+		NM_ATT_BS11_TRX_AREA, 0x01, 0x00,
+};
+
+/*
+ * Patch the various SYSTEM INFORMATION tables to update
+ * the LAI
+ */
+static void patch_nm_tables(struct gsm_bts *bts)
+{
+	uint8_t arfcn_low = bts->c0->arfcn & 0xff;
+	uint8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+	/* T3105 attribute in units of 10ms */
+	bs11_attr_bts[2] = bts->network->T3105 / 10;
+
+	/* patch ARFCN into BTS Attributes */
+	bs11_attr_bts[69] &= 0xf0;
+	bs11_attr_bts[69] |= arfcn_high;
+	bs11_attr_bts[70] = arfcn_low;
+
+	/* patch ARFCN into TRX Attributes */
+	bs11_attr_radio[2] &= 0xf0;
+	bs11_attr_radio[2] |= arfcn_high;
+	bs11_attr_radio[3] = arfcn_low;
+
+	/* patch the RACH attributes */
+	if (bts->rach_b_thresh != -1)
+		bs11_attr_bts[33] = bts->rach_b_thresh & 0xff;
+
+	if (bts->rach_ldavg_slots != -1) {
+		uint8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		uint8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+		bs11_attr_bts[35] = avg_high;
+		bs11_attr_bts[36] = avg_low;
+	}
+
+	/* patch BSIC */
+	bs11_attr_bts[1] = bts->bsic;
+
+	/* patch the power reduction */
+	bs11_attr_radio[5] = bts->c0->max_power_red / 2;
+}
+
+
+static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+	enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan);
+	struct gsm_e1_subslot *e1l = &ts->e1_link;
+
+	abis_nm_set_channel_attr(ts, ccomb);
+
+	if (is_ipaccess_bts(ts->trx->bts))
+		return;
+
+	if (ts_is_tch(ts))
+		abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+}
+
+static void nm_reconfig_trx(struct gsm_bts_trx *trx)
+{
+	struct gsm_e1_subslot *e1l = &trx->rsl_e1_link;
+	int i;
+
+	patch_nm_tables(trx->bts);
+
+	switch (trx->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		/* FIXME: discover this by fetching an attribute */
+#if 0
+		trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */
+#else
+		trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */
+#endif
+		abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+		abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr,
+				      e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei);
+
+		/* Set Radio Attributes */
+		if (trx == trx->bts->c0)
+			abis_nm_set_radio_attr(trx, bs11_attr_radio,
+					       sizeof(bs11_attr_radio));
+		else {
+			uint8_t trx1_attr_radio[sizeof(bs11_attr_radio)];
+			uint8_t arfcn_low = trx->arfcn & 0xff;
+			uint8_t arfcn_high = (trx->arfcn >> 8) & 0x0f;
+			memcpy(trx1_attr_radio, bs11_attr_radio,
+				sizeof(trx1_attr_radio));
+
+			/* patch ARFCN into TRX Attributes */
+			trx1_attr_radio[2] &= 0xf0;
+			trx1_attr_radio[2] |= arfcn_high;
+			trx1_attr_radio[3] = arfcn_low;
+
+			abis_nm_set_radio_attr(trx, trx1_attr_radio,
+					       sizeof(trx1_attr_radio));
+		}
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		switch (trx->bts->band) {
+		case GSM_BAND_850:
+		case GSM_BAND_900:
+			trx->nominal_power = 20;
+			break;
+		case GSM_BAND_1800:
+		case GSM_BAND_1900:
+			trx->nominal_power = 23;
+			break;
+		default:
+			LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n",
+				gsm_band_name(trx->bts->band));
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		nm_reconfig_ts(&trx->ts[i]);
+}
+
+static void nm_reconfig_bts(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		patch_nm_tables(bts);
+		abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/
+		abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts));
+		abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */
+		abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */
+		break;
+	default:
+		break;
+	}
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		nm_reconfig_trx(trx);
+}
+
+
+static void bootstrap_om_bs11(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+	/* stop sending event reports */
+	abis_nm_event_reports(bts, 0);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* reconfigure BTS with all TRX and all TS */
+	nm_reconfig_bts(bts);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	/* restart sending event reports */
+	abis_nm_event_reports(bts, 1);
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+	/* stop sending event reports */
+	abis_nm_event_reports(bts, 0);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts *bts;
+
+	if (subsys != SS_L_GLOBAL)
+		return 0;
+
+	switch (signal) {
+	case S_GLOBAL_BTS_CLOSE_OM:
+		bts = signal_data;
+		if (bts->type == GSM_BTS_TYPE_BS11)
+			shutdown_om(signal_data);
+		break;
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+
+	if (subsys != SS_L_INPUT)
+		return 0;
+
+	switch (signal) {
+	case S_L_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			if (isd->trx->bts->type == GSM_BTS_TYPE_BS11)
+				bootstrap_om_bs11(isd->trx->bts);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int bts_model_bs11_start(struct gsm_network *net)
+{
+	model_bs11.features.data = &model_bs11._features_data[0];
+	model_bs11.features.data_len = sizeof(model_bs11._features_data);
+
+	gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HSCSD);
+	gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_MULTI_TSC);
+
+	osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
+	osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
+
+	return 0;
+}
+
+int bts_model_bs11_init(void)
+{
+	return gsm_bts_model_register(&model_bs11);
+}
diff --git a/openbsc/src/libbsc/bts_sysmobts.c b/openbsc/src/libbsc/bts_sysmobts.c
new file mode 100644
index 0000000..e4b6cdc
--- /dev/null
+++ b/openbsc/src/libbsc/bts_sysmobts.c
@@ -0,0 +1,60 @@
+/* sysmocom sysmoBTS specific code */
+
+/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/logging.h>
+
+extern struct gsm_bts_model bts_model_nanobts;
+
+static struct gsm_bts_model model_sysmobts;
+
+int bts_model_sysmobts_init(void)
+{
+	model_sysmobts = bts_model_nanobts;
+	model_sysmobts.name = "sysmobts";
+	model_sysmobts.type = GSM_BTS_TYPE_OSMOBTS;
+
+	model_sysmobts.features.data = &model_sysmobts._features_data[0];
+	model_sysmobts.features.data_len =
+				sizeof(model_sysmobts._features_data);
+	memset(model_sysmobts.features.data, 0, sizeof(model_sysmobts.features.data_len));
+
+	gsm_btsmodel_set_feature(&model_sysmobts, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&model_sysmobts, BTS_FEAT_EGPRS);
+
+	return gsm_bts_model_register(&model_sysmobts);
+}
diff --git a/openbsc/src/libbsc/bts_unknown.c b/openbsc/src/libbsc/bts_unknown.c
new file mode 100644
index 0000000..f1135294
--- /dev/null
+++ b/openbsc/src/libbsc/bts_unknown.c
@@ -0,0 +1,40 @@
+/* Generic BTS - VTY code tries to allocate this BTS before type is known */
+
+/* (C) 2010 by Daniel Willmann <daniel@totalueberwachung.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/gsm/tlv.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_unknown = {
+	.type = GSM_BTS_TYPE_UNKNOWN,
+	.name = "unknown",
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.nm_att_tlvdef = {
+		.def = {
+		},
+	},
+};
+
+int bts_model_unknown_init(void)
+{
+	return gsm_bts_model_register(&model_unknown);
+}
diff --git a/openbsc/src/libbsc/chan_alloc.c b/openbsc/src/libbsc/chan_alloc.c
new file mode 100644
index 0000000..606dfa7
--- /dev/null
+++ b/openbsc/src/libbsc/chan_alloc.c
@@ -0,0 +1,628 @@
+/* GSM Channel allocation routines
+ *
+ * (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+static int ts_is_usable(struct gsm_bts_trx_ts *ts)
+{
+	/* FIXME: How does this behave for BS-11 ? */
+	if (is_ipaccess_bts(ts->trx->bts)) {
+		if (!nm_is_running(&ts->mo.nm_state))
+			return 0;
+	}
+
+	/* If a TCH/F_PDCH TS is busy changing, it is already taken or not
+	 * yet available. */
+	if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+		if (ts->flags & TS_F_PDCH_PENDING_MASK)
+			return 0;
+	}
+
+	/* If a dynamic channel is busy changing, it is already taken or not
+	 * yet available. */
+	if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+		if (ts->dyn.pchan_is != ts->dyn.pchan_want)
+			return 0;
+	}
+
+	return 1;
+}
+
+int trx_is_usable(struct gsm_bts_trx *trx)
+{
+	/* FIXME: How does this behave for BS-11 ? */
+	if (is_ipaccess_bts(trx->bts)) {
+		if (!nm_is_running(&trx->mo.nm_state) ||
+		    !nm_is_running(&trx->bb_transc.mo.nm_state))
+			return 0;
+	}
+
+	return 1;
+}
+
+static struct gsm_lchan *
+_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
+	     enum gsm_phys_chan_config dyn_as_pchan)
+{
+	struct gsm_bts_trx_ts *ts;
+	int j, start, stop, dir, ss;
+	int check_subslots;
+
+	if (!trx_is_usable(trx))
+		return NULL;
+
+	if (trx->bts->chan_alloc_reverse) {
+		/* check TS 7..0 */
+		start = 7;
+		stop = -1;
+		dir = -1;
+	} else {
+		/* check TS 0..7 */
+		start = 0;
+		stop = 8;
+		dir = 1;
+	}
+
+	for (j = start; j != stop; j += dir) {
+		ts = &trx->ts[j];
+		if (!ts_is_usable(ts))
+			continue;
+		if (ts->pchan != pchan)
+			continue;
+
+		/*
+		 * Allocation for fully dynamic timeslots
+		 * (does not apply for ip.access style GSM_PCHAN_TCH_F_PDCH)
+		 *
+		 * Note the special nature of a dynamic timeslot in PDCH mode:
+		 * in PDCH mode, typically, lchan->type is GSM_LCHAN_NONE and
+		 * lchan->state is LCHAN_S_NONE -- an otherwise unused slot
+		 * becomes PDCH implicitly. In the same sense, this channel
+		 * allocator will never be asked to find an available PDCH
+		 * slot; only TCH/F or TCH/H will be requested, and PDCH mode
+		 * means that it is available for switchover.
+		 *
+		 * A dynamic timeslot in PDCH mode may be switched to TCH/F or
+		 * TCH/H. If a dyn TS is already in TCH/F or TCH/H mode, it
+		 * means that it is in use and its mode can't be switched.
+		 *
+		 * The logic concerning channels for TCH/F is trivial: there is
+		 * only one channel, so a dynamic TS in TCH/F mode is already
+		 * taken and not available for allocation. For TCH/H, we need
+		 * to check whether a dynamic timeslot is already in TCH/H mode
+		 * and whether one of the two channels is still available.
+		 */
+		switch (pchan) {
+		case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+			if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+				/* The TS's mode is being switched. Not
+				 * available anymore/yet. */
+				DEBUGP(DRLL, "%s already in switchover\n",
+				       gsm_ts_and_pchan_name(ts));
+				continue;
+			}
+			if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) {
+				/* This slot is available. Still check for
+				 * error states to be sure; in all cases the
+				 * first lchan will be used. */
+				if (ts->lchan->state != LCHAN_S_NONE
+				    && ts->lchan->state != LCHAN_S_ACTIVE)
+					continue;
+				return ts->lchan;
+			}
+			if (ts->dyn.pchan_is != dyn_as_pchan)
+				/* not applicable. */
+				continue;
+			/* The requested type matches the dynamic timeslot's
+			 * current mode. A channel may still be available
+			 * (think TCH/H). */
+			check_subslots = ts_subslots(ts);
+			break;
+
+		case GSM_PCHAN_TCH_F_PDCH:
+			/* Available for voice when in PDCH mode */
+			if (ts_pchan(ts) != GSM_PCHAN_PDCH)
+				continue;
+			/* Subslots of a PDCH ts don't need to be checked. */
+			return ts->lchan;
+
+		default:
+			/* Not a dynamic channel, there is only one pchan kind: */
+			check_subslots = ts_subslots(ts);
+			break;
+		}
+
+		/* Is a sub-slot still available? */
+		for (ss = 0; ss < check_subslots; ss++) {
+			struct gsm_lchan *lc = &ts->lchan[ss];
+			if (lc->type == GSM_LCHAN_NONE &&
+			    lc->state == LCHAN_S_NONE)
+				return lc;
+		}
+	}
+
+	return NULL;
+}
+
+static struct gsm_lchan *
+_lc_dyn_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan,
+		 enum gsm_phys_chan_config dyn_as_pchan)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_lchan *lc;
+
+	if (bts->chan_alloc_reverse) {
+		llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
+			if (lc)
+				return lc;
+		}
+	} else {
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
+			if (lc)
+				return lc;
+		}
+	}
+
+	return NULL;
+}
+
+static struct gsm_lchan *
+_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+{
+	return _lc_dyn_find_bts(bts, pchan, GSM_PCHAN_NONE);
+}
+
+/* Allocate a logical channel.
+ *
+ * Dynamic channel types: we always prefer a dedicated TS, and only pick +
+ * switch a dynamic TS if no pure TS of the requested PCHAN is available.
+ *
+ * TCH_F/PDCH: if we pick a PDCH ACT style dynamic TS as TCH/F channel, PDCH
+ * will be disabled in rsl_chan_activate_lchan(); there is no need to check
+ * whether PDCH mode is currently active, here.
+ */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type,
+			      int allow_bigger)
+{
+	struct gsm_lchan *lchan = NULL;
+	enum gsm_phys_chan_config first, first_cbch, second, second_cbch;
+
+	switch (type) {
+	case GSM_LCHAN_SDCCH:
+		if (bts->chan_alloc_reverse) {
+			first = GSM_PCHAN_SDCCH8_SACCH8C;
+			first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
+			second = GSM_PCHAN_CCCH_SDCCH4;
+			second_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH;
+		} else {
+			first = GSM_PCHAN_CCCH_SDCCH4;
+			first_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH;
+			second = GSM_PCHAN_SDCCH8_SACCH8C;
+			second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
+		}
+
+		lchan = _lc_find_bts(bts, first);
+		if (lchan == NULL)
+			lchan = _lc_find_bts(bts, first_cbch);
+		if (lchan == NULL)
+			lchan = _lc_find_bts(bts, second);
+		if (lchan == NULL)
+			lchan = _lc_find_bts(bts, second_cbch);
+
+		/* allow to assign bigger channels */
+		if (allow_bigger) {
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+				if (lchan)
+					type = GSM_LCHAN_TCH_H;
+			}
+
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+				if (lchan)
+					type = GSM_LCHAN_TCH_F;
+			}
+
+			/* try dynamic TCH/F_PDCH */
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F_PDCH);
+				/* TCH/F_PDCH will be used as TCH/F */
+				if (lchan)
+					type = GSM_LCHAN_TCH_F;
+			}
+
+			/* try fully dynamic TCH/F_TCH/H_PDCH */
+			if (lchan == NULL) {
+				lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_TCH_H_PDCH,
+							 GSM_PCHAN_TCH_H);
+				if (lchan)
+					type = GSM_LCHAN_TCH_H;
+			}
+			/*
+			 * No need to check fully dynamic channels for TCH/F:
+			 * if no TCH/H was available, neither will be TCH/F.
+			 */
+		}
+		break;
+	case GSM_LCHAN_TCH_F:
+		lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+		/* If we don't have TCH/F available, fall-back to TCH/H */
+		if (!lchan) {
+			lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+			if (lchan)
+				type = GSM_LCHAN_TCH_H;
+		}
+		/* If we don't have TCH/H either, try dynamic TCH/F_PDCH */
+		if (!lchan) {
+			lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F_PDCH);
+			/* TCH/F_PDCH used as TCH/F -- here, type is already
+			 * set to GSM_LCHAN_TCH_F, but for clarity's sake... */
+			if (lchan)
+				type = GSM_LCHAN_TCH_F;
+		}
+
+		/* Try fully dynamic TCH/F_TCH/H_PDCH as TCH/F... */
+		if (!lchan && bts->network->dyn_ts_allow_tch_f) {
+			lchan = _lc_dyn_find_bts(bts,
+						 GSM_PCHAN_TCH_F_TCH_H_PDCH,
+						 GSM_PCHAN_TCH_F);
+			if (lchan)
+				type = GSM_LCHAN_TCH_F;
+		}
+		/* ...and as TCH/H. */
+		if (!lchan) {
+			lchan = _lc_dyn_find_bts(bts,
+						 GSM_PCHAN_TCH_F_TCH_H_PDCH,
+						 GSM_PCHAN_TCH_H);
+			if (lchan)
+				type = GSM_LCHAN_TCH_H;
+		}
+		break;
+	case GSM_LCHAN_TCH_H:
+		lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+		/* If we don't have TCH/H available, fall-back to TCH/F */
+		if (!lchan) {
+			lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+			if (lchan)
+				type = GSM_LCHAN_TCH_F;
+		}
+		/* No dedicated TCH/x available -- try fully dynamic
+		 * TCH/F_TCH/H_PDCH */
+		if (!lchan) {
+			lchan = _lc_dyn_find_bts(bts,
+						 GSM_PCHAN_TCH_F_TCH_H_PDCH,
+						 GSM_PCHAN_TCH_H);
+			if (lchan)
+				type = GSM_LCHAN_TCH_H;
+		}
+		/*
+		 * No need to check TCH/F_TCH/H_PDCH channels for TCH/F:
+		 * if no TCH/H was available, neither will be TCH/F.
+		 */
+		/* If we don't have TCH/F either, try dynamic TCH/F_PDCH */
+		if (!lchan) {
+			lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F_PDCH);
+			if (lchan)
+				type = GSM_LCHAN_TCH_F;
+		}
+		break;
+	default:
+		LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
+	}
+
+	if (lchan) {
+		lchan->type = type;
+
+		LOGP(DRLL, LOGL_INFO, "%s Allocating lchan=%u as %s\n",
+		     gsm_ts_and_pchan_name(lchan->ts),
+		     lchan->nr, gsm_lchant_name(lchan->type));
+
+		/* clear sapis */
+		memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis));
+
+		/* clear multi rate config */
+		memset(&lchan->mr_ms_lv, 0, sizeof(lchan->mr_ms_lv));
+		memset(&lchan->mr_bts_lv, 0, sizeof(lchan->mr_bts_lv));
+		lchan->broken_reason = "";
+	} else {
+		struct challoc_signal_data sig;
+
+		LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to allocate %s channel\n",
+		     bts->nr, gsm_lchant_name(type));
+
+		sig.bts = bts;
+		sig.type = type;
+		osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig);
+	}
+
+	return lchan;
+}
+
+/* Free a logical channel */
+void lchan_free(struct gsm_lchan *lchan)
+{
+	struct challoc_signal_data sig;
+	int i;
+
+	sig.type = lchan->type;
+	lchan->type = GSM_LCHAN_NONE;
+
+
+	if (lchan->conn) {
+		struct lchan_signal_data sig;
+
+		/* We might kill an active channel... */
+		sig.lchan = lchan;
+		sig.mr = NULL;
+		osmo_signal_dispatch(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig);
+	}
+
+	if (lchan->abis_ip.rtp_socket) {
+		LOGP(DRLL, LOGL_ERROR, "%s RTP Proxy Socket remained open.\n",
+			gsm_lchan_name(lchan));
+		rtp_socket_free(lchan->abis_ip.rtp_socket);
+		lchan->abis_ip.rtp_socket = NULL;
+	}
+
+	/* stop the timer */
+	osmo_timer_del(&lchan->T3101);
+
+	/* clear cached measuement reports */
+	lchan->meas_rep_idx = 0;
+	for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) {
+		lchan->meas_rep[i].flags = 0;
+		lchan->meas_rep[i].nr = 0;
+	}
+	for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++)
+		lchan->neigh_meas[i].arfcn = 0;
+
+	if (lchan->rqd_ref) {
+		talloc_free(lchan->rqd_ref);
+		lchan->rqd_ref = NULL;
+		lchan->rqd_ta = 0;
+	}
+
+	sig.lchan = lchan;
+	sig.bts = lchan->ts->trx->bts;
+	osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_FREED, &sig);
+
+	if (lchan->conn) {
+		LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n");
+		lchan->conn = NULL;
+	}
+
+	/* FIXME: ts_free() the timeslot, if we're the last logical
+	 * channel using it */
+}
+
+/*
+ * There was an error with the TRX and we need to forget
+ * any state so that a lchan can be allocated again after
+ * the trx is fully usable.
+ *
+ * This should be called after lchan_free to force a channel
+ * be available for allocation again. This means that this
+ * method will stop the "delay after error"-timer and set the
+ * state to LCHAN_S_NONE.
+ */
+void lchan_reset(struct gsm_lchan *lchan)
+{
+	osmo_timer_del(&lchan->T3101);
+	osmo_timer_del(&lchan->T3109);
+	osmo_timer_del(&lchan->T3111);
+	osmo_timer_del(&lchan->error_timer);
+
+	lchan->type = GSM_LCHAN_NONE;
+	lchan->state = LCHAN_S_NONE;
+
+	if (lchan->abis_ip.rtp_socket) {
+		rtp_socket_free(lchan->abis_ip.rtp_socket);
+		lchan->abis_ip.rtp_socket = NULL;
+	}
+}
+
+/* Drive the release process of the lchan */
+static void _lchan_handle_release(struct gsm_lchan *lchan,
+				  int sacch_deact, int mode)
+{
+	/* Release all SAPIs on the local end and continue */
+	rsl_release_sapis_from(lchan, 1, RSL_REL_LOCAL_END);
+
+	/*
+	 * Shall we send a RR Release, start T3109 and wait for the
+	 * release indication from the BTS or just take it down (e.g.
+	 * on assignment requests)
+	 */
+	if (sacch_deact) {
+		gsm48_send_rr_release(lchan);
+
+		/* Deactivate the SACCH on the BTS side */
+		rsl_deact_sacch(lchan);
+		rsl_start_t3109(lchan);
+	} else if (lchan->sapis[0] == LCHAN_SAPI_UNUSED) {
+		rsl_direct_rf_release(lchan);
+	} else {
+		rsl_release_request(lchan, 0, mode);
+	}
+}
+
+/* Consider releasing the channel now */
+int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode mode)
+{
+	DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+
+	lchan->conn = NULL;
+	_lchan_handle_release(lchan, sacch_deact, mode);
+	return 1;
+}
+
+void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int i;
+
+		/* skip administratively deactivated tranxsceivers */
+		if (!nm_is_running(&trx->mo.nm_state) ||
+		    !nm_is_running(&trx->bb_transc.mo.nm_state))
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+			struct load_counter *pl = &cl->pchan[ts->pchan];
+			int j;
+			int subslots;
+
+			/* skip administratively deactivated timeslots */
+			if (!nm_is_running(&ts->mo.nm_state))
+				continue;
+
+			subslots = ts_subslots(ts);
+			for (j = 0; j < subslots; j++) {
+				struct gsm_lchan *lchan = &ts->lchan[j];
+
+				pl->total++;
+
+				switch (lchan->state) {
+				case LCHAN_S_NONE:
+					break;
+				default:
+					pl->used++;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	memset(pl, 0, sizeof(*pl));
+
+	llist_for_each_entry(bts, &net->bts_list, list)
+		bts_chan_load(pl, bts);
+}
+
+/* Update T3122 wait indicator based on samples of BTS channel load. */
+void
+bts_update_t3122_chan_load(struct gsm_bts *bts)
+{
+	struct pchan_load pl;
+	uint64_t used = 0;
+	uint32_t total = 0;
+	uint64_t load;
+	uint64_t wait_ind;
+	static const uint8_t min_wait_ind = GSM_T3122_DEFAULT;
+	static const uint8_t max_wait_ind = 128; /* max wait ~2 minutes */
+	int i;
+
+	/* Ignore BTS that are not in operation, in order to not flood the log with "bogus channel load"
+	 * messages */
+	if (!trx_is_usable(bts->c0))
+		return;
+
+	/* Sum up current load across all channels. */
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+	for (i = 0; i < ARRAY_SIZE(pl.pchan); i++) {
+		struct load_counter *lc = &pl.pchan[i];
+
+		/* Ignore samples too large for fixed-point calculations (shouldn't happen). */
+		if (lc->used > UINT16_MAX || lc->total > UINT16_MAX) {
+			LOGP(DRLL, LOGL_NOTICE, "(bts=%d) numbers in channel load sample "
+			     "too large (used=%u / total=%u)\n", bts->nr, lc->used, lc->total);
+			continue;
+		}
+
+		used += lc->used;
+		total += lc->total;
+	}
+
+	/* Check for invalid samples (shouldn't happen). */
+	if (total == 0 || used > total) {
+		LOGP(DRLL, LOGL_NOTICE, "(bts=%d) bogus channel load sample (used=%"PRIu64" / total=%"PRIu32")\n",
+		     bts->nr, used, total);
+		bts->T3122 = 0; /* disable override of network-wide default value */
+		bts->chan_load_samples_idx = 0; /* invalidate other samples collected so far */
+		return;
+	}
+
+	/* If we haven't got enough samples yet, store measurement for later use. */
+	if (bts->chan_load_samples_idx < ARRAY_SIZE(bts->chan_load_samples)) {
+		struct load_counter *sample = &bts->chan_load_samples[bts->chan_load_samples_idx++];
+		sample->total = (unsigned int)total;
+		sample->used = (unsigned int)used;
+		return;
+	}
+
+	/* We have enough samples and will overwrite our current samples later. */
+	bts->chan_load_samples_idx = 0;
+
+	/* Add all previous samples to the current sample. */
+	for (i = 0; i < ARRAY_SIZE(bts->chan_load_samples); i++) {
+		struct load_counter *sample = &bts->chan_load_samples[i];
+		total += sample->total;
+		used += sample->used;
+	}
+
+	used <<= 8; /* convert to fixed-point */
+
+	/* Log channel load average. */
+	load = ((used / total) * 100);
+	LOGP(DRLL, LOGL_DEBUG, "(bts=%d) channel load average is %"PRIu64".%.2"PRIu64"%%\n",
+	     bts->nr, (load & 0xffffff00) >> 8, (load & 0xff) / 10);
+	bts->chan_load_avg = ((load & 0xffffff00) >> 8);
+	OSMO_ASSERT(bts->chan_load_avg <= 100);
+	osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_LOAD_AVERAGE], bts->chan_load_avg);
+
+	/* Calculate new T3122 wait indicator. */
+	wait_ind = ((used / total) * max_wait_ind);
+	wait_ind >>= 8; /* convert from fixed-point to integer */
+	if (wait_ind < min_wait_ind)
+		wait_ind = min_wait_ind;
+	else if (wait_ind > max_wait_ind)
+		wait_ind = max_wait_ind;
+
+	LOGP(DRLL, LOGL_DEBUG, "(bts=%d) T3122 wait indicator set to %"PRIu64" seconds\n", bts->nr, wait_ind);
+	bts->T3122 = (uint8_t)wait_ind;
+	osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_T3122], wait_ind);
+}
diff --git a/openbsc/src/libbsc/e1_config.c b/openbsc/src/libbsc/e1_config.c
new file mode 100644
index 0000000..92b2475
--- /dev/null
+++ b/openbsc/src/libbsc/e1_config.c
@@ -0,0 +1,301 @@
+/* OpenBSC E1 Input code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/misdn.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_rsl.h>
+
+#define SAPI_L2ML	0
+#define SAPI_OML	62
+#define SAPI_RSL	0	/* 63 ? */
+
+/* The e1_reconfig_*() functions below take the configuration present in the
+ * bts/trx/ts data structures and ensure the E1 configuration reflects the
+ * timeslot/subslot/TEI configuration */
+
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+	struct gsm_e1_subslot *e1_link = &ts->e1_link;
+	struct e1inp_line *line;
+	struct e1inp_ts *e1_ts;
+
+	DEBUGP(DLMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	if (!e1_link->e1_ts) {
+		LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n",
+		     ts->nr, ts->trx->nr, ts->trx->bts->nr);
+		return 0;
+	}
+
+	line = e1inp_line_find(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) referring to "
+		     "non-existing E1 line %u\n", ts->nr, ts->trx->nr,
+		     ts->trx->bts->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+
+	if (ts_is_tch(ts)) {
+		e1_ts = &line->ts[e1_link->e1_ts-1];
+		e1inp_ts_config_trau(e1_ts, line, subch_cb);
+		subch_demux_activate(&e1_ts->trau.demux, e1_link->e1_ts_ss);
+	}
+
+	return 0;
+}
+
+int e1_reconfig_trx(struct gsm_bts_trx *trx)
+{
+	struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_line *line;
+	struct e1inp_sign_link *rsl_link;
+	int i;
+
+	if (!e1_link->e1_ts) {
+		LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link without "
+		     "timeslot?\n", trx->bts->nr, trx->nr);
+		return -EINVAL;
+	}
+
+	/* RSL Link */
+	line = e1inp_line_find(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring "
+		     "to non-existing E1 line %u\n", trx->bts->nr,
+		     trx->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config_sign(sign_ts, line);
+	/* Ericsson RBS have a per-TRX OML link in parallel to RSL */
+	if (trx->bts->type == GSM_BTS_TYPE_RBS2000) {
+		struct e1inp_sign_link *oml_link;
+		oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx,
+						  trx->rsl_tei, SAPI_OML);
+		if (!oml_link) {
+			LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) OML link creation "
+				"failed\n", trx->bts->nr, trx->nr);
+			return -ENOMEM;
+		}
+		if (trx->oml_link)
+			e1inp_sign_link_destroy(trx->oml_link);
+		trx->oml_link = oml_link;
+	}
+	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, trx->rsl_tei, SAPI_RSL);
+	if (!rsl_link) {
+		LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation "
+		     "failed\n", trx->bts->nr, trx->nr);
+		return -ENOMEM;
+	}
+	if (trx->rsl_link)
+		e1inp_sign_link_destroy(trx->rsl_link);
+	trx->rsl_link = rsl_link;
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		e1_reconfig_ts(&trx->ts[i]);
+
+	return 0;
+}
+
+/* this is the generic callback for all ISDN-based BTS. */
+static int bts_isdn_sign_link(struct msgb *msg)
+{
+	int ret = -EINVAL;
+	struct e1inp_sign_link *link = msg->dst;
+	struct gsm_bts *bts;
+
+	switch (link->type) {
+	case E1INP_SIGN_OML:
+		bts = link->trx->bts;
+		ret = bts->model->oml_rcvmsg(msg);
+		break;
+	case E1INP_SIGN_RSL:
+		ret = abis_rsl_rcvmsg(msg);
+		break;
+	default:
+		LOGP(DLMI, LOGL_ERROR, "unknown link type %u\n", link->type);
+		break;
+	}
+	return ret;
+}
+
+struct e1inp_line_ops bts_isdn_e1inp_line_ops = {
+	.sign_link	= bts_isdn_sign_link,
+};
+
+int e1_reconfig_bts(struct gsm_bts *bts)
+{
+	struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_line *line;
+	struct e1inp_sign_link *oml_link;
+	struct gsm_bts_trx *trx;
+	struct timespec tp;
+	int rc;
+
+	DEBUGP(DLMI, "e1_reconfig_bts(%u)\n", bts->nr);
+
+	line = e1inp_line_find(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to "
+		     "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+
+	if (!bts->model->e1line_bind_ops) {
+		LOGP(DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n");
+		return -EINVAL;
+	}
+	if (!line->ops)
+		bts->model->e1line_bind_ops(line);
+
+	/* skip signal link initialization, this is done later for these BTS. */
+	if (bts->type == GSM_BTS_TYPE_NANOBTS ||
+	    bts->type == GSM_BTS_TYPE_OSMOBTS)
+		return e1inp_line_update(line);
+
+	/* OML link */
+	if (!e1_link->e1_ts) {
+		LOGP(DLINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n",
+		     bts->nr);
+		return -EINVAL;
+	}
+
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config_sign(sign_ts, line);
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, bts->oml_tei, SAPI_OML);
+	if (!oml_link) {
+		LOGP(DLINP, LOGL_ERROR, "BTS %u OML link creation failed\n",
+		     bts->nr);
+		return -ENOMEM;
+	}
+	if (bts->oml_link)
+		e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = oml_link;
+	rc = clock_gettime(CLOCK_MONOTONIC, &tp);
+	bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		e1_reconfig_trx(trx);
+
+	/* notify E1 input something has changed */
+	return e1inp_line_update(line);
+}
+
+#if 0
+/* do some compiled-in configuration for our BTS/E1 setup */
+int e1_config(struct gsm_bts *bts, int cardnr, int release_l2)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+	struct gsm_bts_trx *trx = bts->c0;
+	int base_ts;
+
+	switch (bts->nr) {
+	case 0:
+		/* First BTS uses E1 TS 01,02,03,04,05 */
+		base_ts = HARDCODED_BTS0_TS - 1;
+		break;
+	case 1:
+		/* Second BTS uses E1 TS 06,07,08,09,10 */
+		base_ts = HARDCODED_BTS1_TS - 1;
+		break;
+	case 2:
+		/* Third BTS uses E1 TS 11,12,13,14,15 */
+		base_ts = HARDCODED_BTS2_TS - 1;
+	default:
+		return -EINVAL;
+	}
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return -ENOMEM;
+
+	/* create E1 timeslots for signalling and TRAU frames */
+	e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN);
+	e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU);
+	e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[base_ts+1-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  trx, TEI_OML, SAPI_OML);
+	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, TEI_RSL, SAPI_RSL);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	trx->rsl_link = rsl_link;
+
+	/* enable subchannel demuxer on TS2 */
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1);
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2);
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3);
+
+	/* enable subchannel demuxer on TS3 */
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3);
+
+	trx = gsm_bts_trx_num(bts, 1);
+	if (trx) {
+		/* create E1 timeslots for TRAU frames of TRX1 */
+		e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU);
+		e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU);
+
+		/* create RSL signalling link for TRX1 */
+		sign_ts = &line->ts[base_ts+1-1];
+		rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, TEI_RSL+1, SAPI_RSL);
+		/* create back-links from trx */
+		trx->rsl_link = rsl_link;
+
+		/* enable subchannel demuxer on TS2 */
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3);
+
+		/* enable subchannel demuxer on TS3 */
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3);
+	}
+
+	return mi_setup(cardnr, line, release_l2);
+}
+#endif
diff --git a/openbsc/src/libbsc/gsm_04_08_utils.c b/openbsc/src/libbsc/gsm_04_08_utils.c
new file mode 100644
index 0000000..3447d27
--- /dev/null
+++ b/openbsc/src/libbsc/gsm_04_08_utils.c
@@ -0,0 +1,687 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0
+ * utility functions
+ */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/bsc_api.h>
+
+/* should ip.access BTS use direct RTP streams between each other (1),
+ * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
+int ipacc_rtp_direct = 1;
+
+static int gsm48_sendmsg(struct msgb *msg)
+{
+	if (msg->lchan)
+		msg->dst = msg->lchan->ts->trx->rsl_link;
+
+	msg->l3h = msg->data;
+	return rsl_data_request(msg, 0);
+}
+
+/* Section 9.1.8 / Table 9.9 */
+struct chreq {
+	uint8_t val;
+	uint8_t mask;
+	enum chreq_type type;
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */
+static const struct chreq chreq_type_neci1[] = {
+	{ 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+	{ 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F },
+	{ 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H },
+	{ 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL },
+	{ 0xe0, 0xe0, CHREQ_T_TCH_F },
+	{ 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H },
+	{ 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+	{ 0x00, 0xf0, CHREQ_T_LOCATION_UPD },
+	{ 0x10, 0xf0, CHREQ_T_SDCCH },
+	{ 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 },
+	{ 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+	{ 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+	{ 0x67, 0xff, CHREQ_T_LMU },
+	{ 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+	{ 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+	{ 0x63, 0xff, CHREQ_T_RESERVED_SDCCH },
+	{ 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE },
+	{ 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE },
+	{ 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE },
+	{ 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE },
+	{ 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */
+static const struct chreq chreq_type_neci0[] = {
+	{ 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+	{ 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H },
+	{ 0xe0, 0xe0, CHREQ_T_TCH_F },
+	{ 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+	{ 0x00, 0xe0, CHREQ_T_LOCATION_UPD },
+	{ 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 },
+	{ 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+	{ 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+	{ 0x67, 0xff, CHREQ_T_LMU },
+	{ 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+	{ 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+	{ 0x63, 0xff, CHREQ_T_RESERVED_SDCCH },
+	{ 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE },
+	{ 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE },
+	{ 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE },
+	{ 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE },
+	{ 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+static const enum gsm_chan_t ctype_by_chreq[] = {
+	[CHREQ_T_EMERG_CALL]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_CALL_REEST_TCH_F]	= GSM_LCHAN_TCH_F,
+	[CHREQ_T_CALL_REEST_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_CALL_REEST_TCH_H_DBL]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_SDCCH]			= GSM_LCHAN_SDCCH,
+	[CHREQ_T_TCH_F]			= GSM_LCHAN_TCH_F,
+	[CHREQ_T_VOICE_CALL_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_DATA_CALL_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_LOCATION_UPD]		= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_ANY_NECI1]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_ANY_NECI0]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_TCH_F]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_PAG_R_TCH_FH]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_LMU]			= GSM_LCHAN_SDCCH,
+	[CHREQ_T_RESERVED_SDCCH]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PDCH_ONE_PHASE]	= GSM_LCHAN_PDTCH,
+	[CHREQ_T_PDCH_TWO_PHASE]	= GSM_LCHAN_PDTCH,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_LCHAN_UNKNOWN,
+};
+
+static const enum gsm_chreq_reason_t reason_by_chreq[] = {
+	[CHREQ_T_EMERG_CALL]		= GSM_CHREQ_REASON_EMERG,
+	[CHREQ_T_CALL_REEST_TCH_F]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_CALL_REEST_TCH_H]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_CALL_REEST_TCH_H_DBL]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_SDCCH]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_TCH_F]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_VOICE_CALL_TCH_H]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_DATA_CALL_TCH_H]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_LOCATION_UPD]		= GSM_CHREQ_REASON_LOCATION_UPD,
+	[CHREQ_T_PAG_R_ANY_NECI1]	= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_ANY_NECI0]	= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_TCH_F]		= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_TCH_FH]		= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_LMU]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_PDCH_ONE_PHASE]	= GSM_CHREQ_REASON_PDCH,
+	[CHREQ_T_PDCH_TWO_PHASE]	= GSM_CHREQ_REASON_PDCH,
+	[CHREQ_T_RESERVED_SDCCH]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_CHREQ_REASON_OTHER,
+};
+
+/* verify that the two tables match */
+osmo_static_assert(sizeof(ctype_by_chreq) ==
+	      sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size);
+
+/*
+ * Update channel types for request based on policy. E.g. in the
+ * case of a TCH/H network/bsc use TCH/H for the emergency calls,
+ * for early assignment assign a SDCCH and some other options.
+ */
+void gsm_net_update_ctype(struct gsm_network *network)
+{
+	/* copy over the data */
+	memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq));
+
+	/*
+	 * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it
+	 * is better to iterate over the BTS/TRX and check if no TCH/F is available
+	 * and then set it to TCH/H.
+	 */
+	if (network->neci)
+		network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H;
+
+	if (network->pag_any_tch) {
+		if (network->neci) {
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H;
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H;
+		} else {
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F;
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F;
+		}
+	}
+}
+
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra)
+{
+	int i;
+	int length;
+	const struct chreq *chreq;
+
+	if (network->neci) {
+		chreq = chreq_type_neci1;
+		length = ARRAY_SIZE(chreq_type_neci1);
+	} else {
+		chreq = chreq_type_neci0;
+		length = ARRAY_SIZE(chreq_type_neci0);
+	}
+
+
+	for (i = 0; i < length; i++) {
+		const struct chreq *chr = &chreq[i];
+		if ((ra & chr->mask) == chr->val)
+			return network->ctype_by_chreq[chr->type];
+	}
+	LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra);
+	return GSM_LCHAN_SDCCH;
+}
+
+int get_reason_by_chreq(uint8_t ra, int neci)
+{
+	int i;
+	int length;
+	const struct chreq *chreq;
+
+	if (neci) {
+		chreq = chreq_type_neci1;
+		length = ARRAY_SIZE(chreq_type_neci1);
+	} else {
+		chreq = chreq_type_neci0;
+		length = ARRAY_SIZE(chreq_type_neci0);
+	}
+
+	for (i = 0; i < length; i++) {
+		const struct chreq *chr = &chreq[i];
+		if ((ra & chr->mask) == chr->val)
+			return reason_by_chreq[chr->type];
+	}
+	LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra);
+	return GSM_CHREQ_REASON_OTHER;
+}
+
+static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg)
+{
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, lchan->mr_ms_lv[0],
+			lchan->mr_ms_lv + 1);
+}
+
+/* 7.1.7 and 9.1.7: RR CHANnel RELease */
+int gsm48_send_rr_release(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	uint8_t *cause;
+
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CHAN_REL;
+
+	cause = msgb_put(msg, 1);
+	cause[0] = GSM48_RR_CAUSE_NORMAL;
+
+	DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n",
+		lchan->nr, lchan->type);
+
+	/* Send actual release request to MS */
+	return gsm48_sendmsg(msg);
+}
+
+int send_siemens_mrpci(struct gsm_lchan *lchan,
+		       uint8_t *classmark2_lv)
+{
+	struct rsl_mrpci mrpci;
+
+	if (classmark2_lv[0] < 2)
+		return -EINVAL;
+
+	mrpci.power_class = classmark2_lv[1] & 0x7;
+	mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1);
+	mrpci.vbs_capable = classmark2_lv[2] & (1 <<2);
+	mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3;
+
+	return rsl_siemens_mrpci(lchan, &mrpci);
+}
+
+int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type)
+{
+	/* Check the size for the classmark */
+	if (length < 1 + *classmark2_lv)
+		return -1;
+
+	uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1;
+	if (length < 2 + *classmark2_lv + mi_lv[0])
+		return -2;
+
+	*mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
+	return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv);
+}
+
+int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length,
+			    char *mi_string, uint8_t *mi_type)
+{
+	static const uint32_t classmark_offset =
+		offsetof(struct gsm48_pag_resp, classmark2);
+	uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2;
+	return gsm48_extract_mi(classmark2_lv, length - classmark_offset,
+				mi_string, mi_type);
+}
+
+int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg, struct bsc_subscr *bsub)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t *classmark2_lv = gh->data + 1;
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2_lv);
+
+	if (!conn->bsub) {
+		conn->bsub = bsub;
+	} else if (conn->bsub != bsub) {
+		LOGP(DRR, LOGL_ERROR,
+		     "<- Channel already owned by someone else?\n");
+		bsc_subscr_put(bsub);
+		return -EINVAL;
+	} else {
+		DEBUGP(DRR, "<- Channel already owned by us\n");
+		bsc_subscr_put(bsub);
+		bsub = conn->bsub;
+	}
+
+	rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_COMPLETED]);
+
+	/* Stop paging on the bts we received the paging response */
+	paging_request_stop(&bts->network->bts_list, conn->bts, bsub, conn,
+			    msg);
+	return 0;
+}
+
+/* Chapter 9.1.9: Ciphering Mode Command */
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CIPH");
+	struct gsm48_hdr *gh;
+	uint8_t ciph_mod_set;
+
+	msg->lchan = lchan;
+
+	DEBUGP(DRR, "TX CIPHERING MODE CMD\n");
+
+	if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0))
+		ciph_mod_set = 0;
+	else
+		ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CIPH_M_CMD;
+	gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf);
+
+	return rsl_encryption_cmd(msg);
+}
+
+static void gsm48_cell_desc(struct gsm48_cell_desc *cd,
+			    const struct gsm_bts *bts)
+{
+	cd->ncc = (bts->bsic >> 3 & 0x7);
+	cd->bcc = (bts->bsic & 0x7);
+	cd->arfcn_hi = bts->c0->arfcn >> 8;
+	cd->arfcn_lo = bts->c0->arfcn & 0xff;
+}
+
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+			   const struct gsm_lchan *lchan)
+{
+	uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+
+	cd->chan_nr = gsm_lchan2chan_nr(lchan);
+	if (!lchan->ts->hopping.enabled) {
+		cd->h0.tsc = gsm_ts_tsc(lchan->ts);
+		cd->h0.h = 0;
+		cd->h0.arfcn_high = arfcn >> 8;
+		cd->h0.arfcn_low = arfcn & 0xff;
+	} else {
+		cd->h1.tsc = gsm_ts_tsc(lchan->ts);
+		cd->h1.h = 1;
+		cd->h1.maio_high = lchan->ts->hopping.maio >> 2;
+		cd->h1.maio_low = lchan->ts->hopping.maio & 0x03;
+		cd->h1.hsn = lchan->ts->hopping.hsn;
+	}
+}
+
+/*! \brief Encode a TS 04.08 multirate config LV according to 10.5.2.21aa
+ *  \param[out] lv caller-allocated buffer of 7 bytes. First octet is IS length
+ *  \param[in] mr multi-rate configuration to encode
+ *  \param[in] modes array describing the AMR modes
+ *  \returns 0 on success */
+int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes)
+{
+	int num = 0, i;
+
+	for (i = 0; i < 8; i++) {
+		if (((mr->gsm48_ie[1] >> i) & 1))
+			num++;
+	}
+	if (num > 4) {
+		LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with too "
+				"many modes in config.\n");
+		num = 4;
+	}
+	if (num < 1) {
+		LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with no "
+				"mode in config.\n");
+		num = 1;
+	}
+
+	lv[0] = (num == 1) ? 2 : (num + 2);
+	memcpy(lv + 1, mr->gsm48_ie, 2);
+	if (num == 1)
+		return 0;
+
+	lv[3] = modes[0].threshold & 0x3f;
+	lv[4] = modes[0].hysteresis << 4;
+	if (num == 2)
+		return 0;
+	lv[4] |= (modes[1].threshold & 0x3f) >> 2;
+	lv[5] = modes[1].threshold << 6;
+	lv[5] |= (modes[1].hysteresis & 0x0f) << 2;
+	if (num == 3)
+		return 0;
+	lv[5] |= (modes[2].threshold & 0x3f) >> 4;
+	lv[6] = modes[2].threshold << 4;
+	lv[6] |= modes[2].hysteresis & 0x0f;
+
+	return 0;
+}
+
+#define GSM48_HOCMD_CCHDESC_LEN	16
+
+/* Chapter 9.1.15: Handover Command */
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+		      uint8_t power_command, uint8_t ho_ref)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 HO CMD");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_ho_cmd *ho =
+		(struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho));
+
+	msg->lchan = old_lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_HANDO_CMD;
+
+	/* mandatory bits */
+	gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts);
+	gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan);
+	ho->ho_ref = ho_ref;
+	ho->power_command = power_command;
+
+	if (new_lchan->ts->hopping.enabled) {
+		struct gsm_bts *bts = new_lchan->ts->trx->bts;
+		struct gsm48_system_information_type_1 *si1;
+		uint8_t *cur;
+
+		si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1);
+		/* Copy the Cell Chan Desc (ARFCNS in this cell) */
+		msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC);
+		cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN);
+		memcpy(cur, si1->cell_channel_description,
+			GSM48_HOCMD_CCHDESC_LEN);
+		/* Copy the Mobile Allocation */
+		msgb_tlv_put(msg, GSM48_IE_MA_BEFORE,
+			     new_lchan->ts->hopping.ma_len,
+			     new_lchan->ts->hopping.ma_data);
+	}
+	/* FIXME: optional bits for type of synchronization? */
+
+	return gsm48_sendmsg(msg);
+}
+
+/* Chapter 9.1.2: Assignment Command */
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ASS CMD");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_ass_cmd *ass =
+		(struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass));
+
+	DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode);
+
+	msg->lchan = dest_lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_ASS_CMD;
+
+	/*
+	 * fill the channel information element, this code
+	 * should probably be shared with rsl_rx_chan_rqd(),
+	 * gsm48_lchan_modify(). But beware that 10.5.2.5
+	 * 10.5.2.5.a have slightly different semantic for
+	 * the chan_desc. But as long as multi-slot configurations
+	 * are not used we seem to be fine.
+	 */
+	gsm48_lchan2chan_desc(&ass->chan_desc, lchan);
+	ass->power_command = power_command;
+
+	/* optional: cell channel description */
+
+	msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode);
+
+	/* mobile allocation in case of hopping */
+	if (lchan->ts->hopping.enabled) {
+		msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len,
+			     lchan->ts->hopping.ma_data);
+	}
+
+	/* in case of multi rate we need to attach a config */
+	mr_config_for_ms(lchan, msg);
+
+	return gsm48_sendmsg(msg);
+}
+
+/* 9.1.5 Channel mode modify: Modify the mode on the MS side */
+int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CHN MOD");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_chan_mode_modify *cmm =
+		(struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm));
+
+	DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode);
+
+	lchan->tch_mode = mode;
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF;
+
+	/* fill the channel information element, this code
+	 * should probably be shared with rsl_rx_chan_rqd() */
+	gsm48_lchan2chan_desc(&cmm->chan_desc, lchan);
+	cmm->mode = mode;
+
+	/* in case of multi rate we need to attach a config */
+	mr_config_for_ms(lchan, msg);
+
+	return gsm48_sendmsg(msg);
+}
+
+int gsm48_rx_rr_modif_ack(struct msgb *msg)
+{
+	int rc;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_chan_mode_modify *mod =
+				(struct gsm48_chan_mode_modify *) gh->data;
+
+	DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n");
+
+	if (mod->mode != msg->lchan->tch_mode) {
+		LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n",
+			msg->lchan->tch_mode, mod->mode);
+		return -1;
+	}
+
+	/* update the channel type */
+	switch (mod->mode) {
+	case GSM48_CMODE_SIGN:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+		break;
+	case GSM48_CMODE_SPEECH_V1:
+	case GSM48_CMODE_SPEECH_EFR:
+	case GSM48_CMODE_SPEECH_AMR:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_6k0:
+	case GSM48_CMODE_DATA_3k6:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA;
+		break;
+	}
+
+	/* We've successfully modified the MS side of the channel,
+	 * now go on to modify the BTS side of the channel */
+	rc = rsl_chan_mode_modify_req(msg->lchan);
+
+	/* FIXME: we not only need to do this after mode modify, but
+	 * also after channel activation */
+	if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN)
+		rsl_ipacc_crcx(msg->lchan);
+	return rc;
+}
+
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t *data = gh->data;
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct bitvec *nbv = &bts->si_common.neigh_list;
+	struct gsm_meas_rep_cell *mrc;
+
+	if (gh->msg_type != GSM48_MT_RR_MEAS_REP)
+		return -EINVAL;
+
+	if (data[0] & 0x80)
+		rep->flags |= MEAS_REP_F_BA1;
+	if (data[0] & 0x40)
+		rep->flags |= MEAS_REP_F_UL_DTX;
+	if ((data[1] & 0x40) == 0x00)
+		rep->flags |= MEAS_REP_F_DL_VALID;
+
+	rep->dl.full.rx_lev = data[0] & 0x3f;
+	rep->dl.sub.rx_lev = data[1] & 0x3f;
+	rep->dl.full.rx_qual = (data[2] >> 4) & 0x7;
+	rep->dl.sub.rx_qual = (data[2] >> 1) & 0x7;
+
+	rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2);
+	if (rep->num_cell < 1 || rep->num_cell > 6)
+		return 0;
+
+	/* an encoding nightmare in perfection */
+	mrc = &rep->cell[0];
+	mrc->rxlev = data[3] & 0x3f;
+	mrc->neigh_idx = data[4] >> 3;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5);
+	if (rep->num_cell < 2)
+		return 0;
+
+	mrc = &rep->cell[1];
+	mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7);
+	mrc->neigh_idx = (data[6] >> 2) & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4);
+	if (rep->num_cell < 3)
+		return 0;
+
+	mrc = &rep->cell[2];
+	mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6);
+	mrc->neigh_idx = (data[8] >> 1) & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3);
+	if (rep->num_cell < 4)
+		return 0;
+
+	mrc = &rep->cell[3];
+	mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5);
+	mrc->neigh_idx = data[10] & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = data[11] >> 2;
+	if (rep->num_cell < 5)
+		return 0;
+
+	mrc = &rep->cell[4];
+	mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4);
+	mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7);
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = (data[13] >> 1) & 0x3f;
+	if (rep->num_cell < 6)
+		return 0;
+
+	mrc = &rep->cell[5];
+	mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3);
+	mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6);
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = data[15] & 0x3f;
+
+	return 0;
+}
+
+/* 9.2.5 CM service accept */
+int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACK");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	msg->lchan = conn->lchan;
+
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_CM_SERV_ACC;
+
+	DEBUGP(DMM, "-> CM SERVICE ACK\n");
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+/* 9.2.6 CM service reject */
+int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+				enum gsm48_reject_value value)
+{
+	struct msgb *msg;
+
+	msg = gsm48_create_mm_serv_rej(value);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n");
+		return -1;
+	}
+
+	DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value);
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/openbsc/src/libbsc/gsm_04_80_utils.c b/openbsc/src/libbsc/gsm_04_80_utils.c
new file mode 100644
index 0000000..e0db81e
--- /dev/null
+++ b/openbsc/src/libbsc/gsm_04_80_utils.c
@@ -0,0 +1,40 @@
+/* OpenBSC utility functions for 3GPP TS 04.80 */
+
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/gsm0480.h>
+#include <openbsc/bsc_api.h>
+
+int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level,
+			 const char *text)
+{
+	struct msgb *msg = gsm0480_create_ussd_notify(level, text);
+	if (!msg)
+		return -1;
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm0480_create_ussd_release_complete();
+	if (!msg)
+		return -1;
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/openbsc/src/libbsc/handover_decision.c b/openbsc/src/libbsc/handover_decision.c
new file mode 100644
index 0000000..f81f1dd
--- /dev/null
+++ b/openbsc/src/libbsc/handover_decision.c
@@ -0,0 +1,304 @@
+/* Handover Decision making for Inter-BTS (Intra-BSC) Handover.  This
+ * only implements the handover algorithm/decision, but not execution
+ * of it */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/handover.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+/* issue handover to a cell identified by ARFCN and BSIC */
+static int handover_to_arfcn_bsic(struct gsm_lchan *lchan,
+				  uint16_t arfcn, uint8_t bsic)
+{
+	struct gsm_bts *new_bts;
+
+	/* resolve the gsm_bts structure for the best neighbor */
+	new_bts = gsm_bts_neighbor(lchan->ts->trx->bts, arfcn, bsic);
+	if (!new_bts) {
+		LOGP(DHO, LOGL_NOTICE, "unable to determine neighbor BTS "
+		     "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic);
+		return -EINVAL;
+	}
+
+	/* and actually try to handover to that cell */
+	return bsc_handover_start(lchan, new_bts);
+}
+
+/* did we get a RXLEV for a given cell in the given report? */
+static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr,
+				 uint16_t arfcn, uint8_t bsic)
+{
+	int i;
+
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+
+		/* search for matching report */
+		if (!(mrc->arfcn == arfcn && mrc->bsic == bsic))
+			continue;
+
+		mrc->flags |= MRC_F_PROCESSED;
+		return mrc->rxlev;
+	}
+	return -ENODEV;
+}
+
+/* obtain averaged rxlev for given neighbor */
+static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window)
+{
+	unsigned int i, idx;
+	int avg = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev),
+				nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev),
+				window);
+
+	for (i = 0; i < window; i++) {
+		int j = (idx+i) % ARRAY_SIZE(nmp->rxlev);
+
+		avg += nmp->rxlev[j];
+	}
+
+	return avg / window;
+}
+
+/* find empty or evict bad neighbor */
+static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan)
+{
+	int j, worst = 999999;
+	struct neigh_meas_proc *nmp_worst = NULL;
+
+	/* first try to find an empty/unused slot */
+	for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+		if (!nmp->arfcn)
+			return nmp;
+	}
+
+	/* no empty slot found. evict worst neighbor from list */
+	for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+		int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG);
+		if (!nmp_worst || avg < worst) {
+			worst = avg;
+			nmp_worst = nmp;
+		}
+	}
+
+	return nmp_worst;
+}
+
+/* process neighbor cell measurement reports */
+static void process_meas_neigh(struct gsm_meas_rep *mr)
+{
+	int i, j, idx;
+
+	/* for each reported cell, try to update global state */
+	for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j];
+		unsigned int idx;
+		int rxlev;
+
+		/* skip unused entries */
+		if (!nmp->arfcn)
+			continue;
+
+		rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic);
+		idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+		if (rxlev >= 0) {
+			nmp->rxlev[idx] = rxlev;
+			nmp->last_seen_nr = mr->nr;
+		} else
+			nmp->rxlev[idx] = 0;
+		nmp->rxlev_cnt++;
+	}
+
+	/* iterate over list of reported cells, check if we did not
+	 * process all of them */
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		struct neigh_meas_proc *nmp;
+
+		if (mrc->flags & MRC_F_PROCESSED)
+			continue;
+
+		nmp = find_evict_neigh(mr->lchan);
+
+		nmp->arfcn = mrc->arfcn;
+		nmp->bsic = mrc->bsic;
+
+		idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+		nmp->rxlev[idx] = mrc->rxlev;
+		nmp->rxlev_cnt++;
+		nmp->last_seen_nr = mr->nr;
+
+		mrc->flags |= MRC_F_PROCESSED;
+	}
+}
+
+/* attempt to do a handover */
+static int attempt_handover(struct gsm_meas_rep *mr)
+{
+	struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+	struct neigh_meas_proc *best_cell = NULL;
+	unsigned int best_better_db = 0;
+	int i, rc;
+
+	/* find the best cell in this report that is at least RXLEV_HYST
+	 * better than the current serving cell */
+
+	for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) {
+		struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i];
+		int avg, better;
+
+		/* skip empty slots */
+		if (nmp->arfcn == 0)
+			continue;
+
+		/* caculate average rxlev for this cell over the window */
+		avg = neigh_meas_avg(nmp, net->handover.win_rxlev_avg_neigh);
+
+		/* check if hysteresis is fulfilled */
+		if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis)
+			continue;
+
+		better = avg - mr->dl.full.rx_lev;
+		if (better > best_better_db) {
+			best_cell = nmp;
+			best_better_db = better;
+		}
+	}
+
+	if (!best_cell)
+		return 0;
+
+	LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ",
+		gsm_ts_name(mr->lchan->ts), best_cell->arfcn);
+	if (!net->handover.active) {
+		LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n");
+		return 0;
+	}
+
+	rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic);
+	switch (rc) {
+	case 0:
+		LOGPC(DHO, LOGL_INFO, "Starting handover\n");
+		break;
+	case -ENOSPC:
+		LOGPC(DHO, LOGL_INFO, "No channel available\n");
+		break;
+	case -EBUSY:
+		LOGPC(DHO, LOGL_INFO, "Handover already active\n");
+		break;
+	default:
+		LOGPC(DHO, LOGL_ERROR, "Unknown error\n");
+	}
+	return rc;
+}
+
+/* process an already parsed measurement report and decide if we want to
+ * attempt a handover */
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+	struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+	enum meas_rep_field dlev, dqual;
+	int av_rxlev;
+
+	/* we currently only do handover for TCH channels */
+	switch (mr->lchan->type) {
+	case GSM_LCHAN_TCH_F:
+	case GSM_LCHAN_TCH_H:
+		break;
+	default:
+		return 0;
+	}
+
+	if (mr->flags & MEAS_REP_F_DL_DTX) {
+		dlev = MEAS_REP_DL_RXLEV_SUB;
+		dqual = MEAS_REP_DL_RXQUAL_SUB;
+	} else {
+		dlev = MEAS_REP_DL_RXLEV_FULL;
+		dqual = MEAS_REP_DL_RXQUAL_FULL;
+	}
+
+	/* parse actual neighbor cell info */
+	if (mr->num_cell > 0 && mr->num_cell < 7)
+		process_meas_neigh(mr);
+
+	av_rxlev = get_meas_rep_avg(mr->lchan, dlev,
+				    net->handover.win_rxlev_avg);
+
+	/* Interference HO */
+	if (rxlev2dbm(av_rxlev) > -85 &&
+	    meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Bad Quality */
+	if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Low Level */
+	if (rxlev2dbm(av_rxlev) <= -110)
+		return attempt_handover(mr);
+
+	/* Distance */
+	if (mr->ms_l1.ta > net->handover.max_distance)
+		return attempt_handover(mr);
+
+	/* Power Budget AKA Better Cell */
+	if ((mr->nr % net->handover.pwr_interval) == net->handover.pwr_interval - 1)
+		return attempt_handover(mr);
+
+	return 0;
+
+}
+
+static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct lchan_signal_data *lchan_data;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+	lchan_data = signal_data;
+	switch (signal) {
+	case S_LCHAN_MEAS_REP:
+		process_meas_rep(lchan_data->mr);
+		break;
+	}
+
+	return 0;
+}
+
+void on_dso_load_ho_dec(void)
+{
+	osmo_signal_register_handler(SS_LCHAN, ho_dec_sig_cb, NULL);
+}
diff --git a/openbsc/src/libbsc/handover_logic.c b/openbsc/src/libbsc/handover_logic.c
new file mode 100644
index 0000000..795a2ee
--- /dev/null
+++ b/openbsc/src/libbsc/handover_logic.c
@@ -0,0 +1,379 @@
+/* Handover Logic for Inter-BTS (Intra-BSC) Handover.  This does not
+ * actually implement the handover algorithm/decision, but executes a
+ * handover decision */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/signal.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/trau_mux.h>
+
+struct bsc_handover {
+	struct llist_head list;
+
+	struct gsm_lchan *old_lchan;
+	struct gsm_lchan *new_lchan;
+
+	struct osmo_timer_list T3103;
+
+	uint8_t ho_ref;
+};
+
+static LLIST_HEAD(bsc_handovers);
+
+static void handover_free(struct bsc_handover *ho)
+{
+	osmo_timer_del(&ho->T3103);
+	llist_del(&ho->list);
+	talloc_free(ho);
+}
+
+static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	llist_for_each_entry(ho, &bsc_handovers, list) {
+		if (ho->new_lchan == new_lchan)
+			return ho;
+	}
+
+	return NULL;
+}
+
+static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
+{
+	struct bsc_handover *ho;
+
+	llist_for_each_entry(ho, &bsc_handovers, list) {
+		if (ho->old_lchan == old_lchan)
+			return ho;
+	}
+
+	return NULL;
+}
+
+/*! \brief Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm, after the
+ * decision whether to initiate HO to a specific BTS. */
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts)
+{
+	struct gsm_lchan *new_lchan;
+	struct bsc_handover *ho;
+	static uint8_t ho_ref;
+	int rc;
+
+	/* don't attempt multiple handovers for the same lchan at
+	 * the same time */
+	if (bsc_ho_by_old_lchan(old_lchan))
+		return -EBUSY;
+
+	DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n",
+		old_lchan->ts->trx->bts->nr, bts->nr);
+
+	rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
+
+	if (!old_lchan->conn) {
+		LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n");
+		return -ENOSPC;
+	}
+
+	new_lchan = lchan_alloc(bts, old_lchan->type, 0);
+	if (!new_lchan) {
+		LOGP(DHO, LOGL_NOTICE, "No free channel\n");
+		rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]);
+		return -ENOSPC;
+	}
+
+	ho = talloc_zero(tall_bsc_ctx, struct bsc_handover);
+	if (!ho) {
+		LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
+		lchan_free(new_lchan);
+		return -ENOMEM;
+	}
+	ho->old_lchan = old_lchan;
+	ho->new_lchan = new_lchan;
+	ho->ho_ref = ho_ref++;
+
+	/* copy some parameters from old lchan */
+	memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr));
+	new_lchan->ms_power = old_lchan->ms_power;
+	new_lchan->bs_power = old_lchan->bs_power;
+	new_lchan->rsl_cmode = old_lchan->rsl_cmode;
+	new_lchan->tch_mode = old_lchan->tch_mode;
+	memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, ARRAY_SIZE(new_lchan->mr_ms_lv));
+	memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, ARRAY_SIZE(new_lchan->mr_bts_lv));
+
+	new_lchan->conn = old_lchan->conn;
+	new_lchan->conn->ho_lchan = new_lchan;
+
+	/* FIXME: do we have a better idea of the timing advance? */
+	rc = rsl_chan_activate_lchan(new_lchan, RSL_ACT_INTER_ASYNC, ho->ho_ref);
+	if (rc < 0) {
+		LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+		new_lchan->conn->ho_lchan = NULL;
+		new_lchan->conn = NULL;
+		talloc_free(ho);
+		lchan_free(new_lchan);
+		return rc;
+	}
+
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+	llist_add(&ho->list, &bsc_handovers);
+	/* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
+
+	return 0;
+}
+
+void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(conn->ho_lchan);
+
+
+	if (!ho && conn->ho_lchan)
+		LOGP(DHO, LOGL_ERROR, "BUG: We lost some state.\n");
+
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return;
+	}
+
+	conn->ho_lchan->conn = NULL;
+	conn->ho_lchan = NULL;
+
+	if (free_lchan)
+		lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
+
+	handover_free(ho);
+}
+
+/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */
+static void ho_T3103_cb(void *_ho)
+{
+	struct bsc_handover *ho = _ho;
+	struct gsm_network *net = ho->new_lchan->ts->trx->bts->network;
+
+	DEBUGP(DHO, "HO T3103 expired\n");
+	rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]);
+
+	ho->new_lchan->conn->ho_lchan = NULL;
+	ho->new_lchan->conn = NULL;
+	lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
+	handover_free(ho);
+}
+
+/* RSL has acknowledged activation of the new lchan */
+static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	/* we need to check if this channel activation is related to
+	 * a handover at all (and if, which particular handover) */
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho)
+		return -ENODEV;
+
+	DEBUGP(DHO, "handover activate ack, send HO Command\n");
+
+	/* we can now send the 04.08 HANDOVER COMMAND to the MS
+	 * using the old lchan */
+
+	gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref);
+
+	/* start T3103.  We can continue either with T3103 expiration,
+	 * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */
+	osmo_timer_setup(&ho->T3103, ho_T3103_cb, ho);
+	osmo_timer_schedule(&ho->T3103, 10, 0);
+
+	/* create a RTP connection */
+	if (is_ipaccess_bts(new_lchan->ts->trx->bts))
+		rsl_ipacc_crcx(new_lchan);
+
+	return 0;
+}
+
+/* RSL has not acknowledged activation of the new lchan */
+static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_INFO, "ACT NACK: unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	new_lchan->conn->ho_lchan = NULL;
+	new_lchan->conn = NULL;
+	handover_free(ho);
+
+	/* FIXME: maybe we should try to allocate a new LCHAN here? */
+
+	return 0;
+}
+
+/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */
+static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
+{
+	struct gsm_network *net;
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	net = new_lchan->ts->trx->bts->network;
+	LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN "
+	     "%u->%u\n", subscr_name(ho->old_lchan->conn->subscr),
+	     ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr,
+	     ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn);
+
+	rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED]);
+
+	osmo_timer_del(&ho->T3103);
+
+	/* switch TRAU muxer for E1 based BTS from one channel to another */
+	if (is_e1_bts(new_lchan->conn->bts))
+		switch_trau_mux(ho->old_lchan, new_lchan);
+
+	/* Replace the ho lchan with the primary one */
+	if (ho->old_lchan != new_lchan->conn->lchan)
+		LOGP(DHO, LOGL_ERROR, "Primary lchan changed during handover.\n");
+
+	if (new_lchan != new_lchan->conn->ho_lchan)
+		LOGP(DHO, LOGL_ERROR, "Handover channel changed during this handover.\n");
+
+	new_lchan->conn->ho_lchan = NULL;
+	new_lchan->conn->lchan = new_lchan;
+	new_lchan->conn->bts = new_lchan->ts->trx->bts;
+	ho->old_lchan->conn = NULL;
+
+	lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END);
+
+	handover_free(ho);
+	return 0;
+}
+
+/* GSM 04.08 HANDOVER FAIL has been received */
+static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
+{
+	struct gsm_network *net = old_lchan->ts->trx->bts->network;
+	struct bsc_handover *ho;
+	struct gsm_lchan *new_lchan;
+
+	ho = bsc_ho_by_old_lchan(old_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
+
+	new_lchan = ho->new_lchan;
+
+	/* release the channel and forget about it */
+	ho->new_lchan->conn->ho_lchan = NULL;
+	ho->new_lchan->conn = NULL;
+	handover_free(ho);
+
+	lchan_release(new_lchan, 0, RSL_REL_LOCAL_END);
+
+
+	return 0;
+}
+
+/* GSM 08.58 HANDOVER DETECT has been received */
+static int ho_rsl_detect(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	/* FIXME: do we actually want to do something here ? */
+
+	return 0;
+}
+
+static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct lchan_signal_data *lchan_data;
+	struct gsm_lchan *lchan;
+
+	lchan_data = signal_data;
+	switch (subsys) {
+	case SS_LCHAN:
+		lchan = lchan_data->lchan;
+		switch (signal) {
+		case S_LCHAN_ACTIVATE_ACK:
+			return ho_chan_activ_ack(lchan);
+		case S_LCHAN_ACTIVATE_NACK:
+			return ho_chan_activ_nack(lchan);
+		case S_LCHAN_HANDOVER_DETECT:
+			return ho_rsl_detect(lchan);
+		case S_LCHAN_HANDOVER_COMPL:
+			return ho_gsm48_ho_compl(lchan);
+		case S_LCHAN_HANDOVER_FAIL:
+			return ho_gsm48_ho_fail(lchan);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho)
+		return NULL;
+	return ho->old_lchan;
+}
+
+static __attribute__((constructor)) void on_dso_load_ho_logic(void)
+{
+	osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
+}
diff --git a/openbsc/src/libbsc/meas_proc.c b/openbsc/src/libbsc/meas_proc.c
new file mode 100644
index 0000000..5b97e74
--- /dev/null
+++ b/openbsc/src/libbsc/meas_proc.c
@@ -0,0 +1,84 @@
+/* Measurement Processing */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+
+/* process an already parsed measurement report */
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+	struct gsm_meas_rep_cell *mr_cell = NULL;
+	unsigned int best_better_db;
+	int i;
+
+	/* FIXME: implement actual averaging over multiple measurement
+	 * reports */
+
+	/* find the best cell in this report that is at least RXLEV_HYST
+	 * better than the current serving cell */
+	for (i = 0; i < mr->num_cell; i++) {
+		unsigned int better;
+		if (mr->cell[i].rxlev < mr->dl.full.rx_lev + RXLEV_HYST)
+			continue;
+
+		better = mr->cell[i].rxlev - mr->dl.full.rx_lev;
+		if (better > best_better_db) {
+			mr_cell = &mr->cell[i];
+			best_better_db = better;
+		}
+	}
+
+	if (mr_cell)
+		return handover_to_arfcn_bsic(mr->lchan, mr_cell->arfcn,
+						mr_cell->bsic);
+	return 0;
+}
+
+static int meas_proc_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct gsm_lchan *lchan;
+	struct gsm_meas_rep *mr;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+	switch (signal) {
+	case S_LCHAN_MEAS_REP:
+		mr = signal_data;
+		process_meas_rep(mr);
+		break;
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_meas(void)
+{
+	osmo_signal_register_handler(SS_LCHAN, meas_proc_sig_cb, NULL);
+}
diff --git a/openbsc/src/libbsc/meas_rep.c b/openbsc/src/libbsc/meas_rep.c
new file mode 100644
index 0000000..808103d
--- /dev/null
+++ b/openbsc/src/libbsc/meas_rep.c
@@ -0,0 +1,115 @@
+/* Measurement Report Processing */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+
+static int get_field(const struct gsm_meas_rep *rep,
+		     enum meas_rep_field field)
+{
+	switch (field) {
+	case MEAS_REP_DL_RXLEV_FULL:
+		return rep->dl.full.rx_lev;
+	case MEAS_REP_DL_RXLEV_SUB:
+		return rep->dl.sub.rx_lev;
+	case MEAS_REP_DL_RXQUAL_FULL:
+		return rep->dl.full.rx_qual;
+	case MEAS_REP_DL_RXQUAL_SUB:
+		return rep->dl.sub.rx_qual;
+	case MEAS_REP_UL_RXLEV_FULL:
+		return rep->ul.full.rx_lev;
+	case MEAS_REP_UL_RXLEV_SUB:
+		return rep->ul.sub.rx_lev;
+	case MEAS_REP_UL_RXQUAL_FULL:
+		return rep->ul.full.rx_qual;
+	case MEAS_REP_UL_RXQUAL_SUB:
+		return rep->ul.sub.rx_qual;
+	}
+
+	return 0;
+}
+
+
+unsigned int calc_initial_idx(unsigned int array_size,
+			      unsigned int meas_rep_idx,
+			      unsigned int num_values)
+{
+	int offs, idx;
+
+	/* from which element do we need to start if we're interested
+	 * in an average of 'num' elements */
+	offs = meas_rep_idx - num_values;
+
+	if (offs < 0)
+		idx = array_size + offs;
+	else
+		idx = offs;
+
+	return idx;
+}
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+		     enum meas_rep_field field, unsigned int num)
+{
+	unsigned int i, idx;
+	int avg = 0;
+
+	if (num < 1)
+		return 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				lchan->meas_rep_idx, num);
+
+	for (i = 0; i < num; i++) {
+		int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep);
+
+		avg += get_field(&lchan->meas_rep[j], field);
+	}
+
+	return avg / num;
+}
+
+/* Check if N out of M last values for FIELD are >= bd */
+int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
+			enum meas_rep_field field,
+			unsigned int n, unsigned int m, int be)
+{
+	unsigned int i, idx;
+	int count = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				lchan->meas_rep_idx, m);
+
+	for (i = 0; i < m; i++) {
+		int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep);
+		int val = get_field(&lchan->meas_rep[j], field);
+
+		if (val >= be)
+			count++;
+
+		if (count >= n)
+			return 1;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/libbsc/net_init.c b/openbsc/src/libbsc/net_init.c
new file mode 100644
index 0000000..08e2bd6
--- /dev/null
+++ b/openbsc/src/libbsc/net_init.c
@@ -0,0 +1,104 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/common_cs.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/chan_alloc.h>
+
+/* XXX hard-coded for now */
+#define T3122_CHAN_LOAD_SAMPLE_INTERVAL 1 /* in seconds */
+
+static void update_t3122_chan_load_timer(void *data)
+{
+	struct gsm_network *net = data;
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list)
+		bts_update_t3122_chan_load(bts);
+
+	/* Keep this timer ticking. */
+	osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
+}
+
+struct gsm_network *bsc_network_init(void *ctx,
+				     uint16_t country_code,
+				     uint16_t network_code,
+				     mncc_recv_cb_t mncc_recv)
+{
+	struct gsm_network *net;
+
+	net = gsm_network_init(ctx, country_code, network_code, mncc_recv);
+
+	net->bsc_data = talloc_zero(net, struct osmo_bsc_data);
+	if (!net->bsc_data) {
+		talloc_free(net);
+		return NULL;
+	}
+
+	/* Init back pointer */
+	net->bsc_data->auto_off_timeout = -1;
+	net->bsc_data->network = net;
+	INIT_LLIST_HEAD(&net->bsc_data->mscs);
+
+	net->num_bts = 0;
+	net->reject_cause = GSM48_REJECT_ROAMING_NOT_ALLOWED;
+	net->T3101 = GSM_T3101_DEFAULT;
+	net->T3103 = GSM_T3103_DEFAULT;
+	net->T3105 = GSM_T3105_DEFAULT;
+	net->T3107 = GSM_T3107_DEFAULT;
+	net->T3109 = GSM_T3109_DEFAULT;
+	net->T3111 = GSM_T3111_DEFAULT;
+	net->T3113 = GSM_T3113_DEFAULT;
+	net->T3115 = GSM_T3115_DEFAULT;
+	net->T3117 = GSM_T3117_DEFAULT;
+	net->T3119 = GSM_T3119_DEFAULT;
+	net->T3122 = GSM_T3122_DEFAULT;
+	net->T3141 = GSM_T3141_DEFAULT;
+
+	/* default set of handover parameters */
+	net->handover.win_rxlev_avg = 10;
+	net->handover.win_rxqual_avg = 1;
+	net->handover.win_rxlev_avg_neigh = 10;
+	net->handover.pwr_interval = 6;
+	net->handover.pwr_hysteresis = 3;
+	net->handover.max_distance = 9999;
+
+	INIT_LLIST_HEAD(&net->bts_list);
+
+	/*
+	 * At present all BTS in the network share one channel load timeout.
+	 * If this becomes a problem for networks with a lot of BTS, this
+	 * code could be refactored to run the timeout individually per BTS.
+	 */
+	osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net);
+	osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
+
+	/* init statistics */
+	net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0);
+	if (!net->bsc_ctrs) {
+		talloc_free(net);
+		return NULL;
+	}
+
+	gsm_net_update_ctype(net);
+
+	return net;
+}
+
diff --git a/openbsc/src/libbsc/paging.c b/openbsc/src/libbsc/paging.c
new file mode 100644
index 0000000..9bf1a57
--- /dev/null
+++ b/openbsc/src/libbsc/paging.c
@@ -0,0 +1,452 @@
+/* Paging helper and manager.... */
+/* (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * Relevant specs:
+ *     12.21:
+ *       - 9.4.12 for CCCH Local Threshold
+ *
+ *     05.58:
+ *       - 8.5.2 CCCH Load indication
+ *       - 9.3.15 Paging Load
+ *
+ * Approach:
+ *       - Send paging command to subscriber
+ *       - On Channel Request we will remember the reason
+ *       - After the ACK we will request the identity
+ *	 - Then we will send assign the gsm_subscriber and
+ *	 - and call a callback
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/paging.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+void *tall_paging_ctx;
+
+#define PAGING_TIMER 0, 500000
+
+/*
+ * Kill one paging request update the internal list...
+ */
+static void paging_remove_request(struct gsm_bts_paging_state *paging_bts,
+				  struct gsm_paging_request *to_be_deleted)
+{
+	osmo_timer_del(&to_be_deleted->T3113);
+	llist_del(&to_be_deleted->entry);
+	bsc_subscr_put(to_be_deleted->bsub);
+	talloc_free(to_be_deleted);
+}
+
+static void page_ms(struct gsm_paging_request *request)
+{
+	uint8_t mi[128];
+	unsigned int mi_len;
+	unsigned int page_group;
+	struct gsm_bts *bts = request->bts;
+
+	/* the bts is down.. we will just wait for the paging to expire */
+	if (!bts->oml_link)
+		return;
+
+	log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub);
+
+	LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: %s tmsi: "
+	     "0x%08x for ch. type %d (attempt %d)\n", request->bsub->imsi,
+	     request->bsub->tmsi, request->chan_type, request->attempts);
+
+	if (request->bsub->tmsi == GSM_RESERVED_TMSI)
+		mi_len = gsm48_generate_mid_from_imsi(mi, request->bsub->imsi);
+	else
+		mi_len = gsm48_generate_mid_from_tmsi(mi, request->bsub->tmsi);
+
+	page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
+					       str_to_imsi(request->bsub->imsi));
+	gsm0808_page(bts, page_group, mi_len, mi, request->chan_type);
+	log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
+}
+
+static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts)
+{
+	if (llist_empty(&paging_bts->pending_requests))
+		return;
+
+	if (!osmo_timer_pending(&paging_bts->work_timer))
+		osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER);
+}
+
+
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts);
+static void paging_give_credit(void *data)
+{
+	struct gsm_bts_paging_state *paging_bts = data;
+
+	LOGP(DPAG, LOGL_NOTICE, "No slots available on bts nr %d\n", paging_bts->bts->nr);
+	paging_bts->available_slots = 20;
+	paging_handle_pending_requests(paging_bts);
+}
+
+static int can_send_pag_req(struct gsm_bts *bts, int rsl_type)
+{
+	struct pchan_load pl;
+	int count;
+
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+
+	switch (rsl_type) {
+	case RSL_CHANNEED_TCH_F:
+	case RSL_CHANNEED_TCH_ForH:
+		goto count_tch;
+		break;
+	case RSL_CHANNEED_SDCCH:
+		goto count_sdcch;
+		break;
+	case RSL_CHANNEED_ANY:
+	default:
+		if (bts->network->pag_any_tch)
+			goto count_tch;
+		else
+			goto count_sdcch;
+		break;
+	}
+
+	return 0;
+
+	/* could available SDCCH */
+count_sdcch:
+	count = 0;
+	count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total
+			- pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used;
+	count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total
+			- pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used;
+	return bts->paging.free_chans_need > count;
+
+count_tch:
+	count = 0;
+	count += pl.pchan[GSM_PCHAN_TCH_F].total
+			- pl.pchan[GSM_PCHAN_TCH_F].used;
+	if (bts->network->neci)
+		count += pl.pchan[GSM_PCHAN_TCH_H].total
+				- pl.pchan[GSM_PCHAN_TCH_H].used;
+	return bts->paging.free_chans_need > count;
+}
+
+/*
+ * This is kicked by the periodic PAGING LOAD Indicator
+ * coming from abis_rsl.c
+ *
+ * We attempt to iterate once over the list of items but
+ * only upto available_slots.
+ */
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts)
+{
+	struct gsm_paging_request *request = NULL;
+
+	/*
+	 * Determine if the pending_requests list is empty and
+	 * return then.
+	 */
+	if (llist_empty(&paging_bts->pending_requests)) {
+		/* since the list is empty, no need to reschedule the timer */
+		return;
+	}
+
+	/*
+	 * In case the BTS does not provide us with load indication and we
+	 * ran out of slots, call an autofill routine. It might be that the
+	 * BTS did not like our paging messages and then we have counted down
+	 * to zero and we do not get any messages.
+	 */
+	if (paging_bts->available_slots == 0) {
+		osmo_timer_setup(&paging_bts->credit_timer, paging_give_credit,
+				 paging_bts);
+		osmo_timer_schedule(&paging_bts->credit_timer, 5, 0);
+		return;
+	}
+
+	request = llist_entry(paging_bts->pending_requests.next,
+			      struct gsm_paging_request, entry);
+
+	/* we need to determine the number of free channels */
+	if (paging_bts->free_chans_need != -1) {
+		if (can_send_pag_req(request->bts, request->chan_type) != 0)
+			goto skip_paging;
+	}
+
+	/* handle the paging request now */
+	page_ms(request);
+	paging_bts->available_slots--;
+	request->attempts++;
+
+	/* take the current and add it to the back */
+	llist_del(&request->entry);
+	llist_add_tail(&request->entry, &paging_bts->pending_requests);
+
+skip_paging:
+	osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER);
+}
+
+static void paging_worker(void *data)
+{
+	struct gsm_bts_paging_state *paging_bts = data;
+
+	paging_handle_pending_requests(paging_bts);
+}
+
+static void paging_init_if_needed(struct gsm_bts *bts)
+{
+	if (bts->paging.bts)
+		return;
+
+	bts->paging.bts = bts;
+	INIT_LLIST_HEAD(&bts->paging.pending_requests);
+	osmo_timer_setup(&bts->paging.work_timer, paging_worker,
+			 &bts->paging);
+
+	/* Large number, until we get a proper message */
+	bts->paging.available_slots = 20;
+}
+
+static int paging_pending_request(struct gsm_bts_paging_state *bts,
+				  struct bsc_subscr *bsub)
+{
+	struct gsm_paging_request *req;
+
+	llist_for_each_entry(req, &bts->pending_requests, entry) {
+		if (bsub == req->bsub)
+			return 1;
+	}
+
+	return 0;	
+}
+
+static void paging_T3113_expired(void *data)
+{
+	struct gsm_paging_request *req = (struct gsm_paging_request *)data;
+	void *cbfn_param;
+	gsm_cbfn *cbfn;
+	int msg;
+
+	log_set_context(LOG_CTX_BSC_SUBSCR, req->bsub);
+
+	LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n",
+	     req, bsc_subscr_name(req->bsub));
+
+	/* must be destroyed before calling cbfn, to prevent double free */
+	rate_ctr_inc(&req->bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_EXPIRED]);
+	cbfn_param = req->cbfn_param;
+	cbfn = req->cbfn;
+
+	/* did we ever manage to page the subscriber */
+	msg = req->attempts > 0 ? GSM_PAGING_EXPIRED : GSM_PAGING_BUSY;
+
+	/* destroy it now. Do not access req afterwards */
+	paging_remove_request(&req->bts->paging, req);
+
+	if (cbfn)
+		cbfn(GSM_HOOK_RR_PAGING, msg, NULL, NULL,
+			  cbfn_param);
+
+}
+
+static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub,
+			   int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req;
+
+	if (paging_pending_request(bts_entry, bsub)) {
+		LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n",
+		     bsc_subscr_name(bsub));
+		return -EEXIST;
+	}
+
+	LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %s on bts %d.\n",
+	     bsc_subscr_name(bsub), bts->nr);
+	req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
+	req->bsub = bsc_subscr_get(bsub);
+	req->bts = bts;
+	req->chan_type = type;
+	req->cbfn = cbfn;
+	req->cbfn_param = data;
+	osmo_timer_setup(&req->T3113, paging_T3113_expired, req);
+	osmo_timer_schedule(&req->T3113, bts->network->T3113, 0);
+	llist_add_tail(&req->entry, &bts_entry->pending_requests);
+	paging_schedule_if_needed(bts_entry);
+
+	return 0;
+}
+
+int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub,
+		       int type, gsm_cbfn *cbfn, void *data)
+{
+	int rc;
+
+	/* skip all currently inactive TRX */
+	if (!trx_is_usable(bts->c0))
+		return 0;
+
+	/* maybe it is the first time we use it */
+	paging_init_if_needed(bts);
+
+	/* Trigger paging, pass any error to the caller */
+	rc = _paging_request(bts, bsub, type, cbfn, data);
+	if (rc < 0)
+		return rc;
+	return 1;
+}
+
+/*! Page a request on all BTS within Location Area
+ *  \returns Amount of BTS to which the paging request was sent, negative on error.
+ */
+int paging_request(struct gsm_network *network, struct bsc_subscr *bsub,
+		   int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts *bts = NULL;
+	int num_pages = 0;
+
+	rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED]);
+
+	/* start paging subscriber on all BTS within Location Area */
+	do {
+		int rc;
+
+		bts = gsm_bts_by_lac(network, bsub->lac, bts);
+		if (!bts)
+			break;
+
+		rc = paging_request_bts(bts, bsub, type, cbfn, data);
+		if (rc >= 0)
+			num_pages += rc;
+		else if (rc == -EEXIST)
+			num_pages += 1;
+		else
+			return rc;
+	} while (1);
+
+	if (num_pages == 0)
+		rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_PAGING_DETACHED]);
+
+	return num_pages;
+}
+
+
+/* we consciously ignore the type of the request here */
+static void _paging_request_stop(struct gsm_bts *bts, struct bsc_subscr *bsub,
+				 struct gsm_subscriber_connection *conn,
+				 struct msgb *msg)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req, *req2;
+
+	paging_init_if_needed(bts);
+
+	llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
+				  entry) {
+		if (req->bsub == bsub) {
+			gsm_cbfn *cbfn = req->cbfn;
+			void *param = req->cbfn_param;
+
+			/* now give up the data structure */
+			paging_remove_request(&bts->paging, req);
+			req = NULL;
+
+			if (conn && cbfn) {
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging %s on bts %d, calling cbfn.\n", bsub->imsi, bts->nr);
+				cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+				     msg, conn, param);
+			} else
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging %s on bts %d silently.\n", bsub->imsi, bts->nr);
+			break;
+		}
+	}
+}
+
+/* Stop paging on all other bts' */
+void paging_request_stop(struct llist_head *bts_list,
+			 struct gsm_bts *_bts, struct bsc_subscr *bsub,
+			 struct gsm_subscriber_connection *conn,
+			 struct msgb *msg)
+{
+	struct gsm_bts *bts;
+
+	log_set_context(LOG_CTX_BSC_SUBSCR, bsub);
+
+	/* Stop this first and dispatch the request */
+	if (_bts)
+		_paging_request_stop(_bts, bsub, conn, msg);
+
+	/* Make sure to cancel this everywhere else */
+	llist_for_each_entry(bts, bts_list, list) {
+		/* Sort of an optimization. */
+		if (bts == _bts)
+			continue;
+		_paging_request_stop(bts, bsub, NULL, NULL);
+	}
+}
+
+void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots)
+{
+	paging_init_if_needed(bts);
+
+	osmo_timer_del(&bts->paging.credit_timer);
+	bts->paging.available_slots = free_slots;
+	paging_schedule_if_needed(&bts->paging);
+}
+
+unsigned int paging_pending_requests_nr(struct gsm_bts *bts)
+{
+	unsigned int requests = 0;
+	struct gsm_paging_request *req;
+
+	paging_init_if_needed(bts);
+
+	llist_for_each_entry(req, &bts->paging.pending_requests, entry)
+		++requests;
+
+	return requests;
+}
+
+/**
+ * Find any paging data for the given subscriber at the given BTS.
+ */
+void *paging_get_data(struct gsm_bts *bts, struct bsc_subscr *bsub)
+{
+	struct gsm_paging_request *req;
+
+	llist_for_each_entry(req, &bts->paging.pending_requests, entry)
+		if (req->bsub == bsub)
+			return req->cbfn_param;
+
+	return NULL;
+}
diff --git a/openbsc/src/libbsc/pcu_sock.c b/openbsc/src/libbsc/pcu_sock.c
new file mode 100644
index 0000000..e2066ba
--- /dev/null
+++ b/openbsc/src/libbsc/pcu_sock.c
@@ -0,0 +1,743 @@
+/* pcu_sock.c: Connect from PCU via unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2012 by Holger Hans Peter Freyther
+ * 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 2 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.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/l1sap.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/pcu_if.h>
+#include <openbsc/pcuif_proto.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_rsl.h>
+
+static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg);
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
+int pcu_direct = 1;
+
+static const char *sapi_string[] = {
+	[PCU_IF_SAPI_RACH] =	"RACH",
+	[PCU_IF_SAPI_AGCH] =	"AGCH",
+	[PCU_IF_SAPI_PCH] =	"PCH",
+	[PCU_IF_SAPI_BCCH] =	"BCCH",
+	[PCU_IF_SAPI_PDTCH] =	"PDTCH",
+	[PCU_IF_SAPI_PRACH] =	"PRACH",
+	[PCU_IF_SAPI_PTCCH] = 	"PTCCH",
+	[PCU_IF_SAPI_AGCH_DT] = 	"AGCH_DT",
+};
+
+/* Check if BTS has a PCU connection */
+static bool pcu_connected(struct gsm_bts *bts)
+{
+	struct pcu_sock_state *state = bts->pcu_state;
+
+	if (!state)
+		return false;
+	if (state->conn_bfd.fd <= 0)
+		return false;
+	return true;
+}
+
+/*
+ * PCU messages
+ */
+
+/* Set up an message buffer to package an pcu interface message */
+struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr)
+{
+	struct msgb *msg;
+	struct gsm_pcu_if *pcu_prim;
+
+	msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx");
+	if (!msg)
+		return NULL;
+
+	msgb_put(msg, sizeof(struct gsm_pcu_if));
+	pcu_prim = (struct gsm_pcu_if *) msg->data;
+	pcu_prim->msg_type = msg_type;
+	pcu_prim->bts_nr = bts_nr;
+
+	return msg;
+}
+
+/* Helper function exclusivly used by pcu_if_signal_cb() */
+static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
+	if (ts->pchan == GSM_PCHAN_PDCH)
+		return true;
+	if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+		/* When we're busy deactivating the PDCH, we first set
+		 * DEACT_PENDING, tell the PCU about it and wait for a
+		 * response. So DEACT_PENDING means "no PDCH" to the PCU.
+		 * Similarly, when we're activating PDCH, we set the
+		 * ACT_PENDING and wait for an activation response from the
+		 * PCU, so ACT_PENDING means "is PDCH". */
+		if (ts->flags & TS_F_PDCH_ACTIVE)
+			return !(ts->flags & TS_F_PDCH_DEACT_PENDING);
+		else
+			return (ts->flags & TS_F_PDCH_ACT_PENDING);
+	}
+	if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+		/*
+		 * When we're busy de-/activating the PDCH, we first set
+		 * ts->dyn.pchan_want, tell the PCU about it and wait for a
+		 * response. So only care about dyn.pchan_want here.
+		 */
+		return ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+	}
+	return false;
+}
+
+/* Send BTS properties to the PCU */
+static int pcu_tx_info_ind(struct gsm_bts *bts)
+{
+	struct msgb *msg;
+	struct gsm_pcu_if *pcu_prim;
+	struct gsm_pcu_if_info_ind *info_ind;
+	struct gprs_rlc_cfg *rlcc;
+	struct gsm_bts_gprs_nsvc *nsvc;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	int i, j;
+
+	OSMO_ASSERT(bts);
+	OSMO_ASSERT(bts->network);
+
+	LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr);
+
+	rlcc = &bts->gprs.cell.rlc_cfg;
+
+	msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr);
+	if (!msg)
+		return -ENOMEM;
+
+	pcu_prim = (struct gsm_pcu_if *) msg->data;
+	info_ind = &pcu_prim->u.info_ind;
+	info_ind->version = PCU_IF_VERSION;
+	info_ind->flags |= PCU_IF_FLAG_ACTIVE;
+
+	if (pcu_direct)
+		info_ind->flags |= PCU_IF_FLAG_SYSMO;
+
+	/* RAI */
+	info_ind->mcc = bts->network->plmn.mcc;
+	info_ind->mnc = bts->network->plmn.mnc;
+	info_ind->mnc_3_digits = bts->network->plmn.mnc_3_digits;
+	info_ind->lac = bts->location_area_code;
+	info_ind->rac = bts->gprs.rac;
+
+	/* NSE */
+	info_ind->nsei = bts->gprs.nse.nsei;
+	memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7);
+	memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11);
+
+	/* cell attributes */
+	info_ind->cell_id = bts->cell_identity;
+	info_ind->repeat_time = rlcc->paging.repeat_time;
+	info_ind->repeat_count = rlcc->paging.repeat_count;
+	info_ind->bvci = bts->gprs.cell.bvci;
+	info_ind->t3142 = rlcc->parameter[RLC_T3142];
+	info_ind->t3169 = rlcc->parameter[RLC_T3169];
+	info_ind->t3191 = rlcc->parameter[RLC_T3191];
+	info_ind->t3193_10ms = rlcc->parameter[RLC_T3193];
+	info_ind->t3195 = rlcc->parameter[RLC_T3195];
+	info_ind->n3101 = rlcc->parameter[RLC_N3101];
+	info_ind->n3103 = rlcc->parameter[RLC_N3103];
+	info_ind->n3105 = rlcc->parameter[RLC_N3105];
+	info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN];
+	if (rlcc->cs_mask & (1 << GPRS_CS1))
+		info_ind->flags |= PCU_IF_FLAG_CS1;
+	if (rlcc->cs_mask & (1 << GPRS_CS2))
+		info_ind->flags |= PCU_IF_FLAG_CS2;
+	if (rlcc->cs_mask & (1 << GPRS_CS3))
+		info_ind->flags |= PCU_IF_FLAG_CS3;
+	if (rlcc->cs_mask & (1 << GPRS_CS4))
+		info_ind->flags |= PCU_IF_FLAG_CS4;
+	if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+		if (rlcc->cs_mask & (1 << GPRS_MCS1))
+			info_ind->flags |= PCU_IF_FLAG_MCS1;
+		if (rlcc->cs_mask & (1 << GPRS_MCS2))
+			info_ind->flags |= PCU_IF_FLAG_MCS2;
+		if (rlcc->cs_mask & (1 << GPRS_MCS3))
+			info_ind->flags |= PCU_IF_FLAG_MCS3;
+		if (rlcc->cs_mask & (1 << GPRS_MCS4))
+			info_ind->flags |= PCU_IF_FLAG_MCS4;
+		if (rlcc->cs_mask & (1 << GPRS_MCS5))
+			info_ind->flags |= PCU_IF_FLAG_MCS5;
+		if (rlcc->cs_mask & (1 << GPRS_MCS6))
+			info_ind->flags |= PCU_IF_FLAG_MCS6;
+		if (rlcc->cs_mask & (1 << GPRS_MCS7))
+			info_ind->flags |= PCU_IF_FLAG_MCS7;
+		if (rlcc->cs_mask & (1 << GPRS_MCS8))
+			info_ind->flags |= PCU_IF_FLAG_MCS8;
+		if (rlcc->cs_mask & (1 << GPRS_MCS9))
+			info_ind->flags |= PCU_IF_FLAG_MCS9;
+	}
+#warning	"isn't dl_tbf_ext wrong?: * 10 and no ntohs"
+	info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT];
+#warning	"isn't ul_tbf_ext wrong?: * 10 and no ntohs"
+	info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT];
+	info_ind->initial_cs = rlcc->initial_cs;
+	info_ind->initial_mcs = rlcc->initial_mcs;
+
+	/* NSVC */
+	for (i = 0; i < ARRAY_SIZE(info_ind->nsvci); i++) {
+		nsvc = &bts->gprs.nsvc[i];
+		info_ind->nsvci[i] = nsvc->nsvci;
+		info_ind->local_port[i] = nsvc->local_port;
+		info_ind->remote_port[i] = nsvc->remote_port;
+		info_ind->remote_ip[i] = nsvc->remote_ip;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(info_ind->trx); i++) {
+		trx = gsm_bts_trx_num(bts, i);
+		if (!trx)
+			continue;
+		info_ind->trx[i].hlayer1 = 0x2342;
+		info_ind->trx[i].pdch_mask = 0;
+		info_ind->trx[i].arfcn = trx->arfcn;
+		for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
+			ts = &trx->ts[j];
+			if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+			    && ts_should_be_pdch(ts)) {
+				info_ind->trx[i].pdch_mask |= (1 << j);
+				info_ind->trx[i].tsc[j] =
+					(ts->tsc >= 0) ? ts->tsc : bts->bsic & 7;
+				LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: "
+					"available (tsc=%d arfcn=%d)\n",
+					trx->nr, ts->nr,
+					info_ind->trx[i].tsc[j],
+					info_ind->trx[i].arfcn);
+			}
+		}
+	}
+
+	return pcu_sock_send(bts, msg);
+}
+
+void pcu_info_update(struct gsm_bts *bts)
+{
+	if (pcu_connected(bts))
+		pcu_tx_info_ind(bts);
+}
+
+/* Forward rach indication to PCU */
+int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
+	uint8_t is_11bit, enum ph_burst_type burst_type)
+{
+	struct msgb *msg;
+	struct gsm_pcu_if *pcu_prim;
+	struct gsm_pcu_if_rach_ind *rach_ind;
+
+	/* Bail if no PCU is connected */
+	if (!pcu_connected(bts)) {
+		LOGP(DRSL, LOGL_ERROR, "BTS %d CHAN RQD(GPRS) but PCU not "
+			"connected!\n", bts->nr);
+		return -ENODEV;
+	}
+
+	LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, "
+		"fn=%d\n", qta, ra, fn);
+
+	msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr);
+	if (!msg)
+		return -ENOMEM;
+	pcu_prim = (struct gsm_pcu_if *) msg->data;
+	rach_ind = &pcu_prim->u.rach_ind;
+
+	rach_ind->sapi = PCU_IF_SAPI_RACH;
+	rach_ind->ra = ra;
+	rach_ind->qta = qta;
+	rach_ind->fn = fn;
+	rach_ind->is_11bit = is_11bit;
+	rach_ind->burst_type = burst_type;
+
+	return pcu_sock_send(bts, msg);
+}
+
+/* Confirm the sending of an immediate assignment to the pcu */
+int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli)
+{
+	struct msgb *msg;
+	struct gsm_pcu_if *pcu_prim;
+	struct gsm_pcu_if_data_cnf_dt *data_cnf_dt;
+
+	LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n");
+
+	msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, bts->nr);
+	if (!msg)
+		return -ENOMEM;
+	pcu_prim = (struct gsm_pcu_if *) msg->data;
+	data_cnf_dt = &pcu_prim->u.data_cnf_dt;
+
+	data_cnf_dt->sapi = PCU_IF_SAPI_PCH;
+	data_cnf_dt->tlli = tlli;
+
+	return pcu_sock_send(bts, msg);
+}
+
+/* we need to decode the raw RR paging messsage (see PCU code
+ * Encoding::write_paging_request) and extract the mobile identity
+ * (P-TMSI) from it */
+static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group,
+			    const uint8_t *raw_rr_msg)
+{
+	struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg;
+	uint8_t chan_needed;
+	unsigned int mi_len;
+	uint8_t *mi;
+	int rc;
+
+	switch (p1->msg_type) {
+	case GSM48_MT_RR_PAG_REQ_1:
+		chan_needed = (p1->cneed2 << 2) | p1->cneed1;
+		mi_len = p1->data[0];
+		mi = p1->data+1;
+		LOGP(DPCU, LOGL_ERROR, "PCU Sends paging "
+		     "request type %02x (chan_needed=%02x, mi_len=%u, mi=%s)\n",
+		     p1->msg_type, chan_needed, mi_len,
+		     osmo_hexdump_nospc(mi,mi_len));
+		/* NOTE: We will have to add 2 to mi_len and subtract 2 from
+		 * the mi pointer because rsl_paging_cmd() will perform the
+		 * reverse operations. This is because rsl_paging_cmd() is
+		 * normally expected to chop off the element identifier (0xC0)
+		 * and the length field. In our parameter, we do not have
+		 * those fields included. */
+		rc = rsl_paging_cmd(bts, paging_group, mi_len+2, mi-2,
+				    chan_needed, true);
+		break;
+	case GSM48_MT_RR_PAG_REQ_2:
+	case GSM48_MT_RR_PAG_REQ_3:
+		LOGP(DPCU, LOGL_ERROR, "PCU Sends unsupported paging "
+			"request type %02x\n", p1->msg_type);
+		rc = -EINVAL;
+		break;
+	default:
+		LOGP(DPCU, LOGL_ERROR, "PCU Sends unknown paging "
+			"request type %02x\n", p1->msg_type);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
+	struct gsm_pcu_if_data *data_req)
+{
+	uint8_t is_ptcch;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct msgb *msg;
+	char imsi_digit_buf[4];
+	uint32_t tlli = -1;
+	uint8_t pag_grp;
+	int rc = 0;
+
+	LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d "
+		"block=%d data=%s\n", sapi_string[data_req->sapi],
+		data_req->arfcn, data_req->block_nr,
+		osmo_hexdump(data_req->data, data_req->len));
+
+	switch (data_req->sapi) {
+	case PCU_IF_SAPI_PCH:
+		/* the first three bytes are the last three digits of
+		 * the IMSI, which we need to compute the paging group */
+		imsi_digit_buf[0] = data_req->data[0];
+		imsi_digit_buf[1] = data_req->data[1];
+		imsi_digit_buf[2] = data_req->data[2];
+		imsi_digit_buf[3] = '\0';
+		LOGP(DPCU, LOGL_DEBUG, "SAPI PCH imsi %s\n", imsi_digit_buf);
+		pag_grp = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
+						str_to_imsi(imsi_digit_buf));
+		pcu_rx_rr_paging(bts, pag_grp, data_req->data+3);
+		break;
+	case PCU_IF_SAPI_AGCH:
+		msg = msgb_alloc(data_req->len, "pcu_agch");
+		if (!msg) {
+			rc = -ENOMEM;
+			break;
+		}
+		msg->l3h = msgb_put(msg, data_req->len);
+		memcpy(msg->l3h, data_req->data, data_req->len);
+
+		if (rsl_imm_assign_cmd(bts, msg->len, msg->data)) {
+			msgb_free(msg);
+			rc = -EIO;
+		}
+		break;
+	case PCU_IF_SAPI_AGCH_DT:
+		/* DT = direct tlli. A tlli is prefixed */
+
+		if (data_req->len < 5) {
+			LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+					"invalid/small length %d\n", data_req->len);
+			break;
+		}
+		tlli = *((uint32_t *)data_req->data);
+
+		msg = msgb_alloc(data_req->len - 4, "pcu_agch");
+		if (!msg) {
+			rc = -ENOMEM;
+			break;
+		}
+		msg->l3h = msgb_put(msg, data_req->len - 4);
+		memcpy(msg->l3h, data_req->data + 4, data_req->len - 4);
+
+		if (bts->type == GSM_BTS_TYPE_RBS2000)
+			rc = rsl_ericsson_imm_assign_cmd(bts, tlli, msg->len, msg->data);
+		else
+			rc = rsl_imm_assign_cmd(bts, msg->len, msg->data);
+
+		if (rc) {
+			msgb_free(msg);
+			rc = -EIO;
+		}
+		break;
+	default:
+		LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+			"unsupported sapi %d\n", data_req->sapi);
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static int pcu_rx(struct gsm_network *net, uint8_t msg_type,
+	struct gsm_pcu_if *pcu_prim)
+{
+	int rc = 0;
+	struct gsm_bts *bts;
+
+	/* FIXME: allow multiple BTS */
+	bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+
+	switch (msg_type) {
+	case PCU_IF_MSG_DATA_REQ:
+	case PCU_IF_MSG_PAG_REQ:
+		rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req);
+		break;
+	default:
+		LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n",
+			msg_type);
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+/*
+ * PCU socket interface
+ */
+
+static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg)
+{
+	struct pcu_sock_state *state = bts->pcu_state;
+	struct osmo_fd *conn_bfd;
+	struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data;
+
+	if (!state) {
+		if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+			LOGP(DPCU, LOGL_INFO, "PCU socket not created, "
+				"dropping message\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	conn_bfd = &state->conn_bfd;
+	if (conn_bfd->fd <= 0) {
+		if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+			LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, "
+				"dropping message\n");
+		msgb_free(msg);
+		return -EIO;
+	}
+	msgb_enqueue(&state->upqueue, msg);
+	conn_bfd->when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void pcu_sock_close(struct pcu_sock_state *state)
+{
+	struct osmo_fd *bfd = &state->conn_bfd;
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	int i, j;
+
+	/* FIXME: allow multiple BTS */
+	bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list);
+
+	LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
+
+	close(bfd->fd);
+	bfd->fd = -1;
+	osmo_fd_unregister(bfd);
+
+	/* re-enable the generation of ACCEPT for new connections */
+	state->listen_bfd.when |= BSC_FD_READ;
+
+#if 0
+	/* remove si13, ... */
+	bts->si_valid &= ~(1 << SYSINFO_TYPE_13);
+	osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+#endif
+
+	/* release PDCH */
+	for (i = 0; i < 8; i++) {
+		trx = gsm_bts_trx_num(bts, i);
+		if (!trx)
+			break;
+		for (j = 0; j < 8; j++) {
+			ts = &trx->ts[j];
+			if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+			 && ts->pchan == GSM_PCHAN_PDCH) {
+				printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n");
+			}
+		}
+	}
+
+	/* flush the queue */
+	while (!llist_empty(&state->upqueue)) {
+		struct msgb *msg = msgb_dequeue(&state->upqueue);
+		msgb_free(msg);
+	}
+}
+
+static int pcu_sock_read(struct osmo_fd *bfd)
+{
+	struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+	struct gsm_pcu_if *pcu_prim;
+	struct msgb *msg;
+	int rc;
+
+	msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx");
+	if (!msg)
+		return -ENOMEM;
+
+	pcu_prim = (struct gsm_pcu_if *) msg->tail;
+
+	rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+	if (rc == 0)
+		goto close;
+
+	if (rc < 0) {
+		if (errno == EAGAIN)
+			return 0;
+		goto close;
+	}
+
+	rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim);
+
+	/* as we always synchronously process the message in pcu_rx() and
+	 * its callbacks, we can free the message here. */
+	msgb_free(msg);
+
+	return rc;
+
+close:
+	msgb_free(msg);
+	pcu_sock_close(state);
+	return -1;
+}
+
+static int pcu_sock_write(struct osmo_fd *bfd)
+{
+	struct pcu_sock_state *state = bfd->data;
+	int rc;
+
+	while (!llist_empty(&state->upqueue)) {
+		struct msgb *msg, *msg2;
+		struct gsm_pcu_if *pcu_prim;
+
+		/* peek at the beginning of the queue */
+		msg = llist_entry(state->upqueue.next, struct msgb, list);
+		pcu_prim = (struct gsm_pcu_if *)msg->data;
+
+		bfd->when &= ~BSC_FD_WRITE;
+
+		/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+		if (!msgb_length(msg)) {
+			LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO "
+				"bytes!\n", pcu_prim->msg_type);
+			goto dontsend;
+		}
+
+		/* try to send it over the socket */
+		rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+		if (rc == 0)
+			goto close;
+		if (rc < 0) {
+			if (errno == EAGAIN) {
+				bfd->when |= BSC_FD_WRITE;
+				break;
+			}
+			goto close;
+		}
+
+dontsend:
+		/* _after_ we send it, we can deueue */
+		msg2 = msgb_dequeue(&state->upqueue);
+		assert(msg == msg2);
+		msgb_free(msg);
+	}
+	return 0;
+
+close:
+	pcu_sock_close(state);
+
+	return -1;
+}
+
+static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+	int rc = 0;
+
+	if (flags & BSC_FD_READ)
+		rc = pcu_sock_read(bfd);
+	if (rc < 0)
+		return rc;
+
+	if (flags & BSC_FD_WRITE)
+		rc = pcu_sock_write(bfd);
+
+	return rc;
+}
+
+/* accept connection comming from PCU */
+static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags)
+{
+	struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+	struct osmo_fd *conn_bfd = &state->conn_bfd;
+	struct sockaddr_un un_addr;
+	socklen_t len;
+	int rc;
+
+	len = sizeof(un_addr);
+	rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+	if (rc < 0) {
+		LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n");
+		return -1;
+	}
+
+	if (conn_bfd->fd >= 0) {
+		LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have "
+			"another active connection ?!?\n");
+		/* We already have one PCU connected, this is all we support */
+		state->listen_bfd.when &= ~BSC_FD_READ;
+		close(rc);
+		return 0;
+	}
+
+	conn_bfd->fd = rc;
+	conn_bfd->when = BSC_FD_READ;
+	conn_bfd->cb = pcu_sock_cb;
+	conn_bfd->data = state;
+
+	if (osmo_fd_register(conn_bfd) != 0) {
+		LOGP(DPCU, LOGL_ERROR, "Failed to register new connection "
+			"fd\n");
+		close(conn_bfd->fd);
+		conn_bfd->fd = -1;
+		return -1;
+	}
+
+	LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n");
+
+	return 0;
+}
+
+/* Open connection to PCU */
+int pcu_sock_init(const char *path, struct gsm_bts *bts)
+{
+	struct pcu_sock_state *state;
+	struct osmo_fd *bfd;
+	int rc;
+
+	state = talloc_zero(NULL, struct pcu_sock_state);
+	if (!state)
+		return -ENOMEM;
+
+	INIT_LLIST_HEAD(&state->upqueue);
+	state->net = bts->network;
+	state->conn_bfd.fd = -1;
+
+	bfd = &state->listen_bfd;
+
+	bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path,
+		OSMO_SOCK_F_BIND);
+	if (bfd->fd < 0) {
+		LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n",
+			strerror(errno));
+		talloc_free(state);
+		return -1;
+	}
+
+	bfd->when = BSC_FD_READ;
+	bfd->cb = pcu_sock_accept;
+	bfd->data = state;
+
+	rc = osmo_fd_register(bfd);
+	if (rc < 0) {
+		LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n",
+			rc);
+		close(bfd->fd);
+		talloc_free(state);
+		return rc;
+	}
+
+	bts->pcu_state = state;
+	return 0;
+}
+
+/* Close connection to PCU */
+void pcu_sock_exit(struct gsm_bts *bts)
+{
+	struct pcu_sock_state *state = bts->pcu_state;
+	struct osmo_fd *bfd, *conn_bfd;
+
+	if (!state)
+		return;
+
+	conn_bfd = &state->conn_bfd;
+	if (conn_bfd->fd > 0)
+		pcu_sock_close(state);
+	bfd = &state->listen_bfd;
+	close(bfd->fd);
+	osmo_fd_unregister(bfd);
+	talloc_free(state);
+	bts->pcu_state = NULL;
+}
+
diff --git a/openbsc/src/libbsc/rest_octets.c b/openbsc/src/libbsc/rest_octets.c
new file mode 100644
index 0000000..49c38b5
--- /dev/null
+++ b/openbsc/src/libbsc/rest_octets.c
@@ -0,0 +1,844 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface,
+ * rest octet handling according to
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/bitvec.h>
+#include <osmocom/gsm/bitvec_gsm.h>
+#include <openbsc/rest_octets.h>
+#include <openbsc/arfcn_range_encode.h>
+#include <openbsc/system_information.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 1;
+
+	if (nch_pos) {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, *nch_pos, 5);
+	} else
+		bitvec_set_bit(&bv, L);
+
+	if (is1800_net)
+		bitvec_set_bit(&bv, L);
+	else
+		bitvec_set_bit(&bv, H);
+
+	bitvec_spare_padding(&bv, 6);
+	return bv.data_len;
+}
+
+/* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */
+static inline void append_eutran_neib_cell(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
+{
+	const struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	unsigned i, skip = 0;
+	size_t offset = bts->e_offset;
+	uint8_t rem = budget - 6, earfcn_budget; /* account for mandatory stop bit and THRESH_E-UTRAN_high */
+
+	if (budget <= 6)
+		return;
+
+	OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
+	/* first we have to properly adjust budget requirements */
+	if (e->prio_valid) /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
+		rem -= 4;
+	else
+		rem--;
+
+	if (e->thresh_lo_valid) /* THRESH_E-UTRAN_low: */
+		rem -= 6;
+	else
+		rem--;
+
+	if (e->qrxlm_valid) /* E-UTRAN_QRXLEVMIN: */
+		rem -= 6;
+	else
+		rem--;
+
+	/* now we can proceed with actually adding EARFCNs within adjusted budget limit */
+	for (i = 0; i < e->length; i++) {
+		if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
+			if (skip < offset) {
+				skip++; /* ignore EARFCNs added on previous calls */
+			} else {
+				earfcn_budget = 17; /* compute budget per-EARFCN */
+				if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
+					earfcn_budget++;
+				else
+					earfcn_budget += 4;
+
+				if (rem - earfcn_budget < 0)
+					break;
+				else {
+					bts->e_offset++;
+					rem -= earfcn_budget;
+					bitvec_set_bit(bv, 1); /* EARFCN: */
+					bitvec_set_uint(bv, e->arfcn[i], 16);
+
+					if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
+						bitvec_set_bit(bv, 0);
+					else { /* Measurement Bandwidth: 9.1.54 */
+						bitvec_set_bit(bv, 1);
+						bitvec_set_uint(bv, e->meas_bw[i], 3);
+					}
+				}
+			}
+		}
+	}
+
+	/* stop bit - end of EARFCN + Measurement Bandwidth sequence */
+	bitvec_set_bit(bv, 0);
+
+	/* Note: we don't support different EARFCN arrays each with different priority, threshold etc. */
+
+	if (e->prio_valid) {
+		/* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, e->prio, 3);
+	} else
+		bitvec_set_bit(bv, 0);
+
+	/* THRESH_E-UTRAN_high */
+	bitvec_set_uint(bv, e->thresh_hi, 5);
+
+	if (e->thresh_lo_valid) {
+		/* THRESH_E-UTRAN_low: */
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, e->thresh_lo, 5);
+	} else
+		bitvec_set_bit(bv, 0);
+
+	if (e->qrxlm_valid) {
+		/* E-UTRAN_QRXLEVMIN: */
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, e->qrxlm, 5);
+	} else
+		bitvec_set_bit(bv, 0);
+}
+
+static inline void append_earfcn(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
+{
+	int rem = budget - 25;
+	if (rem <= 0)
+		return;
+
+	OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
+	/* Additions in Rel-5: */
+	bitvec_set_bit(bv, H);
+	/* No 3G Additional Measurement Param. Descr. */
+	bitvec_set_bit(bv, 0);
+	/* No 3G ADDITIONAL MEASUREMENT Param. Descr. 2 */
+	bitvec_set_bit(bv, 0);
+	/* Additions in Rel-6: */
+	bitvec_set_bit(bv, H);
+	/* 3G_CCN_ACTIVE */
+	bitvec_set_bit(bv, 0);
+	/* Additions in Rel-7: */
+	bitvec_set_bit(bv, H);
+	/* No 700_REPORTING_OFFSET */
+	bitvec_set_bit(bv, 0);
+	/* No 810_REPORTING_OFFSET */
+	bitvec_set_bit(bv, 0);
+	/* Additions in Rel-8: */
+	bitvec_set_bit(bv, H);
+
+	/* Priority and E-UTRAN Parameters Description */
+	bitvec_set_bit(bv, 1);
+
+	/* No Serving Cell Priority Parameters Descr. */
+	bitvec_set_bit(bv, 0);
+	/* No 3G Priority Parameters Description */
+	bitvec_set_bit(bv, 0);
+	/* E-UTRAN Parameters Description */
+	bitvec_set_bit(bv, 1);
+
+	/* E-UTRAN_CCN_ACTIVE */
+	bitvec_set_bit(bv, 0);
+	/* E-UTRAN_Start: 9.1.54 */
+	bitvec_set_bit(bv, 1);
+	/* E-UTRAN_Stop: 9.1.54 */
+	bitvec_set_bit(bv, 1);
+
+	/* No E-UTRAN Measurement Parameters Descr. */
+	bitvec_set_bit(bv, 0);
+	/* No GPRS E-UTRAN Measurement Param. Descr. */
+	bitvec_set_bit(bv, 0);
+
+	/* Note: each of next 3 "repeated" structures might be repeated any
+	   (0, 1, 2...) times - we only support 1 and 0 */
+
+	/* Repeated E-UTRAN Neighbour Cells */
+	bitvec_set_bit(bv, 1);
+
+	/* N. B: 25 bits are set in append_earfcn() - keep it in sync with budget adjustment below: */
+	append_eutran_neib_cell(bv, bts, rem);
+
+	/* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */
+	bitvec_set_bit(bv, 0);
+
+	/* Note: following 2 repeated structs are not supported ATM */
+	/* stop bit - end of Repeated E-UTRAN Not Allowed Cells sequence: */
+	bitvec_set_bit(bv, 0);
+	/* stop bit - end of Repeated E-UTRAN PCID to TA mapping sequence: */
+	bitvec_set_bit(bv, 0);
+
+	/* Priority and E-UTRAN Parameters Description ends here */
+	/* No 3G CSG Description */
+	bitvec_set_bit(bv, 0);
+	/* No E-UTRAN CSG Description */
+	bitvec_set_bit(bv, 0);
+	/* No Additions in Rel-9: */
+	bitvec_set_bit(bv, L);
+}
+
+static inline int f0_helper(int *sc, size_t length, uint8_t *chan_list)
+{
+	int w[RANGE_ENC_MAX_ARFCNS] = { 0 };
+
+	return range_encode(ARFCN_RANGE_1024, sc, length, w, 0, chan_list);
+}
+
+/* Estimate how many bits it'll take to append single FDD UARFCN */
+static inline int append_utran_fdd_length(uint16_t u, int *sc, size_t sc_len, size_t length)
+{
+	uint8_t chan_list[16] = { 0 };
+	int tmp[sc_len], f0;
+
+	memcpy(tmp, sc, sizeof(tmp));
+
+	f0 = f0_helper(tmp, length, chan_list);
+	if (f0 < 0)
+		return f0;
+
+	return 21 + range1024_p(length);
+}
+
+/* Append single FDD UARFCN */
+static inline int append_utran_fdd(struct bitvec *bv, uint16_t u, int *sc, size_t length)
+{
+	uint8_t chan_list[16] = { 0 };
+	int f0 = f0_helper(sc, length, chan_list);
+
+	if (f0 < 0)
+		return f0;
+
+	/* Repeated UTRAN FDD Neighbour Cells */
+	bitvec_set_bit(bv, 1);
+
+	/* FDD-ARFCN */
+	bitvec_set_bit(bv, 0);
+	bitvec_set_uint(bv, u, 14);
+
+	/* FDD_Indic0: parameter value '0000000000' is a member of the set? */
+	bitvec_set_bit(bv, f0);
+	/* NR_OF_FDD_CELLS */
+	bitvec_set_uint(bv, length, 5);
+
+	f0 = bv->cur_bit;
+	bitvec_add_range1024(bv, (struct gsm48_range_1024 *)chan_list);
+	bv->cur_bit = f0 + range1024_p(length);
+
+	return 21 + range1024_p(length);
+}
+
+/* Append multiple FDD UARFCNs */
+static inline int append_uarfcns(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
+{
+	const uint16_t *u = bts->si_common.data.uarfcn_list, *sc = bts->si_common.data.scramble_list;
+	int i, j, k, rc, st = 0, a[bts->si_common.uarfcn_length];
+	uint16_t cu = u[bts->u_offset]; /* caller ensures that length is positive */
+	uint8_t rem = budget - 7, offset_diff; /* account for constant bits right away */
+
+	OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
+	if (budget <= 7)
+		return -ENOMEM;
+
+	/* 3G Neighbour Cell Description */
+	bitvec_set_bit(bv, 1);
+	/* No Index_Start_3G */
+	bitvec_set_bit(bv, 0);
+	/* No Absolute_Index_Start_EMR */
+	bitvec_set_bit(bv, 0);
+
+	/* UTRAN FDD Description */
+	bitvec_set_bit(bv, 1);
+	/* No Bandwidth_FDD */
+	bitvec_set_bit(bv, 0);
+
+	for (i = bts->u_offset; i < bts->si_common.uarfcn_length; i++) {
+		offset_diff = 0;
+		for (j = st, k = 0; j < i; j++) {
+			a[k++] = sc[j]; /* copy corresponding SCs */
+			offset_diff++; /* compute proper offset step */
+		}
+		if (u[i] != cu) { /* we've reached new UARFCN */
+			rc = append_utran_fdd_length(cu, a, bts->si_common.uarfcn_length, k);
+			if (rc < 0) { /* estimate bit length requirements */
+				return rc;
+			}
+
+			if (rem - rc <= 0)
+				break; /* we have ran out of budget in current SI2q */
+			else {
+				rem -= append_utran_fdd(bv, cu, a, k);
+				bts->u_offset += offset_diff;
+			}
+			cu = u[i];
+			st = i; /* update start position */
+		}
+	}
+
+	if (rem > 22) {	/* add last UARFCN not covered by previous cycle if it could possibly fit into budget */
+		offset_diff = 0;
+		for (i = st, k = 0; i < bts->si_common.uarfcn_length; i++) {
+			a[k++] = sc[i];
+			offset_diff++;
+		}
+		rc = append_utran_fdd_length(cu, a, bts->si_common.uarfcn_length, k);
+		if (rc < 0) {
+			return rc;
+		}
+
+		if (rem - rc >= 0) {
+			rem -= append_utran_fdd(bv, cu, a, k);
+			bts->u_offset += offset_diff;
+		}
+	}
+
+	/* stop bit - end of Repeated UTRAN FDD Neighbour Cells */
+	bitvec_set_bit(bv, 0);
+
+	/* UTRAN TDD Description */
+	bitvec_set_bit(bv, 0);
+
+	return 0;
+}
+
+/* generate SI2quater rest octets: 3GPP TS 44.018 § 10.5.2.33b */
+int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts)
+{
+	int rc;
+	struct bitvec bv;
+
+	if (bts->si2q_count < bts->si2q_index)
+		return -EINVAL;
+
+	bv.data = data;
+	bv.data_len = 20;
+	bitvec_zero(&bv);
+
+	/* BA_IND: Set to '0' as that's what we use for SI2xxx type,
+	 * whereas '1' is used for SI5xxx type messages. The point here
+	 * is to be able to correlate whether a given MS measurement
+	 * report was using the neighbor cells advertised in SI2 or in
+	 * SI5, as those two could very well be different */
+	bitvec_set_bit(&bv, 0);
+	/* 3G_BA_IND */
+	bitvec_set_bit(&bv, 1);
+	/* MP_CHANGE_MARK */
+	bitvec_set_bit(&bv, 0);
+
+	/* SI2quater_INDEX */
+	bitvec_set_uint(&bv, bts->si2q_index, 4);
+	/* SI2quater_COUNT */
+	bitvec_set_uint(&bv, bts->si2q_count, 4);
+
+	/* No Measurement_Parameters Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_Real Time Difference Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_BSIC Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_REPORT PRIORITY Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_MEASUREMENT_Parameters Description */
+	bitvec_set_bit(&bv, 0);
+	/* No NC Measurement Parameters */
+	bitvec_set_bit(&bv, 0);
+	/* No extension (length) */
+	bitvec_set_bit(&bv, 0);
+
+	rc = SI2Q_MAX_LEN - (bv.cur_bit + 3);
+	if (rc > 0 && bts->si_common.uarfcn_length - bts->u_offset > 0) {
+		rc = append_uarfcns(&bv, bts, rc);
+		if (rc < 0) {
+			LOGP(DRR, LOGL_ERROR, "SI2quater [%u/%u]: failed to append %zu UARFCNs due to range encoding "
+			     "failure: %s\n",
+			     bts->si2q_index, bts->si2q_count, bts->si_common.uarfcn_length, strerror(-rc));
+			return rc;
+		}
+	} else /* No 3G Neighbour Cell Description */
+		bitvec_set_bit(&bv, 0);
+
+	/* No 3G Measurement Parameters Description */
+	bitvec_set_bit(&bv, 0);
+	/* No GPRS_3G_MEASUREMENT Parameters Descr. */
+	bitvec_set_bit(&bv, 0);
+
+	rc = SI2Q_MAX_LEN - bv.cur_bit;
+	if (rc > 0 && si2q_earfcn_count(&bts->si_common.si2quater_neigh_list) - bts->e_offset > 0)
+		append_earfcn(&bv, bts, rc);
+	else /* No Additions in Rel-5: */
+		bitvec_set_bit(&bv, L);
+
+	bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+	return bv.data_len;
+}
+
+/* Append selection parameters to bitvec */
+static void append_selection_params(struct bitvec *bv,
+				    const struct gsm48_si_selection_params *sp)
+{
+	if (sp->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_bit(bv, sp->cbq);
+		bitvec_set_uint(bv, sp->cell_resel_off, 6);
+		bitvec_set_uint(bv, sp->temp_offs, 3);
+		bitvec_set_uint(bv, sp->penalty_time, 5);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+/* Append power offset to bitvec */
+static void append_power_offset(struct bitvec *bv,
+				const struct gsm48_si_power_offset *po)
+{
+	if (po->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_uint(bv, po->power_offset, 2);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+/* Append GPRS indicator to bitvec */
+static void append_gprs_ind(struct bitvec *bv,
+			    const struct gsm48_si3_gprs_ind *gi)
+{
+	if (gi->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_uint(bv, gi->ra_colour, 3);
+		/* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */
+		bitvec_set_bit(bv, gi->si13_position);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(uint8_t *data, const struct gsm48_si_ro_info *si3)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 4;
+
+	/* Optional Selection Parameters */
+	append_selection_params(&bv, &si3->selection_params);
+
+	/* Optional Power Offset */
+	append_power_offset(&bv, &si3->power_offset);
+
+	/* Do we have a SI2ter on the BCCH? */
+	if (si3->si2ter_indicator)
+		bitvec_set_bit(&bv, H);
+	else
+		bitvec_set_bit(&bv, L);
+
+	/* Early Classmark Sending Control */
+	if (si3->early_cm_ctrl)
+		bitvec_set_bit(&bv, H);
+	else
+		bitvec_set_bit(&bv, L);
+
+	/* Do we have a SI Type 9 on the BCCH? */
+	if (si3->scheduling.present) {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, si3->scheduling.where, 3);
+	} else
+		bitvec_set_bit(&bv, L);
+
+	/* GPRS Indicator */
+	append_gprs_ind(&bv, &si3->gprs_ind);
+
+	/* 3G Early Classmark Sending Restriction. If H, then controlled by
+	 * early_cm_ctrl above */
+	if (si3->early_cm_restrict_3g)
+		bitvec_set_bit(&bv, L);
+	else
+		bitvec_set_bit(&bv, H);
+
+	if (si3->si2quater_indicator) {
+		bitvec_set_bit(&bv, H); /* indicator struct present */
+		bitvec_set_uint(&bv, 0, 1); /* message is sent on BCCH Norm */
+	}
+
+	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+	return bv.data_len;
+}
+
+static int append_lsa_params(struct bitvec *bv,
+			     const struct gsm48_lsa_params *lsa_params)
+{
+	/* FIXME */
+	return -1;
+}
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = len;
+
+	/* SI4 Rest Octets O */
+	append_selection_params(&bv, &si4->selection_params);
+	append_power_offset(&bv, &si4->power_offset);
+	append_gprs_ind(&bv, &si4->gprs_ind);
+
+	if (0 /* FIXME */) {
+		/* H and SI4 Rest Octets S */
+		bitvec_set_bit(&bv, H);
+
+		/* LSA Parameters */
+		if (si4->lsa_params.present) {
+			bitvec_set_bit(&bv, H);
+			append_lsa_params(&bv, &si4->lsa_params);
+		} else
+			bitvec_set_bit(&bv, L);
+
+		/* Cell Identity */
+		if (1) {
+			bitvec_set_bit(&bv, H);
+			bitvec_set_uint(&bv, si4->cell_id, 16);
+		} else
+			bitvec_set_bit(&bv, L);
+
+		/* LSA ID Information */
+		if (0) {
+			bitvec_set_bit(&bv, H);
+			/* FIXME */
+		} else
+			bitvec_set_bit(&bv, L);
+	} else {
+		/* L and break indicator */
+		bitvec_set_bit(&bv, L);
+		bitvec_set_bit(&bv, si4->break_ind ? H : L);
+	}
+
+	return bv.data_len;
+}
+
+
+/* GSM 04.18 ETSI TS 101 503 V8.27.0 (2006-05)
+
+<SI6 rest octets> ::=
+{L | H <PCH and NCH info>}
+{L | H <VBS/VGCS options : bit(2)>}
+{ < DTM_support : bit == L > I < DTM_support : bit == H >
+< RAC : bit (8) >
+< MAX_LAPDm : bit (3) > }
+< Band indicator >
+{ L | H < GPRS_MS_TXPWR_MAX_CCH : bit (5) > }
+<implicit spare >;
+*/
+int rest_octets_si6(uint8_t *data, bool is1800_net)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 1;
+
+	/* no PCH/NCH info */
+	bitvec_set_bit(&bv, L);
+	/* no VBS/VGCS options */
+	bitvec_set_bit(&bv, L);
+	/* no DTM_support */
+	bitvec_set_bit(&bv, L);
+	/* band indicator */
+	if (is1800_net)
+		bitvec_set_bit(&bv, L);
+	else
+		bitvec_set_bit(&bv, H);
+	/* no GPRS_MS_TXPWR_MAX_CCH */
+	bitvec_set_bit(&bv, L);
+
+	bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+	return bv.data_len;
+}
+
+/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
+   < GPRS Mobile Allocation IE > ::=
+     < HSN : bit (6) >
+     { 0 | 1 < RFL number list : < RFL number list struct > > }
+     { 0 < MA_LENGTH : bit (6) >
+         < MA_BITMAP: bit (val(MA_LENGTH) + 1) >
+     | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ;
+
+     < RFL number list struct > :: =
+       < RFL_NUMBER : bit (4) >
+       { 0 | 1 < RFL number list struct > } ;
+     < ARFCN index list struct > ::=
+       < ARFCN_INDEX : bit(6) >
+       { 0 | 1 < ARFCN index list struct > } ;
+ */
+static int append_gprs_mobile_alloc(struct bitvec *bv)
+{
+	/* Hopping Sequence Number */
+	bitvec_set_uint(bv, 0, 6);
+
+	if (0) {
+		/* We want to use a RFL number list */
+		bitvec_set_bit(bv, 1);
+		/* FIXME: RFL number list */
+	} else
+		bitvec_set_bit(bv, 0);
+
+	if (0) {
+		/* We want to use a MA_BITMAP */
+		bitvec_set_bit(bv, 0);
+		/* FIXME: MA_LENGTH, MA_BITMAP, ... */
+	} else {
+		bitvec_set_bit(bv, 1);
+		if (0) {
+			/* We want to provide an ARFCN index list */
+			bitvec_set_bit(bv, 1);
+			/* FIXME */
+		} else
+			bitvec_set_bit(bv, 0);
+	}
+	return 0;
+}
+
+static int encode_t3192(unsigned int t3192)
+{
+	/* See also 3GPP TS 44.060
+	   Table 12.24.2: GPRS Cell Options information element details */
+	if (t3192 == 0)
+		return 3;
+	else if (t3192 <= 80)
+		return 4;
+	else if (t3192 <= 120)
+		return 5;
+	else if (t3192 <= 160)
+		return 6;
+	else if (t3192 <= 200)
+		return 7;
+	else if (t3192 <= 500)
+		return 0;
+	else if (t3192 <= 1000)
+		return 1;
+	else if (t3192 <= 1500)
+		return 2;
+	else
+		return -EINVAL;
+}
+
+static int encode_drx_timer(unsigned int drx)
+{
+	if (drx == 0)
+		return 0;
+	else if (drx == 1)
+		return 1;
+	else if (drx == 2)
+		return 2;
+	else if (drx <= 4)
+		return 3;
+	else if (drx <= 8)
+		return 4;
+	else if (drx <= 16)
+		return 5;
+	else if (drx <= 32)
+		return 6;
+	else if (drx <= 64)
+		return 7;
+	else
+		return -EINVAL;
+}
+
+/* GPRS Cell Options as per TS 04.60 Chapter 12.24
+	< GPRS Cell Options IE > ::=
+		< NMO : bit(2) >
+		< T3168 : bit(3) >
+		< T3192 : bit(3) >
+		< DRX_TIMER_MAX: bit(3) >
+		< ACCESS_BURST_TYPE: bit >
+		< CONTROL_ACK_TYPE : bit >
+		< BS_CV_MAX: bit(4) >
+		{ 0 | 1 < PAN_DEC : bit(3) >
+			< PAN_INC : bit(3) >
+			< PAN_MAX : bit(3) >
+		{ 0 | 1 < Extension Length : bit(6) >
+			< bit (val(Extension Length) + 1
+			& { < Extension Information > ! { bit ** = <no string> } } ;
+	< Extension Information > ::=
+		{ 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit >
+			< BEP_PERIOD : bit(4) > }
+		< PFC_FEATURE_MODE : bit >
+		< DTM_SUPPORT : bit >
+		<BSS_PAGING_COORDINATION: bit >
+		<spare bit > ** ;
+ */
+static int append_gprs_cell_opt(struct bitvec *bv,
+				const struct gprs_cell_options *gco)
+{
+	int t3192, drx_timer_max;
+
+	t3192 = encode_t3192(gco->t3192);
+	if (t3192 < 0)
+		return t3192;
+
+	drx_timer_max = encode_drx_timer(gco->drx_timer_max);
+	if (drx_timer_max < 0)
+		return drx_timer_max;
+
+	bitvec_set_uint(bv, gco->nmo, 2);
+
+	/* See also 3GPP TS 44.060
+	   Table 12.24.2: GPRS Cell Options information element details */
+	bitvec_set_uint(bv, gco->t3168 / 500 - 1, 3);
+
+	bitvec_set_uint(bv, t3192, 3);
+	bitvec_set_uint(bv, drx_timer_max, 3);
+	/* ACCESS_BURST_TYPE: Hard-code 8bit */
+	bitvec_set_bit(bv, 0);
+	/* CONTROL_ACK_TYPE: */
+	bitvec_set_bit(bv, gco->ctrl_ack_type_use_block);
+	bitvec_set_uint(bv, gco->bs_cv_max, 4);
+
+	if (0) {
+		/* hard-code no PAN_{DEC,INC,MAX} */
+		bitvec_set_bit(bv, 0);
+	} else {
+		/* copied from ip.access BSC protocol trace */
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, 1, 3);	/* DEC */
+		bitvec_set_uint(bv, 1, 3);	/* INC */
+		bitvec_set_uint(bv, 15, 3);	/* MAX */
+	}
+
+	if (!gco->ext_info_present) {
+		/* no extension information */
+		bitvec_set_bit(bv, 0);
+	} else {
+		/* extension information */
+		bitvec_set_bit(bv, 1);
+		if (!gco->ext_info.egprs_supported) {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 0);
+		} else {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 1);
+
+			/* 1bit EGPRS PACKET CHANNEL REQUEST */
+			if (gco->supports_egprs_11bit_rach == 0) {
+				bitvec_set_bit(bv,
+					gco->ext_info.use_egprs_p_ch_req);
+			} else {
+				bitvec_set_bit(bv, 0);
+			}
+
+			/* 4bit BEP PERIOD */
+			bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
+		}
+		bitvec_set_bit(bv, gco->ext_info.pfc_supported);
+		bitvec_set_bit(bv, gco->ext_info.dtm_supported);
+		bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+	}
+
+	return 0;
+}
+
+static void append_gprs_pwr_ctrl_pars(struct bitvec *bv,
+				      const struct gprs_power_ctrl_pars *pcp)
+{
+	bitvec_set_uint(bv, pcp->alpha, 4);
+	bitvec_set_uint(bv, pcp->t_avg_w, 5);
+	bitvec_set_uint(bv, pcp->t_avg_t, 5);
+	bitvec_set_uint(bv, pcp->pc_meas_chan, 1);
+	bitvec_set_uint(bv, pcp->n_avg_i, 4);
+}
+
+/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
+int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 20;
+
+	if (0) {
+		/* No rest octets */
+		bitvec_set_bit(&bv, L);
+	} else {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, si13->bcch_change_mark, 3);
+		bitvec_set_uint(&bv, si13->si_change_field, 4);
+		if (1) {
+			bitvec_set_bit(&bv, 0);
+		} else {
+			bitvec_set_bit(&bv, 1);
+			bitvec_set_uint(&bv, si13->bcch_change_mark, 2);
+			append_gprs_mobile_alloc(&bv);
+		}
+		/* PBCCH not present in cell:
+		   it shall never be indicated according to 3GPP TS 44.018 Table 10.5.2.37b.1 */
+		bitvec_set_bit(&bv, 0);
+		bitvec_set_uint(&bv, si13->rac, 8);
+		bitvec_set_bit(&bv, si13->spgc_ccch_sup);
+		bitvec_set_uint(&bv, si13->prio_acc_thr, 3);
+		bitvec_set_uint(&bv, si13->net_ctrl_ord, 2);
+		append_gprs_cell_opt(&bv, &si13->cell_opts);
+		append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars);
+
+		/* 3GPP TS 44.018 Release 6 / 10.5.2.37b */
+		bitvec_set_bit(&bv, H);	/* added Release 99 */
+		/* claim our SGSN is compatible with Release 99, as EDGE and EGPRS
+		 * was only added in this Release */
+		bitvec_set_bit(&bv, 1);
+	}
+	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+	return bv.data_len;
+}
diff --git a/openbsc/src/libbsc/system_information.c b/openbsc/src/libbsc/system_information.c
new file mode 100644
index 0000000..a6226a4
--- /dev/null
+++ b/openbsc/src/libbsc/system_information.c
@@ -0,0 +1,1191 @@
+/* GSM 04.08 System Information (SI) encoding and decoding
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/sysinfo.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/rest_octets.h>
+#include <openbsc/arfcn_range_encode.h>
+#include <openbsc/acc_ramp.h>
+
+/*
+ * DCS1800 and PCS1900 have overlapping ARFCNs. We would need to set the
+ * ARFCN_PCS flag on the 1900 ARFCNs but this would increase cell_alloc
+ * and other arrays to make sure (ARFCN_PCS + 1024)/8 ARFCNs fit into the
+ * array. DCS1800 and PCS1900 can not be used at the same time so conserve
+ * memory and do the below.
+ */
+static int band_compatible(const struct gsm_bts *bts, int arfcn)
+{
+	enum gsm_band band = gsm_arfcn2band(arfcn);
+
+	/* normal case */
+	if (band == bts->band)
+		return 1;
+	/* deal with ARFCN_PCS not set */
+	if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+		return 1;
+
+	return 0;
+}
+
+static int is_dcs_net(const struct gsm_bts *bts)
+{
+	if (bts->band == GSM_BAND_850)
+		return 0;
+	if (bts->band == GSM_BAND_1900)
+		return 0;
+	return 1;
+}
+
+/* Return p(n) for given NR_OF_TDD_CELLS - see Table 9.1.54.1a, 3GPP TS 44.018 */
+unsigned range1024_p(unsigned n)
+{
+	switch (n) {
+	case 0: return 0;
+	case 1: return 10;
+	case 2: return 19;
+	case 3: return 28;
+	case 4: return 36;
+	case 5: return 44;
+	case 6: return 52;
+	case 7: return 60;
+	case 8: return 67;
+	case 9: return 74;
+	case 10: return 81;
+	case 11: return 88;
+	case 12: return 95;
+	case 13: return 102;
+	case 14: return 109;
+	case 15: return 116;
+	case 16: return 122;
+	default: return 0;
+	}
+}
+
+/* Return q(m) for given NR_OF_TDD_CELLS - see Table 9.1.54.1b, 3GPP TS 44.018 */
+unsigned range512_q(unsigned m)
+{
+	switch (m) {
+	case 0: return 0;
+	case 1: return 9;
+	case 2: return 17;
+	case 3: return 25;
+	case 4: return 32;
+	case 5: return 39;
+	case 6: return 46;
+	case 7: return 53;
+	case 8: return 59;
+	case 9: return 65;
+	case 10: return 71;
+	case 11: return 77;
+	case 12: return 83;
+	case 13: return 89;
+	case 14: return 95;
+	case 15: return 101;
+	case 16: return 106;
+	case 17: return 111;
+	case 18: return 116;
+	case 19: return 121;
+	case 20: return 126;
+	default: return 0;
+	}
+}
+
+size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e)
+{
+	unsigned i, ret = 0;
+
+	if (!e)
+		return 0;
+
+	for (i = 0; i < e->length; i++)
+		if (e->arfcn[i] != OSMO_EARFCN_INVALID)
+			ret++;
+
+	return ret;
+}
+
+/* generate SI2quater messages, return rest octets length of last generated message or negative error code */
+static int make_si2quaters(struct gsm_bts *bts, bool counting)
+{
+	int rc;
+	bool memory_exceeded = true;
+	struct gsm48_system_information_type_2quater *si2q;
+
+	for (bts->si2q_index = 0; bts->si2q_index < SI2Q_MAX_NUM; bts->si2q_index++) {
+		si2q = GSM_BTS_SI2Q(bts, bts->si2q_index);
+		if (counting) { /* that's legitimate if we're called for counting purpose: */
+			if (bts->si2q_count < bts->si2q_index)
+				bts->si2q_count = bts->si2q_index;
+		} else {
+			memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+			si2q->header.l2_plen = GSM48_LEN2PLEN(22);
+			si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+			si2q->header.skip_indicator = 0;
+			si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater;
+		}
+
+		rc = rest_octets_si2quater(si2q->rest_octets, bts);
+		if (rc < 0)
+			return rc;
+
+		if (bts->u_offset >= bts->si_common.uarfcn_length &&
+		    bts->e_offset >= si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) {
+			memory_exceeded = false;
+			break;
+		}
+	}
+
+	if (memory_exceeded)
+		return -ENOMEM;
+
+	return rc;
+}
+
+/* we generate SI2q rest octets twice to get proper estimation but it's one time cost anyway */
+uint8_t si2q_num(struct gsm_bts *bts)
+{
+	int rc = make_si2quaters(bts, true);
+	uint8_t num = bts->si2q_index + 1; /* number of SI2quater messages */
+
+	/* N. B: si2q_num() should NEVER be called during actualSI2q rest octets generation
+	   we're not re-entrant because of the following code: */
+	bts->u_offset = 0;
+	bts->e_offset = 0;
+
+	if (rc < 0)
+		return 0xFF; /* return impossible index as an indicator of error in generating SI2quater */
+
+	return num;
+}
+
+/* 3GPP TS 44.018, Table 9.1.54.1 - prepend diversity bit to scrambling code */
+static inline uint16_t encode_fdd(uint16_t scramble, bool diversity)
+{
+	if (diversity)
+		return scramble | (1 << 9);
+	return scramble;
+}
+
+int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint8_t thresh_lo, uint8_t prio,
+		   uint8_t qrx, uint8_t meas_bw)
+{
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	int r = osmo_earfcn_add(e, earfcn, (meas_bw < EARFCN_MEAS_BW_INVALID) ? meas_bw : OSMO_EARFCN_MEAS_INVALID);
+
+	if (r < 0)
+		return r;
+
+	if (e->thresh_hi && thresh_hi != e->thresh_hi)
+		r = 1;
+
+	e->thresh_hi = thresh_hi;
+
+	if (thresh_lo != EARFCN_THRESH_LOW_INVALID) {
+		if (e->thresh_lo_valid && e->thresh_lo != thresh_lo)
+			r = EARFCN_THRESH_LOW_INVALID;
+		e->thresh_lo = thresh_lo;
+		e->thresh_lo_valid = true;
+	}
+
+	if (qrx != EARFCN_QRXLV_INVALID) {
+		if (e->qrxlm_valid && e->qrxlm != qrx)
+			r = EARFCN_QRXLV_INVALID + 1;
+		e->qrxlm = qrx;
+		e->qrxlm_valid = true;
+	}
+
+	if (prio != EARFCN_PRIO_INVALID) {
+		if (e->prio_valid && e->prio != prio)
+			r = EARFCN_PRIO_INVALID;
+		e->prio = prio;
+		e->prio_valid = true;
+	}
+
+	return r;
+}
+
+int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble)
+{
+	uint16_t sc0 = encode_fdd(scramble, false), sc1 = encode_fdd(scramble, true),
+		*ual = bts->si_common.data.uarfcn_list,
+		*scl = bts->si_common.data.scramble_list;
+	size_t len = bts->si_common.uarfcn_length, i;
+	for (i = 0; i < len; i++) {
+		if (arfcn == ual[i] && (sc0 == scl[i] || sc1 == scl[i])) {
+			/* we rely on the assumption that (uarfcn, scramble)
+			   tuple is unique in the lists */
+			if (i != len - 1) { /* move the tail if necessary */
+				memmove(ual + i, ual + i + 1, 2 * (len - i + 1));
+				memmove(scl + i, scl + i + 1, 2 * (len - i + 1));
+			}
+			break;
+		}
+	}
+
+	if (i == len)
+		return -EINVAL;
+
+	bts->si_common.uarfcn_length--;
+	return 0;
+}
+
+int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity)
+{
+	size_t len = bts->si_common.uarfcn_length, i, k = 0;
+	uint16_t scr, chk,
+		*ual = bts->si_common.data.uarfcn_list,
+		*scl = bts->si_common.data.scramble_list,
+		scramble1 = encode_fdd(scramble, true),
+		scramble0 = encode_fdd(scramble, false);
+
+	scr = diversity ? scramble1 : scramble0;
+	chk = diversity ? scramble0 : scramble1;
+
+	if (len == MAX_EARFCN_LIST)
+		return -ENOMEM;
+
+	for (i = 0; i < len; i++) /* find the position of arfcn if any */
+		if (arfcn == ual[i])
+			break;
+
+	for (k = 0; i < len; i++) {
+		if (arfcn == ual[i] && (scr == scl[i] || chk == scl[i]))
+			return -EADDRINUSE;
+		if (scr > scl[i])
+			k = i + 1;
+	}
+	/* we keep lists sorted by scramble code:
+	   insert into appropriate position and move the tail */
+	if (len - k) {
+		memmove(ual + k + 1, ual + k, (len - k) * 2);
+		memmove(scl + k + 1, scl + k, (len - k) * 2);
+	}
+
+	ual[k] = arfcn;
+	scl[k] = scr;
+	bts->si_common.uarfcn_length++;
+
+	if (si2q_num(bts) <= SI2Q_MAX_NUM) {
+		bts->si2q_count = si2q_num(bts) - 1;
+		return 0;
+	}
+
+	bts_uarfcn_del(bts, arfcn, scramble);
+	return -ENOSPC;
+}
+
+static inline int use_arfcn(const struct gsm_bts *bts, const bool bis, const bool ter,
+			const bool pgsm, const int arfcn)
+{
+	if (bts->force_combined_si)
+		return !bis && !ter;
+	if (!bis && !ter && band_compatible(bts, arfcn))
+		return 1;
+	/* Correct but somehow broken with either the nanoBTS or the iPhone5 */
+	if (bis && pgsm && band_compatible(bts, arfcn) && (arfcn < 1 || arfcn > 124))
+		return 1;
+	if (ter && !band_compatible(bts, arfcn))
+		return 1;
+	return 0;
+}
+
+/* Frequency Lists as per TS 04.08 10.5.2.13 */
+
+/* 10.5.2.13.2: Bit map 0 format */
+static int freq_list_bm0_set_arfcn(uint8_t *chan_list, unsigned int arfcn)
+{
+	unsigned int byte, bit;
+
+	if (arfcn > 124 || arfcn < 1) {
+		LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n");
+		return -EINVAL;
+	}
+
+	/* the bitmask is from 1..124, not from 0..123 */
+	arfcn--;
+
+	byte = arfcn / 8;
+	bit = arfcn % 8;
+
+	chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit);
+
+	return 0;
+}
+
+/* 10.5.2.13.7: Variable bit map format */
+static int freq_list_bmrel_set_arfcn(uint8_t *chan_list, unsigned int arfcn)
+{
+	unsigned int byte, bit;
+	unsigned int min_arfcn;
+	unsigned int bitno;
+
+	min_arfcn = (chan_list[0] & 1) << 9;
+	min_arfcn |= chan_list[1] << 1;
+	min_arfcn |= (chan_list[2] >> 7) & 1;
+
+	/* The lower end of our bitmaks is always implicitly included */
+	if (arfcn == min_arfcn)
+		return 0;
+
+	if (((arfcn - min_arfcn) & 1023) > 111) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+
+	bitno = (arfcn - min_arfcn) & 1023;
+	byte = bitno / 8;
+	bit = bitno % 8;
+
+	chan_list[2 + byte] |= 1 << (7 - bit);
+
+	return 0;
+}
+
+/* generate a variable bitmap */
+static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list,
+				struct bitvec *bv, const struct gsm_bts *bts,
+				bool bis, bool ter, int min, bool pgsm)
+{
+	int i;
+
+	/* set it to 'Variable bitmap format' */
+	chan_list[0] = 0x8e;
+
+	chan_list[0] |= (min >> 9) & 1;
+	chan_list[1] = (min >> 1);
+	chan_list[2] = (min & 1) << 7;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		/* see notes in bitvec2freq_list */
+		if (bitvec_get_bit_pos(bv, i)
+		 && ((!bis && !ter && band_compatible(bts,i))
+		  || (bis && pgsm && band_compatible(bts,i) && (i < 1 || i > 124))
+		  || (ter && !band_compatible(bts, i)))) {
+			int rc = freq_list_bmrel_set_arfcn(chan_list, i);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
+		 int f0, uint8_t *chan_list)
+{
+	/*
+	 * Manipulate the ARFCN list according to the rules in J4 depending
+	 * on the selected range.
+	 */
+	int rc, f0_included;
+
+	range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included);
+
+	rc = range_enc_arfcns(r, arfcns, arfcns_used, w, 0);
+	if (rc < 0)
+		return rc;
+
+	/* Select the range and the amount of bits needed */
+	switch (r) {
+	case ARFCN_RANGE_128:
+		return range_enc_range128(chan_list, f0, w);
+	case ARFCN_RANGE_256:
+		return range_enc_range256(chan_list, f0, w);
+	case ARFCN_RANGE_512:
+		return range_enc_range512(chan_list, f0, w);
+	case ARFCN_RANGE_1024:
+		return range_enc_range1024(chan_list, f0, f0_included, w);
+	default:
+		return -ERANGE;
+	};
+
+	return f0_included;
+}
+
+/* generate a frequency list with the range 512 format */
+static inline int enc_freq_lst_range(uint8_t *chan_list,
+				struct bitvec *bv, const struct gsm_bts *bts,
+				bool bis, bool ter, bool pgsm)
+{
+	int arfcns[RANGE_ENC_MAX_ARFCNS];
+	int w[RANGE_ENC_MAX_ARFCNS];
+	int arfcns_used = 0;
+	int i, range, f0;
+
+	/*
+	 * Select ARFCNs according to the rules in bitvec2freq_list
+	 */
+	for (i = 0; i < bv->data_len * 8; ++i) {
+		/* More ARFCNs than the maximum */
+		if (arfcns_used > ARRAY_SIZE(arfcns))
+			return -1;
+		/* Check if we can select it? */
+		if (bitvec_get_bit_pos(bv, i) && use_arfcn(bts, bis, ter, pgsm, i))
+			arfcns[arfcns_used++] = i;
+	}
+
+	/*
+	 * Check if the given list of ARFCNs can be encoded.
+	 */
+	range = range_enc_determine_range(arfcns, arfcns_used, &f0);
+	if (range == ARFCN_RANGE_INVALID)
+		return -2;
+
+	memset(w, 0, sizeof(w));
+	return range_encode(range, arfcns, arfcns_used, w, f0, chan_list);
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv,
+			    const struct gsm_bts *bts, bool bis, bool ter)
+{
+	int i, rc, min = -1, max = -1, arfcns = 0;
+	bool pgsm = false;
+	memset(chan_list, 0, 16);
+
+	if (bts->band == GSM_BAND_900
+	 && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124)
+		pgsm = true;
+	/* P-GSM-only handsets only support 'bit map 0 format' */
+	if (!bis && !ter && pgsm) {
+		chan_list[0] = 0;
+
+		for (i = 0; i < bv->data_len*8; i++) {
+			if (i >= 1 && i <= 124
+			 && bitvec_get_bit_pos(bv, i)) {
+				rc = freq_list_bm0_set_arfcn(chan_list, i);
+				if (rc < 0)
+					return rc;
+			}
+		}
+		return 0;
+	}
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		/* in case of SI2 or SI5 allow all neighbours in same band
+		 * in case of SI*bis, allow neighbours in same band ouside pgsm
+		 * in case of SI*ter, allow neighbours in different bands
+		 */
+		if (!bitvec_get_bit_pos(bv, i))
+			continue;
+		if (!use_arfcn(bts, bis, ter, pgsm, i))
+			continue;
+		/* count the arfcns we want to carry */
+		arfcns += 1;
+
+		/* 955..1023 < 0..885 */
+		if (min < 0)
+			min = i;
+		if (i >= 955 && min < 955)
+			min = i;
+		if (i >= 955 && min >= 955 && i < min)
+			min = i;
+		if (i < 955 && min < 955 && i < min)
+			min = i;
+		if (max < 0)
+			max = i;
+		if (i < 955 && max >= 955)
+			max = i;
+		if (i >= 955 && max >= 955 && i > max)
+			max = i;
+		if (i < 955 && max < 955 && i > max)
+			max = i;
+	}
+
+	if (max == -1) {
+		/* Empty set, use 'bit map 0 format' */
+		chan_list[0] = 0;
+		return 0;
+	}
+
+	/* Now find the best encoding */
+	if (((max - min) & 1023) <= 111)
+		return enc_freq_lst_var_bitmap(chan_list, bv, bts, bis,
+				ter, min, pgsm);
+
+	/* Attempt to do the range encoding */
+	rc = enc_freq_lst_range(chan_list, bv, bts, bis, ter, pgsm);
+	if (rc >= 0)
+		return 0;
+
+	LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, arfcns=%d "
+		"can not generate ARFCN list", min, max, arfcns);
+	return -EINVAL;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+/* static*/ int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	struct bitvec *bv = &bts->si_common.cell_alloc;
+
+	/* Zero-initialize the bit-vector */
+	memset(bv->data, 0, bv->data_len);
+
+	/* first we generate a bitvec of all TRX ARFCN's in our BTS */
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		unsigned int i, j;
+		/* Always add the TRX's ARFCN */
+		bitvec_set_bit_pos(bv, trx->arfcn, 1);
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+			/* Add any ARFCNs present in hopping channels */
+			for (j = 0; j < 1024; j++) {
+				if (bitvec_get_bit_pos(&ts->hopping.arfcns, j))
+					bitvec_set_bit_pos(bv, j, 1);
+			}
+		}
+	}
+
+	/* then we generate a GSM 04.08 frequency list from the bitvec */
+	return bitvec2freq_list(chan_list, bv, bts, false, false);
+}
+
+/*! generate a cell channel list as per Section 10.5.2.22 of 04.08
+ *  \param[out] chan_list caller-provided output buffer
+ *  \param[in] bts BTS descriptor used for input data
+ *  \param[in] si5 Are we generating SI5xxx (true) or SI2xxx (false)
+ *  \param[in] bis Are we generating SIXbis (true) or not (false)
+ *  \param[in] ter Are we generating SIXter (true) or not (false)
+ */
+static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts,
+	bool si5, bool bis, bool ter)
+{
+	struct gsm_bts *cur_bts;
+	struct bitvec *bv;
+	int rc;
+
+	if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP)
+		bv = &bts->si_common.si5_neigh_list;
+	else
+		bv = &bts->si_common.neigh_list;
+
+	/* Generate list of neighbor cells if we are in automatic mode */
+	if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+		/* Zero-initialize the bit-vector */
+		memset(bv->data, 0, bv->data_len);
+
+		/* first we generate a bitvec of the BCCH ARFCN's in our BSC */
+		llist_for_each_entry(cur_bts, &bts->network->bts_list, list) {
+			if (cur_bts == bts)
+				continue;
+			bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1);
+		}
+	}
+
+	/* then we generate a GSM 04.08 frequency list from the bitvec */
+	rc = bitvec2freq_list(chan_list, bv, bts, bis, ter);
+	if (rc < 0)
+		return rc;
+
+	/* Set BA-IND depending on whether we're generating SI2 or SI5.
+	 * The point here is to be able to correlate whether a given MS
+	 * measurement report was using the neighbor cells advertised in
+	 * SI2 or in SI5, as those two could very well be different */
+	if (si5)
+		chan_list[0] |= 0x10;
+	else
+		chan_list[0] &= ~0x10;
+
+	return rc;
+}
+
+static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text)
+{
+	int n = 0, i;
+	struct gsm_sysinfo_freq freq[1024];
+
+	memset(freq, 0, sizeof(freq));
+	gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1);
+	for (i = 0; i < 1024; i++) {
+		if (freq[i].mask) {
+			if (!n)
+				LOGP(DRR, LOGL_INFO, "%s", text);
+			LOGPC(DRR, LOGL_INFO, " %d", i);
+			n++;
+		}
+	}
+	if (n)
+		LOGPC(DRR, LOGL_INFO, "\n");
+
+	return n;
+}
+
+static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_1 *si1 = (struct gsm48_system_information_type_1 *) GSM_BTS_SI(bts, t);
+
+	memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si1->header.l2_plen = GSM48_LEN2PLEN(21);
+	si1->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si1->header.skip_indicator = 0;
+	si1->header.system_information = GSM48_MT_RR_SYSINFO_1;
+
+	rc = generate_cell_chan_list(si1->cell_channel_description, bts);
+	if (rc < 0)
+		return rc;
+	list_arfcn(si1->cell_channel_description, 0xce, "Serving cell:");
+
+	si1->rach_control = bts->si_common.rach_control;
+	if (acc_ramp_is_enabled(&bts->acc_ramp))
+		acc_ramp_apply(&si1->rach_control, &bts->acc_ramp);
+
+	/*
+	 * SI1 Rest Octets (10.5.2.32), contains NCH position and band
+	 * indicator but that is not in the 04.08.
+	 */
+	rc = rest_octets_si1(si1->rest_octets, NULL, is_dcs_net(bts));
+
+	return sizeof(*si1) + rc;
+}
+
+static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2 *si2 = (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, t);
+
+	memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2->header.l2_plen = GSM48_LEN2PLEN(22);
+	si2->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si2->header.skip_indicator = 0;
+	si2->header.system_information = GSM48_MT_RR_SYSINFO_2;
+
+	rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, false, false, false);
+	if (rc < 0)
+		return rc;
+	list_arfcn(si2->bcch_frequency_list, 0xce,
+		"SI2 Neighbour cells in same band:");
+
+	si2->ncc_permitted = bts->si_common.ncc_permitted;
+	si2->rach_control = bts->si_common.rach_control;
+	if (acc_ramp_is_enabled(&bts->acc_ramp))
+		acc_ramp_apply(&si2->rach_control, &bts->acc_ramp);
+
+	return sizeof(*si2);
+}
+
+static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2bis *si2b =
+		(struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, t);
+	int n;
+
+	memset(si2b, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2b->header.l2_plen = GSM48_LEN2PLEN(22);
+	si2b->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si2b->header.skip_indicator = 0;
+	si2b->header.system_information = GSM48_MT_RR_SYSINFO_2bis;
+
+	rc = generate_bcch_chan_list(si2b->bcch_frequency_list, bts, false, true, false);
+	if (rc < 0)
+		return rc;
+	n = list_arfcn(si2b->bcch_frequency_list, 0xce,
+		"Neighbour cells in same band, but outside P-GSM:");
+	if (n) {
+		/* indicate in SI2 and SI2bis: there is an extension */
+		struct gsm48_system_information_type_2 *si2 =
+			(struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+		si2->bcch_frequency_list[0] |= 0x20;
+		si2b->bcch_frequency_list[0] |= 0x20;
+	} else
+		bts->si_valid &= ~(1 << SYSINFO_TYPE_2bis);
+
+	si2b->rach_control = bts->si_common.rach_control;
+	if (acc_ramp_is_enabled(&bts->acc_ramp))
+		acc_ramp_apply(&si2b->rach_control, &bts->acc_ramp);
+
+	return sizeof(*si2b);
+}
+
+static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2ter *si2t =
+		(struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, t);
+	int n;
+
+	memset(si2t, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2t->header.l2_plen = GSM48_LEN2PLEN(22);
+	si2t->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si2t->header.skip_indicator = 0;
+	si2t->header.system_information = GSM48_MT_RR_SYSINFO_2ter;
+
+	rc = generate_bcch_chan_list(si2t->ext_bcch_frequency_list, bts, false, false, true);
+	if (rc < 0)
+		return rc;
+	n = list_arfcn(si2t->ext_bcch_frequency_list, 0x8e,
+		"Neighbour cells in different band:");
+	if (!n)
+		bts->si_valid &= ~(1 << SYSINFO_TYPE_2ter);
+
+	return sizeof(*si2t);
+}
+
+/* SI2quater messages are optional - we only generate them when neighbor UARFCNs or EARFCNs are configured */
+static inline bool si2quater_not_needed(struct gsm_bts *bts)
+{
+	unsigned i = MAX_EARFCN_LIST;
+
+	if (bts->si_common.si2quater_neigh_list.arfcn)
+		for (i = 0; i < MAX_EARFCN_LIST; i++)
+			if (bts->si_common.si2quater_neigh_list.arfcn[i] != OSMO_EARFCN_INVALID)
+				break;
+
+	if (!bts->si_common.uarfcn_length && i == MAX_EARFCN_LIST) {
+		bts->si_valid &= ~(1 << SYSINFO_TYPE_2quater); /* mark SI2q as invalid if no (E|U)ARFCNs are present */
+		return true;
+	}
+
+	return false;
+}
+
+static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2quater *si2q;
+
+	if (si2quater_not_needed(bts)) /* generate rest_octets for SI2q only when necessary */
+		return GSM_MACBLOCK_LEN;
+
+	bts->u_offset = 0;
+	bts->e_offset = 0;
+	bts->si2q_index = 0;
+	bts->si2q_count = si2q_num(bts) - 1;
+
+	rc = make_si2quaters(bts, false);
+	if (rc < 0)
+		return rc;
+
+	OSMO_ASSERT(bts->si2q_count == bts->si2q_index);
+	OSMO_ASSERT(bts->si2q_count <= SI2Q_MAX_NUM);
+
+	return sizeof(*si2q) + rc;
+}
+
+static struct gsm48_si_ro_info si_info = {
+	.selection_params = {
+		.present = 0,
+	},
+	.power_offset = {
+		.present = 0,
+	},
+	.si2ter_indicator = false,
+	.early_cm_ctrl = true,
+	.scheduling = {
+		.present = 0,
+	},
+	.gprs_ind = {
+		.si13_position = 0,
+		.ra_colour = 0,
+		.present = 1,
+	},
+	.early_cm_restrict_3g = false,
+	.si2quater_indicator = false,
+	.lsa_params = {
+		.present = 0,
+	},
+	.cell_id = 0,	/* FIXME: doesn't the bts have this? */
+	.break_ind = 0,
+};
+
+static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_3 *si3 = (struct gsm48_system_information_type_3 *) GSM_BTS_SI(bts, t);
+
+	memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si3->header.l2_plen = GSM48_LEN2PLEN(18);
+	si3->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si3->header.skip_indicator = 0;
+	si3->header.system_information = GSM48_MT_RR_SYSINFO_3;
+
+	si3->cell_identity = htons(bts->cell_identity);
+	gsm48_generate_lai2(&si3->lai, bts_lai(bts));
+	si3->control_channel_desc = bts->si_common.chan_desc;
+	si3->cell_options = bts->si_common.cell_options;
+	si3->cell_sel_par = bts->si_common.cell_sel_par;
+	si3->rach_control = bts->si_common.rach_control;
+	if (acc_ramp_is_enabled(&bts->acc_ramp))
+		acc_ramp_apply(&si3->rach_control, &bts->acc_ramp);
+
+	/* allow/disallow DTXu */
+	gsm48_set_dtx(&si3->cell_options, bts->dtxu, bts->dtxu, true);
+
+	if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) {
+		LOGP(DRR, LOGL_INFO, "SI 2ter is included.\n");
+		si_info.si2ter_indicator = true;
+	} else {
+		si_info.si2ter_indicator = false;
+	}
+	if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater)) {
+		LOGP(DRR, LOGL_INFO, "SI 2quater is included, based on %zu EARFCNs and %zu UARFCNs.\n",
+		     si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length);
+		si_info.si2quater_indicator = true;
+	} else {
+		si_info.si2quater_indicator = false;
+	}
+	si_info.early_cm_ctrl = bts->early_classmark_allowed;
+	si_info.early_cm_restrict_3g = !bts->early_classmark_allowed_3g;
+
+	/* SI3 Rest Octets (10.5.2.34), containing
+		CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME
+		Power Offset, 2ter Indicator, Early Classmark Sending,
+		Scheduling if and WHERE, GPRS Indicator, SI13 position */
+	rc = rest_octets_si3(si3->rest_octets, &si_info);
+
+	return sizeof(*si3) + rc;
+}
+
+static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_4 *si4 = (struct gsm48_system_information_type_4 *) GSM_BTS_SI(bts, t);
+	struct gsm_lchan *cbch_lchan;
+	uint8_t *restoct = si4->data;
+
+	/* length of all IEs present except SI4 rest octets and l2_plen */
+	int l2_plen = sizeof(*si4) - 1;
+
+	memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si4->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si4->header.skip_indicator = 0;
+	si4->header.system_information = GSM48_MT_RR_SYSINFO_4;
+
+	gsm48_generate_lai2(&si4->lai, bts_lai(bts));
+	si4->cell_sel_par = bts->si_common.cell_sel_par;
+	si4->rach_control = bts->si_common.rach_control;
+	if (acc_ramp_is_enabled(&bts->acc_ramp))
+		acc_ramp_apply(&si4->rach_control, &bts->acc_ramp);
+
+	/* Optional: CBCH Channel Description + CBCH Mobile Allocation */
+	cbch_lchan = gsm_bts_get_cbch(bts);
+	if (cbch_lchan) {
+		struct gsm48_chan_desc cd;
+		gsm48_lchan2chan_desc(&cd, cbch_lchan);
+		tv_fixed_put(si4->data, GSM48_IE_CBCH_CHAN_DESC, 3,
+			     (uint8_t *) &cd);
+		l2_plen += 3 + 1;
+		restoct += 3 + 1;
+		/* we don't use hopping and thus don't need a CBCH MA */
+	}
+
+	si4->header.l2_plen = GSM48_LEN2PLEN(l2_plen);
+
+	/* SI4 Rest Octets (10.5.2.35), containing
+		Optional Power offset, GPRS Indicator,
+		Cell Identity, LSA ID, Selection Parameter */
+	rc = rest_octets_si4(restoct, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - restoct);
+
+	return l2_plen + 1 + rc;
+}
+
+static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_5 *si5;
+	uint8_t *output = GSM_BTS_SI(bts, t);
+	int rc, l2_plen = 18;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		*output++ = GSM48_LEN2PLEN(l2_plen);
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si5 = (struct gsm48_system_information_type_5 *) output;
+
+	/* l2 pseudo length, not part of msg: 18 */
+	si5->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si5->skip_indicator = 0;
+	si5->system_information = GSM48_MT_RR_SYSINFO_5;
+	rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, true, false, false);
+	if (rc < 0)
+		return rc;
+	list_arfcn(si5->bcch_frequency_list, 0xce,
+		"SI5 Neighbour cells in same band:");
+
+	/* 04.08 9.1.37: L2 Pseudo Length of 18 */
+	return l2_plen;
+}
+
+static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_5bis *si5b;
+	uint8_t *output = GSM_BTS_SI(bts, t);
+	int rc, l2_plen = 18;
+	int n;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		*output++ = GSM48_LEN2PLEN(l2_plen);
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si5b = (struct gsm48_system_information_type_5bis *) output;
+
+	/* l2 pseudo length, not part of msg: 18 */
+	si5b->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si5b->skip_indicator = 0;
+	si5b->system_information = GSM48_MT_RR_SYSINFO_5bis;
+	rc = generate_bcch_chan_list(si5b->bcch_frequency_list, bts, true, true, false);
+	if (rc < 0)
+		return rc;
+	n = list_arfcn(si5b->bcch_frequency_list, 0xce,
+		"Neighbour cells in same band, but outside P-GSM:");
+	if (n) {
+		/* indicate in SI5 and SI5bis: there is an extension */
+		struct gsm48_system_information_type_5 *si5 =
+			(struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5)+1;
+		si5->bcch_frequency_list[0] |= 0x20;
+		si5b->bcch_frequency_list[0] |= 0x20;
+	} else
+		bts->si_valid &= ~(1 << SYSINFO_TYPE_5bis);
+
+	/* 04.08 9.1.37: L2 Pseudo Length of 18 */
+	return l2_plen;
+}
+
+static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_5ter *si5t;
+	uint8_t *output = GSM_BTS_SI(bts, t);
+	int rc, l2_plen = 18;
+	int n;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		*output++ = GSM48_LEN2PLEN(l2_plen);
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si5t = (struct gsm48_system_information_type_5ter *) output;
+
+	/* l2 pseudo length, not part of msg: 18 */
+	si5t->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si5t->skip_indicator = 0;
+	si5t->system_information = GSM48_MT_RR_SYSINFO_5ter;
+	rc = generate_bcch_chan_list(si5t->bcch_frequency_list, bts, true, false, true);
+	if (rc < 0)
+		return rc;
+	n = list_arfcn(si5t->bcch_frequency_list, 0x8e,
+		"Neighbour cells in different band:");
+	if (!n)
+		bts->si_valid &= ~(1 << SYSINFO_TYPE_5ter);
+
+	/* 04.08 9.1.37: L2 Pseudo Length of 18 */
+	return l2_plen;
+}
+
+static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_6 *si6;
+	uint8_t *output = GSM_BTS_SI(bts, t);
+	int l2_plen = 11;
+	int rc;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		*output++ = GSM48_LEN2PLEN(l2_plen);
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si6 = (struct gsm48_system_information_type_6 *) output;
+
+	/* l2 pseudo length, not part of msg: 11 */
+	si6->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si6->skip_indicator = 0;
+	si6->system_information = GSM48_MT_RR_SYSINFO_6;
+	si6->cell_identity = htons(bts->cell_identity);
+	gsm48_generate_lai2(&si6->lai, bts_lai(bts));
+	si6->cell_options = bts->si_common.cell_options;
+	si6->ncc_permitted = bts->si_common.ncc_permitted;
+	/* allow/disallow DTXu */
+	gsm48_set_dtx(&si6->cell_options, bts->dtxu, bts->dtxu, false);
+
+	/* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */
+	rc = rest_octets_si6(si6->rest_octets, is_dcs_net(bts));
+
+	return l2_plen + rc;
+}
+
+static struct gsm48_si13_info si13_default = {
+	.cell_opts = {
+		.nmo 		= GPRS_NMO_II,
+		.t3168		= 2000,
+		.t3192		= 1500,
+		.drx_timer_max	= 3,
+		.bs_cv_max	= 15,
+		.ctrl_ack_type_use_block = true,
+		.ext_info_present = 0,
+		.supports_egprs_11bit_rach = 0,
+		.ext_info = {
+			/* The values below are just guesses ! */
+			.egprs_supported = 0,
+			.use_egprs_p_ch_req = 1,
+			.bep_period = 5,
+			.pfc_supported = 0,
+			.dtm_supported = 0,
+			.bss_paging_coordination = 0,
+		},
+	},
+	.pwr_ctrl_pars = {
+		.alpha		= 0,	/* a = 0.0 */
+		.t_avg_w	= 16,
+		.t_avg_t	= 16,
+		.pc_meas_chan	= 0, 	/* downling measured on CCCH */
+		.n_avg_i	= 8,
+	},
+	.bcch_change_mark	= 1,
+	.si_change_field	= 0,
+	.rac		= 0,	/* needs to be patched */
+	.spgc_ccch_sup 	= 0,
+	.net_ctrl_ord	= 0,
+	.prio_acc_thr	= 6,
+};
+
+static int generate_si13(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_13 *si13 =
+		(struct gsm48_system_information_type_13 *) GSM_BTS_SI(bts, t);
+	int ret;
+
+	memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si13->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si13->header.skip_indicator = 0;
+	si13->header.system_information = GSM48_MT_RR_SYSINFO_13;
+
+	si13_default.rac = bts->gprs.rac;
+	si13_default.net_ctrl_ord = bts->gprs.net_ctrl_ord;
+
+	si13_default.cell_opts.ctrl_ack_type_use_block =
+		bts->gprs.ctrl_ack_type_use_block;
+
+	/* Information about the other SIs */
+	si13_default.bcch_change_mark = bts->bcch_change_mark;
+	si13_default.cell_opts.supports_egprs_11bit_rach =
+					bts->gprs.supports_egprs_11bit_rach;
+
+	ret = rest_octets_si13(si13->rest_octets, &si13_default);
+	if (ret < 0)
+		return ret;
+
+	/* length is coded in bit 2 an up */
+	si13->header.l2_plen = 0x01;
+
+	return sizeof (*si13) + ret;
+}
+
+typedef int (*gen_si_fn_t)(enum osmo_sysinfo_type t, struct gsm_bts *bts);
+
+static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = {
+	[SYSINFO_TYPE_1] = &generate_si1,
+	[SYSINFO_TYPE_2] = &generate_si2,
+	[SYSINFO_TYPE_2bis] = &generate_si2bis,
+	[SYSINFO_TYPE_2ter] = &generate_si2ter,
+	[SYSINFO_TYPE_2quater] = &generate_si2quater,
+	[SYSINFO_TYPE_3] = &generate_si3,
+	[SYSINFO_TYPE_4] = &generate_si4,
+	[SYSINFO_TYPE_5] = &generate_si5,
+	[SYSINFO_TYPE_5bis] = &generate_si5bis,
+	[SYSINFO_TYPE_5ter] = &generate_si5ter,
+	[SYSINFO_TYPE_6] = &generate_si6,
+	[SYSINFO_TYPE_13] = &generate_si13,
+};
+
+int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type)
+{
+	gen_si_fn_t gen_si;
+
+	switch (bts->gprs.mode) {
+	case BTS_GPRS_EGPRS:
+		si13_default.cell_opts.ext_info_present = 1;
+		si13_default.cell_opts.ext_info.egprs_supported = 1;
+		/* fallthrough */
+	case BTS_GPRS_GPRS:
+		si_info.gprs_ind.present = 1;
+		break;
+	case BTS_GPRS_NONE:
+		si_info.gprs_ind.present = 0;
+		break;
+	}
+
+	memcpy(&si_info.selection_params,
+	       &bts->si_common.cell_ro_sel_par,
+	       sizeof(struct gsm48_si_selection_params));
+
+	gen_si = gen_si_fn[si_type];
+	if (!gen_si)
+		return -EINVAL;
+
+	return gen_si(si_type, bts);
+}
diff --git a/openbsc/src/libcommon-cs/Makefile.am b/openbsc/src/libcommon-cs/Makefile.am
new file mode 100644
index 0000000..f3921ba
--- /dev/null
+++ b/openbsc/src/libcommon-cs/Makefile.am
@@ -0,0 +1,20 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+noinst_LIBRARIES = libcommon-cs.a
+
+libcommon_cs_a_SOURCES = \
+	common_cs.c \
+	common_cs_vty.c
diff --git a/openbsc/src/libcommon-cs/common_cs.c b/openbsc/src/libcommon-cs/common_cs.c
new file mode 100644
index 0000000..d8d5ec7
--- /dev/null
+++ b/openbsc/src/libcommon-cs/common_cs.c
@@ -0,0 +1,139 @@
+/* Code used by both libbsc and libmsc (common_cs means "BSC or MSC").
+ *
+ * (C) 2016 by sysmocom s.m.f.c. <info@sysmocom.de>
+ * (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdbool.h>
+
+#include <osmocom/gsm/gsm0480.h>
+
+#include <openbsc/common_cs.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+
+/* Warning: if bsc_network_init() is not called, some of the members of
+ * gsm_network are not initialized properly and must not be used! (In
+ * particular the llist heads and stats counters.)
+ * The long term aim should be to have entirely separate structs for libbsc and
+ * libmsc with some common general items.
+ */
+struct gsm_network *gsm_network_init(void *ctx,
+				     uint16_t country_code,
+				     uint16_t network_code,
+				     mncc_recv_cb_t mncc_recv)
+{
+	struct gsm_network *net;
+
+	const char *default_regexp = ".*";
+
+	net = talloc_zero(ctx, struct gsm_network);
+	if (!net)
+		return NULL;
+
+	net->subscr_group = talloc_zero(net, struct gsm_subscriber_group);
+	if (!net->subscr_group) {
+		talloc_free(net);
+		return NULL;
+	}
+
+	if (gsm_parse_reg(net, &net->authorized_regexp, &net->authorized_reg_str, 1,
+			  &default_regexp) != 0)
+		return NULL;
+
+	net->subscr_group->net = net;
+	net->auto_create_subscr = true;
+	net->auto_assign_exten = true;
+
+	net->plmn = (struct osmo_plmn_id){
+		.mcc = country_code,
+		.mnc = network_code,
+	};
+
+	INIT_LLIST_HEAD(&net->trans_list);
+	INIT_LLIST_HEAD(&net->upqueue);
+	INIT_LLIST_HEAD(&net->subscr_conns);
+
+	net->bsc_subscribers = talloc_zero(net, struct llist_head);
+	INIT_LLIST_HEAD(net->bsc_subscribers);
+
+	/* init statistics */
+	net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
+	if (!net->msc_ctrs) {
+		talloc_free(net);
+		return NULL;
+	}
+	net->active_calls = osmo_counter_alloc("msc.active_calls");
+
+	net->mncc_recv = mncc_recv;
+	net->ext_min = GSM_MIN_EXTEN;
+	net->ext_max = GSM_MAX_EXTEN;
+
+	net->dyn_ts_allow_tch_f = true;
+
+	return net;
+}
+
+struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value)
+{
+	struct msgb *msg;
+	struct gsm48_hdr *gh;
+
+	msg = gsm48_msgb_alloc_name("GSM 04.08 SERV REJ");
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_CM_SERV_REJ;
+	gh->data[0] = value;
+
+	return msg;
+}
+
+struct msgb *gsm48_create_loc_upd_rej(uint8_t cause)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD REJ");
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT;
+	gh->data[0] = cause;
+	return msg;
+}
+
+uint8_t sms_next_rp_msg_ref(uint8_t *next_rp_ref)
+{
+	const uint8_t rp_msg_ref = *next_rp_ref;
+	/*
+	 * This should wrap as the valid range is 0 to 255. We only
+	 * transfer one SMS at a time so we don't need to check if
+	 * the id has been already assigned.
+	 */
+	*next_rp_ref += 1;
+
+	return rp_msg_ref;
+}
diff --git a/openbsc/src/libcommon-cs/common_cs_vty.c b/openbsc/src/libcommon-cs/common_cs_vty.c
new file mode 100644
index 0000000..c323b11
--- /dev/null
+++ b/openbsc/src/libcommon-cs/common_cs_vty.c
@@ -0,0 +1,338 @@
+/* Code used by both libbsc and libmsc (common_cs means "BSC or MSC").
+ *
+ * (C) 2016 by sysmocom s.m.f.c. <info@sysmocom.de>
+ * (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+
+#include <openbsc/vty.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+
+struct cmd_node net_node = {
+	GSMNET_NODE,
+	"%s(config-net)# ",
+	1,
+};
+
+#define NETWORK_STR "Configure the GSM network\n"
+#define CODE_CMD_STR "Code commands\n"
+#define NAME_CMD_STR "Name Commands\n"
+#define NAME_STR "Name to use\n"
+
+DEFUN(cfg_net,
+      cfg_net_cmd,
+      "network", NETWORK_STR)
+{
+	vty->index = gsmnet_from_vty(vty);
+	vty->node = GSMNET_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ncc,
+      cfg_net_ncc_cmd,
+      "network country code <1-999>",
+      "Set the GSM network country code\n"
+      "Country commands\n"
+      CODE_CMD_STR
+      "Network Country Code to use\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	uint16_t mcc;
+
+	if (osmo_mcc_from_str(argv[0], &mcc)) {
+		vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	gsmnet->plmn.mcc = mcc;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mnc,
+      cfg_net_mnc_cmd,
+      "mobile network code <0-999>",
+      "Set the GSM mobile network code\n"
+      "Network Commands\n"
+      CODE_CMD_STR
+      "Mobile Network Code to use\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	uint16_t mnc;
+	bool mnc_3_digits;
+
+	if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) {
+		vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	gsmnet->plmn.mnc = mnc;
+	gsmnet->plmn.mnc_3_digits = mnc_3_digits;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_short,
+      cfg_net_name_short_cmd,
+      "short name NAME",
+      "Set the short GSM network name\n" NAME_CMD_STR NAME_STR)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	osmo_talloc_replace_string(gsmnet, &gsmnet->name_short, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_long,
+      cfg_net_name_long_cmd,
+      "long name NAME",
+      "Set the long GSM network name\n" NAME_CMD_STR NAME_STR)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	osmo_talloc_replace_string(gsmnet, &gsmnet->name_long, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_auth_policy,
+      cfg_net_auth_policy_cmd,
+      "auth policy (closed|accept-all|regexp|token)",
+	"Authentication (not cryptographic)\n"
+	"Set the GSM network authentication policy\n"
+	"Require the MS to be activated in HLR\n"
+	"Accept all MS, whether in HLR or not\n"
+	"Use regular expression for IMSI authorization decision\n"
+	"Use SMS-token based authentication\n")
+{
+	enum gsm_auth_policy policy = gsm_auth_policy_parse(argv[0]);
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->auth_policy = policy;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_authorize_regexp, cfg_net_authorize_regexp_cmd,
+      "authorized-regexp REGEXP",
+      "Set regexp for IMSI which will be used for authorization decision\n"
+      "Regular expression, IMSIs matching it are allowed to use the network\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	if (gsm_parse_reg(gsmnet, &gsmnet->authorized_regexp,
+			  &gsmnet->authorized_reg_str, argc, argv) != 0) {
+		vty_out(vty, "%%Failed to parse the authorized-regexp: '%s'%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_reject_cause,
+      cfg_net_reject_cause_cmd,
+      "location updating reject cause <2-111>",
+      "Set the reject cause of location updating reject\n"
+      "Set the reject cause of location updating reject\n"
+      "Set the reject cause of location updating reject\n"
+      "Set the reject cause of location updating reject\n"
+      "Cause Value as Per GSM TS 04.08\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->reject_cause = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_encryption,
+      cfg_net_encryption_cmd,
+      "encryption a5 (0|1|2|3)",
+	"Encryption options\n"
+	"A5 encryption\n" "A5/0: No encryption\n"
+	"A5/1: Encryption\n" "A5/2: Export-grade Encryption\n"
+	"A5/3: 'New' Secure Encryption\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->a5_encryption = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd,
+      "rrlp mode (none|ms-based|ms-preferred|ass-preferred)",
+	"Radio Resource Location Protocol\n"
+	"Set the Radio Resource Location Protocol Mode\n"
+	"Don't send RRLP request\n"
+	"Request MS-based location\n"
+	"Request any location, prefer MS-based\n"
+	"Request any location, prefer MS-assisted\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->rrlp.mode = rrlp_mode_parse(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mm_info, cfg_net_mm_info_cmd,
+      "mm info (0|1)",
+	"Mobility Management\n"
+	"Send MM INFO after LOC UPD ACCEPT\n"
+	"Disable\n" "Enable\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->send_mm_info = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_dyn_ts_allow_tch_f,
+      cfg_net_dyn_ts_allow_tch_f_cmd,
+      "dyn_ts_allow_tch_f (0|1)",
+      "Allow or disallow allocating TCH/F on TCH_F_TCH_H_PDCH timeslots\n"
+      "Disallow TCH/F on TCH_F_TCH_H_PDCH (default)\n"
+      "Allow TCH/F on TCH_F_TCH_H_PDCH\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->dyn_ts_allow_tch_f = atoi(argv[0]) ? true : false;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_subscr_keep,
+      cfg_net_subscr_keep_cmd,
+      "subscriber-keep-in-ram (0|1)",
+      "Keep unused subscribers in RAM.\n"
+      "Delete unused subscribers\n" "Keep unused subscribers\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->subscr_group->keep_subscr = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_timezone,
+      cfg_net_timezone_cmd,
+      "timezone <-19-19> (0|15|30|45)",
+      "Set the Timezone Offset of the network\n"
+      "Timezone offset (hours)\n"
+      "Timezone offset (00 minutes)\n"
+      "Timezone offset (15 minutes)\n"
+      "Timezone offset (30 minutes)\n"
+      "Timezone offset (45 minutes)\n"
+      )
+{
+	struct gsm_network *net = vty->index;
+	int tzhr = atoi(argv[0]);
+	int tzmn = atoi(argv[1]);
+
+	net->tz.hr = tzhr;
+	net->tz.mn = tzmn;
+	net->tz.dst = 0;
+	net->tz.override = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_timezone_dst,
+      cfg_net_timezone_dst_cmd,
+      "timezone <-19-19> (0|15|30|45) <0-2>",
+      "Set the Timezone Offset of the network\n"
+      "Timezone offset (hours)\n"
+      "Timezone offset (00 minutes)\n"
+      "Timezone offset (15 minutes)\n"
+      "Timezone offset (30 minutes)\n"
+      "Timezone offset (45 minutes)\n"
+      "DST offset (hours)\n"
+      )
+{
+	struct gsm_network *net = vty->index;
+	int tzhr = atoi(argv[0]);
+	int tzmn = atoi(argv[1]);
+	int tzdst = atoi(argv[2]);
+
+	net->tz.hr = tzhr;
+	net->tz.mn = tzmn;
+	net->tz.dst = tzdst;
+	net->tz.override = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_no_timezone,
+      cfg_net_no_timezone_cmd,
+      "no timezone",
+      NO_STR
+      "Disable network timezone override, use system tz\n")
+{
+	struct gsm_network *net = vty->index;
+
+	net->tz.override = 0;
+
+	return CMD_SUCCESS;
+}
+
+static struct gsm_network *vty_global_gsm_network = NULL;
+
+/* initialize VTY elements used in both BSC and MSC */
+int common_cs_vty_init(struct gsm_network *network,
+                 int (* config_write_net )(struct vty *))
+{
+	OSMO_ASSERT(vty_global_gsm_network == NULL);
+	vty_global_gsm_network = network;
+
+	install_element(CONFIG_NODE, &cfg_net_cmd);
+	install_node(&net_node, config_write_net);
+	install_element(GSMNET_NODE, &cfg_net_ncc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mnc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_short_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_long_cmd);
+	install_element(GSMNET_NODE, &cfg_net_auth_policy_cmd);
+	install_element(GSMNET_NODE, &cfg_net_authorize_regexp_cmd);
+	install_element(GSMNET_NODE, &cfg_net_reject_cause_cmd);
+	install_element(GSMNET_NODE, &cfg_net_encryption_cmd);
+	install_element(GSMNET_NODE, &cfg_net_rrlp_mode_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mm_info_cmd);
+	install_element(GSMNET_NODE, &cfg_net_subscr_keep_cmd);
+	install_element(GSMNET_NODE, &cfg_net_timezone_cmd);
+	install_element(GSMNET_NODE, &cfg_net_timezone_dst_cmd);
+	install_element(GSMNET_NODE, &cfg_net_no_timezone_cmd);
+	install_element(GSMNET_NODE, &cfg_net_dyn_ts_allow_tch_f_cmd);
+
+	return CMD_SUCCESS;
+}
+
+struct gsm_network *gsmnet_from_vty(struct vty *v)
+{
+	/* It can't hurt to force callers to continue to pass the vty instance
+	 * to this function, in case we'd like to retrieve the global
+	 * gsm_network instance from the vty at some point in the future. But
+	 * until then, just return the global pointer, which should have been
+	 * initialized by common_cs_vty_init().
+	 */
+	OSMO_ASSERT(vty_global_gsm_network);
+	return vty_global_gsm_network;
+}
diff --git a/openbsc/src/libcommon/Makefile.am b/openbsc/src/libcommon/Makefile.am
new file mode 100644
index 0000000..0b258c0
--- /dev/null
+++ b/openbsc/src/libcommon/Makefile.am
@@ -0,0 +1,47 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libcommon.a \
+	$(NULL)
+
+libcommon_a_SOURCES = \
+	bsc_version.c \
+	common_vty.c \
+	debug.c \
+	gsm_data.c \
+	gsm_data_shared.c \
+	gsup_client.c \
+	oap_client.c \
+	socket.c \
+	talloc_ctx.c \
+	gsm_subscriber_base.c \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	gsup_test_client \
+	$(NULL)
+
+gsup_test_client_SOURCES = \
+	gsup_test_client.c \
+	$(NULL)
+gsup_test_client_LDADD = \
+	libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	-lrt \
+	$(NULL)
diff --git a/openbsc/src/libcommon/bsc_version.c b/openbsc/src/libcommon/bsc_version.c
new file mode 100644
index 0000000..f0369bf
--- /dev/null
+++ b/openbsc/src/libcommon/bsc_version.c
@@ -0,0 +1,30 @@
+/* Hold the copyright and version string */
+/* (C) 2010-2016 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../bscconfig.h"
+
+const char *openbsc_copyright =
+	"Copyright (C) 2008-2016 Harald Welte, Holger Freyther\r\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+	"Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+
diff --git a/openbsc/src/libcommon/common_vty.c b/openbsc/src/libcommon/common_vty.c
new file mode 100644
index 0000000..6e1c10b
--- /dev/null
+++ b/openbsc/src/libcommon/common_vty.c
@@ -0,0 +1,145 @@
+/* OpenBSC VTY common helpers */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/abis_om2000.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+
+int bsc_vty_go_parent(struct vty *vty)
+{
+	switch (vty->node) {
+	case GSMNET_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case BTS_NODE:
+		vty->node = GSMNET_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts *bts = vty->index;
+			vty->index = bts->network;
+			vty->index_sub = NULL;
+		}
+		break;
+	case TRX_NODE:
+		vty->node = BTS_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx *trx = vty->index;
+			vty->index = trx->bts;
+			vty->index_sub = &trx->bts->description;
+		}
+		break;
+	case TS_NODE:
+		vty->node = TRX_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx_ts *ts = vty->index;
+			vty->index = ts->trx;
+			vty->index_sub = &ts->trx->description;
+		}
+		break;
+	case OML_NODE:
+	case OM2K_NODE:
+		vty->node = ENABLE_NODE;
+		/* NOTE: this only works because it's not part of the config
+		 * tree, where outer commands are searched via vty_go_parent()
+		 * and only (!) executed when a matching one is found.
+		 */
+		talloc_free(vty->index);
+		vty->index = NULL;
+		break;
+	case OM2K_CON_GROUP_NODE:
+		vty->node = BTS_NODE;
+		{
+			struct con_group *cg = vty->index;
+			struct gsm_bts *bts = cg->bts;
+			vty->index = bts;
+			vty->index_sub = &bts->description;
+		}
+		break;
+	case NAT_BSC_NODE:
+		vty->node = NAT_NODE;
+		{
+			struct bsc_config *bsc_config = vty->index;
+			vty->index = bsc_config->nat;
+		}
+		break;
+	case PGROUP_NODE:
+		vty->node = NAT_NODE;
+		vty->index = NULL;
+		break;
+	case TRUNK_NODE:
+		vty->node = MGCP_NODE;
+		vty->index = NULL;
+		break;
+	case SMPP_ESME_NODE:
+		vty->node = SMPP_NODE;
+		vty->index = NULL;
+		break;
+	case SMPP_NODE:
+	case MGCP_NODE:
+	case GBPROXY_NODE:
+	case SGSN_NODE:
+	case NAT_NODE:
+	case BSC_NODE:
+	case MSC_NODE:
+	case MNCC_INT_NODE:
+	case NITB_NODE:
+	default:
+		if (bsc_vty_is_config_node(vty, vty->node))
+			vty->node = CONFIG_NODE;
+		else
+			vty->node = ENABLE_NODE;
+
+		vty->index = NULL;
+	}
+
+	return vty->node;
+}
+
+int bsc_vty_is_config_node(struct vty *vty, int node)
+{
+	switch (node) {
+	/* add items that are not config */
+	case OML_NODE:
+	case OM2K_NODE:
+	case SUBSCR_NODE:
+	case CONFIG_NODE:
+		return 0;
+
+	default:
+		return 1;
+	}
+}
diff --git a/openbsc/src/libcommon/debug.c b/openbsc/src/libcommon/debug.c
new file mode 100644
index 0000000..f29f168
--- /dev/null
+++ b/openbsc/src/libcommon/debug.c
@@ -0,0 +1,235 @@
+/* OpenBSC Debugging/Logging support code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+/* default categories */
+static const struct log_info_cat default_categories[] = {
+	[DRLL] = {
+		.name = "DRLL",
+		.description = "A-bis Radio Link Layer (RLL)",
+		.color = "\033[1;31m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DCC] = {
+		.name = "DCC",
+		.description = "Layer3 Call Control (CC)",
+		.color = "\033[1;32m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMM] = {
+		.name = "DMM",
+		.description = "Layer3 Mobility Management (MM)",
+		.color = "\033[1;33m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DRR] = {
+		.name = "DRR",
+		.description = "Layer3 Radio Resource (RR)",
+		.color = "\033[1;34m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DRSL] = {
+		.name = "DRSL",
+		.description = "A-bis Radio Siganlling Link (RSL)",
+		.color = "\033[1;35m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DNM] =	{
+		.name = "DNM",
+		.description = "A-bis Network Management / O&M (NM/OML)",
+		.color = "\033[1;36m",
+		.enabled = 1, .loglevel = LOGL_INFO,
+	},
+	[DMNCC] = {
+		.name = "DMNCC",
+		.description = "MNCC API for Call Control application",
+		.color = "\033[1;39m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DPAG]	= {
+		.name = "DPAG",
+		.description = "Paging Subsystem",
+		.color = "\033[1;38m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMEAS] = {
+		.name = "DMEAS",
+		.description = "Radio Measurement Processing",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DSCCP] = {
+		.name = "DSCCP",
+		.description = "SCCP Protocol",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMSC] = {
+		.name = "DMSC",
+		.description = "Mobile Switching Center",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMGCP] = {
+		.name = "DMGCP",
+		.description = "Media Gateway Control Protocol",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DHO] = {
+		.name = "DHO",
+		.description = "Hand-Over",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DDB] = {
+		.name = "DDB",
+		.description = "Database Layer",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DREF] = {
+		.name = "DREF",
+		.description = "Reference Counting",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DGPRS] = {
+		.name = "DGPRS",
+		.description = "GPRS Packet Service",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DNS] = {
+		.name = "DNS",
+		.description = "GPRS Network Service (NS)",
+		.enabled = 1, .loglevel = LOGL_INFO,
+	},
+	[DBSSGP] = {
+		.name = "DBSSGP",
+		.description = "GPRS BSS Gateway Protocol (BSSGP)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DLLC] = {
+		.name = "DLLC",
+		.description = "GPRS Logical Link Control Protocol (LLC)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DSNDCP] = {
+		.name = "DSNDCP",
+		.description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DNAT] = {
+		.name = "DNAT",
+		.description = "GSM 08.08 NAT/Multiplexer",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DCTRL] = {
+		.name = "DCTRL",
+		.description = "Control interface",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DSMPP] = {
+		.name = "DSMPP",
+		.description = "SMPP interface for external SMS apps",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DFILTER] = {
+		.name = "DFILTER",
+		.description = "BSC/NAT IMSI based filtering",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DRANAP] = {
+		.name = "DRANAP",
+		.description = "Radio Access Network Application Part Protocol",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DSUA] = {
+		.name = "DSUA",
+		.description = "SCCP User Adaptation Protocol",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DPCU] = {
+		.name = "DPCU",
+		.description = "PCU Interface",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+};
+
+static int filter_fn(const struct log_context *ctx, struct log_target *tar)
+{
+	const struct gsm_subscriber *subscr = ctx->ctx[LOG_CTX_VLR_SUBSCR];
+	const struct bsc_subscr *bsub = ctx->ctx[LOG_CTX_BSC_SUBSCR];
+	const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
+	const struct gprs_nsvc *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+
+	if ((tar->filter_map & (1 << LOG_FLT_VLR_SUBSCR)) != 0
+	    && subscr && subscr == tar->filter_data[LOG_FLT_VLR_SUBSCR])
+		return 1;
+
+	if ((tar->filter_map & (1 << LOG_FLT_BSC_SUBSCR)) != 0
+	    && bsub && bsub == tar->filter_data[LOG_FLT_BSC_SUBSCR])
+		return 1;
+
+	/* Filter on the NS Virtual Connection */
+	if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0
+	    && nsvc && (nsvc == tar->filter_data[LOG_FLT_GB_NSVC]))
+		return 1;
+
+	/* Filter on the NS Virtual Connection */
+	if ((tar->filter_map & (1 << LOG_FLT_GB_BVC)) != 0
+	    && bvc && (bvc == tar->filter_data[LOG_FLT_GB_BVC]))
+		return 1;
+
+	return 0;
+}
+
+const struct log_info log_info = {
+	.filter_fn = filter_fn,
+	.cat = default_categories,
+	.num_cat = ARRAY_SIZE(default_categories),
+};
+
+void log_set_filter_vlr_subscr(struct log_target *target,
+			       struct gsm_subscriber *vlr_subscr)
+{
+	struct gsm_subscriber **fsub = (void*)&target->filter_data[LOG_FLT_VLR_SUBSCR];
+
+	/* free the old data */
+	if (*fsub) {
+		subscr_put(*fsub);
+		*fsub = NULL;
+	}
+
+	if (vlr_subscr) {
+		target->filter_map |= (1 << LOG_FLT_VLR_SUBSCR);
+		*fsub = subscr_get(vlr_subscr);
+	} else
+		target->filter_map &= ~(1 << LOG_FLT_VLR_SUBSCR);
+}
diff --git a/openbsc/src/libcommon/gsm_data.c b/openbsc/src/libcommon/gsm_data.c
new file mode 100644
index 0000000..b45a4d3
--- /dev/null
+++ b/openbsc/src/libcommon/gsm_data.c
@@ -0,0 +1,477 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/abis_nm.h>
+
+void *tall_bsc_ctx;
+
+static LLIST_HEAD(bts_models);
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr,
+		   uint8_t e1_ts, uint8_t e1_ts_ss)
+{
+	ts->e1_link.e1_nr = e1_nr;
+	ts->e1_link.e1_ts = e1_ts;
+	ts->e1_link.e1_ts_ss = e1_ts_ss;
+}
+
+static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
+{
+	struct gsm_bts_model *model;
+
+	llist_for_each_entry(model, &bts_models, list) {
+		if (model->type == type)
+			return model;
+	}
+
+	return NULL;
+}
+
+int gsm_bts_model_register(struct gsm_bts_model *model)
+{
+	if (bts_model_find(model->type))
+		return -EEXIST;
+
+	tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef);
+	llist_add_tail(&model->list, &bts_models);
+	return 0;
+}
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+				 uint16_t arfcn, uint8_t bsic)
+{
+	struct gsm_bts *neigh;
+	/* FIXME: use some better heuristics here to determine which cell
+	 * using this ARFCN really is closest to the target cell.  For
+	 * now we simply assume that each ARFCN will only be used by one
+	 * cell */
+
+	llist_for_each_entry(neigh, &bts->network->bts_list, list) {
+		if (neigh->c0->arfcn == arfcn &&
+		    neigh->bsic == bsic)
+			return neigh;
+	}
+
+	return NULL;
+}
+
+const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = {
+	{ GSM_BTS_TYPE_UNKNOWN,		"Unknown BTS Type" },
+	{ GSM_BTS_TYPE_BS11,		"Siemens BTS (BS-11 or compatible)" },
+	{ GSM_BTS_TYPE_NANOBTS,		"ip.access nanoBTS or compatible" },
+	{ GSM_BTS_TYPE_RBS2000,		"Ericsson RBS2000 Series" },
+	{ GSM_BTS_TYPE_NOKIA_SITE,	"Nokia {Metro,Ultra,In}Site" },
+	{ GSM_BTS_TYPE_OSMOBTS,		"sysmocom sysmoBTS" },
+	{ 0,				NULL }
+};
+
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->nr == nr)
+			return trx;
+	}
+	return NULL;
+}
+
+/* Search for a BTS in the given Location Area; optionally start searching
+ * with start_bts (for continuing to search after the first result) */
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+				struct gsm_bts *start_bts)
+{
+	int i;
+	struct gsm_bts *bts;
+	int skip = 0;
+
+	if (start_bts)
+		skip = 1;
+
+	for (i = 0; i < net->num_bts; i++) {
+		bts = gsm_bts_num(net, i);
+
+		if (skip) {
+			if (start_bts == bts)
+				skip = 0;
+			continue;
+		}
+
+		if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac)
+			return bts;
+	}
+	return NULL;
+}
+
+static const struct value_string auth_policy_names[] = {
+	{ GSM_AUTH_POLICY_CLOSED,	"closed" },
+	{ GSM_AUTH_POLICY_ACCEPT_ALL,	"accept-all" },
+	{ GSM_AUTH_POLICY_TOKEN,	"token" },
+	{ GSM_AUTH_POLICY_REGEXP,	"regexp" },
+	{ 0,				NULL }
+};
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg)
+{
+	return get_string_value(auth_policy_names, arg);
+}
+
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy)
+{
+	return get_value_string(auth_policy_names, policy);
+}
+
+static const struct value_string rrlp_mode_names[] = {
+	{ RRLP_MODE_NONE,	"none" },
+	{ RRLP_MODE_MS_BASED,	"ms-based" },
+	{ RRLP_MODE_MS_PREF,	"ms-preferred" },
+	{ RRLP_MODE_ASS_PREF,	"ass-preferred" },
+	{ 0,			NULL }
+};
+
+enum rrlp_mode rrlp_mode_parse(const char *arg)
+{
+	return get_string_value(rrlp_mode_names, arg);
+}
+
+const char *rrlp_mode_name(enum rrlp_mode mode)
+{
+	return get_value_string(rrlp_mode_names, mode);
+}
+
+static const struct value_string bts_gprs_mode_names[] = {
+	{ BTS_GPRS_NONE,	"none" },
+	{ BTS_GPRS_GPRS,	"gprs" },
+	{ BTS_GPRS_EGPRS,	"egprs" },
+	{ 0,			NULL }
+};
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid)
+{
+	int rc;
+
+	rc = get_string_value(bts_gprs_mode_names, arg);
+	if (valid)
+		*valid = rc != -EINVAL;
+	return rc;
+}
+
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode)
+{
+	return get_value_string(bts_gprs_mode_names, mode);
+}
+
+int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode)
+{
+	if (mode != BTS_GPRS_NONE &&
+	    !gsm_btsmodel_has_feature(bts->model, BTS_FEAT_GPRS)) {
+		return 0;
+	}
+	if (mode == BTS_GPRS_EGPRS &&
+	    !gsm_btsmodel_has_feature(bts->model, BTS_FEAT_EGPRS)) {
+		return 0;
+	}
+
+	return 1;
+}
+
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan)
+{
+	struct gsm_meas_rep *meas_rep;
+
+	meas_rep = &lchan->meas_rep[lchan->meas_rep_idx];
+	memset(meas_rep, 0, sizeof(*meas_rep));
+	meas_rep->lchan = lchan;
+	lchan->meas_rep_idx = (lchan->meas_rep_idx + 1)
+					% ARRAY_SIZE(lchan->meas_rep);
+
+	return meas_rep;
+}
+
+int gsm_btsmodel_set_feature(struct gsm_bts_model *model, enum gsm_bts_features feat)
+{
+	OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+	return bitvec_set_bit_pos(&model->features, feat, 1);
+}
+
+bool gsm_btsmodel_has_feature(struct gsm_bts_model *model, enum gsm_bts_features feat)
+{
+	OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+	return bitvec_get_bit_pos(&model->features, feat);
+}
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
+{
+	struct gsm_bts_model *model;
+
+	model = bts_model_find(type);
+	if (!model)
+		return -EINVAL;
+
+	bts->type = type;
+	bts->model = model;
+
+	if (model->start && !model->started) {
+		int ret = model->start(bts->network);
+		if (ret < 0)
+			return ret;
+
+		model->started = true;
+	}
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		/* Set the default OML Stream ID to 0xff */
+		bts->oml_tei = 0xff;
+		bts->c0->nominal_power = 23;
+		break;
+	case GSM_BTS_TYPE_RBS2000:
+		INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups);
+		INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups);
+		break;
+	case GSM_BTS_TYPE_BS11:
+	case GSM_BTS_TYPE_UNKNOWN:
+	case GSM_BTS_TYPE_NOKIA_SITE:
+		/* Set default BTS reset timer */
+		bts->nokia.bts_reset_timer_cnf = 15;
+	case _NUM_GSM_BTS_TYPE:
+		break;
+	}
+
+	return 0;
+}
+
+struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type,
+					uint8_t bsic)
+{
+	struct gsm_bts_model *model = bts_model_find(type);
+	struct gsm_bts *bts;
+
+	if (!model && type != GSM_BTS_TYPE_UNKNOWN)
+		return NULL;
+
+	bts = gsm_bts_alloc(net, net->num_bts);
+	if (!bts)
+		return NULL;
+
+	net->num_bts++;
+
+	bts->network = net;
+	bts->type = type;
+	bts->model = model;
+	bts->bsic = bsic;
+	bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
+	bts->dtxd = false;
+	bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */
+	bts->neigh_list_manual_mode = 0;
+	bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */
+	bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
+	bts->si_common.cell_sel_par.rxlev_acc_min = 0;
+	bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
+	bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
+	bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+	bts->si_common.si2quater_neigh_list.thresh_hi = 0;
+	osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+	bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
+	bts->si_common.neigh_list.data_len =
+				sizeof(bts->si_common.data.neigh_list);
+	bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list;
+	bts->si_common.si5_neigh_list.data_len =
+				sizeof(bts->si_common.data.si5_neigh_list);
+	bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc;
+	bts->si_common.cell_alloc.data_len =
+				sizeof(bts->si_common.data.cell_alloc);
+	bts->si_common.rach_control.re = 1; /* no re-establishment */
+	bts->si_common.rach_control.tx_integer = 9;  /* 12 slots spread - 217/115 slots delay */
+	bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */
+	bts->si_common.rach_control.t2 = 4; /* no emergency calls */
+	bts->si_common.chan_desc.att = 1; /* attachment required */
+	bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; /* paging frames */
+	bts->si_common.chan_desc.bs_ag_blks_res = 1; /* reserved AGCH blocks */
+	bts->si_common.chan_desc.t3212 = 5; /* Use 30 min periodic update interval as sane default */
+	gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */
+
+	llist_add_tail(&bts->list, &net->bts_list);
+
+	INIT_LLIST_HEAD(&bts->abis_queue);
+
+	INIT_LLIST_HEAD(&bts->loc_list);
+
+	return bts;
+}
+
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts)
+{
+	*raid = (struct gprs_ra_id){
+		.mcc = bts->network->plmn.mcc,
+		.mnc = bts->network->plmn.mnc,
+		.mnc_3_digits = bts->network->plmn.mnc_3_digits,
+		.lac = bts->location_area_code,
+		.rac = bts->gprs.rac,
+	};
+}
+
+void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts)
+{
+	struct gprs_ra_id raid;
+
+	gprs_ra_id_by_bts(&raid, bts);
+	gsm48_encode_ra(buf, &raid);
+}
+
+int gsm_parse_reg(void *ctx, regex_t *reg, char **str, int argc, const char **argv)
+{
+	int ret;
+
+	ret = 0;
+	if (*str) {
+		talloc_free(*str);
+		*str = NULL;
+	}
+	regfree(reg);
+
+	if (argc > 0) {
+		*str = talloc_strdup(ctx, argv[0]);
+		ret = regcomp(reg, argv[0], 0);
+
+		/* handle compilation failures */
+		if (ret != 0) {
+			talloc_free(*str);
+			*str = NULL;
+		}
+	}
+
+	return ret;
+}
+
+/* Assume there are only 256 possible bts */
+osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256);
+static void depends_calc_index_bit(int bts_nr, int *idx, int *bit)
+{
+	*idx = bts_nr / (8 * 4);
+	*bit = bts_nr % (8 * 4);
+}
+
+void bts_depend_mark(struct gsm_bts *bts, int dep)
+{
+	int idx, bit;
+	depends_calc_index_bit(dep, &idx, &bit);
+
+	bts->depends_on[idx] |= 1 << bit;
+}
+
+void bts_depend_clear(struct gsm_bts *bts, int dep)
+{
+	int idx, bit;
+	depends_calc_index_bit(dep, &idx, &bit);
+
+	bts->depends_on[idx] &= ~(1 << bit);
+}
+
+int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other)
+{
+	int idx, bit;
+	depends_calc_index_bit(other->nr, &idx, &bit);
+
+	/* Check if there is a depends bit */
+	return (base->depends_on[idx] & (1 << bit)) > 0;
+}
+
+static int bts_is_online(struct gsm_bts *bts)
+{
+	/* TODO: support E1 BTS too */
+	if (!is_ipaccess_bts(bts))
+		return 1;
+
+	if (!bts->oml_link)
+		return 0;
+
+	return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED;
+}
+
+int bts_depend_check(struct gsm_bts *bts)
+{
+	struct gsm_bts *other_bts;
+
+	llist_for_each_entry(other_bts, &bts->network->bts_list, list) {
+		if (!bts_depend_is_depedency(bts, other_bts))
+			continue;
+		if (bts_is_online(other_bts))
+			continue;
+		return 0;
+	}
+	return 1;
+}
+
+/* get the radio link timeout (based on SACCH decode errors, according
+ * to algorithm specified in TS 05.08 section 5.2.  A value of -1
+ * indicates we should use an infinitely long timeout, which only works
+ * with OsmoBTS as the BTS implementation */
+int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts)
+{
+	const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
+
+	if (bts->infinite_radio_link_timeout)
+		return -1;
+	else {
+		/* Encoding as per Table 10.5.21 of TS 04.08 */
+		return (cell_options->radio_link_timeout + 1) << 2;
+	}
+}
+
+/* set the radio link timeout (based on SACCH decode errors, according
+ * to algorithm specified in TS 05.08 Section 5.2.  A value of -1
+ * indicates we should use an infinitely long timeout, which only works
+ * with OsmoBTS as the BTS implementation */
+void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value)
+{
+	struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
+
+	if (value < 0)
+		bts->infinite_radio_link_timeout = true;
+	else {
+		bts->infinite_radio_link_timeout = false;
+		/* Encoding as per Table 10.5.21 of TS 04.08 */
+		if (value < 4)
+			value = 4;
+		if (value > 64)
+			value = 64;
+		cell_options->radio_link_timeout = (value >> 2) - 1;
+	}
+}
diff --git a/openbsc/src/libcommon/gsm_data_shared.c b/openbsc/src/libcommon/gsm_data_shared.c
new file mode 100644
index 0000000..75fe0b0
--- /dev/null
+++ b/openbsc/src/libcommon/gsm_data_shared.c
@@ -0,0 +1,875 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/counter.h>
+#include <osmocom/core/stats.h>
+
+#include <openbsc/gsm_data.h>
+
+static const struct osmo_stat_item_desc bts_stat_desc[] = {
+	{ "chanloadavg", "Channel load average.", "%", 16, 0 },
+	{ "T3122", "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator.", "s", 16, GSM_T3122_DEFAULT },
+};
+
+static const struct osmo_stat_item_group_desc bts_statg_desc = {
+	.group_name_prefix = "bts",
+	.group_description = "base transceiver station",
+	.class_id = OSMO_STATS_CLASS_GLOBAL,
+	.num_items = ARRAY_SIZE(bts_stat_desc),
+	.item_desc = bts_stat_desc,
+};
+
+void gsm_abis_mo_reset(struct gsm_abis_mo *mo)
+{
+	mo->nm_state.operational = NM_OPSTATE_NULL;
+	mo->nm_state.availability = NM_AVSTATE_POWER_OFF;
+}
+
+static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
+			uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3)
+{
+	mo->bts = bts;
+	mo->obj_class = obj_class;
+	mo->obj_inst.bts_nr = p1;
+	mo->obj_inst.trx_nr = p2;
+	mo->obj_inst.ts_nr = p3;
+	gsm_abis_mo_reset(mo);
+}
+
+const struct value_string bts_attribute_names[] = {
+	OSMO_VALUE_STRING(BTS_TYPE_VARIANT),
+	OSMO_VALUE_STRING(BTS_SUB_MODEL),
+	OSMO_VALUE_STRING(TRX_PHY_VERSION),
+	{ 0, NULL }
+};
+
+enum bts_attribute str2btsattr(const char *s)
+{
+	return get_string_value(bts_attribute_names, s);
+}
+
+const char *btsatttr2str(enum bts_attribute v)
+{
+	return get_value_string(bts_attribute_names, v);
+}
+
+const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
+	{ BTS_UNKNOWN,		"unknown" },
+	{ BTS_OSMO_LITECELL15,	"osmo-bts-lc15" },
+	{ BTS_OSMO_OCTPHY,	"osmo-bts-octphy" },
+	{ BTS_OSMO_SYSMO,	"osmo-bts-sysmo" },
+	{ BTS_OSMO_TRX,		"omso-bts-trx" },
+	{ 0, NULL }
+};
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg)
+{
+	return get_string_value(osmo_bts_variant_names, arg);
+}
+
+const char *btsvariant2str(enum gsm_bts_type_variant v)
+{
+	return get_value_string(osmo_bts_variant_names, v);
+}
+
+const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = {
+	{ GSM_BTS_TYPE_UNKNOWN,		"unknown" },
+	{ GSM_BTS_TYPE_BS11,		"bs11" },
+	{ GSM_BTS_TYPE_NANOBTS,		"nanobts" },
+	{ GSM_BTS_TYPE_RBS2000,		"rbs2000" },
+	{ GSM_BTS_TYPE_NOKIA_SITE,	"nokia_site" },
+	{ GSM_BTS_TYPE_OSMOBTS,		"sysmobts" },
+	{ 0, NULL }
+};
+
+enum gsm_bts_type str2btstype(const char *arg)
+{
+	return get_string_value(bts_type_names, arg);
+}
+
+const char *btstype2str(enum gsm_bts_type type)
+{
+	return get_value_string(bts_type_names, type);
+}
+
+const struct value_string gsm_bts_features_descs[] = {
+	{ BTS_FEAT_HSCSD,		"HSCSD" },
+	{ BTS_FEAT_GPRS,		"GPRS" },
+	{ BTS_FEAT_EGPRS,		"EGPRS" },
+	{ BTS_FEAT_ECSD,		"ECSD" },
+	{ BTS_FEAT_HOPPING,		"Frequency Hopping" },
+	{ BTS_FEAT_MULTI_TSC,		"Multi-TSC" },
+	{ BTS_FEAT_OML_ALERTS,		"OML Alerts" },
+	{ BTS_FEAT_AGCH_PCH_PROP,	"AGCH/PCH proportional allocation" },
+	{ BTS_FEAT_CBCH,		"CBCH" },
+	{ 0, NULL }
+};
+
+const struct value_string gsm_chreq_descs[] = {
+	{ GSM_CHREQ_REASON_EMERG,	"emergency call" },
+	{ GSM_CHREQ_REASON_PAG,		"answer to paging" },
+	{ GSM_CHREQ_REASON_CALL,	"call re-establishment" },
+	{ GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" },
+	{ GSM_CHREQ_REASON_PDCH,	"one phase packet access" },
+	{ GSM_CHREQ_REASON_OTHER,	"other" },
+	{ 0,				NULL }
+};
+
+const struct value_string gsm_pchant_names[13] = {
+	{ GSM_PCHAN_NONE,	"NONE" },
+	{ GSM_PCHAN_CCCH,	"CCCH" },
+	{ GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" },
+	{ GSM_PCHAN_TCH_F,	"TCH/F" },
+	{ GSM_PCHAN_TCH_H,	"TCH/H" },
+	{ GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+	{ GSM_PCHAN_PDCH,	"PDCH" },
+	{ GSM_PCHAN_TCH_F_PDCH,	"TCH/F_PDCH" },
+	{ GSM_PCHAN_UNKNOWN,	"UNKNOWN" },
+	{ GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" },
+	{ GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" },
+	{ GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" },
+	{ 0,			NULL }
+};
+
+const struct value_string gsm_pchant_descs[13] = {
+	{ GSM_PCHAN_NONE,	"Physical Channel not configured" },
+	{ GSM_PCHAN_CCCH,	"FCCH + SCH + BCCH + CCCH (Comb. IV)" },
+	{ GSM_PCHAN_CCCH_SDCCH4,
+		"FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" },
+	{ GSM_PCHAN_TCH_F,	"TCH/F + FACCH/F + SACCH (Comb. I)" },
+	{ GSM_PCHAN_TCH_H,	"2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" },
+	{ GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" },
+	{ GSM_PCHAN_PDCH,	"Packet Data Channel for GPRS/EDGE" },
+	{ GSM_PCHAN_TCH_F_PDCH,	"Dynamic TCH/F or GPRS PDCH" },
+	{ GSM_PCHAN_UNKNOWN,	"Unknown / Unsupported channel combination" },
+	{ GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" },
+	{ GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" },
+	{ GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" },
+	{ 0,			NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+	return get_value_string(gsm_pchant_names, c);
+}
+
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
+{
+	return get_string_value(gsm_pchant_names, name);
+}
+
+/* TODO: move to libosmocore, next to gsm_chan_t_names? */
+const char *gsm_lchant_name(enum gsm_chan_t c)
+{
+	return get_value_string(gsm_chan_t_names, c);
+}
+
+static const struct value_string lchan_s_names[] = {
+	{ LCHAN_S_NONE,		"NONE" },
+	{ LCHAN_S_ACT_REQ,	"ACTIVATION REQUESTED" },
+	{ LCHAN_S_ACTIVE,	"ACTIVE" },
+	{ LCHAN_S_INACTIVE,	"INACTIVE" },
+	{ LCHAN_S_REL_REQ,	"RELEASE REQUESTED" },
+	{ LCHAN_S_REL_ERR,	"RELEASE DUE ERROR" },
+	{ LCHAN_S_BROKEN,	"BROKEN UNUSABLE" },
+	{ 0,			NULL }
+};
+
+const char *gsm_lchans_name(enum gsm_lchan_state s)
+{
+	return get_value_string(lchan_s_names, s);
+}
+
+static const struct value_string chreq_names[] = {
+	{ GSM_CHREQ_REASON_EMERG,	"EMERGENCY" },
+	{ GSM_CHREQ_REASON_PAG,		"PAGING" },
+	{ GSM_CHREQ_REASON_CALL,	"CALL" },
+	{ GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" },
+	{ GSM_CHREQ_REASON_OTHER,	"OTHER" },
+	{ 0,				NULL }
+};
+
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c)
+{
+	return get_value_string(chreq_names, c);
+}
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num)
+{
+	struct gsm_bts *bts;
+
+	if (num >= net->num_bts)
+		return NULL;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (bts->nr == num)
+			return bts;
+	}
+
+	return NULL;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
+	int k;
+
+	if (!trx)
+		return NULL;
+
+	trx->bts = bts;
+	trx->nr = bts->num_trx++;
+	trx->mo.nm_state.administrative = NM_STATE_UNLOCKED;
+
+	gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER,
+		    bts->nr, trx->nr, 0xff);
+	gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC,
+		    bts->nr, trx->nr, 0xff);
+
+	for (k = 0; k < TRX_NR_TS; k++) {
+		struct gsm_bts_trx_ts *ts = &trx->ts[k];
+		int l;
+
+		ts->trx = trx;
+		ts->nr = k;
+		ts->pchan = GSM_PCHAN_NONE;
+		ts->dyn.pchan_is = GSM_PCHAN_NONE;
+		ts->dyn.pchan_want = GSM_PCHAN_NONE;
+		ts->tsc = -1;
+
+		gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
+			    bts->nr, trx->nr, ts->nr);
+
+		ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
+		ts->hopping.arfcns.data = ts->hopping.arfcns_data;
+		ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
+		ts->hopping.ma.data = ts->hopping.ma_data;
+
+		for (l = 0; l < TS_MAX_LCHAN; l++) {
+			struct gsm_lchan *lchan;
+			char *name;
+			lchan = &ts->lchan[l];
+
+			lchan->ts = ts;
+			lchan->nr = l;
+			lchan->type = GSM_LCHAN_NONE;
+
+			name = gsm_lchan_name_compute(lchan);
+			lchan->name = talloc_strdup(trx, name);
+#ifndef ROLE_BSC
+			INIT_LLIST_HEAD(&lchan->sapi_cmds);
+#endif
+		}
+	}
+
+	if (trx->nr != 0)
+		trx->nominal_power = bts->c0->nominal_power;
+
+	llist_add_tail(&trx->list, &bts->trx_list);
+
+	return trx;
+}
+
+
+static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+static const uint8_t bts_cell_timer_default[] =
+				{ 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 };
+static const struct gprs_rlc_cfg rlc_cfg_default = {
+	.parameter = {
+		[RLC_T3142] = 20,
+		[RLC_T3169] = 5,
+		[RLC_T3191] = 5,
+		[RLC_T3193] = 160, /* 10ms */
+		[RLC_T3195] = 5,
+		[RLC_N3101] = 10,
+		[RLC_N3103] = 4,
+		[RLC_N3105] = 8,
+		[CV_COUNTDOWN] = 15,
+		[T_DL_TBF_EXT] = 250 * 10, /* ms */
+		[T_UL_TBF_EXT] = 250 * 10, /* ms */
+	},
+	.paging = {
+		.repeat_time = 5 * 50, /* ms */
+		.repeat_count = 3,
+	},
+	.cs_mask = 0x1fff,
+	.initial_cs = 2,
+	.initial_mcs = 6,
+};
+
+struct gsm_bts *gsm_bts_alloc(void *ctx, uint8_t bts_num)
+{
+	struct gsm_bts *bts = talloc_zero(ctx, struct gsm_bts);
+	int i;
+
+	if (!bts)
+		return NULL;
+
+	bts->nr = bts_num;
+	bts->num_trx = 0;
+	INIT_LLIST_HEAD(&bts->trx_list);
+	bts->ms_max_power = 15;	/* dBm */
+
+	gsm_mo_init(&bts->mo, bts, NM_OC_BTS,
+			bts->nr, 0xff, 0xff);
+	gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+		bts->gprs.nsvc[i].bts = bts;
+		bts->gprs.nsvc[i].id = i;
+		gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC,
+				bts->nr, i, 0xff);
+	}
+	memcpy(&bts->gprs.nse.timer, bts_nse_timer_default,
+		sizeof(bts->gprs.nse.timer));
+	gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE,
+			bts->nr, 0xff, 0xff);
+	memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
+		sizeof(bts->gprs.cell.timer));
+	gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL,
+			bts->nr, 0xff, 0xff);
+	memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default,
+		sizeof(bts->gprs.cell.rlc_cfg));
+
+	bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, 0);
+
+	/* create our primary TRX */
+	bts->c0 = gsm_bts_trx_alloc(bts);
+	if (!bts->c0) {
+		osmo_stat_item_group_free(bts->bts_statg);
+		talloc_free(bts);
+		return NULL;
+	}
+	bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4;
+
+	bts->rach_b_thresh = -1;
+	bts->rach_ldavg_slots = -1;
+	bts->paging.free_chans_need = -1;
+	bts->features.data = &bts->_features_data[0];
+	bts->features.data_len = sizeof(bts->_features_data);
+
+	/* si handling */
+	bts->bcch_change_mark = 1;
+
+	bts->chan_load_avg = 0;
+
+	/* timer overrides */
+	bts->T3122 = 0; /* not overriden by default */
+
+	return bts;
+}
+
+/* reset the state of all MO in the BTS */
+void gsm_bts_mo_reset(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	unsigned int i;
+
+	gsm_abis_mo_reset(&bts->mo);
+	gsm_abis_mo_reset(&bts->site_mgr.mo);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++)
+		gsm_abis_mo_reset(&bts->gprs.nsvc[i].mo);
+	gsm_abis_mo_reset(&bts->gprs.nse.mo);
+	gsm_abis_mo_reset(&bts->gprs.cell.mo);
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		gsm_abis_mo_reset(&trx->mo);
+		gsm_abis_mo_reset(&trx->bb_transc.mo);
+
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+			gsm_abis_mo_reset(&ts->mo);
+		}
+	}
+}
+
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num)
+{
+	struct gsm_bts_trx *trx;
+
+	if (num >= bts->num_trx)
+		return NULL;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->nr == num)
+			return trx;
+	}
+
+	return NULL;
+}
+
+static char ts2str[255];
+
+char *gsm_trx_name(const struct gsm_bts_trx *trx)
+{
+	if (!trx)
+		snprintf(ts2str, sizeof(ts2str), "(trx=NULL)");
+	else
+		snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
+			 trx->bts->nr, trx->nr);
+
+	return ts2str;
+}
+
+
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts)
+{
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)",
+		 ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	return ts2str;
+}
+
+/*! Log timeslot number with full pchan information */
+char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts)
+{
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+			snprintf(ts2str, sizeof(ts2str),
+				 "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)",
+				 ts->trx->bts->nr, ts->trx->nr, ts->nr,
+				 gsm_pchan_name(ts->pchan),
+				 gsm_pchan_name(ts->dyn.pchan_is));
+		else
+			snprintf(ts2str, sizeof(ts2str),
+				 "(bts=%d,trx=%d,ts=%d,pchan=%s"
+				 " switching %s -> %s)",
+				 ts->trx->bts->nr, ts->trx->nr, ts->nr,
+				 gsm_pchan_name(ts->pchan),
+				 gsm_pchan_name(ts->dyn.pchan_is),
+				 gsm_pchan_name(ts->dyn.pchan_want));
+		break;
+	case GSM_PCHAN_TCH_F_PDCH:
+		if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+			snprintf(ts2str, sizeof(ts2str),
+				 "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)",
+				 ts->trx->bts->nr, ts->trx->nr, ts->nr,
+				 gsm_pchan_name(ts->pchan),
+				 (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+							       : "TCH/F");
+		else
+			snprintf(ts2str, sizeof(ts2str),
+				 "(bts=%d,trx=%d,ts=%d,pchan=%s"
+				 " switching %s -> %s)",
+				 ts->trx->bts->nr, ts->trx->nr, ts->nr,
+				 gsm_pchan_name(ts->pchan),
+				 (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+							       : "TCH/F",
+				 (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+								    : "TCH/F");
+		break;
+	default:
+		snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)",
+			 ts->trx->bts->nr, ts->trx->nr, ts->nr,
+			 gsm_pchan_name(ts->pchan));
+		break;
+	}
+
+	return ts2str;
+}
+
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan)
+{
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
+		 ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
+
+	return ts2str;
+}
+
+/* obtain the MO structure for a given object instance */
+struct gsm_abis_mo *
+gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+	    const struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_abis_mo *mo = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		mo = &bts->mo;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		mo = &trx->mo;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		mo = &trx->bb_transc.mo;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		mo = &trx->ts[obj_inst->ts_nr].mo;
+		break;
+	case NM_OC_SITE_MANAGER:
+		mo = &bts->site_mgr.mo;
+		break;
+	case NM_OC_BS11:
+		switch (obj_inst->bts_nr) {
+		case BS11_OBJ_CCLK:
+			mo = &bts->bs11.cclk.mo;
+			break;
+		case BS11_OBJ_BBSIG:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			mo = &trx->bs11.bbsig.mo;
+			break;
+		case BS11_OBJ_PA:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			mo = &trx->bs11.pa.mo;
+			break;
+		default:
+			return NULL;
+		}
+		break;
+	case NM_OC_BS11_RACK:
+		mo = &bts->bs11.rack.mo;
+		break;
+	case NM_OC_BS11_ENVABTSE:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+			return NULL;
+		mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo;
+		break;
+	case NM_OC_GPRS_NSE:
+		mo = &bts->gprs.nse.mo;
+		break;
+	case NM_OC_GPRS_CELL:
+		mo = &bts->gprs.cell.mo;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo;
+		break;
+	}
+	return mo;
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+		 const struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_abis_mo *mo;
+
+	mo = gsm_objclass2mo(bts, obj_class, obj_inst);
+	if (!mo)
+		return NULL;
+
+	return &mo->nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+	     const struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	void *obj = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		obj = bts;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = trx;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = &trx->bb_transc;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		obj = &trx->ts[obj_inst->ts_nr];
+		break;
+	case NM_OC_SITE_MANAGER:
+		obj = &bts->site_mgr;
+		break;
+	case NM_OC_GPRS_NSE:
+		obj = &bts->gprs.nse;
+		break;
+	case NM_OC_GPRS_CELL:
+		obj = &bts->gprs.cell;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+		break;
+	}
+	return obj;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+			  uint8_t ts_nr, uint8_t lchan_nr)
+{
+	uint8_t cbits, chan_nr;
+
+	switch (pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_F_PDCH:
+		OSMO_ASSERT(lchan_nr == 0);
+		cbits = 0x01;
+		break;
+	case GSM_PCHAN_PDCH:
+		OSMO_ASSERT(lchan_nr == 0);
+		cbits = RSL_CHAN_OSMO_PDCH >> 3;
+		break;
+	case GSM_PCHAN_TCH_H:
+		OSMO_ASSERT(lchan_nr < 2);
+		cbits = 0x02;
+		cbits += lchan_nr;
+		break;
+	case GSM_PCHAN_CCCH_SDCCH4:
+	case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+		/*
+		 * As a special hack for BCCH, lchan_nr == 4 may be passed
+		 * here. This should never be sent in an RSL message.
+		 * See osmo-bts-xxx/oml.c:opstart_compl().
+		 */
+		if (lchan_nr == CCCH_LCHAN)
+			chan_nr = 0;
+		else
+			OSMO_ASSERT(lchan_nr < 4);
+		cbits = 0x04;
+		cbits += lchan_nr;
+		break;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+	case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+		OSMO_ASSERT(lchan_nr < 8);
+		cbits = 0x08;
+		cbits += lchan_nr;
+		break;
+	default:
+	case GSM_PCHAN_CCCH:
+#ifdef ROLE_BSC
+		OSMO_ASSERT(lchan_nr == 0);
+#else
+		/*
+		 * FIXME: On octphy and litecell, we hit above assertion (see
+		 * Max's comment at https://gerrit.osmocom.org/589 ); disabled
+		 * for BTS until this is clarified; remove the #ifdef when it
+		 * is fixed.
+		 */
+#warning "fix caller that passes lchan_nr != 0"
+#endif
+		cbits = 0x10;
+		break;
+	}
+
+	chan_nr = (cbits << 3) | (ts_nr & 0x7);
+
+	return chan_nr;
+}
+
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+	enum gsm_phys_chan_config pchan = lchan->ts->pchan;
+	if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+		return gsm_lchan_as_pchan2chan_nr(lchan,
+						  lchan->ts->dyn.pchan_is);
+	return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr);
+}
+
+uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan,
+				   enum gsm_phys_chan_config as_pchan)
+{
+	if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+	    && as_pchan == GSM_PCHAN_PDCH)
+		return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK);
+	return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr);
+}
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts)
+{
+	struct gsm_lchan *lchan = NULL;
+	struct gsm_bts_trx *trx = bts->c0;
+
+	if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+		lchan = &trx->ts[0].lchan[2];
+	else {
+		int i;
+		for (i = 0; i < 8; i++) {
+			if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) {
+				lchan = &trx->ts[i].lchan[2];
+				break;
+			}
+		}
+	}
+
+	return lchan;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+				   int *rc)
+{
+	uint8_t ts_nr = chan_nr & 0x07;
+	uint8_t cbits = chan_nr >> 3;
+	uint8_t lch_idx;
+	struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+	bool ok = true;
+
+	if (rc)
+		*rc = -EINVAL;
+
+	if (cbits == 0x01) {
+		lch_idx = 0;	/* TCH/F */	
+		if (ts->pchan != GSM_PCHAN_TCH_F &&
+		    ts->pchan != GSM_PCHAN_PDCH &&
+		    ts->pchan != GSM_PCHAN_TCH_F_PDCH
+		    && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+			 && (ts->dyn.pchan_is == GSM_PCHAN_TCH_F
+			     || ts->dyn.pchan_want == GSM_PCHAN_TCH_F)))
+			ok = false;
+	} else if ((cbits & 0x1e) == 0x02) {
+		lch_idx = cbits & 0x1;	/* TCH/H */
+		if (ts->pchan != GSM_PCHAN_TCH_H
+		    && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+			 && (ts->dyn.pchan_is == GSM_PCHAN_TCH_H
+			     || ts->dyn.pchan_want == GSM_PCHAN_TCH_H)))
+			ok = false;
+	} else if ((cbits & 0x1c) == 0x04) {
+		lch_idx = cbits & 0x3;	/* SDCCH/4 */
+		if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+		    ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+			ok = false;
+	} else if ((cbits & 0x18) == 0x08) {
+		lch_idx = cbits & 0x7;	/* SDCCH/8 */
+		if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C &&
+		    ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH)
+			ok = false;
+	} else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+		lch_idx = 0;
+		if (ts->pchan != GSM_PCHAN_CCCH &&
+		    ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+		    ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+			ok = false;
+		/* FIXME: we should not return first sdcch4 !!! */
+	} else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) {
+		lch_idx = 0;
+		if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+			ok = false;
+	} else
+		return NULL;
+
+	if (rc && ok)
+		*rc = 0;
+
+	return &ts->lchan[lch_idx];
+}
+
+static const uint8_t subslots_per_pchan[] = {
+	[GSM_PCHAN_NONE] = 0,
+	[GSM_PCHAN_CCCH] = 0,
+	[GSM_PCHAN_PDCH] = 0,
+	[GSM_PCHAN_CCCH_SDCCH4] = 4,
+	[GSM_PCHAN_TCH_F] = 1,
+	[GSM_PCHAN_TCH_H] = 2,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = 8,
+	[GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4,
+	[GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8,
+	/*
+	 * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+	 * part of this, those TS are handled according to their dynamic state.
+	 */
+};
+
+/*! Return the actual pchan type, also heeding dynamic TS. */
+enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts)
+{
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+		return ts->dyn.pchan_is;
+	case GSM_PCHAN_TCH_F_PDCH:
+		if (ts->flags & TS_F_PDCH_ACTIVE)
+			return GSM_PCHAN_PDCH;
+		else
+			return GSM_PCHAN_TCH_F;
+	default:
+		return ts->pchan;
+	}
+}
+
+/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of
+ * logical channels available in the timeslot. */
+uint8_t ts_subslots(struct gsm_bts_trx_ts *ts)
+{
+	return subslots_per_pchan[ts_pchan(ts)];
+}
+
+static bool pchan_is_tch(enum gsm_phys_chan_config pchan)
+{
+	switch (pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool ts_is_tch(struct gsm_bts_trx_ts *ts)
+{
+	return pchan_is_tch(ts_pchan(ts));
+}
diff --git a/openbsc/src/libcommon/gsm_subscriber_base.c b/openbsc/src/libcommon/gsm_subscriber_base.c
new file mode 100644
index 0000000..1ecdee5
--- /dev/null
+++ b/openbsc/src/libcommon/gsm_subscriber_base.c
@@ -0,0 +1,163 @@
+/* The concept of a subscriber as seen by the BSC */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+LLIST_HEAD(active_subscribers);
+void *tall_subscr_ctx;
+
+/* for the gsm_subscriber.c */
+struct llist_head *subscr_bsc_active_subscribers(void)
+{
+	return &active_subscribers;
+}
+
+
+char *subscr_name(struct gsm_subscriber *subscr)
+{
+	if (!subscr)
+		return "unknown";
+
+	if (strlen(subscr->name))
+		return subscr->name;
+
+	return subscr->imsi;
+}
+
+struct gsm_subscriber *subscr_alloc(void)
+{
+	struct gsm_subscriber *s;
+
+	s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber);
+	if (!s)
+		return NULL;
+
+	llist_add_tail(&s->entry, &active_subscribers);
+	s->use_count = 1;
+	s->tmsi = GSM_RESERVED_TMSI;
+
+	INIT_LLIST_HEAD(&s->requests);
+
+	return s;
+}
+
+static void subscr_free(struct gsm_subscriber *subscr)
+{
+	llist_del(&subscr->entry);
+	talloc_free(subscr);
+}
+
+void subscr_direct_free(struct gsm_subscriber *subscr)
+{
+	OSMO_ASSERT(subscr->use_count == 1);
+	subscr_free(subscr);
+}
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
+{
+	subscr->use_count++;
+	DEBUGP(DREF, "subscr %s usage increases usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
+{
+	subscr->use_count--;
+	DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	if (subscr->use_count <= 0 &&
+	    !((subscr->group && subscr->group->keep_subscr) ||
+	      subscr->keep_in_ram))
+		subscr_free(subscr);
+	return NULL;
+}
+
+struct gsm_subscriber *subscr_get_or_create(struct gsm_subscriber_group *sgrp,
+					    const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0 && subscr->group == sgrp)
+			return subscr_get(subscr);
+	}
+
+	subscr = subscr_alloc();
+	if (!subscr)
+		return NULL;
+
+	osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi));
+	subscr->group = sgrp;
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_subscriber_group *sgrp,
+					     uint32_t tmsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (subscr->tmsi == tmsi && subscr->group == sgrp)
+			return subscr_get(subscr);
+	}
+
+	return NULL;
+}
+
+struct gsm_subscriber *subscr_active_by_imsi(struct gsm_subscriber_group *sgrp,
+					     const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0 && subscr->group == sgrp)
+			return subscr_get(subscr);
+	}
+
+	return NULL;
+}
+
+int subscr_purge_inactive(struct gsm_subscriber_group *sgrp)
+{
+	struct gsm_subscriber *subscr, *tmp;
+	int purged = 0;
+
+	llist_for_each_entry_safe(subscr, tmp, subscr_bsc_active_subscribers(), entry) {
+		if (subscr->group == sgrp && subscr->use_count <= 0) {
+			subscr_free(subscr);
+			purged += 1;
+		}
+	}
+
+	return purged;
+}
diff --git a/openbsc/src/libcommon/gsup_client.c b/openbsc/src/libcommon/gsup_client.c
new file mode 100644
index 0000000..de00d8d
--- /dev/null
+++ b/openbsc/src/libcommon/gsup_client.c
@@ -0,0 +1,341 @@
+/* Generic Subscriber Update Protocol client */
+
+/* (C) 2014-2016 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Jacob Erlbeck
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsup_client.h>
+
+#include <osmocom/abis/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <openbsc/debug.h>
+
+#include <errno.h>
+#include <string.h>
+
+extern void *tall_bsc_ctx;
+
+static void start_test_procedure(struct gsup_client *gsupc);
+
+static void gsup_client_send_ping(struct gsup_client *gsupc)
+{
+	struct msgb *msg = gsup_client_msgb_alloc();
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = IPAC_MSGT_PING;
+	ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
+	ipa_client_conn_send(gsupc->link, msg);
+}
+
+static int gsup_client_connect(struct gsup_client *gsupc)
+{
+	int rc;
+
+	if (gsupc->is_connected)
+		return 0;
+
+	if (osmo_timer_pending(&gsupc->connect_timer)) {
+		LOGP(DLGSUP, LOGL_DEBUG,
+		     "GSUP connect: connect timer already running\n");
+		osmo_timer_del(&gsupc->connect_timer);
+	}
+
+	if (osmo_timer_pending(&gsupc->ping_timer)) {
+		LOGP(DLGSUP, LOGL_DEBUG,
+		     "GSUP connect: ping timer already running\n");
+		osmo_timer_del(&gsupc->ping_timer);
+	}
+
+	if (ipa_client_conn_clear_queue(gsupc->link) > 0)
+		LOGP(DLGSUP, LOGL_DEBUG, "GSUP connect: discarded stored messages\n");
+
+	rc = ipa_client_conn_open(gsupc->link);
+
+	if (rc >= 0) {
+		LOGP(DLGSUP, LOGL_INFO, "GSUP connecting to %s:%d\n",
+		     gsupc->link->addr, gsupc->link->port);
+		return 0;
+	}
+
+	LOGP(DLGSUP, LOGL_INFO, "GSUP failed to connect to %s:%d: %s\n",
+	     gsupc->link->addr, gsupc->link->port, strerror(-rc));
+
+	if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT ||
+	    rc == -EINVAL)
+		return rc;
+
+	osmo_timer_schedule(&gsupc->connect_timer,
+			    GSUP_CLIENT_RECONNECT_INTERVAL, 0);
+
+	LOGP(DLGSUP, LOGL_INFO, "Scheduled timer to retry GSUP connect to %s:%d\n",
+	     gsupc->link->addr, gsupc->link->port);
+
+	return 0;
+}
+
+static void connect_timer_cb(void *gsupc_)
+{
+	struct gsup_client *gsupc = gsupc_;
+
+	if (gsupc->is_connected)
+		return;
+
+	gsup_client_connect(gsupc);
+}
+
+static void client_send(struct gsup_client *gsupc, int proto_ext,
+			struct msgb *msg_tx)
+{
+	ipa_prepend_header_ext(msg_tx, proto_ext);
+	ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO);
+	ipa_client_conn_send(gsupc->link, msg_tx);
+	/* msg_tx is now queued and will be freed. */
+}
+
+static void gsup_client_oap_register(struct gsup_client *gsupc)
+{
+	struct msgb *msg_tx;
+	int rc;
+	rc = oap_client_register(&gsupc->oap_state, &msg_tx);
+
+	if ((rc < 0) || (!msg_tx)) {
+		LOGP(DLGSUP, LOGL_ERROR, "GSUP OAP set up, but cannot register.\n");
+		return;
+	}
+
+	client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
+}
+
+static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
+{
+	struct gsup_client *gsupc = link->data;
+
+	LOGP(DLGSUP, LOGL_INFO, "GSUP link to %s:%d %s\n",
+		     link->addr, link->port, up ? "UP" : "DOWN");
+
+	gsupc->is_connected = up;
+
+	if (up) {
+		start_test_procedure(gsupc);
+
+		if (gsupc->oap_state.state == OAP_INITIALIZED)
+			gsup_client_oap_register(gsupc);
+
+		osmo_timer_del(&gsupc->connect_timer);
+	} else {
+		osmo_timer_del(&gsupc->ping_timer);
+
+		osmo_timer_schedule(&gsupc->connect_timer,
+				    GSUP_CLIENT_RECONNECT_INTERVAL, 0);
+	}
+}
+
+static int gsup_client_oap_handle(struct gsup_client *gsupc, struct msgb *msg_rx)
+{
+	int rc;
+	struct msgb *msg_tx;
+
+	/* If the oap_state is disabled, this will reject the messages. */
+	rc = oap_client_handle(&gsupc->oap_state, msg_rx, &msg_tx);
+	msgb_free(msg_rx);
+	if (rc < 0)
+		return rc;
+
+	if (msg_tx)
+		client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
+
+	return 0;
+}
+
+static int gsup_client_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+{
+	struct ipaccess_head *hh = (struct ipaccess_head *) msg->data;
+	struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg);
+	struct gsup_client *gsupc = (struct gsup_client *)link->data;
+	int rc;
+	static struct ipaccess_unit ipa_dev = {
+		.unit_name = "SGSN"
+	};
+
+	msg->l2h = &hh->data[0];
+
+	rc = ipaccess_bts_handle_ccm(link, &ipa_dev, msg);
+
+	if (rc < 0) {
+		LOGP(DLGSUP, LOGL_NOTICE,
+		     "GSUP received an invalid IPA/CCM message from %s:%d\n",
+		     link->addr, link->port);
+		/* Link has been closed */
+		gsupc->is_connected = 0;
+		msgb_free(msg);
+		return -1;
+	}
+
+	if (rc == 1) {
+		uint8_t msg_type = *(msg->l2h);
+		/* CCM message */
+		if (msg_type == IPAC_MSGT_PONG) {
+			LOGP(DLGSUP, LOGL_DEBUG, "GSUP receiving PONG\n");
+			gsupc->got_ipa_pong = 1;
+		}
+
+		msgb_free(msg);
+		return 0;
+	}
+
+	if (hh->proto != IPAC_PROTO_OSMO)
+		goto invalid;
+
+	if (!he || msgb_l2len(msg) < sizeof(*he))
+		goto invalid;
+
+	msg->l2h = &he->data[0];
+
+	if (he->proto == IPAC_PROTO_EXT_GSUP) {
+		OSMO_ASSERT(gsupc->read_cb != NULL);
+		gsupc->read_cb(gsupc, msg);
+		/* expecting read_cb() to free msg */
+	} else if (he->proto == IPAC_PROTO_EXT_OAP) {
+		return gsup_client_oap_handle(gsupc, msg);
+		/* gsup_client_oap_handle frees msg */
+	} else
+		goto invalid;
+
+	return 0;
+
+invalid:
+	LOGP(DLGSUP, LOGL_NOTICE,
+	     "GSUP received an invalid IPA message from %s:%d, size = %d\n",
+	     link->addr, link->port, msgb_length(msg));
+
+	msgb_free(msg);
+	return -1;
+}
+
+static void ping_timer_cb(void *gsupc_)
+{
+	struct gsup_client *gsupc = gsupc_;
+
+	LOGP(DLGSUP, LOGL_INFO, "GSUP ping callback (%s, %s PONG)\n",
+	     gsupc->is_connected ? "connected" : "not connected",
+	     gsupc->got_ipa_pong ? "got" : "didn't get");
+
+	if (gsupc->got_ipa_pong) {
+		start_test_procedure(gsupc);
+		return;
+	}
+
+	LOGP(DLGSUP, LOGL_NOTICE, "GSUP ping timed out, reconnecting\n");
+	ipa_client_conn_close(gsupc->link);
+	gsupc->is_connected = 0;
+
+	gsup_client_connect(gsupc);
+}
+
+static void start_test_procedure(struct gsup_client *gsupc)
+{
+	osmo_timer_setup(&gsupc->ping_timer, ping_timer_cb, gsupc);
+
+	gsupc->got_ipa_pong = 0;
+	osmo_timer_schedule(&gsupc->ping_timer, GSUP_CLIENT_PING_INTERVAL, 0);
+	LOGP(DLGSUP, LOGL_DEBUG, "GSUP sending PING\n");
+	gsup_client_send_ping(gsupc);
+}
+
+struct gsup_client *gsup_client_create(const char *ip_addr,
+				       unsigned int tcp_port,
+				       gsup_client_read_cb_t read_cb,
+				       struct oap_client_config *oapc_config)
+{
+	struct gsup_client *gsupc;
+	int rc;
+
+	gsupc = talloc_zero(tall_bsc_ctx, struct gsup_client);
+	OSMO_ASSERT(gsupc);
+
+	/* a NULL oapc_config will mark oap_state disabled. */
+	rc = oap_client_init(oapc_config, &gsupc->oap_state);
+	if (rc != 0)
+		goto failed;
+
+	gsupc->link = ipa_client_conn_create(gsupc,
+					     /* no e1inp */ NULL,
+					     0,
+					     ip_addr, tcp_port,
+					     gsup_client_updown_cb,
+					     gsup_client_read_cb,
+					     /* default write_cb */ NULL,
+					     gsupc);
+	if (!gsupc->link)
+		goto failed;
+
+	osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc);
+
+	rc = gsup_client_connect(gsupc);
+
+	if (rc < 0)
+		goto failed;
+
+	gsupc->read_cb = read_cb;
+
+	return gsupc;
+
+failed:
+	gsup_client_destroy(gsupc);
+	return NULL;
+}
+
+void gsup_client_destroy(struct gsup_client *gsupc)
+{
+	osmo_timer_del(&gsupc->connect_timer);
+	osmo_timer_del(&gsupc->ping_timer);
+
+	if (gsupc->link) {
+		ipa_client_conn_close(gsupc->link);
+		ipa_client_conn_destroy(gsupc->link);
+		gsupc->link = NULL;
+	}
+	talloc_free(gsupc);
+}
+
+int gsup_client_send(struct gsup_client *gsupc, struct msgb *msg)
+{
+	if (!gsupc) {
+		msgb_free(msg);
+		return -ENOTCONN;
+	}
+
+	if (!gsupc->is_connected) {
+		msgb_free(msg);
+		return -EAGAIN;
+	}
+
+	client_send(gsupc, IPAC_PROTO_EXT_GSUP, msg);
+
+	return 0;
+}
+
+struct msgb *gsup_client_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(4000, 64, __func__);
+}
diff --git a/openbsc/src/libcommon/gsup_test_client.c b/openbsc/src/libcommon/gsup_test_client.c
new file mode 100644
index 0000000..8fc38d6
--- /dev/null
+++ b/openbsc/src/libcommon/gsup_test_client.c
@@ -0,0 +1,298 @@
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsup.h>
+
+#include <openbsc/gsup_client.h>
+#include <openbsc/debug.h>
+
+static struct gsup_client *g_gc;
+
+
+/***********************************************************************
+ * IMSI Operation
+ ***********************************************************************/
+static LLIST_HEAD(g_imsi_ops);
+
+struct imsi_op_stats {
+	uint32_t num_alloc;
+	uint32_t num_released;
+	uint32_t num_rx_success;
+	uint32_t num_rx_error;
+	uint32_t num_timeout;
+};
+
+enum imsi_op_type {
+	IMSI_OP_SAI,
+	IMSI_OP_LU,
+	IMSI_OP_ISD,
+	_NUM_IMSI_OP
+};
+
+static const struct value_string imsi_op_names[] = {
+	{ IMSI_OP_SAI, "SAI" },
+	{ IMSI_OP_LU, "LU" },
+	{ IMSI_OP_ISD, "ISD" },
+	{ 0, NULL }
+};
+
+static struct imsi_op_stats imsi_op_stats[_NUM_IMSI_OP];
+
+struct imsi_op {
+	struct llist_head list;
+	char imsi[17];
+	enum imsi_op_type type;
+	struct osmo_timer_list timer;
+};
+
+static struct imsi_op *imsi_op_find(const char *imsi,
+			     enum imsi_op_type type)
+{
+	struct imsi_op *io;
+
+	llist_for_each_entry(io, &g_imsi_ops, list) {
+		if (!strcmp(io->imsi, imsi) && io->type == type)
+			return io;
+	}
+	return NULL;
+}
+
+static void imsi_op_timer_cb(void *data);
+
+static struct imsi_op *imsi_op_alloc(void *ctx, const char *imsi,
+				enum imsi_op_type type)
+{
+	struct imsi_op *io;
+
+	if (imsi_op_find(imsi, type))
+		return NULL;
+
+	io = talloc_zero(ctx, struct imsi_op);
+	osmo_strlcpy(io->imsi, imsi, sizeof(io->imsi));
+	io->type = type;
+	osmo_timer_setup(&io->timer, imsi_op_timer_cb, io);
+	llist_add(&io->list, &g_imsi_ops);
+	imsi_op_stats[type].num_alloc++;
+
+	return io;
+}
+
+static void imsi_op_release(struct imsi_op *io)
+{
+	osmo_timer_del(&io->timer);
+	llist_del(&io->list);
+	imsi_op_stats[io->type].num_released++;
+	talloc_free(io);
+}
+
+static void imsi_op_timer_cb(void *data)
+{
+	struct imsi_op *io = data;
+	printf("%s: Timer expiration\n", io->imsi);
+	imsi_op_stats[io->type].num_timeout++;
+	imsi_op_release(io);
+}
+
+/* allocate + generate + send Send-Auth-Info */
+int req_auth_info(const char *imsi)
+{
+	struct imsi_op *io = imsi_op_alloc(g_gc, imsi, IMSI_OP_SAI);
+	struct osmo_gsup_message gsup = {0};
+	struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
+
+	osmo_strlcpy(gsup.imsi, io->imsi, sizeof(gsup.imsi));
+	gsup.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+
+	osmo_gsup_encode(msg, &gsup);
+
+	return gsup_client_send(g_gc, msg);
+}
+
+/* allocate + generate + send Send-Auth-Info */
+int req_loc_upd(const char *imsi)
+{
+	struct imsi_op *io = imsi_op_alloc(g_gc, imsi, IMSI_OP_LU);
+	struct osmo_gsup_message gsup = {0};
+	struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
+
+	osmo_strlcpy(gsup.imsi, io->imsi, sizeof(gsup.imsi));
+	gsup.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
+
+	osmo_gsup_encode(msg, &gsup);
+
+	return gsup_client_send(g_gc, msg);
+}
+
+int resp_isd(struct imsi_op *io)
+{
+	struct osmo_gsup_message gsup = {0};
+	struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
+
+	osmo_strlcpy(gsup.imsi, io->imsi, sizeof(gsup.imsi));
+	gsup.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
+
+	osmo_gsup_encode(msg, &gsup);
+
+	imsi_op_release(io);
+
+	return gsup_client_send(g_gc, msg);
+}
+
+/* receive an incoming GSUP message */
+static void imsi_op_rx_gsup(struct imsi_op *io, const struct osmo_gsup_message *gsup)
+{
+	int is_error = 0;
+
+	if (OSMO_GSUP_IS_MSGT_ERROR(gsup->message_type)) {
+		imsi_op_stats[io->type].num_rx_error++;
+		is_error = 1;
+	} else
+		imsi_op_stats[io->type].num_rx_success++;
+
+	switch (io->type) {
+	case IMSI_OP_SAI:
+		printf("%s; SAI Response%s\n", io->imsi, is_error ? ": ERROR" : "");
+		/* now that we have auth tuples, request LU */
+		req_loc_upd(io->imsi);
+		imsi_op_release(io);
+		break;
+	case IMSI_OP_LU:
+		printf("%s; LU Response%s\n", io->imsi, is_error ? ": ERROR" : "");
+		imsi_op_release(io);
+		break;
+	case IMSI_OP_ISD:
+		printf("%s; ISD Request%s\n", io->imsi, is_error ? ": ERROR" : "");
+		resp_isd(io);
+		break;
+	default:
+		printf("%s: Unknown\n", io->imsi);
+		imsi_op_release(io);
+		break;
+	}
+}
+
+static int op_type_by_gsup_msgt(enum osmo_gsup_message_type msg_type)
+{
+	switch (msg_type) {
+	case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+	case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+		return IMSI_OP_SAI;
+	case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+	case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
+		return IMSI_OP_LU;
+	case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+		return IMSI_OP_ISD;
+	default:
+		printf("Unknown GSUP msg_type %u\n", msg_type);
+		return -1;
+	}
+}
+
+static int gsupc_read_cb(struct gsup_client *gsupc, struct msgb *msg)
+{
+	struct osmo_gsup_message gsup_msg = {0};
+	struct imsi_op *io;
+	int rc;
+
+	DEBUGP(DGPRS, "Rx GSUP %s\n", osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+	rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup_msg);
+	if (rc < 0)
+		return rc;
+
+	if (!gsup_msg.imsi[0])
+		return -1;
+
+	rc = op_type_by_gsup_msgt(gsup_msg.message_type);
+	if (rc < 0)
+		return rc;
+
+	switch (rc) {
+	case IMSI_OP_SAI:
+	case IMSI_OP_LU:
+		io = imsi_op_find(gsup_msg.imsi, rc);
+		if (!io)
+			return -1;
+		break;
+	case IMSI_OP_ISD:
+		/* ISD is an inbound transaction */
+		io = imsi_op_alloc(g_gc, gsup_msg.imsi, IMSI_OP_ISD);
+		break;
+	}
+
+	imsi_op_rx_gsup(io, &gsup_msg);
+	msgb_free(msg);
+
+	return 0;
+}
+
+static void print_report(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(imsi_op_stats); i++) {
+		struct imsi_op_stats *st = &imsi_op_stats[i];
+		const char *name = get_value_string(imsi_op_names, i);
+		printf("%s: %u alloc, %u released, %u success, %u error , %u tout\n",
+			name, st->num_alloc, st->num_released, st->num_rx_success,
+			st->num_rx_error, st->num_timeout);
+	}
+}
+
+static void sig_cb(int sig)
+{
+	switch (sig) {
+	case SIGINT:
+		print_report();
+		exit(0);
+		break;
+	}
+}
+
+void *tall_bsc_ctx = NULL;
+
+/* default categories */
+static struct log_info_cat default_categories[] = {
+};
+
+static const struct log_info gsup_test_client_log_info = {
+	.cat = default_categories,
+	.num_cat = ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char **argv)
+{
+	unsigned long long i;
+	char *server_host = "127.0.0.1";
+	uint16_t server_port = 2222;
+
+	osmo_init_logging(&gsup_test_client_log_info);
+
+	g_gc = gsup_client_create(server_host, server_port, gsupc_read_cb,
+				       NULL);
+
+
+	signal(SIGINT, sig_cb);
+
+	for (i = 0; i < 10000; i++) {
+		unsigned long long imsi = 901790000000000 + i;
+		char imsi_buf[17];
+		snprintf(imsi_buf, sizeof(imsi_buf), "%015llu", imsi);
+		req_auth_info(imsi_buf);
+		osmo_select_main(0);
+	}
+
+	while (1) {
+		osmo_select_main(0);
+	}
+
+	print_report();
+	exit(0);
+}
diff --git a/openbsc/src/libcommon/oap_client.c b/openbsc/src/libcommon/oap_client.c
new file mode 100644
index 0000000..5128ac1
--- /dev/null
+++ b/openbsc/src/libcommon/oap_client.c
@@ -0,0 +1,280 @@
+/* Osmocom Authentication Protocol API */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/oap.h>
+
+#include <openbsc/oap_client.h>
+#include <openbsc/debug.h>
+
+int oap_client_init(struct oap_client_config *config,
+		    struct oap_client_state *state)
+{
+	OSMO_ASSERT(state->state == OAP_UNINITIALIZED);
+
+	if (!config)
+		goto disable;
+
+	if (config->client_id == 0)
+		goto disable;
+
+	if (config->secret_k_present == 0) {
+		LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret K missing.\n");
+		goto disable;
+	}
+
+	if (config->secret_opc_present == 0) {
+		LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret OPC missing.\n");
+		goto disable;
+	}
+
+	state->client_id = config->client_id;
+	memcpy(state->secret_k, config->secret_k, sizeof(state->secret_k));
+	memcpy(state->secret_opc, config->secret_opc, sizeof(state->secret_opc));
+	state->state = OAP_INITIALIZED;
+	return 0;
+
+disable:
+	state->state = OAP_DISABLED;
+	return 0;
+}
+
+/* From the given state and received RAND and AUTN octets, validate the
+ * server's authenticity and formulate the matching milenage reply octets in
+ * *tx_xres. The state is not modified.
+ * On success, and if tx_res is not NULL, exactly 8 octets will be written to
+ * *tx_res. If not NULL, tx_res must point at allocated memory of at least 8
+ * octets. The caller will want to send XRES back to the server in a challenge
+ * response message and update the state.
+ * Return 0 on success; -1 if OAP is disabled; -2 if rx_random and rx_autn fail
+ * the authentication check; -3 for any other errors. */
+static int oap_evaluate_challenge(const struct oap_client_state *state,
+				  const uint8_t *rx_random,
+				  const uint8_t *rx_autn,
+				  uint8_t *tx_xres)
+{
+	struct osmo_auth_vector vec;
+
+	struct osmo_sub_auth_data auth = {
+		.type		= OSMO_AUTH_TYPE_UMTS,
+		.algo		= OSMO_AUTH_ALG_MILENAGE,
+	};
+
+	osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.k)
+			   == sizeof(state->secret_k), _secret_k_size_match);
+	osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.opc)
+			   == sizeof(state->secret_opc), _secret_opc_size_match);
+
+	switch (state->state) {
+	case OAP_UNINITIALIZED:
+	case OAP_DISABLED:
+		return -1;
+	default:
+		break;
+	}
+
+	memcpy(auth.u.umts.k, state->secret_k, sizeof(auth.u.umts.k));
+	memcpy(auth.u.umts.opc, state->secret_opc, sizeof(auth.u.umts.opc));
+	memset(auth.u.umts.amf, '\0', sizeof(auth.u.umts.amf));
+	auth.u.umts.sqn = 41; /* TODO use incrementing sequence nr */
+
+	memset(&vec, 0, sizeof(vec));
+	osmo_auth_gen_vec(&vec, &auth, rx_random);
+
+	if (vec.res_len != 8) {
+		LOGP(DLOAP, LOGL_ERROR, "OAP: Expected XRES to be 8 octets, got %d\n",
+		     vec.res_len);
+		return -3;
+	}
+
+	if (osmo_constant_time_cmp(vec.autn, rx_autn, sizeof(vec.autn)) != 0) {
+		LOGP(DLOAP, LOGL_ERROR, "OAP: AUTN mismatch!\n");
+		LOGP(DLOAP, LOGL_INFO, "OAP: AUTN from server: %s\n",
+		     osmo_hexdump_nospc(rx_autn, sizeof(vec.autn)));
+		LOGP(DLOAP, LOGL_INFO, "OAP: AUTN expected:    %s\n",
+		     osmo_hexdump_nospc(vec.autn, sizeof(vec.autn)));
+		return -2;
+	}
+
+	if (tx_xres != NULL)
+		memcpy(tx_xres, vec.res, 8);
+	return 0;
+}
+
+struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_msg)
+{
+	struct msgb *msg = msgb_alloc_headroom(1000, 64, __func__);
+	OSMO_ASSERT(msg);
+	osmo_oap_encode(msg, oap_msg);
+	return msg;
+}
+
+/* Create a new msgb containing an OAP registration message.
+ * On error, return NULL. */
+static struct msgb* oap_msg_register(uint16_t client_id)
+{
+	struct osmo_oap_message oap_msg = {0};
+
+	if (client_id < 1) {
+		LOGP(DLOAP, LOGL_ERROR, "OAP: Invalid client ID: %d\n", client_id);
+		return NULL;
+	}
+
+	oap_msg.message_type = OAP_MSGT_REGISTER_REQUEST;
+	oap_msg.client_id = client_id;
+	return oap_client_encoded(&oap_msg);
+}
+
+int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx)
+{
+	*msg_tx = oap_msg_register(state->client_id);
+	if (!(*msg_tx))
+		return -1;
+
+	state->state = OAP_REQUESTED_CHALLENGE;
+	return 0;
+}
+
+/* Create a new msgb containing an OAP challenge response message.
+ * xres must point at 8 octets to return as challenge response.
+ * On error, return NULL. */
+static struct msgb* oap_msg_challenge_response(uint8_t *xres)
+{
+	struct osmo_oap_message oap_reply = {0};
+
+	oap_reply.message_type = OAP_MSGT_CHALLENGE_RESULT;
+	memcpy(oap_reply.xres, xres, sizeof(oap_reply.xres));
+	oap_reply.xres_present = 1;
+	return oap_client_encoded(&oap_reply);
+}
+
+static int handle_challenge(struct oap_client_state *state,
+			    struct osmo_oap_message *oap_rx,
+			    struct msgb **msg_tx)
+{
+	int rc;
+	uint8_t xres[8];
+
+	if (!(oap_rx->rand_present && oap_rx->autn_present)) {
+		LOGP(DLOAP, LOGL_ERROR,
+		     "OAP challenge incomplete (rand_present: %d, autn_present: %d)\n",
+		     oap_rx->rand_present, oap_rx->autn_present);
+		rc = -2;
+		goto failure;
+	}
+
+	rc = oap_evaluate_challenge(state,
+				    oap_rx->rand,
+				    oap_rx->autn,
+				    xres);
+	if (rc < 0)
+		goto failure;
+
+	*msg_tx = oap_msg_challenge_response(xres);
+	if ((*msg_tx) == NULL) {
+		rc = -1;
+		goto failure;
+	}
+
+	state->state = OAP_SENT_CHALLENGE_RESULT;
+	return 0;
+
+failure:
+	OSMO_ASSERT(rc < 0);
+	state->state = OAP_INITIALIZED;
+	return rc;
+}
+
+int oap_client_handle(struct oap_client_state *state,
+		      const struct msgb *msg_rx, struct msgb **msg_tx)
+{
+	uint8_t *data = msgb_l2(msg_rx);
+	size_t data_len = msgb_l2len(msg_rx);
+	struct osmo_oap_message oap_msg = {0};
+	int rc = 0;
+
+	*msg_tx = NULL;
+
+	OSMO_ASSERT(data);
+
+	rc = osmo_oap_decode(&oap_msg, data, data_len);
+	if (rc < 0) {
+		LOGP(DLOAP, LOGL_ERROR,
+		     "Decoding OAP message failed with error '%s' (%d)\n",
+		     get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+		return -10;
+	}
+
+	switch (state->state) {
+	case OAP_UNINITIALIZED:
+		LOGP(DLOAP, LOGL_ERROR,
+		     "Received OAP message %d, but the OAP client is"
+		     " not initialized\n", oap_msg.message_type);
+		return -ENOTCONN;
+	case OAP_DISABLED:
+		LOGP(DLOAP, LOGL_ERROR,
+		     "Received OAP message %d, but the OAP client is"
+		     " disabled\n", oap_msg.message_type);
+		return -ENOTCONN;
+	default:
+		break;
+	}
+
+	switch (oap_msg.message_type) {
+	case OAP_MSGT_CHALLENGE_REQUEST:
+		return handle_challenge(state, &oap_msg, msg_tx);
+
+	case OAP_MSGT_REGISTER_RESULT:
+		/* successfully registered */
+		state->state = OAP_REGISTERED;
+		break;
+
+	case OAP_MSGT_REGISTER_ERROR:
+		LOGP(DLOAP, LOGL_ERROR,
+		     "OAP registration failed\n");
+		state->state = OAP_INITIALIZED;
+		if (state->registration_failures < 3) {
+			state->registration_failures ++;
+			return oap_client_register(state, msg_tx);
+		}
+		return -11;
+
+	case OAP_MSGT_REGISTER_REQUEST:
+	case OAP_MSGT_CHALLENGE_RESULT:
+		LOGP(DLOAP, LOGL_ERROR,
+		     "Received invalid OAP message type for OAP client side: %d\n",
+		     (int)oap_msg.message_type);
+		return -12;
+
+	default:
+		LOGP(DLOAP, LOGL_ERROR,
+		     "Unknown OAP message type: %d\n",
+		     (int)oap_msg.message_type);
+		return -13;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/libcommon/socket.c b/openbsc/src/libcommon/socket.c
new file mode 100644
index 0000000..2a64767
--- /dev/null
+++ b/openbsc/src/libcommon/socket.c
@@ -0,0 +1,111 @@
+/* OpenBSC sokcet code, taken from Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/talloc.h>
+
+int make_sock(struct osmo_fd *bfd, int proto,
+	      uint32_t ip, uint16_t port, int priv_nr,
+	      int (*cb)(struct osmo_fd *fd, unsigned int what), void *data)
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+	int type = SOCK_STREAM;
+
+	switch (proto) {
+	case IPPROTO_TCP:
+		type = SOCK_STREAM;
+		break;
+	case IPPROTO_UDP:
+		type = SOCK_DGRAM;
+		break;
+#ifdef IPPROTO_GRE
+	case IPPROTO_GRE:
+		type = SOCK_RAW;
+		break;
+#endif
+	default:
+		return -EINVAL;
+	}
+
+	bfd->fd = socket(AF_INET, type, proto);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	bfd->data = data;
+	bfd->priv_nr = priv_nr;
+
+	if (bfd->fd < 0) {
+		LOGP(DLINP, LOGL_ERROR, "could not create socket.\n");
+		return -EIO;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	if (ip != INADDR_ANY)
+		addr.sin_addr.s_addr = htonl(ip);
+	else
+		addr.sin_addr.s_addr = INADDR_ANY;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "could not bind socket %s\n",
+			strerror(errno));
+		close(bfd->fd);
+		return -EIO;
+	}
+
+	if (proto == IPPROTO_TCP) {
+		ret = listen(bfd->fd, 1);
+		if (ret < 0) {
+			perror("listen");
+			close(bfd->fd);
+			return ret;
+		}
+	}
+
+	ret = osmo_fd_register(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		close(bfd->fd);
+		return ret;
+	}
+	return 0;
+}
diff --git a/openbsc/src/libcommon/talloc_ctx.c b/openbsc/src/libcommon/talloc_ctx.c
new file mode 100644
index 0000000..5e3d9ae
--- /dev/null
+++ b/openbsc/src/libcommon/talloc_ctx.c
@@ -0,0 +1,56 @@
+/* OpenBSC allocation contexts initialization code */
+/* (C) 2011-2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+
+extern void *tall_bsc_ctx;
+extern void *tall_fle_ctx;
+extern void *tall_locop_ctx;
+extern void *tall_authciphop_ctx;
+extern void *tall_gsms_ctx;
+extern void *tall_subscr_ctx;
+extern void *tall_sub_req_ctx;
+extern void *tall_call_ctx;
+extern void *tall_paging_ctx;
+extern void *tall_sigh_ctx;
+extern void *tall_tqe_ctx;
+extern void *tall_trans_ctx;
+extern void *tall_map_ctx;
+extern void *tall_upq_ctx;
+extern void *tall_ctr_ctx;
+
+void talloc_ctx_init(void *ctx_root)
+{
+	msgb_talloc_ctx_init(ctx_root, 0);
+	tall_fle_ctx = talloc_named_const(ctx_root, 0, "bs11_file_list_entry");
+	tall_locop_ctx = talloc_named_const(ctx_root, 0, "loc_updating_oper");
+	tall_authciphop_ctx = talloc_named_const(ctx_root, 0, "auth_ciph_oper");
+	tall_gsms_ctx = talloc_named_const(ctx_root, 0, "sms");
+	tall_subscr_ctx = talloc_named_const(ctx_root, 0, "subscriber");
+	tall_sub_req_ctx = talloc_named_const(ctx_root, 0, "subscr_request");
+	tall_call_ctx = talloc_named_const(ctx_root, 0, "gsm_call");
+	tall_paging_ctx = talloc_named_const(ctx_root, 0, "paging_request");
+	tall_sigh_ctx = talloc_named_const(ctx_root, 0, "signal_handler");
+	tall_tqe_ctx = talloc_named_const(ctx_root, 0, "subch_txq_entry");
+	tall_trans_ctx = talloc_named_const(ctx_root, 0, "transaction");
+	tall_map_ctx = talloc_named_const(ctx_root, 0, "trau_map_entry");
+	tall_upq_ctx = talloc_named_const(ctx_root, 0, "trau_upq_entry");
+	tall_ctr_ctx = talloc_named_const(ctx_root, 0, "counter");
+}
diff --git a/openbsc/src/libfilter/Makefile.am b/openbsc/src/libfilter/Makefile.am
new file mode 100644
index 0000000..6d3db0b
--- /dev/null
+++ b/openbsc/src/libfilter/Makefile.am
@@ -0,0 +1,26 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libfilter.a \
+	$(NULL)
+
+libfilter_a_SOURCES = \
+	bsc_msg_filter.c \
+	bsc_msg_acc.c \
+	bsc_msg_vty.c \
+	$(NULL)
+
diff --git a/openbsc/src/libfilter/bsc_msg_acc.c b/openbsc/src/libfilter/bsc_msg_acc.c
new file mode 100644
index 0000000..0789fb6
--- /dev/null
+++ b/openbsc/src/libfilter/bsc_msg_acc.c
@@ -0,0 +1,133 @@
+/*
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/bsc_nat.h>
+
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
+
+#include <string.h>
+
+static const struct rate_ctr_desc acc_list_ctr_description[] = {
+	[ACC_LIST_LOCAL_FILTER]	= { "access-list:local-filter", "Rejected by rule for local"},
+	[ACC_LIST_GLOBAL_FILTER]= { "access-list:global-filter", "Rejected by rule for global"},
+};
+
+static const struct rate_ctr_group_desc bsc_cfg_acc_list_desc = {
+	.group_name_prefix = "nat:filter",
+	.group_description = "NAT Access-List Statistics",
+	.num_ctr = ARRAY_SIZE(acc_list_ctr_description),
+	.ctr_desc = acc_list_ctr_description,
+	.class_id = OSMO_STATS_CLASS_GLOBAL,
+};
+
+/*! Find an unused index for this rate counter group.
+ *  \param[in] head List of allocated ctr groups of the same type
+ *  \returns the largest used index number + 1, or 0 if none exist yet. */
+static unsigned int rate_ctr_get_unused_idx(struct llist_head *head)
+{
+	unsigned int idx = 0;
+	struct bsc_msg_acc_lst *lst;
+
+	llist_for_each_entry(lst, head, list) {
+		if (idx <= lst->stats->idx)
+			idx = lst->stats->idx + 1;
+	}
+	return idx;
+}
+
+
+int bsc_msg_acc_lst_check_allow(struct bsc_msg_acc_lst *lst, const char *mi_string)
+{
+	struct bsc_msg_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (!entry->imsi_allow)
+			continue;
+		if (regexec(&entry->imsi_allow_re, mi_string, 0, NULL, 0) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_find(struct llist_head *head, const char *name)
+{
+	struct bsc_msg_acc_lst *lst;
+
+	if (!name)
+		return NULL;
+
+	llist_for_each_entry(lst, head, list)
+		if (strcmp(lst->name, name) == 0)
+			return lst;
+
+	return NULL;
+}
+
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_get(void *ctx, struct llist_head *head, const char *name)
+{
+	struct bsc_msg_acc_lst *lst;
+	unsigned int new_idx;
+
+	lst = bsc_msg_acc_lst_find(head, name);
+	if (lst)
+		return lst;
+
+	lst = talloc_zero(ctx, struct bsc_msg_acc_lst);
+	if (!lst) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate access list");
+		return NULL;
+	}
+
+	new_idx = rate_ctr_get_unused_idx(head);
+	lst->stats = rate_ctr_group_alloc(lst, &bsc_cfg_acc_list_desc, new_idx);
+	if (!lst->stats) {
+		talloc_free(lst);
+		return NULL;
+	}
+
+	INIT_LLIST_HEAD(&lst->fltr_list);
+	lst->name = talloc_strdup(lst, name);
+	llist_add_tail(&lst->list, head);
+	return lst;
+}
+
+void bsc_msg_acc_lst_delete(struct bsc_msg_acc_lst *lst)
+{
+	llist_del(&lst->list);
+	rate_ctr_group_free(lst->stats);
+	talloc_free(lst);
+}
+
+struct bsc_msg_acc_lst_entry *bsc_msg_acc_lst_entry_create(struct bsc_msg_acc_lst *lst)
+{
+	struct bsc_msg_acc_lst_entry *entry;
+
+	entry = talloc_zero(lst, struct bsc_msg_acc_lst_entry);
+	if (!entry)
+		return NULL;
+
+	entry->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	entry->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	llist_add_tail(&entry->list, &lst->fltr_list);
+	return entry;
+}
diff --git a/openbsc/src/libfilter/bsc_msg_filter.c b/openbsc/src/libfilter/bsc_msg_filter.c
new file mode 100644
index 0000000..115d376
--- /dev/null
+++ b/openbsc/src/libfilter/bsc_msg_filter.c
@@ -0,0 +1,398 @@
+/*
+ * Access filtering
+ */
+/*
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_msg_filter.h>
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+int bsc_filter_barr_find(struct rb_root *root, const char *imsi, int *cm, int *lu)
+{
+	struct bsc_filter_barr_entry *n;
+	n = rb_entry(root->rb_node, struct bsc_filter_barr_entry, node);
+
+	while (n) {
+		int rc = strcmp(imsi, n->imsi);
+		if (rc == 0) {
+			*cm = n->cm_reject_cause;
+			*lu = n->lu_reject_cause;
+			return 1;
+		}
+
+		n = rb_entry(
+			(rc < 0) ? n->node.rb_left : n->node.rb_right,
+			struct bsc_filter_barr_entry, node);
+	};
+
+	return 0;
+}
+
+static int insert_barr_node(struct bsc_filter_barr_entry *entry, struct rb_root *root)
+{
+	struct rb_node **new = &root->rb_node, *parent = NULL;
+
+	while (*new) {
+		int rc;
+		struct bsc_filter_barr_entry *this;
+		this = rb_entry(*new, struct bsc_filter_barr_entry, node);
+		parent = *new;
+
+		rc = strcmp(entry->imsi, this->imsi);
+		if (rc < 0)
+			new = &((*new)->rb_left);
+		else if (rc > 0)
+			new = &((*new)->rb_right);
+		else {
+			LOGP(DFILTER, LOGL_ERROR,
+				"Duplicate entry for IMSI(%s)\n", entry->imsi);
+			talloc_free(entry);
+			return -1;
+		}
+	}
+
+	rb_link_node(&entry->node, parent, new);
+	rb_insert_color(&entry->node, root);
+	return 0;
+}
+
+int bsc_filter_barr_adapt(void *ctx, struct rb_root *root,
+			const struct osmo_config_list *list)
+{
+	struct osmo_config_entry *cfg_entry;
+	int err = 0;
+
+	/* free the old data */
+	while (!RB_EMPTY_ROOT(root)) {
+		struct rb_node *node = rb_first(root);
+		rb_erase(node, root);
+		talloc_free(node);
+	}
+
+	if (!list)
+		return 0;
+
+	/* now adapt the new list */
+	llist_for_each_entry(cfg_entry, &list->entry, list) {
+		struct bsc_filter_barr_entry *entry;
+		entry = talloc_zero(ctx, struct bsc_filter_barr_entry);
+		if (!entry) {
+			LOGP(DFILTER, LOGL_ERROR,
+				"Allocation of the barr entry failed.\n");
+			continue;
+		}
+
+		entry->imsi = talloc_strdup(entry, cfg_entry->mcc);
+		entry->cm_reject_cause = atoi(cfg_entry->mnc);
+		entry->lu_reject_cause = atoi(cfg_entry->option);
+		err |= insert_barr_node(entry, root);
+	}
+
+	return err;
+}
+
+
+static int lst_check_deny(struct bsc_msg_acc_lst *lst, const char *mi_string,
+			int *cm_cause, int *lu_cause)
+{
+	struct bsc_msg_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (!entry->imsi_deny)
+			continue;
+		if (regexec(&entry->imsi_deny_re, mi_string, 0, NULL, 0) == 0) {
+			*cm_cause = entry->cm_reject_cause;
+			*lu_cause = entry->lu_reject_cause;
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/* apply white/black list */
+static int auth_imsi(struct bsc_filter_request *req,
+		const char *imsi,
+		struct bsc_filter_reject_cause *cause)
+{
+	/*
+	 * Now apply blacklist/whitelist of the BSC and the NAT.
+	 * 1.) Check the global IMSI barr list
+	 * 2.) Allow directly if the IMSI is allowed at the BSC
+	 * 3.) Reject if the IMSI is not allowed at the BSC
+	 * 4.) Reject if the IMSI not allowed at the global level.
+	 * 5.) Allow directly if the IMSI is allowed at the global level
+	 */
+	int cm, lu;
+	struct bsc_msg_acc_lst *nat_lst = NULL;
+	struct bsc_msg_acc_lst *bsc_lst = NULL;
+
+	/* 1. global check for barred imsis */
+	if (req->black_list && bsc_filter_barr_find(req->black_list, imsi, &cm, &lu)) {
+		cause->cm_reject_cause = cm;
+		cause->lu_reject_cause = lu;
+		LOGP(DFILTER, LOGL_DEBUG,
+			"Blocking subscriber IMSI %s with CM: %d LU: %d\n",
+			imsi, cm, lu);
+		return -4;
+	}
+
+
+	bsc_lst = bsc_msg_acc_lst_find(req->access_lists, req->local_lst_name);
+	nat_lst = bsc_msg_acc_lst_find(req->access_lists, req->global_lst_name);
+
+
+	if (bsc_lst) {
+		/* 2. BSC allow */
+		if (bsc_msg_acc_lst_check_allow(bsc_lst, imsi) == 0)
+			return 1;
+
+		/* 3. BSC deny */
+		if (lst_check_deny(bsc_lst, imsi, &cm, &lu) == 0) {
+			LOGP(DFILTER, LOGL_ERROR,
+			     "Filtering %s by imsi_deny on config nr: %d.\n", imsi, req->bsc_nr);
+			rate_ctr_inc(&bsc_lst->stats->ctr[ACC_LIST_LOCAL_FILTER]);
+			cause->cm_reject_cause = cm;
+			cause->lu_reject_cause = lu;
+			return -2;
+		}
+
+	}
+
+	/* 4. NAT deny */
+	if (nat_lst) {
+		if (lst_check_deny(nat_lst, imsi, &cm, &lu) == 0) {
+			LOGP(DFILTER, LOGL_ERROR,
+			     "Filtering %s global imsi_deny on bsc nr: %d.\n", imsi, req->bsc_nr);
+			rate_ctr_inc(&nat_lst->stats->ctr[ACC_LIST_GLOBAL_FILTER]);
+			cause->cm_reject_cause = cm;
+			cause->lu_reject_cause = lu;
+			return -3;
+		}
+	}
+
+	return 1;
+}
+
+static int _cr_check_loc_upd(void *ctx,
+			     uint8_t *data, unsigned int length,
+			     char **imsi)
+{
+	uint8_t mi_type;
+	struct gsm48_loc_upd_req *lu;
+	char mi_string[GSM48_MI_SIZE];
+
+	if (length < sizeof(*lu)) {
+		LOGP(DFILTER, LOGL_ERROR,
+		     "LU does not fit. Length is %d \n", length);
+		return -1;
+	}
+
+	lu = (struct gsm48_loc_upd_req *) data;
+	mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+	/*
+	 * We can only deal with the IMSI. This will fail for a phone that
+	 * will send the TMSI of a previous network to us.
+	 */
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+	*imsi = talloc_strdup(ctx, mi_string);
+	return 1;
+}
+
+static int _cr_check_cm_serv_req(void *ctx,
+				 uint8_t *data, unsigned int length,
+				 int *con_type, char **imsi)
+{
+	static const uint32_t classmark_offset =
+				offsetof(struct gsm48_service_request, classmark);
+
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+	int rc;
+	struct gsm48_service_request *req;
+
+	/* unfortunately in Phase1 the classmark2 length is variable */
+
+	if (length < sizeof(*req)) {
+		LOGP(DFILTER, LOGL_ERROR,
+		     "CM Serv Req does not fit. Length is %d\n", length);
+		return -1;
+	}
+
+	req = (struct gsm48_service_request *) data;
+	if (req->cm_service_type == 0x8)
+		*con_type = FLT_CON_TYPE_SSA;
+	rc = gsm48_extract_mi((uint8_t *) &req->classmark,
+			      length - classmark_offset, mi_string, &mi_type);
+	if (rc < 0) {
+		LOGP(DFILTER, LOGL_ERROR, "Failed to parse the classmark2/mi. error: %d\n", rc);
+		return -1;
+	}
+
+	/* we have to let the TMSI or such pass */
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	*imsi = talloc_strdup(ctx, mi_string);
+	return 1;
+}
+
+static int _cr_check_pag_resp(void *ctx,
+			      uint8_t *data, unsigned int length, char **imsi)
+{
+	struct gsm48_pag_resp *resp;
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+
+	if (length < sizeof(*resp)) {
+		LOGP(DFILTER, LOGL_ERROR, "PAG RESP does not fit. Length was %d.\n", length);
+		return -1;
+	}
+
+	resp = (struct gsm48_pag_resp *) data;
+	if (gsm48_paging_extract_mi(resp, length, mi_string, &mi_type) < 0) {
+		LOGP(DFILTER, LOGL_ERROR, "Failed to extract the MI.\n");
+		return -1;
+	}
+
+	/* we need to let it pass for now */
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	*imsi = talloc_strdup(ctx, mi_string);
+	return 1;
+}
+
+static int _dt_check_id_resp(struct bsc_filter_request *req,
+			     uint8_t *data, unsigned int length,
+			     struct bsc_filter_state *state,
+			     struct bsc_filter_reject_cause *cause)
+{
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+
+	if (length < 2) {
+		LOGP(DFILTER, LOGL_ERROR, "mi does not fit.\n");
+		return -1;
+	}
+
+	if (data[0] < length - 1) {
+		LOGP(DFILTER, LOGL_ERROR, "mi length too big.\n");
+		return -2;
+	}
+
+	mi_type = data[1] & GSM_MI_TYPE_MASK;
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &data[1], data[0]);
+
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	state->imsi_checked = 1;
+	state->imsi = talloc_strdup(req->ctx, mi_string);
+	return auth_imsi(req, mi_string, cause);
+}
+
+
+/* Filter out CR data... */
+int bsc_msg_filter_initial(struct gsm48_hdr *hdr48, size_t hdr48_len,
+			struct bsc_filter_request *req,
+			int *con_type,
+			char **imsi, struct bsc_filter_reject_cause *cause)
+{
+	int ret = 0;
+	uint8_t msg_type, proto;
+
+	*con_type = FLT_CON_TYPE_NONE;
+	cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	*imsi = NULL;
+
+	proto = gsm48_hdr_pdisc(hdr48);
+	msg_type = gsm48_hdr_msg_type(hdr48);
+	if (proto == GSM48_PDISC_MM &&
+	    msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) {
+		*con_type = FLT_CON_TYPE_LU;
+		ret = _cr_check_loc_upd(req->ctx, &hdr48->data[0],
+					hdr48_len - sizeof(*hdr48), imsi);
+	} else if (proto == GSM48_PDISC_MM &&
+		   msg_type == GSM48_MT_MM_CM_SERV_REQ) {
+		*con_type = FLT_CON_TYPE_CM_SERV_REQ;
+		ret = _cr_check_cm_serv_req(req->ctx, &hdr48->data[0],
+					     hdr48_len - sizeof(*hdr48),
+					     con_type, imsi);
+	} else if (proto == GSM48_PDISC_RR &&
+		   msg_type == GSM48_MT_RR_PAG_RESP) {
+		*con_type = FLT_CON_TYPE_PAG_RESP;
+		ret = _cr_check_pag_resp(req->ctx, &hdr48->data[0],
+					hdr48_len - sizeof(*hdr48), imsi);
+	} else {
+		/* We only want to filter the above, let other things pass */
+		*con_type = FLT_CON_TYPE_OTHER;
+		return 0;
+	}
+
+	/* check if we are done */
+	if (ret != 1)
+		return ret;
+
+	/* the memory allocation failed */
+	if (!*imsi)
+		return -1;
+
+	/* now check the imsi */
+	return auth_imsi(req, *imsi, cause);
+}
+
+int bsc_msg_filter_data(struct gsm48_hdr *hdr48, size_t len,
+		struct bsc_filter_request *req,
+		struct bsc_filter_state *state,
+		struct bsc_filter_reject_cause *cause)
+{
+	uint8_t msg_type, proto;
+
+	cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+
+	if (state->imsi_checked)
+		return 0;
+
+	proto = gsm48_hdr_pdisc(hdr48);
+	msg_type = gsm48_hdr_msg_type(hdr48);
+	if (proto != GSM48_PDISC_MM || msg_type != GSM48_MT_MM_ID_RESP)
+		return 0;
+
+	return _dt_check_id_resp(req, &hdr48->data[0],
+					len - sizeof(*hdr48), state, cause);
+}
diff --git a/openbsc/src/libfilter/bsc_msg_vty.c b/openbsc/src/libfilter/bsc_msg_vty.c
new file mode 100644
index 0000000..7106dea
--- /dev/null
+++ b/openbsc/src/libfilter/bsc_msg_vty.c
@@ -0,0 +1,149 @@
+/* (C) 2010-2015 by Holger Hans Peter Freyther
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/misc.h>
+
+static struct llist_head *_acc_lst;
+static void *_ctx;
+
+static void bsc_msg_acc_lst_write_one(struct vty *vty, struct bsc_msg_acc_lst *lst)
+{
+	struct bsc_msg_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (entry->imsi_allow)
+			vty_out(vty, " access-list %s imsi-allow %s%s",
+				lst->name, entry->imsi_allow, VTY_NEWLINE);
+		if (entry->imsi_deny)
+			vty_out(vty, " access-list %s imsi-deny %s %d %d%s",
+				lst->name, entry->imsi_deny,
+				entry->cm_reject_cause, entry->lu_reject_cause,
+				VTY_NEWLINE);
+	}
+}
+
+DEFUN(cfg_lst_no,
+      cfg_lst_no_cmd,
+      "no access-list NAME",
+      NO_STR "Remove an access-list by name\n"
+      "The access-list to remove\n")
+{
+	struct bsc_msg_acc_lst *acc;
+	acc = bsc_msg_acc_lst_find(_acc_lst, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	bsc_msg_acc_lst_delete(acc);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_acc_lst,
+      show_acc_lst_cmd,
+      "show access-list NAME",
+      SHOW_STR "IMSI access list\n" "Name of the access list\n")
+{
+	struct bsc_msg_acc_lst *acc;
+	acc = bsc_msg_acc_lst_find(_acc_lst, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	vty_out(vty, "access-list %s%s", acc->name, VTY_NEWLINE);
+	bsc_msg_acc_lst_write_one(vty, acc);
+	vty_out_rate_ctr_group(vty, " ", acc->stats);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_lst_imsi_allow,
+      cfg_lst_imsi_allow_cmd,
+      "access-list NAME imsi-allow [REGEXP]",
+      "Access list commands\n"
+      "Name of the access list\n"
+      "Add allowed IMSI to the list\n"
+      "Regexp for IMSIs\n")
+{
+	struct bsc_msg_acc_lst *acc;
+	struct bsc_msg_acc_lst_entry *entry;
+
+	acc = bsc_msg_acc_lst_get(_ctx, _acc_lst, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	entry = bsc_msg_acc_lst_entry_create(acc);
+	if (!entry)
+		return CMD_WARNING;
+
+	if (gsm_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, argc - 1, &argv[1]) != 0)
+		return CMD_WARNING;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_lst_imsi_deny,
+      cfg_lst_imsi_deny_cmd,
+      "access-list NAME imsi-deny [REGEXP] (<0-256>) (<0-256>)",
+      "Access list commands\n"
+      "Name of the access list\n"
+      "Add denied IMSI to the list\n"
+      "Regexp for IMSIs\n"
+      "CM Service Reject reason\n"
+      "LU Reject reason\n")
+{
+	struct bsc_msg_acc_lst *acc;
+	struct bsc_msg_acc_lst_entry *entry;
+
+	acc = bsc_msg_acc_lst_get(_ctx, _acc_lst, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	entry = bsc_msg_acc_lst_entry_create(acc);
+	if (!entry)
+		return CMD_WARNING;
+
+	if (gsm_parse_reg(acc, &entry->imsi_deny_re, &entry->imsi_deny, argc - 1, &argv[1]) != 0)
+		return CMD_WARNING;
+	if (argc >= 3)
+		entry->cm_reject_cause = atoi(argv[2]);
+	if (argc >= 4)
+		entry->lu_reject_cause = atoi(argv[3]);
+	return CMD_SUCCESS;
+}
+
+void bsc_msg_acc_lst_write(struct vty *vty)
+{
+	struct bsc_msg_acc_lst *lst;
+	llist_for_each_entry(lst, _acc_lst, list) {
+		bsc_msg_acc_lst_write_one(vty, lst);
+	}
+}
+
+void bsc_msg_acc_lst_vty_init(void *ctx, struct llist_head *lst, int node)
+{
+	_ctx = ctx;
+	_acc_lst = lst;
+	install_element_ve(&show_acc_lst_cmd);
+
+	/* access-list */
+	install_element(node, &cfg_lst_imsi_allow_cmd);
+	install_element(node, &cfg_lst_imsi_deny_cmd);
+	install_element(node, &cfg_lst_no_cmd);
+}
diff --git a/openbsc/src/libiu/Makefile.am b/openbsc/src/libiu/Makefile.am
new file mode 100644
index 0000000..e5f9e27
--- /dev/null
+++ b/openbsc/src/libiu/Makefile.am
@@ -0,0 +1,28 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(COVERAGE_CFLAGS) \
+	$(LIBCRYPTO_CFLAGS) \
+	$(LIBASN1C_CFLAGS) \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMOSIGTRAN_CFLAGS) \
+	$(LIBOSMORANAP_CFLAGS) \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libiu.a \
+	$(NULL)
+
+libiu_a_SOURCES = \
+	iu.c \
+	iu_vty.c \
+	$(NULL)
+
diff --git a/openbsc/src/libiu/iu.c b/openbsc/src/libiu/iu.c
new file mode 100644
index 0000000..b4e7ac8
--- /dev/null
+++ b/openbsc/src/libiu/iu.c
@@ -0,0 +1,757 @@
+/* Common parts of IuCS and IuPS interfaces implementation */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/prim.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/logging.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gprs/gprs_msgb.h>
+
+#include <osmocom/sigtran/sua.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/iu.h>
+#include <openbsc/debug.h>
+
+#include <pdp.h>
+
+#include <osmocom/ranap/ranap_ies_defs.h>
+#include <osmocom/ranap/ranap_common.h>
+#include <osmocom/ranap/ranap_common_cn.h>
+#include <osmocom/ranap/ranap_msg_factory.h>
+
+#include <asn1c/asn1helpers.h>
+
+/* Parsed global RNC id. See also struct RANAP_GlobalRNC_ID, and note that the
+ * PLMN identity is a BCD representation of the MCC and MNC.
+ * See iu_grnc_id_parse(). */
+struct iu_grnc_id {
+	struct osmo_plmn_id plmn;
+	uint16_t rnc_id;
+};
+
+/* A remote RNC (Radio Network Controller, like BSC but for UMTS) that has
+ * called us and is currently reachable at the given osmo_sccp_link. So, when we
+ * know a LAC for a subscriber, we can page it at the RNC matching that LAC or
+ * RAC. An HNB-GW typically presents itself as if it were a single RNC, even
+ * though it may have several RNCs in hNodeBs connected to it. Those will then
+ * share the same RNC id, which they actually receive and adopt from the HNB-GW
+ * in the HNBAP HNB REGISTER ACCEPT message. */
+struct iu_rnc {
+	struct llist_head entry;
+
+	uint16_t rnc_id;
+	uint16_t lac; /* Location Area Code (used for CS and PS) */
+	uint8_t rac; /* Routing Area Code (used for PS only) */
+	struct osmo_sccp_link *link;
+};
+
+void *talloc_iu_ctx;
+
+int asn1_xer_print = 1;
+void *talloc_asn1_ctx;
+
+iu_recv_cb_t global_iu_recv_cb = NULL;
+iu_event_cb_t global_iu_event_cb = NULL;
+
+static LLIST_HEAD(ue_conn_ctx_list);
+static LLIST_HEAD(rnc_list);
+
+const struct value_string iu_event_type_names[] = {
+	OSMO_VALUE_STRING(IU_EVENT_RAB_ASSIGN),
+	OSMO_VALUE_STRING(IU_EVENT_SECURITY_MODE_COMPLETE),
+	OSMO_VALUE_STRING(IU_EVENT_IU_RELEASE),
+	OSMO_VALUE_STRING(IU_EVENT_LINK_INVALIDATED),
+	{ 0, NULL }
+};
+
+struct ue_conn_ctx *ue_conn_ctx_alloc(struct osmo_sccp_link *link, uint32_t conn_id)
+{
+	struct ue_conn_ctx *ctx = talloc_zero(talloc_iu_ctx, struct ue_conn_ctx);
+
+	ctx->link = link;
+	ctx->conn_id = conn_id;
+	llist_add(&ctx->list, &ue_conn_ctx_list);
+
+	return ctx;
+}
+
+struct ue_conn_ctx *ue_conn_ctx_find(struct osmo_sccp_link *link,
+				     uint32_t conn_id)
+{
+	struct ue_conn_ctx *ctx;
+
+	llist_for_each_entry(ctx, &ue_conn_ctx_list, list) {
+		if (ctx->link == link && ctx->conn_id == conn_id)
+			return ctx;
+	}
+	return NULL;
+}
+
+static struct iu_rnc *iu_rnc_alloc(uint16_t rnc_id, uint16_t lac, uint8_t rac,
+				   struct osmo_sccp_link *link)
+{
+	struct iu_rnc *rnc = talloc_zero(talloc_iu_ctx, struct iu_rnc);
+
+	rnc->rnc_id = rnc_id;
+	rnc->lac = lac;
+	rnc->rac = rac;
+	rnc->link = link;
+	llist_add(&rnc->entry, &rnc_list);
+
+	LOGP(DRANAP, LOGL_NOTICE, "New RNC %d (LAC=%d RAC=%d)\n",
+	     rnc->rnc_id, rnc->lac, rnc->rac);
+
+	return rnc;
+}
+
+static struct iu_rnc *iu_rnc_register(uint16_t rnc_id, uint16_t lac,
+				      uint8_t rac, struct osmo_sccp_link *link)
+{
+	struct iu_rnc *rnc;
+	llist_for_each_entry(rnc, &rnc_list, entry) {
+		if (rnc->rnc_id != rnc_id)
+			continue;
+
+		/* We have this RNC Id registered already. Make sure that the
+		 * details match. */
+
+		/* TODO should a mismatch be an error? */
+		if (rnc->lac != lac || rnc->rac != rac)
+			LOGP(DRANAP, LOGL_NOTICE, "RNC %d changes its details:"
+			     " LAC=%d RAC=%d --> LAC=%d RAC=%d\n",
+			     rnc->rnc_id, rnc->lac, rnc->rac,
+			     lac, rac);
+		rnc->lac = lac;
+		rnc->rac = rac;
+
+		if (link && rnc->link != link)
+			LOGP(DRANAP, LOGL_NOTICE, "RNC %d on new link"
+			     " (LAC=%d RAC=%d)\n",
+			     rnc->rnc_id, rnc->lac, rnc->rac);
+		rnc->link = link;
+		return rnc;
+	}
+
+	/* Not found, make a new one. */
+	return iu_rnc_alloc(rnc_id, lac, rac, link);
+}
+
+/* Discard/invalidate all ue_conn_ctx and iu_rnc entries that reference the
+ * given link, since this link is invalid and about to be deallocated. For
+ * each ue_conn_ctx, invoke the iu_event_cb_t with IU_EVENT_LINK_INVALIDATED.
+ */
+void iu_link_del(struct osmo_sccp_link *link)
+{
+	struct iu_rnc *rnc, *rnc_next;
+	llist_for_each_entry_safe(rnc, rnc_next, &rnc_list, entry) {
+		if (!rnc->link)
+			continue;
+		if (rnc->link != link)
+			continue;
+		rnc->link = NULL;
+		llist_del(&rnc->entry);
+		talloc_free(rnc);
+	}
+
+	struct ue_conn_ctx *uec, *uec_next;
+	llist_for_each_entry_safe(uec, uec_next, &ue_conn_ctx_list, list) {
+		if (uec->link != link)
+			continue;
+		uec->link = NULL;
+		global_iu_event_cb(uec, IU_EVENT_LINK_INVALIDATED, NULL);
+	}
+}
+
+/***********************************************************************
+ * RANAP handling
+ ***********************************************************************/
+
+int iu_rab_act(struct ue_conn_ctx *ue_ctx, struct msgb *msg)
+{
+	struct osmo_scu_prim *prim;
+
+	/* wrap RANAP message in SCCP N-DATA.req */
+	prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim));
+	prim->u.data.conn_id = ue_ctx->conn_id;
+	osmo_prim_init(&prim->oph,
+		       SCCP_SAP_USER,
+		       OSMO_SCU_PRIM_N_DATA,
+		       PRIM_OP_REQUEST,
+		       msg);
+	return osmo_sua_user_link_down(ue_ctx->link, &prim->oph);
+}
+
+int iu_rab_deact(struct ue_conn_ctx *ue_ctx, uint8_t rab_id)
+{
+	/* FIXME */
+	return -1;
+}
+
+int iu_tx_sec_mode_cmd(struct ue_conn_ctx *uectx, struct gsm_auth_tuple *tp,
+		       int send_ck, int new_key)
+{
+	struct osmo_scu_prim *prim;
+	struct msgb *msg;
+	uint8_t ik[16];
+	uint8_t ck[16];
+	unsigned int i;
+
+	/* C5 function to derive IK from Kc */
+	for (i = 0; i < 4; i++)
+		ik[i] = tp->vec.kc[i] ^ tp->vec.kc[i+4];
+	memcpy(ik+4, tp->vec.kc, 8);
+	for (i = 12; i < 16; i++)
+		ik[i] = ik[i-12];
+
+	if (send_ck) {
+		/* C4 function to derive CK from Kc */
+		memcpy(ck, tp->vec.kc, 8);
+		memcpy(ck+8, tp->vec.kc, 8);
+	}
+
+	/* create RANAP message */
+	msg = ranap_new_msg_sec_mod_cmd(ik, send_ck? ck : NULL, new_key ? RANAP_KeyStatus_new : RANAP_KeyStatus_old);
+	msg->l2h = msg->data;
+	/* wrap RANAP message in SCCP N-DATA.req */
+	prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim));
+	prim->u.data.conn_id = uectx->conn_id;
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			OSMO_SCU_PRIM_N_DATA,
+			PRIM_OP_REQUEST, msg);
+	osmo_sua_user_link_down(uectx->link, &prim->oph);
+
+	return 0;
+}
+
+static int iu_grnc_id_parse(struct iu_grnc_id *dst,
+			    struct RANAP_GlobalRNC_ID *src)
+{
+	/* The size is coming from arbitrary sender, check it gracefully */
+	if (src->pLMNidentity.size != 3) {
+		LOGP(DRANAP, LOGL_ERROR, "Invalid PLMN Identity size:"
+		     " should be 3, is %d\n", src->pLMNidentity.size);
+		return -1;
+	}
+	osmo_plmn_from_bcd(&src->pLMNidentity.buf[0], &dst->plmn);
+	dst->rnc_id = (uint16_t)src->rNC_ID;
+	return 0;
+}
+
+#if 0
+ -- not used at present --
+static int iu_grnc_id_compose(struct iu_grnc_id *src,
+			      struct RANAP_GlobalRNC_ID *dst)
+{
+	/* The caller must ensure proper size */
+	OSMO_ASSERT(dst->pLMNidentity.size == 3);
+	gsm48_mcc_mnc_to_bcd(&dst->pLMNidentity.buf[0],
+			     src->mcc, src->mnc);
+	dst->rNC_ID = src->rnc_id;
+	return 0;
+}
+#endif
+
+static int ranap_handle_co_initial_ue(void *ctx, RANAP_InitialUE_MessageIEs_t *ies)
+{
+	struct ue_conn_ctx *ue_conn = ctx;
+	struct gprs_ra_id ra_id;
+	struct iu_grnc_id grnc_id;
+	uint16_t sai;
+	struct msgb *msg = msgb_alloc(256, "RANAP->NAS");
+
+	if (ranap_parse_lai(&ra_id, &ies->lai) != 0) {
+		LOGP(DRANAP, LOGL_ERROR, "Failed to parse RANAP LAI IE\n");
+		return -1;
+	}
+
+	if (ies->presenceMask & INITIALUE_MESSAGEIES_RANAP_RAC_PRESENT) {
+		ra_id.rac = asn1str_to_u8(&ies->rac);
+	}
+
+	if (iu_grnc_id_parse(&grnc_id, &ies->globalRNC_ID) != 0) {
+		LOGP(DRANAP, LOGL_ERROR,
+		     "Failed to parse RANAP Global-RNC-ID IE\n");
+		return -1;
+	}
+
+	sai = asn1str_to_u16(&ies->sai.sAC);
+	msgb_gmmh(msg) = msgb_put(msg, ies->nas_pdu.size);
+	memcpy(msgb_gmmh(msg), ies->nas_pdu.buf, ies->nas_pdu.size);
+
+	/* Make sure we know the RNC Id and LAC+RAC coming in on this connection. */
+	iu_rnc_register(grnc_id.rnc_id, ra_id.lac, ra_id.rac, ue_conn->link);
+	ue_conn->ra_id = ra_id;
+
+	/* Feed into the MM layer */
+	msg->dst = ctx;
+	global_iu_recv_cb(msg, &ra_id, &sai);
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+static int ranap_handle_co_dt(void *ctx, RANAP_DirectTransferIEs_t *ies)
+{
+	struct gprs_ra_id _ra_id, *ra_id = NULL;
+	uint16_t _sai, *sai = NULL;
+	struct msgb *msg = msgb_alloc(256, "RANAP->NAS");
+
+	if (ies->presenceMask & DIRECTTRANSFERIES_RANAP_LAI_PRESENT) {
+		if (ranap_parse_lai(&_ra_id, &ies->lai) != 0) {
+			LOGP(DRANAP, LOGL_ERROR, "Failed to parse RANAP LAI IE\n");
+			return -1;
+		}
+		ra_id = &_ra_id;
+		if (ies->presenceMask & DIRECTTRANSFERIES_RANAP_RAC_PRESENT) {
+			_ra_id.rac = asn1str_to_u8(&ies->rac);
+		}
+		if (ies->presenceMask & DIRECTTRANSFERIES_RANAP_SAI_PRESENT) {
+			_sai = asn1str_to_u16(&ies->sai.sAC);
+			sai = &_sai;
+		}
+	}
+
+	msgb_gmmh(msg) = msgb_put(msg, ies->nas_pdu.size);
+	memcpy(msgb_gmmh(msg), ies->nas_pdu.buf, ies->nas_pdu.size);
+
+	/* Feed into the MM/CC/SMS-CP layer */
+	msg->dst = ctx;
+	global_iu_recv_cb(msg, ra_id, sai);
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+static int ranap_handle_co_err_ind(void *ctx, RANAP_ErrorIndicationIEs_t *ies)
+{
+	if (ies->presenceMask & ERRORINDICATIONIES_RANAP_CAUSE_PRESENT)
+		LOGP(DRANAP, LOGL_ERROR, "Rx Error Indication (%s)\n",
+			ranap_cause_str(&ies->cause));
+	else
+		LOGP(DRANAP, LOGL_ERROR, "Rx Error Indication\n");
+
+	return 0;
+}
+
+int iu_tx(struct msgb *msg_nas, uint8_t sapi)
+{
+	struct ue_conn_ctx *uectx = msg_nas->dst;
+	struct msgb *msg;
+	struct osmo_scu_prim *prim;
+
+	LOGP(DRANAP, LOGL_INFO, "Transmitting L3 Message as RANAP DT (SUA link %p conn_id %u)\n",
+	     uectx->link, uectx->conn_id);
+
+	msg = ranap_new_msg_dt(sapi, msg_nas->data, msgb_length(msg_nas));
+	msgb_free(msg_nas);
+	msg->l2h = msg->data;
+	prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim));
+	prim->u.data.conn_id = uectx->conn_id;
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			OSMO_SCU_PRIM_N_DATA,
+			PRIM_OP_REQUEST, msg);
+	osmo_sua_user_link_down(uectx->link, &prim->oph);
+	return 0;
+}
+
+static int ranap_handle_co_iu_rel_req(struct ue_conn_ctx *ctx, RANAP_Iu_ReleaseRequestIEs_t *ies)
+{
+	struct msgb *msg;
+	struct osmo_scu_prim *prim;
+
+	LOGP(DRANAP, LOGL_INFO, "Received Iu Release Request, Sending Release Command\n");
+	msg = ranap_new_msg_iu_rel_cmd(&ies->cause);
+	msg->l2h = msg->data;
+	prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim));
+	prim->u.data.conn_id = ctx->conn_id;
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			OSMO_SCU_PRIM_N_DATA,
+			PRIM_OP_REQUEST, msg);
+	osmo_sua_user_link_down(ctx->link, &prim->oph);
+	return 0;
+}
+
+static int ranap_handle_co_rab_ass_resp(struct ue_conn_ctx *ctx, RANAP_RAB_AssignmentResponseIEs_t *ies)
+{
+	int rc = -1;
+
+	LOGP(DRANAP, LOGL_INFO,
+	     "Rx RAB Assignment Response for UE conn_id %u\n", ctx->conn_id);
+	if (ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT) {
+		/* TODO: Iterate over list of SetupOrModifiedList IEs and handle each one */
+		RANAP_IE_t *ranap_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[0];
+		RANAP_RAB_SetupOrModifiedItemIEs_t setup_ies;
+
+		rc = ranap_decode_rab_setupormodifieditemies_fromlist(&setup_ies, &ranap_ie->value);
+		if (rc) {
+			LOGP(DRANAP, LOGL_ERROR, "Error in ranap_decode_rab_setupormodifieditemies()\n");
+			return rc;
+		}
+
+		rc = global_iu_event_cb(ctx, IU_EVENT_RAB_ASSIGN, &setup_ies);
+
+		ranap_free_rab_setupormodifieditemies(&setup_ies);
+	}
+
+	return rc;
+}
+
+/* Entry point for connection-oriented RANAP message */
+static void cn_ranap_handle_co(void *ctx, ranap_message *message)
+{
+	int rc;
+
+	LOGP(DRANAP, LOGL_NOTICE, "handle_co(dir=%u, proc=%u)\n", message->direction, message->procedureCode);
+
+	switch (message->direction) {
+	case RANAP_RANAP_PDU_PR_initiatingMessage:
+		switch (message->procedureCode) {
+		case RANAP_ProcedureCode_id_InitialUE_Message:
+			rc = ranap_handle_co_initial_ue(ctx, &message->msg.initialUE_MessageIEs);
+			break;
+		case RANAP_ProcedureCode_id_DirectTransfer:
+			rc = ranap_handle_co_dt(ctx, &message->msg.directTransferIEs);
+			break;
+		case RANAP_ProcedureCode_id_ErrorIndication:
+			rc = ranap_handle_co_err_ind(ctx, &message->msg.errorIndicationIEs);
+			break;
+		case RANAP_ProcedureCode_id_Iu_ReleaseRequest:
+			/* Iu Release Request */
+			rc = ranap_handle_co_iu_rel_req(ctx, &message->msg.iu_ReleaseRequestIEs);
+			break;
+		default:
+			LOGP(DRANAP, LOGL_ERROR, "Received Initiating Message: unknown Procedure Code %d\n",
+			     message->procedureCode);
+			rc = -1;
+			break;
+		}
+		break;
+	case RANAP_RANAP_PDU_PR_successfulOutcome:
+		switch (message->procedureCode) {
+		case RANAP_ProcedureCode_id_SecurityModeControl:
+			/* Security Mode Complete */
+			rc = global_iu_event_cb(ctx, IU_EVENT_SECURITY_MODE_COMPLETE, NULL);
+			break;
+		case RANAP_ProcedureCode_id_Iu_Release:
+			/* Iu Release Complete */
+			rc = global_iu_event_cb(ctx, IU_EVENT_IU_RELEASE, NULL);
+			if (rc) {
+				LOGP(DRANAP, LOGL_ERROR, "Iu Release event: Iu Event callback returned %d\n",
+				     rc);
+			}
+			break;
+		default:
+			LOGP(DRANAP, LOGL_ERROR, "Received Successful Outcome: unknown Procedure Code %d\n",
+			     message->procedureCode);
+			rc = -1;
+			break;
+		}
+		break;
+	case RANAP_RANAP_PDU_PR_outcome:
+		switch (message->procedureCode) {
+		case RANAP_ProcedureCode_id_RAB_Assignment:
+			/* RAB Assignment Response */
+			rc = ranap_handle_co_rab_ass_resp(ctx, &message->msg.raB_AssignmentResponseIEs);
+			break;
+		default:
+			LOGP(DRANAP, LOGL_ERROR, "Received Outcome: unknown Procedure Code %d\n",
+			     message->procedureCode);
+			rc = -1;
+			break;
+		}
+		break;
+	case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
+	default:
+		LOGP(DRANAP, LOGL_ERROR, "Received Unsuccessful Outcome: Procedure Code %d\n",
+		     message->procedureCode);
+		rc = -1;
+		break;
+	}
+
+	if (rc) {
+		LOGP(DRANAP, LOGL_ERROR, "Error in cn_ranap_handle_co (%d)\n",
+		     rc);
+		/* TODO handling of the error? */
+	}
+}
+
+static int ranap_handle_cl_reset_req(void *ctx, RANAP_ResetIEs_t *ies)
+{
+	/* FIXME: send reset response */
+	return -1;
+}
+
+static int ranap_handle_cl_err_ind(void *ctx, RANAP_ErrorIndicationIEs_t *ies)
+{
+	if (ies->presenceMask & ERRORINDICATIONIES_RANAP_CAUSE_PRESENT)
+		LOGP(DRANAP, LOGL_ERROR, "Rx Error Indication (%s)\n",
+			ranap_cause_str(&ies->cause));
+	else
+		LOGP(DRANAP, LOGL_ERROR, "Rx Error Indication\n");
+
+	return 0;
+}
+
+/* Entry point for connection-less RANAP message */
+static void cn_ranap_handle_cl(void *ctx, ranap_message *message)
+{
+	int rc;
+
+	switch (message->direction) {
+	case RANAP_RANAP_PDU_PR_initiatingMessage:
+		switch (message->procedureCode) {
+		case RANAP_ProcedureCode_id_Reset:
+			/* received reset.req, send reset.resp */
+			rc = ranap_handle_cl_reset_req(ctx, &message->msg.resetIEs);
+			break;
+		case RANAP_ProcedureCode_id_ErrorIndication:
+			rc = ranap_handle_cl_err_ind(ctx, &message->msg.errorIndicationIEs);
+			break;
+		default:
+			rc = -1;
+			break;
+		}
+		break;
+	case RANAP_RANAP_PDU_PR_successfulOutcome:
+	case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
+	case RANAP_RANAP_PDU_PR_outcome:
+	default:
+		rc = -1;
+		break;
+	}
+
+	if (rc) {
+		LOGP(DRANAP, LOGL_ERROR, "Error in cn_ranap_handle_cl (%d)\n",
+		     rc);
+		/* TODO handling of the error? */
+	}
+}
+
+/***********************************************************************
+ * Paging
+ ***********************************************************************/
+
+/* Send a paging command down a given SUA link. tmsi and paging_cause are
+ * optional and may be passed NULL and 0, respectively, to disable their use.
+ * See enum RANAP_PagingCause.
+ *
+ * If TMSI is given, the IMSI is not sent over the air interface. Nevertheless,
+ * the IMSI is still required for resolution in the HNB-GW and/or(?) RNC. */
+static int iu_tx_paging_cmd(struct osmo_sccp_link *link,
+			    const char *imsi, const uint32_t *tmsi,
+			    bool is_ps, uint32_t paging_cause)
+{
+	struct msgb *msg;
+	msg = ranap_new_msg_paging_cmd(imsi, tmsi, is_ps? 1 : 0, paging_cause);
+	msg->l2h = msg->data;
+	return osmo_sccp_tx_unitdata_ranap(link, 1, 2, msg->data,
+					   msgb_length(msg));
+}
+
+static int iu_page(const char *imsi, const uint32_t *tmsi_or_ptimsi,
+		   uint16_t lac, uint8_t rac, bool is_ps)
+{
+	struct iu_rnc *rnc;
+	int pagings_sent = 0;
+
+	if (tmsi_or_ptimsi) {
+		LOGP(DRANAP, LOGL_DEBUG, "%s: Looking for RNCs to page for IMSI %s"
+		     " (paging will use %s %x)\n",
+		     is_ps? "IuPS" : "IuCS",
+		     imsi,
+		     is_ps? "PTMSI" : "TMSI",
+		     *tmsi_or_ptimsi);
+	} else {
+		LOGP(DRANAP, LOGL_DEBUG, "%s: Looking for RNCs to page for IMSI %s"
+		     " (paging will use IMSI)\n",
+		     is_ps? "IuPS" : "IuCS",
+		     imsi
+		    );
+	}
+
+	llist_for_each_entry(rnc, &rnc_list, entry) {
+		if (!rnc->link) {
+			/* Not actually connected, don't count it. */
+			continue;
+		}
+		if (rnc->lac != lac)
+			continue;
+		if (is_ps && rnc->rac != rac)
+			continue;
+
+		/* Found a match! */
+		if (iu_tx_paging_cmd(rnc->link, imsi, tmsi_or_ptimsi, is_ps, 0)
+		    == 0) {
+			LOGP(DRANAP, LOGL_DEBUG,
+			     "%s: Paged for IMSI %s on RNC %d, on SUA link %p\n",
+			     is_ps? "IuPS" : "IuCS",
+			     imsi, rnc->rnc_id, rnc->link);
+			pagings_sent ++;
+		}
+	}
+
+	/* Some logging... */
+	if (pagings_sent > 0) {
+		LOGP(DRANAP, LOGL_DEBUG,
+		     "%s: %d RNCs were paged for IMSI %s.\n",
+		     is_ps? "IuPS" : "IuCS",
+		     pagings_sent, imsi);
+	}
+	else {
+		if (is_ps) {
+			LOGP(DRANAP, LOGL_ERROR, "IuPS: Found no RNC to page for"
+			     " LAC %d RAC %d (would have paged IMSI %s)\n",
+			     lac, rac, imsi);
+		}
+		else {
+			LOGP(DRANAP, LOGL_ERROR, "IuCS: Found no RNC to page for"
+			     " LAC %d (would have paged IMSI %s)\n",
+			     lac, imsi);
+		}
+	}
+
+	return pagings_sent;
+}
+
+int iu_page_cs(const char *imsi, const uint32_t *tmsi, uint16_t lac)
+{
+	return iu_page(imsi, tmsi, lac, 0, false);
+}
+
+int iu_page_ps(const char *imsi, const uint32_t *ptmsi, uint16_t lac, uint8_t rac)
+{
+	return iu_page(imsi, ptmsi, lac, rac, true);
+}
+
+
+/***********************************************************************
+ *
+ ***********************************************************************/
+
+int tx_unitdata(struct osmo_sccp_link *link);
+int tx_conn_req(struct osmo_sccp_link *link, uint32_t conn_id);
+
+struct osmo_prim_hdr *make_conn_req(uint32_t conn_id);
+struct osmo_prim_hdr *make_dt1_req(uint32_t conn_id, const uint8_t *data, unsigned int len);
+
+struct osmo_prim_hdr *make_conn_resp(struct osmo_scu_connect_param *param)
+{
+	struct msgb *msg = msgb_alloc(1024, "conn_resp");
+	struct osmo_scu_prim *prim;
+
+	prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim));
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			OSMO_SCU_PRIM_N_CONNECT,
+			PRIM_OP_RESPONSE, msg);
+	memcpy(&prim->u.connect, param, sizeof(prim->u.connect));
+	return &prim->oph;
+}
+
+static int sccp_sap_up(struct osmo_prim_hdr *oph, void *link)
+{
+	struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
+	struct osmo_prim_hdr *resp = NULL;
+	int rc;
+	struct ue_conn_ctx *ue;
+
+	DEBUGP(DRANAP, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph));
+
+	switch (OSMO_PRIM_HDR(oph)) {
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
+		/* confirmation of outbound connection */
+		rc = -1;
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
+		/* indication of new inbound connection request*/
+		DEBUGP(DRANAP, "N-CONNECT.ind(X->%u)\n", prim->u.connect.conn_id);
+		if (/*  prim->u.connect.called_addr.ssn != OSMO_SCCP_SSN_RANAP || */
+		    !msgb_l2(oph->msg) || msgb_l2len(oph->msg) == 0) {
+			LOGP(DRANAP, LOGL_NOTICE,
+			     "Received invalid N-CONNECT.ind\n");
+			return 0;
+		}
+		ue = ue_conn_ctx_alloc(link, prim->u.connect.conn_id);
+		/* first ensure the local SUA/SCCP socket is ACTIVE */
+		resp = make_conn_resp(&prim->u.connect);
+		osmo_sua_user_link_down(link, resp);
+		/* then handle the RANAP payload */
+		rc = ranap_cn_rx_co(cn_ranap_handle_co, ue, msgb_l2(oph->msg), msgb_l2len(oph->msg));
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
+		/* indication of disconnect */
+		DEBUGP(DRANAP, "N-DISCONNECT.ind(%u)\n",
+		       prim->u.disconnect.conn_id);
+		ue = ue_conn_ctx_find(link, prim->u.disconnect.conn_id);
+		rc = ranap_cn_rx_co(cn_ranap_handle_co, ue, msgb_l2(oph->msg), msgb_l2len(oph->msg));
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
+		/* connection-oriented data received */
+		DEBUGP(DRANAP, "N-DATA.ind(%u, %s)\n", prim->u.data.conn_id,
+		       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+		/* resolve UE context */
+		ue = ue_conn_ctx_find(link, prim->u.data.conn_id);
+		rc = ranap_cn_rx_co(cn_ranap_handle_co, ue, msgb_l2(oph->msg), msgb_l2len(oph->msg));
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
+		/* connection-less data received */
+		DEBUGP(DRANAP, "N-UNITDATA.ind(%s)\n",
+		       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+		rc = ranap_cn_rx_cl(cn_ranap_handle_cl, link, msgb_l2(oph->msg), msgb_l2len(oph->msg));
+		break;
+	default:
+		rc = -1;
+		break;
+	}
+
+	msgb_free(oph->msg);
+	return rc;
+}
+
+int iu_init(void *ctx, const char *listen_addr, uint16_t listen_port,
+	    iu_recv_cb_t iu_recv_cb, iu_event_cb_t iu_event_cb)
+{
+	struct osmo_sccp_user *user;
+	talloc_iu_ctx = talloc_named_const(ctx, 1, "iu");
+	talloc_asn1_ctx = talloc_named_const(talloc_iu_ctx, 1, "asn1");
+
+	global_iu_recv_cb = iu_recv_cb;
+	global_iu_event_cb = iu_event_cb;
+	osmo_sua_set_log_area(DSUA);
+	user = osmo_sua_user_create(talloc_iu_ctx, sccp_sap_up, talloc_iu_ctx);
+	return osmo_sua_server_listen(user, listen_addr, listen_port);
+}
+
diff --git a/openbsc/src/libiu/iu_vty.c b/openbsc/src/libiu/iu_vty.c
new file mode 100644
index 0000000..91eed96
--- /dev/null
+++ b/openbsc/src/libiu/iu_vty.c
@@ -0,0 +1,50 @@
+/* OpenBSC Iu related interface to quagga VTY */
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+
+/* Pointer to the actual asn_debug value as passed from main scopes. */
+static int *g_asn_debug_p = NULL;
+
+DEFUN(logging_asn_debug,
+      logging_asn_debug_cmd,
+      "logging asn1-debug (1|0)",
+      LOGGING_STR
+      "Log human readable representations of all ASN.1 messages to stderr\n"
+      "Log decoded ASN.1 messages to stderr\n"
+      "Do not log decoded ASN.1 messages to stderr\n")
+{
+	if (!g_asn_debug_p) {
+		vty_out(vty, "%%ASN.1 debugging not available%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	*g_asn_debug_p = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+void iu_vty_init(int *asn_debug_p)
+{
+	g_asn_debug_p = asn_debug_p;
+
+	install_element(CFG_LOG_NODE, &logging_asn_debug_cmd);
+}
diff --git a/openbsc/src/libmgcp/Makefile.am b/openbsc/src/libmgcp/Makefile.am
new file mode 100644
index 0000000..5faf602
--- /dev/null
+++ b/openbsc/src/libmgcp/Makefile.am
@@ -0,0 +1,43 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(LIBBCG729_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMONETIF_LIBS) \
+	$(COVERAGE_LDFLAGS) \
+	$(LIBBCG729_LIBS) \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libmgcp.a \
+	$(NULL)
+
+noinst_HEADERS = \
+	g711common.h \
+	$(NULL)
+
+libmgcp_a_SOURCES = \
+	mgcp_protocol.c \
+	mgcp_network.c \
+	mgcp_vty.c \
+	mgcp_osmux.c \
+	mgcp_sdp.c \
+	$(NULL)
+if BUILD_MGCP_TRANSCODING
+libmgcp_a_SOURCES += \
+	mgcp_transcode.c \
+	$(NULL)
+endif
diff --git a/openbsc/src/libmgcp/g711common.h b/openbsc/src/libmgcp/g711common.h
new file mode 100644
index 0000000..cb35fc6
--- /dev/null
+++ b/openbsc/src/libmgcp/g711common.h
@@ -0,0 +1,187 @@
+/*
+ *  PCM - A-Law conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *  Wrapper for linphone Codec class by Simon Morlat <simon.morlat@linphone.org>
+ *
+ *
+ *  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 2 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+static inline int val_seg(int val)
+{
+	int r = 0;
+	val >>= 7; /*7 = 4 + 3*/
+	if (val & 0xf0) {
+		val >>= 4;
+		r += 4;
+	}
+	if (val & 0x0c) {
+		val >>= 2;
+		r += 2;
+	}
+	if (val & 0x02)
+		r += 1;
+	return r;
+}
+
+/*
+ * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
+ *
+ * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data.
+ *
+ *		Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	0000000wxyza			000wxyz
+ *	0000001wxyza			001wxyz
+ *	000001wxyzab			010wxyz
+ *	00001wxyzabc			011wxyz
+ *	0001wxyzabcd			100wxyz
+ *	001wxyzabcde			101wxyz
+ *	01wxyzabcdef			110wxyz
+ *	1wxyzabcdefg			111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ * G711 is designed for 13 bits input signal, this function add extra shifting to take this into account.
+ */
+
+static inline unsigned char s16_to_alaw(int pcm_val)
+{
+	int		mask;
+	int		seg;
+	unsigned char	aval;
+
+	if (pcm_val >= 0) {
+		mask = 0xD5;
+	} else {
+		mask = 0x55;
+		pcm_val = -pcm_val;
+		if (pcm_val > 0x7fff)
+			pcm_val = 0x7fff;
+	}
+
+	if (pcm_val < 256) /*256 = 32 << 3*/
+		aval = pcm_val >> 4; /*4 = 1 + 3*/
+	else {
+		/* Convert the scaled magnitude to segment number. */
+		seg = val_seg(pcm_val);
+		aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+	}
+	return aval ^ mask;
+}
+
+/*
+ * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM
+ *
+ */
+static inline int alaw_to_s16(unsigned char a_val)
+{
+	int		t;
+	int		seg;
+
+	a_val ^= 0x55;
+	t = a_val & 0x7f;
+	if (t < 16)
+		t = (t << 4) + 8;
+	else {
+		seg = (t >> 4) & 0x07;
+		t = ((t & 0x0f) << 4) + 0x108;
+		t <<= seg -1;
+	}
+	return ((a_val & 0x80) ? t : -t);
+}
+/*
+ * s16_to_ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ *	Biased Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	00000001wxyza			000wxyz
+ *	0000001wxyzab			001wxyz
+ *	000001wxyzabc			010wxyz
+ *	00001wxyzabcd			011wxyz
+ *	0001wxyzabcde			100wxyz
+ *	001wxyzabcdef			101wxyz
+ *	01wxyzabcdefg			110wxyz
+ *	1wxyzabcdefgh			111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz.  * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+
+static inline unsigned char s16_to_ulaw(int pcm_val)	/* 2's complement (16-bit range) */
+{
+	int mask;
+	int seg;
+	unsigned char uval;
+
+	if (pcm_val < 0) {
+		pcm_val = 0x84 - pcm_val;
+		mask = 0x7f;
+	} else {
+		pcm_val += 0x84;
+		mask = 0xff;
+	}
+	if (pcm_val > 0x7fff)
+		pcm_val = 0x7fff;
+
+	/* Convert the scaled magnitude to segment number. */
+	seg = val_seg(pcm_val);
+
+	/*
+	 * Combine the sign, segment, quantization bits;
+	 * and complement the code word.
+	 */
+	uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+	return uval ^ mask;
+}
+
+/*
+ * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+static inline int ulaw_to_s16(unsigned char u_val)
+{
+	int t;
+
+	/* Complement to obtain normal u-law value. */
+	u_val = ~u_val;
+
+	/*
+	 * Extract and bias the quantization bits. Then
+	 * shift up by the segment number and subtract out the bias.
+	 */
+	t = ((u_val & 0x0f) << 3) + 0x84;
+	t <<= (u_val & 0x70) >> 4;
+
+	return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
+}
diff --git a/openbsc/src/libmgcp/mgcp_network.c b/openbsc/src/libmgcp/mgcp_network.c
new file mode 100644
index 0000000..49500d9
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_network.c
@@ -0,0 +1,1100 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/netif/rtp.h>
+
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <openbsc/osmux.h>
+
+#warning "Make use of the rtp proxy code"
+
+
+#define RTP_SEQ_MOD		(1 << 16)
+#define RTP_MAX_DROPOUT		3000
+#define RTP_MAX_MISORDER	100
+#define RTP_BUF_SIZE		4096
+
+enum {
+	MGCP_PROTO_RTP,
+	MGCP_PROTO_RTCP,
+};
+
+/**
+ * This does not need to be a precision timestamp and
+ * is allowed to wrap quite fast. The returned value is
+ * 1/unit seconds.
+ */
+static uint32_t get_current_ts(unsigned unit)
+{
+	struct timespec tp;
+	uint64_t ret;
+
+	if (!unit)
+		return 0;
+
+	memset(&tp, 0, sizeof(tp));
+	if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
+		LOGP(DMGCP, LOGL_NOTICE,
+			"Getting the clock failed.\n");
+
+	/* convert it to 1/unit seconds */
+	ret = tp.tv_sec;
+	ret *= unit;
+	ret += (int64_t)tp.tv_nsec * unit / 1000 / 1000 / 1000;
+
+	return ret;
+}
+
+int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
+{
+	struct sockaddr_in out;
+	out.sin_family = AF_INET;
+	out.sin_port = port;
+	memcpy(&out.sin_addr, addr, sizeof(*addr));
+
+	return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out));
+}
+
+int mgcp_send_dummy(struct mgcp_endpoint *endp)
+{
+	static char buf[] = { MGCP_DUMMY_LOAD };
+	int rc;
+	int was_rtcp = 0;
+
+	rc = mgcp_udp_send(endp->net_end.rtp.fd, &endp->net_end.addr,
+			   endp->net_end.rtp_port, buf, 1);
+
+	if (rc == -1)
+		goto failed;
+
+	if (endp->tcfg->omit_rtcp)
+		return rc;
+
+	was_rtcp = 1;
+	rc = mgcp_udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr,
+			   endp->net_end.rtcp_port, buf, 1);
+
+	if (rc >= 0)
+		return rc;
+
+failed:
+	LOGP(DMGCP, LOGL_ERROR,
+		"Failed to send dummy %s packet: %s on: 0x%x to %s:%d\n",
+		was_rtcp ? "RTCP" : "RTP",
+		strerror(errno), ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr),
+		was_rtcp ? endp->net_end.rtcp_port : endp->net_end.rtp_port);
+
+	return -1;
+}
+
+static int32_t compute_timestamp_aligment_error(struct mgcp_rtp_stream_state *sstate,
+						int ptime, uint32_t timestamp)
+{
+	int32_t timestamp_delta;
+
+	if (ptime == 0)
+		return 0;
+
+	/* Align according to: T - Tlast = k * Tptime */
+	timestamp_delta = timestamp - sstate->last_timestamp;
+
+	return timestamp_delta % ptime;
+}
+
+static int check_rtp_timestamp(struct mgcp_endpoint *endp,
+			       struct mgcp_rtp_state *state,
+			       struct mgcp_rtp_stream_state *sstate,
+			       struct mgcp_rtp_end *rtp_end,
+			       struct sockaddr_in *addr,
+			       uint16_t seq, uint32_t timestamp,
+			       const char *text, int32_t *tsdelta_out)
+{
+	int32_t tsdelta;
+	int32_t timestamp_error;
+
+	/* Not fully intialized, skip */
+	if (sstate->last_tsdelta == 0 && timestamp == sstate->last_timestamp)
+		return 0;
+
+	if (seq == sstate->last_seq) {
+		if (timestamp != sstate->last_timestamp) {
+			sstate->err_ts_counter += 1;
+			LOGP(DMGCP, LOGL_ERROR,
+			     "The %s timestamp delta is != 0 but the sequence "
+			     "number %d is the same, "
+			     "TS offset: %d, SeqNo offset: %d "
+			     "on 0x%x SSRC: %u timestamp: %u "
+			     "from %s:%d in %d\n",
+			     text, seq,
+			     state->timestamp_offset, state->seq_offset,
+			     ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+			     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			     endp->conn_mode);
+		}
+		return 0;
+	}
+
+	tsdelta =
+		(int32_t)(timestamp - sstate->last_timestamp) /
+		(int16_t)(seq - sstate->last_seq);
+
+	if (tsdelta == 0) {
+		/* Don't update *tsdelta_out */
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "The %s timestamp delta is %d "
+		     "on 0x%x SSRC: %u timestamp: %u "
+		     "from %s:%d in %d\n",
+		     text, tsdelta,
+		     ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+		     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+		     endp->conn_mode);
+
+		return 0;
+	}
+
+	if (sstate->last_tsdelta != tsdelta) {
+		if (sstate->last_tsdelta) {
+			LOGP(DMGCP, LOGL_INFO,
+			     "The %s timestamp delta changes from %d to %d "
+			     "on 0x%x SSRC: %u timestamp: %u from %s:%d in %d\n",
+			     text, sstate->last_tsdelta, tsdelta,
+			     ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+			     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			     endp->conn_mode);
+		}
+	}
+
+	if (tsdelta_out)
+		*tsdelta_out = tsdelta;
+
+	timestamp_error =
+		compute_timestamp_aligment_error(sstate, state->packet_duration,
+						 timestamp);
+
+	if (timestamp_error) {
+		sstate->err_ts_counter += 1;
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "The %s timestamp has an alignment error of %d "
+		     "on 0x%x SSRC: %u "
+		     "SeqNo delta: %d, TS delta: %d, dTS/dSeq: %d "
+		     "from %s:%d in mode %d. ptime: %d\n",
+		     text, timestamp_error,
+		     ENDPOINT_NUMBER(endp), sstate->ssrc,
+		     (int16_t)(seq - sstate->last_seq),
+		     (int32_t)(timestamp - sstate->last_timestamp),
+		     tsdelta,
+		     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+		     endp->conn_mode, state->packet_duration);
+	}
+	return 1;
+}
+
+/* Set the timestamp offset according to the packet duration. */
+static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp,
+				       struct mgcp_rtp_state *state,
+				       struct mgcp_rtp_end *rtp_end,
+				       struct sockaddr_in *addr,
+				       int16_t delta_seq, uint32_t in_timestamp)
+{
+	int32_t tsdelta = state->packet_duration;
+	int timestamp_offset;
+	uint32_t out_timestamp;
+
+	if (tsdelta == 0) {
+		tsdelta = state->out_stream.last_tsdelta;
+		if (tsdelta != 0) {
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "A fixed packet duration is not available on 0x%x, "
+			     "using last output timestamp delta instead: %d "
+			     "from %s:%d in %d\n",
+			     ENDPOINT_NUMBER(endp), tsdelta,
+			     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			     endp->conn_mode);
+		} else {
+			tsdelta = rtp_end->codec.rate * 20 / 1000;
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "Fixed packet duration and last timestamp delta "
+			     "are not available on 0x%x, "
+			     "using fixed 20ms instead: %d "
+			     "from %s:%d in %d\n",
+			     ENDPOINT_NUMBER(endp), tsdelta,
+			     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			     endp->conn_mode);
+		}
+	}
+
+	out_timestamp = state->out_stream.last_timestamp + delta_seq * tsdelta;
+	timestamp_offset = out_timestamp - in_timestamp;
+
+	if (state->timestamp_offset != timestamp_offset) {
+		state->timestamp_offset = timestamp_offset;
+
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "Timestamp offset change on 0x%x SSRC: %u "
+		     "SeqNo delta: %d, TS offset: %d, "
+		     "from %s:%d in %d\n",
+		     ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+		     delta_seq, state->timestamp_offset,
+		     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+		     endp->conn_mode);
+	}
+
+	return timestamp_offset;
+}
+
+/* Set the timestamp offset according to the packet duration. */
+static int align_rtp_timestamp_offset(struct mgcp_endpoint *endp,
+				      struct mgcp_rtp_state *state,
+				      struct mgcp_rtp_end *rtp_end,
+				      struct sockaddr_in *addr,
+				      uint32_t timestamp)
+{
+	int timestamp_error = 0;
+	int ptime = state->packet_duration;
+
+	/* Align according to: T + Toffs - Tlast = k * Tptime */
+
+	timestamp_error = compute_timestamp_aligment_error(
+		&state->out_stream, ptime,
+		timestamp + state->timestamp_offset);
+
+	if (timestamp_error) {
+		state->timestamp_offset += ptime - timestamp_error;
+
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "Corrected timestamp alignment error of %d on 0x%x SSRC: %u "
+		     "new TS offset: %d, "
+		     "from %s:%d in %d\n",
+		     timestamp_error,
+		     ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+		     state->timestamp_offset, inet_ntoa(addr->sin_addr),
+		     ntohs(addr->sin_port), endp->conn_mode);
+	}
+
+	OSMO_ASSERT(compute_timestamp_aligment_error(&state->out_stream, ptime,
+						     timestamp + state->timestamp_offset) == 0);
+
+	return timestamp_error;
+}
+
+int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end,
+				char *data, int *len, int buf_size)
+{
+	return 0;
+}
+
+int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp,
+				      struct mgcp_rtp_end *dst_end,
+				      struct mgcp_rtp_end *src_end)
+{
+	return 0;
+}
+
+void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**audio_name,
+					  const char**fmtp_extra)
+{
+	/* Use the BTS side parameters when passing the SDP data (for
+	 * downlink) to the net peer.
+	 */
+	*payload_type = endp->bts_end.codec.payload_type;
+	*audio_name = endp->bts_end.codec.audio_name;
+	*fmtp_extra = endp->bts_end.fmtp_extra;
+}
+
+
+void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
+			const uint16_t seq, const int32_t transit,
+			const uint32_t ssrc)
+{
+	int32_t d;
+
+	/* initialize or re-initialize */
+	if (!state->stats_initialized || state->stats_ssrc != ssrc) {
+		state->stats_initialized = 1;
+		state->stats_base_seq = seq;
+		state->stats_max_seq = seq - 1;
+		state->stats_ssrc = ssrc;
+		state->stats_jitter = 0;
+		state->stats_transit = transit;
+		state->stats_cycles = 0;
+	} else {
+		uint16_t udelta;
+
+		/*
+		 * The below takes the shape of the validation of
+		 * Appendix A. Check if there is something weird with
+		 * the sequence number, otherwise check for a wrap
+		 * around in the sequence number.
+		 * It can't wrap during the initialization so let's
+		 * skip it here. The Appendix A probably doesn't have
+		 * this issue because of the probation.
+		 */
+		udelta = seq - state->stats_max_seq;
+		if (udelta < RTP_MAX_DROPOUT) {
+			if (seq < state->stats_max_seq)
+				state->stats_cycles += RTP_SEQ_MOD;
+		} else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) {
+			LOGP(DMGCP, LOGL_NOTICE,
+				"RTP seqno made a very large jump on 0x%x delta: %u\n",
+				ENDPOINT_NUMBER(endp), udelta);
+		}
+	}
+
+	/*
+	 * Calculate the jitter between the two packages. The TS should be
+	 * taken closer to the read function. This was taken from the
+	 * Appendix A of RFC 3550. Timestamp and arrival_time have a 1/rate
+	 * resolution.
+	 */
+	d = transit - state->stats_transit;
+	state->stats_transit = transit;
+	if (d < 0)
+		d = -d;
+	state->stats_jitter += d - ((state->stats_jitter + 8) >> 4);
+	state->stats_max_seq = seq;
+}
+
+
+
+/**
+ * The RFC 3550 Appendix A assumes there are multiple sources but
+ * some of the supported endpoints (e.g. the nanoBTS) can only handle
+ * one source and this code will patch RTP header to appear as if there
+ * is only one source.
+ * There is also no probation period for new sources. Every RTP header
+ * we receive will be seen as a switch in streams.
+ */
+void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
+			  struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr,
+			  char *data, int len)
+{
+	uint32_t arrival_time;
+	int32_t transit;
+	uint16_t seq;
+	uint32_t timestamp, ssrc;
+	struct rtp_hdr *rtp_hdr;
+	int payload = rtp_end->codec.payload_type;
+
+	if (len < sizeof(*rtp_hdr))
+		return;
+
+	rtp_hdr = (struct rtp_hdr *) data;
+	seq = ntohs(rtp_hdr->sequence);
+	timestamp = ntohl(rtp_hdr->timestamp);
+	arrival_time = get_current_ts(rtp_end->codec.rate);
+	ssrc = ntohl(rtp_hdr->ssrc);
+	transit = arrival_time - timestamp;
+
+	mgcp_rtp_annex_count(endp, state, seq, transit, ssrc);
+
+	if (!state->initialized) {
+		state->initialized = 1;
+		state->in_stream.last_seq = seq - 1;
+		state->in_stream.ssrc = state->orig_ssrc = ssrc;
+		state->in_stream.last_tsdelta = 0;
+		state->packet_duration = mgcp_rtp_packet_duration(endp, rtp_end);
+		state->out_stream = state->in_stream;
+		state->out_stream.last_timestamp = timestamp;
+		state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */
+		LOGP(DMGCP, LOGL_INFO,
+			"Initializing stream on 0x%x SSRC: %u timestamp: %u "
+			"pkt-duration: %d, from %s:%d in %d\n",
+			ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+			state->seq_offset, state->packet_duration,
+			inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			endp->conn_mode);
+		if (state->packet_duration == 0) {
+			state->packet_duration = rtp_end->codec.rate * 20 / 1000;
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "Fixed packet duration is not available on 0x%x, "
+			     "using fixed 20ms instead: %d from %s:%d in %d\n",
+			     ENDPOINT_NUMBER(endp), state->packet_duration,
+			     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			     endp->conn_mode);
+		}
+	} else if (state->in_stream.ssrc != ssrc) {
+		LOGP(DMGCP, LOGL_NOTICE,
+			"The SSRC changed on 0x%x: %u -> %u  "
+			"from %s:%d in %d\n",
+			ENDPOINT_NUMBER(endp),
+			state->in_stream.ssrc, rtp_hdr->ssrc,
+			inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			endp->conn_mode);
+
+		state->in_stream.ssrc = ssrc;
+		if (rtp_end->force_constant_ssrc) {
+			int16_t delta_seq;
+
+			/* Always increment seqno by 1 */
+			state->seq_offset =
+				(state->out_stream.last_seq + 1) - seq;
+
+			/* Estimate number of packets that would have been sent */
+			delta_seq =
+				(arrival_time - state->in_stream.last_arrival_time
+				 + state->packet_duration/2) /
+				state->packet_duration;
+
+			adjust_rtp_timestamp_offset(endp, state, rtp_end, addr,
+						    delta_seq, timestamp);
+
+			state->patch_ssrc = 1;
+			ssrc = state->orig_ssrc;
+			if (rtp_end->force_constant_ssrc != -1)
+				rtp_end->force_constant_ssrc -= 1;
+
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "SSRC patching enabled on 0x%x SSRC: %u "
+			     "SeqNo offset: %d, TS offset: %d "
+			     "from %s:%d in %d\n",
+			     ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+			     state->seq_offset, state->timestamp_offset,
+			     inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			     endp->conn_mode);
+		}
+
+		state->in_stream.last_tsdelta = 0;
+	} else {
+		/* Compute current per-packet timestamp delta */
+		check_rtp_timestamp(endp, state, &state->in_stream, rtp_end, addr,
+				    seq, timestamp, "input",
+				    &state->in_stream.last_tsdelta);
+
+		if (state->patch_ssrc)
+			ssrc = state->orig_ssrc;
+	}
+
+	/* Save before patching */
+	state->in_stream.last_timestamp = timestamp;
+	state->in_stream.last_seq = seq;
+	state->in_stream.last_arrival_time = arrival_time;
+
+	if (rtp_end->force_aligned_timing &&
+	    state->out_stream.ssrc == ssrc && state->packet_duration)
+		/* Align the timestamp offset */
+		align_rtp_timestamp_offset(endp, state, rtp_end, addr, timestamp);
+
+	/* Store the updated SSRC back to the packet */
+	if (state->patch_ssrc)
+		rtp_hdr->ssrc = htonl(ssrc);
+
+	/* Apply the offset and store it back to the packet.
+	 * This won't change anything if the offset is 0, so the conditional is
+	 * omitted. */
+	seq += state->seq_offset;
+	rtp_hdr->sequence = htons(seq);
+	timestamp += state->timestamp_offset;
+	rtp_hdr->timestamp = htonl(timestamp);
+
+	/* Check again, whether the timestamps are still valid */
+	if (state->out_stream.ssrc == ssrc)
+		check_rtp_timestamp(endp, state, &state->out_stream, rtp_end,
+				    addr, seq, timestamp, "output",
+				    &state->out_stream.last_tsdelta);
+
+	/* Save output values */
+	state->out_stream.last_seq = seq;
+	state->out_stream.last_timestamp = timestamp;
+	state->out_stream.ssrc = ssrc;
+
+	if (payload < 0)
+		return;
+
+	rtp_hdr->payload_type = payload;
+}
+
+/*
+ * The below code is for dispatching. We have a dedicated port for
+ * the data coming from the net and one to discover the BTS.
+ */
+static int forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, int len)
+{
+	if (!tap->enabled)
+		return 0;
+
+	return sendto(fd, buf, len, 0,
+		      (struct sockaddr *)&tap->forward, sizeof(tap->forward));
+}
+
+static int mgcp_send_transcoder(struct mgcp_rtp_end *end,
+				struct mgcp_config *cfg, int is_rtp,
+				const char *buf, int len)
+{
+	int rc;
+	int port;
+	struct sockaddr_in addr;
+
+	port = is_rtp ? end->rtp_port : end->rtcp_port;
+
+	addr.sin_family = AF_INET;
+	addr.sin_addr = cfg->transcoder_in;
+	addr.sin_port = port;
+
+	rc = sendto(is_rtp ?
+		end->rtp.fd :
+		end->rtcp.fd, buf, len, 0,
+		(struct sockaddr *) &addr, sizeof(addr));
+
+	if (rc != len)
+		LOGP(DMGCP, LOGL_ERROR,
+			"Failed to send data to the transcoder: %s\n",
+			strerror(errno));
+
+	return rc;
+}
+
+void mgcp_dejitter_udp_send(struct msgb *msg, void *data)
+{
+	struct mgcp_rtp_end *rtp_end = (struct mgcp_rtp_end *) data;
+
+	int rc = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr,
+			   rtp_end->rtp_port, (char*) msg->data, msg->len);
+	if (rc != msg->len)
+		LOGP(DMGCP, LOGL_ERROR,
+			"Failed to send data after jitter buffer: %d\n", rc);
+	msgb_free(msg);
+}
+
+static int enqueue_dejitter(struct osmo_jibuf *jb, struct mgcp_rtp_end *rtp_end, char *buf, int len)
+{
+	struct msgb *msg;
+	msg = msgb_alloc(len, "mgcp-jibuf");
+	if (!msg)
+		return -1;
+
+	memcpy(msg->data, buf, len);
+	msgb_put(msg, len);
+
+	if (osmo_jibuf_enqueue(jb, msg) < 0) {
+		rtp_end->dropped_packets += 1;
+		msgb_free(msg);
+	}
+
+	return len;
+}
+
+int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp,
+	      struct sockaddr_in *addr, char *buf, int rc)
+{
+	struct mgcp_trunk_config *tcfg = endp->tcfg;
+	struct mgcp_rtp_end *rtp_end;
+	struct mgcp_rtp_state *rtp_state;
+	int tap_idx;
+	struct osmo_jibuf *jb;
+
+	/* For loop toggle the destination and then dispatch. */
+	if (tcfg->audio_loop)
+		dest = !dest;
+
+	/* Loop based on the conn_mode, maybe undoing the above */
+	if (endp->conn_mode == MGCP_CONN_LOOPBACK)
+		dest = !dest;
+
+	if (dest == MGCP_DEST_NET) {
+		rtp_end = &endp->net_end;
+		rtp_state = &endp->bts_state;
+		tap_idx = MGCP_TAP_NET_OUT;
+		jb = endp->bts_jb;
+	} else {
+		rtp_end = &endp->bts_end;
+		rtp_state = &endp->net_state;
+		tap_idx = MGCP_TAP_BTS_OUT;
+		jb = NULL;
+	}
+
+	if (!rtp_end->output_enabled)
+		rtp_end->dropped_packets += 1;
+	else if (is_rtp) {
+		int cont;
+		int nbytes = 0;
+		int len = rc;
+		do {
+			cont = endp->cfg->rtp_processing_cb(endp, rtp_end,
+							buf, &len, RTP_BUF_SIZE);
+			if (cont < 0)
+				break;
+
+			mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len);
+			forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx],
+				     buf, len);
+			if (jb)
+				rc = enqueue_dejitter(jb, rtp_end, buf, len);
+			else
+				rc = mgcp_udp_send(rtp_end->rtp.fd,
+						   &rtp_end->addr,
+						   rtp_end->rtp_port, buf, len);
+
+			if (rc <= 0)
+				return rc;
+			nbytes += rc;
+			len = cont;
+		} while (len > 0);
+		return nbytes;
+	} else if (!tcfg->omit_rtcp) {
+		return mgcp_udp_send(rtp_end->rtcp.fd,
+				     &rtp_end->addr,
+				     rtp_end->rtcp_port, buf, rc);
+	}
+
+	return 0;
+}
+
+static int receive_from(struct mgcp_endpoint *endp, int fd, struct sockaddr_in *addr,
+			char *buf, int bufsize)
+{
+	int rc;
+	socklen_t slen = sizeof(*addr);
+
+	rc = recvfrom(fd, buf, bufsize, 0,
+			    (struct sockaddr *) addr, &slen);
+	if (rc < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n",
+			ENDPOINT_NUMBER(endp), errno, strerror(errno));
+		return -1;
+	}
+
+	/* do not forward aynthing... maybe there is a packet from the bts */
+	if (!endp->allocated)
+		return -1;
+
+	#warning "Slight spec violation. With connection mode recvonly we should attempt to forward."
+
+	return rc;
+}
+
+static int rtp_data_net(struct osmo_fd *fd, unsigned int what)
+{
+	char buf[RTP_BUF_SIZE];
+	struct sockaddr_in addr;
+	struct mgcp_endpoint *endp;
+	int rc, proto;
+
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf));
+	if (rc <= 0)
+		return -1;
+
+	if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Endpoint 0x%x data from wrong address %s vs. ",
+			ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr));
+		LOGPC(DMGCP, LOGL_ERROR,
+			"%s\n", inet_ntoa(endp->net_end.addr));
+		return -1;
+	}
+
+	switch(endp->type) {
+	case MGCP_RTP_DEFAULT:
+	case MGCP_RTP_TRANSCODED:
+		if (endp->net_end.rtp_port != addr.sin_port &&
+		    endp->net_end.rtcp_port != addr.sin_port) {
+			LOGP(DMGCP, LOGL_ERROR,
+				"Data from wrong source port %d on 0x%x\n",
+				ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
+			return -1;
+		}
+		break;
+	case MGCP_OSMUX_BSC:
+	case MGCP_OSMUX_BSC_NAT:
+		break;
+	}
+
+	/* throw away the dummy message */
+	if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	proto = fd == &endp->net_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+	endp->net_end.packets += 1;
+	endp->net_end.octets += rc;
+
+	forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc);
+
+	switch (endp->type) {
+	case MGCP_RTP_DEFAULT:
+		return mgcp_send(endp, MGCP_DEST_BTS, proto == MGCP_PROTO_RTP,
+				 &addr, buf, rc);
+	case MGCP_RTP_TRANSCODED:
+		return mgcp_send_transcoder(&endp->trans_net, endp->cfg,
+					    proto == MGCP_PROTO_RTP, buf, rc);
+	case MGCP_OSMUX_BSC_NAT:
+		return osmux_xfrm_to_osmux(MGCP_DEST_BTS, buf, rc, endp);
+	case MGCP_OSMUX_BSC:	/* Should not happen */
+		break;
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint 0x%x\n",
+	     endp->type, ENDPOINT_NUMBER(endp));
+	return 0;
+}
+
+static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr)
+{
+	struct mgcp_config *cfg = endp->cfg;
+
+	if (proto == MGCP_PROTO_RTP && endp->bts_end.rtp_port == 0) {
+		if (!cfg->bts_ip ||
+		    memcmp(&addr->sin_addr,
+			   &cfg->bts_in, sizeof(cfg->bts_in)) == 0 ||
+		    memcmp(&addr->sin_addr,
+			   &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) {
+
+			endp->bts_end.rtp_port = addr->sin_port;
+			endp->bts_end.addr = addr->sin_addr;
+
+			LOGP(DMGCP, LOGL_NOTICE,
+				"Found BTS for endpoint: 0x%x on port: %d/%d of %s\n",
+				ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port),
+				ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr));
+		}
+	} else if (proto == MGCP_PROTO_RTCP && endp->bts_end.rtcp_port == 0) {
+		if (memcmp(&endp->bts_end.addr, &addr->sin_addr,
+				sizeof(endp->bts_end.addr)) == 0) {
+			endp->bts_end.rtcp_port = addr->sin_port;
+		}
+	}
+}
+
+static int rtp_data_bts(struct osmo_fd *fd, unsigned int what)
+{
+	char buf[RTP_BUF_SIZE];
+	struct sockaddr_in addr;
+	struct mgcp_endpoint *endp;
+	int rc, proto;
+
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf));
+	if (rc <= 0)
+		return -1;
+
+	proto = fd == &endp->bts_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+
+	/* We have no idea who called us, maybe it is the BTS. */
+	/* it was the BTS... */
+	discover_bts(endp, proto, &addr);
+
+	if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong bts %s on 0x%x\n",
+			inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp));
+		return -1;
+	}
+
+	if (endp->bts_end.rtp_port != addr.sin_port &&
+	    endp->bts_end.rtcp_port != addr.sin_port) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong bts source port %d on 0x%x\n",
+			ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
+		return -1;
+	}
+
+	/* throw away the dummy message */
+	if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	/* do this before the loop handling */
+	endp->bts_end.packets += 1;
+	endp->bts_end.octets += rc;
+
+	forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc);
+
+	switch (endp->type) {
+	case MGCP_RTP_DEFAULT:
+		return mgcp_send(endp, MGCP_DEST_NET, proto == MGCP_PROTO_RTP,
+				 &addr, buf, rc);
+	case MGCP_RTP_TRANSCODED:
+		return mgcp_send_transcoder(&endp->trans_bts, endp->cfg,
+					    proto == MGCP_PROTO_RTP, buf, rc);
+	case MGCP_OSMUX_BSC:
+		/* OSMUX translation: BTS -> BSC */
+		return osmux_xfrm_to_osmux(MGCP_DEST_NET, buf, rc, endp);
+	case MGCP_OSMUX_BSC_NAT:
+		break;	/* Should not happen */
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint 0x%x\n",
+	     endp->type, ENDPOINT_NUMBER(endp));
+	return 0;
+}
+
+static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp,
+			      int dest, struct osmo_fd *fd)
+{
+	char buf[RTP_BUF_SIZE];
+	struct sockaddr_in addr;
+	struct mgcp_config *cfg;
+	int rc, proto;
+
+	cfg = _endp->cfg;
+	rc = receive_from(_endp, fd->fd, &addr, buf, sizeof(buf));
+	if (rc <= 0)
+		return -1;
+
+	proto = fd == &end->rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+
+	if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data not coming from transcoder dest: %d %s on 0x%x\n",
+			dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp));
+		return -1;
+	}
+
+	if (end->rtp_port != addr.sin_port &&
+	    end->rtcp_port != addr.sin_port) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong transcoder dest %d source port %d on 0x%x\n",
+			dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp));
+		return -1;
+	}
+
+	/* throw away the dummy message */
+	if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n",
+			dest, ENDPOINT_NUMBER(_endp));
+		return 0;
+	}
+
+	end->packets += 1;
+	return mgcp_send(_endp, dest, proto == MGCP_PROTO_RTP, &addr, buf, rc);
+}
+
+static int rtp_data_trans_net(struct osmo_fd *fd, unsigned int what)
+{
+	struct mgcp_endpoint *endp;
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	return rtp_data_transcoder(&endp->trans_net, endp, MGCP_DEST_NET, fd);
+}
+
+static int rtp_data_trans_bts(struct osmo_fd *fd, unsigned int what)
+{
+	struct mgcp_endpoint *endp;
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	return rtp_data_transcoder(&endp->trans_bts, endp, MGCP_DEST_BTS, fd);
+}
+
+int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port)
+{
+	struct sockaddr_in addr;
+	int on = 1;
+
+	fd->fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd->fd < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create UDP port.\n");
+		return -1;
+	}
+
+	setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	inet_aton(source_addr, &addr.sin_addr);
+
+	if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		close(fd->fd);
+		fd->fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+int mgcp_set_ip_tos(int fd, int tos)
+{
+	int ret;
+	ret = setsockopt(fd, IPPROTO_IP, IP_TOS,
+			 &tos, sizeof(tos));
+	return ret != 0;
+}
+
+static int bind_rtp(struct mgcp_config *cfg, const char *source_addr,
+			struct mgcp_rtp_end *rtp_end, int endpno)
+{
+	if (mgcp_create_bind(source_addr, &rtp_end->rtp,
+			     rtp_end->local_port) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n",
+		       source_addr, rtp_end->local_port, endpno);
+		goto cleanup0;
+	}
+
+	if (mgcp_create_bind(source_addr, &rtp_end->rtcp,
+			     rtp_end->local_port + 1) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n",
+		       source_addr, rtp_end->local_port + 1, endpno);
+		goto cleanup1;
+	}
+
+	mgcp_set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp);
+	mgcp_set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp);
+
+	rtp_end->rtp.when = BSC_FD_READ;
+	if (osmo_fd_register(&rtp_end->rtp) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n",
+			rtp_end->local_port, endpno);
+		goto cleanup2;
+	}
+
+	rtp_end->rtcp.when = BSC_FD_READ;
+	if (osmo_fd_register(&rtp_end->rtcp) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n",
+			rtp_end->local_port + 1, endpno);
+		goto cleanup3;
+	}
+
+	return 0;
+
+cleanup3:
+	osmo_fd_unregister(&rtp_end->rtp);
+cleanup2:
+	close(rtp_end->rtcp.fd);
+	rtp_end->rtcp.fd = -1;
+cleanup1:
+	close(rtp_end->rtp.fd);
+	rtp_end->rtp.fd = -1;
+cleanup0:
+	return -1;
+}
+
+static int int_bind(const char *port,
+		    struct mgcp_rtp_end *end, int (*cb)(struct osmo_fd *, unsigned),
+		    struct mgcp_endpoint *_endp,
+		    const char *source_addr, int rtp_port)
+{
+	if (end->rtp.fd != -1 || end->rtcp.fd != -1) {
+		LOGP(DMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n",
+			port, ENDPOINT_NUMBER(_endp));
+		mgcp_free_rtp_port(end);
+	}
+
+	end->local_port = rtp_port;
+	end->rtp.cb = cb;
+	end->rtp.data = _endp;
+	end->rtcp.data = _endp;
+	end->rtcp.cb = cb;
+	return bind_rtp(_endp->cfg, source_addr, end, ENDPOINT_NUMBER(_endp));
+}
+
+int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("bts-port", &endp->bts_end,
+			rtp_data_bts, endp,
+			mgcp_bts_src_addr(endp), rtp_port);
+}
+
+int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("net-port", &endp->net_end,
+			rtp_data_net, endp,
+			mgcp_net_src_addr(endp), rtp_port);
+}
+
+int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("trans-net", &endp->trans_net,
+			rtp_data_trans_net, endp,
+			endp->cfg->source_addr, rtp_port);
+}
+
+int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("trans-bts", &endp->trans_bts,
+			rtp_data_trans_bts, endp,
+			endp->cfg->source_addr, rtp_port);
+}
+
+int mgcp_free_rtp_port(struct mgcp_rtp_end *end)
+{
+	if (end->rtp.fd != -1) {
+		close(end->rtp.fd);
+		end->rtp.fd = -1;
+		osmo_fd_unregister(&end->rtp);
+	}
+
+	if (end->rtcp.fd != -1) {
+		close(end->rtcp.fd);
+		end->rtcp.fd = -1;
+		osmo_fd_unregister(&end->rtcp);
+	}
+
+	return 0;
+}
+
+
+void mgcp_state_calc_loss(struct mgcp_rtp_state *state,
+			struct mgcp_rtp_end *end, uint32_t *expected,
+			int *loss)
+{
+	*expected = state->stats_cycles + state->stats_max_seq;
+	*expected = *expected - state->stats_base_seq + 1;
+
+	if (!state->stats_initialized) {
+		*expected = 0;
+		*loss = 0;
+		return;
+	}
+
+	/*
+	 * Make sure the sign is correct and use the biggest
+	 * positive/negative number that fits.
+	 */
+	*loss = *expected - end->packets;
+	if (*expected < end->packets) {
+		if (*loss > 0)
+			*loss = INT_MIN;
+	} else {
+		if (*loss < 0)
+			*loss = INT_MAX;
+	}
+}
+
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *state)
+{
+	if (!state->stats_initialized)
+		return 0;
+	return state->stats_jitter >> 4;
+}
diff --git a/openbsc/src/libmgcp/mgcp_osmux.c b/openbsc/src/libmgcp/mgcp_osmux.c
new file mode 100644
index 0000000..ce344ca
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_osmux.c
@@ -0,0 +1,608 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@gnumonks.org>
+ * (C) 2012-2013 by On Waves ehf <http://www.on-waves.com>
+ * All rights not specifically granted under this license are reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <stdio.h> /* for printf */
+#include <string.h> /* for memcpy */
+#include <stdlib.h> /* for abs */
+#include <inttypes.h> /* for PRIu64 */
+#include <netinet/in.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/netif/osmux.h>
+#include <osmocom/netif/rtp.h>
+
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/osmux.h>
+
+static struct osmo_fd osmux_fd;
+
+static LLIST_HEAD(osmux_handle_list);
+
+struct osmux_handle {
+	struct llist_head head;
+	struct osmux_in_handle *in;
+	struct in_addr rem_addr;
+	int rem_port;
+	int refcnt;
+};
+
+static void *osmux;
+
+static void osmux_deliver(struct msgb *batch_msg, void *data)
+{
+	struct osmux_handle *handle = data;
+	struct sockaddr_in out = {
+		.sin_family = AF_INET,
+		.sin_port = handle->rem_port,
+	};
+
+	memcpy(&out.sin_addr, &handle->rem_addr, sizeof(handle->rem_addr));
+	sendto(osmux_fd.fd, batch_msg->data, batch_msg->len, 0,
+		(struct sockaddr *)&out, sizeof(out));
+	msgb_free(batch_msg);
+}
+
+static struct osmux_handle *
+osmux_handle_find_get(struct in_addr *addr, int rem_port)
+{
+	struct osmux_handle *h;
+
+	/* Lookup for existing OSMUX handle for this destination address. */
+	llist_for_each_entry(h, &osmux_handle_list, head) {
+		if (memcmp(&h->rem_addr, addr, sizeof(struct in_addr)) == 0 &&
+		    h->rem_port == rem_port) {
+			LOGP(DMGCP, LOGL_DEBUG, "using existing OSMUX handle "
+						"for addr=%s:%d\n",
+				inet_ntoa(*addr), ntohs(rem_port));
+			h->refcnt++;
+			return h;
+		}
+	}
+
+	return NULL;
+}
+
+static void osmux_handle_put(struct osmux_in_handle *in)
+{
+	struct osmux_handle *h;
+
+	/* Lookup for existing OSMUX handle for this destination address. */
+	llist_for_each_entry(h, &osmux_handle_list, head) {
+		if (h->in == in) {
+			if (--h->refcnt == 0) {
+				LOGP(DMGCP, LOGL_INFO,
+				     "Releasing unused osmux handle for %s:%d\n",
+				     inet_ntoa(h->rem_addr),
+				     ntohs(h->rem_port));
+				LOGP(DMGCP, LOGL_INFO, "Stats: "
+				     "input RTP msgs: %u bytes: %"PRIu64" "
+				     "output osmux msgs: %u bytes: %"PRIu64"\n",
+				     in->stats.input_rtp_msgs,
+				     in->stats.input_rtp_bytes,
+				     in->stats.output_osmux_msgs,
+				     in->stats.output_osmux_bytes);
+				llist_del(&h->head);
+				osmux_xfrm_input_fini(h->in);
+				talloc_free(h);
+			}
+			return;
+		}
+	}
+	LOGP(DMGCP, LOGL_ERROR, "cannot find Osmux input handle %p\n", in);
+}
+
+static struct osmux_handle *
+osmux_handle_alloc(struct mgcp_config *cfg, struct in_addr *addr, int rem_port)
+{
+	struct osmux_handle *h;
+
+	h = talloc_zero(osmux, struct osmux_handle);
+	if (!h)
+		return NULL;
+	h->rem_addr = *addr;
+	h->rem_port = rem_port;
+	h->refcnt++;
+
+	h->in = talloc_zero(h, struct osmux_in_handle);
+	if (!h->in) {
+		talloc_free(h);
+		return NULL;
+	}
+
+	h->in->osmux_seq = 0; /* sequence number to start OSmux message from */
+	h->in->batch_factor = cfg->osmux_batch;
+	/* If batch size is zero, the library defaults to 1470 bytes. */
+	h->in->batch_size = cfg->osmux_batch_size;
+	h->in->deliver = osmux_deliver;
+	osmux_xfrm_input_init(h->in);
+	h->in->data = h;
+
+	llist_add(&h->head, &osmux_handle_list);
+
+	LOGP(DMGCP, LOGL_DEBUG, "created new OSMUX handle for addr=%s:%d\n",
+		inet_ntoa(*addr), ntohs(rem_port));
+
+	return h;
+}
+
+static struct osmux_in_handle *
+osmux_handle_lookup(struct mgcp_config *cfg, struct in_addr *addr, int rem_port)
+{
+	struct osmux_handle *h;
+
+	h = osmux_handle_find_get(addr, rem_port);
+	if (h != NULL)
+		return h->in;
+
+	h = osmux_handle_alloc(cfg, addr, rem_port);
+	if (h == NULL)
+		return NULL;
+
+	return h->in;
+}
+
+int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp)
+{
+	int ret;
+	struct msgb *msg;
+
+	msg = msgb_alloc(4096, "RTP");
+	if (!msg)
+		return 0;
+
+	memcpy(msg->data, buf, rc);
+	msgb_put(msg, rc);
+
+	while ((ret = osmux_xfrm_input(endp->osmux.in, msg, endp->osmux.cid)) > 0) {
+		/* batch full, build and deliver it */
+		osmux_xfrm_input_deliver(endp->osmux.in);
+	}
+	return 0;
+}
+
+static struct mgcp_endpoint *
+endpoint_lookup(struct mgcp_config *cfg, int cid,
+		struct in_addr *from_addr, int type)
+{
+	struct mgcp_endpoint *tmp = NULL;
+	int i;
+
+	/* Lookup for the endpoint that corresponds to this port */
+	for (i=0; i<cfg->trunk.number_endpoints; i++) {
+		struct in_addr *this;
+
+		tmp = &cfg->trunk.endpoints[i];
+
+		if (!tmp->allocated)
+			continue;
+
+		switch(type) {
+		case MGCP_DEST_NET:
+			this = &tmp->net_end.addr;
+			break;
+		case MGCP_DEST_BTS:
+			this = &tmp->bts_end.addr;
+			break;
+		default:
+			/* Should not ever happen */
+			LOGP(DMGCP, LOGL_ERROR, "Bad type %d. Fix your code.\n", type);
+			return NULL;
+		}
+
+		if (tmp->osmux.cid == cid && this->s_addr == from_addr->s_addr)
+			return tmp;
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Cannot find endpoint with cid=%d\n", cid);
+
+	return NULL;
+}
+
+static void scheduled_tx_net_cb(struct msgb *msg, void *data)
+{
+	struct mgcp_endpoint *endp = data;
+	struct sockaddr_in addr = {
+		.sin_addr = endp->net_end.addr,
+		.sin_port = endp->net_end.rtp_port,
+	};
+
+	endp->bts_end.octets += msg->len;
+	endp->bts_end.packets++;
+
+	mgcp_send(endp, MGCP_DEST_NET, 1, &addr, (char *)msg->data, msg->len);
+	msgb_free(msg);
+}
+
+static void scheduled_tx_bts_cb(struct msgb *msg, void *data)
+{
+	struct mgcp_endpoint *endp = data;
+	struct sockaddr_in addr = {
+		.sin_addr = endp->bts_end.addr,
+		.sin_port = endp->bts_end.rtp_port,
+	};
+
+	endp->net_end.octets += msg->len;
+	endp->net_end.packets++;
+
+	mgcp_send(endp, MGCP_DEST_BTS, 1, &addr, (char *)msg->data, msg->len);
+	msgb_free(msg);
+}
+
+static struct msgb *osmux_recv(struct osmo_fd *ofd, struct sockaddr_in *addr)
+{
+	struct msgb *msg;
+	socklen_t slen = sizeof(*addr);
+	int ret;
+
+	msg = msgb_alloc(4096, "OSMUX");
+	if (!msg) {
+		LOGP(DMGCP, LOGL_ERROR, "cannot allocate message\n");
+		return NULL;
+	}
+	ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0,
+			(struct sockaddr *)addr, &slen);
+	if (ret <= 0) {
+		msgb_free(msg);
+		LOGP(DMGCP, LOGL_ERROR, "cannot receive message\n");
+		return NULL;
+	}
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+/* Updates endp osmux state and returns 0 if it can process messages, -1 otherwise */
+static int endp_osmux_state_check(struct mgcp_endpoint *endp, struct sockaddr_in *addr, bool sending)
+{
+	switch(endp->osmux.state) {
+	case OSMUX_STATE_ACTIVATING:
+		if (osmux_enable_endpoint(endp, &addr->sin_addr, addr->sin_port) < 0 ) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Could not enable osmux in endpoint 0x%x for %s:%u\n",
+			     ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr),
+			     ntohs(addr->sin_port));
+			return -1;
+		}
+		LOGP(DMGCP, LOGL_INFO, "Enabling osmux in endpoint 0x%x for %s:%u\n",
+		     ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr),
+		     ntohs(addr->sin_port));
+		return 0;
+	case OSMUX_STATE_ENABLED:
+		return 0;
+	default:
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Osmux %s in endpoint 0x%x for %s:%u without full negotiation, state %d\n",
+		     sending ? "sent" : "received",
+		     ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr),
+		     ntohs(addr->sin_port), endp->osmux.state);
+		return -1;
+	}
+}
+
+static int osmux_legacy_dummy_parse_cid(struct sockaddr_in *addr, struct msgb *msg,
+					uint8_t *osmux_cid)
+{
+	if (msg->len < 1 + sizeof(osmux_cid)) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Discarding truncated Osmux dummy load\n");
+		return -1;
+	}
+
+	/* extract the osmux CID from the dummy message */
+	memcpy(osmux_cid, &msg->data[1], sizeof(*osmux_cid));
+	return 0;
+}
+
+#define osmux_chunk_length(msg, rem) (rem - msg->len);
+
+int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	struct msgb *msg;
+	struct osmux_hdr *osmuxh;
+	struct sockaddr_in addr;
+	struct mgcp_config *cfg = ofd->data;
+	uint32_t rem;
+
+	msg = osmux_recv(ofd, &addr);
+	if (!msg)
+		return -1;
+
+	if (!cfg->osmux) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "bsc-nat wants to use Osmux but bsc did not request it\n");
+		goto out;
+	}
+
+	/* not any further processing dummy messages */
+	if (msg->data[0] == MGCP_DUMMY_LOAD)
+		goto out;
+
+	rem = msg->len;
+	while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) {
+		struct mgcp_endpoint *endp;
+
+		/* Yes, we use MGCP_DEST_NET to locate the origin */
+		endp = endpoint_lookup(cfg, osmuxh->circuit_id,
+				       &addr.sin_addr, MGCP_DEST_NET);
+		if (!endp) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Cannot find an endpoint for circuit_id=%d\n",
+			     osmuxh->circuit_id);
+			goto out;
+		}
+		if (endp_osmux_state_check(endp, &addr, false) == 0) {
+			endp->osmux.stats.octets += osmux_chunk_length(msg, rem);
+			endp->osmux.stats.chunks++;
+			osmux_xfrm_output_sched(&endp->osmux.out, osmuxh);
+		}
+		rem = msg->len;
+	}
+out:
+	msgb_free(msg);
+	return 0;
+}
+
+/* This is called from the bsc-nat */
+static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr,
+			      struct msgb *msg, int endp_type)
+{
+	struct mgcp_endpoint *endp;
+	uint8_t osmux_cid;
+
+	if (osmux_legacy_dummy_parse_cid(addr, msg, &osmux_cid) < 0)
+		goto out;
+
+	endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, endp_type);
+	if (!endp) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot find endpoint for Osmux CID %d\n", osmux_cid);
+		goto out;
+	}
+	endp_osmux_state_check(endp, addr, false);
+	/* Only needed to punch hole in firewall, it can be dropped */
+out:
+	msgb_free(msg);
+	return 0;
+}
+
+int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	struct msgb *msg;
+	struct osmux_hdr *osmuxh;
+	struct sockaddr_in addr;
+	struct mgcp_config *cfg = ofd->data;
+	uint32_t rem;
+
+	msg = osmux_recv(ofd, &addr);
+	if (!msg)
+		return -1;
+
+	if (!cfg->osmux) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "bsc wants to use Osmux but bsc-nat did not request it\n");
+		goto out;
+	}
+
+	/* not any further processing dummy messages */
+	if (msg->data[0] == MGCP_DUMMY_LOAD)
+		return osmux_handle_dummy(cfg, &addr, msg, MGCP_DEST_BTS);
+
+	rem = msg->len;
+	while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) {
+		struct mgcp_endpoint *endp;
+
+		/* Yes, we use MGCP_DEST_BTS to locate the origin */
+		endp = endpoint_lookup(cfg, osmuxh->circuit_id,
+				       &addr.sin_addr, MGCP_DEST_BTS);
+		if (!endp) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Cannot find an endpoint for circuit_id=%d\n",
+			     osmuxh->circuit_id);
+			goto out;
+		}
+		if (endp_osmux_state_check(endp, &addr, false) == 0) {
+			endp->osmux.stats.octets += osmux_chunk_length(msg, rem);
+			endp->osmux.stats.chunks++;
+			osmux_xfrm_output_sched(&endp->osmux.out, osmuxh);
+		}
+		rem = msg->len;
+	}
+out:
+	msgb_free(msg);
+	return 0;
+}
+
+int osmux_init(int role, struct mgcp_config *cfg)
+{
+	int ret;
+
+	switch(role) {
+	case OSMUX_ROLE_BSC:
+		osmux_fd.cb = osmux_read_from_bsc_nat_cb;
+		break;
+	case OSMUX_ROLE_BSC_NAT:
+		osmux_fd.cb = osmux_read_from_bsc_cb;
+		break;
+	default:
+		LOGP(DMGCP, LOGL_ERROR, "wrong role for OSMUX\n");
+		return -1;
+	}
+	osmux_fd.data = cfg;
+
+	ret = mgcp_create_bind(cfg->osmux_addr, &osmux_fd, cfg->osmux_port);
+	if (ret < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "cannot bind OSMUX socket\n");
+		return ret;
+	}
+	mgcp_set_ip_tos(osmux_fd.fd, cfg->endp_dscp);
+	osmux_fd.when |= BSC_FD_READ;
+
+	ret = osmo_fd_register(&osmux_fd);
+	if (ret < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "cannot register OSMUX socket\n");
+		return ret;
+	}
+	cfg->osmux_init = 1;
+
+	return 0;
+}
+
+int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port)
+{
+	/* If osmux is enabled, initialize the output handler. This handler is
+	 * used to reconstruct the RTP flow from osmux. The RTP SSRC is
+	 * allocated based on the circuit ID (endp->osmux.cid), which is unique
+	 * in the local scope to the BSC/BSC-NAT. We use it to divide the RTP
+	 * SSRC space (2^32) by the OSMUX_CID_MAX + 1 possible circuit IDs, then randomly
+	 * select one value from that window. Thus, we have no chance to have
+	 * overlapping RTP SSRC traveling to the BTSes behind the BSC,
+	 * similarly, for flows traveling to the MSC.
+	 */
+	static const uint32_t rtp_ssrc_winlen = UINT32_MAX / (OSMUX_CID_MAX + 1);
+
+	if (endp->osmux.state != OSMUX_STATE_ACTIVATING) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x didn't negotiate Osmux, state %d\n",
+		     ENDPOINT_NUMBER(endp), endp->osmux.state);
+		return -1;
+	}
+
+	endp->osmux.in = osmux_handle_lookup(endp->cfg, addr, port);
+	if (!endp->osmux.in) {
+		LOGP(DMGCP, LOGL_ERROR, "Cannot allocate input osmux handle\n");
+		return -1;
+	}
+	if (osmux_xfrm_input_open_circuit(endp->osmux.in, endp->osmux.cid,
+					   endp->cfg->osmux_dummy) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Cannot open osmux circuit %u\n",
+		     endp->osmux.cid);
+		return -1;
+	}
+
+	osmux_xfrm_output_init(&endp->osmux.out,
+			       (endp->osmux.cid * rtp_ssrc_winlen) +
+			       (random() % rtp_ssrc_winlen));
+
+	switch (endp->cfg->role) {
+		case MGCP_BSC_NAT:
+			endp->type = MGCP_OSMUX_BSC_NAT;
+			osmux_xfrm_output_set_tx_cb(&endp->osmux.out,
+							scheduled_tx_net_cb, endp);
+			break;
+		case MGCP_BSC:
+			endp->type = MGCP_OSMUX_BSC;
+			osmux_xfrm_output_set_tx_cb(&endp->osmux.out,
+							scheduled_tx_bts_cb, endp);
+			break;
+	}
+	endp->osmux.state = OSMUX_STATE_ENABLED;
+
+	return 0;
+}
+
+void osmux_disable_endpoint(struct mgcp_endpoint *endp)
+{
+	LOGP(DMGCP, LOGL_INFO, "Releasing endpoint 0x%x using Osmux CID %u\n",
+	     ENDPOINT_NUMBER(endp), endp->osmux.cid);
+
+	/* We are closing, we don't need pending RTP packets to be transmitted */
+	osmux_xfrm_output_set_tx_cb(&endp->osmux.out, NULL, NULL);
+	osmux_xfrm_output_flush(&endp->osmux.out);
+
+	osmux_xfrm_input_close_circuit(endp->osmux.in, endp->osmux.cid);
+	endp->osmux.state = OSMUX_STATE_DISABLED;
+	endp->osmux.cid = -1;
+	osmux_handle_put(endp->osmux.in);
+}
+
+void osmux_release_cid(struct mgcp_endpoint *endp)
+{
+	if (endp->osmux.allocated_cid >= 0)
+		osmux_put_cid(endp->osmux.allocated_cid);
+	endp->osmux.allocated_cid = -1;
+}
+
+void osmux_allocate_cid(struct mgcp_endpoint *endp)
+{
+	osmux_release_cid(endp);
+	endp->osmux.allocated_cid = osmux_get_cid();
+}
+
+/* We don't need to send the dummy load for osmux so often as another endpoint
+ * may have already punched the hole in the firewall. This approach is simple
+ * though.
+ */
+int osmux_send_dummy(struct mgcp_endpoint *endp)
+{
+	char buf[1 + sizeof(uint8_t)];
+	struct in_addr addr_unset = {};
+
+	buf[0] = MGCP_DUMMY_LOAD;
+	memcpy(&buf[1], &endp->osmux.cid, sizeof(endp->osmux.cid));
+
+	/* Wait until we have the connection information from MDCX */
+	if (memcmp(&endp->net_end.addr, &addr_unset, sizeof(addr_unset)) == 0)
+		return 0;
+
+	if (endp_osmux_state_check(endp, &endp->net_end.addr, true) < 0)
+		return 0;
+
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "sending OSMUX dummy load to %s CID %u\n",
+	     inet_ntoa(endp->net_end.addr), endp->osmux.cid);
+
+	return mgcp_udp_send(osmux_fd.fd, &endp->net_end.addr,
+			     htons(endp->cfg->osmux_port), buf, sizeof(buf));
+}
+
+/* bsc-nat allocates/releases the Osmux circuit ID. +7 to round up to 8 bit boundary. */
+static uint8_t osmux_cid_bitmap[(OSMUX_CID_MAX + 1 + 7) / 8];
+
+int osmux_used_cid(void)
+{
+	int i, j, used = 0;
+
+	for (i = 0; i < sizeof(osmux_cid_bitmap); i++) {
+		for (j = 0; j < 8; j++) {
+			if (osmux_cid_bitmap[i] & (1 << j))
+				used += 1;
+		}
+	}
+
+	return used;
+}
+
+int osmux_get_cid(void)
+{
+	int i, j;
+
+	for (i = 0; i < sizeof(osmux_cid_bitmap); i++) {
+		for (j = 0; j < 8; j++) {
+			if (osmux_cid_bitmap[i] & (1 << j))
+				continue;
+
+			osmux_cid_bitmap[i] |= (1 << j);
+			LOGP(DMGCP, LOGL_DEBUG,
+			     "Allocating Osmux CID %u from pool\n", (i * 8) + j);
+			return (i * 8) + j;
+		}
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "All Osmux circuits are in use!\n");
+	return -1;
+}
+
+void osmux_put_cid(uint8_t osmux_cid)
+{
+	LOGP(DMGCP, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid);
+	osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8));
+}
diff --git a/openbsc/src/libmgcp/mgcp_protocol.c b/openbsc/src/libmgcp/mgcp_protocol.c
new file mode 100644
index 0000000..84dbc1f
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_protocol.c
@@ -0,0 +1,1626 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#define for_each_non_empty_line(line, save)			\
+	for (line = strtok_r(NULL, "\r\n", &save); line;\
+	     line = strtok_r(NULL, "\r\n", &save))
+
+
+static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end);
+
+struct mgcp_request {
+	char *name;
+	struct msgb *(*handle_request) (struct mgcp_parse_data *data);
+	char *debug_name;
+};
+
+#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \
+	{ .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME },
+
+static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *data);
+static struct msgb *handle_create_con(struct mgcp_parse_data *data);
+static struct msgb *handle_delete_con(struct mgcp_parse_data *data);
+static struct msgb *handle_modify_con(struct mgcp_parse_data *data);
+static struct msgb *handle_rsip(struct mgcp_parse_data *data);
+static struct msgb *handle_noti_req(struct mgcp_parse_data *data);
+
+static void create_transcoder(struct mgcp_endpoint *endp);
+static void delete_transcoder(struct mgcp_endpoint *endp);
+
+static int setup_rtp_processing(struct mgcp_endpoint *endp);
+
+static int mgcp_analyze_header(struct mgcp_parse_data *parse, char *data);
+
+static int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line)
+{
+	const size_t line_len = strlen(line);
+	if (line[0] != '\0' && line_len < 2) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Wrong MGCP option format: '%s' on 0x%x\n",
+			line, ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	return 1;
+}
+
+static uint32_t generate_call_id(struct mgcp_config *cfg)
+{
+	int i;
+
+	/* use the call id */
+	++cfg->last_call_id;
+
+	/* handle wrap around */
+	if (cfg->last_call_id == CI_UNUSED)
+		++cfg->last_call_id;
+
+	/* callstack can only be of size number_of_endpoints */
+	/* verify that the call id is free, e.g. in case of overrun */
+	for (i = 1; i < cfg->trunk.number_endpoints; ++i)
+		if (cfg->trunk.endpoints[i].ci == cfg->last_call_id)
+			return generate_call_id(cfg);
+
+	return cfg->last_call_id;
+}
+
+/*
+ * array of function pointers for handling various
+ * messages. In the future this might be binary sorted
+ * for performance reasons.
+ */
+static const struct mgcp_request mgcp_requests [] = {
+	MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint")
+	MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection")
+	MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection")
+	MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection")
+	MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest")
+
+	/* SPEC extension */
+	MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress")
+};
+
+static struct msgb *mgcp_msgb_alloc(void)
+{
+	struct msgb *msg;
+	msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+	if (!msg)
+	    LOGP(DMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n");
+
+	return msg;
+}
+
+static struct msgb *do_retransmission(const struct mgcp_endpoint *endp)
+{
+	struct msgb *msg = mgcp_msgb_alloc();
+	if (!msg)
+		return NULL;
+
+	msg->l2h = msgb_put(msg, strlen(endp->last_response));
+	memcpy(msg->l2h, endp->last_response, msgb_l2len(msg));
+	return msg;
+}
+
+static struct msgb *create_resp(struct mgcp_endpoint *endp, int code,
+				const char *txt, const char *msg,
+				const char *trans, const char *param,
+				const char *sdp)
+{
+	int len;
+	struct msgb *res;
+
+	res = mgcp_msgb_alloc();
+	if (!res)
+		return NULL;
+
+	len = snprintf((char *) res->data, 2048, "%d %s%s%s\r\n%s",
+			code, trans, txt, param ? param : "", sdp ? sdp : "");
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n");
+		msgb_free(res);
+		return NULL;
+	}
+
+	res->l2h = msgb_put(res, len);
+	LOGP(DMGCP, LOGL_DEBUG, "Generated response: code: %d for '%s'\n", code, res->l2h);
+
+	/*
+	 * Remember the last transmission per endpoint.
+	 */
+	if (endp) {
+		struct mgcp_trunk_config *tcfg = endp->tcfg;
+		talloc_free(endp->last_response);
+		talloc_free(endp->last_trans);
+		endp->last_trans = talloc_strdup(tcfg->endpoints, trans);
+		endp->last_response = talloc_strndup(tcfg->endpoints,
+						(const char *) res->l2h,
+						msgb_l2len(res));
+	}
+
+	return res;
+}
+
+static struct msgb *create_ok_resp_with_param(struct mgcp_endpoint *endp,
+					int code, const char *msg,
+					const char *trans, const char *param)
+{
+	return create_resp(endp, code, " OK", msg, trans, param, NULL);
+}
+
+static struct msgb *create_ok_response(struct mgcp_endpoint *endp,
+					int code, const char *msg, const char *trans)
+{
+	return create_ok_resp_with_param(endp, code, msg, trans, NULL);
+}
+
+static struct msgb *create_err_response(struct mgcp_endpoint *endp,
+					int code, const char *msg, const char *trans)
+{
+	return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL);
+}
+
+static int write_response_sdp(struct mgcp_endpoint *endp,
+			      char *sdp_record, size_t size, const char *addr)
+{
+	const char *fmtp_extra;
+	const char *audio_name;
+	int payload_type;
+	int len;
+	int nchars;
+
+	endp->cfg->get_net_downlink_format_cb(endp, &payload_type,
+					      &audio_name, &fmtp_extra);
+
+	len = snprintf(sdp_record, size,
+			"v=0\r\n"
+			"o=- %u 23 IN IP4 %s\r\n"
+			"s=-\r\n"
+			"c=IN IP4 %s\r\n"
+			"t=0 0\r\n",
+			endp->ci, addr, addr);
+
+	if (len < 0 || len >= size)
+		goto buffer_too_small;
+
+	if (payload_type >= 0) {
+		nchars = snprintf(sdp_record + len, size - len,
+				  "m=audio %d RTP/AVP %d\r\n",
+				  endp->net_end.local_port, payload_type);
+		if (nchars < 0 || nchars >= size - len)
+			goto buffer_too_small;
+
+		len += nchars;
+
+		if (audio_name && endp->tcfg->audio_send_name) {
+			nchars = snprintf(sdp_record + len, size - len,
+					  "a=rtpmap:%d %s\r\n",
+					  payload_type, audio_name);
+
+			if (nchars < 0 || nchars >= size - len)
+				goto buffer_too_small;
+
+			len += nchars;
+		}
+
+		if (fmtp_extra) {
+			nchars = snprintf(sdp_record + len, size - len,
+					  "%s\r\n", fmtp_extra);
+
+			if (nchars < 0 || nchars >= size - len)
+				goto buffer_too_small;
+
+			len += nchars;
+		}
+	}
+	if (endp->bts_end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) {
+		nchars = snprintf(sdp_record + len, size - len,
+				  "a=ptime:%d\r\n",
+				  endp->bts_end.packet_duration_ms);
+		if (nchars < 0 || nchars >= size - len)
+			goto buffer_too_small;
+
+		len += nchars;
+	}
+
+	return len;
+
+buffer_too_small:
+	LOGP(DMGCP, LOGL_ERROR, "SDP buffer too small: %zu (needed %d)\n",
+	     size, len);
+	return -1;
+}
+
+static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
+					     const char *msg, const char *trans_id)
+{
+	const char *addr = endp->cfg->local_ip;
+	char sdp_record[4096];
+	int len;
+	int nchars;
+	char osmux_extension[strlen("\nX-Osmux: 255") + 1];
+
+	if (!addr)
+		addr = mgcp_net_src_addr(endp);
+
+	if (endp->osmux.state == OSMUX_STATE_NEGOTIATING) {
+		sprintf(osmux_extension, "\nX-Osmux: %u", endp->osmux.cid);
+		endp->osmux.state = OSMUX_STATE_ACTIVATING;
+	} else {
+		osmux_extension[0] = '\0';
+	}
+
+	len = snprintf(sdp_record, sizeof(sdp_record),
+		       "I: %u%s\n\n", endp->ci, osmux_extension);
+	if (len < 0)
+		return NULL;
+
+	nchars = write_response_sdp(endp, sdp_record + len,
+				    sizeof(sdp_record) - len - 1, addr);
+	if (nchars < 0)
+		return NULL;
+
+	len += nchars;
+
+	sdp_record[sizeof(sdp_record) - 1] = '\0';
+
+	return create_resp(endp, 200, " OK", msg, trans_id, NULL, sdp_record);
+}
+
+static void send_dummy(struct mgcp_endpoint *endp)
+{
+	if (endp->osmux.state != OSMUX_STATE_DISABLED)
+		osmux_send_dummy(endp);
+	else
+		mgcp_send_dummy(endp);
+}
+
+/*
+ * handle incoming messages:
+ *   - this can be a command (four letters, space, transaction id)
+ *   - or a response (three numbers, space, transaction id)
+ */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_parse_data pdata;
+	int i, code, handled = 0;
+	struct msgb *resp = NULL;
+	char *data;
+	unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */
+
+	if (msgb_l2len(msg) < 4) {
+		LOGP(DMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len);
+		return NULL;
+	}
+
+	/* Ensure that the msg->l2h is NUL terminated. */
+	if (tail[-1] == '\0')
+		/* nothing to do */;
+	else if (msgb_tailroom(msg) > 0)
+		tail[0] = '\0';
+	else if (tail[-1] == '\r' || tail[-1] == '\n')
+		tail[-1] = '\0';
+	else {
+		LOGP(DMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: "
+		     "Length: %d, Buffer size: %d\n",
+		     msgb_l2len(msg), msg->data_len);
+		return NULL;
+	}
+
+        /* attempt to treat it as a response */
+        if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) {
+		LOGP(DMGCP, LOGL_DEBUG, "Response: Code: %d\n", code);
+		return NULL;
+	}
+
+	msg->l3h = &msg->l2h[4];
+
+
+	/*
+	 * Check for a duplicate message and respond.
+	 */
+	memset(&pdata, 0, sizeof(pdata));
+	pdata.cfg = cfg;
+	data = strline_r((char *) msg->l3h, &pdata.save);
+	pdata.found = mgcp_analyze_header(&pdata, data);
+	if (pdata.endp && pdata.trans
+			&& pdata.endp->last_trans
+			&& strcmp(pdata.endp->last_trans, pdata.trans) == 0) {
+		return do_retransmission(pdata.endp);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i) {
+		if (strncmp(mgcp_requests[i].name, (const char *) &msg->l2h[0], 4) == 0) {
+			handled = 1;
+			resp = mgcp_requests[i].handle_request(&pdata);
+			break;
+		}
+	}
+
+	if (!handled)
+		LOGP(DMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]);
+
+	return resp;
+}
+
+/**
+ * We have a null terminated string with the endpoint name here. We only
+ * support two kinds. Simple ones as seen on the BSC level and the ones
+ * seen on the trunk side.
+ */
+static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg,
+					     const char *mgcp)
+{
+	char *rest = NULL;
+	struct mgcp_trunk_config *tcfg;
+	int trunk, endp;
+
+	trunk = strtoul(mgcp + 6, &rest, 10);
+	if (rest == NULL || rest[0] != '/' || trunk < 1) {
+		LOGP(DMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp);
+		return NULL;
+	}
+
+	endp = strtoul(rest + 1, &rest, 10);
+	if (rest == NULL || rest[0] != '@') {
+		LOGP(DMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp);
+		return NULL;
+	}
+
+	/* signalling is on timeslot 1 */
+	if (endp == 1)
+		return NULL;
+
+	tcfg = mgcp_trunk_num(cfg, trunk);
+	if (!tcfg) {
+		LOGP(DMGCP, LOGL_ERROR, "The trunk %d is not declared.\n", trunk);
+		return NULL;
+	}
+
+	if (!tcfg->endpoints) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoints of trunk %d not allocated.\n", trunk);
+		return NULL;
+	}
+
+	if (endp < 1 || endp >= tcfg->number_endpoints) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n", mgcp);
+		return NULL;
+	}
+
+	return &tcfg->endpoints[endp];
+}
+
+static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, const char *mgcp)
+{
+	char *endptr = NULL;
+	unsigned int gw = INT_MAX;
+
+	if (strncmp(mgcp, "ds/e1", 5) == 0)
+		return find_e1_endpoint(cfg, mgcp);
+
+	gw = strtoul(mgcp, &endptr, 16);
+	if (gw > 0 && gw < cfg->trunk.number_endpoints && endptr[0] == '@')
+		return &cfg->trunk.endpoints[gw];
+
+	LOGP(DMGCP, LOGL_ERROR, "Not able to find the endpoint: '%s'\n", mgcp);
+	return NULL;
+}
+
+/**
+ * @returns 0 when the status line was complete and transaction_id and
+ * endp out parameters are set.
+ */
+static int mgcp_analyze_header(struct mgcp_parse_data *pdata, char *data)
+{
+	int i = 0;
+	char *elem, *save = NULL;
+
+	OSMO_ASSERT(data);
+	pdata->trans = "000000";
+
+	for (elem = strtok_r(data, " ", &save); elem;
+	     elem = strtok_r(NULL, " ", &save)) {
+		switch (i) {
+		case 0:
+			pdata->trans = elem;
+			break;
+		case 1:
+			pdata->endp = find_endpoint(pdata->cfg, elem);
+			if (!pdata->endp) {
+				LOGP(DMGCP, LOGL_ERROR,
+				     "Unable to find Endpoint `%s'\n", elem);
+				return -1;
+			}
+			break;
+		case 2:
+			if (strcmp("MGCP", elem)) {
+				LOGP(DMGCP, LOGL_ERROR,
+				     "MGCP header parsing error\n");
+				return -1;
+			}
+			break;
+		case 3:
+			if (strcmp("1.0", elem)) {
+				LOGP(DMGCP, LOGL_ERROR, "MGCP version `%s' "
+					"not supported\n", elem);
+				return -1;
+			}
+			break;
+		}
+		i++;
+	}
+
+	if (i != 4) {
+		LOGP(DMGCP, LOGL_ERROR, "MGCP status line too short.\n");
+		pdata->trans = "000000";
+		pdata->endp = NULL;
+		return -1;
+	}
+
+	return 0;
+}
+
+static int verify_call_id(const struct mgcp_endpoint *endp,
+			  const char *callid)
+{
+	if (strcmp(endp->callid, callid) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n",
+			ENDPOINT_NUMBER(endp), endp->callid, callid);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int verify_ci(const struct mgcp_endpoint *endp,
+		     const char *_ci)
+{
+	uint32_t ci = strtoul(_ci, NULL, 10);
+
+	if (ci != endp->ci) {
+		LOGP(DMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n",
+			ENDPOINT_NUMBER(endp), endp->ci, _ci);
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p)
+{
+	if (p->found != 0)
+		return create_err_response(NULL, 500, "AUEP", p->trans);
+	else
+		return create_ok_response(p->endp, 200, "AUEP", p->trans);
+}
+
+static int parse_conn_mode(const char *msg, struct mgcp_endpoint *endp)
+{
+	int ret = 0;
+	if (strcmp(msg, "recvonly") == 0)
+		endp->conn_mode = MGCP_CONN_RECV_ONLY;
+	else if (strcmp(msg, "sendrecv") == 0)
+		endp->conn_mode = MGCP_CONN_RECV_SEND;
+	else if (strcmp(msg, "sendonly") == 0)
+		endp->conn_mode = MGCP_CONN_SEND_ONLY;
+	else if (strcmp(msg, "loopback") == 0)
+		endp->conn_mode = MGCP_CONN_LOOPBACK;
+	else {
+		LOGP(DMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg);
+		ret = -1;
+	}
+
+	endp->net_end.output_enabled =
+		endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0;
+	endp->bts_end.output_enabled =
+		endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0;
+
+	return ret;
+}
+
+static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end,
+			 struct mgcp_port_range *range,
+			 int (*alloc)(struct mgcp_endpoint *endp, int port))
+{
+	int i;
+
+	if (range->mode == PORT_ALLOC_STATIC) {
+		end->local_alloc = PORT_ALLOC_STATIC;
+		return 0;
+	}
+
+	/* attempt to find a port */
+	for (i = 0; i < 200; ++i) {
+		int rc;
+
+		if (range->last_port >= range->range_end)
+			range->last_port = range->range_start;
+
+		rc = alloc(endp, range->last_port);
+
+		range->last_port += 2;
+		if (rc == 0) {
+			end->local_alloc = PORT_ALLOC_DYNAMIC;
+			return 0;
+		}
+
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n",
+	     ENDPOINT_NUMBER(endp));
+	return -1;
+}
+
+static int allocate_ports(struct mgcp_endpoint *endp)
+{
+	if (allocate_port(endp, &endp->net_end, &endp->cfg->net_ports,
+			  mgcp_bind_net_rtp_port) != 0)
+		return -1;
+
+	if (allocate_port(endp, &endp->bts_end, &endp->cfg->bts_ports,
+			  mgcp_bind_bts_rtp_port) != 0) {
+		mgcp_rtp_end_reset(&endp->net_end);
+		return -1;
+	}
+
+	if (endp->cfg->transcoder_ip && endp->tcfg->trunk_type == MGCP_TRUNK_VIRTUAL) {
+		if (allocate_port(endp, &endp->trans_net,
+				  &endp->cfg->transcoder_ports,
+				  mgcp_bind_trans_net_rtp_port) != 0) {
+			mgcp_rtp_end_reset(&endp->net_end);
+			mgcp_rtp_end_reset(&endp->bts_end);
+			return -1;
+		}
+
+		if (allocate_port(endp, &endp->trans_bts,
+				  &endp->cfg->transcoder_ports,
+				  mgcp_bind_trans_bts_rtp_port) != 0) {
+			mgcp_rtp_end_reset(&endp->net_end);
+			mgcp_rtp_end_reset(&endp->bts_end);
+			mgcp_rtp_end_reset(&endp->trans_net);
+			return -1;
+		}
+
+		/* remember that we have set up transcoding */
+		endp->type = MGCP_RTP_TRANSCODED;
+	}
+
+	return 0;
+}
+
+/* Set the LCO from a string (see RFC 3435).
+ * The string is stored in the 'string' field. A NULL string is handled excatly
+ * like an empty string, the 'string' field is never NULL after this function
+ * has been called. */
+static void set_local_cx_options(void *ctx, struct mgcp_lco *lco,
+				 const char *options)
+{
+	char *p_opt, *a_opt;
+	char codec[9];
+
+	talloc_free(lco->string);
+	talloc_free(lco->codec);
+	lco->codec = NULL;
+	lco->pkt_period_min = lco->pkt_period_max = 0;
+	lco->string = talloc_strdup(ctx, options ? options : "");
+
+	p_opt = strstr(lco->string, "p:");
+	if (p_opt && sscanf(p_opt, "p:%d-%d",
+			    &lco->pkt_period_min, &lco->pkt_period_max) == 1)
+		lco->pkt_period_max = lco->pkt_period_min;
+
+	a_opt = strstr(lco->string, "a:");
+	if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1)
+		lco->codec = talloc_strdup(ctx, codec);
+}
+
+void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change,
+			 struct mgcp_rtp_end *rtp)
+{
+	struct mgcp_trunk_config *tcfg = endp->tcfg;
+
+	int patch_ssrc = expect_ssrc_change && tcfg->force_constant_ssrc;
+
+	rtp->force_aligned_timing = tcfg->force_aligned_timing;
+	rtp->force_constant_ssrc = patch_ssrc ? 1 : 0;
+
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "Configuring RTP endpoint: local port %d%s%s\n",
+	     ntohs(rtp->rtp_port),
+	     rtp->force_aligned_timing ? ", force constant timing" : "",
+	     rtp->force_constant_ssrc ? ", force constant ssrc" : "");
+}
+
+uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp,
+				  struct mgcp_rtp_end *rtp)
+{
+	int f = 0;
+
+	/* Get the number of frames per channel and packet */
+	if (rtp->frames_per_packet)
+		f = rtp->frames_per_packet;
+	else if (rtp->packet_duration_ms && rtp->codec.frame_duration_num) {
+		int den = 1000 * rtp->codec.frame_duration_num;
+		f = (rtp->packet_duration_ms * rtp->codec.frame_duration_den + den/2)
+			/ den;
+	}
+
+	return rtp->codec.rate * f * rtp->codec.frame_duration_num / rtp->codec.frame_duration_den;
+}
+
+static int mgcp_parse_osmux_cid(const char *line)
+{
+	int osmux_cid;
+
+	if (sscanf(line + 2, "Osmux: %u", &osmux_cid) != 1)
+		return -1;
+
+	if (osmux_cid > OSMUX_CID_MAX) {
+		LOGP(DMGCP, LOGL_ERROR, "Osmux ID too large: %u > %u\n",
+		     osmux_cid, OSMUX_CID_MAX);
+		return -1;
+	}
+	LOGP(DMGCP, LOGL_DEBUG, "bsc-nat offered Osmux CID %u\n", osmux_cid);
+
+	return osmux_cid;
+}
+
+static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line)
+{
+	if (!endp->cfg->osmux_init) {
+		if (osmux_init(OSMUX_ROLE_BSC, endp->cfg) < 0) {
+			LOGP(DMGCP, LOGL_ERROR, "Cannot init OSMUX\n");
+			return -1;
+		}
+		LOGP(DMGCP, LOGL_NOTICE, "OSMUX socket has been set up\n");
+	}
+
+	return mgcp_parse_osmux_cid(line);
+}
+
+static struct msgb *handle_create_con(struct mgcp_parse_data *p)
+{
+	struct mgcp_trunk_config *tcfg;
+	struct mgcp_endpoint *endp = p->endp;
+	int error_code = 400;
+
+	const char *local_options = NULL;
+	const char *callid = NULL;
+	const char *mode = NULL;
+	char *line;
+	int have_sdp = 0, osmux_cid = -1;
+
+	if (p->found != 0)
+		return create_err_response(NULL, 510, "CRCX", p->trans);
+
+	/* parse CallID C: and LocalParameters L: */
+	for_each_line(line, p->save) {
+		if (!mgcp_check_param(endp, line))
+			continue;
+
+		switch (line[0]) {
+		case 'L':
+			local_options = (const char *) line + 3;
+			break;
+		case 'C':
+			callid = (const char *) line + 3;
+			break;
+		case 'M':
+			mode = (const char *) line + 3;
+			break;
+		case 'X':
+			/* Osmux is not enabled in this bsc, ignore it so the
+			 * bsc-nat knows that we don't want to use Osmux.
+			 */
+			if (!p->endp->cfg->osmux)
+				break;
+
+			if (strncmp("Osmux: ", line + 2, strlen("Osmux: ")) == 0)
+				osmux_cid = mgcp_osmux_setup(endp, line);
+			break;
+		case '\0':
+			have_sdp = 1;
+			goto mgcp_header_done;
+		default:
+			LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+				*line, *line, ENDPOINT_NUMBER(endp));
+			break;
+		}
+	}
+
+mgcp_header_done:
+	tcfg = p->endp->tcfg;
+
+	/* Check required data */
+	if (!callid || !mode) {
+		LOGP(DMGCP, LOGL_ERROR, "Missing callid and mode in CRCX on 0x%x\n",
+		     ENDPOINT_NUMBER(endp));
+		return create_err_response(endp, 400, "CRCX", p->trans);
+	}
+
+	if (endp->allocated) {
+		if (tcfg->force_realloc) {
+			LOGP(DMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n",
+			    ENDPOINT_NUMBER(endp));
+			mgcp_release_endp(endp);
+			if (p->cfg->realloc_cb)
+				p->cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp));
+		} else {
+			LOGP(DMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			return create_err_response(endp, 400, "CRCX", p->trans);
+		}
+	}
+
+	/* copy some parameters */
+	endp->callid = talloc_strdup(tcfg->endpoints, callid);
+
+	set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
+			     local_options);
+
+	if (parse_conn_mode(mode, endp) != 0) {
+		    error_code = 517;
+		    goto error2;
+	}
+
+	/* initialize */
+	endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0;
+	mgcp_rtp_end_config(endp, 0, &endp->net_end);
+	mgcp_rtp_end_config(endp, 0, &endp->bts_end);
+
+	/* set to zero until we get the info */
+	memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr));
+
+	/* bind to the port now */
+	if (allocate_ports(endp) != 0)
+		goto error2;
+
+	/* assign a local call identifier or fail */
+	endp->ci = generate_call_id(p->cfg);
+	if (endp->ci == CI_UNUSED)
+		goto error2;
+
+	/* Annotate Osmux circuit ID and set it to negotiating state until this
+	 * is fully set up from the dummy load.
+	 */
+	endp->osmux.state = OSMUX_STATE_DISABLED;
+	if (osmux_cid >= 0) {
+		endp->osmux.cid = osmux_cid;
+		endp->osmux.state = OSMUX_STATE_NEGOTIATING;
+	} else if (endp->cfg->osmux == OSMUX_USAGE_ONLY) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Osmux only and no osmux offered on 0x%x\n", ENDPOINT_NUMBER(endp));
+		goto error2;
+	}
+
+	/* Apply Jiter buffer settings for this endpoint, they can be overriden by CRCX policy later */
+	endp->bts_use_jibuf = endp->cfg->bts_use_jibuf;
+	endp->bts_jitter_delay_min = endp->cfg->bts_jitter_delay_min;
+	endp->bts_jitter_delay_max = endp->cfg->bts_jitter_delay_max;
+
+
+	endp->allocated = 1;
+
+	/* set up RTP media parameters */
+	mgcp_set_audio_info(p->cfg, &endp->bts_end.codec, tcfg->audio_payload, tcfg->audio_name);
+	endp->bts_end.fmtp_extra = talloc_strdup(tcfg->endpoints,
+						tcfg->audio_fmtp_extra);
+	if (have_sdp)
+		mgcp_parse_sdp_data(endp, &endp->net_end, p);
+	else if (endp->local_options.codec)
+		mgcp_set_audio_info(p->cfg, &endp->net_end.codec,
+			       PTYPE_UNDEFINED, endp->local_options.codec);
+
+	if (p->cfg->bts_force_ptime) {
+		endp->bts_end.packet_duration_ms = p->cfg->bts_force_ptime;
+		endp->bts_end.force_output_ptime = 1;
+	}
+
+	if (setup_rtp_processing(endp) != 0)
+		goto error2;
+
+	/* policy CB */
+	if (p->cfg->policy_cb) {
+		int rc;
+		rc = p->cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp),
+				MGCP_ENDP_CRCX, p->trans);
+		switch (rc) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			mgcp_release_endp(endp);
+			return create_err_response(endp, 400, "CRCX", p->trans);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			create_transcoder(endp);
+			/* Set up jitter buffer if required after policy has updated jibuf endp values */
+			if (endp->bts_use_jibuf) {
+				endp->bts_jb = osmo_jibuf_alloc(tcfg->endpoints);
+				osmo_jibuf_set_min_delay(endp->bts_jb, endp->bts_jitter_delay_min);
+				osmo_jibuf_set_max_delay(endp->bts_jb, endp->bts_jitter_delay_max);
+				osmo_jibuf_set_dequeue_cb(endp->bts_jb, mgcp_dejitter_udp_send, &endp->net_end);
+			}
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	/* Set up jitter buffer if required after policy has updated jibuf endp values */
+	if (endp->bts_use_jibuf) {
+		endp->bts_jb = osmo_jibuf_alloc(tcfg->endpoints);
+		osmo_jibuf_set_min_delay(endp->bts_jb, endp->bts_jitter_delay_min);
+		osmo_jibuf_set_max_delay(endp->bts_jb, endp->bts_jitter_delay_max);
+		osmo_jibuf_set_dequeue_cb(endp->bts_jb, mgcp_dejitter_udp_send, &endp->net_end);
+	}
+
+	LOGP(DMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n",
+		ENDPOINT_NUMBER(endp), endp->ci,
+		endp->net_end.local_port, endp->bts_end.local_port);
+	if (p->cfg->change_cb)
+		p->cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX);
+
+	if (endp->conn_mode & MGCP_CONN_RECV_ONLY && tcfg->keepalive_interval != 0) {
+		send_dummy(endp);
+	}
+
+	create_transcoder(endp);
+	return create_response_with_sdp(endp, "CRCX", p->trans);
+error2:
+	mgcp_release_endp(endp);
+	LOGP(DMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp));
+	return create_err_response(endp, error_code, "CRCX", p->trans);
+}
+
+static struct msgb *handle_modify_con(struct mgcp_parse_data *p)
+{
+	struct mgcp_endpoint *endp = p->endp;
+	int error_code = 500;
+	int silent = 0;
+	int have_sdp = 0;
+	char *line;
+	const char *local_options = NULL;
+
+	if (p->found != 0)
+		return create_err_response(NULL, 510, "MDCX", p->trans);
+
+	if (endp->ci == CI_UNUSED) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not "
+			"holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp));
+		return create_err_response(endp, 400, "MDCX", p->trans);
+	}
+
+	for_each_line(line, p->save) {
+		if (!mgcp_check_param(endp, line))
+			continue;
+
+		switch (line[0]) {
+		case 'C': {
+			if (verify_call_id(endp, line + 3) != 0)
+				goto error3;
+			break;
+		}
+		case 'I': {
+			if (verify_ci(endp, line + 3) != 0)
+				goto error3;
+			break;
+		}
+		case 'L':
+			local_options = (const char *) line + 3;
+			break;
+		case 'M':
+			if (parse_conn_mode(line + 3, endp) != 0) {
+			    error_code = 517;
+			    goto error3;
+			}
+			endp->orig_mode = endp->conn_mode;
+			break;
+		case 'Z':
+			silent = strcmp("noanswer", line + 3) == 0;
+			break;
+		case '\0':
+			/* SDP file begins */
+			have_sdp = 1;
+			mgcp_parse_sdp_data(endp, &endp->net_end, p);
+			/* This will exhaust p->save, so the loop will
+			 * terminate next time.
+			 */
+			break;
+		default:
+			LOGP(DMGCP, LOGL_NOTICE, "Unhandled MGCP option: '%c'/%d on 0x%x\n",
+				line[0], line[0], ENDPOINT_NUMBER(endp));
+			break;
+		}
+	}
+
+	set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
+			     local_options);
+
+	if (!have_sdp && endp->local_options.codec)
+		mgcp_set_audio_info(p->cfg, &endp->net_end.codec,
+			       PTYPE_UNDEFINED, endp->local_options.codec);
+
+	if (setup_rtp_processing(endp) != 0)
+		goto error3;
+
+	/* policy CB */
+	if (p->cfg->policy_cb) {
+		int rc;
+		rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp),
+						MGCP_ENDP_MDCX, p->trans);
+		switch (rc) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			if (silent)
+				goto out_silent;
+			return create_err_response(endp, 400, "MDCX", p->trans);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	mgcp_rtp_end_config(endp, 1, &endp->net_end);
+	mgcp_rtp_end_config(endp, 1, &endp->bts_end);
+
+	/* modify */
+	LOGP(DMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n",
+		ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+	if (p->cfg->change_cb)
+		p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX);
+
+	if (endp->conn_mode & MGCP_CONN_RECV_ONLY &&
+	    endp->tcfg->keepalive_interval != 0)
+		send_dummy(endp);
+
+	if (silent)
+		goto out_silent;
+
+	return create_response_with_sdp(endp, "MDCX", p->trans);
+
+error3:
+	return create_err_response(endp, error_code, "MDCX", p->trans);
+
+
+out_silent:
+	return NULL;
+}
+
+static struct msgb *handle_delete_con(struct mgcp_parse_data *p)
+{
+	struct mgcp_endpoint *endp = p->endp;
+	int error_code = 400;
+	int silent = 0;
+	char *line;
+	char stats[1048];
+
+	if (p->found != 0)
+		return create_err_response(NULL, error_code, "DLCX", p->trans);
+
+	if (!p->endp->allocated) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return create_err_response(endp, 400, "DLCX", p->trans);
+	}
+
+	for_each_line(line, p->save) {
+		if (!mgcp_check_param(endp, line))
+			continue;
+
+		switch (line[0]) {
+		case 'C':
+			if (verify_call_id(endp, line + 3) != 0)
+				goto error3;
+			break;
+		case 'I':
+			if (verify_ci(endp, line + 3) != 0)
+				goto error3;
+			break;
+		case 'Z':
+			silent = strcmp("noanswer", line + 3) == 0;
+			break;
+		default:
+			LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+				line[0], line[0], ENDPOINT_NUMBER(endp));
+			break;
+		}
+	}
+
+	/* policy CB */
+	if (p->cfg->policy_cb) {
+		int rc;
+		rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp),
+						MGCP_ENDP_DLCX, p->trans);
+		switch (rc) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			if (silent)
+				goto out_silent;
+			return create_err_response(endp, 400, "DLCX", p->trans);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			delete_transcoder(endp);
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	/* free the connection */
+	LOGP(DMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n",
+		ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+
+	/* save the statistics of the current call */
+	mgcp_format_stats(endp, stats, sizeof(stats));
+
+	delete_transcoder(endp);
+	mgcp_release_endp(endp);
+	if (p->cfg->change_cb)
+		p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX);
+
+	if (silent)
+		goto out_silent;
+	return create_ok_resp_with_param(endp, 250, "DLCX", p->trans, stats);
+
+error3:
+	return create_err_response(endp, error_code, "DLCX", p->trans);
+
+out_silent:
+	return NULL;
+}
+
+static struct msgb *handle_rsip(struct mgcp_parse_data *p)
+{
+	if (p->found != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to find the endpoint.\n");
+		return NULL;
+	}
+
+	if (p->cfg->reset_cb)
+		p->cfg->reset_cb(p->endp->tcfg);
+	return NULL;
+}
+
+static char extract_tone(const char *line)
+{
+	const char *str = strstr(line, "D/");
+	if (!str)
+		return CHAR_MAX;
+
+	return str[2];
+}
+
+/*
+ * This can request like DTMF detection and forward, fax detection... it
+ * can also request when the notification should be send and such. We don't
+ * do this right now.
+ */
+static struct msgb *handle_noti_req(struct mgcp_parse_data *p)
+{
+	int res = 0;
+	char *line;
+	char tone = CHAR_MAX;
+
+	if (p->found != 0)
+		return create_err_response(NULL, 400, "RQNT", p->trans);
+
+	for_each_line(line, p->save) {
+		switch (line[0]) {
+		case 'S':
+			tone = extract_tone(line);
+			break;
+		}
+	}
+
+	/* we didn't see a signal request with a tone */
+	if (tone == CHAR_MAX)
+		return create_ok_response(p->endp, 200, "RQNT", p->trans);
+
+	if (p->cfg->rqnt_cb)
+		res = p->cfg->rqnt_cb(p->endp, tone);
+
+	return res == 0 ?
+		create_ok_response(p->endp, 200, "RQNT", p->trans) :
+		create_err_response(p->endp, res, "RQNT", p->trans);
+}
+
+static void mgcp_keepalive_timer_cb(void *_tcfg)
+{
+	struct mgcp_trunk_config *tcfg = _tcfg;
+	int i;
+	LOGP(DMGCP, LOGL_DEBUG, "Triggered trunk %d keepalive timer.\n",
+	     tcfg->trunk_nr);
+
+	if (tcfg->keepalive_interval <= 0)
+		return;
+
+	for (i = 1; i < tcfg->number_endpoints; ++i) {
+		struct mgcp_endpoint *endp = &tcfg->endpoints[i];
+		if (endp->conn_mode == MGCP_CONN_RECV_ONLY)
+			send_dummy(endp);
+	}
+
+	LOGP(DMGCP, LOGL_DEBUG, "Rescheduling trunk %d keepalive timer.\n",
+	     tcfg->trunk_nr);
+	osmo_timer_schedule(&tcfg->keepalive_timer, tcfg->keepalive_interval, 0);
+}
+
+void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval)
+{
+	tcfg->keepalive_interval = interval;
+	osmo_timer_setup(&tcfg->keepalive_timer, mgcp_keepalive_timer_cb, tcfg);
+
+	if (interval <= 0)
+		osmo_timer_del(&tcfg->keepalive_timer);
+	else
+		osmo_timer_schedule(&tcfg->keepalive_timer,
+				    tcfg->keepalive_interval, 0);
+}
+
+struct mgcp_config *mgcp_config_alloc(void)
+{
+	struct mgcp_config *cfg;
+
+	cfg = talloc_zero(NULL, struct mgcp_config);
+	if (!cfg) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to allocate config.\n");
+		return NULL;
+	}
+
+	cfg->source_port = 2427;
+	cfg->source_addr = talloc_strdup(cfg, "0.0.0.0");
+	cfg->osmux_addr = talloc_strdup(cfg, "0.0.0.0");
+
+	cfg->transcoder_remote_base = 4000;
+
+	cfg->bts_ports.base_port = RTP_PORT_DEFAULT;
+	cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT;
+
+	cfg->rtp_processing_cb = &mgcp_rtp_processing_default;
+	cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default;
+
+	cfg->get_net_downlink_format_cb = &mgcp_get_net_downlink_format_default;
+
+	/* default trunk handling */
+	cfg->trunk.cfg = cfg;
+	cfg->trunk.trunk_nr = 0;
+	cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL;
+	cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000");
+	cfg->trunk.audio_payload = 126;
+	cfg->trunk.audio_send_ptime = 1;
+	cfg->trunk.audio_send_name = 1;
+	cfg->trunk.omit_rtcp = 0;
+	mgcp_trunk_set_keepalive(&cfg->trunk, MGCP_KEEPALIVE_ONCE);
+
+	INIT_LLIST_HEAD(&cfg->trunks);
+
+	return cfg;
+}
+
+struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr)
+{
+	struct mgcp_trunk_config *trunk;
+
+	trunk = talloc_zero(cfg, struct mgcp_trunk_config);
+	if (!trunk) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate.\n");
+		return NULL;
+	}
+
+	trunk->cfg = cfg;
+	trunk->trunk_type = MGCP_TRUNK_E1;
+	trunk->trunk_nr = nr;
+	trunk->audio_name = talloc_strdup(cfg, "AMR/8000");
+	trunk->audio_payload = 126;
+	trunk->audio_send_ptime = 1;
+	trunk->audio_send_name = 1;
+	trunk->number_endpoints = 33;
+	trunk->omit_rtcp = 0;
+	mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
+	llist_add_tail(&trunk->entry, &cfg->trunks);
+	return trunk;
+}
+
+struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index)
+{
+	struct mgcp_trunk_config *trunk;
+
+	llist_for_each_entry(trunk, &cfg->trunks, entry)
+		if (trunk->trunk_nr == index)
+			return trunk;
+
+	return NULL;
+}
+
+static void mgcp_rtp_codec_reset(struct mgcp_rtp_codec *codec)
+{
+	codec->payload_type = -1;
+	talloc_free(codec->subtype_name);
+	codec->subtype_name = NULL;
+	talloc_free(codec->audio_name);
+	codec->audio_name = NULL;
+	codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+	codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+	codec->rate               = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
+	codec->channels           = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS;
+}
+
+static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end)
+{
+	if (end->local_alloc == PORT_ALLOC_DYNAMIC) {
+		mgcp_free_rtp_port(end);
+		end->local_port = 0;
+	}
+
+	end->packets = 0;
+	end->octets = 0;
+	end->dropped_packets = 0;
+	memset(&end->addr, 0, sizeof(end->addr));
+	end->rtp_port = end->rtcp_port = 0;
+	end->local_alloc = -1;
+	talloc_free(end->fmtp_extra);
+	end->fmtp_extra = NULL;
+	talloc_free(end->rtp_process_data);
+	end->rtp_process_data = NULL;
+
+	/* Set default values */
+	end->frames_per_packet  = 0; /* unknown */
+	end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS;
+	end->output_enabled	= 0;
+
+	mgcp_rtp_codec_reset(&end->codec);
+	mgcp_rtp_codec_reset(&end->alt_codec);
+}
+
+static void mgcp_rtp_end_init(struct mgcp_rtp_end *end)
+{
+	mgcp_rtp_end_reset(end);
+	end->rtp.fd = -1;
+	end->rtcp.fd = -1;
+}
+
+int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg)
+{
+	int i;
+
+	/* Initialize all endpoints */
+	tcfg->endpoints = _talloc_zero_array(tcfg->cfg,
+				       sizeof(struct mgcp_endpoint),
+				       tcfg->number_endpoints, "endpoints");
+	if (!tcfg->endpoints)
+		return -1;
+
+	for (i = 0; i < tcfg->number_endpoints; ++i) {
+		tcfg->endpoints[i].osmux.allocated_cid = -1;
+		tcfg->endpoints[i].ci = CI_UNUSED;
+		tcfg->endpoints[i].cfg = tcfg->cfg;
+		tcfg->endpoints[i].tcfg = tcfg;
+		mgcp_rtp_end_init(&tcfg->endpoints[i].net_end);
+		mgcp_rtp_end_init(&tcfg->endpoints[i].bts_end);
+		mgcp_rtp_end_init(&tcfg->endpoints[i].trans_net);
+		mgcp_rtp_end_init(&tcfg->endpoints[i].trans_bts);
+	}
+
+	return 0;
+}
+
+void mgcp_release_endp(struct mgcp_endpoint *endp)
+{
+	LOGP(DMGCP, LOGL_DEBUG, "Releasing endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp));
+	if (endp->bts_jb)
+		osmo_jibuf_delete(endp->bts_jb);
+	endp->bts_jb = NULL;
+	endp->ci = CI_UNUSED;
+	endp->allocated = 0;
+
+	talloc_free(endp->callid);
+	endp->callid = NULL;
+
+	talloc_free(endp->local_options.string);
+	endp->local_options.string = NULL;
+	talloc_free(endp->local_options.codec);
+	endp->local_options.codec = NULL;
+
+	mgcp_rtp_end_reset(&endp->bts_end);
+	mgcp_rtp_end_reset(&endp->net_end);
+	mgcp_rtp_end_reset(&endp->trans_net);
+	mgcp_rtp_end_reset(&endp->trans_bts);
+	endp->type = MGCP_RTP_DEFAULT;
+
+	memset(&endp->net_state, 0, sizeof(endp->net_state));
+	memset(&endp->bts_state, 0, sizeof(endp->bts_state));
+
+	endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE;
+
+	if (endp->osmux.state == OSMUX_STATE_ENABLED)
+		osmux_disable_endpoint(endp);
+
+	/* release the circuit ID if it had been allocated */
+	osmux_release_cid(endp);
+
+	memset(&endp->taps, 0, sizeof(endp->taps));
+}
+
+void mgcp_initialize_endp(struct mgcp_endpoint *endp)
+{
+	return mgcp_release_endp(endp);
+}
+
+static int send_trans(struct mgcp_config *cfg, const char *buf, int len)
+{
+	struct sockaddr_in addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr = cfg->transcoder_in;
+	addr.sin_port = htons(2427);
+	return sendto(cfg->gw_fd.bfd.fd, buf, len, 0,
+		      (struct sockaddr *) &addr, sizeof(addr));
+}
+
+static void send_msg(struct mgcp_endpoint *endp, int endpoint, int port,
+		     const char *msg, const char *mode)
+{
+	char buf[2096];
+	int len;
+	int nchars;
+
+	/* hardcoded to AMR right now, we do not know the real type at this point */
+	len = snprintf(buf, sizeof(buf),
+			"%s 42 %x@mgw MGCP 1.0\r\n"
+			"C: 4256\r\n"
+			"M: %s\r\n"
+			"\r\n",
+			msg, endpoint, mode);
+
+	if (len < 0)
+		return;
+
+	nchars = write_response_sdp(endp, buf + len, sizeof(buf) + len - 1, NULL);
+	if (nchars < 0)
+		return;
+
+	len += nchars;
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	send_trans(endp->cfg, buf, len);
+}
+
+static void send_dlcx(struct mgcp_endpoint *endp, int endpoint)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+			"DLCX 43 %x@mgw MGCP 1.0\r\n"
+			"C: 4256\r\n"
+			, endpoint);
+
+	if (len < 0)
+		return;
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	send_trans(endp->cfg, buf, len);
+}
+
+static int send_agent(struct mgcp_config *cfg, const char *buf, int len)
+{
+	return write(cfg->gw_fd.bfd.fd, buf, len);
+}
+
+int mgcp_send_reset_all(struct mgcp_config *cfg)
+{
+	static const char mgcp_reset[] = {
+	    "RSIP 1 *@mgw MGCP 1.0\r\n"
+	};
+
+	return send_agent(cfg, mgcp_reset, sizeof mgcp_reset -1);
+}
+
+int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint)
+{
+	char buf[128];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+			"RSIP 39 %x@mgw MGCP 1.0\r\n"
+			, endpoint);
+	if (len < 0)
+		return len;
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	return send_agent(endp->cfg, buf, len);
+}
+
+static int setup_rtp_processing(struct mgcp_endpoint *endp)
+{
+	int rc = 0;
+	struct mgcp_config *cfg = endp->cfg;
+
+	if (endp->type != MGCP_RTP_DEFAULT)
+		return 0;
+
+	if (endp->conn_mode == MGCP_CONN_LOOPBACK)
+		return 0;
+
+	if (endp->conn_mode & MGCP_CONN_SEND_ONLY)
+		rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end);
+	else
+		rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, NULL);
+
+	if (endp->conn_mode & MGCP_CONN_RECV_ONLY)
+		rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end);
+	else
+		rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, NULL);
+	return rc;
+}
+
+static void create_transcoder(struct mgcp_endpoint *endp)
+{
+	int port;
+	int in_endp = ENDPOINT_NUMBER(endp);
+	int out_endp = endp_back_channel(in_endp);
+
+	if (endp->type != MGCP_RTP_TRANSCODED)
+		return;
+
+	send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv");
+	send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv");
+	send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv");
+	send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv");
+
+	port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base);
+	endp->trans_bts.rtp_port = htons(port);
+	endp->trans_bts.rtcp_port = htons(port + 1);
+
+	port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base);
+	endp->trans_net.rtp_port = htons(port);
+	endp->trans_net.rtcp_port = htons(port + 1);
+}
+
+static void delete_transcoder(struct mgcp_endpoint *endp)
+{
+	int in_endp = ENDPOINT_NUMBER(endp);
+	int out_endp = endp_back_channel(in_endp);
+
+	if (endp->type != MGCP_RTP_TRANSCODED)
+		return;
+
+	send_dlcx(endp, in_endp);
+	send_dlcx(endp, out_endp);
+}
+
+int mgcp_reset_transcoder(struct mgcp_config *cfg)
+{
+	if (!cfg->transcoder_ip)
+		return 0;
+
+	static const char mgcp_reset[] = {
+	    "RSIP 1 13@mgw MGCP 1.0\r\n"
+	};
+
+	return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1);
+}
+
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *msg, size_t size)
+{
+	uint32_t expected, jitter;
+	int ploss;
+	int nchars;
+	mgcp_state_calc_loss(&endp->net_state, &endp->net_end,
+				&expected, &ploss);
+	jitter = mgcp_state_calc_jitter(&endp->net_state);
+
+	nchars = snprintf(msg, size,
+			  "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u",
+			  endp->bts_end.packets, endp->bts_end.octets,
+			  endp->net_end.packets, endp->net_end.octets,
+			  ploss, jitter);
+	if (nchars < 0 || nchars >= size)
+		goto truncate;
+
+	msg += nchars;
+	size -= nchars;
+
+	if (endp->cfg->osmux != OSMUX_USAGE_OFF) {
+		/* Error Counter */
+		nchars = snprintf(msg, size,
+				  "\r\nX-Osmo-CP: EC TIS=%u, TOS=%u, TIR=%u, TOR=%u",
+				  endp->net_state.in_stream.err_ts_counter,
+				  endp->net_state.out_stream.err_ts_counter,
+				  endp->bts_state.in_stream.err_ts_counter,
+				  endp->bts_state.out_stream.err_ts_counter);
+		if (nchars < 0 || nchars >= size)
+			goto truncate;
+
+		msg += nchars;
+		size -= nchars;
+
+		if (endp->osmux.state == OSMUX_STATE_ENABLED) {
+			snprintf(msg, size,
+				 "\r\nX-Osmux-ST: CR=%u, BR=%u",
+				 endp->osmux.stats.chunks,
+				 endp->osmux.stats.octets);
+		}
+	}
+truncate:
+	msg[size - 1] = '\0';
+}
+
+int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os,
+		uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter)
+{
+	char *line, *save;
+	int rc;
+
+	/* initialize with bad values */
+	*ps = *os = *pr = *_or = *jitter = UINT_MAX;
+	*loss = INT_MAX;
+
+
+	line = strtok_r((char *) msg->l2h, "\r\n", &save);
+	if (!line)
+		return -1;
+
+	/* this can only parse the message that is created above... */
+	for_each_non_empty_line(line, save) {
+		switch (line[0]) {
+		case 'P':
+			rc = sscanf(line, "P: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u",
+					ps, os, pr, _or, loss, jitter);
+			return rc == 6 ? 0 : -1;
+		}
+	}
+
+	return -1;
+}
diff --git a/openbsc/src/libmgcp/mgcp_sdp.c b/openbsc/src/libmgcp/mgcp_sdp.c
new file mode 100644
index 0000000..b648944
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_sdp.c
@@ -0,0 +1,305 @@
+/*
+ * Some SDP file parsing...
+ *
+ * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <errno.h>
+
+struct sdp_rtp_map {
+	/* the type */
+	int payload_type;
+	/* null, static or later dynamic codec name */
+	char *codec_name;
+	/* A pointer to the original line for later parsing */
+	char *map_line;
+
+	int rate;
+	int channels;
+};
+
+int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec,
+			int payload_type, const char *audio_name)
+{
+	int rate = codec->rate;
+	int channels = codec->channels;
+	char audio_codec[64];
+
+	talloc_free(codec->subtype_name);
+	codec->subtype_name = NULL;
+	talloc_free(codec->audio_name);
+	codec->audio_name = NULL;
+
+	if (payload_type != PTYPE_UNDEFINED)
+		codec->payload_type = payload_type;
+
+	if (!audio_name) {
+		switch (payload_type) {
+		case 0: audio_name = "PCMU/8000/1"; break;
+		case 3: audio_name = "GSM/8000/1"; break;
+		case 8: audio_name = "PCMA/8000/1"; break;
+		case 18: audio_name = "G729/8000/1"; break;
+		default:
+			 /* Payload type is unknown, don't change rate and
+			  * channels. */
+			 /* TODO: return value? */
+			 return 0;
+		}
+	}
+
+	if (sscanf(audio_name, "%63[^/]/%d/%d",
+		   audio_codec, &rate, &channels) < 1)
+		return -EINVAL;
+
+	codec->rate = rate;
+	codec->channels = channels;
+	codec->subtype_name = talloc_strdup(ctx, audio_codec);
+	codec->audio_name = talloc_strdup(ctx, audio_name);
+
+	if (!strcmp(audio_codec, "G729")) {
+		codec->frame_duration_num = 10;
+		codec->frame_duration_den = 1000;
+	} else {
+		codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+		codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+	}
+
+	if (payload_type < 0) {
+		payload_type = 96;
+		if (rate == 8000 && channels == 1) {
+			if (!strcmp(audio_codec, "GSM"))
+				payload_type = 3;
+			else if (!strcmp(audio_codec, "PCMA"))
+				payload_type = 8;
+			else if (!strcmp(audio_codec, "PCMU"))
+				payload_type = 0;
+			else if (!strcmp(audio_codec, "G729"))
+				payload_type = 18;
+		}
+
+		codec->payload_type = payload_type;
+	}
+
+	if (channels != 1)
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "Channels != 1 in SDP: '%s'\n", audio_name);
+
+	return 0;
+}
+
+void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used)
+{
+	int i;
+
+	for (i = 0; i < used; ++i) {
+		switch (codecs[i].payload_type) {
+		case 0:
+			codecs[i].codec_name = "PCMU";
+			codecs[i].rate = 8000;
+			codecs[i].channels = 1;
+			break;
+		case 3:
+			codecs[i].codec_name = "GSM";
+			codecs[i].rate = 8000;
+			codecs[i].channels = 1;
+			break;
+		case 8:
+			codecs[i].codec_name = "PCMA";
+			codecs[i].rate = 8000;
+			codecs[i].channels = 1;
+			break;
+		case 18:
+			codecs[i].codec_name = "G729";
+			codecs[i].rate = 8000;
+			codecs[i].channels = 1;
+			break;
+		}
+	}
+}
+
+void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, int payload, char *audio_name)
+{
+	int i;
+
+	for (i = 0; i < used; ++i) {
+		char audio_codec[64];
+		int rate = -1;
+		int channels = -1;
+		if (codecs[i].payload_type != payload)
+			continue;
+		if (sscanf(audio_name, "%63[^/]/%d/%d",
+				audio_codec, &rate, &channels) < 1) {
+			LOGP(DMGCP, LOGL_ERROR, "Failed to parse '%s'\n", audio_name);
+			continue;
+		}
+
+		codecs[i].map_line = talloc_strdup(ctx, audio_name);
+		codecs[i].codec_name = talloc_strdup(ctx, audio_codec);
+		codecs[i].rate = rate;
+		codecs[i].channels = channels;
+		return;
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload, audio_name);
+}
+
+int is_codec_compatible(struct mgcp_endpoint *endp, struct sdp_rtp_map *codec)
+{
+	char *bts_codec;
+	char audio_codec[64];
+
+	if (!codec->codec_name)
+		return 0;
+
+	/*
+	 * GSM, GSM/8000 and GSM/8000/1 should all be compatible.. let's go
+	 * by name first.
+	 */
+	bts_codec = endp->tcfg->audio_name;
+	if (sscanf(bts_codec, "%63[^/]/%*d/%*d", audio_codec) < 1)
+		return 0;
+
+	return strcasecmp(audio_codec, codec->codec_name) == 0;
+}
+
+int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p)
+{
+	struct sdp_rtp_map codecs[10];
+	int codecs_used = 0;
+	char *line;
+	int maxptime = -1;
+	int i;
+	int codecs_assigned = 0;
+	void *tmp_ctx = talloc_new(NULL);
+
+	memset(&codecs, 0, sizeof(codecs));
+
+	for_each_line(line, p->save) {
+		switch (line[0]) {
+		case 'o':
+		case 's':
+		case 't':
+		case 'v':
+			/* skip these SDP attributes */
+			break;
+		case 'a': {
+			int payload;
+			int ptime, ptime2 = 0;
+			char audio_name[64];
+
+
+			if (sscanf(line, "a=rtpmap:%d %63s",
+				   &payload, audio_name) == 2) {
+				codecs_update(tmp_ctx, codecs, codecs_used, payload, audio_name);
+			} else if (sscanf(line, "a=ptime:%d-%d",
+					  &ptime, &ptime2) >= 1) {
+				if (ptime2 > 0 && ptime2 != ptime)
+					rtp->packet_duration_ms = 0;
+				else
+					rtp->packet_duration_ms = ptime;
+			} else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) {
+				maxptime = ptime2;
+			}
+			break;
+		}
+		case 'm': {
+			int port, rc;
+
+			rc = sscanf(line, "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d",
+					&port,
+					&codecs[0].payload_type,
+					&codecs[1].payload_type,
+					&codecs[2].payload_type,
+					&codecs[3].payload_type,
+					&codecs[4].payload_type,
+					&codecs[5].payload_type,
+					&codecs[6].payload_type,
+					&codecs[7].payload_type,
+					&codecs[8].payload_type,
+					&codecs[9].payload_type);
+			if (rc >= 2) {
+				rtp->rtp_port = htons(port);
+				rtp->rtcp_port = htons(port + 1);
+				codecs_used = rc - 1;
+				codecs_initialize(tmp_ctx, codecs, codecs_used);
+			}
+			break;
+		}
+		case 'c': {
+			char ipv4[16];
+
+			if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) {
+				inet_aton(ipv4, &rtp->addr);
+			}
+			break;
+		}
+		default:
+			if (p->endp)
+				LOGP(DMGCP, LOGL_NOTICE,
+				     "Unhandled SDP option: '%c'/%d on 0x%x\n",
+				     line[0], line[0], ENDPOINT_NUMBER(p->endp));
+			else
+				LOGP(DMGCP, LOGL_NOTICE,
+				     "Unhandled SDP option: '%c'/%d\n",
+				     line[0], line[0]);
+			break;
+		}
+	}
+
+	/* Now select the primary and alt_codec */
+	for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) {
+		struct mgcp_rtp_codec *codec = codecs_assigned == 0 ?
+					&rtp->codec : &rtp->alt_codec;
+
+		if (endp->tcfg->no_audio_transcoding &&
+			!is_codec_compatible(endp, &codecs[i])) {
+			LOGP(DMGCP, LOGL_NOTICE, "Skipping codec %s\n",
+				codecs[i].codec_name);
+			continue;
+		}
+
+		mgcp_set_audio_info(p->cfg, codec,
+					codecs[i].payload_type,
+					codecs[i].map_line);
+		codecs_assigned += 1;
+	}
+
+	if (codecs_assigned > 0) {
+		/* TODO/XXX: Store this per codec and derive it on use */
+		if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den >
+				rtp->codec.frame_duration_num * 1500) {
+			/* more than 1 frame */
+			rtp->packet_duration_ms = 0;
+		}
+
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "Got media info via SDP: port %d, payload %d (%s), "
+		     "duration %d, addr %s\n",
+		     ntohs(rtp->rtp_port), rtp->codec.payload_type,
+		     rtp->codec.subtype_name ? rtp->codec.subtype_name : "unknown",
+		     rtp->packet_duration_ms, inet_ntoa(rtp->addr));
+	}
+
+	talloc_free(tmp_ctx);
+	return codecs_assigned > 0;
+}
+
diff --git a/openbsc/src/libmgcp/mgcp_transcode.c b/openbsc/src/libmgcp/mgcp_transcode.c
new file mode 100644
index 0000000..f31e7ae
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_transcode.c
@@ -0,0 +1,612 @@
+/*
+ * (C) 2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include "g711common.h"
+
+#include <openbsc/debug.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/mgcp_transcode.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/netif/rtp.h>
+
+int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst)
+{
+	struct mgcp_process_rtp_state *state = state_;
+	if (dst)
+		return (nsamples >= 0 ?
+			nsamples / state->dst_samples_per_frame :
+			1) * state->dst_frame_size;
+	else
+		return (nsamples >= 0 ?
+			nsamples / state->src_samples_per_frame :
+			1) * state->src_frame_size;
+}
+
+static enum audio_format get_audio_format(const struct mgcp_rtp_codec *codec)
+{
+	if (codec->subtype_name) {
+		if (!strcasecmp("GSM", codec->subtype_name))
+			return AF_GSM;
+		if (!strcasecmp("PCMA", codec->subtype_name))
+			return AF_PCMA;
+		if (!strcasecmp("PCMU", codec->subtype_name))
+			return AF_PCMU;
+#ifdef HAVE_BCG729
+		if (!strcasecmp("G729", codec->subtype_name))
+			return AF_G729;
+#endif
+		if (!strcasecmp("L16", codec->subtype_name))
+			return AF_L16;
+	}
+
+	switch (codec->payload_type) {
+	case 0 /* PCMU */:
+		return AF_PCMU;
+	case 3 /* GSM */:
+		return AF_GSM;
+	case 8 /* PCMA */:
+		return AF_PCMA;
+#ifdef HAVE_BCG729
+	case 18 /* G.729 */:
+		return AF_G729;
+#endif
+	case 11 /* L16 */:
+		return AF_L16;
+	default:
+		return AF_INVALID;
+	}
+}
+
+static void l16_encode(short *sample, unsigned char *buf, size_t n)
+{
+	for (; n > 0; --n, ++sample, buf += 2) {
+		buf[0] = sample[0] >> 8;
+		buf[1] = sample[0] & 0xff;
+	}
+}
+
+static void l16_decode(unsigned char *buf, short *sample, size_t n)
+{
+	for (; n > 0; --n, ++sample, buf += 2)
+		sample[0] = ((short)buf[0] << 8) | buf[1];
+}
+
+static void alaw_encode(short *sample, unsigned char *buf, size_t n)
+{
+	for (; n > 0; --n)
+		*(buf++) = s16_to_alaw(*(sample++));
+}
+
+static void alaw_decode(unsigned char *buf, short *sample, size_t n)
+{
+	for (; n > 0; --n)
+		*(sample++) = alaw_to_s16(*(buf++));
+}
+
+static void ulaw_encode(short *sample, unsigned char *buf, size_t n)
+{
+	for (; n > 0; --n)
+		*(buf++) = s16_to_ulaw(*(sample++));
+}
+
+static void ulaw_decode(unsigned char *buf, short *sample, size_t n)
+{
+	for (; n > 0; --n)
+		*(sample++) = ulaw_to_s16(*(buf++));
+}
+
+static int processing_state_destructor(struct mgcp_process_rtp_state *state)
+{
+	switch (state->src_fmt) {
+	case AF_GSM:
+		if (state->src.gsm_handle)
+			gsm_destroy(state->src.gsm_handle);
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		if (state->src.g729_dec)
+			closeBcg729DecoderChannel(state->src.g729_dec);
+		break;
+#endif
+	default:
+		break;
+	}
+	switch (state->dst_fmt) {
+	case AF_GSM:
+		if (state->dst.gsm_handle)
+			gsm_destroy(state->dst.gsm_handle);
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		if (state->dst.g729_enc)
+			closeBcg729EncoderChannel(state->dst.g729_enc);
+		break;
+#endif
+	default:
+		break;
+	}
+	return 0;
+}
+
+int mgcp_transcoding_setup(struct mgcp_endpoint *endp,
+			   struct mgcp_rtp_end *dst_end,
+			   struct mgcp_rtp_end *src_end)
+{
+	struct mgcp_process_rtp_state *state;
+	enum audio_format src_fmt, dst_fmt;
+	const struct mgcp_rtp_codec *dst_codec = &dst_end->codec;
+
+	/* cleanup first */
+	if (dst_end->rtp_process_data) {
+		talloc_free(dst_end->rtp_process_data);
+		dst_end->rtp_process_data = NULL;
+	}
+
+	if (!src_end)
+		return 0;
+
+	const struct mgcp_rtp_codec *src_codec = &src_end->codec;
+
+	if (endp->tcfg->no_audio_transcoding) {
+		LOGP(DMGCP, LOGL_NOTICE,
+			"Transcoding disabled on endpoint 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	src_fmt = get_audio_format(src_codec);
+	dst_fmt = get_audio_format(dst_codec);
+
+	LOGP(DMGCP, LOGL_ERROR,
+	     "Checking transcoding: %s (%d) -> %s (%d)\n",
+	     src_codec->subtype_name, src_codec->payload_type,
+	     dst_codec->subtype_name, dst_codec->payload_type);
+
+	if (src_fmt == AF_INVALID || dst_fmt == AF_INVALID) {
+		if (!src_codec->subtype_name || !dst_codec->subtype_name)
+			/* Not enough info, do nothing */
+			return 0;
+
+		if (strcasecmp(src_codec->subtype_name, dst_codec->subtype_name) == 0)
+			/* Nothing to do */
+			return 0;
+
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot transcode: %s codec not supported (%s -> %s).\n",
+		     src_fmt != AF_INVALID ? "destination" : "source",
+		     src_codec->audio_name, dst_codec->audio_name);
+		return -EINVAL;
+	}
+
+	if (src_codec->rate && dst_codec->rate && src_codec->rate != dst_codec->rate) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot transcode: rate conversion (%d -> %d) not supported.\n",
+		     src_codec->rate, dst_codec->rate);
+		return -EINVAL;
+	}
+
+	state = talloc_zero(endp->tcfg->cfg, struct mgcp_process_rtp_state);
+	talloc_set_destructor(state, processing_state_destructor);
+	dst_end->rtp_process_data = state;
+
+	state->src_fmt = src_fmt;
+
+	switch (state->src_fmt) {
+	case AF_L16:
+	case AF_S16:
+		state->src_frame_size = 80 * sizeof(short);
+		state->src_samples_per_frame = 80;
+		break;
+	case AF_GSM:
+		state->src_frame_size = sizeof(gsm_frame);
+		state->src_samples_per_frame = 160;
+		state->src.gsm_handle = gsm_create();
+		if (!state->src.gsm_handle) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize GSM decoder.\n");
+			return -EINVAL;
+		}
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		state->src_frame_size = 10;
+		state->src_samples_per_frame = 80;
+		state->src.g729_dec = initBcg729DecoderChannel();
+		if (!state->src.g729_dec) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize G.729 decoder.\n");
+			return -EINVAL;
+		}
+		break;
+#endif
+	case AF_PCMU:
+	case AF_PCMA:
+		state->src_frame_size = 80;
+		state->src_samples_per_frame = 80;
+		break;
+	default:
+		break;
+	}
+
+	state->dst_fmt = dst_fmt;
+
+	switch (state->dst_fmt) {
+	case AF_L16:
+	case AF_S16:
+		state->dst_frame_size = 80*sizeof(short);
+		state->dst_samples_per_frame = 80;
+		break;
+	case AF_GSM:
+		state->dst_frame_size = sizeof(gsm_frame);
+		state->dst_samples_per_frame = 160;
+		state->dst.gsm_handle = gsm_create();
+		if (!state->dst.gsm_handle) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize GSM encoder.\n");
+			return -EINVAL;
+		}
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		state->dst_frame_size = 10;
+		state->dst_samples_per_frame = 80;
+		state->dst.g729_enc = initBcg729EncoderChannel();
+		if (!state->dst.g729_enc) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize G.729 decoder.\n");
+			return -EINVAL;
+		}
+		break;
+#endif
+	case AF_PCMU:
+	case AF_PCMA:
+		state->dst_frame_size = 80;
+		state->dst_samples_per_frame = 80;
+		break;
+	default:
+		break;
+	}
+
+	if (dst_end->force_output_ptime)
+		state->dst_packet_duration = mgcp_rtp_packet_duration(endp, dst_end);
+
+	LOGP(DMGCP, LOGL_INFO,
+	     "Initialized RTP processing on: 0x%x "
+	     "conv: %d (%d, %d, %s) -> %d (%d, %d, %s)\n",
+	     ENDPOINT_NUMBER(endp),
+	     src_fmt, src_codec->payload_type, src_codec->rate, src_end->fmtp_extra,
+	     dst_fmt, dst_codec->payload_type, dst_codec->rate, dst_end->fmtp_extra);
+
+	return 0;
+}
+
+void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**audio_name,
+					  const char**fmtp_extra)
+{
+	struct mgcp_process_rtp_state *state = endp->net_end.rtp_process_data;
+	struct mgcp_rtp_codec *net_codec = &endp->net_end.codec;
+	struct mgcp_rtp_codec *bts_codec = &endp->bts_end.codec;
+
+	if (!state || net_codec->payload_type < 0) {
+		*payload_type = bts_codec->payload_type;
+		*audio_name = bts_codec->audio_name;
+		*fmtp_extra = endp->bts_end.fmtp_extra;
+		return;
+	}
+
+	*payload_type = net_codec->payload_type;
+	*audio_name = net_codec->audio_name;
+	*fmtp_extra = endp->net_end.fmtp_extra;
+}
+
+static int decode_audio(struct mgcp_process_rtp_state *state,
+			uint8_t **src, size_t *nbytes)
+{
+	while (*nbytes >= state->src_frame_size) {
+		if (state->sample_cnt + state->src_samples_per_frame > ARRAY_SIZE(state->samples)) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Sample buffer too small: %zu > %zu.\n",
+			     state->sample_cnt + state->src_samples_per_frame,
+			     ARRAY_SIZE(state->samples));
+			return -ENOSPC;
+		}
+		switch (state->src_fmt) {
+		case AF_GSM:
+			if (gsm_decode(state->src.gsm_handle,
+				       (gsm_byte *)*src, state->samples + state->sample_cnt) < 0) {
+				LOGP(DMGCP, LOGL_ERROR,
+				     "Failed to decode GSM.\n");
+				return -EINVAL;
+			}
+			break;
+#ifdef HAVE_BCG729
+		case AF_G729:
+			bcg729Decoder(state->src.g729_dec, *src, 0, state->samples + state->sample_cnt);
+			break;
+#endif
+		case AF_PCMU:
+			ulaw_decode(*src, state->samples + state->sample_cnt,
+				    state->src_samples_per_frame);
+			break;
+		case AF_PCMA:
+			alaw_decode(*src, state->samples + state->sample_cnt,
+				    state->src_samples_per_frame);
+			break;
+		case AF_S16:
+			memmove(state->samples + state->sample_cnt, *src,
+				state->src_frame_size);
+			break;
+		case AF_L16:
+			l16_decode(*src, state->samples + state->sample_cnt,
+				   state->src_samples_per_frame);
+			break;
+		default:
+			break;
+		}
+		*src        += state->src_frame_size;
+		*nbytes     -= state->src_frame_size;
+		state->sample_cnt += state->src_samples_per_frame;
+	}
+	return 0;
+}
+
+static int encode_audio(struct mgcp_process_rtp_state *state,
+			uint8_t *dst, size_t buf_size, size_t max_samples)
+{
+	int nbytes = 0;
+	size_t nsamples = 0;
+	/* Encode samples into dst */
+	while (nsamples + state->dst_samples_per_frame <= max_samples) {
+		if (nbytes + state->dst_frame_size > buf_size) {
+			if (nbytes > 0)
+				break;
+
+			/* Not even one frame fits into the buffer */
+			LOGP(DMGCP, LOGL_INFO,
+			     "Encoding (RTP) buffer too small: %zu > %zu.\n",
+			     nbytes + state->dst_frame_size, buf_size);
+			return -ENOSPC;
+		}
+		switch (state->dst_fmt) {
+		case AF_GSM:
+			gsm_encode(state->dst.gsm_handle,
+				   state->samples + state->sample_offs, dst);
+			break;
+#ifdef HAVE_BCG729
+		case AF_G729:
+			bcg729Encoder(state->dst.g729_enc,
+				      state->samples + state->sample_offs, dst);
+			break;
+#endif
+		case AF_PCMU:
+			ulaw_encode(state->samples + state->sample_offs, dst,
+				    state->src_samples_per_frame);
+			break;
+		case AF_PCMA:
+			alaw_encode(state->samples + state->sample_offs, dst,
+				    state->src_samples_per_frame);
+			break;
+		case AF_S16:
+			memmove(dst, state->samples + state->sample_offs,
+				state->dst_frame_size);
+			break;
+		case AF_L16:
+			l16_encode(state->samples + state->sample_offs, dst,
+				   state->src_samples_per_frame);
+			break;
+		default:
+			break;
+		}
+		dst        += state->dst_frame_size;
+		nbytes     += state->dst_frame_size;
+		state->sample_offs += state->dst_samples_per_frame;
+		nsamples   += state->dst_samples_per_frame;
+	}
+	state->sample_cnt -= nsamples;
+	return nbytes;
+}
+
+static struct mgcp_rtp_end *source_for_dest(struct mgcp_endpoint *endp,
+					struct mgcp_rtp_end *dst_end)
+{
+	if (&endp->bts_end == dst_end)
+		return &endp->net_end;
+	else if (&endp->net_end == dst_end)
+		return &endp->bts_end;
+	OSMO_ASSERT(0);
+}
+
+/*
+ * With some modems we get offered multiple codecs
+ * and we have selected one of them. It might not
+ * be the right one and we need to detect this with
+ * the first audio packets. One difficulty is that
+ * we patch the rtp payload type in place, so we
+ * need to discuss this.
+ */
+struct mgcp_process_rtp_state *check_transcode_state(
+				struct mgcp_endpoint *endp,
+				struct mgcp_rtp_end *dst_end,
+				struct rtp_hdr *rtp_hdr)
+{
+	struct mgcp_rtp_end *src_end;
+
+	/* Only deal with messages from net to bts */
+	if (&endp->bts_end != dst_end)
+		goto done;
+
+	src_end = source_for_dest(endp, dst_end);
+
+	/* Already patched */
+	if (rtp_hdr->payload_type == dst_end->codec.payload_type)
+		goto done;
+	/* The payload we expect */
+	if (rtp_hdr->payload_type == src_end->codec.payload_type)
+		goto done;
+	/* The matching alternate payload type? Then switch */
+	if (rtp_hdr->payload_type == src_end->alt_codec.payload_type) {
+		struct mgcp_config *cfg = endp->cfg;
+		struct mgcp_rtp_codec tmp_codec = src_end->alt_codec;
+		src_end->alt_codec = src_end->codec;
+		src_end->codec = tmp_codec;
+		cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end);
+		cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end);
+	}
+
+done:
+	return dst_end->rtp_process_data;
+}
+
+int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp,
+				struct mgcp_rtp_end *dst_end,
+			     char *data, int *len, int buf_size)
+{
+	struct mgcp_process_rtp_state *state;
+	const size_t rtp_hdr_size = sizeof(struct rtp_hdr);
+	struct rtp_hdr *rtp_hdr = (struct rtp_hdr *) data;
+	char *payload_data = (char *) &rtp_hdr->data[0];
+	int payload_len = *len - rtp_hdr_size;
+	uint8_t *src = (uint8_t *)payload_data;
+	uint8_t *dst = (uint8_t *)payload_data;
+	size_t nbytes = payload_len;
+	size_t nsamples;
+	size_t max_samples;
+	uint32_t ts_no;
+	int rc;
+
+	state = check_transcode_state(endp, dst_end, rtp_hdr);
+	if (!state)
+		return 0;
+
+	if (state->src_fmt == state->dst_fmt) {
+		if (!state->dst_packet_duration)
+			return 0;
+
+		/* TODO: repackage without transcoding */
+	}
+
+	/* If the remaining samples do not fit into a fixed ptime,
+	 * a) discard them, if the next packet is much later
+	 * b) add silence and * send it, if the current packet is not
+	 *    yet too late
+	 * c) append the sample data, if the timestamp matches exactly
+	 */
+
+	/* TODO: check payload type (-> G.711 comfort noise) */
+
+	if (payload_len > 0) {
+		ts_no = ntohl(rtp_hdr->timestamp);
+		if (!state->is_running) {
+			state->next_seq = ntohs(rtp_hdr->sequence);
+			state->next_time = ts_no;
+			state->is_running = 1;
+		}
+
+
+		if (state->sample_cnt > 0) {
+			int32_t delta = ts_no - state->next_time;
+			/* TODO: check sequence? reordering? packet loss? */
+
+			if (delta > state->sample_cnt) {
+				/* There is a time gap between the last packet
+				 * and the current one. Just discard the
+				 * partial data that is left in the buffer.
+				 * TODO: This can be improved by adding silence
+				 * instead if the delta is small enough.
+				 */
+				LOGP(DMGCP, LOGL_NOTICE,
+					"0x%x dropping sample buffer due delta=%d sample_cnt=%zu\n",
+					ENDPOINT_NUMBER(endp), delta, state->sample_cnt);
+				state->sample_cnt = 0;
+				state->next_time = ts_no;
+			} else if (delta < 0) {
+				LOGP(DMGCP, LOGL_NOTICE,
+				     "RTP time jumps backwards, delta = %d, "
+				     "discarding buffered samples\n",
+				     delta);
+				state->sample_cnt = 0;
+				state->sample_offs = 0;
+				return -EAGAIN;
+			}
+
+			/* Make sure the samples start without offset */
+			if (state->sample_offs && state->sample_cnt)
+				memmove(&state->samples[0],
+					&state->samples[state->sample_offs],
+					state->sample_cnt *
+					sizeof(state->samples[0]));
+		}
+
+		state->sample_offs = 0;
+
+		/* Append decoded audio to samples */
+		decode_audio(state, &src, &nbytes);
+
+		if (nbytes > 0)
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "Skipped audio frame in RTP packet: %zu octets\n",
+			     nbytes);
+	} else
+		ts_no = state->next_time;
+
+	if (state->sample_cnt < state->dst_packet_duration)
+		return -EAGAIN;
+
+	max_samples =
+		state->dst_packet_duration ?
+		state->dst_packet_duration : state->sample_cnt;
+
+	nsamples = state->sample_cnt;
+
+	rc = encode_audio(state, dst, buf_size, max_samples);
+	/*
+	 * There were no samples to encode?
+	 * TODO: how does this work for comfort noise?
+	 */
+	if (rc == 0)
+		return -ENOMSG;
+	/* Any other error during the encoding */
+	if (rc < 0)
+		return rc;
+
+	nsamples -= state->sample_cnt;
+
+	*len = rtp_hdr_size + rc;
+	rtp_hdr->sequence = htons(state->next_seq);
+	rtp_hdr->timestamp = htonl(ts_no);
+
+	state->next_seq += 1;
+	state->next_time = ts_no + nsamples;
+
+	/*
+	 * XXX: At this point we should always have consumed
+	 * samples. So doing OSMO_ASSERT(nsamples > 0) and returning
+	 * rtp_hdr_size should be fine.
+	 */
+	return nsamples ? rtp_hdr_size : 0;
+}
diff --git a/openbsc/src/libmgcp/mgcp_vty.c b/openbsc/src/libmgcp/mgcp_vty.c
new file mode 100644
index 0000000..23207df
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_vty.c
@@ -0,0 +1,1627 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/core/talloc.h>
+
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/vty.h>
+
+#include <string.h>
+#include <inttypes.h>
+
+#define RTCP_OMIT_STR "Drop RTCP packets in both directions\n"
+#define RTP_PATCH_STR "Modify RTP packet header in both directions\n"
+#define RTP_KEEPALIVE_STR "Send dummy UDP packet to net RTP destination\n"
+
+static struct mgcp_config *g_cfg = NULL;
+
+static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr)
+{
+	struct mgcp_trunk_config *trunk;
+
+	if (nr == 0)
+		trunk = &cfg->trunk;
+	else
+		trunk = mgcp_trunk_num(cfg, nr);
+
+	return trunk;
+}
+
+/*
+ * vty code for mgcp below
+ */
+struct cmd_node mgcp_node = {
+	MGCP_NODE,
+	"%s(config-mgcp)# ",
+	1,
+};
+
+struct cmd_node trunk_node = {
+	TRUNK_NODE,
+	"%s(config-mgcp-trunk)# ",
+	1,
+};
+
+static int config_write_mgcp(struct vty *vty)
+{
+	vty_out(vty, "mgcp%s", VTY_NEWLINE);
+	if (g_cfg->local_ip)
+		vty_out(vty, "  local ip %s%s", g_cfg->local_ip, VTY_NEWLINE);
+	if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0)
+		vty_out(vty, "  bts ip %s%s", g_cfg->bts_ip, VTY_NEWLINE);
+	vty_out(vty, "  bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE);
+	vty_out(vty, "  bind port %u%s", g_cfg->source_port, VTY_NEWLINE);
+
+	if (g_cfg->bts_ports.mode == PORT_ALLOC_STATIC)
+		vty_out(vty, "  rtp bts-base %u%s", g_cfg->bts_ports.base_port, VTY_NEWLINE);
+	else
+		vty_out(vty, "  rtp bts-range %u %u%s",
+			g_cfg->bts_ports.range_start, g_cfg->bts_ports.range_end, VTY_NEWLINE);
+	if (g_cfg->bts_ports.bind_addr)
+		vty_out(vty, "  rtp bts-bind-ip %s%s", g_cfg->bts_ports.bind_addr, VTY_NEWLINE);
+
+	if (g_cfg->net_ports.mode == PORT_ALLOC_STATIC)
+		vty_out(vty, "  rtp net-base %u%s", g_cfg->net_ports.base_port, VTY_NEWLINE);
+	else
+		vty_out(vty, "  rtp net-range %u %u%s",
+			g_cfg->net_ports.range_start, g_cfg->net_ports.range_end, VTY_NEWLINE);
+	if (g_cfg->net_ports.bind_addr)
+		vty_out(vty, "  rtp net-bind-ip %s%s", g_cfg->net_ports.bind_addr, VTY_NEWLINE);
+
+	vty_out(vty, "  rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE);
+	if (g_cfg->trunk.keepalive_interval == MGCP_KEEPALIVE_ONCE)
+		vty_out(vty, "  rtp keep-alive once%s", VTY_NEWLINE);
+	else if (g_cfg->trunk.keepalive_interval)
+		vty_out(vty, "  rtp keep-alive %d%s",
+			g_cfg->trunk.keepalive_interval, VTY_NEWLINE);
+	else
+		vty_out(vty, "  no rtp keep-alive%s", VTY_NEWLINE);
+
+	if (g_cfg->trunk.omit_rtcp)
+		vty_out(vty, "  rtcp-omit%s", VTY_NEWLINE);
+	else
+		vty_out(vty, "  no rtcp-omit%s", VTY_NEWLINE);
+	if (g_cfg->trunk.force_constant_ssrc || g_cfg->trunk.force_aligned_timing) {
+		vty_out(vty, "  %srtp-patch ssrc%s",
+			g_cfg->trunk.force_constant_ssrc ? "" : "no ", VTY_NEWLINE);
+		vty_out(vty, "  %srtp-patch timestamp%s",
+			g_cfg->trunk.force_aligned_timing ? "" : "no ", VTY_NEWLINE);
+	} else
+		vty_out(vty, "  no rtp-patch%s", VTY_NEWLINE);
+	if (g_cfg->trunk.audio_payload != -1)
+		vty_out(vty, "  sdp audio-payload number %d%s",
+			g_cfg->trunk.audio_payload, VTY_NEWLINE);
+	if (g_cfg->trunk.audio_name)
+		vty_out(vty, "  sdp audio-payload name %s%s",
+			g_cfg->trunk.audio_name, VTY_NEWLINE);
+	if (g_cfg->trunk.audio_fmtp_extra)
+		vty_out(vty, "  sdp audio fmtp-extra %s%s",
+			g_cfg->trunk.audio_fmtp_extra, VTY_NEWLINE);
+	vty_out(vty, "  %ssdp audio-payload send-ptime%s",
+		g_cfg->trunk.audio_send_ptime ? "" : "no ", VTY_NEWLINE);
+	vty_out(vty, "  %ssdp audio-payload send-name%s",
+		g_cfg->trunk.audio_send_name ? "" : "no ", VTY_NEWLINE);
+	vty_out(vty, "  loop %u%s", !!g_cfg->trunk.audio_loop, VTY_NEWLINE);
+	vty_out(vty, "  number endpoints %u%s", g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE);
+	vty_out(vty, "  %sallow-transcoding%s",
+		g_cfg->trunk.no_audio_transcoding ? "no " : "", VTY_NEWLINE);
+	if (g_cfg->call_agent_addr)
+		vty_out(vty, "  call-agent ip %s%s", g_cfg->call_agent_addr, VTY_NEWLINE);
+	if (g_cfg->transcoder_ip)
+		vty_out(vty, "  transcoder-mgw %s%s", g_cfg->transcoder_ip, VTY_NEWLINE);
+
+	if (g_cfg->transcoder_ports.mode == PORT_ALLOC_STATIC)
+		vty_out(vty, "  rtp transcoder-base %u%s", g_cfg->transcoder_ports.base_port, VTY_NEWLINE);
+	else
+		vty_out(vty, "  rtp transcoder-range %u %u%s",
+			g_cfg->transcoder_ports.range_start, g_cfg->transcoder_ports.range_end, VTY_NEWLINE);
+	if (g_cfg->bts_force_ptime > 0)
+		vty_out(vty, "  rtp force-ptime %d%s", g_cfg->bts_force_ptime, VTY_NEWLINE);
+	vty_out(vty, "  transcoder-remote-base %u%s", g_cfg->transcoder_remote_base, VTY_NEWLINE);
+
+	switch (g_cfg->osmux) {
+	case OSMUX_USAGE_ON:
+		vty_out(vty, "  osmux on%s", VTY_NEWLINE);
+		break;
+	case OSMUX_USAGE_ONLY:
+		vty_out(vty, "  osmux only%s", VTY_NEWLINE);
+		break;
+	case OSMUX_USAGE_OFF:
+	default:
+		vty_out(vty, "  osmux off%s", VTY_NEWLINE);
+		break;
+	}
+	if (g_cfg->osmux) {
+		vty_out(vty, "  osmux bind-ip %s%s",
+			g_cfg->osmux_addr, VTY_NEWLINE);
+		vty_out(vty, "  osmux batch-factor %d%s",
+			g_cfg->osmux_batch, VTY_NEWLINE);
+		vty_out(vty, "  osmux batch-size %u%s",
+			g_cfg->osmux_batch_size, VTY_NEWLINE);
+		vty_out(vty, "  osmux port %u%s",
+			g_cfg->osmux_port, VTY_NEWLINE);
+		vty_out(vty, "  osmux dummy %s%s",
+			g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE);
+	}
+	if (g_cfg->bts_use_jibuf)
+		vty_out(vty, "  bts-jitter-buffer%s", VTY_NEWLINE);
+	if (g_cfg->bts_jitter_delay_min)
+		vty_out(vty, "  bts-jitter-buffer-delay-min %"PRIu32"%s", g_cfg->bts_jitter_delay_min, VTY_NEWLINE);
+	if (g_cfg->bts_jitter_delay_max)
+		vty_out(vty, "  bts-jitter-buffer-delay-max %"PRIu32"%s", g_cfg->bts_jitter_delay_max, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+static void dump_rtp_end(const char *end_name, struct vty *vty,
+			struct mgcp_rtp_state *state, struct mgcp_rtp_end *end)
+{
+	struct mgcp_rtp_codec *codec = &end->codec;
+
+	vty_out(vty,
+		"  %s%s"
+		"   Timestamp Errs: %d->%d%s"
+		"   Dropped Packets: %d%s"
+		"   Payload Type: %d Rate: %u Channels: %d %s"
+		"   Frame Duration: %u Frame Denominator: %u%s"
+		"   FPP: %d Packet Duration: %u%s"
+		"   FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s"
+		"   Output-Enabled: %d Force-PTIME: %d%s",
+		end_name, VTY_NEWLINE,
+		state->in_stream.err_ts_counter,
+		state->out_stream.err_ts_counter, VTY_NEWLINE,
+		end->dropped_packets, VTY_NEWLINE,
+		codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE,
+		codec->frame_duration_num, codec->frame_duration_den, VTY_NEWLINE,
+		end->frames_per_packet, end->packet_duration_ms, VTY_NEWLINE,
+		end->fmtp_extra, codec->audio_name, codec->subtype_name, VTY_NEWLINE,
+		end->output_enabled, end->force_output_ptime, VTY_NEWLINE);
+}
+
+static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg, int verbose)
+{
+	int i;
+
+	vty_out(vty, "%s trunk nr %d with %d endpoints:%s",
+		cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1",
+		cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE);
+
+	if (!cfg->endpoints) {
+		vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE);
+		return;
+	}
+
+	for (i = 1; i < cfg->number_endpoints; ++i) {
+		struct mgcp_endpoint *endp = &cfg->endpoints[i];
+		vty_out(vty,
+			" Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s "
+			"traffic received bts: %u  remote: %u transcoder: %u/%u%s",
+			i, endp->ci,
+			ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port),
+			ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port),
+			inet_ntoa(endp->bts_end.addr),
+			endp->bts_end.packets, endp->net_end.packets,
+			endp->trans_net.packets, endp->trans_bts.packets,
+			VTY_NEWLINE);
+
+		if (verbose && endp->allocated) {
+			dump_rtp_end("Net->BTS", vty, &endp->bts_state, &endp->bts_end);
+			dump_rtp_end("BTS->Net", vty, &endp->net_state, &endp->net_end);
+		}
+	}
+}
+
+DEFUN(show_mcgp, show_mgcp_cmd,
+      "show mgcp [stats]",
+      SHOW_STR
+      "Display information about the MGCP Media Gateway\n"
+      "Include Statistics\n")
+{
+	struct mgcp_trunk_config *trunk;
+	int show_stats = argc >= 1;
+
+	dump_trunk(vty, &g_cfg->trunk, show_stats);
+
+	llist_for_each_entry(trunk, &g_cfg->trunks, entry)
+		dump_trunk(vty, trunk, show_stats);
+
+	if (g_cfg->osmux)
+		vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(), VTY_NEWLINE);
+	vty_out(vty, "Jitter Buffer by default on Uplink : %s%s",
+		g_cfg->bts_use_jibuf ? "on" : "off", VTY_NEWLINE);
+	if (g_cfg->bts_use_jibuf)
+		vty_out(vty, "Jitter Buffer delays: min=%"PRIu32" max=%"PRIu32"%s",
+		g_cfg->bts_jitter_delay_min, g_cfg->bts_jitter_delay_max, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp,
+      cfg_mgcp_cmd,
+      "mgcp",
+      "Configure the MGCP")
+{
+	vty->node = MGCP_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_local_ip,
+      cfg_mgcp_local_ip_cmd,
+      "local ip A.B.C.D",
+      "Local options for the SDP record\n"
+      IP_STR
+      "IPv4 Address to use in SDP record\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bts_ip,
+      cfg_mgcp_bts_ip_cmd,
+      "bts ip A.B.C.D",
+      "BTS Audio source/destination options\n"
+      IP_STR
+      "IPv4 Address of the BTS\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ip, argv[0]);
+	inet_aton(g_cfg->bts_ip, &g_cfg->bts_in);
+	return CMD_SUCCESS;
+}
+
+#define BIND_STR "Listen/Bind related socket option\n"
+DEFUN(cfg_mgcp_bind_ip,
+      cfg_mgcp_bind_ip_cmd,
+      "bind ip A.B.C.D",
+      BIND_STR
+      IP_STR
+      "IPv4 Address to bind to\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_port,
+      cfg_mgcp_bind_port_cmd,
+      "bind port <0-65534>",
+      BIND_STR
+      "Port information\n"
+      "UDP port to listen for MGCP messages\n")
+{
+	unsigned int port = atoi(argv[0]);
+	g_cfg->source_port = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_early,
+      cfg_mgcp_bind_early_cmd,
+      "bind early (0|1)",
+      BIND_STR
+      "Bind local ports on start up\n"
+      "Bind on demand\n" "Bind on startup\n")
+{
+	vty_out(vty, "bind early is deprecated, remove it from the config.\n");
+	return CMD_WARNING;
+}
+
+static void parse_base(struct mgcp_port_range *range, const char **argv)
+{
+	unsigned int port = atoi(argv[0]);
+	range->mode = PORT_ALLOC_STATIC;
+	range->base_port = port;
+}
+
+static void parse_range(struct mgcp_port_range *range, const char **argv)
+{
+	range->mode = PORT_ALLOC_DYNAMIC;
+	range->range_start = atoi(argv[0]);
+	range->range_end = atoi(argv[1]);
+	range->last_port = g_cfg->bts_ports.range_start;
+}
+
+
+#define RTP_STR "RTP configuration\n"
+#define BTS_START_STR "First UDP port allocated for the BTS side\n"
+#define NET_START_STR "First UDP port allocated for the NET side\n"
+#define UDP_PORT_STR "UDP Port number\n"
+DEFUN(cfg_mgcp_rtp_bts_base_port,
+      cfg_mgcp_rtp_bts_base_port_cmd,
+      "rtp bts-base <0-65534>",
+      RTP_STR
+      BTS_START_STR
+      UDP_PORT_STR)
+{
+	parse_base(&g_cfg->bts_ports, argv);
+	return CMD_SUCCESS;
+}
+
+#define RANGE_START_STR "Start of the range of ports\n"
+#define RANGE_END_STR "End of the range of ports\n"
+DEFUN(cfg_mgcp_rtp_bts_range,
+      cfg_mgcp_rtp_bts_range_cmd,
+      "rtp bts-range <0-65534> <0-65534>",
+      RTP_STR "Range of ports to use for the BTS side\n"
+      RANGE_START_STR RANGE_END_STR)
+{
+	parse_range(&g_cfg->bts_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_range,
+      cfg_mgcp_rtp_net_range_cmd,
+      "rtp net-range <0-65534> <0-65534>",
+      RTP_STR "Range of ports to use for the NET side\n"
+      RANGE_START_STR RANGE_END_STR)
+{
+	parse_range(&g_cfg->net_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_base_port,
+      cfg_mgcp_rtp_net_base_port_cmd,
+      "rtp net-base <0-65534>",
+      RTP_STR NET_START_STR UDP_PORT_STR)
+{
+	parse_base(&g_cfg->net_ports, argv);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_rtp_bts_base_port, cfg_mgcp_rtp_base_port_cmd,
+      "rtp base <0-65534>",
+      RTP_STR BTS_START_STR UDP_PORT_STR)
+
+DEFUN(cfg_mgcp_rtp_transcoder_range,
+      cfg_mgcp_rtp_transcoder_range_cmd,
+      "rtp transcoder-range <0-65534> <0-65534>",
+      RTP_STR "Range of ports to use for the Transcoder\n"
+      RANGE_START_STR RANGE_END_STR)
+{
+	parse_range(&g_cfg->transcoder_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_transcoder_base,
+      cfg_mgcp_rtp_transcoder_base_cmd,
+      "rtp transcoder-base <0-65534>",
+      RTP_STR "First UDP port allocated for the Transcoder side\n"
+      UDP_PORT_STR)
+{
+	parse_base(&g_cfg->transcoder_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_bts_bind_ip,
+      cfg_mgcp_rtp_bts_bind_ip_cmd,
+      "rtp bts-bind-ip A.B.C.D",
+      RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ports.bind_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_no_bts_bind_ip,
+      cfg_mgcp_rtp_no_bts_bind_ip_cmd,
+      "no rtp bts-bind-ip",
+      NO_STR RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n")
+{
+	talloc_free(g_cfg->bts_ports.bind_addr);
+	g_cfg->bts_ports.bind_addr = NULL;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_bind_ip,
+      cfg_mgcp_rtp_net_bind_ip_cmd,
+      "rtp net-bind-ip A.B.C.D",
+      RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->net_ports.bind_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_no_net_bind_ip,
+      cfg_mgcp_rtp_no_net_bind_ip_cmd,
+      "no rtp net-bind-ip",
+      NO_STR RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n")
+{
+	talloc_free(g_cfg->net_ports.bind_addr);
+	g_cfg->net_ports.bind_addr = NULL;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_ip_dscp,
+      cfg_mgcp_rtp_ip_dscp_cmd,
+      "rtp ip-dscp <0-255>",
+      RTP_STR
+      "Apply IP_TOS to the audio stream (including Osmux)\n" "The DSCP value\n")
+{
+	int dscp = atoi(argv[0]);
+	g_cfg->endp_dscp = dscp;
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd,
+      "rtp ip-tos <0-255>",
+      RTP_STR
+      "Apply IP_TOS to the audio stream\n" "The DSCP value\n")
+
+#define FORCE_PTIME_STR "Force a fixed ptime for packets sent to the BTS"
+DEFUN(cfg_mgcp_rtp_force_ptime,
+      cfg_mgcp_rtp_force_ptime_cmd,
+      "rtp force-ptime (10|20|40)",
+      RTP_STR FORCE_PTIME_STR
+      "The required ptime (packet duration) in ms\n"
+      "10 ms\n20 ms\n40 ms\n")
+{
+	g_cfg->bts_force_ptime = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_rtp_force_ptime,
+      cfg_mgcp_no_rtp_force_ptime_cmd,
+      "no rtp force-ptime",
+      NO_STR RTP_STR FORCE_PTIME_STR)
+{
+	g_cfg->bts_force_ptime = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_fmtp_extra,
+      cfg_mgcp_sdp_fmtp_extra_cmd,
+      "sdp audio fmtp-extra .NAME",
+      "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n"
+      "Extra Information\n")
+{
+	char *txt = argv_concat(argv, argc, 0);
+	if (!txt)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_fmtp_extra, txt);
+	talloc_free(txt);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_allow_transcoding,
+      cfg_mgcp_allow_transcoding_cmd,
+      "allow-transcoding",
+      "Allow transcoding\n")
+{
+	g_cfg->trunk.no_audio_transcoding = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_allow_transcoding,
+      cfg_mgcp_no_allow_transcoding_cmd,
+      "no allow-transcoding",
+      NO_STR "Allow transcoding\n")
+{
+	g_cfg->trunk.no_audio_transcoding = 1;
+	return CMD_SUCCESS;
+}
+
+#define SDP_STR "SDP File related options\n"
+#define AUDIO_STR "Audio payload options\n"
+DEFUN(cfg_mgcp_sdp_payload_number,
+      cfg_mgcp_sdp_payload_number_cmd,
+      "sdp audio-payload number <0-255>",
+      SDP_STR AUDIO_STR
+      "Number\n" "Payload number\n")
+{
+	unsigned int payload = atoi(argv[0]);
+	g_cfg->trunk.audio_payload = payload;
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_number, cfg_mgcp_sdp_payload_number_cmd_old,
+      "sdp audio payload number <0-255>",
+      SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload number\n")
+      
+
+DEFUN(cfg_mgcp_sdp_payload_name,
+      cfg_mgcp_sdp_payload_name_cmd,
+      "sdp audio-payload name NAME",
+      SDP_STR AUDIO_STR "Name\n" "Payload name\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_name, cfg_mgcp_sdp_payload_name_cmd_old,
+      "sdp audio payload name NAME",
+      SDP_STR AUDIO_STR AUDIO_STR "Name\n" "Payload name\n")
+
+DEFUN(cfg_mgcp_sdp_payload_send_ptime,
+      cfg_mgcp_sdp_payload_send_ptime_cmd,
+      "sdp audio-payload send-ptime",
+      SDP_STR AUDIO_STR
+      "Send SDP ptime (packet duration) attribute\n")
+{
+	g_cfg->trunk.audio_send_ptime = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_sdp_payload_send_ptime,
+      cfg_mgcp_no_sdp_payload_send_ptime_cmd,
+      "no sdp audio-payload send-ptime",
+      NO_STR SDP_STR AUDIO_STR
+      "Send SDP ptime (packet duration) attribute\n")
+{
+	g_cfg->trunk.audio_send_ptime = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_payload_send_name,
+      cfg_mgcp_sdp_payload_send_name_cmd,
+      "sdp audio-payload send-name",
+      SDP_STR AUDIO_STR
+      "Send SDP rtpmap with the audio name\n")
+{
+	g_cfg->trunk.audio_send_name = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_sdp_payload_send_name,
+      cfg_mgcp_no_sdp_payload_send_name_cmd,
+      "no sdp audio-payload send-name",
+      NO_STR SDP_STR AUDIO_STR
+      "Send SDP rtpmap with the audio name\n")
+{
+	g_cfg->trunk.audio_send_name = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_loop,
+      cfg_mgcp_loop_cmd,
+      "loop (0|1)",
+      "Loop audio for all endpoints on main trunk\n"
+      "Don't Loop\n" "Loop\n")
+{
+	if (g_cfg->osmux) {
+		vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	g_cfg->trunk.audio_loop = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_force_realloc,
+      cfg_mgcp_force_realloc_cmd,
+      "force-realloc (0|1)",
+      "Force endpoint reallocation when the endpoint is still seized\n"
+      "Don't force reallocation\n" "force reallocation\n")
+{
+	g_cfg->trunk.force_realloc = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_number_endp,
+      cfg_mgcp_number_endp_cmd,
+      "number endpoints <0-65534>",
+      "Number options\n" "Endpoints available\n" "Number endpoints\n")
+{
+	/* + 1 as we start counting at one */
+	g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_omit_rtcp,
+      cfg_mgcp_omit_rtcp_cmd,
+      "rtcp-omit",
+      RTCP_OMIT_STR)
+{
+	g_cfg->trunk.omit_rtcp = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_omit_rtcp,
+      cfg_mgcp_no_omit_rtcp_cmd,
+      "no rtcp-omit",
+      NO_STR RTCP_OMIT_STR)
+{
+	g_cfg->trunk.omit_rtcp = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_patch_rtp_ssrc,
+      cfg_mgcp_patch_rtp_ssrc_cmd,
+      "rtp-patch ssrc",
+      RTP_PATCH_STR
+      "Force a fixed SSRC\n"
+      )
+{
+	g_cfg->trunk.force_constant_ssrc = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_patch_rtp_ssrc,
+      cfg_mgcp_no_patch_rtp_ssrc_cmd,
+      "no rtp-patch ssrc",
+      NO_STR RTP_PATCH_STR
+      "Force a fixed SSRC\n"
+      )
+{
+	g_cfg->trunk.force_constant_ssrc = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_patch_rtp_ts,
+      cfg_mgcp_patch_rtp_ts_cmd,
+      "rtp-patch timestamp",
+      RTP_PATCH_STR
+      "Adjust RTP timestamp\n"
+      )
+{
+	g_cfg->trunk.force_aligned_timing = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_patch_rtp_ts,
+      cfg_mgcp_no_patch_rtp_ts_cmd,
+      "no rtp-patch timestamp",
+      NO_STR RTP_PATCH_STR
+      "Adjust RTP timestamp\n"
+      )
+{
+	g_cfg->trunk.force_aligned_timing = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_patch_rtp,
+      cfg_mgcp_no_patch_rtp_cmd,
+      "no rtp-patch",
+      NO_STR RTP_PATCH_STR)
+{
+	g_cfg->trunk.force_constant_ssrc = 0;
+	g_cfg->trunk.force_aligned_timing = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_keepalive,
+      cfg_mgcp_rtp_keepalive_cmd,
+      "rtp keep-alive <1-120>",
+      RTP_STR RTP_KEEPALIVE_STR
+      "Keep alive interval in secs\n"
+      )
+{
+	mgcp_trunk_set_keepalive(&g_cfg->trunk, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_keepalive_once,
+      cfg_mgcp_rtp_keepalive_once_cmd,
+      "rtp keep-alive once",
+      RTP_STR RTP_KEEPALIVE_STR
+      "Send dummy packet only once after CRCX/MDCX\n"
+      )
+{
+	mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_ONCE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_rtp_keepalive,
+      cfg_mgcp_no_rtp_keepalive_cmd,
+      "no rtp keep-alive",
+      NO_STR RTP_STR RTP_KEEPALIVE_STR
+      )
+{
+	mgcp_trunk_set_keepalive(&g_cfg->trunk, 0);
+	return CMD_SUCCESS;
+}
+
+
+
+#define CALL_AGENT_STR "Callagent information\n"
+DEFUN(cfg_mgcp_agent_addr,
+      cfg_mgcp_agent_addr_cmd,
+      "call-agent ip A.B.C.D",
+      CALL_AGENT_STR IP_STR
+      "IPv4 Address of the callagent\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_agent_addr, cfg_mgcp_agent_addr_cmd_old,
+      "call agent ip A.B.C.D",
+      CALL_AGENT_STR CALL_AGENT_STR IP_STR
+      "IPv4 Address of the callagent\n")
+      
+
+DEFUN(cfg_mgcp_transcoder,
+      cfg_mgcp_transcoder_cmd,
+      "transcoder-mgw A.B.C.D",
+      "Use a MGW to detranscoder RTP\n"
+      "The IP address of the MGW")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->transcoder_ip, argv[0]);
+	inet_aton(g_cfg->transcoder_ip, &g_cfg->transcoder_in);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_transcoder,
+      cfg_mgcp_no_transcoder_cmd,
+      "no transcoder-mgw",
+      NO_STR "Disable the transcoding\n")
+{
+	if (g_cfg->transcoder_ip) {
+		LOGP(DMGCP, LOGL_NOTICE, "Disabling transcoding on future calls.\n");
+		talloc_free(g_cfg->transcoder_ip);
+		g_cfg->transcoder_ip = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_transcoder_remote_base,
+      cfg_mgcp_transcoder_remote_base_cmd,
+      "transcoder-remote-base <0-65534>",
+      "Set the base port for the transcoder\n" "The RTP base port on the transcoder")
+{
+	g_cfg->transcoder_remote_base = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd,
+      "trunk <1-64>",
+      "Configure a SS7 trunk\n" "Trunk Nr\n")
+{
+	struct mgcp_trunk_config *trunk;
+	int index = atoi(argv[0]);
+
+	trunk = mgcp_trunk_num(g_cfg, index);
+	if (!trunk)
+		trunk = mgcp_trunk_alloc(g_cfg, index);
+
+	if (!trunk) {
+		vty_out(vty, "%%Unable to allocate trunk %u.%s",
+			index, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->node = TRUNK_NODE;
+	vty->index = trunk;
+	return CMD_SUCCESS;
+}
+
+static int config_write_trunk(struct vty *vty)
+{
+	struct mgcp_trunk_config *trunk;
+
+	llist_for_each_entry(trunk, &g_cfg->trunks, entry) {
+		vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE);
+		vty_out(vty, "  sdp audio-payload number %d%s",
+			trunk->audio_payload, VTY_NEWLINE);
+		vty_out(vty, "  sdp audio-payload name %s%s",
+			trunk->audio_name, VTY_NEWLINE);
+		vty_out(vty, "  %ssdp audio-payload send-ptime%s",
+			trunk->audio_send_ptime ? "" : "no ", VTY_NEWLINE);
+		vty_out(vty, "  %ssdp audio-payload send-name%s",
+			trunk->audio_send_name ? "" : "no ", VTY_NEWLINE);
+
+		if (trunk->keepalive_interval == MGCP_KEEPALIVE_ONCE)
+			vty_out(vty, "  rtp keep-alive once%s", VTY_NEWLINE);
+		else if (trunk->keepalive_interval)
+			vty_out(vty, "  rtp keep-alive %d%s",
+				trunk->keepalive_interval, VTY_NEWLINE);
+		else
+			vty_out(vty, "  no rtp keep-alive%s", VTY_NEWLINE);
+		vty_out(vty, "  loop %d%s",
+			trunk->audio_loop, VTY_NEWLINE);
+		vty_out(vty, "  force-realloc %d%s",
+			trunk->force_realloc, VTY_NEWLINE);
+		if (trunk->omit_rtcp)
+			vty_out(vty, "  rtcp-omit%s", VTY_NEWLINE);
+		else
+			vty_out(vty, "  no rtcp-omit%s", VTY_NEWLINE);
+		if (trunk->force_constant_ssrc || trunk->force_aligned_timing) {
+			vty_out(vty, "  %srtp-patch ssrc%s",
+				trunk->force_constant_ssrc ? "" : "no ", VTY_NEWLINE);
+			vty_out(vty, "  %srtp-patch timestamp%s",
+				trunk->force_aligned_timing ? "" : "no ", VTY_NEWLINE);
+		} else
+			vty_out(vty, "  no rtp-patch%s", VTY_NEWLINE);
+		if (trunk->audio_fmtp_extra)
+			vty_out(vty, "   sdp audio fmtp-extra %s%s",
+				trunk->audio_fmtp_extra, VTY_NEWLINE);
+		vty_out(vty, "  %sallow-transcoding%s",
+			trunk->no_audio_transcoding ? "no " : "", VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_sdp_fmtp_extra,
+      cfg_trunk_sdp_fmtp_extra_cmd,
+      "sdp audio fmtp-extra .NAME",
+      "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n"
+      "Extra Information\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	char *txt = argv_concat(argv, argc, 0);
+	if (!txt)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt);
+	talloc_free(txt);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_payload_number,
+      cfg_trunk_payload_number_cmd,
+      "sdp audio-payload number <0-255>",
+      SDP_STR AUDIO_STR "Number\n" "Payload Number\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	unsigned int payload = atoi(argv[0]);
+
+	trunk->audio_payload = payload;
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_trunk_payload_number, cfg_trunk_payload_number_cmd_old,
+      "sdp audio payload number <0-255>",
+      SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload Number\n")
+
+DEFUN(cfg_trunk_payload_name,
+      cfg_trunk_payload_name_cmd,
+      "sdp audio-payload name NAME",
+       SDP_STR AUDIO_STR "Payload\n" "Payload Name\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+
+	osmo_talloc_replace_string(g_cfg, &trunk->audio_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_trunk_payload_name, cfg_trunk_payload_name_cmd_old,
+      "sdp audio payload name NAME",
+       SDP_STR AUDIO_STR AUDIO_STR "Payload\n" "Payload Name\n")
+
+
+DEFUN(cfg_trunk_loop,
+      cfg_trunk_loop_cmd,
+      "loop (0|1)",
+      "Loop audio for all endpoints on this trunk\n"
+      "Don't Loop\n" "Loop\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+
+	if (g_cfg->osmux) {
+		vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	trunk->audio_loop = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_sdp_payload_send_ptime,
+      cfg_trunk_sdp_payload_send_ptime_cmd,
+      "sdp audio-payload send-ptime",
+      SDP_STR AUDIO_STR
+      "Send SDP ptime (packet duration) attribute\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->audio_send_ptime = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_sdp_payload_send_ptime,
+      cfg_trunk_no_sdp_payload_send_ptime_cmd,
+      "no sdp audio-payload send-ptime",
+      NO_STR SDP_STR AUDIO_STR
+      "Send SDP ptime (packet duration) attribute\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->audio_send_ptime = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_sdp_payload_send_name,
+      cfg_trunk_sdp_payload_send_name_cmd,
+      "sdp audio-payload send-name",
+      SDP_STR AUDIO_STR
+      "Send SDP rtpmap with the audio name\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->audio_send_name = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_sdp_payload_send_name,
+      cfg_trunk_no_sdp_payload_send_name_cmd,
+      "no sdp audio-payload send-name",
+      NO_STR SDP_STR AUDIO_STR
+      "Send SDP rtpmap with the audio name\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->audio_send_name = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_omit_rtcp,
+      cfg_trunk_omit_rtcp_cmd,
+      "rtcp-omit",
+      RTCP_OMIT_STR)
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->omit_rtcp = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_omit_rtcp,
+      cfg_trunk_no_omit_rtcp_cmd,
+      "no rtcp-omit",
+      NO_STR RTCP_OMIT_STR)
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->omit_rtcp = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_patch_rtp_ssrc,
+      cfg_trunk_patch_rtp_ssrc_cmd,
+      "rtp-patch ssrc",
+      RTP_PATCH_STR
+      "Force a fixed SSRC\n"
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->force_constant_ssrc = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_patch_rtp_ssrc,
+      cfg_trunk_no_patch_rtp_ssrc_cmd,
+      "no rtp-patch ssrc",
+      NO_STR RTP_PATCH_STR
+      "Force a fixed SSRC\n"
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->force_constant_ssrc = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_patch_rtp_ts,
+      cfg_trunk_patch_rtp_ts_cmd,
+      "rtp-patch timestamp",
+      RTP_PATCH_STR
+      "Adjust RTP timestamp\n"
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->force_aligned_timing = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_patch_rtp_ts,
+      cfg_trunk_no_patch_rtp_ts_cmd,
+      "no rtp-patch timestamp",
+      NO_STR RTP_PATCH_STR
+      "Adjust RTP timestamp\n"
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->force_aligned_timing = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_patch_rtp,
+      cfg_trunk_no_patch_rtp_cmd,
+      "no rtp-patch",
+      NO_STR RTP_PATCH_STR)
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->force_constant_ssrc = 0;
+	trunk->force_aligned_timing = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_rtp_keepalive,
+      cfg_trunk_rtp_keepalive_cmd,
+      "rtp keep-alive <1-120>",
+      RTP_STR RTP_KEEPALIVE_STR
+      "Keep-alive interval in secs\n"
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	mgcp_trunk_set_keepalive(trunk, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_rtp_keepalive_once,
+      cfg_trunk_rtp_keepalive_once_cmd,
+      "rtp keep-alive once",
+      RTP_STR RTP_KEEPALIVE_STR
+      "Send dummy packet only once after CRCX/MDCX\n"
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_rtp_keepalive,
+      cfg_trunk_no_rtp_keepalive_cmd,
+      "no rtp keep-alive",
+      NO_STR RTP_STR RTP_KEEPALIVE_STR
+      )
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	mgcp_trunk_set_keepalive(trunk, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_allow_transcoding,
+      cfg_trunk_allow_transcoding_cmd,
+      "allow-transcoding",
+      "Allow transcoding\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->no_audio_transcoding = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_allow_transcoding,
+      cfg_trunk_no_allow_transcoding_cmd,
+      "no allow-transcoding",
+      NO_STR "Allow transcoding\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	trunk->no_audio_transcoding = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(loop_endp,
+      loop_endp_cmd,
+      "loop-endpoint <0-64> NAME (0|1)",
+      "Loop a given endpoint\n" "Trunk number\n"
+      "The name in hex of the endpoint\n" "Disable the loop\n" "Enable the loop\n")
+{
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	int endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Loopback number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+
+	endp = &trunk->endpoints[endp_no];
+	int loop = atoi(argv[2]);
+
+	if (loop)
+		endp->conn_mode = MGCP_CONN_LOOPBACK;
+	else
+		endp->conn_mode = endp->orig_mode;
+
+	/* Handle it like a MDCX, switch on SSRC patching if enabled */
+	mgcp_rtp_end_config(endp, 1, &endp->bts_end);
+	mgcp_rtp_end_config(endp, 1, &endp->net_end);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(tap_call,
+      tap_call_cmd,
+      "tap-call <0-64> ENDPOINT (bts-in|bts-out|net-in|net-out) A.B.C.D <0-65534>",
+      "Forward data on endpoint to a different system\n" "Trunk number\n"
+      "The endpoint in hex\n"
+      "Forward the data coming from the bts\n"
+      "Forward the data coming from the bts leaving to the network\n"
+      "Forward the data coming from the net\n"
+      "Forward the data coming from the net leaving to the bts\n"
+      "destination IP of the data\n" "destination port\n")
+{
+	struct mgcp_rtp_tap *tap;
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+	int port = 0;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	int endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	endp = &trunk->endpoints[endp_no];
+
+	if (strcmp(argv[2], "bts-in") == 0) {
+		port = MGCP_TAP_BTS_IN;
+	} else if (strcmp(argv[2], "bts-out") == 0) {
+		port = MGCP_TAP_BTS_OUT;
+	} else if (strcmp(argv[2], "net-in") == 0) {
+		port = MGCP_TAP_NET_IN;
+	} else if (strcmp(argv[2], "net-out") == 0) {
+		port = MGCP_TAP_NET_OUT;
+	} else {
+		vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	tap = &endp->taps[port];
+	memset(&tap->forward, 0, sizeof(tap->forward));
+	inet_aton(argv[3], &tap->forward.sin_addr);
+	tap->forward.sin_port = htons(atoi(argv[4]));
+	tap->enabled = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(free_endp, free_endp_cmd,
+      "free-endpoint <0-64> NUMBER",
+      "Free the given endpoint\n" "Trunk number\n"
+      "Endpoint number in hex.\n")
+{
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	int endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	endp = &trunk->endpoints[endp_no];
+	mgcp_release_endp(endp);
+	return CMD_SUCCESS;
+}
+
+DEFUN(reset_endp, reset_endp_cmd,
+      "reset-endpoint <0-64> NUMBER",
+      "Reset the given endpoint\n" "Trunk number\n"
+      "Endpoint number in hex.\n")
+{
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+	int endp_no, rc;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	endp = &trunk->endpoints[endp_no];
+	rc = mgcp_send_reset_ep(endp, ENDPOINT_NUMBER(endp));
+	if (rc < 0) {
+		vty_out(vty, "Error %d sending reset.%s", rc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+DEFUN(reset_all_endp, reset_all_endp_cmd,
+      "reset-all-endpoints",
+      "Reset all endpoints\n")
+{
+	int rc;
+
+	rc = mgcp_send_reset_all(g_cfg);
+	if (rc < 0) {
+		vty_out(vty, "Error %d during endpoint reset.%s",
+			rc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+#define OSMUX_STR "RTP multiplexing\n"
+DEFUN(cfg_mgcp_osmux,
+      cfg_mgcp_osmux_cmd,
+      "osmux (on|off|only)",
+       OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n")
+{
+	if (strcmp(argv[0], "off") == 0) {
+		g_cfg->osmux = OSMUX_USAGE_OFF;
+		return CMD_SUCCESS;
+	}
+
+	if (strcmp(argv[0], "on") == 0)
+		g_cfg->osmux = OSMUX_USAGE_ON;
+	else if (strcmp(argv[0], "only") == 0)
+		g_cfg->osmux = OSMUX_USAGE_ONLY;
+
+	if (g_cfg->trunk.audio_loop) {
+		vty_out(vty, "Cannot use `loop' with `osmux'.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_ip,
+      cfg_mgcp_osmux_ip_cmd,
+      "osmux bind-ip A.B.C.D",
+      OSMUX_STR IP_STR "IPv4 Address to bind to\n")
+{
+	osmo_talloc_replace_string(g_cfg, &g_cfg->osmux_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_batch_factor,
+      cfg_mgcp_osmux_batch_factor_cmd,
+      "osmux batch-factor <1-8>",
+      OSMUX_STR "Batching factor\n" "Number of messages in the batch\n")
+{
+	g_cfg->osmux_batch = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_batch_size,
+      cfg_mgcp_osmux_batch_size_cmd,
+      "osmux batch-size <1-65535>",
+      OSMUX_STR "batch size\n" "Batch size in bytes\n")
+{
+	g_cfg->osmux_batch_size = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_port,
+      cfg_mgcp_osmux_port_cmd,
+      "osmux port <1-65535>",
+      OSMUX_STR "port\n" "UDP port\n")
+{
+	g_cfg->osmux_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_dummy,
+      cfg_mgcp_osmux_dummy_cmd,
+      "osmux dummy (on|off)",
+      OSMUX_STR "Dummy padding\n" "Enable dummy padding\n" "Disable dummy padding\n")
+{
+	if (strcmp(argv[0], "on") == 0)
+		g_cfg->osmux_dummy = 1;
+	else if (strcmp(argv[0], "off") == 0)
+		g_cfg->osmux_dummy = 0;
+
+	return CMD_SUCCESS;
+}
+
+#define DEJITTER_STR "Uplink Jitter Buffer"
+DEFUN(cfg_mgcp_bts_use_jibuf,
+      cfg_mgcp_bts_use_jibuf_cmd,
+      "bts-jitter-buffer",
+      DEJITTER_STR "\n")
+{
+	g_cfg->bts_use_jibuf = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_bts_use_jibuf,
+      cfg_mgcp_no_bts_use_jibuf_cmd,
+      "no bts-jitter-buffer",
+      NO_STR DEJITTER_STR "\n")
+{
+	g_cfg->bts_use_jibuf = false;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bts_jitter_delay_min,
+      cfg_mgcp_bts_jitter_delay_min_cmd,
+      "bts-jitter-buffer-delay-min <1-65535>",
+      DEJITTER_STR " Minimum Delay in ms\n" "Minimum Delay in ms\n")
+{
+	g_cfg->bts_jitter_delay_min = atoi(argv[0]);
+	if (!g_cfg->bts_jitter_delay_min) {
+		vty_out(vty, "bts-jitter-buffer-delay-min cannot be zero.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (g_cfg->bts_jitter_delay_min && g_cfg->bts_jitter_delay_max &&
+	    g_cfg->bts_jitter_delay_min > g_cfg->bts_jitter_delay_max) {
+		vty_out(vty, "bts-jitter-buffer-delay-min cannot be bigger than " \
+			"bts-jitter-buffer-delay-max.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bts_jitter_delay_max,
+      cfg_mgcp_bts_jitter_delay_max_cmd,
+      "bts-jitter-buffer-delay-max <1-65535>",
+      DEJITTER_STR " Maximum Delay in ms\n" "Maximum Delay in ms\n")
+{
+	g_cfg->bts_jitter_delay_max = atoi(argv[0]);
+	if (!g_cfg->bts_jitter_delay_max) {
+		vty_out(vty, "bts-jitter-buffer-delay-max cannot be zero.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (g_cfg->bts_jitter_delay_min && g_cfg->bts_jitter_delay_max &&
+	    g_cfg->bts_jitter_delay_min > g_cfg->bts_jitter_delay_max) {
+		vty_out(vty, "bts-jitter-buffer-delay-max cannot be smaller than " \
+			"bts-jitter-buffer-delay-min.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+int mgcp_vty_init(void)
+{
+	install_element_ve(&show_mgcp_cmd);
+	install_element(ENABLE_NODE, &loop_endp_cmd);
+	install_element(ENABLE_NODE, &tap_call_cmd);
+	install_element(ENABLE_NODE, &free_endp_cmd);
+	install_element(ENABLE_NODE, &reset_endp_cmd);
+	install_element(ENABLE_NODE, &reset_all_endp_cmd);
+
+	install_element(CONFIG_NODE, &cfg_mgcp_cmd);
+	install_node(&mgcp_node, config_write_mgcp);
+
+	install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bts_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_net_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_range_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_no_bts_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_range_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_force_ptime_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_rtp_force_ptime_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_once_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_rtp_keepalive_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd_old);
+	install_element(MGCP_NODE, &cfg_mgcp_transcoder_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_transcoder_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_transcoder_remote_base_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd_old);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd_old);
+	install_element(MGCP_NODE, &cfg_mgcp_loop_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_force_realloc_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_omit_rtcp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_omit_rtcp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ssrc_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ssrc_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ts_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ts_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_fmtp_extra_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_ptime_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_ptime_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_name_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_name_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_osmux_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_osmux_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_factor_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_size_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_osmux_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_osmux_dummy_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_allow_transcoding_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bts_use_jibuf_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_bts_use_jibuf_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bts_jitter_delay_min_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bts_jitter_delay_max_cmd);
+
+
+	install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd);
+	install_node(&trunk_node, config_write_trunk);
+	install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_once_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_rtp_keepalive_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd_old);
+	install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd_old);
+	install_element(TRUNK_NODE, &cfg_trunk_loop_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_omit_rtcp_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_omit_rtcp_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ssrc_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ssrc_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ts_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ts_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_sdp_fmtp_extra_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_ptime_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_ptime_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_name_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_name_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_allow_transcoding_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_no_allow_transcoding_cmd);
+
+	return 0;
+}
+
+static int allocate_trunk(struct mgcp_trunk_config *trunk)
+{
+	int i;
+	struct mgcp_config *cfg = trunk->cfg;
+
+	if (mgcp_endpoints_allocate(trunk) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Failed to allocate %d endpoints on trunk %d.\n",
+		     trunk->number_endpoints, trunk->trunk_nr);
+		return -1;
+	}
+
+	/* early bind */
+	for (i = 1; i < trunk->number_endpoints; ++i) {
+		struct mgcp_endpoint *endp = &trunk->endpoints[i];
+
+		if (cfg->bts_ports.mode == PORT_ALLOC_STATIC) {
+			cfg->last_bts_port += 2;
+			if (mgcp_bind_bts_rtp_port(endp, cfg->last_bts_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL,
+				     "Failed to bind: %d\n", cfg->last_bts_port);
+				return -1;
+			}
+			endp->bts_end.local_alloc = PORT_ALLOC_STATIC;
+		}
+
+		if (cfg->net_ports.mode == PORT_ALLOC_STATIC) {
+			cfg->last_net_port += 2;
+			if (mgcp_bind_net_rtp_port(endp, cfg->last_net_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL,
+				     "Failed to bind: %d\n", cfg->last_net_port);
+				return -1;
+			}
+			endp->net_end.local_alloc = PORT_ALLOC_STATIC;
+		}
+
+		if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL &&
+		    cfg->transcoder_ip && cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) {
+			int rtp_port;
+
+			/* network side */
+			rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp),
+						      cfg->transcoder_ports.base_port);
+			if (mgcp_bind_trans_net_rtp_port(endp, rtp_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+				return -1;
+			}
+			endp->trans_net.local_alloc = PORT_ALLOC_STATIC;
+
+			/* bts side */
+			rtp_port = rtp_calculate_port(endp_back_channel(ENDPOINT_NUMBER(endp)),
+						      cfg->transcoder_ports.base_port);
+			if (mgcp_bind_trans_bts_rtp_port(endp, rtp_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+				return -1;
+			}
+			endp->trans_bts.local_alloc = PORT_ALLOC_STATIC;
+		}
+	}
+
+	return 0;
+}
+
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg,
+		      enum mgcp_role role)
+{
+	int rc;
+	struct mgcp_trunk_config *trunk;
+
+	cfg->osmux_port = OSMUX_PORT;
+	cfg->osmux_batch = 4;
+	cfg->osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file, NULL);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+
+	if (!g_cfg->bts_ip)
+		fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n");
+
+	if (!g_cfg->source_addr) {
+		fprintf(stderr, "You need to specify a bind address.\n");
+		return -1;
+	}
+
+	/* initialize the last ports */
+	g_cfg->last_bts_port = rtp_calculate_port(0, g_cfg->bts_ports.base_port);
+	g_cfg->last_net_port = rtp_calculate_port(0, g_cfg->net_ports.base_port);
+
+	if (allocate_trunk(&g_cfg->trunk) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to initialize the virtual trunk.\n");
+		return -1;
+	}
+
+	llist_for_each_entry(trunk, &g_cfg->trunks, entry) {
+		if (allocate_trunk(trunk) != 0) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize E1 trunk %d.\n", trunk->trunk_nr);
+			return -1;
+		}
+	}
+	cfg->role = role;
+
+	return 0;
+}
+
diff --git a/openbsc/src/libmsc/Makefile.am b/openbsc/src/libmsc/Makefile.am
new file mode 100644
index 0000000..f746f82
--- /dev/null
+++ b/openbsc/src/libmsc/Makefile.am
@@ -0,0 +1,58 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(NULL)
+
+noinst_HEADERS = \
+	meas_feed.h \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libmsc.a \
+	$(NULL)
+
+libmsc_a_SOURCES = \
+	auth.c \
+	db.c \
+	gsm_04_08.c \
+	gsm_04_11.c \
+	gsm_04_14.c \
+	gsm_04_80.c \
+	gsm_subscriber.c \
+	mncc.c \
+	mncc_builtin.c \
+	mncc_sock.c \
+	rrlp.c \
+	silent_call.c \
+	sms_queue.c \
+	token_auth.c \
+	ussd.c \
+	vty_interface_layer3.c \
+	transaction.c \
+	osmo_msc.c \
+	ctrl_commands.c \
+	meas_feed.c \
+	$(NULL)
+
+if BUILD_SMPP
+noinst_HEADERS += \
+	smpp_smsc.h \
+	$(NULL)
+
+libmsc_a_SOURCES += \
+	smpp_smsc.c \
+	smpp_openbsc.c \
+	smpp_vty.c \
+	smpp_utils.c \
+	$(NULL)
+endif
diff --git a/openbsc/src/libmsc/auth.c b/openbsc/src/libmsc/auth.c
new file mode 100644
index 0000000..85477a3
--- /dev/null
+++ b/openbsc/src/libmsc/auth.c
@@ -0,0 +1,177 @@
+/* Authentication related functions */
+
+/*
+ * (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/auth.h>
+#include <openbsc/gsm_data.h>
+
+#include <osmocom/gsm/comp128v23.h>
+#include <osmocom/gsm/comp128.h>
+#include <osmocom/core/utils.h>
+
+#include <stdlib.h>
+
+const struct value_string auth_action_names[] = {
+	OSMO_VALUE_STRING(AUTH_ERROR),
+	OSMO_VALUE_STRING(AUTH_NOT_AVAIL),
+	OSMO_VALUE_STRING(AUTH_DO_AUTH_THEN_CIPH),
+	OSMO_VALUE_STRING(AUTH_DO_CIPH),
+	OSMO_VALUE_STRING(AUTH_DO_AUTH),
+	{ 0, NULL }
+};
+
+static int
+_use_xor(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+	int i, l = ainfo->a3a8_ki_len;
+
+	if ((l > A38_XOR_MAX_KEY_LEN) || (l < A38_XOR_MIN_KEY_LEN)) {
+		LOGP(DMM, LOGL_ERROR, "Invalid XOR key (len=%d) %s\n",
+			ainfo->a3a8_ki_len,
+			osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+		return -1;
+	}
+
+	for (i=0; i<4; i++)
+		atuple->vec.sres[i] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i];
+	for (i=4; i<12; i++)
+		atuple->vec.kc[i-4] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i];
+
+	return 0;
+}
+
+static int
+_use_comp128(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple,
+	enum gsm_auth_algo algo)
+{
+	if (ainfo->a3a8_ki_len != A38_COMP128_KEY_LEN) {
+		LOGP(DMM, LOGL_ERROR, "Invalid COMP128v1 key (len=%d) %s\n",
+			ainfo->a3a8_ki_len,
+			osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+		return -1;
+	}
+
+	switch (algo) {
+	case AUTH_ALGO_COMP128v1:
+		comp128(ainfo->a3a8_ki, atuple->vec.rand,
+			atuple->vec.sres, atuple->vec.kc);
+		break;
+	case AUTH_ALGO_COMP128v2:
+		comp128v2(ainfo->a3a8_ki, atuple->vec.rand,
+			atuple->vec.sres, atuple->vec.kc);
+		break;
+	case AUTH_ALGO_COMP128v3:
+		comp128v3(ainfo->a3a8_ki, atuple->vec.rand,
+			atuple->vec.sres, atuple->vec.kc);
+		break;
+	default:
+		/* Unsupported version */
+		return -ENOTSUP;
+	}
+
+	return 0;
+}
+
+/* Return values 
+ *  -1 -> Internal error
+ *   0 -> Not available
+ *   1 -> Tuple returned, need to do auth, then enable cipher
+ *   2 -> Tuple returned, need to enable cipher
+ */
+int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple,
+                              struct gsm_subscriber *subscr, int key_seq)
+{
+	struct gsm_auth_info ainfo;
+	int rc;
+
+	/* Get subscriber info (if any) */
+	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+	if (rc < 0) {
+		LOGP(DMM, LOGL_NOTICE,
+		     "No retrievable Ki for subscriber %s, skipping auth\n",
+		     subscr_name(subscr));
+		return rc == -ENOENT ? AUTH_NOT_AVAIL : AUTH_ERROR;
+	}
+
+	/* If possible, re-use the last tuple and skip auth */
+	rc = db_get_lastauthtuple_for_subscr(atuple, subscr);
+	if ((rc == 0) &&
+	    (key_seq != GSM_KEY_SEQ_INVAL) &&
+	    (key_seq == atuple->key_seq) &&
+	    (atuple->use_count < 3))
+	{
+		atuple->use_count++;
+		db_sync_lastauthtuple_for_subscr(atuple, subscr);
+		DEBUGP(DMM, "Auth tuple use < 3, just doing ciphering\n");
+		return AUTH_DO_CIPH;
+	}
+
+	/* Generate a new one */
+	if (rc != 0) {
+		/* If db_get_lastauthtuple_for_subscr() returned nothing, make
+		 * sure the atuple memory is initialized to zero and thus start
+		 * off with key_seq = 0. */
+		memset(atuple, 0, sizeof(*atuple));
+	} else {
+		/* If db_get_lastauthtuple_for_subscr() returned a previous
+		 * tuple, use the next key_seq. */
+		atuple->key_seq = (atuple->key_seq + 1) % 7;
+	}
+	atuple->use_count = 1;
+
+	rc = osmo_get_rand_id(atuple->vec.rand, sizeof(atuple->vec.rand));
+	if (rc < 0) {
+		LOGP(DMM, LOGL_NOTICE, "osmo_get_rand_id failed, can't generate new auth tuple: %s\n",
+		     strerror(-rc));
+		return AUTH_ERROR;
+	}
+
+	switch (ainfo.auth_algo) {
+	case AUTH_ALGO_NONE:
+		DEBUGP(DMM, "No authentication for subscriber\n");
+		return AUTH_NOT_AVAIL;
+
+	case AUTH_ALGO_XOR:
+		if (_use_xor(&ainfo, atuple))
+			return AUTH_NOT_AVAIL;
+		break;
+
+	case AUTH_ALGO_COMP128v1:
+	case AUTH_ALGO_COMP128v2:
+	case AUTH_ALGO_COMP128v3:
+		if (_use_comp128(&ainfo, atuple, ainfo.auth_algo))
+			return AUTH_NOT_AVAIL;
+		break;
+
+	default:
+		DEBUGP(DMM, "Unsupported auth type algo_id=%d\n",
+			ainfo.auth_algo);
+		return AUTH_NOT_AVAIL;
+	}
+
+        db_sync_lastauthtuple_for_subscr(atuple, subscr);
+
+	DEBUGP(DMM, "Need to do authentication and ciphering\n");
+	return AUTH_DO_AUTH_THEN_CIPH;
+}
+
diff --git a/openbsc/src/libmsc/ctrl_commands.c b/openbsc/src/libmsc/ctrl_commands.c
new file mode 100644
index 0000000..8e4e8b6
--- /dev/null
+++ b/openbsc/src/libmsc/ctrl_commands.c
@@ -0,0 +1,220 @@
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ * (C) 2014 by 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 Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+
+#include <stdbool.h>
+
+static bool alg_supported(const char *alg)
+{
+	/*
+	 * TODO: share this with the vty_interface and extend to all
+	 * algorithms supported by libosmocore now. Make it table based
+	 * as well.
+	 */
+	if (strcasecmp(alg, "none") == 0)
+		return true;
+	if (strcasecmp(alg, "xor") == 0)
+		return true;
+	if (strcasecmp(alg, "comp128v1") == 0)
+		return true;
+	if (strcasecmp(alg, "comp128v2") == 0)
+		return true;
+	if (strcasecmp(alg, "comp128v3") == 0)
+		return true;
+	return false;
+}
+
+static int verify_subscriber_modify(struct ctrl_cmd *cmd, const char *value, void *d)
+{
+	char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
+	int rc = 0;
+
+	tmp = talloc_strdup(cmd, value);
+	if (!tmp)
+		return 1;
+
+	imsi = strtok_r(tmp, ",", &saveptr);
+	msisdn = strtok_r(NULL, ",", &saveptr);
+	alg = strtok_r(NULL, ",", &saveptr);
+	ki = strtok_r(NULL, ",", &saveptr);
+
+	if (!imsi || !msisdn)
+		rc = 1;
+	else if (strlen(imsi) > GSM23003_IMSI_MAX_DIGITS)
+		rc = 1;
+	else if (strlen(msisdn) >= GSM_EXTENSION_LENGTH)
+		rc = 1;
+	else if (alg) {
+		if (!alg_supported(alg))
+			rc = 1;
+		else if (strcasecmp(alg, "none") != 0 && !ki)
+			rc = 1;
+	}
+
+	talloc_free(tmp);
+	return rc;
+}
+
+static int set_subscriber_modify(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+	char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
+	struct gsm_subscriber* subscr;
+	int rc;
+
+	tmp = talloc_strdup(cmd, cmd->value);
+	if (!tmp)
+		return 1;
+
+	imsi = strtok_r(tmp, ",", &saveptr);
+	msisdn = strtok_r(NULL, ",", &saveptr);
+	alg = strtok_r(NULL, ",", &saveptr);
+	ki = strtok_r(NULL, ",", &saveptr);
+
+	subscr = subscr_get_by_imsi(net->subscr_group, imsi);
+	if (!subscr)
+		subscr = subscr_create_subscriber(net->subscr_group, imsi);
+	if (!subscr)
+		goto fail;
+
+	subscr->authorized = 1;
+	osmo_strlcpy(subscr->extension, msisdn, sizeof(subscr->extension));
+
+	/* put it back to the db */
+	rc = db_sync_subscriber(subscr);
+	db_subscriber_update(subscr);
+
+	/* handle optional ciphering */
+	if (alg) {
+		if (strcasecmp(alg, "none") == 0)
+			db_sync_authinfo_for_subscr(NULL, subscr);
+		else {
+			struct gsm_auth_info ainfo = { 0, };
+			/* the verify should make sure that this is okay */
+			OSMO_ASSERT(alg);
+			OSMO_ASSERT(ki);
+
+			if (strcasecmp(alg, "xor") == 0)
+				ainfo.auth_algo = AUTH_ALGO_XOR;
+			else if (strcasecmp(alg, "comp128v1") == 0)
+				ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+			else if (strcasecmp(alg, "comp128v2") == 0)
+				ainfo.auth_algo = AUTH_ALGO_COMP128v2;
+			else if (strcasecmp(alg, "comp128v3") == 0)
+				ainfo.auth_algo = AUTH_ALGO_COMP128v3;
+
+			rc = osmo_hexparse(ki, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+			if (rc < 0) {
+				subscr_put(subscr);
+				talloc_free(tmp);
+				cmd->reply = "Failed to parse KI";
+				return CTRL_CMD_ERROR;
+			}
+
+			ainfo.a3a8_ki_len = rc;
+			db_sync_authinfo_for_subscr(&ainfo, subscr);
+			rc = 0;
+		}
+		db_sync_lastauthtuple_for_subscr(NULL, subscr);
+	}
+	subscr_put(subscr);
+
+	talloc_free(tmp);
+
+	if (rc != 0) {
+		cmd->reply = "Failed to store the record in the DB";
+		return CTRL_CMD_ERROR;
+	}
+
+	cmd->reply = "OK";
+	return CTRL_CMD_REPLY;
+
+fail:
+	talloc_free(tmp);
+	cmd->reply = "Failed to create subscriber";
+	return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_WO(subscriber_modify, "subscriber-modify-v1");
+
+static int set_subscriber_delete(struct ctrl_cmd *cmd, void *data)
+{
+	int was_used = 0;
+	int rc;
+	struct gsm_subscriber *subscr;
+	struct gsm_network *net = cmd->node;
+
+	subscr = subscr_get_by_imsi(net->subscr_group, cmd->value);
+	if (!subscr) {
+		cmd->reply = "Failed to find subscriber";
+		return CTRL_CMD_ERROR;
+	}
+
+	if (subscr->use_count != 1) {
+		LOGP(DCTRL, LOGL_NOTICE, "Going to remove active subscriber.\n");
+		was_used = 1;
+	}
+
+	rc = db_subscriber_delete(subscr);
+	subscr_put(subscr);
+
+	if (rc != 0) {
+		cmd->reply = "Failed to remove subscriber";
+		return CTRL_CMD_ERROR;
+	}
+
+	cmd->reply = was_used ? "Removed active subscriber" : "Removed";
+	return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_WO_NOVRF(subscriber_delete, "subscriber-delete-v1");
+
+static void list_cb(struct gsm_subscriber *subscr, void *d)
+{
+	char **data = (char **) d;
+	*data = talloc_asprintf_append(*data, "%s,%s\n",
+				subscr->imsi, subscr->extension);
+}
+
+static int get_subscriber_list(struct ctrl_cmd *cmd, void *d)
+{
+	cmd->reply = talloc_strdup(cmd, "");
+
+	db_subscriber_list_active(list_cb, &cmd->reply);
+	printf("%s\n", cmd->reply);
+	return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(subscriber_list, "subscriber-list-active-v1");
+
+int msc_ctrl_cmds_install(void)
+{
+	int rc = 0;
+
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_modify);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_delete);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_list);
+	return rc;
+}
diff --git a/openbsc/src/libmsc/db.c b/openbsc/src/libmsc/db.c
new file mode 100644
index 0000000..0b61b4f
--- /dev/null
+++ b/openbsc/src/libmsc/db.c
@@ -0,0 +1,1915 @@
+/* Simple HLR/VLR database backend using dbi */
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <dbi/dbi.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/utils.h>
+
+/* Semi-Private-Interface (SPI) for the subscriber code */
+void subscr_direct_free(struct gsm_subscriber *subscr);
+
+static char *db_basename = NULL;
+static char *db_dirname = NULL;
+static dbi_conn conn;
+
+#define SCHEMA_REVISION "5"
+
+enum {
+	SCHEMA_META,
+	INSERT_META,
+	SCHEMA_SUBSCRIBER,
+	SCHEMA_AUTH,
+	SCHEMA_EQUIPMENT,
+	SCHEMA_EQUIPMENT_WATCH,
+	SCHEMA_SMS,
+	SCHEMA_VLR,
+	SCHEMA_APDU,
+	SCHEMA_COUNTERS,
+	SCHEMA_RATE,
+	SCHEMA_AUTHKEY,
+	SCHEMA_AUTHLAST,
+};
+
+static const char *create_stmts[] = {
+	[SCHEMA_META] = "CREATE TABLE IF NOT EXISTS Meta ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"key TEXT UNIQUE NOT NULL, "
+		"value TEXT NOT NULL"
+		")",
+	[INSERT_META] = "INSERT OR IGNORE INTO Meta "
+		"(key, value) "
+		"VALUES "
+		"('revision', " SCHEMA_REVISION ")",
+	[SCHEMA_SUBSCRIBER] = "CREATE TABLE IF NOT EXISTS 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, "
+		"expire_lu TIMESTAMP DEFAULT NULL"
+		")",
+	[SCHEMA_AUTH] = "CREATE TABLE IF NOT EXISTS AuthToken ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"subscriber_id INTEGER UNIQUE NOT NULL, "
+		"created TIMESTAMP NOT NULL, "
+		"token TEXT UNIQUE NOT NULL"
+		")",
+	[SCHEMA_EQUIPMENT] = "CREATE TABLE IF NOT EXISTS Equipment ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"name TEXT, "
+		"classmark1 NUMERIC, "
+		"classmark2 BLOB, "
+		"classmark3 BLOB, "
+		"imei NUMERIC UNIQUE NOT NULL"
+		")",
+	[SCHEMA_EQUIPMENT_WATCH] = "CREATE TABLE IF NOT EXISTS EquipmentWatch ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC NOT NULL, "
+		"equipment_id NUMERIC NOT NULL, "
+		"UNIQUE (subscriber_id, equipment_id) "
+		")",
+	[SCHEMA_SMS] = "CREATE TABLE IF NOT EXISTS SMS ("
+		/* metadata, not part of sms */
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"sent TIMESTAMP, "
+		"deliver_attempts INTEGER NOT NULL DEFAULT 0, "
+		/* data directly copied/derived from SMS */
+		"valid_until TIMESTAMP, "
+		"reply_path_req INTEGER NOT NULL, "
+		"status_rep_req INTEGER NOT NULL, "
+		"is_report INTEGER NOT NULL, "
+		"msg_ref INTEGER NOT NULL, "
+		"protocol_id INTEGER NOT NULL, "
+		"data_coding_scheme INTEGER NOT NULL, "
+		"ud_hdr_ind INTEGER NOT NULL, "
+		"src_addr TEXT NOT NULL, "
+		"src_ton INTEGER NOT NULL, "
+		"src_npi INTEGER NOT NULL, "
+		"dest_addr TEXT NOT NULL, "
+		"dest_ton INTEGER NOT NULL, "
+		"dest_npi INTEGER NOT NULL, "
+		"user_data BLOB, "	/* TP-UD */
+		/* additional data, interpreted from SMS */
+		"header BLOB, "		/* UD Header */
+		"text TEXT "		/* decoded UD after UDH */
+		")",
+	[SCHEMA_VLR] = "CREATE TABLE IF NOT EXISTS VLR ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC UNIQUE NOT NULL, "
+		"last_bts NUMERIC NOT NULL "
+		")",
+	[SCHEMA_APDU] = "CREATE TABLE IF NOT EXISTS ApduBlobs ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"apdu_id_flags INTEGER NOT NULL, "
+		"subscriber_id INTEGER NOT NULL, "
+		"apdu BLOB "
+		")",
+	[SCHEMA_COUNTERS] = "CREATE TABLE IF NOT EXISTS Counters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL "
+		")",
+	[SCHEMA_RATE] = "CREATE TABLE IF NOT EXISTS RateCounters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL, "
+		"idx INTEGER NOT NULL "
+		")",
+	[SCHEMA_AUTHKEY] = "CREATE TABLE IF NOT EXISTS AuthKeys ("
+		"subscriber_id INTEGER PRIMARY KEY, "
+		"algorithm_id INTEGER NOT NULL, "
+		"a3a8_ki BLOB "
+		")",
+	[SCHEMA_AUTHLAST] = "CREATE TABLE IF NOT EXISTS AuthLastTuples ("
+		"subscriber_id INTEGER PRIMARY KEY, "
+		"issued TIMESTAMP NOT NULL, "
+		"use_count INTEGER NOT NULL DEFAULT 0, "
+		"key_seq INTEGER NOT NULL, "
+		"rand BLOB NOT NULL, "
+		"sres BLOB NOT NULL, "
+		"kc BLOB NOT NULL "
+		")",
+};
+
+static inline int next_row(dbi_result result)
+{
+	if (!dbi_result_has_next_row(result))
+		return 0;
+	return dbi_result_next_row(result);
+}
+
+void db_error_func(dbi_conn conn, void *data)
+{
+	const char *msg;
+	dbi_conn_error(conn, &msg);
+	LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg);
+	osmo_log_backtrace(DDB, LOGL_ERROR);
+}
+
+static int update_db_revision_2(void)
+{
+	dbi_result result;
+
+	result = dbi_conn_query(conn,
+				"ALTER TABLE Subscriber "
+				"ADD COLUMN expire_lu "
+				"TIMESTAMP DEFAULT NULL");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to alter table Subscriber (upgrade from rev 2).\n");
+		return -EINVAL;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_query(conn,
+				"UPDATE Meta "
+				"SET value = '3' "
+				"WHERE key = 'revision'");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to update DB schema revision  (upgrade from rev 2).\n");
+		return -EINVAL;
+	}
+	dbi_result_free(result);
+
+	return 0;
+}
+
+/**
+ * Copied from the normal sms_from_result_v3 to avoid having
+ * to make sure that the real routine will remain backward
+ * compatible.
+ */
+static struct gsm_sms *sms_from_result_v3(dbi_result result)
+{
+	struct gsm_sms *sms = sms_alloc();
+	long long unsigned int sender_id;
+	struct gsm_subscriber *sender;
+	const char *text, *daddr;
+	const unsigned char *user_data;
+	char buf[32];
+
+	if (!sms)
+		return NULL;
+
+	sms->id = dbi_result_get_ulonglong(result, "id");
+
+	sender_id = dbi_result_get_ulonglong(result, "sender_id");
+	snprintf(buf, sizeof(buf), "%llu", sender_id);
+	sender = db_get_subscriber(GSM_SUBSCRIBER_ID, buf);
+	OSMO_ASSERT(sender);
+	osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr));
+	subscr_direct_free(sender);
+	sender = NULL;
+
+	sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
+	sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
+	sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
+	sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
+	sms->data_coding_scheme = dbi_result_get_ulonglong(result,
+						  "data_coding_scheme");
+
+	daddr = dbi_result_get_string(result, "dest_addr");
+	if (daddr)
+		osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
+
+	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+	user_data = dbi_result_get_binary(result, "user_data");
+	if (sms->user_data_len > sizeof(sms->user_data))
+		sms->user_data_len = (uint8_t) sizeof(sms->user_data);
+	memcpy(sms->user_data, user_data, sms->user_data_len);
+
+	text = dbi_result_get_string(result, "text");
+	if (text)
+		osmo_strlcpy(sms->text, text, sizeof(sms->text));
+	return sms;
+}
+
+static int update_db_revision_3(void)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 3\n");
+
+	result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to begin transaction (upgrade from rev 3)\n");
+		return -EINVAL;
+	}
+	dbi_result_free(result);
+
+	/* Rename old SMS table to be able create a new one */
+	result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_3");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to rename the old SMS table (upgrade from rev 3).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	/* Create new SMS table with all the bells and whistles! */
+	result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to create a new SMS table (upgrade from rev 3).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	/* Cycle through old messages and convert them to the new format */
+	result = dbi_conn_query(conn, "SELECT * FROM SMS_3");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed fetch messages from the old SMS table (upgrade from rev 3).\n");
+		goto rollback;
+	}
+	while (next_row(result)) {
+		sms = sms_from_result_v3(result);
+		if (db_sms_store(sms) != 0) {
+			LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 3).\n");
+			sms_free(sms);
+			dbi_result_free(result);
+			goto rollback;
+		}
+		sms_free(sms);
+	}
+	dbi_result_free(result);
+
+	/* Remove the temporary table */
+	result = dbi_conn_query(conn, "DROP TABLE SMS_3");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to drop the old SMS table (upgrade from rev 3).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	/* We're done. Bump DB Meta revision to 4 */
+	result = dbi_conn_query(conn,
+				"UPDATE Meta "
+				"SET value = '4' "
+				"WHERE key = 'revision'");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to update DB schema revision (upgrade from rev 3).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_query(conn, "COMMIT TRANSACTION");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to commit the transaction (upgrade from rev 3)\n");
+		return -EINVAL;
+	} else {
+		dbi_result_free(result);
+	}
+
+	/* Shrink DB file size by actually wiping out SMS_3 table data */
+	result = dbi_conn_query(conn, "VACUUM");
+	if (!result)
+		LOGP(DDB, LOGL_ERROR,
+			"VACUUM failed. Ignoring it (upgrade from rev 3).\n");
+	else
+		dbi_result_free(result);
+
+	return 0;
+
+rollback:
+	result = dbi_conn_query(conn, "ROLLBACK TRANSACTION");
+	if (!result)
+		LOGP(DDB, LOGL_ERROR,
+			"Rollback failed (upgrade from rev 3).\n");
+	else
+		dbi_result_free(result);
+	return -EINVAL;
+}
+
+/* Just like v3, but there is a new message reference field for status reports,
+ * that is set to zero for existing entries since there is no way we can infer
+ * this.
+ */
+static struct gsm_sms *sms_from_result_v4(dbi_result result)
+{
+	struct gsm_sms *sms = sms_alloc();
+	const unsigned char *user_data;
+	const char *text, *addr;
+
+	if (!sms)
+		return NULL;
+
+	sms->id = dbi_result_get_ulonglong(result, "id");
+
+	sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
+	sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
+	sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
+	sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
+	sms->data_coding_scheme = dbi_result_get_ulonglong(result,
+						  "data_coding_scheme");
+
+	addr = dbi_result_get_string(result, "src_addr");
+	osmo_strlcpy(sms->src.addr, addr, sizeof(sms->src.addr));
+	sms->src.ton = dbi_result_get_ulonglong(result, "src_ton");
+	sms->src.npi = dbi_result_get_ulonglong(result, "src_npi");
+
+	addr = dbi_result_get_string(result, "dest_addr");
+	osmo_strlcpy(sms->dst.addr, addr, sizeof(sms->dst.addr));
+	sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton");
+	sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi");
+
+	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+	user_data = dbi_result_get_binary(result, "user_data");
+	if (sms->user_data_len > sizeof(sms->user_data))
+		sms->user_data_len = (uint8_t) sizeof(sms->user_data);
+	memcpy(sms->user_data, user_data, sms->user_data_len);
+
+	text = dbi_result_get_string(result, "text");
+	if (text)
+		osmo_strlcpy(sms->text, text, sizeof(sms->text));
+	return sms;
+}
+
+static int update_db_revision_4(void)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 4\n");
+
+	result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to begin transaction (upgrade from rev 4)\n");
+		return -EINVAL;
+	}
+	dbi_result_free(result);
+
+	/* Rename old SMS table to be able create a new one */
+	result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_4");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to rename the old SMS table (upgrade from rev 4).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	/* Create new SMS table with all the bells and whistles! */
+	result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to create a new SMS table (upgrade from rev 4).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	/* Cycle through old messages and convert them to the new format */
+	result = dbi_conn_query(conn, "SELECT * FROM SMS_4");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed fetch messages from the old SMS table (upgrade from rev 4).\n");
+		goto rollback;
+	}
+	while (next_row(result)) {
+		sms = sms_from_result_v4(result);
+		if (db_sms_store(sms) != 0) {
+			LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 4).\n");
+			sms_free(sms);
+			dbi_result_free(result);
+			goto rollback;
+		}
+		sms_free(sms);
+	}
+	dbi_result_free(result);
+
+	/* Remove the temporary table */
+	result = dbi_conn_query(conn, "DROP TABLE SMS_4");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to drop the old SMS table (upgrade from rev 4).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	/* We're done. Bump DB Meta revision to 4 */
+	result = dbi_conn_query(conn,
+				"UPDATE Meta "
+				"SET value = '5' "
+				"WHERE key = 'revision'");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to update DB schema revision (upgrade from rev 4).\n");
+		goto rollback;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_query(conn, "COMMIT TRANSACTION");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to commit the transaction (upgrade from rev 4)\n");
+		return -EINVAL;
+	} else {
+		dbi_result_free(result);
+	}
+
+	/* Shrink DB file size by actually wiping out SMS_4 table data */
+	result = dbi_conn_query(conn, "VACUUM");
+	if (!result)
+		LOGP(DDB, LOGL_ERROR,
+			"VACUUM failed. Ignoring it (upgrade from rev 4).\n");
+	else
+		dbi_result_free(result);
+
+	return 0;
+
+rollback:
+	result = dbi_conn_query(conn, "ROLLBACK TRANSACTION");
+	if (!result)
+		LOGP(DDB, LOGL_ERROR,
+			"Rollback failed (upgrade from rev 4).\n");
+	else
+		dbi_result_free(result);
+	return -EINVAL;
+}
+
+static int check_db_revision(void)
+{
+	dbi_result result;
+	const char *rev_s;
+	int db_rev = 0;
+
+	/* Make a query */
+	result = dbi_conn_query(conn,
+		"SELECT value FROM Meta "
+		"WHERE key = 'revision'");
+
+	if (!result)
+		return -EINVAL;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+
+	/* Fetch the DB schema revision */
+	rev_s = dbi_result_get_string(result, "value");
+	if (!rev_s) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+
+	if (!strcmp(rev_s, SCHEMA_REVISION)) {
+		/* Everything is fine */
+		dbi_result_free(result);
+		return 0;
+	}
+
+	db_rev = atoi(rev_s);
+	dbi_result_free(result);
+
+	/* Incremental migration waterfall */
+	switch (db_rev) {
+	case 2:
+		if (update_db_revision_2())
+			goto error;
+	case 3:
+		if (update_db_revision_3())
+			goto error;
+	case 4:
+		if (update_db_revision_4())
+			goto error;
+
+	/* The end of waterfall */
+	break;
+	default:
+		LOGP(DDB, LOGL_FATAL,
+			"Invalid database schema revision '%d'.\n", db_rev);
+		return -EINVAL;
+	}
+
+	return 0;
+
+error:
+	LOGP(DDB, LOGL_FATAL, "Failed to update database "
+		"from schema revision '%d'.\n", db_rev);
+	return -EINVAL;
+}
+
+static int db_configure(void)
+{
+	dbi_result result;
+
+	result = dbi_conn_query(conn,
+				"PRAGMA synchronous = FULL");
+	if (!result)
+		return -EINVAL;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_init(const char *name)
+{
+	dbi_initialize(NULL);
+
+	conn = dbi_conn_new("sqlite3");
+	if (conn == NULL) {
+		LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n");
+		return 1;
+	}
+
+	dbi_conn_error_handler( conn, db_error_func, NULL );
+
+	/* MySQL
+	dbi_conn_set_option(conn, "host", "localhost");
+	dbi_conn_set_option(conn, "username", "your_name");
+	dbi_conn_set_option(conn, "password", "your_password");
+	dbi_conn_set_option(conn, "dbname", "your_dbname");
+	dbi_conn_set_option(conn, "encoding", "UTF-8");
+	*/
+
+	/* SqLite 3 */
+	db_basename = strdup(name);
+	db_dirname = strdup(name);
+	dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname));
+	dbi_conn_set_option(conn, "dbname", basename(db_basename));
+
+	if (dbi_conn_connect(conn) < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	free(db_dirname);
+	free(db_basename);
+	db_dirname = db_basename = NULL;
+	return -1;
+}
+
+
+int db_prepare(void)
+{
+	dbi_result result;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+		result = dbi_conn_query(conn, create_stmts[i]);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR,
+			     "Failed to create some table.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+	}
+
+	if (check_db_revision() < 0) {
+		LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, "
+			"please update your database schema\n");
+                return -1;
+	}
+
+	db_configure();
+
+	return 0;
+}
+
+int db_fini(void)
+{
+	dbi_conn_close(conn);
+	dbi_shutdown();
+
+	free(db_dirname);
+	free(db_basename);
+	return 0;
+}
+
+struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
+					    uint64_t smax, bool alloc_exten)
+{
+	dbi_result result;
+	struct gsm_subscriber *subscr;
+
+	/* Is this subscriber known in the db? */
+	subscr = db_get_subscriber(GSM_SUBSCRIBER_IMSI, imsi);
+	if (subscr) {
+		subscr_put(subscr);
+		return NULL;
+	}
+
+	subscr = subscr_alloc();
+	if (!subscr)
+		return NULL;
+	subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Subscriber "
+		"(imsi, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imsi
+	);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n");
+		subscr_put(subscr);
+		return NULL;
+	}
+	subscr->id = dbi_conn_sequence_last(conn, NULL);
+	osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi));
+	dbi_result_free(result);
+	LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
+	if (alloc_exten)
+		db_subscriber_alloc_exten(subscr, smin, smax);
+	return subscr;
+}
+
+osmo_static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size);
+
+static int get_equipment_by_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const char *string;
+	unsigned char cm1;
+	const unsigned char *cm2, *cm3;
+	struct gsm_equipment *equip = &subscr->equipment;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT Equipment.* "
+			"FROM Equipment JOIN EquipmentWatch ON "
+				"EquipmentWatch.equipment_id=Equipment.id "
+			"WHERE EquipmentWatch.subscriber_id = %llu "
+			"ORDER BY EquipmentWatch.updated DESC", subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	equip->id = dbi_result_get_ulonglong(result, "id");
+
+	string = dbi_result_get_string(result, "imei");
+	if (string)
+		osmo_strlcpy(equip->imei, string, sizeof(equip->imei));
+
+	string = dbi_result_get_string(result, "classmark1");
+	if (string) {
+		cm1 = atoi(string) & 0xff;
+		memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1));
+	}
+
+	equip->classmark2_len = dbi_result_get_field_length(result, "classmark2");
+	cm2 = dbi_result_get_binary(result, "classmark2");
+	if (equip->classmark2_len > sizeof(equip->classmark2))
+		equip->classmark2_len = sizeof(equip->classmark2);
+	if (cm2)
+		memcpy(equip->classmark2, cm2, equip->classmark2_len);
+
+	equip->classmark3_len = dbi_result_get_field_length(result, "classmark3");
+	cm3 = dbi_result_get_binary(result, "classmark3");
+	if (equip->classmark3_len > sizeof(equip->classmark3))
+		equip->classmark3_len = sizeof(equip->classmark3);
+	if (cm3)
+		memcpy(equip->classmark3, cm3, equip->classmark3_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                               struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const unsigned char *a3a8_ki;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthKeys WHERE subscriber_id=%llu",
+			 subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id");
+	ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki");
+	a3a8_ki = dbi_result_get_binary(result, "a3a8_ki");
+	if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki))
+		ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki);
+	memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                                struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_auth_info ainfo_old;
+	int rc, upd;
+	unsigned char *ki_str;
+
+	/* Deletion ? */
+	if (ainfo == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthKeys WHERE subscriber_id=%llu",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = db_get_authinfo_for_subscr(&ainfo_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthKeys "
+				"(subscriber_id, algorithm_id, a3a8_ki) "
+				"VALUES (%llu, %u, %s)",
+				subscr->id, ainfo->auth_algo, ki_str);
+	} else {
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthKeys "
+				"SET algorithm_id=%u, a3a8_ki=%s "
+				"WHERE subscriber_id=%llu",
+				ainfo->auth_algo, ki_str, subscr->id);
+	}
+
+	free(ki_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                    struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int len;
+	const unsigned char *blob;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	memset(atuple, 0, sizeof(*atuple));
+
+	atuple->use_count = dbi_result_get_ulonglong(result, "use_count");
+	atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq");
+
+	len = dbi_result_get_field_length(result, "rand");
+	if (len != sizeof(atuple->vec.rand))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "rand");
+	memcpy(atuple->vec.rand, blob, len);
+
+	len = dbi_result_get_field_length(result, "sres");
+	if (len != sizeof(atuple->vec.sres))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "sres");
+	memcpy(atuple->vec.sres, blob, len);
+
+	len = dbi_result_get_field_length(result, "kc");
+	if (len != sizeof(atuple->vec.kc))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "kc");
+	memcpy(atuple->vec.kc, blob, len);
+
+	dbi_result_free(result);
+
+	return 0;
+
+err_size:
+	dbi_result_free(result);
+	return -EIO;
+}
+
+int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                     struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int rc, upd;
+	struct gsm_auth_tuple atuple_old;
+	unsigned char *rand_str, *sres_str, *kc_str;
+
+	/* Deletion ? */
+	if (atuple == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = db_get_lastauthtuple_for_subscr(&atuple_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		atuple->vec.rand, sizeof(atuple->vec.rand), &rand_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->vec.sres, sizeof(atuple->vec.sres), &sres_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->vec.kc, sizeof(atuple->vec.kc), &kc_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthLastTuples "
+				"(subscriber_id, issued, use_count, "
+				 "key_seq, rand, sres, kc) "
+				"VALUES (%llu, datetime('now'), %u, "
+				 "%u, %s, %s, %s ) ",
+				subscr->id, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str);
+	} else {
+		char *issued = atuple->key_seq == atuple_old.key_seq ?
+					"issued" : "datetime('now')";
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthLastTuples "
+				"SET issued=%s, use_count=%u, "
+				 "key_seq=%u, rand=%s, sres=%s, kc=%s "
+				"WHERE subscriber_id = %llu",
+				issued, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str, subscr->id);
+	}
+
+	free(rand_str);
+	free(sres_str);
+	free(kc_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+static void db_set_from_query(struct gsm_subscriber *subscr, dbi_conn result)
+{
+	const char *string;
+	string = dbi_result_get_string(result, "imsi");
+	if (string)
+		osmo_strlcpy(subscr->imsi, string, sizeof(subscr->imsi));
+
+	string = dbi_result_get_string(result, "tmsi");
+	if (string)
+		subscr->tmsi = tmsi_from_string(string);
+
+	string = dbi_result_get_string(result, "name");
+	if (string)
+		osmo_strlcpy(subscr->name, string, sizeof(subscr->name));
+
+	string = dbi_result_get_string(result, "extension");
+	if (string)
+		osmo_strlcpy(subscr->extension, string, sizeof(subscr->extension));
+
+	subscr->lac = dbi_result_get_ulonglong(result, "lac");
+
+	if (!dbi_result_field_is_null(result, "expire_lu"))
+		subscr->expire_lu = dbi_result_get_datetime(result, "expire_lu");
+	else
+		subscr->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION;
+
+	subscr->authorized = dbi_result_get_ulonglong(result, "authorized");
+
+}
+
+#define BASE_QUERY "SELECT * FROM Subscriber "
+struct gsm_subscriber *db_get_subscriber(enum gsm_subscriber_field field,
+					 const char *id)
+{
+	dbi_result result;
+	char *quoted;
+	struct gsm_subscriber *subscr;
+
+	switch (field) {
+	case GSM_SUBSCRIBER_IMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE imsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_TMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE tmsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_EXTENSION:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE extension = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_ID:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s ", quoted);
+		free(quoted);
+		break;
+	default:
+		LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n");
+		return NULL;
+	}
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n");
+		return NULL;
+	}
+	if (!next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n",
+			field, id);
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	subscr = subscr_alloc();
+	subscr->id = dbi_result_get_ulonglong(result, "id");
+
+	db_set_from_query(subscr, result);
+	DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %x, EXTEN '%s', LAC %hu, AUTH %u\n",
+		subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension,
+		subscr->lac, subscr->authorized);
+	dbi_result_free(result);
+
+	get_equipment_by_subscr(subscr);
+
+	return subscr;
+}
+
+int db_subscriber_update(struct gsm_subscriber *subscr)
+{
+	char buf[32];
+	dbi_result result;
+
+	/* Copy the id to a string as queryf with %llu is failing */
+	sprintf(buf, "%llu", subscr->id);
+	result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s", buf);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber: %llu\n", subscr->id);
+		return -EIO;
+	}
+	if (!next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. %llu\n",
+			subscr->id);
+		dbi_result_free(result);
+		return -EIO;
+	}
+
+	db_set_from_query(subscr, result);
+	dbi_result_free(result);
+	get_equipment_by_subscr(subscr);
+
+	return 0;
+}
+
+int db_sync_subscriber(struct gsm_subscriber *subscriber)
+{
+	dbi_result result;
+	char tmsi[14];
+	char *q_tmsi, *q_name, *q_extension;
+
+	dbi_conn_quote_string_copy(conn, 
+				   subscriber->name, &q_name);
+	if (subscriber->extension[0] != '\0')
+		dbi_conn_quote_string_copy(conn,
+					   subscriber->extension, &q_extension);
+	else
+		q_extension = strdup("NULL");
+	
+	if (subscriber->tmsi != GSM_RESERVED_TMSI) {
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn,
+				   tmsi,
+				   &q_tmsi);
+	} else
+		q_tmsi = strdup("NULL");
+
+	if (subscriber->expire_lu == GSM_SUBSCRIBER_NO_EXPIRATION) {
+		result = dbi_conn_queryf(conn,
+			"UPDATE Subscriber "
+			"SET updated = datetime('now'), "
+			"name = %s, "
+			"extension = %s, "
+			"authorized = %i, "
+			"tmsi = %s, "
+			"lac = %i, "
+			"expire_lu = NULL "
+			"WHERE imsi = %s ",
+			q_name,
+			q_extension,
+			subscriber->authorized,
+			q_tmsi,
+			subscriber->lac,
+			subscriber->imsi);
+	} else {
+		result = dbi_conn_queryf(conn,
+			"UPDATE Subscriber "
+			"SET updated = datetime('now'), "
+			"name = %s, "
+			"extension = %s, "
+			"authorized = %i, "
+			"tmsi = %s, "
+			"lac = %i, "
+			"expire_lu = datetime(%i, 'unixepoch') "
+			"WHERE imsi = %s ",
+			q_name,
+			q_extension,
+			subscriber->authorized,
+			q_tmsi,
+			subscriber->lac,
+			(int) subscriber->expire_lu,
+			subscriber->imsi);
+	}
+
+	free(q_tmsi);
+	free(q_name);
+	free(q_extension);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n");
+		return 1;
+	}
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_subscriber_delete(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthKeys WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete Authkeys for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete AuthLastTuples for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthToken WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete AuthToken for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM EquipmentWatch WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete EquipmentWatch for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	if (subscr->extension[0] != '\0') {
+		result = dbi_conn_queryf(conn,
+			    "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
+					 subscr->extension, subscr->extension);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR,
+			     "Failed to delete SMS for %llu\n", subscr->id);
+			return -1;
+		}
+		dbi_result_free(result);
+	}
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM VLR WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete VLR for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM ApduBlobs WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete ApduBlobs for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	result = dbi_conn_queryf(conn,
+			"DELETE FROM Subscriber WHERE id=%llu",
+			subscr->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+			"Failed to delete Subscriber for %llu\n", subscr->id);
+		return -1;
+	}
+	dbi_result_free(result);
+
+	return 0;
+}
+
+/**
+ * List all the authorized and non-expired subscribers. The callback will
+ * be called one by one. The subscr argument is not fully initialize and
+ * subscr_get/subscr_put must not be called. The passed in pointer will be
+ * deleted after the callback by the database call.
+ */
+int db_subscriber_list_active(void (*cb)(struct gsm_subscriber*,void*), void *closure)
+{
+	dbi_result result;
+
+	result = dbi_conn_query(conn,
+		       "SELECT * from Subscriber WHERE LAC != 0 AND authorized = 1");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to list active subscribers\n");
+		return -1;
+	}
+
+	while (next_row(result)) {
+		struct gsm_subscriber *subscr;
+
+		subscr = subscr_alloc();
+		subscr->id = dbi_result_get_ulonglong(result, "id");
+		db_set_from_query(subscr, result);
+		cb(subscr, closure);
+		OSMO_ASSERT(subscr->use_count == 1);
+		llist_del(&subscr->entry);
+		talloc_free(subscr);
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_sync_equipment(struct gsm_equipment *equip)
+{
+	dbi_result result;
+	unsigned char *cm2, *cm3;
+	char *q_imei;
+	uint8_t classmark1;
+
+	memcpy(&classmark1, &equip->classmark1, sizeof(classmark1));
+	DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x",
+		equip->imei, classmark1);
+	if (equip->classmark2_len)
+		DEBUGPC(DDB, ", classmark2=%s",
+			osmo_hexdump(equip->classmark2, equip->classmark2_len));
+	if (equip->classmark3_len)
+		DEBUGPC(DDB, ", classmark3=%s",
+			osmo_hexdump(equip->classmark3, equip->classmark3_len));
+	DEBUGPC(DDB, "\n");
+
+	dbi_conn_quote_binary_copy(conn, equip->classmark2,
+				   equip->classmark2_len, &cm2);
+	dbi_conn_quote_binary_copy(conn, equip->classmark3,
+				   equip->classmark3_len, &cm3);
+	dbi_conn_quote_string_copy(conn, equip->imei, &q_imei);
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Equipment SET "
+			"updated = datetime('now'), "
+			"classmark1 = %u, "
+			"classmark2 = %s, "
+			"classmark3 = %s "
+		"WHERE imei = %s ",
+		classmark1, cm2, cm3, q_imei);
+
+	free(cm2);
+	free(cm3);
+	free(q_imei);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n");
+		return -EIO;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_subscriber_expire(void *priv, void (*callback)(void *priv, long long unsigned int id))
+{
+	dbi_result result;
+
+	result = dbi_conn_query(conn,
+			"SELECT id "
+			"FROM Subscriber "
+			"WHERE lac != 0 AND "
+				"( expire_lu is NOT NULL "
+				"AND expire_lu < datetime('now') ) "
+			"LIMIT 1");
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to get expired subscribers\n");
+		return -EIO;
+	}
+
+	while (next_row(result))
+		callback(priv, dbi_result_get_ulonglong(result, "id"));
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	char tmsi[14];
+	char *tmsi_quoted;
+
+	for (;;) {
+		int rc = osmo_get_rand_id((uint8_t *) &subscriber->tmsi, sizeof(subscriber->tmsi));
+		if (rc < 0) {
+			LOGP(DDB, LOGL_ERROR, "osmo_get_rand_id() failed: %s\n", strerror(-rc));
+			return 1;
+		}
+		if (subscriber->tmsi == GSM_RESERVED_TMSI)
+			continue;
+
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE tmsi = %s ",
+			tmsi_quoted);
+
+		free(tmsi_quoted);
+
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new TMSI.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!next_row(result)) {
+			dbi_result_free(result);
+			DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n",
+				subscriber->tmsi, subscriber->imsi);
+			return db_sync_subscriber(subscriber);
+		}
+		dbi_result_free(result);
+	}
+	return 0;
+}
+
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber, uint64_t smin,
+			      uint64_t smax)
+{
+	dbi_result result = NULL;
+	uint64_t try;
+
+	for (;;) {
+		try = (rand() % (smax - smin + 1) + smin);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE extension = %"PRIu64,
+			try
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new extension.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)){
+			dbi_result_free(result);
+			continue;
+		}
+		if (!next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	sprintf(subscriber->extension, "%"PRIu64, try);
+	DEBUGP(DDB, "Allocated extension %"PRIu64 " for IMSI %s.\n", try, subscriber->imsi);
+	return db_sync_subscriber(subscriber);
+}
+/*
+ * try to allocate a new unique token for this subscriber and return it
+ * via a parameter. if the subscriber already has a token, return
+ * an error.
+ */
+
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, uint32_t *token)
+{
+	dbi_result result;
+	uint32_t try;
+
+	for (;;) {
+		int rc = osmo_get_rand_id((uint8_t *) &try, sizeof(try));
+		if (rc < 0) {
+			LOGP(DDB, LOGL_ERROR, "osmo_get_rand_id() failed: %s\n", strerror(-rc));
+			return 1;
+		}
+		if (!try) /* 0 is an invalid token */
+			continue;
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthToken "
+			"WHERE subscriber_id = %llu OR token = \"%08X\" ",
+			subscriber->id, try);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken "
+				"while allocating new token.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO AuthToken "
+		"(subscriber_id, created, token) "
+		"VALUES "
+		"(%llu, datetime('now'), \"%08X\") ",
+		subscriber->id, try);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for "
+			"IMSI %s.\n", try, subscriber->imsi);
+		return 1;
+	}
+	dbi_result_free(result);
+	*token = try;
+	DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi);
+
+	return 0;
+}
+
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM23003_IMEISV_NUM_DIGITS])
+{
+	unsigned long long equipment_id, watch_id;
+	dbi_result result;
+
+	osmo_strlcpy(subscriber->equipment.imei, imei, sizeof(subscriber->equipment.imei));
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO Equipment "
+		"(imei, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imei);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n");
+		return 1;
+	}
+
+	equipment_id = 0;
+	if (dbi_result_get_numrows_affected(result)) {
+		equipment_id = dbi_conn_sequence_last(conn, NULL);
+	}
+	dbi_result_free(result);
+
+	if (equipment_id)
+		DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"SELECT id FROM Equipment "
+			"WHERE imei = %s ",
+			imei
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n");
+			return 1;
+		}
+		if (!next_row(result)) {
+			LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n");
+			dbi_result_free(result);
+			return 1;
+		}
+		equipment_id = dbi_result_get_ulonglong(result, "id");
+		dbi_result_free(result);
+	}
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO EquipmentWatch "
+		"(subscriber_id, equipment_id, created, updated) "
+		"VALUES "
+		"(%llu, %llu, datetime('now'), datetime('now')) ",
+		subscriber->id, equipment_id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n");
+		return 1;
+	}
+
+	watch_id = 0;
+	if (dbi_result_get_numrows_affected(result))
+		watch_id = dbi_conn_sequence_last(conn, NULL);
+
+	dbi_result_free(result);
+	if (watch_id)
+		DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"UPDATE EquipmentWatch "
+			"SET updated = datetime('now') "
+			"WHERE subscriber_id = %llu AND equipment_id = %llu ",
+			subscriber->id, equipment_id);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+		DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	}
+
+	return 0;
+}
+
+/* store an [unsent] SMS to the database */
+int db_sms_store(struct gsm_sms *sms)
+{
+	dbi_result result;
+	char *q_text, *q_daddr, *q_saddr;
+	unsigned char *q_udata;
+	char *validity_timestamp = "2222-2-2";
+
+	/* FIXME: generate validity timestamp based on validity_minutes */
+
+	dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
+	dbi_conn_quote_string_copy(conn, (char *)sms->dst.addr, &q_daddr);
+	dbi_conn_quote_string_copy(conn, (char *)sms->src.addr, &q_saddr);
+	dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
+				   &q_udata);
+
+	/* FIXME: correct validity period */
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO SMS "
+		"(created, valid_until, "
+		 "reply_path_req, status_rep_req, is_report, "
+		 "msg_ref, protocol_id, data_coding_scheme, "
+		 "ud_hdr_ind, "
+		 "user_data, text, "
+		 "dest_addr, dest_ton, dest_npi, "
+		 "src_addr, src_ton, src_npi) VALUES "
+		"(datetime('now'), %u, "
+		"%u, %u, %u, "
+		"%u, %u, %u, "
+		"%u, "
+		"%s, %s, "
+		"%s, %u, %u, "
+		"%s, %u, %u)",
+		validity_timestamp,
+		sms->reply_path_req, sms->status_rep_req, sms->is_report,
+		sms->msg_ref, sms->protocol_id, sms->data_coding_scheme,
+		sms->ud_hdr_ind,
+		q_udata, q_text,
+		q_daddr, sms->dst.ton, sms->dst.npi,
+		q_saddr, sms->src.ton, sms->src.npi);
+	free(q_text);
+	free(q_udata);
+	free(q_daddr);
+	free(q_saddr);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result)
+{
+	struct gsm_sms *sms = sms_alloc();
+	const char *text, *daddr, *saddr;
+	const unsigned char *user_data;
+
+	if (!sms)
+		return NULL;
+
+	sms->id = dbi_result_get_ulonglong(result, "id");
+
+	/* FIXME: validity */
+	/* FIXME: those should all be get_uchar, but sqlite3 is braindead */
+	sms->created = dbi_result_get_datetime(result, "created");
+	sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
+	sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
+	sms->is_report = dbi_result_get_ulonglong(result, "is_report");
+	sms->msg_ref = dbi_result_get_ulonglong(result, "msg_ref");
+	sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
+	sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
+	sms->data_coding_scheme = dbi_result_get_ulonglong(result,
+						  "data_coding_scheme");
+
+	sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi");
+	sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton");
+	daddr = dbi_result_get_string(result, "dest_addr");
+	if (daddr)
+		osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
+	sms->receiver = subscr_get_by_extension(net->subscr_group, sms->dst.addr);
+
+	sms->src.npi = dbi_result_get_ulonglong(result, "src_npi");
+	sms->src.ton = dbi_result_get_ulonglong(result, "src_ton");
+	saddr = dbi_result_get_string(result, "src_addr");
+	if (saddr)
+		osmo_strlcpy(sms->src.addr, saddr, sizeof(sms->src.addr));
+
+	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+	user_data = dbi_result_get_binary(result, "user_data");
+	if (sms->user_data_len > sizeof(sms->user_data))
+		sms->user_data_len = (uint8_t) sizeof(sms->user_data);
+	memcpy(sms->user_data, user_data, sms->user_data_len);
+
+	text = dbi_result_get_string(result, "text");
+	if (text)
+		osmo_strlcpy(sms->text, text, sizeof(sms->text));
+	return sms;
+}
+
+struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS WHERE SMS.id = %llu", id);
+	if (!result)
+		return NULL;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS with ID >= min_id */
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.dest_addr = Subscriber.extension "
+			"WHERE SMS.id >= %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 "
+			"ORDER BY SMS.id LIMIT 1",
+		min_id);
+	if (!result)
+		return NULL;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net,
+					    unsigned long long min_subscr_id,
+					    unsigned int failed)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.dest_addr = Subscriber.extension "
+			"WHERE Subscriber.id >= %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u "
+			"ORDER BY Subscriber.id, SMS.id LIMIT 1",
+		min_subscr_id, failed);
+	if (!result)
+		return NULL;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS for a given subscriber */
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.dest_addr = Subscriber.extension "
+			"WHERE Subscriber.id = %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 "
+			"ORDER BY SMS.id LIMIT 1",
+		subscr->id);
+	if (!result)
+		return NULL;
+
+	if (!next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(subscr->group->net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* mark a given SMS as delivered */
+int db_sms_mark_delivered(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET sent = datetime('now') "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+/* increase the number of attempted deliveries */
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET deliver_attempts = deliver_attempts + 1 "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for "
+			"SMS %llu.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_apdu_blob_store(struct gsm_subscriber *subscr,
+			uint8_t apdu_id_flags, uint8_t len,
+			uint8_t *apdu)
+{
+	dbi_result result;
+	unsigned char *q_apdu;
+
+	dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO ApduBlobs "
+		"(created,subscriber_id,apdu_id_flags,apdu) VALUES "
+		"(datetime('now'),%llu,%u,%s)",
+		subscr->id, apdu_id_flags, q_apdu);
+
+	free(q_apdu);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_counter(struct osmo_counter *ctr)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctr->name, &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Counters "
+		"(timestamp,name,value) VALUES "
+		"(datetime('now'),%s,%lu)", q_name, ctr->value);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num,
+			     char *q_prefix)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name,
+				   &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"Insert INTO RateCounters "
+		"(timestamp,name,idx,value) VALUES "
+		"(datetime('now'),%s.%s,%u,%"PRIu64")",
+		q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg)
+{
+	unsigned int i;
+	char *q_prefix;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix);
+
+	for (i = 0; i < ctrg->desc->num_ctr; i++)
+		db_store_rate_ctr(ctrg, i, q_prefix);
+
+	free(q_prefix);
+
+	return 0;
+}
diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c
new file mode 100644
index 0000000..09e35cc
--- /dev/null
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -0,0 +1,4050 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <regex.h>
+#include <sys/types.h>
+
+#include "bscconfig.h"
+
+#include <openbsc/auth.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_04_14.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <osmocom/abis/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/transaction.h>
+#include <openbsc/ussd.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/bsc_api.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/handover.h>
+#include <openbsc/mncc_int.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/core/bitvec.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <assert.h>
+
+void *tall_locop_ctx;
+void *tall_authciphop_ctx;
+
+static int tch_rtp_signal(struct gsm_lchan *lchan, int signal);
+
+static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn);
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+			   uint8_t pdisc, uint8_t msg_type);
+static void schedule_reject(struct gsm_subscriber_connection *conn);
+static void release_anchor(struct gsm_subscriber_connection *conn);
+
+static int apply_codec_restrictions(struct gsm_bts *bts,
+	struct gsm_mncc_bearer_cap *bcap)
+{
+	int i, j;
+
+	/* remove unsupported speech versions from list */
+	for (i = 0, j = 0; bcap->speech_ver[i] >= 0; i++) {
+		if (bcap->speech_ver[i] == GSM48_BCAP_SV_FR)
+			bcap->speech_ver[j++] = GSM48_BCAP_SV_FR;
+		if (bcap->speech_ver[i] == GSM48_BCAP_SV_EFR && bts->codec.efr)
+			bcap->speech_ver[j++] = GSM48_BCAP_SV_EFR;
+		if (bcap->speech_ver[i] == GSM48_BCAP_SV_AMR_F && bts->codec.amr)
+			bcap->speech_ver[j++] = GSM48_BCAP_SV_AMR_F;
+		if (bcap->speech_ver[i] == GSM48_BCAP_SV_HR && bts->codec.hr)
+			bcap->speech_ver[j++] = GSM48_BCAP_SV_HR;
+		if (bcap->speech_ver[i] == GSM48_BCAP_SV_AMR_H && bts->codec.amr)
+			bcap->speech_ver[j++] = GSM48_BCAP_SV_AMR_H;
+	}
+	bcap->speech_ver[j] = -1;
+
+	return 0;
+}
+
+static uint32_t new_callref = 0x80000001;
+
+void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg)
+{
+	net->mncc_recv(net, msg);
+}
+
+static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection *conn,
+			      struct gsm_trans *trans)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
+
+	/* if we get passed a transaction reference, do some common
+	 * work that the caller no longer has to do */
+	if (trans) {
+		gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
+		msg->lchan = trans->conn->lchan;
+	}
+
+	if (msg->lchan) {
+		struct e1inp_sign_link *sign_link =
+				msg->lchan->ts->trx->rsl_link;
+
+		msg->dst = sign_link;
+		if (gsm48_hdr_pdisc(gh) == GSM48_PDISC_CC)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) "
+				"Sending '%s' to MS.\n",
+				sign_link->trx->bts->nr,
+				sign_link->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr & 0xf0,
+				gsm48_cc_msg_name(gh->msg_type));
+		else
+			DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) "
+				"Sending 0x%02x to MS.\n",
+				sign_link->trx->bts->nr,
+				sign_link->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr, gh->msg_type);
+	}
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *ss_notify;
+
+	ss_notify = gsm0480_create_notifySS(message);
+	if (!ss_notify)
+		return -1;
+
+	gsm0480_wrap_invoke(ss_notify, GSM0480_OP_CODE_NOTIFY_SS, 0);
+	uint8_t *data = msgb_push(ss_notify, 1);
+	data[0] = ss_notify->len - 1;
+	gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh));
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+	return gsm48_conn_sendmsg(ss_notify, trans->conn, trans);
+}
+
+void release_security_operation(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->sec_operation)
+		return;
+
+	talloc_free(conn->sec_operation);
+	conn->sec_operation = NULL;
+	msc_release_connection(conn);
+}
+
+void allocate_security_operation(struct gsm_subscriber_connection *conn)
+{
+	conn->sec_operation = talloc_zero(tall_authciphop_ctx,
+	                                  struct gsm_security_operation);
+}
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+                         gsm_cbfn *cb, void *cb_data)
+{
+	struct gsm_network *net = conn->network;
+	struct gsm_subscriber *subscr = conn->subscr;
+	struct gsm_security_operation *op;
+	struct gsm_auth_tuple atuple;
+	int status = -1, rc;
+
+	/* Check if we _can_ enable encryption. Cases where we can't:
+	 *  - Encryption disabled in config
+	 *  - Channel already secured (nothing to do)
+	 *  - Subscriber equipment doesn't support configured encryption
+	 */
+	if (!net->a5_encryption) {
+		status = GSM_SECURITY_NOAVAIL;
+	} else if (conn->lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		DEBUGP(DMM, "Requesting to secure an already secure channel");
+		status = GSM_SECURITY_ALREADY;
+	} else if (!ms_cm2_a5n_support(subscr->equipment.classmark2,
+	                               net->a5_encryption)) {
+		DEBUGP(DMM, "Subscriber equipment doesn't support requested encryption");
+		status = GSM_SECURITY_NOAVAIL;
+	}
+
+	/* If not done yet, try to get info for this user */
+	if (status < 0) {
+		rc = auth_get_tuple_for_subscr(&atuple, subscr, key_seq);
+		if (rc <= 0)
+			status = GSM_SECURITY_NOAVAIL;
+	}
+
+	/* Are we done yet ? */
+	if (status >= 0)
+		return cb ?
+			cb(GSM_HOOK_RR_SECURITY, status, NULL, conn, cb_data) :
+			0;
+
+	/* Start an operation (can't have more than one pending !!!) */
+	if (conn->sec_operation)
+		return -EBUSY;
+
+	allocate_security_operation(conn);
+	op = conn->sec_operation;
+	op->cb = cb;
+	op->cb_data = cb_data;
+	memcpy(&op->atuple, &atuple, sizeof(struct gsm_auth_tuple));
+
+		/* FIXME: Should start a timer for completion ... */
+
+	/* Then do whatever is needed ... */
+	if (rc == AUTH_DO_AUTH_THEN_CIPH) {
+		/* Start authentication */
+		return gsm48_tx_mm_auth_req(conn, op->atuple.vec.rand, NULL,
+					    op->atuple.key_seq);
+	} else if (rc == AUTH_DO_CIPH) {
+		/* Start ciphering directly */
+		return gsm0808_cipher_mode(conn, net->a5_encryption,
+		                           op->atuple.vec.kc, 8, 0);
+	}
+
+	return -EINVAL; /* not reached */
+}
+
+static bool subscr_regexp_check(const struct gsm_network *net, const char *imsi)
+{
+	if (!net->authorized_reg_str)
+		return false;
+
+	if (regexec(&net->authorized_regexp, imsi, 0, NULL, 0) != REG_NOMATCH)
+		return true;
+
+	return false;
+}
+
+static int authorize_subscriber(struct gsm_loc_updating_operation *loc,
+				struct gsm_subscriber *subscriber)
+{
+	if (!subscriber)
+		return 0;
+
+	/*
+	 * Do not send accept yet as more information should arrive. Some
+	 * phones will not send us the information and we will have to check
+	 * what we want to do with that.
+	 */
+	if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei))
+		return 0;
+
+	switch (subscriber->group->net->auth_policy) {
+	case GSM_AUTH_POLICY_CLOSED:
+		return subscriber->authorized;
+	case GSM_AUTH_POLICY_REGEXP:
+		if (subscriber->authorized)
+			return 1;
+		if (subscr_regexp_check(subscriber->group->net,
+					subscriber->imsi))
+			subscriber->authorized = 1;
+		return subscriber->authorized;
+	case GSM_AUTH_POLICY_TOKEN:
+		if (subscriber->authorized)
+			return subscriber->authorized;
+		return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT);
+	case GSM_AUTH_POLICY_ACCEPT_ALL:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static void _release_loc_updating_req(struct gsm_subscriber_connection *conn, int release)
+{
+	if (!conn->loc_operation)
+		return;
+
+	/* No need to keep the connection up */
+	release_anchor(conn);
+
+	osmo_timer_del(&conn->loc_operation->updating_timer);
+	talloc_free(conn->loc_operation);
+	conn->loc_operation = NULL;
+	if (release)
+		msc_release_connection(conn);
+}
+
+static void loc_updating_failure(struct gsm_subscriber_connection *conn, int release)
+{
+	if (!conn->loc_operation)
+		return;
+	LOGP(DMM, LOGL_ERROR, "Location Updating failed for %s\n",
+	     subscr_name(conn->subscr));
+	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED]);
+	_release_loc_updating_req(conn, release);
+}
+
+static void loc_updating_success(struct gsm_subscriber_connection *conn, int release)
+{
+	if (!conn->loc_operation)
+		return;
+	LOGP(DMM, LOGL_INFO, "Location Updating completed for %s\n",
+	     subscr_name(conn->subscr));
+	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]);
+	_release_loc_updating_req(conn, release);
+}
+
+static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	if (conn->loc_operation)
+		LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n");
+	loc_updating_failure(conn, 0);
+
+	conn->loc_operation = talloc_zero(tall_locop_ctx,
+					   struct gsm_loc_updating_operation);
+}
+
+static int finish_lu(struct gsm_subscriber_connection *conn)
+{
+	int rc = 0;
+	int avoid_tmsi = conn->network->avoid_tmsi;
+
+	/* We're all good */
+	if (avoid_tmsi) {
+		conn->subscr->tmsi = GSM_RESERVED_TMSI;
+		db_sync_subscriber(conn->subscr);
+	} else {
+		db_subscriber_alloc_tmsi(conn->subscr);
+	}
+
+	rc = gsm0408_loc_upd_acc(conn);
+	if (conn->network->send_mm_info) {
+		/* send MM INFO with network name */
+		rc = gsm48_tx_mm_info(conn);
+	}
+
+	/* call subscr_update after putting the loc_upd_acc
+	 * in the transmit queue, since S_SUBSCR_ATTACHED might
+	 * trigger further action like SMS delivery */
+	subscr_update(conn->subscr, conn->bts,
+		      GSM_SUBSCRIBER_UPDATE_ATTACHED);
+
+	/*
+	 * The gsm0408_loc_upd_acc sends a MI with the TMSI. The
+	 * MS needs to respond with a TMSI REALLOCATION COMPLETE
+	 * (even if the TMSI is the same).
+	 * If avoid_tmsi == true, we don't send a TMSI, we don't
+	 * expect a reply and Location Updating is done.
+	 */
+	if (avoid_tmsi)
+		loc_updating_success(conn, 1);
+
+	return rc;
+}
+
+static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event,
+                                     struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	int rc = 0;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			loc_updating_failure(conn, 1);
+			break;
+
+		case GSM_SECURITY_ALREADY:
+			LOGP(DMM, LOGL_ERROR, "We don't expect LOCATION "
+				"UPDATING after CM SERVICE REQUEST\n");
+			/* fall through */
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_SUCCEEDED:
+			rc = finish_lu(conn);
+			break;
+
+		default:
+			rc = -EINVAL;
+	};
+
+	return rc;
+}
+
+static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	if (!conn->loc_operation)
+		return 0;
+
+	if (authorize_subscriber(conn->loc_operation, conn->subscr))
+		return gsm48_secure_channel(conn,
+			conn->loc_operation->key_seq,
+			_gsm0408_authorize_sec_cb, NULL);
+	return 0;
+}
+
+void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	struct gsm_trans *trans, *temp;
+
+	/* avoid someone issuing a clear */
+	conn->in_release = 1;
+
+	/*
+	 * Cancel any outstanding location updating request
+	 * operation taking place on the subscriber connection.
+	 */
+	loc_updating_failure(conn, 0);
+
+	/* We might need to cancel the paging response or such. */
+	if (conn->sec_operation && conn->sec_operation->cb) {
+		conn->sec_operation->cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+					NULL, conn, conn->sec_operation->cb_data);
+	}
+
+	release_security_operation(conn);
+	release_anchor(conn);
+
+	/*
+	 * Free all transactions that are associated with the released
+	 * connection. The transaction code will inform the CC or SMS
+	 * facilities that will send the release indications. As part of
+	 * the CC REL_IND the remote leg might be released and this will
+	 * trigger the call to trans_free. This is something the llist
+	 * macro can not handle and we will need to re-iterate the list.
+	 *
+	 * TODO: Move the trans_list into the subscriber connection and
+	 * create a pending list for MT transactions. These exist before
+	 * we have a subscriber connection.
+	 */
+restart:
+	llist_for_each_entry_safe(trans, temp, &conn->network->trans_list, entry) {
+		if (trans->conn == conn) {
+			trans_free(trans);
+			goto restart;
+		}
+	}
+}
+
+void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
+{
+	struct gsm_trans *trans, *temp;
+
+	LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n");
+
+	llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) {
+		if (trans->protocol == protocol) {
+			trans->callref = 0;
+			trans_free(trans);
+		}
+	}
+}
+
+/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
+int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct msgb *msg;
+
+	msg = gsm48_create_loc_upd_rej(cause);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+		return -1;
+	}
+
+	msg->lchan = conn->lchan;
+
+	LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT "
+	     "LAC=%u BTS=%u\n", subscr_name(conn->subscr),
+	     bts->location_area_code, bts->nr);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
+static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD ACC");
+	struct gsm48_hdr *gh;
+	struct gsm48_loc_area_id *lai;
+	uint8_t *mid;
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT;
+
+	lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
+	gsm48_generate_lai2(lai, bts_lai(conn->bts));
+
+	if (conn->subscr->tmsi == GSM_RESERVED_TMSI) {
+		uint8_t mi[10];
+		int len;
+		len = gsm48_generate_mid_from_imsi(mi, conn->subscr->imsi);
+		mid = msgb_put(msg, len);
+		memcpy(mid, mi, len);
+	} else {
+		mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+		gsm48_generate_mid_from_tmsi(mid, conn->subscr->tmsi);
+	}
+
+	DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Transmit Chapter 9.2.10 Identity Request */
+static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, uint8_t id_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ID REQ");
+	struct gsm48_hdr *gh;
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_ID_REQ;
+	gh->data[0] = id_type;
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static struct gsm_subscriber *subscr_create(const struct gsm_network *net,
+					    const char *imsi)
+{
+	if (!net->auto_create_subscr)
+		return NULL;
+
+	if (!subscr_regexp_check(net, imsi))
+		return NULL;
+
+	return subscr_create_subscriber(net->subscr_group, imsi);
+}
+
+/* Parse Chapter 9.2.11 Identity Response */
+static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_network *net = conn->network;
+	uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+	DEBUGP(DMM, "IDENTITY RESPONSE: MI(%s)=%s\n",
+		gsm48_mi_type_name(mi_type), mi_string);
+
+	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* look up subscriber based on IMSI, create if not found */
+		if (!conn->subscr) {
+			conn->subscr = subscr_get_by_imsi(net->subscr_group,
+							  mi_string);
+			if (!conn->subscr)
+				conn->subscr = subscr_create(net, mi_string);
+		}
+		if (!conn->subscr && conn->loc_operation) {
+			gsm0408_loc_upd_rej(conn, net->reject_cause);
+			loc_updating_failure(conn, 1);
+			return 0;
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imsi = 0;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* update subscribe <-> IMEI mapping */
+		if (conn->subscr) {
+			db_subscriber_assoc_imei(conn->subscr, mi_string);
+			db_sync_equipment(&conn->subscr->equipment);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imei = 0;
+		break;
+	}
+
+	/* Check if we can let the mobile station enter */
+	return gsm0408_authorize(conn, msg);
+}
+
+
+static void loc_upd_rej_cb(void *data)
+{
+	struct gsm_subscriber_connection *conn = data;
+
+	LOGP(DMM, LOGL_DEBUG, "Location Updating Request procedure timedout.\n");
+	gsm0408_loc_upd_rej(conn, conn->network->reject_cause);
+	loc_updating_failure(conn, 1);
+}
+
+static void schedule_reject(struct gsm_subscriber_connection *conn)
+{
+	osmo_timer_setup(&conn->loc_operation->updating_timer, loc_upd_rej_cb,
+			 conn);
+	osmo_timer_schedule(&conn->loc_operation->updating_timer, 5, 0);
+}
+
+static const struct value_string lupd_names[] = {
+	{ GSM48_LUPD_NORMAL, "NORMAL" },
+	{ GSM48_LUPD_PERIODIC, "PERIODIC" },
+	{ GSM48_LUPD_IMSI_ATT, "IMSI ATTACH" },
+	{ 0, NULL }
+};
+
+/* Chapter 9.2.15: Receive Location Updating Request */
+static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_loc_upd_req *lu;
+	struct gsm_subscriber *subscr = NULL;
+	uint8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+
+ 	lu = (struct gsm48_loc_upd_req *) gh->data;
+
+	mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+
+	DEBUGPC(DMM, "MI(%s)=%s type=%s ", gsm48_mi_type_name(mi_type),
+		mi_string, get_value_string(lupd_names, lu->type));
+
+	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
+
+	switch (lu->type) {
+	case GSM48_LUPD_NORMAL:
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]);
+		break;
+	case GSM48_LUPD_IMSI_ATT:
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]);
+		break;
+	case GSM48_LUPD_PERIODIC:
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]);
+		break;
+	}
+
+	/*
+	 * Pseudo Spoof detection: Just drop a second/concurrent
+	 * location updating request.
+	 */
+	if (conn->loc_operation) {
+		DEBUGPC(DMM, "ignoring request due an existing one: %p.\n",
+			conn->loc_operation);
+		gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR);
+		return 0;
+	}
+
+	allocate_loc_updating_req(conn);
+
+	conn->loc_operation->key_seq = lu->key_seq;
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		DEBUGPC(DMM, "\n");
+		/* we always want the IMEI, too */
+		mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+
+		/* look up subscriber based on IMSI, create if not found */
+		subscr = subscr_get_by_imsi(conn->network->subscr_group, mi_string);
+		if (!subscr)
+			subscr = subscr_create(conn->network, mi_string);
+		if (!subscr) {
+			gsm0408_loc_upd_rej(conn, conn->network->reject_cause);
+			loc_updating_failure(conn, 0); /* FIXME: set release == true? */
+			return 0;
+		}
+		break;
+	case GSM_MI_TYPE_TMSI:
+		DEBUGPC(DMM, "\n");
+		/* look up the subscriber based on TMSI, request IMSI if it fails */
+		subscr = subscr_get_by_tmsi(conn->network->subscr_group,
+					    tmsi_from_string(mi_string));
+		if (!subscr) {
+			/* send IDENTITY REQUEST message to get IMSI */
+			mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI);
+			conn->loc_operation->waiting_for_imsi = 1;
+		}
+		/* we always want the IMEI, too */
+		mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	/* schedule the reject timer */
+	schedule_reject(conn);
+
+	if (!subscr) {
+		DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+
+	conn->subscr = subscr;
+	conn->subscr->equipment.classmark1 = lu->classmark1;
+
+	/* check if we can let the subscriber into our network immediately
+	 * or if we need to wait for identity responses. */
+	return gsm0408_authorize(conn, msg);
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+static uint8_t bcdify(uint8_t value)
+{
+        uint8_t ret;
+
+        ret = value / 10;
+        ret |= (value % 10) << 4;
+
+        return ret;
+}
+
+
+/* Section 9.2.15a */
+int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 MM INF");
+	struct gsm48_hdr *gh;
+	struct gsm_network *net = conn->network;
+	uint8_t *ptr8;
+	int name_len, name_pad;
+
+	time_t cur_t;
+	struct tm* gmt_time;
+	struct tm* local_time;
+	int tzunits;
+	int dst = 0;
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_INFO;
+
+	if (net->name_long) {
+#if 0
+		name_len = strlen(net->name_long);
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len*2 +1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (uint16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_long[i]);
+
+		/* FIXME: Use Cell Broadcast, not UCS-2, since
+		 * UCS-2 is only supported by later revisions of the spec */
+#endif
+		name_len = (strlen(net->name_long)*7)/8;
+		name_pad = (8 - strlen(net->name_long)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode_n(ptr8, name_len, net->name_long, NULL);
+
+	}
+
+	if (net->name_short) {
+#if 0
+		name_len = strlen(net->name_short);
+		/* 10.5.3.5a */
+		ptr8 = (uint8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len*2 + 1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (uint16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_short[i]);
+#endif
+		name_len = (strlen(net->name_short)*7)/8;
+		name_pad = (8 - strlen(net->name_short)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = (uint8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode_n(ptr8, name_len, net->name_short, NULL);
+
+	}
+
+	/* Section 10.5.3.9 */
+	cur_t = time(NULL);
+	gmt_time = gmtime(&cur_t);
+
+	ptr8 = msgb_put(msg, 8);
+	ptr8[0] = GSM48_IE_NET_TIME_TZ;
+	ptr8[1] = bcdify(gmt_time->tm_year % 100);
+	ptr8[2] = bcdify(gmt_time->tm_mon + 1);
+	ptr8[3] = bcdify(gmt_time->tm_mday);
+	ptr8[4] = bcdify(gmt_time->tm_hour);
+	ptr8[5] = bcdify(gmt_time->tm_min);
+	ptr8[6] = bcdify(gmt_time->tm_sec);
+
+	if (net->tz.override) {
+		/* Convert tz.hr and tz.mn to units */
+		if (net->tz.hr < 0) {
+			tzunits = ((net->tz.hr/-1)*4);
+			tzunits = tzunits + (net->tz.mn/15);
+			ptr8[7] = bcdify(tzunits);
+			/* Set negative time */
+			ptr8[7] |= 0x08;
+		}
+		else {
+			tzunits = net->tz.hr*4;
+			tzunits = tzunits + (net->tz.mn/15);
+			ptr8[7] = bcdify(tzunits);
+		}
+		/* Convert DST value */
+		if (net->tz.dst >= 0 && net->tz.dst <= 2)
+			dst = net->tz.dst;
+	}
+	else {
+		/* Need to get GSM offset and convert into 15 min units */
+		/* This probably breaks if gmtoff returns a value not evenly divisible by 15? */
+#ifdef HAVE_TM_GMTOFF_IN_TM
+		local_time = localtime(&cur_t);
+		tzunits = (local_time->tm_gmtoff/60)/15;
+#else
+		/* find timezone offset */
+		time_t utc;
+		double offsetFromUTC;
+		utc = mktime(gmt_time);
+		local_time = localtime(&cur_t);
+		offsetFromUTC = difftime(cur_t, utc);
+		if (local_time->tm_isdst)
+			offsetFromUTC += 3600.0;
+		tzunits = ((int)offsetFromUTC) / 60 / 15;
+#endif
+		if (tzunits < 0) {
+			tzunits = tzunits/-1;
+			ptr8[7] = bcdify(tzunits);
+			/* Flip it to negative */
+			ptr8[7] |= 0x08;
+		}
+		else
+			ptr8[7] = bcdify(tzunits);
+
+		/* Does not support DST +2 */
+		if (local_time->tm_isdst)
+			dst = 1;
+	}
+
+	if (dst) {
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NET_DST;
+		ptr8[1] = 1;
+		ptr8[2] = dst;
+	}
+
+	DEBUGP(DMM, "-> MM INFO\n");
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/*! Send an Authentication Request to MS on the given subscriber connection
+ * according to 3GPP/ETSI TS 24.008, Section 9.2.2.
+ * \param[in] conn  Subscriber connection to send on.
+ * \param[in] rand  Random challenge token to send, must be 16 bytes long.
+ * \param[in] autn  r99: In case of UMTS mutual authentication, AUTN token to
+ * 	send; must be 16 bytes long, or pass NULL for plain GSM auth.
+ * \param[in] key_seq  auth tuple's sequence number.
+ */
+int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, uint8_t *rand,
+			 uint8_t *autn, int key_seq)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH REQ");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar));
+
+	DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", osmo_hexdump(rand, 16));
+	if (autn)
+		DEBUGP(DMM, "   AUTH REQ (autn = %s)\n", osmo_hexdump(autn, 16));
+
+	msg->lchan = conn->lchan;
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_AUTH_REQ;
+
+	ar->key_seq = key_seq;
+
+	/* 16 bytes RAND parameters */
+	osmo_static_assert(sizeof(ar->rand) == 16, sizeof_auth_req_r99_rand);
+	if (rand)
+		memcpy(ar->rand, rand, 16);
+
+
+	/* 16 bytes AUTN */
+	if (autn)
+		msgb_tlv_put(msg, GSM48_IE_AUTN, 16, autn);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Section 9.2.1 */
+int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn)
+{
+	DEBUGP(DMM, "-> AUTH REJECT\n");
+	return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
+}
+
+/*
+ * At the 30C3 phones miss their periodic update
+ * interval a lot and then remain unreachable. In case
+ * we still know the TMSI we can just attach it again.
+ */
+static void implit_attach(struct gsm_subscriber_connection *conn)
+{
+	if (conn->subscr->lac != GSM_LAC_RESERVED_DETACHED)
+		return;
+
+	subscr_update(conn->subscr, conn->bts,
+		      GSM_SUBSCRIBER_UPDATE_ATTACHED);
+}
+
+
+static int _gsm48_rx_mm_serv_req_sec_cb(
+	unsigned int hooknum, unsigned int event,
+	struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	int rc = 0;
+
+	/* auth failed or succeeded, the timer was stopped */
+	conn->expire_timer_stopped = 1;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			/* Nothing to do */
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_ALREADY:
+			rc = gsm48_tx_mm_serv_ack(conn);
+			implit_attach(conn);
+			break;
+
+		case GSM_SECURITY_SUCCEEDED:
+			/* nothing to do. CIPHER MODE COMMAND is
+			 * implicit CM SERV ACK */
+			implit_attach(conn);
+			break;
+
+		default:
+			rc = -EINVAL;
+	};
+
+	return rc;
+}
+
+/*
+ * Handle CM Service Requests
+ * a) Verify that the packet is long enough to contain the information
+ *    we require otherwsie reject with INCORRECT_MESSAGE
+ * b) Try to parse the TMSI. If we do not have one reject
+ * c) Check that we know the subscriber with the TMSI otherwise reject
+ *    with a HLR cause
+ * d) Set the subscriber on the gsm_lchan and accept
+ */
+static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	uint8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+
+	struct gsm_network *network = conn->network;
+	struct gsm_subscriber *subscr;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_service_request *req =
+			(struct gsm48_service_request *)gh->data;
+	/* unfortunately in Phase1 the classmark2 length is variable */
+	uint8_t classmark2_len = gh->data[1];
+	uint8_t *classmark2 = gh->data+2;
+	uint8_t mi_len = *(classmark2 + classmark2_len);
+	uint8_t *mi = (classmark2 + classmark2_len + 1);
+
+	DEBUGP(DMM, "<- CM SERVICE REQUEST ");
+	if (msg->data_len < sizeof(struct gsm48_service_request*)) {
+		DEBUGPC(DMM, "wrong sized message\n");
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	if (msg->data_len < req->mi_len + 6) {
+		DEBUGPC(DMM, "does not fit in packet\n");
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+	mi_type = mi[0] & GSM_MI_TYPE_MASK;
+
+	if (mi_type == GSM_MI_TYPE_IMSI) {
+		DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n",
+			req->cm_service_type, gsm48_mi_type_name(mi_type),
+			mi_string);
+		subscr = subscr_get_by_imsi(network->subscr_group,
+					    mi_string);
+	} else if (mi_type == GSM_MI_TYPE_TMSI) {
+		DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n",
+			req->cm_service_type, gsm48_mi_type_name(mi_type),
+			mi_string);
+		subscr = subscr_get_by_tmsi(network->subscr_group,
+				tmsi_from_string(mi_string));
+	} else {
+		DEBUGPC(DMM, "mi_type is not expected: %d\n", mi_type);
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len));
+
+	if (is_siemens_bts(conn->bts))
+		send_siemens_mrpci(msg->lchan, classmark2-1);
+
+
+	/* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */
+	if (!subscr)
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_IMSI_UNKNOWN_IN_VLR);
+
+	if (!conn->subscr)
+		conn->subscr = subscr;
+	else if (conn->subscr == subscr)
+		subscr_put(subscr); /* lchan already has a ref, don't need another one */
+	else {
+		DEBUGP(DMM, "<- CM Channel already owned by someone else?\n");
+		subscr_put(subscr);
+	}
+
+	subscr->equipment.classmark2_len = classmark2_len;
+	memcpy(subscr->equipment.classmark2, classmark2, classmark2_len);
+	db_sync_equipment(&subscr->equipment);
+
+	/* we will send a MM message soon */
+	conn->expire_timer_stopped = 1;
+
+	return gsm48_secure_channel(conn, req->cipher_key_seq,
+			_gsm48_rx_mm_serv_req_sec_cb, NULL);
+}
+
+static int gsm48_rx_mm_imsi_detach_ind(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm_network *network = conn->network;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_imsi_detach_ind *idi =
+				(struct gsm48_imsi_detach_ind *) gh->data;
+	uint8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len);
+	DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s",
+		gsm48_mi_type_name(mi_type), mi_string);
+
+	rate_ctr_inc(&network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		DEBUGPC(DMM, "\n");
+		subscr = subscr_get_by_tmsi(network->subscr_group,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		DEBUGPC(DMM, "\n");
+		subscr = subscr_get_by_imsi(network->subscr_group,
+					    mi_string);
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, ": unimplemented mobile identity type\n");
+		break;
+	default:
+		DEBUGPC(DMM, ": unknown mobile identity type\n");
+		break;
+	}
+
+	if (subscr) {
+		subscr_update(subscr, conn->bts,
+			      GSM_SUBSCRIBER_UPDATE_DETACHED);
+		DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr));
+
+		subscr->equipment.classmark1 = idi->classmark1;
+		db_sync_equipment(&subscr->equipment);
+
+		subscr_put(subscr);
+	} else
+		DEBUGP(DMM, "Unknown Subscriber ?!?\n");
+
+	/* FIXME: iterate over all transactions and release them,
+	 * imagine an IMSI DETACH happening during an active call! */
+
+	release_anchor(conn);
+	return 0;
+}
+
+static int gsm48_rx_mm_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]);
+
+	return 0;
+}
+
+static int parse_gsm_auth_resp(uint8_t *res, uint8_t *res_len,
+			       struct gsm_subscriber_connection *conn,
+			       struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data;
+
+	if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*ar)) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: MM AUTHENTICATION RESPONSE:"
+		     " l3 length invalid: %u\n",
+		     subscr_name(conn->subscr), msgb_l3len(msg));
+		return -EINVAL;
+	}
+
+	*res_len = sizeof(ar->sres);
+	memcpy(res, ar->sres, sizeof(ar->sres));
+	return 0;
+}
+
+static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len,
+				struct gsm_subscriber_connection *conn,
+				struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	uint8_t *data;
+	uint8_t iei;
+	uint8_t ie_len;
+	unsigned int data_len;
+
+	/* First parse the GSM part */
+	if (parse_gsm_auth_resp(res, res_len, conn, msg))
+		return -EINVAL;
+	OSMO_ASSERT(*res_len == 4);
+
+	/* Then add the extended res part */
+	gh = msgb_l3(msg);
+	data = gh->data + sizeof(struct gsm48_auth_resp);
+	data_len = msgb_l3len(msg) - (data - (uint8_t*)msgb_l3(msg));
+
+	if (data_len < 3) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: MM AUTHENTICATION RESPONSE:"
+		     " l3 length invalid: %u\n",
+		     subscr_name(conn->subscr), msgb_l3len(msg));
+		return -EINVAL;
+	}
+
+	iei = data[0];
+	ie_len = data[1];
+	if (iei != GSM48_IE_AUTH_RES_EXT) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: MM R99 AUTHENTICATION RESPONSE:"
+		     " expected IEI 0x%02x, got 0x%02x\n",
+		     subscr_name(conn->subscr),
+		     GSM48_IE_AUTH_RES_EXT, iei);
+		return -EINVAL;
+	}
+
+	if (ie_len > 12) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: MM R99 AUTHENTICATION RESPONSE:"
+		     " extended Auth Resp IE 0x%02x is too large: %u bytes\n",
+		     subscr_name(conn->subscr), GSM48_IE_AUTH_RES_EXT, ie_len);
+		return -EINVAL;
+	}
+
+	*res_len += ie_len;
+	memcpy(res + 4, &data[2], ie_len);
+	return 0;
+}
+
+/* Chapter 9.2.3: Authentication Response */
+static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm_network *net = conn->network;
+	uint8_t res[16];
+	uint8_t res_len = 0;
+	int rc;
+	bool is_r99;
+
+	if (!conn->subscr) {
+		LOGP(DMM, LOGL_ERROR,
+		     "MM AUTHENTICATION RESPONSE: invalid: no subscriber\n");
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	if (msgb_l3len(msg) >
+	    sizeof(struct gsm48_hdr) + sizeof(struct gsm48_auth_resp)) {
+		rc = parse_umts_auth_resp(res, &res_len, conn, msg);
+		is_r99 = true;
+	} else {
+		rc = parse_gsm_auth_resp(res, &res_len, conn, msg);
+		is_r99 = false;
+	}
+
+	if (rc) {
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	DEBUGP(DMM, "%s: MM %s AUTHENTICATION RESPONSE (%s = %s)\n",
+	       subscr_name(conn->subscr),
+	       is_r99 ? "R99" : "GSM", is_r99 ? "res" : "sres",
+	       osmo_hexdump_nospc(res, res_len));
+
+	/* Future: vlr_sub_rx_auth_resp(conn->vsub, is_r99,
+	 *				conn->via_ran == RAN_UTRAN_IU,
+	 *				res, res_len);
+	 */
+
+	if (res_len != 4) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: MM AUTHENTICATION RESPONSE:"
+		     " UMTS authentication not supported\n",
+		     subscr_name(conn->subscr));
+	}
+
+	/* Safety check */
+	if (!conn->sec_operation) {
+		DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n");
+		return -EIO;
+	}
+
+	/* Validate SRES */
+	if (memcmp(conn->sec_operation->atuple.vec.sres, res, 4)) {
+		int rc;
+		gsm_cbfn *cb = conn->sec_operation->cb;
+
+		DEBUGPC(DMM, "Invalid (expected %s)\n",
+			osmo_hexdump(conn->sec_operation->atuple.vec.sres, 4));
+
+		if (cb)
+			cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+			   NULL, conn, conn->sec_operation->cb_data);
+
+		rc = gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return rc;
+	}
+
+	DEBUGPC(DMM, "OK\n");
+
+	/* Start ciphering */
+	return gsm0808_cipher_mode(conn, net->a5_encryption,
+	                           conn->sec_operation->atuple.vec.kc, 8, 0);
+}
+
+static int gsm48_rx_mm_auth_fail(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t cause;
+	uint8_t auts_tag;
+	uint8_t auts_len;
+	uint8_t *auts;
+	int rc;
+
+	if (!conn->sec_operation) {
+		DEBUGP(DMM, "%s: MM R99 AUTHENTICATION FAILURE:"
+		       " No authentication/cipher operation in progress\n",
+		       subscr_name(conn->subscr));
+		return -EINVAL;
+	}
+
+	if (!conn->subscr) {
+		LOGP(DMM, LOGL_ERROR,
+		     "MM R99 AUTHENTICATION FAILURE: invalid: no subscriber\n");
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	if (msgb_l3len(msg) < sizeof(*gh) + 1) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: MM R99 AUTHENTICATION FAILURE:"
+		     " l3 length invalid: %u\n",
+		     subscr_name(conn->subscr), msgb_l3len(msg));
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	cause = gh->data[0];
+
+	if (cause != GSM48_REJECT_SYNCH_FAILURE) {
+		LOGP(DMM, LOGL_INFO,
+		     "%s: MM R99 AUTHENTICATION FAILURE: cause 0x%0x\n",
+		     subscr_name(conn->subscr), cause);
+		rc = gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return rc;
+	}
+
+	/* This is a Synch Failure procedure, which should pass an AUTS to
+	 * resynchronize the sequence nr with the HLR. Expecting exactly one
+	 * TLV with 14 bytes of AUTS. */
+
+	if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2) {
+		LOGP(DMM, LOGL_INFO,
+		     "%s: MM R99 AUTHENTICATION FAILURE:"
+		     " invalid Synch Failure: missing AUTS IE\n",
+		     subscr_name(conn->subscr));
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	auts_tag = gh->data[1];
+	auts_len = gh->data[2];
+	auts = &gh->data[3];
+
+	if (auts_tag != GSM48_IE_AUTS
+	    || auts_len != 14) {
+		LOGP(DMM, LOGL_INFO,
+		     "%s: MM R99 AUTHENTICATION FAILURE:"
+		     " invalid Synch Failure:"
+		     " expected AUTS IE 0x%02x of 14 bytes,"
+		     " got IE 0x%02x of %u bytes\n",
+		     subscr_name(conn->subscr),
+		     GSM48_IE_AUTS, auts_tag, auts_len);
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2 + auts_len) {
+		LOGP(DMM, LOGL_INFO,
+		     "%s: MM R99 AUTHENTICATION FAILURE:"
+		     " invalid Synch Failure msg: message truncated (%u)\n",
+		     subscr_name(conn->subscr), msgb_l3len(msg));
+		gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return -EINVAL;
+	}
+
+	/* We have an AUTS IE with exactly 14 bytes of AUTS and the msgb is
+	 * large enough. */
+
+	DEBUGP(DMM, "%s: MM R99 AUTHENTICATION SYNCH (AUTS = %s)\n",
+	       subscr_name(conn->subscr), osmo_hexdump_nospc(auts, 14));
+
+	/* Future: vlr_sub_rx_auth_fail(conn->vsub, auts); */
+
+	LOGP(DMM, LOGL_ERROR, "%s: MM R99 AUTHENTICATION not supported\n",
+	     subscr_name(conn->subscr));
+	rc = gsm48_tx_mm_auth_rej(conn);
+	release_security_operation(conn);
+	return rc;
+}
+
+/* Receive a GSM 04.08 Mobility Management (MM) message */
+static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gsm48_hdr_msg_type(gh)) {
+	case GSM48_MT_MM_LOC_UPD_REQUEST:
+		DEBUGP(DMM, "LOCATION UPDATING REQUEST: ");
+		rc = mm_rx_loc_upd_req(conn, msg);
+		break;
+	case GSM48_MT_MM_ID_RESP:
+		rc = mm_rx_id_resp(conn, msg);
+		break;
+	case GSM48_MT_MM_CM_SERV_REQ:
+		rc = gsm48_rx_mm_serv_req(conn, msg);
+		break;
+	case GSM48_MT_MM_STATUS:
+		rc = gsm48_rx_mm_status(msg);
+		break;
+	case GSM48_MT_MM_TMSI_REALL_COMPL:
+		DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+		       subscr_name(conn->subscr));
+		loc_updating_success(conn, 1);
+		break;
+	case GSM48_MT_MM_IMSI_DETACH_IND:
+		rc = gsm48_rx_mm_imsi_detach_ind(conn, msg);
+		break;
+	case GSM48_MT_MM_CM_REEST_REQ:
+		DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n");
+		break;
+	case GSM48_MT_MM_AUTH_RESP:
+		rc = gsm48_rx_mm_auth_resp(conn, msg);
+		break;
+	case GSM48_MT_MM_AUTH_FAIL:
+		rc = gsm48_rx_mm_auth_fail(conn, msg);
+		break;
+	default:
+		LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+/* Receive a PAGING RESPONSE message from the MS */
+static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_pag_resp *resp;
+	uint8_t *classmark2_lv = gh->data + 1;
+	uint8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+	struct bsc_subscr *bsub;
+	uint32_t tmsi;
+	int rc = 0;
+
+	resp = (struct gsm48_pag_resp *) &gh->data[0];
+	gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+				mi_string, &mi_type);
+	DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
+		gsm48_mi_type_name(mi_type), mi_string);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		tmsi = tmsi_from_string(mi_string);
+		subscr = subscr_get_by_tmsi(conn->network->subscr_group, tmsi);
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(conn->network->subscr_group,
+					    mi_string);
+		break;
+	}
+
+	if (!subscr) {
+		DEBUGP(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+
+	if (!conn->subscr) {
+		conn->subscr = subscr;
+	} else if (conn->subscr != subscr) {
+		LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n");
+		subscr_put(subscr);
+		return -EINVAL;
+	} else {
+		DEBUGP(DRR, "<- Channel already owned by us\n");
+		subscr_put(subscr);
+		subscr = conn->subscr;
+	}
+
+	log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+	DEBUGP(DRR, "<- Channel was requested by %s\n",
+		subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi);
+
+	subscr->equipment.classmark2_len = *classmark2_lv;
+	memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv);
+	db_sync_equipment(&subscr->equipment);
+
+	/* TODO MSC split -- creating a BSC subscriber directly from MSC data
+	 * structures in RAM. At some point the MSC will send a message to the
+	 * BSC instead. */
+	bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers,
+						 subscr->imsi);
+	bsub->tmsi = subscr->tmsi;
+	bsub->lac = subscr->lac;
+
+	/* We received a paging */
+	conn->expire_timer_stopped = 1;
+
+	rc = gsm48_handle_paging_resp(conn, msg, bsub);
+	return rc;
+}
+
+static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t apdu_id_flags;
+	uint8_t apdu_len;
+	uint8_t *apdu_data;
+
+	apdu_id_flags = gh->data[0];
+	apdu_len = gh->data[1];
+	apdu_data = gh->data+2;
+
+	DEBUGP(DRR, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s\n",
+		apdu_id_flags, apdu_len, osmo_hexdump(apdu_data, apdu_len));
+
+	return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data);
+}
+
+/* Receive a GSM 04.08 Radio Resource (RR) message */
+static int gsm0408_rcv_rr(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_RR_PAG_RESP:
+		rc = gsm48_rx_rr_pag_resp(conn, msg);
+		break;
+	case GSM48_MT_RR_APP_INFO:
+		rc = gsm48_rx_rr_app_info(conn, msg);
+		break;
+	default:
+		LOGP(DRR, LOGL_NOTICE, "MSC: Unimplemented %s GSM 04.08 RR "
+		     "message\n", gsm48_rr_msg_name(gh->msg_type));
+		break;
+	}
+
+	return rc;
+}
+
+int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, uint8_t apdu_id,
+			   uint8_t apdu_len, const uint8_t *apdu)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 APP INF");
+	struct gsm48_hdr *gh;
+
+	msg->lchan = conn->lchan;
+
+	DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n",
+		apdu_id, apdu_len);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_APP_INFO;
+	gh->data[0] = apdu_id;
+	gh->data[1] = apdu_len;
+	memcpy(gh->data+2, apdu, apdu_len);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* FIXME: this count_statistics is a state machine behaviour. we should convert
+ * the complete call control into a state machine. Afterwards we can move this
+ * code into state transitions.
+ */
+static void count_statistics(struct gsm_trans *trans, int new_state)
+{
+	int old_state = trans->cc.state;
+	struct rate_ctr_group *msc = trans->net->msc_ctrs;
+
+	if (old_state == new_state)
+		return;
+
+	/* state incoming */
+	switch (new_state) {
+	case GSM_CSTATE_ACTIVE:
+		osmo_counter_inc(trans->net->active_calls);
+		rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_ACTIVE]);
+		break;
+	}
+
+	/* state outgoing */
+	switch (old_state) {
+	case GSM_CSTATE_ACTIVE:
+		osmo_counter_dec(trans->net->active_calls);
+		if (new_state == GSM_CSTATE_DISCONNECT_REQ ||
+				new_state == GSM_CSTATE_DISCONNECT_IND)
+			rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_COMPLETE]);
+		else
+			rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_INCOMPLETE]);
+		break;
+	}
+}
+
+/* Call Control */
+
+/* The entire call control code is written in accordance with Figure 7.10c
+ * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE
+ * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY
+ * it for voice */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+	if (state > 31 || state < 0)
+		return;
+
+	DEBUGP(DCC, "new state %s -> %s\n",
+		gsm48_cc_state_name(trans->cc.state),
+		gsm48_cc_state_name(state));
+
+	count_statistics(trans, state);
+	trans->cc.state = state;
+}
+
+static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STATUS");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	uint8_t *cause, *call_state;
+
+	gh->msg_type = GSM48_MT_CC_STATUS;
+
+	cause = msgb_put(msg, 3);
+	cause[0] = 2;
+	cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER;
+	cause[2] = 0x80 | 30;	/* response to status inquiry */
+
+	call_state = msgb_put(msg, 1);
+	call_state[0] = 0xc0 | 0x00;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+			   uint8_t pdisc, uint8_t msg_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 TX SIMPLE");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	msg->lchan = conn->lchan;
+
+	gh->proto_discr = pdisc;
+	gh->msg_type = msg_type;
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+	if (osmo_timer_pending(&trans->cc.timer)) {
+		DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent);
+		osmo_timer_del(&trans->cc.timer);
+		trans->cc.Tcurrent = 0;
+	}
+}
+
+static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans,
+			int msg_type, struct gsm_mncc *mncc)
+{
+	struct msgb *msg;
+	unsigned char *data;
+
+	if (trans)
+		if (trans->conn && trans->conn->lchan)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+				"Sending '%s' to MNCC.\n",
+				trans->conn->lchan->ts->trx->bts->nr,
+				trans->conn->lchan->ts->trx->nr,
+				trans->conn->lchan->ts->nr, trans->transaction_id,
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+		else
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Sending '%s' to MNCC.\n",
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+	else
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) "
+			"Sending '%s' to MNCC.\n", get_mncc_name(msg_type));
+
+	mncc->msg_type = msg_type;
+
+	msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+	if (!msg)
+		return -ENOMEM;
+
+	data = msgb_put(msg, sizeof(struct gsm_mncc));
+	memcpy(data, mncc, sizeof(struct gsm_mncc));
+
+	cc_tx_to_mncc(net, msg);
+
+	return 0;
+}
+
+int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans,
+		     uint32_t callref, int location, int value)
+{
+	struct gsm_mncc rel;
+
+	memset(&rel, 0, sizeof(rel));
+	rel.callref = callref;
+	mncc_set_cause(&rel, location, value);
+	if (trans && trans->cc.state == GSM_CSTATE_RELEASE_REQ)
+		return mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+	return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+}
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF! */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+	gsm48_stop_cc_timer(trans);
+
+	/* send release to L4, if callref still exists */
+	if (trans->callref) {
+		/* Ressource unavailable */
+		mncc_release_ind(trans->net, trans, trans->callref,
+				 GSM48_CAUSE_LOC_PRN_S_LU,
+				 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+	}
+	if (trans->cc.state != GSM_CSTATE_NULL)
+		new_cc_state(trans, GSM_CSTATE_NULL);
+	if (trans->conn)
+		trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref);
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+
+/* call-back from paging the B-end of the connection */
+static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_conn, void *_transt)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct gsm_trans *transt = _transt;
+
+	OSMO_ASSERT(!transt->conn);
+
+	/* check all tranactions (without lchan) for subscriber */
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		DEBUGP(DCC, "Paging subscr %s succeeded!\n", transt->subscr->extension);
+		OSMO_ASSERT(conn);
+		/* Assign lchan */
+		transt->conn = conn;
+		/* send SETUP request to called party */
+		gsm48_cc_tx_setup(transt, &transt->cc.msg);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_BUSY:
+		DEBUGP(DCC, "Paging subscr %s expired!\n",
+			transt->subscr->extension);
+		/* Temporarily out of order */
+		mncc_release_ind(transt->net, transt,
+				 transt->callref,
+				 GSM48_CAUSE_LOC_PRN_S_LU,
+				 GSM48_CC_CAUSE_DEST_OOO);
+		transt->callref = 0;
+		transt->paging_request = NULL;
+		trans_free(transt);
+		break;
+	default:
+		LOGP(DCC, LOGL_ERROR, "Unknown paging event %d\n", event);
+		break;
+	}
+
+	transt->paging_request = NULL;
+	return 0;
+}
+
+static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable);
+
+/* handle audio path for handover */
+static int switch_for_handover(struct gsm_lchan *old_lchan,
+			struct gsm_lchan *new_lchan)
+{
+	struct rtp_socket *old_rs, *new_rs, *other_rs;
+
+	/* Ask the new socket to send to the already known port. */
+	if (new_lchan->conn->mncc_rtp_bridge) {
+		LOGP(DHO, LOGL_DEBUG, "Forwarding RTP\n");
+		rsl_ipacc_mdcx(new_lchan,
+					old_lchan->abis_ip.connect_ip,
+					old_lchan->abis_ip.connect_port, 0);
+		return 0;
+	}
+
+	if (ipacc_rtp_direct) {
+		LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n");
+		return 0;
+	}
+
+	/* RTP Proxy mode */
+	new_rs = new_lchan->abis_ip.rtp_socket;
+	old_rs = old_lchan->abis_ip.rtp_socket;
+
+	if (!new_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n");
+		return -EIO;
+	}
+
+	rsl_ipacc_mdcx_to_rtpsock(new_lchan);
+
+	if (!old_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for old_lchan\n");
+		return -EIO;
+	}
+
+	/* copy rx_action and reference to other sock */
+	new_rs->rx_action = old_rs->rx_action;
+	new_rs->tx_action = old_rs->tx_action;
+	new_rs->transmit = old_rs->transmit;
+
+	switch (old_lchan->abis_ip.rtp_socket->rx_action) {
+	case RTP_PROXY:
+		other_rs = old_rs->proxy.other_sock;
+		rtp_socket_proxy(new_rs, other_rs);
+		/* delete reference to other end socket to prevent
+		 * rtp_socket_free() from removing the inverse reference */
+		old_rs->proxy.other_sock = NULL;
+		break;
+	case RTP_RECV_UPSTREAM:
+		new_rs->receive = old_rs->receive;
+		break;
+	case RTP_NONE:
+		break;
+	}
+
+	return 0;
+}
+
+static void maybe_switch_for_handover(struct gsm_lchan *lchan)
+{
+	struct gsm_lchan *old_lchan;
+	old_lchan = bsc_handover_pending(lchan);
+	if (old_lchan)
+		switch_for_handover(old_lchan, lchan);
+}
+
+/* some other part of the code sends us a signal */
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+				 void *handler_data, void *signal_data)
+{
+	struct gsm_lchan *lchan = signal_data;
+	int rc;
+	struct gsm_network *net;
+	struct gsm_trans *trans;
+
+	if (subsys != SS_ABISIP)
+		return 0;
+
+	/* RTP bridge handling */
+	if (lchan->conn && lchan->conn->mncc_rtp_bridge)
+		return tch_rtp_signal(lchan, signal);
+
+	/* in case we use direct BTS-to-BTS RTP */
+	if (ipacc_rtp_direct)
+		return 0;
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		/* in case we don't use direct BTS-to-BTS RTP */
+		/* the BTS has successfully bound a TCH to a local ip/port,
+		 * which means we can connect our UDP socket to it */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		lchan->abis_ip.rtp_socket = rtp_socket_create();
+		if (!lchan->abis_ip.rtp_socket)
+			return -EIO;
+
+		rc = rtp_socket_connect(lchan->abis_ip.rtp_socket,
+				   lchan->abis_ip.bound_ip,
+				   lchan->abis_ip.bound_port);
+		if (rc < 0)
+			return -EIO;
+
+		/* check if any transactions on this lchan still have
+		 * a tch_recv_mncc request pending */
+		net = lchan->ts->trx->bts->network;
+		llist_for_each_entry(trans, &net->trans_list, entry) {
+			if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) {
+				DEBUGP(DCC, "pending tch_recv_mncc request\n");
+				tch_recv_mncc(net, trans->callref, 1);
+			}
+		}
+
+		/*
+		 * TODO: this appears to be too early? Why not until after
+		 * the handover detect or the handover complete?
+		 *
+		 * Do we have a handover pending for this new lchan? In that
+		 * case re-route the audio from the old channel to the new one.
+		 */
+		maybe_switch_for_handover(lchan);
+		break;
+	case S_ABISIP_DLCX_IND:
+		/* the BTS tells us a RTP stream has been disconnected */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+/* map two ipaccess RTP streams onto each other */
+static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts;
+	enum gsm_chan_t lt = lchan->type, rt = remote_lchan->type;
+	enum gsm48_chan_mode lm = lchan->tch_mode, rm = remote_lchan->tch_mode;
+	int rc;
+
+	DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u,%s) and "
+	       "(bts=%u,trx=%u,ts=%u,%s)\n",
+	       bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+	       get_value_string(gsm_chan_t_names, lt),
+	       remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr,
+	       get_value_string(gsm_chan_t_names, rt));
+
+	if (bts->type != remote_bts->type) {
+		LOGP(DCC, LOGL_ERROR, "Cannot switch calls between different BTS types yet\n");
+		return -EINVAL;
+	}
+
+	if (lt != rt) {
+		LOGP(DCC, LOGL_ERROR, "Cannot patch through call with different"
+		     " channel types: local = %s, remote = %s\n",
+		     get_value_string(gsm_chan_t_names, lt),
+		     get_value_string(gsm_chan_t_names, rt));
+		return -EBADSLT;
+	}
+
+	if (lm != rm) {
+		LOGP(DCC, LOGL_ERROR, "Cannot patch through call with different"
+		     " channel modes: local = %s, remote = %s\n",
+		     get_value_string(gsm48_chan_mode_names, lm),
+		     get_value_string(gsm48_chan_mode_names, rm));
+		return -EMEDIUMTYPE;
+	}
+
+	// todo: map between different bts types
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		if (!ipacc_rtp_direct) {
+			if (!lchan->abis_ip.rtp_socket) {
+				LOGP(DHO, LOGL_ERROR, "no RTP socket for "
+					"lchan\n");
+				return -EIO;
+			}
+			if (!remote_lchan->abis_ip.rtp_socket) {
+				LOGP(DHO, LOGL_ERROR, "no RTP socket for "
+					"remote_lchan\n");
+				return -EIO;
+			}
+
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan);
+			if (rc < 0)
+				return rc;
+			/* connect them with each other */
+			rtp_socket_proxy(lchan->abis_ip.rtp_socket,
+					 remote_lchan->abis_ip.rtp_socket);
+		} else {
+			/* directly connect TCH RTP streams to each other */
+			rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip,
+						remote_lchan->abis_ip.bound_port,
+						remote_lchan->abis_ip.rtp_payload2);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip,
+						lchan->abis_ip.bound_port,
+						lchan->abis_ip.rtp_payload2);
+		}
+		break;
+	case GSM_BTS_TYPE_BS11:
+	case GSM_BTS_TYPE_RBS2000:
+	case GSM_BTS_TYPE_NOKIA_SITE:
+		trau_mux_map_lchan(lchan, remote_lchan);
+		break;
+	default:
+		LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* bridge channels of two transactions */
+static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge)
+{
+	struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[0]);
+	struct gsm_trans *trans2 = trans_find_by_callref(net, bridge->callref[1]);
+
+	if (!trans1 || !trans2)
+		return -EIO;
+
+	if (!trans1->conn || !trans2->conn)
+		return -EIO;
+
+	/* Which subscriber do we want to track trans1 or trans2? */
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans1->subscr);
+
+	/* through-connect channel */
+	return tch_map(trans1->conn->lchan, trans2->conn->lchan);
+}
+
+/* enable receive of channels to MNCC upqueue */
+static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable)
+{
+	struct gsm_trans *trans;
+	struct gsm_lchan *lchan;
+	struct gsm_bts *bts;
+	int rc;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans)
+		return -EIO;
+	if (!trans->conn)
+		return 0;
+
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	lchan = trans->conn->lchan;
+	bts = lchan->ts->trx->bts;
+
+	/* store receive state */
+	trans->tch_recv = enable;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_OSMOBTS:
+		if (ipacc_rtp_direct) {
+			LOGP(DCC, LOGL_ERROR, "Error: RTP proxy is disabled\n");
+			return -EINVAL;
+		}
+		/* In case, we don't have a RTP socket to the BTS yet, the BTS
+		 * will not be connected to our RTP proxy and the socket will
+		 * not be assigned to the application interface. This method
+		 * will be called again, once the audio socket is created and
+		 * connected. */
+		if (!lchan->abis_ip.rtp_socket) {
+			DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+			return 0;
+		}
+		if (enable) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			/* assign socket to application interface */
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, callref);
+		} else
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, 0);
+		break;
+	case GSM_BTS_TYPE_BS11:
+	case GSM_BTS_TYPE_RBS2000:
+	case GSM_BTS_TYPE_NOKIA_SITE:
+		/* In case we don't have a TCH with correct mode, the TRAU muxer
+		 * will not be asigned to the application interface. This is
+		 * performed by switch_trau_mux() after successful handover or
+		 * assignment. */
+		if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+			DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+			return 0;
+		}
+		if (enable)
+			return trau_recv_lchan(lchan, callref);
+		return trau_mux_unmap(NULL, callref);
+		break;
+	default:
+		LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+	DEBUGP(DCC, "-> STATUS ENQ\n");
+	return gsm48_cc_tx_status(trans, msg);
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+
+static void gsm48_cc_timeout(void *arg)
+{
+	struct gsm_trans *trans = arg;
+	int disconnect = 0, release = 0;
+	int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+	int mo_location = GSM48_CAUSE_LOC_USER;
+	int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+	int l4_location = GSM48_CAUSE_LOC_PRN_S_LU;
+	struct gsm_mncc mo_rel, l4_rel;
+
+	memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+	mo_rel.callref = trans->callref;
+	memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+	l4_rel.callref = trans->callref;
+
+	switch(trans->cc.Tcurrent) {
+	case 0x303:
+		release = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x310:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x313:
+		disconnect = 1;
+		/* unknown, did not find it in the specs */
+		break;
+	case 0x301:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x308:
+		if (!trans->cc.T308_second) {
+			/* restart T308 a second time */
+			gsm48_cc_tx_release(trans, &trans->cc.msg);
+			trans->cc.T308_second = 1;
+			break; /* stay in release state */
+		}
+		trans_free(trans);
+		return;
+//		release = 1;
+//		l4_cause = 14;
+//		break;
+	case 0x306:
+		release = 1;
+		mo_cause = trans->cc.msg.cause.value;
+		mo_location = trans->cc.msg.cause.location;
+		break;
+	case 0x323:
+		disconnect = 1;
+		break;
+	default:
+		release = 1;
+	}
+
+	if (release && trans->callref) {
+		/* process release towards layer 4 */
+		mncc_release_ind(trans->net, trans, trans->callref,
+				 l4_location, l4_cause);
+		trans->callref = 0;
+	}
+
+	if (disconnect && trans->callref) {
+		/* process disconnect towards layer 4 */
+		mncc_set_cause(&l4_rel, l4_location, l4_cause);
+		mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &l4_rel);
+	}
+
+	/* process disconnect towards mobile station */
+	if (disconnect || release) {
+		mncc_set_cause(&mo_rel, mo_location, mo_cause);
+		mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+		mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+		mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+		mo_rel.cause.diag_len = 3;
+
+		if (disconnect)
+			gsm48_cc_tx_disconnect(trans, &mo_rel);
+		if (release)
+			gsm48_cc_tx_release(trans, &mo_rel);
+	}
+
+}
+
+/* disconnect both calls from the bridge */
+static inline void disconnect_bridge(struct gsm_network *net,
+				     struct gsm_mncc_bridge *bridge, int err)
+{
+	struct gsm_trans *trans0 = trans_find_by_callref(net, bridge->callref[0]);
+	struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[1]);
+	struct gsm_mncc mx_rel;
+	if (!trans0 || !trans1)
+		return;
+
+	DEBUGP(DCC, "Failed to bridge TCH for calls %x <-> %x :: %s \n",
+	       trans0->callref, trans1->callref, strerror(err));
+
+	memset(&mx_rel, 0, sizeof(struct gsm_mncc));
+	mncc_set_cause(&mx_rel, GSM48_CAUSE_LOC_INN_NET,
+		       GSM48_CC_CAUSE_CHAN_UNACCEPT);
+
+	mx_rel.callref = trans0->callref;
+	gsm48_cc_tx_disconnect(trans0, &mx_rel);
+
+	mx_rel.callref = trans1->callref;
+	gsm48_cc_tx_disconnect(trans1, &mx_rel);
+}
+
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+				 int sec, int micro)
+{
+	DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec);
+	osmo_timer_setup(&trans->cc.timer, gsm48_cc_timeout, trans);
+	osmo_timer_schedule(&trans->cc.timer, sec, micro);
+	trans->cc.Tcurrent = current;
+}
+
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t msg_type = gsm48_hdr_msg_type(gh);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc setup;
+
+	memset(&setup, 0, sizeof(struct gsm_mncc));
+	setup.callref = trans->callref;
+	setup.lchan_type = trans->conn->lchan->type;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* emergency setup is identified by msg_type */
+	if (msg_type == GSM48_MT_CC_EMERG_SETUP)
+		setup.emergency = 1;
+
+	/* use subscriber as calling party number */
+	setup.fields |= MNCC_F_CALLING;
+	osmo_strlcpy(setup.calling.number, trans->subscr->extension,
+		     sizeof(setup.calling.number));
+	osmo_strlcpy(setup.imsi, trans->subscr->imsi, sizeof(setup.imsi));
+
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		setup.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&setup.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+		apply_codec_restrictions(trans->conn->bts, &setup.bearer_cap);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		setup.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&setup.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* called party bcd number */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+		setup.fields |= MNCC_F_CALLED;
+		gsm48_decode_called(&setup.called,
+			      TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		setup.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&setup.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		setup.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&setup.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+	/* CLIR suppression */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP))
+		setup.clir.sup = 1;
+	/* CLIR invocation */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC))
+		setup.clir.inv = 1;
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		setup.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&setup.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+	LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n",
+	     subscr_name(trans->subscr), trans->subscr->extension,
+	     setup.called.number);
+
+	rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]);
+
+	/* indicate setup to MNCC */
+	mncc_recvmsg(trans->net, trans, MNCC_SETUP_IND, &setup);
+
+	/* MNCC code will modify the channel asynchronously, we should
+	 * ipaccess-bind only after the modification has been made to the
+	 * lchan->tch_mode */
+	return 0;
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STUP");
+	struct gsm48_hdr *gh;
+	struct gsm_mncc *setup = arg;
+	int rc, trans_id;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	/* transaction id must not be assigned */
+	if (trans->transaction_id != 0xff) { /* unasssigned */
+		DEBUGP(DCC, "TX Setup with assigned transaction. "
+			"This is not allowed!\n");
+		/* Temporarily out of order */
+		rc = mncc_release_ind(trans->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+
+	/* Get free transaction_id */
+	trans_id = trans_assign_trans_id(trans->net, trans->subscr,
+					 GSM48_PDISC_CC, 0);
+	if (trans_id < 0) {
+		/* no free transaction ID */
+		rc = mncc_release_ind(trans->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	trans->transaction_id = trans_id;
+
+	gh->msg_type = GSM48_MT_CC_SETUP;
+
+	gsm48_start_cc_timer(trans, 0x303, GSM48_T303);
+
+	/* bearer capability */
+	if (setup->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap);
+	/* facility */
+	if (setup->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &setup->facility);
+	/* progress */
+	if (setup->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &setup->progress);
+	/* calling party BCD number */
+	if (setup->fields & MNCC_F_CALLING)
+		gsm48_encode_calling(msg, &setup->calling);
+	/* called party BCD number */
+	if (setup->fields & MNCC_F_CALLED)
+		gsm48_encode_called(msg, &setup->called);
+	/* user-user */
+	if (setup->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &setup->useruser);
+	/* redirecting party BCD number */
+	if (setup->fields & MNCC_F_REDIRECTING)
+		gsm48_encode_redirecting(msg, &setup->redirecting);
+	/* signal */
+	if (setup->fields & MNCC_F_SIGNAL)
+		gsm48_encode_signal(msg, setup->signal);
+
+	new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+	rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP]);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc call_conf;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x310, GSM48_T310);
+
+	memset(&call_conf, 0, sizeof(struct gsm_mncc));
+	call_conf.callref = trans->callref;
+	call_conf.lchan_type = trans->conn->lchan->type;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+	/* repeat */
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+		call_conf.repeat = 1;
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+		call_conf.repeat = 2;
+#endif
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		call_conf.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&call_conf.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+		apply_codec_restrictions(trans->conn->bts, &call_conf.bearer_cap);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		call_conf.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&call_conf.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		call_conf.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&call_conf.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	/* IMSI of called subscriber */
+	osmo_strlcpy(call_conf.imsi, trans->subscr->imsi,
+		     sizeof(call_conf.imsi));
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND,
+			    &call_conf);
+}
+
+static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *proceeding = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROC");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CALL_PROC;
+
+	new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+	/* bearer capability */
+	if (proceeding->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap);
+	/* facility */
+	if (proceeding->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &proceeding->facility);
+	/* progress */
+	if (proceeding->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &proceeding->progress);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc alerting;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x301, GSM48_T301);
+
+	memset(&alerting, 0, sizeof(struct gsm_mncc));
+	alerting.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		alerting.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&alerting.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+
+	/* progress */
+	if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+		alerting.fields |= MNCC_F_PROGRESS;
+		gsm48_decode_progress(&alerting.progress,
+				TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		alerting.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&alerting.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_ALERT_IND,
+			    &alerting);
+}
+
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *alerting = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC ALERT");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_ALERTING;
+
+	/* facility */
+	if (alerting->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &alerting->facility);
+	/* progress */
+	if (alerting->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &alerting->progress);
+	/* user-user */
+	if (alerting->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &alerting->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *progress = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROGRESS");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_PROGRESS;
+
+	/* progress */
+	gsm48_encode_progress(msg, 1, &progress->progress);
+	/* user-user */
+	if (progress->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &progress->useruser);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *connect = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSN 04.08 CC CON");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x313, GSM48_T313);
+
+	/* facility */
+	if (connect->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &connect->facility);
+	/* progress */
+	if (connect->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &connect->progress);
+	/* connected number */
+	if (connect->fields & MNCC_F_CONNECTED)
+		gsm48_encode_connected(msg, &connect->connected);
+	/* user-user */
+	if (connect->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &connect->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_IND);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc connect;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&connect, 0, sizeof(struct gsm_mncc));
+	connect.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* use subscriber as connected party number */
+	connect.fields |= MNCC_F_CONNECTED;
+	osmo_strlcpy(connect.connected.number, trans->subscr->extension,
+		     sizeof(connect.connected.number));
+	osmo_strlcpy(connect.imsi, trans->subscr->imsi, sizeof(connect.imsi));
+
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		connect.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&connect.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		connect.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&connect.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		connect.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&connect.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+	rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT]);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_SETUP_CNF, &connect);
+}
+
+
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc connect_ack;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+	rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK]);
+
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = trans->callref;
+
+	return mncc_recvmsg(trans->net, trans, MNCC_SETUP_COMPL_IND,
+			    &connect_ack);
+}
+
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC CON ACK");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc disc;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+	memset(&disc, 0, sizeof(struct gsm_mncc));
+	disc.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		disc.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&disc.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		disc.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&disc.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		disc.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&disc.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		disc.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&disc.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &disc);
+
+}
+
+static struct gsm_mncc_cause default_cause = {
+	.location	= GSM48_CAUSE_LOC_PRN_S_LU,
+	.coding		= 0,
+	.rec		= 0,
+	.rec_val	= 0,
+	.value		= GSM48_CC_CAUSE_NORMAL_UNSPEC,
+	.diag_len	= 0,
+	.diag		= { 0 },
+};
+
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *disc = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC DISC");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x306, GSM48_T306);
+
+	/* cause */
+	if (disc->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &disc->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	/* facility */
+	if (disc->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &disc->facility);
+	/* progress */
+	if (disc->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &disc->progress);
+	/* user-user */
+	if (disc->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &disc->useruser);
+
+	/* store disconnect cause for T306 expiry */
+	memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc));
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+		/* release collision 5.4.5 */
+		rc = mncc_recvmsg(trans->net, trans, MNCC_REL_CNF, &rel);
+	} else {
+		rc = gsm48_tx_simple(trans->conn,
+				     GSM48_PDISC_CC | (trans->transaction_id << 4),
+				     GSM48_MT_CC_RELEASE_COMPL);
+		rc = mncc_recvmsg(trans->net, trans, MNCC_REL_IND, &rel);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_NULL);
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC REL");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RELEASE;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x308, GSM48_T308);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	trans->cc.T308_second = 0;
+	memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+	if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+		new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc = 0;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->callref) {
+		switch (trans->cc.state) {
+		case GSM_CSTATE_CALL_PRESENT:
+			rc = mncc_recvmsg(trans->net, trans,
+					  MNCC_REJ_IND, &rel);
+			break;
+		case GSM_CSTATE_RELEASE_REQ:
+			rc = mncc_recvmsg(trans->net, trans,
+					  MNCC_REL_CNF, &rel);
+			break;
+		default:
+			rc = mncc_recvmsg(trans->net, trans,
+					  MNCC_REL_IND, &rel);
+		}
+	}
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC REL COMPL");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	int ret;
+
+	gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+	trans->callref = 0;
+
+	gsm48_stop_cc_timer(trans);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	ret =  gsm48_conn_sendmsg(msg, trans->conn, trans);
+
+	trans_free(trans);
+
+	return ret;
+}
+
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc fac;
+
+	memset(&fac, 0, sizeof(struct gsm_mncc));
+	fac.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		fac.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&fac.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		fac.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&fac.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->net, trans, MNCC_FACILITY_IND, &fac);
+}
+
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *fac = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC FAC");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+
+	/* facility */
+	gsm48_encode_facility(msg, 1, &fac->facility);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc hold;
+
+	memset(&hold, 0, sizeof(struct gsm_mncc));
+	hold.callref = trans->callref;
+	return mncc_recvmsg(trans->net, trans, MNCC_HOLD_IND, &hold);
+}
+
+static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC HLD ACK");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *hold_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC HLD REJ");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_REJ;
+
+	/* cause */
+	if (hold_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &hold_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc retrieve;
+
+	memset(&retrieve, 0, sizeof(struct gsm_mncc));
+	retrieve.callref = trans->callref;
+	return mncc_recvmsg(trans->net, trans, MNCC_RETRIEVE_IND,
+			    &retrieve);
+}
+
+static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC RETR ACK");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *retrieve_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC RETR REJ");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_REJ;
+
+	/* cause */
+	if (retrieve_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &retrieve_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* keypad facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+		dtmf.fields |= MNCC_F_KEYPAD;
+		gsm48_decode_keypad(&dtmf.keypad,
+			      TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+	}
+
+	return mncc_recvmsg(trans->net, trans, MNCC_START_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF ACK");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_ACK;
+
+	/* keypad */
+	if (dtmf->fields & MNCC_F_KEYPAD)
+		gsm48_encode_keypad(msg, dtmf->keypad);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF REJ");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_REJ;
+
+	/* cause */
+	if (dtmf->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &dtmf->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF STP ACK");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+
+	return mncc_recvmsg(trans->net, trans, MNCC_STOP_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+		apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_IND, &modify);
+}
+
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY;
+
+	gsm48_start_cc_timer(trans, 0x323, GSM48_T323);
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+		apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD COMPL");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= GSM48_IE_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+		apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		modify.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&modify.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD REJ");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+	/* cause */
+	gsm48_encode_cause(msg, 1, &modify->cause);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *notify = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC NOT");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+	/* notify */
+	gsm48_encode_notify(msg, notify->notify);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+//	struct tlv_parsed tp;
+	struct gsm_mncc notify;
+
+	memset(&notify, 0, sizeof(struct gsm_mncc));
+	notify.callref = trans->callref;
+//	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len);
+	if (payload_len >= 1)
+		gsm48_decode_notify(&notify.notify, gh->data);
+
+	return mncc_recvmsg(trans->net, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *user = arg;
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USR INFO");
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+	/* user-user */
+	if (user->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 1, &user->useruser);
+	/* more data */
+	if (user->more)
+		gsm48_encode_more(msg);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc user;
+
+	memset(&user, 0, sizeof(struct gsm_mncc));
+	user.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0);
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		user.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&user.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* more data */
+	if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+		user.more = 1;
+
+	return mncc_recvmsg(trans->net, trans, MNCC_USERINFO_IND, &user);
+}
+
+static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *mode = arg;
+	struct gsm_lchan *lchan = trans->conn->lchan;
+
+	/*
+	 * We were forced to make an assignment a lot earlier and
+	 * we should avoid sending another assignment that might
+	 * even lead to a different kind of lchan (TCH/F vs. TCH/H).
+	 * In case of rtp-bridge it is too late to change things
+	 * here.
+	 */
+	if (trans->conn->mncc_rtp_bridge && lchan->tch_mode != GSM48_CMODE_SIGN)
+		return 0;
+
+	return gsm0808_assign_req(trans->conn, mode->lchan_mode,
+		trans->conn->lchan->type != GSM_LCHAN_TCH_H);
+}
+
+static void mncc_recv_rtp(struct gsm_network *net, uint32_t callref,
+		int cmd, uint32_t addr, uint16_t port, uint32_t payload_type,
+		uint32_t payload_msg_type)
+{
+	uint8_t data[sizeof(struct gsm_mncc)];
+	struct gsm_mncc_rtp *rtp;
+
+	memset(&data, 0, sizeof(data));
+	rtp = (struct gsm_mncc_rtp *) &data[0];
+
+	rtp->callref = callref;
+	rtp->msg_type = cmd;
+	rtp->ip = addr;
+	rtp->port = port;
+	rtp->payload_type = payload_type;
+	rtp->payload_msg_type = payload_msg_type;
+	mncc_recvmsg(net, NULL, cmd, (struct gsm_mncc *)data);
+}
+
+static void mncc_recv_rtp_sock(struct gsm_network *net, struct gsm_trans *trans, int cmd)
+{
+	struct gsm_lchan *lchan;
+	int msg_type;
+
+	lchan = trans->conn->lchan;
+	switch (lchan->abis_ip.rtp_payload) {
+	case RTP_PT_GSM_FULL:
+		msg_type = GSM_TCHF_FRAME;
+		break;
+	case RTP_PT_GSM_EFR:
+		msg_type = GSM_TCHF_FRAME_EFR;
+		break;
+	case RTP_PT_GSM_HALF:
+		msg_type = GSM_TCHH_FRAME;
+		break;
+	case RTP_PT_AMR:
+		msg_type = GSM_TCH_FRAME_AMR;
+		break;
+	default:
+		LOGP(DMNCC, LOGL_ERROR, "%s unknown payload type %d\n",
+			gsm_lchan_name(lchan), lchan->abis_ip.rtp_payload);
+		msg_type = 0;
+		break;
+	}
+
+	return mncc_recv_rtp(net, trans->callref, cmd,
+			lchan->abis_ip.bound_ip,
+			lchan->abis_ip.bound_port,
+			lchan->abis_ip.rtp_payload,
+			msg_type);
+}
+
+static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd)
+{
+	return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0);
+}
+
+static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
+{
+	struct gsm_bts *bts;
+	struct gsm_lchan *lchan;
+	struct gsm_trans *trans;
+	enum gsm48_chan_mode m;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return -EIO;
+	}
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	if (!trans->conn) {
+		LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return 0;
+	}
+
+	lchan = trans->conn->lchan;
+	bts = lchan->ts->trx->bts;
+	if (!is_ipaccess_bts(bts)) {
+		/*
+		 * I want this to be straight forward and have no audio flow
+		 * through the nitb/osmo-mss system. This currently means that
+		 * this will not work with BS11/Nokia type BTS. We would need
+		 * to have a trau<->rtp bridge for these but still preferable
+		 * in another process.
+		 */
+		LOGP(DMNCC, LOGL_ERROR, "RTP create only works with IP systems\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return -EINVAL;
+	}
+
+	trans->conn->mncc_rtp_bridge = 1;
+	/*
+	 * *sigh* we need to pick a codec now. Pick the most generic one
+	 * right now and hope we could fix that later on. This is very
+	 * similiar to the routine above.
+	 * Fallback to the internal MNCC mode to select a route.
+	 */
+	if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+		trans->conn->mncc_rtp_create_pending = 1;
+		m = mncc_codec_for_mode(lchan->type);
+		LOGP(DMNCC, LOGL_DEBUG, "RTP create: codec=%s, chan_type=%s\n",
+		     get_value_string(gsm48_chan_mode_names, m),
+		     get_value_string(gsm_chan_t_names, lchan->type));
+		return gsm0808_assign_req(trans->conn, m,
+				lchan->type != GSM_LCHAN_TCH_H);
+	}
+
+	mncc_recv_rtp_sock(trans->net, trans, MNCC_RTP_CREATE);
+	return 0;
+}
+
+static int tch_rtp_connect(struct gsm_network *net, void *arg)
+{
+	struct gsm_lchan *lchan;
+	struct gsm_trans *trans;
+	struct gsm_mncc_rtp *rtp = arg;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, rtp->callref);
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+		return -EIO;
+	}
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	if (!trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+		return 0;
+	}
+
+	lchan = trans->conn->lchan;
+	LOGP(DMNCC, LOGL_DEBUG, "RTP connect: codec=%s, chan_type=%s\n",
+		     get_value_string(gsm48_chan_mode_names,
+				      mncc_codec_for_mode(lchan->type)),
+		     get_value_string(gsm_chan_t_names, lchan->type));
+
+	/* TODO: Check if payload_msg_type is compatible with what we have */
+	if (rtp->payload_type != lchan->abis_ip.rtp_payload) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect with different RTP payload\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+	}
+
+	/*
+	 * FIXME: payload2 can't be sent with MDCX as the osmo-bts code
+	 * complains about both rtp and rtp payload2 being present in the
+	 * same package!
+	 */
+	trans->conn->mncc_rtp_connect_pending = 1;
+	return rsl_ipacc_mdcx(lchan, rtp->ip, rtp->port, 0);
+}
+
+static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
+{
+	struct gsm_network *net;
+	struct gsm_trans *tmp, *trans = NULL;
+
+	net = lchan->ts->trx->bts->network;
+	llist_for_each_entry(tmp, &net->trans_list, entry) {
+		if (!tmp->conn)
+			continue;
+		if (tmp->conn->lchan != lchan && tmp->conn->ho_lchan != lchan)
+			continue;
+		trans = tmp;
+		break;
+	}
+
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "%s IPA abis signal but no transaction.\n",
+			gsm_lchan_name(lchan));
+		return 0;
+	}
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		if (lchan->conn->mncc_rtp_create_pending) {
+			lchan->conn->mncc_rtp_create_pending = 0;
+			LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP create ind.\n",
+				gsm_lchan_name(lchan));
+			mncc_recv_rtp_sock(net, trans, MNCC_RTP_CREATE);
+		}
+		/*
+		 * TODO: this appears to be too early? Why not until after
+		 * the handover detect or the handover complete?
+		 */
+		maybe_switch_for_handover(lchan);
+		break;
+	case S_ABISIP_MDCX_ACK:
+		if (lchan->conn->mncc_rtp_connect_pending) {
+			lchan->conn->mncc_rtp_connect_pending = 0;
+			LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP connect ind.\n",
+				gsm_lchan_name(lchan));
+			mncc_recv_rtp_sock(net, trans, MNCC_RTP_CONNECT);
+		}
+		break;
+	}
+
+	return 0;
+}
+
+
+static struct downstate {
+	uint32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */
+	 MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */
+	 MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */
+	 MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+	{SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */
+	 MNCC_PROGRESS_REQ, gsm48_cc_tx_progress},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+	 MNCC_SETUP_REQ, gsm48_cc_tx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_REQUEST),
+	 MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack},
+	 /* signalling during call */
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+	 MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+	{ALL_STATES,
+	 MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack},
+	{ALL_STATES,
+	 MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej},
+	{ALL_STATES,
+	 MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+	/* clearing */
+	{SBIT(GSM_CSTATE_INITIATED),
+	 MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */
+	 MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 MNCC_REL_REQ, gsm48_cc_tx_release},
+	/* special */
+	{ALL_STATES,
+	 MNCC_LCHAN_MODIFY, _gsm48_lchan_modify},
+};
+
+#define DOWNSLLEN \
+	(sizeof(downstatelist) / sizeof(struct downstate))
+
+
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
+{
+	int i, rc = 0;
+	struct gsm_trans *trans = NULL, *transt;
+	struct gsm_subscriber_connection *conn = NULL;
+	struct gsm_bts *bts = NULL;
+	struct gsm_mncc *data = arg, rel;
+
+	DEBUGP(DMNCC, "receive message %s\n", get_mncc_name(msg_type));
+
+	/* handle special messages */
+	switch(msg_type) {
+	case MNCC_BRIDGE:
+		rc = tch_bridge(net, arg);
+		if (rc < 0)
+			disconnect_bridge(net, arg, -rc);
+		return rc;
+	case MNCC_FRAME_DROP:
+		return tch_recv_mncc(net, data->callref, 0);
+	case MNCC_FRAME_RECV:
+		return tch_recv_mncc(net, data->callref, 1);
+	case MNCC_RTP_CREATE:
+		return tch_rtp_create(net, data->callref);
+	case MNCC_RTP_CONNECT:
+		return tch_rtp_connect(net, arg);
+	case MNCC_RTP_FREE:
+		/* unused right now */
+		return -EIO;
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+	case GSM_TCHH_FRAME:
+	case GSM_TCH_FRAME_AMR:
+		/* Find callref */
+		trans = trans_find_by_callref(net, data->callref);
+		if (!trans) {
+			LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n");
+			return -EIO;
+		}
+		log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+		if (!trans->conn) {
+			LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n");
+			return 0;
+		}
+		if (!trans->conn->lchan) {
+			LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without lchan\n");
+			return 0;
+		}
+		if (trans->conn->lchan->type != GSM_LCHAN_TCH_F
+		 && trans->conn->lchan->type != GSM_LCHAN_TCH_H) {
+			/* This should be LOGL_ERROR or NOTICE, but
+			 * unfortuantely it happens for a couple of frames at
+			 * the beginning of every RTP connection */
+			LOGP(DMNCC, LOGL_DEBUG, "TCH frame for lchan != TCH_F/TCH_H\n");
+			return 0;
+		}
+		bts = trans->conn->lchan->ts->trx->bts;
+		switch (bts->type) {
+		case GSM_BTS_TYPE_NANOBTS:
+		case GSM_BTS_TYPE_OSMOBTS:
+			if (!trans->conn->lchan->abis_ip.rtp_socket) {
+				DEBUGP(DMNCC, "TCH frame to lchan without RTP connection\n");
+				return 0;
+			}
+			return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg);
+		case GSM_BTS_TYPE_BS11:
+		case GSM_BTS_TYPE_RBS2000:
+		case GSM_BTS_TYPE_NOKIA_SITE:
+			return trau_send_frame(trans->conn->lchan, arg);
+		default:
+			LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type);
+		}
+		return -EINVAL;
+	}
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = data->callref;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, data->callref);
+
+	/* Callref unknown */
+	if (!trans) {
+		struct gsm_subscriber *subscr;
+
+		if (msg_type != MNCC_SETUP_REQ) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown callref %d\n", data->called.number,
+				get_mncc_name(msg_type), data->callref);
+			/* Invalid call reference */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INVAL_TRANS_ID);
+		}
+		if (!data->called.number[0] && !data->imsi[0]) {
+			DEBUGP(DCC, "(bts - trx - ts - ti) "
+				"Received '%s' from MNCC with "
+				"no number or IMSI\n", get_mncc_name(msg_type));
+			/* Invalid number */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INV_NR_FORMAT);
+		}
+		/* New transaction due to setup, find subscriber */
+		if (data->called.number[0])
+			subscr = subscr_get_by_extension(net->subscr_group,
+							data->called.number);
+		else
+			subscr = subscr_get_by_imsi(net->subscr_group,
+						    data->imsi);
+
+		/* update the subscriber we deal with */
+		log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+
+		/* If subscriber is not found */
+		if (!subscr) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			/* Unknown subscriber */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_UNASSIGNED_NR);
+		}
+		/* If subscriber is not "attached" */
+		if (!subscr->lac) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"detached subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			subscr_put(subscr);
+			/* Temporarily out of order */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_DEST_OOO);
+		}
+		/* Create transaction */
+		trans = trans_alloc(net, subscr, GSM48_PDISC_CC, 0xff, data->callref);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			subscr_put(subscr);
+			/* Ressource unavailable */
+			mncc_release_ind(net, NULL, data->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			return -ENOMEM;
+		}
+		/* Find lchan */
+		conn = connection_for_subscr(subscr);
+
+		/* If subscriber has no lchan */
+		if (!conn) {
+			/* find transaction with this subscriber already paging */
+			llist_for_each_entry(transt, &net->trans_list, entry) {
+				/* Transaction of our lchan? */
+				if (transt == trans ||
+				    transt->subscr != subscr)
+					continue;
+				DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+					"Received '%s' from MNCC with "
+					"unallocated channel, paging already "
+					"started for lac %d.\n",
+					data->called.number,
+					get_mncc_name(msg_type), subscr->lac);
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+			/* store setup informations until paging was successfull */
+			memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+
+			/* Request a channel */
+			trans->paging_request = subscr_request_channel(subscr,
+							RSL_CHANNEED_TCH_F, setup_trig_pag_evt,
+							trans);
+			if (!trans->paging_request) {
+				LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n");
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+			subscr_put(subscr);
+			return 0;
+		}
+		/* Assign lchan */
+		trans->conn = conn;
+		subscr_put(subscr);
+	} else {
+		/* update the subscriber we deal with */
+		log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	}
+
+	if (trans->conn)
+		conn = trans->conn;
+
+	/* if paging did not respond yet */
+	if (!conn) {
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+			"Received '%s' from MNCC in paging state\n",
+			(trans->subscr)?(trans->subscr->extension):"-",
+			get_mncc_name(msg_type));
+		mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_NORM_CALL_CLEAR);
+		if (msg_type == MNCC_REL_REQ)
+			rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+		else
+			rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) "
+		"Received '%s' from MNCC in state %d (%s)\n",
+		conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+		trans->transaction_id,
+		(trans->conn->subscr)?(trans->conn->subscr->extension):"-",
+		get_mncc_name(msg_type), trans->cc.state,
+		gsm48_cc_state_name(trans->cc.state));
+
+	/* Find function for current state and message */
+	for (i = 0; i < DOWNSLLEN; i++)
+		if ((msg_type == downstatelist[i].type)
+		 && ((1 << trans->cc.state) & downstatelist[i].states))
+			break;
+	if (i == DOWNSLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = downstatelist[i].rout(trans, arg);
+
+	return rc;
+}
+
+
+static struct datastate {
+	uint32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */
+	 GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */
+	 GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */
+	 GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */
+	 GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+	 /* signalling during call */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL),
+	 GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+	{ALL_STATES,
+	 GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_HOLD, gsm48_cc_rx_hold},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+	/* clearing */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */
+	 GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+	{ALL_STATES, /* 5.4.3.4 */
+	 GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+};
+
+#define DATASLLEN \
+	(sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t msg_type = gsm48_hdr_msg_type(gh);
+	uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
+	struct gsm_trans *trans = NULL;
+	int i, rc = 0;
+
+	if (msg_type & 0x80) {
+		DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type);
+		return -EINVAL;
+	}
+
+	if (!conn->subscr) {
+		LOGP(DCC, LOGL_ERROR, "Invalid conn, no subscriber\n");
+		return -EINVAL;
+	}
+
+	/* Find transaction */
+	trans = trans_find_by_id(conn, GSM48_PDISC_CC, transaction_id);
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+		"Received '%s' from MS in state %d (%s)\n",
+		conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+		transaction_id, (conn->subscr)?(conn->subscr->extension):"-",
+		gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
+		gsm48_cc_state_name(trans?(trans->cc.state):0));
+
+	/* Create transaction */
+	if (!trans) {
+		DEBUGP(DCC, "Unknown transaction ID %x, "
+			"creating new trans.\n", transaction_id);
+		/* Create transaction */
+		trans = trans_alloc(conn->network, conn->subscr,
+				    GSM48_PDISC_CC,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			rc = gsm48_tx_simple(conn,
+					     GSM48_PDISC_CC | (transaction_id << 4),
+					     GSM48_MT_CC_RELEASE_COMPL);
+			return -ENOMEM;
+		}
+		/* Assign transaction */
+		trans->conn = conn;
+	}
+
+	/* find function for current state and message */
+	for (i = 0; i < DATASLLEN; i++)
+		if ((msg_type == datastatelist[i].type)
+		 && ((1 << trans->cc.state) & datastatelist[i].states))
+			break;
+	if (i == DATASLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	assert(trans->subscr);
+
+	rc = datastatelist[i].rout(trans, msg);
+
+	return rc;
+}
+
+/* Create a dummy to wait five seconds */
+static void release_anchor(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->anch_operation)
+		return;
+
+	osmo_timer_del(&conn->anch_operation->timeout);
+	talloc_free(conn->anch_operation);
+	conn->anch_operation = NULL;
+}
+
+static void anchor_timeout(void *_data)
+{
+	struct gsm_subscriber_connection *con = _data;
+
+	release_anchor(con);
+	msc_release_connection(con);
+}
+
+int gsm0408_new_conn(struct gsm_subscriber_connection *conn)
+{
+	conn->anch_operation = talloc_zero(conn, struct gsm_anchor_operation);
+	if (!conn->anch_operation)
+		return -1;
+
+	osmo_timer_setup(&conn->anch_operation->timeout, anchor_timeout, conn);
+	osmo_timer_schedule(&conn->anch_operation->timeout, 5, 0);
+	return 0;
+}
+
+struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = talloc_zero(network, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	conn->network = network;
+	llist_add_tail(&conn->entry, &network->subscr_conns);
+	return conn;
+}
+
+void msc_subscr_con_free(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+	if (conn->subscr) {
+		subscr_put(conn->subscr);
+		conn->subscr = NULL;
+	}
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+}
+
+/* Main entry point for GSM 04.08/44.008 Layer 3 data (e.g. from the BSC). */
+int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t pdisc = gsm48_hdr_pdisc(gh);
+	int rc = 0;
+
+	OSMO_ASSERT(conn);
+	OSMO_ASSERT(msg);
+
+	LOGP(DRLL, LOGL_DEBUG, "Dispatching 04.08 message, pdisc=%d\n", pdisc);
+#if 0
+	if (silent_call_reroute(conn, msg))
+		return silent_call_rx(conn, msg);
+#endif
+
+	switch (pdisc) {
+	case GSM48_PDISC_CC:
+		release_anchor(conn);
+		rc = gsm0408_rcv_cc(conn, msg);
+		break;
+	case GSM48_PDISC_MM:
+		rc = gsm0408_rcv_mm(conn, msg);
+		break;
+	case GSM48_PDISC_RR:
+		rc = gsm0408_rcv_rr(conn, msg);
+		break;
+	case GSM48_PDISC_SMS:
+		release_anchor(conn);
+		rc = gsm0411_rcv_sms(conn, msg);
+		break;
+	case GSM48_PDISC_MM_GPRS:
+	case GSM48_PDISC_SM_GPRS:
+		LOGP(DRLL, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		rc = -ENOTSUP;
+		break;
+	case GSM48_PDISC_NC_SS:
+		release_anchor(conn);
+		rc = handle_rcv_ussd(conn, msg);
+		break;
+	case GSM48_PDISC_TEST:
+		rc = gsm0414_rcv_test(conn, msg);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "Unknown "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * This will be run by the linker when loading the DSO. We use it to
+ * do system initialization, e.g. registration of signal handlers.
+ */
+static __attribute__((constructor)) void on_dso_load_0408(void)
+{
+	osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, NULL);
+}
diff --git a/openbsc/src/libmsc/gsm_04_11.c b/openbsc/src/libmsc/gsm_04_11.c
new file mode 100644
index 0000000..25ef487
--- /dev/null
+++ b/openbsc/src/libmsc/gsm_04_11.c
@@ -0,0 +1,1189 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include "bscconfig.h"
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsm0411_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+#ifdef BUILD_SMPP
+#include "smpp_smsc.h"
+#endif
+
+void *tall_gsms_ctx;
+static uint32_t new_callref = 0x40000001;
+
+
+struct gsm_sms *sms_alloc(void)
+{
+	return talloc_zero(tall_gsms_ctx, struct gsm_sms);
+}
+
+void sms_free(struct gsm_sms *sms)
+{
+	/* drop references to subscriber structure */
+	if (sms->receiver)
+		subscr_put(sms->receiver);
+#ifdef BUILD_SMPP
+	if (sms->smpp.esme)
+		smpp_esme_put(sms->smpp.esme);
+#endif
+
+	talloc_free(sms);
+}
+
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver,
+                              struct gsm_subscriber *sender,
+                              int dcs, const char *text)
+{
+	struct gsm_sms *sms = sms_alloc();
+
+	if (!sms)
+		return NULL;
+
+	sms->receiver = subscr_get(receiver);
+	osmo_strlcpy(sms->text, text, sizeof(sms->text));
+
+	osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr));
+	sms->reply_path_req = 0;
+	sms->status_rep_req = 0;
+	sms->ud_hdr_ind = 0;
+	sms->protocol_id = 0; /* implicit */
+	sms->data_coding_scheme = dcs;
+	osmo_strlcpy(sms->dst.addr, receiver->extension, sizeof(sms->dst.addr));
+	/* Generate user_data */
+	sms->user_data_len = gsm_7bit_encode_n(sms->user_data, sizeof(sms->user_data),
+						sms->text, NULL);
+
+	return sms;
+}
+
+
+static void send_signal(int sig_no,
+			struct gsm_trans *trans,
+			struct gsm_sms *sms,
+			int paging_result)
+{
+	struct sms_signal_data sig;
+	sig.trans = trans;
+	sig.sms = sms;
+	sig.paging_result = paging_result;
+	osmo_signal_dispatch(SS_SMS, sig_no, &sig);
+}
+
+static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	DEBUGP(DLSMS, "GSM4.11 TX %s\n", osmo_hexdump(msg->data, msg->len));
+	msg->l3h = msg->data;
+	return gsm0808_submit_dtap(conn, msg, UM_SAPI_SMS, 1);
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     uint8_t msg_type)
+{
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	/* Outgoing needs the highest bit set */
+	gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
+	gh->msg_type = msg_type;
+
+	DEBUGP(DLSMS, "sending CP message (trans=%x)\n", trans->transaction_id);
+
+	return gsm411_sendmsg(trans->conn, msg);
+}
+
+/* mm_send: receive MMCCSMS sap message from SMC */
+static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg, int cp_msg_type)
+{
+	struct gsm_trans *trans =
+		container_of(inst, struct gsm_trans, sms.smc_inst);
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MMSMS_EST_REQ:
+		/* recycle msg */
+		rc = gsm411_smc_recv(inst, GSM411_MMSMS_EST_CNF, msg, 0);
+		msgb_free(msg); /* upper layer does not free msg */
+		break;
+	case GSM411_MMSMS_DATA_REQ:
+		rc = gsm411_cp_sendmsg(msg, trans, cp_msg_type);
+		break;
+	case GSM411_MMSMS_REL_REQ:
+		DEBUGP(DLSMS, "Got MMSMS_REL_REQ, destroying transaction.\n");
+		msgb_free(msg);
+		trans_free(trans);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Unhandled MMCCSMS msg 0x%x\n", msg_type);
+		msgb_free(msg);
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+/* mm_send: receive MNCCSMS sap message from SMR */
+int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg)
+{
+	struct gsm_trans *trans =
+		container_of(inst, struct gsm_trans, sms.smr_inst);
+
+	/* forward to SMC */
+	return gsm411_smc_send(&trans->sms.smc_inst, msg_type, msg);
+}
+
+static int gsm340_rx_sms_submit(struct gsm_sms *gsms)
+{
+	if (db_sms_store(gsms) != 0) {
+		LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	/* dispatch a signal to tell higher level about it */
+	send_signal(S_SMS_SUBMITTED, NULL, gsms, 0);
+
+	return 0;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+static int gsm340_gen_oa_sub(uint8_t *oa, unsigned int oa_len,
+			 const struct gsm_sms_addr *src)
+{
+	/* network specific, private numbering plan */
+	return gsm340_gen_oa(oa, oa_len, src->ton, src->npi, src->addr);
+}
+
+/* generate a msgb containing an 03.40 9.2.2.1 SMS-DELIVER TPDU derived from
+ * struct gsm_sms, returns total size of TPDU */
+static int gsm340_gen_sms_deliver_tpdu(struct msgb *msg, struct gsm_sms *sms)
+{
+	uint8_t *smsp;
+	uint8_t oa[12];	/* max len per 03.40 */
+	uint8_t octet_len;
+	unsigned int old_msg_len = msg->len;
+	int oa_len;
+
+	/* generate first octet with masked bits */
+	smsp = msgb_put(msg, 1);
+	/* TP-MTI (message type indicator) */
+	*smsp = GSM340_SMS_DELIVER_SC2MS;
+	/* TP-MMS (more messages to send) */
+	if (0 /* FIXME */)
+		*smsp |= 0x04;
+	/* TP-SRI(deliver)/SRR(submit) */
+	if (sms->status_rep_req)
+		*smsp |= 0x20;
+	/* TP-UDHI (indicating TP-UD contains a header) */
+	if (sms->ud_hdr_ind)
+		*smsp |= 0x40;
+
+	/* generate originator address */
+	oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), &sms->src);
+	if (oa_len < 0)
+		return -ENOSPC;
+
+	smsp = msgb_put(msg, oa_len);
+	memcpy(smsp, oa, oa_len);
+
+	/* generate TP-PID */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->protocol_id;
+
+	/* generate TP-DCS */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->data_coding_scheme;
+
+	/* generate TP-SCTS */
+	smsp = msgb_put(msg, 7);
+	gsm340_gen_scts(smsp, time(NULL));
+
+	/* generate TP-UDL */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->user_data_len;
+
+	/* generate TP-UD */
+	switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) {
+	case DCS_7BIT_DEFAULT:
+		octet_len = sms->user_data_len*7/8;
+		if (sms->user_data_len*7%8 != 0)
+			octet_len++;
+		/* Warning, user_data_len indicates the amount of septets
+		 * (characters), we need amount of octets occupied */
+		smsp = msgb_put(msg, octet_len);
+		memcpy(smsp, sms->user_data, octet_len);
+		break;
+	case DCS_UCS2:
+	case DCS_8BIT_DATA:
+		smsp = msgb_put(msg, sms->user_data_len);
+		memcpy(smsp, sms->user_data, sms->user_data_len);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n",
+		     sms->data_coding_scheme);
+		break;
+	}
+
+	return msg->len - old_msg_len;
+}
+
+/* As defined by GSM 03.40, Section 9.2.2.3. */
+static int gsm340_gen_sms_status_report_tpdu(struct msgb *msg,
+					     struct gsm_sms *sms)
+{
+	unsigned int old_msg_len = msg->len;
+	uint8_t oa[12];	/* max len per 03.40 */
+	uint8_t *smsp;
+	int oa_len;
+
+	/* generate first octet with masked bits */
+	smsp = msgb_put(msg, 1);
+	/* TP-MTI (message type indicator) */
+	*smsp = GSM340_SMS_STATUS_REP_SC2MS;
+	/* TP-MMS (more messages to send) */
+	if (0 /* FIXME */)
+		*smsp |= 0x04;
+	/* TP-MR (message reference) */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->msg_ref;
+
+	/* generate recipient address */
+	oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), &sms->src);
+	if (oa_len < 0)
+		return -ENOSPC;
+
+	smsp = msgb_put(msg, oa_len);
+	memcpy(smsp, oa, oa_len);
+
+	/* generate TP-SCTS (Service centre timestamp) */
+	smsp = msgb_put(msg, 7);
+	gsm340_gen_scts(smsp, sms->created);
+
+	/* generate TP-DT (Discharge time, in TP-SCTS format). */
+	smsp = msgb_put(msg, 7);
+	gsm340_gen_scts(smsp, sms->created);
+
+	/* TP-ST (status) */
+	smsp = msgb_put(msg, 1);
+	/* From GSM 03.40, Section 9.2.3.15, 0x00 means OK. */
+	*smsp = 0x00;
+
+	LOGP(DLSMS, LOGL_INFO, "sending status report for SMS reference %x\n",
+	     sms->msg_ref);
+
+	return msg->len - old_msg_len;
+}
+
+static int sms_route_mt_sms(struct gsm_subscriber_connection *conn,
+			    struct gsm_sms *gsms)
+{
+	int rc;
+
+#ifdef BUILD_SMPP
+	int smpp_first = smpp_route_smpp_first(gsms, conn);
+
+	/*
+	 * Route through SMPP first before going to the local database. In case
+	 * of a unroutable message and no local subscriber, SMPP will be tried
+	 * twice. In case of an unknown subscriber continue with the normal
+	 * delivery of the SMS.
+	 */
+	if (smpp_first) {
+		rc = smpp_try_deliver(gsms, conn);
+		if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED)
+			/* unknown subscriber, try local */
+			goto try_local;
+		if (rc < 0) {
+	 		LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.",
+			     subscr_name(conn->subscr), rc);
+	 		rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
+			/* rc will be logged by gsm411_send_rp_error() */
+	 		rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[
+					MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
+		}
+		return rc;
+	}
+
+try_local:
+#endif
+
+	/* determine gsms->receiver based on dialled number */
+	gsms->receiver = subscr_get_by_extension(conn->network->subscr_group,
+						 gsms->dst.addr);
+	if (gsms->receiver)
+		return 0;
+
+#ifdef BUILD_SMPP
+	/* Avoid a second look-up */
+	if (smpp_first) {
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+		return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+	}
+
+	rc = smpp_try_deliver(gsms, conn);
+	if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) {
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+	} else if (rc < 0) {
+		LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.",
+		     subscr_name(conn->subscr), rc);
+		rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
+		/* rc will be logged by gsm411_send_rp_error() */
+		rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[
+				MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
+	}
+#else
+	rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+#endif
+
+	return rc;
+}
+
+
+/* process an incoming TPDU (called from RP-DATA)
+ * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */
+static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
+			  uint32_t gsm411_msg_ref)
+{
+	struct gsm_subscriber_connection *conn = trans->conn;
+	uint8_t *smsp = msgb_sms(msg);
+	struct gsm_sms *gsms;
+	unsigned int sms_alphabet;
+	uint8_t sms_mti, sms_vpf;
+	uint8_t *sms_vp;
+	uint8_t da_len_bytes;
+	uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
+	int rc = 0;
+
+	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]);
+
+	gsms = sms_alloc();
+	if (!gsms)
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+	/* invert those fields where 0 means active/present */
+	sms_mti = *smsp & 0x03;
+	sms_vpf = (*smsp & 0x18) >> 3;
+	gsms->status_rep_req = (*smsp & 0x20) >> 5;
+	gsms->ud_hdr_ind = (*smsp & 0x40);
+	/*
+	 * Not evaluating MMS (More Messages to Send) because the
+	 * lchan stays open anyway.
+	 * Not evaluating RP (Reply Path) because we're not aware of its
+	 * benefits.
+	 */
+
+	smsp++;
+	gsms->msg_ref = *smsp++;
+
+	gsms->gsm411.transaction_id = trans->transaction_id;
+	gsms->gsm411.msg_ref = gsm411_msg_ref;
+
+	/* length in bytes of the destination address */
+	da_len_bytes = 2 + *smsp/2 + *smsp%2;
+	if (da_len_bytes > 12) {
+		LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
+		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+		goto out;
+	} else if (da_len_bytes < 4) {
+		LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n");
+		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+		goto out;
+	}
+	memset(address_lv, 0, sizeof(address_lv));
+	memcpy(address_lv, smsp, da_len_bytes);
+	/* mangle first byte to reflect length in bytes, not digits */
+	address_lv[0] = da_len_bytes - 1;
+
+	gsms->dst.ton = (address_lv[1] >> 4) & 7;
+	gsms->dst.npi = address_lv[1] & 0xF;
+	/* convert to real number */
+	gsm48_decode_bcd_number(gsms->dst.addr,
+				sizeof(gsms->dst.addr), address_lv, 1);
+	smsp += da_len_bytes;
+
+	gsms->protocol_id = *smsp++;
+	gsms->data_coding_scheme = *smsp++;
+
+	sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme);
+	if (sms_alphabet == 0xffffffff) {
+		rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+		goto out;
+	}
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		sms_vp = smsp++;
+		break;
+	case GSM340_TP_VPF_ABSOLUTE:
+	case GSM340_TP_VPF_ENHANCED:
+		sms_vp = smsp;
+		/* the additional functionality indicator... */
+		if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
+			smsp++;
+		smsp += 7;
+		break;
+	case GSM340_TP_VPF_NONE:
+		sms_vp = 0;
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE,
+		     "SMS Validity period not implemented: 0x%02x\n", sms_vpf);
+		rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+		goto out;
+	}
+	gsms->user_data_len = *smsp++;
+	if (gsms->user_data_len) {
+		memcpy(gsms->user_data, smsp, gsms->user_data_len);
+
+		switch (sms_alphabet) {
+		case DCS_7BIT_DEFAULT:
+			gsm_7bit_decode_n(gsms->text, sizeof(gsms->text), smsp,
+					  gsms->user_data_len);
+			break;
+		case DCS_8BIT_DATA:
+		case DCS_UCS2:
+		case DCS_NONE:
+			break;
+		}
+	}
+
+	osmo_strlcpy(gsms->src.addr, conn->subscr->extension,
+		     sizeof(gsms->src.addr));
+
+	LOGP(DLSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, "
+	     "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
+	     "UserDataLength: 0x%02x, UserData: \"%s\"\n",
+	     subscr_name(conn->subscr), sms_mti, sms_vpf, gsms->msg_ref,
+	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dst.addr,
+	     gsms->user_data_len,
+			sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
+				osmo_hexdump(gsms->user_data, gsms->user_data_len));
+
+	gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
+
+	/* FIXME: This looks very wrong */
+	send_signal(0, NULL, gsms, 0);
+
+	rc = sms_route_mt_sms(conn, gsms);
+
+	/* This SMS got routed through SMPP. */
+	if (gsms->smpp.esme)
+		return -EINPROGRESS;
+
+	if (!gsms->receiver)
+		return rc;
+
+	switch (sms_mti) {
+	case GSM340_SMS_SUBMIT_MS2SC:
+		/* MS is submitting a SMS */
+		rc = gsm340_rx_sms_submit(gsms);
+		break;
+	case GSM340_SMS_COMMAND_MS2SC:
+	case GSM340_SMS_DELIVER_REP_MS2SC:
+		LOGP(DLSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	}
+out:
+	sms_free(gsms);
+
+	return rc;
+}
+
+/* Prefix msg with a RP-DATA header and send as SMR DATA */
+static int gsm411_rp_sendmsg(struct gsm411_smr_inst *inst, struct msgb *msg,
+			     uint8_t rp_msg_type, uint8_t rp_msg_ref,
+			     int rl_msg_type)
+{
+	struct gsm411_rp_hdr *rp;
+	uint8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref;
+
+	return gsm411_smr_send(inst, rl_msg_type, msg);
+}
+
+int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	DEBUGP(DLSMS, "TX: SMS RP ACK\n");
+
+	return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, GSM411_MT_RP_ACK_MT,
+		msg_ref, GSM411_SM_RL_REPORT_REQ);
+}
+
+int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref,
+			 uint8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	msgb_tv_put(msg, 1, cause);
+
+	LOGP(DLSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause,
+		get_value_string(gsm411_rp_cause_strs, cause));
+
+	return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg,
+		GSM411_MT_RP_ERROR_MT, msg_ref, GSM411_SM_RL_REPORT_REQ);
+}
+
+/* Receive a 04.11 TPDU inside RP-DATA / user data */
+static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans,
+			  struct gsm411_rp_hdr *rph,
+			  uint8_t src_len, uint8_t *src,
+			  uint8_t dst_len, uint8_t *dst,
+			  uint8_t tpdu_len, uint8_t *tpdu)
+{
+	int rc = 0;
+
+	if (src_len && src)
+		LOGP(DLSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n");
+
+	if (!dst_len || !dst || !tpdu_len || !tpdu) {
+		LOGP(DLSMS, LOGL_ERROR,
+			"RP-DATA (MO) without DST or TPDU ?!?\n");
+		gsm411_send_rp_error(trans, rph->msg_ref,
+				     GSM411_RP_CAUSE_INV_MAND_INF);
+		return -EIO;
+	}
+	msg->l4h = tpdu;
+
+	DEBUGP(DLSMS, "DST(%u,%s)\n", dst_len, osmo_hexdump(dst, dst_len));
+
+	rc = gsm340_rx_tpdu(trans, msg, rph->msg_ref);
+	if (rc == 0)
+		return gsm411_send_rp_ack(trans, rph->msg_ref);
+	else if (rc > 0)
+		return gsm411_send_rp_error(trans, rph->msg_ref, rc);
+	else if (rc == -EINPROGRESS)
+		rc = 0;
+
+	return rc;
+}
+
+/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */
+static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	uint8_t src_len, dst_len, rpud_len;
+	uint8_t *src = NULL, *dst = NULL , *rp_ud = NULL;
+
+	/* in the MO case, this should always be zero length */
+	src_len = rph->data[0];
+	if (src_len)
+		src = &rph->data[1];
+
+	dst_len = rph->data[1+src_len];
+	if (dst_len)
+		dst = &rph->data[1+src_len+1];
+
+	rpud_len = rph->data[1+src_len+1+dst_len];
+	if (rpud_len)
+		rp_ud = &rph->data[1+src_len+1+dst_len+1];
+
+	DEBUGP(DLSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n",
+		src_len, dst_len, rpud_len);
+	return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst,
+				rpud_len, rp_ud);
+}
+
+static struct gsm_sms *sms_report_alloc(struct gsm_sms *sms)
+{
+	struct gsm_sms *sms_report;
+	int len;
+
+	sms_report = sms_alloc();
+	OSMO_ASSERT(sms_report);
+
+	sms_report->msg_ref = sms->msg_ref;
+	sms_report->protocol_id = sms->protocol_id;
+	sms_report->data_coding_scheme = GSM338_DCS_1111_8BIT_DATA;
+
+	/* Invert address to send status report back to origin. */
+	sms_report->src = sms->dst;
+	sms_report->dst = sms->src;
+
+	/* As specified by Appendix B. Delivery Receipt Format.
+	 * TODO: Many fields in this string are just set with dummy values,
+	 * 	 revisit this.
+	 */
+	len = snprintf((char *)sms_report->user_data,
+		       sizeof(sms_report->user_data),
+		       "id:%.08llu sub:000 dlvrd:000 submit date:YYMMDDhhmm done date:YYMMDDhhmm stat:DELIVRD err:000 text:%.20s",
+		       sms->id, sms->text);
+	sms_report->user_data_len = len;
+	LOGP(DLSMS, LOGL_NOTICE, "%s\n", sms_report->user_data);
+
+	/* This represents a sms report. */
+	sms_report->is_report = true;
+
+	return sms_report;
+}
+
+static void sms_status_report(struct gsm_sms *gsms,
+			      struct gsm_subscriber_connection *conn)
+{
+	struct gsm_sms *sms_report;
+	int rc;
+
+	sms_report = sms_report_alloc(gsms);
+
+	rc = sms_route_mt_sms(conn, sms_report);
+	if (rc < 0) {
+		LOGP(DLSMS, LOGL_ERROR,
+		     "Failed to send status report! err=%d\n", rc);
+	}
+
+	/* No route via SMPP, send the GSM 03.40 status-report now. */
+	if (gsms->receiver)
+		gsm340_rx_sms_submit(sms_report);
+
+	LOGP(DLSMS, LOGL_NOTICE, "Status report has been sent\n");
+
+	sms_free(sms_report);
+}
+
+/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */
+static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans,
+			    struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms = trans->sms.sms;
+
+	/* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it
+	 * successfully received a SMS.  We can now safely mark it as
+	 * transmitted */
+
+	if (!sms) {
+		LOGP(DLSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+	}
+
+	/* mark this SMS as sent in database */
+	db_sms_mark_delivered(sms);
+
+	send_signal(S_SMS_DELIVERED, trans, sms, 0);
+
+	if (sms->status_rep_req)
+		sms_status_report(sms, trans->conn);
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	return 0;
+}
+
+static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans,
+			      struct gsm411_rp_hdr *rph)
+{
+	struct gsm_network *net = trans->conn->network;
+	struct gsm_sms *sms = trans->sms.sms;
+	uint8_t cause_len = rph->data[0];
+	uint8_t cause = rph->data[1];
+
+	/* Error in response to MT RP_DATA, i.e. the MS did not
+	 * successfully receive the SMS.  We need to investigate
+	 * the cause and take action depending on it */
+
+	LOGP(DLSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
+	     subscr_name(trans->conn->subscr), cause_len, cause,
+	     get_value_string(gsm411_rp_cause_strs, cause));
+
+	if (!sms) {
+		LOGP(DLSMS, LOGL_ERROR,
+			"RX RP-ERR, but no sms in transaction?!?\n");
+		return -EINVAL;
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+#endif
+	}
+
+	if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) {
+		/* MS has not enough memory to store the message.  We need
+		 * to store this in our database and wait for a SMMA message */
+		/* FIXME */
+		send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0);
+		rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM]);
+	} else {
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER]);
+	}
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	return 0;
+}
+
+static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	int rc;
+
+	rc = gsm411_send_rp_ack(trans, rph->msg_ref);
+
+	/* MS tells us that it has memory for more SMS, we need
+	 * to check if we have any pending messages for it and then
+	 * transfer those */
+	send_signal(S_SMS_SMMA, trans, NULL, 0);
+
+	return rc;
+}
+
+/* receive RL DATA */
+static int gsm411_rx_rl_data(struct msgb *msg, struct gsm48_hdr *gh,
+			     struct gsm_trans *trans)
+{
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	uint8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MT_RP_DATA_MO:
+		DEBUGP(DLSMS, "RX SMS RP-DATA (MO)\n");
+		rc = gsm411_rx_rp_data(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_SMMA_MO:
+		DEBUGP(DLSMS, "RX SMS RP-SMMA\n");
+		rc = gsm411_rx_rp_smma(msg, trans, rp_data);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/* receive RL REPORT */
+static int gsm411_rx_rl_report(struct msgb *msg, struct gsm48_hdr *gh,
+			     struct gsm_trans *trans)
+{
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	uint8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MT_RP_ACK_MO:
+		DEBUGP(DLSMS, "RX SMS RP-ACK (MO)\n");
+		rc = gsm411_rx_rp_ack(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ERROR_MO:
+		DEBUGP(DLSMS, "RX SMS RP-ERROR (MO)\n");
+		rc = gsm411_rx_rp_error(msg, trans, rp_data);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/* receive SM-RL sap message from SMR
+ * NOTE: Message is freed by sender
+ */
+int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type,
+                        struct msgb *msg)
+{
+	struct gsm_trans *trans =
+		container_of(inst, struct gsm_trans, sms.smr_inst);
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_SM_RL_DATA_IND:
+		rc = gsm411_rx_rl_data(msg, gh, trans);
+		break;
+	case GSM411_SM_RL_REPORT_IND:
+		if (gh)
+			rc = gsm411_rx_rl_report(msg, gh, trans);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Unhandled SM-RL message 0x%x\n", msg_type);
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+/* receive MNCCSMS sap message from SMC
+ * NOTE: Message is freed by sender
+ */
+static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg)
+{
+	struct gsm_trans *trans =
+		container_of(inst, struct gsm_trans, sms.smc_inst);
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MNSMS_EST_IND:
+	case GSM411_MNSMS_DATA_IND:
+		DEBUGP(DLSMS, "MNSMS-DATA/EST-IND\n");
+		rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg);
+		break;
+	case GSM411_MNSMS_ERROR_IND:
+		if (gh)
+			DEBUGP(DLSMS, "MNSMS-ERROR-IND, cause %d (%s)\n",
+				gh->data[0],
+				get_value_string(gsm411_cp_cause_strs,
+				gh->data[0]));
+		else
+			DEBUGP(DLSMS, "MNSMS-ERROR-IND, no cause\n");
+		rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Unhandled MNCCSMS msg 0x%x\n", msg_type);
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
+int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn,
+		    struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t msg_type = gh->msg_type;
+	uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
+	struct gsm_trans *trans;
+	int new_trans = 0;
+	int rc = 0;
+
+	if (!conn->subscr)
+		return -EIO;
+		/* FIXME: send some error message */
+
+	DEBUGP(DLSMS, "receiving data (trans_id=%x)\n", transaction_id);
+	trans = trans_find_by_id(conn, GSM48_PDISC_SMS, transaction_id);
+
+	/*
+	 * A transaction we created but don't know about?
+	 */
+	if (!trans && (transaction_id & 0x8) == 0) {
+		LOGP(DLSMS, LOGL_ERROR, "trans_id=%x allocated by us but known "
+			"to us anymore. We are ignoring it, maybe a CP-ERROR "
+			"from a MS?\n",
+			transaction_id);
+		return -EINVAL;
+	}
+
+	if (!trans) {
+		DEBUGP(DLSMS, " -> (new transaction)\n");
+		trans = trans_alloc(conn->network, conn->subscr,
+				    GSM48_PDISC_SMS,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGP(DLSMS, " -> No memory for trans\n");
+			/* FIXME: send some error message */
+			return -ENOMEM;
+		}
+		gsm411_smc_init(&trans->sms.smc_inst, 0, 1,
+			gsm411_mn_recv, gsm411_mm_send);
+		gsm411_smr_init(&trans->sms.smr_inst, 0, 1,
+			gsm411_rl_recv, gsm411_mn_send);
+
+		trans->conn = conn;
+
+		new_trans = 1;
+	}
+
+	/* 5.4: For MO, if a CP-DATA is received for a new
+	 * transaction, equals reception of an implicit
+	 * last CP-ACK for previous transaction */
+	if (trans->sms.smc_inst.cp_state == GSM411_CPS_IDLE
+	 && msg_type == GSM411_MT_CP_DATA) {
+		int i;
+		struct gsm_trans *ptrans;
+
+		/* Scan through all remote initiated transactions */
+		for (i=8; i<15; i++) {
+			if (i == transaction_id)
+				continue;
+
+			ptrans = trans_find_by_id(conn, GSM48_PDISC_SMS, i);
+			if (!ptrans)
+				continue;
+
+			DEBUGP(DLSMS, "Implicit CP-ACK for trans_id=%x\n", i);
+
+			/* Finish it for good */
+			trans_free(ptrans);
+		}
+	}
+
+	gsm411_smc_recv(&trans->sms.smc_inst,
+		(new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND,
+		msg, msg_type);
+
+	return rc;
+}
+
+/* Take a SMS in gsm_sms structure and send it through an already
+ * existing lchan. We also assume that the caller ensured this lchan already
+ * has a SAPI3 RLL connection! */
+int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	struct gsm_trans *trans;
+	uint8_t *data, *rp_ud_len;
+	uint8_t msg_ref = sms_next_rp_msg_ref(&conn->next_rp_ref);
+	int transaction_id;
+	int rc;
+
+	transaction_id =
+		trans_assign_trans_id(conn->network, conn->subscr,
+				      GSM48_PDISC_SMS, 0);
+	if (transaction_id == -1) {
+		LOGP(DLSMS, LOGL_ERROR, "No available transaction ids\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+		sms_free(sms);
+		msgb_free(msg);
+		return -EBUSY;
+	}
+
+	DEBUGP(DLSMS, "%s()\n", __func__);
+
+	/* FIXME: allocate transaction with message reference */
+	trans = trans_alloc(conn->network, conn->subscr,
+			    GSM48_PDISC_SMS,
+			    transaction_id, new_callref++);
+	if (!trans) {
+		LOGP(DLSMS, LOGL_ERROR, "No memory for trans\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+		sms_free(sms);
+		msgb_free(msg);
+		/* FIXME: send some error message */
+		return -ENOMEM;
+	}
+	gsm411_smc_init(&trans->sms.smc_inst, sms->id, 1,
+		gsm411_mn_recv, gsm411_mm_send);
+	gsm411_smr_init(&trans->sms.smr_inst, sms->id, 1,
+		gsm411_rl_recv, gsm411_mn_send);
+	trans->sms.sms = sms;
+
+	trans->conn = conn;
+
+	/* Hardcode SMSC Originating Address for now */
+	data = (uint8_t *)msgb_put(msg, 8);
+	data[0] = 0x07;	/* originator length == 7 */
+	data[1] = 0x91; /* type of number: international, ISDN */
+	data[2] = 0x44; /* 447785016005 */
+	data[3] = 0x77;
+	data[4] = 0x58;
+	data[5] = 0x10;
+	data[6] = 0x06;
+	data[7] = 0x50;
+
+	/* Hardcoded Destination Address */
+	data = (uint8_t *)msgb_put(msg, 1);
+	data[0] = 0;	/* destination length == 0 */
+
+	/* obtain a pointer for the rp_ud_len, so we can fill it later */
+	rp_ud_len = (uint8_t *)msgb_put(msg, 1);
+
+	if (sms->is_report) {
+		/* generate the 03.40 SMS-STATUS-REPORT TPDU */
+		rc = gsm340_gen_sms_status_report_tpdu(msg, sms);
+	} else {
+		/* generate the 03.40 SMS-DELIVER TPDU */
+		rc = gsm340_gen_sms_deliver_tpdu(msg, sms);
+	}
+	if (rc < 0) {
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		sms_free(sms);
+		trans->sms.sms = NULL;
+		trans_free(trans);
+		msgb_free(msg);
+		return rc;
+	}
+
+	*rp_ud_len = rc;
+
+	DEBUGP(DLSMS, "TX: SMS DELIVER\n");
+
+	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED]);
+	db_sms_inc_deliver_attempts(trans->sms.sms);
+
+	return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg,
+		GSM411_MT_RP_DATA_MT, msg_ref, GSM411_SM_RL_DATA_REQ);
+}
+
+/* paging callback. Here we get called if paging a subscriber has
+ * succeeded or failed. */
+static int paging_cb_send_sms(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_conn, void *_sms)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct gsm_sms *sms = _sms;
+	int rc = 0;
+
+	DEBUGP(DLSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p,"
+		"conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id);
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		gsm411_send_sms(conn, sms);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_OOM:
+	case GSM_PAGING_BUSY:
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event);
+		sms_free(sms);
+		rc = -ETIMEDOUT;
+		break;
+	default:
+		LOGP(DLSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event);
+	}
+
+	return rc;
+}
+
+/* high-level function to send a SMS to a given subscriber. The function
+ * will take care of paging the subscriber, establishing the RLL SAPI3
+ * connection, etc. */
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms)
+{
+	struct gsm_subscriber_connection *conn;
+	void *res;
+
+	/* check if we already have an open lchan to the subscriber.
+	 * if yes, send the SMS this way */
+	conn = connection_for_subscr(subscr);
+	if (conn) {
+		LOGP(DLSMS, LOGL_DEBUG, "Sending SMS via already open connection %p to %s\n",
+		     conn, subscr_name(subscr));
+		return gsm411_send_sms(conn, sms);
+	}
+
+	/* if not, we have to start paging */
+	LOGP(DLSMS, LOGL_DEBUG, "Sending SMS: no connection open, start paging %s\n",
+	     subscr_name(subscr));
+	res = subscr_request_channel(subscr, RSL_CHANNEED_SDCCH,
+					paging_cb_send_sms, sms);
+	if (!res) {
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, GSM_PAGING_BUSY);
+		sms_free(sms);
+	}
+	return 0;
+}
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans)
+{
+	/* cleanup SMS instance */
+	gsm411_smr_clear(&trans->sms.smr_inst);
+	trans->sms.smr_inst.rl_recv = NULL;
+	trans->sms.smr_inst.mn_send = NULL;
+
+	gsm411_smc_clear(&trans->sms.smc_inst);
+	trans->sms.smc_inst.mn_recv = NULL;
+	trans->sms.smc_inst.mm_send = NULL;
+
+	if (trans->sms.sms) {
+		LOGP(DLSMS, LOGL_ERROR, "Transaction contains SMS.\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0);
+		sms_free(trans->sms.sms);
+		trans->sms.sms = NULL;
+	}
+}
+
+void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_network *net;
+	struct gsm_trans *trans, *tmp;
+
+	net = conn->network;
+
+	llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry) {
+		struct gsm_sms *sms;
+
+		if (trans->conn != conn)
+			continue;
+		if (trans->protocol != GSM48_PDISC_SMS)
+			continue;
+
+		sms = trans->sms.sms;
+		if (!sms) {
+			LOGP(DLSMS, LOGL_ERROR, "SAPI Reject but no SMS.\n");
+			continue;
+		}
+
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		sms_free(sms);
+		trans->sms.sms = NULL;
+		trans_free(trans);
+	}
+}
+
diff --git a/openbsc/src/libmsc/gsm_04_14.c b/openbsc/src/libmsc/gsm_04_14.c
new file mode 100644
index 0000000..23de23e
--- /dev/null
+++ b/openbsc/src/libmsc/gsm_04_14.c
@@ -0,0 +1,132 @@
+/* GSM MS Testing  Layer 3 messages
+ * 3GPP TS 44.014 / GSM TS 04.14 */
+
+/* (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "bscconfig.h"
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/bsc_api.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_14.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+static struct msgb *create_gsm0414_msg(uint8_t msg_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.14");
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_TEST;
+	gh->msg_type = msg_type;
+	return msg;
+}
+
+static int gsm0414_conn_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+static int gsm0414_tx_simple(struct gsm_subscriber_connection *conn, uint8_t msg_type)
+{
+	struct msgb *msg = create_gsm0414_msg(msg_type);
+
+	return gsm0414_conn_sendmsg(conn, msg);
+}
+
+
+/* Send a CLOSE_TCH_LOOOP_CMD according to Section 8.1 */
+int gsm0414_tx_close_tch_loop_cmd(struct gsm_subscriber_connection *conn,
+				  enum gsm414_tch_loop_mode loop_mode)
+{
+	struct msgb *msg = create_gsm0414_msg(GSM414_MT_CLOSE_TCH_LOOP_CMD);
+	uint8_t subch;
+
+	subch = (loop_mode << 1);
+	msgb_put_u8(msg, subch);
+
+	msg->lchan = conn->lchan;
+	return gsm0414_conn_sendmsg(conn, msg);
+}
+
+/* Send a OPEN_LOOP_CMD according to Section 8.3 */
+int gsm0414_tx_open_loop_cmd(struct gsm_subscriber_connection *conn)
+{
+	return gsm0414_tx_simple(conn, GSM414_MT_OPEN_LOOP_CMD);
+}
+
+/* Send a ACT_EMMI_CMD according to Section 8.8 */
+int gsm0414_tx_act_emmi_cmd(struct gsm_subscriber_connection *conn)
+{
+	return gsm0414_tx_simple(conn, GSM414_MT_ACT_EMMI_CMD);
+}
+
+/* Send a DEACT_EMMI_CMD according to Section 8.10 */
+int gsm0414_tx_deact_emmi_cmd(struct gsm_subscriber_connection *conn)
+{
+	return gsm0414_tx_simple(conn, GSM414_MT_DEACT_EMMI_CMD);
+}
+
+/* Send a TEST_INTERFACE according to Section 8.11 */
+int gsm0414_tx_test_interface(struct gsm_subscriber_connection *conn,
+			      uint8_t tested_devs)
+{
+	struct msgb *msg = create_gsm0414_msg(GSM414_MT_TEST_INTERFACE);
+	msgb_put_u8(msg, tested_devs);
+	return gsm0414_conn_sendmsg(conn, msg);
+}
+
+/* Send a RESET_MS_POSITION_STORED according to Section 8.11 */
+int gsm0414_tx_reset_ms_pos_store(struct gsm_subscriber_connection *conn,
+				  uint8_t technology)
+{
+	struct msgb *msg = create_gsm0414_msg(GSM414_MT_RESET_MS_POS_STORED);
+	msgb_put_u8(msg, technology);
+	return gsm0414_conn_sendmsg(conn, msg);
+}
+
+
+
+/* Entry point for incoming GSM48_PDISC_TEST received from MS */
+int gsm0414_rcv_test(struct gsm_subscriber_connection *conn,
+		     struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	if (msgb_l3len(msg) < sizeof(*gh))
+		return -1;
+
+	LOGP(DMM, LOGL_NOTICE, "%s: Received TEST class message '%s'\n", "FIXME",
+		get_value_string(gsm414_msgt_names, gh->msg_type));
+
+	return 0;
+}
diff --git a/openbsc/src/libmsc/gsm_04_80.c b/openbsc/src/libmsc/gsm_04_80.c
new file mode 100644
index 0000000..dc9ae23
--- /dev/null
+++ b/openbsc/src/libmsc/gsm_04_80.c
@@ -0,0 +1,146 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/bsc_api.h>
+
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+
+static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag,
+					    uint8_t value)
+{
+	uint8_t *data = msgb_push(msgb, 3);
+
+	data[0] = tag;
+	data[1] = 1;
+	data[2] = value;
+	return data;
+}
+
+
+/* Send response to a mobile-originated ProcessUnstructuredSS-Request */
+int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
+			       const struct msgb *in_msg, const char *response_text,
+			       const struct ss_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD RSP");
+	struct gsm48_hdr *gh;
+	uint8_t *ptr8;
+	int response_len;
+
+	/* First put the payload text into the message */
+	ptr8 = msgb_put(msg, 0);
+	gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len);
+	msgb_put(msg, response_len);
+
+	/* Then wrap it as an Octet String */
+	msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG);
+
+	/* Pre-pend the DCS octet string */
+	msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
+
+	/* Then wrap these as a Sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the operation code */
+	msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
+			GSM0480_OP_CODE_PROCESS_USS_REQ);
+
+	/* Wrap the operation code and IA5 string as a sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Return Result component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id
+					| (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
+			     const struct msgb *in_msg,
+			     const struct ss_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD REJ");
+	struct gsm48_hdr *gh;
+
+	/* First insert the problem code */
+	msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL,
+			GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
+
+	/* Before it insert the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Reject component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->proto_discr |= req->transaction_id | (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text)
+{
+	struct msgb *msg = gsm0480_create_ussd_notify(level, text);
+	if (!msg)
+		return -1;
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm0480_create_ussd_release_complete();
+	if (!msg)
+		return -1;
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/openbsc/src/libmsc/gsm_subscriber.c b/openbsc/src/libmsc/gsm_subscriber.c
new file mode 100644
index 0000000..1a03cf7
--- /dev/null
+++ b/openbsc/src/libmsc/gsm_subscriber.c
@@ -0,0 +1,422 @@
+/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+#include <stdbool.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <openbsc/chan_alloc.h>
+
+void *tall_sub_req_ctx;
+
+extern struct llist_head *subscr_bsc_active_subscribers(void);
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+                         gsm_cbfn *cb, void *cb_data);
+
+
+/*
+ * Struct for pending channel requests. This is managed in the
+ * llist_head requests of each subscriber. The reference counting
+ * should work in such a way that a subscriber with a pending request
+ * remains in memory.
+ */
+struct subscr_request {
+	struct llist_head entry;
+
+	/* the callback data */
+	gsm_cbfn *cbfn;
+	void *param;
+};
+
+static struct gsm_subscriber *get_subscriber(struct gsm_subscriber_group *sgrp,
+						int type, const char *ident)
+{
+	struct gsm_subscriber *subscr = db_get_subscriber(type, ident);
+	if (subscr)
+		subscr->group = sgrp;
+	return subscr;
+}
+
+/*
+ * We got the channel assigned and can now hand this channel
+ * over to one of our callbacks.
+ */
+static int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
+                                  struct msgb *msg, void *data, void *param)
+{
+	struct subscr_request *request, *tmp;
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm_subscriber *subscr = param;
+	struct paging_signal_data sig_data;
+	struct bsc_subscr *bsub;
+	struct gsm_network *net;
+
+	OSMO_ASSERT(subscr && subscr->is_paging);
+	net = subscr->group->net;
+
+	/*
+	 * Stop paging on all other BTS. E.g. if this is
+	 * the first timeout on a BTS then the others will
+	 * timeout soon as well. Let's just stop everything
+	 * and forget we wanted to page.
+	 */
+
+	/* TODO MSC split -- creating a BSC subscriber directly from MSC data
+	 * structures in RAM. At some point the MSC will send a message to the
+	 * BSC instead. */
+	bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers,
+						 subscr->imsi);
+	bsub->tmsi = subscr->tmsi;
+	bsub->lac = subscr->lac;
+	paging_request_stop(&net->bts_list, NULL, bsub, NULL, NULL);
+	bsc_subscr_put(bsub);
+
+	/* Inform parts of the system we don't know */
+	sig_data.subscr = subscr;
+	sig_data.bts	= conn ? conn->bts : NULL;
+	sig_data.conn	= conn;
+	sig_data.paging_result = event;
+	osmo_signal_dispatch(
+		SS_PAGING,
+		event == GSM_PAGING_SUCCEEDED ?
+			S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
+		&sig_data
+	);
+
+	llist_for_each_entry_safe(request, tmp, &subscr->requests, entry) {
+		llist_del(&request->entry);
+		request->cbfn(hooknum, event, msg, data, request->param);
+		talloc_free(request);
+	}
+
+	/* balanced with the moment we start paging */
+	subscr->is_paging = 0;
+	subscr_put(subscr);
+	return 0;
+}
+
+static int subscr_paging_sec_cb(unsigned int hooknum, unsigned int event,
+                                struct msgb *msg, void *data, void *param)
+{
+	int rc;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			/* Dispatch as paging failure */
+			rc = subscr_paging_dispatch(
+				GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED,
+				msg, data, param);
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_SUCCEEDED:
+			/* Dispatch as paging failure */
+			rc = subscr_paging_dispatch(
+				GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+				msg, data, param);
+			break;
+
+		default:
+			rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static int subscr_paging_cb(unsigned int hooknum, unsigned int event,
+                            struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm48_hdr *gh;
+	struct gsm48_pag_resp *pr;
+
+	/* Other cases mean problem, dispatch direclty */
+	if (event != GSM_PAGING_SUCCEEDED)
+		return subscr_paging_dispatch(hooknum, event, msg, data, param);
+
+	/* Get paging response */
+	gh = msgb_l3(msg);
+	pr = (struct gsm48_pag_resp *)gh->data;
+
+	/* We _really_ have a channel, secure it now ! */
+	return gsm48_secure_channel(conn, pr->key_seq, subscr_paging_sec_cb, param);
+}
+
+struct subscr_request *subscr_request_channel(struct gsm_subscriber *subscr,
+			int channel_type, gsm_cbfn *cbfn, void *param)
+{
+	int rc;
+	struct subscr_request *request;
+	struct bsc_subscr *bsub;
+	struct gsm_network *net = subscr->group->net;
+
+	/* Start paging.. we know it is async so we can do it before */
+	if (!subscr->is_paging) {
+		LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet.\n",
+			subscr_name(subscr));
+		/* TODO MSC split -- creating a BSC subscriber directly from
+		 * MSC data structures in RAM. At some point the MSC will send
+		 * a message to the BSC instead. */
+		bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers,
+							 subscr->imsi);
+		bsub->tmsi = subscr->tmsi;
+		bsub->lac = subscr->lac;
+		rc = paging_request(net, bsub, channel_type, subscr_paging_cb,
+				    subscr);
+		bsc_subscr_put(bsub);
+		if (rc <= 0) {
+			LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n",
+				subscr_name(subscr), rc);
+			return NULL;
+		}
+		/* reduced on the first paging callback */
+		subscr_get(subscr);
+		subscr->is_paging = 1;
+	}
+
+	/* TODO: Stop paging in case of memory allocation failure */
+	request = talloc_zero(subscr, struct subscr_request);
+	if (!request)
+		return NULL;
+
+	request->cbfn = cbfn;
+	request->param = param;
+	llist_add_tail(&request->entry, &subscr->requests);
+	return request;
+}
+
+void subscr_remove_request(struct subscr_request *request)
+{
+	llist_del(&request->entry);
+	talloc_free(request);
+}
+
+struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp,
+						const char *imsi)
+{
+	struct gsm_subscriber *subscr = db_create_subscriber(imsi,
+							     sgrp->net->ext_min,
+							     sgrp->net->ext_max,
+							     sgrp->net->auto_assign_exten);
+	if (subscr)
+		subscr->group = sgrp;
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_subscriber_group *sgrp,
+					  uint32_t tmsi)
+{
+	char tmsi_string[14];
+	struct gsm_subscriber *subscr;
+
+	/* we might have a record in memory already */
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (tmsi == subscr->tmsi)
+			return subscr_get(subscr);
+	}
+
+	sprintf(tmsi_string, "%u", tmsi);
+	return get_subscriber(sgrp, GSM_SUBSCRIBER_TMSI, tmsi_string);
+}
+
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_subscriber_group *sgrp,
+					  const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0)
+			return subscr_get(subscr);
+	}
+
+	return get_subscriber(sgrp, GSM_SUBSCRIBER_IMSI, imsi);
+}
+
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_subscriber_group *sgrp,
+					       const char *ext)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (strcmp(subscr->extension, ext) == 0)
+			return subscr_get(subscr);
+	}
+
+	return get_subscriber(sgrp, GSM_SUBSCRIBER_EXTENSION, ext);
+}
+
+struct gsm_subscriber *subscr_get_by_id(struct gsm_subscriber_group *sgrp,
+					unsigned long long id)
+{
+	struct gsm_subscriber *subscr;
+	char buf[32];
+	sprintf(buf, "%llu", id);
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+		if (subscr->id == id)
+			return subscr_get(subscr);
+	}
+
+	return get_subscriber(sgrp, GSM_SUBSCRIBER_ID, buf);
+}
+
+int subscr_update_expire_lu(struct gsm_subscriber *s, struct gsm_bts *bts)
+{
+	int rc;
+
+	if (!s) {
+		LOGP(DMM, LOGL_ERROR, "LU Expiration but NULL subscriber\n");
+		return -1;
+	}
+	if (!bts) {
+		LOGP(DMM, LOGL_ERROR, "%s: LU Expiration but NULL bts\n",
+		     subscr_name(s));
+		return -1;
+	}
+
+	/* Table 10.5.33: The T3212 timeout value field is coded as the
+	 * binary representation of the timeout value for
+	 * periodic updating in decihours. Mark the subscriber as
+	 * inactive if it missed two consecutive location updates.
+	 * Timeout is twice the t3212 value plus one minute */
+
+	/* Is expiration handling enabled? */
+	if (bts->si_common.chan_desc.t3212 == 0)
+		s->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION;
+	else
+		s->expire_lu = time(NULL) +
+			(bts->si_common.chan_desc.t3212 * 60 * 6 * 2) + 60;
+
+	rc = db_sync_subscriber(s);
+	db_subscriber_update(s);
+	return rc;
+}
+
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason)
+{
+	int rc;
+
+	/* FIXME: Migrate pending requests from one BSC to another */
+	switch (reason) {
+	case GSM_SUBSCRIBER_UPDATE_ATTACHED:
+		s->group = bts->network->subscr_group;
+		/* Indicate "attached to LAC" */
+		s->lac = bts->location_area_code;
+
+		LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n",
+			subscr_name(s), s->lac);
+
+		/*
+		 * The below will set a new expire_lu but as a side-effect
+		 * the new lac will be saved in the database.
+		 */
+		rc = subscr_update_expire_lu(s, bts);
+		osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, s);
+		break;
+	case GSM_SUBSCRIBER_UPDATE_DETACHED:
+		/* Only detach if we are currently in this area */
+		if (bts->location_area_code == s->lac)
+			s->lac = GSM_LAC_RESERVED_DETACHED;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s));
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_DETACHED, s);
+		break;
+	default:
+		fprintf(stderr, "subscr_update with unknown reason: %d\n",
+			reason);
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		break;
+	};
+
+	return rc;
+}
+
+void subscr_update_from_db(struct gsm_subscriber *sub)
+{
+	db_subscriber_update(sub);
+}
+
+static void subscr_expire_callback(void *data, long long unsigned int id)
+{
+	struct gsm_network *net = data;
+	struct gsm_subscriber *s = subscr_get_by_id(net->subscr_group, id);
+	struct gsm_subscriber_connection *conn = connection_for_subscr(s);
+
+	/*
+	 * The subscriber is active and the phone stopped the timer. As
+	 * we don't want to periodically update the database for active
+	 * subscribers we will just do it when the subscriber was selected
+	 * for expiration. This way on the next around another subscriber
+	 * will be selected.
+	 */
+	if (conn && conn->expire_timer_stopped) {
+		LOGP(DMM, LOGL_DEBUG, "Not expiring subscriber %s (ID %llu)\n",
+			subscr_name(s), id);
+		subscr_update_expire_lu(s, conn->bts);
+		subscr_put(s);
+		return;
+	}
+
+
+	LOGP(DMM, LOGL_NOTICE, "Expiring inactive subscriber %s (ID %llu)\n",
+			subscr_name(s), id);
+	s->lac = GSM_LAC_RESERVED_DETACHED;
+	db_sync_subscriber(s);
+
+	subscr_put(s);
+}
+
+void subscr_expire(struct gsm_subscriber_group *sgrp)
+{
+	db_subscriber_expire(sgrp->net, subscr_expire_callback);
+}
+
+struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr)
+{
+	/* FIXME: replace this with a backpointer in gsm_subscriber? */
+	struct gsm_network *net = subscr->group->net;
+	struct gsm_subscriber_connection *conn;
+
+	llist_for_each_entry(conn, &net->subscr_conns, entry) {
+		if (conn->subscr == subscr)
+			return conn;
+	}
+
+	return NULL;
+}
diff --git a/openbsc/src/libmsc/meas_feed.c b/openbsc/src/libmsc/meas_feed.c
new file mode 100644
index 0000000..3ddcdc3
--- /dev/null
+++ b/openbsc/src/libmsc/meas_feed.c
@@ -0,0 +1,167 @@
+/* UDP-Feed of measurement reports */
+
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/meas_feed.h>
+#include <openbsc/vty.h>
+
+#include "meas_feed.h"
+
+struct meas_feed_state {
+	struct osmo_wqueue wqueue;
+	char scenario[31+1];
+	char *dst_host;
+	uint16_t dst_port;
+};
+
+
+static struct meas_feed_state g_mfs;
+
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+	struct msgb *msg;
+	struct meas_feed_meas *mfm;
+	struct gsm_subscriber *subscr;
+
+	/* ignore measurements as long as we don't know who it is */
+	if (!mr->lchan || !mr->lchan->conn || !mr->lchan->conn->subscr)
+		return 0;
+
+	subscr = mr->lchan->conn->subscr;
+
+	msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed");
+	if (!msg)
+		return 0;
+
+	/* fill in the header */
+	mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm));
+	mfm->hdr.msg_type = MEAS_FEED_MEAS;
+	mfm->hdr.version = MEAS_FEED_VERSION;
+
+	/* fill in MEAS_FEED_MEAS specific header */
+	osmo_strlcpy(mfm->imsi, subscr->imsi, sizeof(mfm->imsi));
+	osmo_strlcpy(mfm->name, subscr->name, sizeof(mfm->name));
+	osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario));
+
+	/* copy the entire measurement report */
+	memcpy(&mfm->mr, mr, sizeof(mfm->mr));
+
+	/* copy channel information */
+	/* we assume that the measurement report always belong to some timeslot */
+	mfm->lchan_type = (uint8_t)mr->lchan->type;
+	mfm->pchan_type = (uint8_t)mr->lchan->ts->pchan;
+	mfm->bts_nr = mr->lchan->ts->trx->bts->nr;
+	mfm->trx_nr = mr->lchan->ts->trx->nr;
+	mfm->ts_nr = mr->lchan->ts->nr;
+	mfm->ss_nr = mr->lchan->nr;
+
+	/* and send it to the socket */
+	if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0)
+		msgb_free(msg);
+
+	return 0;
+}
+
+static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct lchan_signal_data *sdata = signal_data;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+	if (signal == S_LCHAN_MEAS_REP)
+		process_meas_rep(sdata->mr);
+
+	return 0;
+}
+
+static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+	return write(ofd->fd, msgb_data(msg), msgb_length(msg));
+}
+
+static int feed_read_cb(struct osmo_fd *ofd)
+{
+	int rc;
+	char buf[256];
+
+	rc = read(ofd->fd, buf, sizeof(buf));
+	ofd->fd &= ~BSC_FD_READ;
+
+	return rc;
+}
+
+int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port)
+{
+	int rc;
+	int already_initialized = 0;
+
+	if (g_mfs.wqueue.bfd.fd)
+		already_initialized = 1;
+
+
+	if (already_initialized &&
+	    !strcmp(dst_host, g_mfs.dst_host) &&
+	    dst_port == g_mfs.dst_port)
+		return 0;
+
+	if (!already_initialized) {
+		osmo_wqueue_init(&g_mfs.wqueue, 10);
+		g_mfs.wqueue.write_cb = feed_write_cb;
+		g_mfs.wqueue.read_cb = feed_read_cb;
+		osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
+	}
+
+	if (already_initialized) {
+		osmo_wqueue_clear(&g_mfs.wqueue);
+		osmo_fd_unregister(&g_mfs.wqueue.bfd);
+		close(g_mfs.wqueue.bfd.fd);
+		/* don't set to zero, as that would mean 'not yet initialized' */
+		g_mfs.wqueue.bfd.fd = -1;
+	}
+	rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM,
+				IPPROTO_UDP, dst_host, dst_port,
+				OSMO_SOCK_F_CONNECT);
+	if (rc < 0)
+		return rc;
+
+	g_mfs.wqueue.bfd.when &= ~BSC_FD_READ;
+
+	if (g_mfs.dst_host)
+		talloc_free(g_mfs.dst_host);
+	g_mfs.dst_host = talloc_strdup(NULL, dst_host);
+	g_mfs.dst_port = dst_port;
+
+	return 0;
+}
+
+void meas_feed_cfg_get(char **host, uint16_t *port)
+{
+	*port = g_mfs.dst_port;
+	*host = g_mfs.dst_host;
+}
+
+void meas_feed_scenario_set(const char *name)
+{
+	osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario));
+}
+
+const char *meas_feed_scenario_get(void)
+{
+	return g_mfs.scenario;
+}
diff --git a/openbsc/src/libmsc/meas_feed.h b/openbsc/src/libmsc/meas_feed.h
new file mode 100644
index 0000000..782a961
--- /dev/null
+++ b/openbsc/src/libmsc/meas_feed.h
@@ -0,0 +1,12 @@
+#ifndef _INT_MEAS_FEED_H
+#define _INT_MEAS_FEED_H
+
+#include <stdint.h>
+
+int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port);
+void meas_feed_cfg_get(char **host, uint16_t *port);
+
+void meas_feed_scenario_set(const char *name);
+const char *meas_feed_scenario_get(void);
+
+#endif  /* _INT_MEAS_FEED_H */
diff --git a/openbsc/src/libmsc/mncc.c b/openbsc/src/libmsc/mncc.c
new file mode 100644
index 0000000..8110ead
--- /dev/null
+++ b/openbsc/src/libmsc/mncc.c
@@ -0,0 +1,107 @@
+/* mncc.c - utility routines for the MNCC API between the 04.08
+ *	    message parsing and the actual Call Control logic */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+
+static const struct value_string mncc_names[] = {
+	{ MNCC_SETUP_REQ, "MNCC_SETUP_REQ" },
+	{ MNCC_SETUP_IND, "MNCC_SETUP_IND" },
+	{ MNCC_SETUP_RSP, "MNCC_SETUP_RSP" },
+	{ MNCC_SETUP_CNF, "MNCC_SETUP_CNF" },
+	{ MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" },
+	{ MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" },
+	{ MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" },
+	{ MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" },
+	{ MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" },
+	{ MNCC_ALERT_REQ, "MNCC_ALERT_REQ" },
+	{ MNCC_ALERT_IND, "MNCC_ALERT_IND" },
+	{ MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" },
+	{ MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" },
+	{ MNCC_DISC_REQ, "MNCC_DISC_REQ" },
+	{ MNCC_DISC_IND, "MNCC_DISC_IND" },
+	{ MNCC_REL_REQ, "MNCC_REL_REQ" },
+	{ MNCC_REL_IND, "MNCC_REL_IND" },
+	{ MNCC_REL_CNF, "MNCC_REL_CNF" },
+	{ MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" },
+	{ MNCC_FACILITY_IND, "MNCC_FACILITY_IND" },
+	{ MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" },
+	{ MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" },
+	{ MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" },
+	{ MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" },
+	{ MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" },
+	{ MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" },
+	{ MNCC_MODIFY_IND, "MNCC_MODIFY_IND" },
+	{ MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" },
+	{ MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" },
+	{ MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" },
+	{ MNCC_HOLD_IND, "MNCC_HOLD_IND" },
+	{ MNCC_HOLD_CNF, "MNCC_HOLD_CNF" },
+	{ MNCC_HOLD_REJ, "MNCC_HOLD_REJ" },
+	{ MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" },
+	{ MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" },
+	{ MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" },
+	{ MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" },
+	{ MNCC_USERINFO_IND, "MNCC_USERINFO_IND" },
+	{ MNCC_REJ_REQ, "MNCC_REJ_REQ" },
+	{ MNCC_REJ_IND, "MNCC_REJ_IND" },
+	{ MNCC_BRIDGE, "MNCC_BRIDGE" },
+	{ MNCC_FRAME_RECV, "MNCC_FRAME_RECV" },
+	{ MNCC_FRAME_DROP, "MNCC_FRAME_DROP" },
+	{ MNCC_LCHAN_MODIFY, "MNCC_LCHAN_MODIFY" },
+	{ MNCC_RTP_CREATE, "MNCC_RTP_CREATE" },
+	{ MNCC_RTP_CONNECT, "MNCC_RTP_CONNECT" },
+	{ MNCC_RTP_FREE, "MNCC_RTP_FREE" },
+	{ GSM_TCHF_FRAME, "GSM_TCHF_FRAME" },
+	{ GSM_TCHF_FRAME_EFR, "GSM_TCHF_FRAME_EFR" },
+	{ GSM_TCHH_FRAME, "GSM_TCHH_FRAME" },
+	{ GSM_TCH_FRAME_AMR, "GSM_TCH_FRAME_AMR" },
+	{ GSM_BAD_FRAME, "GSM_BAD_FRAME" },
+	{ 0, NULL },
+};
+
+const char *get_mncc_name(int value)
+{
+	return get_value_string(mncc_names, value);
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+	data->fields |= MNCC_F_CAUSE;
+	data->cause.location = loc;
+	data->cause.value = val;
+}
+
diff --git a/openbsc/src/libmsc/mncc_builtin.c b/openbsc/src/libmsc/mncc_builtin.c
new file mode 100644
index 0000000..067cc92
--- /dev/null
+++ b/openbsc/src/libmsc/mncc_builtin.c
@@ -0,0 +1,426 @@
+/* mncc_builtin.c - default, minimal built-in MNCC Application for
+ *		    standalone bsc_hack (network-in-the-box mode) */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * 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 2 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.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/mncc_int.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+void *tall_call_ctx;
+
+static LLIST_HEAD(call_list);
+
+static uint32_t new_callref = 0x00000001;
+
+struct mncc_int mncc_int = {
+	.def_codec = { GSM48_CMODE_SPEECH_V1, GSM48_CMODE_SPEECH_V1 },
+};
+
+static void free_call(struct gsm_call *call)
+{
+	llist_del(&call->entry);
+	DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
+	talloc_free(call);
+}
+
+
+static struct gsm_call *get_call_ref(uint32_t callref)
+{
+	struct gsm_call *callt;
+
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref)
+			return callt;
+	}
+	return NULL;
+}
+
+uint8_t mncc_codec_for_mode(int lchan_type)
+{
+	/* FIXME: check codec capabilities of the phone */
+
+	if (lchan_type != GSM_LCHAN_TCH_H)
+		return mncc_int.def_codec[0];
+	else
+		return mncc_int.def_codec[1];
+}
+
+static uint8_t determine_lchan_mode(struct gsm_mncc *setup)
+{
+	return mncc_codec_for_mode(setup->lchan_type);
+}
+
+/* on incoming call, look up database and send setup to remote subscr. */
+static int mncc_setup_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *setup)
+{
+	struct gsm_mncc mncc;
+	struct gsm_call *remote;
+
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+
+	/* already have remote call */
+	if (call->remote_ref)
+		return 0;
+	
+	/* transfer mode 1 would be packet mode, which was never specified */
+	if (setup->bearer_cap.mode != 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support "
+			"packet mode\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* we currently only do speech */
+	if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support "
+			"voice calls\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* create remote call */
+	if (!(remote = talloc_zero(tall_call_ctx, struct gsm_call))) {
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		goto out_reject;
+	}
+	llist_add_tail(&remote->entry, &call_list);
+	remote->net = call->net;
+	remote->callref = new_callref++;
+	DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n",
+		call->callref, remote->callref);
+
+	/* link remote call */
+	call->remote_ref = remote->callref;
+	remote->remote_ref = call->callref;
+
+	/* send call proceeding */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_CALL_PROC_REQ, &mncc);
+
+	/* modify mode */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	mncc.lchan_mode = determine_lchan_mode(setup);
+	DEBUGP(DMNCC, "(call %x) Modify channel mode: %s\n", call->callref,
+	       get_value_string(gsm48_chan_mode_names, mncc.lchan_mode));
+	mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, &mncc);
+
+	/* send setup to remote */
+//	setup->fields |= MNCC_F_SIGNAL;
+//	setup->signal = GSM48_SIGNAL_DIALTONE;
+	setup->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_SETUP_REQ, setup);
+
+out_reject:
+	mncc_tx_to_cc(call->net, MNCC_REJ_REQ, &mncc);
+	free_call(call);
+	return 0;
+}
+
+static int mncc_alert_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *alert)
+{
+	struct gsm_call *remote;
+
+	/* send alerting to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	alert->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_ALERT_REQ, alert);
+}
+
+static int mncc_notify_ind(struct gsm_call *call, int msg_type,
+			   struct gsm_mncc *notify)
+{
+	struct gsm_call *remote;
+
+	/* send notify to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	notify->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_NOTIFY_REQ, notify);
+}
+
+static int mncc_setup_cnf(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *connect)
+{
+	struct gsm_mncc connect_ack, frame_recv;
+	struct gsm_network *net = call->net;
+	struct gsm_call *remote;
+	struct gsm_mncc_bridge bridge = { .msg_type = MNCC_BRIDGE };
+
+	/* acknowledge connect */
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack);
+
+	/* send connect message to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	connect->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref);
+	mncc_tx_to_cc(remote->net, MNCC_SETUP_RSP, connect);
+
+	/* bridge tch */
+	bridge.callref[0] = call->callref;
+	bridge.callref[1] = call->remote_ref;
+	DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref);
+
+	/* in direct mode, we always have to bridge the channels */
+	if (ipacc_rtp_direct)
+		return mncc_tx_to_cc(call->net, MNCC_BRIDGE, &bridge);
+
+	/* proxy mode */
+	if (!net->handover.active) {
+		/* in the no-handover case, we can bridge, i.e. use
+		 * the old RTP proxy code */
+		return mncc_tx_to_cc(call->net, MNCC_BRIDGE, &bridge);
+	} else {
+		/* in case of handover, we need to re-write the RTP
+		 * SSRC, sequence and timestamp values and thus
+		 * need to enable RTP receive for both directions */
+		memset(&frame_recv, 0, sizeof(struct gsm_mncc));
+		frame_recv.callref = call->callref;
+		mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+		frame_recv.callref = call->remote_ref;
+		return mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+	}
+}
+
+static int mncc_disc_ind(struct gsm_call *call, int msg_type,
+			 struct gsm_mncc *disc)
+{
+	struct gsm_call *remote;
+
+	/* send release */
+	DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n",
+		call->callref, disc->cause.value);
+	mncc_tx_to_cc(call->net, MNCC_REL_REQ, disc);
+
+	/* send disc to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		return 0;
+	}
+	disc->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n",
+		remote->callref, disc->cause.value);
+	return mncc_tx_to_cc(remote->net, MNCC_DISC_REQ, disc);
+}
+
+static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	struct gsm_call *remote;
+
+	/* send release to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		free_call(call);
+		return 0;
+	}
+
+	rel->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n",
+		call->callref, rel->cause.value);
+
+	/*
+	 * Release this side of the call right now. Otherwise we end up
+	 * in this method for the other call and will also try to release
+	 * it and then we will end up with a double free and a crash
+	 */
+	free_call(call);
+	mncc_tx_to_cc(remote->net, MNCC_REL_REQ, rel);
+
+	return 0;
+}
+
+static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	free_call(call);
+	return 0;
+}
+
+/* receiving a (speech) traffic frame from the BSC code */
+static int mncc_rcv_data(struct gsm_call *call, int msg_type,
+			 struct gsm_data_frame *dfr)
+{
+	struct gsm_trans *remote_trans;
+
+	remote_trans = trans_find_by_callref(call->net, call->remote_ref);
+
+	/* this shouldn't really happen */
+	if (!remote_trans || !remote_trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n");
+		return -EIO;
+	}
+
+	/* RTP socket of remote end has meanwhile died */
+	if (!remote_trans->conn->lchan->abis_ip.rtp_socket)
+		return -EIO;
+
+	return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr);
+}
+
+
+/* Internal MNCC handler input function (from CC -> MNCC -> here) */
+int int_mncc_recv(struct gsm_network *net, struct msgb *msg)
+{
+	void *arg = msgb_data(msg);
+	struct gsm_mncc *data = arg;
+	int msg_type = data->msg_type;
+	int callref;
+	struct gsm_call *call = NULL, *callt;
+	int rc = 0;
+
+	/* Special messages */
+	switch(msg_type) {
+	}
+	
+	/* find callref */
+	callref = data->callref;
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref) {
+			call = callt;
+			break;
+		}
+	}
+
+	/* create callref, if setup is received */
+	if (!call) {
+		if (msg_type != MNCC_SETUP_IND)
+			goto out_free; /* drop */
+		/* create call */
+		if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
+			struct gsm_mncc rel;
+			
+			memset(&rel, 0, sizeof(struct gsm_mncc));
+			rel.callref = callref;
+			mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				       GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			mncc_tx_to_cc(net, MNCC_REL_REQ, &rel);
+			goto out_free;
+		}
+		llist_add_tail(&call->entry, &call_list);
+		call->net = net;
+		call->callref = callref;
+		DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref);
+	}
+
+	if (mncc_is_data_frame(msg_type)) {
+		rc = mncc_rcv_data(call, msg_type, arg);
+		goto out_free;
+	}
+
+	DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref,
+		get_mncc_name(msg_type));
+
+	switch(msg_type) {
+	case MNCC_SETUP_IND:
+		rc = mncc_setup_ind(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_CNF:
+		rc = mncc_setup_cnf(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_COMPL_IND:
+		break;
+	case MNCC_CALL_CONF_IND:
+		/* we now need to MODIFY the channel */
+		data->lchan_mode = determine_lchan_mode(data);
+		mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, data);
+		break;
+	case MNCC_ALERT_IND:
+		rc = mncc_alert_ind(call, msg_type, arg);
+		break;
+	case MNCC_NOTIFY_IND:
+		rc = mncc_notify_ind(call, msg_type, arg);
+		break;
+	case MNCC_DISC_IND:
+		rc = mncc_disc_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_IND:
+	case MNCC_REJ_IND:
+		rc = mncc_rel_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_CNF:
+		rc = mncc_rel_cnf(call, msg_type, arg);
+		break;
+	case MNCC_FACILITY_IND:
+		break;
+	case MNCC_START_DTMF_IND:
+		rc = mncc_tx_to_cc(net, MNCC_START_DTMF_REJ, data);
+		break;
+	case MNCC_STOP_DTMF_IND:
+		rc = mncc_tx_to_cc(net, MNCC_STOP_DTMF_RSP, data);
+		break;
+	case MNCC_MODIFY_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_MODIFY_REJ, data);
+		break;
+	case MNCC_MODIFY_CNF:
+		break;
+	case MNCC_HOLD_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_HOLD_REJ, data);
+		break;
+	case MNCC_RETRIEVE_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_RETRIEVE_REJ, data);
+		break;
+	default:
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref);
+		break;
+	}
+
+out_free:
+	msgb_free(msg);
+
+	return rc;
+}
diff --git a/openbsc/src/libmsc/mncc_sock.c b/openbsc/src/libmsc/mncc_sock.c
new file mode 100644
index 0000000..0efe3a1
--- /dev/null
+++ b/openbsc/src/libmsc/mncc_sock.c
@@ -0,0 +1,318 @@
+/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * (C) 2012 by Holger Hans Peter Freyther
+ * 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 2 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.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+
+struct mncc_sock_state {
+	struct gsm_network *net;
+	struct osmo_fd listen_bfd;	/* fd for listen socket */
+	struct osmo_fd conn_bfd;		/* fd for connection to lcr */
+};
+
+/* input from CC code into mncc_sock */
+int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg)
+{
+	struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg);
+	int msg_type = mncc_in->msg_type;
+
+	/* Check if we currently have a MNCC handler connected */
+	if (net->mncc_state->conn_bfd.fd < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app "
+			"but socket is gone\n", get_mncc_name(msg_type));
+		if (!mncc_is_data_frame(msg_type)) {
+			/* release the request */
+			struct gsm_mncc mncc_out;
+			memset(&mncc_out, 0, sizeof(mncc_out));
+			mncc_out.callref = mncc_in->callref;
+			mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_TEMP_FAILURE);
+			mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out);
+		}
+		/* free the original message */
+		msgb_free(msg);
+		return -1;
+	}
+
+	/* FIXME: check for some maximum queue depth? */
+
+	/* Actually enqueue the message and mark socket write need */
+	msgb_enqueue(&net->upqueue, msg);
+	net->mncc_state->conn_bfd.when |= BSC_FD_WRITE;
+	return 0;
+}
+
+static void mncc_sock_close(struct mncc_sock_state *state)
+{
+	struct osmo_fd *bfd = &state->conn_bfd;
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n");
+
+	close(bfd->fd);
+	bfd->fd = -1;
+	osmo_fd_unregister(bfd);
+
+	/* re-enable the generation of ACCEPT for new connections */
+	state->listen_bfd.when |= BSC_FD_READ;
+
+	/* release all exisitng calls */
+	gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC);
+
+	/* flush the queue */
+	while (!llist_empty(&state->net->upqueue)) {
+		struct msgb *msg = msgb_dequeue(&state->net->upqueue);
+		msgb_free(msg);
+	}
+}
+
+static int mncc_sock_read(struct osmo_fd *bfd)
+{
+	struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+	struct gsm_mncc *mncc_prim;
+	struct msgb *msg;
+	int rc;
+
+	msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx");
+	if (!msg)
+		return -ENOMEM;
+
+	mncc_prim = (struct gsm_mncc *) msg->tail;
+
+	rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+	if (rc == 0)
+		goto close;
+
+	if (rc < 0) {
+		if (errno == EAGAIN)
+			return 0;
+		goto close;
+	}
+
+	rc = mncc_tx_to_cc(state->net, mncc_prim->msg_type, mncc_prim);
+
+	/* as we always synchronously process the message in mncc_send() and
+	 * its callbacks, we can free the message here. */
+	msgb_free(msg);
+
+	return rc;
+
+close:
+	msgb_free(msg);
+	mncc_sock_close(state);
+	return -1;
+}
+
+static int mncc_sock_write(struct osmo_fd *bfd)
+{
+	struct mncc_sock_state *state = bfd->data;
+	struct gsm_network *net = state->net;
+	int rc;
+
+	while (!llist_empty(&net->upqueue)) {
+		struct msgb *msg, *msg2;
+		struct gsm_mncc *mncc_prim;
+
+		/* peek at the beginning of the queue */
+		msg = llist_entry(net->upqueue.next, struct msgb, list);
+		mncc_prim = (struct gsm_mncc *)msg->data;
+
+		bfd->when &= ~BSC_FD_WRITE;
+
+		/* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+		if (!msgb_length(msg)) {
+			LOGP(DMNCC, LOGL_ERROR, "message type (%d) with ZERO "
+				"bytes!\n", mncc_prim->msg_type);
+			goto dontsend;
+		}
+
+		/* try to send it over the socket */
+		rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+		if (rc == 0)
+			goto close;
+		if (rc < 0) {
+			if (errno == EAGAIN) {
+				bfd->when |= BSC_FD_WRITE;
+				break;
+			}
+			goto close;
+		}
+
+dontsend:
+		/* _after_ we send it, we can deueue */
+		msg2 = msgb_dequeue(&net->upqueue);
+		assert(msg == msg2);
+		msgb_free(msg);
+	}
+	return 0;
+
+close:
+	mncc_sock_close(state);
+
+	return -1;
+}
+
+static int mncc_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+	int rc = 0;
+
+	if (flags & BSC_FD_READ)
+		rc = mncc_sock_read(bfd);
+	if (rc < 0)
+		return rc;
+
+	if (flags & BSC_FD_WRITE)
+		rc = mncc_sock_write(bfd);
+
+	return rc;
+}
+
+/**
+ * Send a version indication to the remote.
+ */
+static void queue_hello(struct mncc_sock_state *mncc)
+{
+	struct gsm_mncc_hello *hello;
+	struct msgb *msg;
+
+	msg = msgb_alloc(512, "mncc hello");
+	if (!msg) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to allocate hello.\n");
+		mncc_sock_close(mncc);
+		return;
+	}
+
+	hello = (struct gsm_mncc_hello *) msgb_put(msg, sizeof(*hello));
+	hello->msg_type = MNCC_SOCKET_HELLO;
+	hello->version = MNCC_SOCK_VERSION;
+	hello->mncc_size = sizeof(struct gsm_mncc);
+	hello->data_frame_size = sizeof(struct gsm_data_frame);
+	hello->called_offset = offsetof(struct gsm_mncc, called);
+	hello->signal_offset = offsetof(struct gsm_mncc, signal);
+	hello->emergency_offset = offsetof(struct gsm_mncc, emergency);
+	hello->lchan_type_offset = offsetof(struct gsm_mncc, lchan_type);
+
+	msgb_enqueue(&mncc->net->upqueue, msg);
+	mncc->conn_bfd.when |= BSC_FD_WRITE;
+}
+
+/* accept a new connection */
+static int mncc_sock_accept(struct osmo_fd *bfd, unsigned int flags)
+{
+	struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+	struct osmo_fd *conn_bfd = &state->conn_bfd;
+	struct sockaddr_un un_addr;
+	socklen_t len;
+	int rc;
+
+	len = sizeof(un_addr);
+	rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n");
+		return -1;
+	}
+
+	if (conn_bfd->fd >= 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have "
+			"another active connection ?!?\n");
+		/* We already have one MNCC app connected, this is all we support */
+		state->listen_bfd.when &= ~BSC_FD_READ;
+		close(rc);
+		return 0;
+	}
+
+	conn_bfd->fd = rc;
+	conn_bfd->when = BSC_FD_READ;
+	conn_bfd->cb = mncc_sock_cb;
+	conn_bfd->data = state;
+
+	if (osmo_fd_register(conn_bfd) != 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n");
+		close(conn_bfd->fd);
+		conn_bfd->fd = -1;
+		return -1;
+	}
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external "
+		"call control application\n");
+
+	queue_hello(state);
+	return 0;
+}
+
+
+int mncc_sock_init(struct gsm_network *net, const char *sock_path)
+{
+	struct mncc_sock_state *state;
+	struct osmo_fd *bfd;
+	int rc;
+
+	state = talloc_zero(tall_bsc_ctx, struct mncc_sock_state);
+	if (!state)
+		return -ENOMEM;
+
+	state->net = net;
+	state->conn_bfd.fd = -1;
+
+	bfd = &state->listen_bfd;
+
+	bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, sock_path,
+		OSMO_SOCK_F_BIND);
+	if (bfd->fd < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s: %s\n",
+		     sock_path, strerror(errno));
+		talloc_free(state);
+		return -1;
+	}
+
+	bfd->when = BSC_FD_READ;
+	bfd->cb = mncc_sock_accept;
+	bfd->data = state;
+
+	rc = osmo_fd_register(bfd);
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
+		close(bfd->fd);
+		talloc_free(state);
+		return rc;
+	}
+
+	net->mncc_state = state;
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC socket at %s\n", sock_path);
+	return 0;
+}
diff --git a/openbsc/src/libmsc/osmo_msc.c b/openbsc/src/libmsc/osmo_msc.c
new file mode 100644
index 0000000..2389980
--- /dev/null
+++ b/openbsc/src/libmsc/osmo_msc.c
@@ -0,0 +1,177 @@
+/* main MSC management code... */
+
+/*
+ * (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/debug.h>
+#include <openbsc/transaction.h>
+#include <openbsc/db.h>
+
+#include <openbsc/gsm_04_11.h>
+
+static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+	int sapi = dlci & 0x7;
+
+	if (sapi == UM_SAPI_SMS)
+		gsm411_sapi_n_reject(conn);
+}
+
+static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	gsm0408_clear_request(conn, cause);
+	return 1;
+}
+
+static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+			uint16_t chosen_channel)
+{
+	gsm0408_new_conn(conn);
+	gsm0408_dispatch(conn, msg);
+
+	/*
+	 * If this is a silent call we want the channel to remain open as long as
+	 * possible and this is why we accept this connection regardless of any
+	 * pending transaction or ongoing operation.
+	 */
+	if (conn->silent_call)
+		return BSC_API_CONN_POL_ACCEPT;
+	if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+		return BSC_API_CONN_POL_ACCEPT;
+	if (trans_has_conn(conn))
+		return BSC_API_CONN_POL_ACCEPT;
+
+	LOGP(DRR, LOGL_INFO, "MSC Complete L3: Rejecting connection.\n");
+	return BSC_API_CONN_POL_REJECT;
+}
+
+static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+	gsm0408_dispatch(conn, msg);
+}
+
+static void msc_assign_compl(struct gsm_subscriber_connection *conn,
+			     uint8_t rr_cause, uint8_t chosen_channel,
+			     uint8_t encr_alg_id, uint8_t speec)
+{
+	LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n");
+}
+
+static void msc_assign_fail(struct gsm_subscriber_connection *conn,
+			    uint8_t cause, uint8_t *rr_cause)
+{
+	LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n");
+}
+
+static void msc_classmark_chg(struct gsm_subscriber_connection *conn,
+			      const uint8_t *cm2, uint8_t cm2_len,
+			      const uint8_t *cm3, uint8_t cm3_len)
+{
+	struct gsm_subscriber *subscr = conn->subscr;
+
+	if (subscr) {
+		subscr->equipment.classmark2_len = cm2_len;
+		memcpy(subscr->equipment.classmark2, cm2, cm2_len);
+		if (cm3) {
+			subscr->equipment.classmark3_len = cm3_len;
+			memcpy(subscr->equipment.classmark3, cm3, cm3_len);
+		}
+		db_sync_equipment(&subscr->equipment);
+	}
+}
+
+static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg, uint8_t alg_id)
+{
+	gsm_cbfn *cb;
+
+	DEBUGP(DRR, "CIPHERING MODE COMPLETE\n");
+
+	/* Safety check */
+	if (!conn->sec_operation) {
+		DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n");
+		return;
+	}
+
+	/* FIXME: check for MI (if any) */
+
+	/* Call back whatever was in progress (if anything) ... */
+	cb = conn->sec_operation->cb;
+	if (cb) {
+		cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED,
+			NULL, conn, conn->sec_operation->cb_data);
+
+	}
+
+	/* Complete the operation */
+	release_security_operation(conn);
+}
+
+
+
+static struct bsc_api msc_handler = {
+	.sapi_n_reject = msc_sapi_n_reject,
+	.compl_l3 = msc_compl_l3,
+	.dtap = msc_dtap,
+	.clear_request = msc_clear_request,
+	.assign_compl = msc_assign_compl,
+	.assign_fail = msc_assign_fail,
+	.classmark_chg = msc_classmark_chg,
+	.cipher_mode_compl = msc_ciph_m_compl,
+};
+
+struct bsc_api *msc_bsc_api() {
+	return &msc_handler;
+}
+
+/* lchan release handling */
+void msc_release_connection(struct gsm_subscriber_connection *conn)
+{
+	/* skip when we are in release, e.g. due an error */
+	if (conn->in_release)
+		return;
+
+	/* skip releasing of silent calls as they have no transaction */
+	if (conn->silent_call)
+		return;
+
+	/* check if there is a pending operation */
+	if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+		return;
+
+	if (trans_has_conn(conn))
+		return;
+
+	/* no more connections, asking to release the channel */
+
+	/*
+	 * We had stopped the LU expire timer T3212. Now we are about
+	 * to send the MS back to the idle state and this should lead
+	 * to restarting the timer. Set the new expiration time.
+	 */
+	if (conn->expire_timer_stopped)
+		subscr_update_expire_lu(conn->subscr, conn->bts);
+
+	conn->in_release = 1;
+	gsm0808_clear(conn);
+	msc_subscr_con_free(conn);
+}
diff --git a/openbsc/src/libmsc/rrlp.c b/openbsc/src/libmsc/rrlp.c
new file mode 100644
index 0000000..e695daa
--- /dev/null
+++ b/openbsc/src/libmsc/rrlp.c
@@ -0,0 +1,104 @@
+/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+/* RRLP msPositionReq, nsBased,
+ *	Accuracy=60, Method=gps, ResponseTime=2, oneSet */
+static const uint8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 };
+
+/* RRLP msPositionReq, msBasedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const uint8_t ms_pref_pos_req[]  = { 0x40, 0x02, 0x79, 0x50 };
+
+/* RRLP msPositionReq, msAssistedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const uint8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 };
+
+static int send_rrlp_req(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_network *net = conn->network;
+	const uint8_t *req;
+
+	switch (net->rrlp.mode) {
+	case RRLP_MODE_MS_BASED:
+		req = ms_based_pos_req;
+		break;
+	case RRLP_MODE_MS_PREF:
+		req = ms_pref_pos_req;
+		break;
+	case RRLP_MODE_ASS_PREF:
+		req = ass_pref_pos_req;
+		break;
+	case RRLP_MODE_NONE:
+	default:
+		return 0;
+	}
+
+	return gsm48_send_rr_app_info(conn, 0x00,
+				      sizeof(ms_based_pos_req), req);
+}
+
+static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_subscriber_connection *conn;
+
+	switch (signal) {
+	case S_SUBSCR_ATTACHED:
+		/* A subscriber has attached. */
+		subscr = signal_data;
+		conn = connection_for_subscr(subscr);
+		if (!conn)
+			break;
+		send_rrlp_req(conn);
+		break;
+	}
+	return 0;
+}
+
+static int paging_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct paging_signal_data *psig_data = signal_data;
+
+	switch (signal) {
+	case S_PAGING_SUCCEEDED:
+		/* A subscriber has attached. */
+		send_rrlp_req(psig_data->conn);
+		break;
+	case S_PAGING_EXPIRED:
+		break;
+	}
+	return 0;
+}
+
+void on_dso_load_rrlp(void)
+{
+	osmo_signal_register_handler(SS_SUBSCR, subscr_sig_cb, NULL);
+	osmo_signal_register_handler(SS_PAGING, paging_sig_cb, NULL);
+}
diff --git a/openbsc/src/libmsc/silent_call.c b/openbsc/src/libmsc/silent_call.c
new file mode 100644
index 0000000..590d01b
--- /dev/null
+++ b/openbsc/src/libmsc/silent_call.c
@@ -0,0 +1,152 @@
+/* GSM silent call feature */
+
+/*
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/osmo_msc.h>
+
+/* paging of the requested subscriber has completed */
+static int paging_cb_silent(unsigned int hooknum, unsigned int event,
+			    struct msgb *msg, void *_conn, void *_data)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct scall_signal_data sigdata;
+	int rc = 0;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	DEBUGP(DLSMS, "paging_cb_silent: ");
+
+	sigdata.conn = conn;
+	sigdata.data = _data;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		DEBUGPC(DLSMS, "success, using Timeslot %u on ARFCN %u\n",
+			conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
+		conn->silent_call = 1;
+		/* increment lchan reference count */
+		osmo_signal_dispatch(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_BUSY:
+	case GSM_PAGING_OOM:
+		DEBUGP(DLSMS, "expired\n");
+		osmo_signal_dispatch(SS_SCALL, S_SCALL_EXPIRED, &sigdata);
+		break;
+	default:
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+#if 0
+/* receive a layer 3 message from a silent call */
+int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	/* FIXME: do something like sending it through a UDP port */
+	LOGP(DLSMS, LOGL_NOTICE, "Discarding L3 message from a silent call.\n");
+	return 0;
+}
+#endif
+
+struct msg_match {
+	uint8_t pdisc;
+	uint8_t msg_type;
+};
+
+/* list of messages that are handled inside OpenBSC, even in a silent call */
+static const struct msg_match silent_call_accept[] = {
+	{ GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST },
+	{ GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ },
+};
+
+#if 0
+/* decide if we need to reroute a message as part of a silent call */
+int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t pdisc = gsm48_hdr_pdisc(gh);
+	uint8_t msg_type = gsm48_hdr_msg_type(gh);
+	int i;
+
+	/* if we're not part of a silent call, never reroute */
+	if (!conn->silent_call)
+		return 0;
+
+	/* check if we are a special message that is handled in openbsc */
+	for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) {
+		if (silent_call_accept[i].pdisc == pdisc &&
+		    silent_call_accept[i].msg_type == msg_type)
+			return 0;
+	}
+
+	/* otherwise, reroute */
+	LOGP(DLSMS, LOGL_INFO, "Rerouting L3 message from a silent call.\n");
+	return 1;
+}
+#endif
+
+
+/* initiate a silent call with a given subscriber */
+int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type)
+{
+	struct subscr_request *req;
+
+	req = subscr_request_channel(subscr, type, paging_cb_silent, data);
+	return req != NULL;
+}
+
+/* end a silent call with a given subscriber */
+int gsm_silent_call_stop(struct gsm_subscriber *subscr)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = connection_for_subscr(subscr);
+	if (!conn)
+		return -EINVAL;
+
+	/* did we actually establish a silent call for this guy? */
+	if (!conn->silent_call)
+		return -EINVAL;
+
+	DEBUGPC(DLSMS, "Stopping silent call using Timeslot %u on ARFCN %u\n",
+		conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
+
+	conn->silent_call = 0;
+	msc_release_connection(conn);
+
+	return 0;
+}
diff --git a/openbsc/src/libmsc/smpp_openbsc.c b/openbsc/src/libmsc/smpp_openbsc.c
new file mode 100644
index 0000000..3fe2dfd
--- /dev/null
+++ b/openbsc/src/libmsc/smpp_openbsc.c
@@ -0,0 +1,791 @@
+/* OpenBSC SMPP 3.4 interface, SMSC-side implementation */
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+#include <osmocom/gsm/protocol/smpp34_osmocom.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/transaction.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+#include "smpp_smsc.h"
+
+/*! \brief find gsm_subscriber for a given SMPP NPI/TON/Address */
+static struct gsm_subscriber *subscr_by_dst(struct gsm_network *net,
+					    uint8_t npi, uint8_t ton, const char *addr)
+{
+	struct gsm_subscriber *subscr = NULL;
+
+	switch (npi) {
+	case NPI_Land_Mobile_E212:
+		subscr = subscr_get_by_imsi(net->subscr_group, addr);
+		break;
+	case NPI_ISDN_E163_E164:
+	case NPI_Private:
+		subscr = subscr_get_by_extension(net->subscr_group, addr);
+		break;
+	default:
+		LOGP(DSMPP, LOGL_NOTICE, "Unsupported NPI: %u\n", npi);
+		break;
+	}
+
+	/* tag the context in case we know it */
+	log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+	return subscr;
+}
+
+static int smpp34_submit_tlv_msg_payload(const struct tlv_t *t,
+					 const struct submit_sm_t *submit,
+					 const uint8_t **sms_msg,
+					 unsigned int *sms_msg_len)
+{
+	if (submit->sm_length) {
+		LOGP(DLSMS, LOGL_ERROR,
+		     "SMPP cannot have payload in TLV _and_ in the header\n");
+		return -1;
+	}
+	*sms_msg = t->value.octet;
+	*sms_msg_len = t->length;
+
+	return 0;
+}
+
+/*! \brief convert from submit_sm_t to gsm_sms */
+static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net,
+			 const struct submit_sm_t *submit)
+{
+	const uint8_t *sms_msg = NULL;
+	unsigned int sms_msg_len = 0;
+	struct gsm_subscriber *dest;
+	uint16_t msg_ref = 0;
+	struct gsm_sms *sms;
+	struct tlv_t *t;
+	int mode;
+
+	dest = subscr_by_dst(net, submit->dest_addr_npi,
+			     submit->dest_addr_ton,
+			     (const char *)submit->destination_addr);
+	if (!dest) {
+		LOGP(DLSMS, LOGL_NOTICE, "SMPP SUBMIT-SM for unknown subscriber: "
+		     "%s (NPI=%u)\n", submit->destination_addr,
+		     submit->dest_addr_npi);
+		return ESME_RINVDSTADR;
+	}
+
+	smpp34_tlv_for_each(t, submit->tlv) {
+		switch (t->tag) {
+		case TLVID_message_payload:
+			if (smpp34_submit_tlv_msg_payload(t, submit, &sms_msg,
+							  &sms_msg_len) < 0) {
+				subscr_put(dest);
+				return ESME_ROPTPARNOTALLWD;
+			}
+			break;
+		case TLVID_user_message_reference:
+			msg_ref = t->value.val16;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!sms_msg) {
+		if (submit->sm_length > 0 && submit->sm_length < 255) {
+			sms_msg = submit->short_message;
+			sms_msg_len = submit->sm_length;
+		} else {
+			LOGP(DLSMS, LOGL_ERROR,
+			     "SMPP neither message payload nor valid sm_length.\n");
+			subscr_put(dest);
+			return ESME_RINVPARLEN;
+		}
+	}
+
+	sms = sms_alloc();
+	sms->source = SMS_SOURCE_SMPP;
+	sms->smpp.sequence_nr = submit->sequence_number;
+	sms->status_rep_req = submit->registered_delivery;
+	sms->msg_ref = msg_ref;
+
+	/* fill in the destination address */
+	sms->receiver = dest;
+	sms->dst.ton = submit->dest_addr_ton;
+	sms->dst.npi = submit->dest_addr_npi;
+	osmo_strlcpy(sms->dst.addr, dest->extension, sizeof(sms->dst.addr));
+
+	/* fill in the source address */
+	sms->src.ton = submit->source_addr_ton;
+	sms->src.npi = submit->source_addr_npi;
+	osmo_strlcpy(sms->src.addr, (char *)submit->source_addr,
+		     sizeof(sms->src.addr));
+
+	if (submit->esm_class == SMPP34_DELIVERY_ACK)
+		sms->is_report = true;
+
+	if (submit->esm_class & SMPP34_UDHI_IND)
+		sms->ud_hdr_ind = 1;
+
+	if (submit->esm_class & SMPP34_REPLY_PATH) {
+		sms->reply_path_req = 1;
+#warning Implement reply path
+	}
+
+	if (submit->data_coding == 0x00 ||	/* SMSC default */
+	    submit->data_coding == 0x01) {	/* GSM default alphabet */
+		sms->data_coding_scheme = GSM338_DCS_1111_7BIT;
+		mode = MODE_7BIT;
+	} else if ((submit->data_coding & 0xFC) == 0xF0) { /* 03.38 DCS default */
+		/* pass DCS 1:1 through from SMPP to GSM */
+		sms->data_coding_scheme = submit->data_coding;
+		mode = MODE_7BIT;
+	} else if (submit->data_coding == 0x02 ||
+		   submit->data_coding == 0x04) {
+		/* 8-bit binary */
+		sms->data_coding_scheme = GSM338_DCS_1111_8BIT_DATA;
+		mode = MODE_8BIT;
+	} else if ((submit->data_coding & 0xFC) == 0xF4) { /* 03.38 DCS 8bit */
+		/* pass DCS 1:1 through from SMPP to GSM */
+		sms->data_coding_scheme = submit->data_coding;
+		mode = MODE_8BIT;
+	} else if (submit->data_coding == 0x08) {
+		/* UCS-2 */
+		sms->data_coding_scheme = (2 << 2);
+		mode = MODE_8BIT;
+	} else {
+		sms_free(sms);
+		LOGP(DLSMS, LOGL_ERROR, "SMPP Unknown Data Coding 0x%02x\n",
+			submit->data_coding);
+		return ESME_RUNKNOWNERR;
+	}
+
+	if (mode == MODE_7BIT) {
+		uint8_t ud_len = 0, padbits = 0;
+		sms->data_coding_scheme = GSM338_DCS_1111_7BIT;
+		if (sms->ud_hdr_ind) {
+			ud_len = *sms_msg + 1;
+			printf("copying %u bytes user data...\n", ud_len);
+			memcpy(sms->user_data, sms_msg,
+				OSMO_MIN(ud_len, sizeof(sms->user_data)));
+			sms_msg += ud_len;
+			sms_msg_len -= ud_len;
+			padbits = 7 - (ud_len % 7);
+		}
+		gsm_septets2octets(sms->user_data+ud_len, sms_msg,
+				   sms_msg_len, padbits);
+		sms->user_data_len = (ud_len*8 + padbits)/7 + sms_msg_len;/* SEPTETS */
+		/* FIXME: sms->text */
+	} else {
+		memcpy(sms->user_data, sms_msg, sms_msg_len);
+		sms->user_data_len = sms_msg_len;
+	}
+
+	*psms = sms;
+	return ESME_ROK;
+}
+
+/*! \brief handle incoming libsmpp34 ssubmit_sm_t from remote ESME */
+int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
+		       struct submit_sm_resp_t *submit_r)
+{
+	struct gsm_sms *sms;
+	struct gsm_network *net = esme->smsc->priv;
+	struct sms_signal_data sig;
+	int rc = -1;
+
+	rc = submit_to_sms(&sms, net, submit);
+	if (rc != ESME_ROK) {
+		submit_r->command_status = rc;
+		return 0;
+	}
+	smpp_esme_get(esme);
+	sms->smpp.esme = esme;
+	sms->protocol_id = submit->protocol_id;
+
+	switch (submit->esm_class & SMPP34_MSG_MODE_MASK) {
+	case 0: /* default */
+	case 1: /* datagram */
+	case 3: /* store-and-forward */
+		rc = db_sms_store(sms);
+		sms_free(sms);
+		sms = NULL;
+		if (rc < 0) {
+			LOGP(DLSMS, LOGL_ERROR, "SMPP SUBMIT-SM: Unable to "
+				"store SMS in database\n");
+			submit_r->command_status = ESME_RSYSERR;
+			return 0;
+		}
+		strcpy((char *)submit_r->message_id, "msg_id_not_implemented");
+		LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Stored in DB\n");
+
+		memset(&sig, 0, sizeof(sig));
+		osmo_signal_dispatch(SS_SMS, S_SMS_SUBMITTED, &sig);
+		rc = 0;
+		break;
+	case 2: /* forward (i.e. transaction) mode */
+		LOGP(DLSMS, LOGL_DEBUG, "SMPP SUBMIT-SM: Forwarding in "
+			"real time (Transaction/Forward mode)\n");
+		sms->smpp.transaction_mode = 1;
+		gsm411_send_sms_subscr(sms->receiver, sms);
+		rc = 1; /* don't send any response yet */
+		break;
+	}
+	return rc;
+}
+
+static void alert_all_esme(struct smsc *smsc, struct gsm_subscriber *subscr,
+			   uint8_t smpp_avail_status)
+{
+	struct osmo_esme *esme;
+
+	llist_for_each_entry(esme, &smsc->esme_list, list) {
+		/* we currently send an alert notification to each ESME that is
+		 * connected, and do not require a (non-existant) delivery
+		 * pending flag to be set before,  FIXME: make this VTY
+		 * configurable */
+		if (esme->acl && esme->acl->deliver_src_imsi) {
+			smpp_tx_alert(esme, TON_Subscriber_Number,
+				      NPI_Land_Mobile_E212,
+				      subscr->imsi, smpp_avail_status);
+		} else {
+			smpp_tx_alert(esme, TON_Network_Specific,
+				      NPI_ISDN_E163_E164,
+				      subscr->extension, smpp_avail_status);
+		}
+	}
+}
+
+
+/*! \brief signal handler for status of attempted SMS deliveries */
+static int smpp_sms_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct sms_signal_data *sig_sms = signal_data;
+	struct gsm_sms *sms = sig_sms->sms;
+	struct smsc *smsc = handler_data;
+	int rc = 0;
+
+	if (!sms)
+		return 0;
+
+	if (sms->source != SMS_SOURCE_SMPP)
+		return 0;
+
+	switch (signal) {
+	case S_SMS_MEM_EXCEEDED:
+		/* fall-through: There is no ESME_Rxxx result code to
+		 * indicate a MEMORY EXCEEDED in transaction mode back
+		 * to the ESME */
+	case S_SMS_UNKNOWN_ERROR:
+		if (sms->smpp.transaction_mode) {
+			/* Send back the SUBMIT-SM response with apropriate error */
+			LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Error\n");
+			rc = smpp_tx_submit_r(sms->smpp.esme,
+					      sms->smpp.sequence_nr,
+					      ESME_RDELIVERYFAILURE,
+					      sms->smpp.msg_id);
+		}
+		break;
+	case S_SMS_DELIVERED:
+		/* SMS layer tells us the delivery has been completed */
+		if (sms->smpp.transaction_mode) {
+			/* Send back the SUBMIT-SM response */
+			LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Success\n");
+			rc = smpp_tx_submit_r(sms->smpp.esme,
+					      sms->smpp.sequence_nr,
+					      ESME_ROK, sms->smpp.msg_id);
+		}
+		break;
+	case S_SMS_SMMA:
+		if (!sig_sms->trans || !sig_sms->trans->subscr) {
+			/* SMMA without a subscriber? strange... */
+			LOGP(DLSMS, LOGL_NOTICE, "SMMA without subscriber?\n");
+			break;
+		}
+
+		/* There's no real 1:1 match for SMMA in SMPP.  However,
+		 * an ALERT NOTIFICATION seems to be the most logical
+		 * choice */
+		alert_all_esme(smsc, sig_sms->trans->subscr, 0);
+		break;
+	}
+
+	return rc;
+}
+
+/*! \brief signal handler for subscriber related signals */
+static int smpp_subscr_cb(unsigned int subsys, unsigned int signal,
+			  void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+	struct smsc *smsc = handler_data;
+	uint8_t smpp_avail_status;
+
+	/* determine the smpp_avail_status depending on attach/detach */
+	switch (signal) {
+	case S_SUBSCR_ATTACHED:
+		smpp_avail_status = 0;
+		break;
+	case S_SUBSCR_DETACHED:
+		smpp_avail_status = 2;
+		break;
+	default:
+		return 0;
+	}
+
+	alert_all_esme(smsc, subscr, smpp_avail_status);
+
+	return 0;
+}
+
+/* GSM 03.38 6.2.1 Character expanding (no decode!) */
+static int gsm_7bit_expand(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind)
+{
+	int i = 0;
+	int shift = 0;
+	uint8_t c;
+
+	/* skip the user data header */
+	if (ud_hdr_ind) {
+		/* get user data header length + 1 (for the 'user data header length'-field) */
+		shift = ((user_data[0] + 1) * 8) / 7;
+		if ((((user_data[0] + 1) * 8) % 7) != 0)
+			shift++;
+		septet_l = septet_l - shift;
+	}
+
+	for (i = 0; i < septet_l; i++) {
+		c =
+			((user_data[((i + shift) * 7 + 7) >> 3] <<
+			  (7 - (((i + shift) * 7 + 7) & 7))) |
+			 (user_data[((i + shift) * 7) >> 3] >>
+			  (((i + shift) * 7) & 7))) & 0x7f;
+
+		*(text++) = c;
+	}
+
+	*text = '\0';
+
+	return i;
+}
+
+
+/* FIXME: libsmpp34 helpers, they should  be part of libsmpp34! */
+void append_tlv(tlv_t **req_tlv, uint16_t tag,
+	        const uint8_t *data, uint16_t len)
+{
+	tlv_t tlv;
+
+	memset(&tlv, 0, sizeof(tlv));
+	tlv.tag = tag;
+	tlv.length = len;
+	memcpy(tlv.value.octet, data, tlv.length);
+	build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u8(tlv_t **req_tlv, uint16_t tag, uint8_t val)
+{
+	tlv_t tlv;
+
+	memset(&tlv, 0, sizeof(tlv));
+	tlv.tag = tag;
+	tlv.length = 1;
+	tlv.value.val08 = val;
+	build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u16(tlv_t **req_tlv, uint16_t tag, uint16_t val)
+{
+	tlv_t tlv;
+
+	memset(&tlv, 0, sizeof(tlv));
+	tlv.tag = tag;
+	tlv.length = 2;
+	tlv.value.val16 = val;
+	build_tlv(req_tlv, &tlv);
+}
+
+/* Append the Osmocom vendor-specific additional TLVs to a SMPP msg */
+static void append_osmo_tlvs(tlv_t **req_tlv, const struct gsm_lchan *lchan)
+{
+	int idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				   lchan->meas_rep_idx, 1);
+	const struct gsm_meas_rep *mr = &lchan->meas_rep[idx];
+	const struct gsm_meas_rep_unidir *ul_meas = &mr->ul;
+	const struct gsm_meas_rep_unidir *dl_meas = &mr->dl;
+
+	/* Osmocom vendor-specific SMPP34 extensions */
+	append_tlv_u16(req_tlv, TLVID_osmo_arfcn, lchan->ts->trx->arfcn);
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		uint8_t ms_dbm;
+		append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_l1.ta);
+		ms_dbm = ms_pwr_dbm(lchan->ts->trx->bts->band, mr->ms_l1.pwr);
+		append_tlv_u8(req_tlv, TLVID_osmo_ms_l1_txpwr, ms_dbm);
+	} else if (mr->flags & MEAS_REP_F_MS_TO) /* Save Timing Offset field = MS Timing Offset + 63 */
+		append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_timing_offset + 63);
+
+	append_tlv_u16(req_tlv, TLVID_osmo_rxlev_ul,
+		       rxlev2dbm(ul_meas->full.rx_lev));
+	append_tlv_u8(req_tlv, TLVID_osmo_rxqual_ul, ul_meas->full.rx_qual);
+
+	if (mr->flags & MEAS_REP_F_DL_VALID) {
+		append_tlv_u16(req_tlv, TLVID_osmo_rxlev_dl,
+			       rxlev2dbm(dl_meas->full.rx_lev));
+		append_tlv_u8(req_tlv, TLVID_osmo_rxqual_dl,
+			      dl_meas->full.rx_qual);
+	}
+
+	if (lchan->conn && lchan->conn->subscr) {
+		struct gsm_subscriber *subscr = lchan->conn->subscr;
+		size_t imei_len = strlen(subscr->equipment.imei);
+		if (imei_len)
+			append_tlv(req_tlv, TLVID_osmo_imei,
+				   (uint8_t *)subscr->equipment.imei, imei_len+1);
+	}
+}
+
+struct {
+	uint32_t smpp_status_code;
+	uint8_t gsm411_cause;
+} smpp_to_gsm411_err_array[] = {
+
+	/* Seems like most phones don't care about the failure cause,
+	 * although some will display a different notification for
+	 * GSM411_RP_CAUSE_MO_NUM_UNASSIGNED
+	 * Some provoke a display of "Try again later"
+	 * while others a more definitive "Message sending failed"
+	 */
+
+	{ ESME_RSYSERR, 	GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER	},
+	{ ESME_RINVDSTADR,	GSM411_RP_CAUSE_MO_NUM_UNASSIGNED	},
+	{ ESME_RMSGQFUL,	GSM411_RP_CAUSE_MO_CONGESTION		},
+	{ ESME_RINVSRCADR,	GSM411_RP_CAUSE_MO_SMS_REJECTED		},
+	{ ESME_RINVMSGID,	GSM411_RP_CAUSE_INV_TRANS_REF		}
+};
+
+static int smpp_to_gsm411_err(uint32_t smpp_status_code, int *gsm411_cause)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(smpp_to_gsm411_err_array); i++) {
+		if (smpp_to_gsm411_err_array[i].smpp_status_code != smpp_status_code)
+			continue;
+		*gsm411_cause = smpp_to_gsm411_err_array[i].gsm411_cause;
+		return 0;
+	}
+	return -1;
+}
+
+static void smpp_cmd_free(struct osmo_smpp_cmd *cmd)
+{
+	osmo_timer_del(&cmd->response_timer);
+	llist_del(&cmd->list);
+	subscr_put(cmd->subscr);
+	talloc_free(cmd);
+}
+
+void smpp_cmd_flush_pending(struct osmo_esme *esme)
+{
+	struct osmo_smpp_cmd *cmd, *next;
+
+	llist_for_each_entry_safe(cmd, next, &esme->smpp_cmd_list, list)
+		smpp_cmd_free(cmd);
+}
+
+void smpp_cmd_ack(struct osmo_smpp_cmd *cmd)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_trans *trans;
+
+	if (cmd->is_report)
+		goto out;
+
+	conn = connection_for_subscr(cmd->subscr);
+	if (!conn) {
+		LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
+		goto out;
+	}
+
+	trans = trans_find_by_id(conn, GSM48_PDISC_SMS, cmd->gsm411_trans_id);
+	if (!trans) {
+		LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
+		     cmd->gsm411_trans_id);
+		goto out;
+	}
+
+	gsm411_send_rp_ack(trans, cmd->gsm411_msg_ref);
+out:
+	smpp_cmd_free(cmd);
+}
+
+void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_trans *trans;
+	int gsm411_cause;
+
+	if (cmd->is_report)
+		goto out;
+
+	conn = connection_for_subscr(cmd->subscr);
+	if (!conn) {
+		LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
+		goto out;
+	}
+
+	trans = trans_find_by_id(conn, GSM48_PDISC_SMS, cmd->gsm411_trans_id);
+	if (!trans) {
+		LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
+		     cmd->gsm411_trans_id);
+		goto out;
+	}
+
+	if (smpp_to_gsm411_err(status, &gsm411_cause) < 0)
+		gsm411_cause = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+	gsm411_send_rp_error(trans, cmd->gsm411_msg_ref, gsm411_cause);
+out:
+	smpp_cmd_free(cmd);
+}
+
+static void smpp_deliver_sm_cb(void *data)
+{
+	smpp_cmd_err(data, ESME_RSYSERR);
+}
+
+static int smpp_cmd_enqueue(struct osmo_esme *esme,
+			    struct gsm_subscriber *subscr, struct gsm_sms *sms,
+			    uint32_t sequence_number)
+{
+	struct osmo_smpp_cmd *cmd;
+
+	cmd = talloc_zero(esme, struct osmo_smpp_cmd);
+	if (!cmd)
+		return -1;
+
+	cmd->sequence_nr	= sequence_number;
+	cmd->is_report		= sms->is_report;
+	cmd->gsm411_msg_ref	= sms->gsm411.msg_ref;
+	cmd->gsm411_trans_id	= sms->gsm411.transaction_id;
+	cmd->subscr		= subscr_get(subscr);
+
+	/* FIXME: No predefined value for this response_timer as specified by
+	 * SMPP 3.4 specs, section 7.2. Make this configurable? Don't forget
+	 * lchan keeps busy until we get a reply to this SMPP command. Too high
+	 * value may exhaust resources.
+	 */
+	osmo_timer_setup(&cmd->response_timer, smpp_deliver_sm_cb, cmd);
+	osmo_timer_schedule(&cmd->response_timer, 5, 0);
+	llist_add_tail(&cmd->list, &esme->smpp_cmd_list);
+
+	return 0;
+}
+
+struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme,
+					      uint32_t sequence_nr)
+{
+	struct osmo_smpp_cmd *cmd;
+
+	llist_for_each_entry(cmd, &esme->smpp_cmd_list, list) {
+		if (cmd->sequence_nr == sequence_nr)
+			return cmd;
+	}
+	return NULL;
+}
+
+static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
+			   struct gsm_subscriber_connection *conn)
+{
+	struct deliver_sm_t deliver;
+	int mode, ret;
+	uint8_t dcs;
+
+	memset(&deliver, 0, sizeof(deliver));
+	deliver.command_length	= 0;
+	deliver.command_id	= DELIVER_SM;
+	deliver.command_status	= ESME_ROK;
+
+	strcpy((char *)deliver.service_type, "CMT");
+	if (esme->acl && esme->acl->deliver_src_imsi) {
+		deliver.source_addr_ton	= TON_Subscriber_Number;
+		deliver.source_addr_npi = NPI_Land_Mobile_E212;
+		snprintf((char *)deliver.source_addr,
+			sizeof(deliver.source_addr), "%s",
+			conn->subscr->imsi);
+	} else {
+		deliver.source_addr_ton = TON_Network_Specific;
+		deliver.source_addr_npi = NPI_ISDN_E163_E164;
+		snprintf((char *)deliver.source_addr,
+			 sizeof(deliver.source_addr), "%s",
+			 conn->subscr->extension);
+	}
+
+	deliver.dest_addr_ton	= sms->dst.ton;
+	deliver.dest_addr_npi	= sms->dst.npi;
+	memcpy(deliver.destination_addr, sms->dst.addr,
+		sizeof(deliver.destination_addr));
+
+	if (sms->is_report)
+		deliver.esm_class = SMPP34_DELIVERY_RECEIPT;
+	else
+		deliver.esm_class = SMPP34_DATAGRAM_MODE;
+
+	if (sms->ud_hdr_ind)
+		deliver.esm_class |= SMPP34_UDHI_IND;
+	if (sms->reply_path_req)
+		deliver.esm_class |= SMPP34_REPLY_PATH;
+
+	deliver.protocol_id 	= sms->protocol_id;
+	deliver.priority_flag	= 0;
+	if (sms->status_rep_req)
+		deliver.registered_delivery = SMPP34_DELIVERY_RECEIPT_ON;
+
+	/* Figure out SMPP DCS from TP-DCS */
+	dcs = sms->data_coding_scheme;
+	if (smpp_determine_scheme(dcs, &deliver.data_coding, &mode) == -1)
+		return -1;
+
+	/* Transparently pass on DCS via SMPP if requested */
+	if (esme->acl && esme->acl->dcs_transparent)
+		deliver.data_coding = dcs;
+
+	if (mode == MODE_7BIT) {
+		uint8_t *dst = deliver.short_message;
+
+		/* SMPP has this strange notion of putting 7bit SMS in
+		 * an octet-aligned mode */
+		if (sms->ud_hdr_ind) {
+			/* length (bytes) of UDH inside UD */
+			uint8_t udh_len = sms->user_data[0] + 1;
+
+			/* copy over the UDH */
+			memcpy(dst, sms->user_data, udh_len);
+			dst += udh_len;
+			deliver.sm_length = udh_len;
+		}
+		/* add decoded text */
+		deliver.sm_length += gsm_7bit_expand((char *)dst, sms->user_data, sms->user_data_len, sms->ud_hdr_ind);
+	} else {
+		deliver.sm_length = sms->user_data_len;
+		memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
+	}
+
+	if (esme->acl && esme->acl->osmocom_ext && conn->lchan)
+		append_osmo_tlvs(&deliver.tlv, conn->lchan);
+
+	append_tlv_u16(&deliver.tlv, TLVID_user_message_reference,
+		       sms->msg_ref);
+
+	ret = smpp_tx_deliver(esme, &deliver);
+	if (ret < 0)
+		return ret;
+
+	OSMO_ASSERT(!sms->smpp.esme);
+	smpp_esme_get(esme);
+	sms->smpp.esme = esme;
+
+	return smpp_cmd_enqueue(esme, conn->subscr, sms,
+				deliver.sequence_number);
+}
+
+static struct smsc *g_smsc;
+
+int smpp_route_smpp_first(struct gsm_sms *sms, struct gsm_subscriber_connection *conn)
+{
+	return g_smsc->smpp_first;
+}
+
+int smpp_try_deliver(struct gsm_sms *sms,
+		     struct gsm_subscriber_connection *conn)
+{
+	struct osmo_esme *esme;
+	struct osmo_smpp_addr dst;
+	int rc;
+
+	memset(&dst, 0, sizeof(dst));
+	dst.ton = sms->dst.ton;
+	dst.npi = sms->dst.npi;
+	memcpy(dst.addr, sms->dst.addr, sizeof(dst.addr));
+
+	rc = smpp_route(g_smsc, &dst, &esme);
+	if (!rc)
+		rc = deliver_to_esme(esme, sms, conn);
+
+	return rc;
+}
+
+struct smsc *smsc_from_vty(struct vty *v)
+{
+	/* FIXME: this is ugly */
+	return g_smsc;
+}
+
+/*! \brief Allocate the OpenBSC SMPP interface struct and init VTY. */
+int smpp_openbsc_alloc_init(void *ctx)
+{
+	g_smsc = smpp_smsc_alloc_init(ctx);
+	if (!g_smsc) {
+		LOGP(DSMPP, LOGL_FATAL, "Cannot allocate smsc struct\n");
+		return -1;
+	}
+	return smpp_vty_init();
+}
+
+/*! \brief Launch the OpenBSC SMPP interface with the parameters set from VTY.
+ */
+int smpp_openbsc_start(struct gsm_network *net)
+{
+	int rc;
+	g_smsc->priv = net;
+
+	/* If a VTY configuration has taken place, the values have been stored
+	 * in the smsc struct. Otherwise, use the defaults (NULL -> any, 0 ->
+	 * default SMPP port, see smpp_smsc_bind()). */
+	rc = smpp_smsc_start(g_smsc, g_smsc->bind_addr, g_smsc->listen_port);
+	if (rc < 0)
+		return rc;
+
+	rc = osmo_signal_register_handler(SS_SMS, smpp_sms_cb, g_smsc);
+	if (rc < 0)
+		return rc;
+	rc = osmo_signal_register_handler(SS_SUBSCR, smpp_subscr_cb, g_smsc);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
diff --git a/openbsc/src/libmsc/smpp_smsc.c b/openbsc/src/libmsc/smpp_smsc.c
new file mode 100644
index 0000000..e4acd3a
--- /dev/null
+++ b/openbsc/src/libmsc/smpp_smsc.c
@@ -0,0 +1,1042 @@
+/* SMPP 3.4 interface, SMSC-side implementation */
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+
+#include "smpp_smsc.h"
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+
+/*! \brief Ugly wrapper. libsmpp34 should do this itself! */
+#define SMPP34_UNPACK(rc, type, str, data, len)		\
+	memset(str, 0, sizeof(*str));			\
+	rc = smpp34_unpack(type, str, data, len)
+
+enum emse_bind {
+	ESME_BIND_RX = 0x01,
+	ESME_BIND_TX = 0x02,
+};
+
+const struct value_string smpp_status_strs[] = {
+	{ ESME_ROK,		"No Error" },
+	{ ESME_RINVMSGLEN,	"Message Length is invalid" },
+	{ ESME_RINVCMDLEN,	"Command Length is invalid" },
+	{ ESME_RINVCMDID,	"Invalid Command ID" },
+	{ ESME_RINVBNDSTS,	"Incorrect BIND Status for given command" },
+	{ ESME_RALYBND,		"ESME Already in Bound State" },
+	{ ESME_RINVPRTFLG,	"Invalid Priority Flag" },
+	{ ESME_RINVREGDLVFLG,	"Invalid Registered Delivery Flag" },
+	{ ESME_RSYSERR,		"System Error" },
+	{ ESME_RINVSRCADR,	"Invalid Source Address" },
+	{ ESME_RINVDSTADR,	"Invalid Destination Address" },
+	{ ESME_RINVMSGID,	"Message ID is invalid" },
+	{ ESME_RBINDFAIL,	"Bind failed" },
+	{ ESME_RINVPASWD,	"Invalid Password" },
+	{ ESME_RINVSYSID,	"Invalid System ID" },
+	{ ESME_RCANCELFAIL,	"Cancel SM Failed" },
+	{ ESME_RREPLACEFAIL,	"Replace SM Failed" },
+	{ ESME_RMSGQFUL,	"Message Queue Full" },
+	{ ESME_RINVSERTYP,	"Invalid Service Type" },
+	{ ESME_RINVNUMDESTS,	"Invalid number of destinations" },
+	{ ESME_RINVDLNAME,	"Invalid Distribution List name" },
+	{ ESME_RINVDESTFLAG,	"Destination flag is invalid" },
+	{ ESME_RINVSUBREP,	"Invalid submit with replace request" },
+	{ ESME_RINVESMCLASS,	"Invalid esm_class field data" },
+	{ ESME_RCNTSUBDL,	"Cannot Submit to Distribution List" },
+	{ ESME_RSUBMITFAIL,	"submit_sm or submit_multi failed" },
+	{ ESME_RINVSRCTON,	"Invalid Source address TON" },
+	{ ESME_RINVSRCNPI,	"Invalid Sourec address NPI" },
+	{ ESME_RINVDSTTON,	"Invalid Destination address TON" },
+	{ ESME_RINVDSTNPI,	"Invalid Desetination address NPI" },
+	{ ESME_RINVSYSTYP,	"Invalid system_type field" },
+	{ ESME_RINVREPFLAG,	"Invalid replace_if_present field" },
+	{ ESME_RINVNUMMSGS,	"Invalid number of messages" },
+	{ ESME_RTHROTTLED,	"Throttling error (ESME has exceeded message limits)" },
+	{ ESME_RINVSCHED,	"Invalid Scheduled Delivery Time" },
+	{ ESME_RINVEXPIRY,	"Invalid message validity period (Expiry time)" },
+	{ ESME_RINVDFTMSGID,	"Predefined Message Invalid or Not Found" },
+	{ ESME_RX_T_APPN,	"ESME Receiver Temporary App Error Code" },
+	{ ESME_RX_P_APPN,	"ESME Receiver Permanent App Error Code" },
+	{ ESME_RX_R_APPN,	"ESME Receiver Reject Message Error Code" },
+	{ ESME_RQUERYFAIL,	"query_sm request failed" },
+	{ ESME_RINVOPTPARSTREAM,"Error in the optional part of the PDU Body" },
+	{ ESME_ROPTPARNOTALLWD,	"Optional Parameter not allowed" },
+	{ ESME_RINVPARLEN,	"Invalid Parameter Length" },
+	{ ESME_RMISSINGOPTPARAM,"Expected Optional Parameter missing" },
+	{ ESME_RINVOPTPARAMVAL,	"Invalid Optional Parameter Value" },
+	{ ESME_RDELIVERYFAILURE,"Delivery Failure (used for data_sm_resp)" },
+	{ ESME_RUNKNOWNERR,	"Unknown Error" },
+	{ 0, NULL }
+};
+
+/*! \brief compare if two SMPP addresses are equal */
+int smpp_addr_eq(const struct osmo_smpp_addr *a,
+		 const struct osmo_smpp_addr *b)
+{
+	if (a->ton == b->ton &&
+	    a->npi == b->npi &&
+	    !strcmp(a->addr, b->addr))
+		return 1;
+
+	return 0;
+}
+
+
+struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
+					    const char *sys_id)
+{
+	struct osmo_smpp_acl *acl;
+
+	llist_for_each_entry(acl, &smsc->acl_list, list) {
+		if (!strcmp(acl->system_id, sys_id))
+			return acl;
+	}
+
+	return NULL;
+}
+
+struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id)
+{
+	struct osmo_smpp_acl *acl;
+
+	if (strlen(sys_id) > SMPP_SYS_ID_LEN)
+		return NULL;
+
+	if (smpp_acl_by_system_id(smsc, sys_id))
+		return NULL;
+
+	acl = talloc_zero(smsc, struct osmo_smpp_acl);
+	if (!acl)
+		return NULL;
+
+	acl->smsc = smsc;
+	strcpy(acl->system_id, sys_id);
+	INIT_LLIST_HEAD(&acl->route_list);
+
+	llist_add_tail(&acl->list, &smsc->acl_list);
+
+	return acl;
+}
+
+void smpp_acl_delete(struct osmo_smpp_acl *acl)
+{
+	struct osmo_smpp_route *r, *r2;
+
+	llist_del(&acl->list);
+
+	/* kill any active ESMEs */
+	if (acl->esme) {
+		struct osmo_esme *esme = acl->esme;
+		osmo_fd_unregister(&esme->wqueue.bfd);
+		close(esme->wqueue.bfd.fd);
+		esme->wqueue.bfd.fd = -1;
+		esme->acl = NULL;
+		smpp_esme_put(esme);
+	}
+
+	/* delete all routes for this ACL */
+	llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
+		llist_del(&r->list);
+		llist_del(&r->global_list);
+		talloc_free(r);
+	}
+
+	talloc_free(acl);
+}
+
+static struct osmo_smpp_route *route_alloc(struct osmo_smpp_acl *acl)
+{
+	struct osmo_smpp_route *r;
+
+	r = talloc_zero(acl, struct osmo_smpp_route);
+	if (!r)
+		return NULL;
+
+	llist_add_tail(&r->list, &acl->route_list);
+	llist_add_tail(&r->global_list, &acl->smsc->route_list);
+
+	return r;
+}
+
+int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
+			const struct osmo_smpp_addr *pfx)
+{
+	struct osmo_smpp_route *r;
+
+	llist_for_each_entry(r, &acl->route_list, list) {
+		if (r->type == SMPP_ROUTE_PREFIX &&
+		    smpp_addr_eq(&r->u.prefix, pfx))
+			return -EEXIST;
+	}
+
+	r = route_alloc(acl);
+	if (!r)
+		return -ENOMEM;
+	r->type = SMPP_ROUTE_PREFIX;
+	r->acl = acl;
+	memcpy(&r->u.prefix, pfx, sizeof(r->u.prefix));
+
+	return 0;
+}
+
+int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
+		       const struct osmo_smpp_addr *pfx)
+{
+	struct osmo_smpp_route *r, *r2;
+
+	llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
+		if (r->type == SMPP_ROUTE_PREFIX &&
+		    smpp_addr_eq(&r->u.prefix, pfx)) {
+			llist_del(&r->list);
+			talloc_free(r);
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+
+/*! \brief increaes the use/reference count */
+void smpp_esme_get(struct osmo_esme *esme)
+{
+	esme->use++;
+}
+
+static void esme_destroy(struct osmo_esme *esme)
+{
+	osmo_wqueue_clear(&esme->wqueue);
+	if (esme->wqueue.bfd.fd >= 0) {
+		osmo_fd_unregister(&esme->wqueue.bfd);
+		close(esme->wqueue.bfd.fd);
+	}
+	smpp_cmd_flush_pending(esme);
+	llist_del(&esme->list);
+	talloc_free(esme);
+}
+
+static uint32_t esme_inc_seq_nr(struct osmo_esme *esme)
+{
+	esme->own_seq_nr++;
+	if (esme->own_seq_nr > 0x7fffffff)
+		esme->own_seq_nr = 1;
+
+	return esme->own_seq_nr;
+}
+
+/*! \brief decrease the use/reference count, free if it is 0 */
+void smpp_esme_put(struct osmo_esme *esme)
+{
+	esme->use--;
+	if (esme->use <= 0)
+		esme_destroy(esme);
+}
+
+/*! \brief try to find a SMPP route (ESME) for given destination */
+int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **pesme)
+{
+	struct osmo_smpp_route *r;
+	struct osmo_smpp_acl *acl = NULL;
+
+	DEBUGP(DSMPP, "Looking up route for (%u/%u/%s)\n",
+		dest->ton, dest->npi, dest->addr);
+
+	/* search for a specific route */
+	llist_for_each_entry(r, &smsc->route_list, global_list) {
+		switch (r->type) {
+		case SMPP_ROUTE_PREFIX:
+			DEBUGP(DSMPP, "Checking prefix route (%u/%u/%s)->%s\n",
+				r->u.prefix.ton, r->u.prefix.npi, r->u.prefix.addr,
+				r->acl->system_id);
+			if (r->u.prefix.ton == dest->ton &&
+			    r->u.prefix.npi == dest->npi &&
+			    !strncmp(r->u.prefix.addr, dest->addr,
+				     strlen(r->u.prefix.addr))) {
+				DEBUGP(DSMPP, "Found prefix route ACL\n");
+				acl = r->acl;
+			}
+			break;
+		default:
+			break;
+		}
+
+		if (acl)
+			break;
+	}
+
+	if (!acl) {
+		/* check for default route */
+		if (smsc->def_route) {
+			DEBUGP(DSMPP, "Using existing default route\n");
+			acl = smsc->def_route;
+		}
+	}
+
+	if (acl && acl->esme) {
+		struct osmo_esme *esme;
+		DEBUGP(DSMPP, "ACL even has ESME, we can route to it!\n");
+		esme = acl->esme;
+		if (esme->bind_flags & ESME_BIND_RX) {
+			*pesme = esme;
+			return 0;
+		} else
+			LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, "
+			     "but not bound for Rx, discarding MO SMS\n",
+				     esme->system_id);
+	}
+
+	*pesme = NULL;
+	if (acl)
+		return GSM48_CC_CAUSE_NETWORK_OOO;
+	else
+		return GSM48_CC_CAUSE_UNASSIGNED_NR;
+}
+
+
+/*! \brief initialize the libsmpp34 data structure for a response */
+#define INIT_RESP(type, resp, req) 		{ \
+	memset((resp), 0, sizeof(*(resp)));	  \
+	(resp)->command_length	= 0;		  \
+	(resp)->command_id	= type;		  \
+	(resp)->command_status	= ESME_ROK;	  \
+	(resp)->sequence_number	= (req)->sequence_number;	\
+}
+
+/*! \brief pack a libsmpp34 data strcutrure and send it to the ESME */
+#define PACK_AND_SEND(esme, ptr)	pack_and_send(esme, (ptr)->command_id, ptr)
+static int pack_and_send(struct osmo_esme *esme, uint32_t type, void *ptr)
+{
+	struct msgb *msg = msgb_alloc(4096, "SMPP_Tx");
+	int rc, rlen;
+	if (!msg)
+		return -ENOMEM;
+
+	rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr);
+	if (rc != 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n",
+		     esme->system_id, smpp34_strerror);
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msgb_put(msg, rlen);
+
+	if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n",
+		     esme->system_id);
+		msgb_free(msg);
+		return -EAGAIN;
+	}
+	return 0;
+}
+
+/*! \brief transmit a generic NACK to a remote ESME */
+static int smpp_tx_gen_nack(struct osmo_esme *esme, uint32_t seq, uint32_t status)
+{
+	struct generic_nack_t nack;
+	char buf[SMALL_BUFF];
+
+	nack.command_length = 0;
+	nack.command_id = GENERIC_NACK;
+	nack.sequence_number = seq;
+	nack.command_status = status;
+
+	LOGP(DSMPP, LOGL_ERROR, "[%s] Tx GENERIC NACK: %s\n",
+	     esme->system_id, str_command_status(status, buf));
+
+	return PACK_AND_SEND(esme, &nack);
+}
+
+/*! \brief retrieve SMPP command ID from a msgb */
+static inline uint32_t smpp_msgb_cmdid(struct msgb *msg)
+{
+	uint8_t *tmp = msgb_data(msg) + 4;
+	return ntohl(*(uint32_t *)tmp);
+}
+
+/*! \brief retrieve SMPP sequence number from a msgb */
+static inline uint32_t smpp_msgb_seq(struct msgb *msg)
+{
+	uint8_t *tmp = msgb_data(msg);
+	return ntohl(*(uint32_t *)tmp);
+}
+
+/*! \brief handle an incoming SMPP generic NACK */
+static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct generic_nack_t nack;
+	char buf[SMALL_BUFF];
+	int rc;
+
+	SMPP34_UNPACK(rc, GENERIC_NACK, &nack, msgb_data(msg),
+			 msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	LOGP(DSMPP, LOGL_ERROR, "[%s] Rx GENERIC NACK: %s\n",
+	     esme->system_id, str_command_status(nack.command_status, buf));
+
+	return 0;
+}
+
+static int _process_bind(struct osmo_esme *esme, uint8_t if_version,
+			 uint32_t bind_flags, const char *sys_id,
+			 const char *passwd)
+{
+	struct osmo_smpp_acl *acl;
+
+	if (if_version != SMPP_VERSION)
+		return ESME_RSYSERR;
+
+	if (esme->bind_flags)
+		return ESME_RALYBND;
+
+	esme->smpp_version = if_version;
+	snprintf(esme->system_id, sizeof(esme->system_id), "%s", sys_id);
+
+	acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
+	if (!esme->smsc->accept_all) {
+		if (!acl) {
+			/* This system is unknown */
+			return ESME_RINVSYSID;
+		} else {
+			if (strlen(acl->passwd) &&
+			    strcmp(acl->passwd, passwd)) {
+				return ESME_RINVPASWD;
+			}
+		}
+	}
+	if (acl) {
+		esme->acl = acl;
+		acl->esme = esme;
+	}
+
+	esme->bind_flags = bind_flags;
+
+	return ESME_ROK;
+}
+
+
+/*! \brief handle an incoming SMPP BIND RECEIVER */
+static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct bind_receiver_t bind;
+	struct bind_receiver_resp_t bind_r;
+	int rc;
+
+	SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind);
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Rx from (Version %02x)\n",
+		bind.system_id, bind.interface_version);
+
+	rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX,
+			   (const char *)bind.system_id, (const char *)bind.password);
+	bind_r.command_status = rc;
+
+	return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP BIND TRANSMITTER */
+static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct bind_transmitter_t bind;
+	struct bind_transmitter_resp_t bind_r;
+	struct tlv_t tlv;
+	int rc;
+
+	SMPP34_UNPACK(rc, BIND_TRANSMITTER, &bind, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind);
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Tx (Version %02x)\n",
+		bind.system_id, bind.interface_version);
+
+	rc = _process_bind(esme, bind.interface_version, ESME_BIND_TX,
+			   (const char *)bind.system_id, (const char *)bind.password);
+	bind_r.command_status = rc;
+
+	/* build response */
+	snprintf((char *)bind_r.system_id, sizeof(bind_r.system_id), "%s",
+		 esme->smsc->system_id);
+
+	/* add interface version TLV */
+	tlv.tag = TLVID_sc_interface_version;
+	tlv.length = sizeof(uint8_t);
+	tlv.value.val16 = esme->smpp_version;
+	build_tlv(&bind_r.tlv, &tlv);
+
+	return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP BIND TRANSCEIVER */
+static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct bind_transceiver_t bind;
+	struct bind_transceiver_resp_t bind_r;
+	int rc;
+
+	SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	INIT_RESP(BIND_TRANSCEIVER_RESP, &bind_r, &bind);
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Trx (Version %02x)\n",
+		bind.system_id, bind.interface_version);
+
+	rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX|ESME_BIND_TX,
+			   (const char *)bind.system_id, (const char *)bind.password);
+	bind_r.command_status = rc;
+
+	return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP UNBIND */
+static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct unbind_t unbind;
+	struct unbind_resp_t unbind_r;
+	int rc;
+
+	SMPP34_UNPACK(rc, UNBIND, &unbind, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	INIT_RESP(UNBIND_RESP, &unbind_r, &unbind);
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx UNBIND\n", esme->system_id);
+
+	if (esme->bind_flags == 0) {
+		unbind_r.command_status = ESME_RINVBNDSTS;
+		goto err;
+	}
+
+	esme->bind_flags = 0;
+err:
+	return PACK_AND_SEND(esme, &unbind_r);
+}
+
+/*! \brief handle an incoming SMPP ENQUIRE LINK */
+static int smpp_handle_enq_link(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct enquire_link_t enq;
+	struct enquire_link_resp_t enq_r;
+	int rc;
+
+	SMPP34_UNPACK(rc, ENQUIRE_LINK, &enq, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	LOGP(DSMPP, LOGL_DEBUG, "[%s] Rx Enquire Link\n", esme->system_id);
+
+	INIT_RESP(ENQUIRE_LINK_RESP, &enq_r, &enq);
+
+	LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx Enquire Link Response\n", esme->system_id);
+
+	return PACK_AND_SEND(esme, &enq_r);
+}
+
+/*! \brief send a SUBMIT-SM RESPONSE to a remote ESME */
+int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr,
+		     uint32_t command_status, char *msg_id)
+{
+	struct submit_sm_resp_t submit_r;
+
+	memset(&submit_r, 0, sizeof(submit_r));
+	submit_r.command_length	= 0;
+	submit_r.command_id	= SUBMIT_SM_RESP;
+	submit_r.command_status	= command_status;
+	submit_r.sequence_number= sequence_nr;
+	snprintf((char *) submit_r.message_id, sizeof(submit_r.message_id), "%s", msg_id);
+
+	return PACK_AND_SEND(esme, &submit_r);
+}
+
+static const struct value_string smpp_avail_strs[] = {
+	{ 0,	"Available" },
+	{ 1,	"Denied" },
+	{ 2,	"Unavailable" },
+	{ 0,	NULL }
+};
+
+/*! \brief send an ALERT_NOTIFICATION to a remote ESME */
+int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
+		  const char *addr, uint8_t avail_status)
+{
+	struct alert_notification_t alert;
+	struct tlv_t tlv;
+
+	memset(&alert, 0, sizeof(alert));
+	alert.command_length	= 0;
+	alert.command_id	= ALERT_NOTIFICATION;
+	alert.command_status	= ESME_ROK;
+	alert.sequence_number	= esme_inc_seq_nr(esme);
+	alert.source_addr_ton 	= ton;
+	alert.source_addr_npi	= npi;
+	snprintf((char *)alert.source_addr, sizeof(alert.source_addr), "%s", addr);
+
+	tlv.tag = TLVID_ms_availability_status;
+	tlv.length = sizeof(uint8_t);
+	tlv.value.val08 = avail_status;
+	build_tlv(&alert.tlv, &tlv);
+
+	LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx ALERT_NOTIFICATION (%s/%u/%u): %s\n",
+		esme->system_id, alert.source_addr, alert.source_addr_ton,
+		alert.source_addr_npi,
+		get_value_string(smpp_avail_strs, avail_status));
+
+	return PACK_AND_SEND(esme, &alert);
+}
+
+/* \brief send a DELIVER-SM message to given ESME */
+int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver)
+{
+	deliver->sequence_number = esme_inc_seq_nr(esme);
+
+	LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx DELIVER-SM (from %s)\n",
+		esme->system_id, deliver->source_addr);
+
+	return PACK_AND_SEND(esme, deliver);
+}
+
+/*! \brief handle an incoming SMPP DELIVER-SM RESPONSE */
+static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct deliver_sm_resp_t deliver_r;
+	struct osmo_smpp_cmd *cmd;
+	int rc;
+
+	memset(&deliver_r, 0, sizeof(deliver_r));
+	SMPP34_UNPACK(rc, DELIVER_SM_RESP, &deliver_r, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	cmd = smpp_cmd_find_by_seqnum(esme, deliver_r.sequence_number);
+	if (!cmd) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Rx DELIVER-SM RESP !? (%s)\n",
+			esme->system_id, get_value_string(smpp_status_strs,
+						  deliver_r.command_status));
+		return -1;
+	}
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx DELIVER-SM RESP (%s)\n",
+		esme->system_id, get_value_string(smpp_status_strs,
+						  deliver_r.command_status));
+
+	if (deliver_r.command_status == ESME_ROK)
+		smpp_cmd_ack(cmd);
+	else
+		smpp_cmd_err(cmd, deliver_r.command_status);
+
+	return 0;
+}
+
+/*! \brief handle an incoming SMPP SUBMIT-SM */
+static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct submit_sm_t submit;
+	struct submit_sm_resp_t submit_r;
+	int rc;
+
+	memset(&submit, 0, sizeof(submit));
+	SMPP34_UNPACK(rc, SUBMIT_SM, &submit, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit);
+
+	if (!(esme->bind_flags & ESME_BIND_TX)) {
+		submit_r.command_status = ESME_RINVBNDSTS;
+		return PACK_AND_SEND(esme, &submit_r);
+	}
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx SUBMIT-SM (%s/%u/%u)\n",
+		esme->system_id, submit.destination_addr,
+		submit.dest_addr_ton, submit.dest_addr_npi);
+
+	INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit);
+
+	rc = handle_smpp_submit(esme, &submit, &submit_r);
+	if (rc == 0)
+		return PACK_AND_SEND(esme, &submit_r);
+
+	return rc;
+}
+
+/*! \brief one complete SMPP PDU from the ESME has been received */
+static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses)
+{
+	uint32_t cmd_id = smpp_msgb_cmdid(msg);
+	int rc = 0;
+
+	LOGP(DSMPP, LOGL_DEBUG, "[%s] smpp_pdu_rx(%s)\n", esme->system_id,
+	     osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+	switch (cmd_id) {
+	case GENERIC_NACK:
+		rc = smpp_handle_gen_nack(esme, msg);
+		break;
+	case BIND_RECEIVER:
+		rc = smpp_handle_bind_rx(esme, msg);
+		break;
+	case BIND_TRANSMITTER:
+		rc = smpp_handle_bind_tx(esme, msg);
+		break;
+	case BIND_TRANSCEIVER:
+		rc = smpp_handle_bind_trx(esme, msg);
+		break;
+	case UNBIND:
+		rc = smpp_handle_unbind(esme, msg);
+		break;
+	case ENQUIRE_LINK:
+		rc = smpp_handle_enq_link(esme, msg);
+		break;
+	case SUBMIT_SM:
+		rc = smpp_handle_submit(esme, msg);
+		break;
+	case DELIVER_SM_RESP:
+		rc = smpp_handle_deliver_resp(esme, msg);
+		break;
+	case DELIVER_SM:
+		break;
+	case DATA_SM:
+		break;
+	case CANCEL_SM:
+	case QUERY_SM:
+	case REPLACE_SM:
+	case SUBMIT_MULTI:
+		LOGP(DSMPP, LOGL_NOTICE, "[%s] Unimplemented PDU Command "
+		     "0x%08x\n", esme->system_id, cmd_id);
+		break;
+	default:
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Unknown PDU Command 0x%08x\n",
+		     esme->system_id, cmd_id);
+		rc = smpp_tx_gen_nack(esme, smpp_msgb_seq(msg), ESME_RINVCMDID);
+		break;
+	}
+
+	return rc;
+}
+
+/* This macro should be called after a call to read() in the read_cb of an
+ * osmo_fd to properly check for errors.
+ * rc is the return value of read, err_label is the label to jump to in case of
+ * an error. The code there should handle closing the connection.
+ * FIXME: This code should go in libosmocore utils.h so it can be used by other
+ * projects as well.
+ * */
+#define OSMO_FD_CHECK_READ(rc, err_label) \
+	if (rc < 0) { \
+		/* EINTR is a non-fatal error, just try again */ \
+		if (errno == EINTR) \
+			return 0; \
+		goto err_label; \
+	} else if (rc == 0) { \
+		goto err_label; \
+	}
+
+/* !\brief call-back when per-ESME TCP socket has some data to be read */
+static int esme_link_read_cb(struct osmo_fd *ofd)
+{
+	struct osmo_esme *esme = ofd->data;
+	uint32_t len;
+	uint8_t *lenptr = (uint8_t *) &len;
+	uint8_t *cur;
+	struct msgb *msg;
+	ssize_t rdlen, rc;
+
+	switch (esme->read_state) {
+	case READ_ST_IN_LEN:
+		rdlen = sizeof(uint32_t) - esme->read_idx;
+		rc = read(ofd->fd, lenptr + esme->read_idx, rdlen);
+		if (rc < 0)
+			LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n",
+					esme->system_id, rc, strerror(errno));
+		OSMO_FD_CHECK_READ(rc, dead_socket);
+
+		esme->read_idx += rc;
+
+		if (esme->read_idx >= sizeof(uint32_t)) {
+			esme->read_len = ntohl(len);
+			if (esme->read_len < 8 || esme->read_len > UINT16_MAX) {
+				LOGP(DSMPP, LOGL_ERROR, "[%s] length invalid %u\n",
+						esme->system_id, esme->read_len);
+				goto dead_socket;
+			}
+
+			msg = msgb_alloc(esme->read_len, "SMPP Rx");
+			if (!msg)
+				return -ENOMEM;
+			esme->read_msg = msg;
+			cur = msgb_put(msg, sizeof(uint32_t));
+			memcpy(cur, lenptr, sizeof(uint32_t));
+			esme->read_state = READ_ST_IN_MSG;
+			esme->read_idx = sizeof(uint32_t);
+		}
+		break;
+	case READ_ST_IN_MSG:
+		msg = esme->read_msg;
+		rdlen = esme->read_len - esme->read_idx;
+		rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg)));
+		if (rc < 0)
+			LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n",
+					esme->system_id, rc, strerror(errno));
+		OSMO_FD_CHECK_READ(rc, dead_socket);
+
+		esme->read_idx += rc;
+		msgb_put(msg, rc);
+
+		if (esme->read_idx >= esme->read_len) {
+			rc = smpp_pdu_rx(esme, esme->read_msg);
+			msgb_free(esme->read_msg);
+			esme->read_msg = NULL;
+			esme->read_idx = 0;
+			esme->read_len = 0;
+			esme->read_state = READ_ST_IN_LEN;
+		}
+		break;
+	}
+
+	return 0;
+dead_socket:
+	msgb_free(esme->read_msg);
+	osmo_fd_unregister(&esme->wqueue.bfd);
+	close(esme->wqueue.bfd.fd);
+	esme->wqueue.bfd.fd = -1;
+	smpp_esme_put(esme);
+
+	return 0;
+}
+
+/* call-back of write queue once it wishes to write a message to the socket */
+static int esme_link_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+	struct osmo_esme *esme = ofd->data;
+	int rc;
+
+	rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+	if (rc == 0) {
+		osmo_fd_unregister(&esme->wqueue.bfd);
+		close(esme->wqueue.bfd.fd);
+		esme->wqueue.bfd.fd = -1;
+		smpp_esme_put(esme);
+	} else if (rc < msgb_length(msg)) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id);
+		return -1;
+	}
+
+	return 0;
+}
+
+/* callback for already-accepted new TCP socket */
+static int link_accept_cb(struct smsc *smsc, int fd,
+			  struct sockaddr_storage *s, socklen_t s_len)
+{
+	struct osmo_esme *esme = talloc_zero(smsc, struct osmo_esme);
+	if (!esme) {
+		close(fd);
+		return -ENOMEM;
+	}
+
+	INIT_LLIST_HEAD(&esme->smpp_cmd_list);
+	smpp_esme_get(esme);
+	esme->own_seq_nr = rand();
+	esme_inc_seq_nr(esme);
+	esme->smsc = smsc;
+	osmo_wqueue_init(&esme->wqueue, 10);
+	esme->wqueue.bfd.fd = fd;
+	esme->wqueue.bfd.data = esme;
+	esme->wqueue.bfd.when = BSC_FD_READ;
+
+	if (osmo_fd_register(&esme->wqueue.bfd) != 0) {
+		close(fd);
+		talloc_free(esme);
+		return -EIO;
+	}
+
+	esme->wqueue.read_cb = esme_link_read_cb;
+	esme->wqueue.write_cb = esme_link_write_cb;
+
+	esme->sa_len = OSMO_MIN(sizeof(esme->sa), s_len);
+	memcpy(&esme->sa, s, esme->sa_len);
+
+	llist_add_tail(&esme->list, &smsc->esme_list);
+
+	return 0;
+}
+
+/* callback of listening TCP socket */
+static int smsc_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	int rc;
+	struct sockaddr_storage sa;
+	socklen_t sa_len = sizeof(sa);
+
+	rc = accept(ofd->fd, (struct sockaddr *)&sa, &sa_len);
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "Accept returns %d (%s)\n",
+		     rc, strerror(errno));
+		return rc;
+	}
+	return link_accept_cb(ofd->data, rc, &sa, sa_len);
+}
+
+/*! \brief allocate and initialize an smsc struct from talloc context ctx. */
+struct smsc *smpp_smsc_alloc_init(void *ctx)
+{
+	struct smsc *smsc = talloc_zero(ctx, struct smsc);
+
+	INIT_LLIST_HEAD(&smsc->esme_list);
+	INIT_LLIST_HEAD(&smsc->acl_list);
+	INIT_LLIST_HEAD(&smsc->route_list);
+
+	smsc->listen_ofd.data = smsc;
+	smsc->listen_ofd.cb = smsc_fd_cb;
+
+	return smsc;
+}
+
+/*! \brief Set the SMPP address and port without binding. */
+int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+	smsc->listen_port = port;
+
+	/* Avoid use-after-free if bind_addr == smsc->bind_addr */
+	if (smsc->bind_addr == bind_addr)
+		return 0;
+
+	talloc_free((void*)smsc->bind_addr);
+	smsc->bind_addr = NULL;
+	if (bind_addr) {
+		smsc->bind_addr = bind_addr ? talloc_strdup(smsc, bind_addr) : NULL;
+		if (!smsc->bind_addr)
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/*! \brief Bind to given address and port and accept connections.
+ * \param[in] bind_addr Local IP address, may be NULL for any.
+ * \param[in] port TCP port number, may be 0 for default SMPP (2775).
+ */
+int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+	int rc;
+
+	/* default port for SMPP */
+	if (!port)
+		port = 2775;
+
+	smpp_smsc_stop(smsc);
+
+	LOGP(DSMPP, LOGL_NOTICE, "SMPP at %s %d\n",
+	     bind_addr? bind_addr : "0.0.0.0", port);
+
+	rc = osmo_sock_init_ofd(&smsc->listen_ofd, AF_UNSPEC, SOCK_STREAM,
+				IPPROTO_TCP, bind_addr, port,
+				OSMO_SOCK_F_BIND);
+	if (rc < 0)
+		return rc;
+
+	/* store new address and port */
+	rc = smpp_smsc_conf(smsc, bind_addr, port);
+	if (rc)
+		smpp_smsc_stop(smsc);
+	return rc;
+}
+
+/*! \brief Change a running connection to a different address/port, and upon
+ * error switch back to the running configuration. */
+int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+	int rc;
+
+	rc = smpp_smsc_start(smsc, bind_addr, port);
+	if (rc)
+		/* if there is an error, try to re-bind to the old port */
+		return smpp_smsc_start(smsc, smsc->bind_addr, smsc->listen_port);
+	return 0;
+}
+
+/*! /brief Close SMPP connection. */
+void smpp_smsc_stop(struct smsc *smsc)
+{
+	if (smsc->listen_ofd.fd > 0) {
+		close(smsc->listen_ofd.fd);
+		smsc->listen_ofd.fd = 0;
+		osmo_fd_unregister(&smsc->listen_ofd);
+	}
+}
diff --git a/openbsc/src/libmsc/smpp_smsc.h b/openbsc/src/libmsc/smpp_smsc.h
new file mode 100644
index 0000000..257383f
--- /dev/null
+++ b/openbsc/src/libmsc/smpp_smsc.h
@@ -0,0 +1,167 @@
+#ifndef _SMPP_SMSC_H
+#define _SMPP_SMSC_H
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#define SMPP_SYS_ID_LEN	16
+#define SMPP_PASSWD_LEN	16
+
+#define MODE_7BIT	7
+#define MODE_8BIT	8
+
+enum esme_read_state {
+	READ_ST_IN_LEN = 0,
+	READ_ST_IN_MSG = 1,
+};
+
+struct osmo_smpp_acl;
+
+struct osmo_smpp_addr {
+	uint8_t ton;
+	uint8_t npi;
+	char addr[21+1];
+};
+
+struct osmo_esme {
+	struct llist_head list;
+	struct smsc *smsc;
+	struct osmo_smpp_acl *acl;
+	int use;
+
+	struct llist_head smpp_cmd_list;
+
+	uint32_t own_seq_nr;
+
+	struct osmo_wqueue wqueue;
+	struct sockaddr_storage sa;
+	socklen_t sa_len;
+
+	enum esme_read_state read_state;
+	uint32_t read_len;
+	uint32_t read_idx;
+	struct msgb *read_msg;
+
+	uint8_t smpp_version;
+	char system_id[SMPP_SYS_ID_LEN+1];
+
+	uint8_t bind_flags;
+};
+
+struct osmo_smpp_acl {
+	struct llist_head list;
+	struct smsc *smsc;
+	struct osmo_esme *esme;
+	char *description;
+	char system_id[SMPP_SYS_ID_LEN+1];
+	char passwd[SMPP_PASSWD_LEN+1];
+	int default_route;
+	int deliver_src_imsi;
+	int osmocom_ext;
+	int dcs_transparent;
+	struct llist_head route_list;
+};
+
+enum osmo_smpp_rtype {
+	SMPP_ROUTE_NONE,
+	SMPP_ROUTE_PREFIX,
+};
+
+struct osmo_smpp_route {
+	struct llist_head list;	/*!< in acl.route_list */
+	struct llist_head global_list; /*!< in smsc->route_list */
+	struct osmo_smpp_acl *acl;
+	enum osmo_smpp_rtype type;
+	union {
+		struct osmo_smpp_addr prefix;
+	} u;
+};
+
+struct osmo_smpp_cmd {
+	struct llist_head	list;
+	struct gsm_subscriber	*subscr;
+	uint32_t		sequence_nr;
+	uint32_t		gsm411_msg_ref;
+	uint8_t			gsm411_trans_id;
+	bool			is_report;
+	struct osmo_timer_list	response_timer;
+};
+
+struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme,
+					      uint32_t sequence_number);
+void smpp_cmd_ack(struct osmo_smpp_cmd *cmd);
+void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status);
+void smpp_cmd_flush_pending(struct osmo_esme *esme);
+
+struct smsc {
+	struct osmo_fd listen_ofd;
+	struct llist_head esme_list;
+	struct llist_head acl_list;
+	struct llist_head route_list;
+	const char *bind_addr;
+	uint16_t listen_port;
+	char system_id[SMPP_SYS_ID_LEN+1];
+	int accept_all;
+	int smpp_first;
+	struct osmo_smpp_acl *def_route;
+	void *priv;
+};
+
+int smpp_addr_eq(const struct osmo_smpp_addr *a,
+		 const struct osmo_smpp_addr *b);
+
+struct smsc *smpp_smsc_alloc_init(void *ctx);
+int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port);
+int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port);
+int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port);
+void smpp_smsc_stop(struct smsc *smsc);
+
+void smpp_esme_get(struct osmo_esme *esme);
+void smpp_esme_put(struct osmo_esme *esme);
+
+int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **emse);
+
+struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id);
+struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
+					    const char *sys_id);
+void smpp_acl_delete(struct osmo_smpp_acl *acl);
+
+int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr,
+		     uint32_t command_status, char *msg_id);
+
+int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
+		  const char *addr, uint8_t avail_status);
+
+int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver);
+
+int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
+			struct submit_sm_resp_t *submit_r);
+
+int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
+		       const struct osmo_smpp_addr *pfx);
+int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
+		       const struct osmo_smpp_addr *pfx);
+
+int smpp_vty_init(void);
+
+int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode);
+
+
+
+struct gsm_sms;
+struct gsm_subscriber_connection;
+
+int smpp_route_smpp_first(struct gsm_sms *sms,
+			    struct gsm_subscriber_connection *conn);
+int smpp_try_deliver(struct gsm_sms *sms,
+		     struct gsm_subscriber_connection *conn);
+#endif
diff --git a/openbsc/src/libmsc/smpp_utils.c b/openbsc/src/libmsc/smpp_utils.c
new file mode 100644
index 0000000..d0850d8
--- /dev/null
+++ b/openbsc/src/libmsc/smpp_utils.c
@@ -0,0 +1,62 @@
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "smpp_smsc.h"
+#include <openbsc/debug.h>
+
+
+int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode)
+{
+	if ((dcs & 0xF0) == 0xF0) {
+		if (dcs & 0x04) {
+			/* bit 2 == 1: 8bit data */
+			*data_coding = 0x02;
+			*mode = MODE_8BIT;
+		} else {
+			/* bit 2 == 0: default alphabet */
+			*data_coding = 0x01;
+			*mode = MODE_7BIT;
+		}
+	} else if ((dcs & 0xE0) == 0) {
+		switch (dcs & 0xC) {
+		case 0:
+			*data_coding = 0x01;
+			*mode = MODE_7BIT;
+			break;
+		case 4:
+			*data_coding = 0x02;
+			*mode = MODE_8BIT;
+			break;
+		case 8:
+			*data_coding = 0x08;     /* UCS-2 */
+			*mode = MODE_8BIT;
+			break;
+		default:
+			goto unknown_mo;
+		}
+	} else {
+unknown_mo:
+		LOGP(DLSMS, LOGL_ERROR, "SMPP MO Unknown Data Coding 0x%02x\n", dcs);
+		return -1;
+	}
+
+	return 0;
+
+}
diff --git a/openbsc/src/libmsc/smpp_vty.c b/openbsc/src/libmsc/smpp_vty.c
new file mode 100644
index 0000000..0a84358
--- /dev/null
+++ b/openbsc/src/libmsc/smpp_vty.c
@@ -0,0 +1,610 @@
+/* SMPP vty interface */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+
+#include <openbsc/vty.h>
+
+#include "smpp_smsc.h"
+
+struct smsc *smsc_from_vty(struct vty *v);
+
+static struct cmd_node smpp_node = {
+	SMPP_NODE,
+	"%s(config-smpp)# ",
+	1,
+};
+
+static struct cmd_node esme_node = {
+	SMPP_ESME_NODE,
+	"%s(config-smpp-esme)# ",
+	1,
+};
+
+DEFUN(cfg_smpp, cfg_smpp_cmd,
+	"smpp", "Configure SMPP SMS Interface")
+{
+	vty->node = SMPP_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_first, cfg_smpp_first_cmd,
+	"smpp-first",
+	"Try SMPP routes before the subscriber DB\n")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	smsc->smpp_first = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_smpp_first, cfg_no_smpp_first_cmd,
+	"no smpp-first",
+	NO_STR "Try SMPP before routes before the subscriber DB\n")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	smsc->smpp_first = 0;
+	return CMD_SUCCESS;
+}
+
+static int smpp_local_tcp(struct vty *vty,
+			  const char *bind_addr, uint16_t port)
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	int is_running = smsc->listen_ofd.fd > 0;
+	int same_bind_addr;
+	int rc;
+
+	/* If it is not up yet, don't rebind, just set values. */
+	if (!is_running) {
+		rc = smpp_smsc_conf(smsc, bind_addr, port);
+		if (rc < 0) {
+			vty_out(vty, "%% Cannot configure new address:port%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		return CMD_SUCCESS;
+	}
+
+	rc = smpp_smsc_restart(smsc, bind_addr, port);
+	if (rc < 0) {
+		vty_out(vty, "%% Cannot bind to new port %s:%u nor to"
+			" old port %s:%u%s",
+			bind_addr? bind_addr : "0.0.0.0",
+			port,
+			smsc->bind_addr? smsc->bind_addr : "0.0.0.0",
+			smsc->listen_port,
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	same_bind_addr = (bind_addr == smsc->bind_addr)
+		|| (bind_addr && smsc->bind_addr
+		    && (strcmp(bind_addr, smsc->bind_addr) == 0));
+
+	if (!same_bind_addr || port != smsc->listen_port) {
+		vty_out(vty, "%% Cannot bind to new port %s:%u, staying on"
+			" old port %s:%u%s",
+			bind_addr? bind_addr : "0.0.0.0",
+			port,
+			smsc->bind_addr? smsc->bind_addr : "0.0.0.0",
+			smsc->listen_port,
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_port, cfg_smpp_port_cmd,
+	"local-tcp-port <1-65535>",
+	"Set the local TCP port on which we listen for SMPP\n"
+	"TCP port number")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	uint16_t port = atoi(argv[0]);
+	return smpp_local_tcp(vty, smsc->bind_addr, port);
+}
+
+DEFUN(cfg_smpp_addr_port, cfg_smpp_addr_port_cmd,
+	"local-tcp-ip A.B.C.D <1-65535>",
+	"Set the local IP address and TCP port on which we listen for SMPP\n"
+	"Local IP address\n"
+	"TCP port number")
+{
+	const char *bind_addr = argv[0];
+	uint16_t port = atoi(argv[1]);
+	return smpp_local_tcp(vty, bind_addr, port);
+}
+
+DEFUN(cfg_smpp_sys_id, cfg_smpp_sys_id_cmd,
+	"system-id ID",
+	"Set the System ID of this SMSC\n"
+	"Alphanumeric SMSC System ID\n")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+
+	if (strlen(argv[0])+1 > sizeof(smsc->system_id))
+		return CMD_WARNING;
+
+	strcpy(smsc->system_id, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_policy, cfg_smpp_policy_cmd,
+	"policy (accept-all|closed)",
+	"Set the authentication policy of this SMSC\n"
+	"Accept all SMPP connections independeint of system ID / passwd\n"
+	"Accept only SMPP connections from ESMEs explicitly configured")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+
+	if (!strcmp(argv[0], "accept-all"))
+		smsc->accept_all = 1;
+	else
+		smsc->accept_all = 0;
+
+	return CMD_SUCCESS;
+}
+
+
+static int config_write_smpp(struct vty *vty)
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+
+	vty_out(vty, "smpp%s", VTY_NEWLINE);
+	if (smsc->bind_addr)
+		vty_out(vty, " local-tcp-ip %s %u%s", smsc->bind_addr,
+			smsc->listen_port, VTY_NEWLINE);
+	else
+		vty_out(vty, " local-tcp-port %u%s", smsc->listen_port,
+			VTY_NEWLINE);
+	if (strlen(smsc->system_id) > 0)
+		vty_out(vty, " system-id %s%s", smsc->system_id, VTY_NEWLINE);
+	vty_out(vty, " policy %s%s",
+		smsc->accept_all ? "accept-all" : "closed", VTY_NEWLINE);
+	vty_out(vty, " %ssmpp-first%s",
+		smsc->smpp_first ? "" : "no ", VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme, cfg_esme_cmd,
+	"esme NAME",
+	"Configure a particular ESME\n"
+	"Alphanumeric System ID of the ESME to be configured\n")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	struct osmo_smpp_acl *acl;
+	const char *id = argv[0];
+
+	if (strlen(id) > 16) {
+		vty_out(vty, "%% System ID cannot be more than 16 "
+			"characters long%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	acl = smpp_acl_by_system_id(smsc, id);
+	if (!acl) {
+		acl = smpp_acl_alloc(smsc, id);
+		if (!acl)
+			return CMD_WARNING;
+	}
+
+	vty->index = acl;
+	vty->index_sub = &acl->description;
+	vty->node = SMPP_ESME_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_esme, cfg_no_esme_cmd,
+	"no esme NAME",
+	NO_STR "Remove ESME configuration\n"
+	"Alphanumeric System ID of the ESME to be removed\n")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	struct osmo_smpp_acl *acl;
+	const char *id = argv[0];
+
+	acl = smpp_acl_by_system_id(smsc, id);
+	if (!acl) {
+		vty_out(vty, "%% ESME with system id '%s' unknown%s",
+			id, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* FIXME: close the connection, free data structure, etc. */
+
+	smpp_acl_delete(acl);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_esme_passwd, cfg_esme_passwd_cmd,
+	"password PASSWORD",
+	"Set the password for this ESME\n"
+	"Alphanumeric password string\n")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	if (strlen(argv[0])+1 > sizeof(acl->passwd))
+		return CMD_WARNING;
+
+	strcpy(acl->passwd, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_passwd, cfg_esme_no_passwd_cmd,
+	"no password",
+	NO_STR "Remove the password for this ESME\n")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	memset(acl->passwd, 0, sizeof(acl->passwd));
+
+	return CMD_SUCCESS;
+}
+
+static int osmo_is_digits(const char *str)
+{
+	int i;
+	for (i = 0; i < strlen(str); i++) {
+		if (!isdigit(str[i]))
+			return 0;
+	}
+	return 1;
+}
+
+static const struct value_string route_errstr[] = {
+	{ -EEXIST,	"Route already exists" },
+	{ -ENODEV,	"Route does not exist" },
+	{ -ENOMEM,	"No memory" },
+	{ -EINVAL,	"Invalid" },
+	{ 0, NULL }
+};
+
+static const struct value_string smpp_ton_str_short[] = {
+	{ TON_Unknown,		"unknown" },
+	{ TON_International,	"international" },
+	{ TON_National,		"national" },
+	{ TON_Network_Specific,	"network" },
+	{ TON_Subscriber_Number,"subscriber" },
+	{ TON_Alphanumeric,	"alpha" },
+	{ TON_Abbreviated,	"abbrev" },
+	{ 0, NULL }
+};
+
+static const struct value_string smpp_npi_str_short[] = {
+	{ NPI_Unknown,		"unknown" },
+	{ NPI_ISDN_E163_E164,	"isdn" },
+	{ NPI_Data_X121,	"x121" },
+	{ NPI_Telex_F69,	"f69" },
+	{ NPI_Land_Mobile_E212,	"e212" },
+	{ NPI_National,		"national" },
+	{ NPI_Private,		"private" },
+	{ NPI_ERMES,		"ermes" },
+	{ NPI_Internet_IP,	"ip" },
+	{ NPI_WAP_Client_Id,	"wap" },
+	{ 0, NULL }
+};
+
+
+#define SMPP_ROUTE_STR "Configure a route for MO-SMS to be sent to this ESME\n"
+#define SMPP_ROUTE_P_STR SMPP_ROUTE_STR "Prefix-match route\n"
+#define SMPP_PREFIX_STR "Destination number prefix\n"
+
+#define TON_CMD "(unknown|international|national|network|subscriber|alpha|abbrev)"
+#define NPI_CMD "(unknown|isdn|x121|f69|e212|national|private|ermes|ip|wap)"
+#define TON_STR "Unknown type-of-number\n"		\
+		"International type-of-number\n"	\
+		"National type-of-number\n"		\
+		"Network specific type-of-number\n"	\
+		"Subscriber type-of-number\n"		\
+		"Alphanumeric type-of-number\n"		\
+		"Abbreviated type-of-number\n"
+#define NPI_STR "Unknown numbering plan\n"		\
+		"ISDN (E.164) numbering plan\n"		\
+		"X.121 numbering plan\n"		\
+		"F.69 numbering plan\n"			\
+		"E.212 numbering plan\n"		\
+		"National numbering plan\n"		\
+		"Private numbering plan\n"		\
+		"ERMES numbering plan\n"		\
+		"IP numbering plan\n"			\
+		"WAP numbeing plan\n"
+
+DEFUN(cfg_esme_route_pfx, cfg_esme_route_pfx_cmd,
+	"route prefix " TON_CMD " " NPI_CMD " PREFIX",
+	SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
+{
+	struct osmo_smpp_acl *acl = vty->index;
+	struct osmo_smpp_addr pfx;
+	int rc;
+
+	/* check if DESTINATION is all-digits */
+	if (!osmo_is_digits(argv[2])) {
+		vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
+	pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
+	snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
+
+	rc = smpp_route_pfx_add(acl, &pfx);
+	if (rc < 0) {
+		vty_out(vty, "%% error adding prefix route: %s%s",
+			get_value_string(route_errstr, rc), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_route_pfx, cfg_esme_no_route_pfx_cmd,
+	"no route prefix " TON_CMD " " NPI_CMD " PREFIX",
+	NO_STR SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
+{
+	struct osmo_smpp_acl *acl = vty->index;
+	struct osmo_smpp_addr pfx;
+	int rc;
+
+	/* check if DESTINATION is all-digits */
+	if (!osmo_is_digits(argv[2])) {
+		vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
+	pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
+	snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
+
+	rc = smpp_route_pfx_del(acl, &pfx);
+	if (rc < 0) {
+		vty_out(vty, "%% error removing prefix route: %s%s",
+			get_value_string(route_errstr, rc), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+
+}
+
+
+DEFUN(cfg_esme_defaultroute, cfg_esme_defaultroute_cmd,
+	"default-route",
+	"Set this ESME as default-route for all SMS to unknown destinations")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->default_route = 1;
+
+	if (!acl->smsc->def_route)
+		acl->smsc->def_route = acl;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_esme_defaultroute, cfg_esme_no_defaultroute_cmd,
+	"no default-route", NO_STR
+	"Remove this ESME as default-route for all SMS to unknown destinations")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->default_route = 0;
+
+	/* remove currently active default route, if it was created by
+	 * this ACL */
+	if (acl->smsc->def_route && acl->smsc->def_route == acl)
+		acl->smsc->def_route = NULL;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_del_src_imsi, cfg_esme_del_src_imsi_cmd,
+	"deliver-src-imsi",
+	"Enable the use of IMSI as source address in DELIVER")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->deliver_src_imsi = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_del_src_imsi, cfg_esme_no_del_src_imsi_cmd,
+	"no deliver-src-imsi", NO_STR
+	"Disable the use of IMSI as source address in DELIVER")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->deliver_src_imsi = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_osmo_ext, cfg_esme_osmo_ext_cmd,
+	"osmocom-extensions",
+	"Enable the use of Osmocom SMPP Extensions for this ESME")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->osmocom_ext = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_osmo_ext, cfg_esme_no_osmo_ext_cmd,
+	"no osmocom-extensions", NO_STR
+	"Disable the use of Osmocom SMPP Extensions for this ESME")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->osmocom_ext = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_dcs_transp, cfg_esme_dcs_transp_cmd,
+	"dcs-transparent",
+	"Enable the transparent pass-through of TP-DCS to SMPP DataCoding")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->dcs_transparent = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_dcs_transp, cfg_esme_no_dcs_transp_cmd,
+	"no dcs-transparent", NO_STR
+	"Disable the transparent pass-through of TP-DCS to SMPP DataCoding")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->dcs_transparent = 0;
+
+	return CMD_SUCCESS;
+}
+
+
+static void dump_one_esme(struct vty *vty, struct osmo_esme *esme)
+{
+	char host[128], serv[128];
+
+	host[0] = 0;
+	serv[0] = 0;
+	getnameinfo((const struct sockaddr *) &esme->sa, esme->sa_len,
+		    host, sizeof(host), serv, sizeof(serv), NI_NUMERICSERV);
+
+	vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s",
+		esme->system_id, esme->acl ? esme->acl->passwd : "",
+		esme->smpp_version, VTY_NEWLINE);
+	vty_out(vty, "  Connected from: %s:%s%s", host, serv, VTY_NEWLINE);
+	if (esme->smsc->def_route == esme->acl)
+		vty_out(vty, "  Is current default route%s", VTY_NEWLINE);
+}
+
+DEFUN(show_esme, show_esme_cmd,
+	"show smpp esme",
+	SHOW_STR "SMPP Interface\n" "SMPP Extrenal SMS Entity\n")
+{
+	struct smsc *smsc = smsc_from_vty(vty);
+	struct osmo_esme *esme;
+
+	llist_for_each_entry(esme, &smsc->esme_list, list)
+		dump_one_esme(vty, esme);
+
+	return CMD_SUCCESS;
+}
+
+static void write_esme_route_single(struct vty *vty, struct osmo_smpp_route *r)
+{
+	switch (r->type) {
+	case SMPP_ROUTE_PREFIX:
+		vty_out(vty, "   route prefix %s ",
+			get_value_string(smpp_ton_str_short, r->u.prefix.ton));
+		vty_out(vty, "%s %s%s",
+			get_value_string(smpp_npi_str_short, r->u.prefix.npi),
+			r->u.prefix.addr, VTY_NEWLINE);
+		break;
+	case SMPP_ROUTE_NONE:
+		break;
+	}
+}
+
+static void config_write_esme_single(struct vty *vty, struct osmo_smpp_acl *acl)
+{
+	struct osmo_smpp_route *r;
+
+	vty_out(vty, " esme %s%s", acl->system_id, VTY_NEWLINE);
+	if (strlen(acl->passwd))
+		vty_out(vty, "  password %s%s", acl->passwd, VTY_NEWLINE);
+	if (acl->default_route)
+		vty_out(vty, "  default-route%s", VTY_NEWLINE);
+	if (acl->deliver_src_imsi)
+		vty_out(vty, "  deliver-src-imsi%s", VTY_NEWLINE);
+	if (acl->osmocom_ext)
+		vty_out(vty, "  osmocom-extensions%s", VTY_NEWLINE);
+	if (acl->dcs_transparent)
+		vty_out(vty, "  dcs-transparent%s", VTY_NEWLINE);
+
+	llist_for_each_entry(r, &acl->route_list, list)
+		write_esme_route_single(vty, r);
+}
+
+static int config_write_esme(struct vty *v)
+{
+	struct smsc *smsc = smsc_from_vty(v);
+	struct osmo_smpp_acl *acl;
+
+	llist_for_each_entry(acl, &smsc->acl_list, list)
+		config_write_esme_single(v, acl);
+
+	return CMD_SUCCESS;
+}
+
+int smpp_vty_init(void)
+{
+	install_node(&smpp_node, config_write_smpp);
+	install_element(CONFIG_NODE, &cfg_smpp_cmd);
+
+	install_element(SMPP_NODE, &cfg_smpp_first_cmd);
+	install_element(SMPP_NODE, &cfg_no_smpp_first_cmd);
+	install_element(SMPP_NODE, &cfg_smpp_port_cmd);
+	install_element(SMPP_NODE, &cfg_smpp_addr_port_cmd);
+	install_element(SMPP_NODE, &cfg_smpp_sys_id_cmd);
+	install_element(SMPP_NODE, &cfg_smpp_policy_cmd);
+	install_element(SMPP_NODE, &cfg_esme_cmd);
+	install_element(SMPP_NODE, &cfg_no_esme_cmd);
+
+	install_node(&esme_node, config_write_esme);
+	install_element(SMPP_ESME_NODE, &cfg_esme_passwd_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_passwd_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_route_pfx_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_route_pfx_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_defaultroute_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_defaultroute_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_del_src_imsi_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_del_src_imsi_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_osmo_ext_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_osmo_ext_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_dcs_transp_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_dcs_transp_cmd);
+
+	install_element_ve(&show_esme_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/libmsc/sms_queue.c b/openbsc/src/libmsc/sms_queue.c
new file mode 100644
index 0000000..dc7f6e8
--- /dev/null
+++ b/openbsc/src/libmsc/sms_queue.c
@@ -0,0 +1,544 @@
+/* SMS queue to continously attempt to deliver SMS */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * The difficulty of such a queue is to send a lot of SMS without
+ * overloading the paging subsystem and the database and other users
+ * of the MSC. To make the best use we would need to know the number
+ * of pending paging requests, then throttle the number of SMS we
+ * want to send and such.
+ * We will start with a very simple SMS Queue and then try to speed
+ * things up by collecting data from other parts of the system.
+ */
+
+#include <openbsc/sms_queue.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+/*
+ * One pending SMS that we wait for.
+ */
+struct gsm_sms_pending {
+	struct llist_head entry;
+
+	struct gsm_subscriber *subscr;
+	unsigned long long sms_id;
+	int failed_attempts;
+	int resend;
+};
+
+struct gsm_sms_queue {
+	struct osmo_timer_list resend_pending;
+	struct osmo_timer_list push_queue;
+	struct gsm_network *network;
+	int max_fail;
+	int max_pending;
+	int pending;
+
+	struct llist_head pending_sms;
+	unsigned long long last_subscr_id;
+};
+
+static int sms_subscr_cb(unsigned int, unsigned int, void *, void *);
+static int sms_sms_cb(unsigned int, unsigned int, void *, void *);
+
+static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq,
+						struct gsm_sms *sms)
+{
+	struct gsm_sms_pending *pending;
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+		if (pending->sms_id == sms->id)
+			return pending;
+	}
+
+	return NULL;
+}
+
+static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms)
+{
+	return sms_find_pending(smsq, sms) != NULL;
+}
+
+static struct gsm_sms_pending *sms_subscriber_find_pending(
+					struct gsm_sms_queue *smsq,
+					struct gsm_subscriber *subscr)
+{
+	struct gsm_sms_pending *pending;
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+		if (pending->subscr == subscr)
+			return pending;
+	}
+
+	return NULL;
+}
+
+static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq,
+				     struct gsm_subscriber *subscr)
+{
+	return sms_subscriber_find_pending(smsq, subscr) != NULL;
+}
+
+static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq,
+						struct gsm_sms *sms)
+{
+	struct gsm_sms_pending *pending;
+
+	pending = talloc_zero(smsq, struct gsm_sms_pending);
+	if (!pending)
+		return NULL;
+
+	pending->subscr = subscr_get(sms->receiver);
+	pending->sms_id = sms->id;
+	return pending;
+}
+
+static void sms_pending_free(struct gsm_sms_pending *pending)
+{
+	subscr_put(pending->subscr);
+	llist_del(&pending->entry);
+	talloc_free(pending);
+}
+
+static void sms_pending_resend(struct gsm_sms_pending *pending)
+{
+	struct gsm_sms_queue *smsq;
+	LOGP(DLSMS, LOGL_DEBUG,
+	     "Scheduling resend of SMS %llu.\n", pending->sms_id);
+
+	pending->resend = 1;
+
+	smsq = pending->subscr->group->net->sms_queue;
+	if (osmo_timer_pending(&smsq->resend_pending))
+		return;
+
+	osmo_timer_schedule(&smsq->resend_pending, 1, 0);
+}
+
+static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error)
+{
+	struct gsm_sms_queue *smsq;
+
+	LOGP(DLSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n",
+	     pending->sms_id, pending->failed_attempts);
+
+	smsq = pending->subscr->group->net->sms_queue;
+	if (++pending->failed_attempts < smsq->max_fail)
+		return sms_pending_resend(pending);
+
+	sms_pending_free(pending);
+	smsq->pending -= 1;
+	sms_queue_trigger(smsq);
+}
+
+/*
+ * Resend all SMS that are scheduled for a resend. This is done to
+ * avoid an immediate failure.
+ */
+static void sms_resend_pending(void *_data)
+{
+	struct gsm_sms_pending *pending, *tmp;
+	struct gsm_sms_queue *smsq = _data;
+
+	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+		struct gsm_sms *sms;
+		if (!pending->resend)
+			continue;
+
+		sms = db_sms_get(smsq->network, pending->sms_id);
+
+		/* the sms is gone? Move to the next */
+		if (!sms) {
+			sms_pending_free(pending);
+			smsq->pending -= 1;
+			sms_queue_trigger(smsq);
+		} else {
+			pending->resend = 0;
+			gsm411_send_sms_subscr(sms->receiver, sms);
+		}
+	}
+}
+
+static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq)
+{
+	struct gsm_sms *sms;
+
+	sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10);
+	if (sms) {
+		smsq->last_subscr_id = sms->receiver->id + 1;
+		return sms;
+	}
+
+	/* need to wrap around */
+	smsq->last_subscr_id = 0;
+	sms = db_sms_get_unsent_by_subscr(smsq->network,
+					  smsq->last_subscr_id, 10);
+	if (sms)
+		smsq->last_subscr_id = sms->receiver->id + 1;
+	return sms;
+}
+
+/**
+ * I will submit up to max_pending - pending SMS to the
+ * subsystem.
+ */
+static void sms_submit_pending(void *_data)
+{
+	struct gsm_sms_queue *smsq = _data;
+	int attempts = smsq->max_pending - smsq->pending;
+	int initialized = 0;
+	unsigned long long first_sub = 0;
+	int attempted = 0, rounds = 0;
+
+	LOGP(DLSMS, LOGL_DEBUG, "Attempting to send %d SMS\n", attempts);
+
+	do {
+		struct gsm_sms_pending *pending;
+		struct gsm_sms *sms;
+
+
+		sms = take_next_sms(smsq);
+		if (!sms) {
+			LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (%d attempted)\n",
+			     attempted);
+			break;
+		}
+
+		rounds += 1;
+		LOGP(DLSMS, LOGL_DEBUG, "Sending SMS round %d\n", rounds);
+
+		/*
+		 * This code needs to detect a loop. It assumes that no SMS
+		 * will vanish during the time this is executed. We will remember
+		 * the id of the first GSM subscriber we see and then will
+		 * compare this. The Database code should make sure that we will
+		 * see all other subscribers first before seeing this one again.
+		 *
+		 * It is always scary to have an infinite loop like this.
+		 */
+		if (!initialized) {
+			first_sub = sms->receiver->id;
+			initialized = 1;
+		} else if (first_sub == sms->receiver->id) {
+			LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (loop) (%d attempted)\n",
+			     attempted);
+			sms_free(sms);
+			break;
+		}
+
+		/* no need to send a pending sms */
+		if (sms_is_in_pending(smsq, sms)) {
+			LOGP(DLSMS, LOGL_DEBUG,
+			     "SMSqueue with pending sms: %llu. Skipping\n", sms->id);
+			sms_free(sms);
+			continue;
+		}
+
+		/* no need to send a SMS with the same receiver */
+		if (sms_subscriber_is_pending(smsq, sms->receiver)) {
+			LOGP(DLSMS, LOGL_DEBUG,
+			     "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id);
+			sms_free(sms);
+			continue;
+		}
+
+		pending = sms_pending_from(smsq, sms);
+		if (!pending) {
+			LOGP(DLSMS, LOGL_ERROR,
+			     "Failed to create pending SMS entry.\n");
+			sms_free(sms);
+			continue;
+		}
+
+		attempted += 1;
+		smsq->pending += 1;
+		llist_add_tail(&pending->entry, &smsq->pending_sms);
+		gsm411_send_sms_subscr(sms->receiver, sms);
+	} while (attempted < attempts && rounds < 1000);
+
+	LOGP(DLSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds);
+}
+
+/**
+ * Send the next SMS or trigger the queue
+ */
+static void sms_send_next(struct gsm_subscriber *subscr)
+{
+	struct gsm_sms_queue *smsq = subscr->group->net->sms_queue;
+	struct gsm_sms_pending *pending;
+	struct gsm_sms *sms;
+
+	/* the subscriber should not be in the queue */
+	OSMO_ASSERT(!sms_subscriber_is_pending(smsq, subscr));
+
+	/* check for more messages for this subscriber */
+	sms = db_sms_get_unsent_for_subscr(subscr);
+	if (!sms)
+		goto no_pending_sms;
+
+	/* No sms should be scheduled right now */
+	OSMO_ASSERT(!sms_is_in_pending(smsq, sms));
+
+	/* Remember that we deliver this SMS and send it */
+	pending = sms_pending_from(smsq, sms);
+	if (!pending) {
+		LOGP(DLSMS, LOGL_ERROR,
+			"Failed to create pending SMS entry.\n");
+		sms_free(sms);
+		goto no_pending_sms;
+	}
+
+	smsq->pending += 1;
+	llist_add_tail(&pending->entry, &smsq->pending_sms);
+	gsm411_send_sms_subscr(sms->receiver, sms);
+	return;
+
+no_pending_sms:
+	/* Try to send the SMS to avoid the queue being stuck */
+	sms_submit_pending(subscr->group->net->sms_queue);
+}
+
+/*
+ * Kick off the queue again.
+ */
+int sms_queue_trigger(struct gsm_sms_queue *smsq)
+{
+	LOGP(DLSMS, LOGL_DEBUG, "Triggering SMS queue\n");
+	if (osmo_timer_pending(&smsq->push_queue))
+		return 0;
+
+	osmo_timer_schedule(&smsq->push_queue, 1, 0);
+	return 0;
+}
+
+int sms_queue_start(struct gsm_network *network, int max_pending)
+{
+	struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue);
+	if (!sms) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n");
+		return -1;
+	}
+
+	osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network);
+	osmo_signal_register_handler(SS_SMS, sms_sms_cb, network);
+
+	network->sms_queue = sms;
+	INIT_LLIST_HEAD(&sms->pending_sms);
+	sms->max_fail = 1;
+	sms->network = network;
+	sms->max_pending = max_pending;
+	osmo_timer_setup(&sms->push_queue, sms_submit_pending, sms);
+	osmo_timer_setup(&sms->resend_pending, sms_resend_pending, sms);
+
+	sms_submit_pending(sms);
+
+	return 0;
+}
+
+static int sub_ready_for_sm(struct gsm_network *net, struct gsm_subscriber *subscr)
+{
+	struct gsm_sms *sms;
+	struct gsm_sms_pending *pending;
+	struct gsm_subscriber_connection *conn;
+
+	/*
+	 * The code used to be very clever and tried to submit
+	 * a SMS during the Location Updating Request. This has
+	 * two issues:
+	 *   1.) The Phone might not be ready yet, e.g. the C155
+	 *       will not respond to the Submit when it is booting.
+	 *   2.) The queue is already trying to submit SMS to the
+	 *	 user and by not responding to the paging request
+	 *	 we will set the LAC back to 0. We would have to
+	 *	 stop the paging and move things over.
+	 *
+	 * We need to be careful in what we try here.
+	 */
+
+	/* check if we have pending requests */
+	pending = sms_subscriber_find_pending(net->sms_queue, subscr);
+	if (pending) {
+		LOGP(DMSC, LOGL_NOTICE,
+		     "Pending paging while subscriber %llu attached.\n",
+		      subscr->id);
+		return 0;
+	}
+
+	conn = connection_for_subscr(subscr);
+	if (!conn)
+		return -1;
+
+	/* Now try to deliver any pending SMS to this sub */
+	sms = db_sms_get_unsent_for_subscr(subscr);
+	if (!sms)
+		return -1;
+	gsm411_send_sms(conn, sms);
+	return 0;
+}
+
+static int sms_subscr_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	/* this is readyForSM */
+	return sub_ready_for_sm(handler_data, subscr);
+}
+
+static int sms_sms_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_network *network = handler_data;
+	struct sms_signal_data *sig_sms = signal_data;
+	struct gsm_sms_pending *pending;
+	struct gsm_subscriber *subscr;
+
+	/* We got a new SMS and maybe should launch the queue again. */
+	if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) {
+		/* TODO: For SMMA we might want to re-use the radio connection. */
+		sms_queue_trigger(network->sms_queue);
+		return 0;
+	}
+
+	if (!sig_sms->sms)
+		return -1;
+
+
+	/*
+	 * Find the entry of our queue. The SMS subsystem will submit
+	 * sms that are not in our control as we just have a channel
+	 * open anyway.
+	 */
+	pending = sms_find_pending(network->sms_queue, sig_sms->sms);
+	if (!pending)
+		return 0;
+
+	switch (signal) {
+	case S_SMS_DELIVERED:
+		/* Remember the subscriber and clear the pending entry */
+		network->sms_queue->pending -= 1;
+		subscr = subscr_get(pending->subscr);
+		sms_pending_free(pending);
+		/* Attempt to send another SMS to this subscriber */
+		sms_send_next(subscr);
+		subscr_put(subscr);
+		break;
+	case S_SMS_MEM_EXCEEDED:
+		network->sms_queue->pending -= 1;
+		sms_pending_free(pending);
+		sms_queue_trigger(network->sms_queue);
+		break;
+	case S_SMS_UNKNOWN_ERROR:
+		/*
+		 * There can be many reasons for this failure. E.g. the paging
+		 * timed out, the subscriber was not paged at all, or there was
+		 * a protocol error. The current strategy is to try sending the
+		 * next SMS for busy/oom and to retransmit when we have paged.
+		 *
+		 * When the paging expires three times we will disable the
+		 * subscriber. If we have some kind of other transmit error we
+		 * should flag the SMS as bad.
+		 */
+		switch (sig_sms->paging_result) {
+		case 0:
+			/* BAD SMS? */
+			db_sms_inc_deliver_attempts(sig_sms->sms);
+			sms_pending_failed(pending, 0);
+			break;
+		case GSM_PAGING_EXPIRED:
+			sms_pending_failed(pending, 1);
+			break;
+
+		case GSM_PAGING_OOM:
+		case GSM_PAGING_BUSY:
+			network->sms_queue->pending -= 1;
+			sms_pending_free(pending);
+			sms_queue_trigger(network->sms_queue);
+			break;
+		default:
+			LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n",
+			     sig_sms->paging_result);
+		}
+		break;
+	default:
+		LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n",
+		     sig_sms->paging_result);
+	}
+
+	return 0;
+}
+
+/* VTY helper functions */
+int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty)
+{
+	struct gsm_sms_pending *pending;
+
+	vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s",
+		smsq->max_pending, smsq->pending, VTY_NEWLINE);
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry)
+		vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s",
+			pending->subscr->id, pending->sms_id,
+			pending->failed_attempts, VTY_NEWLINE);
+	return 0;
+}
+
+int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending)
+{
+	LOGP(DLSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n",
+	     smsq->max_pending, max_pending);
+	smsq->max_pending = max_pending;
+	return 0;
+}
+
+int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail)
+{
+	LOGP(DLSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n",
+	     smsq->max_fail, max_fail);
+	smsq->max_fail = max_fail;
+	return 0;
+}
+
+int sms_queue_clear(struct gsm_sms_queue *smsq)
+{
+	struct gsm_sms_pending *pending, *tmp;
+
+	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+		LOGP(DLSMS, LOGL_NOTICE,
+		     "SMSqueue clearing for sub %llu\n", pending->subscr->id);
+		sms_pending_free(pending);
+	}
+
+	smsq->pending = 0;
+	return 0;
+}
diff --git a/openbsc/src/libmsc/token_auth.c b/openbsc/src/libmsc/token_auth.c
new file mode 100644
index 0000000..5af1e98
--- /dev/null
+++ b/openbsc/src/libmsc/token_auth.c
@@ -0,0 +1,160 @@
+/* SMS based token authentication for ad-hoc GSM networks */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+
+#define TOKEN_SMS_TEXT "HAR 2009 GSM.  Register at http://har2009.gnumonks.org/ " \
+			"Your IMSI is %s, auth token is %08X, phone no is %s."
+
+static char *build_sms_string(struct gsm_subscriber *subscr, uint32_t token)
+{
+	char *sms_str;
+	unsigned int len;
+
+	len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT);
+	sms_str = talloc_size(tall_bsc_ctx, len);
+	if (!sms_str)
+		return NULL;
+
+	snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token,
+		 subscr->extension);
+	sms_str[len-1] = '\0';
+
+	return sms_str;
+}
+
+static int token_subscr_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+	struct gsm_sms *sms;
+	int rc = 0;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	if (subscr->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+	if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) {
+		struct gsm_subscriber *sender;
+		uint32_t token;
+		char *sms_str;
+
+		/* we've seen this subscriber for the first time. */
+		rc = db_subscriber_alloc_token(subscr, &token);
+		if (rc != 0) {
+			rc = -EIO;
+			goto unauth;
+		}
+
+		sms_str = build_sms_string(subscr, token);
+		if (!sms_str) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+
+		/* FIXME: don't use ID 1 static */
+		sender = subscr_get_by_id(subscr->group, 1);
+
+		sms = sms_from_text(subscr, sender, 0, sms_str);
+
+		subscr_put(sender);
+		talloc_free(sms_str);
+		if (!sms) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		rc = gsm411_send_sms_subscr(subscr, sms);
+
+		/* FIXME: else, delete the subscirber from database */
+unauth:
+
+		/* make sure we don't allow him in again unless he clicks the web UI */
+		subscr->authorized = 0;
+		db_sync_subscriber(subscr);
+		if (rc) {
+			struct gsm_subscriber_connection *conn = connection_for_subscr(subscr);
+			if (conn) {
+				uint8_t auth_rand[16];
+				/* kick the subscriber off the network */
+				gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 0);
+				gsm48_tx_mm_auth_rej(conn);
+				/* FIXME: close the channel early ?*/
+				//gsm48_send_rr_Release(lchan);
+			}
+		}
+	}
+
+	return rc;
+}
+
+static int token_sms_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct sms_signal_data *sig = signal_data;
+	struct gsm_sms *sms = sig->sms;;
+	struct gsm_subscriber_connection *conn;
+	uint8_t auth_rand[16];
+
+
+	if (signal != S_SMS_DELIVERED)
+		return 0;
+
+
+	/* these are not the droids we've been looking for */
+	if (!sms->receiver ||
+	    !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT))
+		return 0;
+
+
+	if (sms->receiver->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+
+	conn = connection_for_subscr(sms->receiver);
+	if (conn) {
+		/* kick the subscriber off the network */
+		gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 0);
+		gsm48_tx_mm_auth_rej(conn);
+		/* FIXME: close the channel early ?*/
+		//gsm48_send_rr_Release(lchan);
+	}
+
+	return 0;
+}
+
+//static __attribute__((constructor)) void on_dso_load_token(void)
+void on_dso_load_token(void)
+{
+	osmo_signal_register_handler(SS_SUBSCR, token_subscr_cb, NULL);
+	osmo_signal_register_handler(SS_SMS, token_sms_cb, NULL);
+}
diff --git a/openbsc/src/libmsc/transaction.c b/openbsc/src/libmsc/transaction.c
new file mode 100644
index 0000000..4b46e22
--- /dev/null
+++ b/openbsc/src/libmsc/transaction.c
@@ -0,0 +1,170 @@
+/* GSM 04.07 Transaction handling */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/transaction.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mncc.h>
+#include <openbsc/debug.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mncc.h>
+#include <openbsc/paging.h>
+#include <openbsc/osmo_msc.h>
+
+void *tall_trans_ctx;
+
+void _gsm48_cc_trans_free(struct gsm_trans *trans);
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn,
+				   uint8_t proto, uint8_t trans_id)
+{
+	struct gsm_trans *trans;
+	struct gsm_network *net = conn->network;
+	struct gsm_subscriber *subscr = conn->subscr;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr == subscr &&
+		    trans->protocol == proto &&
+		    trans->transaction_id == trans_id)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					uint32_t callref)
+{
+	struct gsm_trans *trans;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->callref == callref)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_alloc(struct gsm_network *net,
+			      struct gsm_subscriber *subscr,
+			      uint8_t protocol, uint8_t trans_id,
+			      uint32_t callref)
+{
+	struct gsm_trans *trans;
+
+	DEBUGP(DCC, "subscr=%p, net=%p\n", subscr, net);
+
+	/* a valid subscriber is indispensable */
+	if (subscr == NULL) {
+		LOGP(DCC, LOGL_NOTICE,
+		     "unable to alloc transaction, invalid subscriber (NULL)\n");
+		return NULL;
+	}
+
+	trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
+	if (!trans)
+		return NULL;
+
+	trans->subscr = subscr;
+	subscr_get(trans->subscr);
+
+	trans->protocol = protocol;
+	trans->transaction_id = trans_id;
+	trans->callref = callref;
+
+	trans->net = net;
+	llist_add_tail(&trans->entry, &net->trans_list);
+
+	return trans;
+}
+
+void trans_free(struct gsm_trans *trans)
+{
+	switch (trans->protocol) {
+	case GSM48_PDISC_CC:
+		_gsm48_cc_trans_free(trans);
+		break;
+	case GSM48_PDISC_SMS:
+		_gsm411_sms_trans_free(trans);
+		break;
+	}
+
+	if (trans->paging_request) {
+		subscr_remove_request(trans->paging_request);
+		trans->paging_request = NULL;
+	}
+
+	if (trans->subscr) {
+		subscr_put(trans->subscr);
+		trans->subscr = NULL;
+	}
+
+	llist_del(&trans->entry);
+
+	if (trans->conn)
+		msc_release_connection(trans->conn);
+
+	trans->conn = NULL;
+	talloc_free(trans);
+}
+
+/* allocate an unused transaction ID for the given subscriber
+ * in the given protocol using the ti_flag specified */
+int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr,
+			  uint8_t protocol, uint8_t ti_flag)
+{
+	struct gsm_trans *trans;
+	unsigned int used_tid_bitmask = 0;
+	int i, j, h;
+
+	if (ti_flag)
+		ti_flag = 0x8;
+
+	/* generate bitmask of already-used TIDs for this (subscr,proto) */
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr != subscr ||
+		    trans->protocol != protocol ||
+		    trans->transaction_id == 0xff)
+			continue;
+		used_tid_bitmask |= (1 << trans->transaction_id);
+	}
+
+	/* find a new one, trying to go in a 'circular' pattern */
+	for (h = 6; h > 0; h--)
+		if (used_tid_bitmask & (1 << (h | ti_flag)))
+			break;
+	for (i = 0; i < 7; i++) {
+		j = ((h + i) % 7) | ti_flag;
+		if ((used_tid_bitmask & (1 << j)) == 0)
+			return j;
+	}
+
+	return -1;
+}
+
+int trans_has_conn(const struct gsm_subscriber_connection *conn)
+{
+	struct gsm_trans *trans;
+
+	llist_for_each_entry(trans, &conn->network->trans_list, entry)
+		if (trans->conn == conn)
+			return 1;
+
+	return 0;
+}
diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c
new file mode 100644
index 0000000..488e813
--- /dev/null
+++ b/openbsc/src/libmsc/ussd.c
@@ -0,0 +1,92 @@
+/* Network-specific handling of mobile-originated USSDs. */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* This module defines the network-specific handling of mobile-originated
+   USSD messages. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/osmo_msc.h>
+
+/* Declarations of USSD strings to be recognised */
+const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
+
+/* A network-specific handler function */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ss_request *req)
+{
+	char *own_number = conn->subscr->extension;
+	char response_string[GSM_EXTENSION_LENGTH + 20];
+
+	/* Need trailing CR as EOT character */
+	snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number);
+	return gsm0480_send_ussd_response(conn, msg, response_string, req);
+}
+
+
+/* Entrypoint - handler function common to all mobile-originated USSDs */
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	int rc;
+	struct ss_request req;
+	struct gsm48_hdr *gh;
+
+	memset(&req, 0, sizeof(req));
+	gh = msgb_l3(msg);
+	rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req);
+	if (!rc) {
+		DEBUGP(DMM, "Unhandled SS\n");
+		rc = gsm0480_send_ussd_reject(conn, msg, &req);
+		msc_release_connection(conn);
+		return rc;
+	}
+
+	/* Interrogation or releaseComplete? */
+	if (req.ussd_text[0] == '\0' || req.ussd_text[0] == 0xFF) {
+		if (req.ss_code > 0) {
+			/* Assume interrogateSS or modification of it and reject */
+			rc = gsm0480_send_ussd_reject(conn, msg, &req);
+			msc_release_connection(conn);
+			return rc;
+		}
+		/* Still assuming a Release-Complete and returning */
+		return 0;
+	}
+
+	if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)req.ussd_text)) {
+		DEBUGP(DMM, "USSD: Own number requested\n");
+		rc = send_own_number(conn, msg, &req);
+	} else {
+		DEBUGP(DMM, "Unhandled USSD %s\n", req.ussd_text);
+		rc = gsm0480_send_ussd_reject(conn, msg, &req);
+	}
+
+	/* check if we can release it */
+	msc_release_connection(conn);
+	return rc;
+}
diff --git a/openbsc/src/libmsc/vty_interface_layer3.c b/openbsc/src/libmsc/vty_interface_layer3.c
new file mode 100644
index 0000000..a97e1ec
--- /dev/null
+++ b/openbsc/src/libmsc/vty_interface_layer3.c
@@ -0,0 +1,1311 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <time.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/gsm_04_11.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/db.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_04_14.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/sms_queue.h>
+#include <openbsc/mncc_int.h>
+#include <openbsc/handover.h>
+
+#include <osmocom/vty/logging.h>
+
+#include "meas_feed.h"
+
+extern struct gsm_network *gsmnet_from_vty(struct vty *v);
+
+static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+	int rc;
+	int reqs;
+	struct gsm_auth_info ainfo;
+	struct gsm_auth_tuple atuple;
+	struct llist_head *entry;
+	char expire_time[200];
+
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (strlen(subscr->name))
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (strlen(subscr->extension))
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	vty_out(vty, "    LAC: %d/0x%x%s",
+		subscr->lac, subscr->lac, VTY_NEWLINE);
+	vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 algorithm id: %d%s",
+			ainfo.auth_algo, VTY_NEWLINE);
+		vty_out(vty, "    A3A8 Ki: %s%s",
+			osmo_hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len),
+			VTY_NEWLINE);
+	}
+
+	rc = db_get_lastauthtuple_for_subscr(&atuple, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 last tuple (used %d times):%s",
+			atuple.use_count, VTY_NEWLINE);
+		vty_out(vty, "     seq # : %d%s",
+			atuple.key_seq, VTY_NEWLINE);
+		vty_out(vty, "     RAND  : %s%s",
+			osmo_hexdump(atuple.vec.rand, sizeof(atuple.vec.rand)),
+			VTY_NEWLINE);
+		vty_out(vty, "     SRES  : %s%s",
+			osmo_hexdump(atuple.vec.sres, sizeof(atuple.vec.sres)),
+			VTY_NEWLINE);
+		vty_out(vty, "     Kc    : %s%s",
+			osmo_hexdump(atuple.vec.kc, sizeof(atuple.vec.kc)),
+			VTY_NEWLINE);
+	}
+
+	/* print the expiration time of a subscriber */
+	strftime(expire_time, sizeof(expire_time),
+			"%a, %d %b %Y %T %z", localtime(&subscr->expire_lu));
+	expire_time[sizeof(expire_time) - 1] = '\0';
+	vty_out(vty, "    Expiration Time: %s%s", expire_time, VTY_NEWLINE);
+
+	reqs = 0;
+	llist_for_each(entry, &subscr->requests)
+		reqs += 1;
+	vty_out(vty, "    Paging: %s paging Requests: %d%s",
+		subscr->is_paging ? "is" : "not", reqs, VTY_NEWLINE);
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+
+/* Subscriber */
+DEFUN(show_subscr_cache,
+      show_subscr_cache_cmd,
+      "show subscriber cache",
+	SHOW_STR "Show information about subscribers\n"
+	"Display contents of subscriber cache\n")
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, &active_subscribers, entry) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_full_vty(vty, subscr);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(sms_send_pend,
+      sms_send_pend_cmd,
+      "sms send pending",
+      "SMS related commands\n" "SMS Sending related commands\n"
+      "Send all pending SMS")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_sms *sms;
+	int id = 0;
+
+	while (1) {
+		sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX);
+		if (!sms)
+			break;
+
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+		id = sms->receiver->id + 1;
+	}
+
+	return CMD_SUCCESS;
+}
+
+static int _send_sms_str(struct gsm_subscriber *receiver,
+                         struct gsm_subscriber *sender,
+                         char *str, uint8_t tp_pid)
+{
+	struct gsm_sms *sms;
+
+	sms = sms_from_text(receiver, sender, 0, str);
+	sms->protocol_id = tp_pid;
+
+	/* store in database for the queue */
+	if (db_sms_store(sms) != 0) {
+		LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		sms_free(sms);
+		return CMD_WARNING;
+	}
+	LOGP(DLSMS, LOGL_DEBUG, "SMS stored in DB\n");
+
+	sms_free(sms);
+	sms_queue_trigger(receiver->group->net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet,
+						 const char *type,
+						 const char *id)
+{
+	if (!strcmp(type, "extension"))
+		return subscr_get_by_extension(gsmnet->subscr_group, id);
+	else if (!strcmp(type, "imsi"))
+		return subscr_get_by_imsi(gsmnet->subscr_group, id);
+	else if (!strcmp(type, "tmsi"))
+		return subscr_get_by_tmsi(gsmnet->subscr_group, atoi(id));
+	else if (!strcmp(type, "id"))
+		return subscr_get_by_id(gsmnet->subscr_group, atoi(id));
+
+	return NULL;
+}
+#define SUBSCR_TYPES "(extension|imsi|tmsi|id)"
+#define SUBSCR_HELP "Operations on a Subscriber\n"			\
+	"Identify subscriber by extension (phone number)\n"		\
+	"Identify subscriber by IMSI\n"					\
+	"Identify subscriber by TMSI\n"					\
+	"Identify subscriber by database ID\n"				\
+	"Identifier for the subscriber\n"
+
+DEFUN(show_subscr,
+      show_subscr_cmd,
+      "show subscriber " SUBSCR_TYPES " ID",
+	SHOW_STR SUBSCR_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+				get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_dump_full_vty(vty, subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_create,
+      subscriber_create_cmd,
+      "subscriber create imsi ID",
+	"Operations on a Subscriber\n" \
+	"Create new subscriber\n" \
+	"Identify the subscriber by his IMSI\n" \
+	"Identifier for the subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr;
+
+	subscr = subscr_get_by_imsi(gsmnet->subscr_group, argv[0]);
+	if (subscr)
+		db_sync_subscriber(subscr);
+	else {
+		subscr = subscr_create_subscriber(gsmnet->subscr_group, argv[0]);
+
+		if (!subscr) {
+			vty_out(vty, "%% No subscriber created for IMSI %s%s",
+				argv[0], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	/* Show info about the created subscriber. */
+	subscr_dump_full_vty(vty, subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_pending_sms,
+      subscriber_send_pending_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID sms pending-send",
+	SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr;
+	struct gsm_sms *sms;
+
+	subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX);
+	if (sms)
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_sms,
+      subscriber_send_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID sms sender " SUBSCR_TYPES " SENDER_ID send .LINE",
+	SUBSCR_HELP "SMS Operations\n" SUBSCR_HELP "Send SMS\n" "Actual SMS Text\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]);
+	char *str;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		rc = CMD_WARNING;
+		goto err;
+	}
+
+	if (!sender) {
+		vty_out(vty, "%% No sender found for %s %s%s",
+			argv[2], argv[3], VTY_NEWLINE);
+		rc = CMD_WARNING;
+		goto err;
+	}
+
+	str = argv_concat(argv, argc, 4);
+	rc = _send_sms_str(subscr, sender, str, 0);
+	talloc_free(str);
+
+err:
+	if (sender)
+		subscr_put(sender);
+
+	if (subscr)
+		subscr_put(subscr);
+
+	return rc;
+}
+
+DEFUN(subscriber_silent_sms,
+      subscriber_silent_sms_cmd,
+
+      "subscriber " SUBSCR_TYPES " ID silent-sms sender " SUBSCR_TYPES " SENDER_ID send .LINE",
+	SUBSCR_HELP "Silent SMS Operations\n" SUBSCR_HELP "Send SMS\n" "Actual SMS Text\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]);
+	char *str;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		rc = CMD_WARNING;
+		goto err;
+	}
+
+	if (!sender) {
+		vty_out(vty, "%% No sender found for %s %s%s",
+			argv[2], argv[3], VTY_NEWLINE);
+		rc = CMD_WARNING;
+		goto err;
+	}
+
+	str = argv_concat(argv, argc, 4);
+	rc = _send_sms_str(subscr, sender, str, 64);
+	talloc_free(str);
+
+err:
+	if (sender)
+		subscr_put(sender);
+
+	if (subscr)
+		subscr_put(subscr);
+
+	return rc;
+}
+
+#define CHAN_TYPES "(any|tch/f|tch/any|sdcch)"
+#define CHAN_TYPE_HELP 			\
+		"Any channel\n"		\
+		"TCH/F channel\n"	\
+		"Any TCH channel\n"	\
+		"SDCCH channel\n"
+
+DEFUN(subscriber_silent_call_start,
+      subscriber_silent_call_start_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-call start (any|tch/f|tch/any|sdcch)",
+	SUBSCR_HELP "Silent call operation\n" "Start silent call\n"
+	CHAN_TYPE_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int rc, type;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[2], "tch/f"))
+		type = RSL_CHANNEED_TCH_F;
+	else if (!strcmp(argv[2], "tch/any"))
+		type = RSL_CHANNEED_TCH_ForH;
+	else if (!strcmp(argv[2], "sdcch"))
+		type = RSL_CHANNEED_SDCCH;
+	else
+		type = RSL_CHANNEED_ANY;	/* Defaults to ANY */
+
+	rc = gsm_silent_call_start(subscr, vty, type);
+	if (rc <= 0) {
+		vty_out(vty, "%% Subscriber not attached%s",
+			VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_silent_call_stop,
+      subscriber_silent_call_stop_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-call stop",
+	SUBSCR_HELP "Silent call operation\n" "Stop silent call\n"
+	CHAN_TYPE_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = gsm_silent_call_stop(subscr);
+	if (rc < 0) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_ussd_notify,
+      subscriber_ussd_notify_cmd,
+      "subscriber " SUBSCR_TYPES " ID ussd-notify (0|1|2) .TEXT",
+      SUBSCR_HELP "Send a USSD notify to the subscriber\n"
+      "Alerting Level 0\n"
+      "Alerting Level 1\n"
+      "Alerting Level 2\n"
+      "Text of USSD message to send\n")
+{
+	char *text;
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int level;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	level = atoi(argv[2]);
+	text = argv_concat(argv, argc, 3);
+	if (!text) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	conn = connection_for_subscr(subscr);
+	if (!conn) {
+		vty_out(vty, "%% An active connection is required for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		subscr_put(subscr);
+		talloc_free(text);
+		return CMD_WARNING;
+	}
+
+	msc_send_ussd_notify(conn, level, text);
+	msc_send_ussd_release_complete(conn);
+
+	subscr_put(subscr);
+	talloc_free(text);
+	return CMD_SUCCESS;
+}
+
+static int loop_by_char(uint8_t ch)
+{
+	switch (ch) {
+	case 'a':
+		return GSM414_LOOP_A;
+	case 'b':
+		return GSM414_LOOP_B;
+	case 'c':
+		return GSM414_LOOP_C;
+	case 'd':
+		return GSM414_LOOP_D;
+	case 'e':
+		return GSM414_LOOP_E;
+	case 'f':
+		return GSM414_LOOP_F;
+	case 'i':
+		return GSM414_LOOP_I;
+	}
+	return -1;
+}
+
+DEFUN(subscriber_mstest_close,
+      subscriber_mstest_close_cmd,
+      "subscriber " SUBSCR_TYPES " ID ms-test close-loop (a|b|c|d|e|f|i)",
+      SUBSCR_HELP "Send a TS 04.14 MS Test Command to subscriber\n"
+      "Close a TCH Loop inside the MS\n"
+      "Loop Type A\n"
+      "Loop Type B\n"
+      "Loop Type C\n"
+      "Loop Type D\n"
+      "Loop Type E\n"
+      "Loop Type F\n"
+      "Loop Type I\n")
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *loop_str;
+	int loop_mode;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	loop_str = argv[2];
+	loop_mode = loop_by_char(loop_str[0]);
+
+	conn = connection_for_subscr(subscr);
+	if (!conn) {
+		vty_out(vty, "%% An active connection is required for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	gsm0414_tx_close_tch_loop_cmd(conn, loop_mode);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_mstest_open,
+      subscriber_mstest_open_cmd,
+      "subscriber " SUBSCR_TYPES " ID ms-test open-loop",
+      SUBSCR_HELP "Send a TS 04.14 MS Test Command to subscriber\n"
+      "Open a TCH Loop inside the MS\n")
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	conn = connection_for_subscr(subscr);
+	if (!conn) {
+		vty_out(vty, "%% An active connection is required for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	gsm0414_tx_open_loop_cmd(conn);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_delete,
+      ena_subscr_delete_cmd,
+      "subscriber " SUBSCR_TYPES " ID delete",
+	SUBSCR_HELP "Delete subscriber in HLR\n")
+{
+	int rc;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (subscr->use_count != 1) {
+		vty_out(vty, "Removing active subscriber%s", VTY_NEWLINE);
+	}
+
+	rc = db_subscriber_delete(subscr);
+	subscr_put(subscr);
+
+	if (rc != 0) {
+		vty_out(vty, "Failed to remove subscriber%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_expire,
+      ena_subscr_expire_cmd,
+      "subscriber " SUBSCR_TYPES " ID expire",
+	SUBSCR_HELP "Expire the subscriber Now\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr->expire_lu = time(0);
+	db_sync_subscriber(subscr);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_authorized,
+      ena_subscr_authorized_cmd,
+      "subscriber " SUBSCR_TYPES " ID authorized (0|1)",
+	SUBSCR_HELP "(De-)Authorize subscriber in HLR\n"
+	"Subscriber should NOT be authorized\n"
+	"Subscriber should be authorized\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr->authorized = atoi(argv[2]);
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_name,
+      ena_subscr_name_cmd,
+      "subscriber " SUBSCR_TYPES " ID name .NAME",
+	SUBSCR_HELP "Set the name of the subscriber\n"
+	"Name of the Subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *name;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	name = argv_concat(argv, argc, 2);
+	if (!name) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	if (strlen(name) > sizeof(subscr->name)-1) {
+		vty_out(vty,
+			"%% NAME is too long, max. %zu characters are allowed%s",
+			sizeof(subscr->name)-1, VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	osmo_strlcpy(subscr->name, name, sizeof(subscr->name));
+	talloc_free(name);
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_extension,
+      ena_subscr_extension_cmd,
+      "subscriber " SUBSCR_TYPES " ID extension EXTENSION",
+	SUBSCR_HELP "Set the extension (phone number) of the subscriber\n"
+	"Extension (phone number)\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *ext = argv[2];
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (strlen(ext) > sizeof(subscr->extension)-1) {
+		vty_out(vty,
+			"%% EXTENSION is too long, max. %zu characters are allowed%s",
+			sizeof(subscr->extension)-1, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	osmo_strlcpy(subscr->extension, ext, sizeof(subscr->extension));
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_handover,
+      ena_subscr_handover_cmd,
+      "subscriber " SUBSCR_TYPES " ID handover BTS_NR",
+	SUBSCR_HELP "Handover the active connection\n"
+	"Number of the BTS to handover to\n")
+{
+	int ret;
+	struct gsm_subscriber_connection *conn;
+	struct gsm_bts *bts;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s.%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	conn = connection_for_subscr(subscr);
+	if (!conn) {
+		vty_out(vty, "%% No active connection for subscriber %s %s.%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(gsmnet, atoi(argv[2]));
+	if (!bts) {
+		vty_out(vty, "%% BTS with number(%d) could not be found.%s",
+			atoi(argv[2]), VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	/* now start the handover */
+	ret = bsc_handover_start(conn->lchan, bts);
+	if (ret != 0) {
+		vty_out(vty, "%% Handover failed with errno %d.%s",
+			ret, VTY_NEWLINE);
+	} else {
+		vty_out(vty, "%% Handover started from %s",
+			gsm_lchan_name(conn->lchan));
+		vty_out(vty, " to %s.%s", gsm_lchan_name(conn->ho_lchan),
+			VTY_NEWLINE);
+	}
+
+	subscr_put(subscr);
+	return CMD_SUCCESS;
+}
+
+#define A3A8_ALG_TYPES "(none|xor|comp128v1|comp128v2|comp128v3)"
+#define A3A8_ALG_HELP 			\
+	"Use No A3A8 algorithm\n"	\
+	"Use XOR algorithm\n"		\
+	"Use COMP128v1 algorithm\n" \
+	"Use COMP128v2 algorithm\n" \
+	"Use COMP128v3 algorithm\n"
+
+DEFUN(ena_subscr_a3a8,
+      ena_subscr_a3a8_cmd,
+      "subscriber " SUBSCR_TYPES " ID a3a8 " A3A8_ALG_TYPES " [KI]",
+      SUBSCR_HELP "Set a3a8 parameters for the subscriber\n"
+      A3A8_ALG_HELP "Encryption Key Ki\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *alg_str = argv[2];
+	const char *ki_str = argc == 4 ? argv[3] : NULL;
+	struct gsm_auth_info ainfo;
+	int rc, minlen, maxlen;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcasecmp(alg_str, "none")) {
+		ainfo.auth_algo = AUTH_ALGO_NONE;
+		minlen = maxlen = 0;
+	} else if (!strcasecmp(alg_str, "xor")) {
+		ainfo.auth_algo = AUTH_ALGO_XOR;
+		minlen = A38_XOR_MIN_KEY_LEN;
+		maxlen = A38_XOR_MAX_KEY_LEN;
+	} else if (!strcasecmp(alg_str, "comp128v1")) {
+		ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+		minlen = maxlen = A38_COMP128_KEY_LEN;
+	} else if (!strcasecmp(alg_str, "comp128v2")) {
+		ainfo.auth_algo = AUTH_ALGO_COMP128v2;
+		minlen = maxlen = A38_COMP128_KEY_LEN;
+	} else if (!strcasecmp(alg_str, "comp128v3")) {
+		ainfo.auth_algo = AUTH_ALGO_COMP128v3;
+		minlen = maxlen = A38_COMP128_KEY_LEN;
+	} else {
+		/* Unknown method */
+		subscr_put(subscr);
+		vty_out(vty, "%% Unknown auth method %s%s",
+				alg_str, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (ki_str) {
+		rc = osmo_hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+		if ((rc > maxlen) || (rc < minlen)) {
+			subscr_put(subscr);
+			vty_out(vty, "%% Wrong Ki `%s'%s",
+				ki_str, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ainfo.a3a8_ki_len = rc;
+	} else {
+		ainfo.a3a8_ki_len = 0;
+		if (minlen) {
+			subscr_put(subscr);
+			vty_out(vty, "%% Missing Ki argument%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	rc = db_sync_authinfo_for_subscr(
+		ainfo.auth_algo == AUTH_ALGO_NONE ? NULL : &ainfo,
+		subscr);
+
+	/* the last tuple probably invalid with the new auth settings */
+	db_sync_lastauthtuple_for_subscr(NULL, subscr);
+	subscr_put(subscr);
+
+	if (rc) {
+		vty_out(vty, "%% Operation has failed%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_purge,
+      subscriber_purge_cmd,
+      "subscriber purge-inactive",
+      "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	int purged;
+
+	purged = subscr_purge_inactive(net->subscr_group);
+	vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_update,
+      subscriber_update_cmd,
+      "subscriber " SUBSCR_TYPES " ID update",
+      SUBSCR_HELP "Update the subscriber data from the dabase.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_update_from_db(subscr);
+	subscr_put(subscr);
+	return CMD_SUCCESS;
+}
+
+static int scall_cbfn(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct scall_signal_data *sigdata = signal_data;
+	struct vty *vty = sigdata->data;
+
+	switch (signal) {
+	case S_SCALL_SUCCESS:
+		vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s",
+			sigdata->conn->lchan->ts->trx->arfcn, sigdata->conn->lchan->ts->nr,
+			VTY_NEWLINE);
+		break;
+	case S_SCALL_EXPIRED:
+		vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE);
+		break;
+	}
+	return 0;
+}
+
+DEFUN(show_stats,
+      show_stats_cmd,
+      "show statistics",
+	SHOW_STR "Display network statistics\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	openbsc_vty_print_statistics(vty, net);
+	vty_out(vty, "Location Update         : %lu attach, %lu normal, %lu periodic%s",
+		net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH].current,
+		net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL].current,
+		net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC].current,
+		VTY_NEWLINE);
+	vty_out(vty, "IMSI Detach Indications : %lu%s",
+		net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH].current,
+		VTY_NEWLINE);
+	vty_out(vty, "Location Updating Results: %lu completed, %lu failed%s",
+		net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED].current,
+		net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED].current,
+		VTY_NEWLINE);
+	vty_out(vty, "Handover                : %lu attempted, %lu no_channel, %lu timeout, "
+		"%lu completed, %lu failed%s",
+		net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED].current,
+		net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL].current,
+		net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT].current,
+		net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED].current,
+		net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED].current,
+		VTY_NEWLINE);
+	vty_out(vty, "SMS MO                  : %lu submitted, %lu no receiver%s",
+		net->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED].current,
+		net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER].current,
+		VTY_NEWLINE);
+	vty_out(vty, "SMS MT                  : %lu delivered, %lu no memory, %lu other error%s",
+		net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED].current,
+		net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM].current,
+		net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER].current,
+		VTY_NEWLINE);
+	vty_out(vty, "MO Calls                : %lu setup, %lu connect ack%s",
+		net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP].current,
+		net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK].current,
+		VTY_NEWLINE);
+	vty_out(vty, "MT Calls                : %lu setup, %lu connect%s",
+		net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP].current,
+		net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT].current,
+		VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_smsqueue,
+      show_smsqueue_cmd,
+      "show sms-queue",
+      SHOW_STR "Display SMSqueue statistics\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_stats(net->sms_queue, vty);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_trigger,
+      smsqueue_trigger_cmd,
+      "sms-queue trigger",
+      "SMS Queue\n" "Trigger sending messages\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_trigger(net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_max,
+      smsqueue_max_cmd,
+      "sms-queue max-pending <1-500>",
+      "SMS Queue\n" "SMS to deliver in parallel\n" "Amount\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_set_max_pending(net->sms_queue, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_clear,
+      smsqueue_clear_cmd,
+      "sms-queue clear",
+      "SMS Queue\n" "Clear the queue of pending SMS\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_clear(net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_fail,
+      smsqueue_fail_cmd,
+      "sms-queue max-failure <1-500>",
+      "SMS Queue\n" "Maximum amount of delivery failures\n" "Amount\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_set_max_failure(net->sms_queue, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_mncc_int, cfg_mncc_int_cmd,
+      "mncc-int", "Configure internal MNCC handler")
+{
+	vty->node = MNCC_INT_NODE;
+
+	return CMD_SUCCESS;
+}
+
+static struct cmd_node mncc_int_node = {
+	MNCC_INT_NODE,
+	"%s(config-mncc-int)# ",
+	1,
+};
+
+static const struct value_string tchf_codec_names[] = {
+	{ GSM48_CMODE_SPEECH_V1,	"fr" },
+	{ GSM48_CMODE_SPEECH_EFR,	"efr" },
+	{ GSM48_CMODE_SPEECH_AMR,	"amr" },
+	{ 0, NULL }
+};
+
+static const struct value_string tchh_codec_names[] = {
+	{ GSM48_CMODE_SPEECH_V1,	"hr" },
+	{ GSM48_CMODE_SPEECH_AMR,	"amr" },
+	{ 0, NULL }
+};
+
+static int config_write_mncc_int(struct vty *vty)
+{
+	uint16_t meas_port;
+	char *meas_host;
+	const char *meas_scenario;
+
+	meas_feed_cfg_get(&meas_host, &meas_port);
+	meas_scenario = meas_feed_scenario_get();
+
+	vty_out(vty, "mncc-int%s", VTY_NEWLINE);
+	vty_out(vty, " default-codec tch-f %s%s",
+		get_value_string(tchf_codec_names, mncc_int.def_codec[0]),
+		VTY_NEWLINE);
+	vty_out(vty, " default-codec tch-h %s%s",
+		get_value_string(tchh_codec_names, mncc_int.def_codec[1]),
+		VTY_NEWLINE);
+	if (meas_port)
+		vty_out(vty, " meas-feed destination %s %u%s",
+			meas_host, meas_port, VTY_NEWLINE);
+	if (strlen(meas_scenario) > 0)
+		vty_out(vty, " meas-feed scenario %s%s",
+			meas_scenario, VTY_NEWLINE);
+
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(mnccint_def_codec_f,
+      mnccint_def_codec_f_cmd,
+      "default-codec tch-f (fr|efr|amr)",
+      "Set default codec\n" "Codec for TCH/F\n"
+      "Full-Rate\n" "Enhanced Full-Rate\n" "Adaptive Multi-Rate\n")
+{
+	mncc_int.def_codec[0] = get_string_value(tchf_codec_names, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(mnccint_def_codec_h,
+      mnccint_def_codec_h_cmd,
+      "default-codec tch-h (hr|amr)",
+      "Set default codec\n" "Codec for TCH/H\n"
+      "Half-Rate\n" "Adaptive Multi-Rate\n")
+{
+	mncc_int.def_codec[1] = get_string_value(tchh_codec_names, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+#define OBSOLETE_MSG "Obsolete\n"
+/* this is just for backwards compatibility as the sms code moved into
+ * libosmocore and old config files no longer parse... */
+DEFUN_DEPRECATED(log_level_sms, log_level_sms_cmd,
+	"logging level sms (everything|debug|info|notice|error|fatal)",
+	".HIDDEN\n" OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG
+	OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG)
+{
+	vty_out(vty, "%% 'logging level sms' is now called 'logging level "
+		"lsms', please update your config %s", VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+#define MEAS_STR "Measurement export related\n"
+DEFUN(mnccint_meas_feed, mnccint_meas_feed_cmd,
+	"meas-feed destination ADDR <0-65535>",
+	MEAS_STR "destination\n" "address or hostname\n" "port number\n")
+{
+	int rc;
+
+	rc = meas_feed_cfg_set(argv[0], atoi(argv[1]));
+	if (rc < 0)
+		return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(meas_feed_scenario, meas_feed_scenario_cmd,
+	"meas-feed scenario NAME",
+	MEAS_STR "scenario\n" "Name up to 31 characters included in report\n")
+{
+	meas_feed_scenario_set(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(logging_fltr_imsi,
+      logging_fltr_imsi_cmd,
+      "logging filter imsi IMSI",
+	LOGGING_STR FILTER_STR
+      "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+	struct gsm_subscriber *vlr_subscr;
+	struct bsc_subscr *bsc_subscr;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+	const char *imsi = argv[0];
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	vlr_subscr = subscr_get_by_imsi(gsmnet->subscr_group, imsi);
+	bsc_subscr = bsc_subscr_find_by_imsi(gsmnet->bsc_subscribers, imsi);
+
+	if (!vlr_subscr && !bsc_subscr) {
+		vty_out(vty, "%%no subscriber with IMSI(%s)%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_filter_vlr_subscr(tgt, vlr_subscr);
+	log_set_filter_bsc_subscr(tgt, bsc_subscr);
+	return CMD_SUCCESS;
+}
+
+static struct cmd_node nitb_node = {
+	NITB_NODE,
+	"%s(config-nitb)# ",
+	1,
+};
+
+DEFUN(cfg_nitb, cfg_nitb_cmd,
+      "nitb", "Configure NITB options")
+{
+	vty->node = NITB_NODE;
+	return CMD_SUCCESS;
+}
+
+/* Note: limit on the parameter length is set by internal vty code limitations */
+DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd,
+      "subscriber-create-on-demand random <1-9999999999> <2-9999999999>",
+      "Set random parameters for a new record when a subscriber is first seen.\n"
+      "Set random parameters for a new record when a subscriber is first seen.\n"
+      "Minimum for subscriber extension\n""Maximum for subscriber extension\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	uint64_t mi = atoll(argv[0]), ma = atoll(argv[1]);
+	gsmnet->auto_create_subscr = true;
+	gsmnet->auto_assign_exten = true;
+	if (mi >= ma) {
+		vty_out(vty, "Incorrect range: %s >= %s, expected MIN < MAX%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	gsmnet->ext_min = mi;
+	gsmnet->ext_max = ma;
+        return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd,
+      "subscriber-create-on-demand [no-extension]",
+      "Make a new record when a subscriber is first seen.\n"
+      "Do not automatically assign extension to created subscribers\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->auto_create_subscr = true;
+	gsmnet->auto_assign_exten = argc ? false : true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd,
+      "no subscriber-create-on-demand",
+      NO_STR "Make a new record when a subscriber is first seen.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->auto_create_subscr = false;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_assign_tmsi, cfg_nitb_assign_tmsi_cmd,
+      "assign-tmsi",
+      "Assign TMSI during Location Updating.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->avoid_tmsi = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_no_assign_tmsi, cfg_nitb_no_assign_tmsi_cmd,
+      "no assign-tmsi",
+      NO_STR "Assign TMSI during Location Updating.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->avoid_tmsi = 1;
+	return CMD_SUCCESS;
+}
+
+static int config_write_nitb(struct vty *vty)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	vty_out(vty, "nitb%s", VTY_NEWLINE);
+	if (!gsmnet->auto_create_subscr)
+		vty_out(vty, " no subscriber-create-on-demand%s", VTY_NEWLINE);
+	else
+		vty_out(vty, " subscriber-create-on-demand%s%s",
+			gsmnet->auto_assign_exten ? "" : " no-extension",
+			VTY_NEWLINE);
+
+	if (gsmnet->ext_min != GSM_MIN_EXTEN || gsmnet->ext_max != GSM_MAX_EXTEN)
+		vty_out(vty, " subscriber-create-on-demand random %"PRIu64" %"
+			PRIu64"%s", gsmnet->ext_min, gsmnet->ext_max,
+			VTY_NEWLINE);
+	vty_out(vty, " %sassign-tmsi%s",
+		gsmnet->avoid_tmsi ? "no " : "", VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+	osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL);
+
+	install_element_ve(&show_subscr_cmd);
+	install_element_ve(&show_subscr_cache_cmd);
+
+	install_element_ve(&sms_send_pend_cmd);
+
+	install_element_ve(&subscriber_create_cmd);
+	install_element_ve(&subscriber_send_sms_cmd);
+	install_element_ve(&subscriber_silent_sms_cmd);
+	install_element_ve(&subscriber_silent_call_start_cmd);
+	install_element_ve(&subscriber_silent_call_stop_cmd);
+	install_element_ve(&subscriber_ussd_notify_cmd);
+	install_element_ve(&subscriber_mstest_close_cmd);
+	install_element_ve(&subscriber_mstest_open_cmd);
+	install_element_ve(&subscriber_update_cmd);
+	install_element_ve(&show_stats_cmd);
+	install_element_ve(&show_smsqueue_cmd);
+	install_element_ve(&logging_fltr_imsi_cmd);
+
+	install_element(ENABLE_NODE, &ena_subscr_delete_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_expire_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_name_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_extension_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_authorized_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_handover_cmd);
+	install_element(ENABLE_NODE, &subscriber_purge_cmd);
+	install_element(ENABLE_NODE, &smsqueue_trigger_cmd);
+	install_element(ENABLE_NODE, &smsqueue_max_cmd);
+	install_element(ENABLE_NODE, &smsqueue_clear_cmd);
+	install_element(ENABLE_NODE, &smsqueue_fail_cmd);
+	install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd);
+	install_element(ENABLE_NODE, &meas_feed_scenario_cmd);
+
+	install_element(CONFIG_NODE, &cfg_mncc_int_cmd);
+	install_node(&mncc_int_node, config_write_mncc_int);
+	install_element(MNCC_INT_NODE, &mnccint_def_codec_f_cmd);
+	install_element(MNCC_INT_NODE, &mnccint_def_codec_h_cmd);
+	install_element(MNCC_INT_NODE, &mnccint_meas_feed_cmd);
+	install_element(MNCC_INT_NODE, &meas_feed_scenario_cmd);
+
+	install_element(CFG_LOG_NODE, &log_level_sms_cmd);
+	install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+
+	install_element(CONFIG_NODE, &cfg_nitb_cmd);
+	install_node(&nitb_node, config_write_nitb);
+	install_element(NITB_NODE, &cfg_nitb_subscr_create_cmd);
+	install_element(NITB_NODE, &cfg_nitb_subscr_random_cmd);
+	install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd);
+	install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd);
+	install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/libtrau/Makefile.am b/openbsc/src/libtrau/Makefile.am
new file mode 100644
index 0000000..46becd6
--- /dev/null
+++ b/openbsc/src/libtrau/Makefile.am
@@ -0,0 +1,31 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+noinst_LIBRARIES = \
+	libtrau.a \
+	$(NULL)
+
+libtrau_a_SOURCES = \
+	rtp_proxy.c \
+	trau_mux.c \
+	trau_upqueue.c \
+	$(NULL)
diff --git a/openbsc/src/libtrau/rtp_proxy.c b/openbsc/src/libtrau/rtp_proxy.c
new file mode 100644
index 0000000..6b38ee5
--- /dev/null
+++ b/openbsc/src/libtrau/rtp_proxy.c
@@ -0,0 +1,764 @@
+/* RTP proxy handling for ip.access nanoBTS */
+
+/* (C) 2009-2013 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>    /* gettimeofday() */
+#include <unistd.h>      /* get..() */
+#include <time.h>        /* clock() */
+#include <sys/utsname.h> /* uname() */
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <openbsc/debug.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/mncc.h>
+#include <openbsc/trau_upqueue.h>
+
+#include <osmocom/netif/rtp.h>
+
+/* attempt to determine byte order */
+#include <sys/param.h>
+#include <limits.h>
+
+static LLIST_HEAD(rtp_sockets);
+
+/* should we mangle the CNAME inside SDES of RTCP packets? We disable
+ * this by default, as it seems to be not needed */
+static int mangle_rtcp_cname = 0;
+
+enum rtp_bfd_priv {
+	RTP_PRIV_NONE,
+	RTP_PRIV_RTP,
+	RTP_PRIV_RTCP
+};
+
+#define RTP_ALLOC_SIZE	1500
+
+#define RTCP_TYPE_SDES	202
+	
+#define RTCP_IE_CNAME	1
+
+
+#define RTP_VERSION	2
+
+/* 33 for FR, all other codecs have smaller size */
+#define MAX_RTP_PAYLOAD_LEN	33
+
+/* decode an rtp frame and create a new buffer with payload */
+static int rtp_decode(struct msgb *msg, uint32_t callref, struct msgb **data)
+{
+	struct msgb *new_msg;
+	struct gsm_data_frame *frame;
+	struct rtp_hdr *rtph = (struct rtp_hdr *)msg->data;
+	struct rtp_x_hdr *rtpxh;
+	uint8_t *payload, *payload_out;
+	int payload_len;
+	int msg_type;
+	int x_len;
+
+	if (msg->len < 12) {
+		DEBUGPC(DLMUX, "received RTP frame too short (len = %d)\n",
+			msg->len);
+		return -EINVAL;
+	}
+	if (rtph->version != RTP_VERSION) {
+		DEBUGPC(DLMUX, "received RTP version %d not supported.\n",
+			rtph->version);
+		return -EINVAL;
+	}
+	payload = msg->data + sizeof(struct rtp_hdr) + (rtph->csrc_count << 2);
+	payload_len = msg->len - sizeof(struct rtp_hdr) - (rtph->csrc_count << 2);
+	if (payload_len < 0) {
+		DEBUGPC(DLMUX, "received RTP frame too short (len = %d, "
+			"csrc count = %d)\n", msg->len, rtph->csrc_count);
+		return -EINVAL;
+	}
+	if (rtph->extension) {
+		if (payload_len < sizeof(struct rtp_x_hdr)) {
+			DEBUGPC(DLMUX, "received RTP frame too short for "
+				"extension header\n");
+			return -EINVAL;
+		}
+		rtpxh = (struct rtp_x_hdr *)payload;
+		x_len = ntohs(rtpxh->length) * 4 + sizeof(struct rtp_x_hdr);
+		payload += x_len;
+		payload_len -= x_len;
+		if (payload_len < 0) {
+			DEBUGPC(DLMUX, "received RTP frame too short, "
+				"extension header exceeds frame length\n");
+			return -EINVAL;
+		}
+	}
+	if (rtph->padding) {
+		if (payload_len < 1) {
+			DEBUGPC(DLMUX, "received RTP frame too short for "
+				"padding length\n");
+			return -EINVAL;
+		}
+		payload_len -= payload[payload_len - 1];
+		if (payload_len < 0) {
+			DEBUGPC(DLMUX, "received RTP frame with padding "
+				"greater than payload\n");
+			return -EINVAL;
+		}
+	}
+
+	switch (rtph->payload_type) {
+	case RTP_PT_GSM_FULL:
+		msg_type = GSM_TCHF_FRAME;
+		if (payload_len != RTP_LEN_GSM_FULL) {
+			DEBUGPC(DLMUX, "received RTP full rate frame with "
+				"payload length != %d (len = %d)\n",
+				RTP_LEN_GSM_FULL, payload_len);
+			return -EINVAL;
+		}
+		break;
+	case RTP_PT_GSM_EFR:
+		msg_type = GSM_TCHF_FRAME_EFR;
+		if (payload_len != RTP_LEN_GSM_EFR) {
+			DEBUGPC(DLMUX, "received RTP extended full rate frame "
+				"with payload length != %d (len = %d)\n",
+				RTP_LEN_GSM_EFR, payload_len);
+			return -EINVAL;
+		}
+		break;
+	case RTP_PT_GSM_HALF:
+		msg_type = GSM_TCHH_FRAME;
+		if (payload_len != RTP_LEN_GSM_HALF) {
+			DEBUGPC(DLMUX, "received RTP half rate frame with "
+				"payload length != %d (len = %d)\n",
+				RTP_LEN_GSM_HALF, payload_len);
+			return -EINVAL;
+		}
+		break;
+	case RTP_PT_AMR:
+		msg_type = GSM_TCH_FRAME_AMR;
+		break;
+	default:
+		DEBUGPC(DLMUX, "received RTP frame with unknown payload "
+			"type %d\n", rtph->payload_type);
+		return -EINVAL;
+	}
+
+	if (payload_len > MAX_RTP_PAYLOAD_LEN ||
+	    (rtph->payload_type == RTP_PT_AMR &&
+	     payload_len > MAX_RTP_PAYLOAD_LEN - 1)) {
+		DEBUGPC(DLMUX, "RTP payload too large (%d octets)\n",
+			payload_len);
+		return -EINVAL;
+	}
+
+	/* always allocate for the maximum possible size to avoid
+	 * fragmentation */
+	new_msg = msgb_alloc(sizeof(struct gsm_data_frame) +
+			     MAX_RTP_PAYLOAD_LEN+1, "GSM-DATA (TCH)");
+
+	if (!new_msg)
+		return -ENOMEM;
+	frame = (struct gsm_data_frame *) msgb_put(new_msg, sizeof(struct gsm_data_frame));
+	frame->msg_type = msg_type;
+	frame->callref = callref;
+	if (rtph->payload_type == RTP_PT_AMR) {
+		/* for FR/HR/EFR the length is implicit.  In AMR, we
+		 * need to make it explicit by using the first byte of
+		 * the data[] buffer as length byte */
+		uint8_t *data0 = msgb_put(new_msg, 1);
+		*data0 = payload_len;
+	}
+	payload_out = msgb_put(new_msg, payload_len);
+	memcpy(payload_out, payload, payload_len);
+
+	*data = new_msg;
+	return 0;
+}
+
+/*! \brief encode and send a rtp frame
+ *  \param[in] rs RTP socket through which we shall send
+ *  \param[in] frame GSM RTP frame to be sent
+ */
+int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame)
+{
+	struct rtp_sub_socket *rss = &rs->rtp;
+	struct msgb *msg;
+	struct rtp_hdr *rtph;
+	uint8_t *payload;
+	int payload_type;
+	int payload_len;
+	int duration; /* in samples */
+	int is_bfi = 0;
+
+	if (rs->tx_action != RTP_SEND_DOWNSTREAM) {
+		/* initialize sequences */
+		rs->tx_action = RTP_SEND_DOWNSTREAM;
+		rs->transmit.ssrc = rand();
+		rs->transmit.sequence = random();
+		rs->transmit.timestamp = random();
+	}
+
+	switch (frame->msg_type) {
+	case GSM_TCHF_FRAME:
+		payload_type = RTP_PT_GSM_FULL;
+		payload_len = RTP_LEN_GSM_FULL;
+		duration = RTP_GSM_DURATION;
+		break;
+	case GSM_TCHF_FRAME_EFR:
+		payload_type = RTP_PT_GSM_EFR;
+		payload_len = RTP_LEN_GSM_EFR;
+		duration = RTP_GSM_DURATION;
+		break;
+	case GSM_TCHH_FRAME:
+		payload_type = RTP_PT_GSM_HALF;
+		payload_len = RTP_LEN_GSM_HALF;
+		duration = RTP_GSM_DURATION;
+		break;
+	case GSM_TCH_FRAME_AMR:
+		payload_type = RTP_PT_AMR;
+		payload_len = frame->data[0];
+		duration = RTP_GSM_DURATION;
+		break;
+	case GSM_BAD_FRAME:
+		payload_type = 0;
+		payload_len = 0;
+		duration = RTP_GSM_DURATION;
+		is_bfi = 1;
+		break;
+	default:
+		DEBUGPC(DLMUX, "unsupported message type %d\n",
+			frame->msg_type);
+		return -EINVAL;
+	}
+
+	if (payload_len > MAX_RTP_PAYLOAD_LEN) {
+		DEBUGPC(DLMUX, "RTP payload too large (%d octets)\n",
+			payload_len);
+		return -EINVAL;
+	}
+
+	if (is_bfi) {
+		/* In case of a bad frame, just count and drop packet. */
+		rs->transmit.timestamp += duration;
+		rs->transmit.sequence++;
+		return 0;
+	}
+
+	msg = msgb_alloc(sizeof(struct rtp_hdr) + payload_len, "RTP-GSM");
+	if (!msg)
+		return -ENOMEM;
+	rtph = (struct rtp_hdr *) msgb_put(msg, sizeof(struct rtp_hdr));
+	rtph->version = RTP_VERSION;
+	rtph->padding = 0;
+	rtph->extension = 0;
+	rtph->csrc_count = 0;
+	rtph->marker = 0;
+	rtph->payload_type = payload_type;
+	rtph->sequence = htons(rs->transmit.sequence++);
+	rtph->timestamp = htonl(rs->transmit.timestamp);
+	rs->transmit.timestamp += duration;
+	rtph->ssrc = htonl(rs->transmit.ssrc);
+
+	payload = msgb_put(msg, payload_len);
+	if (frame->msg_type == GSM_TCH_FRAME_AMR)
+		memcpy(payload, frame->data + 1, payload_len);
+	else
+		memcpy(payload, frame->data, payload_len);
+	msgb_enqueue(&rss->tx_queue, msg);
+	rss->bfd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+/* iterate over all chunks in one RTCP message, look for CNAME IEs and
+ * replace all of those with 'new_cname' */
+static int rtcp_sdes_cname_mangle(struct msgb *msg, struct rtcp_hdr *rh,
+				  uint16_t *rtcp_len, const char *new_cname)
+{
+	uint8_t *rtcp_end;
+	uint8_t *cur = (uint8_t *) rh;
+	uint8_t tag, len = 0;
+
+	rtcp_end = cur + *rtcp_len;
+	/* move cur to end of RTP header */
+	cur += sizeof(*rh);
+
+	/* iterate over Chunks */
+	while (cur+4 < rtcp_end) {
+		/* skip four bytes SSRC/CSRC */
+		cur += 4;
+	
+		/* iterate over IE's inside the chunk */
+		while (cur+1 < rtcp_end) {
+			tag = *cur++;
+			if (tag == 0) {
+				/* end of chunk, skip additional zero */
+				while ((*cur++ == 0) && (cur < rtcp_end)) { }
+				break;
+			}
+			len = *cur++;
+	
+			if (tag == RTCP_IE_CNAME) {
+				/* we've found the CNAME, lets mangle it */
+				if (len < strlen(new_cname)) {
+					/* we need to make more space */
+					int increase = strlen(new_cname) - len;
+
+					msgb_push(msg, increase);
+					memmove(cur+len+increase, cur+len,
+						rtcp_end - (cur+len));
+					/* FIXME: we have to respect RTCP
+					 * padding/alignment rules! */
+					len += increase;
+					*(cur-1) += increase;
+					rtcp_end += increase;
+					*rtcp_len += increase;
+				}
+				/* copy new CNAME into message */
+				memcpy(cur, new_cname, strlen(new_cname));
+				/* FIXME: zero the padding in case new CNAME
+				 * is smaller than old one !!! */
+			}
+			cur += len;
+		}
+	}
+
+	return 0;
+}
+
+static int rtcp_mangle(struct msgb *msg, struct rtp_socket *rs)
+{
+	struct rtp_sub_socket *rss = &rs->rtcp;
+	struct rtcp_hdr *rtph;
+	uint16_t old_len;
+	int rc;
+
+	if (!mangle_rtcp_cname)
+		return 0;
+
+	printf("RTCP\n");
+	/* iterate over list of RTCP messages */
+	rtph = (struct rtcp_hdr *)msg->data;
+	while ((void *)rtph + sizeof(*rtph) <= (void *)msg->data + msg->len) {
+		old_len = (ntohs(rtph->length) + 1) * 4;
+		if ((void *)rtph + old_len > (void *)msg->data + msg->len) {
+			DEBUGPC(DLMUX, "received RTCP packet too short for "
+				"length element\n");
+			return -EINVAL;
+		}
+		if (rtph->type == RTCP_TYPE_SDES) {
+			char new_cname[255];
+			osmo_strlcpy(new_cname,
+				     inet_ntoa(rss->sin_local.sin_addr),
+				     sizeof(new_cname));
+			rc = rtcp_sdes_cname_mangle(msg, rtph, &old_len,
+						    new_cname);
+			if (rc < 0)
+				return rc;
+		}
+		rtph = (void *)rtph + old_len;
+	}
+
+	return 0;
+}
+
+/* read from incoming RTP/RTCP socket */
+static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss)
+{
+	int rc;
+	struct msgb *msg = msgb_alloc(RTP_ALLOC_SIZE, "RTP/RTCP");
+	struct msgb *new_msg;
+	struct rtp_sub_socket *other_rss;
+
+	if (!msg)
+		return -ENOMEM;
+
+	rc = read(rss->bfd.fd, msg->data, RTP_ALLOC_SIZE);
+	if (rc == 0) {
+		rss->bfd.when &= ~BSC_FD_READ;
+		goto out_free;
+	} else if (rc < 0) {
+		/* Ignore "connection refused". this happens, If we open the
+		 * socket faster than the remote side. */
+		if (errno == ECONNREFUSED)
+			goto out_free;
+		DEBUGPC(DLMUX, "Read of RTP socket (%p) failed (errno %d, "
+			"%s)\n", rs, errno, strerror(errno));
+		rss->bfd.when &= ~BSC_FD_READ;
+		goto out_free;
+	}
+
+	msgb_put(msg, rc);
+
+	switch (rs->rx_action) {
+	case RTP_PROXY:
+		if (!rs->proxy.other_sock) {
+			rc = -EIO;
+			goto out_free;
+		}
+		if (rss->bfd.priv_nr == RTP_PRIV_RTP)
+			other_rss = &rs->proxy.other_sock->rtp;
+		else if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
+			other_rss = &rs->proxy.other_sock->rtcp;
+			/* modify RTCP SDES CNAME */
+			rc = rtcp_mangle(msg, rs);
+			if (rc < 0)
+				goto out_free;
+		} else {
+			rc = -EINVAL;
+			goto out_free;
+		}
+		msgb_enqueue(&other_rss->tx_queue, msg);
+		other_rss->bfd.when |= BSC_FD_WRITE;
+		break;
+
+	case RTP_RECV_UPSTREAM:
+		if (!rs->receive.callref || !rs->receive.net) {
+			rc = -EIO;
+			goto out_free;
+		}
+		if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
+			if (!mangle_rtcp_cname) {
+				msgb_free(msg);
+				break;
+			}
+			/* modify RTCP SDES CNAME */
+			rc = rtcp_mangle(msg, rs);
+			if (rc < 0)
+				goto out_free;
+			msgb_enqueue(&rss->tx_queue, msg);
+			rss->bfd.when |= BSC_FD_WRITE;
+			break;
+		}
+		if (rss->bfd.priv_nr != RTP_PRIV_RTP) {
+			rc = -EINVAL;
+			goto out_free;
+		}
+		rc = rtp_decode(msg, rs->receive.callref, &new_msg);
+		if (rc < 0)
+			goto out_free;
+		msgb_free(msg);
+		trau_tx_to_mncc(rs->receive.net, new_msg);
+		break;
+
+	case RTP_NONE: /* if socket exists, but disabled by app */
+		msgb_free(msg);
+		break;
+	}
+
+	return 0;
+
+out_free:
+	msgb_free(msg);
+	return rc;
+}
+
+/* \brief write from tx_queue to RTP/RTCP socket */
+static int rtp_socket_write(struct rtp_socket *rs, struct rtp_sub_socket *rss)
+{
+	struct msgb *msg;
+	int written;
+
+	msg = msgb_dequeue(&rss->tx_queue);
+	if (!msg) {
+		rss->bfd.when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+
+	written = write(rss->bfd.fd, msg->data, msg->len);
+	if (written < msg->len) {
+		LOGP(DLMIB, LOGL_ERROR, "short write");
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+
+/*! \brief callback for the select.c:bfd_* layer */
+static int rtp_bfd_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+	struct rtp_socket *rs = bfd->data;
+	struct rtp_sub_socket *rss;
+
+	switch (bfd->priv_nr) {
+	case RTP_PRIV_RTP:
+		rss = &rs->rtp;
+		break;
+	case RTP_PRIV_RTCP:
+		rss = &rs->rtcp;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (flags & BSC_FD_READ)
+		rtp_socket_read(rs, rss);
+
+	if (flags & BSC_FD_WRITE)
+		rtp_socket_write(rs, rss);
+
+	return 0;
+}
+
+/*! \brief initialize one rtp sub-socket */
+static void init_rss(struct rtp_sub_socket *rss,
+		     struct rtp_socket *rs, int fd, int priv_nr)
+{
+	/* initialize bfd */
+	rss->bfd.fd = fd;
+	rss->bfd.data = rs;
+	rss->bfd.priv_nr = priv_nr;
+	rss->bfd.cb = rtp_bfd_cb;
+}
+
+/*! \brief create a new RTP/RTCP socket and bind it */
+struct rtp_socket *rtp_socket_create(void)
+{
+	int rc;
+	struct rtp_socket *rs;
+
+	DEBUGP(DLMUX, "rtp_socket_create(): ");
+
+	rs = talloc_zero(tall_bsc_ctx, struct rtp_socket);
+	if (!rs)
+		return NULL;
+
+	INIT_LLIST_HEAD(&rs->rtp.tx_queue);
+	INIT_LLIST_HEAD(&rs->rtcp.tx_queue);
+
+	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (rc < 0)
+		goto out_free;
+
+	init_rss(&rs->rtp, rs, rc, RTP_PRIV_RTP);
+	rc = osmo_fd_register(&rs->rtp.bfd);
+	if (rc < 0)
+		goto out_rtp_socket;
+
+	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (rc < 0)
+		goto out_rtp_bfd;
+
+	init_rss(&rs->rtcp, rs, rc, RTP_PRIV_RTCP);
+	rc = osmo_fd_register(&rs->rtcp.bfd);
+	if (rc < 0)
+		goto out_rtcp_socket;
+
+	DEBUGPC(DLMUX, "success\n");
+
+	rc = rtp_socket_bind(rs, INADDR_ANY);
+	if (rc < 0)
+		goto out_rtcp_bfd;
+
+	return rs;
+
+out_rtcp_bfd:
+	osmo_fd_unregister(&rs->rtcp.bfd);
+out_rtcp_socket:
+	close(rs->rtcp.bfd.fd);
+out_rtp_bfd:
+	osmo_fd_unregister(&rs->rtp.bfd);
+out_rtp_socket:
+	close(rs->rtp.bfd.fd);
+out_free:
+	talloc_free(rs);
+	DEBUGPC(DLMUX, "failed\n");
+	return NULL;
+}
+
+static int rtp_sub_socket_bind(struct rtp_sub_socket *rss, uint32_t ip,
+				uint16_t port)
+{
+	int rc;
+	socklen_t alen = sizeof(rss->sin_local);
+
+	rss->sin_local.sin_family = AF_INET;
+	rss->sin_local.sin_addr.s_addr = htonl(ip);
+	rss->sin_local.sin_port = htons(port);
+	rss->bfd.when |= BSC_FD_READ;
+
+	rc = bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+		  sizeof(rss->sin_local));
+	if (rc < 0)
+		return rc;
+
+	/* retrieve the address we actually bound to, in case we
+	 * passed INADDR_ANY as IP address */
+	return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+			   &alen);
+}
+
+#define RTP_PORT_BASE	30000
+static unsigned int next_udp_port = RTP_PORT_BASE;
+
+/*! \brief bind a RTP socket to a specific local address
+ *  \param[in] rs RTP socket to be bound
+ *  \param[in] ip local IP address to which socket is to be bound
+ */
+int rtp_socket_bind(struct rtp_socket *rs, uint32_t ip)
+{
+	int rc = -EIO;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DLMUX, "rtp_socket_bind(rs=%p, IP=%s): ", rs,
+		inet_ntoa(ia));
+
+	/* try to bind to a consecutive pair of ports */
+	for (next_udp_port = next_udp_port % 0xffff;
+	     next_udp_port < 0xffff; next_udp_port += 2) {
+		rc = rtp_sub_socket_bind(&rs->rtp, ip, next_udp_port);
+		if (rc != 0)
+			continue;
+
+		rc = rtp_sub_socket_bind(&rs->rtcp, ip, next_udp_port+1);
+		if (rc == 0)
+			break;
+	}
+	if (rc < 0) {
+		DEBUGPC(DLMUX, "failed\n");
+		return rc;
+	}
+
+	ia.s_addr = rs->rtp.sin_local.sin_addr.s_addr;
+	DEBUGPC(DLMUX, "BOUND_IP=%s, BOUND_PORT=%u\n",
+		inet_ntoa(ia), ntohs(rs->rtp.sin_local.sin_port));
+	return ntohs(rs->rtp.sin_local.sin_port);
+}
+
+static int rtp_sub_socket_connect(struct rtp_sub_socket *rss,
+				  uint32_t ip, uint16_t port)
+{
+	int rc;
+	socklen_t alen = sizeof(rss->sin_local);
+
+	rss->sin_remote.sin_family = AF_INET;
+	rss->sin_remote.sin_addr.s_addr = htonl(ip);
+	rss->sin_remote.sin_port = htons(port);
+
+	rc = connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote,
+		     sizeof(rss->sin_remote));
+	if (rc < 0)
+		return rc;
+
+	return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+			   &alen);
+}
+
+/*! \brief 'connect' a RTP socket to a remote peer
+ *  \param[in] rs RTP socket to be connected
+ *  \param[in] ip remote IP address to which to connect
+ *  \param[in] port remote UDP port number to which to connect
+ */
+int rtp_socket_connect(struct rtp_socket *rs, uint32_t ip, uint16_t port)
+{
+	int rc;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DLMUX, "rtp_socket_connect(rs=%p, ip=%s, port=%u)\n",
+		rs, inet_ntoa(ia), port);
+
+	rc = rtp_sub_socket_connect(&rs->rtp, ip, port);
+	if (rc < 0)
+		return rc;
+
+	return rtp_sub_socket_connect(&rs->rtcp, ip, port+1);
+}
+
+/*! \brief bind two RTP/RTCP sockets together in the proxy
+ *  \param[in] this First RTP socket
+ *  \param[in] other Second RTP socket
+ */
+int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other)
+{
+	DEBUGP(DLMUX, "rtp_socket_proxy(this=%p, other=%p)\n",
+		this, other);
+
+	this->rx_action = RTP_PROXY;
+	this->proxy.other_sock = other;
+
+	other->rx_action = RTP_PROXY;
+	other->proxy.other_sock = this;
+
+	return 0;
+}
+
+/*! \brief bind RTP/RTCP socket to application, disabling proxy
+ *  \param[in] this RTP socket
+ *  \param[in] net gsm_network argument to trau_tx_to_mncc()
+ *  \param[in] callref callref argument to trau_tx_to_mncc()
+ */
+int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net,
+			uint32_t callref)
+{
+	DEBUGP(DLMUX, "rtp_socket_proxy(this=%p, callref=%u)\n",
+		this, callref);
+
+	if (callref) {
+		this->rx_action = RTP_RECV_UPSTREAM;
+		this->receive.net = net;
+		this->receive.callref = callref;
+	} else
+		this->rx_action = RTP_NONE;
+
+	return 0;
+}
+
+static void free_tx_queue(struct rtp_sub_socket *rss)
+{
+	struct msgb *msg;
+	
+	while ((msg = msgb_dequeue(&rss->tx_queue)))
+		msgb_free(msg);
+}
+
+/*! \brief Free/release a previously allocated RTP socket
+ *  \param[in[] rs RTP/RTCP socket to be released
+ */
+int rtp_socket_free(struct rtp_socket *rs)
+{
+	DEBUGP(DLMUX, "rtp_socket_free(rs=%p)\n", rs);
+
+	/* make sure we don't leave references dangling to us */
+	if (rs->rx_action == RTP_PROXY &&
+	    rs->proxy.other_sock)
+		rs->proxy.other_sock->proxy.other_sock = NULL;
+
+	osmo_fd_unregister(&rs->rtp.bfd);
+	close(rs->rtp.bfd.fd);
+	free_tx_queue(&rs->rtp);
+
+	osmo_fd_unregister(&rs->rtcp.bfd);
+	close(rs->rtcp.bfd.fd);
+	free_tx_queue(&rs->rtcp);
+
+	talloc_free(rs);
+
+	return 0;
+}
diff --git a/openbsc/src/libtrau/trau_mux.c b/openbsc/src/libtrau/trau_mux.c
new file mode 100644
index 0000000..b37c765
--- /dev/null
+++ b/openbsc/src/libtrau/trau_mux.c
@@ -0,0 +1,547 @@
+/* Simple TRAU frame reflector to route voice calls */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/abis/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/debug.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/trau_upqueue.h>
+#include <osmocom/core/crcgen.h>
+#include <openbsc/transaction.h>
+
+/* this corresponds to the bit-lengths of the individual codec
+ * parameters as indicated in Table 1.1 of TS 06.10 */
+static const uint8_t gsm_fr_map[] = {
+	6, 6, 5, 5, 4, 4, 3, 3,
+	7, 2, 2, 6, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 7, 2, 2, 6, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 7, 2, 2, 6, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 7, 2, 2, 6, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3
+};
+
+
+/*
+ * EFR TRAU parity
+ *
+ * g(x) = x^3 + x^1 + 1
+ */
+static const struct osmo_crc8gen_code gsm0860_efr_crc3 = {
+	.bits = 3,
+	.poly = 0x3,
+	.init = 0x0,
+	.remainder = 0x7,
+};
+
+/* EFR parity bits */
+static inline void efr_parity_bits_1(ubit_t *check_bits, const ubit_t *d_bits)
+{
+	memcpy(check_bits + 0 , d_bits + 0, 22);
+	memcpy(check_bits + 22 , d_bits + 24, 3);
+	check_bits[25] = d_bits[28];
+}
+
+static inline void efr_parity_bits_2(ubit_t *check_bits, const ubit_t *d_bits)
+{
+	memcpy(check_bits + 0 , d_bits + 42, 10);
+	memcpy(check_bits + 10 , d_bits + 90, 2);
+}
+
+static inline void efr_parity_bits_3(ubit_t *check_bits, const ubit_t *d_bits)
+{
+	memcpy(check_bits + 0 , d_bits + 98, 5);
+	check_bits[5] = d_bits[104];
+	memcpy(check_bits + 6 , d_bits + 143, 2);
+}
+
+static inline void efr_parity_bits_4(ubit_t *check_bits, const ubit_t *d_bits)
+{
+	memcpy(check_bits + 0 , d_bits + 151, 10);
+	memcpy(check_bits + 10 , d_bits + 199, 2);
+}
+
+static inline void efr_parity_bits_5(ubit_t *check_bits, const ubit_t *d_bits)
+{
+	memcpy(check_bits + 0 , d_bits + 207, 5);
+	check_bits[5] = d_bits[213];
+	memcpy(check_bits + 6 , d_bits + 252, 2);
+}
+
+struct map_entry {
+	struct llist_head list;
+	struct gsm_e1_subslot src, dst;
+};
+
+struct upqueue_entry {
+	struct llist_head list;
+	struct gsm_network *net;
+	struct gsm_e1_subslot src;
+	uint32_t callref;
+};
+
+static LLIST_HEAD(ss_map);
+static LLIST_HEAD(ss_upqueue);
+
+void *tall_map_ctx, *tall_upq_ctx;
+
+/* map one particular subslot to another subslot */
+int trau_mux_map(const struct gsm_e1_subslot *src,
+		 const struct gsm_e1_subslot *dst)
+{
+	struct map_entry *me;
+
+	me = talloc(tall_map_ctx, struct map_entry);
+	if (!me) {
+		LOGP(DLMIB, LOGL_FATAL, "Out of memory\n");
+		return -ENOMEM;
+	}
+
+	DEBUGP(DCC, "Setting up TRAU mux map between (e1=%u,ts=%u,ss=%u) "
+		"and (e1=%u,ts=%u,ss=%u)\n",
+		src->e1_nr, src->e1_ts, src->e1_ts_ss,
+		dst->e1_nr, dst->e1_ts, dst->e1_ts_ss);
+
+	/* make sure to get rid of any stale old mappings */
+	trau_mux_unmap(src, 0);
+	trau_mux_unmap(dst, 0);
+
+	memcpy(&me->src, src, sizeof(me->src));
+	memcpy(&me->dst, dst, sizeof(me->dst));
+	llist_add(&me->list, &ss_map);
+
+	return 0;
+}
+
+int trau_mux_map_lchan(const struct gsm_lchan *src,	
+			const struct gsm_lchan *dst)
+{
+	struct gsm_e1_subslot *src_ss, *dst_ss;
+
+	src_ss = &src->ts->e1_link;
+	dst_ss = &dst->ts->e1_link;
+
+	return trau_mux_map(src_ss, dst_ss);
+}
+
+
+/* unmap one particular subslot from another subslot */
+int trau_mux_unmap(const struct gsm_e1_subslot *ss, uint32_t callref)
+{
+	struct map_entry *me, *me2;
+	struct upqueue_entry *ue, *ue2;
+
+	if (ss)
+		llist_for_each_entry_safe(me, me2, &ss_map, list) {
+			if (!memcmp(&me->src, ss, sizeof(*ss)) ||
+			    !memcmp(&me->dst, ss, sizeof(*ss))) {
+				llist_del(&me->list);
+				return 0;
+			}
+		}
+	llist_for_each_entry_safe(ue, ue2, &ss_upqueue, list) {
+		if (ue->callref == callref) {
+			llist_del(&ue->list);
+			return 0;
+		}
+		if (ss && !memcmp(&ue->src, ss, sizeof(*ss))) {
+			llist_del(&ue->list);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+/* look-up an enty in the TRAU mux map */
+static struct gsm_e1_subslot *
+lookup_trau_mux_map(const struct gsm_e1_subslot *src)
+{
+	struct map_entry *me;
+
+	llist_for_each_entry(me, &ss_map, list) {
+		if (!memcmp(&me->src, src, sizeof(*src)))
+			return &me->dst;
+		if (!memcmp(&me->dst, src, sizeof(*src)))
+			return &me->src;
+	}
+	return NULL;
+}
+
+/* look-up an enty in the TRAU upqueue */
+struct upqueue_entry *
+lookup_trau_upqueue(const struct gsm_e1_subslot *src)
+{
+	struct upqueue_entry *ue;
+
+	llist_for_each_entry(ue, &ss_upqueue, list) {
+		if (!memcmp(&ue->src, src, sizeof(*src)))
+			return ue;
+	}
+	return NULL;
+}
+
+static const uint8_t c_bits_check_fr[] = { 0, 0, 0, 1, 0 };
+static const uint8_t c_bits_check_efr[] = { 1, 1, 0, 1, 0 };
+
+struct msgb *trau_decode_fr(uint32_t callref,
+	const struct decoded_trau_frame *tf)
+{
+	struct msgb *msg;
+	struct gsm_data_frame *frame;
+	unsigned char *data;
+	int i, j, k, l, o;
+
+	msg = msgb_alloc(sizeof(struct gsm_data_frame) + 33,
+				 "GSM-DATA");
+	if (!msg)
+		return NULL;
+
+	frame = (struct gsm_data_frame *)msg->data;
+	memset(frame, 0, sizeof(struct gsm_data_frame));
+	data = frame->data;
+	data[0] = 0xd << 4;
+	/* reassemble d-bits */
+	i = 0; /* counts bits */
+	j = 4; /* counts output bits */
+	k = gsm_fr_map[0]-1; /* current number bit in element */
+	l = 0; /* counts element bits */
+	o = 0; /* offset input bits */
+	while (i < 260) {
+		data[j/8] |= (tf->d_bits[k+o] << (7-(j%8)));
+		/* to avoid out-of-bounds access in gsm_fr_map[++l] */
+		if (i == 259)
+			break;
+		if (--k < 0) {
+			o += gsm_fr_map[l];
+			k = gsm_fr_map[++l]-1;
+		}
+		i++;
+		j++;
+	}
+	if (tf->c_bits[11]) /* BFI */
+		frame->msg_type = GSM_BAD_FRAME;
+	else
+		frame->msg_type = GSM_TCHF_FRAME;
+	frame->callref = callref;
+	msgb_put(msg, sizeof(struct gsm_data_frame) + 33);
+
+	return msg;
+}
+
+struct msgb *trau_decode_efr(uint32_t callref,
+	const struct decoded_trau_frame *tf)
+{
+	struct msgb *msg;
+	struct gsm_data_frame *frame;
+	unsigned char *data;
+	int i, j, rc;
+	ubit_t check_bits[26];
+
+	msg = msgb_alloc(sizeof(struct gsm_data_frame) + 31,
+				 "GSM-DATA");
+	if (!msg)
+		return NULL;
+
+	frame = (struct gsm_data_frame *)msg->data;
+	memset(frame, 0, sizeof(struct gsm_data_frame));
+	frame->msg_type = GSM_TCHF_FRAME_EFR;
+	frame->callref = callref;
+	msgb_put(msg, sizeof(struct gsm_data_frame) + 31);
+
+	if (tf->c_bits[11]) /* BFI */
+		goto bad_frame;
+
+	data = frame->data;
+	data[0] = 0xc << 4;
+	/* reassemble d-bits */
+	for (i = 1, j = 4; i < 39; i++, j++)
+		data[j/8] |= (tf->d_bits[i] << (7-(j%8)));
+	efr_parity_bits_1(check_bits, tf->d_bits);
+	rc = osmo_crc8gen_check_bits(&gsm0860_efr_crc3, check_bits, 26,
+			tf->d_bits + 39);
+	if (rc)
+		goto bad_frame;
+	for (i = 42, j = 42; i < 95; i++, j++)
+		data[j/8] |= (tf->d_bits[i] << (7-(j%8)));
+	efr_parity_bits_2(check_bits, tf->d_bits);
+	rc = osmo_crc8gen_check_bits(&gsm0860_efr_crc3, check_bits, 12,
+			tf->d_bits + 95);
+	if (rc)
+		goto bad_frame;
+	for (i = 98, j = 95; i < 148; i++, j++)
+		data[j/8] |= (tf->d_bits[i] << (7-(j%8)));
+	efr_parity_bits_3(check_bits, tf->d_bits);
+	rc = osmo_crc8gen_check_bits(&gsm0860_efr_crc3, check_bits, 8,
+			tf->d_bits + 148);
+	if (rc)
+		goto bad_frame;
+	for (i = 151, j = 145; i < 204; i++, j++)
+		data[j/8] |= (tf->d_bits[i] << (7-(j%8)));
+	efr_parity_bits_4(check_bits, tf->d_bits);
+	rc = osmo_crc8gen_check_bits(&gsm0860_efr_crc3, check_bits, 12,
+			tf->d_bits + 204);
+	if (rc)
+		goto bad_frame;
+	for (i = 207, j = 198; i < 257; i++, j++)
+		data[j/8] |= (tf->d_bits[i] << (7-(j%8)));
+	efr_parity_bits_5(check_bits, tf->d_bits);
+	rc = osmo_crc8gen_check_bits(&gsm0860_efr_crc3, check_bits, 8,
+			tf->d_bits + 257);
+	if (rc)
+		goto bad_frame;
+
+	return msg;
+
+bad_frame:
+	frame->msg_type = GSM_BAD_FRAME;
+
+	return msg;
+}
+
+/* we get called by subchan_demux */
+int trau_mux_input(struct gsm_e1_subslot *src_e1_ss,
+		   const uint8_t *trau_bits, int num_bits)
+{
+	struct decoded_trau_frame tf;
+	uint8_t trau_bits_out[TRAU_FRAME_BITS];
+	struct gsm_e1_subslot *dst_e1_ss = lookup_trau_mux_map(src_e1_ss);
+	struct subch_mux *mx;
+	struct upqueue_entry *ue;
+	int rc;
+
+	/* decode TRAU, change it to downlink, re-encode */
+	rc = decode_trau_frame(&tf, trau_bits);
+	if (rc)
+		return rc;
+
+	if (!dst_e1_ss) {
+		struct msgb *msg = NULL;
+		/* frame shall be sent to upqueue */
+		if (!(ue = lookup_trau_upqueue(src_e1_ss)))
+			return -EINVAL;
+		if (!ue->callref)
+			return -EINVAL;
+		if (!memcmp(tf.c_bits, c_bits_check_fr, 5))
+			msg = trau_decode_fr(ue->callref, &tf);
+		else if (!memcmp(tf.c_bits, c_bits_check_efr, 5))
+			msg = trau_decode_efr(ue->callref, &tf);
+		else {
+			DEBUGPC(DLMUX, "illegal trau (C1-C5) %s\n",
+				osmo_hexdump(tf.c_bits, 5));
+			DEBUGPC(DLMUX, "test trau (C1-C5) %s\n",
+				osmo_hexdump(c_bits_check_efr, 5));
+			return -EINVAL;
+		}
+		if (!msg)
+			return -ENOMEM;
+		trau_tx_to_mncc(ue->net, msg);
+
+		return 0;
+	}
+
+	mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts);
+	if (!mx)
+		return -EINVAL;
+
+	trau_frame_up2down(&tf);
+	encode_trau_frame(trau_bits_out, &tf);
+
+	/* and send it to the muxer */
+	return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out,
+				   TRAU_FRAME_BITS);
+}
+
+/* callback when a TRAU frame was received */
+int subch_cb(struct subch_demux *dmx, int ch, uint8_t *data, int len,
+	     void *_priv)
+{
+	struct e1inp_ts *e1i_ts = _priv;
+	struct gsm_e1_subslot src_ss;
+
+	src_ss.e1_nr = e1i_ts->line->num;
+	src_ss.e1_ts = e1i_ts->num;
+	src_ss.e1_ts_ss = ch;
+
+	return trau_mux_input(&src_ss, data, len);
+}
+
+/* add receiver instance for lchan and callref */
+int trau_recv_lchan(struct gsm_lchan *lchan, uint32_t callref)
+{
+	struct gsm_e1_subslot *src_ss;
+	struct upqueue_entry *ue;
+
+	ue = talloc(tall_upq_ctx, struct upqueue_entry);
+	if (!ue)
+		return -ENOMEM;
+
+	src_ss = &lchan->ts->e1_link;
+
+	DEBUGP(DCC, "Setting up TRAU receiver (e1=%u,ts=%u,ss=%u) "
+		"and (callref 0x%x)\n",
+		src_ss->e1_nr, src_ss->e1_ts, src_ss->e1_ts_ss,
+		callref);
+
+	/* make sure to get rid of any stale old mappings */
+	trau_mux_unmap(src_ss, callref);
+
+	memcpy(&ue->src, src_ss, sizeof(ue->src));
+	ue->net = lchan->ts->trx->bts->network;
+	ue->callref = callref;
+	llist_add(&ue->list, &ss_upqueue);
+
+	return 0;
+}
+
+void trau_encode_fr(struct decoded_trau_frame *tf,
+	const unsigned char *data)
+{
+	int i, j, k, l, o;
+
+	/* set c-bits and t-bits */
+	tf->c_bits[0] = 1;
+	tf->c_bits[1] = 1;
+	tf->c_bits[2] = 1;
+	tf->c_bits[3] = 0;
+	tf->c_bits[4] = 0;
+	memset(&tf->c_bits[5], 0, 6);
+	memset(&tf->c_bits[11], 1, 10);
+	memset(&tf->t_bits[0], 1, 4);
+	/* reassemble d-bits */
+	i = 0; /* counts bits */
+	j = 4; /* counts input bits */
+	k = gsm_fr_map[0]-1; /* current number bit in element */
+	l = 0; /* counts element bits */
+	o = 0; /* offset output bits */
+	while (i < 260) {
+		tf->d_bits[k+o] = (data[j/8] >> (7-(j%8))) & 1;
+		/* to avoid out-of-bounds access in gsm_fr_map[++l] */
+		if (i == 259)
+			break;
+		if (--k < 0) {
+			o += gsm_fr_map[l];
+			k = gsm_fr_map[++l]-1;
+		}
+		i++;
+		j++;
+	}
+}
+
+void trau_encode_efr(struct decoded_trau_frame *tf,
+	const unsigned char *data)
+{
+	int i, j;
+	ubit_t check_bits[26];
+
+	/* set c-bits and t-bits */
+	tf->c_bits[0] = 1;
+	tf->c_bits[1] = 1;
+	tf->c_bits[2] = 0;
+	tf->c_bits[3] = 1;
+	tf->c_bits[4] = 0;
+	memset(&tf->c_bits[5], 0, 6);
+	memset(&tf->c_bits[11], 1, 10);
+	memset(&tf->t_bits[0], 1, 4);
+	/* reassemble d-bits */
+	tf->d_bits[0] = 1;
+	for (i = 1, j = 4; i < 39; i++, j++)
+		tf->d_bits[i] = (data[j/8] >> (7-(j%8))) & 1;
+	efr_parity_bits_1(check_bits, tf->d_bits);
+	osmo_crc8gen_set_bits(&gsm0860_efr_crc3, check_bits, 26,
+			tf->d_bits + 39);
+	for (i = 42, j = 42; i < 95; i++, j++)
+		tf->d_bits[i] = (data[j/8] >> (7-(j%8))) & 1;
+	efr_parity_bits_2(check_bits, tf->d_bits);
+	osmo_crc8gen_set_bits(&gsm0860_efr_crc3, check_bits, 12,
+			tf->d_bits + 95);
+	for (i = 98, j = 95; i < 148; i++, j++)
+		tf->d_bits[i] = (data[j/8] >> (7-(j%8))) & 1;
+	efr_parity_bits_3(check_bits, tf->d_bits);
+	osmo_crc8gen_set_bits(&gsm0860_efr_crc3, check_bits, 8,
+			tf->d_bits + 148);
+	for (i = 151, j = 145; i < 204; i++, j++)
+		tf->d_bits[i] = (data[j/8] >> (7-(j%8))) & 1;
+	efr_parity_bits_4(check_bits, tf->d_bits);
+	osmo_crc8gen_set_bits(&gsm0860_efr_crc3, check_bits, 12,
+			tf->d_bits + 204);
+	for (i = 207, j = 198; i < 257; i++, j++)
+		tf->d_bits[i] = (data[j/8] >> (7-(j%8))) & 1;
+	efr_parity_bits_5(check_bits, tf->d_bits);
+	osmo_crc8gen_set_bits(&gsm0860_efr_crc3, check_bits, 8,
+			tf->d_bits + 257);
+}
+
+int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame)
+{
+	uint8_t trau_bits_out[TRAU_FRAME_BITS];
+	struct gsm_e1_subslot *dst_e1_ss = &lchan->ts->e1_link;
+	struct subch_mux *mx;
+	struct decoded_trau_frame tf;
+
+	mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts);
+	if (!mx)
+		return -EINVAL;
+
+	switch (frame->msg_type) {
+	case GSM_TCHF_FRAME:
+		trau_encode_fr(&tf, frame->data);
+		break;
+	case GSM_TCHF_FRAME_EFR:
+		trau_encode_efr(&tf, frame->data);
+		break;
+	default:
+		DEBUGPC(DLMUX, "unsupported message type %d\n",
+			frame->msg_type);
+		return -EINVAL;
+	}
+
+	encode_trau_frame(trau_bits_out, &tf);
+
+	/* and send it to the muxer */
+	return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out,
+				   TRAU_FRAME_BITS);
+}
+
+/* switch trau muxer to new lchan */
+int switch_trau_mux(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan)
+{
+	struct gsm_network *net = old_lchan->ts->trx->bts->network;
+	struct gsm_trans *trans;
+
+	/* look up transaction with TCH frame receive enabled */
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->conn && trans->conn->lchan == old_lchan && trans->tch_recv) {
+			/* switch */
+			trau_recv_lchan(new_lchan, trans->callref);
+		}
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/libtrau/trau_upqueue.c b/openbsc/src/libtrau/trau_upqueue.c
new file mode 100644
index 0000000..f8edaf0
--- /dev/null
+++ b/openbsc/src/libtrau/trau_upqueue.c
@@ -0,0 +1,27 @@
+/* trau_upqueue.c - Pass msgb's up the chain */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+
+void trau_tx_to_mncc(struct gsm_network *net, struct msgb *msg)
+{
+	net->mncc_recv(net, msg);
+}
diff --git a/openbsc/src/osmo-bsc/Makefile.am b/openbsc/src/osmo-bsc/Makefile.am
new file mode 100644
index 0000000..343af70
--- /dev/null
+++ b/openbsc/src/osmo-bsc/Makefile.am
@@ -0,0 +1,55 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOCTRL_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+bin_PROGRAMS = \
+	osmo-bsc-sccplite \
+	$(NULL)
+
+osmo_bsc_sccplite_SOURCES = \
+	osmo_bsc_main.c \
+	osmo_bsc_vty.c \
+	osmo_bsc_api.c \
+	osmo_bsc_grace.c \
+	osmo_bsc_msc.c \
+	osmo_bsc_sccp.c \
+	osmo_bsc_filter.c \
+	osmo_bsc_bssap.c \
+	osmo_bsc_audio.c \
+	osmo_bsc_ctrl.c \
+	$(NULL)
+
+# once again since TRAU uses CC symbol :(
+osmo_bsc_sccplite_LDADD = \
+	$(top_builddir)/src/libfilter/libfilter.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOSCCP_LIBS) \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
+	$(COVERAGE_LDFLAGS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(NULL)
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_api.c b/openbsc/src/osmo-bsc/osmo_bsc_api.c
new file mode 100644
index 0000000..25d48c3
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_api.c
@@ -0,0 +1,529 @@
+/* (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/debug.h>
+
+#include <openbsc/gsm_04_80.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#define return_when_not_connected(conn) \
+	if (!conn->sccp_con) {\
+		LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
+		return; \
+	}
+
+#define return_when_not_connected_val(conn, ret) \
+	if (!conn->sccp_con) {\
+		LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
+		return ret; \
+	}
+
+#define queue_msg_or_return(resp) \
+	if (!resp) { \
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \
+		return; \
+	} \
+	bsc_queue_for_msc(conn->sccp_con, resp);
+
+static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
+static int complete_layer3(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg, struct bsc_msc_data *msc);
+
+static struct osmo_cell_global_id *cgi_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+	static struct osmo_cell_global_id cgi;
+	cgi.lai.plmn = msc->network->plmn;
+	if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+		cgi.lai.plmn.mcc = msc->core_plmn.mcc;
+	if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID) {
+		cgi.lai.plmn.mnc = msc->core_plmn.mnc;
+		cgi.lai.plmn.mnc_3_digits = msc->core_plmn.mnc_3_digits;
+	}
+	cgi.lai.lac = (msc->core_lac != -1) ? msc->core_lac : bts->location_area_code;
+	cgi.cell_identity = (msc->core_ci != -1) ? msc->core_ci : bts->cell_identity;
+
+	return &cgi;
+}
+
+static void bsc_maybe_lu_reject(struct gsm_subscriber_connection *conn, int con_type, int cause)
+{
+	struct msgb *msg;
+
+	/* ignore cm service request or such */
+	if (con_type != FLT_CON_TYPE_LU)
+		return;
+
+	msg = gsm48_create_loc_upd_rej(cause);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+		return;
+	}
+
+	msg->lchan = conn->lchan;
+	gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+static int bsc_filter_initial(struct osmo_bsc_data *bsc,
+				struct bsc_msc_data *msc,
+				struct gsm_subscriber_connection *conn,
+				struct msgb *msg, char **imsi, int *con_type,
+				int *lu_cause)
+{
+	struct bsc_filter_request req;
+	struct bsc_filter_reject_cause cause;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc;
+
+	req.ctx = conn;
+	req.black_list = NULL;
+	req.access_lists = bsc_access_lists();
+	req.local_lst_name = msc->acc_lst_name;
+	req.global_lst_name = conn->bts->network->bsc_data->acc_lst_name;
+	req.bsc_nr = 0;
+
+	rc = bsc_msg_filter_initial(gh, msgb_l3len(msg), &req,
+				con_type, imsi, &cause);
+	*lu_cause = cause.lu_reject_cause;
+	return rc;
+}
+
+static int bsc_filter_data(struct gsm_subscriber_connection *conn,
+				struct msgb *msg, int *lu_cause)
+{
+	struct bsc_filter_request req;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct bsc_filter_reject_cause cause;
+	int rc;
+
+	req.ctx = conn;
+	req.black_list = NULL;
+	req.access_lists = bsc_access_lists();
+	req.local_lst_name = conn->sccp_con->msc->acc_lst_name;
+	req.global_lst_name = conn->bts->network->bsc_data->acc_lst_name;
+	req.bsc_nr = 0;
+
+	rc = bsc_msg_filter_data(gh, msgb_l3len(msg), &req,
+				&conn->sccp_con->filter_state,
+				&cause);
+	*lu_cause = cause.lu_reject_cause;
+	return rc;
+}
+
+static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci);
+
+	resp = gsm0808_create_sapi_reject(dlci);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn,
+				  struct msgb *msg, uint8_t chosen_encr)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n");
+	resp = gsm0808_create_cipher_complete(msg, chosen_encr);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn,
+				 struct msgb *msg, const char *text)
+{
+	struct gsm48_hdr *gh;
+	int8_t pdisc;
+	uint8_t mtype;
+	int drop_message = 1;
+
+	if (!text)
+		return;
+
+	if (!msg || msgb_l3len(msg) < sizeof(*gh))
+		return;
+
+	gh = msgb_l3(msg);
+	pdisc = gsm48_hdr_pdisc(gh);
+	mtype = gsm48_hdr_msg_type(gh);
+
+	/* Is CM service request? */
+	if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
+		struct gsm48_service_request *cm;
+
+		cm = (struct gsm48_service_request *) &gh->data[0];
+
+		/* Is type SMS or call? */
+		if (cm->cm_service_type == GSM48_CMSERV_SMS)
+			drop_message = 0;
+		else if (cm->cm_service_type == GSM48_CMSERV_MO_CALL_PACKET)
+			drop_message = 0;
+	}
+
+	if (drop_message) {
+		LOGP(DMSC, LOGL_DEBUG, "Skipping (not sending) USSD message: '%s'\n", text);
+		return;
+	}
+
+	LOGP(DMSC, LOGL_INFO, "Sending CM Service Accept\n");
+	gsm48_tx_mm_serv_ack(conn);
+
+	LOGP(DMSC, LOGL_INFO, "Sending USSD message: '%s'\n", text);
+	bsc_send_ussd_notify(conn, 1, text);
+	bsc_send_ussd_release_complete(conn);
+}
+
+/*
+ * Instruct to reserve data for a new connectiom, create the complete
+ * layer three message, send it to open the connection.
+ */
+static int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+			uint16_t chosen_channel)
+{
+	struct bsc_msc_data *msc;
+
+	LOGP(DMSC, LOGL_INFO, "Tx MSC COMPL L3\n");
+
+	/* find the MSC link we want to use */
+	msc = bsc_find_msc(conn, msg);
+	if (!msc) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to find a MSC for a connection.\n");
+		bsc_send_ussd_no_srv(conn, msg,
+				     conn->bts->network->bsc_data->ussd_no_msc_txt);
+		return -1;
+	}
+
+	return complete_layer3(conn, msg, msc);
+}
+
+static int complete_layer3(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg, struct bsc_msc_data *msc)
+{
+	int con_type, rc, lu_cause;
+	char *imsi = NULL;
+	struct timeval tv;
+	struct msgb *resp;
+	enum bsc_con ret;
+	int send_ping = msc->advanced_ping;
+
+	/* Advanced ping/pong handling */
+	if (osmo_timer_pending(&msc->pong_timer))
+		send_ping = 0;
+	if (msc->ping_timeout <= 0)
+		send_ping = 0;
+	if (send_ping && osmo_timer_remaining(&msc->ping_timer, NULL, &tv) == -1)
+		send_ping = 0;
+
+	/* Check the filter */
+	rc = bsc_filter_initial(msc->network->bsc_data, msc, conn, msg,
+				&imsi, &con_type, &lu_cause);
+	if (rc < 0) {
+		bsc_maybe_lu_reject(conn, con_type, lu_cause);
+		return BSC_API_CONN_POL_REJECT;
+	}
+
+	/* allocate resource for a new connection */
+	ret = bsc_create_new_connection(conn, msc, send_ping);
+
+	if (ret != BSC_CON_SUCCESS) {
+		/* allocation has failed */
+		if (ret == BSC_CON_REJECT_NO_LINK)
+			bsc_send_ussd_no_srv(conn, msg, msc->ussd_msc_lost_txt);
+		else if (ret == BSC_CON_REJECT_RF_GRACE)
+			bsc_send_ussd_no_srv(conn, msg, msc->ussd_grace_txt);
+
+		return BSC_API_CONN_POL_REJECT;
+	}
+
+	if (imsi)
+		conn->sccp_con->filter_state.imsi = talloc_steal(conn, imsi);
+	conn->sccp_con->filter_state.con_type = con_type;
+
+	/* check return value, if failed check msg for and send USSD */
+
+	bsc_scan_bts_msg(conn, msg);
+
+	resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp_con->msc, conn->bts), NULL);
+	if (!resp) {
+		LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n");
+		sccp_connection_free(conn->sccp_con->sccp);
+		bsc_delete_connection(conn->sccp_con);
+		return BSC_API_CONN_POL_REJECT;
+	}
+
+	if (bsc_open_connection(conn->sccp_con, resp) != 0) {
+		sccp_connection_free(conn->sccp_con->sccp);
+		bsc_delete_connection(conn->sccp_con);
+		msgb_free(resp);
+		return BSC_API_CONN_POL_REJECT;
+	}
+
+	return BSC_API_CONN_POL_ACCEPT;
+}
+
+/*
+ * Plastic surgery... we want to give up the current connection
+ */
+static int move_to_msc(struct gsm_subscriber_connection *_conn,
+		       struct msgb *msg, struct bsc_msc_data *msc)
+{
+	struct osmo_bsc_sccp_con *old_con = _conn->sccp_con;
+
+	/*
+	 * 1. Give up the old connection.
+	 * This happens by sending a clear request to the MSC,
+	 * it should end with the MSC releasing the connection.
+	 */
+	old_con->conn = NULL;
+	bsc_clear_request(_conn, 0);
+
+	/*
+	 * 2. Attempt to create a new connection to the local
+	 * MSC. If it fails the caller will need to handle this
+	 * properly.
+	 */
+	_conn->sccp_con = NULL;
+	if (complete_layer3(_conn, msg, msc) != BSC_API_CONN_POL_ACCEPT) {
+		gsm0808_clear(_conn);
+		bsc_subscr_con_free(_conn);
+		return 1;
+	}
+
+	return 2;
+}
+
+static int handle_cc_setup(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t pdisc = gsm48_hdr_pdisc(gh);
+	uint8_t mtype = gsm48_hdr_msg_type(gh);
+
+	struct bsc_msc_data *msc;
+	struct gsm_mncc_number called;
+	struct tlv_parsed tp;
+	unsigned payload_len;
+
+	char _dest_nr[35];
+
+	/*
+	 * Do we have a setup message here? if not return fast.
+	 */
+	if (pdisc != GSM48_PDISC_CC || mtype != GSM48_MT_CC_SETUP)
+		return 0;
+
+	payload_len = msgb_l3len(msg) - sizeof(*gh);
+
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+		LOGP(DMSC, LOGL_ERROR, "Called BCD not present in setup.\n");
+		return -1;
+	}
+
+	memset(&called, 0, sizeof(called));
+	gsm48_decode_called(&called,
+			    TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+	if (called.plan != 1 && called.plan != 0)
+		return 0;
+
+	if (called.plan == 1 && called.type == 1) {
+		_dest_nr[0] = _dest_nr[1] = '0';
+		memcpy(_dest_nr + 2, called.number, sizeof(called.number));
+	} else
+		memcpy(_dest_nr, called.number, sizeof(called.number));
+
+	/*
+	 * Check if the connection should be moved...
+	 */
+	llist_for_each_entry(msc, &conn->bts->network->bsc_data->mscs, entry) {
+		if (msc->type != MSC_CON_TYPE_LOCAL)
+			continue;
+		if (!msc->local_pref)
+			continue;
+		if (regexec(&msc->local_pref_reg, _dest_nr, 0, NULL, 0) != 0)
+			continue;
+
+		return move_to_msc(conn, msg, msc);
+	}
+
+	return 0;
+}
+
+
+static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+	int lu_cause;
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id);
+
+	/*
+	 * We might want to move this connection to a new MSC. Ask someone
+	 * to handle it. If it was handled we will return.
+	 */
+	if (handle_cc_setup(conn, msg) >= 1)
+		return;
+
+	/* Check the filter */
+	if (bsc_filter_data(conn, msg, &lu_cause) < 0) {
+		bsc_maybe_lu_reject(conn,
+					conn->sccp_con->filter_state.con_type,
+					lu_cause);
+		bsc_clear_request(conn, 0);
+		return;
+	}
+
+	bsc_scan_bts_msg(conn, msg);
+
+	resp = gsm0808_create_dtap(msg, link_id);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause,
+			     uint8_t chosen_channel, uint8_t encr_alg_id,
+			     uint8_t speech_model)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL\n");
+
+	resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel,
+						   encr_alg_id, speech_model);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_assign_fail(struct gsm_subscriber_connection *conn,
+			    uint8_t cause, uint8_t *rr_cause)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN FAIL\n");
+
+	resp = gsm0808_create_assignment_failure(cause, rr_cause);
+	queue_msg_or_return(resp);
+}
+
+static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	struct osmo_bsc_sccp_con *sccp;
+	struct msgb *resp;
+	return_when_not_connected_val(conn, 1);
+
+	LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n");
+
+	/*
+	 * Remove the connection from BSC<->SCCP part, the SCCP part
+	 * will either be cleared by channel release or MSC disconnect
+	 */
+	sccp = conn->sccp_con;
+	sccp->conn = NULL;
+	conn->sccp_con = NULL;
+
+	resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n");
+		return 1;
+	}
+
+	bsc_queue_for_msc(sccp, resp);
+	return 1;
+}
+
+static void bsc_cm_update(struct gsm_subscriber_connection *conn,
+			  const uint8_t *cm2, uint8_t cm2_len,
+			  const uint8_t *cm3, uint8_t cm3_len)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len);
+
+	queue_msg_or_return(resp);
+}
+
+static void bsc_mr_config(struct gsm_subscriber_connection *conn,
+				struct gsm_lchan *lchan, int full_rate)
+{
+	struct bsc_msc_data *msc;
+	struct gsm48_multi_rate_conf *ms_conf, *bts_conf;
+
+	if (!conn->sccp_con) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "No msc data available on conn %p. Audio will be broken.\n",
+		     conn);
+		return;
+	}
+
+	msc = conn->sccp_con->msc;
+
+	/* initialize the data structure */
+	lchan->mr_ms_lv[0] = sizeof(*ms_conf);
+	lchan->mr_bts_lv[0] = sizeof(*bts_conf);
+	ms_conf = (struct gsm48_multi_rate_conf *) &lchan->mr_ms_lv[1];
+	bts_conf = (struct gsm48_multi_rate_conf *) &lchan->mr_bts_lv[1];
+	memset(ms_conf, 0, sizeof(*ms_conf));
+	memset(bts_conf, 0, sizeof(*bts_conf));
+
+	bts_conf->ver = ms_conf->ver = 1;
+	bts_conf->icmi = ms_conf->icmi = 1;
+
+	/* maybe gcc see's it is copy of _one_ byte */
+	bts_conf->m4_75 = ms_conf->m4_75 = msc->amr_conf.m4_75;
+	bts_conf->m5_15 = ms_conf->m5_15 = msc->amr_conf.m5_15;
+	bts_conf->m5_90 = ms_conf->m5_90 = msc->amr_conf.m5_90;
+	bts_conf->m6_70 = ms_conf->m6_70 = msc->amr_conf.m6_70;
+	bts_conf->m7_40 = ms_conf->m7_40 = msc->amr_conf.m7_40;
+	bts_conf->m7_95 = ms_conf->m7_95 = msc->amr_conf.m7_95;
+	if (full_rate) {
+		bts_conf->m10_2 = ms_conf->m10_2 = msc->amr_conf.m10_2;
+		bts_conf->m12_2 = ms_conf->m12_2 = msc->amr_conf.m12_2;
+	}
+
+	/* now copy this into the bts structure */
+	memcpy(lchan->mr_bts_lv, lchan->mr_ms_lv, sizeof(lchan->mr_ms_lv));
+}
+
+static struct bsc_api bsc_handler = {
+	.sapi_n_reject = bsc_sapi_n_reject,
+	.cipher_mode_compl = bsc_cipher_mode_compl,
+	.compl_l3 = bsc_compl_l3,
+	.dtap  = bsc_dtap,
+	.assign_compl = bsc_assign_compl,
+	.assign_fail = bsc_assign_fail,
+	.clear_request = bsc_clear_request,
+	.classmark_chg = bsc_cm_update,
+	.mr_config = bsc_mr_config,
+};
+
+struct bsc_api *osmo_bsc_api()
+{
+	return &bsc_handler;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_audio.c b/openbsc/src/osmo-bsc/osmo_bsc_audio.c
new file mode 100644
index 0000000..1160209
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_audio.c
@@ -0,0 +1,73 @@
+/*
+ * ipaccess audio handling
+ *
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+
+#include <arpa/inet.h>
+
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+				 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber_connection *con;
+	struct gsm_lchan *lchan = signal_data;
+	int rc;
+
+	if (subsys != SS_ABISIP)
+		return 0;
+
+	con = lchan->conn;
+	if (!con || !con->sccp_con)
+		return 0;
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		/*
+		 * TODO: handle handover here... then the audio should go to
+		 * the old mgcp port..
+		 */
+		/* we can ask it to connect now */
+		LOGP(DMSC, LOGL_DEBUG, "Connecting BTS to port: %d conn: %d\n",
+		     con->sccp_con->rtp_port, lchan->abis_ip.conn_id);
+
+		rc = rsl_ipacc_mdcx(lchan, ntohl(INADDR_ANY),
+				    con->sccp_con->rtp_port,
+				    lchan->abis_ip.rtp_payload2);
+		if (rc < 0) {
+			LOGP(DMSC, LOGL_ERROR, "Failed to send MDCX: %d\n", rc);
+			return rc;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+int osmo_bsc_audio_init(struct gsm_network *net)
+{
+	osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, net);
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_bssap.c b/openbsc/src/osmo-bsc/osmo_bsc_bssap.c
new file mode 100644
index 0000000..26278d9
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_bssap.c
@@ -0,0 +1,592 @@
+/* GSM 08.08 BSSMAP handling						*/
+/* (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/paging.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sccp/sccp.h>
+
+/*
+ * helpers for the assignment command
+ */
+enum gsm0808_permitted_speech audio_support_to_gsm88(struct gsm_audio_support *audio)
+{
+	if (audio->hr) {
+		switch (audio->ver) {
+		case 1:
+			return GSM0808_PERM_HR1;
+			break;
+		case 2:
+			return GSM0808_PERM_HR2;
+			break;
+		case 3:
+			return GSM0808_PERM_HR3;
+			break;
+		default:
+			    LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver);
+			    return GSM0808_PERM_FR1;
+		}
+	} else {
+		switch (audio->ver) {
+		case 1:
+			return GSM0808_PERM_FR1;
+			break;
+		case 2:
+			return GSM0808_PERM_FR2;
+			break;
+		case 3:
+			return GSM0808_PERM_FR3;
+			break;
+		default:
+			LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver);
+			return GSM0808_PERM_HR1;
+		}
+	}
+}
+
+enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech)
+{
+	switch (speech) {
+	case GSM0808_PERM_HR1:
+	case GSM0808_PERM_FR1:
+		return GSM48_CMODE_SPEECH_V1;
+		break;
+	case GSM0808_PERM_HR2:
+	case GSM0808_PERM_FR2:
+		return GSM48_CMODE_SPEECH_EFR;
+		break;
+	case GSM0808_PERM_HR3:
+	case GSM0808_PERM_FR3:
+		return GSM48_CMODE_SPEECH_AMR;
+		break;
+	}
+
+	LOGP(DMSC, LOGL_FATAL, "Should not be reached.\n");
+	return GSM48_CMODE_SPEECH_AMR;
+}
+
+static int bssmap_handle_reset_ack(struct bsc_msc_data *msc,
+				   struct msgb *msg, unsigned int length)
+{
+	LOGP(DMSC, LOGL_NOTICE, "Reset ACK from MSC\n");
+	return 0;
+}
+
+static int bssmap_send_reset_ack(struct bsc_msc_data *msc)
+{
+	struct msgb *resp;
+	int rc;
+
+	LOGP(DMSC, LOGL_NOTICE, "Tx RESET-ACK to MSC\n");
+
+	resp = gsm0808_create_reset_ack();
+	OSMO_ASSERT(resp);
+
+	rc = sccp_write(resp, &sccp_ssn_bssap, &sccp_ssn_bssap, 0, msc->msc_con);
+	msgb_free(resp);
+	return rc;
+}
+
+static int bssmap_handle_reset(struct bsc_msc_data *msc,
+			       struct msgb *msg, unsigned int length)
+{
+	LOGP(DMSC, LOGL_NOTICE, "Rx RESET from MSC\n");
+
+	/* Instruct the BSC to close all open SCCP connections and to close all
+	 * active radio channels on the BTS side as well */
+	bsc_notify_and_close_conns(msc->msc_con);
+
+	/* Inform the MSC that we have received the reset request and
+	 * that we acted accordingly */
+	return bssmap_send_reset_ack(msc);
+}
+
+/* GSM 08.08 § 3.2.1.19 */
+static int bssmap_handle_paging(struct bsc_msc_data *msc,
+				struct msgb *msg, unsigned int payload_length)
+{
+	struct bsc_subscr *subscr;
+	struct tlv_parsed tp;
+	char mi_string[GSM48_MI_SIZE];
+	uint32_t tmsi = GSM_RESERVED_TMSI;
+	unsigned int lac = GSM_LAC_RESERVED_ALL_BTS;
+	uint8_t data_length;
+	const uint8_t *data;
+	uint8_t chan_needed = RSL_CHANNEED_ANY;
+	int rc;
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandatory IMSI not present.\n");
+		return -1;
+	} else if ((TLVP_VAL(&tp, GSM0808_IE_IMSI)[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI) {
+		LOGP(DMSC, LOGL_ERROR, "Wrong content in the IMSI\n");
+		return -1;
+	}
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandatory CELL IDENTIFIER LIST not present.\n");
+		return -1;
+	}
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI) &&
+	    TLVP_LEN(&tp, GSM0808_IE_TMSI) == 4) {
+		tmsi = ntohl(tlvp_val32_unal(&tp, GSM0808_IE_TMSI));
+	}
+
+	/*
+	 * parse the IMSI
+	 */
+	gsm48_mi_to_string(mi_string, sizeof(mi_string),
+			   TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI));
+
+	/*
+	 * parse the cell identifier list
+	 */
+	data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+	data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+
+	/*
+	 * Support paging to all network or one BTS at one LAC
+	 */
+	if (data_length == 3 && data[0] == CELL_IDENT_LAC) {
+		lac = osmo_load16be(&data[1]);
+	} else if (data_length > 1 || (data[0] & 0x0f) != CELL_IDENT_BSS) {
+		LOGP(DMSC, LOGL_ERROR, "Unsupported Cell Identifier List: %s\n", osmo_hexdump(data, data_length));
+		return -1;
+	}
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1)
+		chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03;
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) {
+		LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n");
+	}
+
+	subscr = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers,
+						   mi_string);
+	if (!subscr) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate a subscriber for %s\n", mi_string);
+		return -1;
+	}
+
+	subscr->lac = lac;
+	subscr->tmsi = tmsi;
+
+	LOGP(DMSC, LOGL_INFO, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac);
+	rc = bsc_grace_paging_request(msc->network->bsc_data->rf_ctrl->policy,
+				 subscr, chan_needed, msc);
+	if (rc <= 0) {
+		LOGP(DMSC, LOGL_ERROR, "Paging request failed (%d): IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n",
+			rc, mi_string, tmsi, tmsi, lac);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * GSM 08.08 § 3.1.9.1 and 3.2.1.21...
+ * release our gsm_subscriber_connection and send message
+ */
+static int bssmap_handle_clear_command(struct osmo_bsc_sccp_con *conn,
+				       struct msgb *msg, unsigned int payload_length)
+{
+	struct msgb *resp;
+
+	/* TODO: handle the cause of this package */
+
+	if (conn->conn) {
+		LOGP(DMSC, LOGL_INFO, "Releasing all transactions on %p\n", conn);
+		gsm0808_clear(conn->conn);
+		bsc_subscr_con_free(conn->conn);
+		conn->conn = NULL;
+	}
+
+	/* send the clear complete message */
+	resp = gsm0808_create_clear_complete();
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n");
+		return -1;
+	}
+
+	bsc_queue_for_msc(conn, resp);
+	return 0;
+}
+
+/*
+ * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick
+ * the cipher to be used for this. In case we are already using
+ * a cipher we will have to send cipher mode reject to the MSC,
+ * otherwise we will have to pick something that we and the MS
+ * is supporting. Currently we are doing it in a rather static
+ * way by picking one ecnryption or no encrytpion.
+ */
+static int bssmap_handle_cipher_mode(struct osmo_bsc_sccp_con *conn,
+				     struct msgb *msg, unsigned int payload_length)
+{
+	uint16_t len;
+	struct gsm_network *network = NULL;
+	const uint8_t *data;
+	struct tlv_parsed tp;
+	struct msgb *resp;
+	int reject_cause = -1;
+	int include_imeisv = 1;
+
+	if (!conn->conn) {
+		LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+		goto reject;
+	}
+
+	if (conn->ciphering_handled) {
+		LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n");
+		goto reject;
+	}
+
+	conn->ciphering_handled = 1;
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) {
+		LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n");
+		goto reject;
+	}
+
+	/*
+	 * check if our global setting is allowed
+	 *  - Currently we check for A5/0 and A5/1
+	 *  - Copy the key if that is necessary
+	 *  - Otherwise reject
+	 */
+	len = TLVP_LEN(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+	if (len < 1) {
+		LOGP(DMSC, LOGL_ERROR, "IE Encryption Information is too short.\n");
+		goto reject;
+	}
+
+	network = conn->conn->bts->network;
+	data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE))
+		include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1;
+
+	if (network->a5_encryption == 0 && (data[0] & 0x1) == 0x1) {
+		gsm0808_cipher_mode(conn->conn, 0, NULL, 0, include_imeisv);
+	} else if (network->a5_encryption != 0 && (data[0] & 0x2) == 0x2) {
+		gsm0808_cipher_mode(conn->conn, 1, &data[1], len - 1, include_imeisv);
+	} else {
+		LOGP(DMSC, LOGL_ERROR, "Can not select encryption...\n");
+		goto reject;
+	}
+
+	return 0;
+
+reject:
+	resp = gsm0808_create_cipher_reject(reject_cause);
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n");
+		return -1;
+	}
+
+	bsc_queue_for_msc(conn, resp);
+	return -1;
+}
+
+/*
+ * Handle the assignment request message.
+ *
+ * See §3.2.1.1 for the message type
+ */
+static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
+				     struct msgb *msg, unsigned int length)
+{
+	struct msgb *resp;
+	struct bsc_msc_data *msc;
+	struct tlv_parsed tp;
+	uint8_t *data;
+	uint8_t timeslot;
+	uint8_t multiplex;
+	enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN;
+	int i, supported, port, full_rate = -1;
+
+	if (!conn->conn) {
+		LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+		return -1;
+	}
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandatory channel type not present.\n");
+		goto reject;
+	}
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+		LOGP(DMSC, LOGL_ERROR, "Identity code missing. Audio routing will not work.\n");
+		goto reject;
+	}
+
+	conn->cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+	timeslot = conn->cic & 0x1f;
+	multiplex = (conn->cic & ~0x1f) >> 5;
+
+	/*
+	 * Currently we only support a limited subset of all
+	 * possible channel types. The limitation ends by not using
+	 * multi-slot, limiting the channel coding, speech...
+	 */
+	if (TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE) < 3) {
+		LOGP(DMSC, LOGL_ERROR, "ChannelType len !=3 not supported: %d\n",
+			TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE));
+		goto reject;
+	}
+
+	/*
+	 * Try to figure out if we support the proposed speech codecs. For
+	 * now we will always pick the full rate codecs.
+	 */
+
+	data = (uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE);
+	if ((data[0] & 0xf) != 0x1) {
+		LOGP(DMSC, LOGL_ERROR, "ChannelType != speech: %d\n", data[0]);
+		goto reject;
+	}
+
+	/*
+	 * go through the list of preferred codecs of our gsm network
+	 * and try to find it among the permitted codecs. If we found
+	 * it we will send chan_mode to the right mode and break the
+	 * inner loop. The outer loop will exit due chan_mode having
+	 * the correct value.
+	 */
+	full_rate = 0;
+	msc = conn->msc;
+	for (supported = 0;
+		chan_mode == GSM48_CMODE_SIGN && supported < msc->audio_length;
+		++supported) {
+
+		int perm_val = audio_support_to_gsm88(msc->audio_support[supported]);
+		for (i = 2; i < TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE); ++i) {
+			if ((data[i] & 0x7f) == perm_val) {
+				chan_mode = gsm88_to_chan_mode(perm_val);
+				full_rate = (data[i] & 0x4) == 0;
+				break;
+			} else if ((data[i] & 0x80) == 0x00) {
+				break;
+			}
+		}
+	}
+
+	if (chan_mode == GSM48_CMODE_SIGN) {
+		LOGP(DMSC, LOGL_ERROR, "No supported audio type found.\n");
+		goto reject;
+	}
+
+	/* map it to a MGCP Endpoint and a RTP port */
+	port = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+	conn->rtp_port = rtp_calculate_port(port, msc->rtp_base);
+
+	return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
+
+reject:
+	resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Channel allocation failure.\n");
+		return -1;
+	}
+
+	bsc_queue_for_msc(conn, resp);
+	return -1;
+}
+
+static int bssmap_rcvmsg_udt(struct bsc_msc_data *msc,
+			     struct msgb *msg, unsigned int length)
+{
+	int ret = 0;
+
+	if (length < 1) {
+		LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+		return -1;
+	}
+
+	LOGP(DMSC, LOGL_INFO, "Rx MSC UDT BSSMAP %s\n",
+		gsm0808_bssmap_name(msg->l4h[0]));
+
+	switch (msg->l4h[0]) {
+	case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+		ret = bssmap_handle_reset_ack(msc, msg, length);
+		break;
+	case BSS_MAP_MSG_RESET:
+		ret = bssmap_handle_reset(msc, msg, length);
+		break;
+	case BSS_MAP_MSG_PAGING:
+		ret = bssmap_handle_paging(msc, msg, length);
+		break;
+	default:
+		LOGP(DMSC, LOGL_NOTICE, "Received unimplemented BSSMAP UDT %s\n",
+			gsm0808_bssmap_name(msg->l4h[0]));
+		break;
+	}
+
+	return ret;
+}
+
+static int bssmap_rcvmsg_dt1(struct osmo_bsc_sccp_con *conn,
+			     struct msgb *msg, unsigned int length)
+{
+	int ret = 0;
+
+	if (length < 1) {
+		LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+		return -1;
+	}
+
+	LOGP(DMSC, LOGL_INFO, "Rx MSC DT1 BSSMAP %s\n",
+		gsm0808_bssmap_name(msg->l4h[0]));
+
+	switch (msg->l4h[0]) {
+	case BSS_MAP_MSG_CLEAR_CMD:
+		ret = bssmap_handle_clear_command(conn, msg, length);
+		break;
+	case BSS_MAP_MSG_CIPHER_MODE_CMD:
+		ret = bssmap_handle_cipher_mode(conn, msg, length);
+		break;
+	case BSS_MAP_MSG_ASSIGMENT_RQST:
+		ret = bssmap_handle_assignm_req(conn, msg, length);
+		break;
+	default:
+		LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
+			gsm0808_bssmap_name(msg->l4h[0]));
+		break;
+	}
+
+	return ret;
+}
+
+static int dtap_rcvmsg(struct osmo_bsc_sccp_con *conn,
+		       struct msgb *msg, unsigned int length)
+{
+	struct dtap_header *header;
+	struct msgb *gsm48;
+	uint8_t *data;
+	int rc, dtap_rc;
+
+	LOGP(DMSC, LOGL_DEBUG, "Rx MSC DTAP: %s\n",
+		osmo_hexdump(msg->l3h, length));
+
+	if (!conn->conn) {
+		LOGP(DMSC, LOGL_ERROR, "No subscriber connection available\n");
+		return -1;
+	}
+
+	header = (struct dtap_header *) msg->l3h;
+	if (sizeof(*header) >= length) {
+		LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %zu got: %u\n", sizeof(*header), length);
+                LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+                return -1;
+	}
+
+	if (header->length > length - sizeof(*header)) {
+		LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length);
+                LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+		return -1;
+	}
+
+	LOGP(DMSC, LOGL_INFO, "Rx MSC DTAP, SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0);
+
+	/* forward the data */
+	gsm48 = gsm48_msgb_alloc_name("GSM 04.08 DTAP RCV");
+	if (!gsm48) {
+		LOGP(DMSC, LOGL_ERROR, "Allocation of the message failed.\n");
+		return -1;
+	}
+
+	gsm48->l3h = gsm48->data;
+	data = msgb_put(gsm48, length - sizeof(*header));
+	memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header));
+
+	/* pass it to the filter for extra actions */
+	rc = bsc_scan_msc_msg(conn->conn, gsm48);
+	dtap_rc = gsm0808_submit_dtap(conn->conn, gsm48, header->link_id, 1);
+	if (rc == BSS_SEND_USSD)
+		bsc_send_welcome_ussd(conn->conn);
+	return dtap_rc;
+}
+
+int bsc_handle_udt(struct bsc_msc_data *msc,
+		   struct msgb *msgb, unsigned int length)
+{
+	struct bssmap_header *bs;
+
+	LOGP(DMSC, LOGL_DEBUG, "Rx MSC UDT: %s\n",
+		osmo_hexdump(msgb->l3h, length));
+
+	if (length < sizeof(*bs)) {
+		LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+		return -1;
+	}
+
+	bs = (struct bssmap_header *) msgb->l3h;
+	if (bs->length < length - sizeof(*bs))
+		return -1;
+
+	switch (bs->type) {
+	case BSSAP_MSG_BSS_MANAGEMENT:
+		msgb->l4h = &msgb->l3h[sizeof(*bs)];
+		bssmap_rcvmsg_udt(msc, msgb, length - sizeof(*bs));
+		break;
+	default:
+		LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
+			gsm0808_bssmap_name(bs->type));
+	}
+
+	return 0;
+}
+
+int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn,
+		   struct msgb *msg, unsigned int len)
+{
+	if (len < sizeof(struct bssmap_header)) {
+		LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+	}
+
+	switch (msg->l3h[0]) {
+	case BSSAP_MSG_BSS_MANAGEMENT:
+		msg->l4h = &msg->l3h[sizeof(struct bssmap_header)];
+		bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header));
+		break;
+	case BSSAP_MSG_DTAP:
+		dtap_rcvmsg(conn, msg, len);
+		break;
+	default:
+		LOGP(DMSC, LOGL_NOTICE, "Unimplemented BSSAP msg type: %s\n",
+			gsm0808_bssap_name(msg->l3h[0]));
+	}
+
+	return -1;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c b/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c
new file mode 100644
index 0000000..54f2e0d
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c
@@ -0,0 +1,653 @@
+/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2011 by Holger Hans Peter Freyther
+ * (C) 2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_04_80.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_connection *msc_con)
+{
+	struct ctrl_cmd *trap;
+	struct ctrl_handle *ctrl;
+	struct bsc_msc_data *msc_data;
+
+	msc_data = (struct bsc_msc_data *) msc_con->write_queue.bfd.data;
+	ctrl = msc_data->network->ctrl;
+
+	trap = ctrl_cmd_trap(cmd);
+	if (!trap) {
+		LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
+		return;
+	}
+
+	ctrl_cmd_send_to_all(ctrl, trap);
+	ctrl_cmd_send(&msc_con->write_queue, trap);
+
+	talloc_free(trap);
+}
+
+CTRL_CMD_DEFINE_RO(msc_connection_status, "msc_connection_status");
+static int msc_connection_status = 0;
+
+static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+	if (msc_connection_status)
+		cmd->reply = "connected";
+	else
+		cmd->reply = "disconnected";
+	return CTRL_CMD_REPLY;
+}
+
+static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+	struct ctrl_cmd *cmd;
+	struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+
+	if (signal == S_MSC_LOST && msc_connection_status == 1) {
+		LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n");
+		msc_connection_status = 0;
+	} else if (signal == S_MSC_CONNECTED && msc_connection_status == 0) {
+		LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n");
+		msc_connection_status = 1;
+	} else {
+		return 0;
+	}
+
+	cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+	if (!cmd) {
+		LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+		return 0;
+	}
+
+	cmd->id = "0";
+	cmd->variable = "msc_connection_status";
+
+	get_msc_connection_status(cmd, NULL);
+
+	ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+	talloc_free(cmd);
+
+	return 0;
+}
+
+CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status");
+static int bts_connection_status = 0;
+
+static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+	if (bts_connection_status)
+		cmd->reply = "connected";
+	else
+		cmd->reply = "disconnected";
+	return CTRL_CMD_REPLY;
+}
+
+static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+	struct ctrl_cmd *cmd;
+	struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+	struct gsm_bts *bts;
+	int bts_current_status;
+
+	if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) {
+		return 0;
+	}
+
+	bts_current_status = 0;
+	/* Check if OML on at least one BTS is up */
+	llist_for_each_entry(bts, &gsmnet->bts_list, list) {
+		if (bts->oml_link) {
+			bts_current_status = 1;
+			break;
+		}
+	}
+	if (bts_connection_status == 0 && bts_current_status == 1) {
+		LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n");
+	} else if (bts_connection_status == 1 && bts_current_status == 0) {
+		LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n");
+	} else {
+		return 0;
+	}
+
+	cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+	if (!cmd) {
+		LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+		return 0;
+	}
+
+	bts_connection_status = bts_current_status;
+
+	cmd->id = "0";
+	cmd->variable = "bts_connection_status";
+
+	get_bts_connection_status(cmd, NULL);
+
+	ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+	talloc_free(cmd);
+
+	return 0;
+}
+
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data);
+
+static void generate_location_state_trap(struct gsm_bts *bts, struct bsc_msc_connection *msc_con)
+{
+	struct ctrl_cmd *cmd;
+	const char *oper, *admin, *policy;
+
+	cmd = ctrl_cmd_create(msc_con, CTRL_TYPE_TRAP);
+	if (!cmd) {
+		LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
+		return;
+	}
+
+	cmd->id = "0";
+	cmd->variable = talloc_asprintf(cmd, "bts.%i.location-state", bts->nr);
+
+	/* Prepare the location reply */
+	cmd->node = bts;
+	get_bts_loc(cmd, NULL);
+
+	oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+	admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+	policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+	cmd->reply = talloc_asprintf_append(cmd->reply,
+				",%s,%s,%s,%s,%s",
+				oper, admin, policy,
+				osmo_mcc_name(bts->network->plmn.mcc),
+				osmo_mnc_name(bts->network->plmn.mnc,
+					      bts->network->plmn.mnc_3_digits));
+
+	osmo_bsc_send_trap(cmd, msc_con);
+	talloc_free(cmd);
+}
+
+void bsc_gen_location_state_trap(struct gsm_bts *bts)
+{
+	struct bsc_msc_data *msc;
+
+	llist_for_each_entry(msc, &bts->network->bsc_data->mscs, entry)
+		generate_location_state_trap(bts, msc->msc_con);
+}
+
+static int location_equal(struct bts_location *a, struct bts_location *b)
+{
+	return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) &&
+		(a->lon == b->lon) && (a->height == b->height));
+}
+
+static void cleanup_locations(struct llist_head *locations)
+{
+	struct bts_location *myloc, *tmp;
+	int invalpos = 0, i = 0;
+
+	LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n");
+	llist_for_each_entry_safe(myloc, tmp, locations, list) {
+		i++;
+		if (i > 3) {
+			LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n");
+			llist_del(&myloc->list);
+			talloc_free(myloc);
+		} else if (myloc->valid == BTS_LOC_FIX_INVALID) {
+			/* Only capture the newest of subsequent invalid positions */
+			invalpos++;
+			if (invalpos > 1) {
+				LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n");
+				invalpos--;
+				i--;
+				llist_del(&myloc->list);
+				talloc_free(myloc);
+			}
+		} else {
+			invalpos = 0;
+		}
+	}
+	LOGP(DCTRL, LOGL_DEBUG, "Found %i positions.\n", i);
+}
+
+CTRL_CMD_DEFINE(bts_loc, "location");
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+	struct bts_location *curloc;
+	struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+	if (!bts) {
+		cmd->reply = "bts not found.";
+		return CTRL_CMD_ERROR;
+	}
+
+	if (llist_empty(&bts->loc_list)) {
+		cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0");
+		return CTRL_CMD_REPLY;
+	} else {
+		curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+	}
+
+	cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp,
+			get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height);
+	if (!cmd->reply) {
+		cmd->reply = "OOM";
+		return CTRL_CMD_ERROR;
+	}
+
+	return CTRL_CMD_REPLY;
+}
+
+static int set_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+	char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp;
+	struct bts_location *curloc, *lastloc;
+	int ret;
+	struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+	if (!bts) {
+		cmd->reply = "bts not found.";
+		return CTRL_CMD_ERROR;
+	}
+
+	tmp = talloc_strdup(cmd, cmd->value);
+	if (!tmp)
+		goto oom;
+
+	curloc = talloc_zero(tall_bsc_ctx, struct bts_location);
+	if (!curloc) {
+		talloc_free(tmp);
+		goto oom;
+	}
+	INIT_LLIST_HEAD(&curloc->list);
+
+
+	tstamp = strtok_r(tmp, ",", &saveptr);
+	valid = strtok_r(NULL, ",", &saveptr);
+	lat = strtok_r(NULL, ",", &saveptr);
+	lon = strtok_r(NULL, ",", &saveptr);
+	height = strtok_r(NULL, "\0", &saveptr);
+
+	curloc->tstamp = atol(tstamp);
+	curloc->valid = get_string_value(bts_loc_fix_names, valid);
+	curloc->lat = atof(lat);
+	curloc->lon = atof(lon);
+	curloc->height = atof(height);
+	talloc_free(tmp);
+
+	lastloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+
+	/* Add location to the end of the list */
+	llist_add(&curloc->list, &bts->loc_list);
+
+	ret = get_bts_loc(cmd, data);
+
+	if (!location_equal(curloc, lastloc))
+		bsc_gen_location_state_trap(bts);
+
+	cleanup_locations(&bts->loc_list);
+
+	return ret;
+
+oom:
+	cmd->reply = "OOM";
+	return CTRL_CMD_ERROR;
+}
+
+static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp;
+	time_t tstamp;
+	int valid;
+	double lat, lon, height __attribute__((unused));
+
+	tmp = talloc_strdup(cmd, value);
+	if (!tmp)
+		return 1;
+
+	tstampstr = strtok_r(tmp, ",", &saveptr);
+	validstr = strtok_r(NULL, ",", &saveptr);
+	latstr = strtok_r(NULL, ",", &saveptr);
+	lonstr = strtok_r(NULL, ",", &saveptr);
+	heightstr = strtok_r(NULL, "\0", &saveptr);
+
+	if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) ||
+			(lonstr == NULL) || (heightstr == NULL))
+		goto err;
+
+	tstamp = atol(tstampstr);
+	valid = get_string_value(bts_loc_fix_names, validstr);
+	lat = atof(latstr);
+	lon = atof(lonstr);
+	height = atof(heightstr);
+	talloc_free(tmp);
+	tmp = NULL;
+
+	if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) ||
+			(lon < -180) || (lon > 180) || (valid < 0)) {
+		goto err;
+	}
+
+	return 0;
+
+err:
+	talloc_free(tmp);
+	cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>");
+	return 1;
+}
+
+CTRL_CMD_DEFINE(net_timezone, "timezone");
+static int get_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = (struct gsm_network*)cmd->node;
+
+	struct gsm_tz *tz = &net->tz;
+	if (tz->override)
+		cmd->reply = talloc_asprintf(cmd, "%d,%d,%d",
+			       tz->hr, tz->mn, tz->dst);
+	else
+		cmd->reply = talloc_asprintf(cmd, "off");
+
+	if (!cmd->reply) {
+		cmd->reply = "OOM";
+		return CTRL_CMD_ERROR;
+	}
+
+	return CTRL_CMD_REPLY;
+}
+
+static int set_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+	char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0;
+	int override;
+	struct gsm_network *net = (struct gsm_network*)cmd->node;
+
+	tmp = talloc_strdup(cmd, cmd->value);
+	if (!tmp)
+		goto oom;
+
+	hourstr = strtok_r(tmp, ",", &saveptr);
+	minstr = strtok_r(NULL, ",", &saveptr);
+	dststr = strtok_r(NULL, ",", &saveptr);
+
+	override = 0;
+
+	if (hourstr != NULL)
+		override = strcasecmp(hourstr, "off") != 0;
+
+	struct gsm_tz *tz = &net->tz;
+	tz->override = override;
+
+	if (override) {
+		tz->hr  = hourstr ? atol(hourstr) : 0;
+		tz->mn  = minstr ? atol(minstr) : 0;
+		tz->dst = dststr ? atol(dststr) : 0;
+	}
+
+	talloc_free(tmp);
+	tmp = NULL;
+
+	return get_net_timezone(cmd, data);
+
+oom:
+	cmd->reply = "OOM";
+	return CTRL_CMD_ERROR;
+}
+
+static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	char *saveptr, *hourstr, *minstr, *dststr, *tmp;
+	int override, tz_hours, tz_mins, tz_dst;
+
+	tmp = talloc_strdup(cmd, value);
+	if (!tmp)
+		return 1;
+
+	hourstr = strtok_r(tmp, ",", &saveptr);
+	minstr = strtok_r(NULL, ",", &saveptr);
+	dststr = strtok_r(NULL, ",", &saveptr);
+
+	if (hourstr == NULL)
+		goto err;
+
+	override = strcasecmp(hourstr, "off") != 0;
+
+	if (!override) {
+		talloc_free(tmp);
+		return 0;
+	}
+
+	if (minstr == NULL || dststr == NULL)
+		goto err;
+
+	tz_hours = atol(hourstr);
+	tz_mins = atol(minstr);
+	tz_dst = atol(dststr);
+
+	talloc_free(tmp);
+	tmp = NULL;
+
+	if ((tz_hours < -19) || (tz_hours > 19) ||
+	       (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) ||
+	       (tz_dst < 0) || (tz_dst > 2))
+		goto err;
+
+	return 0;
+
+err:
+	talloc_free(tmp);
+	cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2");
+	return 1;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification");
+static int set_net_notification(struct ctrl_cmd *cmd, void *data)
+{
+	struct ctrl_cmd *trap;
+	struct gsm_network *net;
+
+	net = cmd->node;
+
+	trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+	if (!trap) {
+		LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+		goto handled;
+	}
+
+	trap->id = "0";
+	trap->variable = "notification";
+	trap->reply = talloc_strdup(trap, cmd->value);
+
+	/*
+	 * This should only be sent to local systems. In the future
+	 * we might even ask for systems to register to receive
+	 * the notifications.
+	 */
+	ctrl_cmd_send_to_all(net->ctrl, trap);
+	talloc_free(trap);
+
+handled:
+	return CTRL_CMD_HANDLED;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_inform_msc, "inform-msc-v1");
+static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net;
+	struct bsc_msc_data *msc;
+
+	net = cmd->node;
+	llist_for_each_entry(msc, &net->bsc_data->mscs, entry) {
+		struct ctrl_cmd *trap;
+
+		trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+		if (!trap) {
+			LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+			continue;
+		}
+
+		trap->id = "0";
+		trap->variable = "inform-msc-v1";
+		trap->reply = talloc_strdup(trap, cmd->value);
+		ctrl_cmd_send(&msc->msc_con->write_queue, trap);
+		talloc_free(trap);
+	}
+
+
+	return CTRL_CMD_HANDLED;
+}
+
+CTRL_CMD_DEFINE_WO(net_ussd_notify, "ussd-notify-v1");
+static int set_net_ussd_notify(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *net;
+	char *saveptr = NULL;
+	char *cic_str, *alert_str, *text_str;
+	int cic, alert;
+
+	/* Verify has done the test for us */
+	cic_str = strtok_r(cmd->value, ",", &saveptr);
+	alert_str = strtok_r(NULL, ",", &saveptr);
+	text_str = strtok_r(NULL, ",", &saveptr);
+
+	if (!cic_str || !alert_str || !text_str) {
+		cmd->reply = "Programming issue. How did this pass verify?";
+		return CTRL_CMD_ERROR;
+	}
+
+	cmd->reply = "No connection found";
+
+	cic = atoi(cic_str);
+	alert = atoi(alert_str);
+
+	net = cmd->node;
+	llist_for_each_entry(conn, &net->subscr_conns, entry) {
+		if (!conn->sccp_con)
+			continue;
+
+		if (conn->sccp_con->cic != cic)
+			continue;
+
+		/*
+		 * This is a hack. My E71 does not like to immediately
+		 * receive a release complete on a TCH. So schedule a
+		 * release complete to clear any previous attempt. The
+		 * right thing would be to track invokeId and only send
+		 * the release complete when we get a returnResultLast
+		 * for this invoke id.
+		 */
+		bsc_send_ussd_release_complete(conn);
+		bsc_send_ussd_notify(conn, alert, text_str);
+		cmd->reply = "Found a connection";
+		break;
+	}
+
+	return CTRL_CMD_REPLY;
+}
+
+static int verify_net_ussd_notify(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	char *saveptr = NULL;
+	char *inp, *cic, *alert, *text;
+
+	OSMO_ASSERT(cmd);
+	inp = talloc_strdup(cmd, value);
+
+	cic = strtok_r(inp, ",", &saveptr);
+	alert = strtok_r(NULL, ",", &saveptr);
+	text = strtok_r(NULL, ",", &saveptr);
+
+	talloc_free(inp);
+	if (!cic || !alert || !text)
+		return 1;
+	return 0;
+}
+
+static int msc_signal_handler(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct msc_signal_data *msc;
+	struct gsm_network *net;
+	struct gsm_bts *bts;
+
+	if (subsys != SS_MSC)
+		return 0;
+	if (signal != S_MSC_AUTHENTICATED)
+		return 0;
+
+	msc = signal_data;
+
+	net = msc->data->network;
+	llist_for_each_entry(bts, &net->bts_list, list)
+		generate_location_state_trap(bts, msc->data->msc_con);	
+
+	return 0;
+}
+
+int bsc_ctrl_cmds_install(struct gsm_network *net)
+{
+	int rc;
+
+	rc = bsc_base_ctrl_cmds_install();
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc);
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone);
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc_connection_status);
+	if (rc)
+		goto end;
+	rc = osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net);
+	if (rc)
+		goto end;
+	rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL);
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status);
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification);
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc);
+	if (rc)
+		goto end;
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_ussd_notify);
+	if (rc)
+		goto end;
+	rc = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net);
+
+end:
+	return rc;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_filter.c b/openbsc/src/osmo-bsc/osmo_bsc_filter.c
new file mode 100644
index 0000000..0c4b3e3
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_filter.c
@@ -0,0 +1,373 @@
+/* (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+
+#include <stdlib.h>
+
+static void handle_lu_request(struct gsm_subscriber_connection *conn,
+			      struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	struct gsm48_loc_upd_req *lu;
+	struct gsm48_loc_area_id lai;
+
+	if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) {
+		LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg));
+		return;
+	}
+
+	gh = msgb_l3(msg);
+	lu = (struct gsm48_loc_upd_req *) gh->data;
+
+	gsm48_generate_lai2(&lai, bts_lai(conn->bts));
+
+	if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) {
+		LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n");
+		conn->sccp_con->new_subscriber = 1;
+	}
+}
+
+/* extract a subscriber from the paging response */
+static struct bsc_subscr *extract_sub(struct gsm_subscriber_connection *conn,
+				   struct msgb *msg)
+{
+	uint8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm48_hdr *gh;
+	struct gsm48_pag_resp *resp;
+	struct bsc_subscr *subscr;
+
+	if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) {
+		LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg));
+		return NULL;
+	}
+
+	gh = msgb_l3(msg);
+	resp = (struct gsm48_pag_resp *) &gh->data[0];
+
+	gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+				mi_string, &mi_type);
+	DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
+		gsm48_mi_type_name(mi_type), mi_string);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = bsc_subscr_find_by_tmsi(conn->network->bsc_subscribers,
+					      tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = bsc_subscr_find_by_imsi(conn->network->bsc_subscribers,
+					      mi_string);
+		break;
+	default:
+		subscr = NULL;
+		break;
+	}
+
+	return subscr;
+}
+
+/* we will need to stop the paging request */
+static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct bsc_subscr *subscr = extract_sub(conn, msg);
+
+	if (!subscr) {
+		LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n");
+		return -1;
+	}
+
+	paging_request_stop(&conn->network->bts_list, conn->bts, subscr, conn,
+			    msg);
+	bsc_subscr_put(subscr);
+	return 0;
+}
+
+static int is_cm_service_for_emerg(struct msgb *msg)
+{
+	struct gsm48_service_request *cm;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*cm)) {
+		LOGP(DMSC, LOGL_ERROR, "CM ServiceRequest does not fit.\n");
+		return 0;
+	}
+
+	cm = (struct gsm48_service_request *) &gh->data[0];
+	return cm->cm_service_type == GSM48_CMSERV_EMERGENCY;
+}
+
+struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
+				   struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	int8_t pdisc;
+	uint8_t mtype;
+	struct osmo_bsc_data *bsc;
+	struct bsc_msc_data *msc, *pag_msc;
+	struct bsc_subscr *subscr;
+	int is_emerg = 0;
+
+	bsc = conn->bts->network->bsc_data;
+
+	if (msgb_l3len(msg) < sizeof(*gh)) {
+		LOGP(DMSC, LOGL_ERROR, "There is no GSM48 header here.\n");
+		return NULL;
+	}
+
+	gh = msgb_l3(msg);
+	pdisc = gsm48_hdr_pdisc(gh);
+	mtype = gsm48_hdr_msg_type(gh);
+
+	/*
+	 * We are asked to select a MSC here but they are not equal. We
+	 * want to respond to a paging request on the MSC where we got the
+	 * request from. This is where we need to decide where this connection
+	 * will go.
+	 */
+	if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP)
+		goto paging;
+	else if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
+		is_emerg = is_cm_service_for_emerg(msg);
+		goto round_robin;
+	} else
+		goto round_robin;
+
+round_robin:
+	llist_for_each_entry(msc, &bsc->mscs, entry) {
+		if (!msc->msc_con->is_authenticated)
+			continue;
+		if (!is_emerg && msc->type != MSC_CON_TYPE_NORMAL)
+			continue;
+		if (is_emerg && !msc->allow_emerg)
+			continue;
+
+		/* force round robin by moving it to the end */
+		llist_move_tail(&msc->entry, &bsc->mscs);
+		return msc;
+	}
+
+	return NULL;
+
+paging:
+	subscr = extract_sub(conn, msg);
+
+	if (!subscr) {
+		LOGP(DMSC, LOGL_ERROR, "Got paged but no subscriber found.\n");
+		return NULL;
+	}
+
+	pag_msc = paging_get_data(conn->bts, subscr);
+	bsc_subscr_put(subscr);
+
+	llist_for_each_entry(msc, &bsc->mscs, entry) {
+		if (msc != pag_msc)
+			continue;
+
+		/*
+		 * We don't check if the MSC is connected. In case it
+		 * is not the connection will be dropped.
+		 */
+
+		/* force round robin by moving it to the end */
+		llist_move_tail(&msc->entry, &bsc->mscs);
+		return msc;
+	}
+
+	LOGP(DMSC, LOGL_ERROR, "Got paged but no request found.\n");
+	return NULL;
+}
+
+
+/**
+ * This is used to scan a message for extra functionality of the BSC. This
+ * includes scanning for location updating requests/acceptd and then send
+ * a welcome USSD message to the subscriber.
+ */
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t pdisc = gsm48_hdr_pdisc(gh);
+	uint8_t mtype = gsm48_hdr_msg_type(gh);
+
+	if (pdisc == GSM48_PDISC_MM) {
+		if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST)
+			handle_lu_request(conn, msg);
+	} else if (pdisc == GSM48_PDISC_RR) {
+		if (mtype == GSM48_MT_RR_PAG_RESP)
+			handle_page_resp(conn, msg);
+	}
+
+	return 0;
+}
+
+static int send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+	struct osmo_bsc_sccp_con *bsc_con;
+
+	bsc_con = conn->sccp_con;
+	if (!bsc_con) {
+		LOGP(DMSC, LOGL_DEBUG, "No SCCP connection associated.\n");
+		return 0;
+	}
+
+	if (!bsc_con->msc->ussd_welcome_txt) {
+		LOGP(DMSC, LOGL_DEBUG, "No USSD Welcome text defined.\n");
+		return 0;
+	}
+
+	return BSS_SEND_USSD;
+}
+
+int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+	bsc_send_ussd_notify(conn, 1, conn->sccp_con->msc->ussd_welcome_txt);
+	bsc_send_ussd_release_complete(conn);
+
+	return 0;
+}
+
+static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn,
+		uint8_t *data, unsigned int length)
+{
+	struct tlv_parsed tp;
+	int parse_res;
+	struct gsm_bts *bts = conn->bts;
+	int tzunits;
+	uint8_t tzbsd = 0;
+	uint8_t dst = 0;
+
+	parse_res = tlv_parse(&tp, &gsm48_mm_att_tlvdef, data, length, 0, 0);
+	if (parse_res <= 0 && parse_res != -3)
+		/* FIXME: -3 means unknown IE error, so this accepts messages
+		 * with unknown IEs. But parsing has aborted with the unknown
+		 * IE and the message is broken or parsed incompletely. */
+		return 0;
+
+	/* Is TZ patching enabled? */
+	struct gsm_tz *tz = &bts->network->tz;
+	if (!tz->override)
+		return 0;
+
+	/* Convert tz.hr and tz.mn to units */
+	if (tz->hr < 0) {
+		tzunits = -tz->hr*4;
+		tzbsd |= 0x08;
+	} else
+		tzunits = tz->hr*4;
+
+	tzunits = tzunits + (tz->mn/15);
+
+	tzbsd |= (tzunits % 10)*0x10 + (tzunits / 10);
+
+	/* Convert DST value */
+	if (tz->dst >= 0 && tz->dst <= 2)
+		dst = tz->dst;
+
+	if (TLVP_PRESENT(&tp, GSM48_IE_UTC)) {
+		LOGP(DMSC, LOGL_DEBUG,
+			"Changing 'Local time zone' from 0x%02x to 0x%02x.\n",
+			TLVP_VAL(&tp, GSM48_IE_UTC)[6], tzbsd);
+		((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_UTC)))[0] = tzbsd;
+	}
+	if (TLVP_PRESENT(&tp, GSM48_IE_NET_TIME_TZ)) {
+		LOGP(DMSC, LOGL_DEBUG,
+			"Changing 'Universal time and local time zone' TZ from "
+			"0x%02x to 0x%02x.\n",
+			TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)[6], tzbsd);
+		((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)))[6] = tzbsd;
+	}
+#ifdef GSM48_IE_NET_DST
+	if (TLVP_PRESENT(&tp, GSM48_IE_NET_DST)) {
+		LOGP(DMSC, LOGL_DEBUG,
+			"Changing 'Network daylight saving time' from "
+			"0x%02x to 0x%02x.\n",
+			TLVP_VAL(&tp, GSM48_IE_NET_DST)[0], dst);
+		((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_DST)))[0] = dst;
+	}
+#endif
+
+	return 0;
+}
+
+static int has_core_identity(struct bsc_msc_data *msc)
+{
+	if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
+		return 1;
+	if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+		return 1;
+	if (msc->core_lac != -1)
+		return 1;
+	if (msc->core_ci != -1)
+		return 1;
+	return 0;
+}
+
+/**
+ * Messages coming back from the MSC.
+ */
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct bsc_msc_data *msc;
+	struct gsm48_loc_area_id *lai;
+	struct gsm48_hdr *gh;
+	uint8_t pdisc;
+	uint8_t mtype;
+	int length = msgb_l3len(msg);
+
+	if (length < sizeof(*gh)) {
+		LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n");
+		return -1;
+	}
+
+	gh = (struct gsm48_hdr *) msgb_l3(msg);
+	length -= (const char *)&gh->data[0] - (const char *)gh;
+
+	pdisc = gsm48_hdr_pdisc(gh);
+	if (pdisc != GSM48_PDISC_MM)
+		return 0;
+
+	mtype = gsm48_hdr_msg_type(gh);
+	msc = conn->sccp_con->msc;
+
+	if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) {
+		if (has_core_identity(msc)) {
+			if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) {
+				/* overwrite LAI in the message */
+				lai = (struct gsm48_loc_area_id *) &gh->data[0];
+				gsm48_generate_lai2(lai, bts_lai(conn->bts));
+			}
+		}
+
+		if (conn->sccp_con->new_subscriber)
+			return send_welcome_ussd(conn);
+		return 0;
+	} else if (mtype == GSM48_MT_MM_INFO) {
+		bsc_patch_mm_info(conn, &gh->data[0], length);
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_grace.c b/openbsc/src/osmo-bsc/osmo_bsc_grace.c
new file mode 100644
index 0000000..bb2634f
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_grace.c
@@ -0,0 +1,176 @@
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+
+int bsc_grace_allow_new_connection(struct gsm_network *network, struct gsm_bts *bts)
+{
+	if (bts->excl_from_rf_lock)
+		return 1;
+	return network->bsc_data->rf_ctrl->policy == S_RF_ON;
+}
+
+
+static int normal_paging(struct bsc_subscr *subscr, int chan_needed,
+			 struct bsc_msc_data *msc)
+{
+	int rc, num_pages = 0;
+	/* we can't page by lac.. we need to page everything */
+	if (msc->core_lac != -1) {
+		struct gsm_bts *bts;
+
+		llist_for_each_entry(bts, &msc->network->bts_list, list) {
+			rc = paging_request_bts(bts, subscr, chan_needed, NULL, msc);
+			if (rc > 0)
+				num_pages += rc;
+		}
+
+		return num_pages;
+	}
+
+	return paging_request(msc->network, subscr, chan_needed, NULL, msc);
+}
+
+static int locked_paging(struct bsc_subscr *subscr, int chan_needed,
+			 struct bsc_msc_data *msc)
+{
+	struct gsm_bts *bts = NULL;
+	int rc, num_pages = 0;
+
+	/*
+	 * Check if there is any BTS that is on for the given lac. Start
+	 * with NULL and iterate through all bts.
+	 */
+	llist_for_each_entry(bts, &msc->network->bts_list, list) {
+		/*
+		 * continue if the BTS is not excluded from the lock
+		 */
+		if (!bts->excl_from_rf_lock)
+			continue;
+
+		/* in case of no lac patching is in place, check the BTS */
+		if (msc->core_lac == -1 && subscr->lac != bts->location_area_code)
+			continue;
+
+		/*
+		 * now page on this bts
+		 */
+		rc = paging_request_bts(bts, subscr, chan_needed, NULL, msc);
+		if (rc > 0)
+			num_pages += rc;
+	};
+
+	/* All bts are either off or in the grace period */
+	return num_pages;
+}
+
+/**
+ * Try to not page if everything the cell is not on.
+ */
+int bsc_grace_paging_request(enum signal_rf rf_policy,
+			     struct bsc_subscr *subscr,
+			     int chan_needed,
+			     struct bsc_msc_data *msc)
+{
+	if (rf_policy == S_RF_ON)
+		return normal_paging(subscr, chan_needed, msc);
+	return locked_paging(subscr, chan_needed, msc);
+}
+
+static int handle_sub(struct gsm_lchan *lchan, const char *text)
+{
+	struct gsm_subscriber_connection *conn;
+
+	/* only send it to TCH */
+	if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F)
+		return -1;
+
+	/* only send on the primary channel */
+	conn = lchan->conn;
+	if (!conn)
+		return -1;
+
+	if (conn->lchan != lchan)
+		return -1;
+
+	/* only when active */
+	if (lchan->state != LCHAN_S_ACTIVE)
+		return -1;
+
+	bsc_send_ussd_notify(conn, 0, text);
+	bsc_send_ussd_release_complete(conn);
+
+	return 0;
+}
+
+/*
+ * The place to handle the grace mode. Right now we will send
+ * USSD messages to the subscriber, in the future we might start
+ * a timer to have different modes for the grace period.
+ */
+static int handle_grace(struct gsm_network *network)
+{
+	int ts_nr, lchan_nr;
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+
+	if (!network->bsc_data->mid_call_txt)
+		return 0;
+
+	llist_for_each_entry(bts, &network->bts_list, list) {
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) {
+				struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+				for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) {
+					handle_sub(&ts->lchan[lchan_nr],
+						   network->bsc_data->mid_call_txt);
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+static int handle_rf_signal(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct rf_signal_data *sig;
+
+	if (subsys != SS_RF)
+		return -1;
+
+	sig = signal_data;
+
+	if (signal == S_RF_GRACE)
+		handle_grace(sig->net);
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_grace(void)
+{
+	osmo_signal_register_handler(SS_RF, handle_rf_signal, NULL);
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_main.c b/openbsc/src/osmo-bsc/osmo_bsc_main.c
new file mode 100644
index 0000000..cc18c14
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_main.c
@@ -0,0 +1,307 @@
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bss.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/vty.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/ctrl.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/abis/abis.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#include "../../bscconfig.h"
+
+struct gsm_network *bsc_gsmnet = 0;
+static const char *config_file = "openbsc.cfg";
+static const char *rf_ctrl = NULL;
+extern const char *openbsc_copyright;
+static int daemonize = 0;
+static LLIST_HEAD(access_lists);
+
+struct llist_head *bsc_access_lists(void)
+{
+	return &access_lists;
+}
+
+static void print_usage()
+{
+	printf("Usage: osmo-bsc-sccplite\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -D --daemonize Fork the process into a background daemon\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+	printf("  -s --disable-color\n");
+	printf("  -T --timestamp. Print a timestamp in the debug output.\n");
+	printf("  -V --version. Print the version of OsmoBSC.\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -l --local=IP. The local address of the MGCP.\n");
+	printf("  -e --log-level number. Set a global loglevel.\n");
+	printf("  -r --rf-ctl NAME. A unix domain socket to listen for cmds.\n");
+	printf("  -t --testmode. A special mode to provoke failures at the MSC.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"daemonize", 0, 0, 'D'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"version", 0, 0, 'V' },
+			{"local", 1, 0, 'l'},
+			{"log-level", 1, 0, 'e'},
+			{"rf-ctl", 1, 0, 'r'},
+			{"testmode", 0, 0, 't'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:DsTVc:e:r:t",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(osmo_stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(osmo_stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'c':
+			config_file = optarg;
+			break;
+		case 'T':
+			log_set_print_timestamp(osmo_stderr_target, 1);
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		case 'e':
+			log_set_log_level(osmo_stderr_target, atoi(optarg));
+			break;
+		case 'r':
+			rf_ctrl = optarg;
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OsmoBSC",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+extern int bsc_shutdown_net(struct gsm_network *net);
+static void signal_handler(int signal)
+{
+	struct bsc_msc_data *msc;
+
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+	case SIGTERM:
+		bsc_shutdown_net(bsc_gsmnet);
+		osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
+		sleep(3);
+		exit(0);
+		break;
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		if (!bsc_gsmnet->bsc_data)
+			return;
+		llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry)
+			bsc_msc_lost(msc->msc_con);
+		break;
+	default:
+		break;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct bsc_msc_data *msc;
+	struct osmo_bsc_data *data;
+	int rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
+	msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+	osmo_init_logging(&log_info);
+	osmo_stats_init(tall_bsc_ctx);
+
+	/* Allocate global gsm_network struct */
+	rc = bsc_network_alloc(NULL);
+	if (rc) {
+		fprintf(stderr, "Allocation failed. exiting.\n");
+		exit(1);
+	}
+
+	bts_init();
+	libosmo_abis_init(tall_bsc_ctx);
+
+	/* enable filters */
+
+	/* This needs to precede handle_options() */
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	bsc_vty_init(bsc_gsmnet);
+	bsc_msg_acc_lst_vty_init(tall_bsc_ctx, &access_lists, BSC_NODE);
+	ctrl_vty_init(tall_bsc_ctx);
+
+	/* parse options */
+	handle_options(argc, argv);
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	/* initialize SCCP */
+	sccp_set_log_area(DSCCP);
+
+	/* Read the config */
+	rc = bsc_network_configure(config_file);
+	if (rc < 0) {
+		fprintf(stderr, "Bootstrapping the network failed. exiting.\n");
+		exit(1);
+	}
+	bsc_api_init(bsc_gsmnet, osmo_bsc_api());
+
+	/* start control interface after reading config for
+	 * ctrl_vty_get_bind_addr() */
+	bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet,
+					       ctrl_vty_get_bind_addr(),
+					       OSMO_CTRL_PORT_NITB_BSC);
+	if (!bsc_gsmnet->ctrl) {
+		fprintf(stderr, "Failed to init the control interface. Exiting.\n");
+		exit(1);
+	}
+
+	rc = bsc_ctrl_cmds_install(bsc_gsmnet);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to install control commands. Exiting.\n");
+		exit(1);
+	}
+
+	data = bsc_gsmnet->bsc_data;
+	if (rf_ctrl)
+		osmo_talloc_replace_string(data, &data->rf_ctrl_name, rf_ctrl);
+
+	data->rf_ctrl = osmo_bsc_rf_create(data->rf_ctrl_name, bsc_gsmnet);
+	if (!data->rf_ctrl) {
+		fprintf(stderr, "Failed to create the RF service.\n");
+		exit(1);
+	}
+
+	llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
+		if (osmo_bsc_msc_init(msc) != 0) {
+			LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n");
+			exit(1);
+		}
+	}
+
+
+	if (osmo_bsc_sccp_init(bsc_gsmnet) != 0) {
+		LOGP(DNM, LOGL_ERROR, "Failed to register SCCP.\n");
+		exit(1);
+	}
+
+	if (osmo_bsc_audio_init(bsc_gsmnet) != 0) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to register audio support.\n");
+		exit(1);
+	}
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGTERM, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	osmo_init_ignore_signals();
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	while (1) {
+		osmo_select_main(0);
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_msc.c b/openbsc/src/osmo-bsc/osmo_bsc_msc.c
new file mode 100644
index 0000000..b179ff1
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_msc.c
@@ -0,0 +1,581 @@
+/*
+ * Handle the connection to the MSC. This include ping/timeout/reconnect
+ * (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2015 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/crypt/auth.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+
+
+static void initialize_if_needed(struct bsc_msc_connection *conn);
+static void send_lacs(struct gsm_network *net, struct bsc_msc_connection *conn);
+static void send_id_get_response(struct bsc_msc_data *data, int fd, struct msgb *inp);
+static void send_ping(struct bsc_msc_data *data);
+static void schedule_ping_pong(struct bsc_msc_data *data);
+
+/*
+ * MGCP forwarding code
+ */
+static int mgcp_do_read(struct osmo_fd *fd)
+{
+	struct bsc_msc_data *data = (struct bsc_msc_data *) fd->data;
+	struct msgb *mgcp;
+	int ret;
+
+	mgcp = msgb_alloc_headroom(4096, 128, "mgcp_from_gw");
+	if (!mgcp) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n");
+		return -1;
+	}
+
+	ret = read(fd->fd, mgcp->data, 4096 - 128);
+	if (ret <= 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno));
+		msgb_free(mgcp);
+		return -1;
+	} else if (ret > 4096 - 128) {
+		LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret);
+		msgb_free(mgcp);
+		return -1;
+        }
+
+	mgcp->l2h = msgb_put(mgcp, ret);
+	msc_queue_write(data->msc_con, mgcp, IPAC_PROTO_MGCP_OLD);
+	return 0;
+}
+
+static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg)
+{
+	int ret;
+
+	LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len);
+
+	ret = write(fd->fd, msg->data, msg->len);
+	if (ret != msg->len)
+		LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP GW (%s).\n", strerror(errno));
+
+	return ret;
+}
+
+static void mgcp_forward(struct bsc_msc_data *data, struct msgb *msg)
+{
+	struct msgb *mgcp;
+
+	if (msgb_l2len(msg) > 4096) {
+		LOGP(DMGCP, LOGL_ERROR, "Can not forward too big message.\n");
+		return;
+	}
+
+	mgcp = msgb_alloc(4096, "mgcp_to_gw");
+	if (!mgcp) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to send message.\n");
+		return;
+	}
+
+	msgb_put(mgcp, msgb_l2len(msg));
+	memcpy(mgcp->data, msg->l2h, mgcp->len);
+	if (osmo_wqueue_enqueue(&data->mgcp_agent, mgcp) != 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW.\n");
+		msgb_free(mgcp);
+	}
+}
+
+static int mgcp_create_port(struct bsc_msc_data *data)
+{
+	int on;
+	struct sockaddr_in addr;
+
+	data->mgcp_agent.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (data->mgcp_agent.bfd.fd < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno);
+		return -1;
+	}
+
+	on = 1;
+	setsockopt(data->mgcp_agent.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	/* try to bind the socket */
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	addr.sin_port = 0;
+
+	if (bind(data->mgcp_agent.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to bind to any port.\n");
+		close(data->mgcp_agent.bfd.fd);
+		data->mgcp_agent.bfd.fd = -1;
+		return -1;
+	}
+
+	/* connect to the remote */
+	addr.sin_port = htons(2427);
+	if (connect(data->mgcp_agent.bfd.fd, (struct sockaddr *) & addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to connect to local MGCP GW. %s\n", strerror(errno));
+		close(data->mgcp_agent.bfd.fd);
+		data->mgcp_agent.bfd.fd = -1;
+		return -1;
+	}
+
+	osmo_wqueue_init(&data->mgcp_agent, 10);
+	data->mgcp_agent.bfd.when = BSC_FD_READ;
+	data->mgcp_agent.bfd.data = data;
+	data->mgcp_agent.read_cb = mgcp_do_read;
+	data->mgcp_agent.write_cb = mgcp_do_write;
+
+	if (osmo_fd_register(&data->mgcp_agent.bfd) != 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n");
+		close(data->mgcp_agent.bfd.fd);
+		data->mgcp_agent.bfd.fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Send data to the network
+ */
+int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto)
+{
+	ipa_prepend_header(msg, proto);
+	if (osmo_wqueue_enqueue(&conn->write_queue, msg) != 0) {
+		LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto);
+		msgb_free(msg);
+		return -1;
+	}
+
+	return 0;
+}
+
+int msc_queue_write_with_ping(struct bsc_msc_connection *conn,
+			struct msgb *msg, int proto)
+{
+	struct bsc_msc_data *data;
+	uint8_t val;
+
+	/* prepend the header */
+	ipa_prepend_header(msg, proto);
+	if (osmo_wqueue_enqueue(&conn->write_queue, msg) != 0) {
+		LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto);
+		msgb_free(msg);
+		return -1;
+	}
+
+	/* add the ping as the other message */
+	val = IPAC_MSGT_PING;
+	msgb_l16tv_put(msg, 1, IPAC_PROTO_IPACCESS, &val);
+
+	data = (struct bsc_msc_data *) conn->write_queue.bfd.data;
+	schedule_ping_pong(data);
+	return 0;
+}
+
+static int msc_alink_do_write(struct osmo_fd *fd, struct msgb *msg)
+{
+	int ret;
+
+	LOGP(DMSC, LOGL_DEBUG, "Sending SCCP to MSC: %u\n", msgb_l2len(msg));
+	LOGP(DLMI, LOGL_DEBUG, "MSC TX %s\n", osmo_hexdump(msg->data, msg->len));
+
+	ret = write(fd->fd, msg->data, msg->len);
+	if (ret < msg->len)
+		perror("MSC: Failed to send SCCP");
+
+	return ret;
+}
+
+static void handle_ctrl(struct bsc_msc_data *msc, struct msgb *msg)
+{
+	int ret;
+	struct ctrl_cmd *cmd;
+	bool parse_failed;
+
+	cmd = ctrl_cmd_parse3(msc->msc_con, msg, &parse_failed);
+	if (cmd->type == CTRL_TYPE_ERROR && parse_failed) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to parse control message.\n");
+
+		ctrl_cmd_send(&msc->msc_con->write_queue, cmd);
+		talloc_free(cmd);
+
+		return;
+	}
+
+	ret = ctrl_cmd_handle(msc->network->ctrl, cmd, msc->network);
+	if (ret != CTRL_CMD_HANDLED)
+		ctrl_cmd_send(&msc->msc_con->write_queue, cmd);
+	talloc_free(cmd);
+}
+
+static void osmo_ext_handle(struct bsc_msc_data *msc, struct msgb *msg)
+{
+	struct ipaccess_head *hh;
+	struct ipaccess_head_ext *hh_ext;
+
+	hh = (struct ipaccess_head *) msg->data;
+	hh_ext = (struct ipaccess_head_ext *) hh->data;
+	if (msg->len < sizeof(*hh) + sizeof(*hh_ext)) {
+		LOGP(DMSC, LOGL_ERROR, "Packet too short for extended header.\n");
+		return;
+	}
+
+	msg->l2h = hh_ext->data;
+	if (hh_ext->proto == IPAC_PROTO_EXT_MGCP)
+		mgcp_forward(msc, msg);
+	else if (hh_ext->proto == IPAC_PROTO_EXT_LAC)
+		send_lacs(msc->network, msc->msc_con);
+	else if (hh_ext->proto == IPAC_PROTO_EXT_CTRL)
+		handle_ctrl(msc, msg);
+}
+
+static int ipaccess_a_fd_cb(struct osmo_fd *bfd)
+{
+	struct msgb *msg = NULL;
+	struct ipaccess_head *hh;
+	struct bsc_msc_data *data = (struct bsc_msc_data *) bfd->data;
+	int ret;
+
+	ret = ipa_msg_recv_buffered(bfd->fd, &msg, &data->msc_con->pending_msg);
+	if (ret <= 0) {
+		if (ret == -EAGAIN)
+			return 0;
+		if (ret == 0) {
+			LOGP(DMSC, LOGL_ERROR, "The connection to the MSC was lost.\n");
+			bsc_msc_lost(data->msc_con);
+			return -1;
+		}
+
+		LOGP(DMSC, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret);
+		return -1;
+	}
+
+	LOGP(DLMI, LOGL_DEBUG, "From MSC: %s proto: %d\n", osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+
+	/* handle base message handling */
+	hh = (struct ipaccess_head *) msg->data;
+
+	/* initialize the networking. This includes sending a GSM08.08 message */
+	msg->cb[0] = (unsigned long) data;
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ipa_ccm_rcvmsg_base(msg, bfd);
+		if (msg->l2h[0] == IPAC_MSGT_ID_ACK)
+			initialize_if_needed(data->msc_con);
+		else if (msg->l2h[0] == IPAC_MSGT_ID_GET) {
+			send_id_get_response(data, bfd->fd, msg);
+		} else if (msg->l2h[0] == IPAC_MSGT_PONG) {
+			osmo_timer_del(&data->pong_timer);
+		}
+	} else if (hh->proto == IPAC_PROTO_SCCP) {
+		sccp_system_incoming_ctx(msg, data->msc_con);
+	} else if (hh->proto == IPAC_PROTO_MGCP_OLD) {
+		mgcp_forward(data, msg);
+	} else if (hh->proto == IPAC_PROTO_OSMO) {
+		osmo_ext_handle(data, msg);
+	}
+
+	msgb_free(msg);
+	return 0;
+}
+
+static void send_ping(struct bsc_msc_data *data)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "ping");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create PING.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = IPAC_MSGT_PING;
+
+	msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS);
+}
+
+static void schedule_ping_pong(struct bsc_msc_data *data)
+{
+	/* send another ping in 20 seconds */
+	osmo_timer_schedule(&data->ping_timer, data->ping_timeout, 0);
+
+	/* also start a pong timer */
+	osmo_timer_schedule(&data->pong_timer, data->pong_timeout, 0);
+}
+
+static void msc_ping_timeout_cb(void *_data)
+{
+	struct bsc_msc_data *data = (struct bsc_msc_data *) _data;
+	if (data->ping_timeout <= 0)
+		return;
+
+	send_ping(data);
+	schedule_ping_pong(data);
+}
+
+static void msc_pong_timeout_cb(void *_data)
+{
+	struct bsc_msc_data *data = (struct bsc_msc_data *) _data;
+
+	LOGP(DMSC, LOGL_ERROR, "MSC didn't answer PING. Closing connection.\n");
+	bsc_msc_lost(data->msc_con);
+}
+
+static void msc_connection_connected(struct bsc_msc_connection *con)
+{
+	struct msc_signal_data sig;
+	struct bsc_msc_data *data;
+	int ret, on;
+	on = 1;
+	ret = setsockopt(con->write_queue.bfd.fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+	if (ret != 0)
+                LOGP(DMSC, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
+
+	data = (struct bsc_msc_data *) con->write_queue.bfd.data;
+	msc_ping_timeout_cb(data);
+
+	sig.data = data;
+	osmo_signal_dispatch(SS_MSC, S_MSC_CONNECTED, &sig);
+}
+
+/*
+ * The connection to the MSC was lost and we will need to free all
+ * resources and then attempt to reconnect.
+ */
+static void msc_connection_was_lost(struct bsc_msc_connection *msc)
+{
+	struct msc_signal_data sig;
+	struct bsc_msc_data *data;
+
+	LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n");
+
+	data = (struct bsc_msc_data *) msc->write_queue.bfd.data;
+	osmo_timer_del(&data->ping_timer);
+	osmo_timer_del(&data->pong_timer);
+
+	sig.data = data;
+	osmo_signal_dispatch(SS_MSC, S_MSC_LOST, &sig);
+
+	msc->is_authenticated = 0;
+	bsc_msc_schedule_connect(msc);
+}
+
+static void send_lacs(struct gsm_network *net, struct bsc_msc_connection *conn)
+{
+	struct ipac_ext_lac_cmd *lac;
+	struct gsm_bts *bts;
+	struct msgb *msg;
+	int lacs = 0;
+
+	if (llist_empty(&net->bts_list)) {
+		LOGP(DMSC, LOGL_ERROR, "No BTSs configured. Not sending LACs.\n");
+		return;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "LAC Command");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create the LAC command.\n");
+		return;
+	}
+
+	lac = (struct ipac_ext_lac_cmd *) msgb_put(msg, sizeof(*lac));
+	lac->add_remove = 1;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (lacs++ == 0)
+			lac->lac = htons(bts->location_area_code);
+		else
+			msgb_put_u16(msg, htons(bts->location_area_code));
+	}
+
+	lac->nr_extra_lacs = lacs - 1;
+	ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_LAC);
+	msc_queue_write(conn, msg, IPAC_PROTO_OSMO);
+}
+
+static void initialize_if_needed(struct bsc_msc_connection *conn)
+{
+	struct msgb *msg;
+
+	if (!conn->is_authenticated) {
+		/* send a gsm 08.08 reset message from here */
+		msg = gsm0808_create_reset();
+		if (!msg) {
+			LOGP(DMSC, LOGL_ERROR, "Failed to create the reset message.\n");
+			return;
+		}
+
+		sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0, conn);
+		msgb_free(msg);
+		conn->is_authenticated = 1;
+	}
+}
+
+static int answer_challenge(struct bsc_msc_data *data, struct msgb *inp, struct osmo_auth_vector *vec)
+{
+	int ret;
+	struct tlv_parsed tvp;
+	const uint8_t *mrand;
+	uint8_t mrand_len;
+	struct osmo_sub_auth_data auth = {
+		.type		= OSMO_AUTH_TYPE_GSM,
+		.algo		= OSMO_AUTH_ALG_MILENAGE,
+	};
+
+	ret = ipa_ccm_idtag_parse_off(&tvp,
+				inp->l2h + 1,
+				msgb_l2len(inp) - 1, 1);
+	if (ret < 0) {
+		LOGP(DMSC, LOGL_ERROR, "ignoring IPA response "
+			"message with malformed TLVs: %s\n", osmo_hexdump(inp->l2h + 1,
+			msgb_l2len(inp) - 1));
+		return 0;
+	}
+
+	mrand = TLVP_VAL(&tvp, 0x23);
+	mrand_len = TLVP_LEN(&tvp, 0x23);
+	if (mrand_len != 16) {
+		LOGP(DMSC, LOGL_ERROR,
+			"RAND is not 16 bytes. Was %d\n",
+			mrand_len);
+		return 0;
+	}
+
+	/* copy the key */
+	memcpy(auth.u.umts.opc, data->bsc_key, 16);
+	memcpy(auth.u.umts.k, data->bsc_key, 16);
+	memset(auth.u.umts.amf, 0, 2);
+	auth.u.umts.sqn = 0;
+
+	/* generate the result */
+	memset(vec, 0, sizeof(*vec));
+	osmo_auth_gen_vec(vec, &auth, mrand);
+	return 1;
+}
+
+
+static void send_id_get_response(struct bsc_msc_data *data, int fd, struct msgb *inp)
+{
+	struct msc_signal_data sig;
+	struct msgb *msg;
+	struct osmo_auth_vector vec;
+	int valid = 0;
+
+	if (data->bsc_key_present)
+		valid = answer_challenge(data, inp, &vec);
+
+	msg = bsc_msc_id_get_resp(valid, data->bsc_token,
+			vec.res, valid ? vec.res_len : 0);
+	if (!msg)
+		return;
+	msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS);
+
+	sig.data = data;
+	osmo_signal_dispatch(SS_MSC, S_MSC_AUTHENTICATED, &sig);
+}
+
+int osmo_bsc_msc_init(struct bsc_msc_data *data)
+{
+	if (mgcp_create_port(data) != 0)
+		return -1;
+
+	data->msc_con = bsc_msc_create(data, &data->dests);
+	if (!data->msc_con) {
+		LOGP(DMSC, LOGL_ERROR, "Creating the MSC network connection failed.\n");
+		return -1;
+	}
+
+	osmo_timer_setup(&data->ping_timer, msc_ping_timeout_cb, data);
+	osmo_timer_setup(&data->pong_timer, msc_pong_timeout_cb, data);
+
+	data->msc_con->write_queue.bfd.data = data;
+	data->msc_con->connection_loss = msc_connection_was_lost;
+	data->msc_con->connected = msc_connection_connected;
+	data->msc_con->write_queue.read_cb = ipaccess_a_fd_cb;
+	data->msc_con->write_queue.write_cb = msc_alink_do_write;
+	bsc_msc_connect(data->msc_con);
+
+	return 0;
+}
+
+struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr)
+{
+	struct bsc_msc_data *msc_data;
+
+	llist_for_each_entry(msc_data, &net->bsc_data->mscs, entry)
+		if (msc_data->nr == nr)
+			return msc_data;
+	return NULL;
+}
+
+struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
+{
+	struct bsc_msc_data *msc_data;
+
+	/* check if there is already one */
+	msc_data = osmo_msc_data_find(net, nr);
+	if (msc_data)
+		return msc_data;
+
+	msc_data = talloc_zero(net, struct bsc_msc_data);
+	if (!msc_data)
+		return NULL;
+
+	llist_add_tail(&msc_data->entry, &net->bsc_data->mscs);
+
+	/* Init back pointer */
+	msc_data->network = net;
+
+	INIT_LLIST_HEAD(&msc_data->dests);
+	msc_data->ping_timeout = 20;
+	msc_data->pong_timeout = 5;
+	msc_data->core_plmn = (struct osmo_plmn_id){
+		.mcc = GSM_MCC_MNC_INVALID,
+		.mnc = GSM_MCC_MNC_INVALID,
+	};
+	msc_data->core_ci = -1;
+	msc_data->core_lac = -1;
+	msc_data->rtp_base = 4000;
+
+	msc_data->nr = nr;
+	msc_data->allow_emerg = 1;
+
+	/* Defaults for the audio setup */
+	msc_data->amr_conf.m5_90 = 1;
+
+	return msc_data;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_sccp.c b/openbsc/src/osmo-bsc/osmo_bsc_sccp.c
new file mode 100644
index 0000000..8388f88
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_sccp.c
@@ -0,0 +1,328 @@
+/* Interaction with the SCCP subsystem */
+/*
+ * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/* SCCP helper */
+#define SCCP_IT_TIMER 60
+
+static LLIST_HEAD(active_connections);
+
+static void free_queued(struct osmo_bsc_sccp_con *conn)
+{
+	struct msgb *msg;
+
+	while (!llist_empty(&conn->sccp_queue)) {
+		/* this is not allowed to fail */
+		msg = msgb_dequeue(&conn->sccp_queue);
+		msgb_free(msg);
+	}
+
+	conn->sccp_queue_size = 0;
+}
+
+static void send_queued(struct osmo_bsc_sccp_con *conn)
+{
+	struct msgb *msg;
+
+	while (!llist_empty(&conn->sccp_queue)) {
+		/* this is not allowed to fail */
+		msg = msgb_dequeue(&conn->sccp_queue);
+		sccp_connection_write(conn->sccp, msg);
+		msgb_free(msg);
+		conn->sccp_queue_size -= 1;
+	}
+}
+
+static void msc_outgoing_sccp_data(struct sccp_connection *conn,
+				   struct msgb *msg, unsigned int len)
+{
+	struct osmo_bsc_sccp_con *bsc_con =
+			(struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+	bsc_handle_dt1(bsc_con, msg, len);
+}
+
+static void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state)
+{
+	struct osmo_bsc_sccp_con *con_data;
+
+	if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+		con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+		if (con_data->conn) {
+			LOGP(DMSC, LOGL_ERROR,
+				"ERROR: The lchan is still associated.\n");
+			gsm0808_clear(con_data->conn);
+			bsc_subscr_con_free(con_data->conn);
+			con_data->conn = NULL;
+		}
+
+		con_data->sccp = NULL;
+		free_queued(con_data);
+		sccp_connection_free(conn);
+		bsc_delete_connection(con_data);
+	} else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn);
+		con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+		osmo_timer_del(&con_data->sccp_cc_timeout);
+		osmo_timer_schedule(&con_data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+
+		send_queued(con_data);
+	}
+}
+
+static void bsc_sccp_force_free(struct osmo_bsc_sccp_con *data)
+{
+	if (data->conn) {
+		gsm0808_clear(data->conn);
+		bsc_subscr_con_free(data->conn);
+		data->conn = NULL;
+	}
+
+	free_queued(data);
+	sccp_connection_force_free(data->sccp);
+	data->sccp = NULL;
+	bsc_delete_connection(data);
+}
+
+static void sccp_it_timeout(void *_data)
+{
+	struct osmo_bsc_sccp_con *data =
+		(struct osmo_bsc_sccp_con *) _data;
+
+	sccp_connection_send_it(data->sccp);
+	osmo_timer_schedule(&data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+}
+
+static void sccp_cc_timeout(void *_data)
+{
+	struct osmo_bsc_sccp_con *data =
+		(struct osmo_bsc_sccp_con *) _data;
+
+	if (data->sccp->connection_state >= SCCP_CONNECTION_STATE_ESTABLISHED)
+		return;
+
+	LOGP(DMSC, LOGL_ERROR, "The connection was never established.\n");
+	bsc_sccp_force_free(data);
+}
+
+static void msc_sccp_write_ipa(struct sccp_connection *conn, struct msgb *msg,
+			      void *global_ctx, void *ctx)
+{
+	struct bsc_msc_connection *msc_con;
+
+	if (conn) {
+		struct osmo_bsc_sccp_con *bsc_con = conn->data_ctx;
+		msc_con = bsc_con->msc->msc_con;
+		if (bsc_con->send_ping) {
+			bsc_con->send_ping = 0;
+			msc_queue_write_with_ping(msc_con, msg, IPAC_PROTO_SCCP);
+			return;
+		}
+	} else {
+		msc_con = ctx;
+	}
+
+	msc_queue_write(msc_con, msg, IPAC_PROTO_SCCP);
+}
+
+static int msc_sccp_accept(struct sccp_connection *connection, void *data)
+{
+	LOGP(DMSC, LOGL_DEBUG, "Rejecting incoming SCCP connection.\n");
+	return -1;
+}
+
+static int msc_sccp_read(struct msgb *msgb, unsigned int length, void *data)
+{
+	struct bsc_msc_data *msc = (struct bsc_msc_data *) msgb->cb[0];
+	return bsc_handle_udt(msc, msgb, length);
+}
+
+int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+	struct sccp_connection *sccp = conn->sccp;
+
+	if (sccp->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+		msgb_free(msg);
+	} else if (sccp->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED
+		   && conn->sccp_queue_size == 0) {
+		sccp_connection_write(sccp, msg);
+		msgb_free(msg);
+	} else if (conn->sccp_queue_size > 10) {
+		LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+		msgb_free(msg);
+	} else {
+		LOGP(DMSC, LOGL_DEBUG, "Queueing packet on %p. Queue size: %d\n", sccp, conn->sccp_queue_size);
+		conn->sccp_queue_size += 1;
+		msgb_enqueue(&conn->sccp_queue, msg);
+	}
+
+	return 0;
+}
+
+enum bsc_con bsc_create_new_connection(struct gsm_subscriber_connection *conn,
+			      struct bsc_msc_data *msc, int send_ping)
+{
+	struct osmo_bsc_sccp_con *bsc_con;
+	struct sccp_connection *sccp;
+
+	/* This should not trigger */
+	if (!msc || !msc->msc_con->is_authenticated) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "How did this happen? MSC is not connected. Dropping.\n");
+		return BSC_CON_REJECT_NO_LINK;
+	}
+
+	if (!bsc_grace_allow_new_connection(conn->bts->network, conn->bts)) {
+		LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n");
+		return BSC_CON_REJECT_RF_GRACE;
+	}
+
+	sccp = sccp_connection_socket();
+	if (!sccp) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n");
+		return BSC_CON_NO_MEM;
+	}
+
+	bsc_con = talloc_zero(conn->bts, struct osmo_bsc_sccp_con);
+	if (!bsc_con) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate.\n");
+		sccp_connection_free(sccp);
+		return BSC_CON_NO_MEM;
+	}
+
+	/* callbacks */
+	sccp->state_cb = msc_outgoing_sccp_state;
+	sccp->data_cb = msc_outgoing_sccp_data;
+	sccp->data_ctx = bsc_con;
+
+	bsc_con->send_ping = send_ping;
+
+	/* prepare the timers */
+	osmo_timer_setup(&bsc_con->sccp_it_timeout, sccp_it_timeout, bsc_con);
+	osmo_timer_setup(&bsc_con->sccp_cc_timeout, sccp_cc_timeout, bsc_con);
+
+	INIT_LLIST_HEAD(&bsc_con->sccp_queue);
+
+	bsc_con->sccp = sccp;
+	bsc_con->msc = msc;
+	bsc_con->conn = conn;
+	llist_add_tail(&bsc_con->entry, &active_connections);
+	conn->sccp_con = bsc_con;
+	return BSC_CON_SUCCESS;
+}
+
+int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+	osmo_timer_schedule(&conn->sccp_cc_timeout, 10, 0);
+	sccp_connection_connect(conn->sccp, &sccp_ssn_bssap, msg);
+	msgb_free(msg);
+	return 0;
+}
+
+int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp)
+{
+	if (!sccp)
+		return 0;
+
+	if (sccp->conn)
+		LOGP(DMSC, LOGL_ERROR, "Should have been cleared.\n");
+
+	llist_del(&sccp->entry);
+	osmo_timer_del(&sccp->sccp_it_timeout);
+	osmo_timer_del(&sccp->sccp_cc_timeout);
+	talloc_free(sccp);
+	return 0;
+}
+
+static void bsc_notify_msc_lost(struct osmo_bsc_sccp_con *con)
+{
+	struct gsm_subscriber_connection *conn = con->conn;
+
+	/* send USSD notification if string configured and con->data is set */
+	if (!conn)
+		return;
+
+	/* check for config string */
+	if (!con->msc->ussd_msc_lost_txt)
+		return;
+	if (con->msc->ussd_msc_lost_txt[0] == '\0')
+		return;
+
+	/* send USSD notification */
+	bsc_send_ussd_notify(conn, 1, conn->sccp_con->msc->ussd_msc_lost_txt);
+	bsc_send_ussd_release_complete(conn);
+}
+
+void bsc_notify_and_close_conns(struct bsc_msc_connection *msc_con)
+{
+	struct osmo_bsc_sccp_con *con, *tmp;
+
+	llist_for_each_entry_safe(con, tmp, &active_connections, entry) {
+		if (con->msc->msc_con != msc_con)
+			continue;
+
+		bsc_notify_msc_lost(con);
+		bsc_sccp_force_free(con);
+	}
+}
+
+static int handle_msc_signal(unsigned int subsys, unsigned int signal,
+			     void *handler_data, void *signal_data)
+{
+	struct msc_signal_data *msc;
+
+	if (subsys != SS_MSC)
+		return 0;
+
+	msc = signal_data;
+	if (signal == S_MSC_LOST)
+		bsc_notify_and_close_conns(msc->data->msc_con);
+
+	return 0;
+}
+
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet)
+{
+	sccp_set_log_area(DSCCP);
+	sccp_system_init(msc_sccp_write_ipa, gsmnet);
+	sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL);
+	sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, gsmnet);
+
+	osmo_signal_register_handler(SS_MSC, handle_msc_signal, gsmnet);
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_vty.c b/openbsc/src/osmo-bsc/osmo_bsc_vty.c
new file mode 100644
index 0000000..66e59bd
--- /dev/null
+++ b/openbsc/src/osmo-bsc/osmo_bsc_vty.c
@@ -0,0 +1,959 @@
+/* Osmo BSC VTY Configuration */
+/* (C) 2009-2015 by Holger Hans Peter Freyther
+ * (C) 2009-2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/vty.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/bsc_msg_filter.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/vty/logging.h>
+
+#include <time.h>
+
+
+#define IPA_STR "IP.ACCESS specific\n"
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct osmo_bsc_data *osmo_bsc_data(struct vty *vty)
+{
+	return bsc_gsmnet->bsc_data;
+}
+
+static struct bsc_msc_data *bsc_msc_data(struct vty *vty)
+{
+	return vty->index;
+}
+
+static struct cmd_node bsc_node = {
+	BSC_NODE,
+	"%s(config-bsc)# ",
+	1,
+};
+
+static struct cmd_node msc_node = {
+	MSC_NODE,
+	"%s(config-msc)# ",
+	1,
+};
+
+DEFUN(cfg_net_msc, cfg_net_msc_cmd,
+      "msc [<0-1000>]", "Configure MSC details\n" "MSC connection to configure\n")
+{
+	int index = argc == 1 ? atoi(argv[0]) : 0;
+	struct bsc_msc_data *msc;
+
+	msc = osmo_msc_data_alloc(bsc_gsmnet, index);
+	if (!msc) {
+		vty_out(vty, "%%Failed to allocate MSC data.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->index = msc;
+	vty->node = MSC_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc, cfg_net_bsc_cmd,
+      "bsc", "Configure BSC\n")
+{
+	vty->node = BSC_NODE;
+	return CMD_SUCCESS;
+}
+
+static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc)
+{
+#define WRITE_AMR(vty, msc, name, var) \
+	vty_out(vty, " amr-config %s %s%s", \
+		name, msc->amr_conf.var ? "allowed" : "forbidden", \
+		VTY_NEWLINE);
+
+	WRITE_AMR(vty, msc, "12_2k", m12_2);
+	WRITE_AMR(vty, msc, "10_2k", m10_2);
+	WRITE_AMR(vty, msc, "7_95k", m7_95);
+	WRITE_AMR(vty, msc, "7_40k", m7_40);
+	WRITE_AMR(vty, msc, "6_70k", m6_70);
+	WRITE_AMR(vty, msc, "5_90k", m5_90);
+	WRITE_AMR(vty, msc, "5_15k", m5_15);
+	WRITE_AMR(vty, msc, "4_75k", m4_75);
+#undef WRITE_AMR
+}
+
+static void write_msc(struct vty *vty, struct bsc_msc_data *msc)
+{
+	struct bsc_msc_dest *dest;
+
+	vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE);
+	if (msc->bsc_token)
+		vty_out(vty, " token %s%s", msc->bsc_token, VTY_NEWLINE);
+	if (msc->bsc_key_present)
+		vty_out(vty, " auth-key %s%s",
+			osmo_hexdump(msc->bsc_key, sizeof(msc->bsc_key)), VTY_NEWLINE);
+	if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
+		vty_out(vty, " core-mobile-network-code %s%s",
+			osmo_mnc_name(msc->core_plmn.mnc, msc->core_plmn.mnc_3_digits), VTY_NEWLINE);
+	if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+		vty_out(vty, " core-mobile-country-code %s%s",
+			osmo_mcc_name(msc->core_plmn.mcc), VTY_NEWLINE);
+	if (msc->core_lac != -1)
+		vty_out(vty, " core-location-area-code %d%s",
+			msc->core_lac, VTY_NEWLINE);
+	if (msc->core_ci != -1)
+		vty_out(vty, " core-cell-identity %d%s",
+			msc->core_ci, VTY_NEWLINE);
+	vty_out(vty, " ip.access rtp-base %d%s", msc->rtp_base, VTY_NEWLINE);
+
+	if (msc->ping_timeout == -1)
+		vty_out(vty, " no timeout-ping%s", VTY_NEWLINE);
+	else {
+		vty_out(vty, " timeout-ping %d%s", msc->ping_timeout, VTY_NEWLINE);
+		vty_out(vty, " timeout-pong %d%s", msc->pong_timeout, VTY_NEWLINE);
+		if (msc->advanced_ping)
+			vty_out(vty, " timeout-ping advanced%s", VTY_NEWLINE);
+		else
+			vty_out(vty, " no timeout-ping advanced%s", VTY_NEWLINE);
+	}
+
+	if (msc->ussd_welcome_txt)
+		vty_out(vty, " bsc-welcome-text %s%s", msc->ussd_welcome_txt, VTY_NEWLINE);
+	else
+		vty_out(vty, " no bsc-welcome-text%s", VTY_NEWLINE);
+
+	if (msc->ussd_msc_lost_txt && msc->ussd_msc_lost_txt[0])
+		vty_out(vty, " bsc-msc-lost-text %s%s", msc->ussd_msc_lost_txt, VTY_NEWLINE);
+	else
+		vty_out(vty, " no bsc-msc-lost-text%s", VTY_NEWLINE);
+
+	if (msc->ussd_grace_txt && msc->ussd_grace_txt[0])
+		vty_out(vty, " bsc-grace-text %s%s", msc->ussd_grace_txt, VTY_NEWLINE);
+	else
+		vty_out(vty, " no bsc-grace-text%s", VTY_NEWLINE);
+
+	if (msc->audio_length != 0) {
+		int i;
+
+		vty_out(vty, " codec-list ");
+		for (i = 0; i < msc->audio_length; ++i) {
+			if (i != 0)
+				vty_out(vty, " ");
+
+			if (msc->audio_support[i]->hr)
+				vty_out(vty, "hr%.1u", msc->audio_support[i]->ver);
+			else
+				vty_out(vty, "fr%.1u", msc->audio_support[i]->ver);
+		}
+		vty_out(vty, "%s", VTY_NEWLINE);
+
+	}
+
+	llist_for_each_entry(dest, &msc->dests, list)
+		vty_out(vty, " dest %s %d %d%s", dest->ip, dest->port,
+			dest->dscp, VTY_NEWLINE);
+
+	vty_out(vty, " type %s%s", msc->type == MSC_CON_TYPE_NORMAL ?
+					"normal" : "local", VTY_NEWLINE);
+	vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ?
+					"allow" : "deny", VTY_NEWLINE);
+
+	if (msc->local_pref)
+		vty_out(vty, " local-prefix %s%s", msc->local_pref, VTY_NEWLINE);
+
+	if (msc->acc_lst_name)
+		vty_out(vty, " access-list-name %s%s", msc->acc_lst_name, VTY_NEWLINE);
+
+	/* write amr options */
+	write_msc_amr_options(vty, msc);
+}
+
+static int config_write_msc(struct vty *vty)
+{
+	struct bsc_msc_data *msc;
+	struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+	llist_for_each_entry(msc, &bsc->mscs, entry)
+		write_msc(vty, msc);
+
+	return CMD_SUCCESS;
+}
+
+static int config_write_bsc(struct vty *vty)
+{
+	struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+	vty_out(vty, "bsc%s", VTY_NEWLINE);
+	if (bsc->mid_call_txt)
+		vty_out(vty, " mid-call-text %s%s", bsc->mid_call_txt, VTY_NEWLINE);
+	vty_out(vty, " mid-call-timeout %d%s", bsc->mid_call_timeout, VTY_NEWLINE);
+	if (bsc->rf_ctrl_name)
+		vty_out(vty, " bsc-rf-socket %s%s",
+			bsc->rf_ctrl_name, VTY_NEWLINE);
+
+	if (bsc->auto_off_timeout != -1)
+		vty_out(vty, " bsc-auto-rf-off %d%s",
+			bsc->auto_off_timeout, VTY_NEWLINE);
+
+	if (bsc->ussd_no_msc_txt && bsc->ussd_no_msc_txt[0])
+		vty_out(vty, " missing-msc-text %s%s", bsc->ussd_no_msc_txt, VTY_NEWLINE);
+	else
+		vty_out(vty, " no missing-msc-text%s", VTY_NEWLINE);
+	if (bsc->acc_lst_name)
+		vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
+
+	bsc_msg_acc_lst_write(vty);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_token,
+      cfg_net_bsc_token_cmd,
+      "token TOKEN",
+      "A token for the BSC to be sent to the MSC\n" "A token\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	osmo_talloc_replace_string(osmo_bsc_data(vty), &data->bsc_token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_key,
+      cfg_net_bsc_key_cmd,
+      "auth-key KEY",
+      "Authentication (secret) key configuration\n"
+      "Security key\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	osmo_hexparse(argv[0], data->bsc_key, sizeof(data->bsc_key));
+	data->bsc_key_present = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_no_bsc_key, cfg_net_bsc_no_key_cmd,
+      "no auth-key",
+      NO_STR "Authentication (secret) key configuration\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	memset(data->bsc_key, 0, sizeof(data->bsc_key));
+	data->bsc_key_present = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ncc,
+      cfg_net_bsc_ncc_cmd,
+      "core-mobile-network-code <1-999>",
+      "Use this network code for the core network\n" "MNC value\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	uint16_t mnc;
+	bool mnc_3_digits;
+
+	if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) {
+		vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	data->core_plmn.mnc = mnc;
+	data->core_plmn.mnc_3_digits = mnc_3_digits;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mcc,
+      cfg_net_bsc_mcc_cmd,
+      "core-mobile-country-code <1-999>",
+      "Use this country code for the core network\n" "MCC value\n")
+{
+	uint16_t mcc;
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	if (osmo_mcc_from_str(argv[0], &mcc)) {
+		vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	data->core_plmn.mcc = mcc;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_lac,
+      cfg_net_bsc_lac_cmd,
+      "core-location-area-code <0-65535>",
+      "Use this location area code for the core network\n" "LAC value\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->core_lac = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ci,
+      cfg_net_bsc_ci_cmd,
+      "core-cell-identity <0-65535>",
+      "Use this cell identity for the core network\n" "CI value\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->core_ci = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_rtp_base,
+      cfg_net_bsc_rtp_base_cmd,
+      "ip.access rtp-base <1-65000>",
+      IPA_STR
+      "Set the rtp-base port for the RTP stream\n"
+      "Port number\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->rtp_base = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_codec_list,
+      cfg_net_bsc_codec_list_cmd,
+      "codec-list .LIST",
+      "Set the allowed audio codecs\n"
+      "List of audio codecs, e.g. fr3 fr1 hr3\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	int saw_fr, saw_hr;
+	int i;
+
+	saw_fr = saw_hr = 0;
+
+	/* free the old list... if it exists */
+	if (data->audio_support) {
+		talloc_free(data->audio_support);
+		data->audio_support = NULL;
+		data->audio_length = 0;
+	}
+
+	/* create a new array */
+	data->audio_support =
+		talloc_zero_array(osmo_bsc_data(vty), struct gsm_audio_support *, argc);
+	data->audio_length = argc;
+
+	for (i = 0; i < argc; ++i) {
+		/* check for hrX or frX */
+		if (strlen(argv[i]) != 3
+				|| argv[i][1] != 'r'
+				|| (argv[i][0] != 'h' && argv[i][0] != 'f')
+				|| argv[i][2] < 0x30
+				|| argv[i][2] > 0x39)
+			goto error;
+
+		data->audio_support[i] = talloc_zero(data->audio_support,
+				struct gsm_audio_support);
+		data->audio_support[i]->ver = atoi(argv[i] + 2);
+
+		if (strncmp("hr", argv[i], 2) == 0) {
+			data->audio_support[i]->hr = 1;
+			saw_hr = 1;
+		} else if (strncmp("fr", argv[i], 2) == 0) {
+			data->audio_support[i]->hr = 0;
+			saw_fr = 1;
+		}
+
+		if (saw_hr && saw_fr) {
+			vty_out(vty, "Can not have full-rate and half-rate codec.%s",
+					VTY_NEWLINE);
+			return CMD_ERR_INCOMPLETE;
+		}
+	}
+
+	return CMD_SUCCESS;
+
+error:
+	vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s",
+			argv[i], VTY_NEWLINE);
+	return CMD_ERR_INCOMPLETE;
+}
+
+DEFUN(cfg_net_msc_dest,
+      cfg_net_msc_dest_cmd,
+      "dest A.B.C.D <1-65000> <0-255>",
+      "Add a destination to a MUX/MSC\n"
+      "IP Address\n" "Port\n" "DSCP\n")
+{
+	struct bsc_msc_dest *dest;
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	dest = talloc_zero(osmo_bsc_data(vty), struct bsc_msc_dest);
+	if (!dest) {
+		vty_out(vty, "%%Failed to create structure.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	dest->ip = talloc_strdup(dest, argv[0]);
+	if (!dest->ip) {
+		vty_out(vty, "%%Failed to copy dest ip.%s", VTY_NEWLINE);
+		talloc_free(dest);
+		return CMD_WARNING;
+	}
+
+	dest->port = atoi(argv[1]);
+	dest->dscp = atoi(argv[2]);
+	llist_add_tail(&dest->list, &data->dests);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_dest,
+      cfg_net_msc_no_dest_cmd,
+      "no dest A.B.C.D <1-65000> <0-255>",
+      NO_STR "Remove a destination to a MUX/MSC\n"
+      "IP Address\n" "Port\n" "DSCP\n")
+{
+	struct bsc_msc_dest *dest, *tmp;
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	int port = atoi(argv[1]);
+	int dscp = atoi(argv[2]);
+
+	llist_for_each_entry_safe(dest, tmp, &data->dests, list) {
+		if (port != dest->port || dscp != dest->dscp
+		    || strcmp(dest->ip, argv[0]) != 0)
+			continue;
+
+		llist_del(&dest->list);
+		talloc_free(dest);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_ping_time,
+      cfg_net_msc_no_ping_time_cmd,
+      "no timeout-ping",
+      NO_STR "Disable the ping/pong handling on A-link\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->ping_timeout = -1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_ping_time,
+      cfg_net_msc_ping_time_cmd,
+      "timeout-ping <1-2147483647>",
+      "Set the PING interval, negative for not sending PING\n"
+      "Timeout in seconds\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->ping_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_pong_time,
+      cfg_net_msc_pong_time_cmd,
+      "timeout-pong <1-2147483647>",
+      "Set the time to wait for a PONG\n" "Timeout in seconds\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->pong_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_advanced_ping,
+      cfg_net_msc_advanced_ping_cmd,
+      "timeout-ping advanced",
+      "Ping timeout handling\nEnable advanced mode during SCCP\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	if (data->ping_timeout == -1) {
+		vty_out(vty, "%%ping handling is disabled. Enable it first.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	data->advanced_ping = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_net_msc_advanced_ping,
+      cfg_no_net_msc_advanced_ping_cmd,
+      "no timeout-ping advanced",
+      NO_STR "Ping timeout handling\nEnable advanced mode during SCCP\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->advanced_ping = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_welcome_ussd,
+      cfg_net_msc_welcome_ussd_cmd,
+      "bsc-welcome-text .TEXT",
+      "Set the USSD notification to be sent\n" "Text to be sent\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	char *str = argv_concat(argv, argc, 0);
+	if (!str)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_welcome_txt, str);
+	talloc_free(str);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_welcome_ussd,
+      cfg_net_msc_no_welcome_ussd_cmd,
+      "no bsc-welcome-text",
+      NO_STR "Clear the USSD notification to be sent\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	talloc_free(data->ussd_welcome_txt);
+	data->ussd_welcome_txt = NULL;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_lost_ussd,
+      cfg_net_msc_lost_ussd_cmd,
+      "bsc-msc-lost-text .TEXT",
+      "Set the USSD notification to be sent on MSC connection loss\n" "Text to be sent\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	char *str = argv_concat(argv, argc, 0);
+	if (!str)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_msc_lost_txt, str);
+	talloc_free(str);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_lost_ussd,
+      cfg_net_msc_no_lost_ussd_cmd,
+      "no bsc-msc-lost-text",
+      NO_STR "Clear the USSD notification to be sent on MSC connection loss\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	talloc_free(data->ussd_msc_lost_txt);
+	data->ussd_msc_lost_txt = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_grace_ussd,
+      cfg_net_msc_grace_ussd_cmd,
+      "bsc-grace-text .TEXT",
+      "Set the USSD notification to be sent when the MSC has entered the grace period\n" "Text to be sent\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	char *str = argv_concat(argv, argc, 0);
+	if (!str)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_grace_txt, str);
+	talloc_free(str);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_grace_ussd,
+      cfg_net_msc_no_grace_ussd_cmd,
+      "no bsc-grace-text",
+      NO_STR "Clear the USSD notification to be sent when the MSC has entered the grace period\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	talloc_free(data->ussd_grace_txt);
+	data->ussd_grace_txt = NULL;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_missing_msc_ussd,
+      cfg_net_bsc_missing_msc_ussd_cmd,
+      "missing-msc-text .TEXT",
+      "Set the USSD notification to be send when a MSC has not been found.\n" "Text to be sent\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+	char *txt = argv_concat(argv, argc, 0);
+	if (!txt)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(data, &data->ussd_no_msc_txt, txt);
+	talloc_free(txt);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_no_missing_msc_text,
+      cfg_net_bsc_no_missing_msc_text_cmd,
+      "no missing-msc-text",
+      NO_STR "Clear the USSD notification to be send when a MSC has not been found.\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+
+	talloc_free(data->ussd_no_msc_txt);
+	data->ussd_no_msc_txt = 0;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_msc_type,
+      cfg_net_msc_type_cmd,
+      "type (normal|local)",
+      "Select the MSC type\n"
+      "Plain GSM MSC\n" "Special MSC for local call routing\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+
+	if (strcmp(argv[0], "normal") == 0)
+		data->type = MSC_CON_TYPE_NORMAL;
+	else if (strcmp(argv[0], "local") == 0)
+		data->type = MSC_CON_TYPE_LOCAL;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_emerg,
+      cfg_net_msc_emerg_cmd,
+      "allow-emergency (allow|deny)",
+      "Allow CM ServiceRequests with type emergency\n"
+      "Allow\n" "Deny\n")
+{
+	struct bsc_msc_data *data = bsc_msc_data(vty);
+	data->allow_emerg = strcmp("allow", argv[0]) == 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_local_prefix,
+      cfg_net_msc_local_prefix_cmd,
+      "local-prefix REGEXP",
+      "Prefix for local numbers\n" "REGEXP used\n")
+{
+	struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+	if (gsm_parse_reg(msc, &msc->local_pref_reg, &msc->local_pref, argc, argv) != 0) {
+		vty_out(vty, "%%Failed to parse the regexp: '%s'%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define AMR_CONF_STR "AMR Multirate Configuration\n"
+#define AMR_COMMAND(name) \
+	DEFUN(cfg_net_msc_amr_##name,					\
+	  cfg_net_msc_amr_##name##_cmd,					\
+	  "amr-config " #name "k (allowed|forbidden)",			\
+	  AMR_CONF_STR "Bitrate\n" "Allowed\n" "Forbidden\n")		\
+{									\
+	struct bsc_msc_data *msc = bsc_msc_data(vty);			\
+									\
+	msc->amr_conf.m##name = strcmp(argv[0], "allowed") == 0; 	\
+	return CMD_SUCCESS;						\
+}
+
+AMR_COMMAND(12_2)
+AMR_COMMAND(10_2)
+AMR_COMMAND(7_95)
+AMR_COMMAND(7_40)
+AMR_COMMAND(6_70)
+AMR_COMMAND(5_90)
+AMR_COMMAND(5_15)
+AMR_COMMAND(4_75)
+
+DEFUN(cfg_msc_acc_lst_name,
+      cfg_msc_acc_lst_name_cmd,
+      "access-list-name NAME",
+      "Set the name of the access list to use.\n"
+      "The name of the to be used access list.")
+{
+	struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+	osmo_talloc_replace_string(msc, &msc->acc_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_no_acc_lst_name,
+      cfg_msc_no_acc_lst_name_cmd,
+      "no access-list-name",
+      NO_STR "Remove the access list from the NAT.\n")
+{
+	struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+	if (msc->acc_lst_name) {
+		talloc_free(msc->acc_lst_name);
+		msc->acc_lst_name = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mid_call_text,
+      cfg_net_bsc_mid_call_text_cmd,
+      "mid-call-text .TEXT",
+      "Set the USSD notification to be send.\n" "Text to be sent\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+	char *txt = argv_concat(argv, argc, 0);
+	if (!txt)
+		return CMD_WARNING;
+
+	osmo_talloc_replace_string(data, &data->mid_call_txt, txt);
+	talloc_free(txt);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mid_call_timeout,
+      cfg_net_bsc_mid_call_timeout_cmd,
+      "mid-call-timeout NR",
+      "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+	data->mid_call_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rf_socket,
+      cfg_net_rf_socket_cmd,
+      "bsc-rf-socket PATH",
+      "Set the filename for the RF control interface.\n" "RF Control path\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+
+	osmo_talloc_replace_string(data, &data->rf_ctrl_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rf_off_time,
+      cfg_net_rf_off_time_cmd,
+      "bsc-auto-rf-off <1-65000>",
+      "Disable RF on MSC Connection\n" "Timeout\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+	data->auto_off_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_no_rf_off_time,
+      cfg_net_no_rf_off_time_cmd,
+      "no bsc-auto-rf-off",
+      NO_STR "Disable RF on MSC Connection\n")
+{
+	struct osmo_bsc_data *data = osmo_bsc_data(vty);
+	data->auto_off_timeout = -1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_acc_lst_name,
+      cfg_bsc_acc_lst_name_cmd,
+      "access-list-name NAME",
+      "Set the name of the access list to use.\n"
+      "The name of the to be used access list.")
+{
+	struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+	osmo_talloc_replace_string(bsc, &bsc->acc_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_acc_lst_name,
+      cfg_bsc_no_acc_lst_name_cmd,
+      "no access-list-name",
+      NO_STR "Remove the access list from the BSC\n")
+{
+	struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+	if (bsc->acc_lst_name) {
+		talloc_free(bsc->acc_lst_name);
+		bsc->acc_lst_name = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_statistics,
+      show_statistics_cmd,
+      "show statistics",
+      SHOW_STR "Statistics about the BSC\n")
+{
+	openbsc_vty_print_statistics(vty, bsc_gsmnet);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_mscs,
+      show_mscs_cmd,
+      "show mscs",
+      SHOW_STR "MSC Connections and State\n")
+{
+	struct bsc_msc_data *msc;
+	llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
+		vty_out(vty, "MSC Nr: %d is connected: %d auth: %d.%s",
+			msc->nr,
+			msc->msc_con ? msc->msc_con->is_connected : -1,
+			msc->msc_con ? msc->msc_con->is_authenticated : -1,
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_pos,
+      show_pos_cmd,
+      "show position",
+      SHOW_STR "Position information of the BTS\n")
+{
+	struct gsm_bts *bts;
+	struct bts_location *curloc;
+	struct tm time;
+	char timestr[50];
+
+	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+		if (llist_empty(&bts->loc_list)) {
+			vty_out(vty, "BTS Nr: %d position invalid%s", bts->nr,
+				VTY_NEWLINE);
+			continue;
+		}
+		curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+		if (gmtime_r(&curloc->tstamp, &time) == NULL) {
+			vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+				VTY_NEWLINE);
+			continue;
+		}
+		if (asctime_r(&time, timestr) == NULL) {
+			vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+				VTY_NEWLINE);
+			continue;
+		}
+		/* Last character in asctime is \n */
+		timestr[strlen(timestr)-1] = 0;
+
+		vty_out(vty, "BTS Nr: %d position: %s time: %s%s", bts->nr,
+			get_value_string(bts_loc_fix_names, curloc->valid), timestr,
+			VTY_NEWLINE);
+		vty_out(vty, " lat: %f lon: %f height: %f%s", curloc->lat, curloc->lon,
+			curloc->height, VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+DEFUN(gen_position_trap,
+      gen_position_trap_cmd,
+      "generate-location-state-trap <0-255>",
+      "Generate location state report\n"
+      "BTS to report\n")
+{
+	int bts_nr;
+	struct gsm_bts *bts;
+	struct gsm_network *net = bsc_gsmnet;
+
+	bts_nr = atoi(argv[0]);
+	if (bts_nr >= net->num_bts) {
+		vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(net, bts_nr);
+	bsc_gen_location_state_trap(bts);
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_imsi,
+      logging_fltr_imsi_cmd,
+      "logging filter imsi IMSI",
+	LOGGING_STR FILTER_STR
+      "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+	struct bsc_subscr *bsc_subscr;
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+	const char *imsi = argv[0];
+
+	bsc_subscr = bsc_subscr_find_by_imsi(bsc_gsmnet->bsc_subscribers, imsi);
+
+	if (!bsc_subscr) {
+		vty_out(vty, "%%no subscriber with IMSI(%s)%s",
+			imsi, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_filter_bsc_subscr(tgt, bsc_subscr);
+	return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+	install_element(CONFIG_NODE, &cfg_net_msc_cmd);
+	install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
+
+	install_node(&bsc_node, config_write_bsc);
+	install_element(BSC_NODE, &cfg_net_bsc_mid_call_text_cmd);
+	install_element(BSC_NODE, &cfg_net_bsc_mid_call_timeout_cmd);
+	install_element(BSC_NODE, &cfg_net_rf_socket_cmd);
+	install_element(BSC_NODE, &cfg_net_rf_off_time_cmd);
+	install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd);
+	install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd);
+	install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd);
+	install_element(BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
+	install_element(BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd);
+
+	install_node(&msc_node, config_write_msc);
+	install_element(MSC_NODE, &cfg_net_bsc_token_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_key_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_no_key_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_lac_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_ci_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_dest_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_advanced_ping_cmd);
+	install_element(MSC_NODE, &cfg_no_net_msc_advanced_ping_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_no_welcome_ussd_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_lost_ussd_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_no_lost_ussd_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_grace_ussd_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_no_grace_ussd_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_type_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_emerg_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_local_prefix_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_12_2_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_10_2_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_7_95_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_7_40_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_6_70_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_5_90_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd);
+	install_element(MSC_NODE, &cfg_msc_acc_lst_name_cmd);
+	install_element(MSC_NODE, &cfg_msc_no_acc_lst_name_cmd);
+
+	install_element_ve(&show_statistics_cmd);
+	install_element_ve(&show_mscs_cmd);
+	install_element_ve(&show_pos_cmd);
+	install_element_ve(&logging_fltr_imsi_cmd);
+
+	install_element(ENABLE_NODE, &gen_position_trap_cmd);
+
+	install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc_mgcp/Makefile.am b/openbsc/src/osmo-bsc_mgcp/Makefile.am
new file mode 100644
index 0000000..a19a4eb
--- /dev/null
+++ b/openbsc/src/osmo-bsc_mgcp/Makefile.am
@@ -0,0 +1,35 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+bin_PROGRAMS = \
+	osmo-bsc_mgcp \
+	$(NULL)
+
+osmo_bsc_mgcp_SOURCES = \
+	mgcp_main.c \
+	$(NULL)
+
+osmo_bsc_mgcp_LDADD = \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMONETIF_LIBS) \
+	$(LIBBCG729_LIBS) \
+	$(LIBRARY_GSM) \
+	-lrt \
+	$(NULL)
diff --git a/openbsc/src/osmo-bsc_mgcp/mgcp_main.c b/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
new file mode 100644
index 0000000..6cf9ab7
--- /dev/null
+++ b/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
@@ -0,0 +1,313 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The main method to drive it as a standalone process      */
+
+/*
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/stats.h>
+
+#include "../../bscconfig.h"
+
+#ifdef BUILD_MGCP_TRANSCODING
+#include "openbsc/mgcp_transcode.h"
+#endif
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#warning "Make use of the rtp proxy code"
+
+static struct mgcp_config *cfg;
+static struct mgcp_trunk_config *reset_trunk;
+static int reset_endpoints = 0;
+static int daemonize = 0;
+
+const char *openbsc_copyright =
+	"Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+	"Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static char *config_file = "mgcp.cfg";
+
+/* used by msgb and mgcp */
+void *tall_bsc_ctx = NULL;
+
+static void print_help()
+{
+	printf("Some useful help...\n");
+	printf(" -h --help is printing this text.\n");
+	printf(" -c --config-file filename The config file to use.\n");
+	printf(" -s --disable-color\n");
+	printf(" -D --daemonize Fork the process into a background daemon\n");
+	printf(" -V --version Print the version number\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"config-file", 1, 0, 'c'},
+			{"daemonize", 0, 0, 'D'},
+			{"version", 0, 0, 'V'},
+			{"disable-color", 0, 0, 's'},
+			{0, 0, 0, 0},
+		};
+
+		c = getopt_long(argc, argv, "hc:VD", long_options, &option_index);
+
+		if (c == -1)
+			break;
+
+		switch(c) {
+		case 'h':
+			print_help();
+			exit(0);
+			break;
+		case 'c':
+			config_file = talloc_strdup(tall_bsc_ctx, optarg);
+			break;
+		case 's':
+			log_set_use_color(osmo_stderr_target, 0);
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		default:
+			/* ignore */
+			break;
+		};
+	}
+}
+
+/* simply remember this */
+static int mgcp_rsip_cb(struct mgcp_trunk_config *tcfg)
+{
+	reset_endpoints = 1;
+	reset_trunk = tcfg;
+
+	return 0;
+}
+
+static int read_call_agent(struct osmo_fd *fd, unsigned int what)
+{
+	struct sockaddr_in addr;
+	socklen_t slen = sizeof(addr);
+	struct msgb *msg;
+	struct msgb *resp;
+	int i;
+
+	msg = (struct msgb *) fd->data;
+
+	/* read one less so we can use it as a \0 */
+	int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0,
+		(struct sockaddr *) &addr, &slen);
+	if (rc < 0) {
+		perror("Gateway failed to read");
+		return -1;
+	} else if (slen > sizeof(addr)) {
+		fprintf(stderr, "Gateway received message from outerspace: %zu %zu\n",
+			(size_t) slen, sizeof(addr));
+		return -1;
+	}
+
+	/* handle message now */
+	msg->l2h = msgb_put(msg, rc);
+	resp = mgcp_handle_message(cfg, msg);
+	msgb_reset(msg);
+
+	if (resp) {
+		sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr));
+		msgb_free(resp);
+	}
+
+	if (reset_endpoints) {
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "Asked to reset endpoints: %d/%d\n",
+		     reset_trunk->trunk_nr, reset_trunk->trunk_type);
+		reset_endpoints = 0;
+
+		/* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */
+		for (i = 1; i < reset_trunk->number_endpoints; ++i)
+			mgcp_release_endp(&reset_trunk->endpoints[i]);
+	}
+
+	return 0;
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OpenBSC MGCP",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct sockaddr_in addr;
+	int on = 1, rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent");
+	msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+	osmo_init_ignore_signals();
+	osmo_init_logging(&log_info);
+
+	cfg = mgcp_config_alloc();
+	if (!cfg)
+		return -1;
+
+#ifdef BUILD_MGCP_TRANSCODING
+	cfg->setup_rtp_processing_cb = &mgcp_transcoding_setup;
+	cfg->rtp_processing_cb = &mgcp_transcoding_process_rtp;
+	cfg->get_net_downlink_format_cb = &mgcp_transcoding_net_downlink_format;
+#endif
+
+	cfg->trunk.force_realloc = 1;
+
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	logging_vty_add_cmds(NULL);
+	osmo_stats_vty_add_cmds(&log_info);
+	mgcp_vty_init();
+
+	handle_options(argc, argv);
+
+	rate_ctr_init(tall_bsc_ctx);
+	osmo_stats_init(tall_bsc_ctx);
+
+	rc = mgcp_parse_config(config_file, cfg, MGCP_BSC);
+	if (rc < 0)
+		return rc;
+
+	/* start telnet after reading config for vty_get_bind_addr() */
+	rc = telnet_init_dynif(tall_bsc_ctx, &dummy_network,
+			       vty_get_bind_addr(), OSMO_VTY_PORT_BSC_MGCP);
+	if (rc < 0)
+		return rc;
+
+	/* set some callbacks */
+	cfg->reset_cb = mgcp_rsip_cb;
+
+        /* we need to bind a socket */
+        if (rc == 0) {
+		cfg->gw_fd.bfd.when = BSC_FD_READ;
+		cfg->gw_fd.bfd.cb = read_call_agent;
+		cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+		if (cfg->gw_fd.bfd.fd < 0) {
+			perror("Gateway failed to listen");
+			return -1;
+		}
+
+		setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+		memset(&addr, 0, sizeof(addr));
+		addr.sin_family = AF_INET;
+		addr.sin_port = htons(cfg->source_port);
+		inet_aton(cfg->source_addr, &addr.sin_addr);
+
+		if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+			perror("Gateway failed to bind");
+			return -1;
+		}
+
+		cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg");
+		if (!cfg->gw_fd.bfd.data) {
+			fprintf(stderr, "Gateway memory error.\n");
+			return -1;
+		}
+
+		if (cfg->call_agent_addr) {
+			addr.sin_port = htons(2727);
+			inet_aton(cfg->call_agent_addr, &addr.sin_addr);
+			if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+				LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n",
+				     cfg->call_agent_addr, errno);
+				close(cfg->gw_fd.bfd.fd);
+				cfg->gw_fd.bfd.fd = -1;
+				return -1;
+			}
+		}
+
+		if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) {
+			LOGP(DMGCP, LOGL_FATAL, "Failed to register the fd\n");
+			return -1;
+		}
+
+		LOGP(DMGCP, LOGL_NOTICE, "Configured for MGCP.\n");
+	}
+
+	/* initialisation */
+	srand(time(NULL));
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	/* main loop */
+	while (1) {
+		osmo_select_main(0);
+	}
+
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc_nat/Makefile.am b/openbsc/src/osmo-bsc_nat/Makefile.am
new file mode 100644
index 0000000..be33d28
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/Makefile.am
@@ -0,0 +1,58 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOCTRL_CFLAGS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(LIBCRYPTO_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+bin_PROGRAMS = \
+	osmo-bsc_nat \
+	$(NULL)
+
+osmo_bsc_nat_SOURCES = \
+	bsc_filter.c \
+	bsc_mgcp_utils.c \
+	bsc_nat.c \
+	bsc_nat_utils.c \
+	bsc_nat_vty.c \
+	bsc_sccp.c \
+	bsc_ussd.c \
+	bsc_nat_ctrl.c \
+	bsc_nat_rewrite.c \
+	bsc_nat_rewrite_trie.c \
+	bsc_nat_filter.c \
+	$(NULL)
+
+osmo_bsc_nat_LDADD = \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libfilter/libfilter.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOSCCP_LIBS) \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMONETIF_LIBS) \
+	$(LIBCRYPTO_LIBS) \
+	-lrt \
+	$(NULL)
diff --git a/openbsc/src/osmo-bsc_nat/bsc_filter.c b/openbsc/src/osmo-bsc_nat/bsc_filter.c
new file mode 100644
index 0000000..432529e
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_filter.c
@@ -0,0 +1,220 @@
+/* BSC Multiplexer/NAT */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/*
+ * The idea is to have a simple struct describing a IPA packet with
+ * SCCP SSN and the GSM 08.08 payload and decide. We will both have
+ * a white and a blacklist of packets we want to handle.
+ *
+ * TODO: Implement a "NOT" in the filter language.
+ */
+
+#define ALLOW_ANY -1
+
+#define FILTER_TO_BSC	1
+#define FILTER_TO_MSC	2
+#define FILTER_TO_BOTH	3
+
+
+struct bsc_pkt_filter {
+	int ipa_proto;
+	int dest_ssn;
+	int bssap;
+	int gsm;
+	int filter_dir;
+};
+
+static struct bsc_pkt_filter black_list[] = {
+	/* filter reset messages to the MSC */
+	{ IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET, FILTER_TO_MSC },
+
+	/* filter reset ack messages to the BSC */
+	{ IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET_ACKNOWLEDGE, FILTER_TO_BSC },
+
+	/* filter ip access */
+	{ IPAC_PROTO_IPACCESS, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_MSC },
+};
+
+static struct bsc_pkt_filter white_list[] = {
+	/* allow IPAC_PROTO_SCCP messages to both sides */
+	{ IPAC_PROTO_SCCP, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH },
+
+	/* allow MGCP messages to both sides */
+	{ IPAC_PROTO_MGCP_OLD, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH },
+};
+
+struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg)
+{
+	struct sccp_parse_result result;
+	struct bsc_nat_parsed *parsed;
+	struct ipaccess_head *hh;
+
+	/* quick fail */
+	if (msg->len < 4)
+		return NULL;
+
+	parsed = talloc_zero(msg, struct bsc_nat_parsed);
+	if (!parsed)
+		return NULL;
+
+	/* more init */
+	parsed->ipa_proto = parsed->called_ssn = parsed->calling_ssn = -1;
+	parsed->sccp_type = parsed->bssap = parsed->gsm_type = -1;
+
+	/* start parsing */
+	hh = (struct ipaccess_head *) msg->data;
+	parsed->ipa_proto = hh->proto;
+
+	msg->l2h = &hh->data[0];
+
+	/* do a size check on the input */
+	if (ntohs(hh->len) != msgb_l2len(msg)) {
+		LOGP(DLINP, LOGL_ERROR, "Wrong input length?\n");
+		talloc_free(parsed);
+		return NULL;
+	}
+
+	/* analyze sccp down here */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+		memset(&result, 0, sizeof(result));
+		if (sccp_parse_header(msg, &result) != 0) {
+			talloc_free(parsed);
+			return 0;
+		}
+
+		if (msg->l3h && msgb_l3len(msg) < 3) {
+			LOGP(DNAT, LOGL_ERROR, "Not enough space or GSM payload\n");
+			talloc_free(parsed);
+			return 0;
+		}
+
+		parsed->sccp_type = sccp_determine_msg_type(msg);
+		parsed->src_local_ref = result.source_local_reference;
+		parsed->dest_local_ref = result.destination_local_reference;
+		if (parsed->dest_local_ref)
+			parsed->original_dest_ref = *parsed->dest_local_ref;
+		parsed->called_ssn = result.called.ssn;
+		parsed->calling_ssn = result.calling.ssn;
+
+		/* in case of connection confirm we have no payload */
+		if (msg->l3h) {
+			parsed->bssap = msg->l3h[0];
+			parsed->gsm_type = msg->l3h[2];
+		}
+	}
+
+	return parsed;
+}
+
+/* Returns 0 if message is whitelisted (has to beforwarded by bsc-nat), 1 if
+/* it's blacklisted (not to be forwarded) */
+int bsc_nat_filter_ipa(int dir, struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+	int i;
+
+	/* go through the blacklist now */
+	for (i = 0; i < ARRAY_SIZE(black_list); ++i) {
+		/* ignore the rule? */
+		if (black_list[i].filter_dir != FILTER_TO_BOTH
+		    && black_list[i].filter_dir != dir)
+			continue;
+
+		/* the proto is not blacklisted */
+		if (black_list[i].ipa_proto != ALLOW_ANY
+		    && black_list[i].ipa_proto != parsed->ipa_proto)
+			continue;
+
+		if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+			/* the SSN is not blacklisted */
+			if (black_list[i].dest_ssn != ALLOW_ANY
+			    && black_list[i].dest_ssn != parsed->called_ssn)
+				continue;
+
+			/* bssap */
+			if (black_list[i].bssap != ALLOW_ANY
+			    && black_list[i].bssap != parsed->bssap)
+				continue;
+
+			/* gsm */
+			if (black_list[i].gsm != ALLOW_ANY
+			    && black_list[i].gsm != parsed->gsm_type)
+				continue;
+
+			/* blacklisted */
+			LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i);
+			return 1;
+		} else {
+			/* blacklisted, we have no content sniffing yet */
+			LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i);
+			return 1;
+		}
+	}
+
+	/* go through the whitelist now */
+	for (i = 0; i < ARRAY_SIZE(white_list); ++i) {
+		/* ignore the rule? */
+		if (white_list[i].filter_dir != FILTER_TO_BOTH
+		    && white_list[i].filter_dir != dir)
+			continue;
+
+		/* the proto is not whitelisted */
+		if (white_list[i].ipa_proto != ALLOW_ANY
+		    && white_list[i].ipa_proto != parsed->ipa_proto)
+			continue;
+
+		if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+			/* the SSN is not whitelisted */
+			if (white_list[i].dest_ssn != ALLOW_ANY
+			    && white_list[i].dest_ssn != parsed->called_ssn)
+				continue;
+
+			/* bssap */
+			if (white_list[i].bssap != ALLOW_ANY
+			    && white_list[i].bssap != parsed->bssap)
+				continue;
+
+			/* gsm */
+			if (white_list[i].gsm != ALLOW_ANY
+			    && white_list[i].gsm != parsed->gsm_type)
+				continue;
+
+			/* whitelisted */
+			LOGP(DNAT, LOGL_DEBUG, "Whitelisted with rule %d\n", i);
+			return 0;
+		} else {
+			/* whitelisted */
+			return 0;
+		}
+	}
+
+	return 1;
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_mgcp_utils.c b/openbsc/src/osmo-bsc_nat/bsc_mgcp_utils.c
new file mode 100644
index 0000000..311ab94
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_mgcp_utils.c
@@ -0,0 +1,1161 @@
+/**
+ * This file contains helper routines for MGCP Gateway handling.
+ *
+ * The first thing to remember is that each BSC has its own namespace/range
+ * of endpoints. Whenever a BSSMAP ASSIGNMENT REQUEST is received this code
+ * will be called to select an endpoint on the BSC. The mapping from original
+ * multiplex/timeslot to BSC multiplex'/timeslot' will be stored.
+ *
+ * The second part is to take messages on the public MGCP GW interface
+ * and forward them to the right BSC. This requires the MSC to first
+ * assign the timeslot. This assumption has been true so far. We are using
+ * the policy_cb of the MGCP protocol code to decide if the request should
+ * be immediately answered or delayed. An extension "Z: noanswer" is used
+ * to request the BSC to not respond. This is saving some bytes of bandwidth
+ * and as we are using TCP to forward the message we know it will arrive.
+ * The mgcp_do_read method reads these messages and hands them to the protocol
+ * parsing code which will call the mentioned policy_cb. The bsc_mgcp_forward
+ * method is used on the way back from the BSC to the network.
+ *
+ * The third part is to patch messages forwarded to the BSC. This includes
+ * the endpoint number, the ports to be used inside the SDP file and maybe
+ * some other bits.
+ *
+ */
+/*
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_callstats.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/osmux.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+static void send_direct(struct bsc_nat *nat, struct msgb *output)
+{
+	if (osmo_wqueue_enqueue(&nat->mgcp_cfg->gw_fd, output) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to queue MGCP msg.\n");
+		msgb_free(output);
+	}
+}
+
+static void mgcp_queue_for_call_agent(struct bsc_nat *nat, struct msgb *output)
+{
+	if (nat->mgcp_ipa)
+		bsc_nat_send_mgcp_to_msc(nat, output);
+	else
+		send_direct(nat, output);
+}
+
+int bsc_mgcp_nr_multiplexes(int max_endpoints)
+{
+	int div = max_endpoints / 32;
+
+	if ((max_endpoints % 32) != 0)
+		div += 1;
+
+	return div;
+}
+
+static int bsc_init_endps_if_needed(struct bsc_connection *con)
+{
+	int multiplexes;
+
+	/* we have done that */
+	if (con->_endpoint_status)
+		return 0;
+
+	/* we have no config... */
+	if (!con->cfg)
+		return -1;
+
+	multiplexes = bsc_mgcp_nr_multiplexes(con->cfg->max_endpoints);
+	con->number_multiplexes = multiplexes;
+	con->max_endpoints = con->cfg->max_endpoints;
+	con->_endpoint_status = talloc_zero_array(con, char, 32 * multiplexes + 1);
+	return con->_endpoint_status == NULL;
+}
+
+static int bsc_assign_endpoint(struct bsc_connection *bsc, struct nat_sccp_connection *con)
+{
+	int multiplex;
+	int timeslot;
+	const int number_endpoints = bsc->max_endpoints;
+	int i;
+
+	mgcp_endpoint_to_timeslot(bsc->last_endpoint, &multiplex, &timeslot);
+	timeslot += 1;
+
+	for (i = 0; i < number_endpoints; ++i) {
+		int endpoint;
+
+		/* Wrap around timeslots */
+		if (timeslot == 0)
+			timeslot = 1;
+
+		if (timeslot == 0x1f) {
+			timeslot = 1;
+			multiplex += 1;
+		}
+
+		/* Wrap around the multiplex */
+		if (multiplex >= bsc->number_multiplexes)
+			multiplex = 0;
+
+		endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+		/* Now check if we are allowed to assign this one */
+		if (endpoint >= bsc->max_endpoints) {
+			multiplex = 0;
+			timeslot = 1;
+			endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+		}
+
+
+		if (bsc->_endpoint_status[endpoint] == 0) {
+			bsc->_endpoint_status[endpoint] = 1;
+			con->bsc_endp = endpoint;
+			bsc->last_endpoint = endpoint;
+			return 0;
+		}
+
+		timeslot += 1;
+	}
+
+	return -1;
+}
+
+static uint16_t create_cic(int endpoint)
+{
+	int timeslot, multiplex;
+
+	mgcp_endpoint_to_timeslot(endpoint, &multiplex, &timeslot);
+	return (multiplex << 5) | (timeslot & 0x1f);
+}
+
+int bsc_mgcp_assign_patch(struct nat_sccp_connection *con, struct msgb *msg)
+{
+	struct nat_sccp_connection *mcon;
+	struct tlv_parsed tp;
+	uint16_t cic;
+	uint8_t timeslot;
+	uint8_t multiplex;
+	unsigned int endp;
+
+	if (!msg->l3h) {
+		LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n");
+		return -1;
+	}
+
+	if (msgb_l3len(msg) < 3) {
+		LOGP(DNAT, LOGL_ERROR, "Assignment message has not enough space for GSM0808.\n");
+		return -1;
+	}
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+		LOGP(DNAT, LOGL_ERROR, "Circuit identity code not found in assignment message.\n");
+		return -1;
+	}
+
+	cic = ntohs(tlvp_val16_unal(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+	timeslot = cic & 0x1f;
+	multiplex = (cic & ~0x1f) >> 5;
+
+
+	endp = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+	if (endp >= con->bsc->nat->mgcp_cfg->trunk.number_endpoints) {
+		LOGP(DNAT, LOGL_ERROR,
+			"MSC attempted to assign bad endpoint 0x%x\n",
+			endp);
+		return -1;
+	}
+
+	/* find stale connections using that endpoint */
+	llist_for_each_entry(mcon, &con->bsc->nat->sccp_connections, list_entry) {
+		if (mcon->msc_endp == endp) {
+			LOGP(DNAT, LOGL_ERROR,
+			     "Endpoint 0x%x was assigned to 0x%x and now 0x%x\n",
+			     endp,
+			     sccp_src_ref_to_int(&mcon->patched_ref),
+			     sccp_src_ref_to_int(&con->patched_ref));
+			bsc_mgcp_dlcx(mcon);
+		}
+	}
+
+	con->msc_endp = endp;
+	if (bsc_init_endps_if_needed(con->bsc) != 0)
+		return -1;
+	if (bsc_assign_endpoint(con->bsc, con) != 0)
+		return -1;
+
+	/*
+	 * now patch the message for the new CIC...
+	 * still assumed to be one multiplex only
+	 */
+	cic = htons(create_cic(con->bsc_endp));
+	memcpy((uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE),
+		&cic, sizeof(cic));
+
+	return 0;
+}
+
+static void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i)
+{
+	if (nat->bsc_endpoints[i].transaction_id) {
+		talloc_free(nat->bsc_endpoints[i].transaction_id);
+		nat->bsc_endpoints[i].transaction_id = NULL;
+	}
+
+	nat->bsc_endpoints[i].transaction_state = 0;
+	nat->bsc_endpoints[i].bsc = NULL;
+}
+
+void bsc_mgcp_free_endpoints(struct bsc_nat *nat)
+{
+	int i;
+
+	for (i = 1; i < nat->mgcp_cfg->trunk.number_endpoints; ++i){
+		bsc_mgcp_free_endpoint(nat, i);
+		mgcp_release_endp(&nat->mgcp_cfg->trunk.endpoints[i]);
+	}
+}
+
+/* send a MDCX where we do not want a response */
+static void bsc_mgcp_send_mdcx(struct bsc_connection *bsc, int port, struct mgcp_endpoint *endp)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+		       "MDCX 23 %x@mgw MGCP 1.0\r\n"
+		       "Z: noanswer\r\n"
+		       "\r\n"
+		       "c=IN IP4 %s\r\n"
+		       "m=audio %d RTP/AVP 255\r\n",
+		       port, mgcp_bts_src_addr(endp),
+		       endp->bts_end.local_port);
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "snprintf for MDCX failed.\n");
+		return;
+	}
+
+	bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+static void bsc_mgcp_send_dlcx(struct bsc_connection *bsc, int endpoint, int trans)
+{
+	char buf[2096];
+	int len;
+
+	/*
+	 * The following is a bit of a spec violation. According to the
+	 * MGCP grammar the transaction id is are upto 9 digits but we
+	 * prefix it with an alpha numeric value so we can easily recognize
+	 * it as a response.
+	 */
+	len = snprintf(buf, sizeof(buf),
+		       "DLCX nat-%u %x@mgw MGCP 1.0\r\n",
+			trans, endpoint);
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+		return;
+	}
+
+	bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+void bsc_mgcp_init(struct nat_sccp_connection *con)
+{
+	con->msc_endp = -1;
+	con->bsc_endp = -1;
+}
+
+/**
+ * This code will remember the network side of the audio statistics and
+ * once the internal DLCX response arrives this can be combined with the
+ * the BSC side and forwarded as a trap.
+ */
+static void remember_pending_dlcx(struct nat_sccp_connection *con, uint32_t transaction)
+{
+	struct bsc_nat_call_stats *stats;
+	struct bsc_connection *bsc = con->bsc;
+	struct mgcp_endpoint *endp;
+
+	stats = talloc_zero(bsc, struct bsc_nat_call_stats);
+	if (!stats) {
+		LOGP(DNAT, LOGL_NOTICE,
+			"Failed to allocate statistics for endpoint 0x%x\n",
+			con->msc_endp);
+		return;
+	}
+
+	/* take the endpoint here */
+	endp = &bsc->nat->mgcp_cfg->trunk.endpoints[con->msc_endp];
+
+	stats->remote_ref = con->remote_ref;
+	stats->src_ref = con->patched_ref;
+
+	stats->ci = endp->ci;
+	stats->bts_rtp_port = endp->bts_end.rtp_port;
+	stats->bts_addr = endp->bts_end.addr;
+	stats->net_rtp_port = endp->net_end.rtp_port;
+	stats->net_addr = endp->net_end.addr;
+
+	stats->net_ps = endp->net_end.packets;
+	stats->net_os = endp->net_end.octets;
+	stats->bts_pr = endp->bts_end.packets;
+	stats->bts_or = endp->bts_end.octets;
+	mgcp_state_calc_loss(&endp->bts_state, &endp->bts_end,
+				&stats->bts_expected, &stats->bts_loss);
+	stats->bts_jitter = mgcp_state_calc_jitter(&endp->bts_state);
+
+	stats->trans_id = transaction;
+	stats->msc_endpoint = con->msc_endp;
+
+	/*
+	 * Too many pending requests.. let's remove the first two items.
+	 */
+	if (!llist_empty(&bsc->pending_dlcx) &&
+			bsc->pending_dlcx_count >= bsc->cfg->max_endpoints * 3) {
+		struct bsc_nat_call_stats *tmp;
+		LOGP(DNAT, LOGL_ERROR,
+			"Too many(%d) pending DLCX responses on BSC: %d\n",
+			bsc->pending_dlcx_count, bsc->cfg->nr);
+		bsc->pending_dlcx_count -= 1;
+		tmp = (struct bsc_nat_call_stats *) bsc->pending_dlcx.next;
+		llist_del(&tmp->entry);
+		talloc_free(tmp);
+	}
+
+	bsc->pending_dlcx_count += 1;
+	llist_add_tail(&stats->entry, &bsc->pending_dlcx);
+}
+
+void bsc_mgcp_dlcx(struct nat_sccp_connection *con)
+{
+	/* send a DLCX down the stream */
+	if (con->bsc_endp != -1 && con->bsc->_endpoint_status) {
+		LOGP(DNAT, LOGL_NOTICE,
+			"Endpoint 0x%x was allocated for bsc: %d. Freeing it.\n",
+			con->bsc_endp, con->bsc->cfg->nr);
+		if (con->bsc->_endpoint_status[con->bsc_endp] != 1)
+			LOGP(DNAT, LOGL_ERROR, "Endpoint 0x%x was not in use\n", con->bsc_endp);
+		remember_pending_dlcx(con, con->bsc->next_transaction);
+		con->bsc->_endpoint_status[con->bsc_endp] = 0;
+		bsc_mgcp_send_dlcx(con->bsc, con->bsc_endp, con->bsc->next_transaction++);
+		bsc_mgcp_free_endpoint(con->bsc->nat, con->msc_endp);
+	}
+
+	bsc_mgcp_init(con);
+
+}
+
+/*
+ * Search for the pending request
+ */
+static void handle_dlcx_response(struct bsc_connection *bsc, struct msgb *msg,
+			int code, const char *transaction)
+{
+	uint32_t trans_id = UINT32_MAX;
+	uint32_t b_ps, b_os, n_pr, n_or, jitter;
+	int loss;
+	struct bsc_nat_call_stats *tmp, *stat = NULL;
+	struct ctrl_cmd *cmd;
+
+	/* parse the transaction identifier */
+	int rc = sscanf(transaction, "nat-%u", &trans_id);
+	if (rc != 1) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse transaction id: '%s'\n",
+			transaction);
+		return;
+	}
+
+	/* find the answer for the request we made */
+	llist_for_each_entry(tmp, &bsc->pending_dlcx, entry) {
+		if (trans_id != tmp->trans_id)
+			continue;
+
+		stat = tmp;
+		break;
+	}
+
+	if (!stat) {
+		LOGP(DNAT, LOGL_ERROR,
+			"Can not find transaction for: %u\n", trans_id);
+		return;
+	}
+
+	/* attempt to parse the data now */
+	rc = mgcp_parse_stats(msg, &b_ps, &b_os, &n_pr, &n_or, &loss, &jitter);
+	if (rc != 0)
+		LOGP(DNAT, LOGL_ERROR,
+			"Can not parse connection statistics: %d\n", rc);
+
+	/* send a trap now */
+	cmd = ctrl_cmd_create(bsc, CTRL_TYPE_TRAP);
+	if (!cmd) {
+		LOGP(DNAT, LOGL_ERROR,
+			"Creating a ctrl cmd failed.\n");
+		goto free_stat;
+	}
+
+	cmd->id = "0";
+	cmd->variable = talloc_asprintf(cmd, "net.0.bsc.%d.call_stats.v2",
+				bsc->cfg->nr);
+	cmd->reply = talloc_asprintf(cmd,
+			"mg_ip_addr=%s,mg_port=%d,",
+			inet_ntoa(stat->net_addr),
+			stat->net_rtp_port);
+	cmd->reply = talloc_asprintf_append(cmd->reply,
+			"endpoint_ip_addr=%s,endpoint_port=%d,",
+			inet_ntoa(stat->bts_addr),
+			stat->bts_rtp_port);
+	cmd->reply = talloc_asprintf_append(cmd->reply,
+			"nat_pkt_in=%u,nat_pkt_out=%u,"
+			"nat_bytes_in=%u,nat_bytes_out=%u,"
+			"nat_jitter=%u,nat_pkt_lost=%d,",
+			stat->bts_pr, stat->net_ps,
+			stat->bts_or, stat->net_os,
+			stat->bts_jitter, stat->bts_loss);
+	cmd->reply = talloc_asprintf_append(cmd->reply,
+			"bsc_pkt_in=%u,bsc_pkt_out=%u,"
+			"bsc_bytes_in=%u,bsc_bytes_out=%u,"
+			"bsc_jitter=%u,bsc_pkt_lost=%d,",
+			n_pr, b_ps,
+			n_or, b_os,
+			jitter, loss);
+	cmd->reply = talloc_asprintf_append(cmd->reply,
+			"sccp_src_ref=%u,sccp_dst_ref=%u",
+			sccp_src_ref_to_int(&stat->src_ref),
+			sccp_src_ref_to_int(&stat->remote_ref));
+
+	/* send it and be done */
+	ctrl_cmd_send_to_all(bsc->nat->ctrl, cmd);
+	talloc_free(cmd);
+
+free_stat:
+	bsc->pending_dlcx_count -= 1;
+	llist_del(&stat->entry);
+	talloc_free(stat);
+}
+
+
+struct nat_sccp_connection *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint)
+{
+	struct nat_sccp_connection *con = NULL;
+	struct nat_sccp_connection *sccp;
+
+	llist_for_each_entry(sccp, &nat->sccp_connections, list_entry) {
+		if (sccp->msc_endp == -1)
+			continue;
+		if (sccp->msc_endp != endpoint)
+			continue;
+
+		con = sccp;
+	}
+
+	if (con)
+		return con;
+
+	LOGP(DMGCP, LOGL_ERROR,
+		"Failed to find the connection for endpoint: 0x%x\n", endpoint);
+	return NULL;
+}
+
+static int nat_osmux_only(struct mgcp_config *mgcp_cfg, struct bsc_config *bsc_cfg)
+{
+	if (mgcp_cfg->osmux == OSMUX_USAGE_ONLY)
+		return 1;
+	if (bsc_cfg->osmux == OSMUX_USAGE_ONLY)
+		return 1;
+	return 0;
+}
+
+static int bsc_mgcp_policy_cb(struct mgcp_trunk_config *tcfg, int endpoint, int state, const char *transaction_id)
+{
+	struct bsc_nat *nat;
+	struct bsc_endpoint *bsc_endp;
+	struct nat_sccp_connection *sccp;
+	struct mgcp_endpoint *mgcp_endp;
+	struct msgb *bsc_msg;
+
+	nat = tcfg->cfg->data;
+	bsc_endp = &nat->bsc_endpoints[endpoint];
+	mgcp_endp = &nat->mgcp_cfg->trunk.endpoints[endpoint];
+
+	if (bsc_endp->transaction_id) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x had pending transaction: '%s' state %d\n",
+		     endpoint, bsc_endp->transaction_id, bsc_endp->transaction_state);
+		talloc_free(bsc_endp->transaction_id);
+		bsc_endp->transaction_id = NULL;
+		bsc_endp->transaction_state = 0;
+	}
+	bsc_endp->bsc = NULL;
+
+	sccp = bsc_mgcp_find_con(nat, endpoint);
+
+	if (!sccp) {
+		LOGP(DMGCP, LOGL_ERROR, "Did not find BSC for change on endpoint: 0x%x state: %d\n", endpoint, state);
+
+		switch (state) {
+		case MGCP_ENDP_CRCX:
+			return MGCP_POLICY_REJECT;
+			break;
+		case MGCP_ENDP_DLCX:
+			return MGCP_POLICY_CONT;
+			break;
+		case MGCP_ENDP_MDCX:
+			return MGCP_POLICY_CONT;
+			break;
+		default:
+			LOGP(DMGCP, LOGL_FATAL, "Unhandled state: %d\n", state);
+			return MGCP_POLICY_CONT;
+			break;
+		}
+	}
+
+	/* Allocate a Osmux circuit ID */
+	if (state == MGCP_ENDP_CRCX) {
+		if (nat->mgcp_cfg->osmux && sccp->bsc->cfg->osmux) {
+			osmux_allocate_cid(mgcp_endp);
+			if (mgcp_endp->osmux.allocated_cid < 0 &&
+				nat_osmux_only(nat->mgcp_cfg, sccp->bsc->cfg)) {
+				LOGP(DMGCP, LOGL_ERROR,
+					"Rejecting usage of endpoint\n");
+				return MGCP_POLICY_REJECT;
+			}
+		}
+	}
+
+	/* we need to generate a new and patched message */
+	bsc_msg = bsc_mgcp_rewrite((char *) nat->mgcp_msg, nat->mgcp_length,
+				   sccp->bsc_endp, mgcp_bts_src_addr(mgcp_endp),
+				   mgcp_endp->bts_end.local_port,
+				   mgcp_endp->osmux.allocated_cid,
+				   &mgcp_endp->net_end.codec.payload_type,
+				   nat->sdp_ensure_amr_mode_set);
+	if (!bsc_msg) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to patch the msg.\n");
+		return MGCP_POLICY_CONT;
+	}
+
+
+	bsc_endp->transaction_id = talloc_strdup(nat, transaction_id);
+	bsc_endp->transaction_state = state;
+	bsc_endp->bsc = sccp->bsc;
+
+	/* we need to update some bits */
+	if (state == MGCP_ENDP_CRCX) {
+		struct sockaddr_in sock;
+
+		/* set up jitter buffer parameters */
+		if (bsc_endp->bsc->cfg->bts_use_jibuf_override)
+			mgcp_endp->bts_use_jibuf = bsc_endp->bsc->cfg->bts_use_jibuf;
+
+		if (bsc_endp->bsc->cfg->bts_jitter_delay_min_override)
+			mgcp_endp->bts_jitter_delay_min = bsc_endp->bsc->cfg->bts_jitter_delay_min;
+
+		if (bsc_endp->bsc->cfg->bts_jitter_delay_max_override)
+			mgcp_endp->bts_jitter_delay_max = bsc_endp->bsc->cfg->bts_jitter_delay_max;
+
+		/* Annotate the allocated Osmux CID until the bsc confirms that
+		 * it agrees to use Osmux for this voice flow.
+		 */
+		if (mgcp_endp->osmux.allocated_cid >= 0 &&
+		    mgcp_endp->osmux.state != OSMUX_STATE_ENABLED) {
+			mgcp_endp->osmux.state = OSMUX_STATE_NEGOTIATING;
+			mgcp_endp->osmux.cid = mgcp_endp->osmux.allocated_cid;
+		}
+
+		socklen_t len = sizeof(sock);
+		if (getpeername(sccp->bsc->write_queue.bfd.fd, (struct sockaddr *) &sock, &len) != 0) {
+			LOGP(DMGCP, LOGL_ERROR, "Can not get the peername...%d/%s\n",
+			      errno, strerror(errno));
+		} else {
+			mgcp_endp->bts_end.addr = sock.sin_addr;
+		}
+
+		/* send the message and a fake MDCX to force sending of a dummy packet */
+		bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+		bsc_mgcp_send_mdcx(sccp->bsc, sccp->bsc_endp, mgcp_endp);
+		return MGCP_POLICY_DEFER;
+	} else if (state == MGCP_ENDP_DLCX) {
+		/* we will free the endpoint now and send a DLCX to the BSC */
+		msgb_free(bsc_msg);
+		bsc_mgcp_dlcx(sccp);
+
+		/* libmgcp clears the MGCP endpoint for us */
+		if (mgcp_endp->osmux.state == OSMUX_STATE_ENABLED)
+			osmux_release_cid(mgcp_endp);
+
+		return MGCP_POLICY_CONT;
+	} else {
+		bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+		return MGCP_POLICY_DEFER;
+	}
+}
+
+/*
+ * We do have a failure, free data downstream..
+ */
+static void free_chan_downstream(struct mgcp_endpoint *endp, struct bsc_endpoint *bsc_endp,
+				 struct bsc_connection *bsc)
+{
+	LOGP(DMGCP, LOGL_ERROR, "No CI, freeing endpoint 0x%x in state %d\n",
+		ENDPOINT_NUMBER(endp), bsc_endp->transaction_state);
+
+	/* if a CRCX failed... send a DLCX down the stream */
+	if (bsc_endp->transaction_state == MGCP_ENDP_CRCX) {
+		struct nat_sccp_connection *con;
+		con = bsc_mgcp_find_con(bsc->nat, ENDPOINT_NUMBER(endp));
+		if (!con) {
+			LOGP(DMGCP, LOGL_ERROR,
+				"No SCCP connection for endp 0x%x\n",
+				ENDPOINT_NUMBER(endp));
+		} else {
+			if (con->bsc == bsc) {
+				bsc_mgcp_send_dlcx(bsc, con->bsc_endp, con->bsc->next_transaction++);
+			} else {
+				LOGP(DMGCP, LOGL_ERROR,
+					"Endpoint belongs to a different BSC\n");
+			}
+		}
+	}
+
+	bsc_mgcp_free_endpoint(bsc->nat, ENDPOINT_NUMBER(endp));
+	mgcp_release_endp(endp);
+}
+
+static void bsc_mgcp_osmux_confirm(struct mgcp_endpoint *endp, const char *str)
+{
+	unsigned int osmux_cid;
+	char *res;
+
+	res = strstr(str, "X-Osmux: ");
+	if (!res) {
+		LOGP(DMGCP, LOGL_INFO,
+		     "BSC doesn't want to use Osmux, failing back to RTP\n");
+		goto err;
+	}
+
+	if (sscanf(res, "X-Osmux: %u", &osmux_cid) != 1) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to parse Osmux CID '%s'\n",
+		     str);
+		goto err;
+	}
+
+	if (endp->osmux.cid != osmux_cid) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "BSC sent us wrong CID %u, we expected %u",
+		     osmux_cid, endp->osmux.cid);
+		goto err;
+	}
+
+	LOGP(DMGCP, LOGL_NOTICE, "bsc accepted to use Osmux (cid=%u)\n",
+	     osmux_cid);
+	endp->osmux.state = OSMUX_STATE_ACTIVATING;
+	return;
+err:
+	osmux_release_cid(endp);
+	endp->osmux.state = OSMUX_STATE_DISABLED;
+}
+
+/*
+ * We have received a msg from the BSC. We will see if we know
+ * this transaction and if it belongs to the BSC. Then we will
+ * need to patch the content to point to the local network and we
+ * need to update the I: that was assigned by the BSS.
+ *
+ * Only responses to CRCX and DLCX should arrive here. The DLCX
+ * needs to be handled specially to combine the two statistics.
+ */
+void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg)
+{
+	struct msgb *output;
+	struct bsc_endpoint *bsc_endp = NULL;
+	struct mgcp_endpoint *endp = NULL;
+	int i, code;
+	char transaction_id[60];
+
+	/* Some assumption that our buffer is big enough.. and null terminate */
+	if (msgb_l2len(msg) > 2000) {
+		LOGP(DMGCP, LOGL_ERROR, "MGCP message too long.\n");
+		return;
+	}
+
+	msg->l2h[msgb_l2len(msg)] = '\0';
+
+	if (bsc_mgcp_parse_response((const char *) msg->l2h, &code, transaction_id) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to parse response code.\n");
+		return;
+	}
+
+	for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+		if (bsc->nat->bsc_endpoints[i].bsc != bsc)
+			continue;
+		/* no one listening? a bug? */
+		if (!bsc->nat->bsc_endpoints[i].transaction_id)
+			continue;
+		if (strcmp(transaction_id, bsc->nat->bsc_endpoints[i].transaction_id) != 0)
+			continue;
+
+		endp = &bsc->nat->mgcp_cfg->trunk.endpoints[i];
+		bsc_endp = &bsc->nat->bsc_endpoints[i];
+		break;
+	}
+
+	if (!bsc_endp && strncmp("nat-", transaction_id, 4) == 0) {
+		handle_dlcx_response(bsc, msg, code, transaction_id);
+		return;
+	}
+
+	if (!bsc_endp) {
+		LOGP(DMGCP, LOGL_ERROR, "Could not find active endpoint: %s for msg: '%s'\n",
+		     transaction_id, (const char *) msg->l2h);
+		return;
+	}
+
+	endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h);
+	if (endp->ci == CI_UNUSED) {
+		free_chan_downstream(endp, bsc_endp, bsc);
+		return;
+	}
+
+	if (endp->osmux.state == OSMUX_STATE_NEGOTIATING)
+		bsc_mgcp_osmux_confirm(endp, (const char *) msg->l2h);
+
+	/* If we require osmux and it is disabled.. fail */
+	if (nat_osmux_only(bsc->nat->mgcp_cfg, bsc->cfg) &&
+		endp->osmux.state == OSMUX_STATE_DISABLED) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Failed to activate osmux endpoint 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		free_chan_downstream(endp, bsc_endp, bsc);
+		return;
+	}
+
+	/* free some stuff */
+	talloc_free(bsc_endp->transaction_id);
+	bsc_endp->transaction_id = NULL;
+	bsc_endp->transaction_state = 0;
+
+	/*
+	 * rewrite the information. In case the endpoint was deleted
+	 * there should be nothing for us to rewrite so putting endp->rtp_port
+	 * with the value of 0 should be no problem.
+	 */
+	output = bsc_mgcp_rewrite((char * ) msg->l2h, msgb_l2len(msg), -1,
+				  mgcp_net_src_addr(endp),
+				  endp->net_end.local_port, -1,
+				  &endp->bts_end.codec.payload_type,
+				  bsc->nat->sdp_ensure_amr_mode_set);
+	if (!output) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to rewrite MGCP msg.\n");
+		return;
+	}
+
+	mgcp_queue_for_call_agent(bsc->nat, output);
+}
+
+int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60])
+{
+	int rc;
+	/* we want to parse two strings */
+	rc = sscanf(str, "%3d %59s\n", code, transaction) != 2;
+	transaction[59] = '\0';
+	return rc;
+}
+
+uint32_t bsc_mgcp_extract_ci(const char *str)
+{
+	unsigned int ci;
+	char *res = strstr(str, "I: ");
+	if (!res) {
+		LOGP(DMGCP, LOGL_ERROR, "No CI in msg '%s'\n", str);
+		return CI_UNUSED;
+	}
+
+	if (sscanf(res, "I: %u", &ci) != 1) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to parse CI in msg '%s'\n", str);
+		return CI_UNUSED;
+	}
+
+	return ci;
+}
+
+/**
+ * Create a new MGCPCommand based on the input and endpoint from a message
+ */
+static void patch_mgcp(struct msgb *output, const char *op, const char *tok,
+		       int endp, int len, int cr, int osmux_cid)
+{
+	int slen;
+	int ret;
+	char buf[40];
+	char osmux_extension[strlen("\nX-Osmux: 255") + 1];
+
+	buf[0] = buf[39] = '\0';
+	ret = sscanf(tok, "%*s %s", buf);
+	if (ret != 1) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Failed to find Endpoint in: %s\n", tok);
+		return;
+	}
+
+	if (osmux_cid >= 0)
+		sprintf(osmux_extension, "\nX-Osmux: %u", osmux_cid & 0xff);
+	else
+		osmux_extension[0] = '\0';
+
+	slen = sprintf((char *) output->l3h, "%s %s %x@mgw MGCP 1.0%s%s",
+			op, buf, endp, osmux_extension, cr ? "\r\n" : "\n");
+	output->l3h = msgb_put(output, slen);
+}
+
+/* we need to replace some strings... */
+struct msgb *bsc_mgcp_rewrite(char *input, int length, int endpoint,
+			      const char *ip, int port, int osmux_cid,
+			      int *first_payload_type, int ensure_mode_set)
+{
+	static const char crcx_str[] = "CRCX ";
+	static const char dlcx_str[] = "DLCX ";
+	static const char mdcx_str[] = "MDCX ";
+
+	static const char ip_str[] = "c=IN IP4 ";
+	static const char aud_str[] = "m=audio ";
+	static const char fmt_str[] = "a=fmtp:";
+
+	char buf[128];
+	char *running, *token;
+	struct msgb *output;
+
+	/* keep state to add the a=fmtp line */
+	int found_fmtp = 0;
+	int payload = -1;
+	int cr = 1;
+
+	if (length > 4096 - 256) {
+		LOGP(DMGCP, LOGL_ERROR, "Input is too long.\n");
+		return NULL;
+	}
+
+	output = msgb_alloc_headroom(4096, 128, "MGCP rewritten");
+	if (!output) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate new MGCP msg.\n");
+		return NULL;
+	}
+
+	running = input;
+	output->l2h = output->data;
+	output->l3h = output->l2h;
+	for (token = strsep(&running, "\n"); running; token = strsep(&running, "\n")) {
+		int len = strlen(token);
+		cr = len > 0 && token[len - 1] == '\r';
+
+		if (strncmp(crcx_str, token, (sizeof crcx_str) - 1) == 0) {
+			patch_mgcp(output, "CRCX", token, endpoint, len, cr, osmux_cid);
+		} else if (strncmp(dlcx_str, token, (sizeof dlcx_str) - 1) == 0) {
+			patch_mgcp(output, "DLCX", token, endpoint, len, cr, -1);
+		} else if (strncmp(mdcx_str, token, (sizeof mdcx_str) - 1) == 0) {
+			patch_mgcp(output, "MDCX", token, endpoint, len, cr, -1);
+		} else if (strncmp(ip_str, token, (sizeof ip_str) - 1) == 0) {
+			output->l3h = msgb_put(output, strlen(ip_str));
+			memcpy(output->l3h, ip_str, strlen(ip_str));
+			output->l3h = msgb_put(output, strlen(ip));
+			memcpy(output->l3h, ip, strlen(ip));
+
+			if (cr) {
+				output->l3h = msgb_put(output, 2);
+				output->l3h[0] = '\r';
+				output->l3h[1] = '\n';
+			} else {
+				output->l3h = msgb_put(output, 1);
+				output->l3h[0] = '\n';
+			}
+		} else if (strncmp(aud_str, token, (sizeof aud_str) - 1) == 0) {
+			int offset;
+			if (sscanf(token, "m=audio %*d RTP/AVP %n%d", &offset, &payload) != 1) {
+				LOGP(DMGCP, LOGL_ERROR, "Could not parsed audio line.\n");
+				msgb_free(output);
+				return NULL;
+			}
+
+			snprintf(buf, sizeof(buf)-1, "m=audio %d RTP/AVP %s\n",
+				 port, &token[offset]);
+			buf[sizeof(buf)-1] = '\0';
+
+			output->l3h = msgb_put(output, strlen(buf));
+			memcpy(output->l3h, buf, strlen(buf));
+		} else if (strncmp(fmt_str, token, (sizeof fmt_str) - 1) == 0) {
+			found_fmtp = 1;
+			goto copy;
+		} else {
+copy:
+			output->l3h = msgb_put(output, len + 1);
+			memcpy(output->l3h, token, len);
+			output->l3h[len] = '\n';
+		}
+	}
+
+	/*
+	 * the above code made sure that we have 128 bytes lefts. So we can
+	 * safely append another line.
+	 */
+	if (ensure_mode_set && !found_fmtp && payload != -1) {
+		snprintf(buf, sizeof(buf) - 1, "a=fmtp:%d mode-set=2 octet-align=1%s",
+			payload, cr ? "\r\n" : "\n");
+		buf[sizeof(buf) - 1] = '\0';
+		output->l3h = msgb_put(output, strlen(buf));
+		memcpy(output->l3h, buf, strlen(buf));
+	}
+
+	if (payload != -1 && first_payload_type)
+		*first_payload_type = payload;
+
+	return output;
+}
+
+/*
+ * This comes from the MSC and we will now parse it. The caller needs
+ * to free the msgb.
+ */
+void bsc_nat_handle_mgcp(struct bsc_nat *nat, struct msgb *msg)
+{
+	struct msgb *resp;
+
+	if (!nat->mgcp_ipa) {
+		LOGP(DMGCP, LOGL_ERROR, "MGCP message not allowed on IPA.\n");
+		return;
+	}
+
+	if (msgb_l2len(msg) > sizeof(nat->mgcp_msg) - 1) {
+		LOGP(DMGCP, LOGL_ERROR, "MGCP msg too big for handling.\n");
+		return;
+	}
+
+	memcpy(nat->mgcp_msg, msg->l2h, msgb_l2len(msg));
+	nat->mgcp_length = msgb_l2len(msg);
+	nat->mgcp_msg[nat->mgcp_length] = '\0';
+
+	/* now handle the message */
+	resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+
+	/* we do have a direct answer... e.g. AUEP */
+	if (resp)
+		mgcp_queue_for_call_agent(nat, resp);
+
+	return;
+}
+
+static int mgcp_do_read(struct osmo_fd *fd)
+{
+	struct bsc_nat *nat;
+	struct msgb *msg, *resp;
+	int rc;
+
+	nat = fd->data;
+
+	rc = read(fd->fd, nat->mgcp_msg, sizeof(nat->mgcp_msg) - 1);
+	if (rc <= 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to read errno: %d\n", errno);
+		return -1;
+	}
+
+	nat->mgcp_msg[rc] = '\0';
+	nat->mgcp_length = rc;
+
+	msg = msgb_alloc(sizeof(nat->mgcp_msg), "MGCP GW Read");
+	if (!msg) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create buffer.\n");
+		return -1;
+	}
+
+	msg->l2h = msgb_put(msg, rc);
+	memcpy(msg->l2h, nat->mgcp_msg, msgb_l2len(msg));
+	resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+	msgb_free(msg);
+
+	/* we do have a direct answer... e.g. AUEP */
+	if (resp)
+		mgcp_queue_for_call_agent(nat, resp);
+
+	return 0;
+}
+
+static int mgcp_do_write(struct osmo_fd *bfd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(bfd->fd, msg->data, msg->len);
+
+	if (rc != msg->len) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to write msg to MGCP CallAgent.\n");
+		return -1;
+	}
+
+	return rc;
+}
+
+static int init_mgcp_socket(struct bsc_nat *nat, struct mgcp_config *cfg)
+{
+	struct sockaddr_in addr;
+	int on;
+
+	cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (cfg->gw_fd.bfd.fd < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create MGCP socket. errno: %d\n", errno);
+		return -1;
+	}
+
+	on = 1;
+	setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(cfg->source_port);
+	inet_aton(cfg->source_addr, &addr.sin_addr);
+
+	if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to bind on %s:%d errno: %d\n",
+		     cfg->source_addr, cfg->source_port, errno);
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	addr.sin_port = htons(2727);
+	inet_aton(cfg->call_agent_addr, &addr.sin_addr);
+	if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n",
+		     cfg->call_agent_addr, errno);
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	osmo_wqueue_init(&cfg->gw_fd, 10);
+	cfg->gw_fd.bfd.when = BSC_FD_READ;
+	cfg->gw_fd.bfd.data = nat;
+	cfg->gw_fd.read_cb = mgcp_do_read;
+	cfg->gw_fd.write_cb = mgcp_do_write;
+
+	if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register MGCP fd.\n");
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+int bsc_mgcp_nat_init(struct bsc_nat *nat)
+{
+	struct mgcp_config *cfg = nat->mgcp_cfg;
+
+	if (!cfg->call_agent_addr) {
+		LOGP(DMGCP, LOGL_ERROR, "The BSC nat requires the call agent ip to be set.\n");
+		return -1;
+	}
+
+	if (cfg->bts_ip) {
+		LOGP(DMGCP, LOGL_ERROR, "Do not set the BTS ip for the nat.\n");
+		return -1;
+	}
+
+	/* initialize the MGCP socket */
+	if (!nat->mgcp_ipa) {
+		int rc =  init_mgcp_socket(nat, cfg);
+		if (rc != 0)
+			return rc;
+	}
+
+
+	/* some more MGCP config handling */
+	cfg->data = nat;
+	cfg->policy_cb = bsc_mgcp_policy_cb;
+
+	if (cfg->bts_ip)
+		talloc_free(cfg->bts_ip);
+	cfg->bts_ip = "";
+
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       cfg->trunk.number_endpoints + 1);
+	if (!nat->bsc_endpoints) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate nat endpoints\n");
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	if (mgcp_reset_transcoder(cfg) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to send packet to the transcoder.\n");
+		talloc_free(nat->bsc_endpoints);
+		nat->bsc_endpoints = NULL;
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc)
+{
+	struct rate_ctr *ctr = NULL;
+	int i;
+
+	if (bsc->cfg)
+		ctr = &bsc->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_CALLS];
+
+	for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+		struct bsc_endpoint *bsc_endp = &bsc->nat->bsc_endpoints[i];
+
+		if (bsc_endp->bsc != bsc)
+			continue;
+
+		if (ctr)
+			rate_ctr_inc(ctr);
+
+		bsc_mgcp_free_endpoint(bsc->nat, i);
+		mgcp_release_endp(&bsc->nat->mgcp_cfg->trunk.endpoints[i]);
+	}
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat.c b/openbsc/src/osmo-bsc_nat/bsc_nat.c
new file mode 100644
index 0000000..c8a9e74
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat.c
@@ -0,0 +1,1758 @@
+/* BSC Multiplexer/NAT */
+
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgen.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/socket.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+
+#include <osmocom/crypt/auth.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include "../../bscconfig.h"
+
+#define SCCP_CLOSE_TIME 20
+#define SCCP_CLOSE_TIME_TIMEOUT 19
+
+static const char *config_file = "bsc-nat.cfg";
+static struct in_addr local_addr;
+static struct osmo_fd bsc_listen;
+static const char *msc_ip = NULL;
+static struct osmo_timer_list sccp_close;
+static int daemonize = 0;
+
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Holger Hans Peter Freyther and On-Waves\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static struct bsc_nat *nat;
+static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int);
+static void msc_send_reset(struct bsc_msc_connection *con);
+static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal);
+
+struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num)
+{
+	struct bsc_config *conf;
+
+	llist_for_each_entry(conf, &nat->bsc_configs, entry)
+		if (conf->nr == num)
+			return conf;
+
+	return NULL;
+}
+
+static void queue_for_msc(struct bsc_msc_connection *con, struct msgb *msg)
+{
+	if (!con) {
+		LOGP(DLINP, LOGL_ERROR, "No MSC Connection assigned. Check your code.\n");
+		msgb_free(msg);
+		return;
+	}
+
+
+	if (osmo_wqueue_enqueue(&con->write_queue, msg) != 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the write.\n");
+		msgb_free(msg);
+	}
+}
+
+static void send_reset_ack(struct bsc_connection *bsc)
+{
+	static const uint8_t gsm_reset_ack[] = {
+		0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+		0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+		0x00, 0x01, 0x31,
+	};
+
+	bsc_send_data(bsc, gsm_reset_ack, sizeof(gsm_reset_ack), IPAC_PROTO_SCCP);
+}
+
+static void send_ping(struct bsc_connection *bsc)
+{
+	static const uint8_t id_ping[] = {
+		IPAC_MSGT_PING,
+	};
+
+	bsc_send_data(bsc, id_ping, sizeof(id_ping), IPAC_PROTO_IPACCESS);
+}
+
+static void send_pong(struct bsc_connection *bsc)
+{
+	static const uint8_t id_pong[] = {
+		IPAC_MSGT_PONG,
+	};
+
+	bsc_send_data(bsc, id_pong, sizeof(id_pong), IPAC_PROTO_IPACCESS);
+}
+
+static void bsc_pong_timeout(void *_bsc)
+{
+	struct bsc_connection *bsc = _bsc;
+
+	LOGP(DNAT, LOGL_ERROR, "BSC Nr: %d PONG timeout.\n", bsc->cfg->nr);
+	bsc_close_connection(bsc);
+}
+
+static void bsc_ping_timeout(void *_bsc)
+{
+	struct bsc_connection *bsc = _bsc;
+
+	if (bsc->nat->ping_timeout < 0)
+		return;
+
+	send_ping(bsc);
+
+	/* send another ping in 20 seconds */
+	osmo_timer_schedule(&bsc->ping_timeout, bsc->nat->ping_timeout, 0);
+
+	/* also start a pong timer */
+	osmo_timer_schedule(&bsc->pong_timeout, bsc->nat->pong_timeout, 0);
+}
+
+static void start_ping_pong(struct bsc_connection *bsc)
+{
+	osmo_timer_setup(&bsc->pong_timeout, bsc_pong_timeout, bsc);
+	osmo_timer_setup(&bsc->ping_timeout, bsc_ping_timeout, bsc);
+
+	bsc_ping_timeout(bsc);
+}
+
+static void send_id_ack(struct bsc_connection *bsc)
+{
+	static const uint8_t id_ack[] = {
+		IPAC_MSGT_ID_ACK
+	};
+
+	bsc_send_data(bsc, id_ack, sizeof(id_ack), IPAC_PROTO_IPACCESS);
+}
+
+static void send_id_req(struct bsc_nat *nat, struct bsc_connection *bsc)
+{
+	static const uint8_t s_id_req[] = {
+		IPAC_MSGT_ID_GET,
+		0x01, IPAC_IDTAG_UNIT,
+		0x01, IPAC_IDTAG_MACADDR,
+		0x01, IPAC_IDTAG_LOCATION1,
+		0x01, IPAC_IDTAG_LOCATION2,
+		0x01, IPAC_IDTAG_EQUIPVERS,
+		0x01, IPAC_IDTAG_SWVERSION,
+		0x01, IPAC_IDTAG_UNITNAME,
+		0x01, IPAC_IDTAG_SERNR,
+	};
+
+	uint8_t *mrand;
+	uint8_t id_req[sizeof(s_id_req) + (2+16)];
+	uint8_t *buf = &id_req[sizeof(s_id_req)];
+
+	/* copy the static data */
+	memcpy(id_req, s_id_req, sizeof(s_id_req));
+
+	/* put the RAND with length, tag, value */
+	buf = v_put(buf, 0x11);
+	buf = v_put(buf, 0x23);
+	mrand = bsc->last_rand;
+
+	if (osmo_get_rand_id(mrand, 16) < 0)
+		goto failed_random;
+
+	memcpy(buf, mrand, 16);
+	buf += 16;
+
+	bsc_send_data(bsc, id_req, sizeof(id_req), IPAC_PROTO_IPACCESS);
+	return;
+
+failed_random:
+	/* the timeout will trigger and close this connection */
+	LOGP(DNAT, LOGL_ERROR, "osmo_get_rand_id() failed.\n");
+	return;
+}
+
+static struct msgb *nat_create_rlsd(struct nat_sccp_connection *conn)
+{
+	struct sccp_connection_released *rel;
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "rlsd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate released.\n");
+		return NULL;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(*rel));
+	rel = (struct sccp_connection_released *) msg->l2h;
+	rel->type = SCCP_MSG_TYPE_RLSD;
+	rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE;
+	rel->destination_local_reference = conn->remote_ref;
+	rel->source_local_reference = conn->patched_ref;
+
+	return msg;
+}
+
+static void nat_send_rlsd_ussd(struct bsc_nat *nat, struct nat_sccp_connection *conn)
+{
+	struct msgb *msg;
+
+	if (!nat->ussd_con)
+		return;
+
+	msg = nat_create_rlsd(conn);
+	if (!msg)
+		return;
+
+	bsc_do_write(&nat->ussd_con->queue, msg, IPAC_PROTO_SCCP);
+}
+
+static void nat_send_rlsd_msc(struct nat_sccp_connection *conn)
+{
+	struct msgb *msg;
+
+	msg = nat_create_rlsd(conn);
+	if (!msg)
+		return;
+
+	ipa_prepend_header(msg, IPAC_PROTO_SCCP);
+	queue_for_msc(conn->msc_con, msg);
+}
+
+static void nat_send_rlsd_bsc(struct nat_sccp_connection *conn)
+{
+	struct msgb *msg;
+	struct sccp_connection_released *rel;
+
+	msg = msgb_alloc_headroom(4096, 128, "rlsd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(*rel));
+	rel = (struct sccp_connection_released *) msg->l2h;
+	rel->type = SCCP_MSG_TYPE_RLSD;
+	rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE;
+	rel->destination_local_reference = conn->real_ref;
+	rel->source_local_reference = conn->remote_ref;
+
+	bsc_write(conn->bsc, msg, IPAC_PROTO_SCCP);
+}
+
+static struct msgb *nat_creat_clrc(struct nat_sccp_connection *conn, uint8_t cause)
+{
+	struct msgb *msg;
+	struct msgb *sccp;
+
+	msg = gsm0808_create_clear_command(cause);
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+		return NULL;
+	}
+
+	sccp = sccp_create_dt1(&conn->real_ref, msg->data, msg->len);
+	if (!sccp) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate SCCP msg.\n");
+		msgb_free(msg);
+		return NULL;
+	}
+
+	msgb_free(msg);
+	return sccp;
+}
+
+static int nat_send_clrc_bsc(struct nat_sccp_connection *conn)
+{
+	struct msgb *sccp;
+
+	sccp = nat_creat_clrc(conn, 0x20);
+	if (!sccp)
+		return -1;
+	return bsc_write(conn->bsc, sccp, IPAC_PROTO_SCCP);
+}
+
+static void nat_send_rlc(struct bsc_msc_connection *msc_con,
+			 struct sccp_source_reference *src,
+			 struct sccp_source_reference *dst)
+{
+	struct sccp_connection_release_complete *rlc;
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "rlc");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to sccp rlc.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(*rlc));
+	rlc = (struct sccp_connection_release_complete *) msg->l2h;
+	rlc->type = SCCP_MSG_TYPE_RLC;
+	rlc->destination_local_reference = *dst;
+	rlc->source_local_reference = *src;
+
+	ipa_prepend_header(msg, IPAC_PROTO_SCCP);
+
+	queue_for_msc(msc_con, msg);
+}
+
+static void send_mgcp_reset(struct bsc_connection *bsc)
+{
+	static const uint8_t mgcp_reset[] = {
+	    "RSIP 1 13@mgw MGCP 1.0\r\n"
+	};
+
+	bsc_write_mgcp(bsc, mgcp_reset, sizeof mgcp_reset - 1);
+}
+
+void bsc_nat_send_mgcp_to_msc(struct bsc_nat *nat, struct msgb *msg)
+{
+	ipa_prepend_header(msg, IPAC_PROTO_MGCP_OLD);
+	queue_for_msc(nat->msc_con, msg);
+}
+
+/*
+ * Below is the handling of messages coming
+ * from the MSC and need to be forwarded to
+ * a real BSC.
+ */
+static void initialize_msc_if_needed(struct bsc_msc_connection *msc_con)
+{
+	if (msc_con->first_contact)
+		return;
+
+	msc_con->first_contact = 1;
+	msc_send_reset(msc_con);
+}
+
+static void send_id_get_response(struct bsc_msc_connection *msc_con)
+{
+	struct msgb *msg = bsc_msc_id_get_resp(0, nat->token, NULL, 0);
+	if (!msg)
+		return;
+
+	ipa_prepend_header(msg, IPAC_PROTO_IPACCESS);
+	queue_for_msc(msc_con, msg);
+}
+
+/*
+ * Currently we are lacking refcounting so we need to copy each message.
+ */
+static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int proto)
+{
+	struct msgb *msg;
+
+	if (length > 4096 - 128) {
+		LOGP(DLINP, LOGL_ERROR, "Can not send message of that size.\n");
+		return;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "to-bsc");
+	if (!msg) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, length);
+	memcpy(msg->data, data, length);
+
+	bsc_write(bsc, msg, proto);
+}
+
+/*
+ * Update the release statistics
+ */
+static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal)
+{
+	if (!bsc->cfg) {
+		LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.");
+		return;
+	}
+
+	if (filter >= 0) {
+		LOGP(DNAT, LOGL_ERROR, "Connection was not rejected");
+		return;
+	}
+
+	if (filter == -1)
+		rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_ILL_PACKET]);
+	else if (normal)
+		rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_MSG]);
+	else
+		rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_CR]);
+}
+
+/*
+ * Release an established connection. We will have to release it to the BSC
+ * and to the network and we do it the following way.
+ * 1.) Give up on the MSC side
+ *  1.1) Send a RLSD message, it is a bit non standard but should work, we
+ *       ignore the RLC... we might complain about it. Other options would
+ *       be to send a Release Request, handle the Release Complete..
+ *  1.2) Mark the data structure to be con_local and wait for 2nd
+ *
+ * 2.) Give up on the BSC side
+ *  2.1) Depending on the con type reject the service, or just close it
+ */
+static void bsc_send_con_release(struct bsc_connection *bsc,
+		struct nat_sccp_connection *con,
+		struct bsc_filter_reject_cause *cause)
+{
+	struct msgb *rlsd;
+	/* 1. release the network */
+	rlsd = sccp_create_rlsd(&con->patched_ref, &con->remote_ref,
+				SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+	if (!rlsd)
+		LOGP(DNAT, LOGL_ERROR, "Failed to create RLSD message.\n");
+	else {
+		ipa_prepend_header(rlsd, IPAC_PROTO_SCCP);
+		queue_for_msc(con->msc_con, rlsd);
+	}
+	con->con_local = NAT_CON_END_LOCAL;
+	con->msc_con = NULL;
+
+	/* 2. release the BSC side */
+	if (con->filter_state.con_type == FLT_CON_TYPE_LU) {
+		struct msgb *payload, *udt;
+		payload = gsm48_create_loc_upd_rej(cause->lu_reject_cause);
+
+		if (payload) {
+			gsm0808_prepend_dtap_header(payload, 0);
+			udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+			if (udt)
+				bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+			else
+				LOGP(DNAT, LOGL_ERROR, "Failed to create DT1\n");
+
+			msgb_free(payload);
+		} else {
+			LOGP(DNAT, LOGL_ERROR, "Failed to allocate LU Reject.\n");
+		}
+	}
+
+	nat_send_clrc_bsc(con);
+
+	rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+				SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+	if (!rlsd) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate RLSD for the BSC.\n");
+		sccp_connection_destroy(con);
+		return;
+	}
+
+	con->filter_state.con_type = FLT_CON_TYPE_LOCAL_REJECT;
+	bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+}
+
+static void bsc_send_con_refuse(struct bsc_connection *bsc,
+			struct bsc_nat_parsed *parsed, int con_type,
+			struct bsc_filter_reject_cause *cause)
+{
+	struct msgb *payload;
+	struct msgb *refuse;
+
+	if (con_type == FLT_CON_TYPE_LU)
+		payload = gsm48_create_loc_upd_rej(cause->lu_reject_cause);
+	else if (con_type == FLT_CON_TYPE_CM_SERV_REQ || con_type == FLT_CON_TYPE_SSA)
+		payload = gsm48_create_mm_serv_rej(cause->cm_reject_cause);
+	else {
+		LOGP(DNAT, LOGL_ERROR, "Unknown connection type: %d\n", con_type);
+		payload = NULL;
+	}
+
+	/*
+	 * Some BSCs do not handle the payload inside a SCCP CREF msg
+	 * so we will need to:
+	 * 1.) Allocate a local connection and mark it as local..
+	 * 2.) queue data for downstream.. and the RLC should delete everything
+	 */
+	if (payload) {
+		struct msgb *cc, *udt, *clear, *rlsd;
+		struct nat_sccp_connection *con;
+		con = create_sccp_src_ref(bsc, parsed);
+		if (!con)
+			goto send_refuse;
+
+		/* declare it local and assign a unique remote_ref */
+		con->filter_state.con_type = FLT_CON_TYPE_LOCAL_REJECT;
+		con->con_local = NAT_CON_END_LOCAL;
+		con->has_remote_ref = 1;
+		con->remote_ref = con->patched_ref;
+
+		/* 1. create a confirmation */
+		cc = sccp_create_cc(&con->remote_ref, &con->real_ref);
+		if (!cc)
+			goto send_refuse;
+
+		/* 2. create the DT1 */
+		gsm0808_prepend_dtap_header(payload, 0);
+		udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+		if (!udt) {
+			msgb_free(cc);
+			goto send_refuse;
+		}
+
+		/* 3. send a Clear Command */
+		clear = nat_creat_clrc(con, 0x20);
+		if (!clear) {
+			msgb_free(cc);
+			msgb_free(udt);
+			goto send_refuse;
+		}
+
+		/* 4. send a RLSD */
+		rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+					SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+		if (!rlsd) {
+			msgb_free(cc);
+			msgb_free(udt);
+			msgb_free(clear);
+			goto send_refuse;
+		}
+
+		bsc_write(bsc, cc, IPAC_PROTO_SCCP);
+		bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+		bsc_write(bsc, clear, IPAC_PROTO_SCCP);
+		bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+		msgb_free(payload);
+		return;
+	}
+
+
+send_refuse:
+	if (payload)
+		msgb_free(payload);
+
+	refuse = sccp_create_refuse(parsed->src_local_ref,
+				    SCCP_REFUSAL_SCCP_FAILURE, NULL, 0);
+	if (!refuse) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "Creating refuse msg failed for SCCP 0x%x on BSC Nr: %d.\n",
+		      sccp_src_ref_to_int(parsed->src_local_ref), bsc->cfg->nr);
+		return;
+	}
+
+	bsc_write(bsc, refuse, IPAC_PROTO_SCCP);
+}
+
+static void bsc_nat_send_paging(struct bsc_connection *bsc, struct msgb *msg)
+{
+	if (bsc->cfg->forbid_paging) {
+		LOGP(DNAT, LOGL_DEBUG, "Paging forbidden for BTS: %d\n", bsc->cfg->nr);
+		return;
+	}
+
+	bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), IPAC_PROTO_SCCP);
+}
+
+static void bsc_nat_handle_paging(struct bsc_nat *nat, struct msgb *msg)
+{
+	struct bsc_connection *bsc;
+	const uint8_t *paging_start;
+	int paging_length, i, discrim;
+
+	discrim = bsc_nat_find_paging(msg, &paging_start, &paging_length);
+	if (discrim < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Could not parse paging message: %d\n", discrim);
+		return;
+	}
+
+	if (discrim == CELL_IDENT_BSS) {
+		if (!nat->paging_bss_forward) {
+			LOGP(DNAT, LOGL_DEBUG, "Dropping BSS paging based on current config\n");
+			return;
+		}
+		/* All cells on the BSS are identified. */
+		llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+			if (!bsc->authenticated)
+				continue;
+			bsc_nat_send_paging(bsc, msg);
+		}
+		return;
+	}
+
+	/* This is quite expensive now */
+	for (i = 0; i < paging_length; i += 2) {
+		unsigned int _lac = ntohs(*(unsigned int *) &paging_start[i]);
+		unsigned int paged = 0;
+		llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+			if (!bsc->cfg)
+				continue;
+			if (!bsc->authenticated)
+				continue;
+			if (!bsc_config_handles_lac(bsc->cfg, _lac))
+				continue;
+			bsc_nat_send_paging(bsc, msg);
+			paged += 1;
+		}
+
+		/* highlight a possible config issue */
+		if (paged == 0)
+			LOGP(DNAT, LOGL_ERROR, "No BSC for LAC %d/0x%d\n", _lac, _lac);
+
+	}
+}
+
+
+/*
+ * Update the auth status. This can be either a CIPHER MODE COMMAND or
+ * a CM Serivce Accept. Maybe also LU Accept or such in the future.
+ */
+static void update_con_authorize(struct nat_sccp_connection *con,
+				 struct bsc_nat_parsed *parsed,
+				 struct msgb *msg)
+{
+	if (!con)
+		return;
+	if (con->authorized)
+		return;
+
+	if (parsed->bssap == BSSAP_MSG_BSS_MANAGEMENT &&
+	    parsed->gsm_type == BSS_MAP_MSG_CIPHER_MODE_CMD) {
+		con->authorized = 1;
+	} else if (parsed->bssap == BSSAP_MSG_DTAP) {
+		uint8_t msg_type, proto;
+		uint32_t len;
+		struct gsm48_hdr *hdr48;
+		hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+		if (!hdr48)
+			return;
+
+		proto = gsm48_hdr_pdisc(hdr48);
+		msg_type = gsm48_hdr_msg_type(hdr48);
+		if (proto == GSM48_PDISC_MM &&
+		    msg_type == GSM48_MT_MM_CM_SERV_ACC)
+			con->authorized = 1;
+	}
+}
+
+static int forward_sccp_to_bts(struct bsc_msc_connection *msc_con, struct msgb *msg)
+{
+	struct nat_sccp_connection *con = NULL;
+	struct bsc_connection *bsc;
+	struct bsc_nat_parsed *parsed;
+	int proto;
+
+	/* filter, drop, patch the message? */
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
+		return -1;
+	}
+
+	if (bsc_nat_filter_ipa(DIR_BSC, msg, parsed))
+		goto exit;
+
+	proto = parsed->ipa_proto;
+
+	/* Route and modify the SCCP packet */
+	if (proto == IPAC_PROTO_SCCP) {
+		switch (parsed->sccp_type) {
+		case SCCP_MSG_TYPE_UDT:
+			/* forward UDT messages to every BSC */
+			goto send_to_all;
+			break;
+		case SCCP_MSG_TYPE_RLSD:
+		case SCCP_MSG_TYPE_CREF:
+		case SCCP_MSG_TYPE_DT1:
+		case SCCP_MSG_TYPE_IT:
+			con = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+			if (parsed->gsm_type == BSS_MAP_MSG_ASSIGMENT_RQST) {
+				osmo_counter_inc(nat->stats.sccp.calls);
+
+				if (con) {
+					struct rate_ctr_group *ctrg;
+					ctrg = con->bsc->cfg->stats.ctrg;
+					rate_ctr_inc(&ctrg->ctr[BCFG_CTR_SCCP_CALLS]);
+					if (bsc_mgcp_assign_patch(con, msg) != 0)
+						LOGP(DNAT, LOGL_ERROR, "Failed to assign...\n");
+				} else
+					LOGP(DNAT, LOGL_ERROR, "Assignment command but no BSC.\n");
+			} else if (con && con->con_local == NAT_CON_END_USSD &&
+				   parsed->gsm_type == BSS_MAP_MSG_CLEAR_CMD) {
+				LOGP(DNAT, LOGL_NOTICE, "Clear Command for USSD Connection. Ignoring.\n");
+				con = NULL;
+			}
+			break;
+		case SCCP_MSG_TYPE_CC:
+			con = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+			if (!con || update_sccp_src_ref(con, parsed) != 0)
+				goto exit;
+			break;
+		case SCCP_MSG_TYPE_RLC:
+			LOGP(DNAT, LOGL_ERROR, "Unexpected release complete from MSC.\n");
+			goto exit;
+			break;
+		case SCCP_MSG_TYPE_CR:
+			/* MSC never opens a SCCP connection, fall through */
+		default:
+			goto exit;
+		}
+
+		if (!con && parsed->sccp_type == SCCP_MSG_TYPE_RLSD) {
+			LOGP(DNAT, LOGL_NOTICE, "Sending fake RLC on RLSD message to network.\n");
+			/* Exchange src/dest for the reply */
+			nat_send_rlc(msc_con, &parsed->original_dest_ref,
+					parsed->src_local_ref);
+		} else if (!con)
+			LOGP(DNAT, LOGL_ERROR, "Unknown connection for msg type: 0x%x from the MSC.\n", parsed->sccp_type);
+	}
+
+	if (!con) {
+		talloc_free(parsed);
+		return -1;
+	}
+	if (!con->bsc->authenticated) {
+		talloc_free(parsed);
+		LOGP(DNAT, LOGL_ERROR, "Selected BSC not authenticated.\n");
+		return -1;
+	}
+
+	update_con_authorize(con, parsed, msg);
+	talloc_free(parsed);
+
+	bsc_send_data(con->bsc, msg->l2h, msgb_l2len(msg), proto);
+	return 0;
+
+send_to_all:
+	/*
+	 * Filter Paging from the network. We do not want to send a PAGING
+	 * Command to every BSC in our network. We will analys the PAGING
+	 * message and then send it to the authenticated messages...
+	 */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP && parsed->gsm_type == BSS_MAP_MSG_PAGING) {
+		bsc_nat_handle_paging(nat, msg);
+		goto exit;
+	}
+	/* currently send this to every BSC connected */
+	llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+		if (!bsc->authenticated)
+			continue;
+
+		bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto);
+	}
+
+exit:
+	talloc_free(parsed);
+	return 0;
+}
+
+static void msc_connection_was_lost(struct bsc_msc_connection *con)
+{
+	struct bsc_connection *bsc, *tmp;
+
+	LOGP(DMSC, LOGL_ERROR, "Closing all connections downstream.\n");
+	llist_for_each_entry_safe(bsc, tmp, &nat->bsc_connections, list_entry)
+		bsc_close_connection(bsc);
+
+	bsc_mgcp_free_endpoints(nat);
+	bsc_msc_schedule_connect(con);
+}
+
+static void msc_connection_connected(struct bsc_msc_connection *con)
+{
+	osmo_counter_inc(nat->stats.msc.reconn);
+}
+
+static void msc_send_reset(struct bsc_msc_connection *msc_con)
+{
+	static const uint8_t reset[] = {
+		0x00, 0x12, 0xfd,
+		0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+		0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+		0x01, 0x20
+	};
+
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "08.08 reset");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate reset msg.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(reset));
+	memcpy(msg->l2h, reset, msgb_l2len(msg));
+
+	queue_for_msc(msc_con, msg);
+
+	LOGP(DMSC, LOGL_NOTICE, "Scheduled GSM0808 reset msg for the MSC.\n");
+}
+
+static int ipaccess_msc_read_cb(struct osmo_fd *bfd)
+{
+	struct bsc_msc_connection *msc_con;
+	struct msgb *msg = NULL;
+	struct ipaccess_head *hh;
+	int ret;
+
+	msc_con = (struct bsc_msc_connection *) bfd->data;
+
+	ret = ipa_msg_recv_buffered(bfd->fd, &msg, &msc_con->pending_msg);
+	if (ret <= 0) {
+		if (ret == -EAGAIN)
+			return 0;
+		if (ret == 0)
+			LOGP(DNAT, LOGL_FATAL,
+				"The connection the MSC(%s) was lost, exiting\n",
+				msc_con->name);
+		else
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to parse ip access message on %s: %d\n",
+				msc_con->name, ret);
+
+		bsc_msc_lost(msc_con);
+		return -EBADF;
+	}
+
+	LOGP(DNAT, LOGL_DEBUG,
+		"MSG from MSC(%s): %s proto: %d\n", msc_con->name,
+		osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+
+	/* handle base message handling */
+	hh = (struct ipaccess_head *) msg->data;
+
+	/* initialize the networking. This includes sending a GSM08.08 message */
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ipa_ccm_rcvmsg_base(msg, bfd);
+		if (msg->l2h[0] == IPAC_MSGT_ID_ACK)
+			initialize_msc_if_needed(msc_con);
+		else if (msg->l2h[0] == IPAC_MSGT_ID_GET)
+			send_id_get_response(msc_con);
+	} else if (hh->proto == IPAC_PROTO_SCCP) {
+		forward_sccp_to_bts(msc_con, msg);
+	} else if (hh->proto == IPAC_PROTO_MGCP_OLD) {
+		bsc_nat_handle_mgcp(nat, msg);
+	}
+
+	msgb_free(msg);
+	return 0;
+}
+
+static int ipaccess_msc_write_cb(struct osmo_fd *bfd, struct msgb *msg)
+{
+	int rc;
+	rc = write(bfd->fd, msg->data, msg->len);
+
+	if (rc != msg->len) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to write MSG to MSC.\n");
+		return -1;
+	}
+
+	return rc;
+}
+
+/*
+ * Below is the handling of messages coming
+ * from the BSC and need to be forwarded to
+ * a real BSC.
+ */
+
+/*
+ * Remove the connection from the connections list,
+ * remove it from the patching of SCCP header lists
+ * as well. Maybe in the future even close connection..
+ */
+void bsc_close_connection(struct bsc_connection *connection)
+{
+	struct nat_sccp_connection *sccp_patch, *tmp;
+	struct bsc_cmd_list *cmd_entry, *cmd_tmp;
+	struct rate_ctr *ctr = NULL;
+
+	/* stop the timeout timer */
+	osmo_timer_del(&connection->id_timeout);
+	osmo_timer_del(&connection->ping_timeout);
+	osmo_timer_del(&connection->pong_timeout);
+
+	if (connection->cfg)
+		ctr = &connection->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_SCCP];
+
+	/* remove all SCCP connections */
+	llist_for_each_entry_safe(sccp_patch, tmp, &nat->sccp_connections, list_entry) {
+		if (sccp_patch->bsc != connection)
+			continue;
+
+		if (ctr)
+			rate_ctr_inc(ctr);
+		if (sccp_patch->has_remote_ref) {
+			if (sccp_patch->con_local == NAT_CON_END_MSC)
+				nat_send_rlsd_msc(sccp_patch);
+			else if (sccp_patch->con_local == NAT_CON_END_USSD)
+				nat_send_rlsd_ussd(nat, sccp_patch);
+		}
+
+		sccp_connection_destroy(sccp_patch);
+	}
+
+	/* Reply to all outstanding commands */
+	llist_for_each_entry_safe(cmd_entry, cmd_tmp, &connection->cmd_pending, list_entry) {
+		cmd_entry->cmd->type = CTRL_TYPE_ERROR;
+		cmd_entry->cmd->reply = "BSC closed the connection";
+		ctrl_cmd_send(&cmd_entry->cmd->ccon->write_queue, cmd_entry->cmd);
+		bsc_nat_ctrl_del_pending(cmd_entry);
+	}
+
+	/* close endpoints allocated by this BSC */
+	bsc_mgcp_clear_endpoints_for(connection);
+
+	osmo_fd_unregister(&connection->write_queue.bfd);
+	close(connection->write_queue.bfd.fd);
+	osmo_wqueue_clear(&connection->write_queue);
+	llist_del(&connection->list_entry);
+
+	if (connection->pending_msg) {
+		LOGP(DNAT, LOGL_ERROR, "Dropping partial message on connection %d.\n",
+		     connection->cfg ? connection->cfg->nr : -1);
+		msgb_free(connection->pending_msg);
+		connection->pending_msg = NULL;
+	}
+
+	talloc_free(connection);
+}
+
+/* Returns true if bsc_close_connection() was called, false otherwise */
+static bool bsc_maybe_close(struct bsc_connection *bsc)
+{
+	struct nat_sccp_connection *sccp;
+	if (!bsc->nat->blocked)
+		return false;
+
+	/* are there any connections left */
+	llist_for_each_entry(sccp, &bsc->nat->sccp_connections, list_entry)
+		if (sccp->bsc == bsc)
+			return false;
+
+	/* nothing left, close the BSC */
+	LOGP(DNAT, LOGL_NOTICE, "Cleaning up BSC %d in blocking mode.\n",
+	     bsc->cfg ? bsc->cfg->nr : -1);
+	bsc_close_connection(bsc);
+	return true;
+}
+
+static void ipaccess_close_bsc(void *data)
+{
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+	struct bsc_connection *conn = data;
+
+
+	getpeername(conn->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+	LOGP(DNAT, LOGL_ERROR, "BSC on %s didn't respond to identity request. Closing.\n",
+	     inet_ntoa(sock.sin_addr));
+	bsc_close_connection(conn);
+}
+
+static int verify_key(struct bsc_connection *conn, struct bsc_config *conf, const uint8_t *key, const int keylen)
+{
+	struct osmo_auth_vector vec;
+
+	struct osmo_sub_auth_data auth = {
+		.type		= OSMO_AUTH_TYPE_GSM,
+		.algo		= OSMO_AUTH_ALG_MILENAGE,
+	};
+
+	/* expect a specific keylen */
+	if (keylen != 8) {
+		LOGP(DNAT, LOGL_ERROR, "Key length is wrong: %d for bsc nr %d\n",
+			keylen, conf->nr);
+		return 0;
+	}
+
+	memcpy(auth.u.umts.opc, conf->key, 16);
+	memcpy(auth.u.umts.k, conf->key, 16);
+	memset(auth.u.umts.amf, 0, 2);
+	auth.u.umts.sqn = 0;
+
+	memset(&vec, 0, sizeof(vec));
+	osmo_auth_gen_vec(&vec, &auth, conn->last_rand);
+
+	if (vec.res_len != 8) {
+		LOGP(DNAT, LOGL_ERROR, "Res length is wrong: %d for bsc nr %d\n",
+			vec.res_len, conf->nr);
+		return 0;
+	}
+
+	return osmo_constant_time_cmp(vec.res, key, 8) == 0;
+}
+
+/* Returns true if connection was successfully authenticated, false otherwise. */
+static bool ipaccess_auth_bsc(struct tlv_parsed *tvp, struct bsc_connection *bsc)
+{
+	struct bsc_config *conf;
+	const char *token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+	int len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+	const uint8_t *xres = TLVP_VAL(tvp, 0x24);
+	const int xlen = TLVP_LEN(tvp, 0x24);
+
+	if (bsc->cfg) {
+		LOGP(DNAT, LOGL_ERROR, "Reauth on fd %d bsc nr %d\n",
+		     bsc->write_queue.bfd.fd, bsc->cfg->nr);
+		return true;
+	}
+
+	if (len <= 0) {
+		LOGP(DNAT, LOGL_ERROR, "Token with length zero on fd: %d\n",
+			bsc->write_queue.bfd.fd);
+		return false;
+	}
+
+	if (token[len - 1] != '\0') {
+		LOGP(DNAT, LOGL_ERROR, "Token not null terminated on fd: %d\n",
+			bsc->write_queue.bfd.fd);
+		return false;
+	}
+
+	/*
+	 * New systems have fixed the structure of the message but
+	 * we need to support old ones too.
+	 */
+	if (len >= 2 && token[len - 2] == '\0')
+		len -= 1;
+
+	conf = bsc_config_by_token(bsc->nat, token, len);
+	if (!conf) {
+		LOGP(DNAT, LOGL_ERROR,
+			"No bsc found for token '%s' len %d on fd: %d.\n", token,
+			bsc->write_queue.bfd.fd, len);
+		return false;
+	}
+
+	/* We have set a key and expect it to be present */
+	if (conf->key_present && !verify_key(bsc, conf, xres, xlen - 1)) {
+		LOGP(DNAT, LOGL_ERROR,
+			"Wrong key for bsc nr %d fd: %d.\n", conf->nr,
+			bsc->write_queue.bfd.fd);
+		return false;
+	}
+
+	rate_ctr_inc(&conf->stats.ctrg->ctr[BCFG_CTR_NET_RECONN]);
+	bsc->authenticated = 1;
+	bsc->cfg = conf;
+	osmo_timer_del(&bsc->id_timeout);
+	LOGP(DNAT, LOGL_NOTICE, "Authenticated bsc nr: %d on fd %d\n",
+		conf->nr, bsc->write_queue.bfd.fd);
+	start_ping_pong(bsc);
+	return true;
+}
+
+static void handle_con_stats(struct nat_sccp_connection *con)
+{
+	struct rate_ctr_group *ctrg;
+	int id = bsc_conn_type_to_ctr(con);
+
+	if (id == -1)
+		return;
+
+	if (!con->bsc || !con->bsc->cfg)
+		return;
+
+	ctrg = con->bsc->cfg->stats.ctrg;
+	rate_ctr_inc(&ctrg->ctr[id]);
+}
+
+/*!
+ * Forward messages to msc and verify received authentication messages.
+ * \param[in] bsc Pointer to bsc_connection structure from which the message was received.
+ * \param[in] msg The msg received to be forwarded to the msc.
+ * \param[out] bsc_conn_closed Whether bsc_close_connection(bsc) was called inside the function.
+ *  \returns 0 on success, -1 on error.
+ */
+static int forward_sccp_to_msc(struct bsc_connection *bsc, struct msgb *msg, bool *bsc_conn_closed)
+{
+	int con_filter = 0;
+	char *imsi = NULL;
+	struct bsc_msc_connection *con_msc = NULL;
+	struct bsc_connection *con_bsc = NULL;
+	int con_type;
+	struct bsc_nat_parsed *parsed;
+	struct bsc_filter_reject_cause cause;
+	*bsc_conn_closed = false;
+
+	/* Parse and filter messages */
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	if (bsc_nat_filter_ipa(DIR_MSC, msg, parsed))
+		goto exit;
+
+	/*
+	 * check authentication after filtering to not reject auth
+	 * responses coming from the BSC. We have to make sure that
+	 * nothing from the exit path will forward things to the MSC
+	 */
+	if (!bsc->authenticated) {
+		LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.\n");
+		goto exit2;
+	}
+
+
+	/* modify the SCCP entries */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+		int filter;
+		struct nat_sccp_connection *con;
+		switch (parsed->sccp_type) {
+		case SCCP_MSG_TYPE_CR:
+			memset(&cause, 0, sizeof(cause));
+			filter = bsc_nat_filter_sccp_cr(bsc, msg, parsed,
+						&con_type, &imsi, &cause);
+			if (filter < 0) {
+				if (imsi)
+					bsc_nat_inform_reject(bsc, imsi);
+				bsc_stat_reject(filter, bsc, 0);
+				/* send a SCCP Connection Refused */
+				bsc_send_con_refuse(bsc, parsed, con_type, &cause);
+				goto exit2;
+			}
+
+			if (!create_sccp_src_ref(bsc, parsed))
+				goto exit2;
+			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+			OSMO_ASSERT(con);
+			con->msc_con = bsc->nat->msc_con;
+			con_msc = con->msc_con;
+			con->filter_state.con_type = con_type;
+			con->filter_state.imsi_checked = filter;
+			bsc_nat_extract_lac(bsc, con, parsed, msg);
+			if (imsi)
+				con->filter_state.imsi = talloc_steal(con, imsi);
+			imsi = NULL;
+			con_bsc = con->bsc;
+			handle_con_stats(con);
+			break;
+		case SCCP_MSG_TYPE_RLSD:
+		case SCCP_MSG_TYPE_CREF:
+		case SCCP_MSG_TYPE_DT1:
+		case SCCP_MSG_TYPE_CC:
+		case SCCP_MSG_TYPE_IT:
+			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+			if (con) {
+				/* only filter non local connections */
+				if (!con->con_local) {
+					memset(&cause, 0, sizeof(cause));
+					filter = bsc_nat_filter_dt(bsc, msg,
+							con, parsed, &cause);
+					if (filter < 0) {
+						if (con->filter_state.imsi)
+							bsc_nat_inform_reject(bsc,
+								con->filter_state.imsi);
+						bsc_stat_reject(filter, bsc, 1);
+						bsc_send_con_release(bsc, con, &cause);
+						con = NULL;
+						goto exit2;
+					}
+
+					/* hand data to a side channel */
+					if (bsc_ussd_check(con, parsed, msg) == 1)
+						con->con_local = NAT_CON_END_USSD;
+
+					/*
+					 * Optionally rewrite setup message. This can
+					 * replace the msg and the parsed structure becomes
+					 * invalid.
+					 */
+					msg = bsc_nat_rewrite_msg(bsc->nat, msg, parsed,
+									con->filter_state.imsi);
+					talloc_free(parsed);
+					parsed = NULL;
+				} else if (con->con_local == NAT_CON_END_USSD) {
+					bsc_ussd_check(con, parsed, msg);
+				}
+
+				con_bsc = con->bsc;
+				con_msc = con->msc_con;
+				con_filter = con->con_local;
+			}
+
+			break;
+		case SCCP_MSG_TYPE_RLC:
+			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+			if (con) {
+				con_bsc = con->bsc;
+				con_msc = con->msc_con;
+				con_filter = con->con_local;
+			}
+			remove_sccp_src_ref(bsc, msg, parsed);
+			*bsc_conn_closed = bsc_maybe_close(bsc);
+			break;
+		case SCCP_MSG_TYPE_UDT:
+			/* simply forward everything */
+			con = NULL;
+			break;
+		default:
+			LOGP(DNAT, LOGL_ERROR, "Not forwarding to msc sccp type: 0x%x\n", parsed->sccp_type);
+			con = NULL;
+			goto exit2;
+			break;
+		}
+        } else if (parsed->ipa_proto == IPAC_PROTO_MGCP_OLD) {
+                bsc_mgcp_forward(bsc, msg);
+                goto exit2;
+	} else {
+		LOGP(DNAT, LOGL_ERROR, "Not forwarding unknown stream id: 0x%x\n", parsed->ipa_proto);
+		goto exit2;
+	}
+
+	if (con_msc && con_bsc != bsc) {
+		LOGP(DNAT, LOGL_ERROR, "The connection belongs to a different BTS: input: %d con: %d\n",
+		     bsc->cfg->nr, con_bsc->cfg->nr);
+		goto exit2;
+	}
+
+	/* do not forward messages to the MSC */
+	if (con_filter)
+		goto exit2;
+
+	if (!con_msc) {
+		LOGP(DNAT, LOGL_ERROR, "Not forwarding data bsc_nr: %d ipa: %d type: 0x%x\n",
+			bsc->cfg->nr,
+			parsed ? parsed->ipa_proto : -1,
+			parsed ? parsed->sccp_type : -1);
+		goto exit2;
+	}
+
+	/* send the non-filtered but maybe modified msg */
+	queue_for_msc(con_msc, msg);
+	if (parsed)
+		talloc_free(parsed);
+	return 0;
+
+exit:
+	/* if we filter out the reset send an ack to the BSC */
+	if (parsed->bssap == 0 && parsed->gsm_type == BSS_MAP_MSG_RESET) {
+		send_reset_ack(bsc);
+	} else if (parsed->ipa_proto == IPAC_PROTO_IPACCESS) {
+		/* do we know who is handling this? */
+		if (msg->l2h[0] == IPAC_MSGT_ID_RESP && msgb_l2len(msg) > 2) {
+			struct tlv_parsed tvp;
+			int ret;
+			ret = ipa_ccm_idtag_parse_off(&tvp,
+					     (unsigned char *) msg->l2h + 2,
+					     msgb_l2len(msg) - 2, 0);
+			if (ret < 0) {
+				LOGP(DNAT, LOGL_ERROR, "ignoring IPA response "
+					"message with malformed TLVs\n");
+				return ret;
+			}
+			if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME)) {
+				if (!ipaccess_auth_bsc(&tvp, bsc)) {
+					bsc_close_connection(bsc);
+					*bsc_conn_closed = true;
+				}
+			}
+		}
+	}
+
+exit2:
+	if (imsi)
+		talloc_free(imsi);
+	talloc_free(parsed);
+	msgb_free(msg);
+	return -1;
+}
+
+static int ipaccess_bsc_read_cb(struct osmo_fd *bfd)
+{
+	struct bsc_connection *bsc = bfd->data;
+	struct msgb *msg = NULL;
+	struct ipaccess_head *hh;
+	struct ipaccess_head_ext *hh_ext;
+	bool fd_closed = false;
+	int ret;
+
+	ret = ipa_msg_recv_buffered(bfd->fd, &msg, &bsc->pending_msg);
+	if (ret == -EAGAIN) {
+		return 0;
+	} else if (ret == 0) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "The connection to the BSC Nr: %d was lost. Cleaning it\n",
+		     bsc->cfg ? bsc->cfg->nr : -1);
+		goto close_fd;
+	} else if (ret < 0) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "Stream error on BSC Nr: %d. Failed to parse ip access message: %d (%s)\n",
+		     bsc->cfg ? bsc->cfg->nr : -1, ret, strerror(-ret));
+		 goto close_fd;
+	}
+
+
+	LOGP(DNAT, LOGL_DEBUG, "MSG from BSC: %s proto: %d\n", osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+
+	/* Handle messages from the BSC */
+	hh = (struct ipaccess_head *) msg->data;
+
+	/* stop the pong timeout */
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_PONG) {
+			osmo_timer_del(&bsc->pong_timeout);
+			msgb_free(msg);
+			return 0;
+		} else if (msg->l2h[0] == IPAC_MSGT_PING) {
+			send_pong(bsc);
+			msgb_free(msg);
+			return 0;
+		}
+	/* Message contains the ipaccess_head_ext header, investigate further */
+	} else if (hh->proto == IPAC_PROTO_OSMO &&
+		   msg->len > sizeof(*hh) + sizeof(*hh_ext)) {
+
+		hh_ext = (struct ipaccess_head_ext *) hh->data;
+		/* l2h is where the actual command data is expected */
+		msg->l2h = hh_ext->data;
+
+		if (hh_ext->proto == IPAC_PROTO_EXT_CTRL)
+			return bsc_nat_handle_ctrlif_msg(bsc, msg);
+	}
+
+	/* FIXME: Currently no ID ACK is sent to the BSC */
+	forward_sccp_to_msc(bsc, msg, &fd_closed);
+	return fd_closed ? -EBADF : 0;
+
+close_fd:
+	bsc_close_connection(bsc);
+	return -EBADF;
+}
+
+static int ipaccess_listen_bsc_cb(struct osmo_fd *bfd, unsigned int what)
+{
+	struct bsc_connection *bsc;
+	int fd, rc, on;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		perror("accept");
+		return fd;
+	}
+
+	/* count the reconnect */
+	osmo_counter_inc(nat->stats.bsc.reconn);
+
+	/*
+	 * if we are not connected to a msc... just close the socket
+	 */
+	if (!bsc_nat_msc_is_connected(nat)) {
+		LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due lack of MSC connection.\n");
+		close(fd);
+		return 0;
+	}
+
+	if (nat->blocked) {
+		LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due NAT being blocked.\n");
+		close(fd);
+		return 0;
+	}
+
+	on = 1;
+	rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+	if (rc != 0)
+                LOGP(DNAT, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
+
+	rc = setsockopt(fd, IPPROTO_IP, IP_TOS,
+			&nat->bsc_ip_dscp, sizeof(nat->bsc_ip_dscp));
+	if (rc != 0)
+		LOGP(DNAT, LOGL_ERROR, "Failed to set IP_TOS: %s\n", strerror(errno));
+
+	/* todo... use GNUtls to see if we want to trust this as a BTS */
+
+	bsc = bsc_connection_alloc(nat);
+	if (!bsc) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate BSC struct.\n");
+		close(fd);
+		return -1;
+	}
+
+	bsc->write_queue.bfd.data = bsc;
+	bsc->write_queue.bfd.fd = fd;
+	bsc->write_queue.read_cb = ipaccess_bsc_read_cb;
+	bsc->write_queue.write_cb = bsc_write_cb;
+	bsc->write_queue.bfd.when = BSC_FD_READ;
+	if (osmo_fd_register(&bsc->write_queue.bfd) < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to register BSC fd.\n");
+		close(fd);
+		talloc_free(bsc);
+		return -2;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "BSC connection on %d with IP: %s\n",
+		fd, inet_ntoa(sa.sin_addr));
+
+	llist_add(&bsc->list_entry, &nat->bsc_connections);
+	bsc->last_id = 0;
+
+	send_id_ack(bsc);
+	send_id_req(nat, bsc);
+	send_mgcp_reset(bsc);
+
+	/*
+	 * start the hangup timer
+	 */
+	osmo_timer_setup(&bsc->id_timeout, ipaccess_close_bsc, bsc);
+	osmo_timer_schedule(&bsc->id_timeout, nat->auth_timeout, 0);
+	return 0;
+}
+
+static void print_usage()
+{
+	printf("Usage: bsc_nat\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+	printf("  -D --daemonize Fork the process into a background daemon\n");
+	printf("  -s --disable-color\n");
+	printf("  -T --timestamp. Print a timestamp in the debug output.\n");
+	printf("  -V --version. Print the version of OsmoBSCNAT.\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -m --msc=IP. The address of the MSC.\n");
+	printf("  -l --local=IP. The local address of this BSC.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"daemonize", 0, 0, 'D'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"version", 0, 0, 'V' },
+			{"msc", 1, 0, 'm'},
+			{"local", 1, 0, 'l'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:sTVPc:m:l:D",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(osmo_stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(osmo_stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'c':
+			config_file = optarg;
+			break;
+		case 'T':
+			log_set_print_timestamp(osmo_stderr_target, 1);
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		case 'm':
+			msc_ip = optarg;
+			break;
+		case 'l':
+			inet_aton(optarg, &local_addr);
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+static void signal_handler(int signal)
+{
+	switch (signal) {
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+static void sccp_close_unconfirmed(void *_data)
+{
+	int destroyed = 0;
+	struct bsc_connection *bsc, *bsc_tmp;
+	struct nat_sccp_connection *conn, *tmp1;
+	struct timespec now;
+	clock_gettime(CLOCK_MONOTONIC, &now);
+
+	llist_for_each_entry_safe(conn, tmp1, &nat->sccp_connections, list_entry) {
+		if (conn->has_remote_ref)
+			continue;
+
+		int diff = (now.tv_sec - conn->creation_time.tv_sec) / 60;
+		if (diff < SCCP_CLOSE_TIME_TIMEOUT)
+			continue;
+
+		LOGP(DNAT, LOGL_ERROR,
+			"SCCP connection 0x%x/0x%x was never confirmed on bsc nr. %d\n",
+			sccp_src_ref_to_int(&conn->real_ref),
+			sccp_src_ref_to_int(&conn->patched_ref),
+			conn->bsc->cfg->nr);
+		sccp_connection_destroy(conn);
+		destroyed = 1;
+	}
+
+	if (!destroyed)
+		goto out;
+
+	/* now close out any BSC */
+	llist_for_each_entry_safe(bsc, bsc_tmp, &nat->bsc_connections, list_entry)
+		bsc_maybe_close(bsc);
+
+out:
+	osmo_timer_schedule(&sccp_close, SCCP_CLOSE_TIME, 0);
+}
+
+extern void *tall_ctr_ctx;
+static void talloc_init_ctx()
+{
+	tall_bsc_ctx = talloc_named_const(NULL, 0, "nat");
+	msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+	tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OsmoBSCNAT",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	talloc_init_ctx();
+
+	osmo_init_logging(&log_info);
+
+	nat = bsc_nat_alloc();
+	if (!nat) {
+		fprintf(stderr, "Failed to allocate the BSC nat.\n");
+		return -4;
+	}
+
+	nat->mgcp_cfg = mgcp_config_alloc();
+	if (!nat->mgcp_cfg) {
+		fprintf(stderr, "Failed to allocate MGCP cfg.\n");
+		return -5;
+	}
+
+	/* We need to add mode-set for amr codecs */
+	nat->sdp_ensure_amr_mode_set = 1;
+
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	logging_vty_add_cmds(NULL);
+	osmo_stats_vty_add_cmds(&log_info);
+	bsc_nat_vty_init(nat);
+	ctrl_vty_init(tall_bsc_ctx);
+
+
+	/* parse options */
+	local_addr.s_addr = INADDR_ANY;
+	handle_options(argc, argv);
+
+	nat->include_base = dirname(talloc_strdup(tall_bsc_ctx, config_file));
+
+	rate_ctr_init(tall_bsc_ctx);
+	osmo_stats_init(tall_bsc_ctx);
+
+	/* Ensure that forced enpoint allocation is turned on by default */
+	nat->mgcp_cfg->trunk.force_realloc = 1;
+
+	/* init vty and parse */
+	if (mgcp_parse_config(config_file, nat->mgcp_cfg, MGCP_BSC_NAT) < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return -3;
+	}
+
+	/* start telnet after reading config for vty_get_bind_addr() */
+	if (telnet_init_dynif(tall_bsc_ctx, NULL, vty_get_bind_addr(),
+			      OSMO_VTY_PORT_BSC_NAT)) {
+		fprintf(stderr, "Creating VTY telnet line failed\n");
+		return -5;
+	}
+
+	/* over rule the VTY config for MSC IP */
+	if (msc_ip)
+		bsc_nat_set_msc_ip(nat, msc_ip);
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	LOGP(DNAT, LOGL_NOTICE, "BSCs configured from %s\n", nat->resolved_path);
+
+	/*
+	 * Setup the MGCP code..
+	 */
+	if (bsc_mgcp_nat_init(nat) != 0)
+		return -4;
+
+	/* connect to the MSC */
+	nat->msc_con = bsc_msc_create(nat, &nat->dests);
+	if (!nat->msc_con) {
+		fprintf(stderr, "Creating a bsc_msc_connection failed.\n");
+		exit(1);
+	}
+
+	/* start control interface after reading config for
+	 * ctrl_vty_get_bind_addr() */
+	nat->ctrl = bsc_nat_controlif_setup(nat, ctrl_vty_get_bind_addr(),
+					    OSMO_CTRL_PORT_BSC_NAT);
+	if (!nat->ctrl) {
+		fprintf(stderr, "Creating the control interface failed.\n");
+		exit(1);
+	}
+
+	nat->msc_con->name = "main MSC";
+	nat->msc_con->connection_loss = msc_connection_was_lost;
+	nat->msc_con->connected = msc_connection_connected;
+	nat->msc_con->write_queue.read_cb = ipaccess_msc_read_cb;
+	nat->msc_con->write_queue.write_cb = ipaccess_msc_write_cb;
+	nat->msc_con->write_queue.bfd.data = nat->msc_con;
+	bsc_msc_connect(nat->msc_con);
+
+	/* wait for the BSC */
+	rc = make_sock(&bsc_listen, IPPROTO_TCP, ntohl(local_addr.s_addr),
+		       5000, 0, ipaccess_listen_bsc_cb, nat);
+	if (rc != 0) {
+		fprintf(stderr, "Failed to listen for BSC.\n");
+		exit(1);
+	}
+
+	rc = bsc_ussd_init(nat);
+	if (rc != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to bind the USSD socket.\n");
+		exit(1);
+	}
+
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	osmo_init_ignore_signals();
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	/* recycle timer */
+	sccp_set_log_area(DSCCP);
+	osmo_timer_setup(&sccp_close, sccp_close_unconfirmed, NULL);
+	osmo_timer_schedule(&sccp_close, SCCP_CLOSE_TIME, 0);
+
+	while (1) {
+		osmo_select_main(0);
+	}
+
+	return 0;
+}
+
+/* Close all connections handed out to the USSD module */
+int bsc_ussd_close_connections(struct bsc_nat *nat)
+{
+	struct nat_sccp_connection *con;
+	llist_for_each_entry(con, &nat->sccp_connections, list_entry) {
+		if (con->con_local != NAT_CON_END_USSD)
+			continue;
+		if (!con->bsc)
+			continue;
+
+		nat_send_clrc_bsc(con);
+		nat_send_rlsd_bsc(con);
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c b/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c
new file mode 100644
index 0000000..152929c
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c
@@ -0,0 +1,536 @@
+/*
+ * (C) 2011-2012 by Holger Hans Peter Freyther
+ * (C) 2011-2012 by On-Waves
+ * (C) 2011 by Daniel Willmann
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <osmocom/vty/misc.h>
+
+#include <openbsc/ctrl.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+
+#define NAT_MAX_CTRL_ID 65535
+
+static struct bsc_nat *g_nat;
+
+static int bsc_id_unused(int id, struct bsc_connection *bsc)
+{
+	struct bsc_cmd_list *pending;
+
+	llist_for_each_entry(pending, &bsc->cmd_pending, list_entry) {
+		if (pending->nat_id == id)
+			return 0;
+	}
+	return 1;
+}
+
+static int get_next_free_bsc_id(struct bsc_connection *bsc)
+{
+	int new_id, overflow = 0;
+
+	new_id = bsc->last_id;
+
+	do {
+		new_id++;
+		if (new_id == NAT_MAX_CTRL_ID) {
+			new_id = 1;
+			overflow++;
+		}
+
+		if (bsc_id_unused(new_id, bsc)) {
+			bsc->last_id = new_id;
+			return new_id;
+		}
+	} while (overflow != 2);
+
+	return -1;
+}
+
+void bsc_nat_ctrl_del_pending(struct bsc_cmd_list *pending)
+{
+	llist_del(&pending->list_entry);
+	osmo_timer_del(&pending->timeout);
+	talloc_free(pending);
+}
+
+static struct bsc_cmd_list *bsc_get_pending(struct bsc_connection *bsc, char *id_str)
+{
+	struct bsc_cmd_list *cmd_entry;
+	char * endptr = NULL;
+	long int long_id;
+	int id;
+
+	errno = 0;
+	long_id = (int) strtol(id_str, &endptr, 10);
+	 /* check parse errors */
+	if (id_str[0] == '\0' || endptr[0] != '\0')
+		return NULL;
+	/* check value store errors */
+	if (errno == ERANGE || long_id > INT_MAX || long_id < 0)
+		return NULL;
+
+	id = (int) long_id;
+	llist_for_each_entry(cmd_entry, &bsc->cmd_pending, list_entry) {
+		if (cmd_entry->nat_id == id) {
+			return cmd_entry;
+		}
+	}
+	return NULL;
+}
+
+int bsc_nat_handle_ctrlif_msg(struct bsc_connection *bsc, struct msgb *msg)
+{
+	struct ctrl_cmd *cmd;
+	bool parse_failed;
+	struct bsc_cmd_list *pending;
+	char *var;
+
+	cmd = ctrl_cmd_parse3(bsc, msg, &parse_failed);
+	msgb_free(msg);
+
+	if (cmd->type == CTRL_TYPE_ERROR && parse_failed)
+		goto err;
+
+	if (bsc->cfg && !llist_empty(&bsc->cfg->lac_list)) {
+		if (cmd->variable) {
+			var = talloc_asprintf(cmd, "net.0.bsc.%i.%s", bsc->cfg->nr,
+					   cmd->variable);
+			if (!var) {
+				cmd->type = CTRL_TYPE_ERROR;
+				cmd->reply = "OOM";
+				goto err;
+			}
+			talloc_free(cmd->variable);
+			cmd->variable = var;
+		}
+
+		/* We have to handle TRAPs before matching pending */
+		if (cmd->type == CTRL_TYPE_TRAP) {
+			ctrl_cmd_send_to_all(bsc->nat->ctrl, cmd);
+			talloc_free(cmd);
+			return 0;
+		}
+
+		/* Find the pending command */
+		pending = bsc_get_pending(bsc, cmd->id);
+		if (pending) {
+			osmo_talloc_replace_string(cmd, &cmd->id, pending->cmd->id);
+			if (!cmd->id) {
+				cmd->type = CTRL_TYPE_ERROR;
+				cmd->reply = "OOM";
+				goto err;
+			}
+			ctrl_cmd_send(&pending->cmd->ccon->write_queue, cmd);
+			bsc_nat_ctrl_del_pending(pending);
+		} else {
+			if ((cmd->type == CTRL_TYPE_ERROR)) {
+				LOGP(DNAT, LOGL_NOTICE, "Got ERROR CTRL message "
+				     "from BSC without pending/valid ID %s: %s\n",
+				     cmd->id, cmd->reply);
+			} else {
+				LOGP(DNAT, LOGL_NOTICE, "Got %s CTRL message "
+				     "from BSC without pending entry\n",
+				     get_value_string(ctrl_type_vals, cmd->type));
+				cmd->type = CTRL_TYPE_ERROR;
+				cmd->reply = "No request outstanding";
+				goto err;
+			}
+		}
+	}
+	talloc_free(cmd);
+	return 0;
+err:
+	ctrl_cmd_send(&bsc->write_queue, cmd);
+	talloc_free(cmd);
+	return 0;
+}
+
+static void pending_timeout_cb(void *data)
+{
+	struct bsc_cmd_list *pending = data;
+	LOGP(DNAT, LOGL_ERROR, "Command timed out\n");
+	pending->cmd->type = CTRL_TYPE_ERROR;
+	pending->cmd->reply = "Command timed out";
+	ctrl_cmd_send(&pending->cmd->ccon->write_queue, pending->cmd);
+
+	bsc_nat_ctrl_del_pending(pending);
+}
+
+static void ctrl_conn_closed_cb(struct ctrl_connection *connection)
+{
+	struct bsc_connection *bsc;
+	struct bsc_cmd_list *pending, *tmp;
+
+	llist_for_each_entry(bsc, &g_nat->bsc_connections, list_entry) {
+		llist_for_each_entry_safe(pending, tmp, &bsc->cmd_pending, list_entry) {
+			if (pending->cmd->ccon == connection)
+				bsc_nat_ctrl_del_pending(pending);
+		}
+	}
+}
+
+static int extract_bsc_nr_variable(char *variable, unsigned int *nr, char **bsc_variable)
+{
+	char *nr_str, *tmp, *saveptr = NULL;
+
+	tmp = strtok_r(variable, ".", &saveptr);
+	tmp = strtok_r(NULL, ".", &saveptr);
+	tmp = strtok_r(NULL, ".", &saveptr);
+	nr_str = strtok_r(NULL, ".", &saveptr);
+	if (!nr_str)
+		return 0;
+	*nr = atoi(nr_str);
+
+	tmp = strtok_r(NULL, "\0", &saveptr);
+	if (!tmp)
+		return 0;
+
+	*bsc_variable = tmp;
+	return 1;
+}
+
+static int forward_to_bsc(struct ctrl_cmd *cmd)
+{
+	int ret = CTRL_CMD_HANDLED;
+	struct ctrl_cmd *bsc_cmd = NULL;
+	struct bsc_connection *bsc;
+	struct bsc_cmd_list *pending = NULL;
+	unsigned int nr;
+	char *bsc_variable;
+
+	/* Skip over the beginning (bsc.) */
+	if (!extract_bsc_nr_variable(cmd->variable, &nr, &bsc_variable)) {
+		cmd->reply = "command incomplete";
+		goto err;
+	}
+
+
+	llist_for_each_entry(bsc, &g_nat->bsc_connections, list_entry) {
+		if (!bsc->cfg)
+			continue;
+		if (!bsc->authenticated)
+			continue;
+		if (bsc->cfg->nr != nr)
+			continue;
+
+		/* Add pending command to list */
+		pending = talloc_zero(bsc, struct bsc_cmd_list);
+		if (!pending) {
+			cmd->reply = "OOM";
+			goto err;
+		}
+
+		pending->nat_id = get_next_free_bsc_id(bsc);
+		if (pending->nat_id < 0) {
+			cmd->reply = "No free ID found";
+			goto err;
+		}
+
+		bsc_cmd = ctrl_cmd_cpy(bsc, cmd);
+		if (!bsc_cmd) {
+			cmd->reply = "Could not forward command";
+			goto err;
+		}
+
+		talloc_free(bsc_cmd->id);
+		bsc_cmd->id = talloc_asprintf(bsc_cmd, "%i", pending->nat_id);
+		if (!bsc_cmd->id) {
+			cmd->reply = "OOM";
+			goto err;
+		}
+
+		talloc_free(bsc_cmd->variable);
+		bsc_cmd->variable = talloc_strdup(bsc_cmd, bsc_variable);
+		if (!bsc_cmd->variable) {
+			cmd->reply = "OOM";
+			goto err;
+		}
+
+		if (ctrl_cmd_send(&bsc->write_queue, bsc_cmd)) {
+			cmd->reply = "Sending failed";
+			goto err;
+		}
+
+		/* caller owns cmd param and will destroy it after we return */
+		pending->cmd = ctrl_cmd_cpy(pending, cmd);
+		if (!pending->cmd) {
+			cmd->reply = "Could not answer command";
+			goto err;
+		}
+		cmd->ccon->closed_cb = ctrl_conn_closed_cb;
+		pending->cmd->ccon = cmd->ccon;
+
+		/* Setup the timeout */
+		osmo_timer_setup(&pending->timeout, pending_timeout_cb,
+				 pending);
+		/* TODO: Make timeout configurable */
+		osmo_timer_schedule(&pending->timeout, 10, 0);
+		llist_add_tail(&pending->list_entry, &bsc->cmd_pending);
+
+		goto done;
+	}
+	/* We end up here if there's no bsc to handle our LAC */
+	cmd->reply = "no BSC with this nr";
+err:
+	ret = CTRL_CMD_ERROR;
+	talloc_free(pending);
+done:
+	talloc_free(bsc_cmd);
+	return ret;
+
+}
+
+
+CTRL_CMD_DEFINE(fwd_cmd, "net 0 bsc *");
+static int get_fwd_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	return forward_to_bsc(cmd);
+}
+
+static int set_fwd_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	return forward_to_bsc(cmd);
+}
+
+static int verify_fwd_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	return 0;
+}
+
+static int extract_bsc_cfg_variable(struct ctrl_cmd *cmd, struct bsc_config **cfg,
+				char **bsc_variable)
+{
+	unsigned int nr;
+
+	if (!extract_bsc_nr_variable(cmd->variable, &nr, bsc_variable)) {
+		cmd->reply = "command incomplete";
+		return 0;
+	}
+
+	*cfg = bsc_config_num(g_nat, nr);
+	if (!*cfg) {
+		cmd->reply = "Unknown BSC";
+		return 0;
+	}
+
+	return 1;
+}
+
+CTRL_CMD_DEFINE(net_cfg_cmd, "net 0 bsc_cfg *");
+static int get_net_cfg_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	char *bsc_variable;
+	struct bsc_config *bsc_cfg;
+
+	if (!extract_bsc_cfg_variable(cmd, &bsc_cfg, &bsc_variable))
+		return CTRL_CMD_ERROR;
+
+	if (strcmp(bsc_variable, "access-list-name") == 0) {
+		cmd->reply = talloc_asprintf(cmd, "%s",
+				bsc_cfg->acc_lst_name ? bsc_cfg->acc_lst_name : "");
+		return CTRL_CMD_REPLY;
+	}
+
+	cmd->reply = "unknown command";
+	return CTRL_CMD_ERROR;
+}
+
+static int set_net_cfg_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	char *bsc_variable;
+	struct bsc_config *bsc_cfg;
+
+	if (!extract_bsc_cfg_variable(cmd, &bsc_cfg, &bsc_variable))
+		return CTRL_CMD_ERROR;
+
+	if (strcmp(bsc_variable, "access-list-name") == 0) {
+		osmo_talloc_replace_string(bsc_cfg, &bsc_cfg->acc_lst_name, cmd->value);
+		cmd->reply = talloc_asprintf(cmd, "%s",
+				bsc_cfg->acc_lst_name ? bsc_cfg->acc_lst_name : "");
+		return CTRL_CMD_REPLY;
+	} else if (strcmp(bsc_variable, "no-access-list-name") == 0) {
+		talloc_free(bsc_cfg->acc_lst_name);
+		bsc_cfg->acc_lst_name = NULL;
+		cmd->reply = "";
+		return CTRL_CMD_REPLY;
+	}
+
+	cmd->reply = "unknown command";
+	return CTRL_CMD_ERROR;
+}
+
+static int verify_net_cfg_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	return 0;
+}
+
+CTRL_CMD_DEFINE(net_cfg_acc_cmd, "net 0 add allow access-list *");
+static const char *extract_acc_name(const char *var)
+{
+	char *str;
+
+	str = strstr(var, "net.0.add.allow.access-list.");
+	if (!str)
+		return NULL;
+	str += strlen("net.0.add.allow.access-list.");
+	if (strlen(str) == 0)
+		return NULL;
+	return str;
+}
+
+static int get_net_cfg_acc_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	cmd->reply = "Append only";
+	return CTRL_CMD_ERROR;
+}
+
+static int set_net_cfg_acc_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	const char *access_name = extract_acc_name(cmd->variable);
+	struct bsc_msg_acc_lst *acc;
+	struct bsc_msg_acc_lst_entry *entry;
+	const char *value = cmd->value;
+	int rc;
+
+	/* Should have been caught by verify_net_cfg_acc_cmd */
+	acc = bsc_msg_acc_lst_find(&g_nat->access_lists, access_name);
+	if (!acc) {
+		cmd->reply = "Access list not found";
+		return CTRL_CMD_ERROR;
+	}
+
+	entry = bsc_msg_acc_lst_entry_create(acc);
+	if (!entry) {
+		cmd->reply = "OOM";
+		return CTRL_CMD_ERROR;
+	}
+
+	rc = gsm_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, 1, &value);
+	if (rc !=  0) {
+		cmd->reply = "Failed to compile expression";
+		return CTRL_CMD_ERROR;
+	}
+
+	cmd->reply = "IMSI allow added to access list";
+	return CTRL_CMD_REPLY;
+}
+
+static int verify_net_cfg_acc_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	const char *access_name = extract_acc_name(cmd->variable);
+	struct bsc_msg_acc_lst *acc = bsc_msg_acc_lst_find(&g_nat->access_lists, access_name);
+
+	if (!acc) {
+		cmd->reply = "Access list not known";
+		return -1;
+	}
+
+	return 0;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_save_cmd, "net 0 save-configuration");
+
+static int set_net_save_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	int rc = osmo_vty_save_config_file();
+	cmd->reply = talloc_asprintf(cmd, "%d", rc);
+	if (!cmd->reply) {
+		cmd->reply = "OOM";
+		return CTRL_CMD_ERROR;
+	}
+
+	return CTRL_CMD_REPLY;
+}
+
+struct ctrl_handle *bsc_nat_controlif_setup(struct bsc_nat *nat,
+					    const char *bind_addr, int port)
+{
+	struct ctrl_handle *ctrl;
+	int rc;
+
+
+	ctrl = bsc_controlif_setup(NULL, bind_addr, OSMO_CTRL_PORT_BSC_NAT);
+	if (!ctrl) {
+		fprintf(stderr, "Failed to initialize the control interface. Exiting.\n");
+		return NULL;
+	}
+
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_fwd_cmd);
+	if (rc) {
+		fprintf(stderr, "Failed to install the control command. Exiting.\n");
+		goto error;
+	}
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_cfg_cmd);
+	if (rc) {
+		fprintf(stderr, "Failed to install the net cfg command. Exiting.\n");
+		goto error;
+	}
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_cfg_acc_cmd);
+	if (rc) {
+		fprintf(stderr, "Failed to install the net acc command. Exiting.\n");
+		goto error;
+	}
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_save_cmd);
+	if (rc) {
+		fprintf(stderr, "Failed to install the net save command. Exiting.\n");
+		goto error;
+	}
+
+	g_nat = nat;
+	return ctrl;
+
+error:
+	osmo_fd_unregister(&ctrl->listen_fd);
+	close(ctrl->listen_fd.fd);
+	talloc_free(ctrl);
+	return NULL;
+}
+
+void bsc_nat_inform_reject(struct bsc_connection *conn, const char *imsi)
+{
+	struct ctrl_cmd *cmd;
+
+	cmd = ctrl_cmd_create(conn, CTRL_TYPE_TRAP);
+	if (!cmd) {
+		LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
+		return;
+	}
+
+	cmd->id = "0";
+	cmd->variable = talloc_asprintf(cmd, "net.0.bsc.%d.notification-rejection-v1",
+					conn->cfg->nr);
+	cmd->reply = talloc_asprintf(cmd, "imsi=%s", imsi);
+
+	ctrl_cmd_send_to_all(conn->cfg->nat->ctrl, cmd);
+	talloc_free(cmd);
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c b/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c
new file mode 100644
index 0000000..e735290
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c
@@ -0,0 +1,119 @@
+/*
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/* Filter out CR data... */
+int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg,
+			struct bsc_nat_parsed *parsed, int *con_type,
+			char **imsi, struct bsc_filter_reject_cause *cause)
+{
+	struct bsc_filter_request req;
+	struct tlv_parsed tp;
+	struct gsm48_hdr *hdr48;
+	int hdr48_len;
+	int len;
+
+	*con_type = FLT_CON_TYPE_NONE;
+	cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	*imsi = NULL;
+
+	if (parsed->gsm_type != BSS_MAP_MSG_COMPLETE_LAYER_3) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "Rejecting CR message due wrong GSM Type %d\n", parsed->gsm_type);
+		return -1;
+	}
+
+	/* the parsed has had some basic l3 length check */
+	len = msg->l3h[1];
+	if (msgb_l3len(msg) - 3 < len) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "The CR Data has not enough space...\n");
+		return -1;
+	}
+
+	msg->l4h = &msg->l3h[3];
+	len -= 1;
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h, len, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) {
+		LOGP(DNAT, LOGL_ERROR, "CR Data does not contain layer3 information.\n");
+		return -1;
+	}
+
+	hdr48_len = TLVP_LEN(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+
+	if (hdr48_len < sizeof(*hdr48)) {
+		LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n");
+		return -1;
+	}
+
+	hdr48 = (struct gsm48_hdr *) TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+	req.ctx = bsc;
+	req.black_list = &bsc->nat->imsi_black_list;
+	req.access_lists = &bsc->nat->access_lists;
+	req.local_lst_name = bsc->cfg->acc_lst_name;
+	req.global_lst_name = bsc->nat->acc_lst_name;
+	req.bsc_nr = bsc->cfg->nr;
+	return bsc_msg_filter_initial(hdr48, hdr48_len, &req, con_type, imsi, cause);
+}
+
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+		struct nat_sccp_connection *con, struct bsc_nat_parsed *parsed,
+		struct bsc_filter_reject_cause *cause)
+{
+	uint32_t len;
+	struct gsm48_hdr *hdr48;
+	struct bsc_filter_request req;
+
+	cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+	cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+
+	if (con->filter_state.imsi_checked)
+		return 0;
+
+	/* only care about DTAP messages */
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return 0;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return -1;
+
+	req.ctx = con;
+	req.black_list = &bsc->nat->imsi_black_list;
+	req.access_lists = &bsc->nat->access_lists;
+	req.local_lst_name = bsc->cfg->acc_lst_name;
+	req.global_lst_name = bsc->nat->acc_lst_name;
+	req.bsc_nr = bsc->cfg->nr;
+	return bsc_msg_filter_data(hdr48, len, &req, &con->filter_state, cause);
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_rewrite.c b/openbsc/src/osmo-bsc_nat/bsc_nat_rewrite.c
new file mode 100644
index 0000000..e7c387c
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_rewrite.c
@@ -0,0 +1,714 @@
+/*
+ * Message rewriting functionality
+ */
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/nat_rewrite_trie.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/sccp/sccp.h>
+
+static char *trie_lookup(struct nat_rewrite *trie, const char *number,
+			regoff_t off, void *ctx)
+{
+	struct nat_rewrite_rule *rule;
+
+	if (!trie) {
+		LOGP(DCC, LOGL_ERROR,
+			"Asked to do a table lookup but no table.\n");
+		return NULL;
+	}
+
+	rule = nat_rewrite_lookup(trie, number);
+	if (!rule) {
+		LOGP(DCC, LOGL_DEBUG,
+			"Couldn't find a prefix rule for %s\n", number);
+		return NULL;
+	}
+
+	return talloc_asprintf(ctx, "%s%s", rule->rewrite, &number[off]);
+}
+
+static char *match_and_rewrite_number(void *ctx, const char *number,
+				const char *imsi, struct llist_head *list,
+				struct nat_rewrite *trie)
+{
+	struct bsc_nat_num_rewr_entry *entry;
+	char *new_number = NULL;
+
+	/* need to find a replacement and then fix it */
+	llist_for_each_entry(entry, list, list) {
+		regmatch_t matches[2];
+
+		/* check the IMSI match */
+		if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+			continue;
+
+		/* this regexp matches... */
+		if (regexec(&entry->num_reg, number, 2, matches, 0) == 0
+			&& matches[1].rm_eo != -1) {
+			if (entry->is_prefix_lookup)
+				new_number = trie_lookup(trie, number,
+						matches[1].rm_so, ctx);
+			else
+				new_number = talloc_asprintf(ctx, "%s%s",
+					entry->replace,
+					&number[matches[1].rm_so]);
+		}
+
+		if (new_number)
+			break;
+	}
+
+	return new_number;
+}
+
+static char *rewrite_isdn_number(struct bsc_nat *nat, struct llist_head *rewr_list,
+				void *ctx, const char *imsi,
+				struct gsm_mncc_number *called)
+{
+	char int_number[sizeof(called->number) + 2];
+	char *number = called->number;
+
+	if (llist_empty(&nat->num_rewr)) {
+		LOGP(DCC, LOGL_DEBUG, "Rewrite rules empty.\n");
+		return NULL;
+	}
+
+	/* only ISDN plan */
+	if (called->plan != 1) {
+		LOGP(DCC, LOGL_DEBUG, "Called plan is not 1 it was %d\n",
+			called->plan);
+		return NULL;
+	}
+
+	/* international, prepend */
+	if (called->type == 1) {
+		int_number[0] = '+';
+		memcpy(&int_number[1], number, strlen(number) + 1);
+		number = int_number;
+	}
+
+	return match_and_rewrite_number(ctx, number,
+					imsi, rewr_list, nat->num_rewr_trie);
+}
+
+static void update_called_number(struct gsm_mncc_number *called,
+				const char *chosen_number)
+{
+	if (strncmp(chosen_number, "00", 2) == 0) {
+		called->type = 1;
+		osmo_strlcpy(called->number, chosen_number + 2,
+			     sizeof(called->number));
+	} else {
+		/* rewrite international to unknown */
+		if (called->type == 1)
+			called->type = 0;
+		osmo_strlcpy(called->number, chosen_number,
+			     sizeof(called->number));
+	}
+}
+
+/**
+ * Rewrite non global numbers... according to rules based on the IMSI
+ */
+static struct msgb *rewrite_setup(struct bsc_nat *nat, struct msgb *msg,
+				  struct bsc_nat_parsed *parsed, const char *imsi,
+				  struct gsm48_hdr *hdr48, const uint32_t len)
+{
+	struct tlv_parsed tp;
+	unsigned int payload_len;
+	struct gsm_mncc_number called;
+	struct msgb *out;
+	char *new_number_pre = NULL, *new_number_post = NULL, *chosen_number;
+	uint8_t *outptr;
+	const uint8_t *msgptr;
+	int sec_len;
+
+	/* decode and rewrite the message */
+	payload_len = len - sizeof(*hdr48);
+	tlv_parse(&tp, &gsm48_att_tlvdef, hdr48->data, payload_len, 0, 0);
+
+	/* no number, well let us ignore it */
+	if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD))
+		return NULL;
+
+	memset(&called, 0, sizeof(called));
+	gsm48_decode_called(&called,
+			    TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+	/* check if it looks international and stop */
+	LOGP(DCC, LOGL_DEBUG,
+		"Pre-Rewrite for IMSI(%s) Plan(%d) Type(%d) Number(%s)\n",
+		imsi, called.plan, called.type, called.number);
+	new_number_pre = rewrite_isdn_number(nat, &nat->num_rewr, msg, imsi, &called);
+
+	if (!new_number_pre) {
+		LOGP(DCC, LOGL_DEBUG, "No IMSI(%s) match found, returning message.\n",
+			imsi);
+		return NULL;
+	}
+
+	if (strlen(new_number_pre) > sizeof(called.number)) {
+		LOGP(DCC, LOGL_ERROR, "Number %s is too long for structure.\n",
+				new_number_pre);
+		talloc_free(new_number_pre);
+		return NULL;
+	}
+	update_called_number(&called, new_number_pre);
+
+	/* another run through the re-write engine with other rules */
+	LOGP(DCC, LOGL_DEBUG,
+		"Post-Rewrite for IMSI(%s) Plan(%d) Type(%d) Number(%s)\n",
+		imsi, called.plan, called.type, called.number);
+	new_number_post = rewrite_isdn_number(nat, &nat->num_rewr_post, msg,
+					imsi, &called);
+	chosen_number = new_number_post ? new_number_post : new_number_pre;
+
+
+	if (strlen(chosen_number) > sizeof(called.number)) {
+		LOGP(DCC, LOGL_ERROR, "Number %s is too long for structure.\n",
+			chosen_number);
+		talloc_free(new_number_pre);
+		talloc_free(new_number_post);
+		return NULL;
+	}
+
+	/*
+	 * Need to create a new message now based on the old onew
+	 * with a new number. We can sadly not patch this in place
+	 * so we will need to regenerate it.
+	 */
+
+	out = msgb_alloc_headroom(4096, 128, "changed-setup");
+	if (!out) {
+		LOGP(DCC, LOGL_ERROR, "Failed to allocate.\n");
+		talloc_free(new_number_pre);
+		talloc_free(new_number_post);
+		return NULL;
+	}
+
+	/* copy the header */
+	outptr = msgb_put(out, sizeof(*hdr48));
+	memcpy(outptr, hdr48, sizeof(*hdr48));
+
+	/* copy everything up to the number */
+	sec_len = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 2 - &hdr48->data[0];
+	outptr = msgb_put(out, sec_len);
+	memcpy(outptr, &hdr48->data[0], sec_len);
+
+	/* create the new number */
+	update_called_number(&called, chosen_number);
+	LOGP(DCC, LOGL_DEBUG,
+		"Chosen number for IMSI(%s) is Plan(%d) Type(%d) Number(%s)\n",
+		imsi, called.plan, called.type, called.number);
+	gsm48_encode_called(out, &called);
+
+	/* copy thre rest */
+	msgptr = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) +
+		 TLVP_LEN(&tp, GSM48_IE_CALLED_BCD);
+	sec_len = payload_len - (msgptr - &hdr48->data[0]);
+	outptr = msgb_put(out, sec_len);
+	memcpy(outptr, msgptr, sec_len);
+
+	talloc_free(new_number_pre);
+	talloc_free(new_number_post);
+	return out;
+}
+
+/**
+ * Find a new SMSC address, returns an allocated string that needs to be
+ * freed or is NULL.
+ */
+static char *find_new_smsc(struct bsc_nat *nat, void *ctx, const char *imsi,
+			   const char *smsc_addr, const char *dest_nr)
+{
+	struct bsc_nat_num_rewr_entry *entry;
+	char *new_number = NULL;
+	uint8_t dest_match = llist_empty(&nat->tpdest_match);
+
+	/* We will find a new number now */
+	llist_for_each_entry(entry, &nat->smsc_rewr, list) {
+		regmatch_t matches[2];
+
+		/* check the IMSI match */
+		if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+			continue;
+
+		/* this regexp matches... */
+		if (regexec(&entry->num_reg, smsc_addr, 2, matches, 0) == 0 &&
+		    matches[1].rm_eo != -1)
+			new_number = talloc_asprintf(ctx, "%s%s",
+					entry->replace,
+					&smsc_addr[matches[1].rm_so]);
+		if (new_number)
+			break;
+	}
+
+	if (!new_number)
+		return NULL;
+
+	/*
+	 * now match the number against another list
+	 */
+	llist_for_each_entry(entry, &nat->tpdest_match, list) {
+		/* check the IMSI match */
+		if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+			continue;
+
+		if (regexec(&entry->num_reg, dest_nr, 0, NULL, 0) == 0) {
+			dest_match = 1;
+			break;
+		}
+	}
+
+	if (!dest_match) {
+		talloc_free(new_number);
+		return NULL;
+	}
+
+	return new_number;
+}
+
+/**
+ * Clear the TP-SRR from the TPDU header
+ */
+static uint8_t sms_new_tpdu_hdr(struct bsc_nat *nat, const char *imsi,
+				const char *dest_nr, uint8_t hdr)
+{
+	struct bsc_nat_num_rewr_entry *entry;
+
+	/* We will find a new number now */
+	llist_for_each_entry(entry, &nat->sms_clear_tp_srr, list) {
+		/* check the IMSI match */
+		if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+			continue;
+		if (regexec(&entry->num_reg, dest_nr, 0, NULL, 0) != 0)
+			continue;
+
+		/* matched phone number and imsi */
+		return hdr & ~0x20;
+	}
+
+	return hdr;
+}
+
+/**
+ * Check if we need to rewrite the number. For this SMS.
+ */
+static char *sms_new_dest_nr(struct bsc_nat *nat, void *ctx,
+			     const char *imsi, const char *dest_nr)
+{
+	return match_and_rewrite_number(ctx, dest_nr, imsi,
+					&nat->sms_num_rewr, NULL);
+}
+
+/**
+ * This is a helper for GSM 04.11 8.2.5.2 Destination address element
+ */
+void sms_encode_addr_element(struct msgb *out, const char *new_number,
+			     int format, int tp_data)
+{
+	uint8_t new_addr_len;
+	uint8_t new_addr[26];
+
+	/*
+	 * Copy the new number. We let libosmocore encode it, then set
+	 * the extension followed after the length. Depending on if
+	 * we want to write RP we will let the TLV code add the
+	 * length for us or we need to use strlen... This is not very clear
+	 * as of 03.40 and 04.11.
+	 */
+	new_addr_len = gsm48_encode_bcd_number(new_addr, ARRAY_SIZE(new_addr),
+					       1, new_number);
+	new_addr[1] = format;
+	if (tp_data) {
+		uint8_t *data = msgb_put(out, new_addr_len);
+		memcpy(data, new_addr, new_addr_len);
+		data[0] = strlen(new_number);
+	} else {
+		msgb_lv_put(out, new_addr_len - 1, new_addr + 1);
+	}
+}
+
+static struct msgb *sms_create_new(uint8_t type, uint8_t ref,
+				   struct gsm48_hdr *old_hdr48,
+				   const uint8_t *orig_addr_ptr,
+				   int orig_addr_len, const char *new_number,
+				   const uint8_t *data_ptr, int data_len,
+				   uint8_t tpdu_first_byte,
+				   const int old_dest_len, const char *new_dest_nr)
+{
+	struct gsm48_hdr *new_hdr48;
+	struct msgb *out;
+
+	/*
+	 * We need to re-create the patched structure. This is why we have
+	 * saved the above pointers.
+	 */
+	out = msgb_alloc_headroom(4096, 128, "changed-smsc");
+	if (!out) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+		return NULL;
+	}
+
+	out->l2h = out->data;
+	msgb_v_put(out, GSM411_MT_RP_DATA_MO);
+	msgb_v_put(out, ref);
+	msgb_lv_put(out, orig_addr_len, orig_addr_ptr);
+
+	sms_encode_addr_element(out, new_number, 0x91, 0);
+
+
+	/* Patch the TPDU from here on */
+
+	/**
+	 * Do we need to put a new TP-Destination-Address (TP-DA) here or
+	 * can we copy the old thing? For the TP-DA we need to find out the
+	 * new size.
+	 */
+	if (new_dest_nr) {
+		uint8_t *data, *new_size;
+
+		/* reserve the size and write the header */
+		new_size = msgb_put(out, 1);
+		out->l3h = new_size + 1;
+		msgb_v_put(out, tpdu_first_byte);
+		msgb_v_put(out, data_ptr[1]);
+
+		/* encode the new number and put it */
+		if (strncmp(new_dest_nr, "00", 2) == 0)
+			sms_encode_addr_element(out, new_dest_nr + 2, 0x91, 1);
+		else
+			sms_encode_addr_element(out, new_dest_nr, 0x81, 1);
+
+		/* Copy the rest after the TP-DS */
+		data = msgb_put(out, data_len - 2 - 1 - old_dest_len);
+		memcpy(data, &data_ptr[2 + 1 + old_dest_len], data_len - 2 - 1 - old_dest_len);
+
+		/* fill in the new size */
+		new_size[0] = msgb_l3len(out);
+	} else {
+		msgb_v_put(out, data_len);
+		msgb_tv_fixed_put(out, tpdu_first_byte, data_len - 1, &data_ptr[1]);
+	}
+
+	/* prepend GSM 04.08 header */
+	new_hdr48 = (struct gsm48_hdr *) msgb_push(out, sizeof(*new_hdr48) + 1);
+	memcpy(new_hdr48, old_hdr48, sizeof(*old_hdr48));
+	new_hdr48->data[0] = msgb_l2len(out);
+
+	return out;
+}
+
+/**
+ * Parse the SMS and check if it needs to be rewritten
+ */
+static struct msgb *rewrite_sms(struct bsc_nat *nat, struct msgb *msg,
+				struct bsc_nat_parsed *parsed, const char *imsi,
+				struct gsm48_hdr *hdr48, const uint32_t len)
+{
+	unsigned int payload_len;
+	unsigned int cp_len;
+
+	uint8_t ref;
+	uint8_t orig_addr_len, *orig_addr_ptr;
+	uint8_t dest_addr_len, *dest_addr_ptr;
+	uint8_t data_len, *data_ptr;
+	char smsc_addr[30];
+
+
+	uint8_t dest_len, orig_dest_len;
+	char _dest_nr[30];
+	char *dest_nr;
+	char *new_dest_nr;
+
+	char *new_number = NULL;
+	uint8_t tpdu_hdr;
+	struct msgb *out;
+
+	payload_len = len - sizeof(*hdr48);
+	if (payload_len < 1) {
+		LOGP(DNAT, LOGL_ERROR, "SMS too short for things. %d\n", payload_len);
+		return NULL;
+	}
+
+	cp_len = hdr48->data[0];
+	if (payload_len + 1 < cp_len) {
+		LOGP(DNAT, LOGL_ERROR, "SMS RPDU can not fit in: %d %d\n", cp_len, payload_len);
+		return NULL;
+	}
+
+	if (hdr48->data[1] != GSM411_MT_RP_DATA_MO)
+		return NULL;
+
+	if (cp_len < 5) {
+		LOGP(DNAT, LOGL_ERROR, "RD-DATA can not fit in the CP len: %d\n", cp_len);
+		return NULL;
+	}
+
+	/* RP */
+	ref = hdr48->data[2];
+	orig_addr_len = hdr48->data[3];
+	orig_addr_ptr = &hdr48->data[4];
+
+	/* the +1 is for checking if the following element has some space */
+	if (cp_len < 3 + orig_addr_len + 1) {
+		LOGP(DNAT, LOGL_ERROR, "RP-Originator addr does not fit: %d\n", orig_addr_len);
+		return NULL;
+	}
+
+	dest_addr_len = hdr48->data[3 + orig_addr_len + 1];
+	dest_addr_ptr = &hdr48->data[3 + orig_addr_len + 2];
+
+	if (cp_len < 3 + orig_addr_len + 1 + dest_addr_len + 1) {
+		LOGP(DNAT, LOGL_ERROR, "RP-Destination addr does not fit: %d\n", dest_addr_len);
+		return NULL;
+	}
+	gsm48_decode_bcd_number(smsc_addr, ARRAY_SIZE(smsc_addr), dest_addr_ptr - 1, 1);
+
+	data_len = hdr48->data[3 + orig_addr_len + 1 + dest_addr_len + 1];
+	data_ptr = &hdr48->data[3 + orig_addr_len + 1 + dest_addr_len + 2];
+
+	if (cp_len < 3 + orig_addr_len + 1 + dest_addr_len + 1 + data_len) {
+		LOGP(DNAT, LOGL_ERROR, "RP-Data does not fit: %d\n", data_len);
+		return NULL;
+	}
+
+	if (data_len < 3) {
+		LOGP(DNAT, LOGL_ERROR, "SMS-SUBMIT is too short.\n");
+		return NULL;
+	}
+
+	/* TP-PDU starts here */
+	if ((data_ptr[0] & 0x03) != GSM340_SMS_SUBMIT_MS2SC)
+		return NULL;
+
+	/*
+	 * look into the phone number. The length is in semi-octets, we will
+	 * need to add the byte for the number type as well.
+	 */
+	orig_dest_len = data_ptr[2];
+	dest_len = ((orig_dest_len + 1) / 2) + 1;
+	if (data_len < dest_len + 3 || dest_len < 2) {
+		LOGP(DNAT, LOGL_ERROR, "SMS-SUBMIT can not have TP-DestAddr.\n");
+		return NULL;
+	}
+
+	if ((data_ptr[3] & 0x80) == 0) {
+		LOGP(DNAT, LOGL_ERROR, "TP-DestAddr has extension. Not handled.\n");
+		return NULL;
+	}
+
+	if ((data_ptr[3] & 0x0F) == 0) {
+		LOGP(DNAT, LOGL_ERROR, "TP-DestAddr is of unknown type.\n");
+		return NULL;
+	}
+
+	/**
+	 * Besides of what I think I read in GSM 03.40 and 04.11 the TP-DA
+	 * contains the semi-octets as length (strlen), change it to the
+	 * the number of bytes, but then change it back.
+	 */
+	data_ptr[2] = dest_len;
+	gsm48_decode_bcd_number(_dest_nr + 2, ARRAY_SIZE(_dest_nr) - 2,
+				&data_ptr[2], 1);
+	data_ptr[2] = orig_dest_len;
+	if ((data_ptr[3] & 0x70) == 0x10) {
+		_dest_nr[0] = _dest_nr[1] = '0';
+		dest_nr = &_dest_nr[0];
+	} else {
+		dest_nr = &_dest_nr[2];
+	}
+
+	/**
+	 * Call functions to rewrite the data
+	 */
+	tpdu_hdr = sms_new_tpdu_hdr(nat, imsi, dest_nr, data_ptr[0]);
+	new_number = find_new_smsc(nat, msg, imsi, smsc_addr, dest_nr);
+	new_dest_nr = sms_new_dest_nr(nat, msg, imsi, dest_nr);
+
+	if (tpdu_hdr == data_ptr[0] && !new_number && !new_dest_nr)
+		return NULL;
+
+	out = sms_create_new(GSM411_MT_RP_DATA_MO, ref, hdr48,
+			orig_addr_ptr, orig_addr_len,
+			new_number ? new_number : smsc_addr,
+			data_ptr, data_len, tpdu_hdr,
+			dest_len, new_dest_nr);
+	talloc_free(new_number);
+	talloc_free(new_dest_nr);
+	return out;
+}
+
+struct msgb *bsc_nat_rewrite_msg(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *parsed, const char *imsi)
+{
+	struct gsm48_hdr *hdr48;
+	uint32_t len;
+	uint8_t msg_type, proto;
+	struct msgb *new_msg = NULL, *sccp;
+	uint8_t link_id;
+
+	if (!imsi || strlen(imsi) < 5)
+		return msg;
+
+	/* only care about DTAP messages */
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return msg;
+	if (!parsed->dest_local_ref)
+		return msg;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return msg;
+
+	link_id = msg->l3h[1];
+	proto = gsm48_hdr_pdisc(hdr48);
+	msg_type = gsm48_hdr_msg_type(hdr48);
+
+	if (proto == GSM48_PDISC_CC && msg_type == GSM48_MT_CC_SETUP)
+		new_msg = rewrite_setup(nat, msg, parsed, imsi, hdr48, len);
+	else if (proto == GSM48_PDISC_SMS && msg_type == GSM411_MT_CP_DATA)
+		new_msg = rewrite_sms(nat, msg, parsed, imsi, hdr48, len);
+
+	if (!new_msg)
+		return msg;
+
+	/* wrap with DTAP, SCCP, then IPA. TODO: Stop copying */
+	gsm0808_prepend_dtap_header(new_msg, link_id);
+	sccp = sccp_create_dt1(parsed->dest_local_ref, new_msg->data, new_msg->len);
+	talloc_free(new_msg);
+
+	if (!sccp) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+		return msg;
+	}
+
+	ipa_prepend_header(sccp, IPAC_PROTO_SCCP);
+
+	/* the parsed hangs off from msg but it needs to survive */
+	talloc_steal(sccp, parsed);
+	msgb_free(msg);
+	return sccp;
+}
+
+static void num_rewr_free_data(struct bsc_nat_num_rewr_entry *entry)
+{
+	regfree(&entry->msisdn_reg);
+	regfree(&entry->num_reg);
+	talloc_free(entry->replace);
+}
+
+void bsc_nat_num_rewr_entry_adapt(void *ctx, struct llist_head *head,
+				  const struct osmo_config_list *list)
+{
+	struct bsc_nat_num_rewr_entry *entry, *tmp;
+	struct osmo_config_entry *cfg_entry;
+
+	/* free the old data */
+	llist_for_each_entry_safe(entry, tmp, head, list) {
+		num_rewr_free_data(entry);
+		llist_del(&entry->list);
+		talloc_free(entry);
+	}
+
+
+	if (!list)
+		return;
+
+	llist_for_each_entry(cfg_entry, &list->entry, list) {
+		char *regexp;
+		if (cfg_entry->text[0] == '+') {
+			LOGP(DNAT, LOGL_ERROR,
+				"Plus is not allowed in the number\n");
+			continue;
+		}
+
+		entry = talloc_zero(ctx, struct bsc_nat_num_rewr_entry);
+		if (!entry) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Allocation of the num_rewr entry failed.\n");
+			continue;
+		}
+
+		entry->replace = talloc_strdup(entry, cfg_entry->text);
+		if (!entry->replace) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to copy the replacement text.\n");
+			talloc_free(entry);
+			continue;
+		}
+
+		if (strcmp("prefix_lookup", entry->replace) == 0)
+			entry->is_prefix_lookup = 1;
+
+		/* we will now build a regexp string */
+		if (cfg_entry->mcc[0] == '^') {
+			regexp = talloc_strdup(entry, cfg_entry->mcc);
+		} else {
+			regexp = talloc_asprintf(entry, "^%s%s",
+					cfg_entry->mcc[0] == '*' ?
+						"[0-9][0-9][0-9]" : cfg_entry->mcc,
+					cfg_entry->mnc[0] == '*' ?
+						"[0-9][0-9]" : cfg_entry->mnc);
+		}
+
+		if (!regexp) {
+			LOGP(DNAT, LOGL_ERROR, "Failed to create a regexp string.\n");
+			talloc_free(entry);
+			continue;
+		}
+
+		if (regcomp(&entry->msisdn_reg, regexp, 0) != 0) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to compile regexp '%s'\n", regexp);
+			talloc_free(regexp);
+			talloc_free(entry);
+			continue;
+		}
+
+		talloc_free(regexp);
+		if (regcomp(&entry->num_reg, cfg_entry->option, REG_EXTENDED) != 0) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to compile regexp '%s'\n", cfg_entry->option);
+			regfree(&entry->msisdn_reg);
+			talloc_free(entry);
+			continue;
+		}
+
+		/* we have copied the number */
+		llist_add_tail(&entry->list, head);
+	}
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c b/openbsc/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c
new file mode 100644
index 0000000..633fa87
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c
@@ -0,0 +1,259 @@
+/* Handling for loading a re-write file/database */
+/*
+ * (C) 2013 by On-Waves
+ * (C) 2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/nat_rewrite_trie.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#define CHECK_IS_DIGIT_OR_FAIL(prefix, pos)						\
+	if (!isdigit(prefix[pos]) && prefix[pos] != '+') {				\
+			LOGP(DNAT, LOGL_ERROR,						\
+				"Prefix(%s) contains non ascii text at(%d=%c)\n",	\
+				prefix, pos, prefix[pos]);				\
+			goto fail;							\
+	}
+#define TO_INT(c) \
+	((c) == '+' ? 10 : ((c - '0') % 10))
+
+static void insert_rewrite_node(struct nat_rewrite_rule *rule, struct nat_rewrite *root)
+{
+	struct nat_rewrite_rule *new = &root->rule;
+
+	const int len = strlen(rule->prefix);
+	int i;
+
+	if (len <= 0) {
+		LOGP(DNAT, LOGL_ERROR, "An empty prefix does not make sense.\n");
+		goto fail;
+	}
+
+	for (i = 0; i < len - 1; ++i) {
+		int pos;
+
+		/* check if the input is valid */
+		CHECK_IS_DIGIT_OR_FAIL(rule->prefix, i);
+
+		/* check if the next node is already valid */
+		pos = TO_INT(rule->prefix[i]);
+		if (!new->rules[pos]) {
+			new->rules[pos] = talloc_zero(root, struct nat_rewrite_rule);
+			if (!new->rules[pos]) {
+				LOGP(DNAT, LOGL_ERROR,
+					"Failed to allocate memory.\n");
+				goto fail;
+			}
+
+			new->rules[pos]->empty = 1;
+		}
+
+		/* we continue here */
+		new = new->rules[pos];
+	}
+
+	/* new now points to the place where we want to add it */
+	int pos;
+
+	/* check if the input is valid */
+	CHECK_IS_DIGIT_OR_FAIL(rule->prefix, (len - 1));
+
+	/* check if the next node is already valid */
+	pos = TO_INT(rule->prefix[len - 1]);
+	if (!new->rules[pos])
+		new->rules[pos] = rule;
+	else if (new->rules[pos]->empty) {
+		/* copy over entries */
+		new->rules[pos]->empty = 0;
+		memcpy(new->rules[pos]->prefix, rule->prefix, sizeof(rule->prefix));
+		memcpy(new->rules[pos]->rewrite, rule->rewrite, sizeof(rule->rewrite));
+		talloc_free(rule);
+	} else {
+		LOGP(DNAT, LOGL_ERROR,
+			"Prefix(%s) is already installed\n", rule->prefix);
+		goto fail;
+	}
+
+	root->prefixes += 1;
+	return;
+
+fail:
+	talloc_free(rule);
+	return;
+}
+
+static void handle_line(struct nat_rewrite *rewrite, char *line)
+{
+	char *split;
+	struct nat_rewrite_rule *rule;
+	size_t size_prefix, size_end, len;
+
+
+	/* Find the ',' in the line */
+	len = strlen(line);
+	split = strstr(line, ",");
+	if (!split) {
+		LOGP(DNAT, LOGL_ERROR, "Line doesn't contain ','\n");
+		return;
+	}
+
+	/* Check if there is space for the rewrite rule */
+	size_prefix = split - line;
+	if (len - size_prefix <= 2) {
+		LOGP(DNAT, LOGL_ERROR, "No rewrite available.\n");
+		return;
+	}
+
+	/* Continue after the ',' to the end */
+	split = &line[size_prefix + 1];
+	size_end = strlen(split) - 1;
+
+	/* Check if both strings can fit into the static array */
+	if (size_prefix > sizeof(rule->prefix) - 1) {
+		LOGP(DNAT, LOGL_ERROR,
+			"Prefix is too long with %zu\n", size_prefix);
+		return;
+	}
+
+	if (size_end > sizeof(rule->rewrite) - 1) {
+		LOGP(DNAT, LOGL_ERROR,
+			"Rewrite is too long with %zu on %s\n",
+			size_end, &line[size_prefix + 1]);
+		return;
+	}
+
+	/* Now create the entry and insert it into the trie */
+	rule = talloc_zero(rewrite, struct nat_rewrite_rule);
+	if (!rule) {
+		LOGP(DNAT, LOGL_ERROR, "Can not allocate memory\n");
+		return;
+	}
+
+	memcpy(rule->prefix, line, size_prefix);
+	assert(size_prefix < sizeof(rule->prefix));
+	rule->prefix[size_prefix] = '\0';
+
+	memcpy(rule->rewrite, split, size_end);
+	assert(size_end < sizeof(rule->rewrite));
+	rule->rewrite[size_end] = '\0';
+
+	/* now insert and balance the tree */
+	insert_rewrite_node(rule, rewrite);
+}
+
+struct nat_rewrite *nat_rewrite_parse(void *ctx, const char *filename)
+{
+	FILE *file;
+	char *line = NULL;
+	size_t n = 0;
+	struct nat_rewrite *res;
+
+	file = fopen(filename, "r");
+	if (!file)
+		return NULL;
+
+	res = talloc_zero(ctx, struct nat_rewrite);
+	if (!res) {
+		fclose(file);
+		return NULL;
+	}
+
+	/* mark the root as empty */
+	res->rule.empty = 1;
+
+	while (getline(&line, &n, file) != -1) {
+		handle_line(res, line);
+	}
+
+	free(line);
+	fclose(file);
+	return res;
+}
+
+/**
+ * Simple find that tries to do a longest match...
+ */
+struct nat_rewrite_rule *nat_rewrite_lookup(struct nat_rewrite *rewrite,
+					const char *prefix)
+{
+	struct nat_rewrite_rule *rule = &rewrite->rule;
+	struct nat_rewrite_rule *last = NULL;
+	const int len = OSMO_MIN(strlen(prefix), (sizeof(rule->prefix) - 1));
+	int i;
+
+	for (i = 0; rule && i < len; ++i) {
+		int pos;
+
+		CHECK_IS_DIGIT_OR_FAIL(prefix, i);
+		pos = TO_INT(prefix[i]);
+
+		rule = rule->rules[pos];
+		if (rule && !rule->empty)
+			last = rule;
+	}
+
+	return last;
+
+fail:
+	return NULL;
+}
+
+static void nat_rewrite_dump_rec(struct nat_rewrite_rule *rule)
+{
+	int i;
+	if (!rule->empty)
+		printf("%s,%s\n", rule->prefix, rule->rewrite);
+
+	for (i = 0; i < ARRAY_SIZE(rule->rules); ++i) {
+		if (!rule->rules[i])
+			continue;
+		nat_rewrite_dump_rec(rule->rules[i]);
+	}
+}
+
+void nat_rewrite_dump(struct nat_rewrite *rewrite)
+{
+	nat_rewrite_dump_rec(&rewrite->rule);
+}
+
+static void nat_rewrite_dump_rec_vty(struct vty *vty, struct nat_rewrite_rule *rule)
+{
+	int i;
+	if (!rule->empty)
+		vty_out(vty, "%s,%s%s", rule->prefix, rule->rewrite, VTY_NEWLINE);
+
+	for (i = 0; i < ARRAY_SIZE(rule->rules); ++i) {
+		if (!rule->rules[i])
+			continue;
+		nat_rewrite_dump_rec_vty(vty, rule->rules[i]);
+	}
+}
+
+void nat_rewrite_dump_vty(struct vty *vty, struct nat_rewrite *rewrite)
+{
+	nat_rewrite_dump_rec_vty(vty, &rewrite->rule);
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c b/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c
new file mode 100644
index 0000000..223ef34
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c
@@ -0,0 +1,551 @@
+
+/* BSC Multiplexer/NAT Utilities */
+
+/*
+ * (C) 2010-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+static const struct rate_ctr_desc bsc_cfg_ctr_description[] = {
+	[BCFG_CTR_SCCP_CONN]     = { "sccp:conn",      "SCCP Connections         "},
+	[BCFG_CTR_SCCP_CALLS]    = { "sccp:calls",     "SCCP Assignment Commands "},
+	[BCFG_CTR_NET_RECONN]    = { "net:reconnects", "Network reconnects       "},
+	[BCFG_CTR_DROPPED_SCCP]  = { "dropped:sccp",   "Dropped SCCP connections."},
+	[BCFG_CTR_DROPPED_CALLS] = { "dropped:calls",  "Dropped active calls.    "},
+	[BCFG_CTR_REJECTED_CR]   = { "rejected:cr",    "Rejected CR due filter   "},
+	[BCFG_CTR_REJECTED_MSG]  = { "rejected:msg",   "Rejected MSG due filter  "},
+	[BCFG_CTR_ILL_PACKET]    = { "rejected:ill",   "Rejected due parse error "},
+	[BCFG_CTR_CON_TYPE_LU]   = { "conn:lu",        "Conn Location Update     "},
+	[BCFG_CTR_CON_CMSERV_RQ] = { "conn:rq",        "Conn CM Service Req      "},
+	[BCFG_CTR_CON_PAG_RESP]  = { "conn:pag",       "Conn Paging Response     "},
+	[BCFG_CTR_CON_SSA]       = { "conn:ssa",       "Conn USSD                "},
+	[BCFG_CTR_CON_OTHER]     = { "conn:other",     "Conn Other               "},
+};
+
+static const struct rate_ctr_group_desc bsc_cfg_ctrg_desc = {
+	.group_name_prefix = "nat:bsc",
+	.group_description = "NAT BSC Statistics",
+	.num_ctr = ARRAY_SIZE(bsc_cfg_ctr_description),
+	.ctr_desc = bsc_cfg_ctr_description,
+	.class_id = OSMO_STATS_CLASS_PEER,
+};
+
+struct bsc_nat *bsc_nat_alloc(void)
+{
+	struct bsc_nat *nat = talloc_zero(tall_bsc_ctx, struct bsc_nat);
+	if (!nat)
+		return NULL;
+
+	nat->main_dest = talloc_zero(nat, struct bsc_msc_dest);
+	if (!nat->main_dest) {
+		talloc_free(nat);
+		return NULL;
+	}
+
+	INIT_LLIST_HEAD(&nat->sccp_connections);
+	INIT_LLIST_HEAD(&nat->bsc_connections);
+	INIT_LLIST_HEAD(&nat->paging_groups);
+	INIT_LLIST_HEAD(&nat->bsc_configs);
+	INIT_LLIST_HEAD(&nat->access_lists);
+	INIT_LLIST_HEAD(&nat->dests);
+	INIT_LLIST_HEAD(&nat->num_rewr);
+	INIT_LLIST_HEAD(&nat->num_rewr_post);
+	INIT_LLIST_HEAD(&nat->smsc_rewr);
+	INIT_LLIST_HEAD(&nat->tpdest_match);
+	INIT_LLIST_HEAD(&nat->sms_clear_tp_srr);
+	INIT_LLIST_HEAD(&nat->sms_num_rewr);
+
+	nat->stats.sccp.conn = osmo_counter_alloc("nat.sccp.conn");
+	nat->stats.sccp.calls = osmo_counter_alloc("nat.sccp.calls");
+	nat->stats.bsc.reconn = osmo_counter_alloc("nat.bsc.conn");
+	nat->stats.bsc.auth_fail = osmo_counter_alloc("nat.bsc.auth_fail");
+	nat->stats.msc.reconn = osmo_counter_alloc("nat.msc.conn");
+	nat->stats.ussd.reconn = osmo_counter_alloc("nat.ussd.conn");
+	nat->auth_timeout = 2;
+	nat->ping_timeout = 20;
+	nat->pong_timeout = 5;
+
+	llist_add(&nat->main_dest->list, &nat->dests);
+	nat->main_dest->ip = talloc_strdup(nat, "127.0.0.1");
+	nat->main_dest->port = 5000;
+
+	return nat;
+}
+
+void bsc_nat_free(struct bsc_nat *nat)
+{
+	struct bsc_config *cfg, *tmp;
+	struct bsc_msg_acc_lst *lst, *tmp_lst;
+
+	llist_for_each_entry_safe(cfg, tmp, &nat->bsc_configs, entry)
+		bsc_config_free(cfg);
+	llist_for_each_entry_safe(lst, tmp_lst, &nat->access_lists, list)
+		bsc_msg_acc_lst_delete(lst);
+
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, NULL);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr_post, NULL);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_clear_tp_srr, NULL);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_num_rewr, NULL);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->tpdest_match, NULL);
+
+	osmo_counter_free(nat->stats.sccp.conn);
+	osmo_counter_free(nat->stats.sccp.calls);
+	osmo_counter_free(nat->stats.bsc.reconn);
+	osmo_counter_free(nat->stats.bsc.auth_fail);
+	osmo_counter_free(nat->stats.msc.reconn);
+	osmo_counter_free(nat->stats.ussd.reconn);
+	talloc_free(nat->mgcp_cfg);
+	talloc_free(nat);
+}
+
+void bsc_nat_set_msc_ip(struct bsc_nat *nat, const char *ip)
+{
+	osmo_talloc_replace_string(nat, &nat->main_dest->ip, ip);
+}
+
+struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat)
+{
+	struct bsc_connection *con = talloc_zero(nat, struct bsc_connection);
+	if (!con)
+		return NULL;
+
+	con->nat = nat;
+	osmo_wqueue_init(&con->write_queue, 100);
+	INIT_LLIST_HEAD(&con->cmd_pending);
+	INIT_LLIST_HEAD(&con->pending_dlcx);
+	return con;
+}
+
+struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token,
+				    unsigned int number)
+{
+	struct bsc_config *conf = talloc_zero(nat, struct bsc_config);
+	if (!conf)
+		return NULL;
+
+	conf->token = talloc_strdup(conf, token);
+	conf->nr = number;
+	conf->nat = nat;
+	conf->max_endpoints = 32;
+	conf->paging_group = PAGIN_GROUP_UNASSIGNED;
+
+	INIT_LLIST_HEAD(&conf->lac_list);
+
+	llist_add_tail(&conf->entry, &nat->bsc_configs);
+	++nat->num_bsc;
+
+	conf->stats.ctrg = rate_ctr_group_alloc(conf, &bsc_cfg_ctrg_desc, conf->nr);
+	if (!conf->stats.ctrg) {
+		llist_del(&conf->entry);
+		talloc_free(conf);
+		return NULL;
+	}
+
+	return conf;
+}
+
+struct bsc_config *bsc_config_by_token(struct bsc_nat *nat, const char *token, int len)
+{
+	struct bsc_config *conf;
+
+	llist_for_each_entry(conf, &nat->bsc_configs, entry) {
+		/*
+		 * Add the '\0' of the token for the memcmp, the IPA messages
+		 * for some reason added null termination.
+		 */
+		const int token_len = strlen(conf->token) + 1;
+
+		if (token_len == len && memcmp(conf->token, token, token_len) == 0)
+			return conf;
+	}
+
+	return NULL;
+}
+
+void bsc_config_free(struct bsc_config *cfg)
+{
+	llist_del(&cfg->entry);
+	rate_ctr_group_free(cfg->stats.ctrg);
+	cfg->nat->num_bsc--;
+	OSMO_ASSERT(cfg->nat->num_bsc >= 0)
+	talloc_free(cfg);
+}
+
+static void _add_lac(void *ctx, struct llist_head *list, int _lac)
+{
+	struct bsc_lac_entry *lac;
+
+	llist_for_each_entry(lac, list, entry)
+		if (lac->lac == _lac)
+			return;
+
+	lac = talloc_zero(ctx, struct bsc_lac_entry);
+	if (!lac) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+		return;
+	}
+
+	lac->lac = _lac;
+	llist_add_tail(&lac->entry, list);
+}
+
+static void _del_lac(struct llist_head *list, int _lac)
+{
+	struct bsc_lac_entry *lac;
+
+	llist_for_each_entry(lac, list, entry)
+		if (lac->lac == _lac) {
+			llist_del(&lac->entry);
+			talloc_free(lac);
+			return;
+		}
+}
+
+void bsc_config_add_lac(struct bsc_config *cfg, int _lac)
+{
+	_add_lac(cfg, &cfg->lac_list, _lac);
+}
+
+void bsc_config_del_lac(struct bsc_config *cfg, int _lac)
+{
+	_del_lac(&cfg->lac_list, _lac);
+}
+
+struct bsc_nat_paging_group *bsc_nat_paging_group_create(struct bsc_nat *nat, int group)
+{
+	struct bsc_nat_paging_group *pgroup;
+
+	pgroup = talloc_zero(nat, struct bsc_nat_paging_group);
+	if (!pgroup) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate a paging group.\n");
+		return NULL;
+	}
+
+	pgroup->nr = group;
+	INIT_LLIST_HEAD(&pgroup->lists);
+	llist_add_tail(&pgroup->entry, &nat->paging_groups);
+	return pgroup;
+}
+
+void bsc_nat_paging_group_delete(struct bsc_nat_paging_group *pgroup)
+{
+	llist_del(&pgroup->entry);
+	talloc_free(pgroup);
+}
+
+struct bsc_nat_paging_group *bsc_nat_paging_group_num(struct bsc_nat *nat, int group)
+{
+	struct bsc_nat_paging_group *pgroup;
+
+	llist_for_each_entry(pgroup, &nat->paging_groups, entry)
+		if (pgroup->nr == group)
+			return pgroup;
+
+	return NULL;
+}
+
+void bsc_nat_paging_group_add_lac(struct bsc_nat_paging_group *pgroup, int lac)
+{
+	_add_lac(pgroup, &pgroup->lists, lac);
+}
+
+void bsc_nat_paging_group_del_lac(struct bsc_nat_paging_group *pgroup, int lac)
+{
+	_del_lac(&pgroup->lists, lac);
+}
+
+int bsc_config_handles_lac(struct bsc_config *cfg, int lac_nr)
+{
+	struct bsc_nat_paging_group *pgroup;
+	struct bsc_lac_entry *entry;
+
+	llist_for_each_entry(entry, &cfg->lac_list, entry)
+		if (entry->lac == lac_nr)
+			return 1;
+
+	/* now lookup the paging group */
+	pgroup = bsc_nat_paging_group_num(cfg->nat, cfg->paging_group);
+	if (!pgroup)
+		return 0;
+
+	llist_for_each_entry(entry, &pgroup->lists, entry)
+		if (entry->lac == lac_nr)
+			return 1;
+
+	return 0;
+}
+
+void sccp_connection_destroy(struct nat_sccp_connection *conn)
+{
+	LOGP(DNAT, LOGL_DEBUG, "Destroy 0x%x <-> 0x%x mapping for con %p\n",
+	     sccp_src_ref_to_int(&conn->real_ref),
+	     sccp_src_ref_to_int(&conn->patched_ref), conn->bsc);
+	bsc_mgcp_dlcx(conn);
+	llist_del(&conn->list_entry);
+	talloc_free(conn);
+}
+
+/*! Parse paging message and provide pointer to first Cell identification item
+ * in Cell Identifier List IE.
+ * See e.g. GSM 08.08 Section 3.2.2.27 for Cell Identifier List.
+ * For multiple occurences, use tlv_parse2().
+ *  \param[in] msg msgbcontaining the paging cmd message to be decoded
+ *  \param[out] out_data pointer to first Cell identification item in Cell Identifier List IE.
+ *  \param[out] out_leng length of \ref out_data in bytes (the size of the array of items)
+ *  \returns Field "Cell identification discriminator" of the "Cell Identifier
+ *           List" IE (>=0) on success. Negative value on error.
+ */
+int bsc_nat_find_paging(struct msgb *msg,
+			const uint8_t **out_data, int *out_leng)
+{
+	int data_length;
+	const uint8_t *data;
+	struct tlv_parsed tp;
+	int rc;
+
+	if (!msg->l3h || msgb_l3len(msg) < 3) {
+		LOGP(DNAT, LOGL_ERROR, "Paging message is too short.\n");
+		return -1;
+	}
+
+	rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+	if (rc < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed parsing PAGING TLV -- discarding message! %s\n",
+			osmo_hexdump(msg->l3h, msgb_l3len(msg)));
+		return -1;
+	}
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+		LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n");
+		return -2;
+	}
+
+	data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+	data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+
+	switch (data[0]) {
+	case CELL_IDENT_LAC:
+		*out_data = &data[1];
+		*out_leng = data_length - 1;
+		/* fall through */
+	case CELL_IDENT_BSS:
+		return data[0];
+	default:
+		LOGP(DNAT, LOGL_ERROR, "Unhandled cell ident discrminator: %s\n",
+		     gsm0808_cell_id_discr_name(data[0]));
+		return -4;
+	}
+}
+
+int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length)
+{
+	struct msgb *msg;
+
+	if (length > 4096 - 128) {
+		LOGP(DLINP, LOGL_ERROR, "Can not send message of that size.\n");
+		return -1;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "to-bsc");
+	if (!msg) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n");
+		return -1;
+	}
+
+	/* copy the data */
+	msg->l3h = msgb_put(msg, length);
+	memcpy(msg->l3h, data, length);
+
+        return bsc_write(bsc, msg, IPAC_PROTO_MGCP_OLD);
+}
+
+int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int proto)
+{
+	return bsc_do_write(&bsc->write_queue, msg, proto);
+}
+
+int bsc_do_write(struct osmo_wqueue *queue, struct msgb *msg, int proto)
+{
+	/* prepend the header */
+	ipa_prepend_header(msg, proto);
+	return bsc_write_msg(queue, msg);
+}
+
+int bsc_write_msg(struct osmo_wqueue *queue, struct msgb *msg)
+{
+	if (osmo_wqueue_enqueue(queue, msg) != 0) {
+		LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the write.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	return 0;
+}
+
+struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed,
+				  struct msgb *msg, uint32_t *len)
+{
+	/* gsm_type is actually the size of the dtap */
+	*len = parsed->gsm_type;
+	if (*len < msgb_l3len(msg) - 3) {
+		LOGP(DNAT, LOGL_ERROR, "Not enough space for DTAP.\n");
+		return NULL;
+	}
+
+	if (msgb_l3len(msg) - 3 < msg->l3h[2]) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "GSM48 payload does not fit: %d %d\n",
+		     msg->l3h[2], msgb_l3len(msg) - 3);
+		return NULL;
+	}
+
+	msg->l4h = &msg->l3h[3];
+	return (struct gsm48_hdr *) msg->l4h;
+}
+
+static const char *con_types [] = {
+	[FLT_CON_TYPE_NONE] = "n/a",
+	[FLT_CON_TYPE_LU] = "Location Update",
+	[FLT_CON_TYPE_CM_SERV_REQ] = "CM Serv Req",
+	[FLT_CON_TYPE_PAG_RESP] = "Paging Response",
+	[FLT_CON_TYPE_SSA] = "Supplementar Service Activation",
+	[FLT_CON_TYPE_LOCAL_REJECT] = "Local Reject",
+	[FLT_CON_TYPE_OTHER] = "Other",
+};
+
+const char *bsc_con_type_to_string(int type)
+{
+	return con_types[type];
+}
+
+int bsc_nat_msc_is_connected(struct bsc_nat *nat)
+{
+	return nat->msc_con->is_connected;
+}
+
+static const int con_to_ctr[] = {
+	[FLT_CON_TYPE_NONE]		= -1,
+	[FLT_CON_TYPE_LU]		= BCFG_CTR_CON_TYPE_LU,
+	[FLT_CON_TYPE_CM_SERV_REQ]	= BCFG_CTR_CON_CMSERV_RQ,
+	[FLT_CON_TYPE_PAG_RESP]		= BCFG_CTR_CON_PAG_RESP,
+	[FLT_CON_TYPE_SSA]		= BCFG_CTR_CON_SSA,
+	[FLT_CON_TYPE_LOCAL_REJECT]	= -1,
+	[FLT_CON_TYPE_OTHER]		= BCFG_CTR_CON_OTHER,
+};
+
+int bsc_conn_type_to_ctr(struct nat_sccp_connection *conn)
+{
+	return con_to_ctr[conn->filter_state.con_type];
+}
+
+int bsc_write_cb(struct osmo_fd *bfd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(bfd->fd, msg->data, msg->len);
+	if (rc != msg->len)
+		LOGP(DNAT, LOGL_ERROR, "Failed to write message to the BSC.\n");
+
+	return rc;
+}
+
+static void extract_lac(const uint8_t *data, uint16_t *lac, uint16_t *ci)
+{
+	memcpy(lac, &data[0], sizeof(*lac));
+	memcpy(ci, &data[2], sizeof(*ci));
+
+	*lac = ntohs(*lac);
+	*ci = ntohs(*ci);
+}
+
+int bsc_nat_extract_lac(struct bsc_connection *bsc,
+			struct nat_sccp_connection *con,
+			struct bsc_nat_parsed *parsed, struct msgb *msg)
+{
+	int data_length;
+	const uint8_t *data;
+	struct tlv_parsed tp;
+	uint16_t lac, ci;
+
+	if (parsed->gsm_type != BSS_MAP_MSG_COMPLETE_LAYER_3) {
+		LOGP(DNAT, LOGL_ERROR, "Can only extract LAC from Complete Layer3\n");
+		return -1;
+	}
+
+	if (!msg->l3h || msgb_l3len(msg) < 3) {
+		LOGP(DNAT, LOGL_ERROR, "Complete Layer3 mssage is too short.\n");
+		return -1;
+	}
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER)) {
+		LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n");
+		return -2;
+	}
+
+	data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER);
+	data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER);
+
+	/* Attemt to get the LAC/CI from it */
+	if (data[0] == CELL_IDENT_WHOLE_GLOBAL) {
+		if (data_length != 8) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Ident too short: %d\n", data_length);
+			return -3;
+		}
+		extract_lac(&data[1 + 3], &lac, &ci);
+	} else if (data[0] == CELL_IDENT_LAC_AND_CI) {
+		if (data_length != 5) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Ident too short: %d\n", data_length);
+			return -3;
+		}
+		extract_lac(&data[1], &lac, &ci);
+	} else {
+		LOGP(DNAT, LOGL_ERROR,
+			"Unhandled cell identifier: %d\n", data[0]);
+		return -1;
+	}
+
+	con->lac = lac;
+	con->ci = ci;
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c b/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
new file mode 100644
index 0000000..bc1050d
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
@@ -0,0 +1,1439 @@
+/* OpenBSC NAT interface to quagga VTY */
+/* (C) 2010-2015 by Holger Hans Peter Freyther
+ * (C) 2010-2015 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/vty.h>
+#include <openbsc/nat_rewrite_trie.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/misc.h>
+#include <openbsc/osmux.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+static struct bsc_nat *_nat;
+
+
+#define BSC_STR "Information about BSCs\n"
+#define MGCP_STR "MGCP related status\n"
+#define PAGING_STR "Paging\n"
+#define SMSC_REWRITE "SMSC Rewriting\n"
+
+static struct cmd_node nat_node = {
+	NAT_NODE,
+	"%s(config-nat)# ",
+	1,
+};
+
+static struct cmd_node bsc_node = {
+	NAT_BSC_NODE,
+	"%s(config-nat-bsc)# ",
+	1,
+};
+
+static struct cmd_node pgroup_node = {
+	PGROUP_NODE,
+	"%s(config-nat-paging-group)# ",
+	1,
+};
+
+static int config_write_pgroup(struct vty *vty)
+{
+	return CMD_SUCCESS;
+}
+
+static void dump_lac(struct vty *vty, struct llist_head *head)
+{
+	struct bsc_lac_entry *lac;
+	llist_for_each_entry(lac, head, entry)
+		vty_out(vty, "  location_area_code %u%s", lac->lac, VTY_NEWLINE);
+}
+
+
+static void write_pgroup_lst(struct vty *vty, struct bsc_nat_paging_group *pgroup)
+{
+	vty_out(vty, " paging-group %d%s", pgroup->nr, VTY_NEWLINE);
+	dump_lac(vty, &pgroup->lists);
+}
+
+static void config_write_bsc_single(struct vty *vty, struct bsc_config *bsc)
+{
+	vty_out(vty, " bsc %u%s", bsc->nr, VTY_NEWLINE);
+	vty_out(vty, "  token %s%s", bsc->token, VTY_NEWLINE);
+	if (bsc->key_present)
+		vty_out(vty, "  auth-key %s%s", osmo_hexdump(bsc->key, 16), VTY_NEWLINE);
+	dump_lac(vty, &bsc->lac_list);
+	if (bsc->description)
+		vty_out(vty, "  description %s%s", bsc->description, VTY_NEWLINE);
+	if (bsc->acc_lst_name)
+		vty_out(vty, "  access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
+	vty_out(vty, "  max-endpoints %d%s", bsc->max_endpoints, VTY_NEWLINE);
+	if (bsc->paging_group != -1)
+		vty_out(vty, "  paging group %d%s", bsc->paging_group, VTY_NEWLINE);
+	vty_out(vty, "  paging forbidden %d%s", bsc->forbid_paging, VTY_NEWLINE);
+	switch (bsc->osmux) {
+	case OSMUX_USAGE_ON:
+		vty_out(vty, "  osmux on%s", VTY_NEWLINE);
+		break;
+	case OSMUX_USAGE_ONLY:
+		vty_out(vty, "  osmux only%s", VTY_NEWLINE);
+		break;
+	}
+	if (bsc->bts_use_jibuf_override)
+		vty_out(vty, "  %sbts-jitter-buffer%s", bsc->bts_use_jibuf? "" : "no ", VTY_NEWLINE);
+	if (bsc->bts_jitter_delay_min_override)
+		vty_out(vty, "  bts-jitter-buffer-delay-min %"PRIu32"%s", bsc->bts_jitter_delay_min, VTY_NEWLINE);
+	if (bsc->bts_jitter_delay_max_override)
+		vty_out(vty, "  bts-jitter-buffer-delay-max %"PRIu32"%s", bsc->bts_jitter_delay_max, VTY_NEWLINE);
+}
+
+static int config_write_bsc(struct vty *vty)
+{
+	struct bsc_config *bsc;
+
+	llist_for_each_entry(bsc, &_nat->bsc_configs, entry)
+		config_write_bsc_single(vty, bsc);
+	return CMD_SUCCESS;
+}
+
+static int config_write_nat(struct vty *vty)
+{
+	struct bsc_nat_paging_group *pgroup;
+
+	vty_out(vty, "nat%s", VTY_NEWLINE);
+	vty_out(vty, " msc ip %s%s", _nat->main_dest->ip, VTY_NEWLINE);
+	vty_out(vty, " msc port %d%s", _nat->main_dest->port, VTY_NEWLINE);
+	vty_out(vty, " timeout auth %d%s", _nat->auth_timeout, VTY_NEWLINE);
+	vty_out(vty, " timeout ping %d%s", _nat->ping_timeout, VTY_NEWLINE);
+	vty_out(vty, " timeout pong %d%s", _nat->pong_timeout, VTY_NEWLINE);
+	if (_nat->include_file)
+		vty_out(vty, " bscs-config-file %s%s", _nat->include_file, VTY_NEWLINE);
+	if (_nat->token)
+		vty_out(vty, " token %s%s", _nat->token, VTY_NEWLINE);
+	vty_out(vty, " ip-dscp %d%s", _nat->bsc_ip_dscp, VTY_NEWLINE);
+	if (_nat->acc_lst_name)
+		vty_out(vty, " access-list-name %s%s", _nat->acc_lst_name, VTY_NEWLINE);
+	if (_nat->imsi_black_list_fn)
+		vty_out(vty, " imsi-black-list-file-name %s%s",
+			_nat->imsi_black_list_fn, VTY_NEWLINE);
+	if (_nat->ussd_lst_name)
+		vty_out(vty, " ussd-list-name %s%s", _nat->ussd_lst_name, VTY_NEWLINE);
+	if (_nat->ussd_query)
+		vty_out(vty, " ussd-query %s%s", _nat->ussd_query, VTY_NEWLINE);
+	if (_nat->ussd_token)
+		vty_out(vty, " ussd-token %s%s", _nat->ussd_token, VTY_NEWLINE);
+	if (_nat->ussd_local)
+		vty_out(vty, " ussd-local-ip %s%s", _nat->ussd_local, VTY_NEWLINE);
+
+	if (_nat->num_rewr_name)
+		vty_out(vty, " number-rewrite %s%s", _nat->num_rewr_name, VTY_NEWLINE);
+	if (_nat->num_rewr_post_name)
+		vty_out(vty, " number-rewrite-post %s%s",
+			_nat->num_rewr_post_name, VTY_NEWLINE);
+
+	if (_nat->smsc_rewr_name)
+		vty_out(vty, " rewrite-smsc addr %s%s",
+			_nat->smsc_rewr_name, VTY_NEWLINE);
+	if (_nat->tpdest_match_name)
+		vty_out(vty, " rewrite-smsc tp-dest-match %s%s",
+			_nat->tpdest_match_name, VTY_NEWLINE);
+	if (_nat->sms_clear_tp_srr_name)
+		vty_out(vty, " sms-clear-tp-srr %s%s",
+			_nat->sms_clear_tp_srr_name, VTY_NEWLINE);
+	if (_nat->sms_num_rewr_name)
+		vty_out(vty, " sms-number-rewrite %s%s",
+			_nat->sms_num_rewr_name, VTY_NEWLINE);
+	if (_nat->num_rewr_trie_name)
+		vty_out(vty, " prefix-tree %s%s",
+			_nat->num_rewr_trie_name, VTY_NEWLINE);
+
+	bsc_msg_acc_lst_write(vty);
+
+	llist_for_each_entry(pgroup, &_nat->paging_groups, entry)
+		write_pgroup_lst(vty, pgroup);
+	if (_nat->mgcp_ipa)
+		vty_out(vty, " use-msc-ipa-for-mgcp%s", VTY_NEWLINE);
+	vty_out(vty, " %ssdp-ensure-amr-mode-set%s",
+		_nat->sdp_ensure_amr_mode_set ? "" : "no ", VTY_NEWLINE);
+	vty_out(vty, " %spaging-bss-forward%s",
+		_nat->paging_bss_forward ? "" : "no ", VTY_NEWLINE);
+
+	config_write_bsc(vty);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bscs, show_bscs_cmd, "show bscs-config",
+      SHOW_STR "Show configured BSCs\n"
+      "Both from included file and vty\n")
+{
+	vty_out(vty, "BSCs configuration loaded from %s:%s", _nat->resolved_path,
+		VTY_NEWLINE);
+	return config_write_bsc(vty);
+}
+
+DEFUN(show_sccp, show_sccp_cmd, "show sccp connections",
+      SHOW_STR "Display information about SCCP\n"
+      "All active connections\n")
+{
+	struct nat_sccp_connection *con;
+	vty_out(vty, "Listing all open SCCP connections%s", VTY_NEWLINE);
+
+	llist_for_each_entry(con, &_nat->sccp_connections, list_entry) {
+		vty_out(vty, "For BSC Nr: %d BSC ref: 0x%x; MUX ref: 0x%x; Network has ref: %d ref: 0x%x MSC/BSC mux: 0x%x/0x%x type: %s%s",
+			con->bsc->cfg ? con->bsc->cfg->nr : -1,
+			sccp_src_ref_to_int(&con->real_ref),
+			sccp_src_ref_to_int(&con->patched_ref),
+			con->has_remote_ref,
+			sccp_src_ref_to_int(&con->remote_ref),
+			con->msc_endp, con->bsc_endp,
+			bsc_con_type_to_string(con->filter_state.con_type),
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_nat_bsc, show_nat_bsc_cmd, "show nat num-bscs-configured",
+      SHOW_STR "Display NAT configuration details\n"
+      "BSCs-related\n")
+{
+	vty_out(vty, "%d BSCs configured%s", _nat->num_bsc, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc, show_bsc_cmd, "show bsc connections",
+      SHOW_STR BSC_STR
+      "All active connections\n")
+{
+	struct bsc_connection *con;
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+
+	llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+		getpeername(con->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+		vty_out(vty, "BSC nr: %d auth: %d fd: %d peername: %s pending-stats: %u%s",
+			con->cfg ? con->cfg->nr : -1,
+			con->authenticated, con->write_queue.bfd.fd,
+			inet_ntoa(sock.sin_addr), con->pending_dlcx_count,
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc_mgcp, show_bsc_mgcp_cmd, "show bsc mgcp NR",
+      SHOW_STR BSC_STR MGCP_STR "Identifier of the BSC\n")
+{
+	struct bsc_connection *con;
+	int nr = atoi(argv[0]);
+	int i, j, endp;
+
+	llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+		int max;
+		if (!con->cfg)
+			continue;
+		if (con->cfg->nr != nr)
+			continue;
+
+		/* this bsc has no audio endpoints yet */
+		if (!con->_endpoint_status)
+			continue;
+
+		vty_out(vty, "MGCP Status for %d%s", con->cfg->nr, VTY_NEWLINE);
+		max = bsc_mgcp_nr_multiplexes(con->max_endpoints);
+		for (i = 0; i < max; ++i) {
+			for (j = 1; j < 32; ++j) {
+				endp = mgcp_timeslot_to_endpoint(i, j);
+				vty_out(vty, " Endpoint 0x%x %s%s", endp,
+					con->_endpoint_status[endp] == 0 
+						? "free" : "allocated",
+				VTY_NEWLINE);
+			}
+		}
+		break;
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void dump_stat_total(struct vty *vty, struct bsc_nat *nat)
+{
+	vty_out(vty, "NAT statistics%s", VTY_NEWLINE);
+	vty_out(vty, " SCCP Connections %lu total, %lu calls%s",
+		osmo_counter_get(nat->stats.sccp.conn),
+		osmo_counter_get(nat->stats.sccp.calls), VTY_NEWLINE);
+	vty_out(vty, " MSC Connections %lu%s",
+		osmo_counter_get(nat->stats.msc.reconn), VTY_NEWLINE);
+	vty_out(vty, " MSC Connected: %d%s",
+		bsc_nat_msc_is_connected(nat), VTY_NEWLINE);
+	vty_out(vty, " BSC Connections %lu total, %lu auth failed.%s",
+		osmo_counter_get(nat->stats.bsc.reconn),
+		osmo_counter_get(nat->stats.bsc.auth_fail), VTY_NEWLINE);
+}
+
+static void dump_bsc_status(struct vty *vty, struct bsc_config *conf)
+{
+
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+	struct bsc_connection *con_iter, *con = NULL;
+	struct bsc_lac_entry *lac;
+
+	vty_out(vty, "BSC token: '%s' nr: %u%s",
+		conf->token, conf->nr, VTY_NEWLINE);
+	if (conf->acc_lst_name)
+		vty_out(vty, " access-list: %s%s",
+			conf->acc_lst_name, VTY_NEWLINE);
+	vty_out(vty, " paging forbidden: %d%s",
+		conf->forbid_paging, VTY_NEWLINE);
+	if (conf->description)
+		vty_out(vty, " description: %s%s", conf->description, VTY_NEWLINE);
+	else
+		vty_out(vty, " No description.%s", VTY_NEWLINE);
+
+	llist_for_each_entry(lac, &conf->lac_list, entry) {
+		vty_out(vty,  " LAC: %d%s", lac->lac, VTY_NEWLINE);
+	}
+
+	llist_for_each_entry(con_iter, &_nat->bsc_connections, list_entry) {
+		if (con_iter->cfg == conf) {
+			con = con_iter;
+			break;
+		}
+	}
+	if (con) {
+		getpeername(con->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+		vty_out(vty, " Conn-status: Connected, auth: %d, fd: %d, peername: %s, pending-stats: %u%s",
+			con->authenticated, con->write_queue.bfd.fd,
+			inet_ntoa(sock.sin_addr), con->pending_dlcx_count,
+			VTY_NEWLINE);
+	} else {
+		vty_out(vty,  " Conn-status: Disconnected%s", VTY_NEWLINE);
+	}
+
+	vty_out_rate_ctr_group(vty, " ", conf->stats.ctrg);
+}
+
+#define BSC_ID_HELP \
+	"Identify BSC by nr\n" \
+	"Identify BSC by token\n" \
+	"Identify BSC by lac\n" \
+	"Show all BSC\n" \
+	"NR/token/lac of the BSC\n"
+DEFUN(show_bsc_status, show_bsc_status_cmd, "show bsc status (nr|token|lac|all) [IDENT]",
+      SHOW_STR BSC_STR "Status of BSC\n" BSC_ID_HELP)
+{
+	const char *id_type = argv[0];
+	const char *id = argv[1];
+	struct bsc_config *conf;
+	bool by_token = false, by_nr = false, by_lac = false, all = false;
+	bool found_one = false;
+
+	if (strcmp(id_type, "all") == 0) {
+		all = true;
+		dump_stat_total(vty, _nat);
+	} else {
+		if (argc != 2) {
+			vty_out(vty, "%% Error: type %s requires an argument%s", id_type, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		if (strcmp(id_type, "nr") == 0)
+			by_nr = true;
+		else if (strcmp(id_type, "token") == 0)
+			by_token = true;
+		else if (strcmp(id_type, "lac") == 0)
+			by_lac = true;
+	}
+
+	llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+		if (by_nr && conf->nr != atoi(id))
+			continue;
+		else if (by_token && strcmp(conf->token, id))
+			continue;
+		else if (by_lac && !bsc_config_handles_lac(conf, atoi(id)))
+			continue;
+
+		found_one = true;
+		dump_bsc_status(vty, conf);
+	}
+
+	if (!all && !found_one) {
+		vty_out(vty, "%% Error: BSC with %s %s not found%s", id_type, id, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_msc,
+      show_msc_cmd,
+      "show msc connection",
+      SHOW_STR "MSC related information\n"
+      "Status of the A-link connection\n")
+{
+	if (!_nat->msc_con) {
+		vty_out(vty, "The MSC is not yet configured.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "MSC is connected: %d%s",
+		bsc_nat_msc_is_connected(_nat), VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(close_bsc,
+      close_bsc_cmd,
+      "close bsc connection BSC_NR",
+      "Close\n" "A-link\n" "Connection\n" "Identifier of the BSC\n")
+{
+	struct bsc_connection *bsc;
+	int bsc_nr = atoi(argv[0]);
+
+	llist_for_each_entry(bsc, &_nat->bsc_connections, list_entry) {
+		if (!bsc->cfg || bsc->cfg->nr != bsc_nr)
+			continue;
+		bsc_close_connection(bsc);
+		break;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat, cfg_nat_cmd, "nat", "Configure the NAT")
+{
+	vty->index = _nat;
+	vty->node = NAT_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_msc_ip,
+      cfg_nat_msc_ip_cmd,
+      "msc ip A.B.C.D",
+      "MSC related configuration\n"
+      "Configure the IP address\n" IP_STR)
+{
+	bsc_nat_set_msc_ip(_nat, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_msc_port,
+      cfg_nat_msc_port_cmd,
+      "msc port <1-65500>",
+      "MSC related configuration\n"
+      "Configure the port\n"
+      "Port number\n")
+{
+	_nat->main_dest->port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_auth_time,
+      cfg_nat_auth_time_cmd,
+      "timeout auth <1-256>",
+      "Timeout configuration\n"
+      "Authentication timeout\n"
+      "Timeout in seconds\n")
+{
+	_nat->auth_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ping_time,
+      cfg_nat_ping_time_cmd,
+      "timeout ping NR",
+      "Timeout configuration\n"
+      "Time between two pings\n"
+      "Timeout in seconds\n")
+{
+	_nat->ping_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_pong_time,
+      cfg_nat_pong_time_cmd,
+      "timeout pong NR",
+      "Timeout configuration\n"
+      "Waiting for pong timeout\n"
+      "Timeout in seconds\n")
+{
+	_nat->pong_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_token, cfg_nat_token_cmd,
+      "token TOKEN",
+      "Authentication token configuration\n"
+      "Token of the BSC, currently transferred in cleartext\n")
+{
+	osmo_talloc_replace_string(_nat, &_nat->token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_dscp_cmd,
+      "ip-dscp <0-255>",
+      "Set the IP DSCP for the BSCs to use\n" "Set the IP_TOS attribute")
+{
+	_nat->bsc_ip_dscp = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_tos_cmd,
+      "ip-tos <0-255>",
+      "Use ip-dscp in the future.\n" "Set the DSCP\n")
+
+
+DEFUN(cfg_nat_acc_lst_name,
+      cfg_nat_acc_lst_name_cmd,
+      "access-list-name NAME",
+      "Set the name of the access list to use.\n"
+      "The name of the to be used access list.")
+{
+	osmo_talloc_replace_string(_nat, &_nat->acc_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_include,
+      cfg_nat_include_cmd,
+      "bscs-config-file NAME",
+      "Set the filename of the BSC configuration to include.\n"
+      "The filename to be included.")
+{
+	char *path;
+	int rc;
+	struct bsc_config *cf1, *cf2;
+	struct bsc_connection *con1, *con2;
+
+	if ('/' == argv[0][0])
+		osmo_talloc_replace_string(_nat, &_nat->resolved_path, argv[0]);
+	else {
+		path = talloc_asprintf(_nat, "%s/%s", _nat->include_base,
+				       argv[0]);
+		osmo_talloc_replace_string(_nat, &_nat->resolved_path, path);
+		talloc_free(path);
+	}
+
+	llist_for_each_entry_safe(cf1, cf2, &_nat->bsc_configs, entry) {
+		cf1->remove = true;
+		cf1->token_updated = false;
+	}
+
+	rc = vty_read_config_file(_nat->resolved_path, NULL);
+	if (rc < 0) {
+		vty_out(vty, "Failed to parse the config file %s: %s%s",
+			_nat->resolved_path, strerror(-rc), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	osmo_talloc_replace_string(_nat, &_nat->include_file, argv[0]);
+
+	llist_for_each_entry_safe(con1, con2, &_nat->bsc_connections,
+				  list_entry) {
+		if (con1->cfg)
+			if (con1->cfg->token_updated || con1->cfg->remove)
+				bsc_close_connection(con1);
+	}
+
+	llist_for_each_entry_safe(cf1, cf2, &_nat->bsc_configs, entry) {
+		if (cf1->remove)
+			bsc_config_free(cf1);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_acc_lst_name,
+      cfg_nat_no_acc_lst_name_cmd,
+      "no access-list-name",
+      NO_STR "Remove the access list from the NAT.\n")
+{
+	if (_nat->acc_lst_name) {
+		talloc_free(_nat->acc_lst_name);
+		_nat->acc_lst_name = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_imsi_black_list_fn,
+      cfg_nat_imsi_black_list_fn_cmd,
+      "imsi-black-list-file-name NAME",
+      "IMSI black listing\n" "Filename IMSI and reject-cause\n")
+{
+
+	osmo_talloc_replace_string(_nat, &_nat->imsi_black_list_fn, argv[0]);
+	if (_nat->imsi_black_list_fn) {
+		int rc;
+		struct osmo_config_list *rewr = NULL;
+		rewr = osmo_config_list_parse(_nat, _nat->imsi_black_list_fn);
+		rc = bsc_filter_barr_adapt(_nat, &_nat->imsi_black_list, rewr);
+		if (rc != 0) {
+			vty_out(vty, "%%There was an error parsing the list."
+				" Please see the error log.%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+
+		return CMD_SUCCESS;
+	}
+
+	bsc_filter_barr_adapt(_nat, &_nat->imsi_black_list, NULL);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_imsi_black_list_fn,
+      cfg_nat_no_imsi_black_list_fn_cmd,
+      "no imsi-black-list-file-name",
+      NO_STR "Remove the imsi-black-list\n")
+{
+	talloc_free(_nat->imsi_black_list_fn);
+	_nat->imsi_black_list_fn = NULL;
+	bsc_filter_barr_adapt(_nat, &_nat->imsi_black_list, NULL);
+	return CMD_SUCCESS;
+}
+
+static int replace_rules(struct bsc_nat *nat, char **name,
+			 struct llist_head *head, const char *file)
+{
+	struct osmo_config_list *rewr = NULL;
+
+	osmo_talloc_replace_string(nat, name, file);
+	if (*name) {
+		rewr = osmo_config_list_parse(nat, *name);
+		bsc_nat_num_rewr_entry_adapt(nat, head, rewr);
+		talloc_free(rewr);
+		return CMD_SUCCESS;
+	} else {
+		bsc_nat_num_rewr_entry_adapt(nat, head, NULL);
+		return CMD_SUCCESS;
+	}
+}
+
+DEFUN(cfg_nat_number_rewrite,
+      cfg_nat_number_rewrite_cmd,
+      "number-rewrite FILENAME",
+      "Set the file with rewriting rules.\n" "Filename")
+{
+	return replace_rules(_nat, &_nat->num_rewr_name,
+			     &_nat->num_rewr, argv[0]);
+}
+
+DEFUN(cfg_nat_no_number_rewrite,
+      cfg_nat_no_number_rewrite_cmd,
+      "no number-rewrite",
+      NO_STR "Set the file with rewriting rules.\n")
+{
+	talloc_free(_nat->num_rewr_name);
+	_nat->num_rewr_name = NULL;
+
+	bsc_nat_num_rewr_entry_adapt(NULL, &_nat->num_rewr, NULL);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_number_rewrite_post,
+      cfg_nat_number_rewrite_post_cmd,
+      "number-rewrite-post FILENAME",
+      "Set the file with post-routing rewriting rules.\n" "Filename")
+{
+	return replace_rules(_nat, &_nat->num_rewr_post_name,
+			     &_nat->num_rewr_post, argv[0]);
+}
+
+DEFUN(cfg_nat_no_number_rewrite_post,
+      cfg_nat_no_number_rewrite_post_cmd,
+      "no number-rewrite-post",
+      NO_STR "Set the file with post-routing rewriting rules.\n")
+{
+	talloc_free(_nat->num_rewr_post_name);
+	_nat->num_rewr_post_name = NULL;
+
+	bsc_nat_num_rewr_entry_adapt(NULL, &_nat->num_rewr_post, NULL);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_smsc_addr,
+      cfg_nat_smsc_addr_cmd,
+      "rewrite-smsc addr FILENAME",
+      SMSC_REWRITE
+      "The SMSC Address to match and replace in RP-DATA\n"
+      "File with rules for the SMSC Address replacing\n")
+{
+	return replace_rules(_nat, &_nat->smsc_rewr_name,
+			     &_nat->smsc_rewr, argv[0]);
+}
+
+DEFUN(cfg_nat_smsc_tpdest,
+      cfg_nat_smsc_tpdest_cmd,
+      "rewrite-smsc tp-dest-match FILENAME",
+      SMSC_REWRITE
+      "Match TP-Destination of a SMS.\n"
+      "File with rules for matching MSISDN and TP-DEST\n")
+{
+	return replace_rules(_nat, &_nat->tpdest_match_name,
+			     &_nat->tpdest_match, argv[0]);
+}
+
+DEFUN(cfg_nat_sms_clear_tpsrr,
+      cfg_nat_sms_clear_tpsrr_cmd,
+      "sms-clear-tp-srr FILENAME",
+      "SMS TPDU Sender Report Request clearing\n"
+      "Files with rules for matching MSISDN\n")
+{
+	return replace_rules(_nat, &_nat->sms_clear_tp_srr_name,
+			     &_nat->sms_clear_tp_srr, argv[0]);
+}
+
+DEFUN(cfg_nat_no_sms_clear_tpsrr,
+      cfg_nat_no_sms_clear_tpsrr_cmd,
+      "no sms-clear-tp-srr",
+      NO_STR
+      "SMS TPDU Sender Report Request clearing\n")
+{
+	talloc_free(_nat->sms_clear_tp_srr_name);
+	_nat->sms_clear_tp_srr_name = NULL;
+
+	bsc_nat_num_rewr_entry_adapt(NULL, &_nat->sms_clear_tp_srr, NULL);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_sms_number_rewrite,
+      cfg_nat_sms_number_rewrite_cmd,
+      "sms-number-rewrite FILENAME",
+      "SMS TP-DA Number rewriting\n"
+      "Files with rules for matching MSISDN\n")
+{
+	return replace_rules(_nat, &_nat->sms_num_rewr_name,
+			     &_nat->sms_num_rewr, argv[0]);
+}
+
+DEFUN(cfg_nat_no_sms_number_rewrite,
+      cfg_nat_no_sms_number_rewrite_cmd,
+      "no sms-number-rewrite",
+      NO_STR "Disable SMS TP-DA rewriting\n")
+{
+	talloc_free(_nat->sms_num_rewr_name);
+	_nat->sms_num_rewr_name = NULL;
+
+	bsc_nat_num_rewr_entry_adapt(NULL, &_nat->sms_num_rewr, NULL);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_prefix_trie,
+      cfg_nat_prefix_trie_cmd,
+      "prefix-tree FILENAME",
+      "Prefix tree for number rewriting\n" "File to load\n")
+{
+	/* give up the old data */
+	talloc_free(_nat->num_rewr_trie);
+	_nat->num_rewr_trie = NULL;
+
+	/* replace the file name */
+	osmo_talloc_replace_string(_nat, &_nat->num_rewr_trie_name, argv[0]);
+	if (!_nat->num_rewr_trie_name) {
+		vty_out(vty, "%% prefix-tree no filename is present.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	_nat->num_rewr_trie = nat_rewrite_parse(_nat, _nat->num_rewr_trie_name);
+	if (!_nat->num_rewr_trie) {
+		vty_out(vty, "%% prefix-tree parsing has failed.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "%% prefix-tree loaded %zu rules.%s",
+		_nat->num_rewr_trie->prefixes, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_prefix_trie, cfg_nat_no_prefix_trie_cmd,
+      "no prefix-tree",
+      NO_STR "Prefix tree for number rewriting\n")
+{
+	talloc_free(_nat->num_rewr_trie);
+	_nat->num_rewr_trie = NULL;
+	talloc_free(_nat->num_rewr_trie_name);
+	_nat->num_rewr_trie_name = NULL;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_prefix_tree, show_prefix_tree_cmd,
+      "show prefix-tree",
+      SHOW_STR "Prefix tree for number rewriting\n")
+{
+	if (!_nat->num_rewr_trie) {
+		vty_out(vty, "%% there is now prefix tree loaded.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nat_rewrite_dump_vty(vty, _nat->num_rewr_trie);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_lst_name,
+      cfg_nat_ussd_lst_name_cmd,
+      "ussd-list-name NAME",
+      "Set the name of the access list to check for IMSIs for USSD message\n"
+      "The name of the access list for HLR USSD handling")
+{
+	osmo_talloc_replace_string(_nat, &_nat->ussd_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_query,
+      cfg_nat_ussd_query_cmd,
+      "ussd-query REGEXP",
+      "Set the USSD query to match with the ussd-list-name\n"
+      "The query to match")
+{
+	if (gsm_parse_reg(_nat, &_nat->ussd_query_re, &_nat->ussd_query, argc, argv) != 0)
+		return CMD_WARNING;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_token,
+      cfg_nat_ussd_token_cmd,
+      "ussd-token TOKEN",
+      "Set the token used to identify the USSD module\n" "Secret key\n")
+{
+	osmo_talloc_replace_string(_nat, &_nat->ussd_token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_local,
+      cfg_nat_ussd_local_cmd,
+      "ussd-local-ip A.B.C.D",
+      "Set the IP to listen for the USSD Provider\n" "IP Address\n")
+{
+	osmo_talloc_replace_string(_nat, &_nat->ussd_local, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_use_ipa_for_mgcp,
+      cfg_nat_use_ipa_for_mgcp_cmd,
+      "use-msc-ipa-for-mgcp",
+      "This needs to be set at start. Handle MGCP messages through "
+      "the IPA protocol and not through the UDP socket.\n")
+{
+	if (_nat->mgcp_cfg->data)
+		vty_out(vty,
+			"%%the setting will not be applied right now.%s",
+			VTY_NEWLINE);
+	_nat->mgcp_ipa = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_sdp_amr_mode_set,
+      cfg_nat_sdp_amr_mode_set_cmd,
+      "sdp-ensure-amr-mode-set",
+      "Ensure that SDP records include a mode-set\n")
+{
+	_nat->sdp_ensure_amr_mode_set = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_sdp_amr_mode_set,
+      cfg_nat_no_sdp_amr_mode_set_cmd,
+      "no sdp-ensure-amr-mode-set",
+      NO_STR "Ensure that SDP records include a mode-set\n")
+{
+	_nat->sdp_ensure_amr_mode_set = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_paging_bss_forward,
+      cfg_nat_paging_bss_forward_cmd,
+      "paging-bss-forward",
+      "Forward Paging messages with BSS as Cell Identity Discriminator\n")
+{
+	_nat->paging_bss_forward = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_paging_bss_forward,
+      cfg_nat_no_paging_bss_forward_cmd,
+      "no paging-bss-forward",
+      NO_STR "Forward Paging messages with BSS as Cell Identity Discriminator\n")
+{
+	_nat->paging_bss_forward = 0;
+	return CMD_SUCCESS;
+}
+
+/* per BSC configuration */
+DEFUN(cfg_bsc, cfg_bsc_cmd, "bsc BSC_NR",
+      "BSC configuration\n" "Identifier of the BSC\n")
+{
+	int bsc_nr = atoi(argv[0]);
+	struct bsc_config *bsc = bsc_config_num(_nat, bsc_nr);
+
+	/* allocate a new one */
+	if (!bsc)
+		bsc = bsc_config_alloc(_nat, "unknown", bsc_nr);
+
+	if (!bsc)
+		return CMD_WARNING;
+
+	bsc->remove = false;
+	vty->index = bsc;
+	vty->node = NAT_BSC_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_token, cfg_bsc_token_cmd, "token TOKEN",
+      "Authentication token configuration\n"
+      "Token of the BSC, currently transferred in cleartext\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	if (strncmp(conf->token, argv[0], 128) != 0)
+		conf->token_updated = true;
+
+	osmo_talloc_replace_string(conf, &conf->token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_auth_key, cfg_bsc_auth_key_cmd,
+      "auth-key KEY",
+      "Authentication (secret) key configuration\n"
+      "Non null security key\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	memset(conf->key, 0, sizeof(conf->key));
+	osmo_hexparse(argv[0], conf->key, sizeof(conf->key));
+	conf->key_present = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_auth_key, cfg_bsc_no_auth_key_cmd,
+      "no auth-key",
+      NO_STR "Authentication (secret) key configuration\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	memset(conf->key, 0, sizeof(conf->key));
+	conf->key_present = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_lac, cfg_bsc_lac_cmd, "location_area_code <0-65535>",
+      "Add the Location Area Code (LAC) of this BSC\n" "LAC\n")
+{
+	struct bsc_config *tmp;
+	struct bsc_config *conf = vty->index;
+
+	int lac = atoi(argv[0]);
+
+	if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+		vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* verify that the LACs are unique */
+	llist_for_each_entry(tmp, &_nat->bsc_configs, entry) {
+		if (bsc_config_handles_lac(tmp, lac)) {
+			if (tmp->nr != conf->nr) {
+				vty_out(vty, "%% LAC %d is already used.%s", lac,
+					VTY_NEWLINE);
+				return CMD_ERR_INCOMPLETE;
+			}
+		}
+	}
+
+	bsc_config_add_lac(conf, lac);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_lac, cfg_bsc_no_lac_cmd,
+      "no location_area_code <0-65535>",
+      NO_STR "Remove the Location Area Code (LAC) of this BSC\n" "LAC\n")
+{
+	int lac = atoi(argv[0]);
+	struct bsc_config *conf = vty->index;
+
+	bsc_config_del_lac(conf, lac);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bar_lst,
+      show_bar_lst_cmd,
+      "show imsi-black-list",
+      SHOW_STR "IMSIs barred from the network\n")
+{
+	struct rb_node *node;
+
+	vty_out(vty, "IMSIs barred from the network:%s", VTY_NEWLINE);
+
+	for (node = rb_first(&_nat->imsi_black_list); node; node = rb_next(node)) {
+		struct bsc_filter_barr_entry *entry;
+		entry = rb_entry(node, struct bsc_filter_barr_entry, node);
+
+		vty_out(vty, " IMSI(%s) CM-Reject-Cause(%d) LU-Reject-Cause(%d)%s",
+			entry->imsi, entry->cm_reject_cause, entry->lu_reject_cause,
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bsc_acc_lst_name,
+      cfg_bsc_acc_lst_name_cmd,
+      "access-list-name NAME",
+      "Set the name of the access list to use.\n"
+      "The name of the to be used access list.")
+{
+	struct bsc_config *conf = vty->index;
+
+	osmo_talloc_replace_string(conf, &conf->acc_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_acc_lst_name,
+      cfg_bsc_no_acc_lst_name_cmd,
+      "no access-list-name",
+      NO_STR "Do not use an access-list for the BSC.\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	if (conf->acc_lst_name) {
+		talloc_free(conf->acc_lst_name);
+		conf->acc_lst_name = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_max_endps, cfg_bsc_max_endps_cmd,
+      "max-endpoints <1-1024>",
+      "Highest endpoint to use (exclusively)\n" "Number of ports\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	conf->max_endpoints = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_paging,
+      cfg_bsc_paging_cmd,
+      "paging forbidden (0|1)",
+      PAGING_STR "Forbid sending PAGING REQUESTS to the BSC.\n"
+      "Do not forbid\n" "Forbid\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	if (strcmp("1", argv[0]) == 0)
+		conf->forbid_paging = 1;
+	else
+		conf->forbid_paging = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_desc,
+      cfg_bsc_desc_cmd,
+      "description DESC",
+      "Provide a description for the given BSC.\n" "Description\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	osmo_talloc_replace_string(conf, &conf->description, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_paging_grp,
+      cfg_bsc_paging_grp_cmd,
+      "paging group <0-1000>",
+      PAGING_STR "Use a paging group\n" "Paging Group to use\n")
+{
+	struct bsc_config *conf = vty->index;
+	conf->paging_group = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_bsc_paging_grp, cfg_bsc_old_grp_cmd,
+		 "paging-group <0-1000>",
+		 "Use a paging group\n" "Paging Group to use\n")
+
+DEFUN(cfg_bsc_no_paging_grp,
+      cfg_bsc_no_paging_grp_cmd,
+      "no paging group",
+      NO_STR PAGING_STR "Disable the usage of a paging group.\n")
+{
+	struct bsc_config *conf = vty->index;
+	conf->paging_group = PAGIN_GROUP_UNASSIGNED;
+	return CMD_SUCCESS;
+}
+
+DEFUN(test_regex, test_regex_cmd,
+      "test regex PATTERN STRING",
+      "Test utilities\n"
+      "Regexp testing\n" "The regexp pattern\n"
+      "The string to match\n")
+{
+	regex_t reg;
+	char *str = NULL;
+
+	memset(&reg, 0, sizeof(reg));
+	if (gsm_parse_reg(_nat, &reg, &str, 1, argv) != 0)
+		return CMD_WARNING;
+
+	vty_out(vty, "String matches allow pattern: %d%s",
+		regexec(&reg, argv[1], 0, NULL, 0) == 0, VTY_NEWLINE);
+
+	talloc_free(str);
+	regfree(&reg);
+	return CMD_SUCCESS;
+}
+
+DEFUN(set_last_endp, set_last_endp_cmd,
+      "set bsc last-used-endpoint <0-9999999999> <0-1024>",
+      "Set a value\n" "Operate on a BSC\n"
+      "Last used endpoint for an assignment\n" "BSC configuration number\n"
+      "Endpoint number used\n")
+{
+	struct bsc_connection *con;
+	int nr = atoi(argv[0]);
+	int endp = atoi(argv[1]);
+
+
+	llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+		if (!con->cfg)
+			continue;
+		if (con->cfg->nr != nr)
+			continue;
+
+		con->last_endpoint = endp;
+		vty_out(vty, "Updated the last endpoint for %d to %d.%s",
+			con->cfg->nr, con->last_endpoint, VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(block_new_conn, block_new_conn_cmd,
+      "nat-block (block|unblock)",
+      "Block the NAT for new connections\n"
+      "Block\n" "Unblock\n")
+{
+	_nat->blocked = argv[0][0] == 'b';
+	vty_out(vty, "%%Going to %s the NAT.%s",
+		_nat->blocked ? "block" : "unblock", VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+/* paging group */
+DEFUN(cfg_nat_pgroup, cfg_nat_pgroup_cmd,
+      "paging-group <0-1000>",
+      "Create a Paging Group\n" "Number of the Group\n")
+{
+	int group = atoi(argv[0]);
+	struct bsc_nat_paging_group *pgroup;
+	pgroup = bsc_nat_paging_group_num(_nat, group);
+	if (!pgroup)
+		pgroup = bsc_nat_paging_group_create(_nat, group);
+	if (!pgroup) {
+		vty_out(vty, "Failed to create the group.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->index = pgroup;
+	vty->node = PGROUP_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_pgroup, cfg_nat_no_pgroup_cmd,
+      "no paging-group <0-1000>",
+      NO_STR "Delete paging-group\n" "Paging-group number\n")
+{
+	int group = atoi(argv[0]);
+	struct bsc_nat_paging_group *pgroup;
+	pgroup = bsc_nat_paging_group_num(_nat, group);
+	if (!pgroup) {
+		vty_out(vty, "No such paging group %d.%s", group, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bsc_nat_paging_group_delete(pgroup);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_pgroup_lac, cfg_pgroup_lac_cmd,
+      "location_area_code <0-65535>",
+       "Add the Location Area Code (LAC)\n" "LAC\n")
+{
+	struct bsc_nat_paging_group *pgroup = vty->index;
+
+	int lac = atoi(argv[0]);
+	if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+		vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bsc_nat_paging_group_add_lac(pgroup, lac);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_pgroup_no_lac, cfg_pgroup_no_lac_cmd,
+      "no location_area_code <0-65535>",
+      NO_STR "Remove the Location Area Code (LAC)\n" "LAC\n")
+{
+	int lac = atoi(argv[0]);
+	struct bsc_nat_paging_group *pgroup = vty->index;
+
+	bsc_nat_paging_group_del_lac(pgroup, lac);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_ussd_connection,
+      show_ussd_connection_cmd,
+      "show ussd-connection",
+      SHOW_STR "USSD connection related information\n")
+{
+	vty_out(vty, "The USSD side channel provider is %sconnected and %sauthorized.%s",
+		_nat->ussd_con ? "" : "not ",
+		_nat->ussd_con && _nat->ussd_con->authorized? "" : "not ",
+		VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+#define OSMUX_STR "RTP multiplexing\n"
+DEFUN(cfg_bsc_osmux,
+      cfg_bsc_osmux_cmd,
+      "osmux (on|off|only)",
+       OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only OSMUX\n")
+{
+	struct bsc_config *conf = vty->index;
+	int old = conf->osmux;
+
+	if (strcmp(argv[0], "on") == 0)
+		conf->osmux = OSMUX_USAGE_ON;
+	else if (strcmp(argv[0], "off") == 0)
+		conf->osmux = OSMUX_USAGE_OFF;
+	else if (strcmp(argv[0], "only") == 0)
+		conf->osmux = OSMUX_USAGE_ONLY;
+
+	if (old == 0 && conf->osmux > 0 && !conf->nat->mgcp_cfg->osmux_init) {
+		LOGP(DMGCP, LOGL_NOTICE, "Setting up OSMUX socket\n");
+		if (osmux_init(OSMUX_ROLE_BSC_NAT, conf->nat->mgcp_cfg) < 0) {
+			LOGP(DMGCP, LOGL_ERROR, "Cannot init OSMUX\n");
+			vty_out(vty, "%% failed to create Osmux socket%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	} else if (old > 0 && conf->osmux == 0) {
+		LOGP(DMGCP, LOGL_NOTICE, "Disabling OSMUX socket\n");
+		/* Don't stop the socket, we may already have ongoing voice
+		 * flows already using Osmux. This just switch indicates that
+		 * new upcoming flows should use RTP.
+		 */
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define DEJITTER_STR "Uplink Jitter Buffer"
+DEFUN(cfg_bsc_bts_use_jibuf,
+      cfg_bsc_bts_use_jibuf_cmd,
+      "bts-jitter-buffer",
+      DEJITTER_STR "\n")
+{
+	struct bsc_config *conf = vty->index;
+	conf->bts_use_jibuf = true;
+	conf->bts_use_jibuf_override = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_bts_use_jibuf,
+      cfg_bsc_no_bts_use_jibuf_cmd,
+      "no bts-jitter-buffer",
+      NO_STR DEJITTER_STR "\n")
+{
+	struct bsc_config *conf = vty->index;
+	conf->bts_use_jibuf = false;
+	conf->bts_use_jibuf_override = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_bts_jitter_delay_min,
+      cfg_bsc_bts_jitter_delay_min_cmd,
+      "bts-jitter-buffer-delay-min <1-65535>",
+      DEJITTER_STR " Minimum Delay in ms\n" "Minimum Delay in ms\n")
+{
+	struct bsc_config *conf = vty->index;
+	conf->bts_jitter_delay_min = atoi(argv[0]);
+	if (!conf->bts_jitter_delay_min) {
+		vty_out(vty, "bts-jitter-buffer-delay-min cannot be zero.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (conf->bts_jitter_delay_min && conf->bts_jitter_delay_max &&
+	    conf->bts_jitter_delay_min > conf->bts_jitter_delay_max) {
+		vty_out(vty, "bts-jitter-buffer-delay-min cannot be bigger than " \
+			"bts-jitter-buffer-delay-max.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	conf->bts_jitter_delay_min_override = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_bts_jitter_delay_max,
+      cfg_bsc_bts_jitter_delay_max_cmd,
+      "bts-jitter-buffer-delay-max <1-65535>",
+      DEJITTER_STR " Maximum Delay in ms\n" "Maximum Delay in ms\n")
+{
+	struct bsc_config *conf = vty->index;
+	conf->bts_jitter_delay_max = atoi(argv[0]);
+	if (!conf->bts_jitter_delay_max) {
+		vty_out(vty, "bts-jitter-buffer-delay-max cannot be zero.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (conf->bts_jitter_delay_min && conf->bts_jitter_delay_max &&
+	    conf->bts_jitter_delay_min > conf->bts_jitter_delay_max) {
+		vty_out(vty, "bts-jitter-buffer-delay-max cannot be smaller than " \
+			"bts-jitter-buffer-delay-min.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	conf->bts_jitter_delay_max_override = true;
+	return CMD_SUCCESS;
+}
+
+int bsc_nat_vty_init(struct bsc_nat *nat)
+{
+	_nat = nat;
+
+	/* show commands */
+	install_element_ve(&show_sccp_cmd);
+	install_element_ve(&show_bsc_cmd);
+	install_element_ve(&show_nat_bsc_cmd);
+	install_element_ve(&show_bsc_status_cmd);
+	install_element_ve(&close_bsc_cmd);
+	install_element_ve(&show_msc_cmd);
+	install_element_ve(&test_regex_cmd);
+	install_element_ve(&show_bsc_mgcp_cmd);
+	install_element_ve(&show_bscs_cmd);
+	install_element_ve(&show_bar_lst_cmd);
+	install_element_ve(&show_prefix_tree_cmd);
+	install_element_ve(&show_ussd_connection_cmd);
+
+	install_element(ENABLE_NODE, &set_last_endp_cmd);
+	install_element(ENABLE_NODE, &block_new_conn_cmd);
+
+	/* nat group */
+	install_element(CONFIG_NODE, &cfg_nat_cmd);
+	install_node(&nat_node, config_write_nat);
+	install_element(NAT_NODE, &cfg_nat_msc_ip_cmd);
+	install_element(NAT_NODE, &cfg_nat_msc_port_cmd);
+	install_element(NAT_NODE, &cfg_nat_auth_time_cmd);
+	install_element(NAT_NODE, &cfg_nat_ping_time_cmd);
+	install_element(NAT_NODE, &cfg_nat_pong_time_cmd);
+	install_element(NAT_NODE, &cfg_nat_token_cmd);
+	install_element(NAT_NODE, &cfg_nat_bsc_ip_dscp_cmd);
+	install_element(NAT_NODE, &cfg_nat_bsc_ip_tos_cmd);
+	install_element(NAT_NODE, &cfg_nat_acc_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_acc_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_include_cmd);
+	install_element(NAT_NODE, &cfg_nat_imsi_black_list_fn_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_imsi_black_list_fn_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_query_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_token_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_local_cmd);
+	install_element(NAT_NODE, &cfg_nat_use_ipa_for_mgcp_cmd);
+
+	bsc_msg_acc_lst_vty_init(nat, &nat->access_lists, NAT_NODE);
+
+	/* number rewriting */
+	install_element(NAT_NODE, &cfg_nat_number_rewrite_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_number_rewrite_cmd);
+	install_element(NAT_NODE, &cfg_nat_number_rewrite_post_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_number_rewrite_post_cmd);
+	install_element(NAT_NODE, &cfg_nat_smsc_addr_cmd);
+	install_element(NAT_NODE, &cfg_nat_smsc_tpdest_cmd);
+	install_element(NAT_NODE, &cfg_nat_sms_clear_tpsrr_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_sms_clear_tpsrr_cmd);
+	install_element(NAT_NODE, &cfg_nat_sms_number_rewrite_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_sms_number_rewrite_cmd);
+	install_element(NAT_NODE, &cfg_nat_prefix_trie_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_prefix_trie_cmd);
+
+	install_element(NAT_NODE, &cfg_nat_sdp_amr_mode_set_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_sdp_amr_mode_set_cmd);
+
+	install_element(NAT_NODE, &cfg_nat_paging_bss_forward_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_paging_bss_forward_cmd);
+
+	install_element(NAT_NODE, &cfg_nat_pgroup_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_pgroup_cmd);
+	install_node(&pgroup_node, config_write_pgroup);
+	install_element(PGROUP_NODE, &cfg_pgroup_lac_cmd);
+	install_element(PGROUP_NODE, &cfg_pgroup_no_lac_cmd);
+
+	/* BSC subgroups */
+	install_element(NAT_NODE, &cfg_bsc_cmd);
+	install_node(&bsc_node, NULL);
+	install_element(NAT_BSC_NODE, &cfg_bsc_token_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_auth_key_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_no_auth_key_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_lac_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_no_lac_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_paging_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_desc_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_max_endps_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_old_grp_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_paging_grp_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_no_paging_grp_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_osmux_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_bts_use_jibuf_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_no_bts_use_jibuf_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_bts_jitter_delay_min_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_bts_jitter_delay_max_cmd);
+
+	mgcp_vty_init();
+
+	return 0;
+}
+
+
+/* called by the telnet interface... we have our own init above */
+int bsc_vty_init(struct gsm_network *network)
+{
+	logging_vty_add_cmds(NULL);
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_sccp.c b/openbsc/src/osmo-bsc_nat/bsc_sccp.c
new file mode 100644
index 0000000..c6c265f
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_sccp.c
@@ -0,0 +1,247 @@
+/* SCCP patching and handling routines */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <string.h>
+#include <time.h>
+
+static int equal(struct sccp_source_reference *ref1, struct sccp_source_reference *ref2)
+{
+	return memcmp(ref1, ref2, sizeof(*ref1)) == 0;
+}
+
+/*
+ * SCCP patching below
+ */
+
+/* check if we are using this ref for patched already */
+static int sccp_ref_is_free(struct sccp_source_reference *ref, struct bsc_nat *nat)
+{
+	struct nat_sccp_connection *conn;
+
+	llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+		if (equal(ref, &conn->patched_ref))
+			return -1;
+	}
+
+	return 0;
+}
+
+/* copied from sccp.c */
+static int assign_src_local_reference(struct sccp_source_reference *ref, struct bsc_nat *nat)
+{
+	static uint32_t last_ref = 0x50000;
+	int wrapped = 0;
+
+	do {
+		struct sccp_source_reference reference;
+		reference.octet1 = (last_ref >>  0) & 0xff;
+		reference.octet2 = (last_ref >>  8) & 0xff;
+		reference.octet3 = (last_ref >> 16) & 0xff;
+
+		++last_ref;
+		/* do not use the reversed word and wrap around */
+		if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) {
+			LOGP(DNAT, LOGL_NOTICE, "Wrapped searching for a free code\n");
+			last_ref = 0;
+			++wrapped;
+		}
+
+		if (sccp_ref_is_free(&reference, nat) == 0) {
+			*ref = reference;
+			return 0;
+		}
+	} while (wrapped != 2);
+
+	LOGP(DNAT, LOGL_ERROR, "Finding a free reference failed\n");
+	return -1;
+}
+
+struct nat_sccp_connection *create_sccp_src_ref(struct bsc_connection *bsc,
+					     struct bsc_nat_parsed *parsed)
+{
+	struct nat_sccp_connection *conn;
+
+	/* Some commercial BSCs like to reassign there SRC ref */
+	llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+		if (conn->bsc != bsc)
+			continue;
+		if (!equal(parsed->src_local_ref, &conn->real_ref))
+			continue;
+
+		/* the BSC has reassigned the SRC ref and we failed to keep track */
+		memset(&conn->remote_ref, 0, sizeof(conn->remote_ref));
+		if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) {
+			LOGP(DNAT, LOGL_ERROR, "BSC %d reused src ref: %d and we failed to generate a new id.\n",
+			     bsc->cfg->nr, sccp_src_ref_to_int(parsed->src_local_ref));
+			bsc_mgcp_dlcx(conn);
+			llist_del(&conn->list_entry);
+			talloc_free(conn);
+			return NULL;
+		} else {
+			clock_gettime(CLOCK_MONOTONIC, &conn->creation_time);
+			bsc_mgcp_dlcx(conn);
+			return conn;
+		}
+	}
+
+
+	conn = talloc_zero(bsc->nat, struct nat_sccp_connection);
+	if (!conn) {
+		LOGP(DNAT, LOGL_ERROR, "Memory allocation failure.\n");
+		return NULL;
+	}
+
+	conn->bsc = bsc;
+	clock_gettime(CLOCK_MONOTONIC, &conn->creation_time);
+	conn->real_ref = *parsed->src_local_ref;
+	if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to assign a ref.\n");
+		talloc_free(conn);
+		return NULL;
+	}
+
+	bsc_mgcp_init(conn);
+	llist_add_tail(&conn->list_entry, &bsc->nat->sccp_connections);
+	rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_SCCP_CONN]);
+	osmo_counter_inc(bsc->cfg->nat->stats.sccp.conn);
+
+	LOGP(DNAT, LOGL_DEBUG, "Created 0x%x <-> 0x%x mapping for con %p\n",
+	     sccp_src_ref_to_int(&conn->real_ref),
+	     sccp_src_ref_to_int(&conn->patched_ref), bsc);
+
+	return conn;
+}
+
+int update_sccp_src_ref(struct nat_sccp_connection *sccp, struct bsc_nat_parsed *parsed)
+{
+	if (!parsed->dest_local_ref || !parsed->src_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "CC MSG should contain both local and dest address.\n");
+		return -1;
+	}
+
+	sccp->remote_ref = *parsed->src_local_ref;
+	sccp->has_remote_ref = 1;
+	LOGP(DNAT, LOGL_DEBUG, "Updating 0x%x to remote 0x%x on %p\n",
+	     sccp_src_ref_to_int(&sccp->patched_ref),
+	     sccp_src_ref_to_int(&sccp->remote_ref), sccp->bsc);
+
+	return 0;
+}
+
+void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+	struct nat_sccp_connection *conn;
+
+	llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+		if (equal(parsed->src_local_ref, &conn->patched_ref)) {
+			sccp_connection_destroy(conn);
+			return;
+		}
+	}
+
+	LOGP(DNAT, LOGL_ERROR, "Can not remove connection: 0x%x\n",
+	     sccp_src_ref_to_int(parsed->src_local_ref));
+}
+
+/*
+ * We have a message from the MSC to the BSC. The MSC is using
+ * an address that was assigned by the MUX, we need to update the
+ * dest reference to the real network.
+ */
+struct nat_sccp_connection *patch_sccp_src_ref_to_bsc(struct msgb *msg,
+						   struct bsc_nat_parsed *parsed,
+						   struct bsc_nat *nat)
+{
+	struct nat_sccp_connection *conn;
+
+	if (!parsed->dest_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "MSG should contain dest_local_ref.\n");
+		return NULL;
+	}
+
+
+	llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+		if (!equal(parsed->dest_local_ref, &conn->patched_ref))
+			continue;
+
+		/* Change the dest address to the real one */
+		*parsed->dest_local_ref = conn->real_ref;
+		return conn;
+	}
+
+	return NULL;
+}
+
+/*
+ * These are message to the MSC. We will need to find the BSC
+ * Connection by either the SRC or the DST local reference.
+ *
+ * In case of a CR we need to work by the SRC local reference
+ * in all other cases we need to work by the destination local
+ * reference..
+ */
+struct nat_sccp_connection *patch_sccp_src_ref_to_msc(struct msgb *msg,
+						   struct bsc_nat_parsed *parsed,
+						   struct bsc_connection *bsc)
+{
+	struct nat_sccp_connection *conn;
+
+	llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+		if (conn->bsc != bsc)
+			continue;
+
+		if (parsed->src_local_ref) {
+			if (equal(parsed->src_local_ref, &conn->real_ref)) {
+				*parsed->src_local_ref = conn->patched_ref;
+				return conn;
+			}
+		} else if (parsed->dest_local_ref) {
+			if (equal(parsed->dest_local_ref, &conn->remote_ref))
+				return conn;
+		} else {
+			LOGP(DNAT, LOGL_ERROR, "Header has neither loc/dst ref.\n");
+			return NULL;
+		}
+	}
+
+	return NULL;
+}
+
+struct nat_sccp_connection *bsc_nat_find_con_by_bsc(struct bsc_nat *nat,
+						 struct sccp_source_reference *ref)
+{
+	struct nat_sccp_connection *conn;
+
+	llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+		if (equal(ref, &conn->real_ref))
+			return conn;
+	}
+
+	return NULL;
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_ussd.c b/openbsc/src/osmo-bsc_nat/bsc_ussd.c
new file mode 100644
index 0000000..ee0b085
--- /dev/null
+++ b/openbsc/src/osmo-bsc_nat/bsc_ussd.c
@@ -0,0 +1,456 @@
+/* USSD Filter Code */
+
+/*
+ * (C) 2010-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include <sys/socket.h>
+#include <string.h>
+#include <unistd.h>
+
+#define USSD_LAC_IE	0
+#define USSD_CI_IE	1
+
+static void ussd_auth_con(struct tlv_parsed *, struct bsc_nat_ussd_con *);
+
+static struct bsc_nat_ussd_con *bsc_nat_ussd_alloc(struct bsc_nat *nat)
+{
+	struct bsc_nat_ussd_con *con;
+
+	con = talloc_zero(nat, struct bsc_nat_ussd_con);
+	if (!con)
+		return NULL;
+
+	con->nat = nat;
+	return con;
+}
+
+static void bsc_nat_ussd_destroy(struct bsc_nat_ussd_con *con)
+{
+	if (con->nat->ussd_con == con) {
+		bsc_ussd_close_connections(con->nat);
+		con->nat->ussd_con = NULL;
+	}
+
+	close(con->queue.bfd.fd);
+	osmo_fd_unregister(&con->queue.bfd);
+	osmo_timer_del(&con->auth_timeout);
+	osmo_wqueue_clear(&con->queue);
+
+	msgb_free(con->pending_msg);
+	talloc_free(con);
+}
+
+static void ussd_pong(struct bsc_nat_ussd_con *conn)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "pong message");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate pong msg\n");
+		return;
+	}
+
+	msgb_v_put(msg, IPAC_MSGT_PONG);
+	bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS);
+}
+
+static int forward_sccp(struct bsc_nat *nat, struct msgb *msg)
+{
+	struct nat_sccp_connection *con;
+	struct bsc_nat_parsed *parsed;
+
+
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from USSD.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	if (!parsed->dest_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "No destination local reference.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	con = bsc_nat_find_con_by_bsc(nat, parsed->dest_local_ref);
+	if (!con || !con->bsc) {
+		LOGP(DNAT, LOGL_ERROR, "No active connection found.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	talloc_free(parsed);
+	bsc_write_msg(&con->bsc->write_queue, msg);
+	return 0;
+}
+
+static int ussd_read_cb(struct osmo_fd *bfd)
+{
+	struct bsc_nat_ussd_con *conn = bfd->data;
+	struct msgb *msg = NULL;
+	struct ipaccess_head *hh;
+	int ret;
+
+	ret = ipa_msg_recv_buffered(bfd->fd, &msg, &conn->pending_msg);
+	if (ret == -EAGAIN)
+		return 0;
+	if (ret <= 0) {
+		LOGP(DNAT, LOGL_ERROR, "USSD Connection was lost.\n");
+		goto close_fd;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "MSG from USSD: %s proto: %d\n",
+		osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+	hh = (struct ipaccess_head *) msg->data;
+
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_ID_RESP) {
+			struct tlv_parsed tvp;
+			int ret;
+			ret = ipa_ccm_idtag_parse(&tvp,
+					     (unsigned char *) msg->l2h + 2,
+					     msgb_l2len(msg) - 2);
+			if (ret < 0) {
+				LOGP(DNAT, LOGL_ERROR, "ignoring IPA response "
+					"message with malformed TLVs\n");
+				msgb_free(msg);
+				return ret;
+			}
+			if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME))
+				ussd_auth_con(&tvp, conn);
+		} else if (msg->l2h[0] == IPAC_MSGT_PING) {
+			LOGP(DNAT, LOGL_DEBUG, "Got USSD ping request.\n");
+			ussd_pong(conn);
+		} else {
+			LOGP(DNAT, LOGL_NOTICE, "Got unknown IPACCESS message 0x%02x.\n", msg->l2h[0]);
+		}
+
+		msgb_free(msg);
+	} else if (hh->proto == IPAC_PROTO_SCCP) {
+		forward_sccp(conn->nat, msg);
+	} else {
+		msgb_free(msg);
+	}
+
+	return 0;
+
+close_fd:
+	bsc_nat_ussd_destroy(conn);
+	return -EBADF;
+}
+
+static void ussd_auth_cb(void *_data)
+{
+	LOGP(DNAT, LOGL_ERROR, "USSD module didn't authenticate\n");
+	bsc_nat_ussd_destroy((struct bsc_nat_ussd_con *) _data);
+}
+
+static void ussd_auth_con(struct tlv_parsed *tvp, struct bsc_nat_ussd_con *conn)
+{
+	const char *token;
+	int len;
+	if (!conn->nat->ussd_token) {
+		LOGP(DNAT, LOGL_ERROR, "No USSD token set. Closing\n");
+		bsc_nat_ussd_destroy(conn);
+		return;
+	}
+
+	token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+ 	len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+
+	/* last byte should be a NULL */
+	if (strlen(conn->nat->ussd_token) != len - 1)
+		goto disconnect;
+	/* compare everything including the null byte */
+	if (memcmp(conn->nat->ussd_token, token, len) != 0)
+		goto disconnect;
+
+	/* it is authenticated now */
+	if (conn->nat->ussd_con && conn->nat->ussd_con != conn)
+		bsc_nat_ussd_destroy(conn->nat->ussd_con);
+
+	LOGP(DNAT, LOGL_ERROR, "USSD token specified. USSD provider is connected.\n");
+	osmo_timer_del(&conn->auth_timeout);
+	conn->authorized = 1;
+	conn->nat->ussd_con = conn;
+	return;
+
+disconnect:
+	LOGP(DNAT, LOGL_ERROR, "Wrong USSD token by client: %d\n",
+		conn->queue.bfd.fd);
+	bsc_nat_ussd_destroy(conn);
+}
+
+static void ussd_start_auth(struct bsc_nat_ussd_con *conn)
+{
+	struct msgb *msg;
+
+	osmo_timer_setup(&conn->auth_timeout, ussd_auth_cb, conn);
+	osmo_timer_schedule(&conn->auth_timeout, conn->nat->auth_timeout, 0);
+
+	msg = msgb_alloc_headroom(4096, 128, "auth message");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate auth msg\n");
+		return;
+	}
+
+	msgb_v_put(msg, IPAC_MSGT_ID_GET);
+	bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS);
+}
+
+static int ussd_listen_cb(struct osmo_fd *bfd, unsigned int what)
+{
+	struct bsc_nat_ussd_con *conn;
+	struct bsc_nat *nat;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+	int fd;
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		perror("accept");
+		return fd;
+	}
+
+	nat = (struct bsc_nat *) bfd->data;
+	osmo_counter_inc(nat->stats.ussd.reconn);
+
+	conn = bsc_nat_ussd_alloc(nat);
+	if (!conn) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate USSD con struct.\n");
+		close(fd);
+		return -1;
+	}
+
+	osmo_wqueue_init(&conn->queue, 10);
+	conn->queue.bfd.data = conn;
+	conn->queue.bfd.fd = fd;
+	conn->queue.bfd.when = BSC_FD_READ;
+	conn->queue.read_cb = ussd_read_cb;
+	conn->queue.write_cb = bsc_write_cb;
+
+	if (osmo_fd_register(&conn->queue.bfd) < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to register USSD fd.\n");
+		bsc_nat_ussd_destroy(conn);
+		return -1;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "USSD Connection on %d with IP: %s\n",
+	     fd, inet_ntoa(sa.sin_addr));
+
+	/* do authentication */
+	ussd_start_auth(conn);
+	return 0;
+}
+
+int bsc_ussd_init(struct bsc_nat *nat)
+{
+	struct in_addr addr;
+
+	addr.s_addr = INADDR_ANY;
+	if (nat->ussd_local)
+		inet_aton(nat->ussd_local, &addr);
+
+	nat->ussd_listen.data = nat;
+	return make_sock(&nat->ussd_listen, IPPROTO_TCP,
+			 ntohl(addr.s_addr), 5001, 0, ussd_listen_cb, nat);
+}
+
+static int forward_ussd_simple(struct nat_sccp_connection *con, struct msgb *input)
+{
+	struct msgb *copy;
+	struct bsc_nat_ussd_con *ussd;
+
+	if (!con->bsc->nat->ussd_con)
+		return -1;
+
+	copy = msgb_alloc_headroom(4096, 128, "forward bts");
+	if (!copy) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		return -1;
+	}
+
+	/* copy the data into the copy */
+	copy->l2h = msgb_put(copy, msgb_l2len(input));
+	memcpy(copy->l2h, input->l2h, msgb_l2len(input));
+
+	/* send it out */
+	ussd = con->bsc->nat->ussd_con;
+	bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP);
+	return 0;
+}
+
+static int forward_ussd(struct nat_sccp_connection *con, const struct ussd_request *req,
+			struct msgb *input)
+{
+	struct msgb *msg, *copy;
+	struct ipac_msgt_sccp_state *state;
+	struct bsc_nat_ussd_con *ussd;
+	uint16_t lac, ci;
+
+	if (!con->bsc->nat->ussd_con)
+		return -1;
+
+	msg = msgb_alloc_headroom(4096, 128, "forward ussd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		return -1;
+	}
+
+	copy = msgb_alloc_headroom(4096, 128, "forward bts");
+	if (!copy) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	copy->l2h = msgb_put(copy, msgb_l2len(input));
+	memcpy(copy->l2h, input->l2h, msgb_l2len(input));
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = IPAC_MSGT_SCCP_OLD;
+
+	/* fill out the data */
+	state = (struct ipac_msgt_sccp_state *) msgb_put(msg, sizeof(*state));
+	state->trans_id = req->transaction_id;
+	state->invoke_id = req->invoke_id;
+	memcpy(&state->src_ref, &con->remote_ref, sizeof(con->remote_ref));
+	memcpy(&state->dst_ref, &con->real_ref, sizeof(con->real_ref));
+	memcpy(state->imsi, con->filter_state.imsi, strlen(con->filter_state.imsi));
+
+	/* add additional tag/values */
+	lac = htons(con->lac);
+	ci = htons(con->ci);
+	msgb_tv_fixed_put(msg, USSD_LAC_IE, sizeof(lac), (const uint8_t *) &lac);
+	msgb_tv_fixed_put(msg, USSD_CI_IE, sizeof(ci), (const uint8_t *) &ci);
+
+	ussd = con->bsc->nat->ussd_con;
+	bsc_do_write(&ussd->queue, msg, IPAC_PROTO_IPACCESS);
+	bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP);
+
+	return 0;
+}
+
+int bsc_ussd_check(struct nat_sccp_connection *con, struct bsc_nat_parsed *parsed,
+		   struct msgb *msg)
+{
+	uint32_t len;
+	uint8_t msg_type;
+	uint8_t proto;
+	uint8_t ti;
+	struct gsm48_hdr *hdr48;
+	struct bsc_msg_acc_lst *lst;
+	struct ussd_request req;
+
+	/*
+	 * various checks to avoid the decoding work. Right now we only want to
+	 * decode if the connection was created for USSD, we do have a USSD access
+	 * list, a query, a IMSI and such...
+	 */
+	if (con->filter_state.con_type != FLT_CON_TYPE_SSA)
+		return 0;
+
+	if (!con->filter_state.imsi)
+		return 0;
+
+	/* We have not verified the IMSI yet */
+	if (!con->authorized)
+		return 0;
+
+	if (!con->bsc->nat->ussd_lst_name)
+		return 0;
+	if (!con->bsc->nat->ussd_query)
+		return 0;
+
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return 0;
+
+	if (strlen(con->filter_state.imsi) > GSM23003_IMSI_MAX_DIGITS)
+		return 0;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return 0;
+
+	proto = gsm48_hdr_pdisc(hdr48);
+	msg_type = gsm48_hdr_msg_type(hdr48);
+	ti = gsm48_hdr_trans_id_no_ti(hdr48);
+	if (proto != GSM48_PDISC_NC_SS)
+		return 0;
+
+	if (msg_type == GSM0480_MTYPE_REGISTER) {
+
+		/* now check if it is a IMSI we care about */
+		lst = bsc_msg_acc_lst_find(&con->bsc->nat->access_lists,
+					   con->bsc->nat->ussd_lst_name);
+		if (!lst)
+			return 0;
+
+		if (bsc_msg_acc_lst_check_allow(lst, con->filter_state.imsi) != 0)
+			return 0;
+
+		/* now decode the message and see if we really want to handle it */
+		memset(&req, 0, sizeof(req));
+		if (gsm0480_decode_ussd_request(hdr48, len, &req) != 1)
+			return 0;
+		if (req.text[0] == 0xff)
+			return 0;
+
+		if (regexec(&con->bsc->nat->ussd_query_re,
+			    req.text, 0, NULL, 0) == REG_NOMATCH)
+			return 0;
+
+		/* found a USSD query for our subscriber */
+		LOGP(DNAT, LOGL_NOTICE, "Found USSD query for %s\n",
+			con->filter_state.imsi);
+		con->ussd_ti[ti] = 1;
+		if (forward_ussd(con, &req, msg) != 0)
+			return 0;
+		return 1;
+	} else if (msg_type == GSM0480_MTYPE_FACILITY && con->ussd_ti[ti]) {
+		LOGP(DNAT, LOGL_NOTICE, "Forwarding message part of TI: %d %s\n",
+		     ti, con->filter_state.imsi);
+		if (forward_ussd_simple(con, msg) != 0)
+			return 0;
+		return 1;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-nitb/Makefile.am b/openbsc/src/osmo-nitb/Makefile.am
new file mode 100644
index 0000000..f4ef487
--- /dev/null
+++ b/openbsc/src/osmo-nitb/Makefile.am
@@ -0,0 +1,45 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(COVERAGE_CFLAGS) \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOCTRL_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(LIBCRYPTO_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+bin_PROGRAMS = \
+	osmo-nitb \
+	$(NULL)
+
+osmo_nitb_SOURCES = \
+	bsc_hack.c \
+	$(NULL)
+
+osmo_nitb_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBSMPP34_LIBS) \
+	$(LIBCRYPTO_LIBS) \
+	-ldbi \
+	$(NULL)
diff --git a/openbsc/src/osmo-nitb/bsc_hack.c b/openbsc/src/osmo-nitb/bsc_hack.c
new file mode 100644
index 0000000..2aeaa8f
--- /dev/null
+++ b/openbsc/src/osmo-nitb/bsc_hack.c
@@ -0,0 +1,402 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/db.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/stats.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/sms_queue.h>
+#include <openbsc/vty.h>
+#include <openbsc/bss.h>
+#include <openbsc/mncc.h>
+#include <openbsc/token_auth.h>
+#include <openbsc/handover_decision.h>
+#include <openbsc/rrlp.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <openbsc/ctrl.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/smpp.h>
+
+#include "../../bscconfig.h"
+
+/* MCC and MNC for the Location Area Identifier */
+struct gsm_network *bsc_gsmnet = 0;
+static const char *database_name = "hlr.sqlite3";
+static const char *config_file = "openbsc.cfg";
+static const char *rf_ctrl_path = NULL;
+extern const char *openbsc_copyright;
+static int daemonize = 0;
+static const char *mncc_sock_path = NULL;
+static int use_db_counter = 1;
+
+/* timer to store statistics */
+#define DB_SYNC_INTERVAL	60, 0
+#define EXPIRE_INTERVAL		10, 0
+
+static struct osmo_timer_list db_sync_timer;
+
+static void create_pcap_file(char *file)
+{
+	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+	int fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, mode);
+
+	if (fd < 0) {
+		perror("Failed to open file for pcap");
+		return;
+	}
+
+	e1_set_pcap_fd(fd);
+}
+
+static void print_usage()
+{
+	printf("Usage: osmo-nitb\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help                  This text.\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM  Enable debugging.\n");
+	printf("  -D --daemonize             Fork the process into a background daemon.\n");
+	printf("  -c --config-file filename  The config file to use.\n");
+	printf("  -s --disable-color\n");
+	printf("  -l --database db-name      The database to use.\n");
+	printf("  -T --timestamp             Prefix every log line with a timestamp.\n");
+	printf("  -V --version               Print the version of OpenBSC.\n");
+	printf("  -P --rtp-proxy             Enable the RTP Proxy code inside OpenBSC.\n");
+	printf("  -e --log-level number      Set a global loglevel.\n");
+	printf("  -M --mncc-sock-path PATH   Disable built-in MNCC handler and offer socket.\n");
+	printf("  -m --mncc-sock 	     Same as `-M /tmp/bsc_mncc' (deprecated).\n");
+	printf("  -C --no-dbcounter          Disable regular syncing of counters to database.\n");
+	printf("  -r --rf-ctl PATH           A unix domain socket to listen for cmds.\n");
+	printf("  -p --pcap PATH             Write abis communication to pcap trace file.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"daemonize", 0, 0, 'D'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"database", 1, 0, 'l'},
+			{"pcap", 1, 0, 'p'},
+			{"timestamp", 0, 0, 'T'},
+			{"version", 0, 0, 'V' },
+			{"rtp-proxy", 0, 0, 'P'},
+			{"log-level", 1, 0, 'e'},
+			{"mncc-sock", 0, 0, 'm'},
+			{"mncc-sock-path", 1, 0, 'M'},
+			{"no-dbcounter", 0, 0, 'C'},
+			{"rf-ctl", 1, 0, 'r'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:Dsl:ar:p:TPVc:e:mCr:M:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(osmo_stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(osmo_stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'l':
+			database_name = optarg;
+			break;
+		case 'c':
+			config_file = optarg;
+			break;
+		case 'p':
+			create_pcap_file(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(osmo_stderr_target, 1);
+			break;
+		case 'P':
+			ipacc_rtp_direct = 0;
+			break;
+		case 'e':
+			log_set_log_level(osmo_stderr_target, atoi(optarg));
+			break;
+		case 'M':
+			mncc_sock_path = optarg;
+			break;
+		case 'm':
+			mncc_sock_path = "/tmp/bsc_mncc";
+			break;
+		case 'C':
+			use_db_counter = 0;
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		case 'r':
+			rf_ctrl_path = optarg;
+			break;
+		default:
+			/* catch unknown options *as well as* missing arguments. */
+			fprintf(stderr, "Error in command line options. Exiting.\n");
+			exit(-1);
+			break;
+		}
+	}
+}
+
+extern void *tall_vty_ctx;
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+	case SIGTERM:
+		bsc_shutdown_net(bsc_gsmnet);
+		osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
+		sleep(3);
+		exit(0);
+		break;
+	case SIGABRT:
+		osmo_generate_backtrace();
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		talloc_report_full(tall_vty_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+/* timer handling */
+static int _db_store_counter(struct osmo_counter *counter, void *data)
+{
+	return db_store_counter(counter);
+}
+
+static void db_sync_timer_cb(void *data)
+{
+	/* store counters to database and re-schedule */
+	osmo_counters_for_each(_db_store_counter, NULL);
+	osmo_timer_schedule(&db_sync_timer, DB_SYNC_INTERVAL);
+}
+
+static void subscr_expire_cb(void *data)
+{
+	subscr_expire(bsc_gsmnet->subscr_group);
+	osmo_timer_schedule(&bsc_gsmnet->subscr_expire_timer, EXPIRE_INTERVAL);
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OpenBSC",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	vty_info.copyright = openbsc_copyright;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
+	talloc_ctx_init(tall_bsc_ctx);
+	on_dso_load_token();
+	on_dso_load_rrlp();
+	on_dso_load_ho_dec();
+
+	libosmo_abis_init(tall_bsc_ctx);
+	osmo_init_logging(&log_info);
+	osmo_stats_init(tall_bsc_ctx);
+	bts_init();
+	vty_init(&vty_info);
+
+	/* Parse options */
+	handle_options(argc, argv);
+
+	/* Allocate global gsm_network struct; choose socket/internal MNCC */
+	rc = bsc_network_alloc(mncc_sock_path?
+			       mncc_sock_from_cc : int_mncc_recv);
+	if (rc) {
+		fprintf(stderr, "Allocation failed. Exiting.\n");
+		exit(1);
+	}
+
+	/* Initialize VTY */
+	bsc_vty_init(bsc_gsmnet);
+	ctrl_vty_init(tall_bsc_ctx);
+
+#ifdef BUILD_SMPP
+	if (smpp_openbsc_alloc_init(tall_bsc_ctx) < 0)
+		return -1;
+#endif
+
+	/* Initialize MNCC socket if appropriate */
+	if (mncc_sock_path) {
+		rc = mncc_sock_init(bsc_gsmnet, mncc_sock_path);
+		if (rc) {
+			fprintf(stderr, "MNCC socket initialization failed. exiting.\n");
+			exit(1);
+		}
+	} else
+		DEBUGP(DMNCC, "Using internal MNCC handler.\n");
+
+	/*
+	 * For osmo-nitb, skip TCH/F for now, because otherwise dyn TS
+	 * always imply the possibility to have a mix of TCH/F and
+	 * TCH/H channels; if two phones request a TCH/F and a TCH/H,
+	 * respectively, they cannot call each other. If we deny TCH/F,
+	 * they will both fall back to TCH/H, and dynamic channels are
+	 * usable. See OS#1778.
+	 *
+	 * A third-party MSC may well be able to handle a TCH/H TCH/F
+	 * mismatch. Moreover, this option may be overwritten in the
+	 * config file or in VTY.
+	 */
+	bsc_gsmnet->dyn_ts_allow_tch_f = false;
+
+	/* Read the config */
+	rc = bsc_network_configure(config_file);
+	if (rc < 0) {
+		fprintf(stderr, "Reading config failed. Exiting.\n");
+		exit(1);
+	}
+
+#ifdef BUILD_SMPP
+	smpp_openbsc_start(bsc_gsmnet);
+#endif
+	bsc_api_init(bsc_gsmnet, msc_bsc_api());
+
+	/* start control interface after reading config for
+	 * ctrl_vty_get_bind_addr() */
+	bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet,
+					       ctrl_vty_get_bind_addr(),
+					       OSMO_CTRL_PORT_NITB_BSC);
+	if (!bsc_gsmnet->ctrl) {
+		printf("Failed to initialize control interface. Exiting.\n");
+		return -1;
+	}
+
+	if (bsc_base_ctrl_cmds_install() != 0) {
+		printf("Failed to initialize the BSC control commands.\n");
+		return -1;
+	}
+
+	if (msc_ctrl_cmds_install() != 0) {
+		printf("Failed to initialize the MSC control commands.\n");
+		return -1;
+	}
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	bsc_gsmnet->bsc_data->rf_ctrl = osmo_bsc_rf_create(rf_ctrl_path, bsc_gsmnet);
+	if (!bsc_gsmnet->bsc_data->rf_ctrl) {
+		fprintf(stderr, "Failed to create the RF service.\n");
+		exit(1);
+	}
+
+	if (db_init(database_name)) {
+		printf("DB: Failed to init database. Please check the option settings.\n");
+		return -1;
+	}
+	printf("DB: Database initialized.\n");
+
+	if (db_prepare()) {
+		printf("DB: Failed to prepare database.\n");
+		return -1;
+	}
+	printf("DB: Database prepared.\n");
+
+	/* setup the timer */
+	osmo_timer_setup(&db_sync_timer, db_sync_timer_cb, NULL);
+	if (use_db_counter)
+		osmo_timer_schedule(&db_sync_timer, DB_SYNC_INTERVAL);
+
+	osmo_timer_setup(&bsc_gsmnet->subscr_expire_timer, subscr_expire_cb,
+			 NULL);
+	osmo_timer_schedule(&bsc_gsmnet->subscr_expire_timer, EXPIRE_INTERVAL);
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGTERM, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	osmo_init_ignore_signals();
+
+	/* start the SMS queue */
+	if (sms_queue_start(bsc_gsmnet, 20) != 0)
+		return -1;
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	while (1) {
+		log_reset_context();
+		osmo_select_main(0);
+	}
+}
diff --git a/openbsc/src/utils/Makefile.am b/openbsc/src/utils/Makefile.am
new file mode 100644
index 0000000..26494e1
--- /dev/null
+++ b/openbsc/src/utils/Makefile.am
@@ -0,0 +1,147 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(SQLITE3_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+noinst_HEADERS = \
+	meas_db.h \
+	$(NULL)
+
+bin_PROGRAMS = \
+	bs11_config \
+	isdnsync \
+	meas_json \
+	$(NULL)
+if HAVE_SQLITE3
+bin_PROGRAMS += \
+	osmo-meas-udp2db \
+	$(NULL)
+if HAVE_PCAP
+bin_PROGRAMS += \
+	osmo-meas-pcap2db \
+	$(NULL)
+endif
+endif
+if HAVE_LIBCDK
+bin_PROGRAMS += \
+	meas_vis \
+	$(NULL)
+endif
+
+if BUILD_SMPP
+noinst_PROGRAMS = \
+	smpp_mirror \
+	$(NULL)
+endif
+
+bs11_config_SOURCES = \
+	bs11_config.c \
+	$(NULL)
+
+bs11_config_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(NULL)
+
+isdnsync_SOURCES = \
+	isdnsync.c \
+	$(NULL)
+
+smpp_mirror_SOURCES = \
+	smpp_mirror.c \
+	$(NULL)
+
+smpp_mirror_LDADD = \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBSMPP34_LIBS) \
+	$(NULL)
+
+meas_vis_SOURCES = \
+	meas_vis.c \
+	$(NULL)
+
+meas_vis_LDADD = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	-lcdk \
+	-lncurses \
+	$(NULL)
+
+meas_vis_CFLAGS = \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(NULL)
+
+osmo_meas_pcap2db_SOURCES = \
+	meas_pcap2db.c \
+	meas_db.c \
+	$(NULL)
+
+osmo_meas_pcap2db_LDADD = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(SQLITE3_LIBS) \
+	-lpcap \
+	$(NULL)
+
+osmo_meas_pcap2db_CFLAGS = \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
+osmo_meas_udp2db_SOURCES = \
+	meas_udp2db.c \
+	meas_db.c \
+	$(NULL)
+
+osmo_meas_udp2db_LDADD = \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(SQLITE3_LIBS) \
+	$(NULL)
+
+osmo_meas_udp2db_CFLAGS = \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
+meas_json_SOURCES = \
+	meas_json.c \
+	$(NULL)
+
+meas_json_LDADD = \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(NULL)
+
+meas_json_CFLAGS = \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
diff --git a/openbsc/src/utils/bs11_config.c b/openbsc/src/utils/bs11_config.c
new file mode 100644
index 0000000..a0f3cb7
--- /dev/null
+++ b/openbsc/src/utils/bs11_config.c
@@ -0,0 +1,953 @@
+/* Siemens BS-11 microBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This software is based on ideas (but not code) of BS11Config
+ * (C) 2009 by Dieter Spaar <spaar@mirider.augusta.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <sys/stat.h>
+
+#include <openbsc/common_bsc.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/tlv.h>
+#include <openbsc/debug.h>
+#include <osmocom/core/select.h>
+#include <openbsc/rs232.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+
+static void *tall_bs11cfg_ctx;
+static struct e1inp_sign_link *oml_link;
+
+/* state of our bs11_config application */
+enum bs11cfg_state {
+	STATE_NONE,
+	STATE_LOGON_WAIT,
+	STATE_LOGON_ACK,
+	STATE_SWLOAD,
+	STATE_QUERY,
+};
+static enum bs11cfg_state bs11cfg_state = STATE_NONE;
+static char *command, *value;
+struct osmo_timer_list status_timer;
+
+static const uint8_t obj_li_attr[] = {
+	NM_ATT_BS11_BIT_ERR_THESH, 0x09, 0x00,
+	NM_ATT_BS11_L1_PROT_TYPE, 0x00,
+	NM_ATT_BS11_LINE_CFG, 0x00,
+};
+static const uint8_t obj_bbsig0_attr[] = {
+	NM_ATT_BS11_RSSI_OFFS, 0x02, 0x00, 0x00,
+	NM_ATT_BS11_DIVERSITY, 0x01, 0x00,
+};
+static const uint8_t obj_pa0_attr[] = {
+	NM_ATT_BS11_TXPWR, 0x01, BS11_TRX_POWER_GSM_30mW,
+};
+static const char *trx1_password = "1111111111";
+#define TEI_OML	25
+
+/* dummy function to keep gsm_data.c happy */
+struct osmo_counter *osmo_counter_alloc(const char *name)
+{
+	return NULL;
+}
+
+int handle_serial_msg(struct msgb *rx_msg);
+
+/* create all objects for an initial configuration */
+static int create_objects(struct gsm_bts *bts)
+{
+	fprintf(stdout, "Crating Objects for minimal config\n");
+	abis_nm_bs11_create_object(bts, BS11_OBJ_LI, 0, sizeof(obj_li_attr),
+				   obj_li_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_GPSU, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_ALCO, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_CCLK, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 0,
+				   sizeof(obj_bbsig0_attr), obj_bbsig0_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 0,
+				   sizeof(obj_pa0_attr), obj_pa0_attr);
+	abis_nm_bs11_create_envaBTSE(bts, 0);
+	abis_nm_bs11_create_envaBTSE(bts, 1);
+	abis_nm_bs11_create_envaBTSE(bts, 2);
+	abis_nm_bs11_create_envaBTSE(bts, 3);
+
+	abis_nm_bs11_conn_oml_tei(bts, 0, 1, 0xff, TEI_OML);
+
+	abis_nm_bs11_set_trx_power(bts->c0, BS11_TRX_POWER_GSM_30mW);
+	
+	sleep(1);
+
+	abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+	sleep(1);
+
+	return 0;
+}
+
+static int create_trx1(struct gsm_bts *bts)
+{
+	uint8_t bbsig1_attr[sizeof(obj_bbsig0_attr)+12];
+	uint8_t *cur = bbsig1_attr;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 1);
+
+	if (!trx)
+		trx = gsm_bts_trx_alloc(bts);
+
+	fprintf(stdout, "Crating Objects for TRX1\n");
+
+	abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+	sleep(1);
+
+	cur = tlv_put(cur, NM_ATT_BS11_PASSWORD, 10,
+		      (uint8_t *)trx1_password);
+	memcpy(cur, obj_bbsig0_attr, sizeof(obj_bbsig0_attr));
+	abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 1,
+				   sizeof(bbsig1_attr), bbsig1_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 1,
+				   sizeof(obj_pa0_attr), obj_pa0_attr);
+	abis_nm_bs11_set_trx_power(trx, BS11_TRX_POWER_GSM_30mW);
+	
+	return 0;
+}
+
+static char *serial_port = "/dev/ttyUSB0";
+static char *fname_safety = "BTSBMC76.SWI";
+static char *fname_software = "HS011106.SWL";
+static int delay_ms = 0;
+static int win_size = 8;
+static int param_disconnect = 0;
+static int param_restart = 0;
+static int param_forced = 0;
+static struct gsm_bts *g_bts;
+
+static int file_is_readable(const char *fname)
+{
+	int rc;
+	struct stat st;
+
+	rc = stat(fname, &st);
+	if (rc < 0)
+		return 0;
+
+	if (S_ISREG(st.st_mode) && (st.st_mode & S_IRUSR))
+		return 1;
+
+	return 0;
+}
+
+static int percent;
+static int percent_old;
+
+/* callback function passed to the ABIS OML code */
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *msg,
+		       void *data, void *param)
+{
+	if (hook != GSM_HOOK_NM_SWLOAD)
+		return 0;
+
+	switch (event) {
+	case NM_MT_LOAD_INIT_ACK:
+		fprintf(stdout, "Software Load Initiate ACK\n");
+		break;
+	case NM_MT_LOAD_INIT_NACK:
+		fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+		exit(5);
+		break;
+	case NM_MT_LOAD_END_ACK:
+		if (data) {
+			/* we did a safety load and must activate it */
+			abis_nm_software_activate(g_bts, fname_safety,
+						  swload_cbfn, g_bts);
+			sleep(5);
+		}
+		break;
+	case NM_MT_LOAD_END_NACK:
+		fprintf(stderr, "ERROR: Software Load End NACK\n");
+		exit(3);
+		break;
+	case NM_MT_ACTIVATE_SW_NACK:
+		fprintf(stderr, "ERROR: Activate Software NACK\n");
+		exit(4);
+		break;
+	case NM_MT_ACTIVATE_SW_ACK:
+		bs11cfg_state = STATE_NONE;
+		
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+		percent = abis_nm_software_load_status(g_bts);
+		if (percent > percent_old)
+			printf("Software Download Progress: %d%%\n", percent);
+		percent_old = percent;
+		break;
+	}
+	return 0;
+}
+
+static const struct value_string bs11_linkst_names[] = {
+	{ 0,	"Down" },
+	{ 1,	"Up" },
+	{ 2,	"Restoring" },
+	{ 0,	NULL }
+};
+
+static const char *linkstate_name(uint8_t linkstate)
+{
+	return get_value_string(bs11_linkst_names, linkstate);
+}
+
+static const struct value_string mbccu_load_names[] = {
+	{ 0,	"No Load" },
+	{ 1,	"Load BTSCAC" },
+	{ 2,	"Load BTSDRX" },
+	{ 3,	"Load BTSBBX" },
+	{ 4,	"Load BTSARC" },
+	{ 5,	"Load" },
+	{ 0,	NULL }
+};
+
+static const char *mbccu_load_name(uint8_t linkstate)
+{
+	return get_value_string(mbccu_load_names, linkstate);
+}
+
+static const char *bts_phase_name(uint8_t phase)
+{
+	switch (phase) {
+	case BS11_STATE_WARM_UP:
+	case BS11_STATE_WARM_UP_2:
+		return "Warm Up";
+		break;
+	case BS11_STATE_LOAD_SMU_SAFETY:
+		return "Load SMU Safety";
+		break;
+	case BS11_STATE_LOAD_SMU_INTENDED:
+		return "Load SMU Intended";
+		break;
+	case BS11_STATE_LOAD_MBCCU:
+		return "Load MBCCU";
+		break;
+	case BS11_STATE_SOFTWARE_RQD:
+		return "Software required";
+		break;
+	case BS11_STATE_WAIT_MIN_CFG:
+	case BS11_STATE_WAIT_MIN_CFG_2:
+		return "Wait minimal config";
+		break;
+	case BS11_STATE_MAINTENANCE:
+		return "Maintenance";
+		break;
+	case BS11_STATE_NORMAL:
+		return "Normal";
+		break;
+	case BS11_STATE_ABIS_LOAD:
+		return "Abis load";
+		break;
+	default:
+		return "Unknown";
+		break;
+	}
+}
+
+static const char *trx_power_name(uint8_t pwr)
+{
+	switch (pwr) {
+	case BS11_TRX_POWER_GSM_2W:	
+		return "2W (GSM)";
+	case BS11_TRX_POWER_GSM_250mW:
+		return "250mW (GSM)";
+	case BS11_TRX_POWER_GSM_80mW:
+		return "80mW (GSM)";
+	case BS11_TRX_POWER_GSM_30mW:
+		return "30mW (GSM)";
+	case BS11_TRX_POWER_DCS_3W:
+		return "3W (DCS)";
+	case BS11_TRX_POWER_DCS_1W6:
+		return "1.6W (DCS)";
+	case BS11_TRX_POWER_DCS_500mW:
+		return "500mW (DCS)";
+	case BS11_TRX_POWER_DCS_160mW:
+		return "160mW (DCS)";
+	default:
+		return "unknown value";
+	}
+}
+
+static const char *pll_mode_name(uint8_t mode)
+{
+	switch (mode) {
+	case BS11_LI_PLL_LOCKED:
+		return "E1 Locked";
+	case BS11_LI_PLL_STANDALONE:
+		return "Standalone";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *cclk_acc_name(uint8_t acc)
+{
+	switch (acc) {
+	case 0:
+		/* Out of the demanded +/- 0.05ppm */
+		return "Medium";
+	case 1:
+		/* Synchronized with Abis, within demanded tolerance +/- 0.05ppm */
+		return "High";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *bport_lcfg_name(uint8_t lcfg)
+{
+	switch (lcfg) {
+	case BS11_LINE_CFG_STAR:
+		return "Star";
+	case BS11_LINE_CFG_MULTIDROP:
+		return "Multi-Drop";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *obj_name(struct abis_om_fom_hdr *foh)
+{
+	static char retbuf[256];
+
+	retbuf[0] = 0;
+
+	switch (foh->obj_class) {
+	case NM_OC_BS11:
+		strcat(retbuf, "BS11 ");
+		switch (foh->obj_inst.bts_nr) {
+		case BS11_OBJ_PA:
+			sprintf(retbuf+strlen(retbuf), "Power Amplifier %d ",
+				foh->obj_inst.ts_nr);
+			break;
+		case BS11_OBJ_LI:
+			sprintf(retbuf+strlen(retbuf), "Line Interface ");
+			break;
+		case BS11_OBJ_CCLK:
+			sprintf(retbuf+strlen(retbuf), "CCLK ");
+			break;
+		}
+		break;
+	case NM_OC_SITE_MANAGER:
+		strcat(retbuf, "SITE MANAGER ");
+		break;
+	case NM_OC_BS11_BPORT:
+		sprintf(retbuf+strlen(retbuf), "BPORT%u ",
+			foh->obj_inst.bts_nr);
+		break;
+	}
+	return retbuf;
+}
+
+static void print_state(struct tlv_parsed *tp)
+{
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_BTS_STATE)) {
+		uint8_t phase, mbccu;
+		if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 1) {
+			phase = *TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE);
+			printf("PHASE: %u %-20s ", phase & 0xf,
+				bts_phase_name(phase));
+		}
+		if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 2) {
+			mbccu = *(TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE)+1);
+			printf("MBCCU0: %-11s MBCCU1: %-11s ",
+				mbccu_load_name(mbccu & 0xf), mbccu_load_name(mbccu >> 4));
+		}
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_E1_STATE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_E1_STATE) >= 1) {
+		uint8_t e1_state = *TLVP_VAL(tp, NM_ATT_BS11_E1_STATE);
+		printf("Abis-link: %-9s ", linkstate_name(e1_state & 0xf));
+	}
+	printf("\n");
+}
+
+static int print_attr(struct tlv_parsed *tp)
+{
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_PCB_SERIAL)) {
+		printf("\tBS-11 ESN PCB Serial Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_PCB_SERIAL));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_HW_CODE_NO)) {
+		printf("\tBS-11 ESN Hardware Code Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_HW_CODE_NO)+6);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_FW_CODE_NO)) {
+		printf("\tBS-11 ESN Firmware Code Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_FW_CODE_NO)+6);
+	}
+#if 0
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_BOOT_SW_VERS)) {
+		printf("BS-11 Boot Software Version: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_BOOT_SW_VERS)+6);
+	}
+#endif
+	if (TLVP_PRESENT(tp, NM_ATT_ABIS_CHANNEL) &&
+	    TLVP_LEN(tp, NM_ATT_ABIS_CHANNEL) >= 3) {
+		const uint8_t *chan = TLVP_VAL(tp, NM_ATT_ABIS_CHANNEL);
+		printf("\tE1 Channel: Port=%u Timeslot=%u ",
+			chan[0], chan[1]);
+		if (chan[2] == 0xff)
+			printf("(Full Slot)\n");
+		else
+			printf("Subslot=%u\n", chan[2]);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_TEI))
+		printf("\tTEI: %d\n", *TLVP_VAL(tp, NM_ATT_TEI));
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_TXPWR) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_TXPWR) >= 1) {
+		printf("\tTRX Power: %s\n",
+			trx_power_name(*TLVP_VAL(tp, NM_ATT_BS11_TXPWR)));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL_MODE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_PLL_MODE) >= 1) {
+		printf("\tPLL Mode: %s\n",
+			pll_mode_name(*TLVP_VAL(tp, NM_ATT_BS11_PLL_MODE)));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_PLL) >= 4) {
+		const uint8_t *vp = TLVP_VAL(tp, NM_ATT_BS11_PLL);
+		printf("\tPLL Set Value=%d, Work Value=%d\n",
+			vp[0] << 8 | vp[1], vp[2] << 8 | vp[3]);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_ACCURACY) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_CCLK_ACCURACY) >= 1) {
+		const uint8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_ACCURACY);
+		printf("\tCCLK Accuracy: %s (%d)\n", cclk_acc_name(*acc), *acc);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_TYPE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_CCLK_TYPE) >= 1) {
+		const uint8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_TYPE);
+		printf("\tCCLK Type=%d\n", *acc);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_LINE_CFG) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_LINE_CFG) >= 1) {
+		const uint8_t *lcfg = TLVP_VAL(tp, NM_ATT_BS11_LINE_CFG);
+		printf("\tLine Configuration: %s (%d)\n",
+			bport_lcfg_name(*lcfg), *lcfg);
+	}
+
+
+
+	return 0;
+}
+
+static void cmd_query(void)
+{
+	struct gsm_bts_trx *trx = g_bts->c0;
+
+	bs11cfg_state = STATE_QUERY;
+	abis_nm_bs11_get_serno(g_bts);
+	abis_nm_bs11_get_oml_tei_ts(g_bts);
+	abis_nm_bs11_get_pll_mode(g_bts);
+	abis_nm_bs11_get_cclk(g_bts);
+	abis_nm_bs11_get_trx_power(trx);
+	trx = gsm_bts_trx_num(g_bts, 1);
+	if (trx)
+		abis_nm_bs11_get_trx_power(trx);
+	abis_nm_bs11_get_bport_line_cfg(g_bts, 0);
+	abis_nm_bs11_get_bport_line_cfg(g_bts, 1);
+	sleep(1);
+	abis_nm_bs11_factory_logon(g_bts, 0);
+	command = NULL;
+}
+
+/* handle a response from the BTS to a GET STATE command */
+static int handle_state_resp(enum abis_bs11_phase state)
+{
+	int rc = 0;
+
+	switch (state) {
+	case BS11_STATE_WARM_UP:
+	case BS11_STATE_LOAD_SMU_SAFETY:
+	case BS11_STATE_LOAD_SMU_INTENDED:
+	case BS11_STATE_LOAD_MBCCU:
+		break;
+	case BS11_STATE_SOFTWARE_RQD:
+		bs11cfg_state = STATE_SWLOAD;
+		/* send safety load. Use g_bts as private 'param'
+		 * argument, so our swload_cbfn can distinguish
+		 * a safety load from a regular software */
+		if (file_is_readable(fname_safety))
+			rc = abis_nm_software_load(g_bts, 0xff, fname_safety,
+						   win_size, param_forced,
+						   swload_cbfn, g_bts);
+		else
+			fprintf(stderr, "No valid Safety Load file \"%s\"\n",
+				fname_safety);
+		break;
+	case BS11_STATE_WAIT_MIN_CFG:
+	case BS11_STATE_WAIT_MIN_CFG_2:
+		bs11cfg_state = STATE_SWLOAD;
+		rc = create_objects(g_bts);
+		break;
+	case BS11_STATE_MAINTENANCE:
+		if (command) {
+			if (!strcmp(command, "disconnect"))
+				abis_nm_bs11_factory_logon(g_bts, 0);
+			else if (!strcmp(command, "reconnect"))
+				rc = abis_nm_bs11_bsc_disconnect(g_bts, 1);
+			else if (!strcmp(command, "software")
+			    && bs11cfg_state != STATE_SWLOAD) {
+				bs11cfg_state = STATE_SWLOAD;
+				/* send software (FIXME: over A-bis?) */
+				if (file_is_readable(fname_software))
+					rc = abis_nm_bs11_load_swl(g_bts, fname_software,
+								   win_size, param_forced,
+								   swload_cbfn);
+				else
+					fprintf(stderr, "No valid Software file \"%s\"\n",
+						fname_software);
+			} else if (!strcmp(command, "delete-trx1")) {
+				printf("Locing BBSIG and PA objects of TRX1\n");
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+						      BS11_OBJ_BBSIG, 0, 1,
+						      NM_STATE_LOCKED);
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+						      BS11_OBJ_PA, 0, 1,
+						      NM_STATE_LOCKED);
+				sleep(1);
+				printf("Deleting BBSIG and PA objects of TRX1\n");
+				abis_nm_bs11_delete_object(g_bts, BS11_OBJ_BBSIG, 1);
+				abis_nm_bs11_delete_object(g_bts, BS11_OBJ_PA, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "create-trx1")) {
+				create_trx1(g_bts);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-e1-locked")) {
+				abis_nm_bs11_set_pll_locked(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-standalone")) {
+				abis_nm_bs11_set_pll_locked(g_bts, 0);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-setvalue")) {
+				abis_nm_bs11_set_pll(g_bts, atoi(value));
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-workvalue")) {
+				/* To set the work value we need to login as FIELD */
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				sleep(1);
+				abis_nm_bs11_infield_logon(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_set_pll(g_bts, atoi(value));
+				sleep(1);
+				abis_nm_bs11_infield_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "oml-tei")) {
+				abis_nm_bs11_conn_oml_tei(g_bts, 0, 1, 0xff, TEI_OML);
+				command = NULL;
+			} else if (!strcmp(command, "restart")) {
+				abis_nm_bs11_restart(g_bts);
+				command = NULL;
+			} else if (!strcmp(command, "query")) {
+				cmd_query();
+			} else if (!strcmp(command, "create-bport1")) {
+				abis_nm_bs11_create_bport(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "delete-bport1")) {
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11_BPORT, 1, 0xff, 0xff, NM_STATE_LOCKED);
+				sleep(1);
+				abis_nm_bs11_delete_bport(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport0-star")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_STAR);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport0-multidrop")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_MULTIDROP);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport1-multidrop")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 1, BS11_LINE_CFG_MULTIDROP);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			}
+
+		}
+		break;
+	case BS11_STATE_NORMAL:
+		if (command) {
+			if (!strcmp(command, "reconnect"))
+				abis_nm_bs11_factory_logon(g_bts, 0);
+			else if (!strcmp(command, "disconnect"))
+				abis_nm_bs11_bsc_disconnect(g_bts, 0);
+			else if (!strcmp(command, "query")) {
+				cmd_query();
+			}
+		} else if (param_disconnect) {
+			param_disconnect = 0;
+			abis_nm_bs11_bsc_disconnect(g_bts, 0);
+			if (param_restart) {
+				param_restart = 0;
+				abis_nm_bs11_restart(g_bts);
+			}
+		}
+		break;
+	default:
+		break;
+	}
+	return rc;
+}
+
+/* handle a fully-received message/packet from the RS232 port */
+static int abis_nm_bs11cfg_rcvmsg(struct msgb *rx_msg)
+{
+	struct e1inp_sign_link *link = rx_msg->dst;
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	struct tlv_parsed tp;
+	int rc = -1;
+
+#if 0
+	const uint8_t too_fast[] = { 0x12, 0x80, 0x00, 0x00, 0x02, 0x02 };
+
+	if (rx_msg->len < LAPD_HDR_LEN
+			  + sizeof(struct abis_om_fom_hdr)
+			  + sizeof(struct abis_om_hdr)) {
+		if (!memcmp(rx_msg->data + 2, too_fast,
+			    sizeof(too_fast))) {
+			fprintf(stderr, "BS11 tells us we're too "
+				"fast, try --delay bigger than %u\n",
+				delay_ms);
+			return -E2BIG;
+		} else
+			fprintf(stderr, "unknown BS11 message\n");
+	}
+#endif
+
+	oh = (struct abis_om_hdr *) msgb_l2(rx_msg);
+	foh = (struct abis_om_fom_hdr *) oh->data;
+	switch (foh->msg_type) {
+	case NM_MT_BS11_LMT_LOGON_ACK:
+		printf("LMT LOGON: ACK\n\n");
+		if (bs11cfg_state == STATE_NONE)
+			bs11cfg_state = STATE_LOGON_ACK;
+		rc = abis_nm_bs11_get_state(g_bts);
+		break;
+	case NM_MT_BS11_LMT_LOGOFF_ACK:
+		printf("LMT LOGOFF: ACK\n");
+		exit(0);
+		break;
+	case NM_MT_BS11_GET_STATE_ACK:
+		rc = abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+		print_state(&tp);
+		if (TLVP_PRESENT(&tp, NM_ATT_BS11_BTS_STATE) &&
+		    TLVP_LEN(&tp, NM_ATT_BS11_BTS_STATE) >= 1)
+			rc = handle_state_resp(*TLVP_VAL(&tp, NM_ATT_BS11_BTS_STATE));
+		break;
+	case NM_MT_GET_ATTR_RESP:
+		printf("\n%sATTRIBUTES:\n", obj_name(foh));
+		abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+		rc = print_attr(&tp);
+		//osmo_hexdump(foh->data, oh->length-sizeof(*foh));
+		break;
+	case NM_MT_BS11_SET_ATTR_ACK:
+		printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) ACK\n",
+			foh->obj_class, foh->obj_inst.bts_nr,
+			foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+		rc = 0;
+		break;
+	case NM_MT_BS11_SET_ATTR_NACK:
+		printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) NACK\n",
+			foh->obj_class, foh->obj_inst.bts_nr,
+			foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+		break;
+	case NM_MT_GET_ATTR_NACK:
+		printf("\n%sGET ATTR NACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_CREATE_OBJ_ACK:
+		printf("\n%sCREATE OBJECT ACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_CREATE_OBJ_NACK:
+		printf("\n%sCREATE OBJECT NACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_DELETE_OBJ_ACK:
+		printf("\n%sDELETE OBJECT ACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_DELETE_OBJ_NACK:
+		printf("\n%sDELETE OBJECT NACK\n", obj_name(foh));
+		break;
+	default:
+		rc = abis_nm_rcvmsg(rx_msg);
+	}
+	if (rc < 0) {
+		perror("ERROR in main loop");
+		//break;
+	}
+	/* flush the queue of pending messages to be sent. */
+	abis_nm_queue_send_next(link->trx->bts);
+	if (rc == 1)
+		return rc;
+
+	switch (bs11cfg_state) {
+	case STATE_NONE:
+		abis_nm_bs11_factory_logon(g_bts, 1);
+		break;
+	case STATE_LOGON_ACK:
+		osmo_timer_schedule(&status_timer, 5, 0);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+void status_timer_cb(void *data)
+{
+	abis_nm_bs11_get_state(g_bts);
+}
+
+static void print_banner(void)
+{
+	printf("bs11_config (C) 2009-2010 by Harald Welte and Dieter Spaar\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+}
+
+static void print_help(void)
+{
+	printf("bs11_config [options] [command]\n");
+	printf("\nSupported options:\n");
+	printf("\t-h --help\t\t\tPrint this help text\n");
+	printf("\t-p --port </dev/ttyXXX>\t\tSpecify serial port\n");
+	printf("\t-s --software <file>\t\tSpecify Software file\n");
+	printf("\t-S --safety <file>\t\tSpecify Safety Load file\n");
+	printf("\t-d --delay <ms>\t\t\tSpecify delay in milliseconds\n");
+	printf("\t-D --disconnect\t\t\tDisconnect BTS from BSC\n");
+	printf("\t-w --win-size <num>\t\tSpecify Window Size\n");
+	printf("\t-f --forced\t\t\tForce Software Load\n");
+	printf("\nSupported commands:\n");
+	printf("\tquery\t\t\tQuery the BS-11 about serial number and configuration\n");
+	printf("\tdisconnect\t\tDisconnect A-bis link (go into administrative state)\n");
+	printf("\tresconnect\t\tReconnect A-bis link (go into normal state)\n");
+	printf("\trestart\t\t\tRestart the BTS\n");
+	printf("\tsoftware\t\tDownload Software (only in administrative state)\n");
+	printf("\tcreate-trx1\t\tCreate objects for TRX1 (Danger: Your BS-11 might overheat)\n");
+	printf("\tdelete-trx1\t\tDelete objects for TRX1\n");
+	printf("\tpll-e1-locked\t\tSet the PLL to be locked to E1 clock\n");
+	printf("\tpll-standalone\t\tSet the PLL to be in standalone mode\n");
+	printf("\tpll-setvalue <value>\tSet the PLL set value\n");
+	printf("\tpll-workvalue <value>\tSet the PLL work value\n");
+	printf("\toml-tei\t\t\tSet OML E1 TS and TEI\n");
+	printf("\tbport0-star\t\tSet BPORT0 line config to star\n");
+	printf("\tbport0-multidrop\tSet BPORT0 line config to multidrop\n");
+	printf("\tbport1-multidrop\tSet BPORT1 line config to multidrop\n");
+	printf("\tcreate-bport1\t\tCreate BPORT1 object\n");
+	printf("\tdelete-bport1\t\tDelete BPORT1 object\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	int option_index = 0;
+	print_banner();
+
+	while (1) {
+		int c;
+		static struct option long_options[] = {
+			{ "help", 0, 0, 'h' },
+			{ "port", 1, 0, 'p' },
+			{ "software", 1, 0, 's' },
+			{ "safety", 1, 0, 'S' },
+			{ "delay", 1, 0, 'd' },
+			{ "disconnect", 0, 0, 'D' },
+			{ "win-size", 1, 0, 'w' },
+			{ "forced", 0, 0, 'f' },
+			{ "restart", 0, 0, 'r' },
+			{ "debug", 1, 0, 'b'},
+		};
+
+		c = getopt_long(argc, argv, "hp:s:S:td:Dw:fra:",
+				long_options, &option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_help();
+			exit(0);
+		case 'p':
+			serial_port = optarg;
+			break;
+		case 'b':
+			log_parse_category_mask(osmo_stderr_target, optarg);
+			break;
+		case 's':
+			fname_software = optarg;
+			break;
+		case 'S':
+			fname_safety = optarg;
+			break;
+		case 'd':
+			delay_ms = atoi(optarg);
+			break;
+		case 'w':
+			win_size = atoi(optarg);
+			break;
+		case 'D':
+			param_disconnect = 1;
+			break;
+		case 'f':
+			param_forced = 1;
+			break;
+		case 'r':
+			param_disconnect = 1;
+			param_restart = 1;
+			break;
+		default:
+			break;
+		}
+	}
+	if (optind < argc) {
+		command = argv[optind];
+	        if (optind+1 < argc)
+			value = argv[optind+1];
+	}
+
+}
+
+static int num_sigint;
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "\nsignal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		num_sigint++;
+		abis_nm_bs11_factory_logon(g_bts, 0);
+		if (num_sigint >= 3)
+			exit(0);
+		break;
+	}
+}
+
+static int bs11cfg_sign_link(struct msgb *msg)
+{
+	msg->dst = oml_link;
+	return abis_nm_bs11cfg_rcvmsg(msg);
+}
+
+struct e1inp_line_ops bs11cfg_e1inp_line_ops = {
+	.sign_link	= bs11cfg_sign_link,
+};
+
+extern int bts_model_bs11_init(void);
+int main(int argc, char **argv)
+{
+	struct gsm_network *gsmnet;
+	struct e1inp_line *line;
+
+	tall_bs11cfg_ctx = talloc_named_const(NULL, 0, "bs11-config");
+	msgb_talloc_ctx_init(tall_bs11cfg_ctx, 0);
+
+	osmo_init_logging(&log_info);
+	handle_options(argc, argv);
+	bts_model_bs11_init();
+
+	gsmnet = bsc_network_init(tall_bs11cfg_ctx, 1, 1, NULL);
+	if (!gsmnet) {
+		fprintf(stderr, "Unable to allocate gsm network\n");
+		exit(1);
+	}
+	g_bts = gsm_bts_alloc_register(gsmnet, GSM_BTS_TYPE_BS11,
+					HARDCODED_BSIC);
+
+	/* Override existing OML callback handler to set our own. */
+	g_bts->model->oml_rcvmsg = abis_nm_bs11cfg_rcvmsg;
+
+	libosmo_abis_init(tall_bs11cfg_ctx);
+
+	/* Initialize virtual E1 line over rs232. */
+	line = talloc_zero(tall_bs11cfg_ctx, struct e1inp_line);
+	if (!line) {
+		fprintf(stderr, "Unable to allocate memory for virtual E1 line\n");
+		exit(1);
+	}
+	/* set the serial port. */
+	bs11cfg_e1inp_line_ops.cfg.rs232.port = serial_port;
+	bs11cfg_e1inp_line_ops.cfg.rs232.delay = delay_ms;
+
+	line->driver = e1inp_driver_find("rs232");
+	if (!line->driver) {
+		fprintf(stderr, "cannot find `rs232' driver, giving up.\n");
+		exit(1);
+	}
+	e1inp_line_bind_ops(line, &bs11cfg_e1inp_line_ops);
+
+	/* configure and create signalling link for OML. */
+	e1inp_ts_config_sign(&line->ts[0], line);
+	g_bts->oml_link = oml_link =
+		e1inp_sign_link_create(&line->ts[0], E1INP_SIGN_OML,
+					g_bts->c0, TEI_OML, 0);
+
+	e1inp_line_update(line);
+
+	signal(SIGINT, &signal_handler);
+
+	abis_nm_bs11_factory_logon(g_bts, 1);
+	//abis_nm_bs11_get_serno(g_bts);
+
+	osmo_timer_setup(&status_timer, status_timer_cb, NULL);
+
+	while (1) {
+		if (osmo_select_main(0) < 0)
+			break;
+	}
+
+	abis_nm_bs11_factory_logon(g_bts, 0);
+
+	exit(0);
+}
diff --git a/openbsc/src/utils/isdnsync.c b/openbsc/src/utils/isdnsync.c
new file mode 100644
index 0000000..cc8ff67
--- /dev/null
+++ b/openbsc/src/utils/isdnsync.c
@@ -0,0 +1,189 @@
+/* isdnsync.c
+ *
+ * Author       Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All rights reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include "mISDNif.h"
+#define MISDN_OLD_AF_COMPATIBILITY
+#define AF_COMPATIBILITY_FUNC
+#include "compat_af_isdn.h"
+
+int card = 0;
+int sock = -1;
+
+int mISDN_open(void)
+{
+	int			fd, ret;
+	struct mISDN_devinfo	devinfo;
+	struct sockaddr_mISDN	l2addr;
+
+	fd = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (fd < 0) {
+		fprintf(stderr, "could not open socket (%s)\n", strerror(errno));
+		return fd;
+	}
+	devinfo.id = card;
+	ret = ioctl(fd, IMGETDEVINFO, &devinfo);
+	if (ret < 0) {
+		fprintf(stderr,"could not send IOCTL IMGETCOUNT (%s)\n", strerror(errno));
+		close(fd);
+		return ret;
+	}
+	close(fd);
+	if (!(devinfo.Dprotocols & (1 << ISDN_P_TE_S0))
+	 && !(devinfo.Dprotocols & (1 << ISDN_P_TE_E1))) {
+		fprintf(stderr,"Interface does not support TE mode (%s)\n", strerror(errno));
+		return ret;
+	}
+	fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_TE);
+	if (fd < 0) {
+		fprintf(stderr,"could not open ISDN_P_LAPD_TE socket (%s)\n", strerror(errno));
+		return fd;
+	}
+	l2addr.family = AF_ISDN;
+	l2addr.dev = card;
+	l2addr.channel = 0;
+	l2addr.sapi = 0;
+	l2addr.tei = 0;
+	ret = bind(fd, (struct sockaddr *)&l2addr, sizeof(l2addr));
+	if (ret < 0) {
+		fprintf(stderr,"could not bind socket for card %d (%s)\n", card, strerror(errno));
+		close(fd);
+		return ret;
+	}
+	sock = fd;
+
+	return sock;
+}
+
+
+void mISDN_handle(void)
+{
+	int ret;
+	fd_set rfd;
+	struct timeval tv;
+	struct sockaddr_mISDN addr;
+	socklen_t alen;
+	unsigned char buffer[2048];
+	struct mISDNhead *hh = (struct mISDNhead *)buffer;
+	int l1 = 0, l2 = 0, tei = 0;
+
+	while(1) {
+again:
+		FD_ZERO(&rfd);
+		FD_SET(sock, &rfd);
+		tv.tv_sec = 2;
+		tv.tv_usec = 0;
+		ret = select(sock+1, &rfd, NULL, NULL, &tv);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			fprintf(stderr, "%s aborted: %s\n", __FUNCTION__, strerror(errno));
+			break;
+		}
+		if (FD_ISSET(sock, &rfd)) {
+			alen = sizeof(addr);
+			ret = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &alen);
+			if (ret < 0) {
+				fprintf(stderr, "%s read socket error %s\n", __FUNCTION__, strerror(errno));
+			} else if (ret < MISDN_HEADER_LEN) {
+					fprintf(stderr, "%s read socket shor frame\n", __FUNCTION__);
+			} else {
+				switch(hh->prim) {
+					case MPH_ACTIVATE_IND:
+					case PH_ACTIVATE_IND:
+						if (!l1) {
+							printf("PH_ACTIVATE\n");
+							printf("*** Sync available from interface :-)\n");
+							l1 = 1;
+						}
+						goto again;
+					break;
+					case MPH_DEACTIVATE_IND:
+					case PH_DEACTIVATE_IND:
+						if (l1) {
+							printf("PH_DEACTIVATE\n");
+							printf("*** Lost sync on interface        :-(\n");
+							l1 = 0;
+						}
+						goto again;
+					break;
+					case DL_ESTABLISH_IND:
+					case DL_ESTABLISH_CNF:
+						printf("DL_ESTABLISH\n");
+						l2 = 1;
+						goto again;
+					break;
+					case DL_RELEASE_IND:
+					case DL_RELEASE_CNF:
+						printf("DL_RELEASE\n");
+						l2 = 0;
+						goto again;
+					break;
+					case DL_INFORMATION_IND:
+						printf("DL_INFORMATION (tei %d sapi %d)\n", addr.tei, addr.sapi);
+						tei = 1;
+					break;
+					default:
+//						printf("prim %x\n", hh->prim);
+						goto again;
+				}
+			}
+		}
+		if (tei && !l2) {
+			hh->prim = DL_ESTABLISH_REQ;
+			printf("-> activating layer 2\n");
+			sendto(sock, buffer, MISDN_HEADER_LEN, 0, (struct sockaddr *) &addr, alen);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	if (argc <= 1)
+	{
+		printf("Usage: %s <card>\n\n", argv[0]);
+		printf("Opens given card number in TE-mode PTP and tries to keep layer 2 established.\n");
+		printf("This keeps layer 1 activated to retrieve a steady sync signal from network.\n");
+		return(0);
+	}
+
+	card = atoi(argv[1]);
+
+	init_af_isdn();
+
+	if ((ret = mISDN_open() < 0))
+		return(ret);
+
+	mISDN_handle();
+
+	close(sock);
+
+	return 0;
+}
diff --git a/openbsc/src/utils/meas_db.c b/openbsc/src/utils/meas_db.c
new file mode 100644
index 0000000..d81efca
--- /dev/null
+++ b/openbsc/src/utils/meas_db.c
@@ -0,0 +1,330 @@
+/* Routines for storing measurement reports in SQLite3 database */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <sqlite3.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/meas_rep.h>
+
+#include "meas_db.h"
+
+#define INS_MR "INSERT INTO meas_rep (time, imsi, name, scenario, nr, bs_power, ms_timing_offset, fpc, ms_l1_pwr, ms_l1_ta) VALUES (?,?,?,?,?,?,?,?,?,?)"
+#define INS_UD "INSERT INTO meas_rep_unidir (meas_id, rx_lev_full, rx_lev_sub, rx_qual_full, rx_qual_sub, dtx, uplink) VALUES (?,?,?,?,?,?,?)"
+#define UPD_MR "UPDATE meas_rep SET ul_unidir=?, dl_unidir=? WHERE id=?"
+
+struct meas_db_state {
+	sqlite3 *db;
+	sqlite3_stmt *stmt_ins_ud;
+	sqlite3_stmt *stmt_ins_mr;
+	sqlite3_stmt *stmt_upd_mr;
+};
+
+/* macros to check for SQLite3 result codes */
+#define _SCK_OK(db, call, exp)				\
+	do {						\
+		int rc = call;				\
+		if (rc != exp) {			\
+			fprintf(stderr,"SQL Error in line %u: %s\n",	\
+				__LINE__, sqlite3_errmsg(db));		\
+			goto err_io;					\
+		}							\
+	} while (0)
+#define SCK_OK(db, call)	_SCK_OK(db, call, SQLITE_OK)
+#define SCK_DONE(db, call)	_SCK_OK(db, call, SQLITE_DONE)
+
+static int _insert_ud(struct meas_db_state *st, unsigned long meas_id, int dtx,
+		      int uplink, const struct gsm_meas_rep_unidir *ud)
+{
+	unsigned long rowid;
+
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 1, meas_id));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 2,
+					rxlev2dbm(ud->full.rx_lev)));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 3,
+					rxlev2dbm(ud->sub.rx_lev)));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 4, ud->full.rx_qual));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 5, ud->sub.rx_qual));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 6, dtx));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 7, uplink));
+
+	SCK_DONE(st->db, sqlite3_step(st->stmt_ins_ud));
+
+	SCK_OK(st->db, sqlite3_reset(st->stmt_ins_ud));
+
+	return sqlite3_last_insert_rowid(st->db);
+err_io:
+	exit(1);
+}
+
+/* insert a measurement report into the database */
+int meas_db_insert(struct meas_db_state *st, const char *imsi,
+		   const char *name, unsigned long timestamp,
+		   const char *scenario,
+		   const struct gsm_meas_rep *mr)
+{
+	int rc;
+	sqlite3_int64 rowid, ul_rowid, dl_rowid;
+
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 1, timestamp));
+
+	if (imsi)
+		SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 2,
+						 imsi, -1, SQLITE_STATIC));
+	else
+		SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 2));
+
+	if (name)
+		SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 3,
+						 name, -1, SQLITE_STATIC));
+	else
+		SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 3));
+
+	if (scenario)
+		SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 4,
+						 scenario, -1, SQLITE_STATIC));
+	else
+		SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 4));
+
+
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 5, mr->nr));
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 6, mr->bs_power));
+
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 7, mr->ms_timing_offset));
+	else
+		SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 7));
+
+	if (mr->flags & MEAS_REP_F_FPC)
+		SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 1));
+	else
+		SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 0));
+
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 9,
+						mr->ms_l1.pwr));
+		SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 10,
+						mr->ms_l1.ta));
+	}
+
+	SCK_DONE(st->db, sqlite3_step(st->stmt_ins_mr));
+	SCK_OK(st->db, sqlite3_reset(st->stmt_ins_mr));
+
+	rowid = sqlite3_last_insert_rowid(st->db);
+
+	/* insert uplink measurement */
+	ul_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_UL_DTX,
+				1, &mr->ul);
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 1, ul_rowid));
+
+	/* insert downlink measurement, if present */
+	if (mr->flags & MEAS_REP_F_DL_VALID) {
+		dl_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_DL_DTX,
+			       	      0, &mr->dl);
+		SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 2, dl_rowid));
+	} else
+		SCK_OK(st->db, sqlite3_bind_null(st->stmt_upd_mr, 2));
+
+	/* update meas_rep with the id's of the unidirectional
+	 * measurements */
+	SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 3, rowid));
+	SCK_DONE(st->db, sqlite3_step(st->stmt_upd_mr));
+	SCK_OK(st->db, sqlite3_reset(st->stmt_upd_mr));
+
+	return 0;
+
+err_io:
+	return -EIO;
+}
+
+int meas_db_begin(struct meas_db_state *st)
+{
+	SCK_OK(st->db, sqlite3_exec(st->db, "BEGIN", NULL, NULL, NULL));
+
+	return 0;
+
+err_io:
+	return -EIO;
+}
+
+int meas_db_commit(struct meas_db_state *st)
+{
+	SCK_OK(st->db, sqlite3_exec(st->db, "COMMIT", NULL, NULL, NULL));
+
+	return 0;
+
+err_io:
+	return -EIO;
+}
+
+static const char *create_stmts[] = {
+	"CREATE TABLE IF NOT EXISTS meas_rep ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT,"
+		"time TIMESTAMP,"
+		"imsi TEXT,"
+		"name TEXT,"
+		"scenario TEXT,"
+		"nr INTEGER,"
+		"bs_power INTEGER NOT NULL,"
+		"ms_timing_offset INTEGER,"
+		"fpc INTEGER NOT NULL DEFAULT 0,"
+		"ul_unidir INTEGER REFERENCES meas_rep_unidir(id),"
+		"dl_unidir INTEGER REFERENCES meas_rep_unidir(id),"
+		"ms_l1_pwr INTEGER,"
+		"ms_l1_ta INTEGER"
+	")",
+	"CREATE TABLE IF NOT EXISTS meas_rep_unidir ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT,"
+		"meas_id INTEGER NOT NULL REFERENCES meas_rep(id),"
+		"rx_lev_full INTEGER NOT NULL,"
+		"rx_lev_sub INTEGER NOT NULL,"
+		"rx_qual_full INTEGER NOT NULL,"
+		"rx_qual_sub INTEGER NOT NULL,"
+		"dtx BOOLEAN NOT NULL DEFAULT 0,"
+	       	"uplink BOOLEAN NOT NULL"
+	")",
+	"CREATE VIEW IF NOT EXISTS path_loss AS "
+		"SELECT "
+			"meas_rep.id, "
+			"datetime(time,'unixepoch') AS timestamp, "
+			"imsi, "
+			"name, "
+			"scenario, "
+			"ms_timing_offset, "
+			"ms_l1_ta, "
+			"fpc, "
+			"ms_l1_pwr, "
+			"ud_ul.rx_lev_full AS ul_rx_lev_full, "
+			"ms_l1_pwr-ud_ul.rx_lev_full AS ul_path_loss_full, "
+			"ud_ul.rx_lev_sub ul_rx_lev_sub, "
+			"ms_l1_pwr-ud_ul.rx_lev_sub AS ul_path_loss_sub, "
+			"ud_ul.rx_qual_full AS ul_rx_qual_full, "
+			"ud_ul.rx_qual_sub AS ul_rx_qual_sub, "
+			"bs_power, "
+			"ud_dl.rx_lev_full AS dl_rx_lev_full, "
+			"bs_power-ud_dl.rx_lev_full AS dl_path_loss_full, "
+			"ud_dl.rx_lev_sub AS dl_rx_lev_sub, "
+			"bs_power-ud_dl.rx_lev_sub AS dl_path_loss_sub, "
+			"ud_dl.rx_qual_full AS dl_rx_qual_full, "
+			"ud_dl.rx_qual_sub AS dl_rx_qual_sub "
+		"FROM "
+			"meas_rep, "
+			"meas_rep_unidir AS ud_dl, "
+			"meas_rep_unidir AS ud_ul "
+		"WHERE "
+			"ud_ul.id = meas_rep.ul_unidir AND "
+			"ud_dl.id = meas_rep.dl_unidir",
+	"CREATE VIEW IF NOT EXISTS overview AS "
+		"SELECT "
+			"id,"
+			"timestamp,"
+			"imsi,"
+			"name,"
+			"scenario,"
+			"ms_l1_pwr,"
+			"ul_rx_lev_full,"
+			"ul_path_loss_full,"
+			"ul_rx_qual_full,"
+			"bs_power,"
+			"dl_rx_lev_full,"
+			"dl_path_loss_full,"
+			"dl_rx_qual_full "
+		"FROM path_loss",
+};
+
+static int check_create_tbl(struct meas_db_state *st)
+{
+	int i, rc;
+
+	for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+		SCK_OK(st->db, sqlite3_exec(st->db, create_stmts[i],
+					    NULL, NULL, NULL));
+	}
+
+	return 0;
+err_io:
+	return -EIO;
+}
+
+
+#define PREP_CHK(db, stmt, ptr)						\
+	do {								\
+		int rc;							\
+		rc = sqlite3_prepare_v2(db, stmt, strlen(stmt)+1,	\
+					ptr, NULL); 			\
+		if (rc != SQLITE_OK) {					\
+			fprintf(stderr, "Error during prepare of '%s': %s\n", \
+				stmt, sqlite3_errmsg(db)); 		\
+			goto err_io;					\
+		}							\
+	} while (0)
+
+struct meas_db_state *meas_db_open(void *ctx, const char *fname)
+{
+	int rc;
+	struct meas_db_state *st = talloc_zero(ctx, struct meas_db_state);
+
+	if (!st)
+		return NULL;
+
+	rc = sqlite3_open_v2(fname, &st->db,
+			     SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,
+			     NULL);
+	if (rc != SQLITE_OK) {
+		fprintf(stderr, "Unable to open DB: %s\n",
+			sqlite3_errmsg(st->db));
+		goto err_io;
+	}
+
+	rc = check_create_tbl(st);
+
+	PREP_CHK(st->db, INS_MR, &st->stmt_ins_mr);
+	PREP_CHK(st->db, INS_UD, &st->stmt_ins_ud);
+	PREP_CHK(st->db, UPD_MR, &st->stmt_upd_mr);
+
+	return st;
+err_io:
+	talloc_free(st);
+	return NULL;
+}
+
+void meas_db_close(struct meas_db_state *st)
+{
+	if (sqlite3_finalize(st->stmt_ins_mr) != SQLITE_OK)
+		fprintf(stderr, "DB insert measurement report finalize error: %s\n",
+			sqlite3_errmsg(st->db));
+	if (sqlite3_finalize(st->stmt_ins_ud) != SQLITE_OK)
+		fprintf(stderr, "DB insert unidir finalize error: %s\n",
+			sqlite3_errmsg(st->db));
+	if (sqlite3_finalize(st->stmt_upd_mr) != SQLITE_OK)
+		fprintf(stderr, "DB update measurement report finalize error: %s\n",
+			sqlite3_errmsg(st->db));
+	if (sqlite3_close(st->db) != SQLITE_OK)
+		fprintf(stderr, "Unable to close DB, abandoning.\n");
+
+	talloc_free(st);
+
+}
diff --git a/openbsc/src/utils/meas_db.h b/openbsc/src/utils/meas_db.h
new file mode 100644
index 0000000..889e902
--- /dev/null
+++ b/openbsc/src/utils/meas_db.h
@@ -0,0 +1,17 @@
+#ifndef OPENBSC_MEAS_DB_H
+#define OPENBSC_MEAS_DB_H
+
+struct meas_db_state;
+
+struct meas_db_state *meas_db_open(void *ctx, const char *fname);
+void meas_db_close(struct meas_db_state *st);
+
+int meas_db_begin(struct meas_db_state *st);
+int meas_db_commit(struct meas_db_state *st);
+
+int meas_db_insert(struct meas_db_state *st, const char *imsi,
+		   const char *name, unsigned long timestamp,
+		   const char *scenario,
+		   const struct gsm_meas_rep *mr);
+
+#endif
diff --git a/openbsc/src/utils/meas_json.c b/openbsc/src/utils/meas_json.c
new file mode 100644
index 0000000..51eb6c7
--- /dev/null
+++ b/openbsc/src/utils/meas_json.c
@@ -0,0 +1,190 @@
+/* Convert measurement report feed into JSON feed printed to stdout.
+ * Each measurement report is printed as a separae JSON root entry.
+ * All measurement reports are separated by a new line.
+ */
+
+/* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * With parts of code adopted from different places in OpenBSC.
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_data_shared.h>
+#include <openbsc/meas_feed.h>
+
+static void print_meas_rep_uni_json(struct gsm_meas_rep_unidir *mru)
+{
+	printf("\"RXL-FULL\":%d, \"RXL-SUB\":%d, ",
+		rxlev2dbm(mru->full.rx_lev),
+		rxlev2dbm(mru->sub.rx_lev));
+	printf("\"RXQ-FULL\":%d, \"RXQ-SUB\":%d",
+		mru->full.rx_qual, mru->sub.rx_qual);
+}
+
+static void print_meas_rep_json(struct gsm_meas_rep *mr)
+{
+	int i;
+
+	printf("\"NR\":%d", mr->nr);
+
+	if (mr->flags & MEAS_REP_F_DL_DTX)
+		printf(", \"DTXd\":true");
+
+	printf(", \"UL_MEAS\":{");
+	print_meas_rep_uni_json(&mr->ul);
+	printf("}");
+	printf(", \"BS_POWER\":%d", mr->bs_power);
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		printf(", \"MS_TO\":%d", mr->ms_timing_offset);
+
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		printf(", \"L1_MS_PWR\":%d", mr->ms_l1.pwr);
+		printf(", \"L1_FPC\":%s",
+			mr->flags & MEAS_REP_F_FPC ? "true" : "false");
+		printf(", \"L1_TA\":%u", mr->ms_l1.ta);
+	}
+
+	if (mr->flags & MEAS_REP_F_UL_DTX)
+		printf(", \"DTXu\":true");
+	if (mr->flags & MEAS_REP_F_BA1)
+		printf(", \"BA1\":true");
+	if (mr->flags & MEAS_REP_F_DL_VALID) {
+		printf(", \"DL_MEAS\":{");
+		print_meas_rep_uni_json(&mr->dl);
+		printf("}");
+	}
+
+	if (mr->num_cell == 7)
+		return;
+	printf(", \"NUM_NEIGH\":%u, \"NEIGH\":[", mr->num_cell);
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		if (i!=0) printf(", ");
+		printf("{\"IDX\":%u, \"ARFCN\":%u, \"BSIC\":%u, \"POWER\":%d}",
+			mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+	}
+	printf("]");
+}
+
+static void print_chan_info_json(struct meas_feed_meas *mfm)
+{
+	printf("\"lchan_type\":\"%s\", \"pchan_type\":\"%s\", "
+		   "\"bts_nr\":%d, \"trx_nr\":%d, \"ts_nr\":%d, \"ss_nr\":%d",
+	gsm_lchant_name(mfm->lchan_type), gsm_pchan_name(mfm->pchan_type),
+	mfm->bts_nr, mfm->trx_nr, mfm->ts_nr, mfm->ss_nr);
+}
+
+static void print_meas_feed_json(struct meas_feed_meas *mfm)
+{
+	time_t now = time(NULL);
+
+	printf("{");
+	printf("\"time\":%ld, \"imsi\":\"%s\", \"name\":\"%s\", \"scenario\":\"%s\", ",
+		now, mfm->imsi, mfm->name, mfm->scenario);
+
+	switch (mfm->hdr.version) {
+	case 1:
+		printf("\"chan_info\":{");
+		print_chan_info_json(mfm);
+		printf("}, ");
+		/* no break, fall to version 0 */
+	case 0:
+		printf("\"meas_rep\":{");
+		print_meas_rep_json(&mfm->mr);
+		printf("}");
+		break;
+	}
+
+	printf("}\n");
+
+}
+
+static int handle_meas(struct msgb *msg)
+{
+	struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
+
+	print_meas_feed_json(mfm);
+
+	return 0;
+}
+
+static int handle_msg(struct msgb *msg)
+{
+	struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
+
+	if (mfh->version != MEAS_FEED_VERSION)
+		return -EINVAL;
+
+	switch (mfh->msg_type) {
+	case MEAS_FEED_MEAS:
+		handle_meas(msg);
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	int rc;
+
+	if (what & BSC_FD_READ) {
+		struct msgb *msg = msgb_alloc(1024, "UDP Rx");
+
+		rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+		if (rc < 0)
+			return rc;
+		msgb_put(msg, rc);
+		handle_msg(msg);
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int rc;
+	struct osmo_fd udp_ofd;
+
+	udp_ofd.cb = udp_fd_cb;
+	rc =  osmo_sock_init_ofd(&udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+	if (rc < 0)
+		exit(1);
+
+	while (1) {
+		osmo_select_main(0);
+	};
+
+	exit(0);
+}
diff --git a/openbsc/src/utils/meas_pcap2db.c b/openbsc/src/utils/meas_pcap2db.c
new file mode 100644
index 0000000..b874ac4
--- /dev/null
+++ b/openbsc/src/utils/meas_pcap2db.c
@@ -0,0 +1,138 @@
+/* read PCAP file with meas_feed data and write it to sqlite3 database */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <openbsc/meas_feed.h>
+
+#include <pcap/pcap.h>
+
+#include "meas_db.h"
+
+static struct meas_db_state *db;
+
+static void handle_mfm(const struct pcap_pkthdr *h,
+		       const struct meas_feed_meas *mfm)
+{
+	const char *scenario;
+
+	if (strlen(mfm->scenario))
+		scenario = mfm->scenario;
+	else
+		scenario = NULL;
+
+	meas_db_insert(db, mfm->imsi, mfm->name, h->ts.tv_sec,
+			scenario, &mfm->mr);
+}
+
+static void pcap_cb(u_char *user, const struct pcap_pkthdr *h,
+		   const u_char *bytes)
+{
+	const char *cur = bytes;
+	const struct iphdr *ip;
+	const struct udphdr *udp;
+	const struct meas_feed_meas *mfm;
+	uint16_t udplen;
+
+	if (h->caplen < 14+20+8)
+		return;
+
+	/* Check if there is IPv4 in the Ethernet */
+	if (cur[12] != 0x08 || cur[13] != 0x00)
+		return;
+
+	cur += 14;	/* ethernet header */
+	ip = (struct iphdr *) cur;
+
+	if (ip->version != 4)
+		return;
+	cur += ip->ihl * 4;
+
+	if (ip->protocol != IPPROTO_UDP)
+		return;
+
+	udp = (struct udphdr *) cur;
+
+	if (udp->dest != htons(8888))
+		return;
+
+	udplen = ntohs(udp->len);
+	if (udplen != sizeof(*udp) + sizeof(*mfm))
+		return;
+	cur += sizeof(*udp);
+
+	mfm = (const struct meas_feed_meas *) cur;
+
+	handle_mfm(h, mfm);
+}
+
+int main(int argc, char **argv)
+{
+	char errbuf[PCAP_ERRBUF_SIZE+1];
+	char *pcap_fname, *db_fname;
+	pcap_t *pc;
+	int rc;
+
+	if (argc < 3) {
+		fprintf(stderr, "You need to specify PCAP and database file\n");
+		exit(2);
+	}
+
+	pcap_fname = argv[1];
+	db_fname = argv[2];
+
+	pc = pcap_open_offline(pcap_fname, errbuf);
+	if (!pc) {
+		fprintf(stderr, "Cannot open %s: %s\n", pcap_fname, errbuf);
+		exit(1);
+	}
+
+	db = meas_db_open(NULL, db_fname);
+	if (!db)
+		exit(0);
+
+	rc = meas_db_begin(db);
+	if (rc < 0) {
+		fprintf(stderr, "Error during BEGIN\n");
+		exit(1);
+	}
+
+	pcap_loop(pc, 0 , pcap_cb, NULL);
+
+	meas_db_commit(db);
+
+	exit(0);
+}
diff --git a/openbsc/src/utils/meas_udp2db.c b/openbsc/src/utils/meas_udp2db.c
new file mode 100644
index 0000000..5032d0c
--- /dev/null
+++ b/openbsc/src/utils/meas_udp2db.c
@@ -0,0 +1,126 @@
+/* liesten to meas_feed on UDP and write it to sqlite3 database */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <openbsc/meas_feed.h>
+
+#include "meas_db.h"
+
+static struct osmo_fd udp_ofd;
+static struct meas_db_state *db;
+
+static int handle_msg(struct msgb *msg)
+{
+	struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
+	struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
+	const char *scenario;
+	time_t now = time(NULL);
+
+	if (mfh->version != MEAS_FEED_VERSION)
+		return -EINVAL;
+
+	if (mfh->msg_type != MEAS_FEED_MEAS)
+		return -EINVAL;
+
+	if (strlen(mfm->scenario))
+		scenario = mfm->scenario;
+	else
+		scenario = NULL;
+
+	meas_db_insert(db, mfm->imsi, mfm->name, now,
+			scenario, &mfm->mr);
+
+	return 0;
+}
+
+static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	int rc;
+
+	if (what & BSC_FD_READ) {
+		struct msgb *msg = msgb_alloc(1024, "UDP Rx");
+
+		rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+		if (rc < 0)
+			return rc;
+		msgb_put(msg, rc);
+		handle_msg(msg);
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	char *db_fname;
+	int rc;
+
+	msgb_talloc_ctx_init(NULL, 0);
+
+	if (argc < 2) {
+		fprintf(stderr, "You have to specify the database file name\n");
+		exit(2);
+	}
+
+	db_fname = argv[1];
+
+	udp_ofd.cb = udp_fd_cb;
+	rc =  osmo_sock_init_ofd(&udp_ofd, AF_INET, SOCK_DGRAM,
+			 	 IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+	if (rc < 0) {
+		fprintf(stderr, "Unable to create UDP listen socket\n");
+		exit(1);
+	}
+
+	db = meas_db_open(NULL, db_fname);
+	if (!db) {
+		fprintf(stderr, "Unable to open database\n");
+		exit(1);
+	}
+
+	/* FIXME: timer-based BEGIN/COMMIT */
+
+	while (1) {
+		osmo_select_main(0);
+	};
+
+	meas_db_close(db);
+
+	exit(0);
+}
+
diff --git a/openbsc/src/utils/meas_vis.c b/openbsc/src/utils/meas_vis.c
new file mode 100644
index 0000000..77194de
--- /dev/null
+++ b/openbsc/src/utils/meas_vis.c
@@ -0,0 +1,310 @@
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <netinet/in.h>
+
+#include <cdk/cdk.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <openbsc/meas_feed.h>
+
+struct ms_state_uni {
+	CDKSLIDER *cdk;
+	CDKLABEL *cdk_label;
+
+	time_t last_update;
+	char label[32];
+	char *_lbl[1];
+};
+
+
+struct ms_state {
+	struct llist_head list;
+
+	char name[31+1];
+	char imsi[15+1];
+	struct gsm_meas_rep mr;
+
+	struct ms_state_uni ul;
+	struct ms_state_uni dl;
+};
+
+struct state {
+	struct osmo_fd udp_ofd;
+	struct llist_head ms_list;
+
+	CDKSCREEN *cdkscreen;
+	WINDOW *curses_win;
+
+	CDKLABEL *cdk_title;
+	char *title;
+
+	CDKLABEL *cdk_header;
+	char header[256];
+};
+
+static struct state g_st;
+
+struct ms_state *find_ms(const char *imsi)
+{
+	struct ms_state *ms;
+
+	llist_for_each_entry(ms, &g_st.ms_list, list) {
+		if (!strcmp(ms->imsi, imsi))
+			return ms;
+	}
+	return NULL;
+}
+
+static struct ms_state *find_alloc_ms(const char *imsi)
+{
+	struct ms_state *ms;
+
+	ms = find_ms(imsi);
+	if (!ms) {
+		ms = talloc_zero(NULL, struct ms_state);
+		osmo_strlcpy(ms->imsi, imsi, sizeof(ms->imsi));
+		ms->ul._lbl[0] = ms->ul.label;
+		ms->dl._lbl[0] = ms->dl.label;
+		llist_add_tail(&ms->list, &g_st.ms_list);
+	}
+
+	return ms;
+}
+
+static int handle_meas(struct msgb *msg)
+{
+	struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
+	struct ms_state *ms = find_alloc_ms(mfm->imsi);
+	time_t now = time(NULL);
+
+	osmo_strlcpy(ms->name, mfm->name, sizeof(ms->name));
+	memcpy(&ms->mr, &mfm->mr, sizeof(ms->mr));
+	ms->ul.last_update = now;
+	if (ms->mr.flags & MEAS_REP_F_DL_VALID)
+		ms->dl.last_update = now;
+
+	/* move to head of list */
+	llist_del(&ms->list);
+	llist_add(&ms->list, &g_st.ms_list);
+
+	return 0;
+}
+
+static int handle_msg(struct msgb *msg)
+{
+	struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
+
+	if (mfh->version != MEAS_FEED_VERSION)
+		return -EINVAL;
+
+	switch (mfh->msg_type) {
+	case MEAS_FEED_MEAS:
+		handle_meas(msg);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	int rc;
+
+	if (what & BSC_FD_READ) {
+		struct msgb *msg = msgb_alloc(1024, "UDP Rx");
+
+		rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+		if (rc < 0)
+			return rc;
+		msgb_put(msg, rc);
+		handle_msg(msg);
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+
+static void destroy_dir(struct ms_state_uni *uni)
+{
+	if (uni->cdk) {
+		destroyCDKSlider(uni->cdk);
+		uni->cdk = NULL;
+	}
+	if (uni->cdk_label) {
+		destroyCDKLabel(uni->cdk_label);
+		uni->cdk_label = NULL;
+	}
+}
+
+#define DIR_UL	0
+#define DIR_DL	1
+static const char *dir_str[2] = {
+	[DIR_UL]	= "UL",
+	[DIR_DL]	= "DL",
+};
+
+static int colpair_by_qual(uint8_t rx_qual)
+{
+	if (rx_qual == 0)
+		return 24;
+	else if (rx_qual <= 4)
+		return 32;
+	else
+		return 16;
+}
+
+static int colpair_by_lev(int rx_lev)
+{
+	if (rx_lev < -95)
+		return 16;
+	else if (rx_lev < -80)
+		return 32;
+	else
+		return 24;
+}
+
+
+void write_uni(struct ms_state *ms, struct ms_state_uni *msu,
+		struct gsm_rx_lev_qual *lq, int dir, int row)
+{
+
+	char label[128];
+	time_t now = time(NULL);
+	int qual_col = colpair_by_qual(lq->rx_qual);
+	int lev_col = colpair_by_lev(rxlev2dbm(lq->rx_lev));
+	int color, pwr;
+
+	if (dir == DIR_UL) {
+		pwr = ms->mr.ms_l1.pwr;
+	} else {
+		pwr = ms->mr.bs_power;
+	}
+
+	color = A_REVERSE | COLOR_PAIR(lev_col) | ' ';
+	snprintf(label, sizeof(label), "%s %s ", ms->imsi, dir_str[dir]);
+	msu->cdk = newCDKSlider(g_st.cdkscreen, 0, row, NULL, label, color,
+				  COLS-40, rxlev2dbm(lq->rx_lev), -110, -47,
+				  1, 2, FALSE, FALSE);
+	//IsVisibleObj(ms->ul.cdk) = FALSE;
+	snprintf(msu->label, sizeof(msu->label), "</%d>%1d<!%d> %3d %2u %2d %4u",
+		 qual_col, lq->rx_qual, qual_col, pwr,
+		 ms->mr.ms_l1.ta, ms->mr.ms_timing_offset,
+		 now - msu->last_update);
+	msu->cdk_label = newCDKLabel(g_st.cdkscreen, RIGHT, row,
+					msu->_lbl, 1, FALSE, FALSE);
+}
+
+static void update_sliders(void)
+{
+	int num_vis_sliders = 0;
+	struct ms_state *ms;
+#define HEADER_LINES 2
+
+	/* remove all sliders */
+	llist_for_each_entry(ms, &g_st.ms_list, list) {
+		destroy_dir(&ms->ul);
+		destroy_dir(&ms->dl);
+
+	}
+
+	llist_for_each_entry(ms, &g_st.ms_list, list) {
+		struct gsm_rx_lev_qual *lq;
+		unsigned int row = HEADER_LINES + num_vis_sliders*3;
+
+		if (ms->mr.flags & MEAS_REP_F_UL_DTX)
+			lq = &ms->mr.ul.sub;
+		else
+			lq = &ms->mr.ul.full;
+		write_uni(ms, &ms->ul, lq, DIR_UL, row);
+
+		if (ms->mr.flags & MEAS_REP_F_DL_DTX)
+			lq = &ms->mr.dl.sub;
+		else
+			lq = &ms->mr.dl.full;
+		write_uni(ms, &ms->dl, lq, DIR_DL, row+1);
+
+		num_vis_sliders++;
+		if (num_vis_sliders >= LINES/3)
+			break;
+	}
+
+	refreshCDKScreen(g_st.cdkscreen);
+
+}
+
+const struct value_string col_strs[] = {
+	{ COLOR_WHITE,	"white" },
+	{ COLOR_RED,	"red" },
+	{ COLOR_GREEN,	"green" },
+	{ COLOR_YELLOW,	"yellow" },
+	{ COLOR_BLUE,	"blue" },
+	{ COLOR_MAGENTA,"magenta" },
+	{ COLOR_CYAN,	"cyan" },
+	{ COLOR_BLACK, 	"black" },
+	{ 0, NULL }
+};
+
+int main(int argc, char **argv)
+{
+	int rc;
+	char *header[1];
+	char *title[1];
+
+	msgb_talloc_ctx_init(NULL, 0);
+
+	printf("sizeof(gsm_meas_rep)=%u\n", sizeof(struct gsm_meas_rep));
+	printf("sizeof(meas_feed_meas)=%u\n", sizeof(struct meas_feed_meas));
+
+	INIT_LLIST_HEAD(&g_st.ms_list);
+	g_st.curses_win = initscr();
+	g_st.cdkscreen = initCDKScreen(g_st.curses_win);
+	initCDKColor();
+
+	g_st.title = "OpenBSC link quality monitor";
+	title[0] = g_st.title;
+	g_st.cdk_title = newCDKLabel(g_st.cdkscreen, CENTER, 0, title, 1, FALSE, FALSE);
+
+	snprintf(g_st.header, sizeof(g_st.header), "Q Pwr TA TO Time");
+	header[0] = g_st.header;
+	g_st.cdk_header = newCDKLabel(g_st.cdkscreen, RIGHT, 1, header, 1, FALSE, FALSE);
+
+#if 0
+	int i;
+	for (i = 0; i < 64; i++) {
+		short f, b;
+		pair_content(i, &f, &b);
+		attron(COLOR_PAIR(i));
+		printw("%u: %u (%s) ", i, f, get_value_string(col_strs, f));
+		printw("%u (%s)\n\r", b, get_value_string(col_strs, b));
+	}
+	refresh();
+	getch();
+	exit(0);
+#endif
+
+	g_st.udp_ofd.cb = udp_fd_cb;
+	rc =  osmo_sock_init_ofd(&g_st.udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+	if (rc < 0)
+		exit(1);
+
+	while (1) {
+		osmo_select_main(0);
+		update_sliders();
+	};
+
+	exit(0);
+}
diff --git a/openbsc/src/utils/smpp_mirror.c b/openbsc/src/utils/smpp_mirror.c
new file mode 100644
index 0000000..c570505
--- /dev/null
+++ b/openbsc/src/utils/smpp_mirror.c
@@ -0,0 +1,359 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+
+#include <openbsc/debug.h>
+
+/* FIXME: merge with smpp_smsc.c */
+#define SMPP_SYS_ID_LEN	16
+enum esme_read_state {
+	READ_ST_IN_LEN = 0,
+	READ_ST_IN_MSG = 1,
+};
+/* FIXME: merge with smpp_smsc.c */
+
+struct esme {
+	struct osmo_fd ofd;
+
+	uint32_t own_seq_nr;
+
+	struct osmo_wqueue wqueue;
+	enum esme_read_state read_state;
+	uint32_t read_len;
+	uint32_t read_idx;
+	struct msgb *read_msg;
+
+	uint8_t smpp_version;
+	char system_id[SMPP_SYS_ID_LEN+1];
+	char password[SMPP_SYS_ID_LEN+1];
+};
+
+/* FIXME: merge with smpp_smsc.c */
+#define SMPP34_UNPACK(rc, type, str, data, len)		\
+	memset(str, 0, sizeof(*str));			\
+	rc = smpp34_unpack(type, str, data, len)
+#define INIT_RESP(type, resp, req) 		{ \
+	memset((resp), 0, sizeof(*(resp)));	  \
+	(resp)->command_length	= 0;		  \
+	(resp)->command_id	= type;		  \
+	(resp)->command_status	= ESME_ROK;	  \
+	(resp)->sequence_number	= (req)->sequence_number;	\
+}
+#define PACK_AND_SEND(esme, ptr)	pack_and_send(esme, (ptr)->command_id, ptr)
+static inline uint32_t smpp_msgb_cmdid(struct msgb *msg)
+{
+	uint8_t *tmp = msgb_data(msg) + 4;
+	return ntohl(*(uint32_t *)tmp);
+}
+static uint32_t esme_inc_seq_nr(struct esme *esme)
+{
+	esme->own_seq_nr++;
+	if (esme->own_seq_nr > 0x7fffffff)
+		esme->own_seq_nr = 1;
+
+	return esme->own_seq_nr;
+}
+static int pack_and_send(struct esme *esme, uint32_t type, void *ptr)
+{
+	struct msgb *msg = msgb_alloc(4096, "SMPP_Tx");
+	int rc, rlen;
+	if (!msg)
+		return -ENOMEM;
+
+	rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr);
+	if (rc != 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n",
+		     esme->system_id, smpp34_strerror);
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msgb_put(msg, rlen);
+
+	if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n",
+		     esme->system_id);
+		msgb_free(msg);
+		return -EAGAIN;
+	}
+	return 0;
+}
+/* FIXME: merge with smpp_smsc.c */
+
+static struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag)
+{
+	struct tlv_t *t;
+
+	for (t = head; t != NULL; t = t->next) {
+		if (t->tag == tag)
+			return t;
+	}
+	return NULL;
+}
+
+static int smpp_handle_deliver(struct esme *esme, struct msgb *msg)
+{
+	struct deliver_sm_t deliver;
+	struct deliver_sm_resp_t deliver_r;
+	struct submit_sm_t submit;
+	tlv_t *t;
+	int rc;
+
+	memset(&deliver, 0, sizeof(deliver));
+	SMPP34_UNPACK(rc, DELIVER_SM, &deliver, msgb_data(msg), msgb_length(msg));
+	if (rc < 0)
+		return rc;
+
+	INIT_RESP(DELIVER_SM_RESP, &deliver_r, &deliver);
+
+	PACK_AND_SEND(esme, &deliver_r);
+
+	memset(&submit, 0, sizeof(submit));
+	submit.command_id = SUBMIT_SM;
+	submit.command_status = ESME_ROK;
+	submit.sequence_number = esme_inc_seq_nr(esme);
+
+	submit.dest_addr_ton =  deliver.source_addr_ton;
+	submit.dest_addr_npi =  deliver.source_addr_npi;
+	memcpy(submit.destination_addr, deliver.source_addr,
+		OSMO_MIN(sizeof(submit.destination_addr),
+			 sizeof(deliver.source_addr)));
+
+	submit.source_addr_ton = deliver.dest_addr_ton;
+	submit.source_addr_npi = deliver.dest_addr_npi;
+	memcpy(submit.source_addr, deliver.destination_addr,
+		OSMO_MIN(sizeof(submit.source_addr),
+			 sizeof(deliver.destination_addr)));
+
+	/* Mirror delivery receipts as a delivery acknowledgements. */
+	if (deliver.esm_class == 0x04) {
+		LOGP(DSMPP, LOGL_DEBUG, "%s\n", deliver.short_message);
+		submit.esm_class = 0x08;
+	} else {
+		submit.esm_class = deliver.esm_class;
+	}
+
+	submit.registered_delivery = deliver.registered_delivery;
+	submit.protocol_id = deliver.protocol_id;
+	submit.priority_flag = deliver.priority_flag;
+	memcpy(submit.schedule_delivery_time, deliver.schedule_delivery_time,
+	       OSMO_MIN(sizeof(submit.schedule_delivery_time),
+		        sizeof(deliver.schedule_delivery_time)));
+	memcpy(submit.validity_period, deliver.validity_period,
+		OSMO_MIN(sizeof(submit.validity_period),
+			 sizeof(deliver.validity_period)));
+	submit.registered_delivery = deliver.registered_delivery;
+	submit.replace_if_present_flag = deliver.replace_if_present_flag;
+	submit.data_coding = deliver.data_coding;
+	submit.sm_default_msg_id = deliver.sm_default_msg_id;
+	submit.sm_length = deliver.sm_length;
+	memcpy(submit.short_message, deliver.short_message,
+		OSMO_MIN(sizeof(submit.short_message),
+			 sizeof(deliver.short_message)));
+
+	/* FIXME: More TLV? */
+	t = find_tlv(deliver.tlv, TLVID_user_message_reference);
+	if (t) {
+		tlv_t tlv;
+
+		memset(&tlv, 0, sizeof(tlv));
+		tlv.tag = TLVID_user_message_reference;
+		tlv.length = 2;
+		tlv.value.val16 = t->value.val16;
+		build_tlv(&submit.tlv, &tlv);
+	}
+
+	return PACK_AND_SEND(esme, &submit);
+}
+
+static int bind_transceiver(struct esme *esme)
+{
+	struct bind_transceiver_t bind;
+
+	memset(&bind, 0, sizeof(bind));
+	bind.command_id = BIND_TRANSCEIVER;
+	bind.sequence_number = esme_inc_seq_nr(esme);
+	snprintf((char *)bind.system_id, sizeof(bind.system_id), "%s", esme->system_id);
+	snprintf((char *)bind.password, sizeof(bind.password), "%s", esme->password);
+	snprintf((char *)bind.system_type, sizeof(bind.system_type), "mirror");
+	bind.interface_version = esme->smpp_version;
+
+	return PACK_AND_SEND(esme, &bind);
+}
+
+static int smpp_pdu_rx(struct esme *esme, struct msgb *msg)
+{
+	uint32_t cmd_id = smpp_msgb_cmdid(msg);
+	int rc;
+
+	switch (cmd_id) {
+	case DELIVER_SM:
+		rc = smpp_handle_deliver(esme, msg);
+		break;
+	default:
+		LOGP(DSMPP, LOGL_NOTICE, "unhandled case %d\n", cmd_id);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+/* FIXME: merge with smpp_smsc.c */
+static int esme_read_cb(struct osmo_fd *ofd)
+{
+	struct esme *esme = ofd->data;
+	uint32_t len;
+	uint8_t *lenptr = (uint8_t *) &len;
+	uint8_t *cur;
+	struct msgb *msg;
+	int rdlen;
+	int rc;
+
+	switch (esme->read_state) {
+	case READ_ST_IN_LEN:
+		rdlen = sizeof(uint32_t) - esme->read_idx;
+		rc = read(ofd->fd, lenptr + esme->read_idx, rdlen);
+		if (rc < 0) {
+			LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %d\n",
+			     esme->system_id, rc);
+		} else if (rc == 0) {
+			goto dead_socket;
+		} else
+			esme->read_idx += rc;
+		if (esme->read_idx >= sizeof(uint32_t)) {
+			esme->read_len = ntohl(len);
+			msg = msgb_alloc(esme->read_len, "SMPP Rx");
+			if (!msg)
+				return -ENOMEM;
+			esme->read_msg = msg;
+			cur = msgb_put(msg, sizeof(uint32_t));
+			memcpy(cur, lenptr, sizeof(uint32_t));
+			esme->read_state = READ_ST_IN_MSG;
+			esme->read_idx = sizeof(uint32_t);
+		}
+		break;
+	case READ_ST_IN_MSG:
+		msg = esme->read_msg;
+		rdlen = esme->read_len - esme->read_idx;
+		rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg)));
+		if (rc < 0) {
+			LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %d\n",
+				esme->system_id, rc);
+		} else if (rc == 0) {
+			goto dead_socket;
+		} else {
+			esme->read_idx += rc;
+			msgb_put(msg, rc);
+		}
+
+		if (esme->read_idx >= esme->read_len) {
+			rc = smpp_pdu_rx(esme, esme->read_msg);
+			esme->read_msg = NULL;
+			esme->read_idx = 0;
+			esme->read_len = 0;
+			esme->read_state = READ_ST_IN_LEN;
+		}
+		break;
+	}
+
+	return 0;
+dead_socket:
+	msgb_free(esme->read_msg);
+	osmo_fd_unregister(&esme->wqueue.bfd);
+	close(esme->wqueue.bfd.fd);
+	esme->wqueue.bfd.fd = -1;
+	exit(2342);
+
+	return 0;
+}
+
+static int esme_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+	struct esme *esme = ofd->data;
+	int rc;
+
+	rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+	if (rc == 0) {
+		osmo_fd_unregister(&esme->wqueue.bfd);
+		close(esme->wqueue.bfd.fd);
+		esme->wqueue.bfd.fd = -1;
+		exit(99);
+	} else if (rc < msgb_length(msg)) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id);
+		return 0;
+	}
+
+	return 0;
+}
+
+static int smpp_esme_init(struct esme *esme, const char *host, uint16_t port)
+{
+	int rc;
+
+	if (port == 0)
+		port = 2775;
+
+	esme->own_seq_nr = rand();
+	esme_inc_seq_nr(esme);
+	osmo_wqueue_init(&esme->wqueue, 10);
+	esme->wqueue.bfd.data = esme;
+	esme->wqueue.read_cb = esme_read_cb;
+	esme->wqueue.write_cb = esme_write_cb;
+
+	rc = osmo_sock_init_ofd(&esme->wqueue.bfd, AF_UNSPEC, SOCK_STREAM,
+				IPPROTO_TCP, host, port, OSMO_SOCK_F_CONNECT);
+	if (rc < 0)
+		return rc;
+
+	return bind_transceiver(esme);
+}
+
+
+int main(int argc, char **argv)
+{
+	struct esme esme;
+	char *host = "localhost";
+	int port = 0;
+	int rc;
+
+	msgb_talloc_ctx_init(NULL, 0);
+
+	memset(&esme, 0, sizeof(esme));
+
+	osmo_init_logging(&log_info);
+
+	snprintf((char *) esme.system_id, sizeof(esme.system_id), "mirror");
+	snprintf((char *) esme.password, sizeof(esme.password), "mirror");
+	esme.smpp_version = 0x34;
+
+	if (argc >= 2)
+		host = argv[1];
+	if (argc >= 3)
+		port = atoi(argv[2]);
+
+	rc = smpp_esme_init(&esme, host, port);
+	if (rc < 0)
+		exit(1);
+
+	while (1) {
+		osmo_select_main(0);
+	}
+
+	exit(0);
+}
diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am
new file mode 100644
index 0000000..7208a2f
--- /dev/null
+++ b/openbsc/tests/Makefile.am
@@ -0,0 +1,94 @@
+SUBDIRS = \
+	gsm0408 \
+	db \
+	channel \
+	mgcp \
+	abis \
+	trau \
+	subscr \
+	mm_auth \
+	nanobts_omlattr \
+	$(NULL)
+
+if BUILD_NAT
+SUBDIRS += \
+	bsc-nat \
+	bsc-nat-trie \
+	$(NULL)
+endif
+if BUILD_BSC
+SUBDIRS += \
+	bsc \
+	$(NULL)
+endif
+if BUILD_SMPP
+SUBDIRS += \
+	smpp \
+	$(NULL)
+endif
+
+# The `:;' works around a Bash 3.2 bug when the output is not writeable.
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	:;{ \
+               echo '# Signature of the current package.' && \
+               echo 'm4_define([AT_PACKAGE_NAME],' && \
+               echo '  [$(PACKAGE_NAME)])' && \
+               echo 'm4_define([AT_PACKAGE_TARNAME],' && \
+               echo '  [$(PACKAGE_TARNAME)])' && \
+               echo 'm4_define([AT_PACKAGE_VERSION],' && \
+               echo '  [$(PACKAGE_VERSION)])' && \
+               echo 'm4_define([AT_PACKAGE_STRING],' && \
+               echo '  [$(PACKAGE_STRING)])' && \
+               echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \
+               echo '  [$(PACKAGE_BUGREPORT)])'; \
+               echo 'm4_define([AT_PACKAGE_URL],' && \
+               echo '  [$(PACKAGE_URL)])'; \
+             } >'$(srcdir)/package.m4'
+
+EXTRA_DIST = \
+	testsuite.at \
+	$(srcdir)/package.m4 \
+	$(TESTSUITE) \
+	vty_test_runner.py \
+	ctrl_test_runner.py \
+	smpp_test_runner.py \
+	$(NULL)
+
+TESTSUITE = $(srcdir)/testsuite
+
+DISTCLEANFILES = \
+	atconfig \
+	$(NULL)
+
+if ENABLE_EXT_TESTS
+python-tests: $(BUILT_SOURCES)
+	osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
+	osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
+	$(PYTHON) $(srcdir)/vty_test_runner.py -w $(abs_top_builddir) -v
+	$(PYTHON) $(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
+if BUILD_SMPP
+	$(PYTHON) $(srcdir)/smpp_test_runner.py -w $(abs_top_builddir) -v
+endif
+	rm -f $(top_builddir)/hlr.sqlite3
+else
+python-tests: $(BUILT_SOURCES)
+	echo "Not running python-based tests (determined at configure-time)"
+endif
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
+	$(MAKE) $(AM_MAKEFLAGS) python-tests
+
+installcheck-local: atconfig $(TESTSUITE)
+	$(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \
+		$(TESTSUITEFLAGS)
+
+clean-local:
+	test ! -f '$(TESTSUITE)' || \
+		$(SHELL) '$(TESTSUITE)' --clean
+
+AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4
+	$(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
+	mv $@.tmp $@
diff --git a/openbsc/tests/abis/Makefile.am b/openbsc/tests/abis/Makefile.am
new file mode 100644
index 0000000..1c5dede
--- /dev/null
+++ b/openbsc/tests/abis/Makefile.am
@@ -0,0 +1,34 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	abis_test.ok \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	abis_test \
+	$(NULL)
+
+abis_test_SOURCES = \
+	abis_test.c \
+	$(NULL)
+
+abis_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(NULL)
diff --git a/openbsc/tests/abis/abis_test.c b/openbsc/tests/abis/abis_test.c
new file mode 100644
index 0000000..591f835
--- /dev/null
+++ b/openbsc/tests/abis/abis_test.c
@@ -0,0 +1,93 @@
+/*
+ * (C) 2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+
+static const uint8_t load_config[] = {
+	0x42, 0x12, 0x00, 0x08, 0x31, 0x36, 0x38, 0x64,
+	0x34, 0x37, 0x32, 0x00, 0x13, 0x00, 0x0b, 0x76,
+	0x32, 0x30, 0x30, 0x62, 0x31, 0x34, 0x33, 0x64,
+	0x30, 0x00, 0x42, 0x12, 0x00, 0x08, 0x31, 0x36,
+	0x38, 0x64, 0x34, 0x37, 0x32, 0x00, 0x13, 0x00,
+	0x0b, 0x76, 0x32, 0x30, 0x30, 0x62, 0x31, 0x34,
+	0x33, 0x64, 0x31, 0x00
+};
+
+static void test_sw_selection(void)
+{
+	struct abis_nm_sw_desc descr[8], tmp;
+	uint16_t len0, len1;
+	int rc, pos;
+
+	rc = abis_nm_get_sw_conf(load_config, ARRAY_SIZE(load_config),
+				&descr[0], ARRAY_SIZE(descr));
+	if (rc != 2) {
+		printf("%s(): FAILED to parse the File Id/File version: %d\n",
+		       __func__, rc);
+		abort();
+	}
+
+	len0 = abis_nm_sw_desc_len(&descr[0], true);
+	printf("len: %u\n", len0);
+	printf("file_id:  %s\n", osmo_hexdump(descr[0].file_id, descr[0].file_id_len));
+	printf("file_ver: %s\n", osmo_hexdump(descr[0].file_version, descr[0].file_version_len));
+
+	len1 = abis_nm_sw_desc_len(&descr[1], true);
+	printf("len: %u\n", len1);
+	printf("file_id:  %s\n", osmo_hexdump(descr[1].file_id, descr[1].file_id_len));
+	printf("file_ver: %s\n", osmo_hexdump(descr[1].file_version, descr[1].file_version_len));
+
+	/* start */
+	pos = abis_nm_select_newest_sw(descr, rc);
+	if (pos != 1) {
+		printf("Selected the wrong version: %d\n", pos);
+		abort();
+	}
+	printf("SELECTED: %d\n", pos);
+
+	/* shuffle */
+	tmp = descr[0];
+	descr[0] = descr[1];
+	descr[1] = tmp;
+	pos = abis_nm_select_newest_sw(descr, rc);
+	if (pos != 0) {
+		printf("Selected the wrong version: %d\n", pos);
+		abort();
+	}
+	printf("SELECTED: %d\n", pos);
+	printf("%s(): OK\n", __func__);
+}
+
+int main(int argc, char **argv)
+{
+	osmo_init_logging(&log_info);
+
+	test_sw_selection();
+
+	return EXIT_SUCCESS;
+}
diff --git a/openbsc/tests/abis/abis_test.ok b/openbsc/tests/abis/abis_test.ok
new file mode 100644
index 0000000..8418cad
--- /dev/null
+++ b/openbsc/tests/abis/abis_test.ok
@@ -0,0 +1,9 @@
+len: 26
+file_id:  31 36 38 64 34 37 32 00 
+file_ver: 76 32 30 30 62 31 34 33 64 30 00 
+len: 26
+file_id:  31 36 38 64 34 37 32 00 
+file_ver: 76 32 30 30 62 31 34 33 64 31 00 
+SELECTED: 1
+SELECTED: 0
+test_sw_selection(): OK
diff --git a/openbsc/tests/atlocal.in b/openbsc/tests/atlocal.in
new file mode 100644
index 0000000..542a78e
--- /dev/null
+++ b/openbsc/tests/atlocal.in
@@ -0,0 +1,4 @@
+enable_nat_test='@osmo_ac_build_nat@'
+enable_smpp_test='@osmo_ac_build_smpp@'
+enable_bsc_test='@osmo_ac_build_bsc@'
+enable_mgcp_transcoding_test='@osmo_ac_mgcp_transcoding@'
diff --git a/openbsc/tests/bsc-nat-trie/Makefile.am b/openbsc/tests/bsc-nat-trie/Makefile.am
new file mode 100644
index 0000000..cf8ebaf
--- /dev/null
+++ b/openbsc/tests/bsc-nat-trie/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+EXTRA_DIST = bsc_nat_trie_test.ok prefixes.csv
+
+noinst_PROGRAMS = bsc_nat_trie_test
+
+bsc_nat_trie_test_SOURCES = bsc_nat_trie_test.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c
+bsc_nat_trie_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libmgcp/libmgcp.a \
+			$(top_builddir)/src/libtrau/libtrau.a \
+			$(top_builddir)/src/libcommon/libcommon.a \
+			$(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -lrt \
+			$(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) \
+			$(LIBOSMOABIS_LIBS)
diff --git a/openbsc/tests/bsc-nat-trie/bsc_nat_trie_test.c b/openbsc/tests/bsc-nat-trie/bsc_nat_trie_test.c
new file mode 100644
index 0000000..4b4df2f
--- /dev/null
+++ b/openbsc/tests/bsc-nat-trie/bsc_nat_trie_test.c
@@ -0,0 +1,87 @@
+/*
+ * (C) 2013 by On-Waves
+ * (C) 2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/nat_rewrite_trie.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/backtrace.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+	struct nat_rewrite *trie;
+
+	osmo_init_logging(&log_info);
+
+	printf("Testing the trie\n");
+
+	trie = nat_rewrite_parse(NULL, "prefixes.csv");
+	OSMO_ASSERT(trie);
+
+	/* verify that it has been parsed */
+	OSMO_ASSERT(trie->prefixes == 17);
+	printf("Dumping the internal trie\n");
+	nat_rewrite_dump(trie);
+
+	/* now do the matching... */
+	OSMO_ASSERT(!nat_rewrite_lookup(trie, ""));
+	OSMO_ASSERT(!nat_rewrite_lookup(trie, "2"));
+
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "1")->rewrite, "1") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "12")->rewrite, "2") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "123")->rewrite, "3") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "1234")->rewrite, "4") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "12345")->rewrite, "5") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "123456")->rewrite, "6") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "1234567")->rewrite, "7") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "12345678")->rewrite, "8") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "123456789")->rewrite, "9") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "1234567890")->rewrite, "10") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "13")->rewrite, "11") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "14")->rewrite, "12") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "15")->rewrite, "13") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "16")->rewrite, "14") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "823455")->rewrite, "15") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "82")->rewrite, "16") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "+49123445")->rewrite, "17") == 0);
+
+	/* match a prefix */
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "121")->rewrite, "2") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "1292323")->rewrite, "2") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "12345678901")->rewrite, "10") == 0);
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "160")->rewrite, "14") == 0);
+
+	OSMO_ASSERT(strcmp(nat_rewrite_lookup(trie, "12345678901123452123123")->rewrite, "10") == 0);
+
+	/* invalid input */
+	OSMO_ASSERT(!nat_rewrite_lookup(trie, "12abc"));
+
+	talloc_free(trie);
+
+	trie = nat_rewrite_parse(NULL, "does_not_exist.csv");
+	OSMO_ASSERT(!trie);
+
+	printf("Done with the tests.\n");
+	return 0;
+}
diff --git a/openbsc/tests/bsc-nat-trie/bsc_nat_trie_test.ok b/openbsc/tests/bsc-nat-trie/bsc_nat_trie_test.ok
new file mode 100644
index 0000000..4d4cc99
--- /dev/null
+++ b/openbsc/tests/bsc-nat-trie/bsc_nat_trie_test.ok
@@ -0,0 +1,20 @@
+Testing the trie
+Dumping the internal trie
+1,1
+12,2
+123,3
+1234,4
+12345,5
+123456,6
+1234567,7
+12345678,8
+123456789,9
+1234567890,10
+13,11
+14,12
+15,13
+16,14
+82,16
+823455,15
++49123,17
+Done with the tests.
diff --git a/openbsc/tests/bsc-nat-trie/prefixes.csv b/openbsc/tests/bsc-nat-trie/prefixes.csv
new file mode 100644
index 0000000..35485b1
--- /dev/null
+++ b/openbsc/tests/bsc-nat-trie/prefixes.csv
@@ -0,0 +1,25 @@
+1,1
+12,2
+123,3
+1234,4
+12345,5
+123456,6
+1234567,7
+12345678,8
+123456789,9
+1234567890,10
+13,11
+14,12
+15,13
+16,14
+823455,15
+82,16
++49123,17
+1ABC,18
+12345678901234567890,19
+,20
+14A,21
+124,324324324234
+1234567890,10
+no line
+99,
diff --git a/openbsc/tests/bsc-nat/Makefile.am b/openbsc/tests/bsc-nat/Makefile.am
new file mode 100644
index 0000000..40be3a3
--- /dev/null
+++ b/openbsc/tests/bsc-nat/Makefile.am
@@ -0,0 +1,59 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOCTRL_LIBS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	bsc_nat_test.ok \
+	bsc_data.c \
+	barr.cfg \
+	barr_dup.cfg \
+	prefixes.csv \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	bsc_nat_test \
+	$(NULL)
+
+bsc_nat_test_SOURCES = \
+	bsc_nat_test.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_rewrite.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c \
+	$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_filter.c
+
+bsc_nat_test_LDADD = \
+	$(top_builddir)/src/libfilter/libfilter.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOSCCP_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMONETIF_LIBS) \
+	$(LIBOSMOCTRL_LIBS) \
+	-lrt \
+	$(NULL)
diff --git a/openbsc/tests/bsc-nat/barr.cfg b/openbsc/tests/bsc-nat/barr.cfg
new file mode 100644
index 0000000..a9a4a2b
--- /dev/null
+++ b/openbsc/tests/bsc-nat/barr.cfg
@@ -0,0 +1,12 @@
+12123124:3:2:
+12123123:3:1:
+12123128:3:6:
+12123125:3:3:
+12123127:3:5:
+12123126:3:4:
+12123120:3:4:
+12123119:3:4:
+12123118:3:4:
+12123117:3:4:
+12123116:3:4:
+12123115:3:4:
diff --git a/openbsc/tests/bsc-nat/barr_dup.cfg b/openbsc/tests/bsc-nat/barr_dup.cfg
new file mode 100644
index 0000000..ea94631
--- /dev/null
+++ b/openbsc/tests/bsc-nat/barr_dup.cfg
@@ -0,0 +1,2 @@
+12123124:3:2:
+12123124:3:2:
diff --git a/openbsc/tests/bsc-nat/bsc_data.c b/openbsc/tests/bsc-nat/bsc_data.c
new file mode 100644
index 0000000..71d5391
--- /dev/null
+++ b/openbsc/tests/bsc-nat/bsc_data.c
@@ -0,0 +1,275 @@
+/* test data */
+
+/* BSC -> MSC, CR */
+static const uint8_t bsc_cr[] = {
+0x00, 0x2e, 0xfd,
+0x01, 0x00, 0x00, 0x15, 0x02, 0x02, 0x04, 0x02,
+0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05,
+0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x1c, 0xc3,
+0x51, 0x17, 0x12, 0x05, 0x08, 0x20, 0x72, 0xf4,
+0x90, 0x20, 0x1d, 0x50, 0x08, 0x29, 0x47, 0x80,
+0x00, 0x00, 0x00, 0x00, 0x80, 0x00 };
+
+static const uint8_t bsc_cr_patched[] = {
+0x00, 0x2e, 0xfd,
+0x01, 0x00, 0x00, 0x05, 0x02, 0x02, 0x04, 0x02,
+0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05,
+0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x1c, 0xc3,
+0x51, 0x17, 0x12, 0x05, 0x08, 0x20, 0x72, 0xf4,
+0x90, 0x20, 0x1d, 0x50, 0x08, 0x29, 0x47, 0x80,
+0x00, 0x00, 0x00, 0x00, 0x80, 0x00 };
+
+/* CC, MSC -> BSC */
+static const uint8_t msc_cc[] = {
+0x00, 0x0a, 0xfd,
+0x02, 0x00, 0x00, 0x05, 0x01, 0x1f, 0xe4, 0x02,
+0x01, 0x00 };
+static const uint8_t msc_cc_patched[] = {
+0x00, 0x0a, 0xfd,
+0x02, 0x00, 0x00, 0x15, 0x01, 0x1f, 0xe4, 0x02,
+0x01, 0x00 };
+
+/* Classmark, BSC -> MSC */
+static const uint8_t bsc_dtap[] = {
+0x00, 0x17, 0xfd,
+0x06, 0x01, 0x1f, 0xe4, 0x00, 0x01, 0x10, 0x00,
+0x0e, 0x54, 0x12, 0x03, 0x50, 0x18, 0x93, 0x13,
+0x06, 0x60, 0x14, 0x45, 0x00, 0x81, 0x00 };
+
+static const uint8_t bsc_dtap_patched[] = {
+0x00, 0x17, 0xfd,
+0x06, 0x01, 0x1f, 0xe4, 0x00, 0x01, 0x10, 0x00,
+0x0e, 0x54, 0x12, 0x03, 0x50, 0x18, 0x93, 0x13,
+0x06, 0x60, 0x14, 0x45, 0x00, 0x81, 0x00 };
+
+/* Clear command, MSC -> BSC */
+static const uint8_t msc_dtap[] = {
+0x00, 0x0d, 0xfd,
+0x06, 0x00, 0x00, 0x05, 0x00, 0x01, 0x06, 0x00,
+0x04, 0x20, 0x04, 0x01, 0x09 };
+static const uint8_t msc_dtap_patched[] = {
+0x00, 0x0d, 0xfd,
+0x06, 0x00, 0x00, 0x15, 0x00, 0x01, 0x06, 0x00,
+0x04, 0x20, 0x04, 0x01, 0x09 };
+
+/*RLSD, MSC -> BSC */
+static const uint8_t msc_rlsd[] = {
+0x00, 0x0a, 0xfd,
+0x04, 0x00, 0x00, 0x05, 0x01, 0x1f, 0xe4, 0x00,
+0x01, 0x00 };
+static const uint8_t msc_rlsd_patched[] = {
+0x00, 0x0a, 0xfd,
+0x04, 0x00, 0x00, 0x15, 0x01, 0x1f, 0xe4, 0x00,
+0x01, 0x00 };
+
+/* RLC, BSC -> MSC */
+static const uint8_t bsc_rlc[] = {
+0x00, 0x07, 0xfd,
+0x05, 0x01, 0x1f, 0xe4, 0x00, 0x00, 0x15 };
+
+static const uint8_t bsc_rlc_patched[] = {
+0x00, 0x07, 0xfd,
+0x05, 0x01, 0x1f, 0xe4, 0x00, 0x00, 0x05 };
+
+
+/* a paging command */
+static const uint8_t paging_by_lac_cmd[] = {
+0x00, 0x22, 0xfd, 0x09,
+0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x02, 0x00,
+0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x12, 0x00,
+0x10, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10, 0x02,
+0x01, 0x50, 0x02, 0x30, 0x1a, 0x03, 0x05, 0x20,
+0x15 };
+
+/* an assignment command */
+static const uint8_t ass_cmd[] = {
+0x00, 0x12, 0xfd, 0x06,
+0x00, 0x00, 0x49, 0x00, 0x01, 0x0b, 0x00, 0x09,
+0x01, 0x0b, 0x03, 0x01, 0x0a, 0x11, 0x01, 0x00,
+0x01 };
+
+/* identity response */
+static const uint8_t id_resp[] = {
+0x00, 0x15, 0xfd, 0x06, 0x01, 0x1c, 0xdc,
+0x00, 0x01, 0x0e, 0x01, 0x00, 0x0b, 0x05, 0x59,
+0x08, 0x29, 0x40, 0x21, 0x03, 0x07, 0x48, 0x66,
+0x31
+};
+
+/* sms code msg */
+static const uint8_t smsc_rewrite[] = {
+0x00, 0x30, 0xfd, 0x06, 0x01, 0x13, 0x1e, 0x00,
+0x01, 0x29, 0x01, 0x03, 0x26, 0x09, 0x01, 0x23,
+0x00, 0x0c, 0x00, 0x07, 0x91, 0x36, 0x19, 0x08,
+0x00, 0x10, 0x50, 0x17, 0x21, 0x0c, 0x0f, 0x81,
+0x00, 0x94, 0x51, 0x87, 0x86, 0x78, 0x46, 0xf5,
+0x00, 0x00, 0x09, 0xcc, 0xb7, 0xbd, 0x0c, 0xca,
+0xbf, 0xeb, 0x20
+};
+
+static const uint8_t smsc_rewrite_patched[] = {
+0x00, 0x31, 0xfd, 0x06, 0x01, 0x13, 0x1e, 0x00,
+0x01, 0x2a, 0x01, 0x03, 0x27, 0x09, 0x01, 0x24,
+0x00, 0x0c, 0x00, 0x08, 0x91, 0x66, 0x66, 0x66,
+0x66, 0x66, 0x66, 0xf7, 0x17, 0x01, 0x0c, 0x0f,
+0x81, 0x00, 0x94, 0x51, 0x87, 0x86, 0x78, 0x46,
+0xf5, 0x00, 0x00, 0x09, 0xcc, 0xb7, 0xbd, 0x0c,
+0xca, 0xbf, 0xeb, 0x20
+};
+
+static const uint8_t smsc_rewrite_patched_hdr[] = {
+0x00, 0x30, 0xfd, 0x06, 0x01, 0x13, 0x1e, 0x00,
+0x01, 0x29, 0x01, 0x03, 0x26, 0x09, 0x01, 0x23,
+0x00, 0x0c, 0x00, 0x07, 0x91, 0x36, 0x19, 0x08,
+0x00, 0x10, 0x50, 0x17, 0x01, 0x0c, 0x0f, 0x81,
+0x00, 0x94, 0x51, 0x87, 0x86, 0x78, 0x46, 0xf5,
+0x00, 0x00, 0x09, 0xcc, 0xb7, 0xbd, 0x0c, 0xca,
+0xbf, 0xeb, 0x20
+};
+
+static const uint8_t smsc_rewrite_num_patched[] = {
+0x00, 0x2f, 0xfd, 0x06, 0x01, 0x13, 0x1e, 0x00,
+0x01, 0x28, 0x01, 0x03, 0x25, 0x09, 0x01, 0x22,
+0x00, 0x0c, 0x00, 0x07, 0x91, 0x36, 0x19, 0x08,
+0x00, 0x10, 0x50, 0x16, 0x21, 0x0c, 0x0d, 0x91,
+      0x23, 0x51, 0x87, 0x86, 0x78, 0x46, 0xf5,
+0x00, 0x00, 0x09, 0xcc, 0xb7, 0xbd, 0x0c, 0xca,
+0xbf, 0xeb, 0x20
+};
+
+static const uint8_t smsc_rewrite_num_patched_tp_srr[] = {
+0x00, 0x2f, 0xfd, 0x06, 0x01, 0x13, 0x1e, 0x00,
+0x01, 0x28, 0x01, 0x03, 0x25, 0x09, 0x01, 0x22,
+0x00, 0x0c, 0x00, 0x07, 0x91, 0x36, 0x19, 0x08,
+0x00, 0x10, 0x50, 0x16, 0x01, 0x0c, 0x0d, 0x91,
+      0x23, 0x51, 0x87, 0x86, 0x78, 0x46, 0xf5,
+0x00, 0x00, 0x09, 0xcc, 0xb7, 0xbd, 0x0c, 0xca,
+0xbf, 0xeb, 0x20
+};
+
+/*
+ * MGCP messages
+ */
+
+/* nothing to patch */
+static const char crcx[] = "CRCX 23265295 8@mgw MGCP 1.0\r\nC: 394b0439fb\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n";
+static const char crcx_patched[] = "CRCX 23265295 1e@mgw MGCP 1.0\r\nC: 394b0439fb\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n";
+
+
+/* patch the ip and port */
+static const char crcx_resp[] = "200 23265295\r\nI: 1\r\n\r\nv=0\r\nc=IN IP4 172.16.18.2\r\nm=audio 4002 RTP/AVP 98 3\r\na=rtpmap:98 AMR/8000\r\n";
+static const char crcx_resp_patched[] = "200 23265295\r\nI: 1\r\n\r\nv=0\r\nc=IN IP4 10.0.0.1\r\nm=audio 999 RTP/AVP 98 3\r\na=rtpmap:98 AMR/8000\r\na=fmtp:98 mode-set=2 octet-align=1\r\n";
+
+/* patch the ip and port */
+static const char mdcx[] = "MDCX 23330829 8@mgw MGCP 1.0\r\nC: 394b0439fb\r\nI: 1\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 1049380491 0 IN IP4 172.16.18.2\r\ns=-\r\nc=IN IP4 172.16.18.2\r\nt=0 0\r\nm=audio 4410 RTP/AVP 126\r\na=rtpmap:126 AMR/8000/1\r\na=fmtp:126 mode-set=2  octet-align=1;start-mode=0\r\na=ptime:20\r\na=recvonly\r\nm=image 4412 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n";
+static const char mdcx_patched[] = "MDCX 23330829 1e@mgw MGCP 1.0\r\nC: 394b0439fb\r\nI: 1\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 1049380491 0 IN IP4 172.16.18.2\r\ns=-\r\nc=IN IP4 10.0.0.23\r\nt=0 0\r\nm=audio 6666 RTP/AVP 126\r\na=rtpmap:126 AMR/8000/1\r\na=fmtp:126 mode-set=2  octet-align=1;start-mode=0\r\na=ptime:20\r\na=recvonly\r\nm=image 4412 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n";
+
+
+static const char mdcx_resp[] = "200 23330829\r\n\r\nv=0\r\nc=IN IP4 172.16.18.2\r\nm=audio 4002 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n";
+static const char mdcx_resp_patched[] = "200 23330829\r\n\r\nv=0\r\nc=IN IP4 10.0.0.23\r\nm=audio 5555 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\na=fmtp:98 mode-set=2 octet-align=1\r\n";
+
+/* different line ending */
+static const char mdcx_resp2[] = "200 33330829\n\nv=0\nc=IN IP4 172.16.18.2\nm=audio 4002 RTP/AVP 98\na=rtpmap:98 AMR/8000\n";
+static const char mdcx_resp_patched2[] = "200 33330829\n\nv=0\nc=IN IP4 10.0.0.23\nm=audio 5555 RTP/AVP 98\na=rtpmap:98 AMR/8000\na=fmtp:98 mode-set=2 octet-align=1\n";
+static const char mdcx_resp_patched2_noamr[] = "200 33330829\n\nv=0\nc=IN IP4 10.0.0.23\nm=audio 5555 RTP/AVP 98\na=rtpmap:98 AMR/8000\n";
+
+struct mgcp_patch_test {
+	const char *orig;
+	const char *patch;
+	const char *ip;
+	const int port;
+	const int payload_type;
+	const int ensure_mode_set;
+};
+
+static const struct mgcp_patch_test mgcp_messages[] = {
+	{
+		.orig = crcx,
+		.patch = crcx_patched,
+		.ip = "0.0.0.0",
+		.port = 2323,
+		.ensure_mode_set = 1,
+	},
+	{
+		.orig = crcx_resp,
+		.patch = crcx_resp_patched,
+		.ip = "10.0.0.1",
+		.port = 999,
+		.payload_type = 98,
+		.ensure_mode_set = 1,
+	},
+	{
+		.orig = mdcx,
+		.patch = mdcx_patched,
+		.ip = "10.0.0.23",
+		.port = 6666,
+		.payload_type = 126,
+		.ensure_mode_set = 1,
+	},
+	{
+		.orig = mdcx_resp,
+		.patch = mdcx_resp_patched,
+		.ip = "10.0.0.23",
+		.port = 5555,
+		.payload_type = 98,
+		.ensure_mode_set = 1,
+	},
+	{
+		.orig = mdcx_resp2,
+		.patch = mdcx_resp_patched2,
+		.ip = "10.0.0.23",
+		.port = 5555,
+		.payload_type = 98,
+		.ensure_mode_set = 1,
+	},
+	{
+		.orig = mdcx_resp2,
+		.patch = mdcx_resp_patched2_noamr,
+		.ip = "10.0.0.23",
+		.port = 5555,
+		.payload_type = 98,
+		.ensure_mode_set = 0,
+	},
+};
+
+/* CC Setup messages */
+static const uint8_t cc_setup_national[] = {
+	0x00, 0x20, 0xfd, 0x06, 0x01, 0x12,
+	0x6d, 0x00, 0x01, 0x19, 0x01, 0x00, 0x16, 0x03,
+	0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x06, 0x81, 0x10, 0x27, 0x33, 0x63,
+	0x66, 0x15, 0x02, 0x11, 0x01
+};
+
+static const uint8_t cc_setup_national_patched[] = {
+	0x00, 0x21, 0xfd, 0x06, 0x01, 0x12,
+	0x6d, 0x00, 0x01, 0x1a, 0x01, 0x00, 0x17, 0x03,
+	0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x07, 0x91, 0x94, 0x71, 0x32, 0x33,
+	0x66, 0xf6, 0x15, 0x02, 0x11, 0x01
+};
+
+/* patch the phone number of cc_setup_national_patched */
+static const uint8_t cc_setup_national_patched_patched[] = {
+	0x00, 0x21, 0xfd, 0x06, 0x01, 0x12,
+	0x6d, 0x00, 0x01, 0x1a, 0x01, 0x00, 0x17, 0x03,
+	0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x07, 0x91, 0x63, 0x71, 0x32, 0x33,
+	0x66, 0xf6, 0x15, 0x02, 0x11, 0x01
+};
+
+static const uint8_t cc_setup_international[] = {
+	0x00, 0x22, 0xfd, 0x06, 0x01, 0x13,
+	0xe7, 0x00, 0x01, 0x1b, 0x01, 0x00, 0x18, 0x03,
+	0x45, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x08, 0x81, 0x00, 0x94, 0x71, 0x33,
+	0x63, 0x66, 0x03, 0x15, 0x02, 0x11, 0x01
+};
+
+static const uint8_t cc_setup_national_again[] = {
+	0x00, 0x22, 0xfd, 0x06, 0x01, 0x12, 0x6d, 0x00,
+	0x01, 0x1b, 0x01, 0x00, 0x18, 0x03, 0x05, 0x04,
+	0x06, 0x60, 0x04, 0x02, 0x00, 0x05, 0x81, 0x5e,
+	0x08, 0x81, 0x63, 0x94, 0x71, 0x32, 0x33, 0x66,
+	0xf6, 0x15, 0x02, 0x11, 0x01
+};
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
new file mode 100644
index 0000000..2914a01
--- /dev/null
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -0,0 +1,1584 @@
+/*
+ * BSC NAT Message filtering
+ *
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/nat_rewrite_trie.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/backtrace.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/sccp/sccp.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <stdio.h>
+
+/* test messages for ipa */
+static uint8_t ipa_id[] = {
+	0x00, 0x01, 0xfe, 0x06,
+};
+
+/* SCCP messages are below */
+static uint8_t gsm_reset[] = {
+	0x00, 0x12, 0xfd,
+	0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+	0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+	0x01, 0x20,
+};
+
+static const uint8_t gsm_reset_ack[] = {
+	0x00, 0x13, 0xfd,
+	0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+	0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+	0x00, 0x01, 0x31,
+};
+
+static const uint8_t gsm_paging[] = {
+	0x00, 0x20, 0xfd,
+	0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+	0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10,
+	0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10,
+	0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06,
+};
+
+/* BSC -> MSC connection open */
+static const uint8_t bssmap_cr[] = {
+	0x00, 0x2c, 0xfd,
+	0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02,
+	0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05,
+	0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3,
+	0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33,
+	0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01,
+	0x31, 0x97, 0x61, 0x00
+};
+
+/* MSC -> BSC connection confirm */
+static const uint8_t bssmap_cc[] = {
+	0x00, 0x0a, 0xfd,
+	0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00,
+};
+
+/* MSC -> BSC released */
+static const uint8_t bssmap_released[] = {
+	0x00, 0x0e, 0xfd,
+	0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f,
+	0x02, 0x23, 0x42, 0x00,
+};
+
+/* BSC -> MSC released */
+static const uint8_t bssmap_release_complete[] = {
+	0x00, 0x07, 0xfd,
+	0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03
+};
+
+/* both directions IT timer */
+static const uint8_t connnection_it[] = {
+	0x00, 0x0b, 0xfd,
+	0x10, 0x01, 0x02, 0x03, 0x01, 0x02, 0x03,
+	0x00, 0x00, 0x00, 0x00,
+};
+
+/* error in both directions */
+static const uint8_t proto_error[] = {
+	0x00, 0x05, 0xfd,
+	0x0f, 0x22, 0x33, 0x44, 0x00,
+};
+
+/* MGCP wrap... */
+static const uint8_t mgcp_msg[] = {
+	0x00, 0x03, 0xfc,
+	0x20, 0x20, 0x20,
+};
+
+/* location updating request */
+static const uint8_t bss_lu[] = {
+	0x00, 0x2e, 0xfd,
+	0x01, 0x91, 0x45, 0x14, 0x02, 0x02, 0x04, 0x02,
+	0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05,
+	0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x14, 0xc3,
+	0x50, 0x17, 0x12, 0x05, 0x08, 0x70, 0x72, 0xf4,
+	0x80, 0xff, 0xfe, 0x30, 0x08, 0x29, 0x44, 0x50,
+	0x12, 0x03, 0x24, 0x01, 0x95, 0x00
+};
+
+/* paging response */
+static const uint8_t pag_resp[] = {
+	0x00, 0x2c, 0xfd, 0x01, 0xe5, 0x68,
+	0x14, 0x02, 0x02, 0x04, 0x02, 0x42, 0xfe, 0x0f,
+	0x1f, 0x00, 0x1d, 0x57, 0x05, 0x08, 0x00, 0x72,
+	0xf4, 0x80, 0x20, 0x16, 0xc3, 0x50, 0x17, 0x10,
+	0x06, 0x27, 0x01, 0x03, 0x30, 0x18, 0x96, 0x08,
+	0x29, 0x26, 0x30, 0x32, 0x11, 0x42, 0x01, 0x19,
+	0x00
+};
+
+struct filter_result {
+	const uint8_t *data;
+	const uint16_t length;
+	const int dir;
+	const int result;
+};
+
+static const struct filter_result results[] = {
+	{
+		.data = ipa_id,
+		.length = ARRAY_SIZE(ipa_id),
+		.dir = DIR_MSC,
+		.result = 1,
+	},
+	{
+		.data = gsm_reset,
+		.length = ARRAY_SIZE(gsm_reset),
+		.dir = DIR_MSC,
+		.result = 1,
+	},
+	{
+		.data = gsm_reset_ack,
+		.length = ARRAY_SIZE(gsm_reset_ack),
+		.dir = DIR_BSC,
+		.result = 1,
+	},
+	{
+		.data = gsm_paging,
+		.length = ARRAY_SIZE(gsm_paging),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_cr,
+		.length = ARRAY_SIZE(bssmap_cr),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_cc,
+		.length = ARRAY_SIZE(bssmap_cc),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_released,
+		.length = ARRAY_SIZE(bssmap_released),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_release_complete,
+		.length = ARRAY_SIZE(bssmap_release_complete),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = mgcp_msg,
+		.length = ARRAY_SIZE(mgcp_msg),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = connnection_it,
+		.length = ARRAY_SIZE(connnection_it),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = connnection_it,
+		.length = ARRAY_SIZE(connnection_it),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = proto_error,
+		.length = ARRAY_SIZE(proto_error),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = proto_error,
+		.length = ARRAY_SIZE(proto_error),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+
+};
+
+static void test_filter(void)
+{
+	int i;
+
+
+	/* start testinh with proper messages */
+	printf("Testing BSS Filtering.\n");
+	for (i = 0; i < ARRAY_SIZE(results); ++i) {
+		int result;
+		struct bsc_nat_parsed *parsed;
+		struct msgb *msg = msgb_alloc(4096, "test-message");
+
+		printf("Going to test item: %d\n", i);
+		memcpy(msg->data, results[i].data, results[i].length);
+		msg->l2h = msgb_put(msg, results[i].length);
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed) {
+			printf("FAIL: Failed to parse the message\n");
+			continue;
+		}
+
+		result = bsc_nat_filter_ipa(results[i].dir, msg, parsed);
+		if (result != results[i].result) {
+			printf("FAIL: Not the expected result got: %d wanted: %d\n",
+				result, results[i].result);
+		}
+
+		msgb_free(msg);
+	}
+}
+
+#include "bsc_data.c"
+
+static void copy_to_msg(struct msgb *msg, const uint8_t *data, unsigned int length)
+{
+	msgb_reset(msg);
+	msg->l2h = msgb_put(msg, length);
+	memcpy(msg->l2h, data, msgb_l2len(msg));
+}
+
+static void verify_msg(struct msgb *out, const uint8_t *ref, int ref_len)
+{
+	if (out->len != ref_len) {
+		printf("FAIL: The size should match: %d vs. %d\n",
+			out->len, ref_len);
+		printf("%s\n", osmo_hexdump(out->data, out->len));
+		printf("Wanted\n");
+		printf("%s\n", osmo_hexdump(ref, ref_len));
+		abort();
+	}
+
+	if (memcmp(out->data, ref, out->len) != 0) {
+		printf("FAIL: the data should be changed.\n");
+		printf("%s\n", osmo_hexdump(out->data, out->len));
+		printf("Wanted\n");
+		printf("%s\n", osmo_hexdump(ref, ref_len));
+		abort();
+	}
+}
+
+
+#define VERIFY(con_found, con, msg, ver, str) \
+	if (!con_found) {						\
+		printf("Failed to find connection.\n");			\
+		abort();						\
+	}								\
+	if (con_found->bsc != con) {					\
+		printf("Got connection of the wrong BSC: %d\n",		\
+			con_found->bsc->cfg->nr);			\
+		abort();						\
+	}								\
+	if (memcmp(msg->data, ver, sizeof(ver)) != 0) { \
+		printf("Failed to patch the %s msg.\n", str); 		\
+		abort(); \
+	}
+
+/* test conn tracking once */
+static void test_contrack()
+{
+	struct bsc_nat *nat;
+	struct bsc_connection *con;
+	struct nat_sccp_connection *con_found;
+	struct nat_sccp_connection *rc_con;
+	struct bsc_nat_parsed *parsed;
+	struct msgb *msg;
+
+	printf("Testing connection tracking.\n");
+	nat = bsc_nat_alloc();
+	con = bsc_connection_alloc(nat);
+	con->cfg = bsc_config_alloc(nat, "foo", 0);
+	bsc_config_add_lac(con->cfg, 23);
+	bsc_config_add_lac(con->cfg, 49);
+	bsc_config_add_lac(con->cfg, 42);
+	bsc_config_del_lac(con->cfg, 49);
+	bsc_config_add_lac(con->cfg, 1111);
+	msg = msgb_alloc(4096, "test");
+
+	/* 1.) create a connection */
+	copy_to_msg(msg, bsc_cr, sizeof(bsc_cr));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	if (con_found != NULL) {
+		printf("Con should not exist realref(%u)\n",
+		       sccp_src_ref_to_int(&con_found->real_ref));
+		abort();
+	}
+	rc_con = create_sccp_src_ref(con, parsed);
+	if (!rc_con) {
+		printf("Failed to create a ref\n");
+		abort();
+	}
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	if (!con_found) {
+		printf("Failed to find connection.\n");
+		abort();
+	}
+	if (con_found->bsc != con) {
+		printf("Got connection of the wrong BSC: %d\n",
+			con_found->bsc->cfg->nr);
+		abort();
+	}
+	if (con_found != rc_con) {
+		printf("Failed to find the right connection.\n");
+		abort();
+	}
+	if (memcmp(msg->data, bsc_cr_patched, sizeof(bsc_cr_patched)) != 0) {
+		printf("Failed to patch the BSC CR msg.\n");
+		abort();
+	}
+	talloc_free(parsed);
+
+	/* 2.) get the cc */
+	copy_to_msg(msg, msc_cc, sizeof(msc_cc));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+	VERIFY(con_found, con, msg, msc_cc_patched, "MSC CC");
+	if (update_sccp_src_ref(con_found, parsed) != 0) {
+		printf("Failed to update the SCCP con.\n");
+		abort();
+	}
+
+	/* 3.) send some data */
+	copy_to_msg(msg, bsc_dtap, sizeof(bsc_dtap));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	VERIFY(con_found, con, msg, bsc_dtap_patched, "BSC DTAP");
+
+	/* 4.) receive some data */
+	copy_to_msg(msg, msc_dtap, sizeof(msc_dtap));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+	VERIFY(con_found, con, msg, msc_dtap_patched, "MSC DTAP");
+
+	/* 5.) close the connection */
+	copy_to_msg(msg, msc_rlsd, sizeof(msc_rlsd));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+	VERIFY(con_found, con, msg, msc_rlsd_patched, "MSC RLSD");
+
+	/* 6.) confirm the connection close */
+	copy_to_msg(msg, bsc_rlc, sizeof(bsc_rlc));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	if (!con_found) {
+		printf("Failed to find connection.\n");
+		abort();
+	}
+	if (con_found->bsc != con) {
+		printf("Got connection of the wrong BSC: %d\n",
+			con_found->bsc->cfg->nr);
+		abort();
+	}
+	if (memcmp(msg->data, bsc_rlc_patched, sizeof(bsc_rlc_patched)) != 0) {
+		printf("Failed to patch the BSC CR msg.\n");
+		abort();
+	}
+	remove_sccp_src_ref(con, msg, parsed);
+	talloc_free(parsed);
+
+	copy_to_msg(msg, bsc_rlc, sizeof(bsc_rlc));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+
+	/* verify that it is gone */
+	if (con_found != NULL) {
+		printf("Con should not exist real_ref(%u)\n",
+		       sccp_src_ref_to_int(&con_found->real_ref));
+		abort();
+	}
+	talloc_free(parsed);
+
+
+	bsc_config_free(con->cfg);
+	bsc_nat_free(nat);
+	msgb_free(msg);
+}
+
+static void test_paging(void)
+{
+	struct bsc_nat *nat;
+	struct bsc_connection *con;
+	struct bsc_config *cfg;
+
+	printf("Testing paging by lac.\n");
+
+	nat = bsc_nat_alloc();
+	con = bsc_connection_alloc(nat);
+	cfg = bsc_config_alloc(nat, "unknown", 0);
+	con->cfg = cfg;
+	bsc_config_add_lac(cfg, 23);
+	con->authenticated = 1;
+	llist_add(&con->list_entry, &nat->bsc_connections);
+
+	/* Test it by not finding it */
+	if (bsc_config_handles_lac(cfg, 8213) != 0) {
+		printf("Should not be handled.\n");
+		abort();
+	}
+
+	/* Test by finding it */
+	bsc_config_del_lac(cfg, 23);
+	bsc_config_add_lac(cfg, 8213);
+	if (bsc_config_handles_lac(cfg, 8213) == 0) {
+		printf("Should have found it.\n");
+		abort();
+	}
+
+	bsc_nat_free(nat);
+}
+
+static void test_mgcp_allocations(void)
+{
+#if 0
+	struct bsc_connection *bsc;
+	struct bsc_nat *nat;
+	struct nat_sccp_connection con;
+	int i, j, multiplex;
+
+	printf("Testing MGCP.\n");
+	memset(&con, 0, sizeof(con));
+
+	nat = bsc_nat_alloc();
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       65);
+	nat->mgcp_cfg = mgcp_config_alloc();
+	nat->mgcp_cfg->trunk.number_endpoints = 64;
+
+	bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo", 0);
+	bsc->cfg->max_endpoints = 60;
+	bsc_config_add_lac(bsc->cfg, 2323);
+	bsc->last_endpoint = 0x22;
+	con.bsc = bsc;
+
+	bsc_init_endps_if_needed(bsc);
+
+	i  = 1;
+	do {
+		if (bsc_assign_endpoint(bsc, &con) != 0) {
+			printf("failed to allocate... on iteration %d\n", i);
+			break;
+		}
+		++i;
+	} while(1);
+
+	multiplex = bsc_mgcp_nr_multiplexes(bsc->cfg->max_endpoints);
+	for (i = 0; i < multiplex; ++i) {
+		for (j = 0; j < 32; ++j)
+			printf("%d", bsc->_endpoint_status[i*32 + j]);
+		printf(": %d of %d\n", i*32 + 32, 32 * 8);
+	}
+#endif
+}
+
+static void test_mgcp_ass_tracking(void)
+{
+	struct bsc_connection *bsc;
+	struct bsc_nat *nat;
+	struct nat_sccp_connection con;
+	struct bsc_nat_parsed *parsed;
+	struct msgb *msg;
+
+	printf("Testing MGCP.\n");
+	memset(&con, 0, sizeof(con));
+
+	nat = bsc_nat_alloc();
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       33);
+	nat->mgcp_cfg = mgcp_config_alloc();
+	nat->mgcp_cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&nat->mgcp_cfg->trunk);
+
+	bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo", 0);
+	bsc_config_add_lac(bsc->cfg, 2323);
+	bsc->last_endpoint = 0x1e;
+	con.bsc = bsc;
+
+	msg = msgb_alloc(4096, "foo");
+	copy_to_msg(msg, ass_cmd, sizeof(ass_cmd));
+	parsed = bsc_nat_parse(msg);
+
+	if (msg->l2h[16] != 0 ||
+	    msg->l2h[17] != 0x1) {
+		printf("Input is not as expected.. %s 0x%x\n",
+			osmo_hexdump(msg->l2h, msgb_l2len(msg)),
+			msg->l2h[17]);
+		abort();
+	}
+
+	if (bsc_mgcp_assign_patch(&con, msg) != 0) {
+		printf("Failed to handle assignment.\n");
+		abort();
+	}
+
+	if (con.msc_endp != 1) {
+		printf("Timeslot should be 1.\n");
+		abort();
+	}
+
+	if (con.bsc_endp != 0x1) {
+		printf("Assigned timeslot should have been 1.\n");
+		abort();
+	}
+	if (con.bsc->_endpoint_status[0x1] != 1) {
+		printf("The status on the BSC is wrong.\n");
+		abort();
+	}
+
+	int multiplex, timeslot;
+	mgcp_endpoint_to_timeslot(0x1, &multiplex, &timeslot);
+
+	uint16_t cic = htons(timeslot & 0x1f);
+	if (memcmp(&cic, &msg->l2h[16], sizeof(cic)) != 0) {
+		printf("Message was not patched properly\n");
+		printf("data cic: 0x%x %s\n", cic, osmo_hexdump(msg->l2h, msgb_l2len(msg)));
+		abort();
+	}
+
+	talloc_free(parsed);
+
+	bsc_mgcp_dlcx(&con);
+	if (con.bsc_endp != -1 || con.msc_endp != -1 ||
+	    con.bsc->_endpoint_status[1] != 0 || con.bsc->last_endpoint != 0x1) {
+		printf("Clearing should remove the mapping.\n");
+		abort();
+	}
+
+	bsc_config_free(bsc->cfg);
+	bsc_nat_free(nat);
+}
+
+/* test the code to find a given connection */
+static void test_mgcp_find(void)
+{
+	struct bsc_nat *nat;
+	struct bsc_connection *con;
+	struct nat_sccp_connection *sccp_con;
+
+	printf("Testing finding of a BSC Connection\n");
+
+	nat = bsc_nat_alloc();
+	con = bsc_connection_alloc(nat);
+	llist_add(&con->list_entry, &nat->bsc_connections);
+
+	sccp_con = talloc_zero(con, struct nat_sccp_connection);
+	sccp_con->msc_endp = 12;
+	sccp_con->bsc_endp = 12;
+	sccp_con->bsc = con;
+	llist_add(&sccp_con->list_entry, &nat->sccp_connections);
+
+	if (bsc_mgcp_find_con(nat, 11) != NULL) {
+		printf("Found the wrong connection.\n");
+		abort();
+	}
+
+	if (bsc_mgcp_find_con(nat, 12) != sccp_con) {
+		printf("Didn't find the connection\n");
+		abort();
+	}
+
+	/* free everything */
+	bsc_nat_free(nat);
+}
+
+static void test_mgcp_rewrite(void)
+{
+	int i;
+	struct msgb *output;
+	printf("Testing rewriting MGCP messages.\n");
+
+	for (i = 0; i < ARRAY_SIZE(mgcp_messages); ++i) {
+		const char *orig = mgcp_messages[i].orig;
+		const char *patc = mgcp_messages[i].patch;
+		const char *ip = mgcp_messages[i].ip;
+		const int port = mgcp_messages[i].port;
+		const int expected_payload_type = mgcp_messages[i].payload_type;
+		const int ensure_mode_set = mgcp_messages[i].ensure_mode_set;
+		int payload_type = -1;
+
+		char *input = strdup(orig);
+
+		output = bsc_mgcp_rewrite(input, strlen(input), 0x1e,
+					  ip, port, -1, &payload_type, ensure_mode_set);
+
+		if (payload_type != -1) {
+			fprintf(stderr, "Found media payload type %d in SDP data\n",
+				payload_type);
+			if (payload_type != expected_payload_type) {
+				printf("Wrong payload type %d (expected %d)\n",
+				       payload_type, expected_payload_type);
+				abort();
+			}
+		}
+
+		if (msgb_l2len(output) != strlen(patc)) {
+			printf("Wrong sizes for test: %d  %u != %zu != %zu\n", i, msgb_l2len(output), strlen(patc), strlen(orig));
+			printf("String '%s' vs '%s'\n", (const char *) output->l2h, patc);
+			abort();
+		}
+
+		if (memcmp(output->l2h, patc, msgb_l2len(output)) != 0) {
+			printf("Broken on %d msg: '%s'\n", i, (const char *) output->l2h);
+			abort();
+		}
+
+		msgb_free(output);
+		free(input);
+	}
+}
+
+static void test_mgcp_parse(void)
+{
+	int code, ci;
+	char transaction[60];
+
+	printf("Testing MGCP response parsing.\n");
+
+	if (bsc_mgcp_parse_response(crcx_resp, &code, transaction) != 0) {
+		printf("Failed to parse CRCX resp.\n");
+		abort();
+	}
+
+	if (code != 200) {
+		printf("Failed to parse the CODE properly. Got: %d\n", code);
+		abort();
+	}
+
+	if (strcmp(transaction, "23265295") != 0) {
+		printf("Failed to parse transaction id: '%s'\n", transaction);
+		abort();
+	}
+
+	ci = bsc_mgcp_extract_ci(crcx_resp);
+	if (ci != 1) {
+		printf("Failed to parse the CI. Got: %d\n", ci);
+		abort();
+	}
+}
+
+struct cr_filter {
+	const uint8_t *data;
+	int length;
+	int result;
+	int contype;
+
+	const char *bsc_imsi_allow;
+	const char *bsc_imsi_deny;
+	const char *nat_imsi_deny;
+	int nat_cm_reject_cause;
+	int nat_lu_reject_cause;
+	int bsc_cm_reject_cause;
+	int bsc_lu_reject_cause;
+	int want_cm_reject_cause;
+	int want_lu_reject_cause;
+};
+
+static struct cr_filter cr_filter[] = {
+	{
+		.data = bssmap_cr,
+		.length = sizeof(bssmap_cr),
+		.result = 1,
+		.contype = FLT_CON_TYPE_CM_SERV_REQ,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		.data = pag_resp,
+		.length = sizeof(pag_resp),
+		.result = 1,
+		.contype = FLT_CON_TYPE_PAG_RESP,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* nat deny is before blank/null BSC */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -3,
+		.nat_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* BSC allow is before NAT deny */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.nat_imsi_deny = "[0-9]*",
+		.bsc_imsi_allow = "2440[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* BSC allow is before NAT deny */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.bsc_imsi_allow = "[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* filter as deny is first */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.bsc_imsi_deny = "[0-9]*",
+		.bsc_imsi_allow = "[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* deny by nat rule */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -3,
+		.bsc_imsi_deny = "000[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* deny by nat rule */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -3,
+		.bsc_imsi_deny = "000[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = 0x23,
+		.nat_lu_reject_cause = 0x42,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = 0x42,
+		.want_cm_reject_cause = 0x23,
+	},
+	{
+		/* deny by bsc rule */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -2,
+		.bsc_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.want_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+	},
+	{
+		/* deny by bsc rule */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -2,
+		.bsc_imsi_deny = "[0-9]*",
+		.contype = FLT_CON_TYPE_LU,
+		.nat_cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.nat_lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED,
+		.bsc_cm_reject_cause = 0x42,
+		.bsc_lu_reject_cause = 0x23,
+		.want_lu_reject_cause = 0x23,
+		.want_cm_reject_cause = 0x42,
+	},
+};
+
+static void test_cr_filter()
+{
+	int i, res, contype;
+	struct msgb *msg = msgb_alloc(4096, "test_cr_filter");
+	struct bsc_nat_parsed *parsed;
+	struct bsc_msg_acc_lst *nat_lst, *bsc_lst;
+	struct bsc_msg_acc_lst_entry *nat_entry, *bsc_entry;
+	struct bsc_filter_reject_cause cause;
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+	struct bsc_connection *bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo", 0);
+	bsc_config_add_lac(bsc->cfg, 1234);
+	bsc->cfg->acc_lst_name = "bsc";
+	nat->acc_lst_name = "nat";
+
+	nat_lst = bsc_msg_acc_lst_get(nat, &nat->access_lists, "nat");
+	bsc_lst = bsc_msg_acc_lst_get(nat, &nat->access_lists, "bsc");
+
+	bsc_entry = bsc_msg_acc_lst_entry_create(bsc_lst);
+	nat_entry = bsc_msg_acc_lst_entry_create(nat_lst);
+
+	/* test the default value as we are going to overwrite it */
+	OSMO_ASSERT(bsc_entry->cm_reject_cause == GSM48_REJECT_PLMN_NOT_ALLOWED);
+	OSMO_ASSERT(bsc_entry->lu_reject_cause == GSM48_REJECT_PLMN_NOT_ALLOWED);
+
+	for (i = 0; i < ARRAY_SIZE(cr_filter); ++i) {
+		char *imsi;
+		msgb_reset(msg);
+		copy_to_msg(msg, cr_filter[i].data, cr_filter[i].length);
+
+		bsc_entry->cm_reject_cause = cr_filter[i].bsc_cm_reject_cause;
+		bsc_entry->lu_reject_cause = cr_filter[i].bsc_lu_reject_cause;
+		nat_entry->cm_reject_cause = cr_filter[i].nat_cm_reject_cause;
+		nat_entry->lu_reject_cause = cr_filter[i].nat_lu_reject_cause;
+
+		if (gsm_parse_reg(nat_entry, &nat_entry->imsi_deny_re, &nat_entry->imsi_deny,
+			      cr_filter[i].nat_imsi_deny ? 1 : 0,
+			      &cr_filter[i].nat_imsi_deny) != 0)
+			abort();
+		if (gsm_parse_reg(bsc_entry, &bsc_entry->imsi_allow_re, &bsc_entry->imsi_allow,
+			      cr_filter[i].bsc_imsi_allow ? 1 : 0,
+			      &cr_filter[i].bsc_imsi_allow) != 0)
+			abort();
+		if (gsm_parse_reg(bsc_entry, &bsc_entry->imsi_deny_re, &bsc_entry->imsi_deny,
+			      cr_filter[i].bsc_imsi_deny ? 1 : 0,
+			      &cr_filter[i].bsc_imsi_deny) != 0)
+			abort();
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed) {
+			printf("FAIL: Failed to parse the message\n");
+			abort();
+		}
+
+		memset(&cause, 0, sizeof(cause));
+		res = bsc_nat_filter_sccp_cr(bsc, msg, parsed, &contype, &imsi, &cause);
+		if (res != cr_filter[i].result) {
+			printf("FAIL: Wrong result %d for test %d.\n", res, i);
+			abort();
+		}
+
+
+		OSMO_ASSERT(cause.cm_reject_cause == cr_filter[i].want_cm_reject_cause);
+		OSMO_ASSERT(cause.lu_reject_cause == cr_filter[i].want_lu_reject_cause);
+
+		if (contype != cr_filter[i].contype) {
+			printf("FAIL: Wrong contype %d for test %d.\n", res, contype);
+			abort();
+		}
+
+		talloc_steal(parsed, imsi);
+		talloc_free(parsed);
+	}
+
+	msgb_free(msg);
+	bsc_nat_free(nat);
+}
+
+static void test_dt_filter()
+{
+	int i;
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct bsc_nat_parsed *parsed;
+	struct bsc_filter_reject_cause cause;
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+	struct bsc_connection *bsc = bsc_connection_alloc(nat);
+	struct nat_sccp_connection *con = talloc_zero(0, struct nat_sccp_connection);
+
+	bsc->cfg = bsc_config_alloc(nat, "foo", 0);
+	bsc_config_add_lac(bsc->cfg, 23);
+	con->bsc = bsc;
+
+	msgb_reset(msg);
+	copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp));
+
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	if (parsed->bssap != BSSAP_MSG_DTAP) {
+		printf("FAIL: It should be dtap\n");
+		abort();
+	}
+
+	/* gsm_type is actually the size of the dtap */
+	if (parsed->gsm_type < msgb_l3len(msg) - 3) {
+		printf("FAIL: Not enough space for the content\n");
+		abort();
+	}
+
+	memset(&cause, 0, sizeof(cause));
+	OSMO_ASSERT(!con->filter_state.imsi);
+	if (bsc_nat_filter_dt(bsc, msg, con, parsed, &cause) != 1) {
+		printf("FAIL: Should have passed..\n");
+		abort();
+	}
+	OSMO_ASSERT(con->filter_state.imsi);
+	OSMO_ASSERT(talloc_parent(con->filter_state.imsi) == con);
+
+	/* just some basic length checking... */
+	for (i = ARRAY_SIZE(id_resp); i >= 0; --i) {
+		msgb_reset(msg);
+		copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp));
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed)
+			continue;
+
+		con->filter_state.imsi_checked = 0;
+		memset(&cause, 0, sizeof(cause));
+		bsc_nat_filter_dt(bsc, msg, con, parsed, &cause);
+	}
+
+	msgb_free(msg);
+	bsc_nat_free(nat);
+}
+
+static void test_setup_rewrite()
+{
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct msgb *out;
+	struct bsc_nat_parsed *parsed;
+	const char *imsi = "27408000001234";
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+
+	/* a fake list */
+	struct osmo_config_list entries;
+	struct osmo_config_entry entry;
+
+	INIT_LLIST_HEAD(&entries.entry);
+	entry.mcc = "274";
+	entry.mnc = "08";
+	entry.option = "^0([1-9])";
+	entry.text = "0049";
+	llist_add_tail(&entry.list, &entries.entry);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+
+	/* verify that nothing changed */
+	msgb_reset(msg);
+	copy_to_msg(msg, cc_setup_international, ARRAY_SIZE(cc_setup_international));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (msg != out) {
+		printf("FAIL: The message should not have been changed\n");
+		abort();
+	}
+
+	verify_msg(out, cc_setup_international, ARRAY_SIZE(cc_setup_international));
+	talloc_free(parsed);
+
+	/* verify that something in the message changes */
+	msgb_reset(msg);
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (!out) {
+		printf("FAIL: A new message should be created.\n");
+		abort();
+	}
+
+	if (msg == out) {
+		printf("FAIL: The message should have changed\n");
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national_patched, ARRAY_SIZE(cc_setup_national_patched));
+	msgb_free(out);
+
+	/* Make sure that a wildcard is matching */
+	entry.mnc = "*";
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+	msg = msgb_alloc(4096, "test_dt_filter");
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (!out) {
+		printf("FAIL: A new message should be created.\n");
+		abort();
+	}
+
+	if (msg == out) {
+		printf("FAIL: The message should have changed\n");
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national_patched, ARRAY_SIZE(cc_setup_national_patched));
+	msgb_free(out);
+
+	/* Make sure that a wildcard is matching */
+	entry.mnc = "09";
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+	msg = msgb_alloc(4096, "test_dt_filter");
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (out != msg) {
+		printf("FAIL: The message should be unchanged.\n");
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	msgb_free(out);
+
+	/* Now see what happens to an international number */
+	entry.mnc = "*";
+	entry.option = "^\\+[0-9][0-9]([1-9])";
+	entry.text = "0036";
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+	msg = msgb_alloc(4096, "test_dt_filter");
+	copy_to_msg(msg, cc_setup_national_patched, ARRAY_SIZE(cc_setup_national_patched));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp %d\n", __LINE__);
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (!out) {
+		printf("FAIL: A new message should be created %d.\n", __LINE__);
+		abort();
+	}
+
+	if (msg == out) {
+		printf("FAIL: The message should have changed %d\n", __LINE__);
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national_patched_patched,
+			ARRAY_SIZE(cc_setup_national_patched_patched));
+	msgb_free(out);
+
+	/* go from international back to national */
+	entry.mnc = "*";
+	entry.option = "^\\+([0-9])";
+	entry.text = "36";
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+	msg = msgb_alloc(4096, "test_dt_filter");
+	copy_to_msg(msg, cc_setup_national_patched, ARRAY_SIZE(cc_setup_national_patched));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp %d\n", __LINE__);
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (!out) {
+		printf("FAIL: A new message should be created %d.\n", __LINE__);
+		abort();
+	}
+
+	if (msg == out) {
+		printf("FAIL: The message should have changed %d\n", __LINE__);
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national_again,
+			ARRAY_SIZE(cc_setup_national_again));
+	msgb_free(out);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, NULL);
+	bsc_nat_free(nat);
+}
+
+static void test_setup_rewrite_prefix(void)
+{
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct msgb *out;
+	struct bsc_nat_parsed *parsed;
+	const char *imsi = "27408000001234";
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+
+	/* a fake list */
+	struct osmo_config_list entries;
+	struct osmo_config_entry entry;
+
+	INIT_LLIST_HEAD(&entries.entry);
+	entry.mcc = "274";
+	entry.mnc = "08";
+	entry.option = "^0([1-9])";
+	entry.text = "prefix_lookup";
+	llist_add_tail(&entry.list, &entries.entry);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+
+        nat->num_rewr_trie = nat_rewrite_parse(nat, "prefixes.csv");
+
+	msgb_reset(msg);
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (!out) {
+		printf("FAIL: A new message should be created.\n");
+		abort();
+	}
+
+	if (msg == out) {
+		printf("FAIL: The message should have changed\n");
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national_patched, ARRAY_SIZE(cc_setup_national_patched));
+	msgb_free(out);
+
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, NULL);
+	bsc_nat_free(nat);
+}
+
+static void test_setup_rewrite_post(void)
+{
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct msgb *out;
+	struct bsc_nat_parsed *parsed;
+	const char *imsi = "27408000001234";
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+
+	/* a fake list */
+	struct osmo_config_list entries;
+	struct osmo_config_entry entry;
+	struct osmo_config_list entries_post;
+	struct osmo_config_entry entry_post;
+
+	INIT_LLIST_HEAD(&entries.entry);
+	entry.mcc = "274";
+	entry.mnc = "08";
+	entry.option = "^0([1-9])";
+	entry.text = "0049";
+	llist_add_tail(&entry.list, &entries.entry);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, &entries);
+
+	/* attempt to undo the previous one */
+	INIT_LLIST_HEAD(&entries_post.entry);
+	entry_post.mcc = "274";
+	entry_post.mnc = "08";
+	entry_post.option = "^\\+49([1-9])";
+	entry_post.text = "prefix_lookup";
+	llist_add_tail(&entry_post.list, &entries_post.entry);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr_post, &entries_post);
+
+        nat->num_rewr_trie = nat_rewrite_parse(nat, "prefixes.csv");
+
+	msgb_reset(msg);
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (!out) {
+		printf("FAIL: A new message should be created.\n");
+		abort();
+	}
+
+	if (msg == out) {
+		printf("FAIL: The message should have changed\n");
+		abort();
+	}
+
+	verify_msg(out, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	msgb_free(out);
+
+	bsc_nat_free(nat);
+}
+
+static void test_sms_smsc_rewrite()
+{
+	struct msgb *msg = msgb_alloc(4096, "SMSC rewrite"), *out;
+	struct bsc_nat_parsed *parsed;
+	const char *imsi = "515039900406700";
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+
+	/* a fake list */
+	struct osmo_config_list smsc_entries, dest_entries, clear_entries;
+	struct osmo_config_entry smsc_entry, dest_entry, clear_entry;
+
+	INIT_LLIST_HEAD(&smsc_entries.entry);
+	INIT_LLIST_HEAD(&dest_entries.entry);
+	INIT_LLIST_HEAD(&clear_entries.entry);
+	smsc_entry.mcc = "^515039";
+	smsc_entry.option = "639180000105()";
+	smsc_entry.text   = "6666666666667";
+	llist_add_tail(&smsc_entry.list, &smsc_entries.entry);
+	dest_entry.mcc = "515";
+	dest_entry.mnc = "03";
+	dest_entry.option = "^0049";
+	dest_entry.text   = "";
+	llist_add_tail(&dest_entry.list, &dest_entries.entry);
+	clear_entry.mcc = "^515039";
+	clear_entry.option = "^0049";
+	clear_entry.text   = "";
+	llist_add_tail(&clear_entry.list, &clear_entries.entry);
+
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->smsc_rewr, &smsc_entries);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->tpdest_match, &dest_entries);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_clear_tp_srr, &clear_entries);
+
+	printf("Testing SMSC rewriting.\n");
+
+	/*
+	 * Check if the SMSC address is changed
+	 */
+	copy_to_msg(msg, smsc_rewrite, ARRAY_SIZE(smsc_rewrite));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse SMS\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (out == msg) {
+		printf("FAIL: This should have changed.\n");
+		abort();
+	}
+
+	verify_msg(out, smsc_rewrite_patched, ARRAY_SIZE(smsc_rewrite_patched));
+	msgb_free(out);
+
+	/* clear out the filter for SMSC */
+	printf("Attempting to only rewrite the HDR\n");
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->smsc_rewr, NULL);
+	msg = msgb_alloc(4096, "SMSC rewrite");
+	copy_to_msg(msg, smsc_rewrite, ARRAY_SIZE(smsc_rewrite));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse SMS\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (out == msg) {
+		printf("FAIL: This should have changed.\n");
+		abort();
+	}
+
+	verify_msg(out, smsc_rewrite_patched_hdr, ARRAY_SIZE(smsc_rewrite_patched_hdr));
+	msgb_free(out);
+
+	/* clear out the next filter */
+	printf("Attempting to change nothing.\n");
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_clear_tp_srr, NULL);
+	msg = msgb_alloc(4096, "SMSC rewrite");
+	copy_to_msg(msg, smsc_rewrite, ARRAY_SIZE(smsc_rewrite));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse SMS\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (out != msg) {
+		printf("FAIL: This should not have changed.\n");
+		abort();
+	}
+
+	verify_msg(out, smsc_rewrite, ARRAY_SIZE(smsc_rewrite));
+	msgb_free(out);
+	bsc_nat_free(nat);
+}
+
+static void test_sms_number_rewrite(void)
+{
+	struct msgb *msg, *out;
+	struct bsc_nat_parsed *parsed;
+	const char *imsi = "515039900406700";
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+
+	/* a fake list */
+	struct osmo_config_list num_entries, clear_entries;
+	struct osmo_config_entry num_entry, clear_entry;
+
+	INIT_LLIST_HEAD(&num_entries.entry);
+	num_entry.mcc = "^515039";
+	num_entry.option = "^0049()";
+	num_entry.text   = "0032";
+	llist_add_tail(&num_entry.list, &num_entries.entry);
+
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_num_rewr, &num_entries);
+
+	printf("Testing SMS TP-DA rewriting.\n");
+
+	/*
+	 * Check if the SMSC address is changed
+	 */
+ 	msg = msgb_alloc(4096, "SMSC rewrite");
+	copy_to_msg(msg, smsc_rewrite, ARRAY_SIZE(smsc_rewrite));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse SMS\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (out == msg) {
+		printf("FAIL: This should have changed.\n");
+		abort();
+	}
+
+	verify_msg(out, smsc_rewrite_num_patched,
+		   ARRAY_SIZE(smsc_rewrite_num_patched));
+	msgb_free(out);
+
+	/*
+	 * Now with TP-SRR rewriting enabled
+	 */
+	INIT_LLIST_HEAD(&clear_entries.entry);
+	clear_entry.mcc = "^515039";
+	clear_entry.option = "";
+	clear_entry.text   = "";
+	llist_add_tail(&clear_entry.list, &clear_entries.entry);
+	bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_clear_tp_srr, &clear_entries);
+
+ 	msg = msgb_alloc(4096, "SMSC rewrite");
+	copy_to_msg(msg, smsc_rewrite, ARRAY_SIZE(smsc_rewrite));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		printf("FAIL: Could not parse SMS\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_msg(nat, msg, parsed, imsi);
+	if (out == msg) {
+		printf("FAIL: This should have changed.\n");
+		abort();
+	}
+
+	verify_msg(out, smsc_rewrite_num_patched_tp_srr,
+		   ARRAY_SIZE(smsc_rewrite_num_patched_tp_srr));
+	msgb_free(out);
+	bsc_nat_free(nat);
+}
+
+static void test_barr_list_parsing(void)
+{
+	int rc;
+	int cm, lu;
+	struct rb_node *node;
+	struct rb_root root = RB_ROOT;
+	struct osmo_config_list *lst = osmo_config_list_parse(NULL, "barr.cfg");
+	if (lst == NULL)
+		abort();
+
+	rc = bsc_filter_barr_adapt(NULL, &root, lst);
+	if (rc != 0)
+		abort();
+	talloc_free(lst);
+
+
+	for (node = rb_first(&root); node; node = rb_next(node)) {
+		struct bsc_filter_barr_entry *entry;
+		entry = rb_entry(node, struct bsc_filter_barr_entry, node);
+		printf("IMSI: %s CM: %d LU: %d\n", entry->imsi,
+			entry->cm_reject_cause, entry->lu_reject_cause);
+	}
+
+	/* do the look up now.. */
+	rc = bsc_filter_barr_find(&root, "12123119", &cm, &lu);
+	if (!rc) {
+		printf("Failed to find the IMSI.\n");
+		abort();
+	}
+
+	if (cm != 3 || lu != 4) {
+		printf("Found CM(%d) and LU(%d)\n", cm, lu);
+		abort();
+	}
+
+	/* empty and check that it is empty */
+	bsc_filter_barr_adapt(NULL, &root, NULL);
+	if (!RB_EMPTY_ROOT(&root)) {
+		printf("Failed to empty the list.\n");
+		abort();
+	}
+
+	/* check that dup results in an error */
+	lst = osmo_config_list_parse(NULL, "barr_dup.cfg");
+	if (lst == NULL) {
+		printf("Failed to parse list with dups\n");
+		abort();
+	}
+
+	rc = bsc_filter_barr_adapt(NULL, &root, lst);
+	if (rc != -1) {
+		printf("It should have failed due dup\n");
+		abort();
+	}
+	talloc_free(lst);
+
+	/* dump for reference */
+	for (node = rb_first(&root); node; node = rb_next(node)) {
+		struct bsc_filter_barr_entry *entry;
+		entry = rb_entry(node, struct bsc_filter_barr_entry, node);
+		printf("IMSI: %s CM: %d LU: %d\n", entry->imsi,
+			entry->cm_reject_cause, entry->lu_reject_cause);
+
+	}
+	rc = bsc_filter_barr_adapt(NULL, &root, NULL);
+}
+
+static void test_nat_extract_lac()
+{
+	int res;
+	struct bsc_connection *bsc;
+	struct bsc_nat *nat;
+	struct nat_sccp_connection con;
+	struct bsc_nat_parsed *parsed;
+	struct msgb *msg = msgb_alloc(4096, "test-message");
+
+	printf("Testing LAC extraction from SCCP CR\n");
+
+	/* initialize the testcase */
+	nat = bsc_nat_alloc();
+	bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo", 0);
+
+	memset(&con, 0, sizeof(con));
+	con.bsc = bsc;
+
+	/* create the SCCP CR */
+	msg->l2h = msgb_put(msg, ARRAY_SIZE(bssmap_cr));
+	memcpy(msg->l2h, bssmap_cr, ARRAY_SIZE(bssmap_cr));
+
+	/* parse it and pass it on */
+	parsed = bsc_nat_parse(msg);
+	res = bsc_nat_extract_lac(bsc, &con, parsed, msg);
+	OSMO_ASSERT(res == 0);
+
+	/* verify the LAC */
+	OSMO_ASSERT(con.lac == 8210);
+	OSMO_ASSERT(con.ci == 50000);
+
+	bsc_nat_free(nat);
+}
+
+int main(int argc, char **argv)
+{
+	msgb_talloc_ctx_init(NULL, 0);
+	sccp_set_log_area(DSCCP);
+	osmo_init_logging(&log_info);
+
+	test_filter();
+	test_contrack();
+	test_paging();
+	test_mgcp_ass_tracking();
+	test_mgcp_find();
+	test_mgcp_rewrite();
+	test_mgcp_parse();
+	test_cr_filter();
+	test_dt_filter();
+	test_setup_rewrite();
+	test_setup_rewrite_prefix();
+	test_setup_rewrite_post();
+	test_sms_smsc_rewrite();
+	test_sms_number_rewrite();
+	test_mgcp_allocations();
+	test_barr_list_parsing();
+	test_nat_extract_lac();
+
+	printf("Testing execution completed.\n");
+	return 0;
+}
+
+/* stub */
+void bsc_nat_send_mgcp_to_msc(struct bsc_nat *nat, struct msgb *msg)
+{
+	abort();
+}
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.ok b/openbsc/tests/bsc-nat/bsc_nat_test.ok
new file mode 100644
index 0000000..ab04f42
--- /dev/null
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.ok
@@ -0,0 +1,39 @@
+Testing BSS Filtering.
+Going to test item: 0
+Going to test item: 1
+Going to test item: 2
+Going to test item: 3
+Going to test item: 4
+Going to test item: 5
+Going to test item: 6
+Going to test item: 7
+Going to test item: 8
+Going to test item: 9
+Going to test item: 10
+Going to test item: 11
+Going to test item: 12
+Testing connection tracking.
+Testing paging by lac.
+Testing MGCP.
+Testing finding of a BSC Connection
+Testing rewriting MGCP messages.
+Testing MGCP response parsing.
+Testing SMSC rewriting.
+Attempting to only rewrite the HDR
+Attempting to change nothing.
+Testing SMS TP-DA rewriting.
+IMSI: 12123115 CM: 3 LU: 4
+IMSI: 12123116 CM: 3 LU: 4
+IMSI: 12123117 CM: 3 LU: 4
+IMSI: 12123118 CM: 3 LU: 4
+IMSI: 12123119 CM: 3 LU: 4
+IMSI: 12123120 CM: 3 LU: 4
+IMSI: 12123123 CM: 3 LU: 1
+IMSI: 12123124 CM: 3 LU: 2
+IMSI: 12123125 CM: 3 LU: 3
+IMSI: 12123126 CM: 3 LU: 4
+IMSI: 12123127 CM: 3 LU: 5
+IMSI: 12123128 CM: 3 LU: 6
+IMSI: 12123124 CM: 3 LU: 2
+Testing LAC extraction from SCCP CR
+Testing execution completed.
diff --git a/openbsc/tests/bsc-nat/prefixes.csv b/openbsc/tests/bsc-nat/prefixes.csv
new file mode 100644
index 0000000..0c7660f
--- /dev/null
+++ b/openbsc/tests/bsc-nat/prefixes.csv
@@ -0,0 +1,2 @@
+0172,0049
++49,0
diff --git a/openbsc/tests/bsc/Makefile.am b/openbsc/tests/bsc/Makefile.am
new file mode 100644
index 0000000..9de4145
--- /dev/null
+++ b/openbsc/tests/bsc/Makefile.am
@@ -0,0 +1,46 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	bsc_test.ok \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	bsc_test \
+	$(NULL)
+
+bsc_test_SOURCES = \
+	bsc_test.c \
+	$(top_srcdir)/src/osmo-bsc/osmo_bsc_filter.c \
+	$(NULL)
+
+bsc_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOSCCP_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	-lrt \
+	$(NULL)
diff --git a/openbsc/tests/bsc/bsc_test.c b/openbsc/tests/bsc/bsc_test.c
new file mode 100644
index 0000000..20ed5b4
--- /dev/null
+++ b/openbsc/tests/bsc/bsc_test.c
@@ -0,0 +1,209 @@
+/*
+ * BSC Message filtering
+ *
+ * (C) 2013 by sysmocom s.f.m.c. GmbH
+ * Written by Jacob Erlbeck <jerlbeck@sysmocom.de>
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/backtrace.h>
+#include <osmocom/core/talloc.h>
+
+#include <stdio.h>
+#include <search.h>
+
+enum test {
+	TEST_SCAN_TO_BTS,
+	TEST_SCAN_TO_MSC,
+};
+
+/* GSM 04.08 MM INFORMATION test message */
+static uint8_t gsm48_mm_info_nn_tzt[] = {
+	0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+	0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+	0x11, 0x02, 0x73, 0x00,
+};
+
+static uint8_t gsm48_mm_info_nn_tzt_out[] = {
+	0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+	0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+	0x11, 0x02, 0x73, 0x1a,
+};
+
+static uint8_t gsm48_mm_info_nn_tzt_dst[] = {
+	0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+	0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+	0x11, 0x02, 0x73, 0x00, 0x49, 0x01, 0x00,
+};
+
+static uint8_t gsm48_mm_info_nn_tzt_dst_out[] = {
+	0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+	0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+	0x11, 0x02, 0x73, 0x1a, 0x49, 0x01, 0x02,
+};
+
+struct test_definition {
+	const uint8_t *data;
+	const uint16_t length;
+	const int dir;
+	const int result;
+	const uint8_t *out_data;
+	const uint16_t out_length;
+	const char* params;
+	const int n_params;
+};
+
+static int get_int(const char *params, size_t nmemb, const char *key, int def, int *is_set)
+{
+	const char *kv = NULL;
+
+	kv = strstr(params, key);
+	if (kv) {
+		kv += strlen(key) + 1;
+		fprintf(stderr, "get_int(%s) -> %d\n", key, atoi(kv));
+		if (is_set)
+			*is_set = 1;
+	}
+
+	return kv ? atoi(kv) : def;
+}
+
+static const struct test_definition test_scan_defs[] = {
+	{
+		.data = gsm48_mm_info_nn_tzt_dst,
+		.length = ARRAY_SIZE(gsm48_mm_info_nn_tzt),
+		.dir = TEST_SCAN_TO_BTS,
+		.result = 0,
+		.out_data = gsm48_mm_info_nn_tzt_dst_out,
+		.out_length = ARRAY_SIZE(gsm48_mm_info_nn_tzt_out),
+		.params = "tz_hr=-5 tz_mn=15 tz_dst=2",
+		.n_params = 3,
+	},
+	{
+		.data = gsm48_mm_info_nn_tzt_dst,
+		.length = ARRAY_SIZE(gsm48_mm_info_nn_tzt_dst),
+		.dir = TEST_SCAN_TO_BTS,
+		.result = 0,
+		.out_data = gsm48_mm_info_nn_tzt_dst_out,
+		.out_length = ARRAY_SIZE(gsm48_mm_info_nn_tzt_dst_out),
+		.params = "tz_hr=-5 tz_mn=15 tz_dst=2",
+		.n_params = 3,
+	},
+};
+
+static void test_scan(void)
+{
+	int i;
+
+	struct gsm_network *net;
+	struct gsm_bts *bts;
+	struct osmo_bsc_sccp_con *sccp_con;
+	struct bsc_msc_data *msc;
+	struct gsm_subscriber_connection *conn;
+
+	net = talloc_zero(NULL, struct gsm_network);
+	bts = talloc_zero(net, struct gsm_bts);
+	sccp_con = talloc_zero(net, struct osmo_bsc_sccp_con);
+	msc = talloc_zero(net, struct bsc_msc_data);
+	conn = talloc_zero(net, struct gsm_subscriber_connection);
+
+	bts->network = net;
+	sccp_con->msc = msc;
+	conn->bts = bts;
+	conn->sccp_con = sccp_con;
+
+	/* start testing with proper messages */
+	printf("Testing BTS<->MSC message scan.\n");
+	for (i = 0; i < ARRAY_SIZE(test_scan_defs); ++i) {
+		const struct test_definition *test_def = &test_scan_defs[i];
+		int result;
+		struct msgb *msg = msgb_alloc(4096, "test-message");
+		int is_set = 0;
+
+		net->tz.hr = get_int(test_def->params, test_def->n_params, "tz_hr", 0, &is_set);
+		net->tz.mn = get_int(test_def->params, test_def->n_params, "tz_mn", 0, &is_set);
+		net->tz.dst = get_int(test_def->params, test_def->n_params, "tz_dst", 0, &is_set);
+		net->tz.override = 1;
+
+		printf("Going to test item: %d\n", i);
+		msg->l3h = msgb_put(msg, test_def->length);
+		memcpy(msg->l3h, test_def->data, test_def->length);
+
+		switch (test_def->dir) {
+		case TEST_SCAN_TO_BTS:
+			/* override timezone of msg coming from the MSC */
+			result = bsc_scan_msc_msg(conn, msg);
+			break;
+		case TEST_SCAN_TO_MSC:
+			/* override timezone of msg coming from the BSC */
+			/* FIXME: no test for this case is defined in
+			 * test_scan_defs[], so this is never used. */
+			result = bsc_scan_bts_msg(conn, msg);
+			break;
+		default:
+			abort();
+			break;
+		}
+
+		if (result != test_def->result) {
+			printf("FAIL: Not the expected result, got: %d wanted: %d\n",
+				result, test_def->result);
+			goto out;
+		}
+
+		if (msgb_l3len(msg) != test_def->out_length) {
+			printf("FAIL: Not the expected message size, got: %d wanted: %d\n",
+				msgb_l3len(msg), test_def->out_length);
+			goto out;
+		}
+
+		if (memcmp(msgb_l3(msg), test_def->out_data, test_def->out_length) != 0) {
+			printf("FAIL: Not the expected message\n");
+			goto out;
+		}
+
+out:
+		msgb_free(msg);
+	}
+
+	talloc_free(net);
+}
+
+
+int main(int argc, char **argv)
+{
+	msgb_talloc_ctx_init(NULL, 0);
+	osmo_init_logging(&log_info);
+
+	test_scan();
+
+	printf("Testing execution completed.\n");
+	return 0;
+}
diff --git a/openbsc/tests/bsc/bsc_test.ok b/openbsc/tests/bsc/bsc_test.ok
new file mode 100644
index 0000000..0564bf0
--- /dev/null
+++ b/openbsc/tests/bsc/bsc_test.ok
@@ -0,0 +1,4 @@
+Testing BTS<->MSC message scan.
+Going to test item: 0
+Going to test item: 1
+Testing execution completed.
diff --git a/openbsc/tests/channel/Makefile.am b/openbsc/tests/channel/Makefile.am
new file mode 100644
index 0000000..5fec00a
--- /dev/null
+++ b/openbsc/tests/channel/Makefile.am
@@ -0,0 +1,40 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	channel_test.ok \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	channel_test \
+	$(NULL)
+
+channel_test_SOURCES = \
+	channel_test.c \
+	$(NULL)
+
+channel_test_LDFLAGS = \
+	-Wl,--wrap=paging_request \
+	$(NULL)
+
+channel_test_LDADD = \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBCRYPTO_LIBS) \
+	-ldbi \
+	$(NULL)
diff --git a/openbsc/tests/channel/channel_test.c b/openbsc/tests/channel/channel_test.c
new file mode 100644
index 0000000..428182f
--- /dev/null
+++ b/openbsc/tests/channel/channel_test.c
@@ -0,0 +1,173 @@
+/*
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <assert.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+
+#include <openbsc/common_bsc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_subscriber.h>
+
+static int s_end = 0;
+static struct gsm_subscriber_connection s_conn;
+static void *s_data;
+static gsm_cbfn *s_cbfn = NULL;
+
+/* our handler */
+static int subscr_cb(unsigned int hook, unsigned int event, struct msgb *msg, void *data, void *param)
+{
+	assert(hook == 101);
+	assert(event == 200);
+	assert(msg == (void*)0x1323L);
+	assert(data == &s_conn);
+	assert(param == (void*)0x2342L);
+	printf("Reached, didn't crash, test passed\n");
+	s_end = true;
+	return 0;
+}
+
+/* override, requires '-Wl,--wrap=paging_request'.
+/  mock object for testing, directly invoke the cb... maybe later through the timer */
+int __real_paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, gsm_cbfn *cbfn, void *data);
+int __wrap_paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, gsm_cbfn *cbfn, void *data)
+{
+	s_data = data;
+	s_cbfn = cbfn;
+
+	/* claim we have patched */
+	return 1;
+}
+
+
+void test_request_chan(struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	printf("Testing the gsm_subscriber chan logic\n");
+
+	bts = gsm_bts_alloc(net, 0);
+	bts->location_area_code = 23;
+	s_conn.network = net;
+
+	/* Create a dummy subscriber */
+	struct gsm_subscriber *subscr = subscr_alloc();
+	subscr->lac = 23;
+	subscr->group = net->subscr_group;
+
+	OSMO_ASSERT(subscr->group);
+	OSMO_ASSERT(subscr->group->net == net);
+
+	/* Ask for a channel... */
+	struct subscr_request *sr;
+	sr = subscr_request_channel(subscr, RSL_CHANNEED_TCH_F, subscr_cb, (void*)0x2342L);
+	OSMO_ASSERT(sr);
+	OSMO_ASSERT(s_cbfn);
+	s_cbfn(101, 200, (void*)0x1323L, &s_conn, s_data);
+
+	OSMO_ASSERT(s_end);
+}
+
+
+void test_bts_debug_print(struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+
+	printf("Testing the lchan printing:");
+
+	/* Add a BTS with some reasonanbly non-zero id */
+	bts = gsm_bts_alloc(net, 45);
+	/* Add a second TRX to test on multiple TRXs */
+	gsm_bts_trx_alloc(bts);
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		char *name = gsm_lchan_name(&trx->ts[3].lchan[4]);
+
+		if (name)
+			printf(" %s", name);
+		else
+			printf("NULL name");
+	}
+	printf("\n");
+}
+
+
+void test_dyn_ts_subslots(void)
+{
+	struct gsm_bts_trx_ts ts;
+
+	printf("Testing subslot numbers for pchan types\n");
+
+	ts.pchan = GSM_PCHAN_TCH_F;
+	OSMO_ASSERT(ts_subslots(&ts) == 1);
+
+	ts.pchan = GSM_PCHAN_TCH_H;
+	OSMO_ASSERT(ts_subslots(&ts) == 2);
+
+	ts.pchan = GSM_PCHAN_PDCH;
+	OSMO_ASSERT(ts_subslots(&ts) == 0);
+
+	ts.pchan = GSM_PCHAN_TCH_F_PDCH;
+	ts.flags = 0; /* TCH_F mode */
+	OSMO_ASSERT(ts_subslots(&ts) == 1);
+	ts.flags = TS_F_PDCH_ACTIVE;
+	OSMO_ASSERT(ts_subslots(&ts) == 0);
+
+	ts.pchan = GSM_PCHAN_TCH_F_TCH_H_PDCH;
+	ts.dyn.pchan_is = GSM_PCHAN_TCH_F;
+	OSMO_ASSERT(ts_subslots(&ts) == 1);
+	ts.dyn.pchan_is = GSM_PCHAN_TCH_H;
+	OSMO_ASSERT(ts_subslots(&ts) == 2);
+	ts.dyn.pchan_is = GSM_PCHAN_PDCH;
+	OSMO_ASSERT(ts_subslots(&ts) == 0);
+}
+
+int main(int argc, char **argv)
+{
+	struct gsm_network *network;
+
+	osmo_init_logging(&log_info);
+
+	/* Create a dummy network */
+	network = bsc_network_init(tall_bsc_ctx, 1, 1, NULL);
+	if (!network)
+		return EXIT_FAILURE;
+
+	test_request_chan(network);
+	test_dyn_ts_subslots();
+	test_bts_debug_print(network);
+
+	return EXIT_SUCCESS;
+}
+
+void sms_alloc() {}
+void sms_free() {}
+void gsm48_secure_channel() {}
+void vty_out() {}
+void switch_trau_mux() {}
+void rtp_socket_free() {}
+
+struct tlv_definition nm_att_tlvdef;
+
diff --git a/openbsc/tests/channel/channel_test.ok b/openbsc/tests/channel/channel_test.ok
new file mode 100644
index 0000000..e2e93ef
--- /dev/null
+++ b/openbsc/tests/channel/channel_test.ok
@@ -0,0 +1,4 @@
+Testing the gsm_subscriber chan logic
+Reached, didn't crash, test passed
+Testing subslot numbers for pchan types
+Testing the lchan printing: (bts=45,trx=0,ts=3,ss=4) (bts=45,trx=1,ts=3,ss=4)
diff --git a/openbsc/tests/ctrl_test_runner.py b/openbsc/tests/ctrl_test_runner.py
new file mode 100644
index 0000000..d76ed65
--- /dev/null
+++ b/openbsc/tests/ctrl_test_runner.py
@@ -0,0 +1,675 @@
+#!/usr/bin/env python
+
+# (C) 2013 by Jacob Erlbeck <jerlbeck@sysmocom.de>
+# (C) 2014 by Holger Hans Peter Freyther
+# based on vty_test_runner.py:
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# (C) 2013 by Holger Hans Peter Freyther
+# based on bsc_control.py.
+
+# 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import time
+import unittest
+import socket
+import sys
+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
+
+# to be able to find $top_srcdir/doc/...
+confpath = os.path.join(sys.path[0], '..')
+verbose = False
+
+class TestCtrlBase(unittest.TestCase):
+
+    def ctrl_command(self):
+        raise Exception("Needs to be implemented by a subclass")
+
+    def ctrl_app(self):
+        raise Exception("Needs to be implemented by a subclass")
+
+    def setUp(self):
+        osmo_ctrl_cmd = self.ctrl_command()[:]
+        config_index = osmo_ctrl_cmd.index('-c')
+        if config_index:
+            cfi = config_index + 1
+            osmo_ctrl_cmd[cfi] = os.path.join(confpath, osmo_ctrl_cmd[cfi])
+
+        try:
+            self.proc = osmoutil.popen_devnull(osmo_ctrl_cmd)
+        except OSError:
+            print >> sys.stderr, "Current directory: %s" % os.getcwd()
+            print >> sys.stderr, "Consider setting -b"
+        time.sleep(2)
+
+        appstring = self.ctrl_app()[2]
+        appport = self.ctrl_app()[0]
+        self.connect("127.0.0.1", appport)
+        self.next_id = 1000
+
+    def tearDown(self):
+        self.disconnect()
+        osmoutil.end_proc(self.proc)
+
+    def disconnect(self):
+        if not (self.sock is None):
+            self.sock.close()
+
+    def connect(self, host, port):
+        if verbose:
+            print "Connecting to host %s:%i" % (host, port)
+
+        retries = 30
+        while True:
+            try:
+                sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                sck.setblocking(1)
+                sck.connect((host, port))
+            except IOError:
+                retries -= 1
+                if retries <= 0:
+                    raise
+                time.sleep(.1)
+                continue
+            break
+        self.sock = sck
+        return sck
+
+    def send(self, data):
+        if verbose:
+            print "Sending \"%s\"" %(data)
+        data = Ctrl().add_header(data)
+        return self.sock.send(data) == len(data)
+
+    def send_set(self, var, value, id):
+        setmsg = "SET %s %s %s" %(id, var, value)
+        return self.send(setmsg)
+
+    def send_get(self, var, id):
+        getmsg = "GET %s %s" %(id, var)
+        return self.send(getmsg)
+
+    def do_set(self, var, value):
+        id = self.next_id
+        self.next_id += 1
+        self.send_set(var, value, id)
+        return self.recv_msgs()[id]
+
+    def do_get(self, var):
+        id = self.next_id
+        self.next_id += 1
+        self.send_get(var, id)
+        return self.recv_msgs()[id]
+
+    def recv_msgs(self):
+        responses = {}
+        data = self.sock.recv(4096)
+        while (len(data)>0):
+            (head, data) = IPA().split_combined(data)
+            answer = Ctrl().rem_header(head)
+            if verbose:
+                print "Got message:", answer
+            (mtype, id, msg) = answer.split(None, 2)
+            id = int(id)
+            rsp = {'mtype': mtype, 'id': id}
+            if mtype == "ERROR":
+                rsp['error'] = msg
+            else:
+                split = msg.split(None, 1)
+                rsp['var'] = split[0]
+                if len(split) > 1:
+                    rsp['value'] = split[1]
+                else:
+                    rsp['value'] = None
+            responses[id] = rsp
+
+        if verbose:
+            print "Decoded replies: ", responses
+
+        return responses
+
+
+class TestCtrlBSC(TestCtrlBase):
+
+    def tearDown(self):
+        TestCtrlBase.tearDown(self)
+        os.unlink("tmp_dummy_sock")
+
+    def ctrl_command(self):
+        return ["./src/osmo-bsc/osmo-bsc-sccplite", "-r", "tmp_dummy_sock", "-c",
+                "doc/examples/osmo-bsc-sccplite/osmo-bsc-sccplite.cfg"]
+
+    def ctrl_app(self):
+        return (4249, "./src/osmo-bsc/osmo-bsc-sccplite", "OsmoBSC", "bsc")
+
+    def testCtrlErrs(self):
+        r = self.do_get('invalid')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Command not found')
+
+        r = self.do_set('rf_locked', '999')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Value failed verification.')
+
+        r = self.do_get('bts')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Error while parsing the index.')
+
+        r = self.do_get('bts.999')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Error while resolving object')
+
+    def testBtsLac(self):
+        r = self.do_get('bts.0.location-area-code')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.location-area-code')
+        self.assertEquals(r['value'], '1')
+
+        r = self.do_set('bts.0.location-area-code', '23')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.location-area-code')
+        self.assertEquals(r['value'], '23')
+
+        r = self.do_get('bts.0.location-area-code')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.location-area-code')
+        self.assertEquals(r['value'], '23')
+
+        r = self.do_set('bts.0.location-area-code', '-1')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Input not within the range')
+
+    def testBtsCi(self):
+        r = self.do_get('bts.0.cell-identity')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.cell-identity')
+        self.assertEquals(r['value'], '0')
+
+        r = self.do_set('bts.0.cell-identity', '23')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.cell-identity')
+        self.assertEquals(r['value'], '23')
+
+        r = self.do_get('bts.0.cell-identity')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.cell-identity')
+        self.assertEquals(r['value'], '23')
+
+        r = self.do_set('bts.0.cell-identity', '-1')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Input not within the range')
+
+    def testBtsGenerateSystemInformation(self):
+        r = self.do_get('bts.0.send-new-system-informations')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Write Only attribute')
+
+        # No RSL links so it will fail
+        r = self.do_set('bts.0.send-new-system-informations', '1')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Failed to generate SI')
+
+    def testBtsChannelLoad(self):
+        r = self.do_set('bts.0.channel-load', '1')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Read Only attribute')
+
+        # No RSL link so everything is 0
+        r = self.do_get('bts.0.channel-load')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['value'],
+		'CCCH+SDCCH4,0,0 TCH/F,0,0 TCH/H,0,0 SDCCH8,0,0'
+		+ ' TCH/F_PDCH,0,0 CCCH+SDCCH4+CBCH,0,0'
+		+ ' SDCCH8+CBCH,0,0 TCH/F_TCH/H_PDCH,0,0')
+
+    def testBtsOmlConnectionState(self):
+        """Check OML state. It will not be connected"""
+        r = self.do_set('bts.0.oml-connection-state', '1')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Read Only attribute')
+
+        # No RSL link so everything is 0
+        r = self.do_get('bts.0.oml-connection-state')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['value'], 'disconnected')
+
+    def testTrxPowerRed(self):
+        r = self.do_get('bts.0.trx.0.max-power-reduction')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.trx.0.max-power-reduction')
+        self.assertEquals(r['value'], '20')
+
+        r = self.do_set('bts.0.trx.0.max-power-reduction', '22')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.trx.0.max-power-reduction')
+        self.assertEquals(r['value'], '22')
+        
+        r = self.do_get('bts.0.trx.0.max-power-reduction')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.trx.0.max-power-reduction')
+        self.assertEquals(r['value'], '22')
+        
+        r = self.do_set('bts.0.trx.0.max-power-reduction', '1')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Value must be even')
+
+    def testTrxArfcn(self):
+        r = self.do_get('bts.0.trx.0.arfcn')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.trx.0.arfcn')
+        self.assertEquals(r['value'], '871')
+
+        r = self.do_set('bts.0.trx.0.arfcn', '873')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.trx.0.arfcn')
+        self.assertEquals(r['value'], '873')
+
+        r = self.do_get('bts.0.trx.0.arfcn')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.trx.0.arfcn')
+        self.assertEquals(r['value'], '873')
+
+        r = self.do_set('bts.0.trx.0.arfcn', '2000')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Input not within the range')
+
+    def testRfLock(self):
+        r = self.do_get('bts.0.rf_state')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.rf_state')
+        self.assertEquals(r['value'], 'inoperational,unlocked,on')
+
+        r = self.do_set('rf_locked', '1')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'rf_locked')
+        self.assertEquals(r['value'], '1')
+
+        time.sleep(1.5)
+
+        r = self.do_get('bts.0.rf_state')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.rf_state')
+        self.assertEquals(r['value'], 'inoperational,locked,off')
+
+        r = self.do_get('rf_locked')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'rf_locked')
+        self.assertEquals(r['value'], 'state=off,policy=off')
+
+        r = self.do_set('rf_locked', '0')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'rf_locked')
+        self.assertEquals(r['value'], '0')
+
+        time.sleep(1.5)
+
+        r = self.do_get('bts.0.rf_state')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.rf_state')
+        self.assertEquals(r['value'], 'inoperational,unlocked,on')
+
+        r = self.do_get('rf_locked')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'rf_locked')
+        self.assertEquals(r['value'], 'state=off,policy=on')
+
+    def testTimezone(self):
+        r = self.do_get('timezone')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'timezone')
+        self.assertEquals(r['value'], 'off')
+
+        r = self.do_set('timezone', '-2,15,2')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'timezone')
+        self.assertEquals(r['value'], '-2,15,2')
+
+        r = self.do_get('timezone')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'timezone')
+        self.assertEquals(r['value'], '-2,15,2')
+
+        # Test invalid input
+        r = self.do_set('timezone', '-2,15,2,5,6,7')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'timezone')
+        self.assertEquals(r['value'], '-2,15,2')
+
+        r = self.do_set('timezone', '-2,15')
+        self.assertEquals(r['mtype'], 'ERROR')
+        r = self.do_set('timezone', '-2')
+        self.assertEquals(r['mtype'], 'ERROR')
+        r = self.do_set('timezone', '1')
+
+        r = self.do_set('timezone', 'off')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'timezone')
+        self.assertEquals(r['value'], 'off')
+
+        r = self.do_get('timezone')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'timezone')
+        self.assertEquals(r['value'], 'off')
+
+    def testMcc(self):
+        r = self.do_set('mcc', '23')
+        r = self.do_get('mcc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mcc')
+        self.assertEquals(r['value'], '023')
+
+        r = self.do_set('mcc', '023')
+        r = self.do_get('mcc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mcc')
+        self.assertEquals(r['value'], '023')
+
+    def testMnc(self):
+        r = self.do_set('mnc', '9')
+        r = self.do_get('mnc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mnc')
+        self.assertEquals(r['value'], '09')
+
+        r = self.do_set('mnc', '09')
+        r = self.do_get('mnc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mnc')
+        self.assertEquals(r['value'], '09')
+
+        r = self.do_set('mnc', '009')
+        r = self.do_get('mnc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mnc')
+        self.assertEquals(r['value'], '009')
+
+    def testMccMncApply(self):
+        # Test some invalid input
+        r = self.do_set('mcc-mnc-apply', 'WRONG')
+        self.assertEquals(r['mtype'], 'ERROR')
+
+        r = self.do_set('mcc-mnc-apply', '1,')
+        self.assertEquals(r['mtype'], 'ERROR')
+
+        r = self.do_set('mcc-mnc-apply', '200,3')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        # Set it again
+        r = self.do_set('mcc-mnc-apply', '200,3')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Nothing changed')
+
+        # Change it
+        r = self.do_set('mcc-mnc-apply', '200,4')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        # Change it
+        r = self.do_set('mcc-mnc-apply', '201,4')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        # Verify
+        r = self.do_get('mnc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mnc')
+        self.assertEquals(r['value'], '04')
+
+        r = self.do_get('mcc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mcc')
+        self.assertEquals(r['value'], '201')
+
+        # Change it
+        r = self.do_set('mcc-mnc-apply', '202,03')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        r = self.do_get('mnc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mnc')
+        self.assertEquals(r['value'], '03')
+
+        r = self.do_get('mcc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mcc')
+        self.assertEquals(r['value'], '202')
+
+class TestCtrlNITB(TestCtrlBase):
+
+    def tearDown(self):
+        TestCtrlBase.tearDown(self)
+        os.unlink("test_hlr.sqlite3")
+
+    def ctrl_command(self):
+        return ["./src/osmo-nitb/osmo-nitb", "-c",
+                "doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg", "-l", "test_hlr.sqlite3"]
+
+    def ctrl_app(self):
+        return (4249, "./src/osmo-nitb/osmo-nitb", "OsmoBSC", "nitb")
+
+    def testNumberOfBTS(self):
+        r = self.do_get('number-of-bts')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'number-of-bts')
+        self.assertEquals(r['value'], '1')
+
+    def testSubscriberAddWithKi(self):
+        """Test that we can set the algorithm to none, xor, comp128v1"""
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,none')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,xor')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Value failed verification.')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,comp128v1,00112233445566778899AABBCCDDEEFF')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,comp128v2,00112233445566778899AABBCCDDEEFF')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,comp128v3,00112233445566778899AABBCCDDEEFF')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,none')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+    def testSubscriberAddRemove(self):
+        r = self.do_set('subscriber-modify-v1', '2620345,445566')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445567')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        # TODO. verify that the entry has been created and modified? Invoke
+        # the sqlite3 CLI or do it through the DB libraries?
+
+        r = self.do_set('subscriber-delete-v1', '2620345')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['value'], 'Removed')
+
+        r = self.do_set('subscriber-delete-v1', '2620345')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Failed to find subscriber')
+
+    def testSubscriberList(self):
+        # TODO. Add command to mark a subscriber as active
+        r = self.do_get('subscriber-list-active-v1')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-list-active-v1')
+        self.assertEquals(r['value'], None)
+
+    def testApplyConfiguration(self):
+        r = self.do_get('bts.0.apply-configuration')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Write Only attribute')
+
+        r = self.do_set('bts.0.apply-configuration', '1')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+    def testGprsMode(self):
+        r = self.do_get('bts.0.gprs-mode')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.gprs-mode')
+        self.assertEquals(r['value'], 'none')
+
+        r = self.do_set('bts.0.gprs-mode', 'bla')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Mode is not known')
+
+        r = self.do_set('bts.0.gprs-mode', 'egprs')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['value'], 'egprs')
+
+        r = self.do_get('bts.0.gprs-mode')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'bts.0.gprs-mode')
+        self.assertEquals(r['value'], 'egprs')
+
+class TestCtrlNAT(TestCtrlBase):
+
+    def ctrl_command(self):
+        return ["./src/osmo-bsc_nat/osmo-bsc_nat", "-c",
+                "doc/examples/osmo-bsc_nat/osmo-bsc-nat.cfg"]
+
+    def ctrl_app(self):
+        return (4250, "./src/osmo-bsc_nat/osmo-bsc_nat", "OsmoNAT", "nat")
+
+    def testAccessList(self):
+        r = self.do_get('net.0.bsc_cfg.0.access-list-name')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], None)
+
+        r = self.do_set('net.0.bsc_cfg.0.access-list-name', 'bla')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], 'bla')
+
+        r = self.do_get('net.0.bsc_cfg.0.access-list-name')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], 'bla')
+
+        r = self.do_set('net.0.bsc_cfg.0.no-access-list-name', '1')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], None)
+
+        r = self.do_get('net.0.bsc_cfg.0.access-list-name')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], None)
+
+    def testAccessListManagement(self):
+        r = self.do_set("net.0.add.allow.access-list.404", "abc")
+        self.assertEquals(r['mtype'], 'ERROR')
+
+        r = self.do_set("net.0.add.allow.access-list.bla", "^234$")
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'net.0.add.allow.access-list.bla')
+        self.assertEquals(r['value'], 'IMSI allow added to access list')
+
+        # TODO.. find a way to actually see if this rule has been
+        # added. e.g. by implementing a get for the list.
+
+def add_bsc_test(suite, workdir):
+    if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc/osmo-bsc-sccplite")):
+        print("Skipping the BSC test")
+        return
+    test = unittest.TestLoader().loadTestsFromTestCase(TestCtrlBSC)
+    suite.addTest(test)
+
+def add_nitb_test(suite, workdir):
+    test = unittest.TestLoader().loadTestsFromTestCase(TestCtrlNITB)
+    suite.addTest(test)
+
+def add_nat_test(suite, workdir):
+    if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc_nat/osmo-bsc_nat")):
+        print("Skipping the NAT test")
+        return
+    test = unittest.TestLoader().loadTestsFromTestCase(TestCtrlNAT)
+    suite.addTest(test)
+
+if __name__ == '__main__':
+    import argparse
+    import sys
+
+    workdir = '.'
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-v", "--verbose", dest="verbose",
+                        action="store_true", help="verbose mode")
+    parser.add_argument("-p", "--pythonconfpath", dest="p",
+                        help="searchpath for config")
+    parser.add_argument("-w", "--workdir", dest="w",
+                        help="Working directory")
+    args = parser.parse_args()
+
+    verbose_level = 1
+    if args.verbose:
+        verbose_level = 2
+        verbose = True
+
+    if args.w:
+        workdir = args.w
+
+    if args.p:
+        confpath = args.p
+
+    print "confpath %s, workdir %s" % (confpath, workdir)
+    os.chdir(workdir)
+    print "Running tests for specific control commands"
+    suite = unittest.TestSuite()
+    add_bsc_test(suite, workdir)
+    add_nitb_test(suite, workdir)
+    add_nat_test(suite, workdir)
+    res = unittest.TextTestRunner(verbosity=verbose_level).run(suite)
+    sys.exit(len(res.errors) + len(res.failures))
diff --git a/openbsc/tests/db/Makefile.am b/openbsc/tests/db/Makefile.am
new file mode 100644
index 0000000..7099645
--- /dev/null
+++ b/openbsc/tests/db/Makefile.am
@@ -0,0 +1,47 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	db_test.ok \
+	db_test.err \
+	hlr.sqlite3 \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	db_test \
+	$(NULL)
+
+db_test_SOURCES = \
+	db_test.c \
+	$(NULL)
+
+db_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBSMPP34_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	-ldbi \
+	$(NULL)
diff --git a/openbsc/tests/db/db_test.c b/openbsc/tests/db/db_test.c
new file mode 100644
index 0000000..755a6e9
--- /dev/null
+++ b/openbsc/tests/db/db_test.c
@@ -0,0 +1,256 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009-2016 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2014 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+
+#include <osmocom/core/application.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+static struct gsm_network dummy_net;
+static struct gsm_subscriber_group dummy_sgrp;
+
+#define SUBSCR_PUT(sub) \
+	sub->group = &dummy_sgrp;	\
+	subscr_put(sub);
+
+#define COMPARE(original, copy) \
+	if (original->id != copy->id) \
+		printf("Ids do not match in %s:%d %llu %llu\n", \
+			__FUNCTION__, __LINE__, original->id, copy->id); \
+	if (original->lac != copy->lac) \
+		printf("LAC do not match in %s:%d %d %d\n", \
+			__FUNCTION__, __LINE__, original->lac, copy->lac); \
+	if (original->authorized != copy->authorized) \
+		printf("Authorize do not match in %s:%d %d %d\n", \
+			__FUNCTION__, __LINE__, original->authorized, \
+			copy->authorized); \
+	if (strcmp(original->imsi, copy->imsi) != 0) \
+		printf("IMSIs do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->imsi, copy->imsi); \
+	if (original->tmsi != copy->tmsi) \
+		printf("TMSIs do not match in %s:%d '%u' '%u'\n", \
+			__FUNCTION__, __LINE__, original->tmsi, copy->tmsi); \
+	if (strcmp(original->name, copy->name) != 0) \
+		printf("names do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->name, copy->name); \
+	if (strcmp(original->extension, copy->extension) != 0) \
+		printf("Extensions do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->extension, copy->extension); \
+
+/*
+ * Create/Store a SMS and then try to load it.
+ */
+static void test_sms(void)
+{
+	int rc;
+	struct gsm_sms *sms;
+	struct gsm_subscriber *subscr;
+	subscr = db_get_subscriber(GSM_SUBSCRIBER_IMSI, "9993245423445");
+	OSMO_ASSERT(subscr);
+	subscr->group = &dummy_sgrp;
+
+	sms = sms_alloc();
+	sms->receiver = subscr_get(subscr);
+
+	sms->src.ton = 0x23;
+	sms->src.npi = 0x24;
+	memcpy(sms->src.addr, "1234", strlen("1234") + 1);
+
+	sms->dst.ton = 0x32;
+	sms->dst.npi = 0x42;
+	memcpy(sms->dst.addr, subscr->extension, sizeof(subscr->extension));
+
+	memcpy(sms->text, "Text123", strlen("Text123") + 1);
+	memcpy(sms->user_data, "UserData123", strlen("UserData123") + 1);
+	sms->user_data_len = strlen("UserData123");
+
+	/* random values */
+	sms->reply_path_req = 1;
+	sms->status_rep_req = 2;
+	sms->ud_hdr_ind = 3;
+	sms->protocol_id = 4;
+	sms->data_coding_scheme = 5;
+
+	rc = db_sms_store(sms);
+	sms_free(sms);
+	OSMO_ASSERT(rc == 0);
+
+	/* now query */
+	sms = db_sms_get_unsent_for_subscr(subscr);
+	OSMO_ASSERT(sms);
+	OSMO_ASSERT(sms->receiver == subscr);
+	OSMO_ASSERT(sms->reply_path_req == 1);
+	OSMO_ASSERT(sms->status_rep_req == 2);
+	OSMO_ASSERT(sms->ud_hdr_ind == 3);
+	OSMO_ASSERT(sms->protocol_id == 4);
+	OSMO_ASSERT(sms->data_coding_scheme == 5);
+	OSMO_ASSERT(sms->src.ton == 0x23);
+	OSMO_ASSERT(sms->src.npi == 0x24);
+	OSMO_ASSERT(sms->dst.ton == 0x32);
+	OSMO_ASSERT(sms->dst.npi == 0x42);
+	OSMO_ASSERT(strcmp((char *) sms->text, "Text123") == 0);
+	OSMO_ASSERT(sms->user_data_len == strlen("UserData123"));
+	OSMO_ASSERT(strcmp((char *) sms->user_data, "UserData123") == 0);
+
+	/* Mark the SMS as delivered */
+	db_sms_mark_delivered(sms);
+	sms_free(sms);
+
+	sms = db_sms_get_unsent_for_subscr(subscr);
+	OSMO_ASSERT(!sms);
+
+	subscr_put(subscr);
+}
+
+static void test_sms_migrate(void)
+{
+	struct gsm_subscriber *rcv_subscr;
+	struct gsm_sms *sms;
+	static const uint8_t user_data_1[] = {
+		0x41, 0xf1, 0xd8, 0x05, 0x22, 0x96, 0xcd, 0x2e,
+		0x90, 0xf1, 0xfd, 0x06, 0x00 };
+	static const uint8_t user_data_2[] = {
+		0x41, 0xf1, 0xd8, 0x05, 0x22, 0x96, 0xcd, 0x2e,
+		0xd0, 0xf1, 0xfd, 0x06, 0x00 };
+
+	rcv_subscr = db_get_subscriber(GSM_SUBSCRIBER_IMSI, "901010000001111");
+	rcv_subscr->group = &dummy_sgrp;
+
+	sms = db_sms_get(&dummy_net, 1);
+	OSMO_ASSERT(sms->id == 1);
+	OSMO_ASSERT(sms->receiver == rcv_subscr);
+	OSMO_ASSERT(strcmp(sms->text, "Abc. Def. Foo") == 0);
+	OSMO_ASSERT(sms->user_data_len == ARRAY_SIZE(user_data_1));
+	OSMO_ASSERT(memcmp(sms->user_data, user_data_1, ARRAY_SIZE(user_data_1)) == 0);
+	sms_free(sms);
+
+	sms = db_sms_get(&dummy_net, 2);
+	OSMO_ASSERT(sms->id == 2);
+	OSMO_ASSERT(sms->receiver == rcv_subscr);
+	OSMO_ASSERT(strcmp(sms->text, "Abc. Def. Goo") == 0);
+	OSMO_ASSERT(sms->user_data_len == ARRAY_SIZE(user_data_2));
+	OSMO_ASSERT(memcmp(sms->user_data, user_data_2, ARRAY_SIZE(user_data_2)) == 0);
+	sms_free(sms);
+
+	subscr_put(rcv_subscr);
+}
+
+static void test_subs(const char *imsi, char *imei1, char *imei2, bool make_ext)
+{
+	struct gsm_subscriber *alice = NULL, *alice_db;
+	char scratch_str[256];
+
+	alice = db_create_subscriber(imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN,
+				     make_ext);
+	db_subscriber_assoc_imei(alice, imei1);
+	if (imei2)
+		db_subscriber_assoc_imei(alice, imei2);
+	db_subscriber_alloc_tmsi(alice);
+	alice->lac=42;
+	db_sync_subscriber(alice);
+	/* Get by TMSI */
+	snprintf(scratch_str, sizeof(scratch_str), "%"PRIu32, alice->tmsi);
+	alice_db = db_get_subscriber(GSM_SUBSCRIBER_TMSI, scratch_str);
+	COMPARE(alice, alice_db);
+	SUBSCR_PUT(alice_db);
+	/* Get by IMSI */
+	alice_db = db_get_subscriber(GSM_SUBSCRIBER_IMSI, imsi);
+	COMPARE(alice, alice_db);
+	SUBSCR_PUT(alice_db);
+	/* Get by id */
+	snprintf(scratch_str, sizeof(scratch_str), "%llu", alice->id);
+	alice_db = db_get_subscriber(GSM_SUBSCRIBER_ID, scratch_str);
+	COMPARE(alice, alice_db);
+	SUBSCR_PUT(alice_db);
+	/* Get by extension */
+	alice_db = db_get_subscriber(GSM_SUBSCRIBER_EXTENSION, alice->extension);
+	if (alice_db) {
+		if (!make_ext)
+			printf("FAIL: bogus extension created for IMSI %s\n",
+			       imsi);
+		COMPARE(alice, alice_db);
+		SUBSCR_PUT(alice_db);
+	} else if (make_ext)
+		printf("FAIL: no subscriber extension for IMSI %s\n", imsi);
+	SUBSCR_PUT(alice);
+}
+
+int main()
+{
+	printf("Testing subscriber database code.\n");
+	osmo_init_logging(&log_info);
+	log_set_print_filename(osmo_stderr_target, 0);
+
+	dummy_net.subscr_group = &dummy_sgrp;
+	dummy_sgrp.net         = &dummy_net;
+
+	if (db_init("hlr.sqlite3")) {
+		printf("DB: Failed to init database. Please check the option settings.\n");
+		return 1;
+	}	 
+	printf("DB: Database initialized.\n");
+
+	if (db_prepare()) {
+		printf("DB: Failed to prepare database.\n");
+		return 1;
+	}
+	printf("DB: Database prepared.\n");
+
+	struct gsm_subscriber *alice = NULL;
+	struct gsm_subscriber *alice_db;
+
+	char *alice_imsi = "3243245432345";
+	alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN,
+				     true);
+	db_sync_subscriber(alice);
+	alice_db = db_get_subscriber(GSM_SUBSCRIBER_IMSI, alice->imsi);
+	COMPARE(alice, alice_db);
+	SUBSCR_PUT(alice_db);
+	SUBSCR_PUT(alice);
+
+	test_subs("3693245423445", "1234567890", NULL, true);
+	test_subs("9993245423445", "1234567890", "6543560920", true);
+	test_subs("3123122223445", "1234567890", NULL, false);
+	test_subs("9123121223445", "1234567890", "6543560920", false);
+
+	/* create it again and see it fails */
+	alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN,
+				     true);
+	OSMO_ASSERT(!alice);
+
+	test_sms();
+	test_sms_migrate();
+
+	db_fini();
+
+	printf("Done\n");
+	return 0;
+}
+
+/* stubs */
+void vty_out() {}
diff --git a/openbsc/tests/db/db_test.err b/openbsc/tests/db/db_test.err
new file mode 100644
index 0000000..27e5703
--- /dev/null
+++ b/openbsc/tests/db/db_test.err
@@ -0,0 +1,3 @@
+Going to migrate from revision 3
+Going to migrate from revision 4
+
\ No newline at end of file
diff --git a/openbsc/tests/db/db_test.ok b/openbsc/tests/db/db_test.ok
new file mode 100644
index 0000000..2632a8c
--- /dev/null
+++ b/openbsc/tests/db/db_test.ok
@@ -0,0 +1,4 @@
+Testing subscriber database code.
+DB: Database initialized.
+DB: Database prepared.
+Done
diff --git a/openbsc/tests/db/hlr.sqlite3 b/openbsc/tests/db/hlr.sqlite3
new file mode 100644
index 0000000..e59dcdc
--- /dev/null
+++ b/openbsc/tests/db/hlr.sqlite3
Binary files differ
diff --git a/openbsc/tests/gsm0408/Makefile.am b/openbsc/tests/gsm0408/Makefile.am
new file mode 100644
index 0000000..ae81c2c
--- /dev/null
+++ b/openbsc/tests/gsm0408/Makefile.am
@@ -0,0 +1,34 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	gsm0408_test \
+	$(NULL)
+
+EXTRA_DIST = \
+	gsm0408_test.ok \
+	$(NULL)
+
+gsm0408_test_SOURCES = \
+	gsm0408_test.c \
+	$(NULL)
+
+gsm0408_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	-ldbi \
+	$(NULL)
diff --git a/openbsc/tests/gsm0408/gsm0408_test.c b/openbsc/tests/gsm0408/gsm0408_test.c
new file mode 100644
index 0000000..6f48599
--- /dev/null
+++ b/openbsc/tests/gsm0408/gsm0408_test.c
@@ -0,0 +1,763 @@
+/* simple test for the gsm0408 formatting functions */
+/*
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <arpa/inet.h>
+
+#include <openbsc/common_bsc.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_data_shared.h>
+#include <openbsc/debug.h>
+#include <openbsc/arfcn_range_encode.h>
+#include <openbsc/system_information.h>
+#include <openbsc/abis_rsl.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/gsm/sysinfo.h>
+
+#define COMPARE(result, op, value) \
+    if (!((result) op (value))) {\
+	fprintf(stderr, "Compare failed. Was %x should be %x in %s:%d\n",result, value, __FILE__, __LINE__); \
+	exit(-1); \
+    }
+
+#define COMPARE_STR(result, value) \
+	if (strcmp(result, value) != 0) { \
+		fprintf(stderr, "Compare failed. Was %s should be %s in %s:%d\n",result, value, __FILE__, __LINE__); \
+		exit(-1); \
+	}
+
+#define DBG(...)
+
+#define VERIFY(res, cmp, wanted)					\
+	if (!(res cmp wanted)) {					\
+		printf("ASSERT failed: %s:%d Wanted: %d %s %d\n",	\
+			__FILE__, __LINE__, (int) res, # cmp, (int) wanted);	\
+	}
+
+
+
+/*
+ * Test Location Area Identifier formatting. Table 10.5.3 of 04.08
+ */
+static void test_location_area_identifier(void)
+{
+    struct gsm48_loc_area_id lai48;
+
+    printf("Testing test location area identifier\n");
+
+    /*
+     * Test the default/test setup. Coming from
+     * bsc_hack.c dumps
+     */
+    gsm48_generate_lai(&lai48, 1, 1, 1);
+    COMPARE(lai48.digits[0], ==, 0x00);
+    COMPARE(lai48.digits[1], ==, 0xF1);
+    COMPARE(lai48.digits[2], ==, 0x10);
+    COMPARE(lai48.lac, ==, htons(0x0001));
+
+    gsm48_generate_lai(&lai48, 602, 1, 15);
+    COMPARE(lai48.digits[0], ==, 0x06);
+    COMPARE(lai48.digits[1], ==, 0xF2);
+    COMPARE(lai48.digits[2], ==, 0x10);
+    COMPARE(lai48.lac, ==, htons(0x000f));
+}
+
+static inline void gen(struct gsm_bts *bts, const char *s)
+{
+	int r;
+
+	bts->si_valid = 0;
+	bts->si_valid |= (1 << SYSINFO_TYPE_2quater);
+
+	printf("generating SI2quater for %zu EARFCNs and %zu UARFCNs...\n",
+	       si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length);
+
+	r = gsm_generate_si(bts, SYSINFO_TYPE_2quater);
+	if (r > 0)
+		for (bts->si2q_index = 0; bts->si2q_index < bts->si2q_count + 1; bts->si2q_index++)
+			printf("generated %s SI2quater [%02u/%02u]: [%d] %s\n",
+			       GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) ? "valid" : "invalid",
+			       bts->si2q_index, bts->si2q_count, r,
+			       osmo_hexdump((void *)GSM_BTS_SI2Q(bts, bts->si2q_index), GSM_MACBLOCK_LEN));
+	else
+		printf("%s() failed to generate SI2quater: %s\n", s, strerror(-r));
+}
+
+static inline void del_earfcn_b(struct gsm_bts *bts, uint16_t earfcn)
+{
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	int r = osmo_earfcn_del(e, earfcn);
+	if (r)
+		printf("failed to remove EARFCN %u: %s\n", earfcn, strerror(-r));
+	else
+		printf("removed EARFCN %u - ", earfcn);
+
+	gen(bts, __func__);
+}
+
+static inline void add_earfcn_b(struct gsm_bts *bts, uint16_t earfcn, uint8_t bw)
+{
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	int r = osmo_earfcn_add(e, earfcn, bw);
+	if (r)
+		printf("failed to add EARFCN %u: %s\n", earfcn, strerror(-r));
+	else
+		printf("added EARFCN %u - ", earfcn);
+
+	gen(bts, __func__);
+}
+
+static inline void _bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity)
+{
+	int r;
+
+	bts->u_offset = 0;
+
+	r = bts_uarfcn_add(bts, arfcn, scramble, diversity);
+	if (r < 0)
+		printf("failed to add UARFCN to SI2quater: %s\n", strerror(-r));
+	else {
+		bts->si2q_count = si2q_num(bts) - 1;
+		gen(bts, __func__);
+	}
+}
+
+static inline struct gsm_bts *bts_init(void *ctx, struct gsm_network *net, const char *msg)
+{
+	struct gsm_bts *bts = gsm_bts_alloc(net, 0);
+	if (!bts) {
+		printf("BTS allocation failure in %s()\n", msg);
+		exit(1);
+	}
+	printf("BTS allocation OK in %s()\n", msg);
+
+	bts->network = net;
+
+	return bts;
+}
+
+static inline void test_si2q_segfault(struct gsm_network *net)
+{
+	struct gsm_bts *bts = bts_init(tall_bsc_ctx, net, __func__);
+	printf("Test SI2quater UARFCN (same scrambling code and diversity):\n");
+
+	_bts_uarfcn_add(bts, 10564, 319, 0);
+	_bts_uarfcn_add(bts, 10612, 319, 0);
+	gen(bts, __func__);
+
+	talloc_free(bts);
+}
+
+static inline void test_si2q_mu(struct gsm_network *net)
+{
+	struct gsm_bts *bts = bts_init(tall_bsc_ctx, net, __func__);
+	printf("Test SI2quater multiple UARFCNs:\n");
+
+	_bts_uarfcn_add(bts, 10564, 318, 0);
+	_bts_uarfcn_add(bts, 10612, 319, 0);
+	_bts_uarfcn_add(bts, 10612, 31, 0);
+	_bts_uarfcn_add(bts, 10612, 19, 0);
+	_bts_uarfcn_add(bts, 10613, 64, 0);
+	_bts_uarfcn_add(bts, 10613, 164, 0);
+	_bts_uarfcn_add(bts, 10613, 14, 0);
+
+	talloc_free(bts);
+}
+
+static inline void test_si2q_u(struct gsm_network *net)
+{
+	struct gsm_bts *bts = bts_init(tall_bsc_ctx, net, __func__);
+	printf("Testing SYSINFO_TYPE_2quater UARFCN generation:\n");
+
+	/* first generate invalid SI as no UARFCN added */
+	gen(bts, __func__);
+
+	/* subsequent calls should produce valid SI if there's enough memory */
+	_bts_uarfcn_add(bts, 1982, 13, 1);
+	_bts_uarfcn_add(bts, 1982, 44, 0);
+	_bts_uarfcn_add(bts, 1982, 61, 1);
+	_bts_uarfcn_add(bts, 1982, 89, 1);
+	_bts_uarfcn_add(bts, 1982, 113, 0);
+	_bts_uarfcn_add(bts, 1982, 123, 0);
+	_bts_uarfcn_add(bts, 1982, 56, 1);
+	_bts_uarfcn_add(bts, 1982, 72, 1);
+	_bts_uarfcn_add(bts, 1982, 223, 1);
+	_bts_uarfcn_add(bts, 1982, 14, 0);
+	_bts_uarfcn_add(bts, 1982, 88, 0);
+
+	talloc_free(bts);
+}
+
+static inline void test_si2q_e(struct gsm_network *net)
+{
+	struct gsm_bts *bts = bts_init(tall_bsc_ctx, net, __func__);
+	printf("Testing SYSINFO_TYPE_2quater EARFCN generation:\n");
+
+	bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
+	bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
+	bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+	bts->si_common.si2quater_neigh_list.thresh_hi = 5;
+
+	osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+
+	/* first generate invalid SI as no EARFCN added */
+	gen(bts, __func__);
+
+	/* subsequent calls should produce valid SI if there's enough memory and EARFCNs */
+	add_earfcn_b(bts, 1917, 5);
+	del_earfcn_b(bts, 1917);
+	add_earfcn_b(bts, 1917, 1);
+	add_earfcn_b(bts, 1932, OSMO_EARFCN_MEAS_INVALID);
+	add_earfcn_b(bts, 1937, 2);
+	add_earfcn_b(bts, 1945, OSMO_EARFCN_MEAS_INVALID);
+	add_earfcn_b(bts, 1965, OSMO_EARFCN_MEAS_INVALID);
+	add_earfcn_b(bts, 1967, 4);
+	add_earfcn_b(bts, 1982, 3);
+
+	talloc_free(bts);
+}
+
+static inline void test_si2q_long(struct gsm_network *net)
+{
+	struct gsm_bts *bts = bts_init(tall_bsc_ctx, net, __func__);
+	printf("Testing SYSINFO_TYPE_2quater combined EARFCN & UARFCN generation:\n");
+
+	bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
+	bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
+	bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+	bts->si_common.si2quater_neigh_list.thresh_hi = 5;
+
+	osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+
+	bts_earfcn_add(bts, 1922, 11, 22, 8,32, 8);
+	bts_earfcn_add(bts, 1922, 11, 22, 8, 32, 8);
+	bts_earfcn_add(bts, 1924, 11, 12, 6, 11, 5);
+	bts_earfcn_add(bts, 1923, 11, 12, 6, 11, 5);
+	bts_earfcn_add(bts, 1925, 11, 12, 6, 11, 5);
+	bts_earfcn_add(bts, 2111, 11, 12, 6, 11, 5);
+	bts_earfcn_add(bts, 2112, 11, 12, 6, 11, 4);
+	bts_earfcn_add(bts, 2113, 11, 12, 6, 11, 3);
+	bts_earfcn_add(bts, 2114, 11, 12, 6, 11, 2);
+	bts_earfcn_add(bts, 2131, 11, 12, 6, 11, 5);
+	bts_earfcn_add(bts, 2132, 11, 12, 6, 11, 4);
+	bts_earfcn_add(bts, 2133, 11, 12, 6, 11, 3);
+	bts_earfcn_add(bts, 2134, 11, 12, 6, 11, 2);
+	bts_earfcn_add(bts, 2121, 11, 12, 6, 11, 5);
+	bts_earfcn_add(bts, 2122, 11, 12, 6, 11, 4);
+	bts_earfcn_add(bts, 2123, 11, 12, 6, 11, 3);
+	bts_earfcn_add(bts, 2124, 11, 12, 6, 11, 2);
+	_bts_uarfcn_add(bts, 1976, 13, 1);
+	_bts_uarfcn_add(bts, 1976, 38, 1);
+	_bts_uarfcn_add(bts, 1976, 44, 1);
+	_bts_uarfcn_add(bts, 1976, 120, 1);
+	_bts_uarfcn_add(bts, 1976, 140, 1);
+	_bts_uarfcn_add(bts, 1976, 163, 1);
+	_bts_uarfcn_add(bts, 1976, 166, 1);
+	_bts_uarfcn_add(bts, 1976, 217, 1);
+	_bts_uarfcn_add(bts, 1976, 224, 1);
+	_bts_uarfcn_add(bts, 1976, 225, 1);
+	_bts_uarfcn_add(bts, 1976, 226, 1);
+
+	talloc_free(bts);
+}
+
+static void test_mi_functionality(void)
+{
+	const char *imsi_odd  = "987654321098763";
+	const char *imsi_even = "9876543210987654";
+	const uint32_t tmsi = 0xfabeacd0;
+	uint8_t mi[128];
+	unsigned int mi_len;
+	char mi_parsed[GSM48_MI_SIZE];
+
+	printf("Testing parsing and generating TMSI/IMSI\n");
+
+	/* tmsi code */
+	mi_len = gsm48_generate_mid_from_tmsi(mi, tmsi);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len - 2);
+	COMPARE((uint32_t)strtoul(mi_parsed, NULL, 10), ==, tmsi);
+
+	/* imsi code */
+	mi_len = gsm48_generate_mid_from_imsi(mi, imsi_odd);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+	printf("hex: %s\n", osmo_hexdump(mi, mi_len));
+	COMPARE_STR(mi_parsed, imsi_odd);
+
+	mi_len = gsm48_generate_mid_from_imsi(mi, imsi_even);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+	printf("hex: %s\n", osmo_hexdump(mi, mi_len));
+	COMPARE_STR(mi_parsed, imsi_even);
+}
+
+struct {
+	int range;
+	int arfcns_num;
+	int arfcns[RANGE_ENC_MAX_ARFCNS];
+} arfcn_test_ranges[] = {
+	{ARFCN_RANGE_512, 12,
+		{ 1, 12, 31, 51, 57, 91, 97, 98, 113, 117, 120, 125 }},
+	{ARFCN_RANGE_512, 17,
+		{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }},
+	{ARFCN_RANGE_512, 18,
+		{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }},
+	{ARFCN_RANGE_512, 18,
+		{ 1, 17, 31, 45, 58, 79, 81, 97,
+		  113, 127, 213, 277, 287, 311, 331, 391,
+		  417, 511 }},
+	{ARFCN_RANGE_512, 6,
+		{ 1, 17, 31, 45, 58, 79 }},
+	{ARFCN_RANGE_512, 6,
+		{ 10, 17, 31, 45, 58, 79 }},
+	{ARFCN_RANGE_1024, 17,
+		{ 0, 17, 31, 45, 58, 79, 81, 97,
+		  113, 127, 213, 277, 287, 311, 331, 391,
+		  1023 }},
+	{ARFCN_RANGE_1024, 16,
+		{ 17, 31, 45, 58, 79, 81, 97, 113,
+		  127, 213, 277, 287, 311, 331, 391, 1023 }},
+	{-1}
+};
+
+static int test_single_range_encoding(int range, const int *orig_arfcns,
+				       int arfcns_num, int silent)
+{
+	int arfcns[RANGE_ENC_MAX_ARFCNS];
+	int w[RANGE_ENC_MAX_ARFCNS];
+	int f0_included = 0;
+	int rc, f0;
+	uint8_t chan_list[16] = {0};
+	struct gsm_sysinfo_freq dec_freq[1024] = {{0}};
+	int dec_arfcns[RANGE_ENC_MAX_ARFCNS] = {0};
+	int dec_arfcns_count = 0;
+	int arfcns_used = 0;
+	int i;
+
+	arfcns_used = arfcns_num;
+	memmove(arfcns, orig_arfcns, sizeof(arfcns));
+
+	f0 = range == ARFCN_RANGE_1024 ? 0 : arfcns[0];
+	/*
+	 * Manipulate the ARFCN list according to the rules in J4 depending
+	 * on the selected range.
+	 */
+	arfcns_used = range_enc_filter_arfcns(arfcns, arfcns_used,
+					      f0, &f0_included);
+
+	memset(w, 0, sizeof(w));
+	range_enc_arfcns(range, arfcns, arfcns_used, w, 0);
+
+	if (!silent)
+		fprintf(stderr, "range=%d, arfcns_used=%d, f0=%d, f0_included=%d\n",
+			range, arfcns_used, f0, f0_included);
+
+	/* Select the range and the amount of bits needed */
+	switch (range) {
+	case ARFCN_RANGE_128:
+		range_enc_range128(chan_list, f0, w);
+		break;
+	case ARFCN_RANGE_256:
+		range_enc_range256(chan_list, f0, w);
+		break;
+	case ARFCN_RANGE_512:
+		range_enc_range512(chan_list, f0, w);
+		break;
+	case ARFCN_RANGE_1024:
+		range_enc_range1024(chan_list, f0, f0_included, w);
+		break;
+	default:
+		return 1;
+	};
+
+	if (!silent)
+		printf("chan_list = %s\n",
+		       osmo_hexdump(chan_list, sizeof(chan_list)));
+
+	rc = gsm48_decode_freq_list(dec_freq, chan_list, sizeof(chan_list),
+				    0xfe, 1);
+	if (rc != 0) {
+		printf("Cannot decode freq list, rc = %d\n", rc);
+		return 1;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dec_freq); i++) {
+		if (dec_freq[i].mask &&
+		    dec_arfcns_count < ARRAY_SIZE(dec_arfcns))
+			dec_arfcns[dec_arfcns_count++] = i;
+	}
+
+	if (!silent) {
+		printf("Decoded freqs %d (expected %d)\n",
+		       dec_arfcns_count, arfcns_num);
+		printf("Decoded: ");
+		for (i = 0; i < dec_arfcns_count; i++) {
+			printf("%d ", dec_arfcns[i]);
+			if (dec_arfcns[i] != orig_arfcns[i])
+				printf("(!= %d) ", orig_arfcns[i]);
+		}
+		printf("\n");
+	}
+
+	if (dec_arfcns_count != arfcns_num) {
+		printf("Wrong number of arfcns\n");
+		return 1;
+	}
+
+	if (memcmp(dec_arfcns, orig_arfcns, sizeof(dec_arfcns)) != 0) {
+		printf("Decoding error, got wrong freqs\n");
+		fprintf(stderr, " w = ");
+		for (i = 0; i < ARRAY_SIZE(w); i++)
+			fprintf(stderr, "%d ", w[i]);
+		fprintf(stderr, "\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static void test_random_range_encoding(int range, int max_arfcn_num)
+{
+	int arfcns_num = 0;
+	int test_idx;
+	int rc, max_count;
+	int num_tests = 1024;
+
+	printf("Random range test: range %d, max num ARFCNs %d\n",
+	       range, max_arfcn_num);
+
+	srandom(1);
+
+	for (max_count = 1; max_count < max_arfcn_num; max_count++) {
+		for (test_idx = 0; test_idx < num_tests; test_idx++) {
+			int count;
+			int i;
+			int min_freq = 0;
+
+			int rnd_arfcns[RANGE_ENC_MAX_ARFCNS] = {0};
+			char rnd_arfcns_set[1024] = {0};
+
+			if (range < ARFCN_RANGE_1024)
+				min_freq = random() % (1023 - range);
+
+			for (count = max_count; count; ) {
+				int arfcn = min_freq + random() % (range + 1);
+				OSMO_ASSERT(arfcn < ARRAY_SIZE(rnd_arfcns_set));
+
+				if (!rnd_arfcns_set[arfcn]) {
+					rnd_arfcns_set[arfcn] = 1;
+					count -= 1;
+				}
+			}
+
+			arfcns_num = 0;
+			for (i = 0; i < ARRAY_SIZE(rnd_arfcns_set); i++)
+				if (rnd_arfcns_set[i])
+					rnd_arfcns[arfcns_num++] = i;
+
+			rc = test_single_range_encoding(range, rnd_arfcns,
+							arfcns_num, 1);
+			if (rc != 0) {
+				printf("Failed on test %d, range %d, num ARFCNs %d\n",
+				       test_idx, range, max_count);
+				test_single_range_encoding(range, rnd_arfcns,
+							   arfcns_num, 0);
+				return;
+			}
+		}
+	}
+}
+
+static void test_range_encoding()
+{
+	int *arfcns;
+	int arfcns_num = 0;
+	int test_idx;
+	int range;
+
+	for (test_idx = 0; arfcn_test_ranges[test_idx].arfcns_num > 0; test_idx++)
+	{
+		arfcns_num = arfcn_test_ranges[test_idx].arfcns_num;
+		arfcns = &arfcn_test_ranges[test_idx].arfcns[0];
+		range = arfcn_test_ranges[test_idx].range;
+
+		printf("Range test %d: range %d, num ARFCNs %d\n",
+		       test_idx, range, arfcns_num);
+
+		test_single_range_encoding(range, arfcns, arfcns_num, 0);
+	}
+
+	test_random_range_encoding(ARFCN_RANGE_128, 29);
+	test_random_range_encoding(ARFCN_RANGE_256, 22);
+	test_random_range_encoding(ARFCN_RANGE_512, 18);
+	test_random_range_encoding(ARFCN_RANGE_1024, 16);
+}
+
+static int freqs1[] = {
+	12, 70, 121, 190, 250, 320, 401, 475, 520, 574, 634, 700, 764, 830, 905, 980
+};
+
+static int freqs2[] = {
+	402, 460, 1, 67, 131, 197, 272, 347,
+};
+
+static int freqs3[] = {
+	68, 128, 198, 279, 353, 398, 452,
+
+};
+
+static int w_out[] = {
+	122, 2, 69, 204, 75, 66, 60, 70, 83, 3, 24, 67, 54, 64, 70, 9,
+};
+
+static int range128[] = {
+	1, 1 + 127,
+};
+
+static int range256[] = {
+	1, 1 + 128,
+};
+
+static int range512[] = {
+	1, 1+ 511,
+};
+
+
+static void test_arfcn_filter()
+{
+	int arfcns[50], i, res, f0_included;
+	for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
+		arfcns[i] = (i + 1) * 2;
+
+	/* check that the arfcn is taken out. f0_included is only set for Range1024 */
+	f0_included = 24;
+	res = range_enc_filter_arfcns(arfcns, ARRAY_SIZE(arfcns),
+			arfcns[0], &f0_included);
+	VERIFY(res, ==, ARRAY_SIZE(arfcns) - 1);
+	VERIFY(f0_included, ==, 1);
+	for (i = 0; i < res; ++i)
+		VERIFY(arfcns[i], ==, ((i+2) * 2) - (2+1));
+
+	/* check with range1024, ARFCN 0 is included */
+	for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
+		arfcns[i] = i * 2;
+	res = range_enc_filter_arfcns(arfcns, ARRAY_SIZE(arfcns),
+			0, &f0_included);
+	VERIFY(res, ==, ARRAY_SIZE(arfcns) - 1);
+	VERIFY(f0_included, ==, 1);
+	for (i = 0; i < res; ++i)
+		VERIFY(arfcns[i], ==, (i + 1) * 2 - 1);
+
+	/* check with range1024, ARFCN 0 not included */
+	for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
+		arfcns[i] = (i + 1) * 2;
+	res = range_enc_filter_arfcns(arfcns, ARRAY_SIZE(arfcns),
+			0, &f0_included);
+	VERIFY(res, ==, ARRAY_SIZE(arfcns));
+	VERIFY(f0_included, ==, 0);
+	for (i = 0; i < res; ++i)
+		VERIFY(arfcns[i], ==, ((i + 1) * 2) - 1);
+}
+
+static void test_print_encoding()
+{
+	int rc;
+	int w[17];
+	uint8_t chan_list[16];
+	memset(chan_list, 0x23, sizeof(chan_list));
+
+	for (rc = 0; rc < ARRAY_SIZE(w); ++rc)
+		switch (rc % 3) {
+		case 0:
+			w[rc] = 0xAAAA;
+			break;
+		case 1:
+			w[rc] = 0x5555;
+			break;
+		case 2:
+			w[rc] = 0x9696;
+			break;
+		}
+
+	range_enc_range512(chan_list, (1 << 9) | 0x96, w);
+
+	printf("Range512: %s\n", osmo_hexdump(chan_list, ARRAY_SIZE(chan_list)));
+}
+
+static void test_si_range_helpers()
+{
+	int ws[(sizeof(freqs1)/sizeof(freqs1[0]))];
+	int i, f0 = 0xFFFFFF;
+
+	memset(&ws[0], 0x23, sizeof(ws));
+
+	i = range_enc_find_index(1023, freqs1, ARRAY_SIZE(freqs1));
+	printf("Element is: %d => freqs[i] = %d\n", i, i >= 0 ? freqs1[i] : -1);
+	VERIFY(i, ==, 2);
+
+	i = range_enc_find_index(511, freqs2, ARRAY_SIZE(freqs2));
+	printf("Element is: %d => freqs[i] = %d\n", i,  i >= 0 ? freqs2[i] : -1);
+	VERIFY(i, ==, 2);
+
+	i = range_enc_find_index(511, freqs3, ARRAY_SIZE(freqs3));
+	printf("Element is: %d => freqs[i] = %d\n", i,  i >= 0 ? freqs3[i] : -1);
+	VERIFY(i, ==, 0);
+
+	range_enc_arfcns(1023, freqs1, ARRAY_SIZE(freqs1), ws, 0);
+
+	for (i = 0; i < sizeof(freqs1)/sizeof(freqs1[0]); ++i) {
+		printf("w[%d]=%d\n", i, ws[i]);
+		VERIFY(ws[i], ==, w_out[i]);
+	}
+
+	i = range_enc_determine_range(range128, ARRAY_SIZE(range128), &f0);
+	VERIFY(i, ==, ARFCN_RANGE_128);
+	VERIFY(f0, ==, 1);
+
+	i = range_enc_determine_range(range256, ARRAY_SIZE(range256), &f0);
+	VERIFY(i, ==, ARFCN_RANGE_256);
+	VERIFY(f0, ==, 1);
+
+	i = range_enc_determine_range(range512, ARRAY_SIZE(range512), &f0);
+	VERIFY(i, ==, ARFCN_RANGE_512);
+	VERIFY(f0, ==, 1);
+}
+
+static void test_gsm411_rp_ref_wrap(void)
+{
+	struct gsm_subscriber_connection conn;
+	int res;
+
+	printf("testing RP-Reference wrap\n");
+
+	memset(&conn, 0, sizeof(conn));
+	conn.next_rp_ref = 255;
+
+	res = sms_next_rp_msg_ref(&conn.next_rp_ref);
+	printf("Allocated reference: %d\n", res);
+	OSMO_ASSERT(res == 255);
+
+	res = sms_next_rp_msg_ref(&conn.next_rp_ref);
+	printf("Allocated reference: %d\n", res);
+	OSMO_ASSERT(res == 0);
+
+	res = sms_next_rp_msg_ref(&conn.next_rp_ref);
+	printf("Allocated reference: %d\n", res);
+	OSMO_ASSERT(res == 1);
+}
+
+static void test_si_ba_ind(struct gsm_network *net)
+{
+	struct gsm_bts *bts = bts_init(tall_bsc_ctx, net, __func__);
+
+	const struct gsm48_system_information_type_2 *si2 =
+		(struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+	const struct gsm48_system_information_type_2bis *si2bis =
+		(struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, SYSINFO_TYPE_2bis);
+	const struct gsm48_system_information_type_2ter *si2ter =
+		(struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, SYSINFO_TYPE_2ter);
+	const struct gsm48_system_information_type_5 *si5 =
+		(struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5);
+	const struct gsm48_system_information_type_5bis *si5bis =
+		(struct gsm48_system_information_type_5bis *) GSM_BTS_SI(bts, SYSINFO_TYPE_5bis);
+	const struct gsm48_system_information_type_5ter *si5ter =
+		(struct gsm48_system_information_type_5ter *) GSM_BTS_SI(bts, SYSINFO_TYPE_5ter);
+
+	int rc;
+
+	bts->c0->arfcn = 23;
+
+	printf("Testing if BA-IND is set as expected in SI2xxx and SI5xxx\n");
+
+	rc = gsm_generate_si(bts, SYSINFO_TYPE_2);
+	OSMO_ASSERT(rc > 0);
+	printf("SI2: %s\n", osmo_hexdump((uint8_t *)si2, rc));
+	/* Validate BA-IND == 0 */
+	OSMO_ASSERT(!(si2->bcch_frequency_list[0] & 0x10));
+
+	rc = gsm_generate_si(bts, SYSINFO_TYPE_2bis);
+	OSMO_ASSERT(rc > 0);
+	printf("SI2bis: %s\n", osmo_hexdump((uint8_t *)si2bis, rc));
+	/* Validate BA-IND == 0 */
+	OSMO_ASSERT(!(si2bis->bcch_frequency_list[0] & 0x10));
+
+	rc = gsm_generate_si(bts, SYSINFO_TYPE_2ter);
+	OSMO_ASSERT(rc > 0);
+	printf("SI2ter: %s\n", osmo_hexdump((uint8_t *)si2ter, rc));
+	/* Validate BA-IND == 0 */
+	OSMO_ASSERT(!(si2ter->ext_bcch_frequency_list[0] & 0x10));
+
+	rc = gsm_generate_si(bts, SYSINFO_TYPE_5);
+	OSMO_ASSERT(rc > 0);
+	printf("SI5: %s\n", osmo_hexdump((uint8_t *)si5, rc));
+	/* Validate BA-IND == 1 */
+	OSMO_ASSERT(si5->bcch_frequency_list[0] & 0x10);
+
+	rc = gsm_generate_si(bts, SYSINFO_TYPE_5bis);
+	OSMO_ASSERT(rc > 0);
+	printf("SI5bis: %s\n", osmo_hexdump((uint8_t *)si5bis, rc));
+	/* Validate BA-IND == 1 */
+	OSMO_ASSERT(si5bis->bcch_frequency_list[0] & 0x10);
+
+	rc = gsm_generate_si(bts, SYSINFO_TYPE_5ter);
+	OSMO_ASSERT(rc > 0);
+	printf("SI5ter: %s\n", osmo_hexdump((uint8_t *)si5ter, rc));
+	/* Validate BA-IND == 1 */
+	OSMO_ASSERT(si5ter->bcch_frequency_list[0] & 0x10);
+}
+
+int main(int argc, char **argv)
+{
+	struct gsm_network *net;
+
+	osmo_init_logging(&log_info);
+	log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+	net = bsc_network_init(tall_bsc_ctx, 1, 1, NULL);
+	if (!net) {
+		printf("Network init failure.\n");
+		return EXIT_FAILURE;
+	}
+
+	test_location_area_identifier();
+	test_mi_functionality();
+
+	test_si_range_helpers();
+	test_arfcn_filter();
+	test_print_encoding();
+	test_range_encoding();
+	test_gsm411_rp_ref_wrap();
+
+	test_si2q_segfault(net);
+	test_si2q_e(net);
+	test_si2q_u(net);
+	test_si2q_mu(net);
+	test_si2q_long(net);
+
+	test_si_ba_ind(net);
+
+	printf("Done.\n");
+
+	return EXIT_SUCCESS;
+}
diff --git a/openbsc/tests/gsm0408/gsm0408_test.ok b/openbsc/tests/gsm0408/gsm0408_test.ok
new file mode 100644
index 0000000..1039883
--- /dev/null
+++ b/openbsc/tests/gsm0408/gsm0408_test.ok
@@ -0,0 +1,217 @@
+Testing test location area identifier
+Testing parsing and generating TMSI/IMSI
+hex: 17 08 99 78 56 34 12 90 78 36 
+hex: 17 09 91 78 56 34 12 90 78 56 f4 
+Element is: 2 => freqs[i] = 121
+Element is: 2 => freqs[i] = 1
+Element is: 0 => freqs[i] = 68
+w[0]=122
+w[1]=2
+w[2]=69
+w[3]=204
+w[4]=75
+w[5]=66
+w[6]=60
+w[7]=70
+w[8]=83
+w[9]=3
+w[10]=24
+w[11]=67
+w[12]=54
+w[13]=64
+w[14]=70
+w[15]=9
+Range512: 89 4b 2a 95 65 95 55 2c a9 55 aa 55 6a 95 59 55 
+Range test 0: range 511, num ARFCNs 12
+chan_list = 88 00 98 34 85 36 7c 50 22 dc 5e ec 00 00 00 00 
+Decoded freqs 12 (expected 12)
+Decoded: 1 12 31 51 57 91 97 98 113 117 120 125 
+Range test 1: range 511, num ARFCNs 17
+chan_list = 88 00 82 7f 01 3f 7e 04 0b ff ff fc 10 41 07 e0 
+Decoded freqs 17 (expected 17)
+Decoded: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
+Range test 2: range 511, num ARFCNs 18
+chan_list = 88 00 82 7f 01 7f 7e 04 0b ff ff fc 10 41 07 ff 
+Decoded freqs 18 (expected 18)
+Decoded: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 
+Range test 3: range 511, num ARFCNs 18
+chan_list = 88 00 94 3a 44 32 d7 2a 43 2a 13 94 e5 38 39 f6 
+Decoded freqs 18 (expected 18)
+Decoded: 1 17 31 45 58 79 81 97 113 127 213 277 287 311 331 391 417 511 
+Range test 4: range 511, num ARFCNs 6
+chan_list = 88 00 8b 3c 88 b9 6b 00 00 00 00 00 00 00 00 00 
+Decoded freqs 6 (expected 6)
+Decoded: 1 17 31 45 58 79 
+Range test 5: range 511, num ARFCNs 6
+chan_list = 88 05 08 fc 88 b9 6b 00 00 00 00 00 00 00 00 00 
+Decoded freqs 6 (expected 6)
+Decoded: 10 17 31 45 58 79 
+Range test 6: range 1023, num ARFCNs 17
+chan_list = 84 71 e4 ab b9 58 05 cb 39 17 fd b0 75 62 0f 2f 
+Decoded freqs 17 (expected 17)
+Decoded: 0 17 31 45 58 79 81 97 113 127 213 277 287 311 331 391 1023 
+Range test 7: range 1023, num ARFCNs 16
+chan_list = 80 71 e4 ab b9 58 05 cb 39 17 fd b0 75 62 0f 2f 
+Decoded freqs 16 (expected 16)
+Decoded: 17 31 45 58 79 81 97 113 127 213 277 287 311 331 391 1023 
+Random range test: range 127, max num ARFCNs 29
+Random range test: range 255, max num ARFCNs 22
+Random range test: range 511, max num ARFCNs 18
+Random range test: range 1023, max num ARFCNs 16
+testing RP-Reference wrap
+Allocated reference: 255
+Allocated reference: 0
+Allocated reference: 1
+BTS allocation OK in test_si2q_segfault()
+Test SI2quater UARFCN (same scrambling code and diversity):
+generating SI2quater for 0 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 e8 0a 7f 52 88 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 e8 0a 7f 52 88 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b 
+BTS allocation OK in test_si2q_e()
+Testing SYSINFO_TYPE_2quater EARFCN generation:
+generating SI2quater for 0 EARFCNs and 0 UARFCNs...
+generated invalid SI2quater [00/00]: [23] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+added EARFCN 1917 - generating SI2quater for 1 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be e8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+removed EARFCN 1917 - generating SI2quater for 0 EARFCNs and 0 UARFCNs...
+generated invalid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be e8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+added EARFCN 1917 - generating SI2quater for 1 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be c8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+added EARFCN 1932 - generating SI2quater for 2 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 30 14 03 2b 2b 2b 2b 2b 2b 2b 2b 
+added EARFCN 1937 - generating SI2quater for 3 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 31 07 91 a0 a0 2b 2b 2b 2b 2b 2b 
+added EARFCN 1945 - generating SI2quater for 4 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 31 07 91 a8 3c c8 28 0b 2b 2b 2b 
+added EARFCN 1965 - generating SI2quater for 5 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b 
+added EARFCN 1967 - generating SI2quater for 6 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/01]: [23] 59 06 07 40 20 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b 
+generated valid SI2quater [01/01]: [23] 59 06 07 42 20 04 86 59 83 d7 e0 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+added EARFCN 1982 - generating SI2quater for 7 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/01]: [23] 59 06 07 40 20 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b 
+generated valid SI2quater [01/01]: [23] 59 06 07 42 20 04 86 59 83 d7 e4 1e fa c2 80 2b 2b 2b 2b 2b 2b 2b 2b 
+BTS allocation OK in test_si2q_u()
+Testing SYSINFO_TYPE_2quater UARFCN generation:
+generating SI2quater for 0 EARFCNs and 0 UARFCNs...
+generated invalid SI2quater [00/00]: [23] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+generating SI2quater for 0 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 0c 1a 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 14 1a 1f 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 3 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 18 58 12 f0 83 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 4 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 20 58 2e f0 f2 03 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 5 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 28 58 2e 22 f2 4e 83 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 6 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 34 1a 64 26 5d f2 05 03 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 7 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 38 58 12 22 fd ce 8e 05 03 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 8 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 40 58 1d 22 fa ce 88 85 7b 0b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 9 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 4c 7a 34 0e 64 77 85 43 55 c8 0b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 10 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 50 1c 3b 31 fa dd 88 85 7b c4 1c 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 11 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 58 1c 3b 25 7a ea 08 91 fb c4 1f b0 2b 2b 2b 
+BTS allocation OK in test_si2q_mu()
+Test SI2quater multiple UARFCNs:
+generating SI2quater for 0 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7c 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 e8 0a 7f 52 88 0a 7c 0b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 3 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 e8 12 7e e0 a9 44 05 3e 0b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 4 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 e8 18 3f f4 90 54 a2 02 9f 03 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 5 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 ea 08 81 52 e8 18 3f f4 90 54 a2 02 9f 03 2b 2b 
+generating SI2quater for 0 EARFCNs and 6 UARFCNs...
+generated valid SI2quater [00/01]: [23] 59 06 07 40 20 25 52 ea 08 81 52 e8 10 3f f4 a9 75 04 a4 0b 2b 2b 2b 
+generated valid SI2quater [01/01]: [23] 59 06 07 42 20 25 52 e8 28 81 df 7f fa 32 d4 a2 02 9f 03 2b 2b 2b 2b 
+generating SI2quater for 0 EARFCNs and 7 UARFCNs...
+generated valid SI2quater [00/01]: [23] 59 06 07 40 20 25 52 ea 10 81 ce a9 74 08 1f fa 54 ba 82 52 03 2b 2b 
+generated valid SI2quater [01/01]: [23] 59 06 07 42 20 25 52 e8 30 81 d3 7f fd b2 86 54 a2 02 9f 03 2b 2b 2b 
+BTS allocation OK in test_si2q_long()
+Testing SYSINFO_TYPE_2quater combined EARFCN & UARFCN generation:
+generating SI2quater for 17 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 0c 1a 10 99 66 0f 04 83 c1 1c bb 2b 03 2b 2b 
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c2 6c 1e 0f 60 f0 bb 08 3f d7 2e ca c1 2b 
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 20 64 21 06 e1 08 55 08 53 d7 2e ca c1 2b 
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 2a 64 21 56 e1 0a d5 08 49 d7 2e ca c1 2b 
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 25 64 21 2e e1 09 94 e5 d9 58 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 14 4d e7 00 44 b3 07 82 41 e0 8e 5d 95 83 2b 
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c2 6c 1e 0f 60 f0 bb 08 3f d7 2e ca c1 2b 
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 20 64 21 06 e1 08 55 08 53 d7 2e ca c1 2b 
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 2a 64 21 56 e1 0a d5 08 49 d7 2e ca c1 2b 
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 25 64 21 2e e1 09 94 e5 d9 58 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 3 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 1c 4d e7 03 04 86 59 83 c1 20 f0 47 2e ca c1 
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c2 6c 1e 0f 60 f0 bb 08 3f d7 2e ca c1 2b 
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 20 64 21 06 e1 08 55 08 53 d7 2e ca c1 2b 
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 2a 64 21 56 e1 0a d5 08 49 d7 2e ca c1 2b 
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 25 64 21 2e e1 09 94 e5 d9 58 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 4 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 24 59 fa 26 73 84 86 59 83 c1 1c bb 2b 03 2b 
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c1 20 f0 9b 07 83 d8 3c 2e b9 76 56 0b 2b 
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 1f ec 21 03 21 08 37 08 42 a7 2e ca c1 2b 
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 29 ec 21 53 21 0a b7 08 56 a7 2e ca c1 2b 
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 24 ec 21 2b 21 09 77 08 4c a7 2e ca c1 2b 
+generating SI2quater for 17 EARFCNs and 5 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 2c 59 fa 30 73 f6 04 86 59 83 c1 1c bb 2b 03 
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c1 20 f0 9b 07 83 d8 3c 2e b9 76 56 0b 2b 
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 1f ec 21 03 21 08 37 08 42 a7 2e ca c1 2b 
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 29 ec 21 53 21 0a b7 08 56 a7 2e ca c1 2b 
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 24 ec 21 2b 21 09 77 08 4c a7 2e ca c1 2b 
+generating SI2quater for 17 EARFCNs and 6 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 34 f1 ae 15 f3 f4 83 04 86 59 72 ec ac 0b 2b 
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b 
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b 
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b 
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b 
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 7 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 3c f1 ae 15 f3 f4 83 01 84 86 59 72 ec ac 0b 
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b 
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b 
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b 
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b 
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 8 UARFCNs...
+generated valid SI2quater [00/02]: [23] 59 06 07 40 40 25 0f 70 45 19 a0 0d 7d 7e a6 19 e7 00 44 b3 07 82 41 
+generated valid SI2quater [01/02]: [23] 59 06 07 42 40 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b 
+generated valid SI2quater [02/02]: [23] 59 06 07 44 40 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 9 UARFCNs...
+generated valid SI2quater [00/02]: [23] 59 06 07 40 40 25 0f 70 4d 19 a0 26 fd 66 a6 03 e7 fa 10 99 66 0f 04 
+generated valid SI2quater [01/02]: [23] 59 06 07 42 40 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b 
+generated valid SI2quater [02/02]: [23] 59 06 07 44 40 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 10 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 55 47 89 1e fd 7c b0 00 e7 9b b0 04 12 c8 2b 
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b 
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b 
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b 
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b 
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+generating SI2quater for 17 EARFCNs and 11 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 5d 47 89 1e fd 7c b0 01 67 9b b3 f8 2b 2b 2b 
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b 
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b 
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b 
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b 
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b 
+BTS allocation OK in test_si_ba_ind()
+Testing if BA-IND is set as expected in SI2xxx and SI5xxx
+SI2: 59 06 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+SI2bis: 59 06 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+SI2ter: 59 06 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+SI5: 06 1d 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+SI5bis: 06 05 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+SI5ter: 06 06 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+Done.
diff --git a/openbsc/tests/mgcp/Makefile.am b/openbsc/tests/mgcp/Makefile.am
new file mode 100644
index 0000000..33e921f
--- /dev/null
+++ b/openbsc/tests/mgcp/Makefile.am
@@ -0,0 +1,73 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_FLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(LIBBCG729_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	mgcp_test.ok \
+	mgcp_transcoding_test.ok \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	mgcp_test \
+	$(NULL)
+if BUILD_MGCP_TRANSCODING
+noinst_PROGRAMS += \
+	mgcp_transcoding_test \
+	$(NULL)
+endif
+
+mgcp_test_SOURCES = \
+	mgcp_test.c \
+	$(NULL)
+
+mgcp_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOSCCP_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBRARY_DL) \
+	$(LIBOSMONETIF_LIBS) \
+	-lrt \
+	-lm  \
+	$(NULL)
+
+mgcp_transcoding_test_SOURCES = \
+	mgcp_transcoding_test.c \
+	$(NULL)
+
+mgcp_transcoding_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBBCG729_LIBS) \
+	$(LIBOSMOSCCP_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBRARY_DL) \
+	$(LIBOSMONETIF_LIBS) \
+	$(LIBRARY_GSM) \
+	-lrt \
+	-lm \
+	$(NULL)
diff --git a/openbsc/tests/mgcp/mgcp_test.c b/openbsc/tests/mgcp/mgcp_test.c
new file mode 100644
index 0000000..e7a582a
--- /dev/null
+++ b/openbsc/tests/mgcp/mgcp_test.c
@@ -0,0 +1,1235 @@
+/*
+ * (C) 2011-2012,2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011-2012,2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#undef _GNU_SOURCE
+#define _GNU_SOURCE
+
+#include <openbsc/mgcp.h>
+#include <openbsc/vty.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <string.h>
+#include <limits.h>
+#include <dlfcn.h>
+#include <time.h>
+#include <math.h>
+
+char *strline_r(char *str, char **saveptr);
+
+const char *strline_test_data =
+    "one CR\r"
+    "two CR\r"
+    "\r"
+    "one CRLF\r\n"
+    "two CRLF\r\n"
+    "\r\n"
+    "one LF\n"
+    "two LF\n"
+    "\n"
+    "mixed (4 lines)\r\r\n\n\r\n";
+
+#define EXPECTED_NUMBER_OF_LINES 13
+
+static void test_strline(void)
+{
+	char *save = NULL;
+	char *line;
+	char buf[2048];
+	int counter = 0;
+
+	osmo_strlcpy(buf, strline_test_data, sizeof(buf));
+
+	for (line = strline_r(buf, &save); line;
+	     line = strline_r(NULL, &save)) {
+		printf("line: '%s'\n", line);
+		counter++;
+	}
+
+	OSMO_ASSERT(counter == EXPECTED_NUMBER_OF_LINES);
+}
+
+#define AUEP1	"AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n"
+#define AUEP1_RET "200 158663169 OK\r\n"
+#define AUEP2	"AUEP 18983213 ds/e1-2/1@172.16.6.66 MGCP 1.0\r\n"
+#define AUEP2_RET "500 18983213 FAIL\r\n"
+#define EMPTY	"\r\n"
+#define EMPTY_RET NULL
+#define SHORT	"CRCX \r\n"
+#define SHORT_RET "510 000000 FAIL\r\n"
+
+#define MDCX_WRONG_EP "MDCX 18983213 ds/e1-3/1@172.16.6.66 MGCP 1.0\r\n"
+#define MDCX_ERR_RET "510 18983213 FAIL\r\n"
+#define MDCX_UNALLOCATED "MDCX 18983214 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n"
+#define MDCX_RET "400 18983214 FAIL\r\n"
+#define MDCX3 "MDCX 18983215 1@mgw MGCP 1.0\r\n"
+#define MDCX3_RET "200 18983215 OK\r\n"		\
+		 "I: 1\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=rtpmap:126 AMR/8000\r\n"	\
+		 "a=ptime:20\r\n"
+#define MDCX3_FMTP_RET "200 18983215 OK\r\n"		\
+		 "I: 3\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 3 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=rtpmap:126 AMR/8000\r\n"	\
+		 "a=fmtp:126 0/1/2\r\n"		\
+		 "a=ptime:20\r\n"
+#define MDCX4 "MDCX 18983216 1@mgw MGCP 1.0\r\n" \
+		 "M: sendrecv\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: p:20, a:AMR, nt:IN\r\n"    \
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 4441 RTP/AVP 99\r\n"	\
+		 "a=rtpmap:99 AMR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+#define MDCX4_RET(Ident) "200 " Ident " OK\r\n"	\
+		 "I: 1\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=rtpmap:126 AMR/8000\r\n"	\
+		 "a=ptime:20\r\n"
+
+#define MDCX4_PT1 "MDCX 18983217 1@mgw MGCP 1.0\r\n" \
+		 "M: sendrecv\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: p:20-40, a:AMR, nt:IN\r\n"    \
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 4441 RTP/AVP 99\r\n"	\
+		 "a=rtpmap:99 AMR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define MDCX4_PT2 "MDCX 18983218 1@mgw MGCP 1.0\r\n" \
+		 "M: sendrecv\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: p:20-20, a:AMR, nt:IN\r\n"    \
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 4441 RTP/AVP 99\r\n"	\
+		 "a=rtpmap:99 AMR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define MDCX4_PT3 "MDCX 18983219 1@mgw MGCP 1.0\r\n" \
+		 "M: sendrecv\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: a:AMR, nt:IN\r\n"    \
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 4441 RTP/AVP 99\r\n"	\
+		 "a=rtpmap:99 AMR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define MDCX4_SO "MDCX 18983220 1@mgw MGCP 1.0\r\n" \
+		 "M: sendonly\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: p:20, a:AMR, nt:IN\r\n"    \
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 4441 RTP/AVP 99\r\n"	\
+		 "a=rtpmap:99 AMR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define MDCX4_RO "MDCX 18983221 1@mgw MGCP 1.0\r\n" \
+		 "M: recvonly\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: p:20, a:AMR, nt:IN\r\n"
+
+#define SHORT2	"CRCX 1"
+#define SHORT2_RET "510 000000 FAIL\r\n"
+#define SHORT3	"CRCX 1 1@mgw"
+#define SHORT4	"CRCX 1 1@mgw MGCP"
+#define SHORT5	"CRCX 1 1@mgw MGCP 1.0"
+
+#define CRCX	 "CRCX 2 1@mgw MGCP 1.0\r\n"	\
+		 "M: recvonly\r\n"		\
+		 "C: 2\r\n"			\
+		 "X\r\n"			\
+		 "L: p:20\r\n"		\
+		 "\r\n"				\
+		 "v=0\r\n"			\
+		 "c=IN IP4 123.12.12.123\r\n"	\
+		 "m=audio 5904 RTP/AVP 97\r\n"	\
+		 "a=rtpmap:97 GSM-EFR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define CRCX_RET "200 2 OK\r\n"			\
+		 "I: 1\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=rtpmap:126 AMR/8000\r\n"	\
+		 "a=ptime:20\r\n"
+
+#define CRCX_RET_NO_RTPMAP "200 2 OK\r\n"	\
+		 "I: 1\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 1 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=ptime:20\r\n"
+
+#define CRCX_FMTP_RET "200 2 OK\r\n"			\
+		 "I: 3\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 3 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=rtpmap:126 AMR/8000\r\n"	\
+		 "a=fmtp:126 0/1/2\r\n"		\
+		 "a=ptime:20\r\n"
+
+#define CRCX_ZYN "CRCX 2 1@mgw MGCP 1.0\r"	\
+		 "M: recvonly\r"		\
+		 "C: 2\r\r"			\
+		 "v=0\r"			\
+		 "c=IN IP4 123.12.12.123\r"	\
+		 "m=audio 5904 RTP/AVP 97\r"	\
+		 "a=rtpmap:97 GSM-EFR/8000\r"
+
+#define CRCX_ZYN_RET "200 2 OK\r\n"		\
+		 "I: 2\n"			\
+		 "\n"				\
+		 "v=0\r\n"			\
+		 "o=- 2 23 IN IP4 0.0.0.0\r\n"	\
+		 "s=-\r\n"			\
+		 "c=IN IP4 0.0.0.0\r\n"		\
+		 "t=0 0\r\n"			\
+		 "m=audio 0 RTP/AVP 126\r\n"	\
+		 "a=rtpmap:126 AMR/8000\r\n"	\
+		 "a=ptime:20\r\n"
+
+#define DLCX	 "DLCX 7 1@mgw MGCP 1.0\r\n"	\
+		 "C: 2\r\n"
+
+#define DLCX_RET "250 7 OK\r\n"			\
+		 "P: PS=0, OS=0, PR=0, OR=0, PL=0, JI=0\r\n"
+
+ #define DLCX_RET_OSMUX DLCX_RET                 \
+		 "X-Osmo-CP: EC TIS=0, TOS=0, TIR=0, TOR=0\r\n"
+
+#define RQNT	 "RQNT 186908780 1@mgw MGCP 1.0\r\n"	\
+		 "X: B244F267488\r\n"			\
+		 "S: D/9\r\n"
+
+#define RQNT2	 "RQNT 186908781 1@mgw MGCP 1.0\r\n"	\
+		 "X: ADD4F26746F\r\n"			\
+		 "R: D/[0-9#*](N), G/ft, fxr/t38\r\n"
+
+#define RQNT1_RET "200 186908780 OK\r\n"
+#define RQNT2_RET "200 186908781 OK\r\n"
+
+#define PTYPE_IGNORE 0 /* == default initializer */
+#define PTYPE_NONE 128
+#define PTYPE_NYI  PTYPE_NONE
+
+#define CRCX_MULT_1 "CRCX 2 1@mgw MGCP 1.0\r\n"	\
+		 "M: recvonly\r\n"		\
+		 "C: 2\r\n"			\
+		 "X\r\n"			\
+		 "L: p:20\r\n"		\
+		 "\r\n"				\
+		 "v=0\r\n"			\
+		 "c=IN IP4 123.12.12.123\r\n"	\
+		 "m=audio 5904 RTP/AVP 18 97\r\n"\
+		 "a=rtpmap:18 G729/8000\r\n"	\
+		 "a=rtpmap:97 GSM-EFR/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define CRCX_MULT_2 "CRCX 2 2@mgw MGCP 1.0\r\n"	\
+		 "M: recvonly\r\n"		\
+		 "C: 2\r\n"			\
+		 "X\r\n"			\
+		 "L: p:20\r\n"		\
+		 "\r\n"				\
+		 "v=0\r\n"			\
+		 "c=IN IP4 123.12.12.123\r\n"	\
+		 "m=audio 5904 RTP/AVP 18 97 101\r\n"\
+		 "a=rtpmap:18 G729/8000\r\n"	\
+		 "a=rtpmap:97 GSM-EFR/8000\r\n"	\
+		 "a=rtpmap:101 FOO/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define CRCX_MULT_3 "CRCX 2 3@mgw MGCP 1.0\r\n"	\
+		 "M: recvonly\r\n"		\
+		 "C: 2\r\n"			\
+		 "X\r\n"			\
+		 "L: p:20\r\n"		\
+		 "\r\n"				\
+		 "v=0\r\n"			\
+		 "c=IN IP4 123.12.12.123\r\n"	\
+		 "m=audio 5904 RTP/AVP\r\n"	\
+		 "a=rtpmap:18 G729/8000\r\n"	\
+		 "a=rtpmap:97 GSM-EFR/8000\r\n"	\
+		 "a=rtpmap:101 FOO/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define CRCX_MULT_4 "CRCX 2 4@mgw MGCP 1.0\r\n"	\
+		 "M: recvonly\r\n"		\
+		 "C: 2\r\n"			\
+		 "X\r\n"			\
+		 "L: p:20\r\n"		\
+		 "\r\n"				\
+		 "v=0\r\n"			\
+		 "c=IN IP4 123.12.12.123\r\n"	\
+		 "m=audio 5904 RTP/AVP 18\r\n"	\
+		 "a=rtpmap:18 G729/8000\r\n"	\
+		 "a=rtpmap:97 GSM-EFR/8000\r\n"	\
+		 "a=rtpmap:101 FOO/8000\r\n"	\
+		 "a=ptime:40\r\n"
+
+#define CRCX_MULT_GSM_EXACT \
+		"CRCX 259260421 5@mgw MGCP 1.0\r\n"	\
+		"C: 1355c6041e\r\n"			\
+		"I: 3\r\n"				\
+		"L: p:20, a:GSM, nt:IN\r\n"		\
+		"M: recvonly\r\n"			\
+		"\r\n"					\
+		"v=0\r\n"				\
+		"o=- 1439038275 1439038275 IN IP4 192.168.181.247\r\n" \
+		"s=-\r\nc=IN IP4 192.168.181.247\r\n"	\
+		"t=0 0\r\nm=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101\r\n" \
+		"a=rtpmap:0 PCMU/8000\r\n"		\
+		"a=rtpmap:8 PCMA/8000\r\n"		\
+		"a=rtpmap:3 gsm/8000\r\n"		\
+		"a=rtpmap:18 G729/8000\r\n"		\
+		"a=fmtp:18 annexb=no\r\n"		\
+		"a=rtpmap:4 G723/8000\r\n"		\
+		"a=rtpmap:96 iLBC/8000\r\n"		\
+		"a=fmtp:96 mode=20\r\n"			\
+		"a=rtpmap:97 iLBC/8000\r\n"		\
+		"a=fmtp:97 mode=30\r\n"			\
+		"a=rtpmap:101 telephone-event/8000\r\n"	\
+		"a=fmtp:101 0-15\r\n"			\
+		"a=recvonly\r\n"
+#define MDCX_NAT_DUMMY \
+		"MDCX 23 5@mgw MGCP 1.0\r\n"		\
+		"C: 1355c6041e\r\n"			\
+		"\r\n"					\
+		"c=IN IP4 8.8.8.8\r\n"		\
+		"m=audio 16434 RTP/AVP 255\r\n"
+
+
+struct mgcp_test {
+	const char *name;
+	const char *req;
+	const char *exp_resp;
+	int exp_net_ptype;
+	int exp_bts_ptype;
+
+	const char *extra_fmtp;
+};
+
+static const struct mgcp_test tests[] = {
+	{ "AUEP1", AUEP1, AUEP1_RET },
+	{ "AUEP2", AUEP2, AUEP2_RET },
+	{ "MDCX1", MDCX_WRONG_EP, MDCX_ERR_RET },
+	{ "MDCX2", MDCX_UNALLOCATED, MDCX_RET },
+	{ "CRCX", CRCX, CRCX_RET, 97, 126 },
+	{ "MDCX3", MDCX3, MDCX3_RET, PTYPE_NONE, 126 },
+	{ "MDCX4", MDCX4, MDCX4_RET("18983216"), 99, 126 },
+	{ "MDCX4_PT1", MDCX4_PT1, MDCX4_RET("18983217"), 99, 126 },
+	{ "MDCX4_PT2", MDCX4_PT2, MDCX4_RET("18983218"), 99, 126 },
+	{ "MDCX4_PT3", MDCX4_PT3, MDCX4_RET("18983219"), 99, 126 },
+	{ "MDCX4_SO", MDCX4_SO, MDCX4_RET("18983220"), 99, 126 },
+	{ "MDCX4_RO", MDCX4_RO, MDCX4_RET("18983221"), PTYPE_IGNORE, 126 },
+	{ "DLCX", DLCX, DLCX_RET, -1, -1 },
+	{ "CRCX_ZYN", CRCX_ZYN, CRCX_ZYN_RET, 97, 126 },
+	{ "EMPTY", EMPTY, EMPTY_RET },
+	{ "SHORT1", SHORT, SHORT_RET },
+	{ "SHORT2", SHORT2, SHORT2_RET },
+	{ "SHORT3", SHORT3, SHORT2_RET },
+	{ "SHORT4", SHORT4, SHORT2_RET },
+	{ "RQNT1", RQNT, RQNT1_RET },
+	{ "RQNT2", RQNT2, RQNT2_RET },
+	{ "DLCX", DLCX, DLCX_RET, -1, -1 },
+	{ "CRCX", CRCX, CRCX_FMTP_RET, 97, 126, .extra_fmtp = "a=fmtp:126 0/1/2" },
+	{ "MDCX3", MDCX3, MDCX3_FMTP_RET, PTYPE_NONE, 126 , .extra_fmtp = "a=fmtp:126 0/1/2" },
+	{ "DLCX", DLCX, DLCX_RET, -1, -1 , .extra_fmtp = "a=fmtp:126 0/1/2" },
+};
+
+static const struct mgcp_test retransmit[] = {
+	{ "CRCX", CRCX, CRCX_RET },
+	{ "RQNT1", RQNT, RQNT1_RET },
+	{ "RQNT2", RQNT2, RQNT2_RET },
+	{ "MDCX3", MDCX3, MDCX3_RET },
+	{ "DLCX", DLCX, DLCX_RET },
+};
+
+static struct msgb *create_msg(const char *str)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+	int len = sprintf((char *)msg->data, "%s", str);
+	msg->l2h = msgb_put(msg, len);
+	return msg;
+}
+
+static int last_endpoint = -1;
+
+static int mgcp_test_policy_cb(struct mgcp_trunk_config *cfg, int endpoint,
+			       int state, const char *transactio_id)
+{
+	fprintf(stderr, "Policy CB got state %d on endpoint 0x%x\n",
+		state, endpoint);
+	last_endpoint = endpoint;
+	return MGCP_POLICY_CONT;
+}
+
+#define MGCP_DUMMY_LOAD 0x23
+static int dummy_packets = 0;
+/* override and forward */
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
+		const struct sockaddr *dest_addr, socklen_t addrlen)
+{
+	typedef ssize_t (*sendto_t)(int, const void *, size_t, int,
+			const struct sockaddr *, socklen_t);
+	static sendto_t real_sendto = NULL;
+	uint32_t dest_host = htonl(((struct sockaddr_in *)dest_addr)->sin_addr.s_addr);
+	int      dest_port = htons(((struct sockaddr_in *)dest_addr)->sin_port);
+
+	if (!real_sendto)
+		real_sendto = dlsym(RTLD_NEXT, "sendto");
+
+	if (len == 1 && ((const char *)buf)[0] == MGCP_DUMMY_LOAD ) {
+		fprintf(stderr, "Dummy packet to 0x%08x:%d, msg length %zu\n%s\n\n",
+		       dest_host, dest_port,
+		       len, osmo_hexdump(buf, len));
+		dummy_packets += 1;
+	}
+
+	return real_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
+}
+
+static int64_t force_monotonic_time_us = -1;
+/* override and forward */
+int clock_gettime(clockid_t clk_id, struct timespec *tp)
+{
+	typedef int (*clock_gettime_t)(clockid_t clk_id, struct timespec *tp);
+	static clock_gettime_t real_clock_gettime = NULL;
+
+	if (!real_clock_gettime)
+		real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime");
+
+	if (clk_id == CLOCK_MONOTONIC && force_monotonic_time_us >= 0) {
+		tp->tv_sec = force_monotonic_time_us / 1000000;
+		tp->tv_nsec = (force_monotonic_time_us % 1000000) * 1000;
+		return 0;
+	}
+
+	return real_clock_gettime(clk_id, tp);
+}
+
+#define CONN_UNMODIFIED (0x1000)
+
+static void test_values(void)
+{
+	/* Check that NONE disables all output */
+	OSMO_ASSERT((MGCP_CONN_NONE & MGCP_CONN_RECV_SEND) == 0)
+
+	/* Check that LOOPBACK enables all output */
+	OSMO_ASSERT((MGCP_CONN_LOOPBACK & MGCP_CONN_RECV_SEND) ==
+		    MGCP_CONN_RECV_SEND)
+}
+
+
+static void test_messages(void)
+{
+	struct mgcp_config *cfg;
+	struct mgcp_endpoint *endp;
+	int i;
+
+	cfg = mgcp_config_alloc();
+
+	cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&cfg->trunk);
+
+	cfg->policy_cb = mgcp_test_policy_cb;
+
+	mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1));
+
+	/* reset endpoints */
+	for (i = 0; i < cfg->trunk.number_endpoints; i++) {
+		endp = &cfg->trunk.endpoints[i];
+		endp->net_end.codec.payload_type = PTYPE_NONE;
+		endp->net_end.packet_duration_ms = -1;
+
+		OSMO_ASSERT(endp->conn_mode == MGCP_CONN_NONE);
+		endp->conn_mode |= CONN_UNMODIFIED;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		const struct mgcp_test *t = &tests[i];
+		struct msgb *inp;
+		struct msgb *msg;
+
+		printf("Testing %s\n", t->name);
+
+		last_endpoint = -1;
+		dummy_packets = 0;
+
+		osmo_talloc_replace_string(cfg, &cfg->trunk.audio_fmtp_extra, t->extra_fmtp);
+
+		inp = create_msg(t->req);
+		msg = mgcp_handle_message(cfg, inp);
+		msgb_free(inp);
+		if (!t->exp_resp) {
+			if (msg)
+				printf("%s failed '%s'\n", t->name, (char *) msg->data);
+		} else if (strcmp((char *) msg->data, t->exp_resp) != 0)
+			printf("%s failed.\nExpected:\n%s\nGot:\n%s\n",
+			       t->name, t->exp_resp, (char *) msg->data);
+		msgb_free(msg);
+
+		if (dummy_packets)
+			printf("Dummy packets: %d\n", dummy_packets);
+
+		if (last_endpoint != -1) {
+			endp = &cfg->trunk.endpoints[last_endpoint];
+
+			if (endp->net_end.packet_duration_ms != -1)
+				printf("Detected packet duration: %d\n",
+				       endp->net_end.packet_duration_ms);
+			else
+				printf("Packet duration not set\n");
+			if (endp->local_options.pkt_period_min ||
+			    endp->local_options.pkt_period_max)
+				printf("Requested packetetization period: "
+				       "%d-%d\n",
+				       endp->local_options.pkt_period_min,
+				       endp->local_options.pkt_period_max);
+			else
+				printf("Requested packetization period not set\n");
+
+			if ((endp->conn_mode & CONN_UNMODIFIED) == 0) {
+				printf("Connection mode: %d:%s%s%s%s\n",
+				       endp->conn_mode,
+				       !endp->conn_mode ? " NONE" : "",
+				       endp->conn_mode & MGCP_CONN_SEND_ONLY ?
+				       " SEND" : "",
+				       endp->conn_mode & MGCP_CONN_RECV_ONLY ?
+				       " RECV" : "",
+				       endp->conn_mode & MGCP_CONN_LOOPBACK &
+				       ~MGCP_CONN_RECV_SEND ?
+				       " LOOP" : "");
+				fprintf(stderr,
+					"BTS output %sabled, NET output %sabled\n",
+					endp->bts_end.output_enabled ? "en" : "dis",
+					endp->net_end.output_enabled ? "en" : "dis");
+			} else
+				printf("Connection mode not set\n");
+
+			OSMO_ASSERT(endp->net_end.output_enabled ==
+				    (endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0));
+			OSMO_ASSERT(endp->bts_end.output_enabled ==
+				    (endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0));
+
+			endp->net_end.packet_duration_ms = -1;
+			endp->local_options.pkt_period_min = 0;
+			endp->local_options.pkt_period_max = 0;
+			endp->conn_mode |= CONN_UNMODIFIED;
+		}
+
+
+		/* Check detected payload type */
+		if (t->exp_net_ptype != PTYPE_IGNORE ||
+		    t->exp_bts_ptype != PTYPE_IGNORE) {
+			OSMO_ASSERT(last_endpoint != -1);
+			endp = &cfg->trunk.endpoints[last_endpoint];
+
+			fprintf(stderr, "endpoint 0x%x: "
+				"payload type BTS %d (exp %d), NET %d (exp %d)\n",
+				last_endpoint,
+				endp->bts_end.codec.payload_type, t->exp_bts_ptype,
+				endp->net_end.codec.payload_type, t->exp_net_ptype);
+
+			if (t->exp_bts_ptype != PTYPE_IGNORE)
+				OSMO_ASSERT(endp->bts_end.codec.payload_type ==
+					    t->exp_bts_ptype);
+			if (t->exp_net_ptype != PTYPE_IGNORE)
+				OSMO_ASSERT(endp->net_end.codec.payload_type ==
+					    t->exp_net_ptype);
+
+			/* Reset them again for next test */
+			endp->net_end.codec.payload_type = PTYPE_NONE;
+		}
+	}
+
+	talloc_free(cfg);
+}
+
+static void test_retransmission(void)
+{
+	struct mgcp_config *cfg;
+	int i;
+
+	cfg = mgcp_config_alloc();
+
+	cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&cfg->trunk);
+
+	mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1));
+
+	/* reset endpoints */
+	for (i = 0; i < cfg->trunk.number_endpoints; i++) {
+		struct mgcp_endpoint *endp;
+		endp = &cfg->trunk.endpoints[i];
+		endp->bts_end.packet_duration_ms = 20;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(retransmit); i++) {
+		const struct mgcp_test *t = &retransmit[i];
+		struct msgb *inp;
+		struct msgb *msg;
+
+		printf("Testing %s\n", t->name);
+
+		inp = create_msg(t->req);
+		msg = mgcp_handle_message(cfg, inp);
+		msgb_free(inp);
+		if (strcmp((char *) msg->data, t->exp_resp) != 0)
+			printf("%s failed.\nExpected:\n%s\nGot:\n%s\n",
+			       t->name, t->exp_resp, (char *) msg->data);
+		msgb_free(msg);
+
+		/* Retransmit... */
+		printf("Re-transmitting %s\n", t->name);
+		inp = create_msg(t->req);
+		msg = mgcp_handle_message(cfg, inp);
+		msgb_free(inp);
+		if (strcmp((char *) msg->data, t->exp_resp) != 0)
+			printf("%s failed.\nExpected:\n%s\nGot:\n%s\n",
+			       t->name, t->exp_resp, (char *) msg->data);
+		msgb_free(msg);
+	}
+
+	talloc_free(cfg);
+}
+
+static int rqnt_cb(struct mgcp_endpoint *endp, char _tone)
+{
+	ptrdiff_t tone = _tone;
+	endp->cfg->data = (void *) tone;
+	return 0;
+}
+
+static void test_rqnt_cb(void)
+{
+	struct mgcp_config *cfg;
+	struct msgb *inp, *msg;
+
+	cfg = mgcp_config_alloc();
+	cfg->rqnt_cb = rqnt_cb;
+
+	cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&cfg->trunk);
+
+	mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1));
+
+	inp = create_msg(CRCX);
+	msgb_free(mgcp_handle_message(cfg, inp));
+	msgb_free(inp);
+
+	/* send the RQNT and check for the CB */
+	inp = create_msg(RQNT);
+	msg = mgcp_handle_message(cfg, inp);
+	if (strncmp((const char *) msg->l2h, "200", 3) != 0) {
+		printf("FAILED: message is not 200. '%s'\n", msg->l2h);
+		abort();
+	}
+
+	if (cfg->data != (void *) '9') {
+		printf("FAILED: callback not called: %p\n", cfg->data);
+		abort();
+	}
+
+	msgb_free(msg);
+	msgb_free(inp);
+
+	inp = create_msg(DLCX);
+	msgb_free(mgcp_handle_message(cfg, inp));
+	msgb_free(inp);
+	talloc_free(cfg);
+}
+
+struct pl_test {
+	int		cycles;
+	uint16_t	base_seq;
+	uint16_t	max_seq;
+	uint32_t	packets;
+
+	uint32_t	expected;
+	int		loss;
+};
+
+static const struct pl_test pl_test_dat[] = {
+	/* basic.. just one package */
+	{ .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = 1, .expected = 1, .loss = 0},
+	/* some packages and a bit of loss */
+	{ .cycles = 0, .base_seq = 0, .max_seq = 100, .packets = 100, .expected = 101, .loss = 1},
+	/* wrap around */
+	{ .cycles = 1<<16, .base_seq = 0xffff, .max_seq = 2, .packets = 4, .expected = 4, .loss = 0},
+	/* min loss */
+	{ .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = UINT_MAX, .expected = 1, .loss = INT_MIN },
+	/* max loss, with wrap around on expected max */
+	{ .cycles = INT_MAX, .base_seq = 0, .max_seq = UINT16_MAX, .packets = 0, .expected = ((uint32_t)(INT_MAX) + UINT16_MAX + 1), .loss = INT_MAX }, 
+};
+
+static void test_packet_loss_calc(void)
+{
+	int i;
+	printf("Testing packet loss calculation.\n");
+
+	for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) {
+		uint32_t expected;
+		int loss;
+		struct mgcp_rtp_state state;
+		struct mgcp_rtp_end rtp;
+		memset(&state, 0, sizeof(state));
+		memset(&rtp, 0, sizeof(rtp));
+
+		state.stats_initialized = 1;
+		state.stats_base_seq = pl_test_dat[i].base_seq;
+		state.stats_max_seq = pl_test_dat[i].max_seq;
+		state.stats_cycles = pl_test_dat[i].cycles;
+
+		rtp.packets = pl_test_dat[i].packets;
+		mgcp_state_calc_loss(&state, &rtp, &expected, &loss);
+
+		if (loss != pl_test_dat[i].loss || expected != pl_test_dat[i].expected) {
+			printf("FAIL: Wrong exp/loss at idx(%d) Loss(%d vs. %d) Exp(%u vs. %u)\n",
+				i, loss, pl_test_dat[i].loss,
+				expected, pl_test_dat[i].expected);
+		}
+	}
+}
+
+static void test_mgcp_stats(void)
+{
+	printf("Testing stat parsing\n");
+
+	uint32_t bps, bos, pr, _or, jitter;
+	struct msgb *msg;
+	int loss;
+	int rc;
+
+	msg = create_msg(DLCX_RET);
+	rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter);
+	printf("Parsing result: %d\n", rc);
+	if (bps != 0 || bos != 0 || pr != 0 ||  _or != 0 || loss != 0 || jitter != 0)
+		printf("FAIL: Parsing failed1.\n");
+	msgb_free(msg);
+
+	msg = create_msg("250 7 OK\r\nP: PS=10, OS=20, PR=30, OR=40, PL=-3, JI=40\r\n");
+	rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter);
+	printf("Parsing result: %d\n", rc);
+	if (bps != 10 || bos != 20 || pr != 30 || _or != 40 || loss != -3 || jitter != 40)
+		printf("FAIL: Parsing failed2.\n");
+	msgb_free(msg);
+}
+
+struct rtp_packet_info {
+	float txtime;
+	int len;
+	char *data;
+};
+
+struct rtp_packet_info test_rtp_packets1[] = {
+	/* RTP: SeqNo=0, TS=0 */
+	{0.000000, 20, "\x80\x62\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 20, "\x80\x62\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=2, TS=320 */
+	{0.040000, 20, "\x80\x62\x00\x02\x00\x00\x01\x40\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* Repeat RTP timestamp: */
+	/* RTP: SeqNo=3, TS=320 */
+	{0.060000, 20, "\x80\x62\x00\x03\x00\x00\x01\x40\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=4, TS=480 */
+	{0.080000, 20, "\x80\x62\x00\x04\x00\x00\x01\xE0\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=5, TS=640 */
+	{0.100000, 20, "\x80\x62\x00\x05\x00\x00\x02\x80\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* Double skip RTP timestamp (delta = 2*160): */
+	/* RTP: SeqNo=6, TS=960 */
+	{0.120000, 20, "\x80\x62\x00\x06\x00\x00\x03\xC0\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=7, TS=1120 */
+	{0.140000, 20, "\x80\x62\x00\x07\x00\x00\x04\x60\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=8, TS=1280 */
+	{0.160000, 20, "\x80\x62\x00\x08\x00\x00\x05\x00\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* Non 20ms RTP timestamp (delta = 120): */
+	/* RTP: SeqNo=9, TS=1400 */
+	{0.180000, 20, "\x80\x62\x00\x09\x00\x00\x05\x78\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=10, TS=1560 */
+	{0.200000, 20, "\x80\x62\x00\x0A\x00\x00\x06\x18\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=11, TS=1720 */
+	{0.220000, 20, "\x80\x62\x00\x0B\x00\x00\x06\xB8\x11\x22\x33\x44"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* SSRC changed to 0x10203040, RTP timestamp jump */
+	/* RTP: SeqNo=12, TS=34688 */
+	{0.240000, 20, "\x80\x62\x00\x0C\x00\x00\x87\x80\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=13, TS=34848 */
+	{0.260000, 20, "\x80\x62\x00\x0D\x00\x00\x88\x20\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=14, TS=35008 */
+	{0.280000, 20, "\x80\x62\x00\x0E\x00\x00\x88\xC0\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* Non 20ms RTP timestamp (delta = 120): */
+	/* RTP: SeqNo=15, TS=35128 */
+	{0.300000, 20, "\x80\x62\x00\x0F\x00\x00\x89\x38\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=16, TS=35288 */
+	{0.320000, 20, "\x80\x62\x00\x10\x00\x00\x89\xD8\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=17, TS=35448 */
+	{0.340000, 20, "\x80\x62\x00\x11\x00\x00\x8A\x78\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x8A\xAB\xCD\xEF"},
+	/* SeqNo increment by 2, RTP timestamp delta = 320: */
+	/* RTP: SeqNo=19, TS=35768 */
+	{0.360000, 20, "\x80\x62\x00\x13\x00\x00\x8B\xB8\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=20, TS=35928 */
+	{0.380000, 20, "\x80\x62\x00\x14\x00\x00\x8C\x58\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=21, TS=36088 */
+	{0.380000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* Repeat last packet */
+	/* RTP: SeqNo=21, TS=36088 */
+	{0.400000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=22, TS=36248 */
+	{0.420000, 20, "\x80\x62\x00\x16\x00\x00\x8D\x98\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=23, TS=36408 */
+	{0.440000, 20, "\x80\x62\x00\x17\x00\x00\x8E\x38\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* Don't increment SeqNo but increment timestamp by 160 */
+	/* RTP: SeqNo=23, TS=36568 */
+	{0.460000, 20, "\x80\x62\x00\x17\x00\x00\x8E\xD8\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=24, TS=36728 */
+	{0.480000, 20, "\x80\x62\x00\x18\x00\x00\x8F\x78\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=25, TS=36888 */
+	{0.500000, 20, "\x80\x62\x00\x19\x00\x00\x90\x18\x10\x20\x30\x40"
+		       "\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* SSRC changed to 0x50607080, RTP timestamp jump, Delay of 1.5s,
+	 * SeqNo jump */
+	/* RTP: SeqNo=1000, TS=160000 */
+	{2.000000, 20, "\x80\x62\x03\xE8\x00\x02\x71\x00\x50\x60\x70\x80"
+			"\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=1001, TS=160160 */
+	{2.020000, 20, "\x80\x62\x03\xE9\x00\x02\x71\xA0\x50\x60\x70\x80"
+			"\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+	/* RTP: SeqNo=1002, TS=160320 */
+	{2.040000, 20, "\x80\x62\x03\xEA\x00\x02\x72\x40\x50\x60\x70\x80"
+			"\x01\x23\x45\x67\x89\xAB\xCD\xEF"},
+};
+
+void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
+			  struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr,
+			  char *data, int len);
+
+static void test_packet_error_detection(int patch_ssrc, int patch_ts)
+{
+	int i;
+
+	struct mgcp_trunk_config trunk;
+	struct mgcp_endpoint endp;
+	struct mgcp_rtp_state state;
+	struct mgcp_rtp_end *rtp = &endp.net_end;
+	struct sockaddr_in addr = {0};
+	char buffer[4096];
+	uint32_t last_ssrc = 0;
+	uint32_t last_timestamp = 0;
+	uint32_t last_seqno = 0;
+	int last_in_ts_err_cnt = 0;
+	int last_out_ts_err_cnt = 0;
+
+	printf("Testing packet error detection%s%s.\n",
+	       patch_ssrc ? ", patch SSRC" : "",
+	       patch_ts ? ", patch timestamps" : "");
+
+	memset(&trunk, 0, sizeof(trunk));
+	memset(&endp, 0, sizeof(endp));
+	memset(&state, 0, sizeof(state));
+
+	trunk.number_endpoints = 1;
+	trunk.endpoints = &endp;
+	trunk.force_constant_ssrc = patch_ssrc;
+	trunk.force_aligned_timing = patch_ts;
+
+	endp.tcfg = &trunk;
+
+	mgcp_initialize_endp(&endp);
+
+	rtp->codec.payload_type = 98;
+
+	for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) {
+		struct rtp_packet_info *info = test_rtp_packets1 + i;
+
+		force_monotonic_time_us = round(1000000.0 * info->txtime);
+
+		OSMO_ASSERT(info->len <= sizeof(buffer));
+		OSMO_ASSERT(info->len >= 0);
+		memmove(buffer, info->data, info->len);
+
+		mgcp_rtp_end_config(&endp, 1, rtp);
+
+		mgcp_patch_and_count(&endp, &state, rtp, &addr,
+				     buffer, info->len);
+
+		if (state.out_stream.ssrc != last_ssrc) {
+			printf("Output SSRC changed to %08x\n",
+			       state.out_stream.ssrc);
+			last_ssrc = state.out_stream.ssrc;
+		}
+
+		printf("In TS: %d, dTS: %d, Seq: %d\n",
+		       state.in_stream.last_timestamp,
+		       state.in_stream.last_tsdelta,
+		       state.in_stream.last_seq);
+
+		printf("Out TS change: %d, dTS: %d, Seq change: %d, "
+		       "TS Err change: in %+d, out %+d\n",
+		       state.out_stream.last_timestamp - last_timestamp,
+		       state.out_stream.last_tsdelta,
+		       state.out_stream.last_seq - last_seqno,
+		       state.in_stream.err_ts_counter - last_in_ts_err_cnt,
+		       state.out_stream.err_ts_counter - last_out_ts_err_cnt);
+
+		printf("Stats: Jitter = %u, Transit = %d\n",
+		       mgcp_state_calc_jitter(&state), state.stats_transit);
+
+		last_in_ts_err_cnt = state.in_stream.err_ts_counter;
+		last_out_ts_err_cnt = state.out_stream.err_ts_counter;
+		last_timestamp = state.out_stream.last_timestamp;
+		last_seqno = state.out_stream.last_seq;
+	}
+
+	force_monotonic_time_us = -1;
+}
+
+static void test_multilple_codec(void)
+{
+	struct mgcp_config *cfg;
+	struct mgcp_endpoint *endp;
+	struct msgb *inp, *resp;
+	struct in_addr addr;
+
+	printf("Testing multiple payload types\n");
+
+	cfg = mgcp_config_alloc();
+	cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&cfg->trunk);
+	cfg->policy_cb = mgcp_test_policy_cb;
+	mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1));
+
+	/* Allocate endpoint 1@mgw with two codecs */
+	last_endpoint = -1;
+	inp = create_msg(CRCX_MULT_1);
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+
+	OSMO_ASSERT(last_endpoint == 1);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == 18);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 97);
+
+	/* Allocate 2@mgw with three codecs, last one ignored */
+	last_endpoint = -1;
+	inp = create_msg(CRCX_MULT_2);
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+
+	OSMO_ASSERT(last_endpoint == 2);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == 18);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 97);
+
+	/* Allocate 3@mgw with no codecs, check for PT == -1 */
+	last_endpoint = -1;
+	inp = create_msg(CRCX_MULT_3);
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+
+	OSMO_ASSERT(last_endpoint == 3);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == -1);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1);
+
+	/* Allocate 4@mgw with a single codec */
+	last_endpoint = -1;
+	inp = create_msg(CRCX_MULT_4);
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+
+	OSMO_ASSERT(last_endpoint == 4);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == 18);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1);
+
+	/* Allocate 5@mgw at select GSM.. */
+	last_endpoint = -1;
+	inp = create_msg(CRCX_MULT_GSM_EXACT);
+	talloc_free(cfg->trunk.audio_name);
+	cfg->trunk.audio_name = "GSM/8000";
+	cfg->trunk.no_audio_transcoding = 1;
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+
+	OSMO_ASSERT(last_endpoint == 5);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == 3);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1);
+
+	inp = create_msg(MDCX_NAT_DUMMY);
+	last_endpoint = -1;
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+	OSMO_ASSERT(last_endpoint == 5);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == 3);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1);
+	OSMO_ASSERT(endp->net_end.rtp_port == htons(16434));
+	memset(&addr, 0, sizeof(addr));
+	inet_aton("8.8.8.8", &addr);
+	OSMO_ASSERT(endp->net_end.addr.s_addr == addr.s_addr);
+
+	/* Check what happens without that flag */
+
+	/* Free the previous endpoint and the data ... */
+	mgcp_release_endp(endp);
+	talloc_free(endp->last_response);
+	talloc_free(endp->last_trans);
+	endp->last_response = endp->last_trans = NULL;
+
+	last_endpoint = -1;
+	inp = create_msg(CRCX_MULT_GSM_EXACT);
+	cfg->trunk.no_audio_transcoding = 0;
+	resp = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	msgb_free(resp);
+
+	OSMO_ASSERT(last_endpoint == 5);
+	endp = &cfg->trunk.endpoints[last_endpoint];
+	OSMO_ASSERT(endp->net_end.codec.payload_type == 255);
+	OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 0);
+
+	talloc_free(cfg);
+}
+
+static void test_no_cycle(void)
+{
+	struct mgcp_config *cfg;
+	struct mgcp_endpoint *endp;
+
+	printf("Testing no sequence flow on initial packet\n");
+
+	cfg = mgcp_config_alloc();
+	cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&cfg->trunk);
+
+	endp = &cfg->trunk.endpoints[1];
+	OSMO_ASSERT(endp->net_state.stats_initialized == 0);
+
+	mgcp_rtp_annex_count(endp, &endp->net_state, 0, 0, 2342);
+	OSMO_ASSERT(endp->net_state.stats_initialized == 1);
+	OSMO_ASSERT(endp->net_state.stats_cycles == 0);
+	OSMO_ASSERT(endp->net_state.stats_max_seq == 0);
+
+	mgcp_rtp_annex_count(endp, &endp->net_state, 1, 0, 2342);
+	OSMO_ASSERT(endp->net_state.stats_initialized == 1);
+	OSMO_ASSERT(endp->net_state.stats_cycles == 0);
+	OSMO_ASSERT(endp->net_state.stats_max_seq == 1);
+
+	/* now jump.. */
+	mgcp_rtp_annex_count(endp, &endp->net_state, UINT16_MAX, 0, 2342);
+	OSMO_ASSERT(endp->net_state.stats_initialized == 1);
+	OSMO_ASSERT(endp->net_state.stats_cycles == 0);
+	OSMO_ASSERT(endp->net_state.stats_max_seq == UINT16_MAX);
+
+	/* and wrap */
+	mgcp_rtp_annex_count(endp, &endp->net_state, 0, 0, 2342);
+	OSMO_ASSERT(endp->net_state.stats_initialized == 1);
+	OSMO_ASSERT(endp->net_state.stats_cycles == UINT16_MAX + 1);
+	OSMO_ASSERT(endp->net_state.stats_max_seq == 0);
+
+	talloc_free(cfg);
+}
+
+static void test_no_name(void)
+{
+	struct mgcp_config *cfg;
+	struct mgcp_endpoint *endp;
+	struct msgb *inp, *msg;
+	int i;
+
+	printf("Testing no rtpmap name\n");
+	cfg = mgcp_config_alloc();
+
+	cfg->trunk.number_endpoints = 64;
+	cfg->trunk.audio_send_name = 0;
+	mgcp_endpoints_allocate(&cfg->trunk);
+
+	cfg->policy_cb = mgcp_test_policy_cb;
+
+	mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1));
+
+	/* reset endpoints */
+	for (i = 0; i < cfg->trunk.number_endpoints; i++) {
+		endp = &cfg->trunk.endpoints[i];
+		endp->net_end.codec.payload_type = PTYPE_NONE;
+		endp->net_end.packet_duration_ms = -1;
+
+		OSMO_ASSERT(endp->conn_mode == MGCP_CONN_NONE);
+		endp->conn_mode |= CONN_UNMODIFIED;
+	}
+
+	inp = create_msg(CRCX);
+	msg = mgcp_handle_message(cfg, inp);
+	if (strcmp((char *) msg->data, CRCX_RET_NO_RTPMAP) != 0)
+		printf("FAILED: there should not be a RTPMAP: %s\n",
+			(char *) msg->data);
+	msgb_free(inp);
+	msgb_free(msg);
+
+	mgcp_release_endp(&cfg->trunk.endpoints[1]);
+	talloc_free(cfg);
+}
+
+static void test_osmux_cid(void)
+{
+	int id, i;
+
+	OSMO_ASSERT(osmux_used_cid() == 0);
+	id = osmux_get_cid();
+	OSMO_ASSERT(id == 0);
+	OSMO_ASSERT(osmux_used_cid() == 1);
+	osmux_put_cid(id);
+	OSMO_ASSERT(osmux_used_cid() == 0);
+
+	for (i = 0; i < 256; ++i) {
+		id = osmux_get_cid();
+		OSMO_ASSERT(id == i);
+		OSMO_ASSERT(osmux_used_cid() == i + 1);
+	}
+
+	id = osmux_get_cid();
+	OSMO_ASSERT(id == -1);
+
+	for (i = 0; i < 256; ++i)
+		osmux_put_cid(i);
+	OSMO_ASSERT(osmux_used_cid() == 0);
+}
+
+int main(int argc, char **argv)
+{
+	void *msgb_ctx = msgb_talloc_ctx_init(NULL, 0);
+	osmo_init_logging(&log_info);
+
+	test_strline();
+	test_values();
+	test_messages();
+	test_retransmission();
+	test_packet_loss_calc();
+	test_rqnt_cb();
+	test_mgcp_stats();
+	test_packet_error_detection(1, 0);
+	test_packet_error_detection(0, 0);
+	test_packet_error_detection(0, 1);
+	test_packet_error_detection(1, 1);
+	test_multilple_codec();
+	test_no_cycle();
+	test_no_name();
+	test_osmux_cid();
+
+	OSMO_ASSERT(talloc_total_size(msgb_ctx) == 0);
+	OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1);
+	talloc_free(msgb_ctx);
+	printf("Done\n");
+	return EXIT_SUCCESS;
+}
diff --git a/openbsc/tests/mgcp/mgcp_test.ok b/openbsc/tests/mgcp/mgcp_test.ok
new file mode 100644
index 0000000..4e27282
--- /dev/null
+++ b/openbsc/tests/mgcp/mgcp_test.ok
@@ -0,0 +1,480 @@
+line: 'one CR'
+line: 'two CR'
+line: ''
+line: 'one CRLF'
+line: 'two CRLF'
+line: ''
+line: 'one LF'
+line: 'two LF'
+line: ''
+line: 'mixed (4 lines)'
+line: ''
+line: ''
+line: ''
+Testing AUEP1
+Testing AUEP2
+Testing MDCX1
+Testing MDCX2
+Testing CRCX
+Dummy packets: 1
+Detected packet duration: 40
+Requested packetetization period: 20-20
+Connection mode: 1: RECV
+Testing MDCX3
+Dummy packets: 1
+Packet duration not set
+Requested packetization period not set
+Connection mode not set
+Testing MDCX4
+Dummy packets: 1
+Detected packet duration: 40
+Requested packetetization period: 20-20
+Connection mode: 3: SEND RECV
+Testing MDCX4_PT1
+Dummy packets: 1
+Detected packet duration: 40
+Requested packetetization period: 20-40
+Connection mode: 3: SEND RECV
+Testing MDCX4_PT2
+Dummy packets: 1
+Detected packet duration: 40
+Requested packetetization period: 20-20
+Connection mode: 3: SEND RECV
+Testing MDCX4_PT3
+Dummy packets: 1
+Detected packet duration: 40
+Requested packetization period not set
+Connection mode: 3: SEND RECV
+Testing MDCX4_SO
+Detected packet duration: 40
+Requested packetetization period: 20-20
+Connection mode: 2: SEND
+Testing MDCX4_RO
+Dummy packets: 1
+Packet duration not set
+Requested packetetization period: 20-20
+Connection mode: 1: RECV
+Testing DLCX
+Detected packet duration: 20
+Requested packetization period not set
+Connection mode: 0: NONE
+Testing CRCX_ZYN
+Dummy packets: 1
+Packet duration not set
+Requested packetization period not set
+Connection mode: 1: RECV
+Testing EMPTY
+Testing SHORT1
+Testing SHORT2
+Testing SHORT3
+Testing SHORT4
+Testing RQNT1
+Testing RQNT2
+Testing DLCX
+Detected packet duration: 20
+Requested packetization period not set
+Connection mode: 0: NONE
+Testing CRCX
+Dummy packets: 1
+Detected packet duration: 40
+Requested packetetization period: 20-20
+Connection mode: 1: RECV
+Testing MDCX3
+Dummy packets: 1
+Packet duration not set
+Requested packetization period not set
+Connection mode not set
+Testing DLCX
+Detected packet duration: 20
+Requested packetization period not set
+Connection mode: 0: NONE
+Testing CRCX
+Re-transmitting CRCX
+Testing RQNT1
+Re-transmitting RQNT1
+Testing RQNT2
+Re-transmitting RQNT2
+Testing MDCX3
+Re-transmitting MDCX3
+Testing DLCX
+Re-transmitting DLCX
+Testing packet loss calculation.
+Testing stat parsing
+Parsing result: 0
+Parsing result: 0
+Testing packet error detection, patch SSRC.
+Output SSRC changed to 11223344
+In TS: 0, dTS: 0, Seq: 0
+Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 160, dTS: 160, Seq: 1
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 2
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 3
+Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 10, Transit = 160
+In TS: 480, dTS: 160, Seq: 4
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 9, Transit = 160
+In TS: 640, dTS: 160, Seq: 5
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 8, Transit = 160
+In TS: 960, dTS: 320, Seq: 6
+Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 18, Transit = 0
+In TS: 1120, dTS: 160, Seq: 7
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 17, Transit = 0
+In TS: 1280, dTS: 160, Seq: 8
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 0
+In TS: 1400, dTS: 120, Seq: 9
+Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1
+Stats: Jitter = 17, Transit = 40
+In TS: 1560, dTS: 160, Seq: 10
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 40
+In TS: 1720, dTS: 160, Seq: 11
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 15, Transit = 40
+In TS: 34688, dTS: 0, Seq: 12
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 34848, dTS: 160, Seq: 13
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35008, dTS: 160, Seq: 14
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35128, dTS: 120, Seq: 15
+Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1
+Stats: Jitter = 2, Transit = -32728
+In TS: 35288, dTS: 160, Seq: 16
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35448, dTS: 160, Seq: 17
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35768, dTS: 160, Seq: 19
+Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0
+Stats: Jitter = 12, Transit = -32888
+In TS: 35928, dTS: 160, Seq: 20
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 11, Transit = -32888
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 20, Transit = -33048
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 29, Transit = -32888
+In TS: 36248, dTS: 160, Seq: 22
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 27, Transit = -32888
+In TS: 36408, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 25, Transit = -32888
+In TS: 36568, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1
+Stats: Jitter = 24, Transit = -32888
+In TS: 36728, dTS: 160, Seq: 24
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 22, Transit = -32888
+In TS: 36888, dTS: 160, Seq: 25
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 21, Transit = -32888
+In TS: 160000, dTS: 0, Seq: 1000
+Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160160, dTS: 160, Seq: 1001
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160320, dTS: 160, Seq: 1002
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+Testing packet error detection.
+Output SSRC changed to 11223344
+In TS: 0, dTS: 0, Seq: 0
+Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 160, dTS: 160, Seq: 1
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 2
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 3
+Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 10, Transit = 160
+In TS: 480, dTS: 160, Seq: 4
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 9, Transit = 160
+In TS: 640, dTS: 160, Seq: 5
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 8, Transit = 160
+In TS: 960, dTS: 320, Seq: 6
+Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 18, Transit = 0
+In TS: 1120, dTS: 160, Seq: 7
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 17, Transit = 0
+In TS: 1280, dTS: 160, Seq: 8
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 0
+In TS: 1400, dTS: 120, Seq: 9
+Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1
+Stats: Jitter = 17, Transit = 40
+In TS: 1560, dTS: 160, Seq: 10
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 40
+In TS: 1720, dTS: 160, Seq: 11
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 15, Transit = 40
+Output SSRC changed to 10203040
+In TS: 34688, dTS: 0, Seq: 12
+Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 34848, dTS: 160, Seq: 13
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35008, dTS: 160, Seq: 14
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35128, dTS: 120, Seq: 15
+Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1
+Stats: Jitter = 2, Transit = -32728
+In TS: 35288, dTS: 160, Seq: 16
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35448, dTS: 160, Seq: 17
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35768, dTS: 160, Seq: 19
+Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0
+Stats: Jitter = 12, Transit = -32888
+In TS: 35928, dTS: 160, Seq: 20
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 11, Transit = -32888
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 20, Transit = -33048
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 29, Transit = -32888
+In TS: 36248, dTS: 160, Seq: 22
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 27, Transit = -32888
+In TS: 36408, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 25, Transit = -32888
+In TS: 36568, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1
+Stats: Jitter = 24, Transit = -32888
+In TS: 36728, dTS: 160, Seq: 24
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 22, Transit = -32888
+In TS: 36888, dTS: 160, Seq: 25
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 21, Transit = -32888
+Output SSRC changed to 50607080
+In TS: 160000, dTS: 0, Seq: 1000
+Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160160, dTS: 160, Seq: 1001
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160320, dTS: 160, Seq: 1002
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+Testing packet error detection, patch timestamps.
+Output SSRC changed to 11223344
+In TS: 0, dTS: 0, Seq: 0
+Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 160, dTS: 160, Seq: 1
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 2
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 3
+Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 10, Transit = 160
+In TS: 480, dTS: 160, Seq: 4
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 9, Transit = 160
+In TS: 640, dTS: 160, Seq: 5
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 8, Transit = 160
+In TS: 960, dTS: 320, Seq: 6
+Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 18, Transit = 0
+In TS: 1120, dTS: 160, Seq: 7
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 17, Transit = 0
+In TS: 1280, dTS: 160, Seq: 8
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 0
+In TS: 1400, dTS: 120, Seq: 9
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0
+Stats: Jitter = 17, Transit = 40
+In TS: 1560, dTS: 160, Seq: 10
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 40
+In TS: 1720, dTS: 160, Seq: 11
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 15, Transit = 40
+Output SSRC changed to 10203040
+In TS: 34688, dTS: 0, Seq: 12
+Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 34848, dTS: 160, Seq: 13
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35008, dTS: 160, Seq: 14
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35128, dTS: 120, Seq: 15
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35288, dTS: 160, Seq: 16
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35448, dTS: 160, Seq: 17
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35768, dTS: 160, Seq: 19
+Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0
+Stats: Jitter = 12, Transit = -32888
+In TS: 35928, dTS: 160, Seq: 20
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 11, Transit = -32888
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 20, Transit = -33048
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 29, Transit = -32888
+In TS: 36248, dTS: 160, Seq: 22
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 27, Transit = -32888
+In TS: 36408, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 25, Transit = -32888
+In TS: 36568, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1
+Stats: Jitter = 24, Transit = -32888
+In TS: 36728, dTS: 160, Seq: 24
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 22, Transit = -32888
+In TS: 36888, dTS: 160, Seq: 25
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 21, Transit = -32888
+Output SSRC changed to 50607080
+In TS: 160000, dTS: 0, Seq: 1000
+Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160160, dTS: 160, Seq: 1001
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160320, dTS: 160, Seq: 1002
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+Testing packet error detection, patch SSRC, patch timestamps.
+Output SSRC changed to 11223344
+In TS: 0, dTS: 0, Seq: 0
+Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 160, dTS: 160, Seq: 1
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 2
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = 0
+In TS: 320, dTS: 160, Seq: 3
+Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 10, Transit = 160
+In TS: 480, dTS: 160, Seq: 4
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 9, Transit = 160
+In TS: 640, dTS: 160, Seq: 5
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 8, Transit = 160
+In TS: 960, dTS: 320, Seq: 6
+Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 18, Transit = 0
+In TS: 1120, dTS: 160, Seq: 7
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 17, Transit = 0
+In TS: 1280, dTS: 160, Seq: 8
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 0
+In TS: 1400, dTS: 120, Seq: 9
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0
+Stats: Jitter = 17, Transit = 40
+In TS: 1560, dTS: 160, Seq: 10
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 16, Transit = 40
+In TS: 1720, dTS: 160, Seq: 11
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 15, Transit = 40
+In TS: 34688, dTS: 0, Seq: 12
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 34848, dTS: 160, Seq: 13
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35008, dTS: 160, Seq: 14
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -32768
+In TS: 35128, dTS: 120, Seq: 15
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35288, dTS: 160, Seq: 16
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35448, dTS: 160, Seq: 17
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 2, Transit = -32728
+In TS: 35768, dTS: 160, Seq: 19
+Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0
+Stats: Jitter = 12, Transit = -32888
+In TS: 35928, dTS: 160, Seq: 20
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 11, Transit = -32888
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 20, Transit = -33048
+In TS: 36088, dTS: 160, Seq: 21
+Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0
+Stats: Jitter = 29, Transit = -32888
+In TS: 36248, dTS: 160, Seq: 22
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 27, Transit = -32888
+In TS: 36408, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 25, Transit = -32888
+In TS: 36568, dTS: 160, Seq: 23
+Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1
+Stats: Jitter = 24, Transit = -32888
+In TS: 36728, dTS: 160, Seq: 24
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 22, Transit = -32888
+In TS: 36888, dTS: 160, Seq: 25
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 21, Transit = -32888
+In TS: 160000, dTS: 0, Seq: 1000
+Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160160, dTS: 160, Seq: 1001
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+In TS: 160320, dTS: 160, Seq: 1002
+Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0
+Stats: Jitter = 0, Transit = -144000
+Testing multiple payload types
+Testing no sequence flow on initial packet
+Testing no rtpmap name
+Done
diff --git a/openbsc/tests/mgcp/mgcp_transcoding_test.c b/openbsc/tests/mgcp/mgcp_transcoding_test.c
new file mode 100644
index 0000000..c5c0a0b
--- /dev/null
+++ b/openbsc/tests/mgcp/mgcp_transcoding_test.c
@@ -0,0 +1,654 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+#include <stdint.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <osmocom/netif/rtp.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"
+
+uint8_t *audio_frame_l16[] = {
+};
+
+struct rtp_packets {
+	float t;
+	int len;
+	char *data;
+};
+
+struct rtp_packets audio_packets_l16[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 332,
+		"\x80\x0B\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+	},
+};
+
+struct rtp_packets audio_packets_gsm[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 45,
+		"\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B"
+		"\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A"
+		"\xDE"
+	},
+};
+
+struct rtp_packets audio_packets_gsm_invalid_size[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 41,
+		"\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B"
+		"\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A"
+		"\xDE"
+	},
+};
+
+struct rtp_packets audio_packets_gsm_invalid_data[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 45,
+		"\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE"
+		"\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE"
+		"\xEE"
+	},
+};
+
+struct rtp_packets audio_packets_gsm_invalid_ptype[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 45,
+		"\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B"
+		"\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A"
+		"\xDE"
+	},
+};
+
+struct rtp_packets audio_packets_g729[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 32,
+		"\x80\x12\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xAF\xC2\x81\x40\x00\xFA\xCE\xA4\x21\x7C\xC5\xC3\x4F\xA5\x98\xF5"
+		"\xB2\x95\xC4\xAD"
+	},
+};
+
+struct rtp_packets audio_packets_pcma[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 172,
+		"\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+	},
+	/* RTP: SeqNo=26527, TS=232640 */
+	{0.020000, 92,
+		"\x80\x08\x67\x9f\x00\x03\x8c\xc0\x04\xaa\x67\x9f\xd5\xd5\xd5\xd5"
+		"\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5"
+		"\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5"
+		"\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5"
+		"\xd5\xd5\xd5\xd5\xd5\xd5\x55\x55\xd5\xd5\x55\x55\xd5\xd5\x55\x55"
+		"\xd5\xd5\xd5\x55\x55\xd5\xd5\xd5\x55\x55\xd5\xd5"
+	},
+	/* RTP: SeqNo=26528, TS=232720 */
+	{0.020000, 92,
+		"\x80\x08\x67\xa0\x00\x03\x8d\x10\x04\xaa\x67\x9f\x55\xd5\xd5\x55"
+		"\xd5\x55\xd5\xd5\xd5\x55\xd5\x55\xd5\xd5\x55\xd5\x55\xd5\x55\xd5"
+		"\x55\x55\xd5\x55\xd5\xd5\x55\x55\x55\x55\x55\xd5\xd5\x55\xd5\xd5"
+		"\xd5\x55\xd5\xd5\xd5\x55\x54\x55\xd5\xd5\x55\xd5\xd5\xd5\xd5\x55"
+		"\x54\x55\xd5\x55\xd5\x55\x55\x55\x55\x55\xd5\xd5\xd5\xd5\xd5\xd4"
+		"\xd5\x54\x55\xd5\xd4\xd5\x54\xd5\x55\xd5\xd5\xd5"
+	},
+};
+
+
+
+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);
+
+static int given_configured_endpoint(int in_samples, int out_samples,
+				const char *srcfmt, const char *dstfmt,
+				void **out_ctx, struct mgcp_endpoint **out_endp)
+{
+	int rc;
+	struct mgcp_rtp_end *dst_end;
+	struct mgcp_rtp_end *src_end;
+	struct mgcp_config *cfg;
+	struct mgcp_trunk_config *tcfg;
+	struct mgcp_endpoint *endp;
+
+	cfg = mgcp_config_alloc();
+	tcfg = talloc_zero(cfg, struct mgcp_trunk_config);
+	endp = talloc_zero(tcfg, struct mgcp_endpoint);
+
+	cfg->setup_rtp_processing_cb = mgcp_transcoding_setup;
+	cfg->rtp_processing_cb = mgcp_transcoding_process_rtp;
+	cfg->get_net_downlink_format_cb = mgcp_transcoding_net_downlink_format;
+
+	tcfg->endpoints = endp;
+	tcfg->number_endpoints = 1;
+	tcfg->cfg = cfg;
+	endp->tcfg = tcfg;
+	endp->cfg = cfg;
+	mgcp_initialize_endp(endp);
+
+	dst_end = &endp->bts_end;
+	dst_end->codec.payload_type = audio_name_to_type(dstfmt);
+
+	src_end = &endp->net_end;
+	src_end->codec.payload_type = audio_name_to_type(srcfmt);
+
+	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;
+		dst_end->force_output_ptime = 1;
+	}
+
+	rc = mgcp_transcoding_setup(endp, dst_end, src_end);
+	if (rc < 0) {
+		printf("setup failed: %s", strerror(-rc));
+		abort();
+	}
+
+	*out_ctx = cfg;
+	*out_endp = endp;
+	return 0;
+}
+
+
+static int transcode_test(const char *srcfmt, const char *dstfmt,
+			  uint8_t *src_pkts, size_t src_pkt_size)
+{
+	char buf[4096] = {0x80, 0};
+	void *ctx;
+
+	struct mgcp_rtp_end *dst_end;
+	struct mgcp_process_rtp_state *state;
+	struct mgcp_endpoint *endp;
+	int in_size;
+	const int in_samples = 160;
+	int len, cont;
+
+	printf("== Transcoding test ==\n");
+	printf("converting %s -> %s\n", srcfmt, dstfmt);
+
+	given_configured_endpoint(in_samples, 0, srcfmt, dstfmt, &ctx, &endp);
+
+	dst_end = &endp->bts_end;
+	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);
+
+	memcpy(buf, src_pkts, src_pkt_size);
+
+	len = src_pkt_size;
+
+	cont = mgcp_transcoding_process_rtp(endp, dst_end,
+					    buf, &len, sizeof(buf));
+	if (cont < 0) {
+		printf("Nothing encoded due: %s\n", strerror(-cont));
+		talloc_free(ctx);
+		return -1;
+	}
+
+	if (len < 24) {
+		printf("encoded: %s\n", osmo_hexdump((unsigned char *)buf, len));
+	} else {
+		const char *str = osmo_hexdump((unsigned char *)buf, len);
+		int i = 0;
+		const int prefix = 4;
+		const int cutlen = 48;
+		int nchars = 0;
+
+		printf("encoded:\n");
+		do {
+			nchars = printf("%*s%-.*s", prefix, "", cutlen, str + i);
+			i += nchars - prefix;
+			printf("\n");
+		} while (nchars - prefix >= cutlen);
+	}
+	printf("counted: %d\n", cont);
+	talloc_free(ctx);
+	return 0;
+}
+
+static void test_rtp_seq_state(void)
+{
+	char buf[4096];
+	int len;
+	int cont;
+	void *ctx;
+	struct mgcp_endpoint *endp;
+	struct mgcp_process_rtp_state *state;
+	struct rtp_hdr *hdr;
+	uint32_t ts_no;
+	uint16_t seq_no;
+
+	given_configured_endpoint(160, 0, "pcma", "l16", &ctx, &endp);
+	state = endp->bts_end.rtp_process_data;
+	OSMO_ASSERT(!state->is_running);
+	OSMO_ASSERT(state->next_seq == 0);
+	OSMO_ASSERT(state->next_time == 0);
+
+	/* initialize packet */
+	len = audio_packets_pcma[0].len;
+	memcpy(buf, audio_packets_pcma[0].data, len);
+	cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, len);
+	OSMO_ASSERT(cont >= 0);
+	OSMO_ASSERT(state->is_running);
+	OSMO_ASSERT(state->next_seq == 2);
+	OSMO_ASSERT(state->next_time == 240);
+
+	/* verify that the right timestamp was written */
+	OSMO_ASSERT(len == audio_packets_pcma[0].len);
+	hdr = (struct rtp_hdr *) &buf[0];
+
+	memcpy(&ts_no, &hdr->timestamp, sizeof(ts_no));
+	OSMO_ASSERT(htonl(ts_no) == 160);
+	memcpy(&seq_no, &hdr->sequence, sizeof(seq_no));
+	OSMO_ASSERT(htons(seq_no) == 1);
+	/* Check the right sequence number is written */
+	state->next_seq = 1234;
+	len = audio_packets_pcma[0].len;
+	memcpy(buf, audio_packets_pcma[0].data, len);
+	cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, len);
+	OSMO_ASSERT(cont >= 0);
+	OSMO_ASSERT(len == audio_packets_pcma[0].len);
+	hdr = (struct rtp_hdr *) &buf[0];
+
+	memcpy(&seq_no, &hdr->sequence, sizeof(seq_no));
+	OSMO_ASSERT(htons(seq_no) == 1234);
+
+	talloc_free(ctx);
+}
+
+static void test_transcode_result(void)
+{
+	char buf[4096];
+	int len, res;
+	void *ctx;
+	struct mgcp_endpoint *endp;
+	struct mgcp_process_rtp_state *state;
+
+	{
+		/* from GSM to PCMA and same ptime */
+		given_configured_endpoint(160, 0, "gsm", "pcma", &ctx, &endp);
+		state = endp->bts_end.rtp_process_data;
+
+		/* result */
+		len = audio_packets_gsm[0].len;
+		memcpy(buf, audio_packets_gsm[0].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == sizeof(struct rtp_hdr));
+		OSMO_ASSERT(state->sample_cnt == 0);
+
+		len = res;
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == -ENOMSG);
+
+		talloc_free(ctx);
+	}
+
+	{
+		/* from GSM to PCMA and same ptime */
+		given_configured_endpoint(160, 160, "gsm", "pcma", &ctx, &endp);
+		state = endp->bts_end.rtp_process_data;
+
+		/* result */
+		len = audio_packets_gsm[0].len;
+		memcpy(buf, audio_packets_gsm[0].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == sizeof(struct rtp_hdr));
+		OSMO_ASSERT(state->sample_cnt == 0);
+
+		len = res;
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == -EAGAIN);
+
+		talloc_free(ctx);
+	}
+
+	{
+		/* from PCMA to GSM and wrong different ptime */
+		given_configured_endpoint(80, 160, "pcma", "gsm", &ctx, &endp);
+		state = endp->bts_end.rtp_process_data;
+
+		/* Add the first sample */
+		len = audio_packets_pcma[1].len;
+		memcpy(buf, audio_packets_pcma[1].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(state->sample_cnt == 80);
+		OSMO_ASSERT(state->next_time == 232640);
+		OSMO_ASSERT(res < 0);
+
+		/* Add the second sample and it should be consumable */
+		len = audio_packets_pcma[2].len;
+		memcpy(buf, audio_packets_pcma[2].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(state->sample_cnt == 0);
+		OSMO_ASSERT(state->next_time == 232640 + 80 + 160);
+		OSMO_ASSERT(res == sizeof(struct rtp_hdr));
+
+		talloc_free(ctx);
+	}
+
+	{
+		/* from PCMA to GSM with a big time jump */
+		struct rtp_hdr *hdr;
+		uint32_t ts;
+
+		given_configured_endpoint(80, 160, "pcma", "gsm", &ctx, &endp);
+		state = endp->bts_end.rtp_process_data;
+
+		/* Add the first sample */
+		len = audio_packets_pcma[1].len;
+		memcpy(buf, audio_packets_pcma[1].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(state->sample_cnt == 80);
+		OSMO_ASSERT(state->next_time == 232640);
+		OSMO_ASSERT(state->next_seq == 26527);
+		OSMO_ASSERT(res < 0);
+
+		/* Add a skip to the packet to force a 'resync' */
+		len = audio_packets_pcma[2].len;
+		memcpy(buf, audio_packets_pcma[2].data, len);
+		hdr = (struct rtp_hdr *) &buf[0];
+		/* jump the time and add alignment error */
+		ts = ntohl(hdr->timestamp) + 123 * 80 + 2;
+		hdr->timestamp = htonl(ts);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res < 0);
+		OSMO_ASSERT(state->sample_cnt == 80);
+		OSMO_ASSERT(state->next_time == ts);
+		OSMO_ASSERT(state->next_seq == 26527);
+		/* TODO: this can create alignment errors */
+
+
+		/* Now attempt to consume 160 samples */
+		len = audio_packets_pcma[2].len;
+		memcpy(buf, audio_packets_pcma[2].data, len);
+		hdr = (struct rtp_hdr *) &buf[0];
+		ts += 80;
+		hdr->timestamp = htonl(ts);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == 12);
+		OSMO_ASSERT(state->sample_cnt == 0);
+		OSMO_ASSERT(state->next_time == ts + 160);
+		OSMO_ASSERT(state->next_seq == 26528);
+
+		talloc_free(ctx);
+	}
+}
+
+static void test_transcode_change(void)
+{
+	char buf[4096] = {0x80, 0};
+	void *ctx;
+
+	struct mgcp_endpoint *endp;
+	struct mgcp_process_rtp_state *state;
+	struct rtp_hdr *hdr;
+
+	int len, res;
+
+	{
+		/* from GSM to PCMA and same ptime */
+		printf("Testing Initial L16->GSM, PCMA->GSM\n");
+		given_configured_endpoint(160, 0, "l16", "gsm", &ctx, &endp);
+		endp->net_end.alt_codec = endp->net_end.codec;
+		endp->net_end.alt_codec.payload_type = audio_name_to_type("pcma");
+		state = endp->bts_end.rtp_process_data;
+
+		/* initial transcoding work */
+		OSMO_ASSERT(state->src_fmt == AF_L16);
+		OSMO_ASSERT(state->dst_fmt == AF_GSM);
+		OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 8);
+		OSMO_ASSERT(endp->net_end.codec.payload_type == 11);
+
+		/* result */
+		len = audio_packets_pcma[0].len;
+		memcpy(buf, audio_packets_pcma[0].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		state = endp->bts_end.rtp_process_data;
+		OSMO_ASSERT(res == sizeof(struct rtp_hdr));
+		OSMO_ASSERT(state->sample_cnt == 0);
+		OSMO_ASSERT(state->src_fmt == AF_PCMA);
+		OSMO_ASSERT(state->dst_fmt == AF_GSM);
+		OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 11);
+		OSMO_ASSERT(endp->net_end.codec.payload_type == 8);
+
+		len = res;
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == -ENOMSG);
+		OSMO_ASSERT(state == endp->bts_end.rtp_process_data);
+
+
+		/* now check that comfort noise doesn't change anything */
+		len = audio_packets_pcma[1].len;
+		memcpy(buf, audio_packets_pcma[1].data, len);
+		hdr = (struct rtp_hdr *) buf;
+		hdr->payload_type = 12;
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(state == endp->bts_end.rtp_process_data);
+		OSMO_ASSERT(state->sample_cnt == 80);
+		OSMO_ASSERT(state->src_fmt == AF_PCMA);
+		OSMO_ASSERT(state->dst_fmt == AF_GSM);
+		OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 11);
+		OSMO_ASSERT(endp->net_end.codec.payload_type == 8);
+
+		talloc_free(ctx);
+	}
+}
+
+static int test_repacking(int in_samples, int out_samples, int no_transcode)
+{
+	char buf[4096] = {0x80, 0};
+	int cc;
+	struct mgcp_endpoint *endp;
+	void *ctx;
+
+	struct mgcp_process_rtp_state *state;
+	int in_cnt;
+	int out_size;
+	int in_size;
+	uint32_t ts = 0;
+	uint16_t seq = 0;
+	const char *srcfmt = "pcma";
+	const char *dstfmt = no_transcode ? "pcma" : "l16";
+
+	printf("== Transcoding test ==\n");
+	printf("converting %s -> %s\n", srcfmt, dstfmt);
+
+	given_configured_endpoint(in_samples, out_samples, srcfmt, dstfmt, &ctx, &endp);
+
+	state = endp->bts_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);
+
+	out_size = mgcp_transcoding_get_frame_size(state, -1, 1);
+	OSMO_ASSERT(sizeof(buf) >= out_size + 12);
+
+	buf[1] = endp->net_end.codec.payload_type;
+	*(uint16_t*)(buf+2) = htons(1);
+	*(uint32_t*)(buf+4) = htonl(0);
+	*(uint32_t*)(buf+8) = htonl(0xaabbccdd);
+
+	for (in_cnt = 0; in_cnt < 16; in_cnt++) {
+		int cont;
+		int len;
+
+		/* fake PCMA data */
+		printf("generating %d %s input samples\n", in_samples, srcfmt);
+		for (cc = 0; cc < in_samples; cc++)
+			buf[12+cc] = cc;
+
+		*(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, &endp->bts_end,
+							    buf, &len, sizeof(buf));
+			if (cont == -EAGAIN) {
+				fprintf(stderr, "Got EAGAIN\n");
+				break;
+			}
+
+			if (cont < 0) {
+				printf("processing failed: %s", strerror(-cont));
+				abort();
+			}
+
+			len -= 12; /* ignore RTP header */
+
+			printf("got %d %s output frames (%d octets) count=%d\n",
+			       len / out_size, dstfmt, len, cont);
+
+			len = cont;
+		} while (len > 0);
+	}
+
+	talloc_free(ctx);
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int rc;
+	osmo_init_logging(&log_info);
+
+	printf("=== Transcoding Good Cases ===\n");
+
+	transcode_test("l16", "l16",
+		       (uint8_t *)audio_packets_l16[0].data,
+		       audio_packets_l16[0].len);
+	transcode_test("l16", "gsm",
+		       (uint8_t *)audio_packets_l16[0].data,
+		       audio_packets_l16[0].len);
+	transcode_test("l16", "pcma",
+		       (uint8_t *)audio_packets_l16[0].data,
+		       audio_packets_l16[0].len);
+	transcode_test("gsm", "l16",
+		       (uint8_t *)audio_packets_gsm[0].data,
+		       audio_packets_gsm[0].len);
+	transcode_test("gsm", "gsm",
+		       (uint8_t *)audio_packets_gsm[0].data,
+		       audio_packets_gsm[0].len);
+	transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm[0].data,
+		       audio_packets_gsm[0].len);
+	transcode_test("pcma", "l16",
+		       (uint8_t *)audio_packets_pcma[0].data,
+		       audio_packets_pcma[0].len);
+	transcode_test("pcma", "gsm",
+		       (uint8_t *)audio_packets_pcma[0].data,
+		       audio_packets_pcma[0].len);
+	transcode_test("pcma", "pcma",
+		       (uint8_t *)audio_packets_pcma[0].data,
+		       audio_packets_pcma[0].len);
+
+	printf("=== Transcoding Bad Cases ===\n");
+
+	printf("Invalid size:\n");
+	rc = transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm_invalid_size[0].data,
+		       audio_packets_gsm_invalid_size[0].len);
+	OSMO_ASSERT(rc < 0);
+
+	printf("Invalid data:\n");
+	rc = transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm_invalid_data[0].data,
+		       audio_packets_gsm_invalid_data[0].len);
+	OSMO_ASSERT(rc < 0);
+
+	printf("Invalid payload type:\n");
+	rc = transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm_invalid_ptype[0].data,
+		       audio_packets_gsm_invalid_ptype[0].len);
+	OSMO_ASSERT(rc == 0);
+
+	printf("=== Repacking ===\n");
+
+	test_repacking(160, 160, 0);
+	test_repacking(160, 160, 1);
+	test_repacking(160, 80, 0);
+	test_repacking(160, 80, 1);
+	test_repacking(160, 320, 0);
+	test_repacking(160, 320, 1);
+	test_repacking(160, 240, 0);
+	test_repacking(160, 240, 1);
+	test_repacking(160, 100, 0);
+	test_repacking(160, 100, 1);
+	test_rtp_seq_state();
+	test_transcode_result();
+	test_transcode_change();
+
+	return 0;
+}
+
diff --git a/openbsc/tests/mgcp/mgcp_transcoding_test.ok b/openbsc/tests/mgcp/mgcp_transcoding_test.ok
new file mode 100644
index 0000000..387cfd2
--- /dev/null
+++ b/openbsc/tests/mgcp/mgcp_transcoding_test.ok
@@ -0,0 +1,539 @@
+=== Transcoding Good Cases ===
+== Transcoding test ==
+converting l16 -> l16
+encoded:
+    80 0b 00 01 00 00 00 a0 11 22 33 44 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 
+counted: 0
+== Transcoding test ==
+converting l16 -> gsm
+encoded:
+    80 0b 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 
+    62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc 
+    69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de 
+counted: 12
+== Transcoding test ==
+converting l16 -> pcma
+encoded:
+    80 0b 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 
+counted: 12
+== Transcoding test ==
+converting gsm -> l16
+encoded:
+    80 03 00 01 00 00 00 a0 11 22 33 44 00 00 54 00 
+    59 f0 34 20 c4 c8 b9 f8 e2 18 f1 e8 f2 28 f0 e0 
+    46 08 4f 80 2c a0 a9 c8 80 00 c0 58 3f 80 63 c0 
+    24 b8 fa b8 f6 88 0b a0 c8 70 a8 b0 c8 c0 3b a8 
+    66 a0 2e 38 d1 f8 98 98 aa 18 e8 30 26 a0 37 40 
+    37 e8 17 00 ee 50 b7 80 b1 88 de 28 18 40 45 b0 
+    4f 48 21 d8 df 78 ae 68 ab 98 d6 b8 18 18 48 90 
+    4e 70 27 40 e8 10 b5 b0 ac 80 d4 60 14 50 48 48 
+    50 10 2a 00 ec 08 ba 00 af 58 d1 c0 10 60 45 c8 
+    54 10 30 78 f1 a8 bb 18 ad 48 ce 30 0a e8 3f 30 
+    4f 10 32 18 f6 18 bf 20 ac 30 cd 80 0b d0 43 d8 
+    55 e0 34 a0 f5 78 bc 98 ad 98 cd c8 0a 80 40 58 
+    51 c0 35 40 f9 60 c1 68 ac c8 cb 38 08 00 40 98 
+    51 e0 34 d8 fa 28 c2 f0 ae 40 c7 70 02 d0 3c a8 
+    54 78 38 a0 fc 68 c2 08 ad 50 c7 78 01 60 39 c0 
+    51 38 3a e8 00 e8 c6 38 ab d8 c4 00 fe 08 39 18 
+    50 30 39 50 01 d8 ca 70 b1 80 c4 c8 fc 58 36 40 
+    51 d8 3b 08 02 80 c8 58 b0 60 c5 a8 fb d0 33 e8 
+    4e 80 3c e0 06 10 cb 90 ae 48 c2 60 f9 58 34 08 
+    4d a0 3a a8 06 48 cf 80 b4 60 c3 e8 f7 90 30 18 
+    4d a0 3b 98 07 90 cf 18 b4 68 c4 88 
+counted: 12
+== Transcoding test ==
+converting gsm -> gsm
+encoded:
+    80 03 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 
+    62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc 
+    69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de 
+counted: 0
+== Transcoding test ==
+converting gsm -> pcma
+encoded:
+    80 03 00 01 00 00 00 a0 11 22 33 44 d5 a0 a3 bf 
+    38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 
+    17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 
+    04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 
+    02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd 
+    19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf 
+    10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf 
+    62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 
+    db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 
+    f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 
+    ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 
+counted: 12
+== Transcoding test ==
+converting pcma -> l16
+encoded:
+    80 08 00 01 00 00 00 a0 11 22 33 44 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 
+counted: 12
+== Transcoding test ==
+converting pcma -> gsm
+encoded:
+    80 08 00 01 00 00 00 a0 11 22 33 44 d4 b9 f4 5d 
+    d9 50 5a e1 a0 cd 76 ea 52 0e 87 53 ad d4 ea a2 
+    0a 63 ca e9 60 79 e2 2a 25 d2 c0 f3 39 
+counted: 12
+== Transcoding test ==
+converting pcma -> pcma
+encoded:
+    80 08 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 
+counted: 0
+=== Transcoding Bad Cases ===
+Invalid size:
+== Transcoding test ==
+converting gsm -> pcma
+Nothing encoded due: No message of desired type
+Invalid data:
+== Transcoding test ==
+converting gsm -> pcma
+Nothing encoded due: No message of desired type
+Invalid payload type:
+== Transcoding test ==
+converting gsm -> pcma
+encoded:
+    80 08 00 01 00 00 00 a0 11 22 33 44 d5 a0 a3 bf 
+    38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 
+    17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 
+    04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 
+    02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd 
+    19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf 
+    10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf 
+    62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 
+    db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 
+    f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 
+    ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 
+counted: 12
+=== Repacking ===
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets) count=12
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets) count=12
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets) count=12
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets) count=12
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets) count=12
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets) count=12
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets) count=12
+got 1 l16 output frames (160 octets) count=12
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets) count=12
+got 1 pcma output frames (80 octets) count=12
+Testing Initial L16->GSM, PCMA->GSM
diff --git a/openbsc/tests/mm_auth/Makefile.am b/openbsc/tests/mm_auth/Makefile.am
new file mode 100644
index 0000000..cb35198
--- /dev/null
+++ b/openbsc/tests/mm_auth/Makefile.am
@@ -0,0 +1,37 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBCRYPTO_CFLAGS) \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	mm_auth_test \
+	$(NULL)
+
+EXTRA_DIST = \
+	mm_auth_test.ok \
+	$(NULL)
+
+mm_auth_test_SOURCES = \
+	mm_auth_test.c \
+	$(NULL)
+
+mm_auth_test_LDFLAGS = \
+	-Wl,--wrap=db_get_authinfo_for_subscr \
+	-Wl,--wrap=db_get_lastauthtuple_for_subscr \
+	-Wl,--wrap=db_sync_lastauthtuple_for_subscr \
+	$(NULL)
+
+mm_auth_test_LDADD = \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(NULL)
diff --git a/openbsc/tests/mm_auth/mm_auth_test.c b/openbsc/tests/mm_auth/mm_auth_test.c
new file mode 100644
index 0000000..ebd122f
--- /dev/null
+++ b/openbsc/tests/mm_auth/mm_auth_test.c
@@ -0,0 +1,340 @@
+#include <stdbool.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/auth.h>
+
+#define min(A,B) ((A)>(B)? (B) : (A))
+
+static char *auth_tuple_str(struct gsm_auth_tuple *atuple)
+{
+	static char buf[256];
+	char *pos = buf;
+	int len = sizeof(buf);
+	int l;
+
+#define print2buf(FMT, args...) do {\
+		l = snprintf(pos, len, FMT, ## args); \
+		pos += l;\
+		len -= l;\
+	} while (0)
+
+	print2buf("gsm_auth_tuple {\n");
+	print2buf("  .use_count = %d\n", atuple->use_count);
+	print2buf("  .key_seq = %d\n", atuple->key_seq);
+	print2buf("  .rand = %s\n", osmo_hexdump(atuple->vec.rand, sizeof(atuple->vec.rand)));
+	print2buf("  .sres = %s\n", osmo_hexdump(atuple->vec.sres, sizeof(atuple->vec.sres)));
+	print2buf("  .kc = %s\n", osmo_hexdump(atuple->vec.kc, sizeof(atuple->vec.kc)));
+	print2buf("}\n");
+#undef print2buf
+
+	return buf;
+}
+
+static bool auth_tuple_is(struct gsm_auth_tuple *atuple,
+			  const char *expect_str)
+{
+	int l, l1, l2;
+	int i;
+	char *tuple_str = auth_tuple_str(atuple);
+	bool same = (strcmp(expect_str, tuple_str) == 0);
+	if (!same) {
+		l1 = strlen(expect_str);
+		l2 = strlen(tuple_str);
+		printf("Expected %d:\n%s\nGot %d:\n%s\n",
+		       l1, expect_str, l2, tuple_str);
+		l = min(l1, l2);
+		for (i = 0; i < l; i++) {
+			if (expect_str[i] != tuple_str[i]) {
+				printf("Difference at pos %d"
+				       " (%c 0x%0x != %c 0x%0x)\n",
+				       i, expect_str[i], expect_str[i],
+				       tuple_str[i], tuple_str[i]);
+				break;
+			}
+		}
+	}
+	return same;
+}
+
+/* override, requires '-Wl,--wrap=db_get_authinfo_for_subscr' */
+int __real_db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+				      struct gsm_subscriber *subscr);
+
+int test_get_authinfo_rc = 0;
+struct gsm_auth_info test_auth_info = {0};
+struct gsm_auth_info default_auth_info = {
+	.auth_algo = AUTH_ALGO_COMP128v1,
+	.a3a8_ki_len = 16,
+	.a3a8_ki = { 0 }
+};
+
+int __wrap_db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+				      struct gsm_subscriber *subscr)
+{
+	*ainfo = test_auth_info;
+	printf("wrapped: db_get_authinfo_for_subscr(): rc = %d\n", test_get_authinfo_rc);
+	return test_get_authinfo_rc;
+}
+
+/* override, requires '-Wl,--wrap=db_get_lastauthtuple_for_subscr' */
+int __real_db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+					   struct gsm_subscriber *subscr);
+
+int test_get_lastauthtuple_rc = 0;
+struct gsm_auth_tuple test_last_auth_tuple = { 0 };
+struct gsm_auth_tuple default_auth_tuple = { 0 };
+
+int __wrap_db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+					   struct gsm_subscriber *subscr)
+{
+	*atuple = test_last_auth_tuple;
+	printf("wrapped: db_get_lastauthtuple_for_subscr(): rc = %d\n", test_get_lastauthtuple_rc);
+	return test_get_lastauthtuple_rc;
+}
+
+/* override, requires '-Wl,--wrap=db_sync_lastauthtuple_for_subscr' */
+int __real_db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+					    struct gsm_subscriber *subscr);
+int test_sync_lastauthtuple_rc = 0;
+int __wrap_db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+					    struct gsm_subscriber *subscr)
+{
+	test_last_auth_tuple = *atuple;
+	printf("wrapped: db_sync_lastauthtuple_for_subscr(): rc = %d\n", test_sync_lastauthtuple_rc);
+	return test_sync_lastauthtuple_rc;
+}
+
+int auth_get_tuple_for_subscr_verbose(struct gsm_auth_tuple *atuple,
+				      struct gsm_subscriber *subscr,
+				      int key_seq)
+{
+	int auth_action;
+	auth_action = auth_get_tuple_for_subscr(atuple, subscr, key_seq);
+	printf("auth_get_tuple_for_subscr(key_seq=%d) --> auth_action == %s\n",
+	       key_seq, auth_action_str(auth_action));
+	return auth_action;
+}
+
+/* override libssl RAND_bytes() to get testable crypto results */
+int osmo_get_rand_id(uint8_t *rand, size_t len)
+{
+	memset(rand, 23, len);
+	return 1;
+}
+
+static void test_error()
+{
+	int auth_action;
+
+	struct gsm_auth_tuple atuple = {0};
+	struct gsm_subscriber subscr = {0};
+	int key_seq = 0;
+
+	printf("\n* test_error()\n");
+
+	/* any error (except -ENOENT) */
+	test_get_authinfo_rc = -EIO;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_ERROR);
+}
+
+static void test_auth_not_avail()
+{
+	int auth_action;
+
+	struct gsm_auth_tuple atuple = {0};
+	struct gsm_subscriber subscr = {0};
+	int key_seq = 0;
+
+	printf("\n* test_auth_not_avail()\n");
+
+	/* no entry */
+	test_get_authinfo_rc = -ENOENT;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_NOT_AVAIL);
+}
+
+static void test_auth_then_ciph1()
+{
+	int auth_action;
+
+	struct gsm_auth_tuple atuple = {0};
+	struct gsm_subscriber subscr = {0};
+	int key_seq;
+
+	printf("\n* test_auth_then_ciph1()\n");
+
+	/* Ki entry, but no auth tuple negotiated yet */
+	test_auth_info = default_auth_info;
+	test_last_auth_tuple = default_auth_tuple;
+	test_get_authinfo_rc = 0;
+	test_get_lastauthtuple_rc = -ENOENT;
+	key_seq = 0;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
+	OSMO_ASSERT(auth_tuple_is(&atuple,
+		"gsm_auth_tuple {\n"
+		"  .use_count = 1\n"
+		"  .key_seq = 0\n"
+		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
+		"  .sres = a1 ab c6 90 \n"
+		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
+		"}\n"
+		));
+
+	/* With a different last saved key_seq stored in the out-arg of
+	 * db_get_lastauthtuple_for_subscr() by coincidence, expect absolutely
+	 * the same as above. */
+	test_auth_info = default_auth_info;
+	test_last_auth_tuple = default_auth_tuple;
+	test_last_auth_tuple.key_seq = 3;
+	test_get_authinfo_rc = 0;
+	test_get_lastauthtuple_rc = -ENOENT;
+	key_seq = 0;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
+	OSMO_ASSERT(auth_tuple_is(&atuple,
+		"gsm_auth_tuple {\n"
+		"  .use_count = 1\n"
+		"  .key_seq = 0\n"
+		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
+		"  .sres = a1 ab c6 90 \n"
+		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
+		"}\n"
+		));
+}
+
+static void test_auth_then_ciph2()
+{
+	int auth_action;
+
+	struct gsm_auth_tuple atuple = {0};
+	struct gsm_subscriber subscr = {0};
+	int key_seq;
+
+	printf("\n* test_auth_then_ciph2()\n");
+
+	/* Ki entry, auth tuple negotiated, but invalid incoming key_seq */
+	test_auth_info = default_auth_info;
+	test_last_auth_tuple = default_auth_tuple;
+	test_last_auth_tuple.key_seq = 2;
+	test_get_authinfo_rc = 0;
+	test_get_lastauthtuple_rc = 0;
+	key_seq = GSM_KEY_SEQ_INVAL;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
+	OSMO_ASSERT(auth_tuple_is(&atuple,
+		"gsm_auth_tuple {\n"
+		"  .use_count = 1\n"
+		"  .key_seq = 3\n"
+		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
+		"  .sres = a1 ab c6 90 \n"
+		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
+		"}\n"
+		));
+
+	/* Change the last saved key_seq, expect last_auth_tuple.key_seq + 1 */
+	test_auth_info = default_auth_info;
+	test_last_auth_tuple = default_auth_tuple;
+	test_last_auth_tuple.key_seq = 3;
+	test_get_authinfo_rc = 0;
+	test_get_lastauthtuple_rc = 0;
+	key_seq = GSM_KEY_SEQ_INVAL;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
+	OSMO_ASSERT(auth_tuple_is(&atuple,
+		"gsm_auth_tuple {\n"
+		"  .use_count = 1\n"
+		"  .key_seq = 4\n"
+		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
+		"  .sres = a1 ab c6 90 \n"
+		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
+		"}\n"
+		));
+}
+
+static void test_auth_reuse()
+{
+	int auth_action;
+	struct gsm_auth_tuple atuple = {0};
+	struct gsm_subscriber subscr = {0};
+	int key_seq;
+
+	printf("\n* test_auth_reuse()\n");
+
+	/* Ki entry, auth tuple negotiated, valid+matching incoming key_seq */
+	test_auth_info = default_auth_info;
+	test_last_auth_tuple = default_auth_tuple;
+	test_last_auth_tuple.key_seq = key_seq = 3;
+	test_last_auth_tuple.use_count = 1;
+	test_get_authinfo_rc = 0;
+	test_get_lastauthtuple_rc = 0;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_DO_CIPH);
+	OSMO_ASSERT(auth_tuple_is(&atuple,
+		"gsm_auth_tuple {\n"
+		"  .use_count = 2\n"
+		"  .key_seq = 3\n"
+		"  .rand = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \n"
+		"  .sres = 00 00 00 00 \n"
+		"  .kc = 00 00 00 00 00 00 00 00 \n"
+		"}\n"
+		));
+}
+
+static void test_auth_reuse_key_seq_mismatch()
+{
+	int auth_action;
+	struct gsm_auth_tuple atuple = {0};
+	struct gsm_subscriber subscr = {0};
+	int key_seq;
+
+	printf("\n* test_auth_reuse_key_seq_mismatch()\n");
+
+	/* Ki entry, auth tuple negotiated, valid+matching incoming key_seq */
+	test_auth_info = default_auth_info;
+	test_last_auth_tuple = default_auth_tuple;
+	test_last_auth_tuple.key_seq = 3;
+	key_seq = 4;
+	test_last_auth_tuple.use_count = 1;
+	test_get_authinfo_rc = 0;
+	test_get_lastauthtuple_rc = 0;
+	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
+							key_seq);
+	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
+	OSMO_ASSERT(auth_tuple_is(&atuple,
+		"gsm_auth_tuple {\n"
+		"  .use_count = 1\n"
+		"  .key_seq = 4\n"
+		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
+		"  .sres = a1 ab c6 90 \n"
+		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
+		"}\n"
+		));
+}
+
+int main(void)
+{
+	osmo_init_logging(&log_info);
+	log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+	test_error();
+	test_auth_not_avail();
+	test_auth_then_ciph1();
+	test_auth_then_ciph2();
+	test_auth_reuse();
+	test_auth_reuse_key_seq_mismatch();
+	return 0;
+}
diff --git a/openbsc/tests/mm_auth/mm_auth_test.ok b/openbsc/tests/mm_auth/mm_auth_test.ok
new file mode 100644
index 0000000..6c49f97
--- /dev/null
+++ b/openbsc/tests/mm_auth/mm_auth_test.ok
@@ -0,0 +1,40 @@
+
+* test_error()
+wrapped: db_get_authinfo_for_subscr(): rc = -5
+auth_get_tuple_for_subscr(key_seq=0) --> auth_action == AUTH_ERROR
+
+* test_auth_not_avail()
+wrapped: db_get_authinfo_for_subscr(): rc = -2
+auth_get_tuple_for_subscr(key_seq=0) --> auth_action == AUTH_NOT_AVAIL
+
+* test_auth_then_ciph1()
+wrapped: db_get_authinfo_for_subscr(): rc = 0
+wrapped: db_get_lastauthtuple_for_subscr(): rc = -2
+wrapped: db_sync_lastauthtuple_for_subscr(): rc = 0
+auth_get_tuple_for_subscr(key_seq=0) --> auth_action == AUTH_DO_AUTH_THEN_CIPH
+wrapped: db_get_authinfo_for_subscr(): rc = 0
+wrapped: db_get_lastauthtuple_for_subscr(): rc = -2
+wrapped: db_sync_lastauthtuple_for_subscr(): rc = 0
+auth_get_tuple_for_subscr(key_seq=0) --> auth_action == AUTH_DO_AUTH_THEN_CIPH
+
+* test_auth_then_ciph2()
+wrapped: db_get_authinfo_for_subscr(): rc = 0
+wrapped: db_get_lastauthtuple_for_subscr(): rc = 0
+wrapped: db_sync_lastauthtuple_for_subscr(): rc = 0
+auth_get_tuple_for_subscr(key_seq=7) --> auth_action == AUTH_DO_AUTH_THEN_CIPH
+wrapped: db_get_authinfo_for_subscr(): rc = 0
+wrapped: db_get_lastauthtuple_for_subscr(): rc = 0
+wrapped: db_sync_lastauthtuple_for_subscr(): rc = 0
+auth_get_tuple_for_subscr(key_seq=7) --> auth_action == AUTH_DO_AUTH_THEN_CIPH
+
+* test_auth_reuse()
+wrapped: db_get_authinfo_for_subscr(): rc = 0
+wrapped: db_get_lastauthtuple_for_subscr(): rc = 0
+wrapped: db_sync_lastauthtuple_for_subscr(): rc = 0
+auth_get_tuple_for_subscr(key_seq=3) --> auth_action == AUTH_DO_CIPH
+
+* test_auth_reuse_key_seq_mismatch()
+wrapped: db_get_authinfo_for_subscr(): rc = 0
+wrapped: db_get_lastauthtuple_for_subscr(): rc = 0
+wrapped: db_sync_lastauthtuple_for_subscr(): rc = 0
+auth_get_tuple_for_subscr(key_seq=4) --> auth_action == AUTH_DO_AUTH_THEN_CIPH
diff --git a/openbsc/tests/nanobts_omlattr/Makefile.am b/openbsc/tests/nanobts_omlattr/Makefile.am
new file mode 100644
index 0000000..b03d50c
--- /dev/null
+++ b/openbsc/tests/nanobts_omlattr/Makefile.am
@@ -0,0 +1,34 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	nanobts_omlattr_test \
+	$(NULL)
+
+EXTRA_DIST = \
+	nanobts_omlattr_test.ok \
+	$(NULL)
+
+nanobts_omlattr_test_SOURCES = \
+	nanobts_omlattr_test.c \
+	$(NULL)
+
+nanobts_omlattr_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	-ldbi \
+	$(NULL)
diff --git a/openbsc/tests/nanobts_omlattr/nanobts_omlattr_test.c b/openbsc/tests/nanobts_omlattr/nanobts_omlattr_test.c
new file mode 100644
index 0000000..e34b19b
--- /dev/null
+++ b/openbsc/tests/nanobts_omlattr/nanobts_omlattr_test.c
@@ -0,0 +1,286 @@
+/* Test OML attribute generator */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/bts_ipaccess_nanobts_omlattr.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/application.h>
+
+#include <stdio.h>
+#include <string.h>
+
+struct gsm_bts_model bts_model_nanobts = {
+	.type = GSM_BTS_TYPE_NANOBTS,
+	.name = "nanobts",
+	.start = NULL,
+	.oml_rcvmsg = NULL,
+	.e1line_bind_ops = NULL,
+	.nm_att_tlvdef = {
+			  .def = {
+				  /* ip.access specifics */
+				  [NM_ATT_IPACC_DST_IP] = {TLV_TYPE_FIXED, 4},
+				  [NM_ATT_IPACC_DST_IP_PORT] =
+				  {TLV_TYPE_FIXED, 2},
+				  [NM_ATT_IPACC_STREAM_ID] = {TLV_TYPE_TV,},
+				  [NM_ATT_IPACC_SEC_OML_CFG] =
+				  {TLV_TYPE_FIXED, 6},
+				  [NM_ATT_IPACC_IP_IF_CFG] =
+				  {TLV_TYPE_FIXED, 8},
+				  [NM_ATT_IPACC_IP_GW_CFG] =
+				  {TLV_TYPE_FIXED, 12},
+				  [NM_ATT_IPACC_IN_SERV_TIME] =
+				  {TLV_TYPE_FIXED, 4},
+				  [NM_ATT_IPACC_LOCATION] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_PAGING_CFG] =
+				  {TLV_TYPE_FIXED, 2},
+				  [NM_ATT_IPACC_UNIT_ID] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_UNIT_NAME] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_SNMP_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_PRIM_OML_CFG_LIST] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_NV_FLAGS] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_FREQ_CTRL] =
+				  {TLV_TYPE_FIXED, 2},
+				  [NM_ATT_IPACC_PRIM_OML_FB_TOUT] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_CUR_SW_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_TIMING_BUS] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_CGI] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_RAC] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_OBJ_VERSION] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_GPRS_PAGING_CFG] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_NSEI] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_BVCI] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_NSVCI] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_NS_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_BSSGP_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_NS_LINK_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_RLC_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_ALM_THRESH_LIST] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_MONIT_VAL_LIST] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_TIB_CONTROL] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_SUPP_FEATURES] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_CODING_SCHEMES] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_RLC_CFG_2] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_HEARTB_TOUT] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_UPTIME] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_RLC_CFG_3] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_SSL_CFG] = {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_SEC_POSSIBLE] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_IML_SSL_STATE] =
+				  {TLV_TYPE_TL16V},
+				  [NM_ATT_IPACC_REVOC_DATE] = {TLV_TYPE_TL16V},
+				  },
+			  },
+};
+
+static void test_nanobts_attr_bts_get(struct gsm_bts *bts, uint8_t *expected)
+{
+	struct msgb *msgb;
+
+	printf("Testing nanobts_attr_bts_get()...\n");
+
+	msgb = nanobts_attr_bts_get(bts);
+	printf("result=  %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+	printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+	OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+	msgb_free(msgb);
+
+	printf("ok.\n");
+	printf("\n");
+}
+
+static void test_nanobts_attr_nse_get(struct gsm_bts *bts, uint8_t *expected)
+{
+	struct msgb *msgb;
+
+	printf("Testing nanobts_attr_nse_get()...\n");
+
+	msgb = nanobts_attr_nse_get(bts);
+	printf("result=  %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+	printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+	OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+	msgb_free(msgb);
+
+	printf("ok.\n");
+	printf("\n");
+}
+
+static void test_nanobts_attr_cell_get(struct gsm_bts *bts, uint8_t *expected)
+{
+	struct msgb *msgb;
+
+	printf("Testing nanobts_attr_cell_get()...\n");
+
+	msgb = nanobts_attr_cell_get(bts);
+	printf("result=  %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+	printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+	OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+	msgb_free(msgb);
+
+	printf("ok.\n");
+	printf("\n");
+}
+
+static void test_nanobts_attr_nscv_get(struct gsm_bts *bts, uint8_t *expected)
+{
+	struct msgb *msgb;
+
+	printf("Testing nanobts_attr_nscv_get()...\n");
+
+	msgb = nanobts_attr_nscv_get(bts);
+	printf("result=  %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+	printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+	OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+	msgb_free(msgb);
+
+	printf("ok.\n");
+	printf("\n");
+}
+
+static void test_nanobts_attr_radio_get(struct gsm_bts *bts,
+					struct gsm_bts_trx *trx,
+					uint8_t *expected)
+{
+	struct msgb *msgb;
+
+	printf("Testing nanobts_attr_nscv_get()...\n");
+
+	msgb = nanobts_attr_radio_get(bts, trx);
+	printf("result=  %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+	printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+	OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+	msgb_free(msgb);
+
+	printf("ok.\n");
+	printf("\n");
+}
+
+int main(int argc, char **argv)
+{
+	void *ctx;
+
+	struct gsm_bts *bts;
+	struct gsm_network *net;
+	struct gsm_bts_trx *trx;
+
+	osmo_init_logging(&log_info);
+	log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+	ctx = talloc_named_const(NULL, 0, "ctx");
+
+	/* Allocate environmental structs (bts, net, trx) */
+	net = talloc_zero(ctx, struct gsm_network);
+	INIT_LLIST_HEAD(&net->bts_list);
+	gsm_bts_model_register(&bts_model_nanobts);
+	bts = gsm_bts_alloc_register(net, GSM_BTS_TYPE_NANOBTS, 63);
+	OSMO_ASSERT(bts);
+	bts->network = net;
+	trx = talloc_zero(ctx, struct gsm_bts_trx);
+
+	/* Parameters needed by nanobts_attr_bts_get() */
+	bts->rach_b_thresh = -1;
+	bts->rach_ldavg_slots = -1;
+	bts->c0->arfcn = 866;
+	bts->cell_identity = 1337;
+	bts->network->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
+	bts->location_area_code = 1;
+	bts->gprs.rac = 0;
+	uint8_t attr_bts_expected[] =
+	    { 0x19, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x73, 0x18, 0x06, 0x0e, 0x00,
+		0x02, 0x01, 0x20, 0x33, 0x1e, 0x24, 0x24, 0xa8, 0x34, 0x21,
+		0xa8, 0x1f, 0x3f, 0x25,
+		0x00, 0x01, 0x0a, 0x0c, 0x0a, 0x0b, 0x01, 0x2a, 0x0a, 0x2b,
+		0x03, 0xe8, 0x0a, 0x80,
+		0x23, 0x0a, 0x08, 0x03, 0x62, 0x09, 0x3f, 0x99, 0x00, 0x07,
+		0x00, 0xf1, 0x10, 0x00,
+		0x01, 0x05, 0x39
+	};
+
+	/* Parameters needed to test nanobts_attr_nse_get() */
+	bts->gprs.nse.nsei = 101;
+	uint8_t attr_nse_expected[] =
+	    { 0x9d, 0x00, 0x02, 0x00, 0x65, 0xa0, 0x00, 0x07, 0x03, 0x03, 0x03,
+		0x03, 0x1e, 0x03, 0x0a, 0xa1, 0x00, 0x0b, 0x03, 0x03, 0x03,
+		0x03, 0x03, 0x0a, 0x03,
+		0x0a, 0x03, 0x0a, 0x03
+	};
+
+	/* Parameters needed to test nanobts_attr_cell_get() */
+	bts->gprs.rac = 0x00;
+	bts->gprs.cell.bvci = 2;
+	bts->gprs.mode = BTS_GPRS_GPRS;
+	uint8_t attr_cell_expected[] =
+	    { 0x9a, 0x00, 0x01, 0x00, 0x9c, 0x00, 0x02, 0x05, 0x03, 0x9e, 0x00,
+		0x02, 0x00, 0x02, 0xa3, 0x00, 0x09, 0x14, 0x05, 0x05, 0xa0,
+		0x05, 0x0a, 0x04, 0x08,
+		0x0f, 0xa8, 0x00, 0x02, 0x0f, 0x00, 0xa9, 0x00, 0x05, 0x00,
+		0xfa, 0x00, 0xfa, 0x02
+	};
+
+	/* Parameters needed to test nanobts_attr_nscv_get() */
+	bts->gprs.nsvc[0].nsvci = 0x65;
+	bts->gprs.nsvc[0].remote_port = 0x59d8;
+	bts->gprs.nsvc[0].remote_ip = 0x0a090165;
+	bts->gprs.nsvc[0].local_port = 0x5a3c;
+	uint8_t attr_nscv_expected[] =
+	    { 0x9f, 0x00, 0x02, 0x00, 0x65, 0xa2, 0x00, 0x08, 0x59, 0xd8, 0x0a,
+		0x09, 0x01, 0x65, 0x5a, 0x3c
+	};
+
+	/* Parameters needed to test nanobts_attr_radio_get() */
+	trx->arfcn = 866;
+	trx->max_power_red = 22;
+	bts->c0->max_power_red = 22;
+	uint8_t attr_radio_expected[] =
+	    { 0x2d, 0x0b, 0x05, 0x00, 0x02, 0x03, 0x62 };
+
+	/* Run tests */
+	test_nanobts_attr_bts_get(bts, attr_bts_expected);
+	test_nanobts_attr_nse_get(bts, attr_nse_expected);
+	test_nanobts_attr_cell_get(bts, attr_cell_expected);
+	test_nanobts_attr_nscv_get(bts, attr_nscv_expected);
+	test_nanobts_attr_radio_get(bts, trx, attr_radio_expected);
+
+	printf("Done\n");
+	talloc_free(bts);
+	talloc_free(net);
+	talloc_free(trx);
+	talloc_report_full(ctx, stderr);
+	OSMO_ASSERT(talloc_total_blocks(ctx) == 1);
+	return 0;
+}
+
+/* stubs */
+struct osmo_prim_hdr;
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+	abort();
+}
diff --git a/openbsc/tests/nanobts_omlattr/nanobts_omlattr_test.ok b/openbsc/tests/nanobts_omlattr/nanobts_omlattr_test.ok
new file mode 100644
index 0000000..91b655f
--- /dev/null
+++ b/openbsc/tests/nanobts_omlattr/nanobts_omlattr_test.ok
@@ -0,0 +1,26 @@
+Testing nanobts_attr_bts_get()...
+result=  19555b61676d7318060e00020120331e2424a83421a81f3f2500010a0c0a0b012a0a2b03e80a80230a080362093f99000700f11000010539
+expected=19555b61676d7318060e00020120331e2424a83421a81f3f2500010a0c0a0b012a0a2b03e80a80230a080362093f99000700f11000010539
+ok.
+
+Testing nanobts_attr_nse_get()...
+result=  9d00020065a00007030303031e030aa1000b03030303030a030a030a03
+expected=9d00020065a00007030303031e030aa1000b03030303030a030a030a03
+ok.
+
+Testing nanobts_attr_cell_get()...
+result=  9a0001009c000205039e00020002a30009140505a0050a04080fa800020f00a9000500fa00fa02
+expected=9a0001009c000205039e00020002a30009140505a0050a04080fa800020f00a9000500fa00fa02
+ok.
+
+Testing nanobts_attr_nscv_get()...
+result=  9f00020065a2000859d80a0901655a3c
+expected=9f00020065a2000859d80a0901655a3c
+ok.
+
+Testing nanobts_attr_nscv_get()...
+result=  2d0b0500020362
+expected=2d0b0500020362
+ok.
+
+Done
diff --git a/openbsc/tests/smpp/Makefile.am b/openbsc/tests/smpp/Makefile.am
new file mode 100644
index 0000000..5082707
--- /dev/null
+++ b/openbsc/tests/smpp/Makefile.am
@@ -0,0 +1,40 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/src/libmsc \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOSCCP_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	smpp_test.ok \
+	smpp_test.err \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	smpp_test \
+	$(NULL)
+
+smpp_test_SOURCES = \
+	smpp_test.c \
+	$(top_builddir)/src/libmsc/smpp_utils.c \
+	$(NULL)
+
+smpp_test_LDADD = \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(NULL)
diff --git a/openbsc/tests/smpp/smpp_test.c b/openbsc/tests/smpp/smpp_test.c
new file mode 100644
index 0000000..62fa9d2
--- /dev/null
+++ b/openbsc/tests/smpp/smpp_test.c
@@ -0,0 +1,73 @@
+/*
+ * (C) 2013 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <openbsc/debug.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/backtrace.h>
+
+#include "smpp_smsc.h"
+
+struct coding_test {
+	uint8_t dcs;
+	uint8_t coding;
+	int	mode;
+	int	res;
+};
+
+static struct coding_test codecs[] = {
+	{ .dcs = 0xf6		, .coding = 0x02, .mode = MODE_8BIT,	.res = 0  },
+	{ .dcs = 0xf2		, .coding = 0x01, .mode = MODE_7BIT,	.res = 0  },
+	{ .dcs = 0x02		, .coding = 0x01, .mode = MODE_7BIT,	.res = 0  },
+	{ .dcs = 0x06		, .coding = 0x02, .mode = MODE_8BIT,	.res = 0  },
+	{ .dcs = 0x0A		, .coding = 0x08, .mode = MODE_8BIT,	.res = 0  },
+	{ .dcs = 0x0E		, .coding = 0xFF, .mode = 0xFF,		.res = -1 },
+	{ .dcs = 0xE0		, .coding = 0xFF, .mode = 0xFF,		.res = -1 },
+};
+
+static void test_coding_scheme(void)
+{
+	int i;
+	printf("Testing coding scheme support\n");
+
+	for (i = 0; i < ARRAY_SIZE(codecs); ++i) {
+		uint8_t coding;
+		int mode, res;
+
+		res = smpp_determine_scheme(codecs[i].dcs, &coding, &mode);
+		OSMO_ASSERT(res == codecs[i].res);
+		if (res != -1) {
+			OSMO_ASSERT(mode == codecs[i].mode);
+			OSMO_ASSERT(coding == codecs[i].coding);
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	osmo_init_logging(&log_info);
+	log_set_use_color(osmo_stderr_target, 0);
+	log_set_print_filename(osmo_stderr_target, 0);
+
+	test_coding_scheme();
+	return EXIT_SUCCESS;
+}
diff --git a/openbsc/tests/smpp/smpp_test.err b/openbsc/tests/smpp/smpp_test.err
new file mode 100644
index 0000000..ec966ba
--- /dev/null
+++ b/openbsc/tests/smpp/smpp_test.err
@@ -0,0 +1,2 @@
+SMPP MO Unknown Data Coding 0x0e
+SMPP MO Unknown Data Coding 0xe0
diff --git a/openbsc/tests/smpp/smpp_test.ok b/openbsc/tests/smpp/smpp_test.ok
new file mode 100644
index 0000000..fd44804
--- /dev/null
+++ b/openbsc/tests/smpp/smpp_test.ok
@@ -0,0 +1 @@
+Testing coding scheme support
diff --git a/openbsc/tests/smpp_test_runner.py b/openbsc/tests/smpp_test_runner.py
new file mode 100644
index 0000000..58645aa
--- /dev/null
+++ b/openbsc/tests/smpp_test_runner.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+
+# (C) 2014 by Holger Hans Peter Freyther
+# based on vty_test_runner.py:
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# (C) 2013 by Holger Hans Peter Freyther
+# 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import time
+import unittest
+import socket
+
+import osmopy.obscvty as obscvty
+import osmopy.osmoutil as osmoutil
+
+confpath = os.path.join(sys.path[0], '..')
+
+class TestVTYBase(unittest.TestCase):
+
+    def vty_command(self):
+        raise Exception("Needs to be implemented by a subclass")
+
+    def vty_app(self):
+        raise Exception("Needs to be implemented by a subclass")
+
+    def setUp(self):
+        osmo_vty_cmd = self.vty_command()[:]
+        config_index = osmo_vty_cmd.index('-c')
+        if config_index:
+            cfi = config_index + 1
+            osmo_vty_cmd[cfi] = os.path.join(confpath, osmo_vty_cmd[cfi])
+
+        try:
+            self.proc = osmoutil.popen_devnull(osmo_vty_cmd)
+        except OSError:
+            print >> sys.stderr, "Current directory: %s" % os.getcwd()
+            print >> sys.stderr, "Consider setting -b"
+
+        appstring = self.vty_app()[2]
+        appport = self.vty_app()[0]
+        self.vty = obscvty.VTYInteract(appstring, "127.0.0.1", appport)
+
+    def tearDown(self):
+        if self.vty:
+            self.vty._close_socket()
+        self.vty = None
+        osmoutil.end_proc(self.proc)
+
+
+class TestSMPPNITB(TestVTYBase):
+
+    def vty_command(self):
+        return ["./src/osmo-nitb/osmo-nitb", "-c",
+                "doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg"]
+
+    def vty_app(self):
+        return (4242, "./src/osmo-nitb/osmo-nitb", "OpenBSC", "nitb")
+
+    def testSMPPCrashes(self):
+        # Enable the configuration
+        self.vty.enable()
+        self.assertTrue(self.vty.verify("configure terminal", ['']))
+        self.assertEquals(self.vty.node(), 'config')
+
+        self.assertTrue(self.vty.verify('smpp', ['']))
+        self.assertEquals(self.vty.node(), 'config-smpp')
+        self.assertTrue(self.vty.verify('system-id test', ['']))
+        self.assertTrue(self.vty.verify('local-tcp-port 2775', ['']))
+        self.assertTrue(self.vty.verify('esme test', ['']))
+        self.assertEquals(self.vty.node(), 'config-smpp-esme')
+        self.assertTrue(self.vty.verify('default-route', ['']))
+        self.assertTrue(self.vty.verify('end', ['']))
+
+        # NITB should listen to 2775 now!
+        sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sck.setblocking(1)
+        sck.connect(('0.0.0.0', 2775))
+        sck.sendall('\x00\x00\x00\x02\x00')
+        sck.close()
+
+        # Check if the VTY is still there
+        self.vty.verify('disable',[''])
+
+        # Now for the second packet
+        sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sck.setblocking(1)
+        sck.connect(('0.0.0.0', 2775))
+        sck.sendall('\x00\x01\x00\x01\x01')
+        sck.close()
+
+        self.vty.verify('enable',[''])
+
+if __name__ == '__main__':
+    import argparse
+    import sys
+
+    workdir = '.'
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-v", "--verbose", dest="verbose",
+                        action="store_true", help="verbose mode")
+    parser.add_argument("-p", "--pythonconfpath", dest="p",
+                        help="searchpath for config")
+    parser.add_argument("-w", "--workdir", dest="w",
+                        help="Working directory")
+    args = parser.parse_args()
+
+    verbose_level = 1
+    if args.verbose:
+        verbose_level = 2
+
+    if args.w:
+        workdir = args.w
+
+    if args.p:
+        confpath = args.p
+
+    print "confpath %s, workdir %s" % (confpath, workdir)
+    os.chdir(workdir)
+    print "Running tests for specific SMPP"
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestSMPPNITB))
+    res = unittest.TextTestRunner(verbosity=verbose_level).run(suite)
+    sys.exit(len(res.errors) + len(res.failures))
diff --git a/openbsc/tests/subscr/Makefile.am b/openbsc/tests/subscr/Makefile.am
new file mode 100644
index 0000000..6342444
--- /dev/null
+++ b/openbsc/tests/subscr/Makefile.am
@@ -0,0 +1,61 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	subscr_test.ok \
+	bsc_subscr_test.ok \
+	bsc_subscr_test.err \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	subscr_test \
+	bsc_subscr_test \
+	$(NULL)
+
+subscr_test_SOURCES = \
+	subscr_test.c \
+	$(NULL)
+
+subscr_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBSMPP34_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(NULL)
+
+bsc_subscr_test_SOURCES = \
+	bsc_subscr_test.c \
+	$(NULL)
+
+bsc_subscr_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBSMPP34_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(NULL)
diff --git a/openbsc/tests/subscr/bsc_subscr_test.c b/openbsc/tests/subscr/bsc_subscr_test.c
new file mode 100644
index 0000000..60d687d
--- /dev/null
+++ b/openbsc/tests/subscr/bsc_subscr_test.c
@@ -0,0 +1,130 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2014 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_subscriber.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+struct llist_head *bsc_subscribers;
+
+#define VERBOSE_ASSERT(val, expect_op, fmt) \
+	do { \
+		printf(#val " == " fmt "\n", (val)); \
+		OSMO_ASSERT((val) expect_op); \
+	} while (0);
+
+static void assert_bsc_subscr(const struct bsc_subscr *bsub, const char *imsi)
+{
+	struct bsc_subscr *sfound;
+	OSMO_ASSERT(bsub);
+	OSMO_ASSERT(strcmp(bsub->imsi, imsi) == 0);
+
+	sfound = bsc_subscr_find_by_imsi(bsc_subscribers, imsi);
+	OSMO_ASSERT(sfound == bsub);
+
+	bsc_subscr_put(sfound);
+}
+
+static void test_bsc_subscr(void)
+{
+	struct bsc_subscr *s1, *s2, *s3;
+	const char *imsi1 = "1234567890";
+	const char *imsi2 = "9876543210";
+	const char *imsi3 = "5656565656";
+
+	printf("Test BSC subscriber allocation and deletion\n");
+
+	/* Check for emptiness */
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 0, "%d");
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi1) == NULL);
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi2) == NULL);
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi3) == NULL);
+
+	/* Allocate entry 1 */
+	s1 = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, imsi1);
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 1, "%d");
+	assert_bsc_subscr(s1, imsi1);
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 1, "%d");
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi2) == NULL);
+
+	/* Allocate entry 2 */
+	s2 = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, imsi2);
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 2, "%d");
+
+	/* Allocate entry 3 */
+	s3 = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, imsi3);
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 3, "%d");
+
+	/* Check entries */
+	assert_bsc_subscr(s1, imsi1);
+	assert_bsc_subscr(s2, imsi2);
+	assert_bsc_subscr(s3, imsi3);
+
+	/* Free entry 1 */
+	bsc_subscr_put(s1);
+	s1 = NULL;
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 2, "%d");
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi1) == NULL);
+
+	assert_bsc_subscr(s2, imsi2);
+	assert_bsc_subscr(s3, imsi3);
+
+	/* Free entry 2 */
+	bsc_subscr_put(s2);
+	s2 = NULL;
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 1, "%d");
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi1) == NULL);
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi2) == NULL);
+	assert_bsc_subscr(s3, imsi3);
+
+	/* Free entry 3 */
+	bsc_subscr_put(s3);
+	s3 = NULL;
+	VERBOSE_ASSERT(llist_count(bsc_subscribers), == 0, "%d");
+	OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi3) == NULL);
+
+	OSMO_ASSERT(llist_empty(bsc_subscribers));
+}
+
+int main()
+{
+	printf("Testing BSC subscriber core code.\n");
+	osmo_init_logging(&log_info);
+	log_set_print_filename(osmo_stderr_target, 0);
+	log_set_print_timestamp(osmo_stderr_target, 0);
+	log_set_use_color(osmo_stderr_target, 0);
+	log_set_print_category(osmo_stderr_target, 1);
+	log_set_category_filter(osmo_stderr_target, DREF, 1, LOGL_DEBUG);
+
+	bsc_subscribers = talloc_zero(NULL, struct llist_head);
+	INIT_LLIST_HEAD(bsc_subscribers);
+
+	test_bsc_subscr();
+
+	printf("Done\n");
+	return 0;
+}
diff --git a/openbsc/tests/subscr/bsc_subscr_test.err b/openbsc/tests/subscr/bsc_subscr_test.err
new file mode 100644
index 0000000..a66317a
--- /dev/null
+++ b/openbsc/tests/subscr/bsc_subscr_test.err
@@ -0,0 +1,17 @@
+DREF BSC subscr IMSI:1234567890 usage increases to: 2
+DREF BSC subscr IMSI:1234567890 usage decreases to: 1
+DREF BSC subscr IMSI:1234567890 usage increases to: 2
+DREF BSC subscr IMSI:1234567890 usage decreases to: 1
+DREF BSC subscr IMSI:9876543210 usage increases to: 2
+DREF BSC subscr IMSI:9876543210 usage decreases to: 1
+DREF BSC subscr IMSI:5656565656 usage increases to: 2
+DREF BSC subscr IMSI:5656565656 usage decreases to: 1
+DREF BSC subscr IMSI:1234567890 usage decreases to: 0
+DREF BSC subscr IMSI:9876543210 usage increases to: 2
+DREF BSC subscr IMSI:9876543210 usage decreases to: 1
+DREF BSC subscr IMSI:5656565656 usage increases to: 2
+DREF BSC subscr IMSI:5656565656 usage decreases to: 1
+DREF BSC subscr IMSI:9876543210 usage decreases to: 0
+DREF BSC subscr IMSI:5656565656 usage increases to: 2
+DREF BSC subscr IMSI:5656565656 usage decreases to: 1
+DREF BSC subscr IMSI:5656565656 usage decreases to: 0
diff --git a/openbsc/tests/subscr/bsc_subscr_test.ok b/openbsc/tests/subscr/bsc_subscr_test.ok
new file mode 100644
index 0000000..0f6a8be
--- /dev/null
+++ b/openbsc/tests/subscr/bsc_subscr_test.ok
@@ -0,0 +1,11 @@
+Testing BSC subscriber core code.
+Test BSC subscriber allocation and deletion
+llist_count(bsc_subscribers) == 0
+llist_count(bsc_subscribers) == 1
+llist_count(bsc_subscribers) == 1
+llist_count(bsc_subscribers) == 2
+llist_count(bsc_subscribers) == 3
+llist_count(bsc_subscribers) == 2
+llist_count(bsc_subscribers) == 1
+llist_count(bsc_subscribers) == 0
+Done
diff --git a/openbsc/tests/subscr/subscr_test.c b/openbsc/tests/subscr/subscr_test.c
new file mode 100644
index 0000000..2a5d0e1
--- /dev/null
+++ b/openbsc/tests/subscr/subscr_test.c
@@ -0,0 +1,117 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2014 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+
+#include <osmocom/core/application.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+static struct gsm_network dummy_net;
+static struct gsm_subscriber_group dummy_sgrp;
+
+static void test_subscr(void)
+{
+	struct gsm_subscriber *subscr;
+	const char *imsi = "1234567890";
+
+	printf("Test subscriber allocation and deletion\n");
+
+	/* Don't keep subscr */
+
+	dummy_sgrp.keep_subscr = 0;
+
+	OSMO_ASSERT(llist_empty(&active_subscribers));
+
+	subscr = subscr_get_or_create(&dummy_sgrp, imsi);
+
+	OSMO_ASSERT(!llist_empty(&active_subscribers));
+	OSMO_ASSERT(subscr->use_count == 1);
+
+	subscr_put(subscr);
+
+	OSMO_ASSERT(llist_empty(&active_subscribers));
+
+	/* Keep subscr */
+
+	dummy_sgrp.keep_subscr = 1;
+
+	subscr = subscr_get_or_create(&dummy_sgrp, imsi);
+
+	OSMO_ASSERT(!llist_empty(&active_subscribers));
+	OSMO_ASSERT(subscr->use_count == 1);
+
+	subscr_put(subscr);
+	OSMO_ASSERT(!llist_empty(&active_subscribers));
+	OSMO_ASSERT(subscr->use_count == 0);
+
+	subscr_get(subscr);
+	OSMO_ASSERT(subscr->use_count == 1);
+
+	subscr_purge_inactive(&dummy_sgrp);
+
+	OSMO_ASSERT(!llist_empty(&active_subscribers));
+	OSMO_ASSERT(subscr->use_count == 1);
+
+	subscr_put(subscr);
+	OSMO_ASSERT(!llist_empty(&active_subscribers));
+	OSMO_ASSERT(subscr->use_count == 0);
+
+	subscr_purge_inactive(&dummy_sgrp);
+
+	OSMO_ASSERT(llist_empty(&active_subscribers));
+
+	/* Test force_no_keep */
+
+	dummy_sgrp.keep_subscr = 0;
+
+	subscr = subscr_get_or_create(&dummy_sgrp, imsi);
+	OSMO_ASSERT(subscr);
+	subscr->keep_in_ram = 1;
+
+	OSMO_ASSERT(!llist_empty(&active_subscribers));
+	OSMO_ASSERT(subscr->use_count == 1);
+
+	subscr->keep_in_ram = 0;
+
+	subscr_put(subscr);
+	OSMO_ASSERT(llist_empty(&active_subscribers));
+}
+
+int main()
+{
+	printf("Testing subscriber core code.\n");
+	osmo_init_logging(&log_info);
+	log_set_print_filename(osmo_stderr_target, 0);
+
+	dummy_net.subscr_group = &dummy_sgrp;
+	dummy_sgrp.net         = &dummy_net;
+
+	test_subscr();
+
+	printf("Done\n");
+	return 0;
+}
diff --git a/openbsc/tests/subscr/subscr_test.ok b/openbsc/tests/subscr/subscr_test.ok
new file mode 100644
index 0000000..72a3769
--- /dev/null
+++ b/openbsc/tests/subscr/subscr_test.ok
@@ -0,0 +1,3 @@
+Testing subscriber core code.
+Test subscriber allocation and deletion
+Done
diff --git a/openbsc/tests/testsuite.at b/openbsc/tests/testsuite.at
new file mode 100644
index 0000000..25be4ac
--- /dev/null
+++ b/openbsc/tests/testsuite.at
@@ -0,0 +1,105 @@
+AT_INIT
+AT_BANNER([Regression tests.])
+
+AT_SETUP([gsm0408])
+AT_KEYWORDS([gsm0408])
+cat $abs_srcdir/gsm0408/gsm0408_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm0408/gsm0408_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([subscr])
+AT_KEYWORDS([subscr])
+cat $abs_srcdir/subscr/subscr_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/subscr/subscr_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bsc_subscr])
+AT_KEYWORDS([bsc_subscr])
+cat $abs_srcdir/subscr/bsc_subscr_test.ok > expout
+cat $abs_srcdir/subscr/bsc_subscr_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/subscr/bsc_subscr_test], [], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([db])
+AT_KEYWORDS([db])
+cat $abs_srcdir/db/db_test.ok > expout
+cat $abs_srcdir/db/db_test.err > experr
+cat $abs_srcdir/db/hlr.sqlite3 > hlr.sqlite3
+AT_CHECK([$abs_top_builddir/tests/db/db_test], [], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([channel])
+AT_KEYWORDS([channel])
+cat $abs_srcdir/channel/channel_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/channel/channel_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([mgcp])
+AT_KEYWORDS([mgcp])
+cat $abs_srcdir/mgcp/mgcp_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([mgcp-trans])
+AT_KEYWORDS([mgcp-trans])
+AT_CHECK([test "$enable_mgcp_transcoding_test" == yes || exit 77])
+cat $abs_srcdir/mgcp/mgcp_transcoding_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_transcoding_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bsc-nat])
+AT_KEYWORDS([bsc-nat])
+AT_CHECK([test "$enable_nat_test" != no || exit 77])
+cp $abs_srcdir/bsc-nat/prefixes.csv .
+cp $abs_srcdir/bsc-nat/barr.cfg .
+cp $abs_srcdir/bsc-nat/barr_dup.cfg .
+cat $abs_srcdir/bsc-nat/bsc_nat_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bsc-nat/bsc_nat_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([smpp])
+AT_KEYWORDS([smpp])
+AT_CHECK([test "$enable_smpp_test" != no || exit 77])
+cat $abs_srcdir/smpp/smpp_test.ok > expout
+cat $abs_srcdir/smpp/smpp_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/smpp/smpp_test], [], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([bsc-nat-trie])
+AT_KEYWORDS([bsc-nat-trie])
+AT_CHECK([test "$enable_nat_test" != no || exit 77])
+cp $abs_srcdir/bsc-nat-trie/prefixes.csv .
+cat $abs_srcdir/bsc-nat-trie/bsc_nat_trie_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bsc-nat-trie/bsc_nat_trie_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([abis])
+AT_KEYWORDS([abis])
+cat $abs_srcdir/abis/abis_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/abis/abis_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bsc])
+AT_KEYWORDS([bsc])
+AT_CHECK([test "$enable_bsc_test" != no || exit 77])
+cat $abs_srcdir/bsc/bsc_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bsc/bsc_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([trau])
+AT_KEYWORDS([trau])
+cat $abs_srcdir/trau/trau_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/trau/trau_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([mm_auth])
+AT_KEYWORDS([mm_auth])
+cat $abs_srcdir/mm_auth/mm_auth_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/mm_auth/mm_auth_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([nanobts_omlattr])
+AT_KEYWORDS([nanobts_omlattr])
+cat $abs_srcdir/nanobts_omlattr/nanobts_omlattr_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/nanobts_omlattr/nanobts_omlattr_test], [], [expout], [ignore])
+AT_CLEANUP
diff --git a/openbsc/tests/trau/Makefile.am b/openbsc/tests/trau/Makefile.am
new file mode 100644
index 0000000..1d014ba
--- /dev/null
+++ b/openbsc/tests/trau/Makefile.am
@@ -0,0 +1,45 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBSMPP34_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	trau_test.ok \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	trau_test \
+	$(NULL)
+
+trau_test_SOURCES = \
+	trau_test.c \
+	$(NULL)
+
+trau_test_LDADD = \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBSMPP34_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBRARY_DL) \
+	-ldbi \
+	$(NULL)
+
diff --git a/openbsc/tests/trau/trau_test.c b/openbsc/tests/trau/trau_test.c
new file mode 100644
index 0000000..c74e6db
--- /dev/null
+++ b/openbsc/tests/trau/trau_test.c
@@ -0,0 +1,84 @@
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/abis/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <osmocom/core/msgb.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+void test_trau_fr_efr(unsigned char *data)
+{
+	struct decoded_trau_frame tf;
+	struct msgb *msg;
+	struct gsm_data_frame *frame;
+
+	printf("Testing TRAU FR transcoding.\n");
+	data[0] = 0xd0;
+	trau_encode_fr(&tf, data);
+	tf.c_bits[11] = 0; /* clear BFI */
+	msg = trau_decode_fr(1, &tf);
+	OSMO_ASSERT(msg != NULL);
+	frame = (struct gsm_data_frame *)msg->data;
+	OSMO_ASSERT(frame->msg_type == GSM_TCHF_FRAME);
+	OSMO_ASSERT(!memcmp(frame->data, data, 33));
+	msgb_free(msg);
+
+	printf("Testing TRAU EFR transcoding.\n");
+	data[0] = 0xc0;
+	trau_encode_efr(&tf, data);
+	OSMO_ASSERT(tf.d_bits[0] == 1); /* spare bit must be 1 */
+	tf.c_bits[11] = 0; /* clear BFI */
+	msg = trau_decode_efr(1, &tf);
+	OSMO_ASSERT(msg != NULL);
+	frame = (struct gsm_data_frame *)msg->data;
+	OSMO_ASSERT(frame->msg_type == GSM_TCHF_FRAME_EFR);
+	OSMO_ASSERT(!memcmp(frame->data, data, 31));
+
+	printf("Testing TRAU EFR decoding with CRC error.\n");
+	tf.d_bits[0] = 0; /* spare bit must be included */
+	msg = trau_decode_efr(1, &tf);
+	OSMO_ASSERT(msg != NULL);
+	frame = (struct gsm_data_frame *)msg->data;
+	OSMO_ASSERT(frame->msg_type == GSM_BAD_FRAME);
+	msgb_free(msg);
+}
+
+int main()
+{
+	unsigned char data[33];
+	int i;
+
+	msgb_talloc_ctx_init(NULL, 0);
+
+	memset(data, 0x00, sizeof(data));
+	test_trau_fr_efr(data);
+	memset(data, 0xff, sizeof(data));
+	test_trau_fr_efr(data);
+	srandom(42);
+	for (i = 0; i < sizeof(data); i++)
+		data[i] = random();
+	test_trau_fr_efr(data);
+	printf("Done\n");
+	return 0;
+}
+
+/* stubs */
+void vty_out() {}
diff --git a/openbsc/tests/trau/trau_test.ok b/openbsc/tests/trau/trau_test.ok
new file mode 100644
index 0000000..ef71230
--- /dev/null
+++ b/openbsc/tests/trau/trau_test.ok
@@ -0,0 +1,10 @@
+Testing TRAU FR transcoding.
+Testing TRAU EFR transcoding.
+Testing TRAU EFR decoding with CRC error.
+Testing TRAU FR transcoding.
+Testing TRAU EFR transcoding.
+Testing TRAU EFR decoding with CRC error.
+Testing TRAU FR transcoding.
+Testing TRAU EFR transcoding.
+Testing TRAU EFR decoding with CRC error.
+Done
diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py
new file mode 100644
index 0000000..5afcd2e
--- /dev/null
+++ b/openbsc/tests/vty_test_runner.py
@@ -0,0 +1,1163 @@
+#!/usr/bin/env python
+
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# (C) 2013 by Holger Hans Peter Freyther
+# 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, see <http://www.gnu.org/licenses/>.
+
+import os, sys
+import time
+import unittest
+import socket
+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
+
+# to be able to find $top_srcdir/doc/...
+confpath = os.path.join(sys.path[0], '..')
+
+class TestVTYBase(unittest.TestCase):
+
+    def vty_command(self):
+        raise Exception("Needs to be implemented by a subclass")
+
+    def vty_app(self):
+        raise Exception("Needs to be implemented by a subclass")
+
+    def setUp(self):
+        osmo_vty_cmd = self.vty_command()[:]
+        config_index = osmo_vty_cmd.index('-c')
+        if config_index:
+            cfi = config_index + 1
+            osmo_vty_cmd[cfi] = os.path.join(confpath, osmo_vty_cmd[cfi])
+
+        try:
+            self.proc = osmoutil.popen_devnull(osmo_vty_cmd)
+        except OSError:
+            print >> sys.stderr, "Current directory: %s" % os.getcwd()
+            print >> sys.stderr, "Consider setting -b"
+
+        appstring = self.vty_app()[2]
+        appport = self.vty_app()[0]
+        self.vty = obscvty.VTYInteract(appstring, "127.0.0.1", appport)
+
+    def tearDown(self):
+        if self.vty:
+            self.vty._close_socket()
+        self.vty = None
+        osmoutil.end_proc(self.proc)
+
+class TestVTYMGCP(TestVTYBase):
+    def vty_command(self):
+        return ["./src/osmo-bsc_mgcp/osmo-bsc_mgcp", "-c",
+                "doc/examples/osmo-bsc_mgcp/osmo-bsc-mgcp.cfg"]
+
+    def vty_app(self):
+        return (4243, "./src/osmo-bsc_mgcp/osmo-bsc_mgcp", "OpenBSC MGCP", "mgcp")
+
+    def testForcePtime(self):
+        self.vty.enable()
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('  rtp force-ptime 20\r') > 0)
+        self.assertEquals(res.find('  no rtp force-ptime\r'), -1)
+
+        self.vty.command("configure terminal")
+        self.vty.command("mgcp")
+        self.vty.command("no rtp force-ptime")
+        res = self.vty.command("show running-config")
+        self.assertEquals(res.find('  rtp force-ptime 20\r'), -1)
+        self.assertEquals(res.find('  no rtp force-ptime\r'), -1)
+
+    def testOmitAudio(self):
+        self.vty.enable()
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('  sdp audio-payload send-name\r') > 0)
+        self.assertEquals(res.find('  no sdp audio-payload send-name\r'), -1)
+
+        self.vty.command("configure terminal")
+        self.vty.command("mgcp")
+        self.vty.command("no sdp audio-payload send-name")
+        res = self.vty.command("show running-config")
+        self.assertEquals(res.find('  rtp sdp audio-payload send-name\r'), -1)
+        self.assert_(res.find('  no sdp audio-payload send-name\r') > 0)
+
+        # TODO: test it for the trunk!
+
+    def testBindAddr(self):
+        self.vty.enable()
+
+        self.vty.command("configure terminal")
+        self.vty.command("mgcp")
+
+        # enable.. disable bts-bind-ip
+        self.vty.command("rtp bts-bind-ip 254.253.252.250")
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('rtp bts-bind-ip 254.253.252.250') > 0)
+        self.vty.command("no rtp bts-bind-ip")
+        res = self.vty.command("show running-config")
+        self.assertEquals(res.find('  rtp bts-bind-ip'), -1)
+
+        # enable.. disable net-bind-ip
+        self.vty.command("rtp net-bind-ip 254.253.252.250")
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('rtp net-bind-ip 254.253.252.250') > 0)
+        self.vty.command("no rtp net-bind-ip")
+        res = self.vty.command("show running-config")
+        self.assertEquals(res.find('  rtp net-bind-ip'), -1)
+
+
+class TestVTYGenericBSC(TestVTYBase):
+
+    def checkForEndAndExit(self):
+        res = self.vty.command("list")
+        #print ('looking for "exit"\n')
+        self.assert_(res.find('  exit\r') > 0)
+        #print 'found "exit"\nlooking for "end"\n'
+        self.assert_(res.find('  end\r') > 0)
+        #print 'found "end"\n'
+
+    def _testConfigNetworkTree(self):
+        self.vty.enable()
+        self.assertTrue(self.vty.verify("configure terminal",['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("network",['']))
+        self.assertEquals(self.vty.node(), 'config-net')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("bts 0",['']))
+        self.assertEquals(self.vty.node(), 'config-net-bts')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("trx 0",['']))
+        self.assertEquals(self.vty.node(), 'config-net-bts-trx')
+        self.checkForEndAndExit()
+        self.vty.command("write terminal")
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertEquals(self.vty.node(), 'config-net-bts')
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertTrue(self.vty.verify("bts 1",['']))
+        self.assertEquals(self.vty.node(), 'config-net-bts')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("trx 1",['']))
+        self.assertEquals(self.vty.node(), 'config-net-bts-trx')
+        self.checkForEndAndExit()
+        self.vty.command("write terminal")
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertEquals(self.vty.node(), 'config-net-bts')
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertEquals(self.vty.node(), 'config-net')
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertTrue(self.vty.node() is None)
+
+class TestVTYNITB(TestVTYGenericBSC):
+
+    def vty_command(self):
+        return ["./src/osmo-nitb/osmo-nitb", "-c",
+                "doc/examples/osmo-nitb/nanobts/osmo-nitb.cfg"]
+
+    def vty_app(self):
+        return (4242, "./src/osmo-nitb/osmo-nitb", "OpenBSC", "nitb")
+
+    def testConfigNetworkTree(self):
+        self._testConfigNetworkTree()
+
+    def checkForSmpp(self):
+        """SMPP is not always enabled, check if it is"""
+        res = self.vty.command("list")
+        return "smpp" in res
+
+    def testSmppFirst(self):
+        # enable the configuration
+        self.vty.enable()
+        self.vty.command("configure terminal")
+
+        if not self.checkForSmpp():
+            return
+
+        self.vty.command("smpp")
+
+        # check the default
+        res = self.vty.command("write terminal")
+        self.assert_(res.find(' no smpp-first') > 0)
+
+        self.vty.verify("smpp-first", [''])
+        res = self.vty.command("write terminal")
+        self.assert_(res.find(' smpp-first') > 0)
+        self.assertEquals(res.find('no smpp-first'), -1)
+
+        self.vty.verify("no smpp-first", [''])
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('no smpp-first') > 0)
+
+    def testVtyTree(self):
+        self.vty.enable()
+        self.assertTrue(self.vty.verify("configure terminal", ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify('mncc-int', ['']))
+        self.assertEquals(self.vty.node(), 'config-mncc-int')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify('exit', ['']))
+
+        if self.checkForSmpp():
+            self.assertEquals(self.vty.node(), 'config')
+            self.assertTrue(self.vty.verify('smpp', ['']))
+            self.assertEquals(self.vty.node(), 'config-smpp')
+            self.checkForEndAndExit()
+            self.assertTrue(self.vty.verify("exit", ['']))
+
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify("exit", ['']))
+        self.assertTrue(self.vty.node() is None)
+
+        # Check searching for outer node's commands
+        self.vty.command("configure terminal")
+        self.vty.command('mncc-int')
+
+        if self.checkForSmpp():
+            self.vty.command('smpp')
+            self.assertEquals(self.vty.node(), 'config-smpp')
+            self.vty.command('mncc-int')
+
+        self.assertEquals(self.vty.node(), 'config-mncc-int')
+
+    def testVtyAuthorization(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("network")
+        self.assertTrue(self.vty.verify("auth policy closed", ['']))
+        self.assertTrue(self.vty.verify("auth policy regexp", ['']))
+        self.assertTrue(self.vty.verify("authorized-regexp ^001", ['']))
+        self.assertTrue(self.vty.verify("authorized-regexp 02$", ['']))
+        self.assertTrue(self.vty.verify("authorized-regexp *123.*", ['']))
+        self.vty.command("end")
+        self.vty.command("configure terminal")
+        self.vty.command("nitb")
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand no-extension", ['']))
+        self.vty.command("end")
+
+    def testSi2Q(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("network")
+        self.vty.command("bts 0")
+        before = self.vty.command("show running-config")
+        self.vty.command("si2quater neighbor-list add earfcn 1911 threshold 11 2")
+        self.vty.command("si2quater neighbor-list add earfcn 1924 threshold 11 3")
+        self.vty.command("si2quater neighbor-list add earfcn 2111 threshold 11")
+        self.vty.command("si2quater neighbor-list del earfcn 1911")
+        self.vty.command("si2quater neighbor-list del earfcn 1924")
+        self.vty.command("si2quater neighbor-list del earfcn 2111")
+        self.assertEquals(before, self.vty.command("show running-config"))
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 13 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 38 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 44 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 120 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 140 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 163 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 166 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 217 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 224 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 225 1")
+        self.vty.command("si2quater neighbor-list add uarfcn 1976 226 1")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 13")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 38")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 44")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 120")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 140")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 163")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 166")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 217")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 224")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 225")
+        self.vty.command("si2quater neighbor-list del uarfcn 1976 226")
+        self.assertEquals(before, self.vty.command("show running-config"))
+
+    def testEnableDisablePeriodicLU(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("network")
+        self.vty.command("bts 0")
+
+        # Test invalid input
+        self.vty.verify("periodic location update 0", ['% Unknown command.'])
+        self.vty.verify("periodic location update 5", ['% Unknown command.'])
+        self.vty.verify("periodic location update 1531", ['% Unknown command.'])
+
+        # Enable periodic lu..
+        self.vty.verify("periodic location update 60", [''])
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('periodic location update 60') > 0)
+        self.assertEquals(res.find('no periodic location update'), -1)
+
+        # Now disable it..
+        self.vty.verify("no periodic location update", [''])
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find('periodic location update 60'), -1)
+        self.assert_(res.find('no periodic location update') > 0)
+
+    def testEnableDisableSiHacks(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("network")
+        self.vty.command("bts 0")
+
+        # Enable periodic lu..
+        self.vty.verify("force-combined-si", [''])
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('  force-combined-si') > 0)
+        self.assertEquals(res.find('no force-combined-si'), -1)
+
+        # Now disable it..
+        self.vty.verify("no force-combined-si", [''])
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find('  force-combined-si'), -1)
+        self.assert_(res.find('no force-combined-si') > 0)
+
+    def testRachAccessControlClass(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("network")
+        self.vty.command("bts 0")
+
+        # Test invalid input
+        self.vty.verify("rach access-control-class", ['% Command incomplete.'])
+        self.vty.verify("rach access-control-class 1", ['% Command incomplete.'])
+        self.vty.verify("rach access-control-class -1", ['% Unknown command.'])
+        self.vty.verify("rach access-control-class 10", ['% Unknown command.'])
+        self.vty.verify("rach access-control-class 16", ['% Unknown command.'])
+
+        # Barred rach access control classes
+        for classNum in range(16):
+            if classNum != 10:
+                self.vty.verify("rach access-control-class " + str(classNum) + " barred", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        for classNum in range(16):
+            if classNum != 10:
+                self.assert_(res.find("rach access-control-class " + str(classNum) + " barred") > 0)
+
+        # Allowed rach access control classes
+        for classNum in range(16):
+            if classNum != 10:
+                self.vty.verify("rach access-control-class " + str(classNum) + " allowed", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        for classNum in range(16):
+            if classNum != 10:
+                self.assertEquals(res.find("rach access-control-class " + str(classNum) + " barred"), -1)
+
+    def testSubscriberCreateDeleteTwice(self):
+        """
+        OS#1657 indicates that there might be an issue creating the
+        same subscriber twice. This test will use the VTY command to
+        create a subscriber and then issue a second create command
+        with the same IMSI. The test passes if the VTY continues to
+        respond to VTY commands.
+        """
+        self.vty.enable()
+
+        imsi = "204300854013739"
+
+        # Initially we don't have this subscriber
+        self.vty.verify('show subscriber imsi '+imsi, ['% No subscriber found for imsi '+imsi])
+
+        # Lets create one
+        res = self.vty.command('subscriber create imsi '+imsi)
+        self.assert_(res.find("    IMSI: "+imsi) > 0)
+        # And now create one again.
+        res2 = self.vty.command('subscriber create imsi '+imsi)
+        self.assert_(res2.find("    IMSI: "+imsi) > 0)
+        self.assertEqual(res, res2)
+
+        # Verify it has been created
+        res = self.vty.command('show subscriber imsi '+imsi)
+        self.assert_(res.find("    IMSI: "+imsi) > 0)
+
+        # Delete it
+        res = self.vty.command('subscriber imsi ' + imsi + ' delete')
+        self.assert_("" == res)
+
+        # Now it should not be there anymore
+        res = self.vty.command('show subscriber imsi '+imsi)
+        self.assert_(('% No subscriber found for imsi ' + imsi) == res)
+
+
+    def testSubscriberCreateDelete(self):
+        self.vty.enable()
+
+        imsi = "204300854013739"
+        imsi2 = "222301824913762"
+        imsi3 = "333500854113763"
+        imsi4 = "444583744053764"
+
+        # Initially we don't have this subscriber
+        self.vty.verify('show subscriber imsi '+imsi, ['% No subscriber found for imsi '+imsi])
+
+        # Lets create one
+        res = self.vty.command('subscriber create imsi '+imsi)
+        self.assert_(res.find("    IMSI: "+imsi) > 0)
+        self.assert_(res.find("Extension") > 0)
+
+        # Now we have it
+        res = self.vty.command('show subscriber imsi '+imsi)
+        self.assert_(res.find("    IMSI: "+imsi) > 0)
+
+        # With narrow random interval
+        self.vty.command("configure terminal")
+        self.vty.command("nitb")
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
+        # wrong interval
+        res = self.vty.command("subscriber-create-on-demand random 221 122")
+        # error string will contain arguments
+        self.assert_(res.find("122") > 0)
+        self.assert_(res.find("221") > 0)
+        # correct interval - silent ok
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand random 221 222", ['']))
+        self.vty.command("end")
+
+        res = self.vty.command('subscriber create imsi ' + imsi2)
+        self.assert_(res.find("    IMSI: " + imsi2) > 0)
+        self.assert_(res.find("221") > 0 or res.find("222") > 0)
+        self.assert_(res.find("    Extension: ") > 0)
+
+        # Without extension
+        self.vty.command("configure terminal")
+        self.vty.command("nitb")
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand no-extension", ['']))
+        self.vty.command("end")
+        res = self.vty.command('subscriber create imsi ' + imsi3)
+        self.assert_(res.find("    IMSI: " + imsi3) > 0)
+        self.assertEquals(res.find("Extension"), -1)
+
+        # With extension again
+        self.vty.command("configure terminal")
+        self.vty.command("nitb")
+        self.assertTrue(self.vty.verify("no subscriber-create-on-demand", ['']))
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand random 221 666", ['']))
+        self.vty.command("end")
+
+        res = self.vty.command('subscriber create imsi ' + imsi4)
+        self.assert_(res.find("    IMSI: " + imsi4) > 0)
+        self.assert_(res.find("    Extension: ") > 0)
+
+        # Delete it
+        res = self.vty.command('subscriber imsi ' + imsi + ' delete')
+        self.assert_("" == res)
+        res = self.vty.command('subscriber imsi ' + imsi2 + ' delete')
+        self.assert_("" == res)
+        res = self.vty.command('subscriber imsi ' + imsi3 + ' delete')
+        self.assert_("" == res)
+        res = self.vty.command('subscriber imsi ' + imsi4 + ' delete')
+        self.assert_("" == res)
+
+        # Now it should not be there anymore
+        res = self.vty.command('show subscriber imsi '+imsi)
+        self.assert_(('% No subscriber found for imsi ' + imsi) == res)
+
+        # range
+        self.vty.command("end")
+        self.vty.command("configure terminal")
+        self.vty.command("nitb")
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand random 9999999998 9999999999", ['']))
+        res = self.vty.command("show running-config")
+        self.assert_(res.find("subscriber-create-on-demand random 9999999998 9999999999"))
+        self.vty.command("end")
+
+        res = self.vty.command('subscriber create imsi ' + imsi)
+        print(res)
+        self.assert_(res.find("    IMSI: " + imsi) > 0)
+        self.assert_(res.find("9999999998") > 0 or res.find("9999999999") > 0)
+        self.assert_(res.find("    Extension: ") > 0)
+
+        res = self.vty.command('subscriber imsi ' + imsi + ' delete')
+        self.assert_("" == res)
+
+        res = self.vty.command('show subscriber imsi '+imsi)
+        self.assert_(('% No subscriber found for imsi ' + imsi) == res)
+
+
+    def testSubscriberSettings(self):
+        self.vty.enable()
+
+        imsi = "204300854013739"
+        imsi2 = "204301824913769"
+        wrong_imsi = "204300999999999"
+
+        # Lets create one
+        res = self.vty.command('subscriber create imsi '+imsi)
+        self.assert_(res.find("    IMSI: "+imsi) > 0)
+        self.assert_(res.find("Extension") > 0)
+
+        self.vty.verify('subscriber imsi '+wrong_imsi+' name wrong', ['% No subscriber found for imsi '+wrong_imsi])
+        res = self.vty.command('subscriber imsi '+imsi+' name '+('X' * 160))
+        self.assert_(res.find("NAME is too long") > 0)
+
+        self.vty.verify('subscriber imsi '+imsi+' name '+('G' * 159), [''])
+
+        self.vty.verify('subscriber imsi '+wrong_imsi+' extension 840', ['% No subscriber found for imsi '+wrong_imsi])
+        res = self.vty.command('subscriber imsi '+imsi+' extension '+('9' * 15))
+        self.assert_(res.find("EXTENSION is too long") > 0)
+
+        self.vty.verify('subscriber imsi '+imsi+' extension '+('1' * 14), [''])
+
+        # With narrow random interval
+        self.vty.command("configure terminal")
+        self.vty.command("nitb")
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
+        # wrong interval
+        res = self.vty.command("subscriber-create-on-demand random 221 122")
+        self.assert_(res.find("122") > 0)
+        self.assert_(res.find("221") > 0)
+        # correct interval
+        self.assertTrue(self.vty.verify("subscriber-create-on-demand random 221 222", ['']))
+        self.vty.command("end")
+
+        # create subscriber with extension in a configured interval
+        res = self.vty.command('subscriber create imsi ' + imsi2)
+        self.assert_(res.find("    IMSI: " + imsi2) > 0)
+        self.assert_(res.find("221") > 0 or res.find("222") > 0)
+        self.assert_(res.find("    Extension: ") > 0)
+
+        # Delete it
+        res = self.vty.command('subscriber imsi ' + imsi + ' delete')
+        self.assert_(res != "")
+        # imsi2 is inactive so deletion should succeed
+        res = self.vty.command('subscriber imsi ' + imsi2 + ' delete')
+        self.assert_("" == res)
+
+    def testShowPagingGroup(self):
+        res = self.vty.command("show paging-group 255 1234567")
+        self.assertEqual(res, "% can't find BTS 255")
+        res = self.vty.command("show paging-group 0 1234567")
+        self.assertEquals(res, "%Paging group for IMSI 1234567 on BTS #0 is 7")
+
+    def testShowNetwork(self):
+        res = self.vty.command("show network")
+        self.assert_(res.startswith('BSC is on Country Code') >= 0)
+
+    def testMeasurementFeed(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("mncc-int")
+
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find('meas-feed scenario'), -1)
+
+        self.vty.command("meas-feed scenario bla")
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('meas-feed scenario bla') > 0)
+
+        self.vty.command("meas-feed scenario abcdefghijklmnopqrstuvwxyz01234567890")
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find('meas-feed scenario abcdefghijklmnopqrstuvwxyz01234567890'), -1)
+        self.assertEquals(res.find('meas-feed scenario abcdefghijklmnopqrstuvwxyz012345'), -1)
+        self.assert_(res.find('meas-feed scenario abcdefghijklmnopqrstuvwxyz01234') > 0)
+
+
+class TestVTYBSC(TestVTYGenericBSC):
+
+    def vty_command(self):
+        return ["./src/osmo-bsc/osmo-bsc-sccplite", "-c",
+                "doc/examples/osmo-bsc-sccplite/osmo-bsc-sccplite.cfg"]
+
+    def vty_app(self):
+        return (4242, "./src/osmo-bsc/osmo-bsc-sccplite", "OsmoBSC", "bsc")
+
+    def testConfigNetworkTree(self):
+        self._testConfigNetworkTree()
+
+    def testVtyTree(self):
+        self.vty.enable()
+        self.assertTrue(self.vty.verify("configure terminal", ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("msc 0", ['']))
+        self.assertEquals(self.vty.node(), 'config-msc')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("exit", ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify("bsc", ['']))
+        self.assertEquals(self.vty.node(), 'config-bsc')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("exit", ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify("exit", ['']))
+        self.assertTrue(self.vty.node() is None)
+
+    def testUssdNotificationsMsc(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("msc")
+
+        # Test invalid input
+        self.vty.verify("bsc-msc-lost-text", ['% Command incomplete.'])
+        self.vty.verify("bsc-welcome-text", ['% Command incomplete.'])
+        self.vty.verify("bsc-grace-text", ['% Command incomplete.'])
+
+        # Enable USSD notifications
+        self.vty.verify("bsc-msc-lost-text MSC disconnected", [''])
+        self.vty.verify("bsc-welcome-text Hello MS", [''])
+        self.vty.verify("bsc-grace-text In grace period", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('bsc-msc-lost-text MSC disconnected') > 0)
+        self.assertEquals(res.find('no bsc-msc-lost-text'), -1)
+        self.assert_(res.find('bsc-welcome-text Hello MS') > 0)
+        self.assertEquals(res.find('no bsc-welcome-text'), -1)
+        self.assert_(res.find('bsc-grace-text In grace period') > 0)
+        self.assertEquals(res.find('no bsc-grace-text'), -1)
+
+        # Now disable it..
+        self.vty.verify("no bsc-msc-lost-text", [''])
+        self.vty.verify("no bsc-welcome-text", [''])
+        self.vty.verify("no bsc-grace-text", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find('bsc-msc-lost-text MSC disconnected'), -1)
+        self.assert_(res.find('no bsc-msc-lost-text') > 0)
+        self.assertEquals(res.find('bsc-welcome-text Hello MS'), -1)
+        self.assert_(res.find('no bsc-welcome-text') > 0)
+        self.assertEquals(res.find('bsc-grace-text In grace period'), -1)
+        self.assert_(res.find('no bsc-grace-text') > 0)
+
+    def testUssdNotificationsBsc(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("bsc")
+
+        # Test invalid input
+        self.vty.verify("missing-msc-text", ['% Command incomplete.'])
+
+        # Enable USSD notifications
+        self.vty.verify("missing-msc-text No MSC found", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('missing-msc-text No MSC found') > 0)
+        self.assertEquals(res.find('no missing-msc-text'), -1)
+
+        # Now disable it..
+        self.vty.verify("no missing-msc-text", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find('missing-msc-text No MSC found'), -1)
+        self.assert_(res.find('no missing-msc-text') > 0)
+
+    def testNetworkTimezone(self):
+        self.vty.enable()
+        self.vty.verify("configure terminal", [''])
+        self.vty.verify("network", [''])
+
+        # Test invalid input
+        self.vty.verify("timezone", ['% Command incomplete.'])
+        self.vty.verify("timezone 20 0", ['% Unknown command.'])
+        self.vty.verify("timezone 0 11", ['% Unknown command.'])
+        self.vty.verify("timezone 0 0 99", ['% Unknown command.'])
+
+        # Set time zone without DST
+        self.vty.verify("timezone 2 30", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('timezone 2 30') > 0)
+        self.assertEquals(res.find('timezone 2 30 '), -1)
+
+        # Set time zone with DST
+        self.vty.verify("timezone 2 30 1", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('timezone 2 30 1') > 0)
+
+        # Now disable it..
+        self.vty.verify("no timezone", [''])
+
+        # Verify settings
+        res = self.vty.command("write terminal")
+        self.assertEquals(res.find(' timezone'), -1)
+
+    def testShowNetwork(self):
+        res = self.vty.command("show network")
+        self.assert_(res.startswith('BSC is on Country Code') >= 0)
+
+    def testPingPongConfiguration(self):
+        self.vty.enable()
+        self.vty.verify("configure terminal", [''])
+        self.vty.verify("msc 0", [''])
+
+        self.vty.verify("timeout-ping 12", [''])
+        self.vty.verify("timeout-pong 14", [''])
+        res = self.vty.command("show running-config")
+        self.assert_(res.find(" timeout-ping 12") > 0)
+        self.assert_(res.find(" timeout-pong 14") > 0)
+        self.assert_(res.find(" no timeout-ping advanced") > 0)
+
+        self.vty.verify("timeout-ping advanced", [''])
+        res = self.vty.command("show running-config")
+        self.assert_(res.find(" timeout-ping 12") > 0)
+        self.assert_(res.find(" timeout-pong 14") > 0)
+        self.assert_(res.find(" timeout-ping advanced") > 0)
+
+        self.vty.verify("no timeout-ping advanced", [''])
+        res = self.vty.command("show running-config")
+        self.assert_(res.find(" timeout-ping 12") > 0)
+        self.assert_(res.find(" timeout-pong 14") > 0)
+        self.assert_(res.find(" no timeout-ping advanced") > 0)
+
+        self.vty.verify("no timeout-ping", [''])
+        res = self.vty.command("show running-config")
+        self.assertEquals(res.find(" timeout-ping 12"), -1)
+        self.assertEquals(res.find(" timeout-pong 14"), -1)
+        self.assertEquals(res.find(" no timeout-ping advanced"), -1)
+        self.assert_(res.find(" no timeout-ping") > 0)
+
+        self.vty.verify("timeout-ping advanced", ['%ping handling is disabled. Enable it first.'])
+
+        # And back to enabling it
+        self.vty.verify("timeout-ping 12", [''])
+        self.vty.verify("timeout-pong 14", [''])
+        res = self.vty.command("show running-config")
+        self.assert_(res.find(" timeout-ping 12") > 0)
+        self.assert_(res.find(" timeout-pong 14") > 0)
+        self.assert_(res.find(" timeout-ping advanced") > 0)
+
+    def testMscDataCoreLACCI(self):
+        self.vty.enable()
+        res = self.vty.command("show running-config")
+        self.assertEquals(res.find("core-location-area-code"), -1)
+        self.assertEquals(res.find("core-cell-identity"), -1)
+
+        self.vty.command("configure terminal")
+        self.vty.command("msc 0")
+        self.vty.command("core-location-area-code 666")
+        self.vty.command("core-cell-identity 333")
+
+        res = self.vty.command("show running-config")
+        self.assert_(res.find("core-location-area-code 666") > 0)
+        self.assert_(res.find("core-cell-identity 333") > 0)
+
+class TestVTYNAT(TestVTYGenericBSC):
+
+    def vty_command(self):
+        return ["./src/osmo-bsc_nat/osmo-bsc_nat", "-l", "127.0.0.1", "-c",
+                "doc/examples/osmo-bsc_nat/osmo-bsc-nat.cfg"]
+
+    def vty_app(self):
+        return (4244, "src/osmo-bsc_nat/osmo-bsc_nat",  "OsmoBSCNAT", "nat")
+
+    def testBSCreload(self):
+        # Use different port for the mock msc to avoid clashing with
+        # the osmo-bsc_nat itself
+        ip = "127.0.0.1"
+        port = 5522
+        self.vty.enable()
+        bscs1 = self.vty.command("show bscs-config")
+        nat_bsc_reload(self)
+        bscs2 = self.vty.command("show bscs-config")
+        # check that multiple calls to bscs-config-file give the same result
+        self.assertEquals(bscs1, bscs2)
+
+        # add new bsc
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+        self.vty.command("bsc 5")
+        self.vty.command("token key")
+        self.vty.command("location_area_code 666")
+        self.vty.command("end")
+
+        # update bsc token
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+        self.vty.command("bsc 1")
+        self.vty.command("token xyu")
+        self.vty.command("end")
+
+        nat_msc_ip(self, ip, port)
+        msc_socket, msc = nat_msc_test(self, ip, port, verbose=True)
+        try:
+            b0 = nat_bsc_sock_test(0, "lol", verbose=True, proc=self.proc)
+            b1 = nat_bsc_sock_test(1, "xyu", verbose=True, proc=self.proc)
+            b2 = nat_bsc_sock_test(5, "key", verbose=True, proc=self.proc)
+
+            self.assertEquals("3 BSCs configured", self.vty.command("show nat num-bscs-configured"))
+            self.assertTrue(3 == nat_bsc_num_con(self))
+            self.assertEquals("MSC is connected: 1", self.vty.command("show msc connection"))
+
+            nat_bsc_reload(self)
+            bscs2 = self.vty.command("show bscs-config")
+            # check that the reset to initial config succeeded
+            self.assertEquals(bscs1, bscs2)
+
+            self.assertEquals("2 BSCs configured", self.vty.command("show nat num-bscs-configured"))
+            self.assertTrue(1 == nat_bsc_num_con(self))
+            rem = self.vty.command("show bsc connections").split(' ')
+            # remaining connection is for BSC0
+            self.assertEquals('0', rem[2])
+            # remaining connection is authorized
+            self.assertEquals('1', rem[4])
+            self.assertEquals("MSC is connected: 1", self.vty.command("show msc connection"))
+        finally:
+            msc.close()
+            msc_socket.close()
+
+    def testVtyTree(self):
+        self.vty.enable()
+        self.assertTrue(self.vty.verify('configure terminal', ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify('mgcp', ['']))
+        self.assertEquals(self.vty.node(), 'config-mgcp')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify('exit', ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify('nat', ['']))
+        self.assertEquals(self.vty.node(), 'config-nat')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify('bsc 0', ['']))
+        self.assertEquals(self.vty.node(), 'config-nat-bsc')
+        self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify('exit', ['']))
+        self.assertEquals(self.vty.node(), 'config-nat')
+        self.assertTrue(self.vty.verify('exit', ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify('exit', ['']))
+        self.assertTrue(self.vty.node() is None)
+
+    def testRewriteNoRewrite(self):
+        self.vty.enable()
+        res = self.vty.command("configure terminal")
+        res = self.vty.command("nat")
+        res = self.vty.command("number-rewrite rewrite.cfg")
+        res = self.vty.command("no number-rewrite")
+
+    def testEnsureNoEnsureModeSet(self):
+        self.vty.enable()
+        res = self.vty.command("configure terminal")
+        res = self.vty.command("nat")
+
+        # Ensure the default
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('\n sdp-ensure-amr-mode-set') > 0)
+
+        self.vty.command("sdp-ensure-amr-mode-set")
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('\n sdp-ensure-amr-mode-set') > 0)
+
+        self.vty.command("no sdp-ensure-amr-mode-set")
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('\n no sdp-ensure-amr-mode-set') > 0)
+
+    def testRewritePostNoRewrite(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+        self.vty.verify("number-rewrite-post rewrite.cfg", [''])
+        self.vty.verify("no number-rewrite-post", [''])
+
+
+    def testPrefixTreeLoading(self):
+        cfg = os.path.join(confpath, "tests/bsc-nat-trie/prefixes.csv")
+
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+        res = self.vty.command("prefix-tree %s" % cfg)
+        self.assertEqual(res, "% prefix-tree loaded 17 rules.")
+        self.vty.command("end")
+
+        res = self.vty.command("show prefix-tree")
+        self.assertEqual(res, '1,1\r\n12,2\r\n123,3\r\n1234,4\r\n12345,5\r\n123456,6\r\n1234567,7\r\n12345678,8\r\n123456789,9\r\n1234567890,10\r\n13,11\r\n14,12\r\n15,13\r\n16,14\r\n82,16\r\n823455,15\r\n+49123,17')
+
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+        self.vty.command("no prefix-tree")
+        self.vty.command("end")
+
+        res = self.vty.command("show prefix-tree")
+        self.assertEqual(res, "% there is now prefix tree loaded.")
+
+    def testUssdSideChannelProvider(self):
+        self.vty.command("end")
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+        self.vty.command("ussd-token key")
+        self.vty.command("end")
+
+        res = self.vty.verify("show ussd-connection", ['The USSD side channel provider is not connected and not authorized.'])
+        self.assertTrue(res)
+
+        ussdSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        ussdSocket.connect(('127.0.0.1', 5001))
+        ussdSocket.settimeout(2.0)
+        print "Connected to %s:%d" % ussdSocket.getpeername()
+
+        print "Expecting ID_GET request"
+        data = ussdSocket.recv(4)
+        self.assertEqual(data, "\x00\x01\xfe\x04")
+
+        print "Going to send ID_RESP response"
+        res = ussdSocket.send(IPA().id_resp(IPA().tag_name('key')))
+        self.assertEqual(res, 10)
+
+        # initiating PING/PONG cycle to know, that the ID_RESP message has been processed
+
+        print "Going to send PING request"
+        res = ussdSocket.send(IPA().ping())
+        self.assertEqual(res, 4)
+
+        print "Expecting PONG response"
+        data = ussdSocket.recv(4)
+        self.assertEqual(data, "\x00\x01\xfe\x01")
+
+        res = self.vty.verify("show ussd-connection", ['The USSD side channel provider is connected and authorized.'])
+        self.assertTrue(res)
+
+        print "Going to shut down connection"
+        ussdSocket.shutdown(socket.SHUT_WR)
+
+        print "Expecting EOF"
+        data = ussdSocket.recv(4)
+        self.assertEqual(data, "")
+
+        ussdSocket.close()
+
+        res = self.vty.verify("show ussd-connection", ['The USSD side channel provider is not connected and not authorized.'])
+        self.assertTrue(res)
+
+    def testAccessList(self):
+        """
+        Verify that the imsi-deny can have a reject cause or no reject cause
+        """
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("nat")
+
+        # Old default
+        self.vty.command("access-list test-default imsi-deny ^123[0-9]*$")
+        res = self.vty.command("show running-config").split("\r\n")
+        asserted = False
+        for line in res:
+           if line.startswith(" access-list test-default"):
+                self.assertEqual(line, " access-list test-default imsi-deny ^123[0-9]*$ 11 11")
+                asserted = True
+        self.assert_(asserted)
+
+        # Check the optional CM Service Reject Cause
+        self.vty.command("access-list test-cm-deny imsi-deny ^123[0-9]*$ 42").split("\r\n")
+        res = self.vty.command("show running-config").split("\r\n")
+        asserted = False
+        for line in res:
+           if line.startswith(" access-list test-cm"):
+                self.assertEqual(line, " access-list test-cm-deny imsi-deny ^123[0-9]*$ 42 11")
+                asserted = True
+        self.assert_(asserted)
+
+        # Check the optional LU Reject Cause
+        self.vty.command("access-list test-lu-deny imsi-deny ^123[0-9]*$ 23 42").split("\r\n")
+        res = self.vty.command("show running-config").split("\r\n")
+        asserted = False
+        for line in res:
+           if line.startswith(" access-list test-lu"):
+                self.assertEqual(line, " access-list test-lu-deny imsi-deny ^123[0-9]*$ 23 42")
+                asserted = True
+        self.assert_(asserted)
+
+def add_nat_test(suite, workdir):
+    if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc_nat/osmo-bsc_nat")):
+        print("Skipping the NAT test")
+        return
+    test = unittest.TestLoader().loadTestsFromTestCase(TestVTYNAT)
+    suite.addTest(test)
+
+def nat_bsc_reload(x):
+    x.vty.command("configure terminal")
+    x.vty.command("nat")
+    x.vty.command("bscs-config-file bscs.config")
+    x.vty.command("end")
+
+def nat_msc_ip(x, ip, port):
+    x.vty.command("configure terminal")
+    x.vty.command("nat")
+    x.vty.command("msc ip " + ip)
+    x.vty.command("msc port " + str(port))
+    x.vty.command("end")
+
+def data2str(d):
+    return d.encode('hex').lower()
+
+def nat_msc_test(x, ip, port, verbose = False):
+    msc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    msc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    msc.settimeout(5)
+    msc.bind((ip, port))
+    msc.listen(5)
+    if (verbose):
+        print "MSC is ready at " + ip
+    conn = None
+    while True:
+        vty_response = x.vty.command("show msc connection")
+        print "'show msc connection' says: %r" % vty_response
+        if vty_response == "MSC is connected: 1":
+            # success
+            break;
+        if vty_response != "MSC is connected: 0":
+            raise Exception("Unexpected response to 'show msc connection'"
+                            " vty command: %r" % vty_response)
+
+        timeout_retries = 6
+        while timeout_retries > 0:
+            try:
+                conn, addr = msc.accept()
+                print "MSC got connection from ", addr
+                break
+            except socket.timeout:
+                print "socket timed out."
+                timeout_retries -= 1
+                continue
+
+    if not conn:
+        raise Exception("VTY reports MSC is connected, but I haven't"
+                        " connected yet: %r %r" % (ip, port))
+    return msc, conn
+
+def ipa_handle_small(x, verbose = False):
+    s = data2str(x.recv(4))
+    if len(s) != 4*2:
+      raise Exception("expected to receive 4 bytes, but got %d (%r)" % (len(s)/2, s))
+    if "0001fe00" == s:
+        if (verbose):
+            print "\tBSC <- NAT: PING?"
+        x.send(IPA().pong())
+    elif "0001fe06" == s:
+        if (verbose):
+            print "\tBSC <- NAT: IPA ID ACK"
+        x.send(IPA().id_ack())
+    elif "0001fe00" == s:
+        if (verbose):
+            print "\tBSC <- NAT: PONG!"
+    else:
+        if (verbose):
+            print "\tBSC <- NAT: ", s
+
+def ipa_handle_resp(x, tk, verbose = False, proc=None):
+    s = data2str(x.recv(38))
+    if "0023fe040108010701020103010401050101010011" in s:
+        retries = 3
+        while True:
+            print "\tsending IPA identity(%s) at %s" % (tk, time.strftime("%T"))
+            try:
+                x.send(IPA().id_resp(IPA().identity(name = tk.encode('utf-8'))))
+                print "\tdone sending IPA identity(%s) at %s" % (tk,
+                                                            time.strftime("%T"))
+                break
+            except:
+                print "\tfailed sending IPA identity at", time.strftime("%T")
+                if proc:
+                  print "\tproc.poll() = %r" % proc.poll()
+                if retries < 1:
+                    print "\tgiving up"
+                    raise
+                print "\tretrying (%d attempts left)" % retries
+                retries -= 1
+    else:
+        if (verbose):
+            print "\tBSC <- NAT: ", s
+
+def nat_bsc_num_con(x):
+    return len(x.vty.command("show bsc connections").split('\n'))
+
+def nat_bsc_sock_test(nr, tk, verbose = False, proc=None):
+    bsc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    bsc.bind(('127.0.0.1', 0))
+    bsc.connect(('127.0.0.1', 5000))
+    if (verbose):
+        print "BSC%d " %nr
+        print "\tconnected to %s:%d" % bsc.getpeername()
+    if proc:
+      print "\tproc.poll() = %r" % proc.poll()
+      print "\tproc.pid = %r" % proc.pid
+    ipa_handle_small(bsc, verbose)
+    ipa_handle_resp(bsc, tk, verbose, proc=proc)
+    if proc:
+      print "\tproc.poll() = %r" % proc.poll()
+    bsc.recv(27) # MGCP msg
+    if proc:
+      print "\tproc.poll() = %r" % proc.poll()
+    ipa_handle_small(bsc, verbose)
+    return bsc
+
+def add_bsc_test(suite, workdir):
+    if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc/osmo-bsc-sccplite")):
+        print("Skipping the BSC test")
+        return
+    test = unittest.TestLoader().loadTestsFromTestCase(TestVTYBSC)
+    suite.addTest(test)
+
+if __name__ == '__main__':
+    import argparse
+    import sys
+
+    workdir = '.'
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-v", "--verbose", dest="verbose",
+                        action="store_true", help="verbose mode")
+    parser.add_argument("-p", "--pythonconfpath", dest="p",
+                        help="searchpath for config")
+    parser.add_argument("-w", "--workdir", dest="w",
+                        help="Working directory")
+    parser.add_argument("test_name", nargs="*", help="(parts of) test names to run, case-insensitive")
+    args = parser.parse_args()
+
+    verbose_level = 1
+    if args.verbose:
+        verbose_level = 2
+
+    if args.w:
+        workdir = args.w
+
+    if args.p:
+        confpath = args.p
+
+    print "confpath %s, workdir %s" % (confpath, workdir)
+    os.chdir(workdir)
+    print "Running tests for specific VTY commands"
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestVTYMGCP))
+    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestVTYNITB))
+    add_bsc_test(suite, workdir)
+    add_nat_test(suite, workdir)
+
+    if args.test_name:
+        osmoutil.pick_tests(suite, *args.test_name)
+
+    res = unittest.TextTestRunner(verbosity=verbose_level, stream=sys.stdout).run(suite)
+    sys.exit(len(res.errors) + len(res.failures))
+
+# vim: shiftwidth=4 expandtab nocin ai
diff --git a/openbsc/tools/hlrstat.pl b/openbsc/tools/hlrstat.pl
new file mode 100755
index 0000000..668fc9a
--- /dev/null
+++ b/openbsc/tools/hlrstat.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+
+use strict;
+use DBI;
+my $dbh = DBI->connect("dbi:SQLite:dbname=hlr.sqlite3","","");
+
+
+my %mcc_names;
+my %mcc_mnc_names;
+
+sub get_mcc_mnc_name($)
+{
+	my $mcc_mnc = shift;
+	my $ret = $mcc_mnc;
+
+	if ($mcc_mnc_names{$mcc_mnc} ne '') {
+		$ret = $mcc_mnc_names{$mcc_mnc};
+	}
+
+	return $ret;
+}
+
+sub read_networks($)
+{
+	my $filename = shift;
+	my $cur_name;
+
+	open(INFILE, $filename);
+	while (my $l = <INFILE>) {
+		chomp($l);
+		if ($l =~ /^#/) {
+			next;
+		}
+		if ($l =~ /^\t/) {
+			my ($mcc, $mnc, $brand, $r) = split(' ', $l, 4);
+			#printf("%s|%s|%s\n", $mcc, $mnc, $brand);
+			$mcc_mnc_names{"$mcc-$mnc"} = $brand;
+			$mcc_names{$mcc} = $cur_name;
+		} elsif ($l =~ /^(\w\w)\t(.*)/) {
+			#printf("%s|%s\n", $1, $2);
+			$cur_name = $2;
+		}
+	}
+	close(INFILE);
+}
+
+read_networks("networks.tab");
+
+my %oper_count;
+my %country_count;
+
+#my $sth = $dbh->prepare("SELECT imsi FROM subscriber where authorized=1");
+my $sth = $dbh->prepare("SELECT imsi FROM subscriber");
+
+$sth->execute();
+
+while (my $href = $sth->fetchrow_hashref) {
+	my ($mcc, $mnc) = $$href{imsi} =~ /(\d{3})(\d{2}).*/;
+	#printf("%s %s-%s \n", $$href{imsi}, $mcc, $mnc);
+	$oper_count{"$mcc-$mnc"}++;
+	$country_count{$mcc}++;
+}
+
+
+foreach my $c (sort{$country_count{$b} <=> $country_count{$a}} keys %country_count) {
+	printf("%s: %d\n", $mcc_names{$c}, $country_count{$c});
+
+	foreach my $k (sort{$oper_count{$b} <=> $oper_count{$a}} keys %oper_count) {
+		if ($k =~ /^$c-/) {
+			printf("\t%s: %d\n", get_mcc_mnc_name($k), $oper_count{$k});
+		}
+	}
+}