aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJonathan Santos <jrsantos@jonathanrsantos.com>2011-05-25 13:54:02 -0400
committerJonathan Santos <jrsantos@jonathanrsantos.com>2011-05-25 13:54:02 -0400
commit03fd8d014f9871896a86534432c8757d65a576fe (patch)
treebad087cacfb6b106f6ca542bf92ef2e2ecea5dd3 /src
parente7dae79f5839029279c9fd4543804882c019bf42 (diff)
Import upstream version 0.9.13upstream/0.9.13
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am13
-rw-r--r--src/Makefile.in548
-rw-r--r--src/gprs/Makefile.am22
-rw-r--r--src/gprs/Makefile.in522
-rw-r--r--src/gprs/crc24.c68
-rw-r--r--src/gprs/gb_proxy.c684
-rw-r--r--src/gprs/gb_proxy_main.c288
-rw-r--r--src/gprs/gb_proxy_vty.c104
-rw-r--r--src/gprs/gprs_gmm.c1597
-rw-r--r--src/gprs/gprs_llc.c852
-rw-r--r--src/gprs/gprs_llc_vty.c108
-rw-r--r--src/gprs/gprs_sgsn.c383
-rw-r--r--src/gprs/gprs_sndcp.c616
-rw-r--r--src/gprs/gprs_sndcp.h53
-rw-r--r--src/gprs/gprs_sndcp_vty.c74
-rw-r--r--src/gprs/sgsn_libgtp.c610
-rw-r--r--src/gprs/sgsn_main.c288
-rw-r--r--src/gprs/sgsn_vty.c357
-rw-r--r--src/ipaccess/Makefile.am21
-rw-r--r--src/ipaccess/Makefile.in520
-rw-r--r--src/ipaccess/ipaccess-config.c865
-rw-r--r--src/ipaccess/ipaccess-find.c227
-rw-r--r--src/ipaccess/ipaccess-firmware.c135
-rw-r--r--src/ipaccess/ipaccess-proxy.c1373
-rw-r--r--src/ipaccess/network_listen.c251
-rw-r--r--src/libabis/Makefile.am14
-rw-r--r--src/libabis/Makefile.in548
-rw-r--r--src/libabis/e1_input.c649
-rw-r--r--src/libabis/e1_input_vty.c102
-rw-r--r--src/libabis/input/dahdi.c494
-rw-r--r--src/libabis/input/hsl.c460
-rw-r--r--src/libabis/input/ipaccess.c797
-rw-r--r--src/libabis/input/lapd.c710
-rw-r--r--src/libabis/input/lapd.h46
-rw-r--r--src/libabis/input/misdn.c542
-rw-r--r--src/libbsc/Makefile.am25
-rw-r--r--src/libbsc/Makefile.in504
-rw-r--r--src/libbsc/abis_nm.c3132
-rw-r--r--src/libbsc/abis_nm_vty.c197
-rw-r--r--src/libbsc/abis_om2000.c1078
-rw-r--r--src/libbsc/abis_om2000_vty.c513
-rw-r--r--src/libbsc/abis_rsl.c1971
-rw-r--r--src/libbsc/bsc_api.c675
-rw-r--r--src/libbsc/bsc_init.c466
-rw-r--r--src/libbsc/bsc_msc.c259
-rw-r--r--src/libbsc/bsc_rll.c141
-rw-r--r--src/libbsc/bsc_vty.c2799
-rw-r--r--src/libbsc/bts_ericsson_rbs2000.c164
-rw-r--r--src/libbsc/bts_hsl_femtocell.c162
-rw-r--r--src/libbsc/bts_ipaccess_nanobts.c447
-rw-r--r--src/libbsc/bts_siemens_bs11.c591
-rw-r--r--src/libbsc/bts_unknown.c41
-rw-r--r--src/libbsc/chan_alloc.c507
-rw-r--r--src/libbsc/e1_config.c296
-rw-r--r--src/libbsc/gsm_04_08_utils.c655
-rw-r--r--src/libbsc/gsm_subscriber_base.c149
-rw-r--r--src/libbsc/handover_decision.c297
-rw-r--r--src/libbsc/handover_logic.c393
-rw-r--r--src/libbsc/meas_rep.c116
-rw-r--r--src/libbsc/paging.c395
-rw-r--r--src/libbsc/rest_octets.c432
-rw-r--r--src/libbsc/system_information.c616
-rw-r--r--src/libbsc/transaction.c152
-rw-r--r--src/libcommon/Makefile.am7
-rw-r--r--src/libcommon/Makefile.in457
-rw-r--r--src/libcommon/bsc_version.c30
-rw-r--r--src/libcommon/common_vty.c225
-rw-r--r--src/libcommon/debug.c249
-rw-r--r--src/libcommon/gsm_data.c592
-rw-r--r--src/libcommon/socket.c108
-rw-r--r--src/libcommon/talloc_ctx.c38
-rw-r--r--src/libgb/Makefile.am9
-rw-r--r--src/libgb/Makefile.in460
-rw-r--r--src/libgb/gprs_bssgp.c856
-rw-r--r--src/libgb/gprs_bssgp_util.c119
-rw-r--r--src/libgb/gprs_bssgp_vty.c176
-rw-r--r--src/libgb/gprs_ns.c992
-rw-r--r--src/libgb/gprs_ns_frgre.c304
-rw-r--r--src/libgb/gprs_ns_vty.c569
-rw-r--r--src/libmgcp/Makefile.am7
-rw-r--r--src/libmgcp/Makefile.in453
-rw-r--r--src/libmgcp/mgcp_network.c579
-rw-r--r--src/libmgcp/mgcp_protocol.c1102
-rw-r--r--src/libmgcp/mgcp_vty.c742
-rw-r--r--src/libmsc/Makefile.am19
-rw-r--r--src/libmsc/Makefile.in482
-rw-r--r--src/libmsc/auth.c132
-rw-r--r--src/libmsc/db.c1303
-rw-r--r--src/libmsc/gsm_04_08.c3345
-rw-r--r--src/libmsc/gsm_04_11.c1240
-rw-r--r--src/libmsc/gsm_04_80.c175
-rw-r--r--src/libmsc/gsm_subscriber.c410
-rw-r--r--src/libmsc/mncc.c110
-rw-r--r--src/libmsc/mncc_builtin.c411
-rw-r--r--src/libmsc/mncc_sock.c337
-rw-r--r--src/libmsc/osmo_msc.c104
-rw-r--r--src/libmsc/rrlp.c105
-rw-r--r--src/libmsc/silent_call.c143
-rw-r--r--src/libmsc/sms_queue.c479
-rw-r--r--src/libmsc/token_auth.c153
-rw-r--r--src/libmsc/ussd.c79
-rw-r--r--src/libmsc/vty_interface_layer3.c790
-rw-r--r--src/libtrau/Makefile.am7
-rw-r--r--src/libtrau/Makefile.in455
-rw-r--r--src/libtrau/rtp_proxy.c728
-rw-r--r--src/libtrau/subchan_demux.c321
-rw-r--r--src/libtrau/trau_frame.c260
-rw-r--r--src/libtrau/trau_mux.c312
-rw-r--r--src/libtrau/trau_upqueue.c27
-rw-r--r--src/osmo-bsc/Makefile.am18
-rw-r--r--src/osmo-bsc/Makefile.in513
-rw-r--r--src/osmo-bsc/osmo_bsc_api.c174
-rw-r--r--src/osmo-bsc/osmo_bsc_audio.c70
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c548
-rw-r--r--src/osmo-bsc/osmo_bsc_filter.c170
-rw-r--r--src/osmo-bsc/osmo_bsc_grace.c107
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c261
-rw-r--r--src/osmo-bsc/osmo_bsc_msc.c368
-rw-r--r--src/osmo-bsc/osmo_bsc_rf.c364
-rw-r--r--src/osmo-bsc/osmo_bsc_sccp.c288
-rw-r--r--src/osmo-bsc/osmo_bsc_vty.c311
-rw-r--r--src/osmo-bsc_mgcp/Makefile.am10
-rw-r--r--src/osmo-bsc_mgcp/Makefile.in487
-rw-r--r--src/osmo-bsc_mgcp/mgcp_main.c283
-rw-r--r--src/osmo-bsc_nat/Makefile.am15
-rw-r--r--src/osmo-bsc_nat/Makefile.in504
-rw-r--r--src/osmo-bsc_nat/bsc_filter.c216
-rw-r--r--src/osmo-bsc_nat/bsc_mgcp_utils.c764
-rw-r--r--src/osmo-bsc_nat/bsc_nat.c1387
-rw-r--r--src/osmo-bsc_nat/bsc_nat_utils.c893
-rw-r--r--src/osmo-bsc_nat/bsc_nat_vty.c788
-rw-r--r--src/osmo-bsc_nat/bsc_sccp.c249
-rw-r--r--src/osmo-bsc_nat/bsc_ussd.c363
-rw-r--r--src/osmo-nitb/Makefile.am14
-rw-r--r--src/osmo-nitb/Makefile.in496
-rw-r--r--src/osmo-nitb/bsc_hack.c313
-rw-r--r--src/utils/Makefile.am12
-rw-r--r--src/utils/Makefile.in496
-rw-r--r--src/utils/bs11_config.c918
-rw-r--r--src/utils/isdnsync.c191
-rw-r--r--src/utils/rs232.c248
141 files changed, 63954 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..1573563f
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs
+
+# Conditional modules
+if BUILD_NAT
+SUBDIRS += osmo-bsc_nat
+endif
+if BUILD_BSC
+SUBDIRS += osmo-bsc
+endif
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 00000000..b1c2f088
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,548 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+
+# Conditional modules
+@BUILD_NAT_TRUE@am__append_1 = osmo-bsc_nat
+@BUILD_BSC_TRUE@am__append_2 = osmo-bsc
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+ html-recursive info-recursive install-data-recursive \
+ install-dvi-recursive install-exec-recursive \
+ install-html-recursive install-info-recursive \
+ install-pdf-recursive install-ps-recursive install-recursive \
+ installcheck-recursive installdirs-recursive pdf-recursive \
+ ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+ $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+ distdir
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau \
+ osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs osmo-bsc_nat \
+ osmo-bsc
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb \
+ osmo-bsc_mgcp utils ipaccess libgb gprs $(am__append_1) \
+ $(am__append_2)
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+# (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+ @fail= failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+ @fail= failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ rev=''; for subdir in $$list; do \
+ if test "$$subdir" = "."; then :; else \
+ rev="$$subdir $$rev"; \
+ fi; \
+ done; \
+ rev="$$rev ."; \
+ target=`echo $@ | sed s/-recursive//`; \
+ for subdir in $$rev; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done && test -z "$$fail"
+tags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+ done
+ctags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+ done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \
+ install-am install-strip tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+ all all-am check check-am clean clean-generic ctags \
+ ctags-recursive distclean distclean-generic distclean-tags \
+ distdir dvi dvi-am html html-am info info-am install \
+ install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
+ tags-recursive uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/gprs/Makefile.am b/src/gprs/Makefile.am
new file mode 100644
index 00000000..16c2200e
--- /dev/null
+++ b/src/gprs/Makefile.am
@@ -0,0 +1,22 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_HEADERS = gprs_sndcp.h
+
+if HAVE_LIBGTP
+bin_PROGRAMS = osmo-gbproxy osmo-sgsn
+else
+bin_PROGRAMS = osmo-gbproxy
+endif
+
+osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c
+osmo_gbproxy_LDADD = $(top_builddir)/src/libgb/libgb.a \
+ $(top_builddir)/src/libcommon/libcommon.a
+
+osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \
+ sgsn_main.c sgsn_vty.c sgsn_libgtp.c \
+ gprs_llc.c gprs_llc_vty.c crc24.c
+osmo_sgsn_LDADD = $(top_builddir)/src/libgb/libgb.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ -lgtp
diff --git a/src/gprs/Makefile.in b/src/gprs/Makefile.in
new file mode 100644
index 00000000..99ea7391
--- /dev/null
+++ b/src/gprs/Makefile.in
@@ -0,0 +1,522 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+@HAVE_LIBGTP_FALSE@bin_PROGRAMS = osmo-gbproxy$(EXEEXT)
+@HAVE_LIBGTP_TRUE@bin_PROGRAMS = osmo-gbproxy$(EXEEXT) \
+@HAVE_LIBGTP_TRUE@ osmo-sgsn$(EXEEXT)
+subdir = src/gprs
+DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+ $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_osmo_gbproxy_OBJECTS = gb_proxy.$(OBJEXT) gb_proxy_main.$(OBJEXT) \
+ gb_proxy_vty.$(OBJEXT)
+osmo_gbproxy_OBJECTS = $(am_osmo_gbproxy_OBJECTS)
+osmo_gbproxy_DEPENDENCIES = $(top_builddir)/src/libgb/libgb.a \
+ $(top_builddir)/src/libcommon/libcommon.a
+am_osmo_sgsn_OBJECTS = gprs_gmm.$(OBJEXT) gprs_sgsn.$(OBJEXT) \
+ gprs_sndcp.$(OBJEXT) gprs_sndcp_vty.$(OBJEXT) \
+ sgsn_main.$(OBJEXT) sgsn_vty.$(OBJEXT) sgsn_libgtp.$(OBJEXT) \
+ gprs_llc.$(OBJEXT) gprs_llc_vty.$(OBJEXT) crc24.$(OBJEXT)
+osmo_sgsn_OBJECTS = $(am_osmo_sgsn_OBJECTS)
+osmo_sgsn_DEPENDENCIES = $(top_builddir)/src/libgb/libgb.a \
+ $(top_builddir)/src/libcommon/libcommon.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo " CC " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo " CCLD " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+SOURCES = $(osmo_gbproxy_SOURCES) $(osmo_sgsn_SOURCES)
+DIST_SOURCES = $(osmo_gbproxy_SOURCES) $(osmo_sgsn_SOURCES)
+HEADERS = $(noinst_HEADERS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+noinst_HEADERS = gprs_sndcp.h
+osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c
+osmo_gbproxy_LDADD = $(top_builddir)/src/libgb/libgb.a \
+ $(top_builddir)/src/libcommon/libcommon.a
+
+osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \
+ sgsn_main.c sgsn_vty.c sgsn_libgtp.c \
+ gprs_llc.c gprs_llc_vty.c crc24.c
+
+osmo_sgsn_LDADD = $(top_builddir)/src/libgb/libgb.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ -lgtp
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/gprs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/gprs/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p; \
+ then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+osmo-gbproxy$(EXEEXT): $(osmo_gbproxy_OBJECTS) $(osmo_gbproxy_DEPENDENCIES)
+ @rm -f osmo-gbproxy$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(osmo_gbproxy_OBJECTS) $(osmo_gbproxy_LDADD) $(LIBS)
+osmo-sgsn$(EXEEXT): $(osmo_sgsn_OBJECTS) $(osmo_sgsn_DEPENDENCIES)
+ @rm -f osmo-sgsn$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(osmo_sgsn_OBJECTS) $(osmo_sgsn_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc24.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy_main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_gmm.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_llc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_llc_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sgsn.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sndcp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sndcp_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_libgtp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_vty.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic ctags distclean distclean-compile \
+ distclean-generic distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/gprs/crc24.c b/src/gprs/crc24.c
new file mode 100644
index 00000000..4d65e6ee
--- /dev/null
+++ b/src/gprs/crc24.c
@@ -0,0 +1,68 @@
+/* GPRS LLC CRC-24 Implementation */
+
+/* (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 <sys/types.h>
+#include <openbsc/crc24.h>
+
+/* CRC24 table - FCS */
+static const u_int32_t tbl_crc24[256] = {
+ 0x00000000, 0x00d6a776, 0x00f64557, 0x0020e221, 0x00b78115, 0x00612663, 0x0041c442, 0x00976334,
+ 0x00340991, 0x00e2aee7, 0x00c24cc6, 0x0014ebb0, 0x00838884, 0x00552ff2, 0x0075cdd3, 0x00a36aa5,
+ 0x00681322, 0x00beb454, 0x009e5675, 0x0048f103, 0x00df9237, 0x00093541, 0x0029d760, 0x00ff7016,
+ 0x005c1ab3, 0x008abdc5, 0x00aa5fe4, 0x007cf892, 0x00eb9ba6, 0x003d3cd0, 0x001ddef1, 0x00cb7987,
+ 0x00d02644, 0x00068132, 0x00266313, 0x00f0c465, 0x0067a751, 0x00b10027, 0x0091e206, 0x00474570,
+ 0x00e42fd5, 0x003288a3, 0x00126a82, 0x00c4cdf4, 0x0053aec0, 0x008509b6, 0x00a5eb97, 0x00734ce1,
+ 0x00b83566, 0x006e9210, 0x004e7031, 0x0098d747, 0x000fb473, 0x00d91305, 0x00f9f124, 0x002f5652,
+ 0x008c3cf7, 0x005a9b81, 0x007a79a0, 0x00acded6, 0x003bbde2, 0x00ed1a94, 0x00cdf8b5, 0x001b5fc3,
+ 0x00fb4733, 0x002de045, 0x000d0264, 0x00dba512, 0x004cc626, 0x009a6150, 0x00ba8371, 0x006c2407,
+ 0x00cf4ea2, 0x0019e9d4, 0x00390bf5, 0x00efac83, 0x0078cfb7, 0x00ae68c1, 0x008e8ae0, 0x00582d96,
+ 0x00935411, 0x0045f367, 0x00651146, 0x00b3b630, 0x0024d504, 0x00f27272, 0x00d29053, 0x00043725,
+ 0x00a75d80, 0x0071faf6, 0x005118d7, 0x0087bfa1, 0x0010dc95, 0x00c67be3, 0x00e699c2, 0x00303eb4,
+ 0x002b6177, 0x00fdc601, 0x00dd2420, 0x000b8356, 0x009ce062, 0x004a4714, 0x006aa535, 0x00bc0243,
+ 0x001f68e6, 0x00c9cf90, 0x00e92db1, 0x003f8ac7, 0x00a8e9f3, 0x007e4e85, 0x005eaca4, 0x00880bd2,
+ 0x00437255, 0x0095d523, 0x00b53702, 0x00639074, 0x00f4f340, 0x00225436, 0x0002b617, 0x00d41161,
+ 0x00777bc4, 0x00a1dcb2, 0x00813e93, 0x005799e5, 0x00c0fad1, 0x00165da7, 0x0036bf86, 0x00e018f0,
+ 0x00ad85dd, 0x007b22ab, 0x005bc08a, 0x008d67fc, 0x001a04c8, 0x00cca3be, 0x00ec419f, 0x003ae6e9,
+ 0x00998c4c, 0x004f2b3a, 0x006fc91b, 0x00b96e6d, 0x002e0d59, 0x00f8aa2f, 0x00d8480e, 0x000eef78,
+ 0x00c596ff, 0x00133189, 0x0033d3a8, 0x00e574de, 0x007217ea, 0x00a4b09c, 0x008452bd, 0x0052f5cb,
+ 0x00f19f6e, 0x00273818, 0x0007da39, 0x00d17d4f, 0x00461e7b, 0x0090b90d, 0x00b05b2c, 0x0066fc5a,
+ 0x007da399, 0x00ab04ef, 0x008be6ce, 0x005d41b8, 0x00ca228c, 0x001c85fa, 0x003c67db, 0x00eac0ad,
+ 0x0049aa08, 0x009f0d7e, 0x00bfef5f, 0x00694829, 0x00fe2b1d, 0x00288c6b, 0x00086e4a, 0x00dec93c,
+ 0x0015b0bb, 0x00c317cd, 0x00e3f5ec, 0x0035529a, 0x00a231ae, 0x007496d8, 0x005474f9, 0x0082d38f,
+ 0x0021b92a, 0x00f71e5c, 0x00d7fc7d, 0x00015b0b, 0x0096383f, 0x00409f49, 0x00607d68, 0x00b6da1e,
+ 0x0056c2ee, 0x00806598, 0x00a087b9, 0x007620cf, 0x00e143fb, 0x0037e48d, 0x001706ac, 0x00c1a1da,
+ 0x0062cb7f, 0x00b46c09, 0x00948e28, 0x0042295e, 0x00d54a6a, 0x0003ed1c, 0x00230f3d, 0x00f5a84b,
+ 0x003ed1cc, 0x00e876ba, 0x00c8949b, 0x001e33ed, 0x008950d9, 0x005ff7af, 0x007f158e, 0x00a9b2f8,
+ 0x000ad85d, 0x00dc7f2b, 0x00fc9d0a, 0x002a3a7c, 0x00bd5948, 0x006bfe3e, 0x004b1c1f, 0x009dbb69,
+ 0x0086e4aa, 0x005043dc, 0x0070a1fd, 0x00a6068b, 0x003165bf, 0x00e7c2c9, 0x00c720e8, 0x0011879e,
+ 0x00b2ed3b, 0x00644a4d, 0x0044a86c, 0x00920f1a, 0x00056c2e, 0x00d3cb58, 0x00f32979, 0x00258e0f,
+ 0x00eef788, 0x003850fe, 0x0018b2df, 0x00ce15a9, 0x0059769d, 0x008fd1eb, 0x00af33ca, 0x007994bc,
+ 0x00dafe19, 0x000c596f, 0x002cbb4e, 0x00fa1c38, 0x006d7f0c, 0x00bbd87a, 0x009b3a5b, 0x004d9d2d
+};
+
+#define INIT_CRC24 0xffffff
+
+u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len)
+{
+ while (len--)
+ fcs = (fcs >> 8) ^ tbl_crc24[(fcs ^ *cp++) & 0xff];
+ return fcs;
+}
diff --git a/src/gprs/gb_proxy.c b/src/gprs/gb_proxy.c
new file mode 100644
index 00000000..8df93a9c
--- /dev/null
+++ b/src/gprs/gb_proxy.c
@@ -0,0 +1,684 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 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/>.
+ *
+ */
+
+#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/types.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gb_proxy.h>
+
+struct gbprox_peer {
+ struct llist_head list;
+
+ /* NS-VC over which we send/receive data to this BVC */
+ struct gprs_nsvc *nsvc;
+
+ /* BVCI used for Point-to-Point to this peer */
+ uint16_t bvci;
+ int blocked;
+
+ /* Routeing Area that this peer is part of (raw 04.08 encoding) */
+ uint8_t ra[6];
+};
+
+/* Linked list of all Gb peers (except SGSN) */
+static LLIST_HEAD(gbprox_bts_peers);
+
+/* Find the gbprox_peer by its BVCI */
+static struct gbprox_peer *peer_by_bvci(uint16_t bvci)
+{
+ struct gbprox_peer *peer;
+ llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+ if (peer->bvci == bvci)
+ return peer;
+ }
+ return NULL;
+}
+
+static struct gbprox_peer *peer_by_nsvc(struct gprs_nsvc *nsvc)
+{
+ struct gbprox_peer *peer;
+ llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+ if (peer->nsvc == nsvc)
+ return peer;
+ }
+ return NULL;
+}
+
+/* look-up a peer by its Routeing Area Code (RAC) */
+static struct gbprox_peer *peer_by_rac(const uint8_t *ra)
+{
+ struct gbprox_peer *peer;
+ llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+ if (!memcmp(peer->ra, ra, 6))
+ return peer;
+ }
+ return NULL;
+}
+
+/* look-up a peer by its Location Area Code (LAC) */
+static struct gbprox_peer *peer_by_lac(const uint8_t *la)
+{
+ struct gbprox_peer *peer;
+ llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+ if (!memcmp(peer->ra, la, 5))
+ return peer;
+ }
+ return NULL;
+}
+
+static struct gbprox_peer *peer_alloc(uint16_t bvci)
+{
+ struct gbprox_peer *peer;
+
+ peer = talloc_zero(tall_bsc_ctx, struct gbprox_peer);
+ if (!peer)
+ return NULL;
+
+ peer->bvci = bvci;
+ llist_add(&peer->list, &gbprox_bts_peers);
+
+ return peer;
+}
+
+static void peer_free(struct gbprox_peer *peer)
+{
+ llist_del(&peer->list);
+ talloc_free(peer);
+}
+
+/* FIXME: this needs to go to libosmocore/msgb.c */
+static struct msgb *msgb_copy(const struct msgb *msg, const char *name)
+{
+ struct openbsc_msgb_cb *old_cb, *new_cb;
+ struct msgb *new_msg;
+
+ new_msg = msgb_alloc(msg->data_len, name);
+ if (!new_msg)
+ return NULL;
+
+ /* copy data */
+ memcpy(new_msg->_data, msg->_data, new_msg->data_len);
+
+ /* copy header */
+ new_msg->len = msg->len;
+ new_msg->data += msg->data - msg->_data;
+ new_msg->head += msg->head - msg->_data;
+ new_msg->tail += msg->tail - msg->_data;
+
+ new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data);
+ new_msg->l2h = new_msg->_data + (msg->l2h - msg->_data);
+ new_msg->l3h = new_msg->_data + (msg->l3h - msg->_data);
+ new_msg->l4h = new_msg->_data + (msg->l4h - msg->_data);
+
+ /* copy GB specific data */
+ old_cb = OBSC_MSGB_CB(msg);
+ new_cb = OBSC_MSGB_CB(new_msg);
+
+ new_cb->bssgph = new_msg->_data + (old_cb->bssgph - msg->_data);
+ new_cb->llch = new_msg->_data + (old_cb->llch - msg->_data);
+
+ /* bssgp_cell_id is a pointer into the old msgb, so we need to make
+ * it a pointer into the new msgb */
+ new_cb->bssgp_cell_id = new_msg->_data + (old_cb->bssgp_cell_id - msg->_data);
+ new_cb->nsei = old_cb->nsei;
+ new_cb->bvci = old_cb->bvci;
+ new_cb->tlli = old_cb->tlli;
+
+ return new_msg;
+}
+
+/* strip off the NS header */
+static void strip_ns_hdr(struct msgb *msg)
+{
+ int strip_len = msgb_bssgph(msg) - msg->data;
+ msgb_pull(msg, strip_len);
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2sgsn(struct msgb *old_msg, uint16_t ns_bvci)
+{
+ /* create a copy of the message so the old one can
+ * be free()d safely when we return from gbprox_rcvmsg() */
+ struct msgb *msg = msgb_copy(old_msg, "msgb_relay2sgsn");
+
+ DEBUGP(DGPRS, "NSEI=%u proxying BTS->SGSN (NS_BVCI=%u, NSEI=%u)\n",
+ msgb_nsei(msg), ns_bvci, gbcfg.nsip_sgsn_nsei);
+
+ msgb_bvci(msg) = ns_bvci;
+ msgb_nsei(msg) = gbcfg.nsip_sgsn_nsei;
+
+ strip_ns_hdr(msg);
+
+ return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2peer(struct msgb *old_msg, struct gbprox_peer *peer,
+ uint16_t ns_bvci)
+{
+ /* create a copy of the message so the old one can
+ * be free()d safely when we return from gbprox_rcvmsg() */
+ struct msgb *msg = msgb_copy(old_msg, "msgb_relay2peer");
+
+ DEBUGP(DGPRS, "NSEI=%u proxying SGSN->BSS (NS_BVCI=%u, NSEI=%u)\n",
+ msgb_nsei(msg), ns_bvci, peer->nsvc->nsei);
+
+ msgb_bvci(msg) = ns_bvci;
+ msgb_nsei(msg) = peer->nsvc->nsei;
+
+ /* Strip the old NS header, it will be replaced with a new one */
+ strip_ns_hdr(msg);
+
+ return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+static int block_unblock_peer(uint16_t ptp_bvci, uint8_t pdu_type)
+{
+ struct gbprox_peer *peer;
+
+ peer = peer_by_bvci(ptp_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n",
+ ptp_bvci);
+ return -ENOENT;
+ }
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_BVC_BLOCK_ACK:
+ peer->blocked = 1;
+ break;
+ case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+ peer->blocked = 0;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Send a message to a peer identified by ptp_bvci but using ns_bvci
+ * in the NS hdr */
+static int gbprox_relay2bvci(struct msgb *msg, uint16_t ptp_bvci,
+ uint16_t ns_bvci)
+{
+ struct gbprox_peer *peer;
+
+ peer = peer_by_bvci(ptp_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n",
+ ptp_bvci);
+ return -ENOENT;
+ }
+
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming signalling message from a BSS-side NS-VC */
+static int gbprox_rx_sig_from_bss(struct msgb *msg, struct gprs_nsvc *nsvc,
+ uint16_t ns_bvci)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct tlv_parsed tp;
+ uint8_t pdu_type = bgph->pdu_type;
+ int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+ struct gbprox_peer *from_peer;
+ struct gprs_ra_id raid;
+
+ if (ns_bvci != 0 && ns_bvci != 1) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u BVCI=%u is not signalling\n",
+ nsvc->nsei, ns_bvci);
+ return -EINVAL;
+ }
+
+ /* we actually should never see those two for BVCI == 0, but double-check
+ * just to make sure */
+ if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+ pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u UNITDATA not allowed in "
+ "signalling\n", nsvc->nsei);
+ return -EINVAL;
+ }
+
+ bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_SUSPEND:
+ case BSSGP_PDUT_RESUME:
+ /* We implement RAC snooping during SUSPEND/RESUME, since
+ * it establishes a relationsip between BVCI/peer and the
+ * routeing area code. The snooped information is then
+ * used for routing the {SUSPEND,RESUME}_[N]ACK back to
+ * the correct BSSGP */
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+ goto err_mand_ie;
+ from_peer = peer_by_nsvc(nsvc);
+ if (!from_peer)
+ goto err_no_peer;
+ memcpy(from_peer->ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
+ sizeof(from_peer->ra));
+ gsm48_parse_ra(&raid, from_peer->ra);
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u BSSGP SUSPEND/RESUME "
+ "RAC snooping: RAC %u-%u-%u-%u behind BVCI=%u, "
+ "NSVCI=%u\n",nsvc->nsei, raid.mcc, raid.mnc, raid.lac,
+ raid.rac , from_peer->bvci, nsvc->nsvci);
+ /* FIXME: This only supports one BSS per RA */
+ break;
+ case BSSGP_PDUT_BVC_RESET:
+ /* If we receive a BVC reset on the signalling endpoint, we
+ * don't want the SGSN to reset, as the signalling endpoint
+ * is common for all point-to-point BVCs (and thus all BTS) */
+ if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u Rx BVC RESET (BVCI=%u)\n",
+ nsvc->nsei, bvci);
+ if (bvci == 0) {
+ /* FIXME: only do this if SGSN is alive! */
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u Tx fake "
+ "BVC RESET ACK of BVCI=0\n", nsvc->nsei);
+ return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+ nsvc->nsei, 0, ns_bvci);
+ }
+ from_peer = peer_by_bvci(bvci);
+ if (!from_peer) {
+ /* if a PTP-BVC is reset, and we don't know that
+ * PTP-BVCI yet, we should allocate a new peer */
+ LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for "
+ "BVCI=%u via NSVCI=%u/NSEI=%u\n", bvci,
+ nsvc->nsvci, nsvc->nsei);
+ from_peer = peer_alloc(bvci);
+ from_peer->nsvc = nsvc;
+ }
+ if (TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID)) {
+ struct gprs_ra_id raid;
+ /* We have a Cell Identifier present in this
+ * PDU, this means we can extend our local
+ * state information about this particular cell
+ * */
+ memcpy(from_peer->ra,
+ TLVP_VAL(&tp, BSSGP_IE_CELL_ID),
+ sizeof(from_peer->ra));
+ gsm48_parse_ra(&raid, from_peer->ra);
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u/BVCI=%u "
+ "Cell ID %u-%u-%u-%u\n", nsvc->nsei,
+ bvci, raid.mcc, raid.mnc, raid.lac,
+ raid.rac);
+ }
+ }
+ break;
+ }
+
+ /* Normally, we can simply pass on all signalling messages from BSS to
+ * SGSN */
+ return gbprox_relay2sgsn(msg, ns_bvci);
+err_no_peer:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) cannot find peer based on RAC\n",
+ nsvc->nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+err_mand_ie:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) missing mandatory RA IE\n",
+ nsvc->nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* Receive paging request from SGSN, we need to relay to proper BSS */
+static int gbprox_rx_paging(struct msgb *msg, struct tlv_parsed *tp,
+ struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+ struct gbprox_peer *peer = NULL;
+
+ LOGP(DGPRS, LOGL_INFO, "NSEI=%u(SGSN) BSSGP PAGING ",
+ nsvc->nsei);
+ if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+ uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+ LOGPC(DGPRS, LOGL_INFO, "routing by BVCI to peer BVCI=%u\n",
+ bvci);
+ } else if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
+ peer = peer_by_rac(TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
+ LOGPC(DGPRS, LOGL_INFO, "routing by RAC to peer BVCI=%u\n",
+ peer ? peer->bvci : -1);
+ } else if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) {
+ peer = peer_by_lac(TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA));
+ LOGPC(DGPRS, LOGL_INFO, "routing by LAC to peer BVCI=%u\n",
+ peer ? peer->bvci : -1);
+ } else
+ LOGPC(DGPRS, LOGL_INFO, "\n");
+
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) BSSGP PAGING: "
+ "unable to route, missing IE\n", nsvc->nsei);
+ return -EINVAL;
+ }
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming BVC-RESET message from the SGSN */
+static int rx_reset_from_sgsn(struct msgb *msg, struct tlv_parsed *tp,
+ struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+ struct gbprox_peer *peer;
+ uint16_t ptp_bvci;
+
+ if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE,
+ NULL, msg);
+ }
+ ptp_bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+
+ if (ptp_bvci >= 2) {
+ /* A reset for a PTP BVC was received, forward it to its
+ * respective peer */
+ peer = peer_by_bvci(ptp_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u BVCI=%u: Cannot find BSS\n",
+ nsvc->nsei, ptp_bvci);
+ return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI,
+ NULL, msg);
+ }
+ return gbprox_relay2peer(msg, peer, ns_bvci);
+ }
+
+ /* A reset for the Signalling entity has been received
+ * from the SGSN. As the signalling BVCI is shared
+ * among all the BSS's that we multiplex, it needs to
+ * be relayed */
+ llist_for_each_entry(peer, &gbprox_bts_peers, list)
+ gbprox_relay2peer(msg, peer, ns_bvci);
+
+ return 0;
+}
+
+/* Receive an incoming signalling message from the SGSN-side NS-VC */
+static int gbprox_rx_sig_from_sgsn(struct msgb *msg, struct gprs_nsvc *nsvc,
+ uint16_t ns_bvci)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct tlv_parsed tp;
+ uint8_t pdu_type = bgph->pdu_type;
+ int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+ struct gbprox_peer *peer;
+ uint16_t bvci;
+ int rc = 0;
+
+ if (ns_bvci != 0 && ns_bvci != 1) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BVCI=%u is not "
+ "signalling\n", nsvc->nsei, ns_bvci);
+ /* FIXME: Send proper error message */
+ return -EINVAL;
+ }
+
+ /* we actually should never see those two for BVCI == 0, but double-check
+ * just to make sure */
+ if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+ pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) UNITDATA not allowed in "
+ "signalling\n", nsvc->nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+ }
+
+ rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+ switch (pdu_type) {
+ case BSSGP_PDUT_BVC_RESET:
+ rc = rx_reset_from_sgsn(msg, &tp, nsvc, ns_bvci);
+ break;
+ case BSSGP_PDUT_FLUSH_LL:
+ case BSSGP_PDUT_BVC_RESET_ACK:
+ /* simple case: BVCI IE is mandatory */
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ goto err_mand_ie;
+ bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+ rc = gbprox_relay2bvci(msg, bvci, ns_bvci);
+ break;
+ case BSSGP_PDUT_PAGING_PS:
+ case BSSGP_PDUT_PAGING_CS:
+ /* process the paging request (LAC/RAC lookup) */
+ rc = gbprox_rx_paging(msg, &tp, nsvc, ns_bvci);
+ break;
+ case BSSGP_PDUT_STATUS:
+ /* Some exception has occurred */
+ LOGP(DGPRS, LOGL_NOTICE,
+ "NSEI=%u(SGSN) BSSGP STATUS ", nsvc->nsei);
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) {
+ LOGPC(DGPRS, LOGL_NOTICE, "\n");
+ goto err_mand_ie;
+ }
+ LOGPC(DGPRS, LOGL_NOTICE,
+ "cause=0x%02x(%s) ", *TLVP_VAL(&tp, BSSGP_IE_CAUSE),
+ bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE)));
+ if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ uint16_t *bvci = (uint16_t *)
+ TLVP_VAL(&tp, BSSGP_IE_BVCI);
+ LOGPC(DGPRS, LOGL_NOTICE,
+ "BVCI=%u\n", ntohs(*bvci));
+ } else
+ LOGPC(DGPRS, LOGL_NOTICE, "\n");
+ break;
+ /* those only exist in the SGSN -> BSS direction */
+ case BSSGP_PDUT_SUSPEND_ACK:
+ case BSSGP_PDUT_SUSPEND_NACK:
+ case BSSGP_PDUT_RESUME_ACK:
+ case BSSGP_PDUT_RESUME_NACK:
+ /* RAC IE is mandatory */
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+ goto err_mand_ie;
+ peer = peer_by_rac(TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA));
+ if (!peer)
+ goto err_no_peer;
+ rc = gbprox_relay2peer(msg, peer, ns_bvci);
+ break;
+ case BSSGP_PDUT_BVC_BLOCK_ACK:
+ case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+ if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ goto err_mand_ie;
+ bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+ if (bvci == 0) {
+ LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BSSGP "
+ "%sBLOCK_ACK for signalling BVCI ?!?\n", nsvc->nsei,
+ pdu_type == BSSGP_PDUT_BVC_UNBLOCK_ACK ? "UN":"");
+ /* should we send STATUS ? */
+ } else {
+ /* Mark BVC as (un)blocked */
+ block_unblock_peer(bvci, pdu_type);
+ }
+ rc = gbprox_relay2bvci(msg, bvci, ns_bvci);
+ break;
+ case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+ LOGP(DGPRS, LOGL_ERROR,
+ "NSEI=%u(SGSN) BSSGP INVOKE TRACE not supported\n",nsvc->nsei);
+ rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
+ break;
+ default:
+ LOGP(DGPRS, LOGL_NOTICE, "BSSGP PDU type 0x%02x unknown\n",
+ pdu_type);
+ rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+ break;
+ }
+
+ return rc;
+err_mand_ie:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) missing mandatory IE\n",
+ nsvc->nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+err_no_peer:
+ LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) cannot find peer based on RAC\n",
+ nsvc->nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+}
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+ int rc;
+ struct gbprox_peer *peer;
+
+ /* Only BVCI=0 messages need special treatment */
+ if (ns_bvci == 0 || ns_bvci == 1) {
+ if (nsvc->remote_end_is_sgsn)
+ rc = gbprox_rx_sig_from_sgsn(msg, nsvc, ns_bvci);
+ else
+ rc = gbprox_rx_sig_from_bss(msg, nsvc, ns_bvci);
+ } else {
+ /* All other BVCI are PTP and thus can be simply forwarded */
+ if (!nsvc->remote_end_is_sgsn) {
+ return gbprox_relay2sgsn(msg, ns_bvci);
+ }
+ /* else: SGSN -> BSS direction */
+ peer = peer_by_bvci(ns_bvci);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for "
+ "BVCI=%u via NSVC=%u/NSEI=%u\n", ns_bvci,
+ nsvc->nsvci, nsvc->nsei);
+ peer = peer_alloc(ns_bvci);
+ peer->nsvc = nsvc;
+ }
+ if (peer->blocked) {
+ LOGP(DGPRS, LOGL_NOTICE, "Dropping PDU for "
+ "blocked BVCI=%u via NSVC=%u/NSEI=%u\n",
+ ns_bvci, nsvc->nsvci, nsvc->nsei);
+ return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, NULL, msg);
+ }
+ rc = gbprox_relay2peer(msg, peer, ns_bvci);
+ }
+
+ return rc;
+}
+
+int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi)
+{
+ struct gprs_nsvc *nsvc;
+
+ llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+ if (!nsvc->persistent)
+ continue;
+ gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+ }
+ return 0;
+}
+
+/* Signal handler for signals from NS layer */
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct ns_signal_data *nssd = signal_data;
+ struct gprs_nsvc *nsvc = nssd->nsvc;
+ struct gbprox_peer *peer;
+
+ if (subsys != SS_NS)
+ return 0;
+
+ if (signal == S_NS_RESET && nsvc->nsei == gbcfg.nsip_sgsn_nsei) {
+ /* We have received a NS-RESET from the NSEI and NSVC
+ * of the SGSN. This might happen with SGSN that start
+ * their own NS-RESET procedure without waiting for our
+ * NS-RESET */
+ nsvc->remote_end_is_sgsn = 1;
+ }
+
+ if (signal == S_NS_ALIVE_EXP && nsvc->remote_end_is_sgsn) {
+ LOGP(DGPRS, LOGL_NOTICE, "Tns alive expired too often, "
+ "re-starting RESET procedure\n");
+ nsip_connect(nsvc->nsi, &nsvc->ip.bts_addr, nsvc->nsei,
+ nsvc->nsvci);
+ }
+
+ if (!nsvc->remote_end_is_sgsn) {
+ /* from BSS to SGSN */
+ peer = peer_by_nsvc(nsvc);
+ if (!peer) {
+ LOGP(DGPRS, LOGL_NOTICE, "signal %u for unknown peer "
+ "NSEI=%u/NSVCI=%u\n", signal, nsvc->nsei,
+ nsvc->nsvci);
+ return 0;
+ }
+ switch (signal) {
+ case S_NS_RESET:
+ case S_NS_BLOCK:
+ if (!peer->blocked)
+ break;
+ LOGP(DGPRS, LOGL_NOTICE, "Converting NS_RESET from "
+ "NSEI=%u/NSVCI=%u into BSSGP_BVC_BLOCK to SGSN\n",
+ nsvc->nsei, nsvc->nsvci);
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, nsvc->nsei,
+ peer->bvci, 0);
+ break;
+ }
+ } else {
+ /* iterate over all BTS peers and send the respective PDU */
+ llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+ switch (signal) {
+ case S_NS_RESET:
+ gprs_ns_tx_reset(peer->nsvc, nssd->cause);
+ break;
+ case S_NS_BLOCK:
+ gprs_ns_tx_block(peer->nsvc, nssd->cause);
+ break;
+ case S_NS_UNBLOCK:
+ gprs_ns_tx_unblock(peer->nsvc);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+
+#include <osmocom/vty/command.h>
+
+gDEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy",
+ SHOW_STR "Display information about the Gb proxy")
+{
+ struct gbprox_peer *peer;
+
+ llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+ struct gprs_nsvc *nsvc = peer->nsvc;
+ struct gprs_ra_id raid;
+ gsm48_parse_ra(&raid, peer->ra);
+
+ vty_out(vty, "NSEI %5u, NS-VC %5u, PTP-BVCI %5u, "
+ "RAC %u-%u-%u-%u",
+ nsvc->nsei, nsvc->nsvci, peer->bvci,
+ raid.mcc, raid.mnc, raid.lac, raid.rac);
+ if (nsvc->ll == GPRS_NS_LL_UDP || nsvc->ll == GPRS_NS_LL_FR_GRE)
+ vty_out(vty, " %s:%u",
+ inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+ ntohs(nsvc->ip.bts_addr.sin_port));
+ if (peer->blocked)
+ vty_out(vty, " [BVC-BLOCKED]");
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
diff --git a/src/gprs/gb_proxy_main.c b/src/gprs/gb_proxy_main.c
new file mode 100644
index 00000000..b53e9857
--- /dev/null
+++ b/src/gprs/gb_proxy_main.c
@@ -0,0 +1,288 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 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/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <osmocore/process.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/vty.h>
+#include <openbsc/gb_proxy.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/telnet_interface.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+const char *openbsc_copyright =
+ "Copyright (C) 2010 Harald Welte 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 log_target *stderr_target;
+static char *config_file = "osmo_gbproxy.cfg";
+struct gbproxy_config gbcfg;
+static int daemonize = 0;
+
+/* Pointer to the SGSN peer */
+extern struct gbprox_peer *gbprox_peer_sgsn;
+
+/* call-back function for the NS protocol */
+static int proxy_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+ struct msgb *msg, u_int16_t bvci)
+{
+ int rc = 0;
+
+ switch (event) {
+ case GPRS_NS_EVT_UNIT_DATA:
+ rc = gbprox_rcvmsg(msg, nsvc, bvci);
+ break;
+ default:
+ LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+ if (msg)
+ talloc_free(msg);
+ rc = -EIO;
+ break;
+ }
+ return rc;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+ sleep(1);
+ 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:
+ talloc_report_full(tall_vty_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static void print_usage()
+{
+ printf("Usage: bsc_hack\n");
+}
+
+static void print_help()
+{
+ printf(" Some useful help...\n");
+ printf(" -h --help this text\n");
+ printf(" -d option --debug=DNS:DGPRS,0:0 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(" -T --timestamp Prefix every log line with a timestamp\n");
+ printf(" -V --version. Print the version of OpenBSC.\n");
+ printf(" -e --log-level number. Set a global loglevel.\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' },
+ { "log-level", 1, 0, 'e' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "hd:Dc:sTVe:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 's':
+ log_set_use_color(stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ config_file = strdup(optarg);
+ break;
+ case 'T':
+ log_set_print_timestamp(stderr_target, 1);
+ break;
+ case 'e':
+ log_set_log_level(stderr_target, atoi(optarg));
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+extern void *tall_msgb_ctx;
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoGbProxy",
+ .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;
+ int rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 0, "nsip_proxy");
+ tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ signal(SIGPIPE, SIG_IGN);
+
+ log_init(&log_info);
+ stderr_target = log_target_create_stderr();
+ log_add_target(stderr_target);
+ log_set_all_filter(stderr_target, 1);
+
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds();
+ gbproxy_vty_init();
+
+ handle_options(argc, argv);
+
+ rate_ctr_init(tall_bsc_ctx);
+
+ rc = telnet_init(tall_bsc_ctx, &dummy_network, 4246);
+ if (rc < 0)
+ exit(1);
+
+ bssgp_nsi = gprs_ns_instantiate(&proxy_ns_cb);
+ if (!bssgp_nsi) {
+ LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+ exit(1);
+ }
+ gbcfg.nsi = bssgp_nsi;
+ gprs_ns_vty_init(bssgp_nsi);
+ register_signal_handler(SS_NS, &gbprox_signal, NULL);
+
+ rc = gbproxy_parse_config(config_file, &gbcfg);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+ exit(2);
+ }
+
+ if (!nsvc_by_nsei(gbcfg.nsi, gbcfg.nsip_sgsn_nsei)) {
+ LOGP(DGPRS, LOGL_FATAL, "You cannot proxy to NSEI %u "
+ "without creating that NSEI before\n",
+ gbcfg.nsip_sgsn_nsei);
+ exit(2);
+ }
+
+ rc = gprs_ns_nsip_listen(bssgp_nsi);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n");
+ exit(2);
+ }
+
+ rc = gprs_ns_frgre_listen(bssgp_nsi);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE "
+ "socket. Do you have CAP_NET_RAW?\n");
+ exit(2);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ /* Reset all the persistent NS-VCs that we've read from the config */
+ gbprox_reset_persistent_nsvcs(bssgp_nsi);
+
+ while (1) {
+ rc = bsc_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
diff --git a/src/gprs/gb_proxy_vty.c b/src/gprs/gb_proxy_vty.c
new file mode 100644
index 00000000..05f5b1e4
--- /dev/null
+++ b/src/gprs/gb_proxy_vty.c
@@ -0,0 +1,104 @@
+/*
+ * (C) 2010 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/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gb_proxy.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+static struct gbproxy_config *g_cfg = NULL;
+
+/*
+ * vty code for mgcp below
+ */
+static struct cmd_node gbproxy_node = {
+ GBPROXY_NODE,
+ "%s(gbproxy)#",
+ 1,
+};
+
+static int config_write_gbproxy(struct vty *vty)
+{
+ vty_out(vty, "gbproxy%s", VTY_NEWLINE);
+
+ vty_out(vty, " sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei,
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy,
+ cfg_gbproxy_cmd,
+ "gbproxy",
+ "Configure the Gb proxy")
+{
+ vty->node = GBPROXY_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_nsei,
+ cfg_nsip_sgsn_nsei_cmd,
+ "sgsn nsei <0-65534>",
+ "Set the NSEI to be used in the connection with the SGSN")
+{
+ unsigned int port = atoi(argv[0]);
+
+ g_cfg->nsip_sgsn_nsei = port;
+ return CMD_SUCCESS;
+}
+
+int gbproxy_vty_init(void)
+{
+ install_element_ve(&show_gbproxy_cmd);
+
+ install_element(CONFIG_NODE, &cfg_gbproxy_cmd);
+ install_node(&gbproxy_node, config_write_gbproxy);
+ install_default(GBPROXY_NODE);
+ install_element(GBPROXY_NODE, &ournode_exit_cmd);
+ install_element(GBPROXY_NODE, &ournode_end_cmd);
+ install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd);
+
+ return 0;
+}
+
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg)
+{
+ int rc;
+
+ 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;
+ }
+
+ return 0;
+}
+
diff --git a/src/gprs/gprs_gmm.c b/src/gprs/gprs_gmm.c
new file mode 100644
index 00000000..949cd96e
--- /dev/null
+++ b/src/gprs/gprs_gmm.c
@@ -0,0 +1,1597 @@
+/* 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) 2009-2010 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/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/db.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/signal.h>
+#include <osmocore/talloc.h>
+#include <osmocore/rate_ctr.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/paging.h>
+#include <openbsc/transaction.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/sgsn.h>
+
+#include <pdp.h>
+
+#define PTMSI_ALLOC
+
+/* Section 11.2.2 / Table 11.4 MM timers netowkr side */
+#define GSM0408_T3322_SECS 6 /* DETACH_REQ -> DETACH_ACC */
+#define GSM0408_T3350_SECS 6 /* waiting for ATT/RAU/TMSI COMPL */
+#define GSM0408_T3360_SECS 6 /* waiting for AUTH/CIPH RESP */
+#define GSM0408_T3370_SECS 6 /* waiting for ID RESP */
+
+/* Section 11.2.2 / Table 11.4a MM timers netowkr side */
+#define GSM0408_T3313_SECS 30 /* waiting for paging response */
+#define GSM0408_T3314_SECS 44 /* force to STBY on expiry */
+#define GSM0408_T3316_SECS 44
+
+/* Section 11.3 / Table 11.2d Timers of Session Management - network side */
+#define GSM0408_T3385_SECS 8 /* wait for ACT PDP CTX REQ */
+#define GSM0408_T3386_SECS 8 /* wait for MODIFY PDP CTX ACK */
+#define GSM0408_T3395_SECS 8 /* wait for DEACT PDP CTX ACK */
+#define GSM0408_T3397_SECS 8 /* wait for DEACT AA PDP CTX ACK */
+
+extern struct sgsn_instance *sgsn;
+
+/* Protocol related stuff, should go into libosmocore */
+
+/* 10.5.5.14 GPRS MM Cause / Table 10.5.147 */
+const struct value_string gmm_cause_names[] = {
+ { GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown in HLR" },
+ { GMM_CAUSE_ILLEGAL_MS, "Illegal MS" },
+ { GMM_CAUSE_ILLEGAL_ME, "Illegal ME" },
+ { GMM_CAUSE_GPRS_NOTALLOWED, "GPRS services not allowed" },
+ { GMM_CAUSE_GPRS_OTHER_NOTALLOWED,
+ "GPRS services and non-GPRS services not allowed" },
+ { GMM_CAUSE_MS_ID_NOT_DERIVED,
+ "MS identity cannot be derived by the network" },
+ { GMM_CAUSE_IMPL_DETACHED, "Implicitly detached" },
+ { GMM_CAUSE_PLMN_NOTALLOWED, "PLMN not allowed" },
+ { GMM_CAUSE_LA_NOTALLOWED, "Location Area not allowed" },
+ { GMM_CAUSE_ROAMING_NOTALLOWED,
+ "Roaming not allowed in this location area" },
+ { GMM_CAUSE_NO_GPRS_PLMN,
+ "GPRS services not allowed in this PLMN" },
+ { GMM_CAUSE_MSC_TEMP_NOTREACH, "MSC temporarily not reachable" },
+ { GMM_CAUSE_NET_FAIL, "Network failure" },
+ { GMM_CAUSE_CONGESTION, "Congestion" },
+ { GMM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
+ { GMM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
+ { GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
+ "Message type non-existant or not implemented" },
+ { GMM_CAUSE_MSGT_INCOMP_P_STATE,
+ "Message type not compatible with protocol state" },
+ { GMM_CAUSE_IE_NOTEXIST_NOTIMPL,
+ "Information element non-existent or not implemented" },
+ { GMM_CAUSE_COND_IE_ERR, "Conditional IE error" },
+ { GMM_CAUSE_MSG_INCOMP_P_STATE,
+ "Message not compatible with protocol state " },
+ { GMM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+ { 0, NULL }
+};
+
+/* 10.5.6.6 SM Cause / Table 10.5.157 */
+const struct value_string gsm_cause_names[] = {
+ { GSM_CAUSE_INSUFF_RSRC, "Insufficient resources" },
+ { GSM_CAUSE_MISSING_APN, "Missing or unknown APN" },
+ { GSM_CAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" },
+ { GSM_CAUSE_AUTH_FAILED, "User Authentication failed" },
+ { GSM_CAUSE_ACT_REJ_GGSN, "Activation rejected by GGSN" },
+ { GSM_CAUSE_ACT_REJ_UNSPEC, "Activation rejected, unspecified" },
+ { GSM_CAUSE_SERV_OPT_NOTSUPP, "Service option not supported" },
+ { GSM_CAUSE_REQ_SERV_OPT_NOTSUB,
+ "Requested service option not subscribed" },
+ { GSM_CAUSE_SERV_OPT_TEMP_OOO,
+ "Service option temporarily out of order" },
+ { GSM_CAUSE_NSAPI_IN_USE, "NSAPI already used" },
+ { GSM_CAUSE_DEACT_REGULAR, "Regular deactivation" },
+ { GSM_CAUSE_QOS_NOT_ACCEPTED, "QoS not accepted" },
+ { GSM_CAUSE_NET_FAIL, "Network Failure" },
+ { GSM_CAUSE_REACT_RQD, "Reactivation required" },
+ { GSM_CAUSE_FEATURE_NOTSUPP, "Feature not supported " },
+ { GSM_CAUSE_INVALID_TRANS_ID, "Invalid transaction identifier" },
+ { GSM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
+ { GSM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
+ { GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
+ "Message type non-existant or not implemented" },
+ { GSM_CAUSE_MSGT_INCOMP_P_STATE,
+ "Message type not compatible with protocol state" },
+ { GSM_CAUSE_IE_NOTEXIST_NOTIMPL,
+ "Information element non-existent or not implemented" },
+ { GSM_CAUSE_COND_IE_ERR, "Conditional IE error" },
+ { GSM_CAUSE_MSG_INCOMP_P_STATE,
+ "Message not compatible with protocol state " },
+ { GSM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+ { 0, NULL }
+};
+
+/* 10.5.5.2 */
+const struct value_string gprs_att_t_strs[] = {
+ { GPRS_ATT_T_ATTACH, "GPRS attach" },
+ { GPRS_ATT_T_ATT_WHILE_IMSI, "GPRS attach while IMSI attached" },
+ { GPRS_ATT_T_COMBINED, "Combined GPRS/IMSI attach" },
+ { 0, NULL }
+};
+
+const struct value_string gprs_upd_t_strs[] = {
+ { GPRS_UPD_T_RA, "RA updating" },
+ { GPRS_UPD_T_RA_LA, "combined RA/LA updating" },
+ { GPRS_UPD_T_RA_LA_IMSI_ATT, "combined RA/LA updating + IMSI attach" },
+ { GPRS_UPD_T_PERIODIC, "periodic updating" },
+ { 0, NULL }
+};
+
+/* 10.5.5.5 */
+const struct value_string gprs_det_t_mo_strs[] = {
+ { GPRS_DET_T_MO_GPRS, "GPRS detach" },
+ { GPRS_DET_T_MO_IMSI, "IMSI detach" },
+ { GPRS_DET_T_MO_COMBINED, "Combined GPRS/IMSI detach" },
+ { 0, NULL }
+};
+
+static const struct tlv_definition gsm48_gmm_att_tlvdef = {
+ .def = {
+ [GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 },
+ [GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 },
+ [GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 },
+ [GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 },
+ [GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 },
+ [GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 },
+ [GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 },
+ },
+};
+
+static const struct tlv_definition gsm48_sm_att_tlvdef = {
+ .def = {
+ [GSM48_IE_GSM_APN] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GSM_PROTO_CONF_OPT] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GSM_PDP_ADDR] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GSM_AA_TMR] = { TLV_TYPE_TV, 1 },
+ [GSM48_IE_GSM_NAME_FULL] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GSM_NAME_SHORT] = { TLV_TYPE_TLV, 0 },
+ [GSM48_IE_GSM_TIMEZONE] = { TLV_TYPE_FIXED, 1 },
+ [GSM48_IE_GSM_UTC_AND_TZ] = { TLV_TYPE_FIXED, 7 },
+ [GSM48_IE_GSM_LSA_ID] = { TLV_TYPE_TLV, 0 },
+ },
+};
+
+/* Our implementation, should be kept in SGSN */
+
+static void mmctx_timer_cb(void *_mm);
+
+static void mmctx_timer_start(struct sgsn_mm_ctx *mm, unsigned int T,
+ unsigned int seconds)
+{
+ if (bsc_timer_pending(&mm->timer))
+ LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old "
+ "timer %u pending\n", T, mm->T);
+ mm->T = T;
+ mm->num_T_exp = 0;
+
+ /* FIXME: we should do this only once ? */
+ mm->timer.data = mm;
+ mm->timer.cb = &mmctx_timer_cb;
+
+ bsc_schedule_timer(&mm->timer, seconds, 0);
+}
+
+static void mmctx_timer_stop(struct sgsn_mm_ctx *mm, unsigned int T)
+{
+ if (mm->T != T)
+ LOGP(DMM, LOGL_ERROR, "Stopping MM timer %u but "
+ "%u is running\n", T, mm->T);
+ bsc_del_timer(&mm->timer);
+}
+
+/* Send a message through the underlying layer */
+static int gsm48_gmm_sendmsg(struct msgb *msg, int command,
+ const struct sgsn_mm_ctx *mm)
+{
+ if (mm)
+ rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_SIG_OUT]);
+
+ /* caller needs to provide TLLI, BVCI and NSEI */
+ return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command, mm);
+}
+
+/* copy identifiers from old message to new message, this
+ * is required so lower layers can route it correctly */
+static void gmm_copy_id(struct msgb *msg, const struct msgb *old)
+{
+ msgb_tlli(msg) = msgb_tlli(old);
+ msgb_bvci(msg) = msgb_bvci(old);
+ msgb_nsei(msg) = msgb_nsei(old);
+}
+
+/* Store BVCI/NSEI in MM context */
+static void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg)
+{
+ mm->bvci = msgb_bvci(msg);
+ mm->nsei = msgb_nsei(msg);
+}
+
+/* Store BVCI/NSEI in MM context */
+static void mmctx2msgid(struct msgb *msg, const struct sgsn_mm_ctx *mm)
+{
+ msgb_tlli(msg) = mm->tlli;
+ msgb_bvci(msg) = mm->bvci;
+ msgb_nsei(msg) = mm->nsei;
+}
+
+/* Chapter 9.4.18 */
+static int _tx_status(struct msgb *msg, uint8_t cause,
+ struct sgsn_mm_ctx *mmctx, int sm)
+{
+ struct gsm48_hdr *gh;
+
+ /* MMCTX might be NULL! */
+
+ DEBUGP(DMM, "<- GPRS MM STATUS (cause: %s)\n",
+ get_value_string(gmm_cause_names, cause));
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+ if (sm) {
+ gh->proto_discr = GSM48_PDISC_SM_GPRS;
+ gh->msg_type = GSM48_MT_GSM_STATUS;
+ } else {
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_STATUS;
+ }
+ gh->data[0] = cause;
+
+ return gsm48_gmm_sendmsg(msg, 0, mmctx);
+}
+static int gsm48_tx_gmm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+
+ mmctx2msgid(msg, mmctx);
+ return _tx_status(msg, cause, mmctx, 0);
+};
+static int gsm48_tx_gmm_status_oldmsg(struct msgb *oldmsg, uint8_t cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+
+ gmm_copy_id(msg, oldmsg);
+ return _tx_status(msg, cause, NULL, 0);
+}
+static int gsm48_tx_sm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+
+ mmctx2msgid(msg, mmctx);
+ return _tx_status(msg, cause, mmctx, 1);
+};
+static int gsm48_tx_sm_status_oldmsg(struct msgb *oldmsg, uint8_t cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+
+ gmm_copy_id(msg, oldmsg);
+ return _tx_status(msg, cause, NULL, 1);
+}
+
+
+static struct gsm48_qos default_qos = {
+ .delay_class = 4, /* best effort */
+ .reliab_class = GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT,
+ .peak_tput = GSM48_QOS_PEAK_TPUT_32000bps,
+ .preced_class = GSM48_QOS_PC_NORMAL,
+ .mean_tput = GSM48_QOS_MEAN_TPUT_BEST_EFFORT,
+ .traf_class = GSM48_QOS_TC_INTERACTIVE,
+ .deliv_order = GSM48_QOS_DO_UNORDERED,
+ .deliv_err_sdu = GSM48_QOS_ERRSDU_YES,
+ .max_sdu_size = GSM48_QOS_MAXSDU_1520,
+ .max_bitrate_up = GSM48_QOS_MBRATE_63k,
+ .max_bitrate_down = GSM48_QOS_MBRATE_63k,
+ .resid_ber = GSM48_QOS_RBER_5e_2,
+ .sdu_err_ratio = GSM48_QOS_SERR_1e_2,
+ .handling_prio = 3,
+ .xfer_delay = 0x10, /* 200ms */
+ .guar_bitrate_up = GSM48_QOS_MBRATE_0k,
+ .guar_bitrate_down = GSM48_QOS_MBRATE_0k,
+ .sig_ind = 0, /* not optimised for signalling */
+ .max_bitrate_down_ext = 0, /* use octet 9 */
+ .guar_bitrate_down_ext = 0, /* use octet 13 */
+};
+
+/* Chapter 9.4.2: Attach accept */
+static int gsm48_tx_gmm_att_ack(struct sgsn_mm_ctx *mm)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ struct gsm48_attach_ack *aa;
+ uint8_t *ptsig, *mid;
+
+ DEBUGP(DMM, "<- GPRS ATTACH ACCEPT (new P-TMSI=0x%08x)\n", mm->p_tmsi);
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_ATTACH_ACK;
+
+ aa = (struct gsm48_attach_ack *) msgb_put(msg, sizeof(*aa));
+ aa->force_stby = 0; /* not indicated */
+ aa->att_result = 1; /* GPRS only */
+ aa->ra_upd_timer = GPRS_TMR_MINUTE | 10;
+ aa->radio_prio = 4; /* lowest */
+ gsm48_construct_ra(aa->ra_id.digits, &mm->ra);
+
+#if 0
+ /* Optional: P-TMSI signature */
+ msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG);
+ ptsig = msgb_put(msg, 3);
+ ptsig[0] = mm->p_tmsi_sig >> 16;
+ ptsig[1] = mm->p_tmsi_sig >> 8;
+ ptsig[2] = mm->p_tmsi_sig & 0xff;
+
+ /* Optional: Negotiated Ready timer value */
+#endif
+
+#ifdef PTMSI_ALLOC
+ /* Optional: Allocated P-TMSI */
+ mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+ gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi);
+ mid[0] = GSM48_IE_GMM_ALLOC_PTMSI;
+#endif
+
+ /* Optional: MS-identity (combined attach) */
+ /* Optional: GMM cause (partial attach result for combined attach) */
+
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Chapter 9.4.5: Attach reject */
+static int _tx_gmm_att_rej(struct msgb *msg, uint8_t gmm_cause)
+{
+ struct gsm48_hdr *gh;
+
+ DEBUGP(DMM, "<- GPRS ATTACH REJECT\n");
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_ATTACH_REJ;
+ gh->data[0] = gmm_cause;
+
+ return gsm48_gmm_sendmsg(msg, 0, NULL);
+}
+static int gsm48_tx_gmm_att_rej_oldmsg(const struct msgb *old_msg,
+ uint8_t gmm_cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ gmm_copy_id(msg, old_msg);
+ return _tx_gmm_att_rej(msg, gmm_cause);
+}
+static int gsm48_tx_gmm_att_rej(struct sgsn_mm_ctx *mm,
+ uint8_t gmm_cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ mmctx2msgid(msg, mm);
+ return _tx_gmm_att_rej(msg, gmm_cause);
+}
+
+/* Chapter 9.4.6.2 Detach accept */
+static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+
+ DEBUGP(DMM, "<- GPRS DETACH ACCEPT\n");
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_DETACH_ACK;
+ gh->data[0] = force_stby;
+
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Transmit Chapter 9.4.12 Identity Request */
+static int gsm48_tx_gmm_id_req(struct sgsn_mm_ctx *mm, uint8_t id_type)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+
+ DEBUGP(DMM, "<- GPRS IDENTITY REQUEST: mi_type=%02x\n", id_type);
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_ID_REQ;
+ /* 10.5.5.9 ID type 2 + identity type and 10.5.5.7 'force to standby' IE */
+ gh->data[0] = id_type & 0xf;
+
+ return gsm48_gmm_sendmsg(msg, 1, mm);
+}
+
+/* Section 9.4.9: Authentication and Ciphering Request */
+static int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, uint8_t *rand,
+ uint8_t key_seq, uint8_t algo)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ struct gsm48_auth_ciph_req *acreq;
+ uint8_t *m_rand, *m_cksn;
+
+ DEBUGP(DMM, "<- GPRS AUTH AND CIPHERING REQ (rand = %s)\n",
+ hexdump(rand, 16));
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REQ;
+
+ acreq = (struct gsm48_auth_ciph_req *) msgb_put(msg, sizeof(*acreq));
+ acreq->ciph_alg = algo & 0xf;
+ acreq->imeisv_req = 0x1;
+ acreq->force_stby = 0x0;
+ acreq->ac_ref_nr = 0x0; /* FIXME: increment this? */
+
+ /* Only if authentication is requested we need to set RAND + CKSN */
+ if (rand) {
+ m_rand = msgb_put(msg, 16+1);
+ m_rand[0] = GSM48_IE_GMM_AUTH_RAND;
+ memcpy(m_rand+1, rand, 16);
+
+ m_cksn = msgb_put(msg, 1+1);
+ m_cksn[0] = GSM48_IE_GMM_CIPH_CKSN;
+ m_cksn[1] = key_seq;
+ }
+
+ /* Start T3360 */
+ mmctx_timer_start(mm, 3360, GSM0408_T3360_SECS);
+
+ /* FIXME: make sure we don't send any other messages to the MS */
+
+ return gsm48_gmm_sendmsg(msg, 1, mm);
+}
+
+/* Section 9.4.11: Authentication and Ciphering Reject */
+static int gsm48_tx_gmm_auth_ciph_rej(struct sgsn_mm_ctx *mm)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+
+ DEBUGP(DMM, "<- GPRS AUTH AND CIPH REJECT\n");
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REJ;
+
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Section 9.4.10: Authentication and Ciphering Response */
+static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ struct gsm48_auth_ciph_resp *acr = (struct gsm48_auth_ciph_resp *)gh->data;
+ struct tlv_parsed tp;
+ int rc;
+
+ /* FIXME: Stop T3360 */
+
+ rc = tlv_parse(&tp, &gsm48_gmm_att_tlvdef, acr->data,
+ (msg->data + msg->len) - acr->data, 0, 0);
+
+ /* FIXME: compare ac_ref? */
+
+ if (!TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_SRES) ||
+ !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV)) {
+ /* FIXME: missing mandatory IE */
+ }
+
+ /* FIXME: compare SRES with what we expected */
+ /* FIXME: enable LLC cipheirng */
+ return 0;
+}
+
+/* Check if we can already authorize a subscriber */
+static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx,
+ enum gprs_t3350_mode t3350_mode)
+{
+ if (strlen(ctx->imei) && strlen(ctx->imsi)) {
+#ifdef PTMSI_ALLOC
+ /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */
+ ctx->t3350_mode = t3350_mode;
+ mmctx_timer_start(ctx, 3350, GSM0408_T3350_SECS);
+#endif
+ ctx->mm_state = GMM_REGISTERED_NORMAL;
+ return gsm48_tx_gmm_att_ack(ctx);
+ }
+ if (!strlen(ctx->imei)) {
+ ctx->mm_state = GMM_COMMON_PROC_INIT;
+ ctx->t3370_id_type = GSM_MI_TYPE_IMEI;
+ mmctx_timer_start(ctx, 3370, GSM0408_T3370_SECS);
+ return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMEI);
+ }
+
+ if (!strlen(ctx->imsi)) {
+ ctx->mm_state = GMM_COMMON_PROC_INIT;
+ ctx->t3370_id_type = GSM_MI_TYPE_IMSI;
+ mmctx_timer_start(ctx, 3370, GSM0408_T3370_SECS);
+ return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMSI);
+ }
+
+ return 0;
+}
+
+/* Parse Chapter 9.4.13 Identity Response */
+static int gsm48_rx_gmm_id_resp(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ 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, "-> GMM IDENTITY RESPONSE: mi_type=0x%02x MI(%s) ",
+ mi_type, mi_string);
+
+ if (!ctx) {
+ DEBUGP(DMM, "from unknown TLLI 0x%08x?!?\n", msgb_tlli(msg));
+ return -EINVAL;
+ }
+
+ if (mi_type == ctx->t3370_id_type)
+ mmctx_timer_stop(ctx, 3370);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ /* we already have a mm context with current TLLI, but no
+ * P-TMSI / IMSI yet. What we now need to do is to fill
+ * this initial context with data from the HLR */
+ if (strlen(ctx->imsi) == 0) {
+ /* Check if we already have a MM context for this IMSI */
+ struct sgsn_mm_ctx *ictx;
+ ictx = sgsn_mm_ctx_by_imsi(mi_string);
+ if (ictx) {
+ DEBUGP(DMM, "Deleting old MM Context for same IMSI ",
+ "p_tmsi_old=0x%08x, p_tmsi_new=0x%08x\n",
+ ictx->p_tmsi, ctx->p_tmsi);
+ gprs_llgmm_assign(ictx->llme, ictx->tlli,
+ 0xffffffff, GPRS_ALGO_GEA0, NULL);
+ sgsn_mm_ctx_free(ictx);
+ }
+ }
+ strncpy(ctx->imsi, mi_string, sizeof(ctx->imei));
+ break;
+ case GSM_MI_TYPE_IMEI:
+ strncpy(ctx->imei, mi_string, sizeof(ctx->imei));
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ break;
+ }
+
+ DEBUGPC(DMM, "\n");
+ /* Check if we can let the mobile station enter */
+ return gsm48_gmm_authorize(ctx, ctx->t3350_mode);
+}
+
+/* Section 9.4.1 Attach request */
+static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg,
+ struct gprs_llc_llme *llme)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ uint8_t *cur = gh->data, *msnc, *mi, *old_ra_info, *ms_ra_acc_cap;
+ uint8_t msnc_len, att_type, mi_len, mi_type, ms_ra_acc_cap_len;
+ uint16_t drx_par;
+ uint32_t tmsi;
+ char mi_string[GSM48_MI_SIZE];
+ struct gprs_ra_id ra_id;
+ uint16_t cid;
+
+ DEBUGP(DMM, "-> GMM ATTACH REQUEST ");
+
+ /* As per TS 04.08 Chapter 4.7.1.4, the attach request arrives either
+ * with a foreign TLLI (P-TMSI that was allocated to the MS before),
+ * or with random TLLI. */
+
+ cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+
+ /* MS network capability 10.5.5.12 */
+ msnc_len = *cur++;
+ msnc = cur;
+ if (msnc_len > 8)
+ goto err_inval;
+ cur += msnc_len;
+
+ /* aTTACH Type 10.5.5.2 */
+ att_type = *cur++ & 0x0f;
+
+ /* DRX parameter 10.5.5.6 */
+ drx_par = *cur++ << 8;
+ drx_par |= *cur++;
+
+ /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */
+ mi_len = *cur++;
+ mi = cur;
+ if (mi_len > 8)
+ goto err_inval;
+ mi_type = *mi & GSM_MI_TYPE_MASK;
+ cur += mi_len;
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+
+ DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string,
+ get_value_string(gprs_att_t_strs, att_type));
+
+ /* Old routing area identification 10.5.5.15 */
+ old_ra_info = cur;
+ cur += 6;
+
+ /* MS Radio Access Capability 10.5.5.12a */
+ ms_ra_acc_cap_len = *cur++;
+ ms_ra_acc_cap = cur;
+ if (ms_ra_acc_cap_len > 51)
+ goto err_inval;
+
+ /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status */
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ /* Try to find MM context based on IMSI */
+ if (!ctx)
+ ctx = sgsn_mm_ctx_by_imsi(mi_string);
+ if (!ctx) {
+#if 0
+ return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_IMSI_UNKNOWN);
+#else
+ /* As a temorary hack, we simply assume that the IMSI exists,
+ * as long as it is part of 'our' network */
+ char mccmnc[16];
+ snprintf(mccmnc, sizeof(mccmnc), "%03d%02d", ra_id.mcc, ra_id.mnc);
+ if (strncmp(mccmnc, mi_string, 5)) {
+ LOGP(DMM, LOGL_INFO, "Rejecting ATTACH REQUESET IMSI=%s\n",
+ mi_string);
+ return gsm48_tx_gmm_att_rej_oldmsg(msg,
+ GMM_CAUSE_GPRS_NOTALLOWED);
+ }
+ ctx = sgsn_mm_ctx_alloc(0, &ra_id);
+ if (!ctx)
+ return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_NET_FAIL);
+ strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi));
+#endif
+ }
+ ctx->tlli = msgb_tlli(msg);
+ ctx->llme = llme;
+ msgid2mmctx(ctx, msg);
+ break;
+ case GSM_MI_TYPE_TMSI:
+ memcpy(&tmsi, mi+1, 4);
+ tmsi = ntohl(tmsi);
+ /* Try to find MM context based on P-TMSI */
+ if (!ctx)
+ ctx = sgsn_mm_ctx_by_ptmsi(tmsi);
+ if (!ctx) {
+ /* Allocate a context as most of our code expects one.
+ * Context will not have an IMSI ultil ID RESP is received */
+ ctx = sgsn_mm_ctx_alloc(msgb_tlli(msg), &ra_id);
+ ctx->p_tmsi = tmsi;
+ }
+ ctx->tlli = msgb_tlli(msg);
+ ctx->llme = llme;
+ msgid2mmctx(ctx, msg);
+ break;
+ default:
+ LOGP(DMM, LOGL_NOTICE, "Rejecting ATTACH REQUEST with "
+ "MI type %u\n", mi_type);
+ return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+ }
+ /* Update MM Context with currient RA and Cell ID */
+ ctx->ra = ra_id;
+ ctx->cell_id = cid;
+ /* Update MM Context with other data */
+ ctx->drx_parms = drx_par;
+ ctx->ms_radio_access_capa.len = ms_ra_acc_cap_len;
+ memcpy(ctx->ms_radio_access_capa.buf, ms_ra_acc_cap, ms_ra_acc_cap_len);
+ ctx->ms_network_capa.len = msnc_len;
+ memcpy(ctx->ms_network_capa.buf, msnc, msnc_len);
+
+#ifdef PTMSI_ALLOC
+ /* Allocate a new P-TMSI (+ P-TMSI signature) and update TLLI */
+ ctx->p_tmsi_old = ctx->p_tmsi;
+ ctx->p_tmsi = sgsn_alloc_ptmsi();
+#endif
+ /* Even if there is no P-TMSI allocated, the MS will switch from
+ * foreign TLLI to local TLLI */
+ ctx->tlli_new = gprs_tmsi2tlli(ctx->p_tmsi, TLLI_LOCAL);
+
+ /* Inform LLC layer about new TLLI but keep old active */
+ gprs_llgmm_assign(ctx->llme, ctx->tlli, ctx->tlli_new,
+ GPRS_ALGO_GEA0, NULL);
+
+ DEBUGPC(DMM, "\n");
+ return ctx ? gsm48_gmm_authorize(ctx, GMM_T3350_MODE_ATT) : 0;
+
+err_inval:
+ DEBUGPC(DMM, "\n");
+ return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_SEM_INCORR_MSG);
+}
+
+/* Section 4.7.4.1 / 9.4.5.2 MO Detach request */
+static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ struct sgsn_pdp_ctx *pdp, *pdp2;
+ uint8_t detach_type, power_off;
+ int rc;
+
+ detach_type = gh->data[0] & 0x7;
+ power_off = gh->data[0] & 0x8;
+
+ /* FIXME: In 24.008 there is an optional P-TMSI and P-TMSI signature IE */
+
+ DEBUGP(DMM, "-> GMM DETACH REQUEST TLLI=0x%08x type=%s %s\n",
+ msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type),
+ power_off ? "Power-off" : "");
+
+ /* Mark MM state as deregistered */
+ ctx->mm_state = GMM_DEREGISTERED;
+
+ /* delete all existing PDP contexts for this MS */
+ llist_for_each_entry_safe(pdp, pdp2, &ctx->pdp_list, list) {
+ LOGP(DMM, LOGL_NOTICE, "Dropping PDP context for NSAPI=%u "
+ "due to GPRS DETACH REQUEST\n", pdp->nsapi);
+ sgsn_delete_pdp_ctx(pdp);
+ /* FIXME: the callback wants to transmit a DEACT PDP CTX ACK,
+ * which is quite stupid for a MS that has just detached.. */
+ }
+
+ /* force_stby = 0 */
+ rc = gsm48_tx_gmm_det_ack(ctx, 0);
+
+ /* TLLI unassignment */
+ gprs_llgmm_assign(ctx->llme, ctx->tlli, 0xffffffff,
+ GPRS_ALGO_GEA0, NULL);
+
+ return rc;
+}
+
+/* Chapter 9.4.15: Routing area update accept */
+static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ struct gsm48_ra_upd_ack *rua;
+ uint8_t *mid;
+
+ DEBUGP(DMM, "<- ROUTING AREA UPDATE ACCEPT\n");
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_RA_UPD_ACK;
+
+ rua = (struct gsm48_ra_upd_ack *) msgb_put(msg, sizeof(*rua));
+ rua->force_stby = 0; /* not indicated */
+ rua->upd_result = 0; /* RA updated */
+ rua->ra_upd_timer = GPRS_TMR_MINUTE | 10;
+
+ gsm48_construct_ra(rua->ra_id.digits, &mm->ra);
+
+#if 0
+ /* Optional: P-TMSI signature */
+ msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG);
+ ptsig = msgb_put(msg, 3);
+ ptsig[0] = mm->p_tmsi_sig >> 16;
+ ptsig[1] = mm->p_tmsi_sig >> 8;
+ ptsig[2] = mm->p_tmsi_sig & 0xff;
+#endif
+
+#ifdef PTMSI_ALLOC
+ /* Optional: Allocated P-TMSI */
+ mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+ gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi);
+ mid[0] = GSM48_IE_GMM_ALLOC_PTMSI;
+#endif
+
+ /* Option: MS ID, ... */
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Chapter 9.4.17: Routing area update reject */
+static int gsm48_tx_gmm_ra_upd_rej(struct msgb *old_msg, uint8_t cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+
+ DEBUGP(DMM, "<- ROUTING AREA UPDATE REJECT\n");
+
+ gmm_copy_id(msg, old_msg);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2);
+ gh->proto_discr = GSM48_PDISC_MM_GPRS;
+ gh->msg_type = GSM48_MT_GMM_RA_UPD_REJ;
+ gh->data[0] = cause;
+ gh->data[1] = 0; /* ? */
+
+ /* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+ return gsm48_gmm_sendmsg(msg, 0, NULL);
+}
+
+static void process_ms_ctx_status(struct sgsn_mm_ctx *mmctx,
+ uint16_t pdp_status)
+{
+ struct sgsn_pdp_ctx *pdp, *pdp2;
+ /* 24.008 4.7.5.1.3: If the PDP context status information element is
+ * included in ROUTING AREA UPDATE REQUEST message, then the network
+ * shall deactivate all those PDP contexts locally (without peer to
+ * peer signalling between the MS and the network), which are not in SM
+ * state PDP-INACTIVE on network side but are indicated by the MS as
+ * being in state PDP-INACTIVE. */
+
+ llist_for_each_entry_safe(pdp, pdp2, &mmctx->pdp_list, list) {
+ if (!(pdp_status & (1 << pdp->nsapi))) {
+ LOGP(DMM, LOGL_NOTICE, "Dropping PDP context for NSAPI=%u "
+ "due to PDP CTX STATUS IE= 0x%04x\n",
+ pdp->nsapi, pdp_status);
+ sgsn_delete_pdp_ctx(pdp);
+ }
+ }
+}
+
+/* Chapter 9.4.14: Routing area update request */
+static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
+ struct gprs_llc_llme *llme)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ uint8_t *cur = gh->data;
+ uint8_t *ms_ra_acc_cap;
+ uint8_t ms_ra_acc_cap_len;
+ struct gprs_ra_id old_ra_id;
+ struct tlv_parsed tp;
+ uint8_t upd_type;
+ int rc;
+
+ /* Update Type 10.5.5.18 */
+ upd_type = *cur++ & 0x0f;
+
+ DEBUGP(DMM, "-> GMM RA UPDATE REQUEST type=\"%s\" ",
+ get_value_string(gprs_upd_t_strs, upd_type));
+
+ /* Old routing area identification 10.5.5.15 */
+ gsm48_parse_ra(&old_ra_id, cur);
+ cur += 6;
+
+ /* MS Radio Access Capability 10.5.5.12a */
+ ms_ra_acc_cap_len = *cur++;
+ ms_ra_acc_cap = cur;
+
+ /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status,
+ * DRX parameter, MS network capability */
+ rc = tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur,
+ (msg->data + msg->len) - cur, 0, 0);
+
+ switch (upd_type) {
+ case GPRS_UPD_T_RA_LA:
+ case GPRS_UPD_T_RA_LA_IMSI_ATT:
+ DEBUGPC(DMM, " unsupported in Mode III, is your SI13 corrupt?\n");
+ return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_PROTO_ERR_UNSPEC);
+ break;
+ case GPRS_UPD_T_RA:
+ case GPRS_UPD_T_PERIODIC:
+ break;
+ }
+
+ /* Look-up the MM context based on old RA-ID and TLLI */
+ mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &old_ra_id);
+ if (!mmctx || mmctx->mm_state == GMM_DEREGISTERED) {
+ /* The MS has to perform GPRS attach */
+ DEBUGPC(DMM, " REJECT\n");
+ /* Device is still IMSI atached for CS but initiate GPRS ATTACH */
+ return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+ }
+
+ /* Store new BVCI/NSEI in MM context (FIXME: delay until we ack?) */
+ msgid2mmctx(mmctx, msg);
+ /* Bump the statistics of received signalling msgs for this MM context */
+ rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]);
+
+ /* Update the MM context with the new RA-ID */
+ bssgp_parse_cell_id(&mmctx->ra, msgb_bcid(msg));
+ /* Update the MM context with the new (i.e. foreign) TLLI */
+ mmctx->tlli = msgb_tlli(msg);
+ /* FIXME: Update the MM context with the MS radio acc capabilities */
+ /* FIXME: Update the MM context with the MS network capabilities */
+
+ rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_RA_UPDATE]);
+
+ DEBUGPC(DMM, " ACCEPT\n");
+#ifdef PTMSI_ALLOC
+ mmctx->p_tmsi_old = mmctx->p_tmsi;
+ mmctx->p_tmsi = sgsn_alloc_ptmsi();
+ /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */
+ mmctx->t3350_mode = GMM_T3350_MODE_RAU;
+ mmctx_timer_start(mmctx, 3350, GSM0408_T3350_SECS);
+#endif
+ /* Even if there is no P-TMSI allocated, the MS will switch from
+ * foreign TLLI to local TLLI */
+ mmctx->tlli_new = gprs_tmsi2tlli(mmctx->p_tmsi, TLLI_LOCAL);
+
+ /* Inform LLC layer about new TLLI but keep old active */
+ gprs_llgmm_assign(mmctx->llme, mmctx->tlli, mmctx->tlli_new,
+ GPRS_ALGO_GEA0, NULL);
+
+ /* Look at PDP Context Status IE and see if MS's view of
+ * activated/deactivated NSAPIs agrees with our view */
+ if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) {
+ uint16_t pdp_status = ntohs(*(uint16_t *)
+ TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS));
+ process_ms_ctx_status(mmctx, pdp_status);
+ }
+
+ /* Send RA UPDATE ACCEPT */
+ return gsm48_tx_gmm_ra_upd_ack(mmctx);
+}
+
+static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ DEBUGP(DMM, "-> GPRS MM STATUS (cause: %s)\n",
+ get_value_string(gmm_cause_names, gh->data[0]));
+
+ return 0;
+}
+
+/* GPRS Mobility Management */
+static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
+ struct gprs_llc_llme *llme)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ int rc;
+
+ /* MMCTX can be NULL when called */
+
+ if (!mmctx &&
+ gh->msg_type != GSM48_MT_GMM_ATTACH_REQ &&
+ gh->msg_type != GSM48_MT_GMM_RA_UPD_REQ) {
+ LOGP(DMM, LOGL_NOTICE, "Cannot handle GMM for unknown MM CTX\n");
+ return gsm48_tx_gmm_status_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+ }
+
+ switch (gh->msg_type) {
+ case GSM48_MT_GMM_RA_UPD_REQ:
+ rc = gsm48_rx_gmm_ra_upd_req(mmctx, msg, llme);
+ break;
+ case GSM48_MT_GMM_ATTACH_REQ:
+ rc = gsm48_rx_gmm_att_req(mmctx, msg, llme);
+ break;
+ case GSM48_MT_GMM_ID_RESP:
+ rc = gsm48_rx_gmm_id_resp(mmctx, msg);
+ break;
+ case GSM48_MT_GMM_STATUS:
+ rc = gsm48_rx_gmm_status(mmctx, msg);
+ break;
+ case GSM48_MT_GMM_DETACH_REQ:
+ rc = gsm48_rx_gmm_det_req(mmctx, msg);
+ break;
+ case GSM48_MT_GMM_ATTACH_COMPL:
+ /* only in case SGSN offered new P-TMSI */
+ DEBUGP(DMM, "-> ATTACH COMPLETE\n");
+ mmctx_timer_stop(mmctx, 3350);
+ mmctx->p_tmsi_old = 0;
+ /* Unassign the old TLLI */
+ mmctx->tlli = mmctx->tlli_new;
+ gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new,
+ GPRS_ALGO_GEA0, NULL);
+ break;
+ case GSM48_MT_GMM_RA_UPD_COMPL:
+ /* only in case SGSN offered new P-TMSI */
+ DEBUGP(DMM, "-> ROUTEING AREA UPDATE COMPLETE\n");
+ mmctx_timer_stop(mmctx, 3350);
+ mmctx->p_tmsi_old = 0;
+ /* Unassign the old TLLI */
+ mmctx->tlli = mmctx->tlli_new;
+ gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new,
+ GPRS_ALGO_GEA0, NULL);
+ break;
+ case GSM48_MT_GMM_PTMSI_REALL_COMPL:
+ DEBUGP(DMM, "-> PTMSI REALLLICATION COMPLETE\n");
+ mmctx_timer_stop(mmctx, 3350);
+ mmctx->p_tmsi_old = 0;
+ /* Unassign the old TLLI */
+ mmctx->tlli = mmctx->tlli_new;
+ //gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new, GPRS_ALGO_GEA0, NULL);
+ break;
+ case GSM48_MT_GMM_AUTH_CIPH_RESP:
+ rc = gsm48_rx_gmm_auth_ciph_resp(mmctx, msg);
+ break;
+ default:
+ DEBUGP(DMM, "Unknown GSM 04.08 GMM msg type 0x%02x\n",
+ gh->msg_type);
+ rc = gsm48_tx_gmm_status(mmctx, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+ break;
+ }
+
+ return rc;
+}
+
+static void mmctx_timer_cb(void *_mm)
+{
+ struct sgsn_mm_ctx *mm = _mm;
+
+ mm->num_T_exp++;
+
+ switch (mm->T) {
+ case 3350: /* waiting for ATTACH COMPLETE */
+ if (mm->num_T_exp >= 5) {
+ LOGP(DMM, LOGL_NOTICE, "T3350 expired >= 5 times\n");
+ mm->mm_state = GMM_DEREGISTERED;
+ /* FIXME: should we return some error? */
+ break;
+ }
+ /* re-transmit the respective msg and re-start timer */
+ switch (mm->t3350_mode) {
+ case GMM_T3350_MODE_ATT:
+ gsm48_tx_gmm_att_ack(mm);
+ break;
+ case GMM_T3350_MODE_RAU:
+ gsm48_tx_gmm_ra_upd_ack(mm);
+ break;
+ case GMM_T3350_MODE_PTMSI_REALL:
+ /* FIXME */
+ break;
+ }
+ bsc_schedule_timer(&mm->timer, GSM0408_T3350_SECS, 0);
+ break;
+ case 3360: /* waiting for AUTH AND CIPH RESP */
+ if (mm->num_T_exp >= 5) {
+ LOGP(DMM, LOGL_NOTICE, "T3360 expired >= 5 times\n");
+ mm->mm_state = GMM_DEREGISTERED;
+ break;
+ }
+ /* FIXME: re-transmit the respective msg and re-start timer */
+ bsc_schedule_timer(&mm->timer, GSM0408_T3360_SECS, 0);
+ break;
+ case 3370: /* waiting for IDENTITY RESPONSE */
+ if (mm->num_T_exp >= 5) {
+ LOGP(DMM, LOGL_NOTICE, "T3370 expired >= 5 times\n");
+ gsm48_tx_gmm_att_rej(mm, GMM_CAUSE_MS_ID_NOT_DERIVED);
+ mm->mm_state = GMM_DEREGISTERED;
+ break;
+ }
+ /* re-tranmit IDENTITY REQUEST and re-start timer */
+ gsm48_tx_gmm_id_req(mm, mm->t3370_id_type);
+ bsc_schedule_timer(&mm->timer, GSM0408_T3370_SECS, 0);
+ break;
+ default:
+ LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n",
+ mm->T);
+ }
+}
+
+/* GPRS SESSION MANAGEMENT */
+
+static void pdpctx_timer_cb(void *_mm);
+
+static void pdpctx_timer_start(struct sgsn_pdp_ctx *pdp, unsigned int T,
+ unsigned int seconds)
+{
+ if (bsc_timer_pending(&pdp->timer))
+ LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old "
+ "timer %u pending\n", T, pdp->T);
+ pdp->T = T;
+ pdp->num_T_exp = 0;
+
+ /* FIXME: we should do this only once ? */
+ pdp->timer.data = pdp;
+ pdp->timer.cb = &pdpctx_timer_cb;
+
+ bsc_schedule_timer(&pdp->timer, seconds, 0);
+}
+
+
+static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr)
+{
+ uint8_t v[6];
+
+ v[0] = PDP_TYPE_ORG_IETF;
+ v[1] = PDP_TYPE_N_IETF_IPv4;
+ *(uint32_t *)(v+2) = htonl(ipaddr);
+
+ msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v);
+}
+
+static void msgb_put_pdp_addr_ppp(struct msgb *msg)
+{
+ uint8_t v[2];
+
+ v[0] = PDP_TYPE_ORG_ETSI;
+ v[1] = PDP_TYPE_N_ETSI_PPP;
+
+ msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v);
+}
+
+/* Section 9.5.2: Ativate PDP Context Accept */
+int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ uint8_t transaction_id = pdp->ti ^ 0x8; /* flip */
+
+ DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT ACK\n");
+
+ mmctx2msgid(msg, pdp->mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+ gh->msg_type = GSM48_MT_GSM_ACT_PDP_ACK;
+
+ /* Negotiated LLC SAPI */
+ msgb_v_put(msg, pdp->sapi);
+
+ /* FIXME: copy QoS parameters from original request */
+ //msgb_lv_put(msg, pdp->lib->qos_neg.l, pdp->lib->qos_neg.v);
+ msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos);
+
+ /* Radio priority 10.5.7.2 */
+ msgb_v_put(msg, pdp->lib->radio_pri);
+
+ /* PDP address */
+ /* Highest 4 bits of first byte need to be set to 1, otherwise
+ * the IE is identical with the 04.08 PDP Address IE */
+ pdp->lib->eua.v[0] &= ~0xf0;
+ msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR,
+ pdp->lib->eua.l, pdp->lib->eua.v);
+ pdp->lib->eua.v[0] |= 0xf0;
+
+ /* Optional: Protocol configuration options (FIXME: why 'req') */
+ if (pdp->lib->pco_req.l && pdp->lib->pco_req.v)
+ msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT,
+ pdp->lib->pco_req.l, pdp->lib->pco_req.v);
+
+ /* Optional: Packet Flow Identifier */
+
+ return gsm48_gmm_sendmsg(msg, 0, pdp->mm);
+}
+
+/* Section 9.5.3: Activate PDP Context reject */
+int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid,
+ uint8_t cause, uint8_t pco_len, uint8_t *pco_v)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+ DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT REJ(cause=%u)\n", cause);
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+ gh->msg_type = GSM48_MT_GSM_ACT_PDP_REJ;
+
+ msgb_v_put(msg, cause);
+ if (pco_len && pco_v)
+ msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, pco_len, pco_v);
+
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Section 9.5.8: Deactivate PDP Context Request */
+static int _gsm48_tx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, uint8_t tid,
+ uint8_t sm_cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+ DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT REQ\n");
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+ gh->msg_type = GSM48_MT_GSM_DEACT_PDP_REQ;
+
+ msgb_v_put(msg, sm_cause);
+
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause)
+{
+ pdpctx_timer_start(pdp, 3395, GSM0408_T3395_SECS);
+
+ return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause);
+}
+
+/* Section 9.5.9: Deactivate PDP Context Accept */
+static int _gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mm, uint8_t tid)
+{
+ struct msgb *msg = gsm48_msgb_alloc();
+ struct gsm48_hdr *gh;
+ uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+ DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT ACK\n");
+
+ mmctx2msgid(msg, mm);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+ gh->msg_type = GSM48_MT_GSM_DEACT_PDP_ACK;
+
+ return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp)
+{
+ return _gsm48_tx_gsm_deact_pdp_acc(pdp->mm, pdp->ti);
+}
+
+/* Section 9.5.1: Activate PDP Context Request */
+static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data;
+ uint8_t req_qos_len, req_pdpa_len;
+ uint8_t *req_qos, *req_pdpa;
+ struct tlv_parsed tp;
+ uint8_t transaction_id = (gh->proto_discr >> 4);
+ struct sgsn_ggsn_ctx *ggsn;
+ struct sgsn_pdp_ctx *pdp;
+
+ DEBUGP(DMM, "-> ACTIVATE PDP CONTEXT REQ: SAPI=%u NSAPI=%u ",
+ act_req->req_llc_sapi, act_req->req_nsapi);
+
+ /* FIXME: length checks! */
+ req_qos_len = act_req->data[0];
+ req_qos = act_req->data + 1; /* 10.5.6.5 */
+ req_pdpa_len = act_req->data[1 + req_qos_len];
+ req_pdpa = act_req->data + 1 + req_qos_len + 1; /* 10.5.6.4 */
+
+ /* Optional: Access Point Name, Protocol Config Options */
+ if (req_pdpa + req_pdpa_len < msg->data + msg->len)
+ tlv_parse(&tp, &gsm48_sm_att_tlvdef, req_pdpa + req_pdpa_len,
+ (msg->data + msg->len) - (req_pdpa + req_pdpa_len), 0, 0);
+ else
+ memset(&tp, 0, sizeof(tp));
+
+ switch (req_pdpa[0] & 0xf) {
+ case 0x0:
+ DEBUGPC(DMM, "ETSI ");
+ break;
+ case 0x1:
+ DEBUGPC(DMM, "IETF ");
+ break;
+ case 0xf:
+ DEBUGPC(DMM, "Empty ");
+ break;
+ }
+
+ switch (req_pdpa[1]) {
+ case 0x21:
+ DEBUGPC(DMM, "IPv4 ");
+ if (req_pdpa_len >= 6) {
+ struct in_addr ia;
+ ia.s_addr = ntohl(*((uint32_t *) (req_pdpa+2)));
+ DEBUGPC(DMM, "%s ", inet_ntoa(ia));
+ }
+ break;
+ case 0x57:
+ DEBUGPC(DMM, "IPv6 ");
+ if (req_pdpa_len >= 18) {
+ /* FIXME: print IPv6 address */
+ }
+ break;
+ default:
+ DEBUGPC(DMM, "0x%02x ", req_pdpa[1]);
+ break;
+ }
+
+ DEBUGPC(DMM, "\n");
+
+ /* put the non-TLV elements in the TLV parser structure to
+ * pass them on to the SGSN / GTP code */
+ tp.lv[OSMO_IE_GSM_REQ_QOS].len = req_qos_len;
+ tp.lv[OSMO_IE_GSM_REQ_QOS].val = req_qos;
+ tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].len = req_pdpa_len;
+ tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].val = req_pdpa;
+
+ /* FIXME: determine GGSN based on APN and subscription options */
+ if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {}
+
+ /* Check if NSAPI is out of range (TS 04.65 / 7.2) */
+ if (act_req->req_nsapi < 5 || act_req->req_nsapi > 15) {
+ /* Send reject with GSM_CAUSE_INV_MAND_INFO */
+ return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id,
+ GSM_CAUSE_INV_MAND_INFO,
+ 0, NULL);
+ }
+
+ /* Check if NSAPI is already in use */
+ pdp = sgsn_pdp_ctx_by_nsapi(mmctx, act_req->req_nsapi);
+ if (pdp) {
+ /* We already have a PDP context for this TLLI + NSAPI tuple */
+ if (pdp->sapi == act_req->req_llc_sapi &&
+ pdp->ti == transaction_id) {
+ /* This apparently is a re-transmission of a PDP CTX
+ * ACT REQ (our ACT ACK must have got dropped) */
+ return gsm48_tx_gsm_act_pdp_acc(pdp);
+ }
+
+ /* Send reject with GSM_CAUSE_NSAPI_IN_USE */
+ return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id,
+ GSM_CAUSE_NSAPI_IN_USE,
+ 0, NULL);
+ }
+
+ /* Only increment counter for a real activation, after we checked
+ * for re-transmissions */
+ rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PDP_CTX_ACT]);
+
+ ggsn = sgsn_ggsn_ctx_by_id(0);
+ if (!ggsn) {
+ LOGP(DGPRS, LOGL_ERROR, "No GGSN context 0 found!\n");
+ return -EIO;
+ }
+ ggsn->gsn = sgsn->gsn;
+ pdp = sgsn_create_pdp_ctx(ggsn, mmctx, act_req->req_nsapi, &tp);
+ if (!pdp)
+ return -1;
+
+ /* Store SAPI and Transaction Identifier */
+ pdp->sapi = act_req->req_llc_sapi;
+ pdp->ti = transaction_id;
+
+ return 0;
+}
+
+/* Section 9.5.8: Deactivate PDP Context Request */
+static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ uint8_t transaction_id = (gh->proto_discr >> 4);
+ struct sgsn_pdp_ctx *pdp;
+
+ DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT REQ (cause: %s)\n",
+ get_value_string(gsm_cause_names, gh->data[0]));
+
+ pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id);
+ if (!pdp) {
+ LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Request for "
+ "non-existing PDP Context (IMSI=%s, TI=%u)\n",
+ mm->imsi, transaction_id);
+ return _gsm48_tx_gsm_deact_pdp_acc(mm, transaction_id);
+ }
+
+ return sgsn_delete_pdp_ctx(pdp);
+}
+
+/* Section 9.5.9: Deactivate PDP Context Accept */
+static int gsm48_rx_gsm_deact_pdp_ack(struct sgsn_mm_ctx *mm, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ uint8_t transaction_id = (gh->proto_discr >> 4);
+ struct sgsn_pdp_ctx *pdp;
+
+ DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT ACK\n");
+
+ pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id);
+ if (!pdp) {
+ LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Accept for "
+ "non-existing PDP Context (IMSI=%s, TI=%u)\n",
+ mm->imsi, transaction_id);
+ return 0;
+ }
+
+ return sgsn_delete_pdp_ctx(pdp);
+}
+
+static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ DEBUGP(DMM, "-> GPRS SM STATUS (cause: %s)\n",
+ get_value_string(gsm_cause_names, gh->data[0]));
+
+ return 0;
+}
+
+static void pdpctx_timer_cb(void *_pdp)
+{
+ struct sgsn_pdp_ctx *pdp = _pdp;
+
+ pdp->num_T_exp++;
+
+ switch (pdp->T) {
+ case 3395: /* waiting for PDP CTX DEACT ACK */
+ if (pdp->num_T_exp >= 4) {
+ LOGP(DMM, LOGL_NOTICE, "T3395 expired >= 5 times\n");
+ pdp->state = PDP_STATE_INACTIVE;
+ sgsn_delete_pdp_ctx(pdp);
+ break;
+ }
+ gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
+ bsc_schedule_timer(&pdp->timer, GSM0408_T3395_SECS, 0);
+ break;
+ default:
+ LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n",
+ pdp->T);
+ }
+}
+
+
+/* GPRS Session Management */
+static int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
+ struct gprs_llc_llme *llme)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ int rc;
+
+ /* MMCTX can be NULL when called */
+
+ if (!mmctx) {
+ LOGP(DMM, LOGL_NOTICE, "Cannot handle SM for unknown MM CTX\n");
+ gsm48_tx_gmm_status_oldmsg(msg, GMM_CAUSE_IMPL_DETACHED);
+ return gsm48_tx_sm_status_oldmsg(msg, GSM_CAUSE_PROTO_ERR_UNSPEC);
+ }
+
+ switch (gh->msg_type) {
+ case GSM48_MT_GSM_ACT_PDP_REQ:
+ rc = gsm48_rx_gsm_act_pdp_req(mmctx, msg);
+ break;
+ case GSM48_MT_GSM_DEACT_PDP_REQ:
+ rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg);
+ break;
+ case GSM48_MT_GSM_DEACT_PDP_ACK:
+ rc = gsm48_rx_gsm_deact_pdp_ack(mmctx, msg);
+ break;
+ case GSM48_MT_GSM_STATUS:
+ rc = gsm48_rx_gsm_status(mmctx, msg);
+ break;
+ case GSM48_MT_GSM_REQ_PDP_ACT_REJ:
+ case GSM48_MT_GSM_ACT_AA_PDP_REQ:
+ case GSM48_MT_GSM_DEACT_AA_PDP_REQ:
+ DEBUGP(DMM, "Unimplemented GSM 04.08 GSM msg type 0x%02x\n",
+ gh->msg_type);
+ rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+ break;
+ default:
+ DEBUGP(DMM, "Unknown GSM 04.08 GSM msg type 0x%02x\n",
+ gh->msg_type);
+ rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+ break;
+
+ }
+
+ return rc;
+}
+
+/* Main entry point for incoming 04.08 GPRS messages */
+int gsm0408_gprs_rcvmsg(struct msgb *msg, struct gprs_llc_llme *llme)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+ uint8_t pdisc = gh->proto_discr & 0x0f;
+ struct sgsn_mm_ctx *mmctx;
+ struct gprs_ra_id ra_id;
+ int rc = -EINVAL;
+
+ bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+ mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
+ if (mmctx) {
+ msgid2mmctx(mmctx, msg);
+ rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]);
+ mmctx->llme = llme;
+ }
+
+ /* MMCTX can be NULL */
+
+ switch (pdisc) {
+ case GSM48_PDISC_MM_GPRS:
+ rc = gsm0408_rcv_gmm(mmctx, msg, llme);
+ break;
+ case GSM48_PDISC_SM_GPRS:
+ rc = gsm0408_rcv_gsm(mmctx, msg, llme);
+ break;
+ default:
+ DEBUGP(DMM, "Unknown GSM 04.08 discriminator 0x%02x\n",
+ pdisc);
+ /* FIXME: return status message */
+ break;
+ }
+
+ return rc;
+}
+
+int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli)
+{
+ struct sgsn_mm_ctx *mmctx;
+
+ mmctx = sgsn_mm_ctx_by_tlli(tlli, raid);
+ if (!mmctx) {
+ LOGP(DMM, LOGL_NOTICE, "SUSPEND request for unknown "
+ "TLLI=%08x\n", tlli);
+ return -EINVAL;
+ }
+
+ if (mmctx->mm_state != GMM_REGISTERED_NORMAL) {
+ LOGP(DMM, LOGL_NOTICE, "SUSPEND request while state "
+ "!= REGISTERED (TLLI=%08x)\n", tlli);
+ return -EINVAL;
+ }
+
+ /* Transition from REGISTERED_NORMAL to REGISTERED_SUSPENDED */
+ mmctx->mm_state = GMM_REGISTERED_SUSPENDED;
+ return 0;
+}
+
+int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli,
+ uint8_t suspend_ref)
+{
+ struct sgsn_mm_ctx *mmctx;
+
+ /* FIXME: make use of suspend reference? */
+
+ mmctx = sgsn_mm_ctx_by_tlli(tlli, raid);
+ if (!mmctx) {
+ LOGP(DMM, LOGL_NOTICE, "RESUME request for unknown "
+ "TLLI=%08x\n", tlli);
+ return -EINVAL;
+ }
+
+ if (mmctx->mm_state != GMM_REGISTERED_SUSPENDED) {
+ LOGP(DMM, LOGL_NOTICE, "RESUME request while state "
+ "!= SUSPENDED (TLLI=%08x)\n", tlli);
+ /* FIXME: should we not simply ignore it? */
+ return -EINVAL;
+ }
+
+ /* Transition from SUSPENDED to NORMAL */
+ mmctx->mm_state = GMM_REGISTERED_NORMAL;
+ return 0;
+}
diff --git a/src/gprs/gprs_llc.c b/src/gprs/gprs_llc.c
new file mode 100644
index 00000000..7991f4c1
--- /dev/null
+++ b/src/gprs/gprs_llc.c
@@ -0,0 +1,852 @@
+/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */
+
+/* (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 <errno.h>
+#include <stdint.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/timer.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/crc24.h>
+
+/* Section 8.9.9 LLC layer parameter default values */
+static const struct gprs_llc_params llc_default_params[] = {
+ [1] = {
+ .t200_201 = 5,
+ .n200 = 3,
+ .n201_u = 400,
+ },
+ [2] = {
+ .t200_201 = 5,
+ .n200 = 3,
+ .n201_u = 270,
+ },
+ [3] = {
+ .iov_i_exp = 27,
+ .t200_201 = 5,
+ .n200 = 3,
+ .n201_u = 500,
+ .n201_i = 1503,
+ .mD = 1520,
+ .mU = 1520,
+ .kD = 16,
+ .kU = 16,
+ },
+ [5] = {
+ .iov_i_exp = 27,
+ .t200_201 = 10,
+ .n200 = 3,
+ .n201_u = 500,
+ .n201_i = 1503,
+ .mD = 760,
+ .mU = 760,
+ .kD = 8,
+ .kU = 8,
+ },
+ [7] = {
+ .t200_201 = 20,
+ .n200 = 3,
+ .n201_u = 270,
+ },
+ [8] = {
+ .t200_201 = 20,
+ .n200 = 3,
+ .n201_u = 270,
+ },
+ [9] = {
+ .iov_i_exp = 27,
+ .t200_201 = 20,
+ .n200 = 3,
+ .n201_u = 500,
+ .n201_i = 1503,
+ .mD = 380,
+ .mU = 380,
+ .kD = 4,
+ .kU = 4,
+ },
+ [11] = {
+ .iov_i_exp = 27,
+ .t200_201 = 40,
+ .n200 = 3,
+ .n201_u = 500,
+ .n201_i = 1503,
+ .mD = 190,
+ .mU = 190,
+ .kD = 2,
+ .kU = 2,
+ },
+};
+
+LLIST_HEAD(gprs_llc_llmes);
+void *llc_tall_ctx;
+
+/* If the TLLI is foreign, return its local version */
+static inline uint32_t tlli_foreign2local(uint32_t tlli)
+{
+ uint32_t new_tlli;
+
+ if (gprs_tlli_type(tlli) == TLLI_FOREIGN) {
+ new_tlli = tlli | 0x40000000;
+ DEBUGP(DLLC, "TLLI 0x%08x is foreign, converting to "
+ "local TLLI 0x%08x\n", tlli, new_tlli);
+ } else
+ new_tlli = tlli;
+
+ return new_tlli;
+}
+
+/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */
+static struct gprs_llc_lle *lle_by_tlli_sapi(uint32_t tlli, uint8_t sapi)
+{
+ struct gprs_llc_llme *llme;
+
+ tlli = tlli_foreign2local(tlli);
+
+ llist_for_each_entry(llme, &gprs_llc_llmes, list) {
+ if (llme->tlli == tlli || llme->old_tlli == tlli)
+ return &llme->lle[sapi];
+ }
+ return NULL;
+}
+
+static void lle_init(struct gprs_llc_llme *llme, uint8_t sapi)
+{
+ struct gprs_llc_lle *lle = &llme->lle[sapi];
+
+ lle->llme = llme;
+ lle->sapi = sapi;
+ lle->state = GPRS_LLES_UNASSIGNED;
+
+ /* Initialize according to parameters */
+ memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params));
+}
+
+static struct gprs_llc_llme *llme_alloc(uint32_t tlli)
+{
+ struct gprs_llc_llme *llme;
+ uint32_t i;
+
+ llme = talloc_zero(llc_tall_ctx, struct gprs_llc_llme);
+ if (!llme)
+ return NULL;
+
+ llme->tlli = tlli;
+ llme->old_tlli = 0xffffffff;
+ llme->state = GPRS_LLMS_UNASSIGNED;
+
+ for (i = 0; i < ARRAY_SIZE(llme->lle); i++)
+ lle_init(llme, i);
+
+ llist_add(&llme->list, &gprs_llc_llmes);
+
+ return llme;
+}
+
+static void llme_free(struct gprs_llc_llme *llme)
+{
+ llist_del(&llme->list);
+ talloc_free(llme);
+}
+
+enum gprs_llc_cmd {
+ GPRS_LLC_NULL,
+ GPRS_LLC_RR,
+ GPRS_LLC_ACK,
+ GPRS_LLC_RNR,
+ GPRS_LLC_SACK,
+ GPRS_LLC_DM,
+ GPRS_LLC_DISC,
+ GPRS_LLC_UA,
+ GPRS_LLC_SABM,
+ GPRS_LLC_FRMR,
+ GPRS_LLC_XID,
+ GPRS_LLC_UI,
+};
+
+static const struct value_string llc_cmd_strs[] = {
+ { GPRS_LLC_NULL, "NULL" },
+ { GPRS_LLC_RR, "RR" },
+ { GPRS_LLC_ACK, "ACK" },
+ { GPRS_LLC_RNR, "RNR" },
+ { GPRS_LLC_SACK, "SACK" },
+ { GPRS_LLC_DM, "DM" },
+ { GPRS_LLC_DISC, "DISC" },
+ { GPRS_LLC_UA, "UA" },
+ { GPRS_LLC_SABM, "SABM" },
+ { GPRS_LLC_FRMR, "FRMR" },
+ { GPRS_LLC_XID, "XID" },
+ { GPRS_LLC_UI, "UI" },
+ { 0, NULL }
+};
+
+struct gprs_llc_hdr_parsed {
+ uint8_t sapi;
+ uint8_t is_cmd:1,
+ ack_req:1,
+ is_encrypted:1;
+ uint32_t seq_rx;
+ uint32_t seq_tx;
+ uint32_t fcs;
+ uint32_t fcs_calc;
+ uint8_t *data;
+ uint16_t data_len;
+ uint16_t crc_length;
+ enum gprs_llc_cmd cmd;
+};
+
+#define LLC_ALLOC_SIZE 16384
+#define UI_HDR_LEN 3
+#define N202 4
+#define CRC24_LENGTH 3
+
+static int gprs_llc_fcs(uint8_t *data, unsigned int len)
+{
+ uint32_t fcs_calc;
+
+ fcs_calc = crc24_calc(INIT_CRC24, data, len);
+ fcs_calc = ~fcs_calc;
+ fcs_calc &= 0xffffff;
+
+ return fcs_calc;
+}
+
+static void t200_expired(void *data)
+{
+ struct gprs_llc_lle *lle = data;
+
+ /* 8.5.1.3: Expiry of T200 */
+
+ if (lle->retrans_ctr >= lle->params.n200) {
+ /* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */
+ lle->state = GPRS_LLES_ASSIGNED_ADM;
+ }
+
+ switch (lle->state) {
+ case GPRS_LLES_LOCAL_EST:
+ /* FIXME: retransmit SABM */
+ /* FIXME: re-start T200 */
+ lle->retrans_ctr++;
+ break;
+ case GPRS_LLES_LOCAL_REL:
+ /* FIXME: retransmit DISC */
+ /* FIXME: re-start T200 */
+ lle->retrans_ctr++;
+ break;
+ }
+
+}
+
+static void t201_expired(void *data)
+{
+ struct gprs_llc_lle *lle = data;
+
+ if (lle->retrans_ctr < lle->params.n200) {
+ /* FIXME: transmit apropriate supervisory frame (8.6.4.1) */
+ /* FIXME: set timer T201 */
+ lle->retrans_ctr++;
+ }
+}
+
+int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command,
+ enum gprs_llc_u_cmd u_cmd, int pf_bit)
+{
+ uint8_t *fcs, *llch;
+ uint8_t addr, ctrl;
+ uint32_t fcs_calc;
+
+ /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+ /* Address Field */
+ addr = sapi & 0xf;
+ if (command)
+ addr |= 0x40;
+
+ /* 6.3 Figure 8 */
+ ctrl = 0xe0 | u_cmd;
+ if (pf_bit)
+ ctrl |= 0x10;
+
+ /* prepend LLC UI header */
+ llch = msgb_push(msg, 2);
+ llch[0] = addr;
+ llch[1] = ctrl;
+
+ /* append FCS to end of frame */
+ fcs = msgb_put(msg, 3);
+ fcs_calc = gprs_llc_fcs(llch, fcs - llch);
+ fcs[0] = fcs_calc & 0xff;
+ fcs[1] = (fcs_calc >> 8) & 0xff;
+ fcs[2] = (fcs_calc >> 16) & 0xff;
+
+ /* Identifiers passed down: (BVCI, NSEI) */
+
+ /* Send BSSGP-DL-UNITDATA.req */
+ return gprs_bssgp_tx_dl_ud(msg, NULL);
+}
+
+/* Send XID response to LLE */
+static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg)
+{
+ /* copy identifiers from LLE to ensure lower layers can route */
+ msgb_tlli(msg) = lle->llme->tlli;
+ msgb_bvci(msg) = lle->llme->bvci;
+ msgb_nsei(msg) = lle->llme->nsei;
+
+ return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_XID, 1);
+}
+
+/* Transmit a UI frame over the given SAPI */
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command,
+ void *mmctx)
+{
+ struct gprs_llc_lle *lle;
+ uint8_t *fcs, *llch;
+ uint8_t addr, ctrl[2];
+ uint32_t fcs_calc;
+ uint16_t nu = 0;
+ uint32_t oc;
+
+ /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+ /* look-up or create the LL Entity for this (TLLI, SAPI) tuple */
+ lle = lle_by_tlli_sapi(msgb_tlli(msg), sapi);
+ if (!lle) {
+ struct gprs_llc_llme *llme;
+ LOGP(DLLC, LOGL_ERROR, "LLC TX: unknown TLLI 0x%08x, "
+ "creating LLME on the fly\n", msgb_tlli(msg));
+ llme = llme_alloc(msgb_tlli(msg));
+ lle = &llme->lle[sapi];
+ }
+
+ if (msg->len > lle->params.n201_u) {
+ LOGP(DLLC, LOGL_ERROR, "Cannot Tx %u bytes (N201-U=%u)\n",
+ msg->len, lle->params.n201_u);
+ return -EFBIG;
+ }
+
+ /* Update LLE's (BVCI, NSEI) tuple */
+ lle->llme->bvci = msgb_bvci(msg);
+ lle->llme->nsei = msgb_nsei(msg);
+
+ /* Obtain current values for N(u) and OC */
+ nu = lle->vu_send;
+ oc = lle->oc_ui_send;
+ /* Increment V(U) */
+ lle->vu_send = (lle->vu_send + 1) % 512;
+ /* Increment Overflow Counter, if needed */
+ if ((lle->vu_send + 1) / 512)
+ lle->oc_ui_send += 512;
+
+ /* Address Field */
+ addr = sapi & 0xf;
+ if (command)
+ addr |= 0x40;
+
+ /* Control Field */
+ ctrl[0] = 0xc0;
+ ctrl[0] |= nu >> 6;
+ ctrl[1] = (nu << 2) & 0xfc;
+ ctrl[1] |= 0x01; /* Protected Mode */
+
+ /* prepend LLC UI header */
+ llch = msgb_push(msg, 3);
+ llch[0] = addr;
+ llch[1] = ctrl[0];
+ llch[2] = ctrl[1];
+
+ /* append FCS to end of frame */
+ fcs = msgb_put(msg, 3);
+ fcs_calc = gprs_llc_fcs(llch, fcs - llch);
+ fcs[0] = fcs_calc & 0xff;
+ fcs[1] = (fcs_calc >> 8) & 0xff;
+ fcs[2] = (fcs_calc >> 16) & 0xff;
+
+ /* encrypt information field + FCS, if needed! */
+ if (lle->llme->algo != GPRS_ALGO_GEA0) {
+ uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */
+ uint16_t crypt_len = (fcs + 3) - (llch + 3);
+ uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK];
+ uint32_t iv;
+ int rc, i;
+ uint64_t kc = *(uint64_t *)&lle->llme->kc;
+
+ /* Compute the 'Input' Paraemeter */
+ iv = gprs_cipher_gen_input_ui(iov_ui, sapi, nu, oc);
+
+ /* Compute the keystream that we need to XOR with the data */
+ rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo,
+ kc, iv, GPRS_CIPH_SGSN2MS);
+ if (rc < 0) {
+ LOGP(DLLC, LOGL_ERROR, "Error crypting UI frame: %d\n", rc);
+ return rc;
+ }
+
+ /* XOR the cipher output with the information field + FCS */
+ for (i = 0; i < crypt_len; i++)
+ *(llch + 3 + i) ^= cipher_out[i];
+
+ /* Mark frame as encrypted */
+ ctrl[1] |= 0x02;
+ }
+
+ /* Identifiers passed down: (BVCI, NSEI) */
+
+ /* Send BSSGP-DL-UNITDATA.req */
+ return gprs_bssgp_tx_dl_ud(msg, mmctx);
+}
+
+static void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph)
+{
+ DEBUGP(DLLC, "LLC SAPI=%u %c %c FCS=0x%06x",
+ gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ',
+ gph->fcs);
+
+ if (gph->cmd)
+ DEBUGPC(DLLC, "CMD=%s ", get_value_string(llc_cmd_strs, gph->cmd));
+
+ if (gph->data)
+ DEBUGPC(DLLC, "DATA ");
+
+ DEBUGPC(DLLC, "\n");
+}
+static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph,
+ struct gprs_llc_lle *lle)
+{
+ switch (gph->cmd) {
+ case GPRS_LLC_SABM: /* Section 6.4.1.1 */
+ lle->v_sent = lle->v_ack = lle->v_recv = 0;
+ if (lle->state == GPRS_LLES_ASSIGNED_ADM) {
+ /* start re-establishment (8.7.1) */
+ }
+ lle->state = GPRS_LLES_REMOTE_EST;
+ /* FIXME: Send UA */
+ lle->state = GPRS_LLES_ABM;
+ /* FIXME: process data */
+ break;
+ case GPRS_LLC_DISC: /* Section 6.4.1.2 */
+ /* FIXME: Send UA */
+ /* terminate ABM */
+ lle->state = GPRS_LLES_ASSIGNED_ADM;
+ break;
+ case GPRS_LLC_UA: /* Section 6.4.1.3 */
+ if (lle->state == GPRS_LLES_LOCAL_EST)
+ lle->state = GPRS_LLES_ABM;
+ break;
+ case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */
+ if (lle->state == GPRS_LLES_LOCAL_EST)
+ lle->state = GPRS_LLES_ASSIGNED_ADM;
+ break;
+ case GPRS_LLC_FRMR: /* Section 6.4.1.5 */
+ break;
+ case GPRS_LLC_XID: /* Section 6.4.1.6 */
+ /* FIXME: implement XID negotiation using SNDCP */
+ {
+ struct msgb *resp;
+ uint8_t *xid;
+ resp = msgb_alloc_headroom(4096, 1024, "LLC_XID");
+ xid = msgb_put(resp, gph->data_len);
+ memcpy(xid, gph->data, gph->data_len);
+ gprs_llc_tx_xid(lle, resp);
+ }
+ break;
+ case GPRS_LLC_UI:
+ if (gph->seq_tx < lle->vu_recv) {
+ LOGP(DLLC, LOGL_NOTICE, "TLLI=%08x dropping UI, vurecv %u <= %u\n",
+ lle->llme ? lle->llme->tlli : -1,
+ gph->seq_tx, lle->vu_recv);
+ return -EIO;
+ }
+ /* Increment the sequence number that we expect in the next frame */
+ lle->vu_recv = (gph->seq_tx + 1) % 512;
+ /* Increment Overflow Counter */
+ if ((gph->seq_tx + 1) / 512)
+ lle->oc_ui_recv += 512;
+ break;
+ }
+
+ return 0;
+}
+
+/* parse a GPRS LLC header, also check for invalid frames */
+static int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp,
+ uint8_t *llc_hdr, int len)
+{
+ uint8_t *ctrl = llc_hdr+1;
+ int is_sack = 0;
+
+ if (len <= CRC24_LENGTH)
+ return -EIO;
+
+ ghp->crc_length = len - CRC24_LENGTH;
+
+ ghp->ack_req = 0;
+
+ /* Section 5.5: FCS */
+ ghp->fcs = *(llc_hdr + len - 3);
+ ghp->fcs |= *(llc_hdr + len - 2) << 8;
+ ghp->fcs |= *(llc_hdr + len - 1) << 16;
+
+ /* Section 6.2.1: invalid PD field */
+ if (llc_hdr[0] & 0x80)
+ return -EIO;
+
+ /* This only works for the MS->SGSN direction */
+ if (llc_hdr[0] & 0x40)
+ ghp->is_cmd = 0;
+ else
+ ghp->is_cmd = 1;
+
+ ghp->sapi = llc_hdr[0] & 0xf;
+
+ /* Section 6.2.3: check for reserved SAPI */
+ switch (ghp->sapi) {
+ case 0:
+ case 4:
+ case 6:
+ case 0xa:
+ case 0xc:
+ case 0xd:
+ case 0xf:
+ return -EINVAL;
+ }
+
+ if ((ctrl[0] & 0x80) == 0) {
+ /* I (Information transfer + Supervisory) format */
+ uint8_t k;
+
+ ghp->data = ctrl + 3;
+
+ if (ctrl[0] & 0x40)
+ ghp->ack_req = 1;
+
+ ghp->seq_tx = (ctrl[0] & 0x1f) << 4;
+ ghp->seq_tx |= (ctrl[1] >> 4);
+
+ ghp->seq_rx = (ctrl[1] & 0x7) << 6;
+ ghp->seq_rx |= (ctrl[2] >> 2);
+
+ switch (ctrl[2] & 0x03) {
+ case 0:
+ ghp->cmd = GPRS_LLC_RR;
+ break;
+ case 1:
+ ghp->cmd = GPRS_LLC_ACK;
+ break;
+ case 2:
+ ghp->cmd = GPRS_LLC_RNR;
+ break;
+ case 3:
+ ghp->cmd = GPRS_LLC_SACK;
+ k = ctrl[3] & 0x1f;
+ ghp->data += 1 + k;
+ break;
+ }
+ ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+ } else if ((ctrl[0] & 0xc0) == 0x80) {
+ /* S (Supervisory) format */
+ ghp->data = NULL;
+ ghp->data_len = 0;
+
+ if (ctrl[0] & 0x20)
+ ghp->ack_req = 1;
+ ghp->seq_rx = (ctrl[0] & 0x7) << 6;
+ ghp->seq_rx |= (ctrl[1] >> 2);
+
+ switch (ctrl[1] & 0x03) {
+ case 0:
+ ghp->cmd = GPRS_LLC_RR;
+ break;
+ case 1:
+ ghp->cmd = GPRS_LLC_ACK;
+ break;
+ case 2:
+ ghp->cmd = GPRS_LLC_RNR;
+ break;
+ case 3:
+ ghp->cmd = GPRS_LLC_SACK;
+ break;
+ }
+ } else if ((ctrl[0] & 0xe0) == 0xc0) {
+ /* UI (Unconfirmed Inforamtion) format */
+ ghp->cmd = GPRS_LLC_UI;
+ ghp->data = ctrl + 2;
+ ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+
+ ghp->seq_tx = (ctrl[0] & 0x7) << 6;
+ ghp->seq_tx |= (ctrl[1] >> 2);
+ if (ctrl[1] & 0x02) {
+ ghp->is_encrypted = 1;
+ /* FIXME: encryption */
+ }
+ if (ctrl[1] & 0x01) {
+ /* FCS over hdr + all inf fields */
+ } else {
+ /* FCS over hdr + N202 octets (4) */
+ if (ghp->crc_length > UI_HDR_LEN + N202)
+ ghp->crc_length = UI_HDR_LEN + N202;
+ }
+ } else {
+ /* U (Unnumbered) format: 1 1 1 P/F M4 M3 M2 M1 */
+ ghp->data = NULL;
+ ghp->data_len = 0;
+
+ switch (ctrl[0] & 0xf) {
+ case GPRS_LLC_U_NULL_CMD:
+ ghp->cmd = GPRS_LLC_NULL;
+ break;
+ case GPRS_LLC_U_DM_RESP:
+ ghp->cmd = GPRS_LLC_DM;
+ break;
+ case GPRS_LLC_U_DISC_CMD:
+ ghp->cmd = GPRS_LLC_DISC;
+ break;
+ case GPRS_LLC_U_UA_RESP:
+ ghp->cmd = GPRS_LLC_UA;
+ break;
+ case GPRS_LLC_U_SABM_CMD:
+ ghp->cmd = GPRS_LLC_SABM;
+ break;
+ case GPRS_LLC_U_FRMR_RESP:
+ ghp->cmd = GPRS_LLC_FRMR;
+ break;
+ case GPRS_LLC_U_XID:
+ ghp->cmd = GPRS_LLC_XID;
+ ghp->data = ctrl + 1;
+ ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+ break;
+ default:
+ return -EIO;
+ }
+ }
+
+ /* FIXME: parse sack frame */
+ if (ghp->cmd == GPRS_LLC_SACK) {
+ LOGP(DLLC, LOGL_NOTICE, "Unsupported SACK frame\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv)
+{
+ struct bssgp_ud_hdr *udh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+ struct gprs_llc_hdr *lh = msgb_llch(msg);
+ struct gprs_llc_hdr_parsed llhp;
+ struct gprs_llc_lle *lle;
+ int rc = 0;
+
+ /* Identifiers from DOWN: NSEI, BVCI, TLLI */
+
+ memset(&llhp, 0, sizeof(llhp));
+ rc = gprs_llc_hdr_parse(&llhp, (uint8_t *) lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU));
+ gprs_llc_hdr_dump(&llhp);
+ if (rc < 0) {
+ LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n");
+ return rc;
+ }
+
+ switch (gprs_tlli_type(msgb_tlli(msg))) {
+ case TLLI_LOCAL:
+ case TLLI_FOREIGN:
+ case TLLI_RANDOM:
+ case TLLI_AUXILIARY:
+ break;
+ default:
+ LOGP(DLLC, LOGL_ERROR,
+ "Discarding frame with strange TLLI type\n");
+ break;
+ }
+
+ /* find the LLC Entity for this TLLI+SAPI tuple */
+ lle = lle_by_tlli_sapi(msgb_tlli(msg), llhp.sapi);
+
+ /* 7.2.1.1 LLC belonging to unassigned TLLI+SAPI shall be discarded,
+ * except UID and XID frames with SAPI=1 */
+ if (!lle) {
+ if (llhp.sapi == GPRS_SAPI_GMM &&
+ (llhp.cmd == GPRS_LLC_XID || llhp.cmd == GPRS_LLC_UI)) {
+ struct gprs_llc_llme *llme;
+ /* FIXME: don't use the TLLI but the 0xFFFF unassigned? */
+ llme = llme_alloc(msgb_tlli(msg));
+ LOGP(DLLC, LOGL_DEBUG, "LLC RX: unknown TLLI 0x08x, "
+ "creating LLME on the fly\n", msgb_tlli(msg));
+ lle = &llme->lle[llhp.sapi];
+ } else {
+ LOGP(DLLC, LOGL_NOTICE,
+ "unknown TLLI/SAPI: Silently dropping\n");
+ return 0;
+ }
+ }
+
+ /* decrypt information field + FCS, if needed! */
+ if (llhp.is_encrypted) {
+ uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */
+ uint16_t crypt_len = llhp.data_len + 3;
+ uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK];
+ uint32_t iv;
+ uint64_t kc = *(uint64_t *)&lle->llme->kc;
+ int rc, i;
+
+ if (lle->llme->algo == GPRS_ALGO_GEA0) {
+ LOGP(DLLC, LOGL_NOTICE, "encrypted frame for LLC that "
+ "has no KC/Algo! Dropping.\n");
+ return 0;
+ }
+
+ iv = gprs_cipher_gen_input_ui(iov_ui, lle->sapi, llhp.seq_tx,
+ lle->oc_ui_recv);
+ rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo,
+ kc, iv, GPRS_CIPH_MS2SGSN);
+ if (rc < 0) {
+ LOGP(DLLC, LOGL_ERROR, "Error decrypting frame: %d\n",
+ rc);
+ return rc;
+ }
+
+ /* XOR the cipher output with the information field + FCS */
+ for (i = 0; i < crypt_len; i++)
+ *(llhp.data + i) ^= cipher_out[i];
+ } else {
+ if (lle->llme->algo != GPRS_ALGO_GEA0) {
+ LOGP(DLLC, LOGL_NOTICE, "unencrypted frame for LLC "
+ "that is supposed to be encrypted. Dropping.\n");
+ return 0;
+ }
+ }
+
+ /* We have to do the FCS check _after_ decryption */
+ llhp.fcs_calc = gprs_llc_fcs((uint8_t *)lh, llhp.crc_length);
+ if (llhp.fcs != llhp.fcs_calc) {
+ LOGP(DLLC, LOGL_INFO, "Dropping frame with invalid FCS\n");
+ return -EIO;
+ }
+
+ /* Update LLE's (BVCI, NSEI) tuple */
+ lle->llme->bvci = msgb_bvci(msg);
+ lle->llme->nsei = msgb_nsei(msg);
+
+ /* Receive and Process the actual LLC frame */
+ rc = gprs_llc_hdr_rx(&llhp, lle);
+ if (rc < 0)
+ return rc;
+
+ /* llhp.data is only set when we need to send LL_[UNIT]DATA_IND up */
+ if (llhp.data) {
+ msgb_gmmh(msg) = llhp.data;
+ switch (llhp.sapi) {
+ case GPRS_SAPI_GMM:
+ /* send LL_UNITDATA_IND to GMM */
+ rc = gsm0408_gprs_rcvmsg(msg, lle->llme);
+ break;
+ case GPRS_SAPI_SNDCP3:
+ case GPRS_SAPI_SNDCP5:
+ case GPRS_SAPI_SNDCP9:
+ case GPRS_SAPI_SNDCP11:
+ /* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */
+ rc = sndcp_llunitdata_ind(msg, lle, llhp.data, llhp.data_len);
+ break;
+ case GPRS_SAPI_SMS:
+ /* FIXME */
+ case GPRS_SAPI_TOM2:
+ case GPRS_SAPI_TOM8:
+ /* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */
+ default:
+ LOGP(DLLC, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi);
+ rc = -EINVAL;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */
+int gprs_llgmm_assign(struct gprs_llc_llme *llme,
+ uint32_t old_tlli, uint32_t new_tlli,
+ enum gprs_ciph_algo alg, const uint8_t *kc)
+{
+ unsigned int i;
+
+ /* Update the crypto parameters */
+ llme->algo = alg;
+ if (alg != GPRS_ALGO_GEA0)
+ memcpy(llme->kc, kc, sizeof(llme->kc));
+
+ if (old_tlli == 0xffffffff && new_tlli != 0xffffffff) {
+ /* TLLI Assignment 8.3.1 */
+ /* New TLLI shall be assigned and used when (re)transmitting LLC frames */
+ /* If old TLLI != 0xffffffff was assigned to LLME, then TLLI
+ * old is unassigned. Only TLLI new shall be accepted when
+ * received from peer. */
+ if (llme->old_tlli != 0xffffffff) {
+ llme->old_tlli = 0xffffffff;
+ llme->tlli = new_tlli;
+ } else {
+ /* If TLLI old == 0xffffffff was assigned to LLME, then this is
+ * TLLI assignmemt according to 8.3.1 */
+ llme->old_tlli = 0xffffffff;
+ llme->tlli = new_tlli;
+ llme->state = GPRS_LLMS_ASSIGNED;
+ /* 8.5.3.1 For all LLE's */
+ for (i = 0; i < ARRAY_SIZE(llme->lle); i++) {
+ struct gprs_llc_lle *l = &llme->lle[i];
+ l->vu_send = l->vu_recv = 0;
+ l->retrans_ctr = 0;
+ l->state = GPRS_LLES_ASSIGNED_ADM;
+ /* FIXME Set parameters according to table 9 */
+ }
+ }
+ } else if (old_tlli != 0xffffffff && new_tlli != 0xffffffff) {
+ /* TLLI Change 8.3.2 */
+ /* Both TLLI Old and TLLI New are assigned; use New when
+ * (re)transmitting. Accept toth Old and New on Rx */
+ llme->old_tlli = llme->tlli;
+ llme->tlli = new_tlli;
+ llme->state = GPRS_LLMS_ASSIGNED;
+ } else if (old_tlli != 0xffffffff && new_tlli == 0xffffffff) {
+ /* TLLI Unassignment 8.3.3) */
+ llme->tlli = llme->old_tlli = 0;
+ llme->state = GPRS_LLMS_UNASSIGNED;
+ for (i = 0; i < ARRAY_SIZE(llme->lle); i++) {
+ struct gprs_llc_lle *l = &llme->lle[i];
+ l->state = GPRS_LLES_UNASSIGNED;
+ }
+ llme_free(llme);
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+int gprs_llc_init(const char *cipher_plugin_path)
+{
+ return gprs_cipher_load(cipher_plugin_path);
+}
diff --git a/src/gprs/gprs_llc_vty.c b/src/gprs/gprs_llc_vty.c
new file mode 100644
index 00000000..d4f743b0
--- /dev/null
+++ b/src/gprs/gprs_llc_vty.c
@@ -0,0 +1,108 @@
+/* VTY interface for our GPRS LLC implementation */
+
+/* (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 <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_llc.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+struct value_string gprs_llc_state_strs[] = {
+ { GPRS_LLES_UNASSIGNED, "TLLI Unassigned" },
+ { GPRS_LLES_ASSIGNED_ADM, "TLLI Assigned" },
+ { GPRS_LLES_LOCAL_EST, "Local Establishment" },
+ { GPRS_LLES_REMOTE_EST, "Remote Establishment" },
+ { GPRS_LLES_ABM, "Asynchronous Balanced Mode" },
+ { GPRS_LLES_LOCAL_REL, "Local Release" },
+ { GPRS_LLES_TIMER_REC, "Timer Recovery" },
+};
+
+static void vty_dump_lle(struct vty *vty, struct gprs_llc_lle *lle)
+{
+ struct gprs_llc_params *par = &lle->params;
+ vty_out(vty, " SAPI %2u State %s VUsend=%u, VUrecv=%u", lle->sapi,
+ get_value_string(gprs_llc_state_strs, lle->state),
+ lle->vu_send, lle->vu_recv);
+ vty_out(vty, " Vsent=%u Vack=%u Vrecv=%u, RetransCtr=%u%s",
+ lle->v_sent, lle->v_ack, lle->v_recv,
+ lle->retrans_ctr, VTY_NEWLINE);
+ vty_out(vty, " T200=%u, N200=%u, N201-U=%u, N201-I=%u, mD=%u, "
+ "mU=%u, kD=%u, kU=%u%s", par->t200_201, par->n200,
+ par->n201_u, par->n201_i, par->mD, par->mU, par->kD,
+ par->kU, VTY_NEWLINE);
+}
+
+static uint8_t valid_sapis[] = { 1, 2, 3, 5, 7, 8, 9, 11 };
+
+static void vty_dump_llme(struct vty *vty, struct gprs_llc_llme *llme)
+{
+ unsigned int i;
+
+ vty_out(vty, "TLLI %08x (Old TLLI %08x) BVCI=%u NSEI=%u: State %s%s",
+ llme->tlli, llme->old_tlli, llme->bvci, llme->nsei,
+ get_value_string(gprs_llc_state_strs, llme->state), VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(valid_sapis); i++) {
+ struct gprs_llc_lle *lle;
+ uint8_t sapi = valid_sapis[i];
+
+ if (sapi >= ARRAY_SIZE(llme->lle))
+ continue;
+
+ lle = &llme->lle[sapi];
+ vty_dump_lle(vty, lle);
+ }
+}
+
+
+DEFUN(show_llc, show_llc_cmd,
+ "show llc",
+ SHOW_STR "Display information about the LLC protocol")
+{
+ struct gprs_llc_llme *llme;
+
+ vty_out(vty, "State of LLC Entities%s", VTY_NEWLINE);
+ llist_for_each_entry(llme, &gprs_llc_llmes, list) {
+ vty_dump_llme(vty, llme);
+ }
+ return CMD_SUCCESS;
+}
+
+int gprs_llc_vty_init(void)
+{
+ install_element_ve(&show_llc_cmd);
+
+ return 0;
+}
diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c
new file mode 100644
index 00000000..44365541
--- /dev/null
+++ b/src/gprs/gprs_sgsn.c
@@ -0,0 +1,383 @@
+/* GPRS SGSN functionality */
+
+/* (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 <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_gmm.h>
+
+extern struct sgsn_instance *sgsn;
+
+LLIST_HEAD(sgsn_mm_ctxts);
+LLIST_HEAD(sgsn_ggsn_ctxts);
+LLIST_HEAD(sgsn_apn_ctxts);
+LLIST_HEAD(sgsn_pdp_ctxts);
+
+static const struct rate_ctr_desc mmctx_ctr_description[] = {
+ { "sign.packets.in", "Signalling Messages ( In)" },
+ { "sign.packets.out", "Signalling Messages (Out)" },
+ { "udata.packets.in", "User Data Messages ( In)" },
+ { "udata.packets.out", "User Data Messages (Out)" },
+ { "udata.bytes.in", "User Data Bytes ( In)" },
+ { "udata.bytes.out", "User Data Bytes (Out)" },
+ { "pdp_ctx_act", "PDP Context Activations " },
+ { "suspend", "SUSPEND Count " },
+ { "paging.ps", "Paging Packet Switched " },
+ { "paging.cs", "Paging Circuit Switched " },
+ { "ra_update", "Routing Area Update " },
+};
+
+static const struct rate_ctr_group_desc mmctx_ctrg_desc = {
+ .group_name_prefix = "sgsn.mmctx",
+ .group_description = "SGSN MM Context Statistics",
+ .num_ctr = ARRAY_SIZE(mmctx_ctr_description),
+ .ctr_desc = mmctx_ctr_description,
+};
+
+static const struct rate_ctr_desc pdpctx_ctr_description[] = {
+ { "udata.packets.in", "User Data Messages ( In)" },
+ { "udata.packets.out", "User Data Messages (Out)" },
+ { "udata.bytes.in", "User Data Bytes ( In)" },
+ { "udata.bytes.out", "User Data Bytes (Out)" },
+};
+
+static const struct rate_ctr_group_desc pdpctx_ctrg_desc = {
+ .group_name_prefix = "sgsn.pdpctx",
+ .group_description = "SGSN PDP Context Statistics",
+ .num_ctr = ARRAY_SIZE(pdpctx_ctr_description),
+ .ctr_desc = pdpctx_ctr_description,
+};
+
+static int ra_id_equals(const struct gprs_ra_id *id1,
+ const struct gprs_ra_id *id2)
+{
+ return (id1->mcc == id2->mcc && id1->mnc == id2->mnc &&
+ id1->lac == id2->lac && id1->rac == id2->rac);
+}
+
+/* See 03.02 Chapter 2.6 */
+static inline uint32_t tlli_foreign(uint32_t tlli)
+{
+ return ((tlli | 0x80000000) & ~0x40000000);
+}
+
+/* look-up a SGSN MM context based on TLLI + RAI */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
+ const struct gprs_ra_id *raid)
+{
+ struct sgsn_mm_ctx *ctx;
+ int tlli_type;
+
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (tlli == ctx->tlli &&
+ ra_id_equals(raid, &ctx->ra))
+ return ctx;
+ }
+
+ tlli_type = gprs_tlli_type(tlli);
+ switch (tlli_type) {
+ case TLLI_LOCAL:
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if ((ctx->p_tmsi | 0xC0000000) == tlli ||
+ (ctx->p_tmsi_old && (ctx->p_tmsi_old | 0xC0000000) == tlli)) {
+ ctx->tlli = tlli;
+ return ctx;
+ }
+ }
+ break;
+ case TLLI_FOREIGN:
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (tlli == tlli_foreign(ctx->tlli) &&
+ ra_id_equals(raid, &ctx->ra))
+ return ctx;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (p_tmsi == ctx->p_tmsi ||
+ (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi))
+ return ctx;
+ }
+ return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (!strcmp(imsi, ctx->imsi))
+ return ctx;
+ }
+ return NULL;
+
+}
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+ const struct gprs_ra_id *raid)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
+ if (!ctx)
+ return NULL;
+
+ memcpy(&ctx->ra, raid, sizeof(ctx->ra));
+ ctx->tlli = tlli;
+ ctx->mm_state = GMM_DEREGISTERED;
+ ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, tlli);
+ INIT_LLIST_HEAD(&ctx->pdp_list);
+
+ llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+ return ctx;
+}
+
+void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm)
+{
+ struct sgsn_pdp_ctx *pdp, *pdp2;
+
+ /* Unlink from global list of MM contexts */
+ llist_del(&mm->list);
+
+ /* Free all PDP contexts */
+ llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list)
+ sgsn_pdp_ctx_free(pdp);
+
+ rate_ctr_group_free(mm->ctrg);
+
+ talloc_free(mm);
+}
+
+/* look up PDP context by MM context and NSAPI */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+ uint8_t nsapi)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->nsapi == nsapi)
+ return pdp;
+ }
+ return NULL;
+}
+
+/* look up PDP context by MM context and transaction ID */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm,
+ uint8_t tid)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->ti == tid)
+ return pdp;
+ }
+ return NULL;
+}
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+ uint8_t nsapi)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi);
+ if (pdp)
+ return NULL;
+
+ pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx);
+ if (!pdp)
+ return NULL;
+
+ pdp->mm = mm;
+ pdp->nsapi = nsapi;
+ pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi);
+ llist_add(&pdp->list, &mm->pdp_list);
+ llist_add(&pdp->g_list, &sgsn_pdp_ctxts);
+
+ return pdp;
+}
+
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp)
+{
+ rate_ctr_group_free(pdp->ctrg);
+ llist_del(&pdp->list);
+ llist_del(&pdp->g_list);
+ talloc_free(pdp);
+}
+
+/* GGSN contexts */
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ ggc = talloc_zero(tall_bsc_ctx, struct sgsn_ggsn_ctx);
+ if (!ggc)
+ return NULL;
+
+ ggc->id = id;
+ ggc->gtp_version = 1;
+ ggc->remote_restart_ctr = -1;
+ /* if we are called from config file parse, this gsn doesn't exist yet */
+ ggc->gsn = sgsn->gsn;
+ llist_add(&ggc->list, &sgsn_ggsn_ctxts);
+
+ return ggc;
+}
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+ if (id == ggc->id)
+ return ggc;
+ }
+ return NULL;
+}
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+ if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr)))
+ return ggc;
+ }
+ return NULL;
+}
+
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ ggc = sgsn_ggsn_ctx_by_id(id);
+ if (!ggc)
+ ggc = sgsn_ggsn_ctx_alloc(id);
+ return ggc;
+}
+
+/* APN contexts */
+
+#if 0
+struct apn_ctx *apn_ctx_alloc(const char *ap_name)
+{
+ struct apn_ctx *actx;
+
+ actx = talloc_zero(talloc_bsc_ctx, struct apn_ctx);
+ if (!actx)
+ return NULL;
+ actx->name = talloc_strdup(actx, ap_name);
+
+ return actx;
+}
+
+struct apn_ctx *apn_ctx_by_name(const char *name)
+{
+ struct apn_ctx *actx;
+
+ llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
+ if (!strcmp(name, actx->name))
+ return actx;
+ }
+ return NULL;
+}
+
+struct apn_ctx *apn_ctx_find_alloc(const char *name)
+{
+ struct apn_ctx *actx;
+
+ actx = apn_ctx_by_name(name);
+ if (!actx)
+ actx = apn_ctx_alloc(name);
+
+ return actx;
+}
+#endif
+
+uint32_t sgsn_alloc_ptmsi(void)
+{
+ struct sgsn_mm_ctx *mm;
+ uint32_t ptmsi;
+
+restart:
+ ptmsi = rand();
+ llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+ if (mm->p_tmsi == ptmsi)
+ goto restart;
+ }
+
+ return ptmsi;
+}
+
+static void drop_one_pdp(struct sgsn_pdp_ctx *pdp)
+{
+ if (pdp->mm->mm_state == GMM_REGISTERED_NORMAL)
+ gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
+ else {
+ /* FIXME: GPRS paging in case MS is SUSPENDED */
+ LOGP(DGPRS, LOGL_NOTICE, "Hard-dropping PDP ctx due to GGSN "
+ "recovery\n");
+ sgsn_pdp_ctx_free(pdp);
+ }
+}
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * ottherwise lost state (recovery procedure) */
+int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn)
+{
+ struct sgsn_mm_ctx *mm;
+ int num = 0;
+
+ llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+ struct sgsn_pdp_ctx *pdp;
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->ggsn == ggsn) {
+ drop_one_pdp(pdp);
+ num++;
+ }
+ }
+ }
+
+ return num;
+}
diff --git a/src/gprs/gprs_sndcp.c b/src/gprs/gprs_sndcp.c
new file mode 100644
index 00000000..4f421e45
--- /dev/null
+++ b/src/gprs/gprs_sndcp.c
@@ -0,0 +1,616 @@
+/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */
+
+/* (C) 2010 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/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/timer.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+
+#include "gprs_sndcp.h"
+
+/* Chapter 7.2: SN-PDU Formats */
+struct sndcp_common_hdr {
+ /* octet 1 */
+ uint8_t nsapi:4;
+ uint8_t more:1;
+ uint8_t type:1;
+ uint8_t first:1;
+ uint8_t spare:1;
+} __attribute__((packed));
+
+/* PCOMP / DCOMP only exist in first fragment */
+struct sndcp_comp_hdr {
+ /* octet 2 */
+ uint8_t pcomp:4;
+ uint8_t dcomp:4;
+} __attribute__((packed));
+
+struct sndcp_udata_hdr {
+ /* octet 3 */
+ uint8_t npdu_high:4;
+ uint8_t seg_nr:4;
+ /* octet 4 */
+ uint8_t npdu_low;
+} __attribute__((packed));
+
+
+static void *tall_sndcp_ctx;
+
+/* A fragment queue entry, containing one framgent of a N-PDU */
+struct defrag_queue_entry {
+ struct llist_head list;
+ /* segment number of this fragment */
+ uint32_t seg_nr;
+ /* length of the data area of this fragment */
+ uint32_t data_len;
+ /* pointer to the data of this fragment */
+ uint8_t *data;
+};
+
+LLIST_HEAD(gprs_sndcp_entities);
+
+/* Enqueue a fragment into the defragment queue */
+static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr,
+ uint8_t *data, uint32_t data_len)
+{
+ struct defrag_queue_entry *dqe;
+
+ dqe = talloc_zero(tall_sndcp_ctx, struct defrag_queue_entry);
+ if (!dqe)
+ return -ENOMEM;
+ dqe->data = talloc_zero_size(dqe, data_len);
+ if (!dqe->data) {
+ talloc_free(dqe);
+ return -ENOMEM;
+ }
+ dqe->seg_nr = seg_nr;
+ dqe->data_len = data_len;
+
+ llist_add(&dqe->list, &sne->defrag.frag_list);
+
+ if (seg_nr > sne->defrag.highest_seg)
+ sne->defrag.highest_seg = seg_nr;
+
+ sne->defrag.seg_have |= (1 << seg_nr);
+ sne->defrag.tot_len += data_len;
+
+ memcpy(dqe->data, data, data_len);
+
+ return 0;
+}
+
+/* return if we have all segments of this N-PDU */
+static int defrag_have_all_segments(struct gprs_sndcp_entity *sne)
+{
+ uint32_t seg_needed = 0;
+ unsigned int i;
+
+ /* create a bitmask of needed segments */
+ for (i = 0; i <= sne->defrag.highest_seg; i++)
+ seg_needed |= (1 << i);
+
+ if (seg_needed == sne->defrag.seg_have)
+ return 1;
+
+ return 0;
+}
+
+static struct defrag_queue_entry *defrag_get_seg(struct gprs_sndcp_entity *sne,
+ uint32_t seg_nr)
+{
+ struct defrag_queue_entry *dqe;
+
+ llist_for_each_entry(dqe, &sne->defrag.frag_list, list) {
+ if (dqe->seg_nr == seg_nr) {
+ llist_del(&dqe->list);
+ return dqe;
+ }
+ }
+ return NULL;
+}
+
+/* Perform actual defragmentation and create an output packet */
+static int defrag_segments(struct gprs_sndcp_entity *sne)
+{
+ struct msgb *msg;
+ unsigned int seg_nr;
+ uint8_t *npdu;
+
+ LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u "
+ "num_seg=%u tot_len=%u\n", sne->lle->llme->tlli, sne->nsapi,
+ sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len);
+ msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag");
+ if (!msg)
+ return -ENOMEM;
+
+ /* FIXME: message headers + identifiers */
+
+ npdu = msg->data;
+
+ for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) {
+ struct defrag_queue_entry *dqe;
+ uint8_t *data;
+
+ dqe = defrag_get_seg(sne, seg_nr);
+ if (!dqe) {
+ LOGP(DSNDCP, LOGL_ERROR, "Segment %u missing\n", seg_nr);
+ talloc_free(msg);
+ return -EIO;
+ }
+ /* actually append the segment to the N-PDU */
+ data = msgb_put(msg, dqe->data_len);
+ memcpy(data, dqe->data, dqe->data_len);
+
+ /* release memory for the fragment queue entry */
+ talloc_free(dqe);
+ }
+
+ /* FIXME: cancel timer */
+
+ /* actually send the N-PDU to the SGSN core code, which then
+ * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
+ return sgsn_rx_sndcp_ud_ind(&sne->ra_id, sne->lle->llme->tlli,
+ sne->nsapi, msg, sne->defrag.tot_len, npdu);
+}
+
+static int defrag_input(struct gprs_sndcp_entity *sne, struct msgb *msg, uint8_t *hdr,
+ unsigned int len)
+{
+ struct sndcp_common_hdr *sch;
+ struct sndcp_comp_hdr *scomph = NULL;
+ struct sndcp_udata_hdr *suh;
+ uint16_t npdu_num;
+ uint8_t *data;
+ int rc;
+
+ sch = (struct sndcp_common_hdr *) hdr;
+ if (sch->first) {
+ scomph = (struct sndcp_comp_hdr *) (hdr + 1);
+ suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
+ } else
+ suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));
+
+ data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr);
+
+ npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
+
+ LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u "
+ "Length %u %s %s\n", sne->lle->llme->tlli, sne->nsapi, npdu_num,
+ suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : "");
+
+ if (sch->first) {
+ /* first segment of a new packet. Discard all leftover fragments of
+ * previous packet */
+ if (!llist_empty(&sne->defrag.frag_list)) {
+ struct defrag_queue_entry *dqe, *dqe2;
+ LOGP(DSNDCP, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping "
+ "SN-PDU %u due to insufficient segments (%04x)\n",
+ sne->lle->llme->tlli, sne->nsapi, sne->defrag.npdu,
+ sne->defrag.seg_have);
+ llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) {
+ llist_del(&dqe->list);
+ talloc_free(dqe);
+ }
+ }
+ /* store the currently de-fragmented PDU number */
+ sne->defrag.npdu = npdu_num;
+
+ /* Re-set fragmentation state */
+ sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0;
+ sne->defrag.tot_len = 0;
+ /* FIXME: (re)start timer */
+ }
+
+ if (sne->defrag.npdu != npdu_num) {
+ LOGP(DSNDCP, LOGL_INFO, "Segment for different SN-PDU "
+ "(%u != %u)\n", npdu_num, sne->defrag.npdu);
+ /* FIXME */
+ }
+
+ /* FIXME: check if seg_nr already exists */
+ /* make sure to subtract length of SNDCP header from 'len' */
+ rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr));
+ if (rc < 0)
+ return rc;
+
+ if (!sch->more) {
+ /* this is suppsed to be the last segment of the N-PDU, but it
+ * might well be not the last to arrive */
+ sne->defrag.no_more = 1;
+ }
+
+ if (sne->defrag.no_more) {
+ /* we have already received the last segment before, let's check
+ * if all the previous segments exist */
+ if (defrag_have_all_segments(sne))
+ return defrag_segments(sne);
+ }
+
+ return 0;
+}
+
+static struct gprs_sndcp_entity *gprs_sndcp_entity_by_lle(const struct gprs_llc_lle *lle,
+ uint8_t nsapi)
+{
+ struct gprs_sndcp_entity *sne;
+
+ llist_for_each_entry(sne, &gprs_sndcp_entities, list) {
+ if (sne->lle == lle && sne->nsapi == nsapi)
+ return sne;
+ }
+ return NULL;
+}
+
+static struct gprs_sndcp_entity *gprs_sndcp_entity_alloc(struct gprs_llc_lle *lle,
+ uint8_t nsapi)
+{
+ struct gprs_sndcp_entity *sne;
+
+ sne = talloc_zero(tall_sndcp_ctx, struct gprs_sndcp_entity);
+ if (!sne)
+ return NULL;
+
+ sne->lle = lle;
+ sne->nsapi = nsapi;
+ sne->defrag.timer.data = sne;
+ //sne->fqueue.timer.cb = FIXME;
+ sne->rx_state = SNDCP_RX_S_FIRST;
+ INIT_LLIST_HEAD(&sne->defrag.frag_list);
+
+ llist_add(&sne->list, &gprs_sndcp_entities);
+
+ return sne;
+}
+
+/* Entry point for the SNSM-ACTIVATE.indication */
+int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi)
+{
+ LOGP(DSNDCP, LOGL_INFO, "SNSM-ACTIVATE.ind (lle=%p TLLI=%08x, "
+ "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi);
+
+ if (gprs_sndcp_entity_by_lle(lle, nsapi)) {
+ LOGP(DSNDCP, LOGL_ERROR, "Trying to ACTIVATE "
+ "already-existing entity (TLLI=%08x, NSAPI=%u)\n",
+ lle->llme->tlli, nsapi);
+ return -EEXIST;
+ }
+
+ if (!gprs_sndcp_entity_alloc(lle, nsapi)) {
+ LOGP(DSNDCP, LOGL_ERROR, "Out of memory during ACTIVATE\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* Entry point for the SNSM-DEACTIVATE.indication */
+int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi)
+{
+ struct gprs_sndcp_entity *sne;
+
+ LOGP(DSNDCP, LOGL_INFO, "SNSM-DEACTIVATE.ind (lle=%p, TLLI=%08x, "
+ "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi);
+
+ sne = gprs_sndcp_entity_by_lle(lle, nsapi);
+ if (!sne) {
+ LOGP(DSNDCP, LOGL_ERROR, "SNSM-DEACTIVATE.ind for non-"
+ "existing TLLI=%08x SAPI=%u NSAPI=%u\n", lle->llme->tlli,
+ lle->sapi, nsapi);
+ return -ENOENT;
+ }
+ llist_del(&sne->list);
+ /* frag queue entries are hierarchically allocated, so no need to
+ * free them explicitly here */
+ talloc_free(sne);
+
+ return 0;
+}
+
+/* Fragmenter state */
+struct sndcp_frag_state {
+ uint8_t frag_nr;
+ struct msgb *msg; /* original message */
+ uint8_t *next_byte; /* first byte of next fragment */
+
+ struct gprs_sndcp_entity *sne;
+ void *mmcontext;
+};
+
+/* returns '1' if there are more fragments to send, '0' if none */
+static int sndcp_send_ud_frag(struct sndcp_frag_state *fs)
+{
+ struct gprs_sndcp_entity *sne = fs->sne;
+ struct gprs_llc_lle *lle = sne->lle;
+ struct sndcp_common_hdr *sch;
+ struct sndcp_comp_hdr *scomph;
+ struct sndcp_udata_hdr *suh;
+ struct msgb *fmsg;
+ unsigned int max_payload_len;
+ unsigned int len;
+ uint8_t *data;
+ int rc, more;
+
+ fmsg = msgb_alloc_headroom(fs->sne->lle->params.n201_u+256, 128,
+ "SNDCP Frag");
+ if (!fmsg)
+ return -ENOMEM;
+
+ /* make sure lower layers route the fragment like the original */
+ msgb_tlli(fmsg) = msgb_tlli(fs->msg);
+ msgb_bvci(fmsg) = msgb_bvci(fs->msg);
+ msgb_nsei(fmsg) = msgb_nsei(fs->msg);
+
+ /* prepend common SNDCP header */
+ sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch));
+ sch->nsapi = sne->nsapi;
+ /* Set FIRST bit if we are the first fragment in a series */
+ if (fs->frag_nr == 0)
+ sch->first = 1;
+ sch->type = 1;
+
+ /* append the compression header for first fragment */
+ if (sch->first) {
+ scomph = (struct sndcp_comp_hdr *)
+ msgb_put(fmsg, sizeof(*scomph));
+ scomph->pcomp = 0;
+ scomph->dcomp = 0;
+ }
+
+ /* append the user-data header */
+ suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh));
+ suh->npdu_low = sne->tx_npdu_nr & 0xff;
+ suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
+ suh->seg_nr = fs->frag_nr % 0xf;
+
+ /* calculate remaining length to be sent */
+ len = (fs->msg->data + fs->msg->len) - fs->next_byte;
+ /* how much payload can we actually send via LLC? */
+ max_payload_len = lle->params.n201_u - (sizeof(*sch) + sizeof(*suh));
+ if (sch->first)
+ max_payload_len -= sizeof(*scomph);
+ /* check if we're exceeding the max */
+ if (len > max_payload_len)
+ len = max_payload_len;
+
+ /* copy the actual fragment data into our fmsg */
+ data = msgb_put(fmsg, len);
+ memcpy(data, fs->next_byte, len);
+
+ /* Increment fragment number and data pointer to next fragment */
+ fs->frag_nr++;
+ fs->next_byte += len;
+
+ /* determine if we have more fragemnts to send */
+ if ((fs->msg->data + fs->msg->len) <= fs->next_byte)
+ more = 0;
+ else
+ more = 1;
+
+ /* set the MORE bit of the SNDCP header accordingly */
+ sch->more = more;
+
+ rc = gprs_llc_tx_ui(fmsg, lle->sapi, 0, fs->mmcontext);
+ if (rc < 0) {
+ /* abort in case of error, do not advance frag_nr / next_byte */
+ msgb_free(fmsg);
+ return rc;
+ }
+
+ if (!more) {
+ /* we've sent all fragments */
+ msgb_free(fs->msg);
+ memset(fs, 0, sizeof(*fs));
+ /* increment NPDU number for next frame */
+ sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
+ return 0;
+ }
+
+ /* default: more fragments to send */
+ return 1;
+}
+
+/* Request transmission of a SN-PDU over specified LLC Entity + SAPI */
+int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi,
+ void *mmcontext)
+{
+ struct gprs_sndcp_entity *sne;
+ struct sndcp_common_hdr *sch;
+ struct sndcp_comp_hdr *scomph;
+ struct sndcp_udata_hdr *suh;
+ struct sndcp_frag_state fs;
+
+ /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+ sne = gprs_sndcp_entity_by_lle(lle, nsapi);
+ if (!sne) {
+ LOGP(DSNDCP, LOGL_ERROR, "Cannot find SNDCP Entity\n");
+ return -EIO;
+ }
+
+ /* Check if we need to fragment this N-PDU into multiple SN-PDUs */
+ if (msg->len > lle->params.n201_u -
+ (sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) {
+ /* initialize the fragmenter state */
+ fs.msg = msg;
+ fs.frag_nr = 0;
+ fs.next_byte = msg->data;
+ fs.sne = sne;
+ fs.mmcontext = mmcontext;
+
+ /* call function to generate and send fragments until all
+ * of the N-PDU has been sent */
+ while (1) {
+ int rc = sndcp_send_ud_frag(&fs);
+ if (rc == 0)
+ return 0;
+ if (rc < 0)
+ return rc;
+ }
+ /* not reached */
+ return 0;
+ }
+
+ /* this is the non-fragmenting case where we only build 1 SN-PDU */
+
+ /* prepend the user-data header */
+ suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh));
+ suh->npdu_low = sne->tx_npdu_nr & 0xff;
+ suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
+ suh->seg_nr = 0;
+ sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
+
+ scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph));
+ scomph->pcomp = 0;
+ scomph->dcomp = 0;
+
+ /* prepend common SNDCP header */
+ sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch));
+ sch->first = 1;
+ sch->type = 1;
+ sch->nsapi = nsapi;
+
+ return gprs_llc_tx_ui(msg, lle->sapi, 0, mmcontext);
+}
+
+/* Section 5.1.2.17 LL-UNITDATA.ind */
+int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle,
+ uint8_t *hdr, uint16_t len)
+{
+ struct gprs_sndcp_entity *sne;
+ struct sndcp_common_hdr *sch = (struct sndcp_common_hdr *)hdr;
+ struct sndcp_comp_hdr *scomph = NULL;
+ struct sndcp_udata_hdr *suh;
+ uint8_t *npdu;
+ uint16_t npdu_num;
+ int npdu_len;
+
+ sch = (struct sndcp_common_hdr *) hdr;
+ if (sch->first) {
+ scomph = (struct sndcp_comp_hdr *) (hdr + 1);
+ suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
+ } else
+ suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));
+
+ if (sch->type == 0) {
+ LOGP(DSNDCP, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n");
+ return -EINVAL;
+ }
+
+ if (len < sizeof(*sch) + sizeof(*suh)) {
+ LOGP(DSNDCP, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len);
+ return -EIO;
+ }
+
+ sne = gprs_sndcp_entity_by_lle(lle, sch->nsapi);
+ if (!sne) {
+ LOGP(DSNDCP, LOGL_ERROR, "Message for non-existing SNDCP Entity "
+ "(lle=%p, TLLI=%08x, SAPI=%u, NSAPI=%u)\n", lle,
+ lle->llme->tlli, lle->sapi, sch->nsapi);
+ return -EIO;
+ }
+ /* FIXME: move this RA_ID up to the LLME or even higher */
+ bssgp_parse_cell_id(&sne->ra_id, msgb_bcid(msg));
+
+ /* any non-first segment is by definition something to defragment
+ * as is any segment that tells us there are more segments */
+ if (!sch->first || sch->more)
+ return defrag_input(sne, msg, hdr, len);
+
+ if (scomph && (scomph->pcomp || scomph->dcomp)) {
+ LOGP(DSNDCP, LOGL_ERROR, "We don't support compression yet\n");
+ return -EIO;
+ }
+
+ npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
+ npdu = (uint8_t *)suh + sizeof(*suh);
+ npdu_len = (msg->data + msg->len) - npdu;
+ if (npdu_len <= 0) {
+ LOGP(DSNDCP, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len);
+ return -EIO;
+ }
+ /* actually send the N-PDU to the SGSN core code, which then
+ * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
+ return sgsn_rx_sndcp_ud_ind(&sne->ra_id, lle->llme->tlli, sne->nsapi, msg, npdu_len, npdu);
+}
+
+/* Section 5.1.2.1 LL-RESET.ind */
+static int sndcp_ll_reset_ind(struct gprs_sndcp_entity *se)
+{
+ /* treat all outstanding SNDCP-LLC request type primitives as not sent */
+ /* reset all SNDCP XID parameters to default values */
+}
+
+static int sndcp_ll_status_ind()
+{
+ /* inform the SM sub-layer by means of SNSM-STATUS.req */
+}
+
+#if 0
+static struct sndcp_state_list {{
+ uint32_t states;
+ unsigned int type;
+ int (*rout)(struct gprs_sndcp_entity *se, struct msgb *msg);
+} sndcp_state_list[] = {
+ { ALL_STATES,
+ LL_RESET_IND, sndcp_ll_reset_ind },
+ { ALL_STATES,
+ LL_ESTABLISH_IND, sndcp_ll_est_ind },
+ { SBIT(SNDCP_S_EST_RQD),
+ LL_ESTABLISH_RESP, sndcp_ll_est_ind },
+ { SBIT(SNDCP_S_EST_RQD),
+ LL_ESTABLISH_CONF, sndcp_ll_est_conf },
+ { SBIT(SNDCP_S_
+};
+
+static int sndcp_rx_llc_prim()
+{
+ case LL_ESTABLISH_REQ:
+ case LL_RELEASE_REQ:
+ case LL_XID_REQ:
+ case LL_DATA_REQ:
+ LL_UNITDATA_REQ, /* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */
+
+ switch (prim) {
+ case LL_RESET_IND:
+ case LL_ESTABLISH_IND:
+ case LL_ESTABLISH_RESP:
+ case LL_ESTABLISH_CONF:
+ case LL_RELEASE_IND:
+ case LL_RELEASE_CONF:
+ case LL_XID_IND:
+ case LL_XID_RESP:
+ case LL_XID_CONF:
+ case LL_DATA_IND:
+ case LL_DATA_CONF:
+ case LL_UNITDATA_IND:
+ case LL_STATUS_IND:
+}
+#endif
diff --git a/src/gprs/gprs_sndcp.h b/src/gprs/gprs_sndcp.h
new file mode 100644
index 00000000..e9a50be1
--- /dev/null
+++ b/src/gprs/gprs_sndcp.h
@@ -0,0 +1,53 @@
+#ifndef _INT_SNDCP_H
+#define _INT_SNDCP_H
+
+#include <stdint.h>
+#include <osmocore/linuxlist.h>
+
+/* A fragment queue header, maintaining list of fragments for one N-PDU */
+struct defrag_state {
+ /* PDU number for which the defragmentation state applies */
+ uint16_t npdu;
+ /* highest segment number we have received so far */
+ uint8_t highest_seg;
+ /* bitmask of the segments we already have */
+ uint32_t seg_have;
+ /* do we still expect more segments? */
+ unsigned int no_more;
+ /* total length of all segments together */
+ unsigned int tot_len;
+
+ /* linked list of defrag_queue_entry: one for each fragment */
+ struct llist_head frag_list;
+
+ struct timer_list timer;
+};
+
+/* See 6.7.1.2 Reassembly */
+enum sndcp_rx_state {
+ SNDCP_RX_S_FIRST,
+ SNDCP_RX_S_SUBSEQ,
+ SNDCP_RX_S_DISCARD,
+};
+
+struct gprs_sndcp_entity {
+ struct llist_head list;
+
+ /* FIXME: move this RA_ID up to the LLME or even higher */
+ struct gprs_ra_id ra_id;
+ /* reference to the LLC Entity below this SNDCP entity */
+ struct gprs_llc_lle *lle;
+ /* The NSAPI we shall use on top of LLC */
+ uint8_t nsapi;
+
+ /* NPDU number for the GTP->SNDCP side */
+ uint16_t tx_npdu_nr;
+ /* SNDCP eeceiver state */
+ enum sndcp_rx_state rx_state;
+ /* The defragmentation queue */
+ struct defrag_state defrag;
+};
+
+extern struct llist_head gprs_sndcp_entities;
+
+#endif /* INT_SNDCP_H */
diff --git a/src/gprs/gprs_sndcp_vty.c b/src/gprs/gprs_sndcp_vty.c
new file mode 100644
index 00000000..5a755d5f
--- /dev/null
+++ b/src/gprs/gprs_sndcp_vty.c
@@ -0,0 +1,74 @@
+/* VTY interface for our GPRS SNDCP implementation */
+
+/* (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 <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_llc.h>
+
+#include "gprs_sndcp.h"
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+static void vty_dump_sne(struct vty *vty, struct gprs_sndcp_entity *sne)
+{
+ unsigned int i;
+
+ vty_out(vty, " TLLI %08x SAPI=%u NSAPI=%u:%s",
+ sne->lle->llme->tlli, sne->lle->sapi, sne->nsapi, VTY_NEWLINE);
+ vty_out(vty, " Defrag: npdu=%u highest_seg=%u seg_have=0x%08x tot_len=%u%s",
+ sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.seg_have,
+ sne->defrag.tot_len, VTY_NEWLINE);
+}
+
+
+DEFUN(show_sndcp, show_sndcp_cmd,
+ "show sndcp",
+ SHOW_STR "Display information about the SNDCP protocol")
+{
+ struct gprs_sndcp_entity *sne;
+
+ vty_out(vty, "State of SNDCP Entities%s", VTY_NEWLINE);
+ llist_for_each_entry(sne, &gprs_sndcp_entities, list)
+ vty_dump_sne(vty, sne);
+
+ return CMD_SUCCESS;
+}
+
+int gprs_sndcp_vty_init(void)
+{
+ install_element_ve(&show_sndcp_cmd);
+
+ return 0;
+}
diff --git a/src/gprs/sgsn_libgtp.c b/src/gprs/sgsn_libgtp.c
new file mode 100644
index 00000000..7b10a45a
--- /dev/null
+++ b/src/gprs/sgsn_libgtp.c
@@ -0,0 +1,610 @@
+/* GPRS SGSN integration with libgtp of OpenGGSN */
+/* libgtp implements the GPRS Tunelling Protocol GTP per TS 09.60 / 29.060 */
+
+/* (C) 2010 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/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/gsm_04_08_gprs.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+
+#include <gtp.h>
+#include <pdp.h>
+
+const struct value_string gtp_cause_strs[] = {
+ { GTPCAUSE_REQ_IMSI, "Request IMSI" },
+ { GTPCAUSE_REQ_IMEI, "Request IMEI" },
+ { GTPCAUSE_REQ_IMSI_IMEI, "Request IMSI and IMEI" },
+ { GTPCAUSE_NO_ID_NEEDED, "No identity needed" },
+ { GTPCAUSE_MS_REFUSES_X, "MS refuses" },
+ { GTPCAUSE_MS_NOT_RESP_X, "MS is not GPRS responding" },
+ { GTPCAUSE_ACC_REQ, "Request accepted" },
+ { GTPCAUSE_NON_EXIST, "Non-existent" },
+ { GTPCAUSE_INVALID_MESSAGE, "Invalid message format" },
+ { GTPCAUSE_IMSI_NOT_KNOWN, "IMSI not known" },
+ { GTPCAUSE_MS_DETACHED, "MS is GPRS detached" },
+ { GTPCAUSE_MS_NOT_RESP, "MS is not GPRS responding" },
+ { GTPCAUSE_MS_REFUSES, "MS refuses" },
+ { GTPCAUSE_NO_RESOURCES, "No resources available" },
+ { GTPCAUSE_NOT_SUPPORTED, "Service not supported" },
+ { GTPCAUSE_MAN_IE_INCORRECT, "Mandatory IE incorrect" },
+ { GTPCAUSE_MAN_IE_MISSING, "Mandatory IE missing" },
+ { GTPCAUSE_OPT_IE_INCORRECT, "Optional IE incorrect" },
+ { GTPCAUSE_SYS_FAIL, "System failure" },
+ { GTPCAUSE_ROAMING_REST, "Roaming restrictions" },
+ { GTPCAUSE_PTIMSI_MISMATCH, "P-TMSI Signature mismatch" },
+ { GTPCAUSE_CONN_SUSP, "GPRS connection suspended" },
+ { GTPCAUSE_AUTH_FAIL, "Authentication failure" },
+ { GTPCAUSE_USER_AUTH_FAIL, "User authentication failed" },
+ { GTPCAUSE_CONTEXT_NOT_FOUND, "Context not found" },
+ { GTPCAUSE_ADDR_OCCUPIED, "All dynamic PDP addresses occupied" },
+ { GTPCAUSE_NO_MEMORY, "No memory is available" },
+ { GTPCAUSE_RELOC_FAIL, "Relocation failure" },
+ { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, "Unknown mandatory ext. header" },
+ { GTPCAUSE_SEM_ERR_TFT, "Semantic error in TFT operation" },
+ { GTPCAUSE_SYN_ERR_TFT, "Syntactic error in TFT operation" },
+ { GTPCAUSE_SEM_ERR_FILTER, "Semantic errors in packet filter" },
+ { GTPCAUSE_SYN_ERR_FILTER, "Syntactic errors in packet filter" },
+ { GTPCAUSE_MISSING_APN, "Missing or unknown APN" },
+ { GTPCAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" },
+ { 0, NULL }
+};
+
+/* Generate the GTP IMSI IE according to 09.60 Section 7.9.2 */
+static uint64_t imsi_str2gtp(char *str)
+{
+ uint64_t imsi64 = 0;
+ unsigned int n;
+ unsigned int imsi_len = strlen(str);
+
+ if (imsi_len > 16) {
+ LOGP(DGPRS, LOGL_NOTICE, "IMSI length > 16 not supported!\n");
+ return 0;
+ }
+
+ for (n = 0; n < 16; n++) {
+ uint64_t val;
+ if (n < imsi_len)
+ val = (str[n]-'0') & 0xf;
+ else
+ val = 0xf;
+ imsi64 |= (val << (n*4));
+ }
+ return imsi64;
+}
+
+/* generate a PDP context based on the IE's from the 04.08 message,
+ * and send the GTP create pdp context request to the GGSN */
+struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn,
+ struct sgsn_mm_ctx *mmctx,
+ uint16_t nsapi,
+ struct tlv_parsed *tp)
+{
+ struct sgsn_pdp_ctx *pctx;
+ struct pdp_t *pdp;
+ uint64_t imsi_ui64;
+ int rc;
+
+ LOGP(DGPRS, LOGL_ERROR, "Create PDP Context\n");
+ pctx = sgsn_pdp_ctx_alloc(mmctx, nsapi);
+ if (!pctx) {
+ LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n");
+ return NULL;
+ }
+
+ imsi_ui64 = imsi_str2gtp(mmctx->imsi);
+
+ rc = pdp_newpdp(&pdp, imsi_ui64, nsapi, NULL);
+ if (rc) {
+ LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n");
+ return NULL;
+ }
+ pdp->priv = pctx;
+ pctx->lib = pdp;
+ pctx->ggsn = ggsn;
+
+ //pdp->peer = /* sockaddr_in of GGSN (receive) */
+ //pdp->ipif = /* not used by library */
+ pdp->version = ggsn->gtp_version;
+ pdp->hisaddr0 = ggsn->remote_addr;
+ pdp->hisaddr1 = ggsn->remote_addr;
+ //pdp->cch_pdp = 512; /* Charging Flat Rate */
+
+ /* MS provided APN, subscription not verified */
+ pdp->selmode = 0x01;
+
+ /* IMSI, TEID/TEIC, FLLU/FLLC, TID, NSAPI set in pdp_newpdp */
+
+ /* FIXME: MSISDN in BCD format from mmctx */
+ //pdp->msisdn.l/.v
+
+ /* End User Address from GMM requested PDP address */
+ pdp->eua.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_PDP_ADDR);
+ if (pdp->eua.l > sizeof(pdp->eua.v))
+ pdp->eua.l = sizeof(pdp->eua.v);
+ memcpy(pdp->eua.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_PDP_ADDR),
+ pdp->eua.l);
+ /* Highest 4 bits of first byte need to be set to 1, otherwise
+ * the IE is identical with the 04.08 PDP Address IE */
+ pdp->eua.v[0] |= 0xf0;
+
+ /* APN name from GMM */
+ pdp->apn_use.l = TLVP_LEN(tp, GSM48_IE_GSM_APN);
+ if (pdp->apn_use.l > sizeof(pdp->apn_use.v))
+ pdp->apn_use.l = sizeof(pdp->apn_use.v);
+ memcpy(pdp->apn_use.v, TLVP_VAL(tp, GSM48_IE_GSM_APN),
+ pdp->apn_use.l);
+
+ /* Protocol Configuration Options from GMM */
+ pdp->pco_req.l = TLVP_LEN(tp, GSM48_IE_GSM_PROTO_CONF_OPT);
+ if (pdp->pco_req.l > sizeof(pdp->pco_req.v))
+ pdp->pco_req.l = sizeof(pdp->pco_req.v);
+ memcpy(pdp->pco_req.v, TLVP_VAL(tp, GSM48_IE_GSM_PROTO_CONF_OPT),
+ pdp->pco_req.l);
+
+ /* QoS options from GMM */
+ pdp->qos_req.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_QOS);
+ if (pdp->qos_req.l > sizeof(pdp->qos_req.v))
+ pdp->qos_req.l = sizeof(pdp->qos_req.v);
+ memcpy(pdp->qos_req.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_QOS),
+ pdp->qos_req.l);
+
+ /* SGSN address for control plane */
+ pdp->gsnlc.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr);
+ memcpy(pdp->gsnlc.v, &sgsn->cfg.gtp_listenaddr.sin_addr,
+ sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+
+ /* SGSN address for user plane */
+ pdp->gsnlu.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr);
+ memcpy(pdp->gsnlu.v, &sgsn->cfg.gtp_listenaddr.sin_addr,
+ sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+
+ /* change pdp state to 'requested' */
+ pctx->state = PDP_STATE_CR_REQ;
+
+ rc = gtp_create_context_req(ggsn->gsn, pdp, pctx);
+ /* FIXME */
+
+ return pctx;
+}
+
+/* SGSN wants to delete a PDP context */
+int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx)
+{
+ LOGP(DGPRS, LOGL_ERROR, "Delete PDP Context\n");
+
+ /* FIXME: decide if we need teardown or not ! */
+ return gtp_delete_context_req(pctx->ggsn->gsn, pctx->lib, pctx, 1);
+}
+
+struct cause_map {
+ uint8_t cause_in;
+ uint8_t cause_out;
+};
+
+static uint8_t cause_map(const struct cause_map *map, uint8_t in, uint8_t deflt)
+{
+ const struct cause_map *m;
+
+ for (m = map; m->cause_in && m->cause_out; m++) {
+ if (m->cause_in == in)
+ return m->cause_out;
+ }
+ return deflt;
+}
+
+/* how do we map from gtp cause to SM cause */
+static const struct cause_map gtp2sm_cause_map[] = {
+ { GTPCAUSE_NO_RESOURCES, GSM_CAUSE_INSUFF_RSRC },
+ { GTPCAUSE_NOT_SUPPORTED, GSM_CAUSE_SERV_OPT_NOTSUPP },
+ { GTPCAUSE_MAN_IE_INCORRECT, GSM_CAUSE_INV_MAND_INFO },
+ { GTPCAUSE_MAN_IE_MISSING, GSM_CAUSE_INV_MAND_INFO },
+ { GTPCAUSE_OPT_IE_INCORRECT, GSM_CAUSE_PROTO_ERR_UNSPEC },
+ { GTPCAUSE_SYS_FAIL, GSM_CAUSE_NET_FAIL },
+ { GTPCAUSE_ROAMING_REST, GSM_CAUSE_REQ_SERV_OPT_NOTSUB },
+ { GTPCAUSE_PTIMSI_MISMATCH, GSM_CAUSE_PROTO_ERR_UNSPEC },
+ { GTPCAUSE_CONN_SUSP, GSM_CAUSE_PROTO_ERR_UNSPEC },
+ { GTPCAUSE_AUTH_FAIL, GSM_CAUSE_AUTH_FAILED },
+ { GTPCAUSE_USER_AUTH_FAIL, GSM_CAUSE_ACT_REJ_GGSN },
+ { GTPCAUSE_CONTEXT_NOT_FOUND, GSM_CAUSE_PROTO_ERR_UNSPEC },
+ { GTPCAUSE_ADDR_OCCUPIED, GSM_CAUSE_INSUFF_RSRC },
+ { GTPCAUSE_NO_MEMORY, GSM_CAUSE_INSUFF_RSRC },
+ { GTPCAUSE_RELOC_FAIL, GSM_CAUSE_PROTO_ERR_UNSPEC },
+ { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, GSM_CAUSE_PROTO_ERR_UNSPEC },
+ { GTPCAUSE_MISSING_APN, GSM_CAUSE_MISSING_APN },
+ { GTPCAUSE_UNKNOWN_PDP, GSM_CAUSE_UNKNOWN_PDP },
+ { 0, 0 }
+};
+
+/* The GGSN has confirmed the creation of a PDP Context */
+static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
+{
+ struct sgsn_pdp_ctx *pctx = cbp;
+ uint8_t reject_cause;
+ int rc;
+
+ DEBUGP(DGPRS, "Received CREATE PDP CTX CONF, cause=%d(%s)\n",
+ cause, get_value_string(gtp_cause_strs, cause));
+
+ /* Check for cause value if it was really successful */
+ if (cause < 0) {
+ LOGP(DGPRS, LOGL_NOTICE, "Create PDP ctx req timed out\n");
+ if (pdp && pdp->version == 1) {
+ pdp->version = 0;
+ gtp_create_context_req(sgsn->gsn, pdp, cbp);
+ return 0;
+ } else {
+ reject_cause = GSM_CAUSE_NET_FAIL;
+ goto reject;
+ }
+ }
+
+ /* Check for cause value if it was really successful */
+ if (cause != GTPCAUSE_ACC_REQ) {
+ reject_cause = cause_map(gtp2sm_cause_map, cause,
+ GSM_CAUSE_ACT_REJ_GGSN);
+ goto reject;
+ }
+
+ /* Activate the SNDCP layer */
+ sndcp_sm_activate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi);
+
+ /* Send PDP CTX ACT to MS */
+ return gsm48_tx_gsm_act_pdp_acc(pctx);
+
+reject:
+ pctx->state = PDP_STATE_NONE;
+ if (pdp)
+ pdp_freepdp(pdp);
+ /* Send PDP CTX ACT REJ to MS */
+ rc = gsm48_tx_gsm_act_pdp_rej(pctx->mm, pctx->ti, reject_cause,
+ 0, NULL);
+ sgsn_pdp_ctx_free(pctx);
+
+ return EOF;
+}
+
+/* Confirmation of a PDP Context Delete */
+static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
+{
+ struct sgsn_pdp_ctx *pctx = cbp;
+ int rc;
+
+ DEBUGP(DGPRS, "Received DELETE PDP CTX CONF, cause=%d(%s)\n",
+ cause, get_value_string(gtp_cause_strs, cause));
+
+ /* Deactivate the SNDCP layer */
+ sndcp_sm_deactivate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi);
+
+ /* Confirm deactivation of PDP context to MS */
+ rc = gsm48_tx_gsm_deact_pdp_acc(pctx);
+
+ sgsn_pdp_ctx_free(pctx);
+
+ return rc;
+}
+
+/* Confirmation of an GTP ECHO request */
+static int echo_conf(struct pdp_t *pdp, void *cbp, int recovery)
+{
+ if (recovery < 0) {
+ DEBUGP(DGPRS, "GTP Echo Request timed out\n");
+ /* FIXME: if version == 1, retry with version 0 */
+ } else {
+ DEBUGP(DGPRS, "GTP Rx Echo Response\n");
+ }
+ return 0;
+}
+
+/* Any message received by GGSN contains a recovery IE */
+static int cb_recovery(struct sockaddr_in *peer, uint8_t recovery)
+{
+ struct sgsn_ggsn_ctx *ggsn;
+
+ ggsn = sgsn_ggsn_ctx_by_addr(&peer->sin_addr);
+ if (!ggsn) {
+ DEBUGP(DGPRS, "Received Recovery IE for unknown GGSN\n");
+ return -EINVAL;
+ }
+
+ if (ggsn->remote_restart_ctr == -1) {
+ /* First received ECHO RESPONSE, note the restart ctr */
+ ggsn->remote_restart_ctr = recovery;
+ } else if (ggsn->remote_restart_ctr != recovery) {
+ /* counter has changed (GGSN restart): release all PDP */
+ LOGP(DGPRS, LOGL_NOTICE, "GGSN recovery (%u->%u), "
+ "releasing all PDP contexts\n",
+ ggsn->remote_restart_ctr, recovery);
+ ggsn->remote_restart_ctr = recovery;
+ drop_all_pdp_for_ggsn(ggsn);
+ }
+ return 0;
+}
+
+/* libgtp callback for confirmations */
+static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp)
+{
+ DEBUGP(DGPRS, "libgtp cb_conf(type=%d, cause=%d, pdp=%p, cbp=%p)\n",
+ type, cause, pdp, cbp);
+
+ if (cause == EOF)
+ LOGP(DGPRS, LOGL_ERROR, "libgtp EOF (type=%u, pdp=%p, cbp=%p)\n",
+ type, pdp, cbp);
+
+ switch (type) {
+ case GTP_ECHO_REQ:
+ /* libgtp hands us the RECOVERY number instead of a cause */
+ return echo_conf(pdp, cbp, cause);
+ case GTP_CREATE_PDP_REQ:
+ return create_pdp_conf(pdp, cbp, cause);
+ case GTP_DELETE_PDP_REQ:
+ return delete_pdp_conf(pdp, cbp, cause);
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Called whenever a PDP context is deleted for any reason */
+static int cb_delete_context(struct pdp_t *pdp)
+{
+ LOGP(DGPRS, LOGL_INFO, "PDP Context was deleted\n");
+ return 0;
+}
+
+/* Called when we receive a Version Not Supported message */
+static int cb_unsup_ind(struct sockaddr_in *peer)
+{
+ LOGP(DGPRS, LOGL_INFO, "GTP Version not supported Indication "
+ "from %s:%u\n", inet_ntoa(peer->sin_addr),
+ ntohs(peer->sin_port));
+ return 0;
+}
+
+/* Called when we receive a Supported Ext Headers Notification */
+static int cb_extheader_ind(struct sockaddr_in *peer)
+{
+ LOGP(DGPRS, LOGL_INFO, "GTP Supported Ext Headers Noficiation "
+ "from %s:%u\n", inet_ntoa(peer->sin_addr),
+ ntohs(peer->sin_port));
+ return 0;
+}
+
+/* Called whenever we recive a DATA packet */
+static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len)
+{
+ struct bssgp_paging_info pinfo;
+ struct sgsn_pdp_ctx *pdp;
+ struct sgsn_mm_ctx *mm;
+ struct msgb *msg;
+ uint8_t *ud;
+ int rc;
+
+ DEBUGP(DGPRS, "GTP DATA IND from GGSN, length=%u\n", len);
+
+ pdp = lib->priv;
+ if (!pdp) {
+ DEBUGP(DGPRS, "GTP DATA IND from GGSN for unknown PDP\n");
+ return -EIO;
+ }
+ mm = pdp->mm;
+
+ msg = msgb_alloc_headroom(len+256, 128, "GTP->SNDCP");
+ ud = msgb_put(msg, len);
+ memcpy(ud, packet, len);
+
+ msgb_tlli(msg) = mm->tlli;
+ msgb_bvci(msg) = mm->bvci;
+ msgb_nsei(msg) = mm->nsei;
+
+ switch (mm->mm_state) {
+ case GMM_REGISTERED_SUSPENDED:
+ /* initiate PS PAGING procedure */
+ memset(&pinfo, 0, sizeof(pinfo));
+ pinfo.mode = BSSGP_PAGING_PS;
+ pinfo.scope = BSSGP_PAGING_BVCI;
+ pinfo.bvci = mm->bvci;
+ pinfo.imsi = mm->imsi;
+ pinfo.ptmsi = &mm->p_tmsi;
+ pinfo.drx_params = mm->drx_parms;
+ pinfo.qos[0] = 0; // FIXME
+ rc = gprs_bssgp_tx_paging(mm->nsei, 0, &pinfo);
+ rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PAGING_PS]);
+ /* FIXME: queue the packet we received from GTP */
+ break;
+ case GMM_REGISTERED_NORMAL:
+ break;
+ default:
+ LOGP(DGPRS, LOGL_ERROR, "GTP DATA IND for TLLI %08X in state "
+ "%u\n", mm->tlli, mm->mm_state);
+ msgb_free(msg);
+ return -1;
+ }
+
+ rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_OUT]);
+ rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_OUT], len);
+ rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_UDATA_OUT]);
+ rate_ctr_add(&mm->ctrg->ctr[GMM_CTR_BYTES_UDATA_OUT], len);
+
+ return sndcp_unitdata_req(msg, &mm->llme->lle[pdp->sapi],
+ pdp->nsapi, mm);
+}
+
+/* Called by SNDCP when it has received/re-assembled a N-PDU */
+int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi,
+ struct msgb *msg, uint32_t npdu_len, uint8_t *npdu)
+{
+ struct sgsn_mm_ctx *mmctx;
+ struct sgsn_pdp_ctx *pdp;
+
+ /* look-up the MM context for this message */
+ mmctx = sgsn_mm_ctx_by_tlli(tlli, ra_id);
+ if (!mmctx) {
+ LOGP(DGPRS, LOGL_ERROR,
+ "Cannot find MM CTX for TLLI %08x\n", tlli);
+ return -EIO;
+ }
+ /* look-up the PDP context for this message */
+ pdp = sgsn_pdp_ctx_by_nsapi(mmctx, nsapi);
+ if (!pdp) {
+ LOGP(DGPRS, LOGL_ERROR, "Cannot find PDP CTX for "
+ "TLLI=%08x, NSAPI=%u\n", tlli, nsapi);
+ return -EIO;
+ }
+ if (!pdp->lib) {
+ LOGP(DGPRS, LOGL_ERROR, "PDP CTX without libgtp\n");
+ return -EIO;
+ }
+
+ rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_IN]);
+ rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_IN], npdu_len);
+ rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_UDATA_IN]);
+ rate_ctr_add(&mmctx->ctrg->ctr[GMM_CTR_BYTES_UDATA_IN], npdu_len);
+
+ return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len);
+
+ return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len);
+}
+
+/* libgtp select loop integration */
+static int sgsn_gtp_fd_cb(struct bsc_fd *fd, unsigned int what)
+{
+ struct sgsn_instance *sgi = fd->data;
+ int rc;
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ switch (fd->priv_nr) {
+ case 0:
+ rc = gtp_decaps0(sgi->gsn);
+ break;
+ case 1:
+ rc = gtp_decaps1c(sgi->gsn);
+ break;
+ case 2:
+ rc = gtp_decaps1u(sgi->gsn);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+ return rc;
+}
+
+static void sgsn_gtp_tmr_start(struct sgsn_instance *sgi)
+{
+ struct timeval next;
+
+ /* Retrieve next retransmission as struct timeval */
+ gtp_retranstimeout(sgi->gsn, &next);
+
+ /* re-schedule the timer */
+ bsc_schedule_timer(&sgi->gtp_timer, next.tv_sec, next.tv_usec/1000);
+}
+
+/* timer callback for libgtp retransmissions and ping */
+static void sgsn_gtp_tmr_cb(void *data)
+{
+ struct sgsn_instance *sgi = data;
+
+ /* Do all the retransmissions as needed */
+ gtp_retrans(sgi->gsn);
+
+ sgsn_gtp_tmr_start(sgi);
+}
+
+int sgsn_gtp_init(struct sgsn_instance *sgi)
+{
+ int rc;
+ struct gsn_t *gsn;
+
+ rc = gtp_new(&sgi->gsn, sgi->cfg.gtp_statedir,
+ &sgi->cfg.gtp_listenaddr.sin_addr, GTP_MODE_SGSN);
+ if (rc) {
+ LOGP(DGPRS, LOGL_ERROR, "Failed to create GTP: %d\n", rc);
+ return rc;
+ }
+ gsn = sgi->gsn;
+
+ sgi->gtp_fd0.fd = gsn->fd0;
+ sgi->gtp_fd0.priv_nr = 0;
+ sgi->gtp_fd0.data = sgi;
+ sgi->gtp_fd0.when = BSC_FD_READ;
+ sgi->gtp_fd0.cb = sgsn_gtp_fd_cb;
+ rc = bsc_register_fd(&sgi->gtp_fd0);
+ if (rc < 0)
+ return rc;
+
+ sgi->gtp_fd1c.fd = gsn->fd1c;
+ sgi->gtp_fd1c.priv_nr = 1;
+ sgi->gtp_fd1c.data = sgi;
+ sgi->gtp_fd1c.when = BSC_FD_READ;
+ sgi->gtp_fd1c.cb = sgsn_gtp_fd_cb;
+ bsc_register_fd(&sgi->gtp_fd1c);
+ if (rc < 0)
+ return rc;
+
+ sgi->gtp_fd1u.fd = gsn->fd1u;
+ sgi->gtp_fd1u.priv_nr = 2;
+ sgi->gtp_fd1u.data = sgi;
+ sgi->gtp_fd1u.when = BSC_FD_READ;
+ sgi->gtp_fd1u.cb = sgsn_gtp_fd_cb;
+ bsc_register_fd(&sgi->gtp_fd1u);
+ if (rc < 0)
+ return rc;
+
+ /* Start GTP re-transmission timer */
+ sgi->gtp_timer.cb = sgsn_gtp_tmr_cb;
+ sgi->gtp_timer.data = sgi;
+ sgsn_gtp_tmr_start(sgi);
+
+ /* Register callbackcs with libgtp */
+ gtp_set_cb_delete_context(gsn, cb_delete_context);
+ gtp_set_cb_conf(gsn, cb_conf);
+ gtp_set_cb_recovery(gsn, cb_recovery);
+ gtp_set_cb_data_ind(gsn, cb_data_ind);
+ gtp_set_cb_unsup_ind(gsn, cb_unsup_ind);
+ gtp_set_cb_extheader_ind(gsn, cb_extheader_ind);
+
+ return 0;
+}
diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c
new file mode 100644
index 00000000..c59265fb
--- /dev/null
+++ b/src/gprs/sgsn_main.c
@@ -0,0 +1,288 @@
+/* GPRS SGSN Implementation */
+
+/* (C) 2010 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/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <osmocore/logging.h>
+#include <osmocore/process.h>
+
+#include <osmocom/vty/telnet_interface.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+
+#include <gtp.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+struct gprs_ns_inst *sgsn_nsi;
+static struct log_target *stderr_target;
+static int daemonize = 0;
+const char *openbsc_copyright =
+ "Copyright (C) 2010 Harald Welte and On-Waves\r\n"
+ "License AGPLv3+: GNU AGPL version 2 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 sgsn_instance sgsn_inst = {
+ .config_file = "osmo_sgsn.cfg",
+ .cfg = {
+ .gtp_statedir = "./",
+ },
+};
+struct sgsn_instance *sgsn = &sgsn_inst;
+
+/* call-back function for the NS protocol */
+static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+ struct msgb *msg, u_int16_t bvci)
+{
+ int rc = 0;
+
+ switch (event) {
+ case GPRS_NS_EVT_UNIT_DATA:
+ /* hand the message into the BSSGP implementation */
+ rc = gprs_bssgp_rcvmsg(msg);
+ break;
+ default:
+ LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+ if (msg)
+ talloc_free(msg);
+ rc = -EIO;
+ break;
+ }
+ return rc;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+ sleep(1);
+ 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:
+ talloc_report_full(tall_vty_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+/* NSI that BSSGP uses when transmitting on NS */
+extern struct gprs_ns_inst *bssgp_nsi;
+extern void *tall_msgb_ctx;
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoSGSN",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+static void print_help(void)
+{
+ printf("Some useful help...\n");
+ printf(" -h --help\tthis text\n");
+ printf(" -D --daemonize\tFork the process into a background daemon\n");
+ printf(" -d option --debug\tenable Debugging\n");
+ printf(" -s --disable-color\n");
+ printf(" -c --config-file\tThe config file to use\n");
+ printf(" -e --log-level number\tSet a global log level\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'},
+ {"log-level", 1, 0, 'e'},
+ {NULL, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hd:Dc:sTe:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ //print_usage();
+ print_help();
+ exit(0);
+ case 's':
+ log_set_use_color(stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ sgsn_inst.config_file = strdup(optarg);
+ break;
+ case 'T':
+ log_set_print_timestamp(stderr_target, 1);
+ break;
+ case 'e':
+ log_set_log_level(stderr_target, atoi(optarg));
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct gsm_network dummy_network;
+ struct sockaddr_in sin;
+ int rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 0, "osmo_sgsn");
+ tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ signal(SIGPIPE, SIG_IGN);
+
+ log_init(&log_info);
+ stderr_target = log_target_create_stderr();
+ log_add_target(stderr_target);
+ log_set_all_filter(stderr_target, 1);
+
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds();
+ sgsn_vty_init();
+
+ handle_options(argc, argv);
+
+ rate_ctr_init(tall_bsc_ctx);
+ rc = telnet_init(tall_bsc_ctx, &dummy_network, 4245);
+ if (rc < 0)
+ exit(1);
+
+ sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb);
+ if (!sgsn_nsi) {
+ LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+ exit(1);
+ }
+ bssgp_nsi = sgsn_inst.cfg.nsi = sgsn_nsi;
+
+ gprs_llc_init("/usr/local/lib/osmocom/crypt/");
+
+ gprs_ns_vty_init(bssgp_nsi);
+ gprs_bssgp_vty_init();
+ gprs_llc_vty_init();
+ gprs_sndcp_vty_init();
+ /* FIXME: register signal handler for SS_NS */
+
+ rc = sgsn_parse_config(sgsn_inst.config_file, &sgsn_inst.cfg);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+ exit(2);
+ }
+
+ rc = sgsn_gtp_init(&sgsn_inst);
+ if (rc) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on GTP socket\n");
+ exit(2);
+ }
+
+ rc = gprs_ns_nsip_listen(sgsn_nsi);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n");
+ exit(2);
+ }
+
+ rc = gprs_ns_frgre_listen(sgsn_nsi);
+ if (rc < 0) {
+ LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE "
+ "socket. Do you have CAP_NET_RAW?\n");
+ exit(2);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ rc = bsc_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
diff --git a/src/gprs/sgsn_vty.c b/src/gprs/sgsn_vty.c
new file mode 100644
index 00000000..74669ffb
--- /dev/null
+++ b/src/gprs/sgsn_vty.c
@@ -0,0 +1,357 @@
+/*
+ * (C) 2010 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/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+#include <osmocore/rate_ctr.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_04_08_gprs.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+#include <pdp.h>
+
+static struct sgsn_config *g_cfg = NULL;
+
+
+#define GSM48_MAX_APN_LEN 102 /* 10.5.6.1 */
+static char *gprs_apn2str(uint8_t *apn, unsigned int len)
+{
+ static char apnbuf[GSM48_MAX_APN_LEN+1];
+ unsigned int i;
+
+ if (!apn)
+ return "";
+
+ if (len > sizeof(apnbuf)-1)
+ len = sizeof(apnbuf)-1;
+
+ memcpy(apnbuf, apn, len);
+ apnbuf[len] = '\0';
+
+ /* replace the domain name step sizes with dots */
+ while (i < len) {
+ unsigned int step = apnbuf[i];
+ apnbuf[i] = '.';
+ i += step+1;
+ }
+
+ return apnbuf+1;
+}
+
+static char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len)
+{
+ static char str[INET6_ADDRSTRLEN + 10];
+
+ if (!pdpa || len < 2)
+ return "none";
+
+ switch (pdpa[0] & 0x0f) {
+ case PDP_TYPE_ORG_IETF:
+ switch (pdpa[1]) {
+ case PDP_TYPE_N_IETF_IPv4:
+ if (len < 2 + 4)
+ break;
+ strcpy(str, "IPv4 ");
+ inet_ntop(AF_INET, pdpa+2, str+5, sizeof(str)-5);
+ return str;
+ case PDP_TYPE_N_IETF_IPv6:
+ if (len < 2 + 8)
+ break;
+ strcpy(str, "IPv6 ");
+ inet_ntop(AF_INET6, pdpa+2, str+5, sizeof(str)-5);
+ return str;
+ default:
+ break;
+ }
+ break;
+ case PDP_TYPE_ORG_ETSI:
+ if (pdpa[1] == PDP_TYPE_N_ETSI_PPP)
+ return "PPP";
+ break;
+ default:
+ break;
+ }
+
+ return "invalid";
+}
+
+static struct cmd_node sgsn_node = {
+ SGSN_NODE,
+ "%s(sgsn)#",
+ 1,
+};
+
+static int config_write_sgsn(struct vty *vty)
+{
+ struct sgsn_ggsn_ctx *gctx;
+
+ vty_out(vty, "sgsn%s", VTY_NEWLINE);
+
+ vty_out(vty, " gtp local-ip %s%s",
+ inet_ntoa(g_cfg->gtp_listenaddr.sin_addr), VTY_NEWLINE);
+
+ llist_for_each_entry(gctx, &sgsn_ggsn_ctxts, list) {
+ vty_out(vty, " ggsn %u remote-ip %s%s", gctx->id,
+ inet_ntoa(gctx->remote_addr), VTY_NEWLINE);
+ vty_out(vty, " ggsn %u gtp-version %u%s", gctx->id,
+ gctx->gtp_version, VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define SGSN_STR "Configure the SGSN"
+
+DEFUN(cfg_sgsn, cfg_sgsn_cmd,
+ "sgsn",
+ SGSN_STR)
+{
+ vty->node = SGSN_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sgsn_bind_addr, cfg_sgsn_bind_addr_cmd,
+ "gtp local-ip A.B.C.D",
+ "GTP Parameters\n"
+ "Set the IP address for the local GTP bind\n")
+{
+ inet_aton(argv[0], &g_cfg->gtp_listenaddr.sin_addr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ggsn_remote_ip, cfg_ggsn_remote_ip_cmd,
+ "ggsn <0-255> remote-ip A.B.C.D",
+ "")
+{
+ uint32_t id = atoi(argv[0]);
+ struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id);
+
+ inet_aton(argv[1], &ggc->remote_addr);
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_ggsn_remote_port, cfg_ggsn_remote_port_cmd,
+ "ggsn <0-255> remote-port <0-65535>",
+ "")
+{
+ uint32_t id = atoi(argv[0]);
+ struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id);
+ uint16_t port = atoi(argv[1]);
+
+}
+#endif
+
+DEFUN(cfg_ggsn_gtp_version, cfg_ggsn_gtp_version_cmd,
+ "ggsn <0-255> gtp-version (0|1)",
+ "")
+{
+ uint32_t id = atoi(argv[0]);
+ struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id);
+
+ if (atoi(argv[1]))
+ ggc->gtp_version = 1;
+ else
+ ggc->gtp_version = 0;
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_apn_ggsn, cfg_apn_ggsn_cmd,
+ "apn APNAME ggsn <0-255>",
+ "")
+{
+ struct apn_ctx **
+}
+#endif
+
+const struct value_string gprs_mm_st_strs[] = {
+ { GMM_DEREGISTERED, "DEREGISTERED" },
+ { GMM_COMMON_PROC_INIT, "COMMON PROCEDURE (INIT)" },
+ { GMM_REGISTERED_NORMAL, "REGISTERED (NORMAL)" },
+ { GMM_REGISTERED_SUSPENDED, "REGISTERED (SUSPENDED)" },
+ { GMM_DEREGISTERED_INIT, "DEREGISTERED (INIT)" },
+ { 0, NULL }
+};
+
+static void vty_dump_pdp(struct vty *vty, const char *pfx,
+ struct sgsn_pdp_ctx *pdp)
+{
+ vty_out(vty, "%sPDP Context IMSI: %s, SAPI: %u, NSAPI: %u%s",
+ pfx, pdp->mm->imsi, pdp->sapi, pdp->nsapi, VTY_NEWLINE);
+ vty_out(vty, "%s APN: %s%s", pfx,
+ gprs_apn2str(pdp->lib->apn_use.v, pdp->lib->apn_use.l),
+ VTY_NEWLINE);
+ vty_out(vty, "%s PDP Address: %s%s", pfx,
+ gprs_pdpaddr2str(pdp->lib->eua.v, pdp->lib->eua.l),
+ VTY_NEWLINE);
+ vty_out_rate_ctr_group(vty, " ", pdp->ctrg);
+}
+
+static void vty_dump_mmctx(struct vty *vty, const char *pfx,
+ struct sgsn_mm_ctx *mm, int pdp)
+{
+ vty_out(vty, "%sMM Context for IMSI %s, IMEI %s, P-TMSI %08x%s",
+ pfx, mm->imsi, mm->imei, mm->p_tmsi, VTY_NEWLINE);
+ vty_out(vty, "%s MSISDN: %s, TLLI: %08x%s", pfx, mm->msisdn,
+ mm->tlli, VTY_NEWLINE);
+ vty_out(vty, "%s MM State: %s, Routeing Area: %u-%u-%u-%u, "
+ "Cell ID: %u%s", pfx,
+ get_value_string(gprs_mm_st_strs, mm->mm_state),
+ mm->ra.mcc, mm->ra.mnc, mm->ra.lac, mm->ra.rac,
+ mm->cell_id, VTY_NEWLINE);
+
+ vty_out_rate_ctr_group(vty, " ", mm->ctrg);
+
+ if (pdp) {
+ struct sgsn_pdp_ctx *pdp;
+
+ llist_for_each_entry(pdp, &mm->pdp_list, list)
+ vty_dump_pdp(vty, " ", pdp);
+ }
+}
+
+DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
+ SHOW_STR "Display information about the SGSN")
+{
+ /* FIXME: statistics */
+ return CMD_SUCCESS;
+}
+
+#define MMCTX_STR "MM Context\n"
+#define INCLUDE_PDP_STR "Include PDP Context Information\n"
+
+#if 0
+DEFUN(show_mmctx_tlli, show_mmctx_tlli_cmd,
+ "show mm-context tlli HEX [pdp]",
+ SHOW_STR MMCTX_STR "Identify by TLLI\n" "TLLI\n" INCLUDE_PDP_STR)
+{
+ uint32_t tlli;
+ struct sgsn_mm_ctx *mm;
+
+ tlli = strtoul(argv[0], NULL, 16);
+ mm = sgsn_mm_ctx_by_tlli(tlli);
+ if (!mm) {
+ vty_out(vty, "No MM context for TLLI %08x%s",
+ tlli, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0);
+ return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(swow_mmctx_imsi, show_mmctx_imsi_cmd,
+ "show mm-context imsi IMSI [pdp]",
+ SHOW_STR MMCTX_STR "Identify by IMSI\n" "IMSI of the MM Context\n"
+ INCLUDE_PDP_STR)
+{
+ struct sgsn_mm_ctx *mm;
+
+ mm = sgsn_mm_ctx_by_imsi(argv[0]);
+ if (!mm) {
+ vty_out(vty, "No MM context for IMSI %s%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(swow_mmctx_all, show_mmctx_all_cmd,
+ "show mm-context all [pdp]",
+ SHOW_STR MMCTX_STR "All MM Contexts\n" INCLUDE_PDP_STR)
+{
+ struct sgsn_mm_ctx *mm;
+
+ llist_for_each_entry(mm, &sgsn_mm_ctxts, list)
+ vty_dump_mmctx(vty, "", mm, argv[0] ? 1 : 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ggsn, show_ggsn_cmd,
+ "show ggsn",
+ "")
+{
+
+}
+
+DEFUN(show_pdpctx_all, show_pdpctx_all_cmd,
+ "show pdp-context all",
+ SHOW_STR "Display information on PDP Context\n")
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ llist_for_each_entry(pdp, &sgsn_pdp_ctxts, g_list)
+ vty_dump_pdp(vty, "", pdp);
+
+ return CMD_SUCCESS;
+}
+
+int sgsn_vty_init(void)
+{
+ install_element_ve(&show_sgsn_cmd);
+ //install_element_ve(&show_mmctx_tlli_cmd);
+ install_element_ve(&show_mmctx_imsi_cmd);
+ install_element_ve(&show_mmctx_all_cmd);
+ install_element_ve(&show_pdpctx_all_cmd);
+
+ install_element(CONFIG_NODE, &cfg_sgsn_cmd);
+ install_node(&sgsn_node, config_write_sgsn);
+ install_default(SGSN_NODE);
+ install_element(SGSN_NODE, &ournode_exit_cmd);
+ install_element(SGSN_NODE, &ournode_end_cmd);
+ install_element(SGSN_NODE, &cfg_sgsn_bind_addr_cmd);
+ install_element(SGSN_NODE, &cfg_ggsn_remote_ip_cmd);
+ //install_element(SGSN_NODE, &cfg_ggsn_remote_port_cmd);
+ install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd);
+
+ return 0;
+}
+
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg)
+{
+ int rc;
+
+ 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;
+ }
+
+ return 0;
+}
diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am
new file mode 100644
index 00000000..144cca1e
--- /dev/null
+++ b/src/ipaccess/Makefile.am
@@ -0,0 +1,21 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = ipaccess-find ipaccess-config ipaccess-proxy
+
+ipaccess_find_SOURCES = ipaccess-find.c
+
+ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c network_listen.c
+
+# FIXME: resolve the bogus dependencies patched around here:
+ipaccess_config_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libmsc/libmsc.a \
+ $(top_builddir)/src/libabis/libabis.a \
+ $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libtrau/libtrau.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ -ldl -ldbi $(LIBCRYPT)
+
+ipaccess_proxy_SOURCES = ipaccess-proxy.c
+ipaccess_proxy_LDADD = $(top_builddir)/src/libcommon/libcommon.a
diff --git a/src/ipaccess/Makefile.in b/src/ipaccess/Makefile.in
new file mode 100644
index 00000000..864fa316
--- /dev/null
+++ b/src/ipaccess/Makefile.in
@@ -0,0 +1,520 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = ipaccess-find$(EXEEXT) ipaccess-config$(EXEEXT) \
+ ipaccess-proxy$(EXEEXT)
+subdir = src/ipaccess
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_ipaccess_config_OBJECTS = ipaccess-config.$(OBJEXT) \
+ ipaccess-firmware.$(OBJEXT) network_listen.$(OBJEXT)
+ipaccess_config_OBJECTS = $(am_ipaccess_config_OBJECTS)
+ipaccess_config_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libmsc/libmsc.a \
+ $(top_builddir)/src/libabis/libabis.a \
+ $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libtrau/libtrau.a \
+ $(top_builddir)/src/libcommon/libcommon.a
+am_ipaccess_find_OBJECTS = ipaccess-find.$(OBJEXT)
+ipaccess_find_OBJECTS = $(am_ipaccess_find_OBJECTS)
+ipaccess_find_LDADD = $(LDADD)
+am_ipaccess_proxy_OBJECTS = ipaccess-proxy.$(OBJEXT)
+ipaccess_proxy_OBJECTS = $(am_ipaccess_proxy_OBJECTS)
+ipaccess_proxy_DEPENDENCIES = \
+ $(top_builddir)/src/libcommon/libcommon.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo " CC " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo " CCLD " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+SOURCES = $(ipaccess_config_SOURCES) $(ipaccess_find_SOURCES) \
+ $(ipaccess_proxy_SOURCES)
+DIST_SOURCES = $(ipaccess_config_SOURCES) $(ipaccess_find_SOURCES) \
+ $(ipaccess_proxy_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+ipaccess_find_SOURCES = ipaccess-find.c
+ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c network_listen.c
+
+# FIXME: resolve the bogus dependencies patched around here:
+ipaccess_config_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libmsc/libmsc.a \
+ $(top_builddir)/src/libabis/libabis.a \
+ $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libtrau/libtrau.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ -ldl -ldbi $(LIBCRYPT)
+
+ipaccess_proxy_SOURCES = ipaccess-proxy.c
+ipaccess_proxy_LDADD = $(top_builddir)/src/libcommon/libcommon.a
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/ipaccess/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/ipaccess/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p; \
+ then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+ipaccess-config$(EXEEXT): $(ipaccess_config_OBJECTS) $(ipaccess_config_DEPENDENCIES)
+ @rm -f ipaccess-config$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(ipaccess_config_OBJECTS) $(ipaccess_config_LDADD) $(LIBS)
+ipaccess-find$(EXEEXT): $(ipaccess_find_OBJECTS) $(ipaccess_find_DEPENDENCIES)
+ @rm -f ipaccess-find$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(ipaccess_find_OBJECTS) $(ipaccess_find_LDADD) $(LIBS)
+ipaccess-proxy$(EXEEXT): $(ipaccess_proxy_OBJECTS) $(ipaccess_proxy_DEPENDENCIES)
+ @rm -f ipaccess-proxy$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(ipaccess_proxy_OBJECTS) $(ipaccess_proxy_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-find.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-firmware.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-proxy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_listen.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic ctags distclean distclean-compile \
+ distclean-generic distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c
new file mode 100644
index 00000000..d02faea3
--- /dev/null
+++ b/src/ipaccess/ipaccess-config.c
@@ -0,0 +1,865 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 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/types.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocore/select.h>
+#include <osmocore/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/network_listen.h>
+#include <osmocore/talloc.h>
+
+static struct gsm_network *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 u_int16_t nv_flags;
+static u_int16_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;
+
+struct sw_load {
+ u_int8_t file_id[255];
+ u_int8_t file_id_len;
+
+ u_int8_t file_version[255];
+ u_int8_t file_version_len;
+};
+
+static void *tall_ctx_config = NULL;
+static struct sw_load *sw_load1 = NULL;
+static struct sw_load *sw_load2 = NULL;
+
+/*
+static u_int8_t prim_oml_attr[] = { 0x95, 0x00, 7, 0x88, 192, 168, 100, 11, 0x00, 0x00 };
+static u_int8_t unit_id_attr[] = { 0x91, 0x00, 9, '2', '3', '4', '2', '/' , '0', '/', '0', 0x00 };
+*/
+
+/*
+ * 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(u_int8_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(u_int8_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", 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:
+#if 0
+ /* re-start full process with CHAN_USAGE */
+ 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);
+#endif
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int nm_state_event(int evt, u_int8_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) {
+ msgb_v_put(msg, NM_ATT_SW_DESCR);
+ msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load1->file_id_len, sw_load1->file_id);
+ msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load1->file_version_len,
+ sw_load1->file_version);
+ }
+
+ if (sw_load2) {
+ msgb_v_put(msg, NM_ATT_SW_DESCR);
+ msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load2->file_id_len, sw_load2->file_id);
+ msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load2->file_version_len,
+ sw_load2->file_version);
+ }
+
+ /* 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 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, u_int8_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 sw_load *create_swload(struct sdp_header *header)
+{
+ struct sw_load *load;
+
+ load = talloc_zero(tall_ctx_config, struct sw_load);
+
+ strncpy((char *)load->file_id, header->firmware_info.sw_part, 20);
+ load->file_id_len = strlen(header->firmware_info.sw_part) + 1;
+
+ strncpy((char *)load->file_version, header->firmware_info.version, 20);
+ load->file_version_len = strlen(header->firmware_info.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");
+ 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");
+ 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\n");
+}
+
+static void print_help(void)
+{
+#if 0
+ printf("Commmands for reading from the BTS:\n");
+ printf(" -D --dump\t\t\tDump the BTS configuration\n");
+ printf("\n");
+#endif
+ printf("Commmands 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(" -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(" -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");
+}
+
+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;
+ struct log_target *stderr_target;
+
+ log_init(&log_info);
+ stderr_target = log_target_create_stderr();
+ log_add_target(stderr_target);
+ log_set_all_filter(stderr_target, 1);
+ log_set_log_level(stderr_target, 0);
+ log_parse_category_mask(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' },
+ { "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'},
+ { 0, 0, 0, 0 },
+ };
+
+ c = getopt_long(argc, argv, "u:o:i:g:rn:S:U:l:hs:d:f:wc", 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 '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(stderr_target, 0);
+ break;
+ case 'h':
+ print_usage();
+ print_help();
+ 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);
+ }
+
+ gsmnet = gsm_network_init(1, 1, NULL);
+ if (!gsmnet)
+ exit(1);
+
+ bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_NANOBTS, HARDCODED_TSC,
+ 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;
+
+ register_signal_handler(SS_NM, nm_sig_cb, NULL);
+ register_signal_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 = bsc_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
+
diff --git a/src/ipaccess/ipaccess-find.c b/src/ipaccess/ipaccess-find.c
new file mode 100644
index 00000000..bea4b77a
--- /dev/null
+++ b/src/ipaccess/ipaccess-find.c
@@ -0,0 +1,227 @@
+/* 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/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocore/select.h>
+#include <osmocore/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+
+static const char *idtag_names[] = {
+ [IPAC_IDTAG_SERNR] = "Serial Number",
+ [IPAC_IDTAG_UNITNAME] = "Unit Name",
+ [IPAC_IDTAG_LOCATION1] = "Location 1",
+ [IPAC_IDTAG_LOCATION2] = "Location 2",
+ [IPAC_IDTAG_EQUIPVERS] = "Equipment Version",
+ [IPAC_IDTAG_SWVERSION] = "Software Version",
+ [IPAC_IDTAG_IPADDR] = "IP Address",
+ [IPAC_IDTAG_MACADDR] = "MAC Address",
+ [IPAC_IDTAG_UNIT] = "Unit ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+ if (tag >= ARRAY_SIZE(idtag_names))
+ return "unknown";
+
+ return idtag_names[tag];
+}
+
+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) {
+ rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
+ strlen(ifname));
+ if (rc < 0)
+ goto err;
+ }
+
+ 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)
+{
+ u_int8_t t_len;
+ u_int8_t t_tag;
+ u_int8_t *cur = buf;
+
+ while (cur < buf + len) {
+ t_len = *cur++;
+ t_tag = *cur++;
+
+ printf("%s='%s' ", ipac_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 (0xfe) */
+ if (buf[2] != 0xfe)
+ return 0;
+
+ if (buf[4] != IPAC_MSGT_ID_RESP)
+ return 0;
+
+ return parse_response(buf+6, len-6);
+}
+
+static int bfd_cb(struct bsc_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 timer_list timer;
+
+static void timer_cb(void *_data)
+{
+ struct bsc_fd *bfd = _data;
+
+ bfd->when |= BSC_FD_WRITE;
+
+ bsc_schedule_timer(&timer, 5, 0);
+}
+
+int main(int argc, char **argv)
+{
+ struct bsc_fd bfd;
+ char *ifname;
+ int rc;
+
+ printf("ipaccess-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]);
+ }
+
+ 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);
+ }
+
+ bsc_register_fd(&bfd);
+
+ timer.cb = timer_cb;
+ timer.data = &bfd;
+
+ bsc_schedule_timer(&timer, 5, 0);
+
+ printf("Trying to find ip.access BTS by broadcast UDP...\n");
+
+ while (1) {
+ rc = bsc_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
+
diff --git a/src/ipaccess/ipaccess-firmware.c b/src/ipaccess/ipaccess-firmware.c
new file mode 100644
index 00000000..7fdd0f82
--- /dev/null
+++ b/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 <osmocore/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PART_LENGTH 138
+
+static_assert(sizeof(struct sdp_header_entry) == 138, right_entry);
+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;
+ u_int16_t table_size;
+ u_int16_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 %x %x %x\n",
+ firmware_header->more_magic[0] & 0xff, firmware_header->more_magic[1] & 0xff,
+ firmware_header->more_magic[2] & 0xff, firmware_header->more_magic[3] & 0xff);
+ 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/src/ipaccess/ipaccess-proxy.c b/src/ipaccess/ipaccess-proxy.c
new file mode 100644
index 00000000..2dc1b2f6
--- /dev/null
+++ b/src/ipaccess/ipaccess-proxy.c
@@ -0,0 +1,1373 @@
+/* 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/types.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 <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+
+static struct log_target *stderr_target;
+
+/* one instance of an ip.access protocol proxy */
+struct ipa_proxy {
+ /* socket where we listen for incoming OML from BTS */
+ struct bsc_fd oml_listen_fd;
+ /* socket where we listen for incoming RSL from BTS */
+ struct bsc_fd rsl_listen_fd;
+ /* list of BTS's (struct ipa_bts_conn */
+ struct llist_head bts_list;
+ /* the BSC reconnect timer */
+ struct 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 bsc_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 {
+ u_int16_t site_id;
+ u_int16_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 bsc_fd udp_bts_fd;
+ struct bsc_fd udp_bsc_fd;
+
+ /* NS data */
+ struct in_addr bts_addr;
+ struct bsc_fd gprs_ns_fd;
+ int gprs_local_port;
+ uint16_t gprs_orig_port;
+ uint32_t gprs_orig_ip;
+
+ char *id_tags[0xff];
+ u_int8_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 make_gprs_sock(struct bsc_fd *bfd, int (*cb)(struct bsc_fd*,unsigned int), void *);
+static int gprs_ns_cb(struct bsc_fd *bfd, unsigned int what);
+
+#define PROXY_ALLOC_SIZE 1200
+
+static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG };
+static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK };
+static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, 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,
+ };
+
+static const char *idtag_names[] = {
+ [IPAC_IDTAG_SERNR] = "Serial_Number",
+ [IPAC_IDTAG_UNITNAME] = "Unit_Name",
+ [IPAC_IDTAG_LOCATION1] = "Location_1",
+ [IPAC_IDTAG_LOCATION2] = "Location_2",
+ [IPAC_IDTAG_EQUIPVERS] = "Equipment_Version",
+ [IPAC_IDTAG_SWVERSION] = "Software_Version",
+ [IPAC_IDTAG_IPADDR] = "IP_Address",
+ [IPAC_IDTAG_MACADDR] = "MAC_Address",
+ [IPAC_IDTAG_UNIT] = "Unit_ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+ if (tag >= ARRAY_SIZE(idtag_names))
+ return "unknown";
+
+ return idtag_names[tag];
+}
+
+static int ipac_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+ u_int8_t t_len;
+ u_int8_t t_tag;
+ u_int8_t *cur = buf;
+
+ while (cur < buf + len) {
+ t_len = *cur++;
+ t_tag = *cur++;
+
+ DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur);
+
+ dec->lv[t_tag].len = t_len;
+ dec->lv[t_tag].val = cur;
+
+ cur += t_len;
+ }
+ return 0;
+}
+
+static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id,
+ u_int16_t *trx_id)
+{
+ unsigned long ul;
+ char *endptr;
+ const char *nptr;
+
+ nptr = str;
+ ul = strtoul(nptr, &endptr, 10);
+ if (endptr <= nptr)
+ return -EINVAL;
+ if (site_id)
+ *site_id = ul & 0xffff;
+
+ if (*endptr++ != '/')
+ return -EINVAL;
+
+ nptr = endptr;
+ ul = strtoul(nptr, &endptr, 10);
+ if (endptr <= nptr)
+ return -EINVAL;
+ if (bts_id)
+ *bts_id = ul & 0xffff;
+
+ if (*endptr++ != '/')
+ return -EINVAL;
+
+ nptr = endptr;
+ ul = strtoul(nptr, &endptr, 10);
+ if (endptr <= nptr)
+ return -EINVAL;
+ if (trx_id)
+ *trx_id = ul & 0xffff;
+
+ return 0;
+}
+
+static struct ipa_bts_conn *find_bts_by_unitid(struct ipa_proxy *ipp,
+ u_int16_t site_id,
+ u_int16_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, u_int8_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 ");
+}
+
+/* UDP socket handling */
+
+static int make_sock(struct bsc_fd *bfd, u_int16_t port, int proto, int priv_nr,
+ int (*cb)(struct bsc_fd *fd, unsigned int what),
+ void *data)
+{
+ struct sockaddr_in addr;
+ int ret, on = 1;
+
+ bfd->fd = socket(AF_INET, SOCK_DGRAM, proto);
+ bfd->cb = cb;
+ bfd->when = BSC_FD_READ;
+ bfd->data = data;
+ bfd->priv_nr = priv_nr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ 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(DINP, LOGL_ERROR, "could not bind socket: %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ perror("register UDP fd");
+ return ret;
+ }
+ return 0;
+}
+
+static int handle_udp_read(struct bsc_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(DINP, LOGL_ERROR, "recv error %s\n", strerror(errno));
+ msgb_free(msg);
+ return ret;
+ }
+ if (ret == 0) {
+ DEBUGP(DINP, "UDP peer disappeared, dead socket\n");
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+ msgb_free(msg);
+ return -EIO;
+ }
+ if (ret < sizeof(*hh)) {
+ DEBUGP(DINP, "could not even read header!?!\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msgb_put(msg, ret);
+ msg->l2h = msg->data + sizeof(*hh);
+ DEBUGP(DMI, "UDP RX: %s\n", hexdump(msg->data, msg->len));
+
+ if (hh->len != msg->len - sizeof(*hh)) {
+ DEBUGP(DINP, "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(DINP, "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(DINP, "Unknown protocol 0x%02x, sending to "
+ "OML FD\n", hh->proto);
+ case IPAC_PROTO_IPACCESS:
+ case IPAC_PROTO_OML:
+ other_conn = ipbc->bsc_oml_conn;
+ break;
+ }
+ break;
+ default:
+ DEBUGP(DINP, "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 bsc_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 bsc_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 bsc_fd *bfd,
+ u_int16_t site_id, u_int16_t bts_id,
+ u_int16_t trx_id, struct tlv_parsed *tlvp,
+ struct msgb *msg)
+{
+ struct ipa_bts_conn *ipbc;
+ u_int16_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(DINP, "(%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(DINP, "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(DINP, "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(DINP, "(%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, udp_port, IPPROTO_UDP,
+ UDP_TO_BTS, udp_fd_cb, ipbc);
+ if (ret < 0)
+ goto err_udp_bts;
+ DEBUGP(DINP, "(%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, udp_port, IPPROTO_UDP,
+ UDP_TO_BSC, udp_fd_cb, ipbc);
+ if (ret < 0)
+ goto err_udp_bsc;
+ DEBUGP(DINP, "(%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);
+ ret = make_gprs_sock(&ipbc->gprs_ns_fd, gprs_ns_cb, ipbc);
+ if (ret < 0) {
+ LOGP(DINP, 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(DINP, 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:
+ bsc_unregister_fd(&ipbc->udp_bts_fd);
+err_udp_bts:
+ bsc_unregister_fd(&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
+ bsc_unregister_fd(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 bsc_fd *bfd)
+{
+ struct tlv_parsed tlvp;
+ u_int8_t msg_type = *(msg->l2h);
+ u_int16_t site_id, bts_id, trx_id;
+ struct ipa_bts_conn *ipbc;
+ int ret = 0;
+
+ switch (msg_type) {
+ case IPAC_MSGT_PING:
+ ret = write(bfd->fd, pong, sizeof(pong));
+ if (ret < 0)
+ return ret;
+ if (ret < sizeof(pong)) {
+ DEBUGP(DINP, "short write\n");
+ return -EIO;
+ }
+ break;
+ case IPAC_MSGT_PONG:
+ DEBUGP(DMI, "PONG!\n");
+ break;
+ case IPAC_MSGT_ID_RESP:
+ DEBUGP(DMI, "ID_RESP ");
+ /* parse tags, search for Unit ID */
+ ipac_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2,
+ msgb_l2len(msg)-2);
+ DEBUGP(DMI, "\n");
+
+ if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) {
+ LOGP(DINP, LOGL_ERROR, "No Unit ID in ID RESPONSE !?!\n");
+ return -EIO;
+ }
+
+ /* lookup BTS, create sign_link, ... */
+ site_id = bts_id = trx_id = 0;
+ parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT),
+ &site_id, &bts_id, &trx_id);
+ ipbc = find_bts_by_unitid(ipp, site_id, 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, site_id, bts_id,
+ 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(DINP, "Identified BTS %u/%u/%u\n",
+ site_id, bts_id, trx_id);
+
+ if ((bfd->priv_nr & 0xff) != RSL_FROM_BTS) {
+ LOGP(DINP, LOGL_ERROR, "Second OML connection from "
+ "same BTS ?!?\n");
+ return 0;
+ }
+
+ if (trx_id > MAX_TRX) {
+ LOGP(DINP, 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 |= trx_id << 8;
+ ipbc->rsl_conn[trx_id] = ipc;
+
+ /* Create RSL TCP connection towards BSC */
+ sin.sin_port = htons(IPA_TCP_PORT_RSL);
+ ipbc->bsc_rsl_conn[trx_id] =
+ connect_bsc(&sin, RSL_TO_BSC | (trx_id << 8), ipbc);
+ if (!ipbc->bsc_oml_conn)
+ return -EIO;
+ DEBUGP(DINP, "(%u/%u/%u) Connected RSL to BSC\n",
+ site_id, bts_id, trx_id);
+ }
+ break;
+ case IPAC_MSGT_ID_GET:
+ DEBUGP(DMI, "ID_GET\n");
+ if ((bfd->priv_nr & 0xff) != OML_TO_BSC &&
+ (bfd->priv_nr & 0xff) != RSL_TO_BSC) {
+ DEBUGP(DINP, "IDentity REQuest from BTS ?!?\n");
+ return -EIO;
+ }
+ ipbc = ipc->bts_conn;
+ if (!ipbc) {
+ DEBUGP(DINP, "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);
+ break;
+ case IPAC_MSGT_ID_ACK:
+ DEBUGP(DMI, "ID_ACK? -> ACK!\n");
+ ret = write(bfd->fd, id_ack, sizeof(id_ack));
+ break;
+ default:
+ LOGP(DMI, LOGL_ERROR, "Unhandled IPA type; %d\n", msg_type);
+ return 1;
+ break;
+ }
+ return 0;
+}
+
+struct msgb *ipaccess_read_msg(struct bsc_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(DINP, 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(DINP, 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(DINP, "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(DINP, LOGL_NOTICE, ipbc, 0);
+ LOGPC(DINP, 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(DINP, LOGL_NOTICE, ipbc, 0);
+ LOGPC(DINP, 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(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+ LOGPC(DINP, LOGL_NOTICE, "RSL Trying to reconnect\n");
+ ipbc->bsc_rsl_conn[i] = connect_bsc(&sin, priv_nr, ipbc);
+ if (!ipbc->bsc_rsl_conn)
+ goto reschedule;
+ logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+ LOGPC(DINP, LOGL_NOTICE, "RSL Reconnected\n");
+ }
+ }
+ return;
+
+reschedule:
+ bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+}
+
+static void handle_dead_socket(struct bsc_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;
+
+ bsc_unregister_fd(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 */
+ ipbc->oml_conn = NULL;
+ bsc_conn = ipbc->bsc_oml_conn;
+ /* close the connection to the BSC */
+ bsc_unregister_fd(&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 */
+ bsc_unregister_fd(&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 */
+ bsc_schedule_timer(&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 */
+ bsc_schedule_timer(&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 = *(u_int16_t *)(nsvci+8);
+ ipbc->gprs_orig_ip = *(u_int32_t *)(nsvci+10);
+ *(u_int16_t *)(nsvci+8) = htons(ipbc->gprs_local_port);
+ *(u_int32_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];
+ *(u_int16_t *)(nsvci+8) = ipbc->gprs_orig_port;
+ *(u_int32_t *)(nsvci+10) = ipbc->gprs_orig_ip;
+ }
+}
+
+static int handle_tcp_read(struct bsc_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_read_msg(bfd, &ret);
+ if (!msg) {
+ if (ret == 0) {
+ logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+ LOGPC(DINP, LOGL_NOTICE, "%s disappeared, "
+ "dead socket\n", btsbsc);
+ handle_dead_socket(bfd);
+ }
+ return ret;
+ }
+
+ msgb_put(msg, ret);
+ logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+ DEBUGPC(DMI, "RX<-%s: %s\n", btsbsc, 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) {
+ bsc_unregister_fd(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(DINP, 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(DINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8);
+ LOGPC(DINP, 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 bsc_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(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+ DEBUGPC(DMI, "TX %04x: %s\n", bfd->priv_nr,
+ hexdump(msg->data, msg->len));
+
+ ret = send(bfd->fd, msg->data, msg->len, 0);
+ msgb_free(msg);
+
+ if (ret == 0) {
+ logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+ LOGP(DINP, 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 ipaccess_fd_cb(struct bsc_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 bsc_fd *listen_bfd, unsigned int what)
+{
+ int ret;
+ struct ipa_proxy_conn *ipc;
+ struct bsc_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(DINP, "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 = ipaccess_fd_cb;
+ bfd->when = BSC_FD_READ;
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ LOGP(DINP, 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 = write(bfd->fd, id_req, sizeof(id_req));
+
+ 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(DINP, LOGL_ERROR, "Failed to forward GPRS message.\n");
+ }
+}
+
+static int gprs_ns_cb(struct bsc_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(DINP, 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(DINP, 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(DINP, LOGL_DEBUG, "GPRS NS msg from BTS.\n");
+ send_ns(bfd->fd, buf, ret, ipp->gprs_addr, 23000);
+ } else {
+ LOGP(DINP, LOGL_ERROR, "Unknown GPRS source: %s\n", inet_ntoa(sock.sin_addr));
+ }
+
+ return 0;
+}
+
+static int make_listen_sock(struct bsc_fd *bfd, u_int16_t port, int priv_nr,
+ int (*cb)(struct bsc_fd *fd, unsigned int what))
+{
+ struct sockaddr_in addr;
+ int ret, on = 1;
+
+ bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ bfd->cb = cb;
+ bfd->when = BSC_FD_READ;
+ bfd->priv_nr = priv_nr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ if (!listen_ipaddr)
+ addr.sin_addr.s_addr = INADDR_ANY;
+ else
+ inet_aton(listen_ipaddr, &addr.sin_addr);
+
+ setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (ret < 0) {
+ LOGP(DINP, LOGL_ERROR,
+ "Could not bind listen socket for IP %s with error: %s.\n",
+ listen_ipaddr, strerror(errno));
+ return -EIO;
+ }
+
+ ret = listen(bfd->fd, 1);
+ if (ret < 0) {
+ perror("listen");
+ return ret;
+ }
+
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ perror("register_listen_fd");
+ return ret;
+ }
+ return 0;
+}
+
+static int make_gprs_sock(struct bsc_fd *bfd, int (*cb)(struct bsc_fd*,unsigned int), void *data)
+{
+ struct sockaddr_in addr;
+ int ret;
+
+ bfd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ bfd->cb = cb;
+ bfd->data = data;
+ bfd->when = BSC_FD_READ;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = 0;
+ inet_aton(listen_ipaddr, &addr.sin_addr);
+
+ ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (ret < 0) {
+ LOGP(DINP, LOGL_ERROR,
+ "Could not bind n socket for IP %s with error: %s.\n",
+ listen_ipaddr, strerror(errno));
+ return -EIO;
+ }
+
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ perror("register_listen_fd");
+ return ret;
+ }
+ 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 bsc_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;
+
+ setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+ if (ret < 0) {
+ LOGP(DINP, 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 = bsc_register_fd(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);
+ ipp->reconn_timer.cb = reconn_tmr_cb;
+ ipp->reconn_timer.data = ipp;
+
+ /* Listen for OML connections */
+ ret = make_listen_sock(&ipp->oml_listen_fd, IPA_TCP_PORT_OML,
+ OML_FROM_BTS, listen_fd_cb);
+ if (ret < 0)
+ return ret;
+
+ /* Listen for RSL connections */
+ ret = make_listen_sock(&ipp->rsl_listen_fd, IPA_TCP_PORT_RSL,
+ RSL_FROM_BTS, listen_fd_cb);
+
+ 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()
+{
+ 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()
+{
+ printf("Usage: ipaccess-proxy\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'},
+ {"disable-color", 0, 0, 's'},
+ {"timestamp", 0, 0, 'T'},
+ {"log-level", 1, 0, 'e'},
+ {"listen", 1, 0, 'l'},
+ {"bsc", 1, 0, 'b'},
+ {"udp", 1, 0, 'u'},
+ {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;
+ break;
+ case 'b':
+ bsc_ipaddr = optarg;
+ break;
+ case 'g':
+ gprs_ns_ipaddr = optarg;
+ break;
+ case 's':
+ log_set_use_color(stderr_target, 0);
+ break;
+ case 'T':
+ log_set_print_timestamp(stderr_target, 1);
+ break;
+ case 'e':
+ log_set_log_level(stderr_target, atoi(optarg));
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ listen_ipaddr = "192.168.100.11";
+ bsc_ipaddr = "192.168.100.239";
+
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "ipaccess-proxy");
+
+ log_init(&log_info);
+ stderr_target = log_target_create_stderr();
+ log_add_target(stderr_target);
+ log_set_all_filter(stderr_target, 1);
+ log_parse_category_mask(stderr_target, "DINP:DMI");
+
+ handle_options(argc, argv);
+
+ rc = ipaccess_proxy_setup();
+ if (rc < 0)
+ exit(1);
+
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+
+ while (1) {
+ bsc_select_main(0);
+ }
+}
diff --git a/src/ipaccess/network_listen.c b/src/ipaccess/network_listen.c
new file mode 100644
index 00000000..aaf7c974
--- /dev/null
+++ b/src/ipaccess/network_listen.c
@@ -0,0 +1,251 @@
+/* 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 <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rxlev_stat.h>
+#include <osmocore/gsm48_ie.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.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;
+ 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 */
+ test_rep_len = ntohs(*(uint16_t *) &foh->data[3]);
+ /* 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 */
+ ferr_list_len = ntohs(*(uint16_t *) &foh->data[7]);
+
+ /* 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 */
+ ferr_list_len = ntohs(*(uint16_t *) &foh->data[7]);
+
+ /* 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(&msg->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: %3d-%d, LAC %d CI %d BSIC %u\n",
+ binfo.arfcn, binfo.rx_lev, binfo.rx_qual,
+ binfo.cgi.mcc, binfo.cgi.mnc,
+ binfo.cgi.lac, binfo.cgi.ci, 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", 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", 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", 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:
+ msg->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");
+ dispatch_signal(SS_IPAC_NWL, S_IPAC_NWL_COMPLETE, msg->trx);
+ break;
+ case NM_IPACC_TESTRES_PARTIAL:
+ msg->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)
+{
+ register_signal_handler(SS_NM, nwl_sig_cb, NULL);
+}
diff --git a/src/libabis/Makefile.am b/src/libabis/Makefile.am
new file mode 100644
index 00000000..0df7b5a4
--- /dev/null
+++ b/src/libabis/Makefile.am
@@ -0,0 +1,14 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libabis.a
+
+libabis_a_SOURCES = e1_input.c e1_input_vty.c \
+ input/misdn.c \
+ input/ipaccess.c \
+ input/hsl.c \
+ input/dahdi.c \
+ input/lapd.c
+
+EXTRA_DIST = input/lapd.h
diff --git a/src/libabis/Makefile.in b/src/libabis/Makefile.in
new file mode 100644
index 00000000..90d62879
--- /dev/null
+++ b/src/libabis/Makefile.in
@@ -0,0 +1,548 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libabis
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo " AR " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libabis_a_AR = $(AR) $(ARFLAGS)
+libabis_a_LIBADD =
+am_libabis_a_OBJECTS = e1_input.$(OBJEXT) e1_input_vty.$(OBJEXT) \
+ misdn.$(OBJEXT) ipaccess.$(OBJEXT) hsl.$(OBJEXT) \
+ dahdi.$(OBJEXT) lapd.$(OBJEXT)
+libabis_a_OBJECTS = $(am_libabis_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_$(V))
+am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY))
+am__v_lt_0 = --silent
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo " CC " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo " CCLD " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+SOURCES = $(libabis_a_SOURCES)
+DIST_SOURCES = $(libabis_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libabis.a
+libabis_a_SOURCES = e1_input.c e1_input_vty.c \
+ input/misdn.c \
+ input/ipaccess.c \
+ input/hsl.c \
+ input/dahdi.c \
+ input/lapd.c
+
+EXTRA_DIST = input/lapd.h
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libabis/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/libabis/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libabis.a: $(libabis_a_OBJECTS) $(libabis_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libabis.a
+ $(AM_V_AR)$(libabis_a_AR) libabis.a $(libabis_a_OBJECTS) $(libabis_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libabis.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dahdi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_input.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_input_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hsl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lapd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misdn.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+misdn.o: input/misdn.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misdn.o -MD -MP -MF $(DEPDIR)/misdn.Tpo -c -o misdn.o `test -f 'input/misdn.c' || echo '$(srcdir)/'`input/misdn.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/misdn.Tpo $(DEPDIR)/misdn.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/misdn.c' object='misdn.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misdn.o `test -f 'input/misdn.c' || echo '$(srcdir)/'`input/misdn.c
+
+misdn.obj: input/misdn.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misdn.obj -MD -MP -MF $(DEPDIR)/misdn.Tpo -c -o misdn.obj `if test -f 'input/misdn.c'; then $(CYGPATH_W) 'input/misdn.c'; else $(CYGPATH_W) '$(srcdir)/input/misdn.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/misdn.Tpo $(DEPDIR)/misdn.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/misdn.c' object='misdn.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misdn.obj `if test -f 'input/misdn.c'; then $(CYGPATH_W) 'input/misdn.c'; else $(CYGPATH_W) '$(srcdir)/input/misdn.c'; fi`
+
+ipaccess.o: input/ipaccess.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ipaccess.o -MD -MP -MF $(DEPDIR)/ipaccess.Tpo -c -o ipaccess.o `test -f 'input/ipaccess.c' || echo '$(srcdir)/'`input/ipaccess.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ipaccess.Tpo $(DEPDIR)/ipaccess.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/ipaccess.c' object='ipaccess.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ipaccess.o `test -f 'input/ipaccess.c' || echo '$(srcdir)/'`input/ipaccess.c
+
+ipaccess.obj: input/ipaccess.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ipaccess.obj -MD -MP -MF $(DEPDIR)/ipaccess.Tpo -c -o ipaccess.obj `if test -f 'input/ipaccess.c'; then $(CYGPATH_W) 'input/ipaccess.c'; else $(CYGPATH_W) '$(srcdir)/input/ipaccess.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ipaccess.Tpo $(DEPDIR)/ipaccess.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/ipaccess.c' object='ipaccess.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ipaccess.obj `if test -f 'input/ipaccess.c'; then $(CYGPATH_W) 'input/ipaccess.c'; else $(CYGPATH_W) '$(srcdir)/input/ipaccess.c'; fi`
+
+hsl.o: input/hsl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hsl.o -MD -MP -MF $(DEPDIR)/hsl.Tpo -c -o hsl.o `test -f 'input/hsl.c' || echo '$(srcdir)/'`input/hsl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/hsl.Tpo $(DEPDIR)/hsl.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/hsl.c' object='hsl.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hsl.o `test -f 'input/hsl.c' || echo '$(srcdir)/'`input/hsl.c
+
+hsl.obj: input/hsl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hsl.obj -MD -MP -MF $(DEPDIR)/hsl.Tpo -c -o hsl.obj `if test -f 'input/hsl.c'; then $(CYGPATH_W) 'input/hsl.c'; else $(CYGPATH_W) '$(srcdir)/input/hsl.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/hsl.Tpo $(DEPDIR)/hsl.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/hsl.c' object='hsl.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hsl.obj `if test -f 'input/hsl.c'; then $(CYGPATH_W) 'input/hsl.c'; else $(CYGPATH_W) '$(srcdir)/input/hsl.c'; fi`
+
+dahdi.o: input/dahdi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dahdi.o -MD -MP -MF $(DEPDIR)/dahdi.Tpo -c -o dahdi.o `test -f 'input/dahdi.c' || echo '$(srcdir)/'`input/dahdi.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dahdi.Tpo $(DEPDIR)/dahdi.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/dahdi.c' object='dahdi.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dahdi.o `test -f 'input/dahdi.c' || echo '$(srcdir)/'`input/dahdi.c
+
+dahdi.obj: input/dahdi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dahdi.obj -MD -MP -MF $(DEPDIR)/dahdi.Tpo -c -o dahdi.obj `if test -f 'input/dahdi.c'; then $(CYGPATH_W) 'input/dahdi.c'; else $(CYGPATH_W) '$(srcdir)/input/dahdi.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dahdi.Tpo $(DEPDIR)/dahdi.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/dahdi.c' object='dahdi.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dahdi.obj `if test -f 'input/dahdi.c'; then $(CYGPATH_W) 'input/dahdi.c'; else $(CYGPATH_W) '$(srcdir)/input/dahdi.c'; fi`
+
+lapd.o: input/lapd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lapd.o -MD -MP -MF $(DEPDIR)/lapd.Tpo -c -o lapd.o `test -f 'input/lapd.c' || echo '$(srcdir)/'`input/lapd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lapd.Tpo $(DEPDIR)/lapd.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/lapd.c' object='lapd.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lapd.o `test -f 'input/lapd.c' || echo '$(srcdir)/'`input/lapd.c
+
+lapd.obj: input/lapd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lapd.obj -MD -MP -MF $(DEPDIR)/lapd.Tpo -c -o lapd.obj `if test -f 'input/lapd.c'; then $(CYGPATH_W) 'input/lapd.c'; else $(CYGPATH_W) '$(srcdir)/input/lapd.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lapd.Tpo $(DEPDIR)/lapd.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/lapd.c' object='lapd.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lapd.obj `if test -f 'input/lapd.c'; then $(CYGPATH_W) 'input/lapd.c'; else $(CYGPATH_W) '$(srcdir)/input/lapd.c'; fi`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-noinstLIBRARIES ctags distclean distclean-compile \
+ distclean-generic distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+ uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libabis/e1_input.c b/src/libabis/e1_input.c
new file mode 100644
index 00000000..3b6644ef
--- /dev/null
+++ b/src/libabis/e1_input.c
@@ -0,0 +1,649 @@
+/* OpenBSC Abis interface to E1 */
+
+/* (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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <mISDNif.h>
+
+//#define AF_COMPATIBILITY_FUNC
+//#include <compat_af_isdn.h>
+#ifndef AF_ISDN
+#define AF_ISDN 34
+#define PF_ISDN AF_ISDN
+#endif
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/misdn.h>
+
+#include "../../bscconfig.h"
+
+#define NUM_E1_TS 32
+
+/* list of all E1 drivers */
+LLIST_HEAD(e1inp_driver_list);
+
+/* list of all E1 lines */
+LLIST_HEAD(e1inp_line_list);
+
+static void *tall_sigl_ctx;
+
+/*
+ * pcap writing of the misdn load
+ * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+#define DLT_LINUX_LAPD 177
+#define PCAP_INPUT 0
+#define PCAP_OUTPUT 1
+
+struct pcap_hdr {
+ u_int32_t magic_number;
+ u_int16_t version_major;
+ u_int16_t version_minor;
+ int32_t thiszone;
+ u_int32_t sigfigs;
+ u_int32_t snaplen;
+ u_int32_t network;
+} __attribute__((packed));
+
+struct pcaprec_hdr {
+ u_int32_t ts_sec;
+ u_int32_t ts_usec;
+ u_int32_t incl_len;
+ u_int32_t orig_len;
+} __attribute__((packed));
+
+struct fake_linux_lapd_header {
+ u_int16_t pkttype;
+ u_int16_t hatype;
+ u_int16_t halen;
+ u_int64_t addr;
+ int16_t protocol;
+} __attribute__((packed));
+
+struct lapd_header {
+ u_int8_t ea1 : 1;
+ u_int8_t cr : 1;
+ u_int8_t sapi : 6;
+ u_int8_t ea2 : 1;
+ u_int8_t tei : 7;
+ u_int8_t control_foo; /* fake UM's ... */
+} __attribute__((packed));
+
+static_assert(offsetof(struct fake_linux_lapd_header, hatype) == 2, hatype_offset);
+static_assert(offsetof(struct fake_linux_lapd_header, halen) == 4, halen_offset);
+static_assert(offsetof(struct fake_linux_lapd_header, addr) == 6, addr_offset);
+static_assert(offsetof(struct fake_linux_lapd_header, protocol) == 14, proto_offset);
+static_assert(sizeof(struct fake_linux_lapd_header) == 16, lapd_header_size);
+
+
+static int pcap_fd = -1;
+
+void e1_set_pcap_fd(int fd)
+{
+ int ret;
+ struct pcap_hdr header = {
+ .magic_number = 0xa1b2c3d4,
+ .version_major = 2,
+ .version_minor = 4,
+ .thiszone = 0,
+ .sigfigs = 0,
+ .snaplen = 65535,
+ .network = DLT_LINUX_LAPD,
+ };
+
+ pcap_fd = fd;
+ ret = write(pcap_fd, &header, sizeof(header));
+}
+
+/* This currently only works for the D-Channel */
+static void write_pcap_packet(int direction, int sapi, int tei,
+ struct msgb *msg) {
+ if (pcap_fd < 0)
+ return;
+
+ int ret;
+ time_t cur_time;
+ struct tm *tm;
+
+ struct fake_linux_lapd_header header = {
+ .pkttype = 4,
+ .hatype = 0,
+ .halen = 0,
+ .addr = direction == PCAP_OUTPUT ? 0x0 : 0x1,
+ .protocol = ntohs(48),
+ };
+
+ struct lapd_header lapd_header = {
+ .ea1 = 0,
+ .cr = direction == PCAP_OUTPUT ? 1 : 0,
+ .sapi = sapi & 0x3F,
+ .ea2 = 1,
+ .tei = tei & 0x7F,
+ .control_foo = 0x03 /* UI */,
+ };
+
+ struct pcaprec_hdr payload_header = {
+ .ts_sec = 0,
+ .ts_usec = 0,
+ .incl_len = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header)
+ + sizeof(struct lapd_header),
+ .orig_len = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header)
+ + sizeof(struct lapd_header),
+ };
+
+
+ cur_time = time(NULL);
+ tm = localtime(&cur_time);
+ payload_header.ts_sec = mktime(tm);
+
+ ret = write(pcap_fd, &payload_header, sizeof(payload_header));
+ ret = write(pcap_fd, &header, sizeof(header));
+ ret = write(pcap_fd, &lapd_header, sizeof(lapd_header));
+ ret = write(pcap_fd, msg->l2h, msgb_l2len(msg));
+}
+
+static const char *sign_types[] = {
+ [E1INP_SIGN_NONE] = "None",
+ [E1INP_SIGN_OML] = "OML",
+ [E1INP_SIGN_RSL] = "RSL",
+};
+const char *e1inp_signtype_name(enum e1inp_sign_type tp)
+{
+ if (tp >= ARRAY_SIZE(sign_types))
+ return "undefined";
+ return sign_types[tp];
+}
+
+static const char *ts_types[] = {
+ [E1INP_TS_TYPE_NONE] = "None",
+ [E1INP_TS_TYPE_SIGN] = "Signalling",
+ [E1INP_TS_TYPE_TRAU] = "TRAU",
+};
+
+const char *e1inp_tstype_name(enum e1inp_ts_type tp)
+{
+ if (tp >= ARRAY_SIZE(ts_types))
+ return "undefined";
+ return ts_types[tp];
+}
+
+/* callback when a TRAU frame was received */
+static int subch_cb(struct subch_demux *dmx, int ch, u_int8_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);
+}
+
+int abis_rsl_sendmsg(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link;
+ struct e1inp_driver *e1inp_driver;
+ struct e1inp_ts *e1i_ts;
+
+ msg->l2h = msg->data;
+
+ if (!msg->trx) {
+ LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx == NULL: %s\n",
+ hexdump(msg->data, msg->len));
+ talloc_free(msg);
+ return -EINVAL;
+ } else if (!msg->trx->rsl_link) {
+ LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx->rsl_link == NULL: %s\n",
+ hexdump(msg->data, msg->len));
+ talloc_free(msg);
+ return -EIO;
+ }
+
+ sign_link = msg->trx->rsl_link;
+ e1i_ts = sign_link->ts;
+ if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) {
+ /* notify the driver we have something to write */
+ e1inp_driver = sign_link->ts->line->driver;
+ e1inp_driver->want_write(e1i_ts);
+ }
+ msgb_enqueue(&sign_link->tx_list, msg);
+
+ /* dump it */
+ write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg);
+
+ return 0;
+}
+
+int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml)
+{
+ struct e1inp_sign_link *sign_link;
+ struct e1inp_driver *e1inp_driver;
+ struct e1inp_ts *e1i_ts;
+
+ msg->l2h = msg->data;
+
+ if (!msg->trx || !msg->trx->bts || !msg->trx->bts->oml_link) {
+ LOGP(DNM, LOGL_ERROR, "nm_sendmsg: msg->trx == NULL\n");
+ return -EINVAL;
+ }
+
+ /* Check for TRX-specific OML link first */
+ if (to_trx_oml) {
+ if (!msg->trx->oml_link)
+ return -ENODEV;
+ sign_link = msg->trx->oml_link;
+ } else
+ sign_link = msg->trx->bts->oml_link;
+
+ e1i_ts = sign_link->ts;
+ if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) {
+ /* notify the driver we have something to write */
+ e1inp_driver = sign_link->ts->line->driver;
+ e1inp_driver->want_write(e1i_ts);
+ }
+ msgb_enqueue(&sign_link->tx_list, msg);
+
+ /* dump it */
+ write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg);
+
+ return 0;
+}
+
+/* Timeslot */
+
+/* configure and initialize one e1inp_ts */
+int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line,
+ enum e1inp_ts_type type)
+{
+ if (ts->type == type && ts->line && line)
+ return 0;
+
+ ts->type = type;
+ ts->line = line;
+
+ switch (type) {
+ case E1INP_TS_TYPE_SIGN:
+ if (line && line->driver)
+ ts->sign.delay = line->driver->default_delay;
+ else
+ ts->sign.delay = 100000;
+ INIT_LLIST_HEAD(&ts->sign.sign_links);
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ subchan_mux_init(&ts->trau.mux);
+ ts->trau.demux.out_cb = subch_cb;
+ ts->trau.demux.data = ts;
+ subch_demux_init(&ts->trau.demux);
+ break;
+ default:
+ LOGP(DMI, LOGL_ERROR, "unsupported E1 timeslot type %u\n",
+ ts->type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+struct e1inp_line *e1inp_line_get(u_int8_t e1_nr)
+{
+ struct e1inp_line *e1i_line;
+
+ /* iterate over global list of e1 lines */
+ llist_for_each_entry(e1i_line, &e1inp_line_list, list) {
+ if (e1i_line->num == e1_nr)
+ return e1i_line;
+ }
+ return NULL;
+}
+
+struct e1inp_line *e1inp_line_create(u_int8_t e1_nr, const char *driver_name)
+{
+ struct e1inp_driver *driver;
+ struct e1inp_line *line;
+ int i;
+
+ line = e1inp_line_get(e1_nr);
+ if (line) {
+ LOGP(DINP, LOGL_ERROR, "E1 Line %u already exists\n",
+ e1_nr);
+ return NULL;
+ }
+
+ driver = e1inp_driver_find(driver_name);
+ if (!driver) {
+ LOGP(DINP, LOGL_ERROR, "No such E1 driver '%s'\n",
+ driver_name);
+ return NULL;
+ }
+
+ line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+ if (!line)
+ return NULL;
+
+ line->driver = driver;
+
+ line->num = e1_nr;
+ for (i = 0; i < NUM_E1_TS; i++) {
+ line->ts[i].num = i+1;
+ line->ts[i].line = line;
+ }
+ llist_add_tail(&line->list, &e1inp_line_list);
+
+ return line;
+}
+
+#if 0
+struct e1inp_line *e1inp_line_get_create(u_int8_t e1_nr)
+{
+ struct e1inp_line *line;
+ int i;
+
+ line = e1inp_line_get(e1_nr);
+ if (line)
+ return line;
+
+ line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+ if (!line)
+ return NULL;
+
+ line->num = e1_nr;
+ for (i = 0; i < NUM_E1_TS; i++) {
+ line->ts[i].num = i+1;
+ line->ts[i].line = line;
+ }
+ llist_add_tail(&line->list, &e1inp_line_list);
+
+ return line;
+}
+#endif
+
+static struct e1inp_ts *e1inp_ts_get(u_int8_t e1_nr, u_int8_t ts_nr)
+{
+ struct e1inp_line *e1i_line;
+
+ e1i_line = e1inp_line_get(e1_nr);
+ if (!e1i_line)
+ return NULL;
+
+ return &e1i_line->ts[ts_nr-1];
+}
+
+struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr)
+{
+ struct e1inp_ts *e1i_ts = e1inp_ts_get(e1_nr, ts_nr);
+
+ if (!e1i_ts)
+ return NULL;
+
+ return &e1i_ts->trau.mux;
+}
+
+/* Signalling Link */
+
+struct e1inp_sign_link *e1inp_lookup_sign_link(struct e1inp_ts *e1i,
+ u_int8_t tei, u_int8_t sapi)
+{
+ struct e1inp_sign_link *link;
+
+ llist_for_each_entry(link, &e1i->sign.sign_links, list) {
+ if (link->sapi == sapi && link->tei == tei)
+ return link;
+ }
+
+ return NULL;
+}
+
+/* create a new signalling link in a E1 timeslot */
+
+struct e1inp_sign_link *
+e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type,
+ struct gsm_bts_trx *trx, u_int8_t tei,
+ u_int8_t sapi)
+{
+ struct e1inp_sign_link *link;
+
+ if (ts->type != E1INP_TS_TYPE_SIGN)
+ return NULL;
+
+ link = talloc_zero(tall_sigl_ctx, struct e1inp_sign_link);
+ if (!link)
+ return NULL;
+
+ link->ts = ts;
+ link->type = type;
+ INIT_LLIST_HEAD(&link->tx_list);
+ link->trx = trx;
+ link->tei = tei;
+ link->sapi = sapi;
+
+ llist_add_tail(&link->list, &ts->sign.sign_links);
+
+ return link;
+}
+
+void e1inp_sign_link_destroy(struct e1inp_sign_link *link)
+{
+ struct msgb *msg;
+
+ llist_del(&link->list);
+ while (!llist_empty(&link->tx_list)) {
+ msg = msgb_dequeue(&link->tx_list);
+ msgb_free(msg);
+ }
+
+ if (link->ts->type == E1INP_TS_TYPE_SIGN)
+ bsc_del_timer(&link->ts->sign.tx_timer);
+
+ talloc_free(link);
+}
+
+/* the E1 driver tells us he has received something on a TS */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+ u_int8_t tei, u_int8_t sapi)
+{
+ struct e1inp_sign_link *link;
+ struct gsm_bts *bts;
+ int ret;
+
+ switch (ts->type) {
+ case E1INP_TS_TYPE_SIGN:
+ /* consult the list of signalling links */
+ write_pcap_packet(PCAP_INPUT, sapi, tei, msg);
+ link = e1inp_lookup_sign_link(ts, tei, sapi);
+ if (!link) {
+ LOGP(DMI, LOGL_ERROR, "didn't find signalling link for "
+ "tei %d, sapi %d\n", tei, sapi);
+ return -EINVAL;
+ }
+
+ log_set_context(BSC_CTX_BTS, link->trx->bts);
+ switch (link->type) {
+ case E1INP_SIGN_OML:
+ msg->trx = link->trx;
+ bts = msg->trx->bts;
+ ret = bts->model->oml_rcvmsg(msg);
+ break;
+ case E1INP_SIGN_RSL:
+ msg->trx = link->trx;
+ ret = abis_rsl_rcvmsg(msg);
+ break;
+ default:
+ ret = -EINVAL;
+ LOGP(DMI, LOGL_ERROR, "unknown link type %u\n", link->type);
+ break;
+ }
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ ret = subch_demux_in(&ts->trau.demux, msg->l2h, msgb_l2len(msg));
+ break;
+ default:
+ ret = -EINVAL;
+ LOGP(DMI, LOGL_ERROR, "unknown TS type %u\n", ts->type);
+ break;
+ }
+
+ return ret;
+}
+
+#define TSX_ALLOC_SIZE 4096
+
+/* called by driver if it wants to transmit on a given TS */
+struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts,
+ struct e1inp_sign_link **sign_link)
+{
+ struct e1inp_sign_link *link;
+ struct msgb *msg = NULL;
+ int len;
+
+ switch (e1i_ts->type) {
+ case E1INP_TS_TYPE_SIGN:
+ /* FIXME: implement this round robin */
+ llist_for_each_entry(link, &e1i_ts->sign.sign_links, list) {
+ msg = msgb_dequeue(&link->tx_list);
+ if (msg) {
+ if (sign_link)
+ *sign_link = link;
+ break;
+ }
+ }
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ msg = msgb_alloc(TSX_ALLOC_SIZE, "TRAU_TX");
+ if (!msg)
+ return NULL;
+ len = subchan_mux_out(&e1i_ts->trau.mux, msg->data, 40);
+ msgb_put(msg, 40);
+ break;
+ default:
+ LOGP(DMI, LOGL_ERROR, "unsupported E1 TS type %u\n", e1i_ts->type);
+ return NULL;
+ }
+ return msg;
+}
+
+/* called by driver in case some kind of link state event */
+int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi)
+{
+ struct e1inp_sign_link *link;
+ struct input_signal_data isd;
+
+ link = e1inp_lookup_sign_link(ts, tei, sapi);
+ if (!link)
+ return -EINVAL;
+
+ isd.link_type = link->type;
+ isd.trx = link->trx;
+ isd.tei = tei;
+ isd.sapi = sapi;
+
+ /* report further upwards */
+ dispatch_signal(SS_INPUT, evt, &isd);
+ return 0;
+}
+
+/* register a driver with the E1 core */
+int e1inp_driver_register(struct e1inp_driver *drv)
+{
+ llist_add_tail(&drv->list, &e1inp_driver_list);
+ return 0;
+}
+
+struct e1inp_driver *e1inp_driver_find(const char *name)
+{
+ struct e1inp_driver *drv;
+
+ llist_for_each_entry(drv, &e1inp_driver_list, list) {
+ if (!strcasecmp(name, drv->name))
+ return drv;
+ }
+ return NULL;
+}
+
+int e1inp_line_update(struct e1inp_line *line)
+{
+ struct input_signal_data isd;
+ int rc;
+
+ if (line->driver && line->driver->line_update)
+ rc = line->driver->line_update(line);
+ else
+ rc = 0;
+
+ /* Send a signal to anyone who is interested in new lines being
+ * configured */
+ memset(&isd, 0, sizeof(isd));
+ isd.line = line;
+ dispatch_signal(SS_INPUT, S_INP_LINE_INIT, &isd);
+
+ return rc;
+}
+
+static int e1i_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ if (subsys != SS_GLOBAL ||
+ signal != S_GLOBAL_SHUTDOWN)
+ return 0;
+
+ if (pcap_fd) {
+ close(pcap_fd);
+ pcap_fd = -1;
+ }
+
+ return 0;
+}
+
+void e1inp_misdn_init(void);
+void e1inp_dahdi_init(void);
+
+void e1inp_init(void)
+{
+ tall_sigl_ctx = talloc_named_const(tall_bsc_ctx, 1,
+ "e1inp_sign_link");
+ register_signal_handler(SS_GLOBAL, e1i_sig_cb, NULL);
+
+ e1inp_misdn_init();
+#ifdef HAVE_DAHDI_USER_H
+ e1inp_dahdi_init();
+#endif
+}
diff --git a/src/libabis/e1_input_vty.c b/src/libabis/e1_input_vty.c
new file mode 100644
index 00000000..66bf6555
--- /dev/null
+++ b/src/libabis/e1_input_vty.c
@@ -0,0 +1,102 @@
+/* OpenBSC E1 vty interface */
+/* (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 <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/talloc.h>
+#include <openbsc/vty.h>
+#include <openbsc/debug.h>
+
+#include "../../bscconfig.h"
+
+#define E1_DRIVER_NAMES "(misdn|dahdi)"
+#define E1_DRIVER_HELP "mISDN supported E1 Card\n" \
+ "DAHDI supported E1/T1/J1 Card\n"
+
+DEFUN(cfg_e1line_driver, cfg_e1_line_driver_cmd,
+ "e1_line <0-255> driver " E1_DRIVER_NAMES,
+ "Configure E1/T1/J1 Line\n" "Line Number\n" "Set driver for this line\n"
+ E1_DRIVER_HELP)
+{
+ struct e1inp_line *line;
+ int e1_nr = atoi(argv[0]);
+
+ line = e1inp_line_get(e1_nr);
+ if (line) {
+ vty_out(vty, "%% Line %d already exists%s", e1_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ line = e1inp_line_create(e1_nr, argv[1]);
+ if (!line) {
+ vty_out(vty, "%% Error creating line %d%s", e1_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_e1inp, cfg_e1inp_cmd,
+ "e1_input",
+ "Configure E1/T1/J1 TDM input\n")
+{
+ vty->node = E1INP_NODE;
+
+ return CMD_SUCCESS;
+}
+
+static int e1inp_config_write(struct vty *vty)
+{
+ struct e1inp_line *line;
+
+ vty_out(vty, "e1_input%s", VTY_NEWLINE);
+
+ llist_for_each_entry(line, &e1inp_line_list, list) {
+ vty_out(vty, " e1_line %u driver %s%s", line->num,
+ line->driver->name, VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+struct cmd_node e1inp_node = {
+ E1INP_NODE,
+ "%s(e1_input)#",
+ 1,
+};
+
+int e1inp_vty_init(void)
+{
+ install_element(CONFIG_NODE, &cfg_e1inp_cmd);
+ install_node(&e1inp_node, e1inp_config_write);
+ install_element(E1INP_NODE, &cfg_e1_line_driver_cmd);
+
+ return 0;
+}
diff --git a/src/libabis/input/dahdi.c b/src/libabis/input/dahdi.c
new file mode 100644
index 00000000..572bb5ae
--- /dev/null
+++ b/src/libabis/input/dahdi.c
@@ -0,0 +1,494 @@
+/* OpenBSC Abis input driver for DAHDI */
+
+/* (C) 2008-2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
+ *
+ * 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 "../../../bscconfig.h"
+
+#ifdef HAVE_DAHDI_USER_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <dahdi/user.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+
+#include "lapd.h"
+
+#define TS1_ALLOC_SIZE 300
+
+/* Corresponds to dahdi/user.h, only PRI related events */
+static const struct value_string dahdi_evt_names[] = {
+ { DAHDI_EVENT_NONE, "NONE" },
+ { DAHDI_EVENT_ALARM, "ALARM" },
+ { DAHDI_EVENT_NOALARM, "NOALARM" },
+ { DAHDI_EVENT_ABORT, "HDLC ABORT" },
+ { DAHDI_EVENT_OVERRUN, "HDLC OVERRUN" },
+ { DAHDI_EVENT_BADFCS, "HDLC BAD FCS" },
+ { DAHDI_EVENT_REMOVED, "REMOVED" },
+ { 0, NULL }
+};
+
+static void handle_dahdi_exception(struct e1inp_ts *ts)
+{
+ int rc, evt;
+ struct input_signal_data isd;
+
+ rc = ioctl(ts->driver.dahdi.fd.fd, DAHDI_GETEVENT, &evt);
+ if (rc < 0)
+ return;
+
+ LOGP(DMI, LOGL_NOTICE, "Line %u(%s) / TS %u DAHDI EVENT %s\n",
+ ts->line->num, ts->line->name, ts->num,
+ get_value_string(dahdi_evt_names, evt));
+
+ isd.line = ts->line;
+
+ switch (evt) {
+ case DAHDI_EVENT_ALARM:
+ /* we should notify the code that the line is gone */
+ dispatch_signal(SS_INPUT, S_INP_LINE_ALARM, &isd);
+ break;
+ case DAHDI_EVENT_NOALARM:
+ /* alarm has gone, we should re-start the SABM requests */
+ dispatch_signal(SS_INPUT, S_INP_LINE_NOALARM, &isd);
+ break;
+ }
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "DAHDI TS1");
+ lapd_mph_type prim;
+ unsigned int sapi, tei;
+ int ilen, ret;
+ uint8_t *idata;
+
+ if (!msg)
+ return -ENOMEM;
+
+ ret = read(bfd->fd, msg->data, TS1_ALLOC_SIZE - 16);
+ if (ret == -1)
+ handle_dahdi_exception(e1i_ts);
+ else if (ret < 0) {
+ perror("read ");
+ }
+ msgb_put(msg, ret - 2);
+ if (ret <= 3) {
+ perror("read ");
+ }
+
+ sapi = msg->data[0] >> 2;
+ tei = msg->data[1] >> 1;
+
+ DEBUGP(DMI, "<= len = %d, sapi(%d) tei(%d)", ret, sapi, tei);
+
+ idata = lapd_receive(e1i_ts->driver.dahdi.lapd, msg->data, msg->len, &ilen, &prim);
+ if (!idata && prim == 0)
+ return -EIO;
+
+ msgb_pull(msg, 2);
+
+ DEBUGP(DMI, "prim %08x\n", prim);
+
+ switch (prim) {
+ case 0:
+ break;
+ case LAPD_MPH_ACTIVATE_IND:
+ DEBUGP(DMI, "MPH_ACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei);
+ ret = e1inp_event(e1i_ts, S_INP_TEI_UP, tei, sapi);
+ break;
+ case LAPD_MPH_DEACTIVATE_IND:
+ DEBUGP(DMI, "MPH_DEACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei);
+ ret = e1inp_event(e1i_ts, S_INP_TEI_DN, tei, sapi);
+ break;
+ case LAPD_DL_DATA_IND:
+ case LAPD_DL_UNITDATA_IND:
+ if (prim == LAPD_DL_DATA_IND)
+ msg->l2h = msg->data + 2;
+ else
+ msg->l2h = msg->data + 1;
+ DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret));
+ ret = e1inp_rx_ts(e1i_ts, msg, tei, sapi);
+ break;
+ default:
+ printf("ERROR: unknown prim\n");
+ break;
+ }
+
+ DEBUGP(DMI, "Returned ok\n");
+ return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+ /* We never include the DAHDI B-Channel FD into the
+ * writeset, since it doesn't support poll() based
+ * write flow control */
+ if (e1i_ts->type == E1INP_TS_TYPE_TRAU) {
+ fprintf(stderr, "Trying to write TRAU ts\n");
+ return 0;
+ }
+
+ e1i_ts->driver.dahdi.fd.when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+ struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+ /* trigger write of ts1, due to tx delay timer */
+ ts_want_write(e1i_ts);
+}
+
+static void dahdi_write_msg(uint8_t *data, int len, void *cbdata)
+{
+ struct bsc_fd *bfd = cbdata;
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ int ret;
+
+ ret = write(bfd->fd, data, len + 2);
+ if (ret == -1)
+ handle_dahdi_exception(e1i_ts);
+ else if (ret < 0)
+ LOGP(DMI, LOGL_NOTICE, "%s write failed %d\n", __func__, ret);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *sign_link;
+ struct msgb *msg;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* get the next msg for this timeslot */
+ msg = e1inp_tx_ts(e1i_ts, &sign_link);
+ if (!msg) {
+ /* no message after tx delay timer */
+ return 0;
+ }
+
+ DEBUGP(DMI, "TX: %s\n", hexdump(msg->data, msg->len));
+ lapd_transmit(e1i_ts->driver.dahdi.lapd, sign_link->tei,
+ sign_link->sapi, msg->data, msg->len);
+ msgb_free(msg);
+
+ /* set tx delay timer for next event */
+ e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+ e1i_ts->sign.tx_timer.data = e1i_ts;
+ bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, 50000);
+
+ return 0;
+}
+
+
+static int invertbits = 1;
+
+static u_int8_t flip_table[256];
+
+static void init_flip_bits(void)
+{
+ int i,k;
+
+ for (i = 0 ; i < 256 ; i++) {
+ u_int8_t sample = 0 ;
+ for (k = 0; k<8; k++) {
+ if ( i & 1 << k ) sample |= 0x80 >> k;
+ }
+ flip_table[i] = sample;
+ }
+}
+
+static u_int8_t * flip_buf_bits ( u_int8_t * buf , int len)
+{
+ int i;
+ u_int8_t * start = buf;
+
+ for (i = 0 ; i < len; i++) {
+ buf[i] = flip_table[(u_int8_t)buf[i]];
+ }
+
+ return start;
+}
+
+#define D_BCHAN_TX_GRAN 160
+/* write to a B channel TS */
+static int handle_tsX_write(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ u_int8_t tx_buf[D_BCHAN_TX_GRAN];
+ struct subch_mux *mx = &e1i_ts->trau.mux;
+ int ret;
+
+ ret = subchan_mux_out(mx, tx_buf, D_BCHAN_TX_GRAN);
+
+ if (ret != D_BCHAN_TX_GRAN) {
+ fprintf(stderr, "Huh, got ret of %d\n", ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ DEBUGP(DMIB, "BCHAN TX: %s\n",
+ hexdump(tx_buf, D_BCHAN_TX_GRAN));
+
+ if (invertbits) {
+ flip_buf_bits(tx_buf, ret);
+ }
+
+ ret = write(bfd->fd, tx_buf, ret);
+ if (ret < D_BCHAN_TX_GRAN)
+ fprintf(stderr, "send returns %d instead of %d\n", ret,
+ D_BCHAN_TX_GRAN);
+
+ return ret;
+}
+
+#define D_TSX_ALLOC_SIZE (D_BCHAN_TX_GRAN)
+/* FIXME: read from a B channel TS */
+static int handle_tsX_read(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct msgb *msg = msgb_alloc(D_TSX_ALLOC_SIZE, "DAHDI TSx");
+ int ret;
+
+ if (!msg)
+ return -ENOMEM;
+
+ ret = read(bfd->fd, msg->data, D_TSX_ALLOC_SIZE);
+ if (ret < 0 || ret != D_TSX_ALLOC_SIZE) {
+ fprintf(stderr, "read error %d %s\n", ret, strerror(errno));
+ return ret;
+ }
+
+ if (invertbits) {
+ flip_buf_bits(msg->data, ret);
+ }
+
+ msgb_put(msg, ret);
+
+ msg->l2h = msg->data;
+ DEBUGP(DMIB, "BCHAN RX: %s\n",
+ hexdump(msgb_l2(msg), ret));
+ ret = e1inp_rx_ts(e1i_ts, msg, 0, 0);
+ /* physical layer indicates that data has been sent,
+ * we thus can send some more data */
+ ret = handle_tsX_write(bfd);
+ msgb_free(msg);
+
+ return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int dahdi_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ unsigned int idx = ts_nr-1;
+ struct e1inp_ts *e1i_ts = &line->ts[idx];
+ int rc = 0;
+
+ switch (e1i_ts->type) {
+ case E1INP_TS_TYPE_SIGN:
+ if (what & BSC_FD_EXCEPT)
+ handle_dahdi_exception(e1i_ts);
+ if (what & BSC_FD_READ)
+ rc = handle_ts1_read(bfd);
+ if (what & BSC_FD_WRITE)
+ rc = handle_ts1_write(bfd);
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ if (what & BSC_FD_EXCEPT)
+ handle_dahdi_exception(e1i_ts);
+ if (what & BSC_FD_READ)
+ rc = handle_tsX_read(bfd);
+ if (what & BSC_FD_WRITE)
+ rc = handle_tsX_write(bfd);
+ /* We never include the DAHDI B-Channel FD into the
+ * writeset, since it doesn't support poll() based
+ * write flow control */
+ break;
+ default:
+ fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type);
+ break;
+ }
+
+ return rc;
+}
+
+static int dahdi_e1_line_update(struct e1inp_line *line);
+
+struct e1inp_driver dahdi_driver = {
+ .name = "dahdi",
+ .want_write = ts_want_write,
+ .line_update = &dahdi_e1_line_update,
+};
+
+void dahdi_set_bufinfo(int fd, int as_sigchan)
+{
+ struct dahdi_bufferinfo bi;
+ int x = 0;
+
+ if (ioctl(fd, DAHDI_GET_BUFINFO, &bi)) {
+ fprintf(stderr, "Error getting bufinfo\n");
+ exit(-1);
+ }
+
+ if (as_sigchan) {
+ bi.numbufs = 4;
+ bi.bufsize = 512;
+ } else {
+ bi.numbufs = 8;
+ bi.bufsize = D_BCHAN_TX_GRAN;
+ bi.txbufpolicy = DAHDI_POLICY_WHEN_FULL;
+ }
+
+ if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
+ fprintf(stderr, "Error setting bufinfo\n");
+ exit(-1);
+ }
+
+ if (!as_sigchan) {
+ if (ioctl(fd, DAHDI_AUDIOMODE, &x)) {
+ fprintf(stderr, "Error setting bufinfo\n");
+ exit(-1);
+ }
+ } else {
+ int one = 1;
+ ioctl(fd, DAHDI_HDLCFCSMODE, &one);
+ /* we cannot reliably check for the ioctl return value here
+ * as this command will fail if the slot _already_ was a
+ * signalling slot before :( */
+ }
+}
+
+static int dahdi_e1_setup(struct e1inp_line *line)
+{
+ int ts, ret;
+
+ /* TS0 is CRC4, don't need any fd for it */
+ for (ts = 1; ts < NUM_E1_TS; ts++) {
+ unsigned int idx = ts-1;
+ char openstr[128];
+ struct e1inp_ts *e1i_ts = &line->ts[idx];
+ struct bsc_fd *bfd = &e1i_ts->driver.dahdi.fd;
+
+ bfd->data = line;
+ bfd->priv_nr = ts;
+ bfd->cb = dahdi_fd_cb;
+ snprintf(openstr, sizeof(openstr), "/dev/dahdi/%d", ts);
+
+ switch (e1i_ts->type) {
+ case E1INP_TS_TYPE_NONE:
+ continue;
+ break;
+ case E1INP_TS_TYPE_SIGN:
+ bfd->fd = open(openstr, O_RDWR | O_NONBLOCK);
+ if (bfd->fd == -1) {
+ fprintf(stderr, "%s could not open %s %s\n",
+ __func__, openstr, strerror(errno));
+ exit(-1);
+ }
+ bfd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+ dahdi_set_bufinfo(bfd->fd, 1);
+ e1i_ts->driver.dahdi.lapd = lapd_instance_alloc(1, dahdi_write_msg, bfd);
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ bfd->fd = open(openstr, O_RDWR | O_NONBLOCK);
+ if (bfd->fd == -1) {
+ fprintf(stderr, "%s could not open %s %s\n",
+ __func__, openstr, strerror(errno));
+ exit(-1);
+ }
+ dahdi_set_bufinfo(bfd->fd, 0);
+ /* We never include the DAHDI B-Channel FD into the
+ * writeset, since it doesn't support poll() based
+ * write flow control */
+ bfd->when = BSC_FD_READ | BSC_FD_EXCEPT;// | BSC_FD_WRITE;
+ break;
+ }
+
+ if (bfd->fd < 0) {
+ fprintf(stderr, "%s could not open %s %s\n",
+ __func__, openstr, strerror(errno));
+ return bfd->fd;
+ }
+
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ fprintf(stderr, "could not register FD: %s\n",
+ strerror(ret));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int dahdi_e1_line_update(struct e1inp_line *line)
+{
+ if (line->driver != &dahdi_driver)
+ return -EINVAL;
+
+ return dahdi_e1_setup(line);
+}
+
+int e1inp_dahdi_init(void)
+{
+ init_flip_bits();
+
+ /* register the driver with the core */
+ return e1inp_driver_register(&dahdi_driver);
+}
+
+#endif /* HAVE_DAHDI_USER_H */
diff --git a/src/libabis/input/hsl.c b/src/libabis/input/hsl.c
new file mode 100644
index 00000000..1afe82b4
--- /dev/null
+++ b/src/libabis/input/hsl.c
@@ -0,0 +1,460 @@
+/* OpenBSC Abis input driver for HSL Femto */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (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/>.
+ *
+ */
+
+/* HSL uses a much more primitive/simplified version of the IPA multiplex.
+ *
+ * They have taken out the nice parts like the ID_GET / ID_RESP for resolving
+ * the UNIT ID, as well as the keepalive ping/pong messages. Furthermore, the
+ * Stream Identifiers are fixed on the BTS side (RSL always 0, OML always 0xff)
+ * and both OML+RSL share a single TCP connection.
+ *
+ * Other oddities include the encapsulation of BSSGP messages in the L3_INFO IE
+ * of RSL
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+
+#define HSL_TCP_PORT 2500
+#define HSL_PROTO_DEBUG 0xdd
+
+#define PRIV_OML 1
+#define PRIV_RSL 2
+
+/* data structure for one E1 interface with A-bis */
+struct hsl_e1_handle {
+ struct bsc_fd listen_fd;
+ struct gsm_network *gsmnet;
+};
+
+static struct hsl_e1_handle *e1h;
+
+
+#define TS1_ALLOC_SIZE 900
+
+#define OML_UP 0x0001
+#define RSL_UP 0x0002
+
+int hsl_drop_oml(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ struct e1inp_ts *ts;
+ struct e1inp_line *line;
+ struct bsc_fd *bfd;
+
+ if (!bts || !bts->oml_link)
+ return -1;
+
+ /* send OML down */
+ ts = bts->oml_link->ts;
+ line = ts->line;
+ e1inp_event(ts, S_INP_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi);
+
+ bfd = &ts->driver.ipaccess.fd;
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+
+ /* clean up OML and RSL */
+ e1inp_sign_link_destroy(bts->oml_link);
+ bts->oml_link = NULL;
+ e1inp_sign_link_destroy(bts->c0->rsl_link);
+ bts->c0->rsl_link = NULL;
+ bts->ip_access.flags = 0;
+
+ /* kill the E1 line now... as we have no one left to use it */
+ talloc_free(line);
+
+ return -1;
+}
+
+static int hsl_drop_ts_fd(struct e1inp_ts *ts, struct bsc_fd *bfd)
+{
+ struct e1inp_sign_link *link, *link2;
+ int bts_nr = -1;
+
+ llist_for_each_entry_safe(link, link2, &ts->sign.sign_links, list) {
+ bts_nr = link->trx->bts->bts_nr;
+ e1inp_sign_link_destroy(link);
+ }
+
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+
+ talloc_free(ts->line);
+
+ return bts_nr;
+}
+
+struct gsm_bts *find_bts_by_serno(struct gsm_network *net, unsigned long serno)
+{
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (bts->type != GSM_BTS_TYPE_HSL_FEMTO)
+ continue;
+
+ if (serno == bts->hsl.serno)
+ return bts;
+ }
+
+ return NULL;
+}
+
+
+static int process_hsl_rsl(struct msgb *msg, struct e1inp_line *line)
+{
+ char serno_buf[16];
+ uint8_t serno_len;
+ unsigned long serno;
+ struct gsm_bts *bts;
+
+ switch (msg->l2h[1]) {
+ case 0x80:
+ /*, contains Serial Number + SW version */
+ if (msg->l2h[2] != 0xc0)
+ break;
+ serno_len = msg->l2h[3];
+ if (serno_len > sizeof(serno_buf)-1)
+ serno_len = sizeof(serno_buf)-1;
+ memcpy(serno_buf, msg->l2h+4, serno_len);
+ serno_buf[serno_len] = '\0';
+ serno = strtoul(serno_buf, NULL, 10);
+ bts = find_bts_by_serno(e1h->gsmnet, serno);
+ if (!bts) {
+ LOGP(DINP, LOGL_ERROR, "Unable to find BTS config for "
+ "serial number %lu(%s)\n", serno, serno_buf);
+ return -EIO;
+ }
+
+ DEBUGP(DINP, "Identified HSL BTS Serial Number %lu\n", serno);
+
+ /* we shouldn't hardcode it, but HSL femto also hardcodes it... */
+ bts->oml_tei = 255;
+ bts->c0->rsl_tei = 0;
+ bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+ E1INP_SIGN_OML, bts->c0,
+ bts->oml_tei, 0);
+ bts->c0->rsl_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+ E1INP_SIGN_RSL, bts->c0,
+ bts->c0->rsl_tei, 0);
+ e1inp_event(&line->ts[PRIV_OML-1], S_INP_TEI_UP, 255, 0);
+ e1inp_event(&line->ts[PRIV_OML-1], S_INP_TEI_UP, 0, 0);
+ bts->ip_access.flags |= OML_UP;
+ bts->ip_access.flags |= (RSL_UP << 0);
+ msgb_free(msg);
+ return 1; /* == we have taken over the msg */
+ case 0x82:
+ /* FIXME: do something with BSSGP, i.e. forward it over
+ * NSIP to OsmoSGSN */
+ msgb_free(msg);
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *link;
+ struct msgb *msg;
+ struct ipaccess_head *hh;
+ int ret = 0, error;
+
+ msg = ipaccess_read_msg(bfd, &error);
+ if (!msg) {
+ if (error == 0) {
+ int ret = hsl_drop_ts_fd(e1i_ts, bfd);
+ if (ret >= 0)
+ LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n",
+ ret);
+ else
+ LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n");
+ }
+ return error;
+ }
+
+ DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+ hh = (struct ipaccess_head *) msg->data;
+ if (hh->proto == HSL_PROTO_DEBUG) {
+ LOGP(DINP, LOGL_NOTICE, "HSL debug: %s\n", msg->data + sizeof(*hh));
+ msgb_free(msg);
+ return ret;
+ }
+
+ /* HSL proprietary RSL extension */
+ if (hh->proto == 0 && (msg->l2h[0] == 0x81 || msg->l2h[0] == 0x80)) {
+ ret = process_hsl_rsl(msg, line);
+ if (ret < 0) {
+ /* FIXME: close connection */
+ hsl_drop_ts_fd(e1i_ts, bfd);
+ return ret;
+ } else if (ret == 1)
+ return 0;
+ /* else: continue... */
+ }
+#ifdef HSL_SR_1_0
+ /* HSL for whatever reason chose to use 0x81 instead of 0x80 for FOM */
+ if (hh->proto == 255 && msg->l2h[0] == (ABIS_OM_MDISC_FOM | 0x01))
+ msg->l2h[0] = ABIS_OM_MDISC_FOM;
+#endif
+ link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0);
+ if (!link) {
+ LOGP(DINP, LOGL_ERROR, "no matching signalling link for "
+ "hh->proto=0x%02x\n", hh->proto);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->trx = link->trx;
+
+ switch (link->type) {
+ case E1INP_SIGN_RSL:
+ if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) {
+ e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+ msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr);
+ }
+ ret = abis_rsl_rcvmsg(msg);
+ break;
+ case E1INP_SIGN_OML:
+ if (!(msg->trx->bts->ip_access.flags & OML_UP)) {
+ e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+ msg->trx->bts->ip_access.flags |= OML_UP;
+ }
+ ret = abis_nm_rcvmsg(msg);
+ break;
+ default:
+ LOGP(DINP, LOGL_NOTICE, "Unknown HSL protocol class 0x%02x\n", hh->proto);
+ msgb_free(msg);
+ break;
+ }
+ return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+ e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+ struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+ /* trigger write of ts1, due to tx delay timer */
+ ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *sign_link;
+ struct msgb *msg;
+ u_int8_t proto;
+ int ret;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* get the next msg for this timeslot */
+ msg = e1inp_tx_ts(e1i_ts, &sign_link);
+ if (!msg) {
+ /* no message after tx delay timer */
+ return 0;
+ }
+
+ switch (sign_link->type) {
+ case E1INP_SIGN_OML:
+ proto = IPAC_PROTO_OML;
+#ifdef HSL_SR_1_0
+ /* HSL uses 0x81 for FOM for some reason */
+ if (msg->data[0] == ABIS_OM_MDISC_FOM)
+ msg->data[0] = ABIS_OM_MDISC_FOM | 0x01;
+#endif
+ break;
+ case E1INP_SIGN_RSL:
+ proto = IPAC_PROTO_RSL;
+ break;
+ default:
+ msgb_free(msg);
+ bfd->when |= BSC_FD_WRITE; /* come back for more msg */
+ return -EINVAL;
+ }
+
+ msg->l2h = msg->data;
+ ipaccess_prepend_header(msg, sign_link->tei);
+
+ DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg)));
+
+ ret = send(bfd->fd, msg->data, msg->len, 0);
+ msgb_free(msg);
+
+ /* set tx delay timer for next event */
+ e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+ e1i_ts->sign.tx_timer.data = e1i_ts;
+
+ /* Reducing this might break the nanoBTS 900 init. */
+ bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay);
+
+ return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int hsl_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ unsigned int idx = ts_nr-1;
+ struct e1inp_ts *e1i_ts;
+ int rc = 0;
+
+ /* In case of early RSL we might not yet have a line */
+
+ if (line)
+ e1i_ts = &line->ts[idx];
+
+ if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+ if (what & BSC_FD_READ)
+ rc = handle_ts1_read(bfd);
+ if (what & BSC_FD_WRITE)
+ rc = handle_ts1_write(bfd);
+ } else
+ LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type);
+
+ return rc;
+}
+
+struct e1inp_driver hsl_driver = {
+ .name = "HSL",
+ .want_write = ts_want_write,
+ .default_delay = 0,
+};
+
+/* callback of the OML listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+ int ret;
+ int idx = 0;
+ int i;
+ struct e1inp_line *line;
+ struct e1inp_ts *e1i_ts;
+ struct bsc_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;
+ }
+ LOGP(DINP, LOGL_NOTICE, "accept()ed new HSL link from %s\n",
+ inet_ntoa(sa.sin_addr));
+
+ line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+ if (!line) {
+ close(ret);
+ return -ENOMEM;
+ }
+ line->driver = &hsl_driver;
+ //line->driver_data = e1h;
+ /* create virrtual E1 timeslots for signalling */
+ e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+
+ /* initialize the fds */
+ for (i = 0; i < ARRAY_SIZE(line->ts); ++i)
+ line->ts[i].driver.ipaccess.fd.fd = -1;
+
+ e1i_ts = &line->ts[idx];
+
+ bfd = &e1i_ts->driver.ipaccess.fd;
+ bfd->fd = ret;
+ bfd->data = line;
+ bfd->priv_nr = PRIV_OML;
+ bfd->cb = hsl_fd_cb;
+ bfd->when = BSC_FD_READ;
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+ close(bfd->fd);
+ talloc_free(line);
+ return ret;
+ }
+
+ return ret;
+ //return e1inp_line_register(line);
+}
+
+int hsl_setup(struct gsm_network *gsmnet)
+{
+ int ret;
+
+ /* register the driver with the core */
+ /* FIXME: do this in the plugin initializer function */
+ ret = e1inp_driver_register(&hsl_driver);
+ if (ret)
+ return ret;
+
+ e1h = talloc_zero(tall_bsc_ctx, struct hsl_e1_handle);
+ if (!e1h)
+ return -ENOMEM;
+
+ e1h->gsmnet = gsmnet;
+
+ /* Listen for connections */
+ ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, 0, HSL_TCP_PORT,
+ listen_fd_cb);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
diff --git a/src/libabis/input/ipaccess.c b/src/libabis/input/ipaccess.c
new file mode 100644
index 00000000..dcf8d1a5
--- /dev/null
+++ b/src/libabis/input/ipaccess.c
@@ -0,0 +1,797 @@
+/* OpenBSC 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/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+#include <openbsc/signal.h>
+
+#define PRIV_OML 1
+#define PRIV_RSL 2
+
+/* data structure for one E1 interface with A-bis */
+struct ia_e1_handle {
+ struct bsc_fd listen_fd;
+ struct bsc_fd rsl_listen_fd;
+ struct gsm_network *gsmnet;
+};
+
+static struct ia_e1_handle *e1h;
+
+
+#define TS1_ALLOC_SIZE 900
+
+static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG };
+static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK };
+static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, 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,
+ };
+
+static const char *idtag_names[] = {
+ [IPAC_IDTAG_SERNR] = "Serial_Number",
+ [IPAC_IDTAG_UNITNAME] = "Unit_Name",
+ [IPAC_IDTAG_LOCATION1] = "Location_1",
+ [IPAC_IDTAG_LOCATION2] = "Location_2",
+ [IPAC_IDTAG_EQUIPVERS] = "Equipment_Version",
+ [IPAC_IDTAG_SWVERSION] = "Software_Version",
+ [IPAC_IDTAG_IPADDR] = "IP_Address",
+ [IPAC_IDTAG_MACADDR] = "MAC_Address",
+ [IPAC_IDTAG_UNIT] = "Unit_ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+ if (tag >= ARRAY_SIZE(idtag_names))
+ return "unknown";
+
+ return idtag_names[tag];
+}
+
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+ u_int8_t t_len;
+ u_int8_t t_tag;
+ u_int8_t *cur = buf;
+
+ memset(dec, 0, sizeof(*dec));
+
+ while (len >= 2) {
+ len -= 2;
+ t_len = *cur++;
+ t_tag = *cur++;
+
+ if (t_len > len + 1) {
+ LOGP(DMI, LOGL_ERROR, "The tag does not fit: %d\n", t_len);
+ return -1;
+ }
+
+ DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur);
+
+ dec->lv[t_tag].len = t_len;
+ dec->lv[t_tag].val = cur;
+
+ cur += t_len;
+ len -= t_len;
+ }
+ return 0;
+}
+
+struct gsm_bts *find_bts_by_unitid(struct gsm_network *net,
+ u_int16_t site_id, u_int16_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;
+}
+
+static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id,
+ u_int16_t *trx_id)
+{
+ unsigned long ul;
+ char *endptr;
+ const char *nptr;
+
+ nptr = str;
+ ul = strtoul(nptr, &endptr, 10);
+ if (endptr <= nptr)
+ return -EINVAL;
+ if (site_id)
+ *site_id = ul & 0xffff;
+
+ if (*endptr++ != '/')
+ return -EINVAL;
+
+ nptr = endptr;
+ ul = strtoul(nptr, &endptr, 10);
+ if (endptr <= nptr)
+ return -EINVAL;
+ if (bts_id)
+ *bts_id = ul & 0xffff;
+
+ if (*endptr++ != '/')
+ return -EINVAL;
+
+ nptr = endptr;
+ ul = strtoul(nptr, &endptr, 10);
+ if (endptr <= nptr)
+ return -EINVAL;
+ if (trx_id)
+ *trx_id = ul & 0xffff;
+
+ return 0;
+}
+
+/* send the id ack */
+int ipaccess_send_id_ack(int fd)
+{
+ return write(fd, id_ack, sizeof(id_ack));
+}
+
+int ipaccess_send_id_req(int fd)
+{
+ return write(fd, id_req, sizeof(id_req));
+}
+
+/* base handling of the ip.access protocol */
+int ipaccess_rcvmsg_base(struct msgb *msg,
+ struct bsc_fd *bfd)
+{
+ u_int8_t msg_type = *(msg->l2h);
+ int ret = 0;
+
+ switch (msg_type) {
+ case IPAC_MSGT_PING:
+ ret = write(bfd->fd, pong, sizeof(pong));
+ break;
+ case IPAC_MSGT_PONG:
+ DEBUGP(DMI, "PONG!\n");
+ break;
+ case IPAC_MSGT_ID_ACK:
+ DEBUGP(DMI, "ID_ACK? -> ACK!\n");
+ ret = ipaccess_send_id_ack(bfd->fd);
+ break;
+ }
+ return 0;
+}
+
+static int ipaccess_rcvmsg(struct e1inp_line *line, struct msgb *msg,
+ struct bsc_fd *bfd)
+{
+ struct tlv_parsed tlvp;
+ u_int8_t msg_type = *(msg->l2h);
+ u_int16_t site_id = 0, bts_id = 0, trx_id = 0;
+ struct gsm_bts *bts;
+ char *unitid;
+ int len;
+
+ /* handle base messages */
+ ipaccess_rcvmsg_base(msg, bfd);
+
+ switch (msg_type) {
+ case IPAC_MSGT_ID_RESP:
+ DEBUGP(DMI, "ID_RESP ");
+ /* parse tags, search for Unit ID */
+ ipaccess_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2,
+ msgb_l2len(msg)-2);
+ DEBUGP(DMI, "\n");
+
+ if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT))
+ break;
+
+ len = TLVP_LEN(&tlvp, IPAC_IDTAG_UNIT);
+ if (len < 1)
+ break;
+
+ /* lookup BTS, create sign_link, ... */
+ unitid = (char *) TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT);
+ unitid[len - 1] = '\0';
+ parse_unitid(unitid, &site_id, &bts_id, &trx_id);
+ bts = find_bts_by_unitid(e1h->gsmnet, site_id, bts_id);
+ if (!bts) {
+ LOGP(DINP, LOGL_ERROR, "Unable to find BTS configuration for "
+ " %u/%u/%u, disconnecting\n", site_id, bts_id,
+ trx_id);
+ return -EIO;
+ }
+ DEBUGP(DINP, "Identified BTS %u/%u/%u\n", site_id, bts_id, trx_id);
+ if (bfd->priv_nr == PRIV_OML) {
+ /* drop any old oml connection */
+ ipaccess_drop_oml(bts);
+ bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+ E1INP_SIGN_OML, bts->c0,
+ bts->oml_tei, 0);
+ } else if (bfd->priv_nr == PRIV_RSL) {
+ struct e1inp_ts *e1i_ts;
+ struct bsc_fd *newbfd;
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_id);
+
+ /* drop any old rsl connection */
+ ipaccess_drop_rsl(trx);
+
+ if (!bts->oml_link) {
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+ talloc_free(bfd);
+ return 0;
+ }
+
+ bfd->data = line = bts->oml_link->ts->line;
+ e1i_ts = &line->ts[PRIV_RSL + trx_id - 1];
+ newbfd = &e1i_ts->driver.ipaccess.fd;
+ e1inp_ts_config(e1i_ts, line, E1INP_TS_TYPE_SIGN);
+
+ trx->rsl_link = e1inp_sign_link_create(e1i_ts,
+ E1INP_SIGN_RSL, trx,
+ trx->rsl_tei, 0);
+ trx->rsl_link->ts->sign.delay = 0;
+
+ /* get rid of our old temporary bfd */
+ memcpy(newbfd, bfd, sizeof(*newbfd));
+ newbfd->priv_nr = PRIV_RSL + trx_id;
+ bsc_unregister_fd(bfd);
+ bfd->fd = -1;
+ talloc_free(bfd);
+ bsc_register_fd(newbfd);
+ }
+ break;
+ }
+ return 0;
+}
+
+#define OML_UP 0x0001
+#define RSL_UP 0x0002
+
+/*
+ * read one ipa message from the socket
+ * return NULL in case of error
+ */
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error)
+{
+ struct msgb *msg = msgb_alloc(TS1_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, sizeof(*hh), 0);
+ if (ret == 0) {
+ msgb_free(msg);
+ *error = ret;
+ return NULL;
+ } else if (ret != sizeof(*hh)) {
+ if (errno != EAGAIN)
+ LOGP(DINP, LOGL_ERROR, "recv error %d %s\n", ret, strerror(errno));
+ 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);
+
+ if (len < 0 || TS1_ALLOC_SIZE < len + sizeof(*hh)) {
+ LOGP(DINP, LOGL_ERROR, "Can not read this packet. %d avail\n", len);
+ msgb_free(msg);
+ *error = -EIO;
+ return NULL;
+ }
+
+ ret = recv(bfd->fd, msg->l2h, len, 0);
+ if (ret < len) {
+ LOGP(DINP, LOGL_ERROR, "short read! Got %d from %d\n", ret, len);
+ msgb_free(msg);
+ *error = -EIO;
+ return NULL;
+ }
+ msgb_put(msg, ret);
+
+ return msg;
+}
+
+int ipaccess_drop_oml(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ struct e1inp_ts *ts;
+ struct e1inp_line *line;
+ struct bsc_fd *bfd;
+
+ if (!bts || !bts->oml_link)
+ return -1;
+
+ /* send OML down */
+ ts = bts->oml_link->ts;
+ line = ts->line;
+ e1inp_event(ts, S_INP_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi);
+
+ bfd = &ts->driver.ipaccess.fd;
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+
+ /* clean up OML and RSL */
+ e1inp_sign_link_destroy(bts->oml_link);
+ bts->oml_link = NULL;
+ bts->ip_access.flags = 0;
+
+ /* drop all RSL connections too */
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ ipaccess_drop_rsl(trx);
+
+ /* kill the E1 line now... as we have no one left to use it */
+ talloc_free(line);
+
+ return -1;
+}
+
+static int ipaccess_drop(struct e1inp_ts *ts, struct bsc_fd *bfd)
+{
+ struct e1inp_sign_link *link;
+ int bts_nr;
+
+ if (!ts) {
+ /*
+ * If we don't have a TS this means that this is a RSL
+ * connection but we are not past the authentication
+ * handling yet. So we can safely delete this bfd and
+ * wait for a reconnect.
+ */
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+ talloc_free(bfd);
+ return -1;
+ }
+
+ /* attempt to find a signalling link */
+ if (ts->type == E1INP_TS_TYPE_SIGN) {
+ llist_for_each_entry(link, &ts->sign.sign_links, list) {
+ bts_nr = link->trx->bts->bts_nr;
+ /* we have issues just reconnecting RLS so we drop OML */
+ ipaccess_drop_oml(link->trx->bts);
+ return bts_nr;
+ }
+ }
+
+ /* error case */
+ LOGP(DINP, LOGL_ERROR, "Failed to find a signalling link for ts: %p\n", ts);
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+ return -1;
+}
+
+int ipaccess_drop_rsl(struct gsm_bts_trx *trx)
+{
+ struct bsc_fd *bfd;
+ struct e1inp_ts *ts;
+
+ if (!trx || !trx->rsl_link)
+ return -1;
+
+ /* send RSL down */
+ ts = trx->rsl_link->ts;
+ e1inp_event(ts, S_INP_TEI_DN, trx->rsl_link->tei, trx->rsl_link->sapi);
+
+ /* close the socket */
+ bfd = &ts->driver.ipaccess.fd;
+ bsc_unregister_fd(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+
+ /* destroy */
+ e1inp_sign_link_destroy(trx->rsl_link);
+ trx->rsl_link = NULL;
+
+ return -1;
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *link;
+ struct msgb *msg;
+ struct ipaccess_head *hh;
+ int ret = 0, error;
+
+ msg = ipaccess_read_msg(bfd, &error);
+ if (!msg) {
+ if (error == 0) {
+ int ret = ipaccess_drop(e1i_ts, bfd);
+ if (ret >= 0)
+ LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n",
+ ret);
+ else
+ LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n");
+ }
+ return error;
+ }
+
+ DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+ hh = (struct ipaccess_head *) msg->data;
+ if (hh->proto == IPAC_PROTO_IPACCESS) {
+ ret = ipaccess_rcvmsg(line, msg, bfd);
+ if (ret < 0)
+ ipaccess_drop(e1i_ts, bfd);
+ msgb_free(msg);
+ return ret;
+ }
+ /* BIG FAT WARNING: bfd might no longer exist here, since ipaccess_rcvmsg()
+ * might have free'd it !!! */
+
+ link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0);
+ if (!link) {
+ LOGP(DINP, LOGL_ERROR, "no matching signalling link for "
+ "hh->proto=0x%02x\n", hh->proto);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->trx = link->trx;
+
+ switch (link->type) {
+ case E1INP_SIGN_RSL:
+ if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) {
+ e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+ msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr);
+ }
+ ret = abis_rsl_rcvmsg(msg);
+ break;
+ case E1INP_SIGN_OML:
+ if (!(msg->trx->bts->ip_access.flags & OML_UP)) {
+ e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+ msg->trx->bts->ip_access.flags |= OML_UP;
+ }
+ ret = abis_nm_rcvmsg(msg);
+ break;
+ default:
+ LOGP(DINP, LOGL_NOTICE, "Unknown IP.access protocol proto=0x%02x\n", hh->proto);
+ msgb_free(msg);
+ break;
+ }
+ return ret;
+}
+
+void ipaccess_prepend_header(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh));
+ hh->len = htons(msg->len - sizeof(*hh));
+ hh->proto = proto;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+ e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+ struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+ /* trigger write of ts1, due to tx delay timer */
+ ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *sign_link;
+ struct msgb *msg;
+ u_int8_t proto;
+ int ret;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* get the next msg for this timeslot */
+ msg = e1inp_tx_ts(e1i_ts, &sign_link);
+ if (!msg) {
+ /* no message after tx delay timer */
+ return 0;
+ }
+
+ switch (sign_link->type) {
+ case E1INP_SIGN_OML:
+ proto = IPAC_PROTO_OML;
+ break;
+ case E1INP_SIGN_RSL:
+ proto = IPAC_PROTO_RSL;
+ break;
+ default:
+ msgb_free(msg);
+ bfd->when |= BSC_FD_WRITE; /* come back for more msg */
+ return -EINVAL;
+ }
+
+ msg->l2h = msg->data;
+ ipaccess_prepend_header(msg, sign_link->tei);
+
+ DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg)));
+
+ ret = send(bfd->fd, msg->data, msg->len, 0);
+ msgb_free(msg);
+
+ /* set tx delay timer for next event */
+ e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+ e1i_ts->sign.tx_timer.data = e1i_ts;
+
+ /* Reducing this might break the nanoBTS 900 init. */
+ bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay);
+
+ return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ unsigned int idx = ts_nr-1;
+ struct e1inp_ts *e1i_ts;
+ int rc = 0;
+
+ /* In case of early RSL we might not yet have a line */
+
+ if (line)
+ e1i_ts = &line->ts[idx];
+
+ if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+ if (what & BSC_FD_READ)
+ rc = handle_ts1_read(bfd);
+ if (what & BSC_FD_WRITE)
+ rc = handle_ts1_write(bfd);
+ } else
+ LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type);
+
+ return rc;
+}
+
+struct e1inp_driver ipaccess_driver = {
+ .name = "ip.access",
+ .want_write = ts_want_write,
+ .default_delay = 0,
+};
+
+/* callback of the OML listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+ int ret;
+ int idx = 0;
+ int i;
+ struct e1inp_line *line;
+ struct e1inp_ts *e1i_ts;
+ struct bsc_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;
+ }
+ LOGP(DINP, LOGL_NOTICE, "accept()ed new OML link from %s\n",
+ inet_ntoa(sa.sin_addr));
+
+ line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+ if (!line) {
+ close(ret);
+ return -ENOMEM;
+ }
+ line->driver = &ipaccess_driver;
+ //line->driver_data = e1h;
+ /* create virrtual E1 timeslots for signalling */
+ e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+
+ /* initialize the fds */
+ for (i = 0; i < ARRAY_SIZE(line->ts); ++i)
+ line->ts[i].driver.ipaccess.fd.fd = -1;
+
+ e1i_ts = &line->ts[idx];
+
+ bfd = &e1i_ts->driver.ipaccess.fd;
+ bfd->fd = ret;
+ bfd->data = line;
+ bfd->priv_nr = PRIV_OML;
+ bfd->cb = ipaccess_fd_cb;
+ bfd->when = BSC_FD_READ;
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+ close(bfd->fd);
+ talloc_free(line);
+ return ret;
+ }
+
+ /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+ ret = ipaccess_send_id_req(bfd->fd);
+
+ return ret;
+ //return e1inp_line_register(line);
+}
+
+static int rsl_listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+ struct sockaddr_in sa;
+ socklen_t sa_len = sizeof(sa);
+ struct bsc_fd *bfd;
+ int ret;
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ bfd = talloc_zero(tall_bsc_ctx, struct bsc_fd);
+ if (!bfd)
+ return -ENOMEM;
+
+ /* Some BTS has connected to us, but we don't know yet which line
+ * (as created by the OML link) to associate it with. Thus, we
+ * allocate a temporary bfd until we have received ID from BTS */
+
+ bfd->fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+ if (bfd->fd < 0) {
+ perror("accept");
+ return bfd->fd;
+ }
+ LOGP(DINP, LOGL_NOTICE, "accept()ed new RSL link from %s\n", inet_ntoa(sa.sin_addr));
+ bfd->priv_nr = PRIV_RSL;
+ bfd->cb = ipaccess_fd_cb;
+ bfd->when = BSC_FD_READ;
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+ close(bfd->fd);
+ talloc_free(bfd);
+ return ret;
+ }
+ /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+ ret = write(bfd->fd, id_req, sizeof(id_req));
+
+ return 0;
+}
+
+/* Actively connect to a BTS. Currently used by ipaccess-config.c */
+int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
+{
+ struct e1inp_ts *e1i_ts = &line->ts[0];
+ struct bsc_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 = PRIV_OML;
+
+ if (bfd->fd < 0) {
+ LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n");
+ return -EIO;
+ }
+
+ setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+ if (ret < 0) {
+ LOGP(DINP, LOGL_ERROR, "could not connect socket\n");
+ close(bfd->fd);
+ return ret;
+ }
+
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ close(bfd->fd);
+ return ret;
+ }
+
+ line->driver = &ipaccess_driver;
+
+ return ret;
+ //return e1inp_line_register(line);
+}
+
+int ipaccess_setup(struct gsm_network *gsmnet)
+{
+ int ret;
+
+ /* register the driver with the core */
+ /* FIXME: do this in the plugin initializer function */
+ ret = e1inp_driver_register(&ipaccess_driver);
+ if (ret)
+ return ret;
+
+ e1h = talloc_zero(tall_bsc_ctx, struct ia_e1_handle);
+ if (!e1h)
+ return -ENOMEM;
+
+ e1h->gsmnet = gsmnet;
+
+ /* Listen for OML connections */
+ ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, 0, IPA_TCP_PORT_OML,
+ listen_fd_cb);
+ if (ret < 0)
+ return ret;
+
+ /* Listen for RSL connections */
+ ret = make_sock(&e1h->rsl_listen_fd, IPPROTO_TCP, 0,
+ IPA_TCP_PORT_RSL, rsl_listen_fd_cb);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
diff --git a/src/libabis/input/lapd.c b/src/libabis/input/lapd.c
new file mode 100644
index 00000000..7bce6cc5
--- /dev/null
+++ b/src/libabis/input/lapd.c
@@ -0,0 +1,710 @@
+/* OpenBSC minimal LAPD implementation */
+
+/* (C) 2009 by oystein@homelien.no
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
+ * (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 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.
+ *
+ */
+
+/* TODO:
+ * detect RR timeout and set SAP state back to SABM_RETRANSMIT
+ * use of value_string
+ * further code cleanup (spaghetti)
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "lapd.h"
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/msgb.h>
+#include <osmocore/timer.h>
+#include <openbsc/debug.h>
+
+#define SABM_INTERVAL 0, 300000
+
+typedef enum {
+ LAPD_TEI_NONE = 0,
+ LAPD_TEI_ASSIGNED,
+ LAPD_TEI_ACTIVE,
+} lapd_tei_state;
+
+const char *lapd_tei_states[] = {
+ "NONE",
+ "ASSIGNED",
+ "ACTIVE",
+};
+
+typedef enum {
+ LAPD_TYPE_NONE = 0,
+
+ LAPD_TYPE_I,
+ LAPD_TYPE_S,
+ LAPD_TYPE_U,
+} lapd_msg_type;
+
+typedef enum {
+ /* commands/responses */
+ LAPD_CMD_NONE = 0,
+
+ LAPD_CMD_I,
+ LAPD_CMD_RR,
+ LAPD_CMD_RNR,
+ LAPD_CMD_REJ,
+
+ LAPD_CMD_SABME,
+ LAPD_CMD_DM,
+ LAPD_CMD_UI,
+ LAPD_CMD_DISC,
+ LAPD_CMD_UA,
+ LAPD_CMD_FRMR,
+ LAPD_CMD_XID,
+} lapd_cmd_type;
+
+const char *lapd_cmd_types[] = {
+ "NONE",
+
+ "I",
+ "RR",
+ "RNR",
+ "REJ",
+
+ "SABME",
+ "DM",
+ "UI",
+ "DISC",
+ "UA",
+ "FRMR",
+ "XID",
+
+};
+
+enum lapd_sap_state {
+ SAP_STATE_INACTIVE,
+ SAP_STATE_SABM_RETRANS,
+ SAP_STATE_ACTIVE,
+};
+
+const char *lapd_sap_states[] = {
+ "INACTIVE",
+ "SABM_RETRANS",
+ "ACTIVE",
+};
+
+const char *lapd_msg_types = "?ISU";
+
+/* structure representing an allocated TEI within a LAPD instance */
+struct lapd_tei {
+ struct llist_head list;
+ struct lapd_instance *li;
+ uint8_t tei;
+ lapd_tei_state state;
+
+ struct llist_head sap_list;
+};
+
+/* Structure representing a SAP within a TEI. We use this for TE-mode to
+ * re-transmit SABM */
+struct lapd_sap {
+ struct llist_head list;
+ struct lapd_tei *tei;
+ uint8_t sapi;
+ enum lapd_sap_state state;
+
+ /* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */
+ int vs; /* next to be transmitted */
+ int va; /* last acked by peer */
+ int vr; /* next expected to be received */
+
+ struct timer_list sabme_timer; /* timer to re-transmit SABM message */
+};
+
+/* 3.5.2.2 Send state variable V(S)
+ * Each point-to-point data link connection endpoint shall have an associated V(S) when using I frame
+ * commands. V(S) denotes the sequence number of the next I frame to be transmitted. The V(S) can
+ * take on the value 0 through n minus 1. The value of V(S) shall be incremented by 1 with each
+ * successive I frame transmission, and shall not exceed V(A) by more than the maximum number of
+ * outstanding I frames k. The value of k may be in the range of 1 ≤ k ≤ 127.
+ *
+ * 3.5.2.3 Acknowledge state variable V(A)
+ * Each point-to-point data link connection endpoint shall have an associated V(A) when using I frame
+ * commands and supervisory frame commands/responses. V(A) identifies the last I frame that has been
+ * acknowledged by its peer [V(A) − 1 equals the N(S) of the last acknowledged I frame]. V(A) can
+ * take on the value 0 through n minus 1. The value of V(A) shall be updated by the valid N(R) values
+ * received from its peer (see 3.5.2.6). A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤
+ * V(S).
+ *
+ * 3.5.2.5 Receive state variable V(R)
+ * Each point-to-point data link connection endpoint shall have an associated V(R) when using I frame
+ * commands and supervisory frame commands/responses. V(R) denotes the sequence number of the
+ * next in-sequence I frame expected to be received. V(R) can take on the value 0 through n minus 1.
+ * The value of V(R) shall be incremented by one with the receipt of an error-free, in-sequence I frame
+ * whose N(S) equals V(R).
+ */
+#define LAPD_NS(sap) (sap->vs)
+#define LAPD_NR(sap) (sap->vr)
+
+/* 3.5.2.4 Send sequence number N(S)
+ * Only I frames contain N(S), the send sequence number of transmitted I frames. At the time that an in-
+ * sequence I frame is designated for transmission, the value of N(S) is set equal to V(S).
+ *
+ * 3.5.2.6 Receive sequence number N(R)
+ * All I frames and supervisory frames contain N(R), the expected send sequence number of the next
+ * received I frame. At the time that a frame of the above types is designated for transmission, the value
+ * of N(R) is set equal to V(R). N(R) indicates that the data link layer entity transmitting the N(R) has
+ * correctly received all I frames numbered up to and including N(R) − 1.
+ */
+
+/* Resolve TEI structure from given numeric TEI */
+static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei)
+{
+ struct lapd_tei *lt;
+
+ llist_for_each_entry(lt, &li->tei_list, list) {
+ if (lt->tei == tei)
+ return lt;
+ }
+ return NULL;
+};
+
+static void lapd_tei_set_state(struct lapd_tei *teip, int newstate)
+{
+ DEBUGP(DMI, "state change on TEI %d: %s -> %s\n", teip->tei,
+ lapd_tei_states[teip->state], lapd_tei_states[newstate]);
+ teip->state = newstate;
+};
+
+/* Allocate a new TEI */
+struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei)
+{
+ struct lapd_tei *teip;
+
+ teip = talloc_zero(li, struct lapd_tei);
+ if (!teip)
+ return NULL;
+
+ teip->li = li;
+ teip->tei = tei;
+ llist_add(&teip->list, &li->tei_list);
+ INIT_LLIST_HEAD(&teip->sap_list);
+
+ lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
+
+ return teip;
+}
+
+/* Find a SAP within a given TEI */
+static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi)
+{
+ struct lapd_sap *sap;
+
+ llist_for_each_entry(sap, &teip->sap_list, list) {
+ if (sap->sapi == sapi)
+ return sap;
+ }
+
+ return NULL;
+}
+
+static void sabme_timer_cb(void *_sap);
+
+/* Allocate a new SAP within a given TEI */
+static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi)
+{
+ struct lapd_sap *sap = talloc_zero(teip, struct lapd_sap);
+
+ LOGP(DMI, LOGL_INFO, "Allocating SAP for SAPI=%u / TEI=%u\n",
+ sapi, teip->tei);
+
+ sap->sapi = sapi;
+ sap->tei = teip;
+ sap->sabme_timer.cb = &sabme_timer_cb;
+ sap->sabme_timer.data = sap;
+
+ llist_add(&sap->list, &teip->sap_list);
+
+ return sap;
+}
+
+static void lapd_sap_set_state(struct lapd_tei *teip, uint8_t sapi,
+ enum lapd_sap_state newstate)
+{
+ struct lapd_sap *sap = lapd_sap_find(teip, sapi);
+ if (!sap)
+ return;
+
+ DEBUGP(DMI, "state change on TEI %u / SAPI %u: %s -> %s\n", teip->tei,
+ sapi, lapd_sap_states[sap->state], lapd_sap_states[newstate]);
+ switch (sap->state) {
+ case SAP_STATE_SABM_RETRANS:
+ if (newstate != SAP_STATE_SABM_RETRANS)
+ bsc_del_timer(&sap->sabme_timer);
+ break;
+ default:
+ if (newstate == SAP_STATE_SABM_RETRANS)
+ bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
+ break;
+ }
+
+ sap->state = newstate;
+};
+
+/* Input function into TEI manager */
+static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len)
+{
+ uint8_t entity = data[0];
+ uint8_t ref = data[1];
+ uint8_t mt = data[3];
+ uint8_t action = data[4] >> 1;
+ uint8_t e = data[4] & 1;
+ uint8_t resp[8];
+ struct lapd_tei *teip;
+
+ DEBUGP(DMI, "TEIMGR: entity %x, ref %x, mt %x, action %x, e %x\n", entity, ref, mt, action, e);
+
+ switch (mt) {
+ case 0x01: /* IDENTITY REQUEST */
+ DEBUGP(DMI, "TEIMGR: identity request for TEI %u\n", action);
+
+ teip = teip_from_tei(li, action);
+ if (!teip) {
+ LOGP(DMI, LOGL_INFO, "TEI MGR: New TEI %u\n", action);
+ lapd_tei_alloc(li, action);
+ }
+
+ /* Send ACCEPT */
+ memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8);
+ resp[7] = (action << 1) | 1;
+ li->transmit_cb(resp, 8, li->cbdata);
+
+ if (teip->state == LAPD_TEI_NONE)
+ lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
+ break;
+ default:
+ LOGP(DMI, LOGL_NOTICE, "TEIMGR: unknown mt %x action %x\n",
+ mt, action);
+ break;
+ };
+};
+
+/* General input function for any data received for this LAPD instance */
+uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len,
+ int *ilen, lapd_mph_type *prim)
+{
+ uint8_t sapi, cr, tei, command;
+ int pf, ns, nr;
+ uint8_t *contents;
+ struct lapd_tei *teip;
+ struct lapd_sap *sap;
+
+ uint8_t resp[8];
+ int l = 0;
+
+ *ilen = 0;
+ *prim = 0;
+
+ if (len < 2) {
+ DEBUGP(DMI, "len %d < 2\n", len);
+ return NULL;
+ };
+
+ if ((data[0] & 1) != 0 || (data[1] & 1) != 1) {
+ DEBUGP(DMI, "address field %x/%x not well formed\n", data[0],
+ data[1]);
+ return NULL;
+ };
+
+ sapi = data[0] >> 2;
+ cr = (data[0] >> 1) & 1;
+ tei = data[1] >> 1;
+ command = li->network_side ^ cr;
+ //DEBUGP(DMI, " address sapi %x tei %d cmd %d cr %d\n", sapi, tei, command, cr);
+
+ if (len < 3) {
+ DEBUGP(DMI, "len %d < 3\n", len);
+ return NULL;
+ };
+
+ lapd_msg_type typ = 0;
+ lapd_cmd_type cmd = 0;
+ pf = -1;
+ ns = -1;
+ nr = -1;
+ if ((data[2] & 1) == 0) {
+ typ = LAPD_TYPE_I;
+ assert(len >= 4);
+ ns = data[2] >> 1;
+ nr = data[3] >> 1;
+ pf = data[3] & 1;
+ cmd = LAPD_CMD_I;
+ } else if ((data[2] & 3) == 1) {
+ typ = LAPD_TYPE_S;
+ assert(len >= 4);
+ nr = data[3] >> 1;
+ pf = data[3] & 1;
+ switch (data[2]) {
+ case 0x1:
+ cmd = LAPD_CMD_RR;
+ break;
+ case 0x5:
+ cmd = LAPD_CMD_RNR;
+ break;
+ case 0x9:
+ cmd = LAPD_CMD_REJ;
+ break;
+ default:
+ LOGP(DMI, LOGL_ERROR, "unknown LAPD S cmd %x\n", data[2]);
+ return NULL;
+ };
+ } else if ((data[2] & 3) == 3) {
+ typ = LAPD_TYPE_U;
+ pf = (data[2] >> 4) & 1;
+ int val = data[2] & ~(1 << 4);
+ switch (val) {
+ case 0x6f:
+ cmd = LAPD_CMD_SABME;
+ break;
+ case 0x0f:
+ cmd = LAPD_CMD_DM;
+ break;
+ case 0x03:
+ cmd = LAPD_CMD_UI;
+ break;
+ case 0x43:
+ cmd = LAPD_CMD_DISC;
+ break;
+ case 0x63:
+ cmd = LAPD_CMD_UA;
+ break;
+ case 0x87:
+ cmd = LAPD_CMD_FRMR;
+ break;
+ case 0xaf:
+ cmd = LAPD_CMD_XID;
+ break;
+
+ default:
+ LOGP(DMI, LOGL_ERROR, "unknown U cmd %x "
+ "(pf %x data %x)\n", val, pf, data[2]);
+ return NULL;
+ };
+ };
+
+ contents = &data[4];
+ if (typ == LAPD_TYPE_U)
+ contents--;
+ *ilen = len - (contents - data);
+
+ if (tei == 127)
+ lapd_tei_receive(li, contents, *ilen);
+
+ teip = teip_from_tei(li, tei);
+ if (!teip) {
+ LOGP(DMI, LOGL_NOTICE, "Unknown TEI %u\n", tei);
+ return NULL;
+ }
+
+ sap = lapd_sap_find(teip, sapi);
+ if (!sap) {
+ LOGP(DMI, LOGL_INFO, "No SAP for TEI=%u / SAPI=%u, "
+ "allocating\n", tei, sapi);
+ sap = lapd_sap_alloc(teip, sapi);
+ }
+
+ DEBUGP(DMI, "<- %c %s sapi %x tei %3d cmd %x pf %x ns %3d nr %3d "
+ "ilen %d teip %p vs %d va %d vr %d len %d\n",
+ lapd_msg_types[typ], lapd_cmd_types[cmd], sapi, tei, command, pf,
+ ns, nr, *ilen, teip, sap->vs, sap->va, sap->vr, len);
+
+ switch (cmd) {
+ case LAPD_CMD_I:
+ if (ns != sap->vr) {
+ DEBUGP(DMI, "ns %d != vr %d\n", ns, sap->vr);
+ if (ns == ((sap->vr - 1) & 0x7f)) {
+ DEBUGP(DMI, "DOUBLE FRAME, ignoring\n");
+ cmd = 0; // ignore
+ } else {
+ assert(0);
+ };
+ } else {
+ //printf("IN SEQUENCE\n");
+ sap->vr = (ns + 1) & 0x7f; // FIXME: hack!
+ };
+
+ break;
+ case LAPD_CMD_UI:
+ break;
+ case LAPD_CMD_SABME:
+ sap->vs = 0;
+ sap->vr = 0;
+ sap->va = 0;
+
+ // ua
+ resp[l++] = data[0];
+ resp[l++] = (tei << 1) | 1;
+ resp[l++] = 0x73;
+ li->transmit_cb(resp, l, li->cbdata);
+ if (teip->state != LAPD_TEI_ACTIVE) {
+ if (teip->state == LAPD_TEI_ASSIGNED) {
+ lapd_tei_set_state(teip,
+ LAPD_TEI_ACTIVE);
+ //printf("ASSIGNED and ACTIVE\n");
+ } else {
+#if 0
+ DEBUGP(DMI, "rr in strange state, send rej\n");
+
+ // rej
+ resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2);
+ resp[l++] = (tei << 1) | 1;
+ resp[l++] = 0x09; //rej
+ resp[l++] = ((sap->vr + 1) << 1) | 0;
+ li->transmit_cb(resp, l, li->cbdata);
+ pf = 0; // dont reply
+#endif
+ };
+ };
+
+ *prim = LAPD_MPH_ACTIVATE_IND;
+ break;
+ case LAPD_CMD_UA:
+ sap->vs = 0;
+ sap->vr = 0;
+ sap->va = 0;
+ lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
+ lapd_sap_set_state(teip, sapi, SAP_STATE_ACTIVE);
+ *prim = LAPD_MPH_ACTIVATE_IND;
+ break;
+ case LAPD_CMD_RR:
+ sap->va = (nr & 0x7f);
+#if 0
+ if (teip->state != LAPD_TEI_ACTIVE) {
+ if (teip->state == LAPD_TEI_ASSIGNED) {
+ lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
+ *prim = LAPD_MPH_ACTIVATE_IND;
+ //printf("ASSIGNED and ACTIVE\n");
+ } else {
+#if 0
+ DEBUGP(DMI, "rr in strange " "state, send rej\n");
+
+ // rej
+ resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2);
+ resp[l++] = (tei << 1) | 1;
+ resp[l++] = 0x09; //rej
+ resp[l++] =
+ ((sap->vr + 1) << 1) | 0;
+ li->transmit_cb(resp, l, li->cbdata);
+ pf = 0; // dont reply
+#endif
+ };
+ };
+#endif
+ if (pf) {
+ // interrogating us, send rr
+ resp[l++] = data[0];
+ resp[l++] = (tei << 1) | 1;
+ resp[l++] = 0x01; // rr
+ resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1); // pf bit from req
+
+ li->transmit_cb(resp, l, li->cbdata);
+
+ };
+ break;
+ case LAPD_CMD_FRMR:
+ // frame reject
+#if 0
+ if (teip->state == LAPD_TEI_ACTIVE)
+ *prim = LAPD_MPH_DEACTIVATE_IND;
+ lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
+#endif
+ LOGP(DMI, LOGL_NOTICE, "frame reject, ignoring\n");
+ break;
+ case LAPD_CMD_DISC:
+ // disconnect
+ resp[l++] = data[0];
+ resp[l++] = (tei << 1) | 1;
+ resp[l++] = 0x73;
+ li->transmit_cb(resp, l, li->cbdata);
+ lapd_tei_set_state(teip, LAPD_TEI_NONE);
+ break;
+ default:
+ LOGP(DMI, LOGL_NOTICE, "unknown cmd for tei %d (cmd %x)\n",
+ tei, cmd);
+ break;
+ }
+
+ if (typ == LAPD_TYPE_I) {
+ /* send rr
+ * Thu Jan 22 19:17:13 2009 <4000> sangoma.c:340 read (62/25) 4: fa 33 01 0a
+ * lapd <- S RR sapi 3e tei 25 cmd 0 pf 0 ns -1 nr 5 ilen 0 teip 0x613800 vs 7 va 5 vr 2 len 4
+ */
+
+ /* interrogating us, send rr */
+ DEBUGP(DMI, "Sending RR response\n");
+ resp[l++] = data[0];
+ resp[l++] = (tei << 1) | 1;
+ resp[l++] = 0x01; // rr
+ resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1); // pf bit from req
+
+ li->transmit_cb(resp, l, li->cbdata);
+
+ if (cmd != 0) {
+ *prim = LAPD_DL_DATA_IND;
+ return contents;
+ }
+ } else if (tei != 127 && typ == LAPD_TYPE_U && cmd == LAPD_CMD_UI) {
+ *prim = LAPD_DL_UNITDATA_IND;
+ return contents;
+ }
+
+ return NULL;
+};
+
+/* low-level function to send a single SABM message */
+static int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+ struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM");
+ if (!msg)
+ return -ENOMEM;
+
+ DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi);
+
+ msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0));
+ msgb_put_u8(msg, (tei << 1) | 1);
+ msgb_put_u8(msg, 0x7F);
+
+ li->transmit_cb(msg->data, msg->len, li->cbdata);
+
+ msgb_free(msg);
+
+ return 0;
+}
+
+/* timer call-back function for SABM re-transmission */
+static void sabme_timer_cb(void *_sap)
+{
+ struct lapd_sap *sap = _sap;
+
+ lapd_send_sabm(sap->tei->li, sap->tei->tei, sap->sapi);
+
+ if (sap->state == SAP_STATE_SABM_RETRANS)
+ bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
+}
+
+/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+ struct lapd_sap *sap;
+ struct lapd_tei *teip;
+
+ teip = teip_from_tei(li, tei);
+ if (!teip)
+ teip = lapd_tei_alloc(li, tei);
+
+ sap = lapd_sap_find(teip, sapi);
+ if (sap)
+ return -EEXIST;
+
+ sap = lapd_sap_alloc(teip, sapi);
+
+ lapd_sap_set_state(teip, sapi, SAP_STATE_SABM_RETRANS);
+
+ return 0;
+}
+
+/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+ struct lapd_tei *teip;
+ struct lapd_sap *sap;
+
+ teip = teip_from_tei(li, tei);
+ if (!teip)
+ return -ENODEV;
+
+ sap = lapd_sap_find(teip, sapi);
+ if (!sap)
+ return -ENODEV;
+
+ lapd_sap_set_state(teip, sapi, SAP_STATE_INACTIVE);
+
+ llist_del(&sap->list);
+ talloc_free(sap);
+
+ return 0;
+}
+
+/* Transmit Data (I-Frame) on the given LAPD Instance / TEI / SAPI */
+void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
+ uint8_t *data, unsigned int len)
+{
+ struct lapd_tei *teip = teip_from_tei(li, tei);
+ struct lapd_sap *sap;
+
+ if (!teip) {
+ LOGP(DMI, LOGL_ERROR, "Cannot transmit on non-existing "
+ "TEI %u\n", tei);
+ return;
+ }
+
+ sap = lapd_sap_find(teip, sapi);
+ if (!sap) {
+ LOGP(DMI, LOGL_INFO, "Tx on unknown SAPI=%u in TEI=%u, "
+ "allocating\n", sapi, tei);
+ sap = lapd_sap_alloc(teip, sapi);
+ }
+
+ /* prepend stuff */
+ uint8_t buf[10000];
+ memset(buf, 0, sizeof(buf));
+ memmove(buf + 4, data, len);
+ len += 4;
+
+ buf[0] = (sapi << 2) | (li->network_side ? 2 : 0);
+ buf[1] = (tei << 1) | 1;
+ buf[2] = (LAPD_NS(sap) << 1);
+ buf[3] = (LAPD_NR(sap) << 1) | 0;
+
+ sap->vs = (sap->vs + 1) & 0x7f;
+
+ li->transmit_cb(buf, len, li->cbdata);
+};
+
+/* Allocate a new LAPD instance */
+struct lapd_instance *lapd_instance_alloc(int network_side,
+ void (*tx_cb)(uint8_t *data, int len,
+ void *cbdata), void *cbdata)
+{
+ struct lapd_instance *li;
+
+ li = talloc_zero(NULL, struct lapd_instance);
+ if (!li)
+ return NULL;
+
+ li->transmit_cb = tx_cb;
+ li->cbdata = cbdata;
+ li->network_side = network_side;
+ INIT_LLIST_HEAD(&li->tei_list);
+
+ return li;
+}
diff --git a/src/libabis/input/lapd.h b/src/libabis/input/lapd.h
new file mode 100644
index 00000000..fd11edaa
--- /dev/null
+++ b/src/libabis/input/lapd.h
@@ -0,0 +1,46 @@
+#ifndef OPENBSC_LAPD_H
+#define OPENBSC_LAPD_H
+
+#include <stdint.h>
+
+#include <osmocore/linuxlist.h>
+
+typedef enum {
+ LAPD_MPH_NONE = 0,
+
+ LAPD_MPH_ACTIVATE_IND,
+ LAPD_MPH_DEACTIVATE_IND,
+
+ LAPD_DL_DATA_IND,
+ LAPD_DL_UNITDATA_IND,
+
+} lapd_mph_type;
+
+struct lapd_instance {
+ struct llist_head list; /* list of LAPD instances */
+ int network_side;
+
+ void (*transmit_cb)(uint8_t *data, int len, void *cbdata);
+ void *cbdata;
+
+ struct llist_head tei_list; /* list of TEI in this LAPD instance */
+};
+
+extern uint8_t *lapd_receive(struct lapd_instance *li, uint8_t *data, unsigned int len,
+ int *ilen, lapd_mph_type *prim);
+
+extern void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
+ uint8_t *data, unsigned int len);
+
+struct lapd_instance *lapd_instance_alloc(int network_side,
+ void (*tx_cb)(uint8_t *data, int len,
+ void *cbdata), void *cbdata);
+
+
+/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
+
+/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
+
+#endif /* OPENBSC_LAPD_H */
diff --git a/src/libabis/input/misdn.c b/src/libabis/input/misdn.c
new file mode 100644
index 00000000..45988791
--- /dev/null
+++ b/src/libabis/input/misdn.c
@@ -0,0 +1,542 @@
+/* OpenBSC Abis input driver for mISDNuser */
+
+/* (C) 2008-2009 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <mISDNif.h>
+
+//#define AF_COMPATIBILITY_FUNC
+//#include <compat_af_isdn.h>
+#ifndef AF_ISDN
+#define AF_ISDN 34
+#define PF_ISDN AF_ISDN
+#endif
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+
+#define TS1_ALLOC_SIZE 300
+
+struct prim_name {
+ unsigned int prim;
+ const char *name;
+};
+
+const struct prim_name prim_names[] = {
+ { PH_CONTROL_IND, "PH_CONTROL_IND" },
+ { PH_DATA_IND, "PH_DATA_IND" },
+ { PH_DATA_CNF, "PH_DATA_CNF" },
+ { PH_ACTIVATE_IND, "PH_ACTIVATE_IND" },
+ { DL_ESTABLISH_IND, "DL_ESTABLISH_IND" },
+ { DL_ESTABLISH_CNF, "DL_ESTABLISH_CNF" },
+ { DL_RELEASE_IND, "DL_RELEASE_IND" },
+ { DL_RELEASE_CNF, "DL_RELEASE_CNF" },
+ { DL_DATA_IND, "DL_DATA_IND" },
+ { DL_UNITDATA_IND, "DL_UNITDATA_IND" },
+ { DL_INFORMATION_IND, "DL_INFORMATION_IND" },
+ { MPH_ACTIVATE_IND, "MPH_ACTIVATE_IND" },
+ { MPH_DEACTIVATE_IND, "MPH_DEACTIVATE_IND" },
+};
+
+const char *get_prim_name(unsigned int prim)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(prim_names); i++) {
+ if (prim_names[i].prim == prim)
+ return prim_names[i].name;
+ }
+
+ return "UNKNOWN";
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *link;
+ struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "mISDN TS1");
+ struct sockaddr_mISDN l2addr;
+ struct mISDNhead *hh;
+ socklen_t alen;
+ int ret;
+
+ if (!msg)
+ return -ENOMEM;
+
+ hh = (struct mISDNhead *) msg->data;
+
+ alen = sizeof(l2addr);
+ ret = recvfrom(bfd->fd, msg->data, 300, 0,
+ (struct sockaddr *) &l2addr, &alen);
+ if (ret < 0) {
+ fprintf(stderr, "recvfrom error %s\n", strerror(errno));
+ return ret;
+ }
+
+ if (alen != sizeof(l2addr)) {
+ fprintf(stderr, "%s error len\n", __func__);
+ return -EINVAL;
+ }
+
+ msgb_put(msg, ret);
+
+ DEBUGP(DMI, "alen =%d, dev(%d) channel(%d) sapi(%d) tei(%d)\n",
+ alen, l2addr.dev, l2addr.channel, l2addr.sapi, l2addr.tei);
+
+ DEBUGP(DMI, "<= len = %d, prim(0x%x) id(0x%x): %s\n",
+ ret, hh->prim, hh->id, get_prim_name(hh->prim));
+
+ switch (hh->prim) {
+ case DL_INFORMATION_IND:
+ /* mISDN tells us which channel number is allocated for this
+ * tuple of (SAPI, TEI). */
+ DEBUGP(DMI, "DL_INFORMATION_IND: use channel(%d) sapi(%d) tei(%d) for now\n",
+ l2addr.channel, l2addr.sapi, l2addr.tei);
+ link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi);
+ if (!link) {
+ DEBUGPC(DMI, "mISDN message for unknown sign_link\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ /* save the channel number in the driver private struct */
+ link->driver.misdn.channel = l2addr.channel;
+ break;
+ case DL_ESTABLISH_IND:
+ DEBUGP(DMI, "DL_ESTABLISH_IND: channel(%d) sapi(%d) tei(%d)\n",
+ l2addr.channel, l2addr.sapi, l2addr.tei);
+ /* For some strange reason, sometimes the DL_INFORMATION_IND tells
+ * us the wrong channel, and we only get the real channel number
+ * during the DL_ESTABLISH_IND */
+ link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi);
+ if (!link) {
+ DEBUGPC(DMI, "mISDN message for unknown sign_link\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ /* save the channel number in the driver private struct */
+ link->driver.misdn.channel = l2addr.channel;
+ ret = e1inp_event(e1i_ts, S_INP_TEI_UP, l2addr.tei, l2addr.sapi);
+ break;
+ case DL_RELEASE_IND:
+ DEBUGP(DMI, "DL_RELEASE_IND: channel(%d) sapi(%d) tei(%d)\n",
+ l2addr.channel, l2addr.sapi, l2addr.tei);
+ ret = e1inp_event(e1i_ts, S_INP_TEI_DN, l2addr.tei, l2addr.sapi);
+ break;
+ case DL_DATA_IND:
+ case DL_UNITDATA_IND:
+ msg->l2h = msg->data + MISDN_HEADER_LEN;
+ DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+ ret = e1inp_rx_ts(e1i_ts, msg, l2addr.tei, l2addr.sapi);
+ break;
+ case PH_ACTIVATE_IND:
+ DEBUGP(DMI, "PH_ACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
+ l2addr.channel, l2addr.sapi, l2addr.tei);
+ break;
+ case PH_DEACTIVATE_IND:
+ DEBUGP(DMI, "PH_DEACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
+ l2addr.channel, l2addr.sapi, l2addr.tei);
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+ /* We never include the mISDN B-Channel FD into the
+ * writeset, since it doesn't support poll() based
+ * write flow control */
+ if (e1i_ts->type == E1INP_TS_TYPE_TRAU)
+ return 0;
+
+ e1i_ts->driver.misdn.fd.when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+ struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+ /* trigger write of ts1, due to tx delay timer */
+ ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct e1inp_sign_link *sign_link;
+ struct sockaddr_mISDN sa;
+ struct msgb *msg;
+ struct mISDNhead *hh;
+ u_int8_t *l2_data;
+ int ret;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* get the next msg for this timeslot */
+ msg = e1inp_tx_ts(e1i_ts, &sign_link);
+ if (!msg) {
+ /* no message after tx delay timer */
+ return 0;
+ }
+
+ l2_data = msg->data;
+
+ /* prepend the mISDNhead */
+ hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh));
+ hh->prim = DL_DATA_REQ;
+
+ DEBUGP(DMI, "TX channel(%d) TEI(%d) SAPI(%d): %s\n",
+ sign_link->driver.misdn.channel, sign_link->tei,
+ sign_link->sapi, hexdump(l2_data, msg->len - MISDN_HEADER_LEN));
+
+ /* construct the sockaddr */
+ sa.family = AF_ISDN;
+ sa.sapi = sign_link->sapi;
+ sa.dev = sign_link->tei;
+ sa.channel = sign_link->driver.misdn.channel;
+
+ ret = sendto(bfd->fd, msg->data, msg->len, 0,
+ (struct sockaddr *)&sa, sizeof(sa));
+ if (ret < 0)
+ fprintf(stderr, "%s sendto failed %d\n", __func__, ret);
+ msgb_free(msg);
+
+ /* set tx delay timer for next event */
+ e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+ e1i_ts->sign.tx_timer.data = e1i_ts;
+ bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay);
+
+ return ret;
+}
+
+#define BCHAN_TX_GRAN 160
+/* write to a B channel TS */
+static int handle_tsX_write(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct mISDNhead *hh;
+ u_int8_t tx_buf[BCHAN_TX_GRAN + sizeof(*hh)];
+ struct subch_mux *mx = &e1i_ts->trau.mux;
+ int ret;
+
+ hh = (struct mISDNhead *) tx_buf;
+ hh->prim = PH_DATA_REQ;
+
+ subchan_mux_out(mx, tx_buf+sizeof(*hh), BCHAN_TX_GRAN);
+
+ DEBUGP(DMIB, "BCHAN TX: %s\n",
+ hexdump(tx_buf+sizeof(*hh), BCHAN_TX_GRAN));
+
+ ret = send(bfd->fd, tx_buf, sizeof(*hh) + BCHAN_TX_GRAN, 0);
+ if (ret < sizeof(*hh) + BCHAN_TX_GRAN)
+ DEBUGP(DMIB, "send returns %d instead of %zu\n", ret,
+ sizeof(*hh) + BCHAN_TX_GRAN);
+
+ return ret;
+}
+
+#define TSX_ALLOC_SIZE 4096
+/* FIXME: read from a B channel TS */
+static int handle_tsX_read(struct bsc_fd *bfd)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+ struct msgb *msg = msgb_alloc(TSX_ALLOC_SIZE, "mISDN TSx");
+ struct mISDNhead *hh;
+ int ret;
+
+ if (!msg)
+ return -ENOMEM;
+
+ hh = (struct mISDNhead *) msg->data;
+
+ ret = recv(bfd->fd, msg->data, TSX_ALLOC_SIZE, 0);
+ if (ret < 0) {
+ fprintf(stderr, "recvfrom error %s\n", strerror(errno));
+ return ret;
+ }
+
+ msgb_put(msg, ret);
+
+ if (hh->prim != PH_CONTROL_IND)
+ DEBUGP(DMIB, "<= BCHAN len = %d, prim(0x%x) id(0x%x): %s\n",
+ ret, hh->prim, hh->id, get_prim_name(hh->prim));
+
+ switch (hh->prim) {
+ case PH_DATA_IND:
+ msg->l2h = msg->data + MISDN_HEADER_LEN;
+ DEBUGP(DMIB, "BCHAN RX: %s\n",
+ hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+ ret = e1inp_rx_ts(e1i_ts, msg, 0, 0);
+ break;
+ case PH_ACTIVATE_IND:
+ case PH_DATA_CNF:
+ /* physical layer indicates that data has been sent,
+ * we thus can send some more data */
+ ret = handle_tsX_write(bfd);
+ default:
+ break;
+ }
+ /* FIXME: why do we free signalling msgs in the caller, and trau not? */
+ msgb_free(msg);
+
+ return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int misdn_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+ struct e1inp_line *line = bfd->data;
+ unsigned int ts_nr = bfd->priv_nr;
+ unsigned int idx = ts_nr-1;
+ struct e1inp_ts *e1i_ts = &line->ts[idx];
+ int rc = 0;
+
+ switch (e1i_ts->type) {
+ case E1INP_TS_TYPE_SIGN:
+ if (what & BSC_FD_READ)
+ rc = handle_ts1_read(bfd);
+ if (what & BSC_FD_WRITE)
+ rc = handle_ts1_write(bfd);
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ if (what & BSC_FD_READ)
+ rc = handle_tsX_read(bfd);
+ /* We never include the mISDN B-Channel FD into the
+ * writeset, since it doesn't support poll() based
+ * write flow control */
+ break;
+ default:
+ fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type);
+ break;
+ }
+
+ return rc;
+}
+
+static int activate_bchan(struct e1inp_line *line, int ts, int act)
+{
+ struct mISDNhead hh;
+ int ret;
+ unsigned int idx = ts-1;
+ struct e1inp_ts *e1i_ts = &line->ts[idx];
+ struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd;
+
+ fprintf(stdout, "activate bchan\n");
+ if (act)
+ hh.prim = PH_ACTIVATE_REQ;
+ else
+ hh.prim = PH_DEACTIVATE_REQ;
+
+ hh.id = MISDN_ID_ANY;
+ ret = sendto(bfd->fd, &hh, sizeof(hh), 0, NULL, 0);
+ if (ret < 0) {
+ fprintf(stdout, "could not send ACTIVATE_RQ %s\n",
+ strerror(errno));
+ }
+
+ return ret;
+}
+
+static int mi_e1_line_update(struct e1inp_line *line);
+
+struct e1inp_driver misdn_driver = {
+ .name = "misdn",
+ .want_write = ts_want_write,
+ .default_delay = 50000,
+ .line_update = &mi_e1_line_update,
+};
+
+static int mi_e1_setup(struct e1inp_line *line, int release_l2)
+{
+ int ts, ret;
+
+ /* TS0 is CRC4, don't need any fd for it */
+ for (ts = 1; ts < NUM_E1_TS; ts++) {
+ unsigned int idx = ts-1;
+ struct e1inp_ts *e1i_ts = &line->ts[idx];
+ struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd;
+ struct sockaddr_mISDN addr;
+
+ bfd->data = line;
+ bfd->priv_nr = ts;
+ bfd->cb = misdn_fd_cb;
+
+ switch (e1i_ts->type) {
+ case E1INP_TS_TYPE_NONE:
+ continue;
+ break;
+ case E1INP_TS_TYPE_SIGN:
+ bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT);
+ bfd->when = BSC_FD_READ;
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_RAW);
+ /* We never include the mISDN B-Channel FD into the
+ * writeset, since it doesn't support poll() based
+ * write flow control */
+ bfd->when = BSC_FD_READ;
+ break;
+ }
+
+ if (bfd->fd < 0) {
+ fprintf(stderr, "%s could not open socket %s\n",
+ __func__, strerror(errno));
+ return bfd->fd;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.family = AF_ISDN;
+ addr.dev = line->num;
+ switch (e1i_ts->type) {
+ case E1INP_TS_TYPE_SIGN:
+ addr.channel = 0;
+ /* SAPI not supported yet in kernel */
+ //addr.sapi = e1inp_ts->sign.sapi;
+ addr.sapi = 0;
+ addr.tei = GROUP_TEI;
+ break;
+ case E1INP_TS_TYPE_TRAU:
+ addr.channel = ts;
+ break;
+ default:
+ DEBUGP(DMI, "unsupported E1 TS type: %u\n",
+ e1i_ts->type);
+ break;
+ }
+
+ ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (ret < 0) {
+ fprintf(stderr, "could not bind l2 socket %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+
+ if (e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+ ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2);
+ if (ret < 0) {
+ fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno));
+ return -EIO;
+ }
+ }
+
+ /* FIXME: only activate B-Channels once we start to
+ * use them to conserve CPU power */
+ if (e1i_ts->type == E1INP_TS_TYPE_TRAU)
+ activate_bchan(line, ts, 1);
+
+ ret = bsc_register_fd(bfd);
+ if (ret < 0) {
+ fprintf(stderr, "could not register FD: %s\n",
+ strerror(ret));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int mi_e1_line_update(struct e1inp_line *line)
+{
+ struct mISDN_devinfo devinfo;
+ int sk, ret, cnt;
+
+ if (line->driver != &misdn_driver)
+ return -EINVAL;
+
+ /* open the ISDN card device */
+ sk = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+ if (sk < 0) {
+ fprintf(stderr, "%s could not open socket %s\n",
+ __func__, strerror(errno));
+ return sk;
+ }
+
+ ret = ioctl(sk, IMGETCOUNT, &cnt);
+ if (ret) {
+ fprintf(stderr, "%s error getting interf count: %s\n",
+ __func__, strerror(errno));
+ close(sk);
+ return -ENODEV;
+ }
+ //DEBUGP(DMI,"%d device%s found\n", cnt, (cnt==1)?"":"s");
+ printf("%d device%s found\n", cnt, (cnt==1)?"":"s");
+#if 1
+ devinfo.id = line->num;
+ ret = ioctl(sk, IMGETDEVINFO, &devinfo);
+ if (ret < 0) {
+ fprintf(stdout, "error getting info for device %d: %s\n",
+ line->num, strerror(errno));
+ return -ENODEV;
+ }
+ fprintf(stdout, " id: %d\n", devinfo.id);
+ fprintf(stdout, " Dprotocols: %08x\n", devinfo.Dprotocols);
+ fprintf(stdout, " Bprotocols: %08x\n", devinfo.Bprotocols);
+ fprintf(stdout, " protocol: %d\n", devinfo.protocol);
+ fprintf(stdout, " nrbchan: %d\n", devinfo.nrbchan);
+ fprintf(stdout, " name: %s\n", devinfo.name);
+#endif
+
+ if (!(devinfo.Dprotocols & (1 << ISDN_P_NT_E1))) {
+ fprintf(stderr, "error: card is not of type E1 (NT-mode)\n");
+ return -EINVAL;
+ }
+
+ ret = mi_e1_setup(line, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+void e1inp_misdn_init(void)
+{
+ /* register the driver with the core */
+ e1inp_driver_register(&misdn_driver);
+}
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
new file mode 100644
index 00000000..de769295
--- /dev/null
+++ b/src/libbsc/Makefile.am
@@ -0,0 +1,25 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libbsc.a
+
+libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \
+ abis_om2000.c abis_om2000_vty.c \
+ abis_rsl.c bsc_rll.c \
+ paging.c \
+ bts_ericsson_rbs2000.c \
+ bts_ipaccess_nanobts.c \
+ bts_siemens_bs11.c \
+ bts_hsl_femtocell.c \
+ bts_unknown.c \
+ chan_alloc.c \
+ gsm_subscriber_base.c \
+ handover_decision.c handover_logic.c meas_rep.c \
+ rest_octets.c system_information.c \
+ e1_config.c \
+ transaction.c \
+ bsc_api.c bsc_msc.c bsc_vty.c \
+ gsm_04_08_utils.c \
+ bsc_init.c
+
diff --git a/src/libbsc/Makefile.in b/src/libbsc/Makefile.in
new file mode 100644
index 00000000..e5d3d201
--- /dev/null
+++ b/src/libbsc/Makefile.in
@@ -0,0 +1,504 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libbsc
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo " AR " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libbsc_a_AR = $(AR) $(ARFLAGS)
+libbsc_a_LIBADD =
+am_libbsc_a_OBJECTS = abis_nm.$(OBJEXT) abis_nm_vty.$(OBJEXT) \
+ abis_om2000.$(OBJEXT) abis_om2000_vty.$(OBJEXT) \
+ abis_rsl.$(OBJEXT) bsc_rll.$(OBJEXT) paging.$(OBJEXT) \
+ bts_ericsson_rbs2000.$(OBJEXT) bts_ipaccess_nanobts.$(OBJEXT) \
+ bts_siemens_bs11.$(OBJEXT) bts_hsl_femtocell.$(OBJEXT) \
+ bts_unknown.$(OBJEXT) chan_alloc.$(OBJEXT) \
+ gsm_subscriber_base.$(OBJEXT) handover_decision.$(OBJEXT) \
+ handover_logic.$(OBJEXT) meas_rep.$(OBJEXT) \
+ rest_octets.$(OBJEXT) system_information.$(OBJEXT) \
+ e1_config.$(OBJEXT) transaction.$(OBJEXT) bsc_api.$(OBJEXT) \
+ bsc_msc.$(OBJEXT) bsc_vty.$(OBJEXT) gsm_04_08_utils.$(OBJEXT) \
+ bsc_init.$(OBJEXT)
+libbsc_a_OBJECTS = $(am_libbsc_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo " CC " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo " CCLD " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+SOURCES = $(libbsc_a_SOURCES)
+DIST_SOURCES = $(libbsc_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libbsc.a
+libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \
+ abis_om2000.c abis_om2000_vty.c \
+ abis_rsl.c bsc_rll.c \
+ paging.c \
+ bts_ericsson_rbs2000.c \
+ bts_ipaccess_nanobts.c \
+ bts_siemens_bs11.c \
+ bts_hsl_femtocell.c \
+ bts_unknown.c \
+ chan_alloc.c \
+ gsm_subscriber_base.c \
+ handover_decision.c handover_logic.c meas_rep.c \
+ rest_octets.c system_information.c \
+ e1_config.c \
+ transaction.c \
+ bsc_api.c bsc_msc.c bsc_vty.c \
+ gsm_04_08_utils.c \
+ bsc_init.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libbsc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/libbsc/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libbsc.a: $(libbsc_a_OBJECTS) $(libbsc_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libbsc.a
+ $(AM_V_AR)$(libbsc_a_AR) libbsc.a $(libbsc_a_OBJECTS) $(libbsc_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libbsc.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_rsl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_api.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_init.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_msc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_rll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ericsson_rbs2000.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_hsl_femtocell.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ipaccess_nanobts.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_siemens_bs11.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_unknown.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chan_alloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_08_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_subscriber_base.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_decision.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_logic.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meas_rep.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paging.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rest_octets.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/system_information.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-noinstLIBRARIES ctags distclean distclean-compile \
+ distclean-generic distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+ uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libbsc/abis_nm.c b/src/libbsc/abis_nm.c
new file mode 100644
index 00000000..0e7fc8d8
--- /dev/null
+++ b/src/libbsc/abis_nm.c
@@ -0,0 +1,3132 @@
+/* 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/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/misdn.h>
+#include <openbsc/signal.h>
+
+#define OM_ALLOC_SIZE 1024
+#define OM_HEADROOM_SIZE 128
+#define IPACC_SEGMENT_SIZE 245
+
+/* unidirectional messages from BTS to BSC */
+static const enum abis_nm_msgtype reports[] = {
+ NM_MT_SW_ACTIVATED_REP,
+ NM_MT_TEST_REP,
+ NM_MT_STATECHG_EVENT_REP,
+ NM_MT_FAILURE_EVENT_REP,
+};
+
+/* messages without ACK/NACK */
+static const enum abis_nm_msgtype no_ack_nack[] = {
+ NM_MT_MEAS_RES_REQ,
+ NM_MT_STOP_MEAS,
+ NM_MT_START_MEAS,
+};
+
+/* Messages related to software load */
+static const enum abis_nm_msgtype sw_load_msgs[] = {
+ NM_MT_LOAD_INIT_ACK,
+ NM_MT_LOAD_INIT_NACK,
+ NM_MT_LOAD_SEG_ACK,
+ NM_MT_LOAD_ABORT,
+ NM_MT_LOAD_END_ACK,
+ NM_MT_LOAD_END_NACK,
+ //NM_MT_SW_ACT_REQ,
+ NM_MT_ACTIVATE_SW_ACK,
+ NM_MT_ACTIVATE_SW_NACK,
+ NM_MT_SW_ACTIVATED_REP,
+};
+
+static const enum abis_nm_msgtype nacks[] = {
+ NM_MT_LOAD_INIT_NACK,
+ NM_MT_LOAD_END_NACK,
+ NM_MT_SW_ACT_REQ_NACK,
+ NM_MT_ACTIVATE_SW_NACK,
+ NM_MT_ESTABLISH_TEI_NACK,
+ NM_MT_CONN_TERR_SIGN_NACK,
+ NM_MT_DISC_TERR_SIGN_NACK,
+ NM_MT_CONN_TERR_TRAF_NACK,
+ NM_MT_DISC_TERR_TRAF_NACK,
+ NM_MT_CONN_MDROP_LINK_NACK,
+ NM_MT_DISC_MDROP_LINK_NACK,
+ NM_MT_SET_BTS_ATTR_NACK,
+ NM_MT_SET_RADIO_ATTR_NACK,
+ NM_MT_SET_CHAN_ATTR_NACK,
+ NM_MT_PERF_TEST_NACK,
+ NM_MT_SEND_TEST_REP_NACK,
+ NM_MT_STOP_TEST_NACK,
+ NM_MT_STOP_EVENT_REP_NACK,
+ NM_MT_REST_EVENT_REP_NACK,
+ NM_MT_CHG_ADM_STATE_NACK,
+ NM_MT_CHG_ADM_STATE_REQ_NACK,
+ NM_MT_REP_OUTST_ALARMS_NACK,
+ NM_MT_CHANGEOVER_NACK,
+ NM_MT_OPSTART_NACK,
+ NM_MT_REINIT_NACK,
+ NM_MT_SET_SITE_OUT_NACK,
+ NM_MT_CHG_HW_CONF_NACK,
+ NM_MT_GET_ATTR_NACK,
+ NM_MT_SET_ALARM_THRES_NACK,
+ NM_MT_BS11_BEGIN_DB_TX_NACK,
+ NM_MT_BS11_END_DB_TX_NACK,
+ NM_MT_BS11_CREATE_OBJ_NACK,
+ NM_MT_BS11_DELETE_OBJ_NACK,
+};
+
+static const struct value_string nack_names[] = {
+ { NM_MT_LOAD_INIT_NACK, "SOFTWARE LOAD INIT" },
+ { NM_MT_LOAD_END_NACK, "SOFTWARE LOAD END" },
+ { NM_MT_SW_ACT_REQ_NACK, "SOFTWARE ACTIVATE REQUEST" },
+ { NM_MT_ACTIVATE_SW_NACK, "ACTIVATE SOFTWARE" },
+ { NM_MT_ESTABLISH_TEI_NACK, "ESTABLISH TEI" },
+ { NM_MT_CONN_TERR_SIGN_NACK, "CONNECT TERRESTRIAL SIGNALLING" },
+ { NM_MT_DISC_TERR_SIGN_NACK, "DISCONNECT TERRESTRIAL SIGNALLING" },
+ { NM_MT_CONN_TERR_TRAF_NACK, "CONNECT TERRESTRIAL TRAFFIC" },
+ { NM_MT_DISC_TERR_TRAF_NACK, "DISCONNECT TERRESTRIAL TRAFFIC" },
+ { NM_MT_CONN_MDROP_LINK_NACK, "CONNECT MULTI-DROP LINK" },
+ { NM_MT_DISC_MDROP_LINK_NACK, "DISCONNECT MULTI-DROP LINK" },
+ { NM_MT_SET_BTS_ATTR_NACK, "SET BTS ATTRIBUTE" },
+ { NM_MT_SET_RADIO_ATTR_NACK, "SET RADIO ATTRIBUTE" },
+ { NM_MT_SET_CHAN_ATTR_NACK, "SET CHANNEL ATTRIBUTE" },
+ { NM_MT_PERF_TEST_NACK, "PERFORM TEST" },
+ { NM_MT_SEND_TEST_REP_NACK, "SEND TEST REPORT" },
+ { NM_MT_STOP_TEST_NACK, "STOP TEST" },
+ { NM_MT_STOP_EVENT_REP_NACK, "STOP EVENT REPORT" },
+ { NM_MT_REST_EVENT_REP_NACK, "RESET EVENT REPORT" },
+ { NM_MT_CHG_ADM_STATE_NACK, "CHANGE ADMINISTRATIVE STATE" },
+ { NM_MT_CHG_ADM_STATE_REQ_NACK,
+ "CHANGE ADMINISTRATIVE STATE REQUEST" },
+ { NM_MT_REP_OUTST_ALARMS_NACK, "REPORT OUTSTANDING ALARMS" },
+ { NM_MT_CHANGEOVER_NACK, "CHANGEOVER" },
+ { NM_MT_OPSTART_NACK, "OPSTART" },
+ { NM_MT_REINIT_NACK, "REINIT" },
+ { NM_MT_SET_SITE_OUT_NACK, "SET SITE OUTPUT" },
+ { NM_MT_CHG_HW_CONF_NACK, "CHANGE HARDWARE CONFIGURATION" },
+ { NM_MT_GET_ATTR_NACK, "GET ATTRIBUTE" },
+ { NM_MT_SET_ALARM_THRES_NACK, "SET ALARM THRESHOLD" },
+ { NM_MT_BS11_BEGIN_DB_TX_NACK, "BS11 BEGIN DATABASE TRANSMISSION" },
+ { NM_MT_BS11_END_DB_TX_NACK, "BS11 END DATABASE TRANSMISSION" },
+ { NM_MT_BS11_CREATE_OBJ_NACK, "BS11 CREATE OBJECT" },
+ { NM_MT_BS11_DELETE_OBJ_NACK, "BS11 DELETE OBJECT" },
+ { 0, NULL }
+};
+
+/* Chapter 9.4.36 */
+static const struct value_string nack_cause_names[] = {
+ /* General Nack Causes */
+ { NM_NACK_INCORR_STRUCT, "Incorrect message structure" },
+ { NM_NACK_MSGTYPE_INVAL, "Invalid message type value" },
+ { NM_NACK_OBJCLASS_INVAL, "Invalid Object class value" },
+ { NM_NACK_OBJCLASS_NOTSUPP, "Object class not supported" },
+ { NM_NACK_BTSNR_UNKN, "BTS no. unknown" },
+ { NM_NACK_TRXNR_UNKN, "Baseband Transceiver no. unknown" },
+ { NM_NACK_OBJINST_UNKN, "Object Instance unknown" },
+ { NM_NACK_ATTRID_INVAL, "Invalid attribute identifier value" },
+ { NM_NACK_ATTRID_NOTSUPP, "Attribute identifier not supported" },
+ { NM_NACK_PARAM_RANGE, "Parameter value outside permitted range" },
+ { NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" },
+ { NM_NACK_SPEC_IMPL_NOTSUPP, "Specified implementation not supported" },
+ { NM_NACK_CANT_PERFORM, "Message cannot be performed" },
+ /* Specific Nack Causes */
+ { NM_NACK_RES_NOTIMPL, "Resource not implemented" },
+ { NM_NACK_RES_NOTAVAIL, "Resource not available" },
+ { NM_NACK_FREQ_NOTAVAIL, "Frequency not available" },
+ { NM_NACK_TEST_NOTSUPP, "Test not supported" },
+ { NM_NACK_CAPACITY_RESTR, "Capacity restrictions" },
+ { NM_NACK_PHYSCFG_NOTPERFORM, "Physical configuration cannot be performed" },
+ { NM_NACK_TEST_NOTINIT, "Test not initiated" },
+ { NM_NACK_PHYSCFG_NOTRESTORE, "Physical configuration cannot be restored" },
+ { NM_NACK_TEST_NOSUCH, "No such test" },
+ { NM_NACK_TEST_NOSTOP, "Test cannot be stopped" },
+ { NM_NACK_MSGINCONSIST_PHYSCFG, "Message inconsistent with physical configuration" },
+ { NM_NACK_FILE_INCOMPLETE, "Complete file notreceived" },
+ { NM_NACK_FILE_NOTAVAIL, "File not available at destination" },
+ { NM_NACK_FILE_NOTACTIVATE, "File cannot be activate" },
+ { NM_NACK_REQ_NOT_GRANT, "Request not granted" },
+ { NM_NACK_WAIT, "Wait" },
+ { NM_NACK_NOTH_REPORT_EXIST, "Nothing reportable existing" },
+ { NM_NACK_MEAS_NOTSUPP, "Measurement not supported" },
+ { NM_NACK_MEAS_NOTSTART, "Measurement not started" },
+ { 0, NULL }
+};
+
+static const char *nack_cause_name(u_int8_t cause)
+{
+ return get_value_string(nack_cause_names, cause);
+}
+
+/* Chapter 9.4.16: Event Type */
+static const struct value_string event_type_names[] = {
+ { NM_EVT_COMM_FAIL, "communication failure" },
+ { NM_EVT_QOS_FAIL, "quality of service failure" },
+ { NM_EVT_PROC_FAIL, "processing failure" },
+ { NM_EVT_EQUIP_FAIL, "equipment failure" },
+ { NM_EVT_ENV_FAIL, "environment failure" },
+ { 0, NULL }
+};
+
+static const char *event_type_name(u_int8_t cause)
+{
+ return get_value_string(event_type_names, cause);
+}
+
+/* Chapter 9.4.63: Perceived Severity */
+static const struct value_string severity_names[] = {
+ { NM_SEVER_CEASED, "failure ceased" },
+ { NM_SEVER_CRITICAL, "critical failure" },
+ { NM_SEVER_MAJOR, "major failure" },
+ { NM_SEVER_MINOR, "minor failure" },
+ { NM_SEVER_WARNING, "warning level failure" },
+ { NM_SEVER_INDETERMINATE, "indeterminate failure" },
+ { 0, NULL }
+};
+
+static const char *severity_name(u_int8_t cause)
+{
+ return get_value_string(severity_names, cause);
+}
+
+/* Attributes that the BSC can set, not only get, according to Section 9.4 */
+static const enum abis_nm_attr nm_att_settable[] = {
+ NM_ATT_ADD_INFO,
+ NM_ATT_ADD_TEXT,
+ NM_ATT_DEST,
+ NM_ATT_EVENT_TYPE,
+ NM_ATT_FILE_DATA,
+ NM_ATT_GET_ARI,
+ NM_ATT_HW_CONF_CHG,
+ NM_ATT_LIST_REQ_ATTR,
+ NM_ATT_MDROP_LINK,
+ NM_ATT_MDROP_NEXT,
+ NM_ATT_NACK_CAUSES,
+ NM_ATT_OUTST_ALARM,
+ NM_ATT_PHYS_CONF,
+ NM_ATT_PROB_CAUSE,
+ NM_ATT_RAD_SUBC,
+ NM_ATT_SOURCE,
+ NM_ATT_SPEC_PROB,
+ NM_ATT_START_TIME,
+ NM_ATT_TEST_DUR,
+ NM_ATT_TEST_NO,
+ NM_ATT_TEST_REPORT,
+ NM_ATT_WINDOW_SIZE,
+ NM_ATT_SEVERITY,
+ NM_ATT_MEAS_RES,
+ NM_ATT_MEAS_TYPE,
+};
+
+const struct tlv_definition nm_att_tlvdef = {
+ .def = {
+ [NM_ATT_ABIS_CHANNEL] = { TLV_TYPE_FIXED, 3 },
+ [NM_ATT_ADD_INFO] = { TLV_TYPE_TL16V },
+ [NM_ATT_ADD_TEXT] = { TLV_TYPE_TL16V },
+ [NM_ATT_ADM_STATE] = { TLV_TYPE_TV },
+ [NM_ATT_ARFCN_LIST]= { TLV_TYPE_TL16V },
+ [NM_ATT_AUTON_REPORT] = { TLV_TYPE_TV },
+ [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TL16V },
+ [NM_ATT_BCCH_ARFCN] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_BSIC] = { TLV_TYPE_TV },
+ [NM_ATT_BTS_AIR_TIMER] = { TLV_TYPE_TV },
+ [NM_ATT_CCCH_L_I_P] = { TLV_TYPE_TV },
+ [NM_ATT_CCCH_L_T] = { TLV_TYPE_TV },
+ [NM_ATT_CHAN_COMB] = { TLV_TYPE_TV },
+ [NM_ATT_CONN_FAIL_CRIT] = { TLV_TYPE_TL16V },
+ [NM_ATT_DEST] = { TLV_TYPE_TL16V },
+ [NM_ATT_EVENT_TYPE] = { TLV_TYPE_TV },
+ [NM_ATT_FILE_DATA] = { TLV_TYPE_TL16V },
+ [NM_ATT_FILE_ID] = { TLV_TYPE_TL16V },
+ [NM_ATT_FILE_VERSION] = { TLV_TYPE_TL16V },
+ [NM_ATT_GSM_TIME] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_HSN] = { TLV_TYPE_TV },
+ [NM_ATT_HW_CONFIG] = { TLV_TYPE_TL16V },
+ [NM_ATT_HW_DESC] = { TLV_TYPE_TL16V },
+ [NM_ATT_INTAVE_PARAM] = { TLV_TYPE_TV },
+ [NM_ATT_INTERF_BOUND] = { TLV_TYPE_FIXED, 6 },
+ [NM_ATT_LIST_REQ_ATTR] = { TLV_TYPE_TL16V },
+ [NM_ATT_MAIO] = { TLV_TYPE_TV },
+ [NM_ATT_MANUF_STATE] = { TLV_TYPE_TV },
+ [NM_ATT_MANUF_THRESH] = { TLV_TYPE_TL16V },
+ [NM_ATT_MANUF_ID] = { TLV_TYPE_TL16V },
+ [NM_ATT_MAX_TA] = { TLV_TYPE_TV },
+ [NM_ATT_MDROP_LINK] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_MDROP_NEXT] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_NACK_CAUSES] = { TLV_TYPE_TV },
+ [NM_ATT_NY1] = { TLV_TYPE_TV },
+ [NM_ATT_OPER_STATE] = { TLV_TYPE_TV },
+ [NM_ATT_OVERL_PERIOD] = { TLV_TYPE_TL16V },
+ [NM_ATT_PHYS_CONF] = { TLV_TYPE_TL16V },
+ [NM_ATT_POWER_CLASS] = { TLV_TYPE_TV },
+ [NM_ATT_POWER_THRESH] = { TLV_TYPE_FIXED, 3 },
+ [NM_ATT_PROB_CAUSE] = { TLV_TYPE_FIXED, 3 },
+ [NM_ATT_RACH_B_THRESH] = { TLV_TYPE_TV },
+ [NM_ATT_LDAVG_SLOTS] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_RAD_SUBC] = { TLV_TYPE_TV },
+ [NM_ATT_RF_MAXPOWR_R] = { TLV_TYPE_TV },
+ [NM_ATT_SITE_INPUTS] = { TLV_TYPE_TL16V },
+ [NM_ATT_SITE_OUTPUTS] = { TLV_TYPE_TL16V },
+ [NM_ATT_SOURCE] = { TLV_TYPE_TL16V },
+ [NM_ATT_SPEC_PROB] = { TLV_TYPE_TV },
+ [NM_ATT_START_TIME] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_T200] = { TLV_TYPE_FIXED, 7 },
+ [NM_ATT_TEI] = { TLV_TYPE_TV },
+ [NM_ATT_TEST_DUR] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_TEST_NO] = { TLV_TYPE_TV },
+ [NM_ATT_TEST_REPORT] = { TLV_TYPE_TL16V },
+ [NM_ATT_VSWR_THRESH] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_WINDOW_SIZE] = { TLV_TYPE_TV },
+ [NM_ATT_TSC] = { TLV_TYPE_TV },
+ [NM_ATT_SW_CONFIG] = { TLV_TYPE_TL16V },
+ [NM_ATT_SEVERITY] = { TLV_TYPE_TV },
+ [NM_ATT_GET_ARI] = { TLV_TYPE_TL16V },
+ [NM_ATT_HW_CONF_CHG] = { TLV_TYPE_TL16V },
+ [NM_ATT_OUTST_ALARM] = { TLV_TYPE_TV },
+ [NM_ATT_MEAS_RES] = { TLV_TYPE_TL16V },
+ },
+};
+
+static const enum abis_nm_chan_comb chcomb4pchan[] = {
+ [GSM_PCHAN_CCCH] = NM_CHANC_mainBCCH,
+ [GSM_PCHAN_CCCH_SDCCH4] = NM_CHANC_BCCHComb,
+ [GSM_PCHAN_TCH_F] = NM_CHANC_TCHFull,
+ [GSM_PCHAN_TCH_H] = NM_CHANC_TCHHalf,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = NM_CHANC_SDCCH,
+ [GSM_PCHAN_PDCH] = NM_CHANC_IPAC_PDCH,
+ [GSM_PCHAN_TCH_F_PDCH] = NM_CHANC_IPAC_TCHFull_PDCH,
+ /* FIXME: bounds check */
+};
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan)
+{
+ if (pchan < ARRAY_SIZE(chcomb4pchan))
+ return chcomb4pchan[pchan];
+
+ return -EINVAL;
+}
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_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, reports, ARRAY_SIZE(reports));
+}
+
+#define MT_ACK(x) (x+1)
+#define MT_NACK(x) (x+2)
+
+static void fill_om_hdr(struct abis_om_hdr *oh, u_int8_t len)
+{
+ oh->mdisc = ABIS_OM_MDISC_FOM;
+ oh->placement = ABIS_OM_PLACEMENT_ONLY;
+ oh->sequence = 0;
+ oh->length = len;
+}
+
+static void fill_om_fom_hdr(struct abis_om_hdr *oh, u_int8_t len,
+ u_int8_t msg_type, u_int8_t obj_class,
+ u_int8_t bts_nr, u_int8_t trx_nr, u_int8_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;
+}
+
+static struct msgb *nm_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+ "OML");
+}
+
+/* Send a OML NM Message from BSC to BTS */
+static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg)
+{
+ msg->trx = bts->c0;
+
+ /* 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, 0);
+ } 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);
+
+const struct value_string abis_nm_obj_class_names[] = {
+ { NM_OC_SITE_MANAGER, "SITE-MANAGER" },
+ { NM_OC_BTS, "BTS" },
+ { NM_OC_RADIO_CARRIER, "RADIO-CARRIER" },
+ { NM_OC_BASEB_TRANSC, "BASEBAND-TRANSCEIVER" },
+ { NM_OC_CHANNEL, "CHANNEL" },
+ { NM_OC_BS11_ADJC, "ADJC" },
+ { NM_OC_BS11_HANDOVER, "HANDOVER" },
+ { NM_OC_BS11_PWR_CTRL, "POWER-CONTROL" },
+ { NM_OC_BS11_BTSE, "BTSE" },
+ { NM_OC_BS11_RACK, "RACK" },
+ { NM_OC_BS11_TEST, "TEST" },
+ { NM_OC_BS11_ENVABTSE, "ENVABTSE" },
+ { NM_OC_BS11_BPORT, "BPORT" },
+ { NM_OC_GPRS_NSE, "GPRS-NSE" },
+ { NM_OC_GPRS_CELL, "GPRS-CELL" },
+ { NM_OC_GPRS_NSVC, "GPRS-NSVC" },
+ { NM_OC_BS11, "SIEMENSHW" },
+ { 0, NULL }
+};
+
+static const char *obj_class_name(u_int8_t oc)
+{
+ return get_value_string(abis_nm_obj_class_names, oc);
+}
+
+const char *nm_opstate_name(u_int8_t os)
+{
+ switch (os) {
+ case NM_OPSTATE_DISABLED:
+ return "Disabled";
+ case NM_OPSTATE_ENABLED:
+ return "Enabled";
+ case NM_OPSTATE_NULL:
+ return "NULL";
+ default:
+ return "RFU";
+ }
+}
+
+/* Chapter 9.4.7 */
+static const struct value_string avail_names[] = {
+ { 0, "In test" },
+ { 1, "Failed" },
+ { 2, "Power off" },
+ { 3, "Off line" },
+ /* Not used */
+ { 5, "Dependency" },
+ { 6, "Degraded" },
+ { 7, "Not installed" },
+ { 0xff, "OK" },
+ { 0, NULL }
+};
+
+const char *nm_avail_name(u_int8_t avail)
+{
+ return get_value_string(avail_names, avail);
+}
+
+static struct value_string test_names[] = {
+ /* FIXME: standard test names */
+ { NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" },
+ { NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
+ { NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" },
+ { NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" },
+ { NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" },
+ { NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" },
+ { NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_adm_state_names[] = {
+ { NM_STATE_LOCKED, "Locked" },
+ { NM_STATE_UNLOCKED, "Unlocked" },
+ { NM_STATE_SHUTDOWN, "Shutdown" },
+ { NM_STATE_NULL, "NULL" },
+ { 0, NULL }
+};
+
+const char *nm_adm_name(u_int8_t adm)
+{
+ return get_value_string(abis_nm_adm_state_names, adm);
+}
+
+int nm_is_running(struct gsm_nm_state *s) {
+ return (s->operational == NM_OPSTATE_ENABLED) && (
+ (s->availability == NM_AVSTATE_OK) ||
+ (s->availability == 0xff)
+ );
+}
+
+static void debugp_foh(struct abis_om_fom_hdr *foh)
+{
+ DEBUGP(DNM, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
+ obj_class_name(foh->obj_class), foh->obj_class,
+ foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
+ foh->obj_inst.ts_nr);
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+static struct gsm_nm_state *
+objclass2nmstate(struct gsm_bts *bts, u_int8_t obj_class,
+ struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_nm_state *nm_state = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ nm_state = &bts->nm_state;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ nm_state = &trx->nm_state;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ nm_state = &trx->bb_transc.nm_state;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ nm_state = &trx->ts[obj_inst->ts_nr].nm_state;
+ break;
+ case NM_OC_SITE_MANAGER:
+ nm_state = &bts->site_mgr.nm_state;
+ break;
+ case NM_OC_BS11:
+ switch (obj_inst->bts_nr) {
+ case BS11_OBJ_CCLK:
+ nm_state = &bts->bs11.cclk.nm_state;
+ 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);
+ nm_state = &trx->bs11.bbsig.nm_state;
+ 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);
+ nm_state = &trx->bs11.pa.nm_state;
+ break;
+ default:
+ return NULL;
+ }
+ case NM_OC_BS11_RACK:
+ nm_state = &bts->bs11.rack.nm_state;
+ break;
+ case NM_OC_BS11_ENVABTSE:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+ return NULL;
+ nm_state = &bts->bs11.envabtse[obj_inst->trx_nr].nm_state;
+ break;
+ case NM_OC_GPRS_NSE:
+ nm_state = &bts->gprs.nse.nm_state;
+ break;
+ case NM_OC_GPRS_CELL:
+ nm_state = &bts->gprs.cell.nm_state;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ nm_state = &bts->gprs.nsvc[obj_inst->trx_nr].nm_state;
+ break;
+ }
+ return nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+static void *
+objclass2obj(struct gsm_bts *bts, u_int8_t obj_class,
+ 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) {
+ DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+ 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) {
+ DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+ 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) {
+ DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+ 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;
+}
+
+/* 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, u_int8_t obj_class,
+ struct abis_om_obj_inst *obj_inst, u_int8_t adm_state)
+{
+ struct gsm_nm_state *nm_state, new_state;
+ struct nm_statechg_signal_data nsd;
+
+ nsd.obj = objclass2obj(bts, obj_class, obj_inst);
+ if (!nsd.obj)
+ return -EINVAL;
+ nm_state = objclass2nmstate(bts, obj_class, obj_inst);
+ if (!nm_state)
+ return -1;
+
+ new_state = *nm_state;
+ new_state.administrative = adm_state;
+
+ nsd.obj_class = obj_class;
+ nsd.old_state = nm_state;
+ nsd.new_state = &new_state;
+ nsd.obj_inst = obj_inst;
+ dispatch_signal(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 gsm_bts *bts = mb->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 = 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 ", 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) ", 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 ", nm_adm_name(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 = 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;
+ dispatch_signal(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 int rx_fail_evt_rep(struct msgb *mb)
+{
+ struct abis_om_hdr *oh = msgb_l2(mb);
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ struct tlv_parsed tp;
+ const uint8_t *p_val;
+ char *p_text;
+
+ DEBUGPC(DNM, "Failure Event Report ");
+
+ abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+ if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
+ DEBUGPC(DNM, "Type=%s ", event_type_name(*TLVP_VAL(&tp, NM_ATT_EVENT_TYPE)));
+ if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
+ DEBUGPC(DNM, "Severity=%s ", 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);
+ DEBUGPC(DNM, "Probable cause= %02X %02X %02X ", p_val[0], p_val[1], p_val[2]);
+ }
+ 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 (p_text) {
+ DEBUGPC(DNM, "Additional Text=%s ", p_text);
+ talloc_free(p_text);
+ }
+ }
+
+ DEBUGPC(DNM, "\n");
+
+ return 0;
+}
+
+static int abis_nm_rcvmsg_report(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ u_int8_t mt = foh->msg_type;
+
+ debugp_foh(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");
+ dispatch_signal(SS_NM, S_NM_SW_ACTIV_REP, mb);
+ break;
+ case NM_MT_FAILURE_EVENT_REP:
+ rx_fail_evt_rep(mb);
+ dispatch_signal(SS_NM, S_NM_FAIL_REP, mb);
+ break;
+ case NM_MT_TEST_REP:
+ DEBUGPC(DNM, "Test Report\n");
+ dispatch_signal(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, u_int8_t obj_class, u_int8_t i0, u_int8_t i1,
+ u_int8_t i2, const u_int8_t *sw_desc, u_int8_t swdesc_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_t len = swdesc_len;
+ u_int8_t *trailer;
+
+ 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);
+
+ trailer = msgb_put(msg, swdesc_len);
+ memcpy(trailer, sw_desc, swdesc_len);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+static int abis_nm_parse_sw_descr(const u_int8_t *sw_descr, int sw_descr_len)
+{
+ static const struct tlv_definition sw_descr_def = {
+ .def = {
+ [NM_ATT_FILE_ID] = { TLV_TYPE_TL16V, },
+ [NM_ATT_FILE_VERSION] = { TLV_TYPE_TL16V, },
+ },
+ };
+
+ u_int8_t tag;
+ u_int16_t tag_len;
+ const u_int8_t *val;
+ int ofs = 0, len;
+
+ /* Classic TLV parsing doesn't work well with SW_DESCR because of it's
+ * nested nature and the fact you have to assume it contains only two sub
+ * tags NM_ATT_FILE_VERSION & NM_ATT_FILE_ID to parse it */
+
+ if (sw_descr[0] != NM_ATT_SW_DESCR) {
+ DEBUGP(DNM, "SW_DESCR attribute identifier not found!\n");
+ return -1;
+ }
+ ofs += 1;
+
+ len = tlv_parse_one(&tag, &tag_len, &val,
+ &sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+ if (len < 0 || (tag != NM_ATT_FILE_ID)) {
+ DEBUGP(DNM, "FILE_ID attribute identifier not found!\n");
+ return -2;
+ }
+ ofs += len;
+
+ len = tlv_parse_one(&tag, &tag_len, &val,
+ &sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+ if (len < 0 || (tag != NM_ATT_FILE_VERSION)) {
+ DEBUGP(DNM, "FILE_VERSION attribute identifier not found!\n");
+ return -3;
+ }
+ ofs += len;
+
+ return ofs;
+}
+
+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 tlv_parsed tp;
+ const u_int8_t *sw_config;
+ int ret, sw_config_len, sw_descr_len;
+
+ debugp_foh(foh);
+
+ DEBUGPC(DNM, "SW Activate Request: ");
+
+ DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n");
+
+ ret = abis_nm_sw_act_req_ack(mb->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));
+
+ abis_nm_tlv_parse(&tp, mb->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)) {
+ DEBUGP(DNM, "SW config not found! Can't continue.\n");
+ return -EINVAL;
+ } else {
+ DEBUGP(DNM, "Found SW config: %s\n", hexdump(sw_config, sw_config_len));
+ }
+
+ /* Use the first SW_DESCR present in SW config */
+ sw_descr_len = abis_nm_parse_sw_descr(sw_config, sw_config_len);
+ if (sw_descr_len < 0)
+ return -EINVAL;
+
+ return ipacc_sw_activate(mb->trx->bts, foh->obj_class,
+ foh->obj_inst.bts_nr,
+ foh->obj_inst.trx_nr,
+ foh->obj_inst.ts_nr,
+ sw_config, sw_descr_len);
+}
+
+/* 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 tlv_parsed tp;
+ u_int8_t adm_state;
+
+ abis_nm_tlv_parse(&tp, mb->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(mb->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 tlv_parsed tp;
+
+ DEBUGP(DNM, "LMT Event ");
+ abis_nm_tlv_parse(&tp, mb->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) {
+ u_int8_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) {
+ u_int8_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;
+}
+
+static 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, 0);
+
+ 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);
+ u_int8_t mt = foh->msg_type;
+ int ret = 0;
+
+ /* check for unsolicited message */
+ if (is_report(mt))
+ return abis_nm_rcvmsg_report(mb);
+
+ if (is_in_arr(mt, sw_load_msgs, ARRAY_SIZE(sw_load_msgs)))
+ return abis_nm_rcvmsg_sw(mb);
+
+ if (is_in_arr(mt, nacks, ARRAY_SIZE(nacks))) {
+ struct nm_nack_signal_data nack_data;
+ struct tlv_parsed tp;
+
+ debugp_foh(foh);
+
+ DEBUGPC(DNM, "%s NACK ", get_value_string(nack_names, mt));
+
+ abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+ if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+ DEBUGPC(DNM, "CAUSE=%s\n",
+ nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ DEBUGPC(DNM, "\n");
+
+ nack_data.msg = mb;
+ nack_data.mt = mt;
+ dispatch_signal(SS_NM, S_NM_NACK, &nack_data);
+ abis_nm_queue_send_next(mb->trx->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_CONN_MDROP_LINK_ACK:
+ DEBUGP(DNM, "CONN MDROP LINK ACK\n");
+ break;
+ case NM_MT_IPACC_RESTART_ACK:
+ dispatch_signal(SS_NM, S_NM_IPACC_RESTART_ACK, NULL);
+ break;
+ case NM_MT_IPACC_RESTART_NACK:
+ dispatch_signal(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
+ break;
+ case NM_MT_SET_BTS_ATTR_ACK:
+ /* The HSL wants an OPSTART _after_ the SI has been set */
+ if (mb->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) {
+ abis_nm_opstart(mb->trx->bts, NM_OC_BTS, 255, 255, 255);
+ }
+ break;
+ }
+
+ abis_nm_queue_send_next(mb->trx->bts);
+ return ret;
+}
+
+static int abis_nm_rx_ipacc(struct msgb *mb);
+
+static int abis_nm_rcvmsg_manuf(struct msgb *mb)
+{
+ int rc;
+ int bts_type = mb->trx->bts->type;
+
+ switch (bts_type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ rc = abis_nm_rx_ipacc(mb);
+ abis_nm_queue_send_next(mb->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)
+ return -EINVAL;
+ }
+ if (oh->sequence != 0) {
+ LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+ oh->sequence);
+ return -EINVAL;
+ }
+#if 0
+ unsigned int l2_len = msg->tail - (u_int8_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);
+ return -EINVAL;
+ }
+
+ 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 */
+ u_int8_t obj_class;
+ u_int8_t obj_instance[3];
+
+ u_int8_t file_id[255];
+ u_int8_t file_id_len;
+
+ u_int8_t file_version[255];
+ u_int8_t file_version_len;
+
+ u_int8_t window_size;
+ u_int8_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();
+ u_int8_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);
+ if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) {
+ fseek(stream, pos, SEEK_SET);
+ 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;
+ u_int8_t 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, (u_int8_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: {
+ 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 u_int8_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();
+ u_int8_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();
+ u_int8_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);
+ 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(mb->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(mb->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(mb->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(mb->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(mb->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(mb->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(mb->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,
+ u_int8_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, u_int8_t bts_port,
+ u_int8_t ts_nr, u_int8_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, u_int8_t trx_nr,
+ u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot,
+ u_int8_t tei)
+{
+ struct abis_om_hdr *oh;
+ struct abis_nm_channel *ch;
+ u_int8_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,
+ u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_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,
+ u_int8_t e1_port, u_int8_t e1_timeslot,
+ u_int8_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,
+ u_int8_t subchan)
+{
+}
+#endif
+
+/* Chapter 8.6.1 */
+int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_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, u_int8_t *attr, int attr_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_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);
+}
+
+static int verify_chan_comb(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+ int i;
+
+ /* 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:
+ /* 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)
+ return -EINVAL;
+ }
+ /* not allowed for TS0 of BCCH-TRX */
+ if (ts->trx == ts->trx->bts->c0 &&
+ ts->nr == 0)
+ 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))
+ 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)
+ return -EINVAL;
+ break;
+ case NM_CHANC_BCCH:
+ /* allowed only for TS 2/4/6 of C0 */
+ if (ts->trx != ts->trx->bts->c0)
+ return -EINVAL;
+ if (ts->nr != 2 && ts->nr != 4 &&
+ ts->nr != 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:
+ return -EINVAL;
+ }
+ } else {
+ switch (chan_comb) {
+ case NM_CHANC_TCHFull:
+ case NM_CHANC_TCHHalf:
+ case NM_CHANC_IPAC_TCHFull_TCHHalf:
+ return 0;
+ default:
+ 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;
+ 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:
+ return 0;
+ }
+ } 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:
+ 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:
+ if (ts->trx->nr == 0)
+ return 0;
+ else
+ return -EINVAL;
+ }
+ break;
+ }
+ return -EINVAL;
+ default:
+ /* unknown BTS type */
+ return 0;
+ }
+ return 0;
+}
+
+/* Chapter 8.6.3 */
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+ struct gsm_bts *bts = ts->trx->bts;
+ struct abis_om_hdr *oh;
+ u_int8_t zero = 0x00;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_t len = 2 + 2;
+
+ 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) < 0) {
+ msgb_free(msg);
+ DEBUGP(DNM, "Invalid Channel Combination!!!\n");
+ 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, bts->tsc); /* 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, u_int8_t obj_class, u_int8_t i1,
+ u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_t msgtype = NM_MT_SW_ACT_REQ_ACK;
+ u_int8_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) {
+ u_int8_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, u_int8_t *rawmsg)
+{
+ struct msgb *msg = nm_msgb_alloc();
+ struct abis_om_hdr *oh;
+ u_int8_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, u_int8_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, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2)
+{
+ 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_OPSTART, obj_class, i0, i1, i2);
+
+ debugp_foh((struct abis_om_fom_hdr *) oh->data);
+ 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, u_int8_t obj_class, u_int8_t i0,
+ u_int8_t i1, u_int8_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, u_int8_t e1_port0, u_int8_t ts0,
+ u_int8_t e1_port1, u_int8_t ts1)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_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, u_int8_t obj_class,
+ u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+ u_int8_t test_nr, u_int8_t auton_report, struct msgb *msg)
+{
+ struct abis_om_hdr *oh;
+
+ DEBUGP(DNM, "PEFORM TEST %s\n", get_value_string(test_names, 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 {
+ u_int16_t year;
+ u_int8_t month;
+ u_int8_t day;
+ u_int8_t hour;
+ u_int8_t min;
+ u_int8_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, u_int8_t idx,
+ u_int8_t attr_len, const u_int8_t *attr)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_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, u_int8_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, u_int8_t idx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_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, u_int8_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, u_int8_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 u_int8_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, u_int8_t e1_port,
+ u_int8_t e1_timeslot, u_int8_t e1_subslot,
+ u_int8_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, u_int8_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();
+ u_int8_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();
+ u_int8_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();
+ u_int8_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 u_int8_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, u_int8_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) {
+ u_int8_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), (u_int8_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), (u_int8_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 u_int8_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;
+ u_int8_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;
+ u_int8_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];
+ u_int8_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 */
+ strncpy(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,
+ u_int8_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;
+
+ strncpy(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 u_int8_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 u_int8_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 u_int8_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), (u_int8_t *) &aet);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ u_int8_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, u_int8_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;
+ u_int8_t idstrlen = oh->data[0];
+ struct tlv_parsed tp;
+ struct ipacc_ack_signal_data signal;
+
+ 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, msg->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+ debugp_foh(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(*((u_int16_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))
+ DEBUGPC(DNM, " CAUSE=%s\n",
+ nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ DEBUGPC(DNM, "\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",
+ 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",
+ 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",
+ 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(msg->trx->bts, foh->obj_inst.trx_nr);
+ signal.msg_type = foh->msg_type;
+ dispatch_signal(SS_NM, S_NM_IPACC_NACK, &signal);
+ break;
+ case NM_MT_IPACC_SET_NVATTR_ACK:
+ signal.trx = gsm_bts_trx_by_nr(msg->trx->bts, foh->obj_inst.trx_nr);
+ signal.msg_type = foh->msg_type;
+ dispatch_signal(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, u_int8_t msg_type,
+ u_int8_t obj_class, u_int8_t bts_nr,
+ u_int8_t trx_nr, u_int8_t ts_nr,
+ u_int8_t *attr, int attr_len)
+{
+ struct msgb *msg = nm_msgb_alloc();
+ struct abis_om_hdr *oh;
+ struct abis_om_fom_hdr *foh;
+ u_int8_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, u_int8_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,
+ u_int32_t ip, u_int16_t port, u_int8_t stream)
+{
+ struct in_addr ia;
+ u_int8_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;
+ *(u_int32_t *)(attr+6) = ia.s_addr;
+
+ /* if ip == 0, we use the default IP */
+ if (ip == 0)
+ attr_len -= 5;
+
+ DEBUGP(DNM, "ip.access RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ 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(trx->bts, msg);
+}
+
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class,
+ u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+ u_int8_t *attr, u_int8_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(u_int8_t *buf, struct gsm_bts *bts)
+{
+ /* we simply reuse the GSM48 function and overwrite the RAC
+ * with the Cell ID */
+ gsm48_ra_id_by_bts(buf, bts);
+ *((u_int16_t *)(buf + 5)) = htons(bts->cell_identity);
+}
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked)
+{
+ int new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+
+ trx->nm_state.administrative = new_state;
+ if (!trx->bts || !trx->bts->oml_link)
+ return;
+
+ 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(u_int8_t res)
+{
+ return get_value_string(ipacc_testres_names, res);
+}
+
+void ipac_parse_cgi(struct cell_global_id *cid, const u_int8_t *buf)
+{
+ cid->mcc = (buf[0] & 0xf) * 100;
+ cid->mcc += (buf[0] >> 4) * 10;
+ cid->mcc += (buf[1] & 0xf) * 1;
+
+ if (buf[1] >> 4 == 0xf) {
+ cid->mnc = (buf[2] & 0xf) * 10;
+ cid->mnc += (buf[2] >> 4) * 1;
+ } else {
+ cid->mnc = (buf[2] & 0xf) * 100;
+ cid->mnc += (buf[2] >> 4) * 10;
+ cid->mnc += (buf[1] >> 4) * 1;
+ }
+
+ cid->lac = ntohs(*((u_int16_t *)&buf[3]));
+ cid->ci = ntohs(*((u_int16_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, u_int8_t *buf)
+{
+ u_int8_t *cur = buf;
+ u_int16_t len;
+
+ memset(binf, 0, sizeof(*binf));
+
+ if (cur[0] != NM_IPAC_EIE_BCCH_INFO)
+ return -EINVAL;
+ cur++;
+
+ len = ntohs(*(u_int16_t *)cur);
+ cur += 2;
+
+ binf->info_type = ntohs(*(u_int16_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(*(u_int16_t *)cur);
+ cur += 2;
+
+ if (binf->info_type & IPAC_BINF_FRAME_OFFSET)
+ binf->frame_offset = ntohs(*(u_int16_t *)cur);
+ cur += 2;
+
+ if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET)
+ binf->frame_nr_offset = ntohl(*(u_int32_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/src/libbsc/abis_nm_vty.c b/src/libbsc/abis_nm_vty.c
new file mode 100644
index 00000000..996a8574
--- /dev/null
+++ b/src/libbsc/abis_nm_vty.c
@@ -0,0 +1,197 @@
+/* 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 <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/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 "FIXME"
+
+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(bsc_gsmnet, 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(bsc_gsmnet, 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_attrib_get, oml_attrib_get_cmd,
+ "attribute get <0-255>",
+ "OML Attribute Actions\n" "Get a single OML Attribute\n"
+ "OML Attribute Number\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ /* FIXME */
+ return CMD_SUCCESS;
+}
+
+DEFUN(oml_attrib_set, oml_attrib_set_cmd,
+ "attribute set <0-255> .HEX",
+ "OML Attribute Actions\n" "Set a single OML Attribute\n"
+ "OML Attribute Number\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ /* FIXME */
+ 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_default(OML_NODE);
+ install_element(OML_NODE, &ournode_exit_cmd);
+ install_element(OML_NODE, &oml_attrib_get_cmd);
+ install_element(OML_NODE, &oml_attrib_set_cmd);
+ install_element(OML_NODE, &oml_chg_adm_state_cmd);
+ install_element(OML_NODE, &oml_opstart_cmd);
+
+ return 0;
+}
diff --git a/src/libbsc/abis_om2000.c b/src/libbsc/abis_om2000.c
new file mode 100644
index 00000000..805b844c
--- /dev/null
+++ b/src/libbsc/abis_om2000.c
@@ -0,0 +1,1078 @@
+/* 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.