Merge branch 'master' into on-waves/mgcp
diff --git a/libosmocore/.gitignore b/libosmocore/.gitignore
index d61cdc5..06904fa 100644
--- a/libosmocore/.gitignore
+++ b/libosmocore/.gitignore
@@ -7,6 +7,7 @@
 *.la
 *.pc
 aclocal.m4
+m4/*.m4
 autom4te.cache
 config.h*
 config.sub
@@ -23,3 +24,7 @@
 
 .tarball-version
 .version
+
+tests/sms/sms_test
+tests/timer/timer_test
+
diff --git a/libosmocore/include/osmocore/Makefile.am b/libosmocore/include/osmocore/Makefile.am
index 1c3a33f..fde0102 100644
--- a/libosmocore/include/osmocore/Makefile.am
+++ b/libosmocore/include/osmocore/Makefile.am
@@ -1,7 +1,7 @@
 osmocore_HEADERS = signal.h linuxlist.h timer.h select.h msgb.h \
 		   tlv.h bitvec.h comp128.h statistics.h gsm_utils.h utils.h \
 		   gsmtap.h write_queue.h rsl.h gsm48.h rxlev_stat.h mncc.h \
-		   gsm48_ie.h logging.h
+		   gsm48_ie.h logging.h gsm0808.h rate_ctr.h
 
 if ENABLE_TALLOC
 osmocore_HEADERS += talloc.h
diff --git a/libosmocore/include/osmocore/gsm0808.h b/libosmocore/include/osmocore/gsm0808.h
new file mode 100644
index 0000000..a40713f
--- /dev/null
+++ b/libosmocore/include/osmocore/gsm0808.h
@@ -0,0 +1,41 @@
+/* (C) 2009,2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009,2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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.
+ *
+ */
+#ifndef OSMOCORE_GSM0808_H
+#define OSMOCORE_GSM0808_H
+
+#include "tlv.h"
+
+struct msgb;
+
+struct msgb *gsm0808_create_layer3(struct msgb *msg, uint16_t netcode, uint16_t countrycode, int lac, int ci);
+struct msgb *gsm0808_create_reset(void);
+struct msgb *gsm0808_create_clear_complete(void);
+struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id);
+struct msgb *gsm0808_create_cipher_reject(uint8_t cause);
+struct msgb *gsm0808_create_classmark_update(const uint8_t *classmark, uint8_t length);
+struct msgb *gsm0808_create_sapi_reject(uint8_t link_id);
+struct msgb *gsm0808_create_assignment_completed(struct gsm_lchan *lchan, uint8_t rr_cause,
+						 uint8_t chosen_channel, uint8_t encr_alg_id,
+						 uint8_t speech_mode);
+struct msgb *gsm0808_create_assignment_failure(uint8_t cause, uint8_t *rr_cause);
+
+const struct tlv_definition *gsm0808_att_tlvdef();
+
+#endif
diff --git a/libosmocore/include/osmocore/gsm48.h b/libosmocore/include/osmocore/gsm48.h
index 1e96357..be9fbbd 100644
--- a/libosmocore/include/osmocore/gsm48.h
+++ b/libosmocore/include/osmocore/gsm48.h
@@ -1,9 +1,18 @@
 #ifndef _OSMOCORE_GSM48_H
+#define _OSMOCORE_GSM48_H
 
 #include <osmocore/tlv.h>
 #include <osmocore/protocol/gsm_04_08.h>
 #include <osmocore/gsm48_ie.h>
 
+/* A parsed GPRS routing area */
+struct gprs_ra_id {
+	uint16_t	mnc;
+	uint16_t	mcc;
+	uint16_t	lac;
+	uint8_t		rac;
+};
+
 extern const struct tlv_definition gsm48_att_tlvdef;
 const char *gsm48_cc_state_name(uint8_t state);
 const char *gsm48_cc_msg_name(uint8_t msgtype);
@@ -14,4 +23,12 @@
 int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi);
 int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi);
 
+/* Convert Mobile Identity (10.5.1.4) to string */
+int gsm48_mi_to_string(char *string, const int str_len,
+			const uint8_t *mi, const int mi_len);
+
+/* Parse Routeing Area Identifier */
+void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf);
+int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid);
+
 #endif
diff --git a/libosmocore/include/osmocore/gsm_utils.h b/libosmocore/include/osmocore/gsm_utils.h
index c87e967..51e9f2e 100644
--- a/libosmocore/include/osmocore/gsm_utils.h
+++ b/libosmocore/include/osmocore/gsm_utils.h
@@ -27,6 +27,13 @@
 
 #include <stdint.h>
 
+#define ADD_MODULO(sum, delta, modulo) do {	\
+	if ((sum += delta) >= modulo)		\
+		sum -= modulo;			\
+	} while (0)
+
+#define GSM_MAX_FN	(26*51*2048)
+
 struct gsm_time {
 	uint32_t	fn;	/* FN count */
 	uint16_t	t1;	/* FN div (26*51) */
@@ -80,5 +87,17 @@
 /* Convert from GSM time to frame number */
 uint32_t gsm_gsmtime2fn(struct gsm_time *time);
 
+/* GSM TS 03.03 Chapter 2.6 */
+enum gprs_tlli_tyoe {
+	TLLI_LOCAL,
+	TLLI_FOREIGN,
+	TLLI_RANDOM,
+	TLLI_AUXILIARY,
+	TLLI_RESERVED,
+};
+
+/* TS 03.03 Chapter 2.6 */
+int gprs_tlli_type(uint32_t tlli);
+
 void generate_backtrace();
 #endif
diff --git a/libosmocore/include/osmocore/logging.h b/libosmocore/include/osmocore/logging.h
index 93f18a0..2e82959 100644
--- a/libosmocore/include/osmocore/logging.h
+++ b/libosmocore/include/osmocore/logging.h
@@ -117,6 +117,7 @@
 void log_set_log_level(struct log_target *target, int log_level);
 void log_parse_category_mask(struct log_target *target, const char* mask);
 int log_parse_level(const char *lvl);
+const char *log_level_str(unsigned int lvl);
 int log_parse_category(const char *category);
 void log_set_category_filter(struct log_target *target, int category,
 			       int enable, int level);
@@ -127,4 +128,8 @@
 void log_add_target(struct log_target *target);
 void log_del_target(struct log_target *target);
 
+/* Gernerate command argument strings for VTY use */
+const char *log_vty_category_string(struct log_info *info);
+const char *log_vty_level_string(struct log_info *info);
+
 #endif /* _OSMOCORE_LOGGING_H */
diff --git a/libosmocore/include/osmocore/msgb.h b/libosmocore/include/osmocore/msgb.h
index 31db719..2841dc5 100644
--- a/libosmocore/include/osmocore/msgb.h
+++ b/libosmocore/include/osmocore/msgb.h
@@ -23,15 +23,11 @@
 #include <stdint.h>
 #include "linuxlist.h"
 
-struct bts_link;
-
 struct msgb {
 	struct llist_head list;
 
-	/* ptr to the physical E1 link to the BTS(s) */
-	struct gsm_bts_link *bts_link;
-
 	/* Part of which TRX logical channel we were received / transmitted */
+	/* FIXME: move them into the control buffer */
 	struct gsm_bts_trx *trx;
 	struct gsm_lchan *lchan;
 
@@ -41,17 +37,11 @@
 	unsigned char *l2h;
 	/* the layer 3 header. For OML: FOM; RSL: 04.08; GPRS: BSSGP */
 	unsigned char *l3h;
-
 	/* the layer 4 header */
-	union {
-		unsigned char *smsh;
-		unsigned char *llch;
-		unsigned char *l4h;
-	};
+	unsigned char *l4h;
 
-	/* the layer 5 header, GPRS: GMM header */
-	unsigned char *gmmh;
-	uint32_t tlli;
+	/* the 'control buffer', large enough to contain 5 pointers */
+	unsigned long cb[5];
 
 	uint16_t data_len;
 	uint16_t len;
@@ -71,7 +61,7 @@
 #define msgb_l1(m)	((void *)(m->l1h))
 #define msgb_l2(m)	((void *)(m->l2h))
 #define msgb_l3(m)	((void *)(m->l3h))
-#define msgb_sms(m)	((void *)(m->smsh))
+#define msgb_sms(m)	((void *)(m->l4h))
 
 static inline unsigned int msgb_l1len(const struct msgb *msgb)
 {
diff --git a/libosmocore/include/osmocore/protocol/Makefile.am b/libosmocore/include/osmocore/protocol/Makefile.am
index 6d8883e..557950e 100644
--- a/libosmocore/include/osmocore/protocol/Makefile.am
+++ b/libosmocore/include/osmocore/protocol/Makefile.am
@@ -1,3 +1,3 @@
-osmocore_proto_HEADERS = gsm_04_08.h gsm_04_11.h gsm_04_80.h gsm_08_58.h gsm_12_21.h
+osmocore_proto_HEADERS = gsm_04_08.h gsm_04_11.h gsm_04_80.h gsm_08_58.h gsm_12_21.h gsm_08_08.h
 
 osmocore_protodir = $(includedir)/osmocore/protocol
diff --git a/libosmocore/include/osmocore/protocol/gsm_04_08.h b/libosmocore/include/osmocore/protocol/gsm_04_08.h
index 801b9b5..1a112a0 100644
--- a/libosmocore/include/osmocore/protocol/gsm_04_08.h
+++ b/libosmocore/include/osmocore/protocol/gsm_04_08.h
@@ -685,6 +685,7 @@
 /* Chapter 5.1.2.2 */
 #define	GSM_CSTATE_NULL			0
 #define	GSM_CSTATE_INITIATED		1
+#define	GSM_CSTATE_MM_CONNECTION_PEND	2 /* see 10.5.4.6 */
 #define	GSM_CSTATE_MO_CALL_PROC		3
 #define	GSM_CSTATE_CALL_DELIVERED	4
 #define	GSM_CSTATE_CALL_PRESENT		6
@@ -734,10 +735,17 @@
 	GSM48_BCAP_RRQ_DUAL_FR	= 3,
 };
 
-
 #define GSM48_TMSI_LEN	5
 #define GSM48_MID_TMSI_LEN	(GSM48_TMSI_LEN + 2)
 #define GSM48_MI_SIZE 32
 
+/* Chapter 10.4.4.15 */
+struct gsm48_ra_id {
+	uint8_t digits[3];	/* MCC + MNC BCD digits */
+	uint16_t lac;		/* Location Area Code */
+	uint8_t rac;		/* Routing Area Code */
+} __attribute__ ((packed));
+
+
 
 #endif /* PROTO_GSM_04_08_H */
diff --git a/libosmocore/include/osmocore/protocol/gsm_08_08.h b/libosmocore/include/osmocore/protocol/gsm_08_08.h
new file mode 100644
index 0000000..6b8f935
--- /dev/null
+++ b/libosmocore/include/osmocore/protocol/gsm_08_08.h
@@ -0,0 +1,303 @@
+/* From GSM08.08 */
+
+#ifndef GSM_0808_H
+#define GSM_0808_H
+
+#include <stdlib.h>
+
+/*
+ * this is from GSM 03.03 CGI but is copied in GSM 08.08
+ * in § 3.2.2.27 for Cell Identifier List
+ */
+enum CELL_IDENT {
+	CELL_IDENT_WHOLE_GLOBAL		= 0,
+	CELL_IDENT_LAC_AND_CI		= 1,
+	CELL_IDENT_CI			= 2,
+	CELL_IDENT_NO_CELL		= 3,
+	CELL_IDENT_LAI_AND_LAC		= 4,
+	CELL_IDENT_LAC			= 5,
+	CELL_IDENT_BSS			= 6,
+	CELL_IDENT_UTRAN_PLMN_LAC_RNC	= 8,
+	CELL_IDENT_UTRAN_RNC		= 9,
+	CELL_IDENT_UTRAN_LAC_RNC	= 10,
+};
+
+
+/* GSM 08.06 § 6.3 */
+enum BSSAP_MSG_TYPE {
+	BSSAP_MSG_BSS_MANAGEMENT    = 0x0,
+	BSSAP_MSG_DTAP		    = 0x1,
+};
+
+struct bssmap_header {
+	uint8_t type;
+	uint8_t length;
+} __attribute__((packed));
+
+struct dtap_header {
+	uint8_t type;
+	uint8_t link_id;
+	uint8_t length;
+} __attribute__((packed));
+
+
+enum BSS_MAP_MSG_TYPE {
+	BSS_MAP_MSG_RESERVED_0		= 0,
+
+	/* ASSIGNMENT MESSAGES */
+	BSS_MAP_MSG_ASSIGMENT_RQST	= 1,
+	BSS_MAP_MSG_ASSIGMENT_COMPLETE	= 2,
+	BSS_MAP_MSG_ASSIGMENT_FAILURE	= 3,
+
+	/*  HANDOVER MESSAGES */
+	BSS_MAP_MSG_HANDOVER_RQST		= 16,
+	BSS_MAP_MSG_HANDOVER_REQUIRED		= 17,
+	BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE= 18,
+	BSS_MAP_MSG_HANDOVER_CMD		= 19,
+	BSS_MAP_MSG_HANDOVER_COMPLETE		= 20,
+	BSS_MAP_MSG_HANDOVER_SUCCEEDED		= 21,
+	BSS_MAP_MSG_HANDOVER_FAILURE		= 22,
+	BSS_MAP_MSG_HANDOVER_PERFORMED		= 23,
+	BSS_MAP_MSG_HANDOVER_CANDIDATE_ENQUIRE	= 24,
+	BSS_MAP_MSG_HANDOVER_CANDIDATE_RESPONSE	= 25,
+	BSS_MAP_MSG_HANDOVER_REQUIRED_REJECT	= 26,
+	BSS_MAP_MSG_HANDOVER_DETECT		= 27,
+
+	/* RELEASE MESSAGES */
+	BSS_MAP_MSG_CLEAR_CMD		= 32,
+	BSS_MAP_MSG_CLEAR_COMPLETE		= 33,
+	BSS_MAP_MSG_CLEAR_RQST		= 34,
+	BSS_MAP_MSG_RESERVED_1			= 35,
+	BSS_MAP_MSG_RESERVED_2			= 36,
+	BSS_MAP_MSG_SAPI_N_REJECT		= 37,
+	BSS_MAP_MSG_CONFUSION			= 38,
+
+	/* OTHER CONNECTION RELATED MESSAGES */
+	BSS_MAP_MSG_SUSPEND			= 40,
+	BSS_MAP_MSG_RESUME			= 41,
+	BSS_MAP_MSG_CONNECTION_ORIENTED_INFORMATION = 42,
+	BSS_MAP_MSG_PERFORM_LOCATION_RQST	= 43,
+	BSS_MAP_MSG_LSA_INFORMATION		= 44,
+	BSS_MAP_MSG_PERFORM_LOCATION_RESPONSE	= 45,
+	BSS_MAP_MSG_PERFORM_LOCATION_ABORT	= 46,
+	BSS_MAP_MSG_COMMON_ID			= 47,
+
+	/* GENERAL MESSAGES */
+	BSS_MAP_MSG_RESET			= 48,
+	BSS_MAP_MSG_RESET_ACKNOWLEDGE		= 49,
+	BSS_MAP_MSG_OVERLOAD			= 50,
+	BSS_MAP_MSG_RESERVED_3			= 51,
+	BSS_MAP_MSG_RESET_CIRCUIT		= 52,
+	BSS_MAP_MSG_RESET_CIRCUIT_ACKNOWLEDGE	= 53,
+	BSS_MAP_MSG_MSC_INVOKE_TRACE		= 54,
+	BSS_MAP_MSG_BSS_INVOKE_TRACE		= 55,
+	BSS_MAP_MSG_CONNECTIONLESS_INFORMATION	= 58,
+
+	/* TERRESTRIAL RESOURCE MESSAGES */
+	BSS_MAP_MSG_BLOCK			= 64,
+	BSS_MAP_MSG_BLOCKING_ACKNOWLEDGE	= 65,
+	BSS_MAP_MSG_UNBLOCK			= 66,
+	BSS_MAP_MSG_UNBLOCKING_ACKNOWLEDGE	= 67,
+	BSS_MAP_MSG_CIRCUIT_GROUP_BLOCK		= 68,
+	BSS_MAP_MSG_CIRCUIT_GROUP_BLOCKING_ACKNOWLEDGE	= 69,
+	BSS_MAP_MSG_CIRCUIT_GROUP_UNBLOCK	= 70,
+	BSS_MAP_MSG_CIRCUIT_GROUP_UNBLOCKING_ACKNOWLEDGE = 71,
+	BSS_MAP_MSG_UNEQUIPPED_CIRCUIT		= 72,
+	BSS_MAP_MSG_CHANGE_CIRCUIT		= 78,
+	BSS_MAP_MSG_CHANGE_CIRCUIT_ACKNOWLEDGE	= 79,
+
+	/* RADIO RESOURCE MESSAGES */
+	BSS_MAP_MSG_RESOURCE_RQST		= 80,
+	BSS_MAP_MSG_RESOURCE_INDICATION		= 81,
+	BSS_MAP_MSG_PAGING			= 82,
+	BSS_MAP_MSG_CIPHER_MODE_CMD		= 83,
+	BSS_MAP_MSG_CLASSMARK_UPDATE		= 84,
+	BSS_MAP_MSG_CIPHER_MODE_COMPLETE	= 85,
+	BSS_MAP_MSG_QUEUING_INDICATION		= 86,
+	BSS_MAP_MSG_COMPLETE_LAYER_3		= 87,
+	BSS_MAP_MSG_CLASSMARK_RQST		= 88,
+	BSS_MAP_MSG_CIPHER_MODE_REJECT		= 89,
+	BSS_MAP_MSG_LOAD_INDICATION		= 90,
+
+	/* VGCS/VBS */
+	BSS_MAP_MSG_VGCS_VBS_SETUP		= 4,
+	BSS_MAP_MSG_VGCS_VBS_SETUP_ACK		= 5,
+	BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE	= 6,
+	BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST	= 7,
+	BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT	= 28,
+	BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE	= 29,
+	BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION	= 30,
+	BSS_MAP_MSG_UPLINK_RQST		= 31,
+	BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE	= 39,
+	BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION	= 73,
+	BSS_MAP_MSG_UPLINK_RELEASE_INDICATION	= 74,
+	BSS_MAP_MSG_UPLINK_REJECT_CMD	= 75,
+	BSS_MAP_MSG_UPLINK_RELEASE_CMD	= 76,
+	BSS_MAP_MSG_UPLINK_SEIZED_CMD	= 77,
+};
+
+enum GSM0808_IE_CODING {
+	GSM0808_IE_CIRCUIT_IDENTITY_CODE	= 1,
+	GSM0808_IE_RESERVED_0			= 2,
+	GSM0808_IE_RESOURCE_AVAILABLE		= 3,
+	GSM0808_IE_CAUSE			= 4,
+	GSM0808_IE_CELL_IDENTIFIER		= 5,
+	GSM0808_IE_PRIORITY			= 6,
+	GSM0808_IE_LAYER_3_HEADER_INFORMATION	= 7,
+	GSM0808_IE_IMSI				= 8,
+	GSM0808_IE_TMSI				= 9,
+	GSM0808_IE_ENCRYPTION_INFORMATION	= 10,
+	GSM0808_IE_CHANNEL_TYPE			= 11,
+	GSM0808_IE_PERIODICITY			= 12,
+	GSM0808_IE_EXTENDED_RESOURCE_INDICATOR	= 13,
+	GSM0808_IE_NUMBER_OF_MSS		= 14,
+	GSM0808_IE_RESERVED_1			= 15,
+	GSM0808_IE_RESERVED_2			= 16,
+	GSM0808_IE_RESERVED_3			= 17,
+	GSM0808_IE_CLASSMARK_INFORMATION_T2	= 18,
+	GSM0808_IE_CLASSMARK_INFORMATION_T3	= 19,
+	GSM0808_IE_INTERFERENCE_BAND_TO_USE	= 20,
+	GSM0808_IE_RR_CAUSE			= 21,
+	GSM0808_IE_RESERVED_4			= 22,
+	GSM0808_IE_LAYER_3_INFORMATION		= 23,
+	GSM0808_IE_DLCI				= 24,
+	GSM0808_IE_DOWNLINK_DTX_FLAG		= 25,
+	GSM0808_IE_CELL_IDENTIFIER_LIST		= 26,
+	GSM0808_IE_RESPONSE_RQST		= 27,
+	GSM0808_IE_RESOURCE_INDICATION_METHOD	= 28,
+	GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1	= 29,
+	GSM0808_IE_CIRCUIT_IDENTITY_CODE_LIST	= 30,
+	GSM0808_IE_DIAGNOSTIC			= 31,
+	GSM0808_IE_LAYER_3_MESSAGE_CONTENTS	= 32,
+	GSM0808_IE_CHOSEN_CHANNEL		= 33,
+	GSM0808_IE_TOTAL_RESOURCE_ACCESSIBLE	= 34,
+	GSM0808_IE_CIPHER_RESPONSE_MODE		= 35,
+	GSM0808_IE_CHANNEL_NEEDED		= 36,
+	GSM0808_IE_TRACE_TYPE			= 37,
+	GSM0808_IE_TRIGGERID			= 38,
+	GSM0808_IE_TRACE_REFERENCE		= 39,
+	GSM0808_IE_TRANSACTIONID		= 40,
+	GSM0808_IE_MOBILE_IDENTITY		= 41,
+	GSM0808_IE_OMCID			= 42,
+	GSM0808_IE_FORWARD_INDICATOR		= 43,
+	GSM0808_IE_CHOSEN_ENCR_ALG		= 44,
+	GSM0808_IE_CIRCUIT_POOL			= 45,
+	GSM0808_IE_CIRCUIT_POOL_LIST		= 46,
+	GSM0808_IE_TIME_INDICATION		= 47,
+	GSM0808_IE_RESOURCE_SITUATION		= 48,
+	GSM0808_IE_CURRENT_CHANNEL_TYPE_1	= 49,
+	GSM0808_IE_QUEUEING_INDICATOR		= 50,
+	GSM0808_IE_SPEECH_VERSION		= 64,
+	GSM0808_IE_ASSIGNMENT_REQUIREMENT	= 51,
+	GSM0808_IE_TALKER_FLAG			= 53,
+	GSM0808_IE_CONNECTION_RELEASE_RQSTED	= 54,
+	GSM0808_IE_GROUP_CALL_REFERENCE		= 55,
+	GSM0808_IE_EMLPP_PRIORITY		= 56,
+	GSM0808_IE_CONFIG_EVO_INDI		= 57,
+	GSM0808_IE_OLD_BSS_TO_NEW_BSS_INFORMATION	= 58,
+	GSM0808_IE_LSA_IDENTIFIER		= 59,
+	GSM0808_IE_LSA_IDENTIFIER_LIST		= 60,
+	GSM0808_IE_LSA_INFORMATION		= 61,
+	GSM0808_IE_LCS_QOS			= 62,
+	GSM0808_IE_LSA_ACCESS_CTRL_SUPPR	= 63,
+	GSM0808_IE_LCS_PRIORITY			= 67,
+	GSM0808_IE_LOCATION_TYPE		= 68,
+	GSM0808_IE_LOCATION_ESTIMATE		= 69,
+	GSM0808_IE_POSITIONING_DATA		= 70,
+	GSM0808_IE_LCS_CAUSE			= 71,
+	GSM0808_IE_LCS_CLIENT_TYPE		= 72,
+	GSM0808_IE_APDU				= 73,
+	GSM0808_IE_NETWORK_ELEMENT_IDENTITY	= 74,
+	GSM0808_IE_GPS_ASSISTANCE_DATA		= 75,
+	GSM0808_IE_DECIPHERING_KEYS		= 76,
+	GSM0808_IE_RETURN_ERROR_RQST		= 77,
+	GSM0808_IE_RETURN_ERROR_CAUSE		= 78,
+	GSM0808_IE_SEGMENTATION			= 79,
+	GSM0808_IE_SERVICE_HANDOVER		= 80,
+	GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_UMTS	= 81,
+	GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_CDMA2000= 82,
+	GSM0808_IE_RESERVED_5			= 65,
+	GSM0808_IE_RESERVED_6			= 66,
+};
+
+enum gsm0808_cause {
+	GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE			= 0,
+	GSM0808_CAUSE_RADIO_INTERFACE_FAILURE				= 1,
+	GSM0808_CAUSE_UPLINK_QUALITY					= 2,
+	GSM0808_CAUSE_UPLINK_STRENGTH					= 3,
+	GSM0808_CAUSE_DOWNLINK_QUALITY					= 4,
+	GSM0808_CAUSE_DOWNLINK_STRENGTH					= 5,
+	GSM0808_CAUSE_DISTANCE						= 6,
+	GSM0808_CAUSE_O_AND_M_INTERVENTION				= 7,
+	GSM0808_CAUSE_RESPONSE_TO_MSC_INVOCATION			= 8,
+	GSM0808_CAUSE_CALL_CONTROL					= 9,
+	GSM0808_CAUSE_RADIO_INTERFACE_FAILURE_REVERSION			= 10,
+	GSM0808_CAUSE_HANDOVER_SUCCESSFUL				= 11,
+	GSM0808_CAUSE_BETTER_CELL					= 12,
+	GSM0808_CAUSE_DIRECTED_RETRY					= 13,
+	GSM0808_CAUSE_JOINED_GROUP_CALL_CHANNEL				= 14,
+	GSM0808_CAUSE_TRAFFIC						= 15,
+	GSM0808_CAUSE_EQUIPMENT_FAILURE					= 32,
+	GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE			= 33,
+	GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE	= 34,
+	GSM0808_CAUSE_CCCH_OVERLOAD					= 35,
+	GSM0808_CAUSE_PROCESSOR_OVERLOAD				= 36,
+	GSM0808_CAUSE_BSS_NOT_EQUIPPED					= 37,
+	GSM0808_CAUSE_MS_NOT_EQUIPPED					= 38,
+	GSM0808_CAUSE_INVALID_CELL					= 39,
+	GSM0808_CAUSE_TRAFFIC_LOAD					= 40,
+	GSM0808_CAUSE_PREEMPTION					= 41,
+	GSM0808_CAUSE_RQSTED_TRANSCODING_RATE_ADAPTION_UNAVAILABLE	= 48,
+	GSM0808_CAUSE_CIRCUIT_POOL_MISMATCH				= 49,
+	GSM0808_CAUSE_SWITCH_CIRCUIT_POOL				= 50,
+	GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE		= 51,
+	GSM0808_CAUSE_LSA_NOT_ALLOWED					= 52,
+	GSM0808_CAUSE_CIPHERING_ALGORITHM_NOT_SUPPORTED			= 64,
+	GSM0808_CAUSE_TERRESTRIAL_CIRCUIT_ALREADY_ALLOCATED		= 80,
+	GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS				= 81,
+	GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING		= 82,
+	GSM0808_CAUSE_INCORRECT_VALUE					= 83,
+	GSM0808_CAUSE_UNKNOWN_MESSAGE_TYPE				= 84,
+	GSM0808_CAUSE_UNKNOWN_INFORMATION_ELEMENT			= 85,
+	GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC		= 96,
+};
+
+/* GSM 08.08 3.2.2.11 Channel Type */
+enum gsm0808_chan_indicator {
+	GSM0808_CHAN_SPEECH = 1,
+	GSM0808_CHAN_DATA   = 2,
+	GSM0808_CHAN_SIGN   = 3,
+};
+
+enum gsm0808_chan_rate_type_data {
+	GSM0808_DATA_FULL_BM	= 0x8,
+	GSM0808_DATA_HALF_LM	= 0x9,
+	GSM0808_DATA_FULL_RPREF	= 0xa,
+	GSM0808_DATA_HALF_PREF	= 0xb,
+	GSM0808_DATA_FULL_PREF_NO_CHANGE	= 0x1a,
+	GSM0808_DATA_HALF_PREF_NO_CHANGE	= 0x1b,
+	GSM0808_DATA_MULTI_MASK	= 0x20,
+	GSM0808_DATA_MULTI_MASK_NO_CHANGE	= 0x30,
+};
+
+enum gsm0808_chan_rate_type_speech {
+	GSM0808_SPEECH_FULL_BM	= 0x8,
+	GSM0808_SPEECH_HALF_LM	= 0x9,
+	GSM0808_SPEECH_FULL_PREF= 0xa,
+	GSM0808_SPEECH_HALF_PREF= 0xb,
+	GSM0808_SPEECH_FULL_PREF_NO_CHANGE	= 0x1a,
+	GSM0808_SPEECH_HALF_PREF_NO_CHANGE	= 0x1b,
+	GSM0808_SPEECH_PERM	= 0xf,
+	GSM0808_SPEECH_PERM_NO_CHANGE = 0x1f,
+};
+
+enum gsm0808_permitted_speech {
+	GSM0808_PERM_FR1	= 0x01,
+	GSM0808_PERM_FR2	= 0x11,
+	GSM0808_PERM_FR3	= 0x21,
+	GSM0808_PERM_HR1	= GSM0808_PERM_FR1 | 0x4,
+	GSM0808_PERM_HR2	= GSM0808_PERM_FR2 | 0x4,
+	GSM0808_PERM_HR3	= GSM0808_PERM_FR3 | 0x4,
+};
+
+#endif
diff --git a/libosmocore/include/osmocore/rate_ctr.h b/libosmocore/include/osmocore/rate_ctr.h
new file mode 100644
index 0000000..88c9de5
--- /dev/null
+++ b/libosmocore/include/osmocore/rate_ctr.h
@@ -0,0 +1,81 @@
+#ifndef _RATE_CTR_H
+#define _RATE_CTR_H
+
+#include <stdint.h>
+
+#include <osmocore/linuxlist.h>
+
+#define RATE_CTR_INTV_NUM	4
+
+enum rate_ctr_intv {
+	RATE_CTR_INTV_SEC,
+	RATE_CTR_INTV_MIN,
+	RATE_CTR_INTV_HOUR,
+	RATE_CTR_INTV_DAY,
+};
+
+/* for each of the intervals, we keep the following values */
+struct rate_ctr_per_intv {
+	uint64_t last;
+	uint64_t rate;
+};
+
+/* for each actual value, we keep the following data */
+struct rate_ctr {
+	uint64_t current;
+	struct rate_ctr_per_intv intv[RATE_CTR_INTV_NUM];
+};
+
+struct rate_ctr_desc {
+	const char *name;
+	const char *description;
+};
+
+/* Describe a counter group class */
+struct rate_ctr_group_desc {
+	/* The prefix to the name of all counters in this group */
+	char *group_name_prefix;
+	/* The human-readable description of the group */
+	char *group_description;
+	/* The number of counters in this group */
+	unsigned int num_ctr;
+	/* Pointer to array of counter names */
+	struct rate_ctr_desc *ctr_desc;
+};
+
+/* One instance of a counter group class */
+struct rate_ctr_group {
+	/* Linked list of all counter groups in the system */
+	struct llist_head list;
+	/* Pointer to the counter group class */
+	const struct rate_ctr_group_desc *desc;
+	/* The index of this ctr_group within its class */
+	unsigned int idx;
+	/* Actual counter structures below */
+	struct rate_ctr ctr[0];
+};
+
+/* Allocate a new group of counters according to description */
+struct rate_ctr_group *rate_ctr_group_alloc(void *ctx,
+					    const struct rate_ctr_group_desc *desc,
+					    unsigned int idx);
+
+/* Free the memory for the specified group of counters */
+void rate_ctr_group_free(struct rate_ctr_group *grp);
+
+/* Add a number to the counter */
+void rate_ctr_add(struct rate_ctr *ctr, int inc);
+
+/* Increment the counter by 1 */
+static inline void rate_ctr_inc(struct rate_ctr *ctr)
+{
+	rate_ctr_add(ctr, 1);
+}
+
+/* Initialize the counter module */
+int rate_ctr_init(void *tall_ctx);
+
+struct vty;
+void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
+			    struct rate_ctr_group *ctrg);
+#endif /* RATE_CTR_H */
diff --git a/libosmocore/include/osmocore/write_queue.h b/libosmocore/include/osmocore/write_queue.h
index 64d4159..ef244c3 100644
--- a/libosmocore/include/osmocore/write_queue.h
+++ b/libosmocore/include/osmocore/write_queue.h
@@ -35,6 +35,7 @@
 
 	int (*read_cb)(struct bsc_fd *fd);
 	int (*write_cb)(struct bsc_fd *fd, struct msgb *msg);
+	int (*except_cb)(struct bsc_fd *fd);
 };
 
 void write_queue_init(struct write_queue *queue, int max_length);
diff --git a/libosmocore/src/Makefile.am b/libosmocore/src/Makefile.am
index 1697807..ce063d0 100644
--- a/libosmocore/src/Makefile.am
+++ b/libosmocore/src/Makefile.am
@@ -10,7 +10,7 @@
 libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c rxlev_stat.c \
 			 tlv_parser.c bitvec.c comp128.c gsm_utils.c statistics.c \
 			 write_queue.c utils.c rsl.c gsm48.c gsm48_ie.c \
-			 logging.c
+			 logging.c gsm0808.c rate_ctr.c
 
 if ENABLE_TALLOC
 libosmocore_la_SOURCES += talloc.c
diff --git a/libosmocore/src/gsm0808.c b/libosmocore/src/gsm0808.c
new file mode 100644
index 0000000..7a7eb3a
--- /dev/null
+++ b/libosmocore/src/gsm0808.c
@@ -0,0 +1,295 @@
+/* (C) 2009,2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009,2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 <osmocore/gsm0808.h>
+#include <osmocore/protocol/gsm_08_08.h>
+#include <osmocore/gsm48.h>
+
+#include <arpa/inet.h>
+
+#define BSSMAP_MSG_SIZE 512
+#define BSSMAP_MSG_HEADROOM 128
+
+struct msgb *gsm0808_create_layer3(struct msgb *msg_l3, uint16_t nc, uint16_t cc, int lac, int _ci)
+{
+	uint8_t *data;
+	uint16_t *ci;
+	struct msgb* msg;
+	struct gsm48_loc_area_id *lai;
+
+	msg  = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+				   "bssmap cmpl l3");
+	if (!msg)
+		return NULL;
+
+	/* create the bssmap header */
+	msg->l3h = msgb_put(msg, 2);
+	msg->l3h[0] = 0x0;
+
+	/* create layer 3 header */
+	data = msgb_put(msg, 1);
+	data[0] = BSS_MAP_MSG_COMPLETE_LAYER_3;
+
+	/* create the cell header */
+	data = msgb_put(msg, 3);
+	data[0] = GSM0808_IE_CELL_IDENTIFIER;
+	data[1] = 1 + sizeof(*lai) + 2;
+	data[2] = CELL_IDENT_WHOLE_GLOBAL;
+
+	lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
+	gsm48_generate_lai(lai, cc, nc, lac);
+
+	ci = (uint16_t *) msgb_put(msg, 2);
+	*ci = htons(_ci);
+
+	/* copy the layer3 data */
+	data = msgb_put(msg, msgb_l3len(msg_l3) + 2);
+	data[0] = GSM0808_IE_LAYER_3_INFORMATION;
+	data[1] = msgb_l3len(msg_l3);
+	memcpy(&data[2], msg_l3->l3h, data[1]);
+
+	/* update the size */
+	msg->l3h[1] = msgb_l3len(msg) - 2;
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_reset(void)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: reset");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 6);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 0x04;
+	msg->l3h[2] = 0x30;
+	msg->l3h[3] = 0x04;
+	msg->l3h[4] = 0x01;
+	msg->l3h[5] = 0x20;
+	return msg;
+}
+
+struct msgb *gsm0808_create_clear_complete(void)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: clear complete");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 3);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 1;
+	msg->l3h[2] = BSS_MAP_MSG_CLEAR_COMPLETE;
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "cipher-complete");
+	if (!msg)
+		return NULL;
+
+        /* send response with BSS override for A5/1... cheating */
+	msg->l3h = msgb_put(msg, 3);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 0xff;
+	msg->l3h[2] = BSS_MAP_MSG_CIPHER_MODE_COMPLETE;
+
+	/* include layer3 in case we have at least two octets */
+	if (layer3 && msgb_l3len(layer3) > 2) {
+		msg->l4h = msgb_put(msg, msgb_l3len(layer3) + 2);
+		msg->l4h[0] = GSM0808_IE_LAYER_3_MESSAGE_CONTENTS;
+		msg->l4h[1] = msgb_l3len(layer3);
+		memcpy(&msg->l4h[2], layer3->l3h, msgb_l3len(layer3));
+	}
+
+	/* and the optional BSS message */
+	msg->l4h = msgb_put(msg, 2);
+	msg->l4h[0] = GSM0808_IE_CHOSEN_ENCR_ALG;
+	msg->l4h[1] = alg_id;
+
+	/* update the size */
+	msg->l3h[1] = msgb_l3len(msg) - 2;
+	return msg;
+}
+
+struct msgb *gsm0808_create_cipher_reject(uint8_t cause)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: clear complete");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 3);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 2;
+	msg->l3h[2] = BSS_MAP_MSG_CIPHER_MODE_REJECT;
+	msg->l3h[3] = cause;
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_classmark_update(const uint8_t *classmark_data, uint8_t length)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "classmark-update");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 3);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 0xff;
+	msg->l3h[2] = BSS_MAP_MSG_CLASSMARK_UPDATE;
+
+	msg->l4h = msgb_put(msg, length);
+	memcpy(msg->l4h, classmark_data, length);
+
+	/* update the size */
+	msg->l3h[1] = msgb_l3len(msg) - 2;
+	return msg;
+}
+
+struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: sapi 'n' reject");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 5);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 3;
+	msg->l3h[2] = BSS_MAP_MSG_SAPI_N_REJECT;
+	msg->l3h[3] = link_id;
+	msg->l3h[4] = GSM0808_CAUSE_BSS_NOT_EQUIPPED;
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_assignment_completed(struct gsm_lchan *lchan, uint8_t rr_cause,
+						 uint8_t chosen_channel, uint8_t encr_alg_id,
+						 uint8_t speech_mode)
+{
+	uint8_t *data;
+
+	struct msgb *msg = msgb_alloc(35, "bssmap: ass compl");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 3);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 0xff;
+	msg->l3h[2] = BSS_MAP_MSG_ASSIGMENT_COMPLETE;
+
+	/* write 3.2.2.22 */
+	data = msgb_put(msg, 2);
+	data[0] = GSM0808_IE_RR_CAUSE;
+	data[1] = rr_cause;
+
+	/* write cirtcuit identity  code 3.2.2.2 */
+	/* write cell identifier 3.2.2.17 */
+	/* write chosen channel 3.2.2.33 when BTS picked it */
+	data = msgb_put(msg, 2);
+	data[0] = GSM0808_IE_CHOSEN_CHANNEL;
+	data[1] = chosen_channel;
+
+	/* write chosen encryption algorithm 3.2.2.44 */
+	data = msgb_put(msg, 2);
+	data[0] = GSM0808_IE_CHOSEN_ENCR_ALG;
+	data[1] = encr_alg_id;
+
+	/* write circuit pool 3.2.2.45 */
+	/* write speech version chosen: 3.2.2.51 when BTS picked it */
+	if (speech_mode != 0) {
+		data = msgb_put(msg, 2);
+		data[0] = GSM0808_IE_SPEECH_VERSION;
+		data[1] = speech_mode;
+	}
+
+	/* write LSA identifier 3.2.2.15 */
+
+
+	/* update the size */
+	msg->l3h[1] = msgb_l3len(msg) - 2;
+	return msg;
+}
+
+struct msgb *gsm0808_create_assignment_failure(uint8_t cause, uint8_t *rr_cause)
+{
+	uint8_t *data;
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: ass fail");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_put(msg, 6);
+	msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
+	msg->l3h[1] = 0xff;
+	msg->l3h[2] = BSS_MAP_MSG_ASSIGMENT_FAILURE;
+	msg->l3h[3] = GSM0808_IE_CAUSE;
+	msg->l3h[4] = 1;
+	msg->l3h[5] = cause;
+
+	/* RR cause 3.2.2.22 */
+	if (rr_cause) {
+		data = msgb_put(msg, 2);
+		data[0] = GSM0808_IE_RR_CAUSE;
+		data[1] = *rr_cause;
+	}
+
+	/* Circuit pool 3.22.45 */
+	/* Circuit pool list 3.2.2.46 */
+
+	/* update the size */
+	msg->l3h[1] = msgb_l3len(msg) - 2;
+	return msg;
+}
+
+static const struct tlv_definition bss_att_tlvdef = {
+	.def = {
+		[GSM0808_IE_IMSI]		    = { TLV_TYPE_TLV },
+		[GSM0808_IE_TMSI]		    = { TLV_TYPE_TLV },
+		[GSM0808_IE_CELL_IDENTIFIER_LIST]   = { TLV_TYPE_TLV },
+		[GSM0808_IE_CHANNEL_NEEDED]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_EMLPP_PRIORITY]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_CHANNEL_TYPE]	    = { TLV_TYPE_TLV },
+		[GSM0808_IE_PRIORITY]		    = { TLV_TYPE_TLV },
+		[GSM0808_IE_CIRCUIT_IDENTITY_CODE]  = { TLV_TYPE_TV },
+		[GSM0808_IE_DOWNLINK_DTX_FLAG]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_INTERFERENCE_BAND_TO_USE] = { TLV_TYPE_TV },
+		[GSM0808_IE_CLASSMARK_INFORMATION_T2] = { TLV_TYPE_TLV },
+		[GSM0808_IE_GROUP_CALL_REFERENCE]   = { TLV_TYPE_TLV },
+		[GSM0808_IE_TALKER_FLAG]	    = { TLV_TYPE_T },
+		[GSM0808_IE_CONFIG_EVO_INDI]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_LSA_ACCESS_CTRL_SUPPR]  = { TLV_TYPE_TV },
+		[GSM0808_IE_SERVICE_HANDOVER]	    = { TLV_TYPE_TV},
+		[GSM0808_IE_ENCRYPTION_INFORMATION] = { TLV_TYPE_TLV },
+		[GSM0808_IE_CIPHER_RESPONSE_MODE]   = { TLV_TYPE_TV },
+	},
+};
+
+const struct tlv_definition *gsm0808_att_tlvdef()
+{
+	return &bss_att_tlvdef;
+}
diff --git a/libosmocore/src/gsm48.c b/libosmocore/src/gsm48.c
index 5761c67..d957aef 100644
--- a/libosmocore/src/gsm48.c
+++ b/libosmocore/src/gsm48.c
@@ -97,10 +97,10 @@
 };
 
 /* FIXME: convert to value_string */
-static const char *cc_state_names[32] = {
+static const char *cc_state_names[33] = {
 	"NULL",
 	"INITIATED",
-	"illegal state 2",
+	"MM_CONNECTION_PEND",
 	"MO_CALL_PROC",
 	"CALL_DELIVERED",
 	"illegal state 5",
@@ -261,3 +261,93 @@
 
 	return 2 + buf[1];
 }
+
+/* Convert Mobile Identity (10.5.1.4) to string */
+int gsm48_mi_to_string(char *string, const int str_len, const uint8_t *mi,
+		       const int mi_len)
+{
+	int i;
+	uint8_t mi_type;
+	char *str_cur = string;
+	uint32_t tmsi;
+
+	mi_type = mi[0] & GSM_MI_TYPE_MASK;
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_NONE:
+		break;
+	case GSM_MI_TYPE_TMSI:
+		/* Table 10.5.4.3, reverse generate_mid_from_tmsi */
+		if (mi_len == GSM48_TMSI_LEN && mi[0] == (0xf0 | GSM_MI_TYPE_TMSI)) {
+			memcpy(&tmsi, &mi[1], 4);
+			tmsi = ntohl(tmsi);
+			return snprintf(string, str_len, "%u", tmsi);
+		}
+		break;
+	case GSM_MI_TYPE_IMSI:
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		*str_cur++ = bcd2char(mi[0] >> 4);
+
+                for (i = 1; i < mi_len; i++) {
+			if (str_cur + 2 >= string + str_len)
+				return str_cur - string;
+			*str_cur++ = bcd2char(mi[i] & 0xf);
+			/* skip last nibble in last input byte when GSM_EVEN */
+			if( (i != mi_len-1) || (mi[0] & GSM_MI_ODD))
+				*str_cur++ = bcd2char(mi[i] >> 4);
+		}
+		break;
+	default:
+		break;
+	}
+	*str_cur++ = '\0';
+
+	return str_cur - string;
+}
+
+void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf)
+{
+	raid->mcc = (buf[0] & 0xf) * 100;
+	raid->mcc += (buf[0] >> 4) * 10;
+	raid->mcc += (buf[1] & 0xf) * 1;
+
+	/* I wonder who came up with the stupidity of encoding the MNC
+	 * differently depending on how many digits its decimal number has! */
+	if ((buf[1] >> 4) == 0xf) {
+		raid->mnc = (buf[2] & 0xf) * 10;
+		raid->mnc += (buf[2] >> 4) * 1;
+	} else {
+		raid->mnc = (buf[2] & 0xf) * 100;
+		raid->mnc += (buf[2] >> 4) * 10;
+		raid->mnc += (buf[1] >> 4) * 1;
+	}
+
+	raid->lac = ntohs(*(uint16_t *)(buf + 3));
+	raid->rac = buf[5];
+}
+
+int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid)
+{
+	uint16_t mcc = raid->mcc;
+	uint16_t mnc = raid->mnc;
+
+	buf[0] = ((mcc / 100) % 10) | (((mcc / 10) % 10) << 4);
+	buf[1] = (mcc % 10);
+
+	/* I wonder who came up with the stupidity of encoding the MNC
+	 * differently depending on how many digits its decimal number has! */
+	if (mnc < 100) {
+		buf[1] |= 0xf0;
+		buf[2] = ((mnc / 10) % 10) | ((mnc % 10) << 4);
+	} else {
+		buf[1] |= (mnc % 10) << 4;
+		buf[2] = ((mnc / 100) % 10) | (((mcc / 10) % 10) << 4);
+	}
+
+	*(uint16_t *)(buf+3) = htons(raid->lac);
+
+	buf[5] = raid->rac;
+
+	return 6;
+}
diff --git a/libosmocore/src/gsm48_ie.c b/libosmocore/src/gsm48_ie.c
index 4ca5fb8..3c2a1f7 100644
--- a/libosmocore/src/gsm48_ie.c
+++ b/libosmocore/src/gsm48_ie.c
@@ -2,7 +2,7 @@
  * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
 
 /* (C) 2008 by Harald Welte <laforge@gnumonks.org>
- * (C) 2008-2010 by Andreas Eversberg
+ * (C) 2009-2010 by Andreas Eversberg
  *
  * All Rights Reserved
  *
diff --git a/libosmocore/src/gsm_utils.c b/libosmocore/src/gsm_utils.c
index 593dd5c..b392fd3 100644
--- a/libosmocore/src/gsm_utils.c
+++ b/libosmocore/src/gsm_utils.c
@@ -359,3 +359,18 @@
 	/* TS 05.02 Chapter 4.3.3 TDMA frame number */
 	return (51 * ((time->t3 - time->t2 + 26) % 26) + time->t3 + (26 * 51 * time->t1));
 }
+
+/* TS 03.03 Chapter 2.6 */
+int gprs_tlli_type(uint32_t tlli)
+{
+	if ((tlli & 0xc0000000) == 0xc0000000)
+		return TLLI_LOCAL;
+	else if ((tlli & 0xc0000000) == 0x80000000)
+		return TLLI_FOREIGN;
+	else if ((tlli & 0xf8000000) == 0x78000000)
+		return TLLI_RANDOM;
+	else if ((tlli & 0xf8000000) == 0x70000000)
+		return TLLI_AUXILIARY;
+
+	return TLLI_RESERVED;
+}
diff --git a/libosmocore/src/logging.c b/libosmocore/src/logging.c
index 508ccfd..e72a6e2 100644
--- a/libosmocore/src/logging.c
+++ b/libosmocore/src/logging.c
@@ -20,11 +20,16 @@
  *
  */
 
+#include "../config.h"
+
 #include <stdarg.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+
+#ifdef HAVE_STRINGS_H
 #include <strings.h>
+#endif
 #include <time.h>
 #include <errno.h>
 
@@ -53,6 +58,11 @@
 	return get_string_value(loglevel_strs, lvl);
 }
 
+const char *log_level_str(unsigned int lvl)
+{
+	return get_value_string(loglevel_strs, lvl);
+}
+
 int log_parse_category(const char *category)
 {
 	int i;
@@ -302,31 +312,46 @@
 	target->categories[category].loglevel = level;
 }
 
+/* since C89/C99 says stderr is a macro, we can safely do this! */
+#ifdef stderr
 static void _stderr_output(struct log_target *target, const char *log)
 {
 	fprintf(target->tgt_stdout.out, "%s", log);
 	fflush(target->tgt_stdout.out);
 }
+#endif
 
 struct log_target *log_target_create(void)
 {
 	struct log_target *target;
+	unsigned int i;
 
 	target = talloc_zero(tall_log_ctx, struct log_target);
 	if (!target)
 		return NULL;
 
 	INIT_LLIST_HEAD(&target->entry);
-	memcpy(target->categories, log_info->cat,
-		sizeof(struct log_category)*log_info->num_cat);
+
+	/* initialize the per-category enabled/loglevel from defaults */
+	for (i = 0; i < log_info->num_cat; i++) {
+		struct log_category *cat = &target->categories[i];
+		cat->enabled = log_info->cat[i].enabled;
+		cat->loglevel = log_info->cat[i].loglevel;
+	}
+
+	/* global settings */
 	target->use_color = 1;
 	target->print_timestamp = 0;
+
+	/* global log level */
 	target->loglevel = 0;
 	return target;
 }
 
 struct log_target *log_target_create_stderr(void)
 {
+/* since C89/C99 says stderr is a macro, we can safely do this! */
+#ifdef stderr
 	struct log_target *target;
 
 	target = log_target_create();
@@ -336,6 +361,55 @@
 	target->tgt_stdout.out = stderr;
 	target->output = _stderr_output;
 	return target;
+#else
+	return NULL;
+#endif /* stderr */
+}
+
+const char *log_vty_level_string(struct log_info *info)
+{
+	const struct value_string *vs;
+	unsigned int len = 3; /* ()\0 */
+	char *str;
+
+	for (vs = loglevel_strs; vs->value || vs->str; vs++)
+		len += strlen(vs->str) + 1;
+
+	str = talloc_zero_size(NULL, len);
+	if (!str)
+		return NULL;
+
+	str[0] = '(';
+	for (vs = loglevel_strs; vs->value || vs->str; vs++) {
+		strcat(str, vs->str);
+		strcat(str, "|");
+	}
+	str[strlen(str)-1] = ')';
+
+	return str;
+}
+
+const char *log_vty_category_string(struct log_info *info)
+{
+	unsigned int len = 3;	/* "()\0" */
+	unsigned int i;
+	char *str;
+
+	for (i = 0; i < info->num_cat; i++)
+		len += strlen(info->cat[i].name) + 1;
+
+	str = talloc_zero_size(NULL, len);
+	if (!str)
+		return NULL;
+
+	str[0] = '(';
+	for (i = 0; i < info->num_cat; i++) {
+		strcat(str, info->cat[i].name+1);
+		strcat(str, "|");
+	}
+	str[strlen(str)-1] = ')';
+
+	return str;
 }
 
 void log_init(const struct log_info *cat)
diff --git a/libosmocore/src/msgb.c b/libosmocore/src/msgb.c
index 60af373..a60e2ff 100644
--- a/libosmocore/src/msgb.c
+++ b/libosmocore/src/msgb.c
@@ -80,10 +80,11 @@
 	msg->head = msg->_data;
 	msg->tail = msg->_data;
 
-	msg->bts_link = NULL;
 	msg->trx = NULL;
 	msg->lchan = NULL;
 	msg->l2h = NULL;
 	msg->l3h = NULL;
-	msg->smsh = NULL;
+	msg->l4h = NULL;
+
+	memset(&msg->cb, 0, sizeof(msg->cb));
 }
diff --git a/libosmocore/src/rate_ctr.c b/libosmocore/src/rate_ctr.c
new file mode 100644
index 0000000..e48c779
--- /dev/null
+++ b/libosmocore/src/rate_ctr.c
@@ -0,0 +1,122 @@
+/* utility routines for keeping conters about events and the event rates */
+
+/* (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 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 <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rate_ctr.h>
+
+static LLIST_HEAD(rate_ctr_groups);
+
+static void *tall_rate_ctr_ctx;
+
+struct rate_ctr_group *rate_ctr_group_alloc(void *ctx,
+					    const struct rate_ctr_group_desc *desc,
+					    unsigned int idx)
+{
+	unsigned int size;
+	struct rate_ctr_group *group;
+
+	size = sizeof(struct rate_ctr_group) +
+			desc->num_ctr * sizeof(struct rate_ctr);
+
+	if (!ctx)
+		ctx = tall_rate_ctr_ctx;
+
+	group = talloc_zero_size(ctx, size);
+	if (!group)
+		return NULL;
+
+	group->desc = desc;
+	group->idx = idx;
+
+	llist_add(&group->list, &rate_ctr_groups);
+
+	return group;
+}
+
+void rate_ctr_group_free(struct rate_ctr_group *grp)
+{
+	llist_del(&grp->list);
+	talloc_free(grp);
+}
+
+void rate_ctr_add(struct rate_ctr *ctr, int inc)
+{
+	ctr->current += inc;
+}
+
+static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
+{
+	/* calculate rate over last interval */
+	ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last;
+	/* save current counter for next interval */
+	ctr->intv[intv].last = ctr->current;
+}
+
+static struct timer_list rate_ctr_timer;
+static uint64_t timer_ticks;
+
+/* The one-second interval has expired */
+static void rate_ctr_group_intv(struct rate_ctr_group *grp)
+{
+	unsigned int i;
+
+	for (i = 0; i < grp->desc->num_ctr; i++) {
+		struct rate_ctr *ctr = &grp->ctr[i];
+
+		interval_expired(ctr, RATE_CTR_INTV_SEC);
+		if ((timer_ticks % 60) == 0)
+			interval_expired(ctr, RATE_CTR_INTV_MIN);
+		if ((timer_ticks % (60*60)) == 0)
+			interval_expired(ctr, RATE_CTR_INTV_HOUR);
+		if ((timer_ticks % (24*60*60)) == 0)
+			interval_expired(ctr, RATE_CTR_INTV_DAY);
+	}
+}
+
+static void rate_ctr_timer_cb(void *data)
+{
+	struct rate_ctr_group *ctrg;
+
+	/* Increment number of ticks before we calculate intervals,
+	 * as a counter value of 0 would already wrap all counters */
+	timer_ticks++;
+
+	llist_for_each_entry(ctrg, &rate_ctr_groups, list)
+		rate_ctr_group_intv(ctrg);
+
+	bsc_schedule_timer(&rate_ctr_timer, 1, 0);
+}
+
+int rate_ctr_init(void *tall_ctx)
+{
+	tall_rate_ctr_ctx = tall_ctx;
+	rate_ctr_timer.cb = rate_ctr_timer_cb;
+	bsc_schedule_timer(&rate_ctr_timer, 1, 0);
+
+	return 0;
+}
diff --git a/libosmocore/src/select.c b/libosmocore/src/select.c
index 9517778..2f6afa7 100644
--- a/libosmocore/src/select.c
+++ b/libosmocore/src/select.c
@@ -121,7 +121,8 @@
 		/* ugly, ugly hack. If more than one filedescriptors were
 		 * unregistered, they might have been consecutive and
 		 * llist_for_each_entry_safe() is no longer safe */
-		if (unregistered_count > 1)
+		/* this seems to happen with the last element of the list as well */
+		if (unregistered_count >= 1)
 			goto restart;
 	}
 	return work;
diff --git a/libosmocore/src/write_queue.c b/libosmocore/src/write_queue.c
index a0ac2d6..618a8c0 100644
--- a/libosmocore/src/write_queue.c
+++ b/libosmocore/src/write_queue.c
@@ -32,6 +32,9 @@
 	if (what & BSC_FD_READ)
 		queue->read_cb(fd);
 
+	if (what & BSC_FD_EXCEPT)
+		queue->except_cb(fd);
+
 	if (what & BSC_FD_WRITE) {
 		struct msgb *msg;
 
diff --git a/openbsc/README b/openbsc/README
index 51807bb..fa01ff4 100644
--- a/openbsc/README
+++ b/openbsc/README
@@ -14,6 +14,8 @@
 
  * A-bis over IP as used by the ip.access nanoBTS product family
 
+You can find the project documentation at http://openbsc.gnumonks.org/
+
 This project is still in its early days, and there are lots of areas where it
 doesn't behave as per GSM spec.
 
diff --git a/openbsc/configure.in b/openbsc/configure.in
index 615e59d..66d4ee1 100644
--- a/openbsc/configure.in
+++ b/openbsc/configure.in
@@ -18,7 +18,7 @@
 AC_SEARCH_LIBS(crypt, crypt,
     [LIBCRYPT="-lcrypt"; AC_DEFINE([VTY_CRYPT_PW], [], [Use crypt functionality of vty.])])
 
-PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.1.3)
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.1.6)
 
 dnl checks for header files
 AC_HEADER_STDC
@@ -48,6 +48,8 @@
     include/sccp/Makefile
     include/Makefile
     src/Makefile
+    src/ipaccess/Makefile
+    src/gprs/Makefile
     tests/Makefile
     tests/debug/Makefile
     tests/gsm0408/Makefile
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
index 259e6d6..afb62de 100644
--- a/openbsc/include/openbsc/Makefile.am
+++ b/openbsc/include/openbsc/Makefile.am
@@ -6,7 +6,9 @@
 		 bsc_rll.h mncc.h transaction.h ussd.h gsm_04_80.h \
 		 silent_call.h mgcp.h meas_rep.h rest_octets.h \
 		 system_information.h handover.h mgcp_internal.h \
-		 vty.h
+		 vty.h \
+		crc24.h gprs_bssgp.h gprs_llc.h gprs_ns.h \
+		gb_proxy.h gprs_sgsn.h gsm_04_08_gprs.h sgsn.h
 
 openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
 openbscdir = $(includedir)/openbsc
diff --git a/openbsc/include/openbsc/abis_nm.h b/openbsc/include/openbsc/abis_nm.h
index 45307e3..c20e4e1 100644
--- a/openbsc/include/openbsc/abis_nm.h
+++ b/openbsc/include/openbsc/abis_nm.h
@@ -92,7 +92,7 @@
 int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *msg);
 int abis_nm_event_reports(struct gsm_bts *bts, int on);
 int abis_nm_reset_resource(struct gsm_bts *bts);
-int abis_nm_software_load(struct gsm_bts *bts, const char *fname,
+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);
 int abis_nm_software_load_status(struct gsm_bts *bts);
@@ -148,7 +148,7 @@
 			 u_int8_t *attr, int attr_len);
 int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr,
 				int attr_len);
-int abis_nm_ipaccess_restart(struct gsm_bts *bts);
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx);
 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);
@@ -164,7 +164,8 @@
 	EVT_STATECHG_ADM,
 };
 int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
-		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state); 
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+		   struct abis_om_obj_inst *obj_inst);
 
 const char *nm_opstate_name(u_int8_t os);
 const char *nm_avail_name(u_int8_t avail);
diff --git a/openbsc/include/openbsc/abis_rsl.h b/openbsc/include/openbsc/abis_rsl.h
index e6973ee..8e6774d 100644
--- a/openbsc/include/openbsc/abis_rsl.h
+++ b/openbsc/include/openbsc/abis_rsl.h
@@ -70,10 +70,11 @@
 u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan);
 int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id);
 
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int);
+
 /* to be provided by external code */
 int abis_rsl_sendmsg(struct msgb *msg);
 int rsl_deact_sacch(struct gsm_lchan *lchan);
-int rsl_chan_release(struct gsm_lchan *lchan);
 
 /* BCCH related code */
 int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf);
diff --git a/openbsc/include/openbsc/chan_alloc.h b/openbsc/include/openbsc/chan_alloc.h
index f564e9e..d4f5858 100644
--- a/openbsc/include/openbsc/chan_alloc.h
+++ b/openbsc/include/openbsc/chan_alloc.h
@@ -45,6 +45,7 @@
 
 /* Free a logical channel (SDCCH, TCH, ...) */
 void lchan_free(struct gsm_lchan *lchan);
+void lchan_reset(struct gsm_lchan *lchan);
 
 /* Consider releasing the channel */
 int lchan_auto_release(struct gsm_lchan *lchan);
diff --git a/openbsc/include/openbsc/crc24.h b/openbsc/include/openbsc/crc24.h
new file mode 100644
index 0000000..358fcb5
--- /dev/null
+++ b/openbsc/include/openbsc/crc24.h
@@ -0,0 +1,8 @@
+#ifndef _CRC24_H
+#define _CRC24_H
+
+#define INIT_CRC24	0xffffff
+
+u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len);
+
+#endif
diff --git a/openbsc/include/openbsc/db.h b/openbsc/include/openbsc/db.h
index d0a1278..1782efb 100644
--- a/openbsc/include/openbsc/db.h
+++ b/openbsc/include/openbsc/db.h
@@ -66,6 +66,9 @@
 			u_int8_t *apdu);
 
 /* Statistics counter storage */
+struct counter;
 int db_store_counter(struct counter *ctr);
+struct rate_ctr_group;
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg);
 
 #endif /* _DB_H */
diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h
index f1c5a69..65fd0bb 100644
--- a/openbsc/include/openbsc/debug.h
+++ b/openbsc/include/openbsc/debug.h
@@ -29,6 +29,9 @@
 	DHO,
 	DDB,
 	DREF,
+	DGPRS,
+	DNS,
+	DBSSGP,
 	Debug_LastEntry,
 };
 
diff --git a/openbsc/include/openbsc/gb_proxy.h b/openbsc/include/openbsc/gb_proxy.h
new file mode 100644
index 0000000..db236b5
--- /dev/null
+++ b/openbsc/include/openbsc/gb_proxy.h
@@ -0,0 +1,42 @@
+#ifndef _GB_PROXY_H
+#define _GB_PROXY_H
+
+#include <sys/types.h>
+
+#include <osmocore/msgb.h>
+
+#include <openbsc/gprs_ns.h>
+#include <vty/command.h>
+
+struct gbproxy_config {
+	/* parsed from config file */
+	u_int32_t nsip_listen_ip;
+	u_int16_t nsip_listen_port;
+
+	u_int32_t nsip_sgsn_ip;
+	u_int16_t nsip_sgsn_port;
+
+	u_int16_t nsip_sgsn_nsei;
+	u_int16_t nsip_sgsn_nsvci;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+};
+
+extern struct gbproxy_config gbcfg;
+extern struct cmd_element show_gbproxy_cmd;
+
+/* gb_proxy_vty .c */
+
+int gbproxy_vty_init(void);
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg);
+
+
+/* gb_proxy.c */
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+		  void *handler_data, void *signal_data);
+#endif
diff --git a/openbsc/include/openbsc/gprs_bssgp.h b/openbsc/include/openbsc/gprs_bssgp.h
new file mode 100644
index 0000000..d3ccb12
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_bssgp.h
@@ -0,0 +1,163 @@
+#ifndef _GPRS_BSSGP_H
+#define _GPRS_BSSGP_H
+
+#include <stdint.h>
+
+/* Section 11.3.26 / Table 11.27 */
+enum bssgp_pdu_type {
+	/* PDUs between RL and BSSGP SAPs */
+	BSSGP_PDUT_DL_UNITDATA		= 0x00,
+	BSSGP_PDUT_UL_UNITDATA		= 0x01,
+	BSSGP_PDUT_RA_CAPABILITY	= 0x02,
+	BSSGP_PDUT_PTM_UNITDATA		= 0x03,
+	/* PDUs between GMM SAPs */
+	BSSGP_PDUT_PAGING_PS		= 0x06,
+	BSSGP_PDUT_PAGING_CS		= 0x07,
+	BSSGP_PDUT_RA_CAPA_UDPATE	= 0x08,
+	BSSGP_PDUT_RA_CAPA_UPDATE_ACK	= 0x09,
+	BSSGP_PDUT_RADIO_STATUS		= 0x0a,
+	BSSGP_PDUT_SUSPEND		= 0x0b,
+	BSSGP_PDUT_SUSPEND_ACK		= 0x0c,
+	BSSGP_PDUT_SUSPEND_NACK		= 0x0d,
+	BSSGP_PDUT_RESUME		= 0x0e,
+	BSSGP_PDUT_RESUME_ACK		= 0x0f,
+	BSSGP_PDUT_RESUME_NACK		= 0x10,
+	/* PDus between NM SAPs */
+	BSSGP_PDUT_BVC_BLOCK		= 0x20,
+	BSSGP_PDUT_BVC_BLOCK_ACK	= 0x21,
+	BSSGP_PDUT_BVC_RESET		= 0x22,
+	BSSGP_PDUT_BVC_RESET_ACK	= 0x23,
+	BSSGP_PDUT_BVC_UNBLOCK		= 0x24,
+	BSSGP_PDUT_BVC_UNBLOCK_ACK	= 0x25,
+	BSSGP_PDUT_FLOW_CONTROL_BVC	= 0x26,
+	BSSGP_PDUT_FLOW_CONTROL_BVC_ACK	= 0x27,
+	BSSGP_PDUT_FLOW_CONTROL_MS	= 0x28,
+	BSSGP_PDUT_FLOW_CONTROL_MS_ACK	= 0x29,
+	BSSGP_PDUT_FLUSH_LL		= 0x2a,
+	BSSGP_PDUT_FLUSH_LL_ACK		= 0x2b,
+	BSSGP_PDUT_LLC_DISCARD		= 0x2c,
+	BSSGP_PDUT_SGSN_INVOKE_TRACE	= 0x40,
+	BSSGP_PDUT_STATUS		= 0x41,
+	/* PDUs between PFM SAP's */
+	BSSGP_PDUT_DOWNLOAD_BSS_PFC	= 0x50,
+	BSSGP_PDUT_CREATE_BSS_PFC	= 0x51,
+	BSSGP_PDUT_CREATE_BSS_PFC_ACK	= 0x52,
+	BSSGP_PDUT_CREATE_BSS_PFC_NACK	= 0x53,
+	BSSGP_PDUT_MODIFY_BSS_PFC	= 0x54,
+	BSSGP_PDUT_MODIFY_BSS_PFC_ACK	= 0x55,
+	BSSGP_PDUT_DELETE_BSS_PFC	= 0x56,
+	BSSGP_PDUT_DELETE_BSS_PFC_ACK	= 0x57,
+};
+
+/* Section 10.2.1 and 10.2.2 */
+struct bssgp_ud_hdr {
+	uint8_t pdu_type;
+	uint32_t tlli;
+	uint8_t qos_profile[3];
+	uint8_t data[0];	/* TLV's */
+} __attribute__((packed));
+
+struct bssgp_normal_hdr {
+	uint8_t pdu_type;
+	uint8_t data[0];	/* TLV's */
+};
+
+enum bssgp_iei_type {
+	BSSGP_IE_ALIGNMENT		= 0x00,
+	BSSGP_IE_BMAX_DEFAULT_MS	= 0x01,
+	BSSGP_IE_BSS_AREA_ID		= 0x02,
+	BSSGP_IE_BUCKET_LEAK_RATE	= 0x03,
+	BSSGP_IE_BVCI			= 0x04,
+	BSSGP_IE_BVC_BUCKET_SIZE	= 0x05,
+	BSSGP_IE_BVC_MEASUREMENT	= 0x06,
+	BSSGP_IE_CAUSE			= 0x07,
+	BSSGP_IE_CELL_ID		= 0x08,
+	BSSGP_IE_CHAN_NEEDED		= 0x09,
+	BSSGP_IE_DRX_PARAMS		= 0x0a,
+	BSSGP_IE_EMLPP_PRIO		= 0x0b,
+	BSSGP_IE_FLUSH_ACTION		= 0x0c,
+	BSSGP_IE_IMSI			= 0x0d,
+	BSSGP_IE_LLC_PDU		= 0x0e,
+	BSSGP_IE_LLC_FRAMES_DISCARDED	= 0x0f,
+	BSSGP_IE_LOCATION_AREA		= 0x10,
+	BSSGP_IE_MOBILE_ID		= 0x11,
+	BSSGP_IE_MS_BUCKET_SIZE		= 0x12,
+	BSSGP_IE_MS_RADIO_ACCESS_CAP	= 0x13,
+	BSSGP_IE_OMC_ID			= 0x14,
+	BSSGP_IE_PDU_IN_ERROR		= 0x15,
+	BSSGP_IE_PDU_LIFETIME		= 0x16,
+	BSSGP_IE_PRIORITY		= 0x17,
+	BSSGP_IE_QOS_PROFILE		= 0x18,
+	BSSGP_IE_RADIO_CAUSE		= 0x19,
+	BSSGP_IE_RA_CAP_UPD_CAUSE	= 0x1a,
+	BSSGP_IE_ROUTEING_AREA		= 0x1b,
+	BSSGP_IE_R_DEFAULT_MS		= 0x1c,
+	BSSGP_IE_SUSPEND_REF_NR		= 0x1d,
+	BSSGP_IE_TAG			= 0x1e,
+	BSSGP_IE_TLLI			= 0x1f,
+	BSSGP_IE_TMSI			= 0x20,
+	BSSGP_IE_TRACE_REFERENC		= 0x21,
+	BSSGP_IE_TRACE_TYPE		= 0x22,
+	BSSGP_IE_TRANSACTION_ID		= 0x23,
+	BSSGP_IE_TRIGGER_ID		= 0x24,
+	BSSGP_IE_NUM_OCT_AFF		= 0x25,
+	BSSGP_IE_LSA_ID_LIST		= 0x26,
+	BSSGP_IE_LSA_INFORMATION	= 0x27,
+	BSSGP_IE_PACKET_FLOW_ID		= 0x28,
+	BSSGP_IE_PACKET_FLOW_TIMER	= 0x29,
+	BSSGP_IE_AGG_BSS_QOS_PROFILE	= 0x3a,
+	BSSGP_IE_FEATURE_BITMAP		= 0x3b,
+	BSSGP_IE_BUCKET_FULL_RATIO	= 0x3c,
+	BSSGP_IE_SERVICE_UTRAN_CCO	= 0x3d,
+};
+
+/* Section 11.3.8 / Table 11.10: Cause coding */
+enum gprs_bssgp_cause {
+	BSSGP_CAUSE_PROC_OVERLOAD	= 0x00,
+	BSSGP_CAUSE_EQUIP_FAIL		= 0x01,
+	BSSGP_CAUSE_TRASIT_NET_FAIL	= 0x02,
+	BSSGP_CAUSE_CAPA_GREATER_0KPBS	= 0x03,
+	BSSGP_CAUSE_UNKNOWN_MS		= 0x04,
+	BSSGP_CAUSE_UNKNOWN_BVCI	= 0x05,
+	BSSGP_CAUSE_CELL_TRAF_CONG	= 0x06,
+	BSSGP_CAUSE_SGSN_CONG		= 0x07,
+	BSSGP_CAUSE_OML_INTERV		= 0x08,
+	BSSGP_CAUSE_BVCI_BLOCKED	= 0x09,
+	BSSGP_CAUSE_PFC_CREATE_FAIL	= 0x0a,
+	BSSGP_CAUSE_SEM_INCORR_PDU	= 0x20,
+	BSSGP_CAUSE_INV_MAND_INF	= 0x21,
+	BSSGP_CAUSE_MISSING_MAND_IE	= 0x22,
+	BSSGP_CAUSE_MISSING_COND_IE	= 0x23,
+	BSSGP_CAUSE_UNEXP_COND_IE	= 0x24,
+	BSSGP_CAUSE_COND_IE_ERR		= 0x25,
+	BSSGP_CAUSE_PDU_INCOMP_STATE	= 0x26,
+	BSSGP_CAUSE_PROTO_ERR_UNSPEC	= 0x27,
+	BSSGP_CAUSE_PDU_INCOMP_FEAT	= 0x28,
+};
+
+/* Our implementation */
+
+/* gprs_bssgp_util.c */
+extern struct gprs_ns_inst *bssgp_nsi;
+struct msgb *bssgp_msgb_alloc(void);
+const char *bssgp_cause_str(enum gprs_bssgp_cause cause);
+/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
+int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
+			 uint16_t bvci, uint16_t ns_bvci);
+/* Chapter 10.4.14: Status */
+int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg);
+
+/* gprs_bssgp.c */
+
+#include <osmocore/tlv.h>
+
+extern int gprs_bssgp_rcvmsg(struct msgb *msg);
+uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf);
+
+/* Wrapper around TLV parser to parse BSSGP IEs */
+static inline int bssgp_tlv_parse(struct tlv_parsed *tp, uint8_t *buf, int len)
+{
+	return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0);
+}
+
+#endif /* _GPRS_BSSGP_H */
diff --git a/openbsc/include/openbsc/gprs_llc.h b/openbsc/include/openbsc/gprs_llc.h
new file mode 100644
index 0000000..5a6682d
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_llc.h
@@ -0,0 +1,32 @@
+#ifndef _GPRS_LLC_H
+#define _GPRS_LLC_H
+
+#include <stdint.h>
+
+/* Section 4.7 LLC Layer Structure */
+enum gprs_llc_sapi {
+	GPRS_SAPI_GMM		= 1,
+	GPRS_SAPI_TOM2		= 2,
+	GPRS_SAPI_SNDCP3	= 3,
+	GPRS_SAPI_SNDCP5	= 5,
+	GPRS_SAPI_SMS		= 7,
+	GPRS_SAPI_TOM8		= 8,
+	GPRS_SAPI_SNDCP9	= 9,
+	GPRS_SAPI_SNDCP11	= 11,
+};
+
+/* Section 6.4 Commands and Responses */
+enum gprs_llc_u_cmd {
+	GPRS_LLC_U_DM_RESP		= 0x01,
+	GPRS_LLC_U_DISC_CMD		= 0x04,
+	GPRS_LLC_U_UA_RESP		= 0x06,
+	GPRS_LLC_U_SABM_CMD		= 0x07,
+	GPRS_LLC_U_FRMR_RESP		= 0x08,
+	GPRS_LLC_U_XID			= 0x0b,
+	GPRS_LLC_U_NULL_CMD		= 0x00,
+};
+
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv);
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command);
+
+#endif
diff --git a/openbsc/include/openbsc/gprs_ns.h b/openbsc/include/openbsc/gprs_ns.h
new file mode 100644
index 0000000..4ccf4c7
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_ns.h
@@ -0,0 +1,215 @@
+#ifndef _GPRS_NS_H
+#define _GPRS_NS_H
+
+#include <stdint.h>
+
+/* GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * 3GPP TS 48.016 version 6.5.0 Release 6 / ETSI TS 148 016 V6.5.0 (2005-11) */
+
+struct gprs_ns_hdr {
+	uint8_t pdu_type;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* TS 08.16, Section 10.3.7, Table 14 */
+enum ns_pdu_type {
+	NS_PDUT_UNITDATA	= 0x00,
+	NS_PDUT_RESET		= 0x02,
+	NS_PDUT_RESET_ACK	= 0x03,
+	NS_PDUT_BLOCK		= 0x04,
+	NS_PDUT_BLOCK_ACK	= 0x05,
+	NS_PDUT_UNBLOCK		= 0x06,
+	NS_PDUT_UNBLOCK_ACK	= 0x07,
+	NS_PDUT_STATUS		= 0x08,
+	NS_PDUT_ALIVE		= 0x0a,
+	NS_PDUT_ALIVE_ACK	= 0x0b,
+	/* TS 48.016 Section 10.3.7, Table 10.3.7.1 */
+	SNS_PDUT_ACK		= 0x0c,
+	SNS_PDUT_ADD		= 0x0d,
+	SNS_PDUT_CHANGE_WEIGHT	= 0x0e,
+	SNS_PDUT_CONFIG		= 0x0f,
+	SNS_PDUT_CONFIG_ACK	= 0x10,
+	SNS_PDUT_DELETE		= 0x11,
+	SNS_PDUT_SIZE		= 0x12,
+	SNS_PDUT_SIZE_ACK	= 0x13,
+};
+
+/* TS 08.16, Section 10.3, Table 12 */
+enum ns_ctrl_ie {
+	NS_IE_CAUSE		= 0x00,
+	NS_IE_VCI		= 0x01,
+	NS_IE_PDU		= 0x02,
+	NS_IE_BVCI		= 0x03,
+	NS_IE_NSEI		= 0x04,
+	/* TS 48.016 Section 10.3, Table 10.3.1 */
+	NS_IE_IPv4_LIST		= 0x05,
+	NS_IE_IPv6_LIST		= 0x06,
+	NS_IE_MAX_NR_NSVC	= 0x07,
+	NS_IE_IPv4_EP_NR	= 0x08,
+	NS_IE_IPv6_EP_NR	= 0x09,
+	NS_IE_RESET_FLAG	= 0x0a,
+	NS_IE_IP_ADDR		= 0x0b,
+};
+
+/* TS 08.16, Section 10.3.2, Table 13 */
+enum ns_cause {
+	NS_CAUSE_TRANSIT_FAIL		= 0x00,
+	NS_CAUSE_OM_INTERVENTION	= 0x01,
+	NS_CAUSE_EQUIP_FAIL		= 0x02,
+	NS_CAUSE_NSVC_BLOCKED		= 0x03,
+	NS_CAUSE_NSVC_UNKNOWN		= 0x04,
+	NS_CAUSE_BVCI_UNKNOWN		= 0x05,
+	NS_CAUSE_SEM_INCORR_PDU		= 0x08,
+	NS_CAUSE_PDU_INCOMP_PSTATE	= 0x0a,
+	NS_CAUSE_PROTO_ERR_UNSPEC	= 0x0b,
+	NS_CAUSE_INVAL_ESSENT_IE	= 0x0c,
+	NS_CAUSE_MISSING_ESSENT_IE	= 0x0d,
+	/* TS 48.016 Section 10.3.2, Table 10.3.2.1 */
+	NS_CAUSE_INVAL_NR_IPv4_EP	= 0x0e,
+	NS_CAUSE_INVAL_NR_IPv6_EP	= 0x0f,
+	NS_CAUSE_INVAL_NR_NS_VC		= 0x10,
+	NS_CAUSE_INVAL_WEIGH		= 0x11,
+	NS_CAUSE_UNKN_IP_EP		= 0x12,
+	NS_CAUSE_UNKN_IP_ADDR		= 0x13,
+	NS_CAUSE_UNKN_IP_TEST_FAILED	= 0x14,
+};
+
+/* Our Implementation */
+#include <netinet/in.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/msgb.h>
+#include <osmocore/timer.h>
+#include <osmocore/select.h>
+
+#define NS_TIMERS_COUNT 7
+#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries)"
+#define NS_TIMERS_HELP	\
+	"(un)blocking Timer (Tns-block) timeout\n"		\
+	"(un)blocking Timer (Tns-block) number of retries\n"	\
+	"Reset Timer (Tns-reset) timeout\n"			\
+	"Reset Timer (Tns-reset) number of retries\n"		\
+	"Test Timer (Tns-test) timeout\n"			\
+
+enum ns_timeout {
+	NS_TOUT_TNS_BLOCK,
+	NS_TOUT_TNS_BLOCK_RETRIES,
+	NS_TOUT_TNS_RESET,
+	NS_TOUT_TNS_RESET_RETRIES,
+	NS_TOUT_TNS_TEST,
+	NS_TOUT_TNS_ALIVE,
+	NS_TOUT_TNS_ALIVE_RETRIES,
+};
+
+#define NSE_S_BLOCKED	0x0001
+#define NSE_S_ALIVE	0x0002
+
+enum gprs_ns_ll {
+	GPRS_NS_LL_UDP,
+	GPRS_NS_LL_E1,
+};
+
+enum gprs_ns_evt {
+	GPRS_NS_EVT_UNIT_DATA,
+};
+
+struct gprs_nsvc;
+typedef int gprs_ns_cb_t(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+			 struct msgb *msg, uint16_t bvci);
+
+/* An instance of the NS protocol stack */
+struct gprs_ns_inst {
+	/* callback to the user for incoming UNIT DATA IND */
+	gprs_ns_cb_t *cb;
+
+	/* linked lists of all NSVC in this instance */
+	struct llist_head gprs_nsvcs;
+
+	/* a NSVC object that's needed to deal with packets for unknown NSVC */
+	struct gprs_nsvc *unknown_nsvc;
+
+	uint16_t timeout[NS_TIMERS_COUNT];
+
+	/* which link-layer are we based on? */
+	enum gprs_ns_ll ll;
+
+	union {
+		/* NS-over-IP specific bits */
+		struct {
+			struct bsc_fd fd;
+		} nsip;
+	};
+};
+
+enum nsvc_timer_mode {
+	/* standard timers */
+	NSVC_TIMER_TNS_TEST,
+	NSVC_TIMER_TNS_ALIVE,
+	NSVC_TIMER_TNS_RESET,
+	_NSVC_TIMER_NR,
+};
+
+struct gprs_nsvc {
+	struct llist_head list;
+	struct gprs_ns_inst *nsi;
+
+	uint16_t nsei;		/* end-to-end significance */
+	uint16_t nsvci;	/* uniquely identifies NS-VC at SGSN */
+
+	uint32_t state;
+	uint32_t remote_state;
+
+	struct timer_list timer;
+	enum nsvc_timer_mode timer_mode;
+	int alive_retries;
+
+	unsigned int remote_end_is_sgsn:1;
+	unsigned int persistent:1;
+
+	struct rate_ctr_group *ctrg;
+
+	union {
+		struct {
+			struct sockaddr_in bts_addr;
+		} ip;
+	};
+};
+
+/* Create a new NS protocol instance */
+struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb);
+
+/* Destroy a NS protocol instance */
+void gprs_ns_destroy(struct gprs_ns_inst *nsi);
+
+/* Listen for incoming GPRS packets */
+int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port);
+
+struct sockaddr_in;
+
+/* main entry point, here incoming NS frames enter */
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr);
+
+/* main function for higher layers (BSSGP) to send NS messages */
+int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg);
+
+int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause);
+int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause);
+int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc);
+
+/* Listen for incoming GPRS packets */
+int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port);
+
+/* Establish a connection (from the BSS) to the SGSN */
+struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci);
+
+struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci);
+void nsvc_delete(struct gprs_nsvc *nsvc);
+struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei);
+
+/* Add NS-specific VTY stuff */
+int gprs_ns_vty_init(struct gprs_ns_inst *nsi);
+
+#endif
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
new file mode 100644
index 0000000..bdc0b1c
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -0,0 +1,114 @@
+#ifndef _GPRS_SGSN_H
+#define _GPRS_SGSN_H
+
+#include <stdint.h>
+
+/* TS 04.08 4.1.3.3 GMM mobility management states on the network side */
+enum gprs_mm_state {
+	GMM_DEREGISTERED,		/* 4.1.3.3.1.1 */
+	GMM_COMMON_PROC_INIT,		/* 4.1.3.3.1.2 */
+	GMM_REGISTERED_NORMAL,		/* 4.1.3.3.2.1 */
+	GMM_REGISTERED_SUSPENDED,	/* 4.1.3.3.2.2 */
+	GMM_DEREGISTERED_INIT,		/* 4.1.3.3.1.4 */
+};
+
+enum gprs_ciph_algo {
+	GPRS_ALGO_GEA0,
+	GPRS_ALGO_GEA1,
+	GPRS_ALGO_GEA2,
+};
+
+#define MS_RADIO_ACCESS_CAPA
+
+/* According to TS 03.60, Table 5: SGSN MM and PDP Contexts */
+/* Extended by 3GPP TS 23.060, Table 6: SGSN MM and PDP Contexts */
+struct sgsn_mm_ctx {
+	struct llist_head	list;
+
+	char 			imsi[GSM_IMSI_LENGTH];
+	enum gprs_mm_state	mm_state;
+	uint32_t 		p_tmsi;
+	uint32_t 		p_tmsi_sig;
+	char 			imei[GSM_IMEI_LENGTH];
+	/* Opt: Software Version Numbber / TS 23.195 */
+	char 			msisdn[GSM_EXTENSION_LENGTH];
+	struct gprs_ra_id	ra;
+	uint16_t		cell_id;
+	uint32_t		cell_id_age;
+	uint16_t		sac;	/* Iu: Service Area Code */
+	uint32_t		sac_age;/* Iu: Service Area Code age */
+	/* VLR number */
+	uint32_t		new_sgsn_addr;
+	/* Authentication Triplets */
+	/* Kc */
+	/* Iu: CK, IK, KSI */
+	/* CKSN */
+	enum gprs_ciph_algo	ciph_algo;
+	struct {
+		uint8_t	buf[14];	/* 10.5.5.12a */
+		uint8_t	len;
+	} ms_radio_access_capa;
+	struct {
+		uint8_t	buf[4];		/* 10.5.5.12 */
+		uint8_t	len;
+	} ms_network_capa;
+	uint16_t		drx_parms;
+	int			mnrg;	/* MS reported to HLR? */
+	int			ngaf;	/* MS reported to MSC/VLR? */
+	int			ppf;	/* paging for GPRS + non-GPRS? */
+	/* SMS Parameters */
+	int			recovery;
+	uint8_t			radio_prio_sms;
+
+	struct llist_head	pdp_list;
+
+	/* Additional bits not present in the GSM TS */
+	uint32_t		tlli;
+	struct timer_list	timer;
+	unsigned int		T;
+};
+
+enum pdp_ctx_state {
+	PDP_STAE_NONE,
+};
+
+enum pdp_type {
+	PDP_TYPE_NONE,
+};
+
+struct sgsn_pdp_ctx {
+	struct llist_head	list;
+
+	unsigned int		id;
+	enum pdp_ctx_state	state;
+	enum pdp_type		type;
+	uint32_t		addresss;
+	char 			*apn_subscribed;
+	char 			*apn_used;
+	uint16_t		nsapi;
+	uint8_t			ti;	/* transaction identifier */
+	uint32_t		ggsn_in_use;
+	int			vplmn_allowed;
+	uint32_t		qos_profile_subscr;
+	uint32_t		qos_profile_req;
+	uint32_t		qos_profile_neg;
+	uint8_t			radio_prio;
+	uint32_t		tx_npdu_nr;
+	uint32_t		rx_npdu_nr;
+	uint32_t		tx_gtp_snd;
+	uint32_t		rx_gtp_snu;
+	uint32_t		charging_id;
+	int			reordering_reqd;
+};
+
+/* 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 *sgsn_mm_ctx_by_ptmsi(uint32_t tmsi);
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi);
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+
+#endif /* _GPRS_SGSN_H */
diff --git a/openbsc/include/openbsc/gsm_04_08.h b/openbsc/include/openbsc/gsm_04_08.h
index daf3bd7..74dcbe5 100644
--- a/openbsc/include/openbsc/gsm_04_08.h
+++ b/openbsc/include/openbsc/gsm_04_08.h
@@ -12,6 +12,15 @@
 struct gsm_network;
 struct gsm_trans;
 
+#define GSM48_ALLOC_SIZE	1024
+#define GSM48_ALLOC_HEADROOM	128
+
+static inline struct msgb *gsm48_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
+				   "GSM 04.08");
+}
+
 /* config options controlling the behaviour of the lower leves */
 void gsm0408_allow_everyone(int allow);
 
@@ -22,7 +31,6 @@
 int gsm48_tx_mm_info(struct gsm_lchan *lchan);
 int gsm48_tx_mm_auth_req(struct gsm_lchan *lchan, u_int8_t *rand, int key_seq);
 int gsm48_tx_mm_auth_rej(struct gsm_lchan *lchan);
-struct msgb *gsm48_msgb_alloc(void);
 int gsm48_sendmsg(struct msgb *msg, struct gsm_trans *trans);
 
 int gsm48_send_rr_release(struct gsm_lchan *lchan);
diff --git a/openbsc/include/openbsc/gsm_04_08_gprs.h b/openbsc/include/openbsc/gsm_04_08_gprs.h
new file mode 100644
index 0000000..344b277
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_08_gprs.h
@@ -0,0 +1,350 @@
+#ifndef _GSM48_GPRS_H
+#define _GSM48_GPRS_H
+
+#include <stdint.h>
+
+/* Table 10.4 / 10.4a, GPRS Mobility Management (GMM) */
+#define GSM48_MT_GMM_ATTACH_REQ		0x01
+#define GSM48_MT_GMM_ATTACH_ACK		0x02
+#define GSM48_MT_GMM_ATTACH_COMPL	0x03
+#define GSM48_MT_GMM_ATTACH_REJ		0x04
+#define GSM48_MT_GMM_DETACH_REQ		0x05
+#define GSM48_MT_GMM_DETACH_ACK		0x06
+
+#define GSM48_MT_GMM_RA_UPD_REQ		0x08
+#define GSM48_MT_GMM_RA_UPD_ACK		0x09
+#define GSM48_MT_GMM_RA_UPD_COMPL	0x0a
+#define GSM48_MT_GMM_RA_UPD_REJ		0x0b
+
+#define GSM48_MT_GMM_PTMSI_REALL_CMD	0x10
+#define GSM48_MT_GMM_PTMSI_REALL_COMPL	0x11
+#define GSM48_MT_GMM_AUTH_CIPH_REQ	0x12
+#define GSM48_MT_GMM_AUTH_CIPH_RESP	0x13
+#define GSM48_MT_GMM_AUTH_CIPH_REJ	0x14
+#define GSM48_MT_GMM_ID_REQ		0x15
+#define GSM48_MT_GMM_ID_RESP		0x16
+#define GSM48_MT_GMM_STATUS		0x20
+#define GSM48_MT_GMM_INFO		0x21
+
+/* Table 10.4a, GPRS Session Management (GSM) */
+#define GSM48_MT_GSM_ACT_PDP_REQ	0x41
+#define GSM48_MT_GSM_ACT_PDP_ACK	0x42
+#define GSM48_MT_GSM_ACT_PDP_REJ	0x43
+#define GSM48_MT_GSM_REQ_PDP_ACT	0x44
+#define GSM48_MT_GSM_REQ_PDP_ACT_REJ	0x45
+#define GSM48_MT_GSM_DEACT_PDP_REQ	0x46
+#define GSM48_MT_GSM_DEACT_PDP_ACK	0x47
+#define GSM48_MT_GSM_ACT_AA_PDP_REQ	0x50
+#define GSM48_MT_GSM_ACT_AA_PDP_ACK	0x51
+#define GSM48_MT_GSM_ACT_AA_PDP_REJ	0x52
+#define GSM48_MT_GSM_DEACT_AA_PDP_REQ	0x53
+#define GSM48_MT_GSM_DEACT_AA_PDP_ACK	0x54
+#define GSM48_MT_GSM_STATUS		0x55
+
+/* Chapter 10.5.5.2 / Table 10.5.135 */
+#define GPRS_ATT_T_ATTACH		1
+#define GPRS_ATT_T_ATT_WHILE_IMSI	2
+#define GPRS_ATT_T_COMBINED		3
+
+/* Chapter 10.5.5.18 / Table 105.150 */
+#define GPRS_UPD_T_RA			0
+#define GPRS_UPD_T_RA_LA		1
+#define GPRS_UPD_T_RA_LA_IMSI_ATT	2
+#define GPRS_UPD_T_PERIODIC		3
+
+enum gsm48_gprs_ie_mm {
+	GSM48_IE_GMM_TIMER_READY	= 0x17, /* 10.5.7.3 */
+	GSM48_IE_GMM_PTMSI_SIG		= 0x19,	/* 10.5.5.8 */
+	GSM48_IE_GMM_AUTH_RAND		= 0x21, /* 10.5.3.1 */
+	GSM48_IE_GMM_AUTH_SRES		= 0x22, /* 10.5.3.2 */
+	GSM48_IE_GMM_IMEISV		= 0x23, /* 10.5.1.4 */
+	GSM48_IE_GMM_DRX_PARAM		= 0x27,	/* 10.5.5.6 */
+	GSM48_IE_GMM_MS_NET_CAPA	= 0x31,	/* 10.5.5.12 */
+};
+
+enum gsm48_gprs_ie_sm {
+	GSM48_IE_GSM_APN		= 0x28,	/* 10.5.6.1 */
+	GSM48_IE_GSM_PROTO_CONF_OPT	= 0x27,	/* 10.5.6.3 */
+	GSM48_IE_GSM_PDP_ADDR		= 0x2b, /* 10.5.6.4 */
+	GSM48_IE_GSM_AA_TMR		= 0x29,	/* 10.5.7.3 */
+	GSM48_IE_GSM_NAME_FULL		= 0x43, /* 10.5.3.5a */
+	GSM48_IE_GSM_NAME_SHORT		= 0x45, /* 10.5.3.5a */
+	GSM48_IE_GSM_TIMEZONE		= 0x46, /* 10.5.3.8 */
+	GSM48_IE_GSM_UTC_AND_TZ		= 0x47, /* 10.5.3.9 */
+	GSM48_IE_GSM_LSA_ID		= 0x48, /* 10.5.3.11 */
+};
+
+/* Chapter 9.4.15 / Table 9.4.15 */
+struct gsm48_ra_upd_ack {
+	uint8_t force_stby:4,	/* 10.5.5.7 */
+		 upd_result:4;	/* 10.5.5.17 */
+	uint8_t ra_upd_timer;	/* 10.5.7.3 */
+	struct gsm48_ra_id ra_id; /* 10.5.5.15 */
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.7.3 */
+enum gsm48_gprs_tmr_unit {
+	GPRS_TMR_2SECONDS	= 0 << 5,
+	GPRS_TMR_MINUTE		= 1 << 5,
+	GPRS_TMR_6MINUTE	= 2 << 5,
+	GPRS_TMR_DEACTIVATED	= 3 << 5,
+};
+
+/* Chapter 9.4.2 / Table 9.4.2 */
+struct gsm48_attach_ack {
+	uint8_t att_result:4,	/* 10.5.5.7 */
+		 force_stby:4;	/* 10.5.5.1 */
+	uint8_t ra_upd_timer;	/* 10.5.7.3 */
+	uint8_t radio_prio;	/* 10.5.7.2 */
+	struct gsm48_ra_id ra_id; /* 10.5.5.15 */
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.5.1 / Table 9.5.1 */
+struct gsm48_act_pdp_ctx_req {
+	uint8_t req_nsapi;
+	uint8_t req_llc_sapi;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.5.14 / Table 10.5.147 */
+enum gsm48_gmm_cause {
+	GMM_CAUSE_IMSI_UNKNOWN		= 0x02,
+	GMM_CAUSE_ILLEGAL_MS		= 0x03,
+	GMM_CAUSE_ILLEGAL_ME		= 0x06,
+	GMM_CAUSE_GPRS_NOTALLOWED	= 0x07,
+	GMM_CAUSE_GPRS_OTHER_NOTALLOWED	= 0x08,
+	GMM_CAUSE_MS_ID_NOT_DERIVED	= 0x09,
+	GMM_CAUSE_IMPL_DETACHED		= 0x0a,
+	GMM_CAUSE_PLMN_NOTALLOWED	= 0x0b,
+	GMM_CAUSE_LA_NOTALLOWED		= 0x0c,
+	GMM_CAUSE_ROAMING_NOTALLOWED	= 0x0d,
+	GMM_CAUSE_NO_GPRS_PLMN		= 0x0e,
+	GMM_CAUSE_MSC_TEMP_NOTREACH	= 0x10,
+	GMM_CAUSE_NET_FAIL		= 0x11,
+	GMM_CAUSE_CONGESTION		= 0x16,
+	GMM_CAUSE_SEM_INCORR_MSG	= 0x5f,
+	GMM_CAUSE_INV_MAND_INFO		= 0x60,
+	GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL	= 0x61,
+	GMM_CAUSE_MSGT_INCOMP_P_STATE	= 0x62,
+	GMM_CAUSE_IE_NOTEXIST_NOTIMPL	= 0x63,
+	GMM_CAUSE_COND_IE_ERR		= 0x64,
+	GMM_CAUSE_MSG_INCOMP_P_STATE	= 0x65,
+	GMM_CAUSE_PROTO_ERR_UNSPEC	= 0x6f,
+};
+
+/* Chapter 10.4.6.6 / Table 10.5.157 */
+enum gsm48_gsm_cause {
+	GSM_CAUSE_INSUFF_RSRC		= 0x1a,
+	GSM_CAUSE_MISSING_APN		= 0x1b,
+	GSM_CAUSE_UNKNOWN_PDP		= 0x1c,
+	GSM_CAUSE_AUTH_FAILED		= 0x1d,
+	GSM_CAUSE_ACT_REJ_GGSN		= 0x1e,
+	GSM_CAUSE_ACT_REJ_UNSPEC	= 0x1f,
+	GSM_CAUSE_SERV_OPT_NOTSUPP	= 0x20,
+	GSM_CAUSE_REQ_SERV_OPT_NOTSUB	= 0x21,
+	GSM_CAUSE_SERV_OPT_TEMP_OOO	= 0x22,
+	GSM_CAUSE_NSAPI_IN_USE		= 0x23,
+	GSM_CAUSE_DEACT_REGULAR		= 0x24,
+	GSM_CAUSE_QOS_NOT_ACCEPTED	= 0x25,
+	GSM_CAUSE_NET_FAIL		= 0x26,
+	GSM_CAUSE_REACT_RQD		= 0x27,
+	GSM_CAUSE_FEATURE_NOTSUPP	= 0x28,
+	GSM_CAUSE_INVALID_TRANS_ID	= 0x51,
+	GSM_CAUSE_SEM_INCORR_MSG	= 0x5f,
+	GSM_CAUSE_INV_MAND_INFO		= 0x60,
+	GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL	= 0x61,
+	GSM_CAUSE_MSGT_INCOMP_P_STATE	= 0x62,
+	GSM_CAUSE_IE_NOTEXIST_NOTIMPL	= 0x63,
+	GSM_CAUSE_COND_IE_ERR		= 0x64,
+	GSM_CAUSE_MSG_INCOMP_P_STATE	= 0x65,
+	GSM_CAUSE_PROTO_ERR_UNSPEC	= 0x6f,
+};
+
+/* Section 6.1.2.2: Session management states on the network side */
+enum gsm48_pdp_state {
+	PDP_S_INACTIVE,
+	PDP_S_ACTIVE_PENDING,
+	PDP_S_ACTIVE,
+	PDP_S_INACTIVE_PENDING,
+	PDP_S_MODIFY_PENDING,
+};
+
+/* Table 10.5.155/3GPP TS 24.008 */
+enum gsm48_pdp_type_org {
+	PDP_TYPE_ORG_ETSI		= 0x00,
+	PDP_TYPE_ORG_IETF		= 0x01,
+};
+enum gsm48_pdp_type_nr {
+	PDP_TYPE_N_ETSI_RESERVED	= 0x00,
+	PDP_TYPE_N_ETSI_PPP		= 0x01,
+	PDP_TYPE_N_IETF_IPv4		= 0x21,
+	PDP_TYPE_N_IETF_IPv6		= 0x57,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_reliab_class {
+	GSM48_QOS_RC_LLC_ACK_RLC_ACK_DATA_PROT	= 2,
+	GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT	= 3,
+	GSM48_QOS_RC_LLC_UN_RLC_UN_PROT_DATA	= 4,
+	GSM48_QOS_RC_LLC_UN_RLC_UN_DATA_UN	= 5,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_preced_class {
+	GSM48_QOS_PC_HIGH	= 1,
+	GSM48_QOS_PC_NORMAL	= 2,
+	GSM48_QOS_PC_LOW	= 3,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_peak_tput {
+	GSM48_QOS_PEAK_TPUT_1000bps	= 1,
+	GSM48_QOS_PEAK_TPUT_2000bps	= 2,
+	GSM48_QOS_PEAK_TPUT_4000bps	= 3,
+	GSM48_QOS_PEAK_TPUT_8000bps	= 4,
+	GSM48_QOS_PEAK_TPUT_16000bps	= 5,
+	GSM48_QOS_PEAK_TPUT_32000bps	= 6,
+	GSM48_QOS_PEAK_TPUT_64000bps	= 7,
+	GSM48_QOS_PEAK_TPUT_128000bps	= 8,
+	GSM48_QOS_PEAK_TPUT_256000bps	= 9,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_mean_tput {
+	GSM48_QOS_MEAN_TPUT_100bph	= 1,
+	GSM48_QOS_MEAN_TPUT_200bph	= 2,
+	GSM48_QOS_MEAN_TPUT_500bph	= 3,
+	GSM48_QOS_MEAN_TPUT_1000bph	= 4,
+	GSM48_QOS_MEAN_TPUT_2000bph	= 5,
+	GSM48_QOS_MEAN_TPUT_5000bph	= 6,
+	GSM48_QOS_MEAN_TPUT_10000bph	= 7,
+	GSM48_QOS_MEAN_TPUT_20000bph	= 8,
+	GSM48_QOS_MEAN_TPUT_50000bph	= 9,
+	GSM48_QOS_MEAN_TPUT_100kbph	= 10,
+	GSM48_QOS_MEAN_TPUT_200kbph	= 11,
+	GSM48_QOS_MEAN_TPUT_500kbph	= 0xc,
+	GSM48_QOS_MEAN_TPUT_1Mbph	= 0xd,
+	GSM48_QOS_MEAN_TPUT_2Mbph	= 0xe,
+	GSM48_QOS_MEAN_TPUT_5Mbph	= 0xf,
+	GSM48_QOS_MEAN_TPUT_10Mbph	= 0x10,
+	GSM48_QOS_MEAN_TPUT_20Mbph	= 0x11,
+	GSM48_QOS_MEAN_TPUT_50Mbph	= 0x12,
+	GSM48_QOS_MEAN_TPUT_BEST_EFFORT	= 0x1f,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_err_sdu {
+	GSM48_QOS_ERRSDU_NODETECT	= 1,
+	GSM48_QOS_ERRSDU_YES		= 2,
+	GSM48_QOS_ERRSDU_NO		= 3,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_deliv_order {
+	GSM48_QOS_DO_ORDERED		= 1,
+	GSM48_QOS_DO_UNORDERED		= 2,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_traf_class {
+	GSM48_QOS_TC_CONVERSATIONAL	= 1,
+	GSM48_QOS_TC_STREAMING		= 2,
+	GSM48_QOS_TC_INTERACTIVE	= 3,
+	GSM48_QOS_TC_BACKGROUND		= 4,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_max_sdu_size {
+	/* values below in 10 octet granularity */
+	GSM48_QOS_MAXSDU_1502		= 0x97,
+	GSM48_QOS_MAXSDU_1510		= 0x98,
+	GSM48_QOS_MAXSDU_1520		= 0x99,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_max_bitrate {
+	GSM48_QOS_MBRATE_1k		= 0x01,
+	GSM48_QOS_MBRATE_63k		= 0x3f,
+	GSM48_QOS_MBRATE_64k		= 0x40,
+	GSM48_QOS_MBRATE_568k		= 0x7f,
+	GSM48_QOS_MBRATE_576k		= 0x80,
+	GSM48_QOS_MBRATE_8640k		= 0xfe,
+	GSM48_QOS_MBRATE_0k		= 0xff,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_resid_ber {
+	GSM48_QOS_RBER_5e_2		= 0x01,
+	GSM48_QOS_RBER_1e_2		= 0x02,
+	GSM48_QOS_RBER_5e_3		= 0x03,
+	GSM48_QOS_RBER_4e_3		= 0x04,
+	GSM48_QOS_RBER_1e_3		= 0x05,
+	GSM48_QOS_RBER_1e_4		= 0x06,
+	GSM48_QOS_RBER_1e_5		= 0x07,
+	GSM48_QOS_RBER_1e_6		= 0x08,
+	GSM48_QOS_RBER_6e_8		= 0x09,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_sdu_err {
+	GSM48_QOS_SERR_1e_2		= 0x01,
+	GSM48_QOS_SERR_7e_2		= 0x02,
+	GSM48_QOS_SERR_1e_3		= 0x03,
+	GSM48_QOS_SERR_1e_4		= 0x04,
+	GSM48_QOS_SERR_1e_5		= 0x05,
+	GSM48_QOS_SERR_1e_6		= 0x06,
+	GSM48_QOS_SERR_1e_1		= 0x07,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+struct gsm48_qos {
+	/* octet 3 */
+	uint8_t reliab_class:3;
+	uint8_t delay_class:3;
+	uint8_t spare:2;
+	/* octet 4 */
+	uint8_t preced_class:3;
+	uint8_t spare2:1;
+	uint8_t peak_tput:4;
+	/* octet 5 */
+	uint8_t mean_tput:5;
+	uint8_t spare3:3;
+	/* octet 6 */
+	uint8_t deliv_err_sdu:3;
+	uint8_t deliv_order:2;
+	uint8_t traf_class:3;
+	/* octet 7 */
+	uint8_t max_sdu_size;
+	/* octet 8 */
+	uint8_t max_bitrate_up;
+	/* octet 9 */
+	uint8_t max_bitrate_down;
+	/* octet 10 */
+	uint8_t sdu_err_ratio:4;
+	uint8_t resid_ber:4;
+	/* octet 11 */
+	uint8_t handling_prio:2;
+	uint8_t xfer_delay:6;
+	/* octet 12 */
+	uint8_t guar_bitrate_up;
+	/* octet 13 */
+	uint8_t guar_bitrate_down;
+	/* octet 14 */
+	uint8_t src_stats_desc:4;
+	uint8_t sig_ind:1;
+	uint8_t spare5:3;
+	/* octet 15 */
+	uint8_t max_bitrate_down_ext;
+	/* octet 16 */
+	uint8_t guar_bitrate_down_ext;
+};
+
+
+int gprs_tlli_type(uint32_t tlli);
+
+struct gsm_bts *gsm48_bts_by_ra_id(struct gsm_network *net,
+				   const uint8_t *buf, unsigned int len);
+
+#endif /* _GSM48_GPRS_H */
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 8dfa588..91527c9 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -73,6 +73,37 @@
 	GSM_PAGING_OOM,
 };
 
+enum bts_gprs_mode {
+	BTS_GPRS_NONE = 0,
+	BTS_GPRS_GPRS = 1,
+	BTS_GPRS_EGPRS = 2,
+};
+
+/* the data structure stored in msgb->cb for openbsc apps */
+struct openbsc_msgb_cb {
+	unsigned char *bssgph;
+	unsigned char *llch;
+
+	/* Cell Identifier */
+	unsigned char *bssgp_cell_id;
+
+	/* Identifiers of a BTS, equal to 'struct bssgp_bts_ctx' */
+	u_int16_t nsei;
+	u_int16_t bvci;
+
+	/* Identifier of a MS (inside BTS), equal to 'struct sgsn_mm_ctx' */
+	u_int32_t tlli;
+} __attribute__((packed));
+#define OBSC_MSGB_CB(__msgb)	((struct openbsc_msgb_cb *)&((__msgb)->cb[0]))
+#define msgb_tlli(__x)		OBSC_MSGB_CB(__x)->tlli
+#define msgb_nsei(__x)		OBSC_MSGB_CB(__x)->nsei
+#define msgb_bvci(__x)		OBSC_MSGB_CB(__x)->bvci
+#define msgb_gmmh(__x)		(__x)->l3h
+#define msgb_bssgph(__x)	OBSC_MSGB_CB(__x)->bssgph
+#define msgb_bssgp_len(__x)	((__x)->tail - (uint8_t *)msgb_bssgph(__x))
+#define msgb_bcid(__x)		OBSC_MSGB_CB(__x)->bssgp_cell_id
+#define msgb_llch(__x)		OBSC_MSGB_CB(__x)->llch
+
 struct msgb;
 typedef int gsm_cbfn(unsigned int hooknum,
 		     unsigned int event,
@@ -99,11 +130,6 @@
 	} while(0);
 
 
-/* communications link with a BTS */
-struct gsm_bts_link {
-	struct gsm_bts *bts;
-};
-
 /* Real authentication information containing Ki */
 enum gsm_auth_algo {
 	AUTH_ALGO_NONE,
@@ -250,6 +276,7 @@
 		u_int16_t bound_port;
 		u_int16_t connect_port;
 		u_int16_t conn_id;
+		u_int8_t rtp_payload;
 		u_int8_t rtp_payload2;
 		u_int8_t speech_mode;
 		struct rtp_socket *rtp_socket;
@@ -368,7 +395,6 @@
 struct gsm_bts_paging_state {
 	/* pending requests */
 	struct llist_head pending_requests;
-	struct gsm_paging_request *last_request;
 	struct gsm_bts *bts;
 
 	struct timer_list work_timer;
@@ -383,11 +409,14 @@
 
 struct gsm_bts_gprs_nsvc {
 	struct gsm_bts *bts;
+	/* data read via VTY config file, to configure the BTS
+	 * via OML from BSC */
 	int id;
 	u_int16_t nsvci;
-	u_int16_t local_port;
-	u_int16_t remote_port;
-	u_int32_t remote_ip;
+	u_int16_t local_port;	/* on the BTS */
+	u_int16_t remote_port;	/* on the SGSN */
+	u_int32_t remote_ip;	/* on the SGSN */
+
 	struct gsm_nm_state nm_state;
 };
 
@@ -476,18 +505,24 @@
 
 	/* Not entirely sure how ip.access specific this is */
 	struct {
-		int enabled;
+		enum bts_gprs_mode mode;
 		struct {
 			struct gsm_nm_state nm_state;
 			u_int16_t nsei;
+			uint8_t timer[7];
 		} nse;
 		struct {
 			struct gsm_nm_state nm_state;
 			u_int16_t bvci;
+			uint8_t timer[11];
 		} cell;
 		struct gsm_bts_gprs_nsvc nsvc[2];
 		u_int8_t rac;
 	} gprs;
+
+	/* RACH NM values */
+	int rach_b_thresh;
+	int rach_ldavg_slots;
 	
 	/* transceivers */
 	int num_trx;
@@ -535,6 +570,14 @@
 		struct counter *alerted;	/* we alerted the other end */
 		struct counter *connected;/* how many calls were accepted */
 	} call;
+	struct {
+		struct counter *rf_fail;
+		struct counter *rll_err;
+	} chan;
+	struct {
+		struct counter *oml_fail;
+		struct counter *rsl_fail;
+	} bts;
 };
 
 enum gsm_auth_policy {
@@ -697,15 +740,10 @@
 enum rrlp_mode rrlp_mode_parse(const char *arg);
 const char *rrlp_mode_name(enum rrlp_mode mode);
 
-void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked);
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg);
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode);
 
-/* A parsed GPRS routing area */
-struct gprs_ra_id {
-	u_int16_t	mnc;
-	u_int16_t	mcc;
-	u_int16_t	lac;
-	u_int8_t	rac;
-};
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked);
 
 int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts);
 void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts);
diff --git a/openbsc/include/openbsc/ipaccess.h b/openbsc/include/openbsc/ipaccess.h
index 86248aa..f8ddfd4 100644
--- a/openbsc/include/openbsc/ipaccess.h
+++ b/openbsc/include/openbsc/ipaccess.h
@@ -53,6 +53,8 @@
 
 int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len);
 
+int ipaccess_drop_oml(struct gsm_bts *bts);
+int ipaccess_drop_rsl(struct gsm_bts_trx *trx);
 
 /*
  * Firmware specific header
diff --git a/openbsc/include/openbsc/rest_octets.h b/openbsc/include/openbsc/rest_octets.h
index 4e72c0f..6d90119 100644
--- a/openbsc/include/openbsc/rest_octets.h
+++ b/openbsc/include/openbsc/rest_octets.h
@@ -71,6 +71,7 @@
 	GPRS_NMO_III	= 2,	/* no paging coordination */
 };
 
+/* TS 04.60 12.24 */
 struct gprs_cell_options {
 	enum gprs_nmo nmo;
 	/* T3168: wait for packet uplink assignment message */
@@ -79,6 +80,16 @@
 	u_int32_t t3192;	/* in milliseconds */
 	u_int32_t drx_timer_max;/* in seconds */
 	u_int32_t bs_cv_max;
+
+	u_int8_t ext_info_present;
+	struct {
+		u_int8_t egprs_supported;
+			u_int8_t use_egprs_p_ch_req;
+			u_int8_t bep_period;
+		u_int8_t pfc_supported;
+		u_int8_t dtm_supported;
+		u_int8_t bss_paging_coordination;
+	} ext_info;
 };
 
 /* TS 04.60 Table 12.9.2 */
diff --git a/openbsc/include/openbsc/rtp_proxy.h b/openbsc/include/openbsc/rtp_proxy.h
index f82711a..65b1a5f 100644
--- a/openbsc/include/openbsc/rtp_proxy.h
+++ b/openbsc/include/openbsc/rtp_proxy.h
@@ -28,6 +28,12 @@
 #include <osmocore/linuxlist.h>
 #include <osmocore/select.h>
 
+#define RTP_PT_GSM_FULL 3
+#define RTP_PT_GSM_HALF 96
+#define RTP_PT_GSM_EFR 97
+#define RTP_PT_AMR_FULL 98
+#define RTP_PT_AMR_HALF 99
+
 enum rtp_rx_action {
 	RTP_NONE,
 	RTP_PROXY,
diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h
new file mode 100644
index 0000000..2dc53c1
--- /dev/null
+++ b/openbsc/include/openbsc/sgsn.h
@@ -0,0 +1,30 @@
+#ifndef _SGSN_H
+#define _SGSN_H
+
+#include <sys/types.h>
+
+#include <osmocore/msgb.h>
+
+#include <openbsc/gprs_ns.h>
+
+struct sgsn_config {
+	/* parsed from config file */
+	u_int32_t nsip_listen_ip;
+	u_int16_t nsip_listen_port;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+};
+
+
+/* sgsn_vty.c */
+
+int sgsn_vty_init(void);
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg);
+
+/* sgsn.c */
+
+/* Main input function for Gb proxy */
+int sgsn_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+#endif
diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h
index 1b974e2..48f7946 100644
--- a/openbsc/include/openbsc/signal.h
+++ b/openbsc/include/openbsc/signal.h
@@ -26,7 +26,6 @@
 #include <errno.h>
 
 #include <openbsc/gsm_data.h>
-#include <openbsc/gsm_subscriber.h>
 
 #include <osmocore/signal.h>
 
@@ -43,6 +42,7 @@
 	SS_SCALL,
 	SS_GLOBAL,
 	SS_CHALLOC,
+	SS_NS,
 };
 
 /* SS_PAGING signals */
@@ -118,6 +118,8 @@
 	S_GLOBAL_SHUTDOWN,
 };
 
+struct gsm_subscriber;
+
 struct paging_signal_data {
 	struct gsm_subscriber *subscr;
 	struct gsm_bts *bts;
@@ -133,7 +135,7 @@
 };
 
 struct ipacc_ack_signal_data {
-	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
 	u_int8_t msg_type;	
 };
 
@@ -143,4 +145,16 @@
 	enum gsm_chan_t type;
 };
 
+enum signal_ns {
+	S_NS_RESET,
+	S_NS_BLOCK,
+	S_NS_UNBLOCK,
+	S_NS_ALIVE_EXP,	/* Tns-alive expired more than N times */
+};
+
+struct ns_signal_data {
+	struct gprs_nsvc *nsvc;
+	uint8_t cause;
+};
+
 #endif
diff --git a/openbsc/include/openbsc/vty.h b/openbsc/include/openbsc/vty.h
index 40e2191..f1b1148 100644
--- a/openbsc/include/openbsc/vty.h
+++ b/openbsc/include/openbsc/vty.h
@@ -1,6 +1,10 @@
 #ifndef OPENBSC_VTY_H
 #define OPENBSC_VTY_H
 
+struct gsm_network;
+struct vty;
+
 void openbsc_vty_add_cmds(void);
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *);
 
 #endif
diff --git a/openbsc/include/sccp/sccp_types.h b/openbsc/include/sccp/sccp_types.h
index 42fda96..22bd70f 100644
--- a/openbsc/include/sccp/sccp_types.h
+++ b/openbsc/include/sccp/sccp_types.h
@@ -411,4 +411,10 @@
 	u_int8_t			credit;
 } __attribute__((packed));
 
+struct sccp_proto_err {
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	u_int8_t			error_cause;
+};
+
 #endif
diff --git a/openbsc/include/vty/buffer.h b/openbsc/include/vty/buffer.h
index 3151940..c9467a9 100644
--- a/openbsc/include/vty/buffer.h
+++ b/openbsc/include/vty/buffer.h
@@ -28,7 +28,7 @@
 /* Create a new buffer.  Memory will be allocated in chunks of the given
    size.  If the argument is 0, the library will supply a reasonable
    default size suitable for buffering socket I/O. */
-struct buffer *buffer_new(size_t);
+struct buffer *buffer_new(void *ctx, size_t);
 
 /* Free all data in the buffer. */
 void buffer_reset(struct buffer *);
diff --git a/openbsc/include/vty/command.h b/openbsc/include/vty/command.h
index 03b071f..1b6e0a7 100644
--- a/openbsc/include/vty/command.h
+++ b/openbsc/include/vty/command.h
@@ -107,6 +107,9 @@
 	TS_NODE,
 	SUBSCR_NODE,
 	MGCP_NODE,
+	GBPROXY_NODE,
+	SGSN_NODE,
+	NS_NODE,
 };
 
 /* Node which has some commands and prompt string and configuration
@@ -173,6 +176,17 @@
 
 /* helper defines for end-user DEFUN* macros */
 #define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+  static struct cmd_element cmdname = \
+  { \
+    .string = cmdstr, \
+    .func = funcname, \
+    .doc = helpstr, \
+    .attr = attrs, \
+    .daemon = dnum, \
+  };
+
+/* global (non static) cmd_element */
+#define gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
   struct cmd_element cmdname = \
   { \
     .string = cmdstr, \
@@ -195,6 +209,12 @@
   DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
   DEFUN_CMD_FUNC_TEXT(funcname)
 
+/* global (non static) cmd_element */
+#define gDEFUN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
 #define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
   DEFUN_CMD_FUNC_DECL(funcname) \
   DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \
@@ -236,6 +256,10 @@
 #define ALIAS(funcname, cmdname, cmdstr, helpstr) \
   DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
 
+/* global (non static) cmd_element */
+#define gALIAS(funcname, cmdname, cmdstr, helpstr) \
+  gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+
 #define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
   DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)
 
@@ -326,6 +350,7 @@
 void install_node(struct cmd_node *, int (*)(struct vty *));
 void install_default(enum node_type);
 void install_element(enum node_type, struct cmd_element *);
+void install_element_ve(struct cmd_element *cmd);
 void sort_node();
 
 /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
index 2c1d37a..177c410 100644
--- a/openbsc/src/Makefile.am
+++ b/openbsc/src/Makefile.am
@@ -2,29 +2,32 @@
 AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
 AM_LDFLAGS = $(LIBOSMOCORE_LIBS)
 
-sbin_PROGRAMS = bsc_hack bs11_config ipaccess-find ipaccess-config \
-                isdnsync bsc_mgcp ipaccess-proxy
-noinst_LIBRARIES = libbsc.a libmsc.a libvty.a
+# build current directory before building gprs
+SUBDIRS = . ipaccess gprs
+
+sbin_PROGRAMS = bsc_hack bs11_config isdnsync bsc_mgcp
+noinst_LIBRARIES = libbsc.a libmsc.a libvty.a libsccp.a
 noinst_HEADERS = vty/cardshell.h
 
 bscdir = $(libdir)
 bsc_LIBRARIES = libsccp.a
 
 libbsc_a_SOURCES = abis_rsl.c abis_nm.c gsm_data.c gsm_04_08_utils.c \
-		chan_alloc.c debug.c \
+		chan_alloc.c debug.c socket.c \
 		gsm_subscriber_base.c subchan_demux.c bsc_rll.c transaction.c \
 		trau_frame.c trau_mux.c paging.c e1_config.c e1_input.c \
 		input/misdn.c input/ipaccess.c \
 		talloc_ctx.c system_information.c rest_octets.c \
 		rtp_proxy.c bts_siemens_bs11.c bts_ipaccess_nanobts.c \
-		bts_unknown.c bsc_version.c bsc_api.c vty_interface_cmds.c
+		bts_unknown.c bsc_version.c bsc_api.c
 
-libmsc_a_SOURCES = gsm_subscriber.c db.c telnet_interface.c \
+libmsc_a_SOURCES = gsm_subscriber.c db.c \
 		mncc.c gsm_04_08.c gsm_04_11.c transaction.c \
 		token_auth.c rrlp.c gsm_04_80.c ussd.c silent_call.c \
 		handover_logic.c handover_decision.c meas_rep.c
 
-libvty_a_SOURCES = vty/buffer.c vty/command.c vty/vector.c vty/vty.c
+libvty_a_SOURCES = vty/buffer.c vty/command.c vty/vector.c vty/vty.c \
+		telnet_interface.c vty_interface_cmds.c vty/utils.c
 
 libsccp_a_SOURCES = sccp/sccp.c
 
@@ -34,15 +37,8 @@
 bs11_config_SOURCES = bs11_config.c abis_nm.c gsm_data.c debug.c \
 		      rs232.c bts_siemens_bs11.c
 
-ipaccess_find_SOURCES = ipaccess/ipaccess-find.c
-
-ipaccess_config_SOURCES = ipaccess/ipaccess-config.c ipaccess/ipaccess-firmware.c
-ipaccess_config_LDADD = libbsc.a libmsc.a libbsc.a libvty.a -ldl -ldbi $(LIBCRYPT)
-
 isdnsync_SOURCES = isdnsync.c
 
 bsc_mgcp_SOURCES = mgcp/mgcp_main.c mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c \
-		   debug.c telnet_interface.c vty_interface_cmds.c
+		   debug.c
 bsc_mgcp_LDADD = libvty.a
-
-ipaccess_proxy_SOURCES = ipaccess/ipaccess-proxy.c debug.c
diff --git a/openbsc/src/abis_nm.c b/openbsc/src/abis_nm.c
index 5e6e819..09285bd 100644
--- a/openbsc/src/abis_nm.c
+++ b/openbsc/src/abis_nm.c
@@ -678,7 +678,7 @@
 	new_state = *nm_state;
 	new_state.administrative = adm_state;
 
-	rc = nm_state_event(EVT_STATECHG_ADM, obj_class, obj, nm_state, &new_state);
+	rc = nm_state_event(EVT_STATECHG_ADM, obj_class, obj, nm_state, &new_state, obj_inst);
 
 	nm_state->administrative = adm_state;
 
@@ -732,7 +732,7 @@
 		/* Update the operational state of a given object in our in-memory data
  		* structures and send an event to the higher layer */
 		void *obj = objclass2obj(bts, foh->obj_class, &foh->obj_inst);
-		rc = nm_state_event(EVT_STATECHG_OPER, foh->obj_class, obj, nm_state, &new_state);
+		rc = nm_state_event(EVT_STATECHG_OPER, foh->obj_class, obj, nm_state, &new_state, &foh->obj_inst);
 		nm_state->operational = new_state.operational;
 		nm_state->availability = new_state.availability;
 		if (nm_state->administrative == 0)
@@ -822,15 +822,56 @@
 	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 sw_config_len;
-	int file_id_len;
-	int ret;
+	int ret, sw_config_len, sw_descr_len;
 
 	debugp_foh(foh);
 
@@ -854,20 +895,16 @@
 		DEBUGP(DNM, "Found SW config: %s\n", hexdump(sw_config, sw_config_len));
 	}
 
-	if (sw_config[0] != NM_ATT_SW_DESCR)
-		DEBUGP(DNM, "SW_DESCR attribute identifier not found!\n");
-	if (sw_config[1] != NM_ATT_FILE_ID)
-		DEBUGP(DNM, "FILE_ID attribute identifier not found!\n");
-	file_id_len = sw_config[2] * 256 + sw_config[3];
+		/* 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;
 
-	/* Assumes first SW file in list is the one to be activated */
-	/* sw_config + 4 to skip over 2 attribute ID bytes and 16-bit length field */
 	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 + 4,
-				 file_id_len);
+				 sw_config, sw_descr_len);
 }
 
 /* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */
@@ -1102,6 +1139,7 @@
 
 struct abis_nm_sw {
 	struct gsm_bts *bts;
+	int trx_nr;
 	gsm_cbfn *cbfn;
 	void *cb_data;
 	int forced;
@@ -1555,7 +1593,7 @@
 }
 
 /* Load the specified software into the BTS */
-int abis_nm_software_load(struct gsm_bts *bts, const char *fname,
+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)
 {
@@ -1569,6 +1607,7 @@
 		return -EBUSY;
 
 	sw->bts = bts;
+	sw->trx_nr = trx_nr;
 
 	switch (bts->type) {
 	case GSM_BTS_TYPE_BS11:
@@ -1579,8 +1618,8 @@
 		break;
 	case GSM_BTS_TYPE_NANOBTS:
 		sw->obj_class = NM_OC_BASEB_TRANSC;
-		sw->obj_instance[0] = 0x00;
-		sw->obj_instance[1] = 0x00;
+		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:
@@ -2514,7 +2553,7 @@
 		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, fle->fname,
+			rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname,
 						   bs11_sw->win_size,
 						   bs11_sw->forced,
 						   &bs11_swload_cbfn, bs11_sw);
@@ -2570,7 +2609,7 @@
 		return -EINVAL;
 
 	/* start download the next file of our file list */
-	rc = abis_nm_software_load(bts, fle->fname, win_size, forced,
+	rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced,
 				   bs11_swload_cbfn, bs11_sw);
 	talloc_free(fle);
 	return rc;
@@ -2738,12 +2777,12 @@
 	case NM_MT_IPACC_RSL_CONNECT_NACK:
 	case NM_MT_IPACC_SET_NVATTR_NACK:
 	case NM_MT_IPACC_GET_NVATTR_NACK:
-		signal.bts = msg->trx->bts;
+		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.bts = msg->trx->bts;
+		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;
@@ -2829,9 +2868,16 @@
 }
 
 /* restart / reboot an ip.access nanoBTS */
-int abis_nm_ipaccess_restart(struct gsm_bts *bts)
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx)
 {
-	return __simple_cmd(bts, NM_MT_IPACC_RESTART);
+	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,
diff --git a/openbsc/src/abis_rsl.c b/openbsc/src/abis_rsl.c
index 0e572cc..53b2982 100644
--- a/openbsc/src/abis_rsl.c
+++ b/openbsc/src/abis_rsl.c
@@ -727,7 +727,6 @@
 			     link_id, 0);
 	msgb_tv_put(msg, RSL_IE_RELEASE_MODE, 0);	/* normal release */
 
-	lchan->state = LCHAN_S_REL_REQ;
 	/* FIXME: start some timer in case we don't receive a REL ACK ? */
 
 	msg->trx = lchan->ts->trx;
@@ -735,6 +734,12 @@
 	return abis_rsl_sendmsg(msg);
 }
 
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int state)
+{
+	lchan->state = state;
+	return 0;
+}
+
 /* Chapter 8.4.2: Channel Activate Acknowledge */
 static int rsl_rx_chan_act_ack(struct msgb *msg)
 {
@@ -749,7 +754,7 @@
 		LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n",
 			gsm_lchan_name(msg->lchan),
 			gsm_lchans_name(msg->lchan->state));
-	msg->lchan->state = LCHAN_S_ACTIVE;
+	rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE);
 
 	dispatch_signal(SS_LCHAN, S_LCHAN_ACTIVATE_ACK, msg->lchan);
 
@@ -775,9 +780,9 @@
 		print_rsl_cause(LOGL_ERROR, cause,
 				TLVP_LEN(&tp, RSL_IE_CAUSE));
 		if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC)
-			msg->lchan->state = LCHAN_S_NONE;
+			rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
 	} else
-		msg->lchan->state = LCHAN_S_NONE;
+		rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
 
 	LOGPC(DRSL, LOGL_ERROR, "\n");
 
@@ -805,6 +810,7 @@
 
 	LOGPC(DRSL, LOGL_NOTICE, "\n");
 	/* FIXME: only free it after channel release ACK */
+	counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rf_fail);
 	return rsl_rf_chan_release(msg->lchan);
 }
 
@@ -981,7 +987,7 @@
 			LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n",
 				gsm_lchan_name(msg->lchan),
 				gsm_lchans_name(msg->lchan->state));
-		msg->lchan->state = LCHAN_S_NONE;
+		rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
 		lchan_free(msg->lchan);
 		break;
 	case RSL_MT_MODE_MODIFY_ACK:
@@ -1124,7 +1130,7 @@
 		LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel "
 		     "in state %s\n", gsm_lchan_name(lchan),
 		     gsm_lchans_name(lchan->state));
-	lchan->state = LCHAN_S_ACT_REQ;
+	rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
 
 	ts_number = lchan->ts->nr;
 	arfcn = lchan->ts->trx->arfcn;
@@ -1181,6 +1187,10 @@
 	switch (rslh->data[0]) {
 	case RSL_IE_PAGING_LOAD:
 		pg_buf_space = rslh->data[1] << 8 | rslh->data[2];
+		if (is_ipaccess_bts(msg->trx->bts) && pg_buf_space == 0xffff) {
+			/* paging load below configured threshold, use 50 as default */
+			pg_buf_space = 50;
+		}
 		paging_update_buffer_space(msg->trx->bts, pg_buf_space);
 		break;
 	case RSL_IE_RACH_LOAD:
@@ -1240,8 +1250,10 @@
 
 	rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
 
-	if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED)
+	if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED) {
+		counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rll_err);
 		return rsl_rf_chan_release(msg->lchan);
+	}
 
 	return 0;
 }
@@ -1363,6 +1375,44 @@
 	return 0;
 }
 
+static u_int8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan)
+{
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_GSM_HALF;
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_EFR;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_AMR_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_AMR_HALF;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for "
+		"tch_mode == 0x%02x\n & lchan_type == %d",
+		lchan->tch_mode, lchan->type);
+	return 0;
+}
+
 /* ip.access specific RSL extensions */
 static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv)
 {
@@ -1429,10 +1479,13 @@
 
 	/* 0x1- == receive-only, 0x-1 == EFR codec */
 	lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
 	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
 
-	DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x\n",
-		gsm_lchan_name(lchan), lchan->abis_ip.speech_mode);
+	DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n",
+		gsm_lchan_name(lchan), lchan->abis_ip.speech_mode,
+		lchan->abis_ip.rtp_payload);
 
 	msg->trx = lchan->ts->trx;
 
@@ -1459,11 +1512,13 @@
 
 	/* 0x0- == both directions, 0x-1 == EFR codec */
 	lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
 
 	ia.s_addr = htonl(ip);
-	DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD2=%d CONN_ID=%d "
-		"speech_mode=0x%02x\n", gsm_lchan_name(lchan), inet_ntoa(ia), port,
-		rtp_payload2, lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode);
+	DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d "
+		"CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan),
+		inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2,
+		lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode);
 
 	msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
 	msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
@@ -1471,6 +1526,7 @@
 	*att_ip = ia.s_addr;
 	msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port);
 	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
 	if (rtp_payload2)
 		msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2);
 	
@@ -1652,9 +1708,21 @@
 /* Entry-point where L2 RSL from BTS enters */
 int abis_rsl_rcvmsg(struct msgb *msg)
 {
-	struct abis_rsl_common_hdr *rslh = msgb_l2(msg)	;
+	struct abis_rsl_common_hdr *rslh;
 	int rc = 0;
 
+	if (!msg) {
+		DEBUGP(DRSL, "Empty RSL msg?..\n");
+		return -1;
+	}
+
+	if (msgb_l2len(msg) < sizeof(*rslh)) {
+		DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg));
+		return -1;
+	}
+
+	rslh = msgb_l2(msg);
+
 	switch (rslh->msg_discr & 0xfe) {
 	case ABIS_RSL_MDISC_RLL:
 		rc = abis_rsl_rx_rll(msg);
diff --git a/openbsc/src/bs11_config.c b/openbsc/src/bs11_config.c
index a7493b4..8f6de8a 100644
--- a/openbsc/src/bs11_config.c
+++ b/openbsc/src/bs11_config.c
@@ -481,7 +481,7 @@
 		 * argument, so our swload_cbfn can distinguish
 		 * a safety load from a regular software */
 		if (file_is_readable(fname_safety))
-			rc = abis_nm_software_load(g_bts, fname_safety,
+			rc = abis_nm_software_load(g_bts, 0xff, fname_safety,
 						   win_size, param_forced,
 						   swload_cbfn, g_bts);
 		else
@@ -697,7 +697,8 @@
 }
 
 int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
-		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+		   struct abis_om_obj_inst *obj_ins)
 {
 	return 0;
 }
diff --git a/openbsc/src/bsc_init.c b/openbsc/src/bsc_init.c
index f343662..77446a2 100644
--- a/openbsc/src/bsc_init.c
+++ b/openbsc/src/bsc_init.c
@@ -31,6 +31,7 @@
 #include <openbsc/system_information.h>
 #include <openbsc/paging.h>
 #include <openbsc/signal.h>
+#include <openbsc/chan_alloc.h>
 #include <osmocore/talloc.h>
 
 /* global pointer to the gsm network data structure */
@@ -377,11 +378,11 @@
 		4,	/* N3103 */
 		8,	/* N3105 */
 		15,	/* RLC CV countdown */
-	NM_ATT_IPACC_CODING_SCHEMES, 0, 2,  0x0f, 0x00,
+	NM_ATT_IPACC_CODING_SCHEMES, 0, 2,  0x0f, 0x00,	/* CS1..CS4 */
 	NM_ATT_IPACC_RLC_CFG_2, 0, 5,
-		0x00, 250,
-		0x00, 250,
-		2,	/* MCS2 */
+		0x00, 250,	/* T downlink TBF extension (0..500) */
+		0x00, 250,	/* T uplink TBF extension (0..500) */
+		2,	/* CS2 */
 #if 0
 	/* EDGE model only, breaks older models.
 	 * Should inquire the BTS capabilities */
@@ -400,7 +401,8 @@
 
 /* Callback function to be called whenever we get a GSM 12.21 state change event */
 int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
-		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+		   struct abis_om_obj_inst *obj_inst)
 {
 	struct gsm_bts *bts;
 	struct gsm_bts_trx *trx;
@@ -461,7 +463,7 @@
 		break;
 	case NM_OC_GPRS_NSE:
 		bts = container_of(obj, struct gsm_bts, gprs.nse);
-		if (!bts->gprs.enabled)
+		if (bts->gprs.mode == BTS_GPRS_NONE)
 			break;
 		if (new_state->availability == 5) {
 			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
@@ -475,7 +477,7 @@
 		break;
 	case NM_OC_GPRS_CELL:
 		bts = container_of(obj, struct gsm_bts, gprs.cell);
-		if (!bts->gprs.enabled)
+		if (bts->gprs.mode == BTS_GPRS_NONE)
 			break;
 		if (new_state->availability == 5) {
 			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
@@ -490,9 +492,9 @@
 	case NM_OC_GPRS_NSVC:
 		nsvc = obj;
 		bts = nsvc->bts;
-		if (!bts->gprs.enabled)
+		if (bts->gprs.mode == BTS_GPRS_NONE)
 			break;
-	        /* We skip NSVC1 since we only use NSVC0 */
+		/* We skip NSVC1 since we only use NSVC0 */
 		if (nsvc->id == 1)
 			break;
 		if (new_state->availability == NM_AVSTATE_OFF_LINE) {
@@ -798,7 +800,7 @@
 			DEBUGP(DRR, "SI%2u: %s\n", i, hexdump(si_tmp, rc));
 			rsl_bcch_info(trx, i, si_tmp, sizeof(si_tmp));
 		}
-		if (bts->gprs.enabled) {
+		if (bts->gprs.mode != BTS_GPRS_NONE) {
 			i = 13;
 			rc = gsm_generate_si(si_tmp, trx->bts, RSL_SYSTEM_INFO_13);
 			if (rc < 0)
@@ -852,6 +854,22 @@
 	bs11_attr_radio[2] |= arfcn_high;
 	bs11_attr_radio[3] = arfcn_low;
 
+	/* patch the RACH attributes */
+	if (bts->rach_b_thresh != -1) {
+		nanobts_attr_bts[33] = bts->rach_b_thresh & 0xff;
+		bs11_attr_bts[33] = bts->rach_b_thresh & 0xff;
+	}
+
+	if (bts->rach_ldavg_slots != -1) {
+		u_int8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+		nanobts_attr_bts[35] = avg_high;
+		nanobts_attr_bts[36] = avg_low;
+		bs11_attr_bts[35] = avg_high;
+		bs11_attr_bts[36] = avg_low;
+	}
+
 	/* patch BSIC */
 	bs11_attr_bts[1] = bts->bsic;
 	nanobts_attr_bts[sizeof(nanobts_attr_bts)-11] = bts->bsic;
@@ -866,6 +884,10 @@
 	/* patch NSEI */
 	nanobts_attr_nse[3] = bts->gprs.nse.nsei >> 8;
 	nanobts_attr_nse[4] = bts->gprs.nse.nsei & 0xff;
+	memcpy(nanobts_attr_nse+8, bts->gprs.nse.timer,
+		ARRAY_SIZE(bts->gprs.nse.timer));
+	memcpy(nanobts_attr_nse+18, bts->gprs.cell.timer,
+		ARRAY_SIZE(bts->gprs.cell.timer));
 
 	/* patch NSVCI */
 	nanobts_attr_nsvc0[3] = bts->gprs.nsvc[0].nsvci >> 8;
@@ -885,6 +907,11 @@
 	/* patch RAC */
 	nanobts_attr_cell[3] = bts->gprs.rac;
 
+	if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+		/* patch EGPRS coding schemes MCS 1..9 */
+		nanobts_attr_cell[29] = 0x8f;
+		nanobts_attr_cell[30] = 0xff;
+	}
 }
 
 static void bootstrap_rsl(struct gsm_bts_trx *trx)
@@ -899,6 +926,8 @@
 
 void input_event(int event, enum e1inp_sign_type type, struct gsm_bts_trx *trx)
 {
+	int ts_no, lchan_no;
+
 	switch (event) {
 	case EVT_E1_TEI_UP:
 		switch (type) {
@@ -913,8 +942,35 @@
 		}
 		break;
 	case EVT_E1_TEI_DN:
-		LOGP(DMI, LOGL_NOTICE, "Lost some E1 TEI link\n");
-		/* FIXME: deal with TEI or L1 link loss */
+		LOGP(DMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", type, trx);
+
+		if (type == E1INP_SIGN_OML)
+			counter_inc(trx->bts->network->stats.bts.oml_fail);
+		else if (type == E1INP_SIGN_RSL)
+			counter_inc(trx->bts->network->stats.bts.rsl_fail);
+
+		/*
+		 * free all allocated channels. change the nm_state so the
+		 * trx and trx_ts becomes unusable and chan_alloc.c can not
+		 * allocate from it.
+		 */
+		for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[ts_no];
+
+			for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) {
+				if (ts->lchan[lchan_no].state != GSM_LCHAN_NONE)
+					lchan_free(&ts->lchan[lchan_no]);
+				lchan_reset(&ts->lchan[lchan_no]);
+			}
+
+			ts->nm_state.operational = 0;
+			ts->nm_state.availability = 0;
+		}
+
+		trx->nm_state.operational = 0;
+		trx->nm_state.availability = 0;
+		trx->bb_transc.nm_state.operational = 0;
+		trx->bb_transc.nm_state.availability = 0;
 		break;
 	default:
 		break;
@@ -923,6 +979,8 @@
 
 static int bootstrap_bts(struct gsm_bts *bts)
 {
+	int i, n;
+
 	switch (bts->band) {
 	case GSM_BAND_1800:
 		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
@@ -959,10 +1017,34 @@
 
 	/* Control Channel Description */
 	bts->si_common.chan_desc.att = 1;
-	bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
 	bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5;
 	/* T3212 is set from vty/config */
 
+	/* Set ccch config by looking at ts config */
+	for (n=0, i=0; i<8; i++)
+		n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0;
+
+	switch (n) {
+	case 0:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
+		break;
+	case 1:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC;
+		break;
+	case 2:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC;
+		break;
+	case 3:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC;
+		break;
+	case 4:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC;
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n");
+		return -EINVAL;
+	}
+
 	/* some defaults for our system information */
 	bts->si_common.cell_options.radio_link_timeout = 2; /* 12 */
 	bts->si_common.cell_options.dtx = 2; /* MS shall not use upplink DTX */
diff --git a/openbsc/src/chan_alloc.c b/openbsc/src/chan_alloc.c
index cd48e43..107abdc 100644
--- a/openbsc/src/chan_alloc.c
+++ b/openbsc/src/chan_alloc.c
@@ -330,6 +330,20 @@
 	 * channel using it */
 }
 
+/*
+ * There was an error with the TRX and we need to forget
+ * any state so that a lchan can be allocated again after
+ * the trx is fully usable.
+ */
+void lchan_reset(struct gsm_lchan *lchan)
+{
+	bsc_del_timer(&lchan->T3101);
+
+	lchan->type = GSM_LCHAN_NONE;
+	lchan->state = LCHAN_S_NONE;
+}
+
+
 /* Consider releasing the channel now */
 int lchan_auto_release(struct gsm_lchan *lchan)
 {
@@ -348,6 +362,7 @@
 			lchan->conn.use_count);
 
 	DEBUGP(DRLL, "%s Recycling Channel\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
 	rsl_release_request(lchan, 0);
 	return 1;
 }
diff --git a/openbsc/src/db.c b/openbsc/src/db.c
index 8bf47ab..57a7863 100644
--- a/openbsc/src/db.c
+++ b/openbsc/src/db.c
@@ -20,13 +20,8 @@
  *
  */
 
-#include <openbsc/gsm_data.h>
-#include <openbsc/gsm_04_11.h>
-#include <openbsc/db.h>
-#include <osmocore/talloc.h>
-#include <openbsc/debug.h>
-#include <osmocore/statistics.h>
-
+#include <stdint.h>
+#include <inttypes.h>
 #include <libgen.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -34,6 +29,14 @@
 #include <errno.h>
 #include <dbi/dbi.h>
 
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <osmocore/statistics.h>
+#include <osmocore/rate_ctr.h>
+
 static char *db_basename = NULL;
 static char *db_dirname = NULL;
 static dbi_conn conn;
@@ -124,6 +127,13 @@
 		"value INTEGER NOT NULL, "
 		"name TEXT NOT NULL "
 		")",
+	"CREATE TABLE IF NOT EXISTS RateCounters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL, "
+		"index INTEGER NOT NULL "
+		")",
 	"CREATE TABLE IF NOT EXISTS AuthKeys ("
 		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
 		"subscriber_id INTEGER UNIQUE NOT NULL, "
@@ -1180,3 +1190,42 @@
 	dbi_result_free(result);
 	return 0;
 }
+
+static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num,
+			     char *q_prefix)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name,
+				   &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"Insert INTO RateCounters "
+		"(timestamp,name,index,value) VALUES "
+		"(datetime('now'),%s.%s,%u,%"PRIu64")",
+		q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg)
+{
+	unsigned int i;
+	char *q_prefix;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix);
+
+	for (i = 0; i < ctrg->desc->num_ctr; i++)
+		db_store_rate_ctr(ctrg, i, q_prefix);
+
+	free(q_prefix);
+
+	return 0;
+}
diff --git a/openbsc/src/debug.c b/openbsc/src/debug.c
index a55d790..9218f64 100644
--- a/openbsc/src/debug.c
+++ b/openbsc/src/debug.c
@@ -39,81 +39,81 @@
 static const struct log_info_cat default_categories[] = {
 	[DRLL] = {
 		.name = "DRLL",
-		.description = "Radio Link Layer",
+		.description = "A-bis Radio Link Layer (RLL)",
 		.color = "\033[1;31m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DCC] = {
 		.name = "DCC",
-		.description = "Call Control",
+		.description = "Layer3 Call Control (CC)",
 		.color = "\033[1;32m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DMM] = {
 		.name = "DMM",
-		.description = "Mobility Management",
+		.description = "Layer3 Mobility Management (MM)",
 		.color = "\033[1;33m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DRR] = {
 		.name = "DRR",
-		.description = "Radio Resource",
+		.description = "Layer3 Radio Resource (RR)",
 		.color = "\033[1;34m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DRSL] = {
 		.name = "DRSL",
-		.description = "Radio Siganlling Link",
+		.description = "A-bis Radio Siganlling Link (RSL)",
 		.color = "\033[1;35m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DNM] =	{
 		.name = "DNM",
-		.description = "Network Management (OML)",
+		.description = "A-bis Network Management / O&M (NM/OML)",
 		.color = "\033[1;36m",
 		.enabled = 1, .loglevel = LOGL_INFO,
 	},
 	[DMNCC] = {
 		.name = "DMNCC",
-		.description = "BSC<->MSC interface",
+		.description = "MNCC API for Call Control application",
 		.color = "\033[1;39m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DSMS] = {
 		.name = "DSMS",
-		.description = "Short Message Service",
+		.description = "Layer3 Short Message Service (SMS)",
 		.color = "\033[1;37m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DPAG]	= {
 		.name = "DPAG",
-		.description = "Paging",
+		.description = "Paging Subsystem",
 		.color = "\033[1;38m",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DMEAS] = {
 		.name = "DMEAS",
-		.description = "Measurement Processing",
+		.description = "Radio Measurement Processing",
 		.enabled = 0, .loglevel = LOGL_NOTICE,
 	},
 	[DMI] = {
 		.name = "DMI",
-		.description = "mISDN Input Driver",
+		.description = "A-bis Input Driver for Signalling",
 		.enabled = 0, .loglevel = LOGL_NOTICE,
 	},
 	[DMIB] = {
 		.name = "DMIB",
-		.description = "mISDN B-Channels",
+		.description = "A-bis Input Driver for B-Channels (voice)",
 		.enabled = 0, .loglevel = LOGL_NOTICE,
 	},
 	[DMUX] = {
 		.name = "DMUX",
-		.description = "TRAU Frame Multiplex",
+		.description = "A-bis B-Subchannel TRAU Frame Multiplex",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DINP] = {
 		.name = "DINP",
-		.description = "Input Driver",
+		.description = "A-bis Intput Subsystem",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DSCCP] = {
@@ -138,7 +138,7 @@
 	},
 	[DDB] = {
 		.name = "DDB",
-		.description = "Database",
+		.description = "Database Layer",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DREF] = {
@@ -146,6 +146,21 @@
 		.description = "Reference Counting",
 		.enabled = 0, .loglevel = LOGL_NOTICE,
 	},
+	[DGPRS] = {
+		.name = "DGPRS",
+		.description = "GPRS Packet Service",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DNS] = {
+		.name = "DNS",
+		.description = "GPRS Network Service (NS)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DBSSGP] = {
+		.name = "DBSSGP",
+		.description = "GPRS BSS Gateway Protocol (BSSGP)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
 };
 
 enum log_ctxt {
diff --git a/openbsc/src/e1_input.c b/openbsc/src/e1_input.c
index fba59a7..b1dfe9b 100644
--- a/openbsc/src/e1_input.c
+++ b/openbsc/src/e1_input.c
@@ -420,7 +420,17 @@
 
 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);
 }
 
diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am
new file mode 100644
index 0000000..88c358a
--- /dev/null
+++ b/openbsc/src/gprs/Makefile.am
@@ -0,0 +1,18 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS)
+
+sbin_PROGRAMS = osmo-gbproxy osmo-sgsn
+noinst_LIBRARIES = libsgsn.a
+
+libsgsn_a_SOURCES = gprs_ns.c gprs_bssgp.c gprs_llc.c gsm_04_08_gprs.c \
+		crc24.c gprs_sgsn.c gprs_bssgp_util.c
+
+osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c \
+			gprs_ns.c gprs_ns_vty.c gprs_bssgp_util.c \
+			$(top_srcdir)/src/socket.c $(top_srcdir)/src/debug.c
+osmo_gbproxy_LDADD = $(top_builddir)/src/libvty.a
+
+osmo_sgsn_SOURCES = sgsn_main.c sgsn_vty.c gprs_ns_vty.c \
+			$(top_srcdir)/src/socket.c $(top_srcdir)/src/debug.c
+osmo_sgsn_LDADD = $(top_builddir)/src/libvty.a libsgsn.a
diff --git a/openbsc/src/gprs/crc24.c b/openbsc/src/gprs/crc24.c
new file mode 100644
index 0000000..1082120
--- /dev/null
+++ b/openbsc/src/gprs/crc24.c
@@ -0,0 +1,69 @@
+/* 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 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 <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/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
new file mode 100644
index 0000000..5fbf9bf
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -0,0 +1,580 @@
+/* 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 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 <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;
+
+	/* 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 msgb *new_msg;
+
+	new_msg = msgb_alloc(msg->data_len, name);
+	if (!new_msg)
+		return NULL;
+
+	/* copy header */
+	memcpy(new_msg, msg, sizeof(*new_msg));
+	/* copy data */
+	memcpy(new_msg->data, msg->data, new_msg->data_len);
+
+	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);
+}
+
+/* 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) {
+		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->bvci);
+	} 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->bvci);
+	} 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) {
+		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_BLOCK_ACK:
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+	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_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;
+
+	/* 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) {
+			rc = gbprox_relay2sgsn(msg, ns_bvci);
+		} else {
+			struct gbprox_peer *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;
+			}
+			rc = gbprox_relay2peer(msg, peer, ns_bvci);
+		}
+	}
+
+	return rc;
+}
+
+/* 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);
+	}
+
+	/* We currently only care about signals from the SGSN */
+	if (!nsvc->remote_end_is_sgsn)
+		return 0;
+
+	/* 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 <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 %u, "
+			"RAC %u-%u-%u-%u%s",
+			nsvc->nsei, nsvc->nsvci, peer->bvci,
+			raid.mcc, raid.mnc, raid.lac, raid.rac, VTY_NEWLINE);
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
+			vty_out(vty, "  remote address %s:%u%s",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port), VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
diff --git a/openbsc/src/gprs/gb_proxy_main.c b/openbsc/src/gprs/gb_proxy_main.c
new file mode 100644
index 0000000..fc8f164
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy_main.c
@@ -0,0 +1,188 @@
+/* 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 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 <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/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/vty.h>
+#include <openbsc/gb_proxy.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_version = "Osmocom NSIP Proxy " PACKAGE_VERSION;
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Harald Welte and On-Waves\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\n"
+	"Dieter Spaar, Andreas Eversberg, Holger Freyther\n\n"
+	"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+	"This is free software: you are free to change and redistribute it.\n"
+	"There is NO WARRANTY, to the extent permitted by law.\n";
+
+static char *config_file = "osmo_gbproxy.cfg";
+struct gbproxy_config gbcfg;
+
+/* 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;
+	}
+}
+
+extern void *tall_msgb_ctx;
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct log_target *stderr_target;
+	struct sockaddr_in sin;
+	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);
+
+	rate_ctr_init(tall_bsc_ctx);
+	telnet_init(&dummy_network, 4246);
+
+	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);
+	}
+
+	nsip_listen(bssgp_nsi, gbcfg.nsip_listen_port);
+
+	/* 'establish' the outgoing connection to the SGSN */
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(gbcfg.nsip_sgsn_port);
+	sin.sin_addr.s_addr = htonl(gbcfg.nsip_sgsn_ip);
+	nsip_connect(bssgp_nsi, &sin, gbcfg.nsip_sgsn_nsei,
+			gbcfg.nsip_sgsn_nsvci);
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
+struct gsm_network;
+int bsc_vty_init(struct gsm_network *dummy)
+{
+	cmd_init(1);
+	vty_init();
+
+	openbsc_vty_add_cmds();
+        gbproxy_vty_init();
+	return 0;
+}
+
diff --git a/openbsc/src/gprs/gb_proxy_vty.c b/openbsc/src/gprs/gb_proxy_vty.c
new file mode 100644
index 0000000..e1cf97e
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy_vty.c
@@ -0,0 +1,183 @@
+/*
+ * (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 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 <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 <vty/command.h>
+#include <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)
+{
+	struct in_addr ia;
+
+	vty_out(vty, "gbproxy%s", VTY_NEWLINE);
+
+	if (g_cfg->nsip_listen_ip) {
+		ia.s_addr = htonl(g_cfg->nsip_listen_ip);
+		vty_out(vty, " nsip bss local ip %s%s", inet_ntoa(ia),
+			VTY_NEWLINE);
+	}
+	vty_out(vty, " nsip bss local port %u%s", g_cfg->nsip_listen_port,
+		VTY_NEWLINE);
+	ia.s_addr = htonl(g_cfg->nsip_sgsn_ip);
+	vty_out(vty, " nsip sgsn remote ip %s%s", inet_ntoa(ia),
+		VTY_NEWLINE);
+	vty_out(vty, " nsip sgsn remote port %u%s", g_cfg->nsip_sgsn_port,
+		VTY_NEWLINE);
+	vty_out(vty, " nsip sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei,
+		VTY_NEWLINE);
+	vty_out(vty, " nsip sgsn nsvci %u%s", g_cfg->nsip_sgsn_nsvci,
+		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_bss_local_ip,
+      cfg_nsip_bss_local_ip_cmd,
+      "nsip bss local ip A.B.C.D",
+      "Set the IP address on which we listen for BSS connects")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	g_cfg->nsip_listen_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_bss_local_port,
+      cfg_nsip_bss_local_port_cmd,
+      "nsip bss local port <0-65534>",
+      "Set the UDP port on which we listen for BSS connects")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_listen_port = port;
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_nsip_sgsn_ip,
+      cfg_nsip_sgsn_ip_cmd,
+      "nsip sgsn remote ip A.B.C.D",
+      "Set the IP of the SGSN to which the proxy shall connect")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	g_cfg->nsip_sgsn_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_port,
+      cfg_nsip_sgsn_port_cmd,
+      "nsip sgsn remote port <0-65534>",
+      "Set the UDP port of the SGSN to which the proxy shall connect")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_sgsn_port = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_nsei,
+      cfg_nsip_sgsn_nsei_cmd,
+      "nsip 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;
+}
+
+DEFUN(cfg_nsip_sgsn_nsvci,
+      cfg_nsip_sgsn_nsvci_cmd,
+      "nsip sgsn nsvci <0-65534>",
+      "Set the NSVCI to be used in the connection with the SGSN")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_sgsn_nsvci = 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, &cfg_nsip_bss_local_ip_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_bss_local_port_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_ip_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_port_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsvci_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);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	return 0;
+}
+
diff --git a/openbsc/src/gprs/gprs_bssgp.c b/openbsc/src/gprs/gprs_bssgp.c
new file mode 100644
index 0000000..9fdfd32
--- /dev/null
+++ b/openbsc/src/gprs/gprs_bssgp.c
@@ -0,0 +1,448 @@
+/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */
+
+/* (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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_ns.h>
+
+void *bssgp_tall_ctx = NULL;
+
+#define BVC_F_BLOCKED	0x0001
+
+/* The per-BTS context that we keep on the SGSN side of the BSSGP link */
+struct bssgp_bts_ctx {
+	struct llist_head list;
+
+	/* parsed RA ID and Cell ID of the remote BTS */
+	struct gprs_ra_id ra_id;
+	uint16_t cell_id;
+
+	/* NSEI and BVCI of underlying Gb link.  Together they
+	 * uniquely identify a link to a BTS (5.4.4) */
+	uint16_t bvci;
+	uint16_t nsei;
+
+	uint32_t bvc_state;
+
+	/* we might want to add this as a shortcut later, avoiding the NSVC
+	 * lookup for every packet, similar to a routing cache */
+	//struct gprs_nsvc *nsvc;
+};
+static LLIST_HEAD(bts_ctxts);
+
+/* Find a BTS Context based on parsed RA ID and Cell ID */
+struct bssgp_bts_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid)
+{
+	struct bssgp_bts_ctx *bctx;
+
+	llist_for_each_entry(bctx, &bts_ctxts, list) {
+		if (!memcmp(&bctx->ra_id, raid, sizeof(bctx->ra_id)) &&
+		    bctx->cell_id == cid)
+			return bctx;
+	}
+	return NULL;
+}
+
+/* Find a BTS context based on BVCI+NSEI tuple */
+struct bssgp_bts_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei)
+{
+	struct bssgp_bts_ctx *bctx;
+
+	llist_for_each_entry(bctx, &bts_ctxts, list) {
+		if (bctx->nsei == nsei && bctx->bvci == bvci)
+			return bctx;
+	}
+	return NULL;
+}
+
+struct bssgp_bts_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
+{
+	struct bssgp_bts_ctx *ctx;
+
+	ctx = talloc_zero(bssgp_tall_ctx, struct bssgp_bts_ctx);
+	if (!ctx)
+		return NULL;
+	ctx->bvci = bvci;
+	ctx->nsei = nsei;
+	llist_add(&ctx->list, &bts_ctxts);
+
+	return ctx;
+}
+
+/* Chapter 10.4.5: Flow Control BVC ACK */
+static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
+{
+	/* 6 octets RAC */
+	gsm48_parse_ra(raid, buf);
+	/* 2 octets CID */
+	return ntohs(*(uint16_t *) (buf+6));
+}
+
+/* Chapter 8.4 BVC-Reset Procedure */
+static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,	
+			      uint16_t ns_bvci)
+{
+	struct bssgp_bts_ctx *bctx;
+	uint16_t nsei = msgb_nsei(msg);
+	uint16_t bvci;
+	int rc;
+
+	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+	DEBUGPC(DBSSGP, "BVCI=%u, cause=%s\n", bvci,
+		bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
+
+	/* look-up or create the BTS context for this BVC */
+	bctx = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bctx)
+		bctx = btsctx_alloc(bvci, nsei);
+
+	/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
+	 * informs us about its RAC + Cell ID, so we can create a mapping */
+	if (bvci != 0 && bvci != 1) {
+		if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) {
+			LOGP(DBSSGP, LOGL_ERROR, "BSSGP RESET BVCI=%u "
+				"missing mandatory IE\n", bvci);
+			return -EINVAL;
+		}
+		/* actually extract RAC / CID */
+		bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
+						TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+		LOGP(DBSSGP, LOGL_NOTICE, "Cell %u-%u-%u-%u CI %u on BVCI %u\n",
+			bctx->ra_id.mcc, bctx->ra_id.mnc, bctx->ra_id.lac,
+			bctx->ra_id.rac, bctx->cell_id, bvci);
+	}
+
+	/* Acknowledge the RESET to the BTS */
+	rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+				  nsei, bvci, ns_bvci);
+	return 0;
+}
+
+/* Uplink unit-data */
+static int bssgp_rx_ul_ud(struct msgb *msg)
+{
+	struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+	int data_len = msgb_bssgp_len(msg) - sizeof(*budh);
+	struct tlv_parsed tp;
+	int rc;
+
+	DEBUGP(DBSSGP, "BSSGP UL-UD\n");
+
+	/* extract TLLI and parse TLV IEs */
+	msgb_tlli(msg) = ntohl(budh->tlli);
+	rc = bssgp_tlv_parse(&tp, budh->data, data_len);
+
+	/* Cell ID and LLC_PDU are the only mandatory IE */
+	if (!TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_LLC_PDU))
+		return -EIO;
+
+	/* FIXME: lookup bssgp_bts_ctx based on BVCI + NSEI */
+
+	/* store pointer to LLC header and CELL ID in msgb->cb */
+	msgb_llch(msg) = TLVP_VAL(&tp, BSSGP_IE_LLC_PDU);
+	msgb_bcid(msg) = TLVP_VAL(&tp, BSSGP_IE_CELL_ID);
+
+	return gprs_llc_rcvmsg(msg, &tp);
+}
+
+static int bssgp_rx_suspend(struct msgb *msg)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct tlv_parsed tp;
+	int rc;
+
+	DEBUGP(DBSSGP, "BSSGP SUSPEND\n");
+
+	rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+	if (rc < 0)
+		return rc;
+
+	if (!TLVP_PRESENT(&tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+		return -EIO;
+
+	/* FIXME: pass the SUSPEND request to GMM */
+	/* SEND SUSPEND_ACK or SUSPEND_NACK */
+}
+
+static int bssgp_rx_resume(struct msgb *msg)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct tlv_parsed tp;
+	int rc;
+
+	DEBUGP(DBSSGP, "BSSGP RESUME\n");
+
+	rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+	if (rc < 0)
+		return rc;
+
+	if (!TLVP_PRESENT(&tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_SUSPEND_REF_NR))
+		return -EIO;
+
+	/* FIXME: pass the RESUME request to GMM */
+	/* SEND RESUME_ACK or RESUME_NACK */
+}
+
+static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp)
+{
+
+	DEBUGP(DBSSGP, "BSSGP FC BVC\n");
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS))
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+
+	/* FIXME: actually implement flow control */
+
+	/* Send FLOW_CONTROL_BVC_ACK */
+	return bssgp_tx_fc_bvc_ack(msgb_nsei(msg), *TLVP_VAL(tp, BSSGP_IE_TAG),
+				   msgb_bvci(msg));
+}
+
+/* We expect msgb_bssgph() to point to the BSSGP header */
+int gprs_bssgp_rcvmsg(struct msgb *msg)
+{
+	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);
+	uint16_t bvci;	/* PTP BVCI */
+	uint16_t ns_bvci = msgb_bvci(msg);
+	int rc = 0;
+
+	/* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */
+
+	/* UNITDATA BSSGP headers have TLLI in front */
+	if (pdu_type != BSSGP_PDUT_UL_UNITDATA &&
+	    pdu_type != BSSGP_PDUT_DL_UNITDATA)
+		rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_UL_UNITDATA:
+		/* some LLC data from the MS */
+		rc = bssgp_rx_ul_ud(msg);
+		break;
+	case BSSGP_PDUT_RA_CAPABILITY:
+		/* BSS requests RA capability or IMSI */
+		DEBUGP(DBSSGP, "BSSGP RA CAPABILITY UPDATE\n");
+		/* FIXME: send RA_CAPA_UPDATE_ACK */
+		break;
+	case BSSGP_PDUT_RADIO_STATUS:
+		DEBUGP(DBSSGP, "BSSGP RADIO STATUS\n");
+		/* BSS informs us of some exception */
+		/* FIXME: notify GMM */
+		break;
+	case BSSGP_PDUT_SUSPEND:
+		/* MS wants to suspend */
+		rc = bssgp_rx_suspend(msg);
+		break;
+	case BSSGP_PDUT_RESUME:
+		/* MS wants to resume */
+		rc = bssgp_rx_resume(msg);
+		break;
+	case BSSGP_PDUT_FLUSH_LL:
+		/* BSS informs MS has moved to one cell to other cell */
+		DEBUGP(DBSSGP, "BSSGP FLUSH LL\n");
+		/* FIXME: notify GMM */
+		/* Send FLUSH_LL_ACK */
+		break;
+	case BSSGP_PDUT_LLC_DISCARD:
+		/* BSS informs that some LLC PDU's have been discarded */
+		DEBUGP(DBSSGP, "BSSGP LLC DISCARDED\n");
+		/* FIXME: notify GMM */
+		break;
+	case BSSGP_PDUT_FLOW_CONTROL_BVC:
+		/* BSS informs us of available bandwidth in Gb interface */
+		rc = bssgp_rx_fc_bvc(msg, &tp);
+		break;
+	case BSSGP_PDUT_FLOW_CONTROL_MS:
+		/* BSS informs us of available bandwidth to one MS */
+		DEBUGP(DBSSGP, "BSSGP FC MS\n");
+		/* FIXME: actually implement flow control */
+		/* FIXME: Send FLOW_CONTROL_MS_ACK */
+		break;
+	case BSSGP_PDUT_BVC_BLOCK:
+		/* BSS tells us that BVC shall be blocked */
+		DEBUGP(DBSSGP, "BSSGP BVC BLOCK ");
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI) ||
+		    !TLVP_PRESENT(&tp, BSSGP_IE_CAUSE))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		DEBUGPC(DBSSGP, "BVCI=%u, cause=%s\n", bvci,
+			bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE)));
+		/* We always acknowledge the BLOCKing */
+		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK,
+					  msgb_nsei(msg), bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_BVC_UNBLOCK:
+		/* BSS tells us that BVC shall be unblocked */
+		DEBUGP(DBSSGP, "BSSGP BVC UNBLOCK ");
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		DEBUGPC(DBSSGP, "BVCI=%u\n", bvci);
+		/* We always acknowledge the unBLOCKing */
+		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK,
+					  msgb_nsei(msg), bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_BVC_RESET:
+		/* BSS tells us that BVC init is required */
+		DEBUGP(DBSSGP, "BSSGP BVC RESET ");
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI) ||
+		    !TLVP_PRESENT(&tp, BSSGP_IE_CAUSE))
+			goto err_mand_ie;
+		rc = bssgp_rx_bvc_reset(msg, &tp, ns_bvci);
+		break;
+	case BSSGP_PDUT_STATUS:
+		/* Some exception has occurred */
+		/* FIXME: notify GMM */
+	case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
+	case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
+	case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
+	case BSSGP_PDUT_MODIFY_BSS_PFC:
+	case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
+		DEBUGP(DBSSGP, "BSSGP PDU type 0x%02x not [yet] implemented\n",
+			pdu_type);
+		break;
+	/* those only exist in the SGSN -> BSS direction */
+	case BSSGP_PDUT_DL_UNITDATA:
+	case BSSGP_PDUT_PAGING_PS:
+	case BSSGP_PDUT_PAGING_CS:
+	case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
+	case BSSGP_PDUT_SUSPEND_ACK:
+	case BSSGP_PDUT_SUSPEND_NACK:
+	case BSSGP_PDUT_RESUME_ACK:
+	case BSSGP_PDUT_RESUME_NACK:
+	case BSSGP_PDUT_FLUSH_LL_ACK:
+	case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
+	case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
+	case BSSGP_PDUT_BVC_BLOCK_ACK:
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+	case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+		DEBUGP(DBSSGP, "BSSGP PDU type 0x%02x only exists in DL\n",
+			pdu_type);
+		rc = -EINVAL;
+		break;
+	default:
+		DEBUGP(DBSSGP, "BSSGP PDU type 0x%02x unknown\n", pdu_type);
+		break;
+	}
+
+	return rc;
+err_mand_ie:
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* Entry function from upper level (LLC), asking us to transmit a BSSGP PDU
+ * to a remote MS (identified by TLLI) at a BTS identified by its BVCI and NSEI */
+int gprs_bssgp_tx_dl_ud(struct msgb *msg)
+{
+	struct bssgp_bts_ctx *bctx;
+	struct bssgp_ud_hdr *budh;
+	uint8_t llc_pdu_tlv_hdr_len = 2;
+	uint8_t *llc_pdu_tlv, *qos_profile;
+	uint16_t pdu_lifetime = 1000; /* centi-seconds */
+	uint8_t qos_profile_default[3] = { 0x00, 0x00, 0x21 };
+	uint16_t msg_len = msg->len;
+	uint16_t bvci = msgb_bvci(msg);
+	uint16_t nsei = msgb_nsei(msg);
+
+	/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
+	if (bvci < 2) {
+		LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
+			bvci);
+		return -EINVAL;
+	}
+
+	bctx = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bctx)
+		bctx = btsctx_alloc(bvci, nsei);
+
+	if (msg->len > TVLV_MAX_ONEBYTE)
+		llc_pdu_tlv_hdr_len += 1;
+
+	/* prepend the tag and length of the LLC-PDU TLV */
+	llc_pdu_tlv = msgb_push(msg, llc_pdu_tlv_hdr_len);
+	llc_pdu_tlv[0] = BSSGP_IE_LLC_PDU;
+	if (llc_pdu_tlv_hdr_len > 2) {
+		llc_pdu_tlv[1] = msg_len >> 8;
+		llc_pdu_tlv[2] = msg_len & 0xff;
+	} else {
+		llc_pdu_tlv[1] = msg_len & 0x3f;
+		llc_pdu_tlv[1] |= 0x80;
+	}
+
+	/* FIXME: optional elements */
+
+	/* prepend the pdu lifetime */
+	pdu_lifetime = htons(pdu_lifetime);
+	msgb_tvlv_push(msg, BSSGP_IE_PDU_LIFETIME, 2, (uint8_t *)&pdu_lifetime);
+
+	/* prepend the QoS profile, TLLI and pdu type */
+	budh = (struct bssgp_ud_hdr *) msgb_push(msg, sizeof(*budh));
+	memcpy(budh->qos_profile, qos_profile_default, sizeof(qos_profile_default));
+	budh->tlli = htonl(msgb_tlli(msg));
+	budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
+
+	/* Identifiers down: BVCI, NSEI (in msgb->cb) */
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
diff --git a/openbsc/src/gprs/gprs_bssgp_util.c b/openbsc/src/gprs/gprs_bssgp_util.c
new file mode 100644
index 0000000..d9b5175
--- /dev/null
+++ b/openbsc/src/gprs/gprs_bssgp_util.c
@@ -0,0 +1,119 @@
+/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */
+
+/* (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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_ns.h>
+
+struct gprs_ns_inst *bssgp_nsi;
+
+/* BSSGP Protocol specific, not implementation specific */
+/* FIXME: This needs to go into libosmocore after finished */
+
+/* Chapter 11.3.9 / Table 11.10: Cause coding */
+static const struct value_string bssgp_cause_strings[] = {
+	{ BSSGP_CAUSE_PROC_OVERLOAD,	"Processor overload" },
+	{ BSSGP_CAUSE_EQUIP_FAIL,	"Equipment Failure" },
+	{ BSSGP_CAUSE_TRASIT_NET_FAIL,	"Transit netowkr service failure" },
+	{ BSSGP_CAUSE_CAPA_GREATER_0KPBS,"Transmission capacity modified" },
+	{ BSSGP_CAUSE_UNKNOWN_MS,	"Unknown MS" },
+	{ BSSGP_CAUSE_UNKNOWN_BVCI,	"Unknown BVCI" },
+	{ BSSGP_CAUSE_CELL_TRAF_CONG,	"Cell traffic congestion" },
+	{ BSSGP_CAUSE_SGSN_CONG,	"SGSN congestion" },
+	{ BSSGP_CAUSE_OML_INTERV,	"O&M intervention" },
+	{ BSSGP_CAUSE_BVCI_BLOCKED,	"BVCI blocked" },
+	{ BSSGP_CAUSE_PFC_CREATE_FAIL,	"PFC create failure" },
+	{ BSSGP_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
+	{ BSSGP_CAUSE_INV_MAND_INF,	"Invalid mandatory information" },
+	{ BSSGP_CAUSE_MISSING_MAND_IE,	"Missing mandatory IE" },
+	{ BSSGP_CAUSE_MISSING_COND_IE,	"Missing conditional IE" },
+	{ BSSGP_CAUSE_UNEXP_COND_IE,	"Unexpected conditional IE" },
+	{ BSSGP_CAUSE_COND_IE_ERR,	"Conditional IE error" },
+	{ BSSGP_CAUSE_PDU_INCOMP_STATE,	"PDU incompatible with protocol state" },
+	{ BSSGP_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error - unspecified" },
+	{ BSSGP_CAUSE_PDU_INCOMP_FEAT, 	"PDU not compatible with feature set" },
+	{ 0, NULL },
+};
+
+const char *bssgp_cause_str(enum gprs_bssgp_cause cause)
+{
+	return get_value_string(bssgp_cause_strings, cause);
+}
+
+
+struct msgb *bssgp_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(4096, 128, "BSSGP");
+}
+
+/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
+int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
+			 uint16_t bvci, uint16_t ns_bvci)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint16_t _bvci;
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	bgph->pdu_type = pdu_type;
+	_bvci = htons(bvci);
+	msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* Chapter 10.4.14: Status */
+int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+
+	DEBUGPC(DBSSGP, "BSSGP: TX STATUS, cause=%s\n", bssgp_cause_str(cause));
+	msgb_nsei(msg) = msgb_nsei(orig_msg);
+	msgb_bvci(msg) = 0;
+
+	bgph->pdu_type = BSSGP_PDUT_STATUS;
+	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
+	if (bvci) {
+		uint16_t _bvci = htons(*bvci);
+		msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+	}
+	if (orig_msg)
+		msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR,
+			      msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg));
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
diff --git a/openbsc/src/gprs/gprs_llc.c b/openbsc/src/gprs/gprs_llc.c
new file mode 100644
index 0000000..9c75a3d
--- /dev/null
+++ b/openbsc/src/gprs/gprs_llc.c
@@ -0,0 +1,549 @@
+/* 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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <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/crc24.h>
+
+/* Section 4.5.2 Logical Link States + Annex C.2 */
+enum gprs_llc_ll_state {
+	GPRS_LLS_UNASSIGNED	= 1,	/* No TLLI yet */
+	GPRS_LLS_ASSIGNED_ADM	= 2,	/* TLLI assigned */
+	GPRS_LLS_LOCAL_EST	= 3,	/* Local Establishment */
+	GPRS_LLS_REMOTE_EST	= 4,	/* Remote Establishment */
+	GPRS_LLS_ABM		= 5,
+	GPRS_LLS_LOCAL_REL	= 6,	/* Local Release */
+	GPRS_LLS_TIMER_REC 	= 7,	/* Timer Recovery */
+};
+
+/* Section 4.7.1: Logical Link Entity: One per DLCI (TLLI + SAPI) */
+struct gprs_llc_lle {
+	struct llist_head list;
+	struct timer_list t200;
+	struct timer_list t201;	/* wait for acknowledgement */
+
+	enum gprs_llc_ll_state state;
+
+	uint32_t tlli;
+	uint32_t sapi;
+
+	uint8_t v_sent;
+	uint8_t v_ack;
+	uint8_t v_recv;
+
+	unsigned int n200;
+	unsigned int retrans_ctr;
+
+	/* over which BSSGP BTS ctx do we need to transmit */
+	uint16_t bvci;
+	uint16_t nsei;
+};
+
+static LLIST_HEAD(gprs_llc_lles);
+void *llc_tall_ctx;
+
+/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */
+static struct gprs_llc_lle *lle_by_tlli_sapi(uint32_t tlli, uint32_t sapi)
+{
+	struct gprs_llc_lle *lle;
+
+	llist_for_each_entry(lle, &gprs_llc_lles, list) {
+		if (lle->tlli == tlli && lle->sapi == sapi)
+			return lle;
+	}
+	return NULL;
+}
+
+static struct gprs_llc_lle *lle_alloc(uint32_t tlli, uint32_t sapi)
+{
+	struct gprs_llc_lle *lle;
+
+	lle = talloc_zero(llc_tall_ctx, struct gprs_llc_lle);
+	if (!lle)
+		return NULL;
+
+	lle->tlli = tlli;
+	lle->sapi = sapi;
+	lle->state = GPRS_LLS_UNASSIGNED;
+	llist_add(&lle->list, &gprs_llc_lles);
+
+	return lle;
+}
+
+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,
+};
+
+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;
+	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->n200) {
+		/* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */
+		lle->state = GPRS_LLS_ASSIGNED_ADM;
+	}
+
+	switch (lle->state) {
+	case GPRS_LLS_LOCAL_EST:
+		/* retransmit SABM */
+		/* re-start T200 */
+		lle->retrans_ctr++;
+		break;
+	case GPRS_LLS_LOCAL_REL:
+		/* retransmit DISC */
+		/* re-start T200 */
+		lle->retrans_ctr++;
+		break;
+	}
+
+}
+
+static void t201_expired(void *data)
+{
+	struct gprs_llc_lle *lle = data;
+
+	if (lle->retrans_ctr < lle->n200) {
+		/* transmit apropriate supervisory frame (8.6.4.1) */
+		/* 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) */
+
+	return gprs_bssgp_tx_dl_ud(msg);
+}
+
+/* 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->tlli;
+	msgb_bvci(msg) = lle->bvci;
+	msgb_nsei(msg) = lle->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)
+{
+	struct gprs_llc_lle *lle;
+	uint8_t *fcs, *llch;
+	uint8_t addr, ctrl[2];
+	uint32_t fcs_calc;
+	uint16_t nu = 0;
+
+	/* 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)
+		lle = lle_alloc(msgb_tlli(msg), sapi);
+	/* Update LLE's (BVCI, NSEI) tuple */
+	lle->bvci = msgb_bvci(msg);
+	lle->nsei = msgb_nsei(msg);
+
+	/* 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;
+
+	/* Identifiers passed down: (BVCI, NSEI) */
+
+	return gprs_bssgp_tx_dl_ud(msg);
+}
+
+static int gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph)
+{
+	DEBUGP(DGPRS, "LLC SAPI=%u %c %c FCS=0x%06x(%s) ",
+		gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ',
+		gph->fcs, gph->fcs_calc == gph->fcs ? "correct" : "WRONG");
+
+	if (gph->cmd)
+		DEBUGPC(DGPRS, "CMD=%u ", gph->cmd);
+
+	if (gph->data)
+		DEBUGPC(DGPRS, "DATA ");
+
+	DEBUGPC(DGPRS, "\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_LLS_ASSIGNED_ADM) {
+			/* start re-establishment (8.7.1) */
+		}
+		lle->state = GPRS_LLS_REMOTE_EST;
+		/* FIXME: Send UA */
+		lle->state = GPRS_LLS_ABM;
+		/* FIXME: process data */
+		break;
+	case GPRS_LLC_DISC: /* Section 6.4.1.2 */
+		/* FIXME: Send UA */
+		/* terminate ABM */
+		lle->state = GPRS_LLS_ASSIGNED_ADM;
+		break;
+	case GPRS_LLC_UA: /* Section 6.4.1.3 */
+		if (lle->state == GPRS_LLS_LOCAL_EST)
+			lle->state = GPRS_LLS_ABM;
+		break;
+	case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */
+		if (lle->state == GPRS_LLS_LOCAL_EST)
+			lle->state = GPRS_LLS_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;
+	}
+
+	return 0;
+}
+
+/* parse a GPRS LLC header, also check for invalid frames */
+static int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp,
+			      const uint8_t *llc_hdr, int len)
+{
+	uint8_t *ctrl = llc_hdr+1;
+	int is_sack = 0;
+	unsigned int crc_length;
+	uint32_t fcs_calc;
+
+	if (len <= CRC24_LENGTH)
+		return -EIO;
+
+	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->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 (crc_length > UI_HDR_LEN + N202)
+				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;
+		}
+	}
+
+	/* calculate what FCS we expect */
+	ghp->fcs_calc = gprs_llc_fcs(llc_hdr, crc_length);
+
+	/* FIXME: parse sack frame */
+}
+
+/* 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 */
+
+	rc = gprs_llc_hdr_parse(&llhp, lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU));
+	/* FIXME */
+
+	gprs_llc_hdr_dump(&llhp);
+
+	/* find the LLC Entity for this TLLI+SAPI tuple */
+	lle = lle_by_tlli_sapi(msgb_tlli(msg), llhp.sapi);
+	/* allocate a new LLE if needed */
+	if (!lle)
+		lle = lle_alloc(msgb_tlli(msg), llhp.sapi);
+
+	/* Update LLE's (BVCI, NSEI) tuple */
+	lle->bvci = msgb_bvci(msg);
+	lle->nsei = msgb_nsei(msg);
+
+	rc = gprs_llc_hdr_rx(&llhp, lle);
+	/* FIXME */
+
+	if (llhp.data) {
+		msgb_gmmh(msg) = llhp.data;
+		switch (llhp.sapi) {
+		case GPRS_SAPI_GMM:
+			rc = gsm0408_gprs_rcvmsg(msg);
+			break;
+		case GPRS_SAPI_TOM2:
+		case GPRS_SAPI_TOM8:
+			/* FIXME */
+		case GPRS_SAPI_SNDCP3:
+		case GPRS_SAPI_SNDCP5:
+		case GPRS_SAPI_SNDCP9:
+		case GPRS_SAPI_SNDCP11:
+			/* FIXME */
+		case GPRS_SAPI_SMS:
+			/* FIXME */
+		default:
+			LOGP(DGPRS, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi);
+			rc = -EINVAL;
+			break;
+		}
+	}
+
+	return rc;
+}
diff --git a/openbsc/src/gprs/gprs_ns.c b/openbsc/src/gprs/gprs_ns.c
new file mode 100644
index 0000000..dc12953
--- /dev/null
+++ b/openbsc/src/gprs/gprs_ns.c
@@ -0,0 +1,926 @@
+/* GPRS Networks Service (NS) messages on the Gb interfacebvci = msgb_bvci(msg);
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */
+
+/* (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 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.
+ *
+ */
+
+/* Some introduction into NS:  NS is used typically on top of frame relay,
+ * but in the ip.access world it is encapsulated in UDP packets.  It serves
+ * as an intermediate shim betwen BSSGP and the underlying medium.  It doesn't
+ * do much, apart from providing congestion notification and status indication.
+ *
+ * Terms:
+ * 	NS		Network Service
+ *	NSVC		NS Virtual Connection
+ *	NSEI		NS Entity Identifier
+ *	NSVL		NS Virtual Link
+ *	NSVLI		NS Virtual Link Identifier
+ *	BVC		BSSGP Virtual Connection
+ *	BVCI		BSSGP Virtual Connection Identifier
+ *	NSVCG		NS Virtual Connection Goup
+ *	Blocked		NS-VC cannot be used for user traffic
+ *	Alive		Ability of a NS-VC to provide communication
+ *
+ *  There can be multiple BSSGP virtual connections over one (group of) NSVC's.  BSSGP will
+ * therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
+ * NS then has to firgure out which NSVC's are responsible for this BVCI.
+ * Those mappings are administratively configured.
+ */
+
+/* This implementation has the following limitations:
+ *  o Only one NS-VC for each NSE: No load-sharing function
+ *  o NSVCI 65535 and 65534 are reserved for internal use
+ *  o Only UDP is supported as of now, no frame relay support
+ *  o The IP Sub-Network-Service (SNS) as specified in 48.016 is not implemented
+ *  o There are no BLOCK and UNBLOCK timers (yet?)
+ */
+
+#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_ns.h>
+#include <openbsc/gprs_bssgp.h>
+
+#define NS_ALLOC_SIZE	1024
+
+static const struct tlv_definition ns_att_tlvdef = {
+	.def = {
+		[NS_IE_CAUSE]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_VCI]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_PDU]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_BVCI]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_NSEI]	= { TLV_TYPE_TvLV, 0 },
+	},
+};
+
+enum ns_ctr {
+	NS_CTR_PKTS_IN,
+	NS_CTR_PKTS_OUT,
+	NS_CTR_BYTES_IN,
+	NS_CTR_BYTES_OUT,
+	NS_CTR_BLOCKED,
+	NS_CTR_DEAD,
+};
+
+static const struct rate_ctr_desc nsvc_ctr_description[] = {
+	{ "packets.in", "Packets at NS Level ( In)" },
+	{ "packets.out","Packets at NS Level (Out)" },
+	{ "bytes.in",	"Bytes at NS Level   ( In)" },
+	{ "bytes.out",	"Bytes at NS Level   (Out)" },
+	{ "blocked",	"NS-VC Block count        " },
+	{ "dead",	"NS-VC gone dead count    " },
+};
+
+static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
+	.group_name_prefix = "ns.nsvc",
+	.group_description = "NSVC Peer Statistics",
+	.num_ctr = ARRAY_SIZE(nsvc_ctr_description),
+	.ctr_desc = nsvc_ctr_description,
+};
+
+/* Lookup struct gprs_nsvc based on NSVCI */
+static struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi,
+					uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->nsvci == nsvci)
+			return nsvc;
+	}
+	return NULL;
+}
+
+/* Lookup struct gprs_nsvc based on NSVCI */
+struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->nsei == nsei)
+			return nsvc;
+	}
+	return NULL;
+}
+
+/* Lookup struct gprs_nsvc based on remote peer socket addr */
+static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi,
+					  struct sockaddr_in *sin)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->ip.bts_addr.sin_addr.s_addr ==
+					sin->sin_addr.s_addr &&
+		    nsvc->ip.bts_addr.sin_port == sin->sin_port)
+			return nsvc;
+	}
+	return NULL;
+}
+
+static void gprs_ns_timer_cb(void *data);
+
+struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	nsvc = talloc_zero(nsi, struct gprs_nsvc);
+	nsvc->nsvci = nsvci;
+	/* before RESET procedure: BLOCKED and DEAD */
+	nsvc->state = NSE_S_BLOCKED;
+	nsvc->nsi = nsi;
+	nsvc->timer.cb = gprs_ns_timer_cb;
+	nsvc->timer.data = nsvc;
+	nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci);
+
+	llist_add(&nsvc->list, &nsi->gprs_nsvcs);
+
+	return nsvc;
+}
+
+void nsvc_delete(struct gprs_nsvc *nsvc)
+{
+	if (bsc_timer_pending(&nsvc->timer))
+		bsc_del_timer(&nsvc->timer);
+	llist_del(&nsvc->list);
+	talloc_free(nsvc);
+}
+
+static void ns_dispatch_signal(struct gprs_nsvc *nsvc, unsigned int signal,
+			       uint8_t cause)
+{
+	struct ns_signal_data nssd;
+
+	nssd.nsvc = nsvc;
+	nssd.cause = cause;
+
+	dispatch_signal(SS_NS, signal, &nssd);
+}
+
+/* Section 10.3.2, Table 13 */
+static const struct value_string ns_cause_str[] = {
+	{ NS_CAUSE_TRANSIT_FAIL,	"Transit network failure" },
+	{ NS_CAUSE_OM_INTERVENTION, 	"O&M intervention" },
+	{ NS_CAUSE_EQUIP_FAIL,		"Equipment failure" },
+	{ NS_CAUSE_NSVC_BLOCKED,	"NS-VC blocked" },
+	{ NS_CAUSE_NSVC_UNKNOWN,	"NS-VC unknown" },
+	{ NS_CAUSE_BVCI_UNKNOWN,	"BVCI unknown" },
+	{ NS_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
+	{ NS_CAUSE_PDU_INCOMP_PSTATE,	"PDU not compatible with protocol state" },
+	{ NS_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error, unspecified" },
+	{ NS_CAUSE_INVAL_ESSENT_IE,	"Invalid essential IE" },
+	{ NS_CAUSE_MISSING_ESSENT_IE,	"Missing essential IE" },
+	{ 0, NULL }
+};
+
+const char *gprs_ns_cause_str(enum ns_cause cause)
+{
+	return get_value_string(ns_cause_str, cause);
+}
+
+static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
+
+static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int ret;
+
+	/* Increment number of Uplink bytes */
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]);
+	rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg));
+
+	switch (nsvc->nsi->ll) {
+	case GPRS_NS_LL_UDP:
+		ret = nsip_sendmsg(nsvc, msg);
+		break;
+	default:
+		LOGP(DNS, LOGL_ERROR, "unsupported NS linklayer %u\n", nsvc->nsi->ll);
+		msgb_free(msg);
+		ret = -EIO;
+		break;
+	}
+	return ret;
+}
+
+static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+
+	if (!msg)
+		return -ENOMEM;
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+	nsh->pdu_type = pdu_type;
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+	uint16_t nsei = htons(nsvc->nsei);
+
+	if (!msg)
+		return -ENOMEM;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n",
+		nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause));
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	nsh->pdu_type = NS_PDUT_RESET;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+
+}
+
+int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause,
+		      uint16_t bvci, struct msgb *orig_msg)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+
+	bvci = htons(bvci);
+
+	if (!msg)
+		return -ENOMEM;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n",
+		nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause));
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	nsh->pdu_type = NS_PDUT_STATUS;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+
+	/* Section 9.2.7.1: Static conditions for NS-VCI */
+	if (cause == NS_CAUSE_NSVC_BLOCKED ||
+	    cause == NS_CAUSE_NSVC_UNKNOWN)
+		msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+
+	/* Section 9.2.7.2: Static conditions for NS PDU */
+	switch (cause) {
+	case NS_CAUSE_SEM_INCORR_PDU:
+	case NS_CAUSE_PDU_INCOMP_PSTATE:
+	case NS_CAUSE_PROTO_ERR_UNSPEC:
+	case NS_CAUSE_INVAL_ESSENT_IE:
+	case NS_CAUSE_MISSING_ESSENT_IE:
+		msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg),
+			      orig_msg->l2h);
+		break;
+	default:
+		break;
+	}
+
+	/* Section 9.2.7.3: Static conditions for BVCI */
+	if (cause == NS_CAUSE_BVCI_UNKNOWN)
+		msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+
+	if (!msg)
+		return -ENOMEM;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK (NSVCI=%u, cause=%s)\n",
+		nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause));
+
+	/* be conservative and mark it as blocked even now! */
+	nsvc->state |= NSE_S_BLOCKED;
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	nsh->pdu_type = NS_PDUT_BLOCK;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc)
+{
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK);
+}
+
+int gprs_ns_tx_alive(struct gprs_nsvc *nsvc)
+{
+	LOGP(DNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+}
+
+int gprs_ns_tx_alive_ack(struct gprs_nsvc *nsvc)
+{
+	LOGP(DNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE_ACK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
+}
+
+static const enum ns_timeout timer_mode_tout[_NSVC_TIMER_NR] = {
+	[NSVC_TIMER_TNS_RESET]	= NS_TOUT_TNS_RESET,
+	[NSVC_TIMER_TNS_ALIVE]	= NS_TOUT_TNS_ALIVE,
+	[NSVC_TIMER_TNS_TEST]	= NS_TOUT_TNS_TEST,
+};
+
+static const struct value_string timer_mode_strs[] = {
+	{ NSVC_TIMER_TNS_RESET, "tns-reset" },
+	{ NSVC_TIMER_TNS_ALIVE, "tns-alive" },
+	{ NSVC_TIMER_TNS_TEST, "tns-test" },
+	{ 0, NULL }
+};
+
+static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
+{
+	enum ns_timeout tout = timer_mode_tout[mode];
+	unsigned int seconds = nsvc->nsi->timeout[tout];
+
+	DEBUGP(DNS, "NSEI=%u Starting timer in mode %s (%u seconds)\n",
+		nsvc->nsei, get_value_string(timer_mode_strs, mode),
+		seconds);
+		
+	if (bsc_timer_pending(&nsvc->timer))
+		bsc_del_timer(&nsvc->timer);
+
+	nsvc->timer_mode = mode;
+	bsc_schedule_timer(&nsvc->timer, seconds, 0);
+}
+
+static void gprs_ns_timer_cb(void *data)
+{
+	struct gprs_nsvc *nsvc = data;
+	enum ns_timeout tout = timer_mode_tout[nsvc->timer_mode];
+	unsigned int seconds = nsvc->nsi->timeout[tout];
+
+	DEBUGP(DNS, "NSEI=%u Timer expired in mode %s (%u seconds)\n",
+		nsvc->nsei, get_value_string(timer_mode_strs, nsvc->timer_mode),
+		seconds);
+		
+	switch (nsvc->timer_mode) {
+	case NSVC_TIMER_TNS_ALIVE:
+		/* Tns-alive case: we expired without response ! */
+		nsvc->alive_retries++;
+		if (nsvc->alive_retries >
+			nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+			/* mark as dead and blocked */
+			nsvc->state = NSE_S_BLOCKED;
+			rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+			rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+			LOGP(DNS, LOGL_NOTICE,
+				"NSEI=%u Tns-alive expired more then "
+				"%u times, blocking NS-VC\n", nsvc->nsei,
+				nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]);
+			ns_dispatch_signal(nsvc, S_NS_ALIVE_EXP, 0);
+			ns_dispatch_signal(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
+			return;
+		}
+		/* Tns-test case: send NS-ALIVE PDU */
+		gprs_ns_tx_alive(nsvc);
+		/* start Tns-alive timer */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_TEST:
+		/* Tns-test case: send NS-ALIVE PDU */
+		gprs_ns_tx_alive(nsvc);
+		/* start Tns-alive timer (transition into faster
+		 * alive retransmissions) */
+		nsvc->alive_retries = 0;
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_RESET:
+		/* Chapter 7.3: Re-send the RESET */
+		gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+		/* Re-start Tns-reset timer */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
+		break;
+	case _NSVC_TIMER_NR:
+		break;
+	}
+}
+
+/* Section 9.2.6 */
+static int gprs_ns_tx_reset_ack(struct gprs_nsvc *nsvc)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci, nsei;
+
+	if (!msg)
+		return -ENOMEM;
+
+	nsvci = htons(nsvc->nsvci);
+	nsei = htons(nsvc->nsei);
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+	nsh->pdu_type = NS_PDUT_RESET_ACK;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+/* Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive */
+int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
+{
+	struct gprs_nsvc *nsvc;
+	struct gprs_ns_hdr *nsh;
+	uint16_t bvci = msgb_bvci(msg);
+
+	nsvc = nsvc_by_nsei(nsi, msgb_nsei(msg));
+	if (!nsvc) {
+		LOGP(DNS, LOGL_ERROR, "Unable to resolve NSEI %u "
+			"to NS-VC!\n", msgb_nsei(msg));
+		return -EINVAL;
+	}
+
+	if (!(nsvc->state & NSE_S_ALIVE)) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u is not alive, cannot send\n",
+			nsvc->nsei);
+		return -EBUSY;
+	}
+	if (nsvc->state & NSE_S_BLOCKED) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u is blocked, cannot send\n",
+			nsvc->nsei);
+		return -EBUSY;
+	}
+
+	msg->l2h = msgb_push(msg, sizeof(*nsh) + 3);
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	if (!nsh) {
+		LOGP(DNS, LOGL_ERROR, "Not enough headroom for NS header\n");
+		return -EIO;
+	}
+
+	nsh->pdu_type = NS_PDUT_UNITDATA;
+	/* spare octet in data[0] */
+	nsh->data[1] = bvci >> 8;
+	nsh->data[2] = bvci & 0xff;
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+/* Section 9.2.10: receive side */
+static int gprs_ns_rx_unitdata(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
+	uint16_t bvci;
+
+	if (nsvc->state & NSE_S_BLOCKED)
+		return gprs_ns_tx_status(nsvc, NS_CAUSE_NSVC_BLOCKED,
+					 0, msg);
+
+	/* spare octet in data[0] */
+	bvci = nsh->data[1] << 8 | nsh->data[2];
+	msgb_bssgph(msg) = &nsh->data[3];
+	msgb_bvci(msg) = bvci;
+
+	/* call upper layer (BSSGP) */
+	return nsvc->nsi->cb(GPRS_NS_EVT_UNIT_DATA, nsvc, msg, bvci);
+}
+
+/* Section 9.2.7 */
+static int gprs_ns_rx_status(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t cause;
+	int rc;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u NS STATUS ", nsvc->nsei);
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0);
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE)) {
+		LOGPC(DNS, LOGL_INFO, "missing cause IE\n");
+		return -EINVAL;
+	}
+
+	cause = *TLVP_VAL(&tp, NS_IE_CAUSE);
+	LOGPC(DNS, LOGL_INFO, "cause=%s\n", gprs_ns_cause_str(cause));
+
+	return 0;
+}
+
+/* Section 7.3 */
+static int gprs_ns_rx_reset(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t *cause;
+	uint16_t *nsvci, *nsei;
+	int rc;
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0);
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+	    !TLVP_PRESENT(&tp, NS_IE_VCI) ||
+	    !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+		LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+		gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg);
+		return -EINVAL;
+	}
+
+	cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE);
+	nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
+	nsei = (uint16_t *) TLVP_VAL(&tp, NS_IE_NSEI);
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET (NSVCI=%u, cause=%s)\n",
+		nsvc->nsvci, nsvc->nsei, gprs_ns_cause_str(*cause));
+
+	/* Mark NS-VC as blocked and alive */
+	nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE;
+
+	nsvc->nsei = ntohs(*nsei);
+	nsvc->nsvci = ntohs(*nsvci);
+
+	/* start the test procedure */
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+
+	/* inform interested parties about the fact that this NSVC
+	 * has received RESET */
+	ns_dispatch_signal(nsvc, S_NS_RESET, *cause);
+
+	return gprs_ns_tx_reset_ack(nsvc);
+}
+
+static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t *cause;
+	int rc;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS BLOCK\n", nsvc->nsei);
+
+	nsvc->state |= NSE_S_BLOCKED;
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0);
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+	    !TLVP_PRESENT(&tp, NS_IE_VCI)) {
+		LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+		gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg);
+		return -EINVAL;
+	}
+
+	cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE);
+	//nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
+
+	ns_dispatch_signal(nsvc, S_NS_BLOCK, *cause);
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_BLOCK_ACK);
+}
+
+/* main entry point, here incoming NS frames enter */
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct gprs_nsvc *nsvc;
+	int rc = 0;
+
+	/* look up the NSVC based on source address */
+	nsvc = nsvc_by_rem_addr(nsi, saddr);
+	if (!nsvc) {
+		struct tlv_parsed tp;
+		uint16_t nsei;
+		/* Only the RESET procedure creates a new NSVC */
+		if (nsh->pdu_type != NS_PDUT_RESET) {
+			/* Since we have no NSVC, we have to use a fake */
+			nsvc = nsi->unknown_nsvc;
+			LOGP(DNS, LOGL_INFO, "Rejecting NS PDU type 0x%0x "
+				"from %s:%u for non-existing NS-VC\n",
+				nsh->pdu_type, inet_ntoa(saddr->sin_addr),
+				ntohs(saddr->sin_port));
+			nsvc->nsvci = nsvc->nsei = 0xfffe;
+			nsvc->ip.bts_addr = *saddr;
+			nsvc->state = NSE_S_ALIVE;
+			return gprs_ns_tx_status(nsvc,
+						NS_CAUSE_PDU_INCOMP_PSTATE, 0,
+						msg);
+		}
+		rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+						msgb_l2len(msg), 0, 0);
+		if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+		    !TLVP_PRESENT(&tp, NS_IE_VCI) ||
+		    !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+			LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+			gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0,
+					  msg);
+			return -EINVAL;
+		}
+		nsei = ntohs(*(uint16_t *)TLVP_VAL(&tp, NS_IE_NSEI));
+		/* Check if we already know this NSEI, the remote end might
+		 * simply have changed addresses, or it is a SGSN */
+		nsvc = nsvc_by_nsei(nsi, nsei);
+		if (!nsvc) {
+			LOGP(DNS, LOGL_INFO, "Creating NS-VC for BSS at %s:%u\n",
+				inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port));
+			nsvc = nsvc_create(nsi, 0xffff);
+		}
+		/* Update the remote peer IP address/port */
+		nsvc->ip.bts_addr = *saddr;
+	} else
+		msgb_nsei(msg) = nsvc->nsei;
+
+	/* Increment number of Incoming bytes */
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_IN]);
+	rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg));
+
+	switch (nsh->pdu_type) {
+	case NS_PDUT_ALIVE:
+		/* If we're dead and blocked and suddenly receive a
+		 * NS-ALIVE out of the blue, we might have been re-started
+		 * and should send a NS-RESET to make sure everything recovers
+		 * fine. */
+		if (nsvc->state == NSE_S_BLOCKED)
+			rc = gprs_ns_tx_reset(nsvc, NS_CAUSE_PDU_INCOMP_PSTATE);
+		else
+			rc = gprs_ns_tx_alive_ack(nsvc);
+		break;
+	case NS_PDUT_ALIVE_ACK:
+		/* stop Tns-alive */
+		bsc_del_timer(&nsvc->timer);
+		/* start Tns-test */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
+		if (nsvc->remote_end_is_sgsn) {
+			/* FIXME: this should be one level higher */
+			if (nsvc->state & NSE_S_BLOCKED)
+				rc = gprs_ns_tx_unblock(nsvc);
+		}
+		break;
+	case NS_PDUT_UNITDATA:
+		/* actual user data */
+		rc = gprs_ns_rx_unitdata(nsvc, msg);
+		break;
+	case NS_PDUT_STATUS:
+		rc = gprs_ns_rx_status(nsvc, msg);
+		break;
+	case NS_PDUT_RESET:
+		rc = gprs_ns_rx_reset(nsvc, msg);
+		break;
+	case NS_PDUT_RESET_ACK:
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET ACK\n", nsvc->nsei);
+		/* mark NS-VC as blocked + active */
+		nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+		if (nsvc->remote_end_is_sgsn) {
+			/* stop RESET timer */
+			bsc_del_timer(&nsvc->timer);
+			/* Initiate TEST proc.: Send ALIVE and start timer */
+			rc = gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+			nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		}
+		break;
+	case NS_PDUT_UNBLOCK:
+		/* Section 7.2: unblocking procedure */
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS UNBLOCK\n", nsvc->nsei);
+		nsvc->state &= ~NSE_S_BLOCKED;
+		ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0);
+		rc = gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
+		break;
+	case NS_PDUT_UNBLOCK_ACK:
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS UNBLOCK ACK\n", nsvc->nsei);
+		/* mark NS-VC as unblocked + active */
+		nsvc->state = NSE_S_ALIVE;
+		nsvc->remote_state = NSE_S_ALIVE;
+		ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0);
+		break;
+	case NS_PDUT_BLOCK:
+		rc = gprs_ns_rx_block(nsvc, msg);
+		break;
+	case NS_PDUT_BLOCK_ACK:
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS BLOCK ACK\n", nsvc->nsei);
+		/* mark remote NS-VC as blocked + active */
+		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		break;
+	default:
+		LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx Unknown NS PDU type 0x%02x\n",
+			nsvc->nsei, nsh->pdu_type);
+		rc = -EINVAL;
+		break;
+	}
+	return rc;
+}
+
+struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb)
+{
+	struct gprs_ns_inst *nsi = talloc_zero(tall_bsc_ctx, struct gprs_ns_inst);
+
+	nsi->cb = cb;
+	INIT_LLIST_HEAD(&nsi->gprs_nsvcs);
+	nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
+	nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3;
+	nsi->timeout[NS_TOUT_TNS_RESET] = 3;
+	nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3;
+	nsi->timeout[NS_TOUT_TNS_TEST] = 30;
+	nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
+	nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
+
+	/* Create the dummy NSVC that we use for sending
+	 * messages to non-existant/unknown NS-VC's */
+	nsi->unknown_nsvc = nsvc_create(nsi, 0xfffe);
+	llist_del(&nsi->unknown_nsvc->list);
+
+	return nsi;
+}
+
+void gprs_ns_destroy(struct gprs_ns_inst *nsi)
+{
+	/* FIXME: clear all timers */
+
+	/* recursively free the NSI and all its NSVCs */
+	talloc_free(nsi);
+}
+
+
+/* NS-over-IP code, according to 3GPP TS 48.016 Chapter 6.2
+ * We don't support Size Procedure, Configuration Procedure, ChangeWeight Procedure */
+
+/* Read a single NS-over-IP message */
+static struct msgb *read_nsip_msg(struct bsc_fd *bfd, int *error,
+				  struct sockaddr_in *saddr)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Abis/IP/GPRS-NS");
+	int ret = 0;
+	socklen_t saddr_len = sizeof(*saddr);
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
+			(struct sockaddr *)saddr, &saddr_len);
+	if (ret < 0) {
+		LOGP(DNS, LOGL_ERROR, "recv error %s during NSIP recv\n",
+			strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msg->l2h = msg->data;
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+static int handle_nsip_read(struct bsc_fd *bfd)
+{
+	int error;
+	struct sockaddr_in saddr;
+	struct gprs_ns_inst *nsi = bfd->data;
+	struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+
+	if (!msg)
+		return error;
+
+	error = gprs_ns_rcvmsg(nsi, msg, &saddr);
+
+	msgb_free(msg);
+
+	return error;
+}
+
+static int handle_nsip_write(struct bsc_fd *bfd)
+{
+	/* FIXME: actually send the data here instead of nsip_sendmsg() */
+	return -EIO;
+}
+
+int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int rc;
+	struct gprs_ns_inst *nsi = nsvc->nsi;
+	struct sockaddr_in *daddr = &nsvc->ip.bts_addr;
+
+	rc = sendto(nsi->nsip.fd.fd, msg->data, msg->len, 0,
+		  (struct sockaddr *)daddr, sizeof(*daddr));
+
+	talloc_free(msg);
+
+	return rc;
+}
+
+/* UDP Port 23000 carries the LLC-in-BSSGP-in-NS protocol stack */
+static int nsip_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_nsip_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_nsip_write(bfd);
+
+	return rc;
+}
+
+extern int make_sock(struct bsc_fd *bfd, int proto, uint16_t port,
+		     int (*cb)(struct bsc_fd *fd, unsigned int what));
+
+/* Listen for incoming GPRS packets */
+int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port)
+{
+	int ret;
+
+	ret = make_sock(&nsi->nsip.fd, IPPROTO_UDP, udp_port, nsip_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	nsi->ll = GPRS_NS_LL_UDP;
+	nsi->nsip.fd.data = nsi;
+
+	return ret;
+}
+
+/* Establish a connection (from the BSS) to the SGSN */
+struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_rem_addr(nsi, dest);
+	if (!nsvc)
+		nsvc = nsvc_create(nsi, nsvci);
+	nsvc->ip.bts_addr = *dest;
+	nsvc->nsei = nsei;
+	nsvc->nsvci = nsvci;
+	nsvc->remote_end_is_sgsn = 1;
+
+	/* Initiate a RESET procedure */
+	/* Mark NS-VC locally as blocked and dead */
+	nsvc->state = NSE_S_BLOCKED;
+	/* Send NS-RESET PDU */
+	if (gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION) < 0) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u, error resetting NS-VC\n",
+			nsei);
+	}
+	/* Start Tns-reset */
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
+
+	return nsvc;
+}
+
+
diff --git a/openbsc/src/gprs/gprs_ns_vty.c b/openbsc/src/gprs/gprs_ns_vty.c
new file mode 100644
index 0000000..8f0628a
--- /dev/null
+++ b/openbsc/src/gprs/gprs_ns_vty.c
@@ -0,0 +1,292 @@
+/* VTY interface for our GPRS Networks Service (NS) implementation */
+
+/* (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 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 <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_ns.h>
+#include <openbsc/gprs_bssgp.h>
+
+#include <vty/vty.h>
+#include <vty/command.h>
+
+static struct gprs_ns_inst *vty_nsi = NULL;
+
+/* FIXME: this should go to some common file as it is copied
+ * in vty_interface.c of the BSC */
+static const struct value_string gprs_ns_timer_strs[] = {
+	{ 0, "tns-block" },
+	{ 1, "tns-block-retries" },
+	{ 2, "tns-reset" },
+	{ 3, "tns-reset-retries" },
+	{ 4, "tns-test" },
+	{ 5, "tns-alive" },
+	{ 6, "tns-alive-retries" },
+	{ 0, NULL }
+};
+
+static struct cmd_node ns_node = {
+	NS_NODE,
+	"%s(ns)#",
+	1,
+};
+
+static int config_write_ns(struct vty *vty)
+{
+	struct gprs_nsvc *nsvc;
+	unsigned int i;
+
+	vty_out(vty, "ns%s", VTY_NEWLINE);
+
+	llist_for_each_entry(nsvc, &vty_nsi->gprs_nsvcs, list) {
+		if (!nsvc->persistent)
+			continue;
+		vty_out(vty, " nse %u nsvci %u%s",
+			nsvc->nsei, nsvc->nsvci, VTY_NEWLINE);
+		vty_out(vty, " nse %u remote-role %s%s",
+			nsvc->nsei, nsvc->remote_end_is_sgsn ? "sgsn" : "bss",
+			VTY_NEWLINE);
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP) {
+			vty_out(vty, " nse %u remote-ip %s%s",
+				nsvc->nsei,
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				VTY_NEWLINE);
+			vty_out(vty, " nse %u remote-port %u%s",
+				nsvc->nsei, ntohs(nsvc->ip.bts_addr.sin_port),
+				VTY_NEWLINE);
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++)
+		vty_out(vty, " timer %s %u%s",
+			get_value_string(gprs_ns_timer_strs, i),
+			vty_nsi->timeout[i], VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns, cfg_ns_cmd,
+      "ns",
+      "Configure the GPRS Network Service")
+{
+	vty->node = NS_NODE;
+	return CMD_SUCCESS;
+}
+
+static void dump_ns(struct vty *vty, struct gprs_ns_inst *nsi, int stats)
+{
+	struct gprs_nsvc *nsvc;
+
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc == nsi->unknown_nsvc)
+			continue;
+		vty_out(vty, "NSEI %5u, NS-VC %5u, Remote: %-4s, %5s %9s",
+			nsvc->nsei, nsvc->nsvci,
+			nsvc->remote_end_is_sgsn ? "SGSN" : "BSS",
+			nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
+			nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED");
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
+			vty_out(vty, ", %15s:%u",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port));
+		vty_out(vty, "%s", VTY_NEWLINE);
+		if (stats)
+			vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+	}
+}
+
+DEFUN(show_ns, show_ns_cmd, "show ns",
+	SHOW_STR "Display information about the NS protocol")
+{
+	struct gprs_ns_inst *nsi = vty_nsi;
+	dump_ns(vty, nsi, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_stats, show_ns_stats_cmd, "show ns stats",
+	SHOW_STR
+	"Display information about the NS protocol\n"
+	"Include statistics\n")
+{
+	struct gprs_ns_inst *nsi = vty_nsi;
+	dump_ns(vty, nsi, 1);
+	return CMD_SUCCESS;
+}
+
+#define NSE_CMD_STR "NS Entity\n" "NS Entity ID (NSEI)\n"
+
+DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd,
+	"nse <0-65535> nsvci <0-65534>",
+	NSE_CMD_STR
+	"NS Virtual Connection\n"
+	"NS Virtual Connection ID (NSVCI)\n"
+	)
+{
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t nsvci = atoi(argv[1]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		nsvc = nsvc_create(vty_nsi, nsvci);
+		nsvc->nsei = nsei;
+	}
+	nsvc->nsvci = nsvci;
+	/* All NSVCs that are explicitly configured by VTY are
+	 * marked as persistent so we can write them to the config
+	 * file at some later point */
+	nsvc->persistent = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_remoteip, cfg_nse_remoteip_cmd,
+	"nse <0-65535> remote-ip A.B.C.D",
+	NSE_CMD_STR
+	"Remote IP Address\n"
+	"Remote IP Address\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	inet_aton(argv[1], &nsvc->ip.bts_addr.sin_addr);
+
+	return CMD_SUCCESS;
+
+}
+
+DEFUN(cfg_nse_remoteport, cfg_nse_remoteport_cmd,
+	"nse <0-65535> remote-port <0-65535>",
+	NSE_CMD_STR
+	"Remote UDP Port\n"
+	"Remote UDP Port Number\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t port = atoi(argv[1]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nsvc->ip.bts_addr.sin_port = htons(port);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd,
+	"nse <0-65535> remote-role (sgsn|bss)",
+	NSE_CMD_STR
+	"Remote NSE Role\n"
+	"Remote Peer is SGSN\n"
+	"Remote Peer is BSS\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[1], "sgsn"))
+		nsvc->remote_end_is_sgsn = 1;
+	else
+		nsvc->remote_end_is_sgsn = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_nse, cfg_no_nse_cmd,
+	"no nse <0-65535>",
+	"Delete NS Entity\n"
+	"Delete " NSE_CMD_STR)
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nsvc_delete(nsvc);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
+	"timer " NS_TIMERS " <0-65535>",
+	"Network Service Timer\n"
+	NS_TIMERS_HELP "Timer Value\n")
+{
+	int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+		return CMD_WARNING;
+
+	vty_nsi->timeout[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+int gprs_ns_vty_init(struct gprs_ns_inst *nsi)
+{
+	vty_nsi = nsi;
+
+	install_element_ve(&show_ns_cmd);
+	install_element_ve(&show_ns_stats_cmd);
+
+	install_element(CONFIG_NODE, &cfg_ns_cmd);
+	install_node(&ns_node, config_write_ns);
+	install_default(NS_NODE);
+	install_element(NS_NODE, &cfg_nse_nsvci_cmd);
+	install_element(NS_NODE, &cfg_nse_remoteip_cmd);
+	install_element(NS_NODE, &cfg_nse_remoteport_cmd);
+	install_element(NS_NODE, &cfg_nse_remoterole_cmd);
+	install_element(NS_NODE, &cfg_no_nse_cmd);
+	install_element(NS_NODE, &cfg_ns_timer_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c
new file mode 100644
index 0000000..ba46719
--- /dev/null
+++ b/openbsc/src/gprs/gprs_sgsn.c
@@ -0,0 +1,96 @@
+/* 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 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 <stdint.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.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>
+
+static LLIST_HEAD(sgsn_mm_ctxts);
+
+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);
+}
+
+/* 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;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (tlli == ctx->tlli &&
+		    ra_id_equals(raid, &ctx->ra))
+			return ctx;
+	}
+	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)
+			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 = talloc_zero(NULL, struct sgsn_mm_ctx);
+
+	if (!ctx)
+		return NULL;
+
+	memcpy(&ctx->ra, raid, sizeof(ctx->ra));
+	ctx->tlli = tlli;
+	ctx->mm_state = GMM_DEREGISTERED;
+
+	llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+	return ctx;
+}
diff --git a/openbsc/src/gprs/gprs_sndcp.c b/openbsc/src/gprs/gprs_sndcp.c
new file mode 100644
index 0000000..0d1a390
--- /dev/null
+++ b/openbsc/src/gprs/gprs_sndcp.c
@@ -0,0 +1,70 @@
+/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */
+
+/* (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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <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>
+
+/* 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;
+	/* octet 2 */
+	uint8_t pcomp;
+	uint8_t dcomp;
+};
+
+struct sndcp_udata_hdr {
+	/* octet 3 */
+	uint8_t npdu_high:4;
+	uint8_t seg_nr:4;
+	/* octet 4 */
+	uint8_t npdu_low;
+};
+
+/* Entry point for the LL-UNITDATA.indication */
+int sndcp_unitdata_ind(struct msgb *msg, uint8_t sapi, uint8_t *hdr, uint8_t len)
+{
+	struct sndcp_udata_hdr *suh;
+	uint16_t npdu;
+
+	if (suh->type == 0) {
+		LOGP(DGPRS, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n");
+		return -EINVAL;
+	}
+
+	npdu = (suh->npdu_high << 8) | suh->npdu_low;
+}
+
diff --git a/openbsc/src/gprs/gsm_04_08_gprs.c b/openbsc/src/gprs/gsm_04_08_gprs.c
new file mode 100644
index 0000000..4a42113
--- /dev/null
+++ b/openbsc/src/gprs/gsm_04_08_gprs.c
@@ -0,0 +1,762 @@
+/* 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>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <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 <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>
+
+/* 10.5.5.14 GPRS MM Cause / Table 10.5.147 */
+struct value_string gmm_cause_names[] = {
+	/* FIXME */
+	{ 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 */
+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 }
+};
+
+static const char *att_name(uint8_t type)
+{
+	switch (type) {
+	case GPRS_ATT_T_ATTACH:
+		return "GPRS attach";
+	case GPRS_ATT_T_ATT_WHILE_IMSI:
+		return "GPRS attach while IMSI attached";
+	case GPRS_ATT_T_COMBINED:
+		return "Combined GPRS/IMSI attach";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *upd_name(uint8_t type)
+{
+	switch (type) {
+	case GPRS_UPD_T_RA:
+		return "RA updating";
+	case GPRS_UPD_T_RA_LA:
+		return "combined RA/LA updating";
+	case GPRS_UPD_T_RA_LA_IMSI_ATT:
+		return "combined RA/LA updating + IMSI attach";
+	case GPRS_UPD_T_PERIODIC:
+		return "periodic updating";
+	}
+	return "unknown";
+}
+
+/* Send a message through the underlying layer */
+static int gsm48_gmm_sendmsg(struct msgb *msg, int command)
+{
+	/* caller needs to provide TLLI, BVCI and NSEI */
+	return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command);
+}
+
+/* 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);
+}
+
+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 msgb *old_msg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_attach_ack *aa;
+	struct gprs_ra_id ra_id;
+
+	DEBUGP(DMM, "<- GPRS ATTACH ACCEPT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	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 */
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(old_msg));
+	gsm48_construct_ra(aa->ra_id.digits, &ra_id);
+
+	/* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Chapter 9.4.5: Attach reject */
+static int gsm48_tx_gmm_att_rej(struct msgb *old_msg, uint8_t gmm_cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- GPRS ATTACH REJECT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	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);
+}
+
+/* Transmit Chapter 9.4.12 Identity Request */
+static int gsm48_tx_gmm_id_req(struct msgb *old_msg, 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);
+
+	gmm_copy_id(msg, old_msg);
+
+	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, 0);
+}
+
+/* Check if we can already authorize a subscriber */
+static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+	if (strlen(ctx->imei) && strlen(ctx->imsi)) {
+		ctx->mm_state = GMM_REGISTERED_NORMAL;
+		return gsm48_tx_gmm_att_ack(msg);
+	} 
+	if (!strlen(ctx->imei))
+		return gsm48_tx_gmm_id_req(msg, GSM_MI_TYPE_IMEI);
+
+	if (!strlen(ctx->imsi))
+		return gsm48_tx_gmm_id_req(msg, GSM_MI_TYPE_IMSI);
+
+	return 0;
+}
+
+/* Parse Chapter 9.4.13 Identity Response */
+static int gsm48_rx_gmm_id_resp(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];
+	struct gprs_ra_id ra_id;
+	struct sgsn_mm_ctx *ctx;
+
+	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);
+
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+	ctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
+	if (!ctx) {
+		DEBUGP(DMM, "from unknown TLLI 0x%08x?!?\n", msgb_tlli(msg));
+		return -EINVAL;
+	}
+
+	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 */
+		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, msg);
+}
+
+static void attach_rej_cb(void *data)
+{
+	struct sgsn_mm_ctx *ctx = data;
+
+	/* FIXME: determine through which BTS/TRX to send this */
+	//gsm48_tx_gmm_att_rej(ctx->tlli, GMM_CAUSE_MS_ID_NOT_DERIVED);
+	ctx->mm_state = GMM_DEREGISTERED;
+	/* FIXME: release the context */
+}
+
+static void schedule_reject(struct sgsn_mm_ctx *ctx)
+{
+	ctx->T = 3370;
+	ctx->timer.cb = attach_rej_cb;
+	ctx->timer.data = ctx;
+	bsc_schedule_timer(&ctx->timer, 6, 0);
+}
+
+/* Section 9.4.1 Attach request */
+static int gsm48_rx_gmm_att_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t *cur = gh->data, *msnc, *mi, *old_ra_info;
+	uint8_t msnc_len, att_type, mi_len, mi_type;
+	uint16_t drx_par;
+	uint32_t tmsi;
+	char mi_string[GSM48_MI_SIZE];
+	struct gprs_ra_id ra_id;
+	uint16_t cid;
+	struct sgsn_mm_ctx *ctx;
+
+	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 > 2)
+		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++;
+	drx_par |= *cur++ << 8;
+
+	/* 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, att_name(att_type));
+
+	/* Old routing area identification 10.5.5.15 */
+	old_ra_info = cur;
+	cur += 6;
+
+	/* MS Radio Access Capability 10.5.5.12a */
+
+	/* 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 */
+		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 */
+			ctx = sgsn_mm_ctx_alloc(0, &ra_id);
+			if (!ctx)
+				return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_NET_FAIL);
+			strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi));
+#endif
+		}
+		/* FIXME: Start some timer */
+		ctx->mm_state = GMM_COMMON_PROC_INIT;
+		ctx->tlli = msgb_tlli(msg);
+		break;
+	case GSM_MI_TYPE_TMSI:
+		tmsi = strtoul(mi_string, NULL, 10);
+		/* Try to find MM context based on P-TMSI */
+		ctx = sgsn_mm_ctx_by_ptmsi(tmsi);
+		if (!ctx) {
+			ctx = sgsn_mm_ctx_alloc(msgb_tlli(msg), &ra_id);
+			/* FIXME: Start some timer */
+			ctx->mm_state = GMM_COMMON_PROC_INIT;
+			ctx->tlli = msgb_tlli(msg);
+		}
+		break;
+	default:
+		return 0;
+	}
+	/* Update MM Context with currient RA and Cell ID */
+	ctx->ra = ra_id;
+	ctx->cell_id = cid;
+
+	/* FIXME: allocate a new P-TMSI (+ P-TMSI signature) */
+	/* FIXME: update the TLLI with the new local TLLI based on the P-TMSI */
+
+	DEBUGPC(DMM, "\n");
+
+	return ctx ? gsm48_gmm_authorize(ctx, msg) : 0;
+
+err_inval:
+	DEBUGPC(DMM, "\n");
+	return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_SEM_INCORR_MSG);
+}
+
+/* Chapter 9.4.15: Routing area update accept */
+static int gsm48_tx_gmm_ra_upd_ack(struct msgb *old_msg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_ra_upd_ack *rua;
+	struct gprs_ra_id ra_id;
+
+	DEBUGP(DMM, "<- ROUTING AREA UPDATE ACCEPT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	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;
+
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(old_msg));
+	gsm48_construct_ra(rua->ra_id.digits, &ra_id);
+
+	/* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* 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);
+}
+
+/* Chapter 9.4.14: Routing area update request */
+static int gsm48_rx_gmm_ra_upd_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct sgsn_mm_ctx *mmctx;
+	uint8_t *cur = gh->data;
+	struct gprs_ra_id old_ra_id;
+	uint8_t upd_type;
+
+	/* Update Type 10.5.5.18 */
+	upd_type = *cur++ & 0x0f;
+
+	DEBUGP(DMM, "GMM RA UPDATE REQUEST type=\"%s\" ", upd_name(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 */
+
+	/* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status,
+	 * DRX parameter, MS network capability */
+
+	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");
+		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_IMPL_DETACHED);
+	}
+
+	/* 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 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 */
+
+	DEBUGPC(DMM, " ACCEPT\n");
+	return gsm48_tx_gmm_ra_upd_ack(msg);
+}
+
+static int gsm48_rx_gmm_status(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 msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	int rc;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_GMM_RA_UPD_REQ:
+		rc = gsm48_rx_gmm_ra_upd_req(msg);
+		break;
+	case GSM48_MT_GMM_ATTACH_REQ:
+		rc = gsm48_rx_gmm_att_req(msg);
+		break;
+	case GSM48_MT_GMM_ID_RESP:
+		rc = gsm48_rx_gmm_id_resp(msg);
+		break;
+	case GSM48_MT_GMM_STATUS:
+		rc = gsm48_rx_gmm_status(msg);
+		break;
+	case GSM48_MT_GMM_RA_UPD_COMPL:
+		/* only in case SGSN offered new P-TMSI */
+	case GSM48_MT_GMM_ATTACH_COMPL:
+		/* only in case SGSN offered new P-TMSI */
+	case GSM48_MT_GMM_DETACH_REQ:
+	case GSM48_MT_GMM_PTMSI_REALL_COMPL:
+	case GSM48_MT_GMM_AUTH_CIPH_RESP:
+		DEBUGP(DMM, "Unimplemented GSM 04.08 GMM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 GMM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+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 */
+static int gsm48_tx_gsm_act_pdp_acc(struct msgb *old_msg, struct gsm48_act_pdp_ctx_req *req)
+{
+	struct gsm48_hdr *old_gh = (struct gsm48_hdr *) msgb_gmmh(old_msg);
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_act_pdp_ctx_ack *act_ack;
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = ((old_gh->proto_discr >> 4) ^ 0x8); /* flip */
+
+	DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT ACK\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	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, req->req_llc_sapi);
+	/* copy QoS parameters from original request */
+	msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos);
+	/* Radio priority 10.5.7.2 */
+	msgb_v_put(msg, 4);
+	/* PDP address */
+	msgb_put_pdp_addr_ipv4(msg, 0x01020304);
+	/* Optional: Protocol configuration options */
+	/* Optional: Packet Flow Identifier */
+
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Section 9.5.9: Deactivate PDP Context Accept */
+static int gsm48_tx_gsm_deact_pdp_acc(struct msgb *old_msg)
+{
+	struct gsm48_hdr *old_gh = (struct gsm48_hdr *) msgb_gmmh(old_msg);
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = ((old_gh->proto_discr >> 4) ^ 0x8); /* flip */
+
+	DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT ACK\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	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);
+}
+
+/* Section 9.5.1: Activate PDP Context Request */
+static int gsm48_rx_gsm_act_pdp_req(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 *pdp_addr_lv = act_req->data;
+	uint8_t req_qos_len, req_pdpa_len;
+	uint8_t *req_qos, *req_pdpa;
+	struct tlv_parsed tp;
+
+	DEBUGP(DMM, "ACTIVATE PDP CONTEXT REQ: ");
+	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 */
+
+	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;
+	}
+
+	/* FIXME: parse TLV for AP name and protocol config options */
+	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {}
+	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)) {}
+
+	return gsm48_tx_gsm_act_pdp_acc(msg, act_req);
+}
+
+/* Section 9.5.8: Deactivate PDP Context Request */
+static int gsm48_rx_gsm_deact_pdp_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+
+	DEBUGP(DMM, "DEACTIVATE PDP CONTEXT REQ (cause: %s)\n",
+		get_value_string(gsm_cause_names, gh->data[0]));
+
+	return gsm48_tx_gsm_deact_pdp_acc(msg);
+}
+
+static int gsm48_rx_gsm_status(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;
+}
+
+/* GPRS Session Management */
+static int gsm0408_rcv_gsm(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	int rc;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_GSM_ACT_PDP_REQ:
+		rc = gsm48_rx_gsm_act_pdp_req(msg);
+		break;
+	case GSM48_MT_GSM_DEACT_PDP_REQ:
+		rc = gsm48_rx_gsm_deact_pdp_req(msg);
+	case GSM48_MT_GSM_STATUS:
+		rc = gsm48_rx_gsm_status(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);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 GSM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+
+	}
+
+	return rc;
+}
+
+/* Main entry point for incoming 04.08 GPRS messages */
+int gsm0408_gprs_rcvmsg(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t pdisc = gh->proto_discr & 0x0f;
+	int rc = -EINVAL;
+
+	switch (pdisc) {
+	case GSM48_PDISC_MM_GPRS:
+		rc = gsm0408_rcv_gmm(msg);
+		break;
+	case GSM48_PDISC_SM_GPRS:
+		rc = gsm0408_rcv_gsm(msg);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 discriminator 0x%02x\n",
+			pdisc);
+		break;
+	}
+
+	return rc;
+}
diff --git a/openbsc/src/gprs/osmo_gbproxy.cfg b/openbsc/src/gprs/osmo_gbproxy.cfg
new file mode 100644
index 0000000..d51b04a
--- /dev/null
+++ b/openbsc/src/gprs/osmo_gbproxy.cfg
@@ -0,0 +1,13 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+!
+line vty
+ no login
+!
+gbproxy
+  nsip bss local port 23000
+  nsip sgsn remote ip 127.0.0.1
+  nsip sgsn remote port 7777
+  nsip sgsn nsei 101
+  nsip sgsn nsvci 101
diff --git a/openbsc/src/gprs/osmo_sgsn.cfg b/openbsc/src/gprs/osmo_sgsn.cfg
new file mode 100644
index 0000000..f39e853
--- /dev/null
+++ b/openbsc/src/gprs/osmo_sgsn.cfg
@@ -0,0 +1,9 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+!
+line vty
+ no login
+!
+sgsn
+  nsip local port 23000
diff --git a/openbsc/src/gprs/sgsn_main.c b/openbsc/src/gprs/sgsn_main.c
new file mode 100644
index 0000000..b355fb5
--- /dev/null
+++ b/openbsc/src/gprs/sgsn_main.c
@@ -0,0 +1,183 @@
+/* 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 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 <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/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/vty.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.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;
+
+const char *openbsc_version = "Osmocom NSIP Proxy " PACKAGE_VERSION;
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Harald Welte and On-Waves\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\n"
+	"Dieter Spaar, Andreas Eversberg, Holger Freyther\n\n"
+	"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+	"This is free software: you are free to change and redistribute it.\n"
+	"There is NO WARRANTY, to the extent permitted by law.\n";
+
+static char *config_file = "osmo_sgsn.cfg";
+static struct sgsn_config sgcfg;
+
+/* 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;
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct log_target *stderr_target;
+	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);
+
+	rate_ctr_init(tall_bsc_ctx);
+	telnet_init(&dummy_network, 4245);
+
+	sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb);
+	if (!sgsn_nsi) {
+		LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+		exit(1);
+	}
+	bssgp_nsi = sgcfg.nsi = sgsn_nsi;
+	gprs_ns_vty_init(bssgp_nsi);
+	/* FIXME: register signal handler for SS_NS */
+
+	rc = sgsn_parse_config(config_file, &sgcfg);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+		exit(2);
+	}
+
+	nsip_listen(sgsn_nsi, sgcfg.nsip_listen_port);
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
+struct gsm_network;
+int bsc_vty_init(struct gsm_network *dummy)
+{
+	cmd_init(1);
+	vty_init();
+
+	openbsc_vty_add_cmds();
+        sgsn_vty_init();
+	return 0;
+}
+
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
new file mode 100644
index 0000000..ec18fcb
--- /dev/null
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -0,0 +1,146 @@
+/*
+ * (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 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 <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/sgsn.h>
+#include <openbsc/gprs_ns.h>
+
+#include <vty/command.h>
+#include <vty/vty.h>
+
+static struct sgsn_config *g_cfg = NULL;
+
+static struct cmd_node sgsn_node = {
+	SGSN_NODE,
+	"%s(sgsn)#",
+	1,
+};
+
+static int config_write_sgsn(struct vty *vty)
+{
+	struct in_addr ia;
+
+	vty_out(vty, "sgsn%s", VTY_NEWLINE);
+
+	if (g_cfg->nsip_listen_ip) {
+		ia.s_addr = htonl(g_cfg->nsip_listen_ip);
+		vty_out(vty, "  nsip local ip %s%s", inet_ntoa(ia),
+			VTY_NEWLINE);
+	}
+	vty_out(vty, "  nsip local port %u%s", g_cfg->nsip_listen_port,
+		VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
+      SHOW_STR "Display information about the SGSN")
+{
+	/* FIXME: iterate over list of NS-VC's and display their state */
+	struct gprs_ns_inst *nsi = g_cfg->nsi;
+	struct gprs_nsvc *nsvc;
+
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		vty_out(vty, "NSEI %5u, NS-VC %5u, %s-mode, %s %s%s",
+			nsvc->nsei, nsvc->nsvci,
+			nsvc->remote_end_is_sgsn ? "BSS" : "SGSN",
+			nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
+			nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED",
+			VTY_NEWLINE);
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
+			vty_out(vty, "  remote peer %s:%u%s",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port), VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sgsn,
+      cfg_sgsn_cmd,
+      "sgsn",
+      "Configure the SGSN")
+{
+	vty->node = SGSN_NODE;
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_nsip_local_ip,
+      cfg_nsip_local_ip_cmd,
+      "nsip local ip A.B.C.D",
+      "Set the IP address on which we listen for BSS connects")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	g_cfg->nsip_listen_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_local_port,
+      cfg_nsip_local_port_cmd,
+      "nsip local port <0-65534>",
+      "Set the UDP port on which we listen for BSS connects")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_listen_port = port;
+	return CMD_SUCCESS;
+}
+
+
+
+
+int sgsn_vty_init(void)
+{
+	install_element(VIEW_NODE, &show_sgsn_cmd);
+
+	install_element(CONFIG_NODE, &cfg_sgsn_cmd);
+	install_node(&sgsn_node, config_write_sgsn);
+	install_default(SGSN_NODE);
+	install_element(SGSN_NODE, &cfg_nsip_local_ip_cmd);
+	install_element(SGSN_NODE, &cfg_nsip_local_port_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);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/gsm_04_08_utils.c b/openbsc/src/gsm_04_08_utils.c
index b770b52..1b3ed25 100644
--- a/openbsc/src/gsm_04_08_utils.c
+++ b/openbsc/src/gsm_04_08_utils.c
@@ -36,19 +36,10 @@
 #include <openbsc/paging.h>
 #include <openbsc/signal.h>
 
-#define GSM48_ALLOC_SIZE	1024
-#define GSM48_ALLOC_HEADROOM	128
-
 /* should ip.access BTS use direct RTP streams between each other (1),
  * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
 int ipacc_rtp_direct = 1;
 
-struct msgb *gsm48_msgb_alloc(void)
-{
-	return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
-				   "GSM 04.08");
-}
-
 int gsm48_sendmsg(struct msgb *msg, struct gsm_trans *trans)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
@@ -288,7 +279,7 @@
 	sig_data.bts	= msg->lchan->ts->trx->bts;
 	sig_data.lchan	= msg->lchan;
 
-	bts->network->stats.paging.completed++;
+	counter_inc(bts->network->stats.paging.completed);
 
 	dispatch_signal(SS_PAGING, S_PAGING_SUCCEEDED, &sig_data);
 
diff --git a/openbsc/src/gsm_04_11.c b/openbsc/src/gsm_04_11.c
index 511ad47..3492f1f 100644
--- a/openbsc/src/gsm_04_11.c
+++ b/openbsc/src/gsm_04_11.c
@@ -675,7 +675,7 @@
 				     GSM411_RP_CAUSE_INV_MAND_INF);
 		return -EIO;
 	}
-	msg->smsh = tpdu;
+	msg->l4h = tpdu;
 
 	DEBUGP(DSMS, "DST(%u,%s)\n", dst_len, hexdump(dst, dst_len));
 
diff --git a/openbsc/src/gsm_04_80.c b/openbsc/src/gsm_04_80.c
index 2112e3f..ef10b17 100644
--- a/openbsc/src/gsm_04_80.c
+++ b/openbsc/src/gsm_04_80.c
@@ -257,7 +257,6 @@
 	if (((strlen(response_text) * 7) % 8) != 0)
 		response_len += 1;
 
-	msg->bts_link = in_msg->bts_link;
 	msg->lchan = in_msg->lchan;
 
 	/* First put the payload text into the message */
@@ -304,7 +303,6 @@
 	struct msgb *msg = gsm48_msgb_alloc();
 	struct gsm48_hdr *gh;
 
-	msg->bts_link = in_msg->bts_link;
 	msg->lchan = in_msg->lchan;
 
 	/* First insert the problem code */
diff --git a/openbsc/src/gsm_data.c b/openbsc/src/gsm_data.c
index 176367d..a4b3212 100644
--- a/openbsc/src/gsm_data.c
+++ b/openbsc/src/gsm_data.c
@@ -171,6 +171,10 @@
 	return trx;
 }
 
+static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+static const uint8_t bts_cell_timer_default[] =
+				{ 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 };
+
 struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type,
 			      u_int8_t tsc, u_int8_t bsic)
 {
@@ -212,6 +216,10 @@
 		bts->gprs.nsvc[i].bts = bts;
 		bts->gprs.nsvc[i].id = i;
 	}
+	memcpy(&bts->gprs.nse.timer, bts_nse_timer_default,
+		sizeof(bts->gprs.nse.timer));
+	memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
+		sizeof(bts->gprs.cell.timer));
 
 	/* create our primary TRX */
 	bts->c0 = gsm_bts_trx_alloc(bts);
@@ -221,6 +229,8 @@
 	}
 	bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4;
 
+	bts->rach_b_thresh = -1;
+	bts->rach_ldavg_slots = -1;
 	llist_add_tail(&bts->list, &net->bts_list);
 
 	return bts;
@@ -280,6 +290,10 @@
 	net->stats.call.dialled = counter_alloc("net.call.dialled");
 	net->stats.call.alerted = counter_alloc("net.call.alerted");
 	net->stats.call.connected = counter_alloc("net.call.connected");
+	net->stats.chan.rf_fail = counter_alloc("net.chan.rf_fail");
+	net->stats.chan.rll_err = counter_alloc("net.chan.rll_err");
+	net->stats.bts.oml_fail = counter_alloc("net.bts.oml_fail");
+	net->stats.bts.rsl_fail = counter_alloc("net.bts.rsl_fail");
 
 	net->mncc_recv = mncc_recv;
 
@@ -436,33 +450,6 @@
 	return get_value_string(auth_policy_names, policy);
 }
 
-/* this should not be here but in gsm_04_08... but that creates
-   in turn a dependency nightmare (abis_nm depending on 04_08, ...) */
-static int gsm48_construct_ra(u_int8_t *buf, const struct gprs_ra_id *raid)
-{
-	u_int16_t mcc = raid->mcc;
-	u_int16_t mnc = raid->mnc;
-
-	buf[0] = ((mcc / 100) % 10) | (((mcc / 10) % 10) << 4);
-	buf[1] = (mcc % 10);
-
-	/* I wonder who came up with the stupidity of encoding the MNC
-	 * differently depending on how many digits its decimal number has! */
-	if (mnc < 100) {
-		buf[1] |= 0xf0;
-		buf[2] = ((mnc / 10) % 10) | ((mnc % 10) << 4);
-	} else {
-		buf[1] |= (mnc % 10) << 4;
-		buf[2] = ((mnc / 100) % 10) | (((mcc / 10) % 10) << 4);
-	}
-
-	*(u_int16_t *)(buf+3) = htons(raid->lac);
-
-	buf[5] = raid->rac;
-
-	return 6;
-}
-
 void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts)
 {
 	raid->mcc = bts->network->country_code;
@@ -498,6 +485,23 @@
 	return get_value_string(rrlp_mode_names, mode);
 }
 
+static const struct value_string bts_gprs_mode_names[] = {
+	{ BTS_GPRS_NONE,	"none" },
+	{ BTS_GPRS_GPRS,	"gprs" },
+	{ BTS_GPRS_EGPRS,	"egprs" },
+	{ 0,			NULL }
+};
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg)
+{
+	return get_string_value(bts_gprs_mode_names, arg);
+}
+
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode)
+{
+	return get_value_string(bts_gprs_mode_names, mode);
+}
+
 struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan)
 {
 	struct gsm_meas_rep *meas_rep;
diff --git a/openbsc/src/handover_logic.c b/openbsc/src/handover_logic.c
index 7fb0b13..b2ffe46 100644
--- a/openbsc/src/handover_logic.c
+++ b/openbsc/src/handover_logic.c
@@ -134,6 +134,7 @@
 		return rc;
 	}
 
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
 	llist_add(&ho->list, &bsc_handovers);
 	/* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
 
@@ -229,7 +230,7 @@
 	/* update lchan pointer of transaction */
 	trans_lchan_change(&ho->old_lchan->conn, &new_lchan->conn);
 
-	ho->old_lchan->state = LCHAN_S_INACTIVE;
+	rsl_lchan_set_state(ho->old_lchan, LCHAN_S_INACTIVE);
 	lchan_auto_release(ho->old_lchan);
 
 	/* do something to re-route the actual speech frames ! */
diff --git a/openbsc/src/input/ipaccess.c b/openbsc/src/input/ipaccess.c
index 323540f..721cadd 100644
--- a/openbsc/src/input/ipaccess.c
+++ b/openbsc/src/input/ipaccess.c
@@ -1,6 +1,8 @@
 /* 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
  *
@@ -57,7 +59,7 @@
 static struct ia_e1_handle *e1h;
 
 
-#define TS1_ALLOC_SIZE	300
+#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 };
@@ -234,6 +236,8 @@
 		}
 		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);
@@ -241,7 +245,18 @@
 			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;
@@ -251,19 +266,13 @@
 							E1INP_SIGN_RSL, trx,
 							trx->rsl_tei, 0);
 
-			if (newbfd->fd >= 0) {
-				LOGP(DINP, LOGL_ERROR, "BTS is still registered. Closing old connection.\n");
-				bsc_unregister_fd(newbfd);
-				close(newbfd->fd);
-				newbfd->fd = -1;
-			}
-
 			/* get rid of our old temporary bfd */
 			memcpy(newbfd, bfd, sizeof(*newbfd));
 			newbfd->priv_nr = PRIV_RSL + trx_id;
 			bsc_unregister_fd(bfd);
-			bsc_register_fd(newbfd);
+			bfd->fd = -1;
 			talloc_free(bfd);
+			bsc_register_fd(newbfd);
 		}
 		break;
 	}
@@ -328,6 +337,103 @@
 	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, EVT_E1_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, EVT_E1_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;
@@ -341,18 +447,12 @@
 	msg = ipaccess_read_msg(bfd, &error);
 	if (!msg) {
 		if (error == 0) {
-			link = e1inp_lookup_sign_link(e1i_ts, IPAC_PROTO_OML, 0);
-			if (link) {
-				link->trx->bts->ip_access.flags = 0;
+			int ret = ipaccess_drop(e1i_ts, bfd);
+			if (ret >= 0)
 				LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n",
-					link->trx->bts->nr);
-			} else
+					ret);
+			else
 				LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n");
-			e1inp_event(e1i_ts, EVT_E1_TEI_DN, 0, IPAC_PROTO_RSL);
-			e1inp_event(e1i_ts, EVT_E1_TEI_DN, 0, IPAC_PROTO_OML);
-			bsc_unregister_fd(bfd);
-			close(bfd->fd);
-			bfd->fd = -1;
 		}
 		return error;
 	}
@@ -362,13 +462,8 @@
 	hh = (struct ipaccess_head *) msg->data;
 	if (hh->proto == IPAC_PROTO_IPACCESS) {
 		ret = ipaccess_rcvmsg(line, msg, bfd);
-		if (ret < 0) {
-			e1inp_event(e1i_ts, EVT_E1_TEI_DN, 0, IPAC_PROTO_RSL);
-			e1inp_event(e1i_ts, EVT_E1_TEI_DN, 0, IPAC_PROTO_OML);
-			bsc_unregister_fd(bfd);
-			close(bfd->fd);
-			bfd->fd = -1;
-		}
+		if (ret < 0)
+			ipaccess_drop(e1i_ts, bfd);
 		msgb_free(msg);
 		return ret;
 	}
@@ -475,7 +570,9 @@
 	/* 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, 100);
+
+	/* Reducing this might break the nanoBTS 900 init. */
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, 100000);
 
 	return ret;
 }
@@ -505,7 +602,6 @@
 	return rc;
 }
 
-
 struct e1inp_driver ipaccess_driver = {
 	.name = "ip.access",
 	.want_write = ts_want_write,
@@ -611,53 +707,6 @@
 	return 0;
 }
 
-static int make_sock(struct bsc_fd *bfd, u_int16_t port,
-		     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->data = line;
-
-	if (bfd->fd < 0) {
-		LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n");
-		return -EIO;
-	}
-
-	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 l2 socket %s\n",
-			strerror(errno));
-		close(bfd->fd);
-		return -EIO;
-	}
-
-	ret = listen(bfd->fd, 1);
-	if (ret < 0) {
-		perror("listen");
-		close(bfd->fd);
-		return ret;
-	}
-	
-	ret = bsc_register_fd(bfd);
-	if (ret < 0) {
-		perror("register_listen_fd");
-		close(bfd->fd);
-		return ret;
-	}
-	return 0;
-}
-
 /* Actively connect to a BTS.  Currently used by ipaccess-config.c */
 int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
 {
@@ -714,12 +763,16 @@
 	e1h->gsmnet = gsmnet;
 
 	/* Listen for OML connections */
-	ret = make_sock(&e1h->listen_fd, IPA_TCP_PORT_OML, listen_fd_cb);
+	ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, IPA_TCP_PORT_OML,
+			listen_fd_cb);
 	if (ret < 0)
 		return ret;
 
 	/* Listen for RSL connections */
-	ret = make_sock(&e1h->rsl_listen_fd, IPA_TCP_PORT_RSL, rsl_listen_fd_cb);
+	ret = make_sock(&e1h->rsl_listen_fd, IPPROTO_TCP,
+			IPA_TCP_PORT_RSL, rsl_listen_fd_cb);
+	if (ret < 0)
+		return ret;
 
 	return ret;
 }
diff --git a/openbsc/src/ipaccess/Makefile.am b/openbsc/src/ipaccess/Makefile.am
new file mode 100644
index 0000000..5339321
--- /dev/null
+++ b/openbsc/src/ipaccess/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS)
+
+sbin_PROGRAMS = ipaccess-find ipaccess-config ipaccess-proxy
+
+ipaccess_find_SOURCES = ipaccess-find.c
+
+ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c
+ipaccess_config_LDADD = $(top_builddir)/src/libbsc.a $(top_builddir)/src/libmsc.a \
+			$(top_builddir)/src/libbsc.a $(top_builddir)/src/libvty.a -ldl -ldbi $(LIBCRYPT)
+
+ipaccess_proxy_SOURCES = ipaccess-proxy.c ../debug.c
diff --git a/openbsc/src/ipaccess/ipaccess-config.c b/openbsc/src/ipaccess/ipaccess-config.c
index 670e3f1..f27e51f 100644
--- a/openbsc/src/ipaccess/ipaccess-config.c
+++ b/openbsc/src/ipaccess/ipaccess-config.c
@@ -1,8 +1,8 @@
 /* ip.access nanoBTS configuration tool */
 
 /* (C) 2009 by Harald Welte <laforge@gnumonks.org>
- * (C) 2009 by Holger Hans Peter Freyther
- * (C) 2009 by On Waves
+ * (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
@@ -59,6 +59,7 @@
 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];
@@ -91,23 +92,23 @@
 	return 0;
 }
 
-static int ipacc_msg_ack(u_int8_t mt, struct gsm_bts *bts)
+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");
-
-		if (restart) {
-			abis_nm_ipaccess_restart(bts);
-		} else {
-			exit(0);
-		}
+		check_restart_or_exit(trx);
 	} else if (oml_state == 1) {
 		fprintf(stderr, "Set the primary OML IP.\n");
-		if (restart) {
-			abis_nm_ipaccess_restart(bts);
-		} else {
-			exit(0);
-		}
+		check_restart_or_exit(trx);
 	}
 
 	return 0;
@@ -202,7 +203,7 @@
 		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->bts);
+		return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->trx);
 	case S_NM_TEST_REP:
 		return test_rep(signal_data);
 	case S_NM_IPACC_RESTART_ACK:
@@ -227,12 +228,12 @@
 		       void *data, void *param)
 {
 	struct msgb *msg;
-	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
 
 	if (hook != GSM_HOOK_NM_SWLOAD)
 		return 0;
 
-	bts = (struct gsm_bts *) data;
+	trx = (struct gsm_bts_trx *) data;
 
 	switch (event) {
 	case NM_MT_LOAD_INIT_ACK:
@@ -271,7 +272,7 @@
 		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(bts->c0, msg->l2h, msgb_l2len(msg));
+		abis_nm_ipaccess_set_nvattr(trx, msg->l2h, msgb_l2len(msg));
 		msgb_free(msg);
 		break;
 	case NM_MT_LOAD_END_NACK:
@@ -285,7 +286,7 @@
 	case NM_MT_ACTIVATE_SW_ACK:
 		break;
 	case NM_MT_LOAD_SEG_ACK:
-		percent = abis_nm_software_load_status(bts);
+		percent = abis_nm_software_load_status(trx->bts);
 		if (percent > percent_old)
 			printf("Software Download Progress: %d%%\n", percent);
 		percent_old = percent;
@@ -298,13 +299,13 @@
 	return 0;
 }
 
-static void bootstrap_om(struct gsm_bts *bts)
+static void bootstrap_om(struct gsm_bts_trx *trx)
 {
 	int len;
 	static u_int8_t buf[1024];
 	u_int8_t *cur = buf;
 
-	printf("OML link established\n");
+	printf("OML link established using TRX %d\n", trx->nr);
 
 	if (unit_id) {
 		len = strlen(unit_id);
@@ -316,7 +317,7 @@
 		memcpy(buf+3, unit_id, len);
 		buf[3+len] = 0;
 		printf("setting Unit ID to '%s'\n", unit_id);
-		abis_nm_ipaccess_set_nvattr(bts->c0, buf, 3+len+1);
+		abis_nm_ipaccess_set_nvattr(trx, buf, 3+len+1);
 	}
 	if (prim_oml_ip) {
 		struct in_addr ia;
@@ -340,7 +341,7 @@
 		*cur++ = 0;
 		printf("setting primary OML link IP to '%s'\n", inet_ntoa(ia));
 		oml_state = 1;
-		abis_nm_ipaccess_set_nvattr(bts->c0, buf, 3+len);
+		abis_nm_ipaccess_set_nvattr(trx, buf, 3+len);
 	}
 	if (nv_mask) {
 		len = 4;
@@ -354,12 +355,12 @@
 		*cur++ = nv_mask >> 8;
 		printf("setting NV Flags/Mask to 0x%04x/0x%04x\n",
 			nv_flags, nv_mask);
-		abis_nm_ipaccess_set_nvattr(bts->c0, buf, 3+len);
+		abis_nm_ipaccess_set_nvattr(trx, buf, 3+len);
 	}
 
 	if (restart && !prim_oml_ip && !software) {
 		printf("restarting BTS\n");
-		abis_nm_ipaccess_restart(bts);
+		abis_nm_ipaccess_restart(trx);
 	}
 
 }
@@ -370,7 +371,6 @@
 	case EVT_E1_TEI_UP:
 		switch (type) {
 		case E1INP_SIGN_OML:
-			bootstrap_om(trx->bts);
 			break;
 		case E1INP_SIGN_RSL:
 			/* FIXME */
@@ -389,22 +389,29 @@
 }
 
 int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
-		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+		   struct abis_om_obj_inst *obj_inst)
 {
-	if (evt == EVT_STATECHG_OPER &&
+	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 == EVT_STATECHG_OPER &&
 	    obj_class == NM_OC_RADIO_CARRIER &&
 	    new_state->availability == 3) {
 		struct gsm_bts_trx *trx = obj;
 
 		if (net_listen_testnr) {
 			u_int8_t phys_config[] = { 0x02, 0x0a, 0x00, 0x01, 0x02 };
-			abis_nm_perform_test(trx->bts, 2, 0, 0, 0xff,
+			abis_nm_perform_test(trx->bts, 2, 0, trx->nr, 0xff,
 					     net_listen_testnr, 1,
 					     phys_config, sizeof(phys_config));
 		} else if (software) {
 			int rc;
 			printf("Attempting software upload with '%s'\n", software);
-			rc = abis_nm_software_load(trx->bts, software, 19, 0, swload_cbfn, trx->bts);
+			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);
@@ -639,6 +646,7 @@
 			{ "software", 1, 0, 'd' },
 			{ "firmware", 1, 0, 'f' },
 			{ "write-firmware", 0, 0, 'w' },
+			{ 0, 0, 0, 0 },
 		};
 
 		c = getopt_long(argc, argv, "u:o:rn:l:hs:d:f:w", long_options,
diff --git a/openbsc/src/mgcp/mgcp_network.c b/openbsc/src/mgcp/mgcp_network.c
index e9c067e..1f51233 100644
--- a/openbsc/src/mgcp/mgcp_network.c
+++ b/openbsc/src/mgcp/mgcp_network.c
@@ -151,7 +151,7 @@
 	 */
 	#warning "Slight spec violation. With connection mode recvonly we should attempt to forward."
 	dest = memcmp(&addr.sin_addr, &endp->remote, sizeof(addr.sin_addr)) == 0 &&
-                    (endp->net_rtp == addr.sin_port || endp->net_rtcp == addr.sin_port)
+		    (endp->net_rtp == addr.sin_port || endp->net_rtcp == addr.sin_port)
 			? DEST_BTS : DEST_NETWORK;
 	proto = fd == &endp->local_rtp ? PROTO_RTP : PROTO_RTCP;
 
diff --git a/openbsc/src/mgcp/mgcp_vty.c b/openbsc/src/mgcp/mgcp_vty.c
index b058882..38d3daf 100644
--- a/openbsc/src/mgcp/mgcp_vty.c
+++ b/openbsc/src/mgcp/mgcp_vty.c
@@ -286,8 +286,8 @@
 
 	/*
 	 * This application supports two modes.
-         *    1.) a true MGCP gateway with support for AUEP, CRCX, MDCX, DLCX
-         *    2.) plain forwarding of RTP packets on the endpoints.
+	 *    1.) a true MGCP gateway with support for AUEP, CRCX, MDCX, DLCX
+	 *    2.) plain forwarding of RTP packets on the endpoints.
 	 * both modes are mutual exclusive
 	 */
 	if (g_cfg->forward_ip) {
diff --git a/openbsc/src/openbsc.cfg.nanobts b/openbsc/src/openbsc.cfg.nanobts
index a1ceaec..da0ba74 100644
--- a/openbsc/src/openbsc.cfg.nanobts
+++ b/openbsc/src/openbsc.cfg.nanobts
@@ -1,6 +1,6 @@
 !
 ! OpenBSC configuration saved from vty
-!
+!   !
 password foo
 !
 line vty
@@ -11,17 +11,52 @@
  mobile network code 1
  short name OpenBSC
  long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 0
+ rrlp mode none
+ mm info 1
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
  timer t3101 10
+ timer t3103 0
+ timer t3105 0
+ timer t3107 0
+ timer t3109 0
+ timer t3111 0
  timer t3113 60
+ timer t3115 0
+ timer t3117 0
+ timer t3119 0
+ timer t3141 0
  bts 0
   type nanobts
-  ip.access unit_id 1801 0
-  band GSM1800
+  band DCS1800
+  cell_identity 0
   location_area_code 1
   training_sequence_code 7
   base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1801 0
+  oml ip.access stream_id 255
+  gprs mode none
   trx 0
+   rf_locked 0
    arfcn 514
+   nominal power 23
+   max_power_red 20
+   rsl e1 tei 0
     timeslot 0
      phys_chan_config CCCH+SDCCH4
     timeslot 1
diff --git a/openbsc/src/openbsc.cfg.nanobts.multitrx b/openbsc/src/openbsc.cfg.nanobts.multitrx
new file mode 100644
index 0000000..6e27ff5
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.nanobts.multitrx
@@ -0,0 +1,97 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 0
+ rrlp mode none
+ mm info 0
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ timer t3101 10
+ timer t3103 0
+ timer t3105 0
+ timer t3107 0
+ timer t3109 0
+ timer t3111 0
+ timer t3113 60
+ timer t3115 0
+ timer t3117 0
+ timer t3119 0
+ timer t3141 0
+ bts 0
+  type nanobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1800 0
+  oml ip.access stream_id 255
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 871
+   nominal power 23
+   max_power_red 0
+   rsl e1 tei 0
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+    timeslot 1
+     phys_chan_config SDCCH8
+    timeslot 2
+     phys_chan_config TCH/F
+    timeslot 3
+     phys_chan_config TCH/F
+    timeslot 4
+     phys_chan_config TCH/F
+    timeslot 5
+     phys_chan_config TCH/F
+    timeslot 6
+     phys_chan_config TCH/F
+    timeslot 7
+     phys_chan_config TCH/F
+  trx 1
+   rf_locked 0
+   arfcn 873
+   nominal power 23
+   max_power_red 0
+   rsl e1 tei 0
+    timeslot 0
+     phys_chan_config SDCCH8
+    timeslot 1
+     phys_chan_config TCH/F
+    timeslot 2
+     phys_chan_config TCH/F
+    timeslot 3
+     phys_chan_config TCH/F
+    timeslot 4
+     phys_chan_config TCH/F
+    timeslot 5
+     phys_chan_config TCH/F
+    timeslot 6
+     phys_chan_config TCH/F
+    timeslot 7
+     phys_chan_config TCH/F
diff --git a/openbsc/src/paging.c b/openbsc/src/paging.c
index 7c3750d..12ed903 100644
--- a/openbsc/src/paging.c
+++ b/openbsc/src/paging.c
@@ -45,6 +45,7 @@
 #include <openbsc/signal.h>
 #include <openbsc/abis_rsl.h>
 #include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
 
 void *tall_paging_ctx;
 
@@ -70,14 +71,6 @@
 static void paging_remove_request(struct gsm_bts_paging_state *paging_bts,
 				struct gsm_paging_request *to_be_deleted)
 {
-	/* Update the last_request if that is necessary */
-	if (to_be_deleted == paging_bts->last_request) {
-		paging_bts->last_request =
-			(struct gsm_paging_request *)paging_bts->last_request->entry.next;
-		if (&to_be_deleted->entry == &paging_bts->pending_requests)
-			paging_bts->last_request = NULL;
-	}
-
 	bsc_del_timer(&to_be_deleted->T3113);
 	llist_del(&to_be_deleted->entry);
 	subscr_put(to_be_deleted->subscr);
@@ -90,7 +83,7 @@
 	unsigned int mi_len;
 	unsigned int page_group;
 
-	DEBUGP(DPAG, "Going to send paging commands: imsi: '%s' tmsi: '0x%x'\n",
+	LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: '%s' tmsi: '0x%x'\n",
 		request->subscr->imsi, request->subscr->tmsi);
 
 	if (request->subscr->tmsi == GSM_RESERVED_TMSI)
@@ -103,14 +96,6 @@
 			request->chan_type);
 }
 
-static void paging_move_to_next(struct gsm_bts_paging_state *paging_bts)
-{
-	paging_bts->last_request =
-		(struct gsm_paging_request *)paging_bts->last_request->entry.next;
-	if (&paging_bts->last_request->entry == &paging_bts->pending_requests)
-		paging_bts->last_request = NULL;
-}
-
 /*
  * This is kicked by the periodic PAGING LOAD Indicator
  * coming from abis_rsl.c
@@ -128,17 +113,26 @@
 	 * return then.
 	 */
 	if (llist_empty(&paging_bts->pending_requests)) {
-		paging_bts->last_request = NULL;
 		/* since the list is empty, no need to reschedule the timer */
 		return;
 	}
 
-	if (!paging_bts->last_request)
-		paging_bts->last_request =
-			(struct gsm_paging_request *)paging_bts->pending_requests.next;
+	/*
+	 * In case the BTS does not provide us with load indication just fill
+	 * up our slots for this round. We should be able to page 20 subscribers
+	 * every two seconds. So we will just give the BTS some extra credit.
+	 * We will have to see how often we run out of this credit, so we might
+	 * need a low watermark and then add credit or give 20 every run when
+	 * the bts sets an option for that.
+	 */
+	if (paging_bts->available_slots == 0) {
+		LOGP(DPAG, LOGL_NOTICE, "No slots available on bts nr %d\n",
+		     paging_bts->bts->nr);
+		paging_bts->available_slots = 20;
+	}
 
-	assert(paging_bts->last_request);
-	initial_request = paging_bts->last_request;
+	initial_request = llist_entry(paging_bts->pending_requests.next,
+				      struct gsm_paging_request, entry);
 	current_request = initial_request;
 
 	do {
@@ -146,21 +140,17 @@
 		page_ms(current_request);
 		paging_bts->available_slots--;
 
-		/*
-		 * move to the next item. We might wrap around
-		 * this means last_request will be NULL and we just
-		 * call paging_page_to_next again. It it guranteed
-		 * that the list is not empty.
-		 */
-		paging_move_to_next(paging_bts);
-		if (!paging_bts->last_request)
-			paging_bts->last_request =
-				(struct gsm_paging_request *)paging_bts->pending_requests.next;
-		current_request = paging_bts->last_request;
+		/* take the current and add it to the back */
+		llist_del(&current_request->entry);
+		llist_add_tail(&current_request->entry, &paging_bts->pending_requests);
+
+		/* take the next request */
+		current_request = llist_entry(paging_bts->pending_requests.next,
+					      struct gsm_paging_request, entry);
 	} while (paging_bts->available_slots > 0
 		    &&  initial_request != current_request);
 
-	bsc_schedule_timer(&paging_bts->work_timer, 1, 0);
+	bsc_schedule_timer(&paging_bts->work_timer, 2, 0);
 }
 
 static void paging_worker(void *data)
@@ -178,7 +168,7 @@
 	bts->paging.work_timer.data = &bts->paging;
 
 	/* Large number, until we get a proper message */
-	bts->paging.available_slots = 100;
+	bts->paging.available_slots = 20;
 }
 
 static int paging_pending_request(struct gsm_bts_paging_state *bts,
@@ -200,7 +190,7 @@
 	void *cbfn_param;
 	gsm_cbfn *cbfn;
 
-	DEBUGP(DPAG, "T3113 expired for request %p (%s)\n",
+	LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n",
 		req, req->subscr->imsi);
 	
 	sig_data.subscr = req->subscr;
@@ -208,11 +198,11 @@
 	sig_data.lchan	= NULL;
 
 	/* must be destroyed before calling cbfn, to prevent double free */
+	counter_inc(req->bts->network->stats.paging.expired);
 	cbfn_param = req->cbfn_param;
 	cbfn = req->cbfn;
 	paging_remove_request(&req->bts->paging, req);
 
-	counter_inc(req->bts->network->stats.paging.expired);
 
 	dispatch_signal(SS_PAGING, S_PAGING_EXPIRED, &sig_data);
 	if (cbfn)
@@ -227,11 +217,11 @@
 	struct gsm_paging_request *req;
 
 	if (paging_pending_request(bts_entry, subscr)) {
-		DEBUGP(DPAG, "Paging request already pending\n");
+		LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n", subscr->imsi);
 		return -EEXIST;
 	}
 
-	DEBUGP(DPAG, "Start paging of subscriber %llu on bts %d.\n",
+	LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %llu on bts %d.\n",
 		subscr->id, bts->nr);
 	req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
 	req->subscr = subscr_get(subscr);
@@ -245,7 +235,7 @@
 	llist_add_tail(&req->entry, &bts_entry->pending_requests);
 
 	if (!bsc_timer_pending(&bts_entry->work_timer))
-		bsc_schedule_timer(&bts_entry->work_timer, 1, 0);
+		bsc_schedule_timer(&bts_entry->work_timer, 2, 0);
 
 	return 0;
 }
@@ -296,11 +286,11 @@
 				 entry) {
 		if (req->subscr == subscr) {
 			if (lchan && req->cbfn) {
-				DEBUGP(DPAG, "Stop paging on bts %d, calling cbfn.\n", bts->nr);
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d, calling cbfn.\n", bts->nr);
 				req->cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
 					  NULL, lchan, req->cbfn_param);
 			} else
-				DEBUGP(DPAG, "Stop paging on bts %d silently.\n", bts->nr);
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d silently.\n", bts->nr);
 			paging_remove_request(&bts->paging, req);
 			break;
 		}
@@ -328,7 +318,7 @@
 			break;
 
 		/* Stop paging */
-                if (bts != _bts)
+		if (bts != _bts)
 			_paging_request_stop(bts, subscr, NULL);
 	} while (1);
 }
diff --git a/openbsc/src/rest_octets.c b/openbsc/src/rest_octets.c
index 16996ce..039d2c8 100644
--- a/openbsc/src/rest_octets.c
+++ b/openbsc/src/rest_octets.c
@@ -133,6 +133,7 @@
 			     const struct gsm48_lsa_params *lsa_params)
 {
 	/* FIXME */
+	return -1;
 }
 
 /* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
@@ -318,8 +319,31 @@
 	/* hard-code no PAN_{DEC,INC,MAX} */
 	bitvec_set_bit(bv, 0);
 
-	/* no extension information (EDGE) */
-	bitvec_set_bit(bv, 0);
+	if (!gco->ext_info_present) {
+		/* no extension information */
+		bitvec_set_bit(bv, 0);
+	} else {
+		/* extension information */
+		bitvec_set_bit(bv, 1);
+		if (!gco->ext_info.egprs_supported) {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 0);
+		} else {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 1);
+			/* 1bit EGPRS PACKET CHANNEL REQUEST */
+			bitvec_set_bit(bv, gco->ext_info.use_egprs_p_ch_req);
+			/* 4bit BEP PERIOD */
+			bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
+		}
+		bitvec_set_bit(bv, gco->ext_info.pfc_supported);
+		bitvec_set_bit(bv, gco->ext_info.dtm_supported);
+		bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+	}
 
 	return 0;
 }
@@ -334,7 +358,7 @@
 	bitvec_set_uint(bv, pcp->n_avg_i, 4);
 }
 
-/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
 int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13)
 {
 	struct bitvec bv;
@@ -390,6 +414,11 @@
 				break;
 			}
 		}
+		/* 3GPP TS 44.018 Release 6 / 10.5.2.37b */
+		bitvec_set_bit(&bv, H);	/* added Release 99 */
+		/* claim our SGSN is compatible with Release 99, as EDGE and EGPRS
+		 * was only added in this Release */
+		bitvec_set_bit(&bv, 1);
 	}
 	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
 	return bv.data_len;
diff --git a/openbsc/src/rtp_proxy.c b/openbsc/src/rtp_proxy.c
index 375204e..924173d 100644
--- a/openbsc/src/rtp_proxy.c
+++ b/openbsc/src/rtp_proxy.c
@@ -91,9 +91,6 @@
 
 #define RTP_VERSION	2
 
-#define RTP_PT_GSM_FULL	3
-#define RTP_PT_GSM_EFR	97
-
 /* decode an rtp frame and create a new buffer with payload */
 static int rtp_decode(struct msgb *msg, u_int32_t callref, struct msgb **data)
 {
diff --git a/openbsc/src/sccp/sccp.c b/openbsc/src/sccp/sccp.c
index e0fd02e..de18614 100644
--- a/openbsc/src/sccp/sccp.c
+++ b/openbsc/src/sccp/sccp.c
@@ -81,7 +81,7 @@
 	/* need to add one */
 	cb = talloc_zero(tall_sccp_ctx, struct sccp_data_callback);
 	if (!cb) {
-		DEBUGP(DSCCP, "Failed to allocate sccp callback.\n");
+		LOGP(DSCCP, LOGL_ERROR, "Failed to allocate sccp callback.\n");
 		return NULL;
 	}
 
@@ -108,13 +108,13 @@
 	u_int8_t length;
 
 	if (room <= 0) {
-		DEBUGP(DSCCP, "Not enough room for an address: %u\n", room);
+		LOGP(DSCCP, LOGL_ERROR, "Not enough room for an address: %u\n", room);
 		return -1;
 	}
 
 	length = msgb->l2h[offset];
 	if (room <= length) {
-		DEBUGP(DSCCP, "Not enough room for optional data %u %u\n", room, length);
+		LOGP(DSCCP, LOGL_ERROR, "Not enough room for optional data %u %u\n", room, length);
 		return -1;
 	}
 
@@ -122,7 +122,7 @@
 	party = (struct sccp_called_party_address *)(msgb->l2h + offset + 1);
 	if (party->point_code_indicator) {
 		if (length <= read + 2) {
-		    DEBUGP(DSCCP, "POI does not fit %u\n", length);
+		    LOGP(DSCCP, LOGL_ERROR, "POI does not fit %u\n", length);
 		    return -1;
 		}
 
@@ -133,7 +133,7 @@
 
 	if (party->ssn_indicator) {
 		if (length <= read + 1) {
-		    DEBUGP(DSCCP, "SSN does not fit %u\n", length);
+		    LOGP(DSCCP, LOGL_ERROR, "SSN does not fit %u\n", length);
 		    return -1;
 		}
 
@@ -142,7 +142,7 @@
 	}
 
 	if (party->global_title_indicator) {
-		DEBUGP(DSCCP, "GTI not supported %u\n", *(u_int8_t *)party);
+		LOGP(DSCCP, LOGL_ERROR, "GTI not supported %u\n", *(u_int8_t *)party);
 		return -1;
 	}
 
@@ -156,7 +156,8 @@
 	if (addr->address.ssn_indicator != 1
 	    || addr->address.global_title_indicator == 1
 	    || addr->address.routing_indicator != 1) {
-		DEBUGP(DSCCP, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+		LOGP(DSCCP, LOGL_ERROR,
+			"Invalid called address according to 08.06: 0x%x 0x%x\n",
 			*(u_int8_t *)&addr->address, addr->ssn);
 		return -1;
 	}
@@ -176,7 +177,7 @@
 			return 0;
 
 		if (read + 1 >= room) {
-			DEBUGP(DSCCP, "no place for length\n");
+			LOGP(DSCCP, LOGL_ERROR, "no place for length\n");
 			return 0;
 		}
 
@@ -185,7 +186,8 @@
 
 
 		if (room <= read) {
-			DEBUGP(DSCCP, "no space for the data: type: %d read: %d room: %d l2: %d\n",
+			LOGP(DSCCP, LOGL_ERROR,
+			       "no space for the data: type: %d read: %d room: %d l2: %d\n",
 			       type, read, room, msgb_l2len(msgb));
 			return 0;
 		}
@@ -214,7 +216,7 @@
 
 	/* header check */
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb < header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
@@ -224,7 +226,7 @@
 		return -1;
 
 	if (check_address(&result->called) != 0) {
-		DEBUGP(DSCCP, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+		LOGP(DSCCP, LOGL_ERROR, "Invalid called address according to 08.06: 0x%x 0x%x\n",
 			*(u_int8_t *)&result->called.address, result->called.ssn);
 		return -1;
 	}
@@ -236,7 +238,7 @@
 	 */
 	memset(&optional_data, 0, sizeof(optional_data));
 	if (_sccp_parse_optional_data(optional_offset + req->optional_start, msgb, &optional_data) != 0) {
-		DEBUGP(DSCCP, "parsing of optional data failed.\n");
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
 		return -1;
 	}
 
@@ -260,14 +262,14 @@
 
 	/* we don't have enough size for the struct */
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb > header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb > header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
 
 	memset(&optional_data, 0, sizeof(optional_data));
 	if (_sccp_parse_optional_data(optional_offset + rls->optional_start, msgb, &optional_data) != 0) {
-		DEBUGP(DSCCP, "parsing of optional data failed.\n");
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
 		return -1;
 	}
 
@@ -295,7 +297,7 @@
 
 	/* header check */
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb < header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
@@ -306,7 +308,7 @@
 
 	memset(&optional_data, 0, sizeof(optional_data));
 	if (_sccp_parse_optional_data(optional_offset + ref->optional_start, msgb, &optional_data) != 0) {
-		DEBUGP(DSCCP, "parsing of optional data failed.\n");
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
 		return -1;
 	}
 
@@ -333,7 +335,7 @@
 
 	/* header check */
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb < header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
@@ -344,7 +346,7 @@
 
 	memset(&optional_data, 0, sizeof(optional_data));
 	if (_sccp_parse_optional_data(optional_offset + con->optional_start, msgb, &optional_data) != 0) {
-		DEBUGP(DSCCP, "parsing of optional data failed.\n");
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
 		return -1;
 	}
 
@@ -366,7 +368,7 @@
 
 	/* header check */
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb < header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
@@ -387,13 +389,13 @@
 
 	/* we don't have enough size for the struct */
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb > header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb > header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
 
 	if (dt1->segmenting != 0) {
-		DEBUGP(DSCCP, "This packet has segmenting, not supported: %d\n", dt1->segmenting);
+		LOGP(DSCCP, LOGL_ERROR, "This packet has segmenting, not supported: %d\n", dt1->segmenting);
 		return -1;
 	}
 
@@ -401,7 +403,7 @@
 
 	/* some more  size checks in here */
 	if (msgb_l2len(msgb) < variable_offset + dt1->variable_start + 1) {
-		DEBUGP(DSCCP, "Not enough space for variable start: %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "Not enough space for variable start: %u %u\n",
 			msgb_l2len(msgb), dt1->variable_start);
 		return -1;
 	}
@@ -410,7 +412,7 @@
 	msgb->l3h = &msgb->l2h[dt1->variable_start + variable_offset + 1];
 
 	if (msgb_l3len(msgb) < result->data_len) {
-		DEBUGP(DSCCP, "Not enough room for the payload: %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "Not enough room for the payload: %u %u\n",
 			msgb_l3len(msgb), result->data_len);
 		return -1;
 	}
@@ -428,7 +430,7 @@
 	struct sccp_data_unitdata *udt = (struct sccp_data_unitdata *)msgb->l2h;
 
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb < header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
@@ -438,7 +440,7 @@
 		return -1;
 
 	if (check_address(&result->called) != 0) {
-		DEBUGP(DSCCP, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+		LOGP(DSCCP, LOGL_ERROR, "Invalid called address according to 08.06: 0x%x 0x%x\n",
 			*(u_int8_t *)&result->called.address, result->called.ssn);
 		return -1;
 	}
@@ -447,13 +449,13 @@
 		return -1;
 
 	if (check_address(&result->calling) != 0) {
-		DEBUGP(DSCCP, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+		LOGP(DSCCP, LOGL_ERROR, "Invalid called address according to 08.06: 0x%x 0x%x\n",
 			*(u_int8_t *)&result->called.address, result->called.ssn);
 	}
 
 	/* we don't have enough size for the data */
 	if (msgb_l2len(msgb) < data_offset + udt->variable_data + 1) {
-		DEBUGP(DSCCP, "msgb < header + offset %u %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header + offset %u %u %u\n",
 			msgb_l2len(msgb), header_size, udt->variable_data);
 		return -1;
 	}
@@ -463,7 +465,7 @@
 	result->data_len = msgb_l3len(msgb);
 
 	if (msgb_l3len(msgb) !=  msgb->l3h[-1]) {
-		DEBUGP(DSCCP, "msgb is truncated is: %u should: %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb is truncated is: %u should: %u\n",
 			msgb_l3len(msgb), msgb->l3h[-1]);
 		return -1;
 	}
@@ -478,7 +480,7 @@
 	struct sccp_data_it *it;
 
 	if (msgb_l2len(msgb) < header_size) {
-		DEBUGP(DSCCP, "msgb < header_size %u %u\n",
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
 		        msgb_l2len(msgb), header_size);
 		return -1;
 	}
@@ -490,6 +492,23 @@
 	return 0;
 }
 
+static int _sccp_parse_err(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static const u_int32_t header_size = sizeof(struct sccp_proto_err);
+
+	struct sccp_proto_err *err;
+
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		     msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	err = (struct sccp_proto_err *) msgb->l2h;
+	result->data_len = 0;
+	result->destination_local_reference = &err->destination_local_reference;
+	return 0;
+}
 
 /*
  * Send UDT. Currently we have a fixed address...
@@ -501,7 +520,7 @@
 	u_int8_t *data;
 
 	if (msgb_l3len(payload) > 256) {
-		DEBUGP(DSCCP, "The payload is too big for one udt\n");
+		LOGP(DSCCP, LOGL_ERROR, "The payload is too big for one udt\n");
 		return -1;
 	}
 
@@ -546,7 +565,7 @@
 
 	cb = _find_ssn(result.called.ssn);
 	if (!cb || !cb->read_cb) {
-		DEBUGP(DSCCP, "No routing for UDT for called SSN: %u\n", result.called.ssn);
+		LOGP(DSCCP, LOGL_ERROR, "No routing for UDT for called SSN: %u\n", result.called.ssn);
 		return -1;
 	}
 
@@ -595,7 +614,7 @@
 		++last_ref;
 		/* do not use the reversed word and wrap around */
 		if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) {
-			DEBUGP(DSCCP, "Wrapped searching for a free code\n");
+			LOGP(DSCCP, LOGL_DEBUG, "Wrapped searching for a free code\n");
 			last_ref = 0;
 			++wrapped;
 		}
@@ -606,7 +625,7 @@
 		}
 	} while (wrapped != 2);
 
-	DEBUGP(DSCCP, "Finding a free reference failed\n");
+	LOGP(DSCCP, LOGL_ERROR, "Finding a free reference failed\n");
 	return -1;
 }
 
@@ -686,13 +705,13 @@
 
 
 	if (msg && (msgb_l3len(msg) < 3 || msgb_l3len(msg) > 130)) {
-		DEBUGP(DSCCP, "Invalid amount of data... %d\n", msgb_l3len(msg));
+		LOGP(DSCCP, LOGL_ERROR, "Invalid amount of data... %d\n", msgb_l3len(msg));
 		return -1;
 	}
 
 	/* try to find a id */
 	if (assign_source_local_reference(connection) != 0) {
-		DEBUGP(DSCCP, "Assigning a local reference failed.\n");
+		LOGP(DSCCP, LOGL_ERROR, "Assigning a local reference failed.\n");
 		_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_SETUP_ERROR);
 		return -1;
 	}
@@ -744,7 +763,7 @@
 	int extra_size;
 
 	if (msgb_l3len(_data) < 2 || msgb_l3len(_data) > 256) {
-		DEBUGP(DSCCP, "data size too big, segmenting unimplemented.\n");
+		LOGP(DSCCP, LOGL_ERROR, "data size too big, segmenting unimplemented.\n");
 		return -1;
 	}
 
@@ -840,14 +859,14 @@
 
 	cb = _find_ssn(result.called.ssn);
 	if (!cb || !cb->accept_cb) {
-		DEBUGP(DSCCP, "No routing for CR for called SSN: %u\n", result.called.ssn);
+		LOGP(DSCCP, LOGL_ERROR, "No routing for CR for called SSN: %u\n", result.called.ssn);
 		return -1;
 	}
 
 	/* check if the system wants this connection */
 	connection = talloc_zero(tall_sccp_ctx, struct sccp_connection);
 	if (!connection) {
-		DEBUGP(DSCCP, "Allocation failed\n");
+		LOGP(DSCCP, LOGL_ERROR, "Allocation failed\n");
 		return -1;
 	}
 
@@ -859,7 +878,7 @@
 	 * one....
 	 */
 	if (destination_local_reference_is_free(result.source_local_reference) != 0) {
-		DEBUGP(DSCCP, "Need to reject connection with existing reference\n");
+		LOGP(DSCCP, LOGL_ERROR, "Need to reject connection with existing reference\n");
 		_sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_SCCP_FAILURE);
 		talloc_free(connection);
 		return -1;
@@ -879,7 +898,7 @@
 	llist_add_tail(&connection->list, &sccp_connections);
 
 	if (_sccp_send_connection_confirm(connection) != 0) {
-		DEBUGP(DSCCP, "Sending confirm failed... no available source reference?\n");
+		LOGP(DSCCP, LOGL_ERROR, "Sending confirm failed... no available source reference?\n");
 
 		_sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_SCCP_FAILURE);
 		_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REFUSED);
@@ -922,7 +941,7 @@
 	}
 
 
-	DEBUGP(DSCCP, "Release complete of unknown connection\n");
+	LOGP(DSCCP, LOGL_ERROR, "Release complete of unknown connection\n");
 	return -1;
 
 found:
@@ -950,7 +969,7 @@
 		}
 	}
 
-	DEBUGP(DSCCP, "No connection found for dt1 data\n");
+	LOGP(DSCCP, LOGL_ERROR, "No connection found for dt1 data\n");
 	return -1;
 
 found:
@@ -1009,7 +1028,7 @@
 	}
 
 
-	DEBUGP(DSCCP, "Unknown connection was released.\n");
+	LOGP(DSCCP, LOGL_ERROR, "Unknown connection was released.\n");
 	return -1;
 
 	/* we have found a connection */
@@ -1021,7 +1040,7 @@
 
 	/* generate a response */
 	if (_sccp_send_connection_release_complete(conn) != 0) {
-		DEBUGP(DSCCP, "Sending release confirmed failed\n");
+		LOGP(DSCCP, LOGL_ERROR, "Sending release confirmed failed\n");
 		return -1;
 	}
 
@@ -1046,7 +1065,7 @@
 		}
 	}
 
-	DEBUGP(DSCCP, "Refused but no connection found\n");
+	LOGP(DSCCP, LOGL_ERROR, "Refused but no connection found\n");
 	return -1;
 
 found:
@@ -1079,7 +1098,7 @@
 		}
 	}
 
-	DEBUGP(DSCCP, "Confirmed but no connection found\n");
+	LOGP(DSCCP, LOGL_ERROR, "Confirmed but no connection found\n");
 	return -1;
 
 found:
@@ -1108,7 +1127,7 @@
 int sccp_system_incoming(struct msgb *msgb)
 {
 	if (msgb_l2len(msgb) < 1 ) {
-		DEBUGP(DSCCP, "Too short packet\n");
+		LOGP(DSCCP, LOGL_ERROR, "Too short packet\n");
 		return -1;
 	}
 
@@ -1137,7 +1156,7 @@
 		return _sccp_handle_read(msgb);
 		break;
 	default:
-		DEBUGP(DSCCP, "unimplemented msg type: %d\n", type);
+		LOGP(DSCCP, LOGL_ERROR, "unimplemented msg type: %d\n", type);
 	};
 
 	return -1;
@@ -1148,7 +1167,7 @@
 {
 	if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM
 	    || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
-		DEBUGP(DSCCP, "sccp_connection_write: Wrong connection state: %p %d\n",
+		LOGP(DSCCP, LOGL_ERROR, "sccp_connection_write: Wrong connection state: %p %d\n",
 		       connection, connection->connection_state);
 		return -1;
 	}
@@ -1165,7 +1184,7 @@
 {
 	if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM
 	    || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
-		DEBUGP(DSCCP, "sccp_connection_write: Wrong connection state: %p %d\n",
+		LOGP(DSCCP, LOGL_ERROR, "sccp_connection_write: Wrong connection state: %p %d\n",
 		       connection, connection->connection_state);
 		return -1;
 	}
@@ -1178,7 +1197,7 @@
 {
 	if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM
 	    || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
-		DEBUGPC(DSCCP, "Can not close the connection. It was never opened: %p %d\n",
+		LOGP(DSCCP, LOGL_ERROR, "Can not close the connection. It was never opened: %p %d\n",
 			connection, connection->connection_state);
 		return -1;
 	}
@@ -1190,7 +1209,7 @@
 {
 	if (connection->connection_state > SCCP_CONNECTION_STATE_NONE
 	    && connection->connection_state < SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
-		DEBUGP(DSCCP, "The connection needs to be released before it is freed");
+		LOGP(DSCCP, LOGL_ERROR, "The connection needs to be released before it is freed");
 		return -1;
 	}
 
@@ -1318,6 +1337,9 @@
 	case SCCP_MSG_TYPE_IT:
 		return _sccp_parse_it(msg, result);
 		break;
+	case SCCP_MSG_TYPE_ERR:
+		return _sccp_parse_err(msg, result);
+		break;
 	};
 
 	LOGP(DSCCP, LOGL_ERROR, "Unimplemented MSG Type: 0x%x\n", type);
diff --git a/openbsc/src/socket.c b/openbsc/src/socket.c
new file mode 100644
index 0000000..3ed4d42
--- /dev/null
+++ b/openbsc/src/socket.c
@@ -0,0 +1,94 @@
+/* OpenBSC sokcet code, taken from Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <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 <osmocore/talloc.h>
+
+int make_sock(struct bsc_fd *bfd, int proto, u_int16_t port,
+	      int (*cb)(struct bsc_fd *fd, unsigned int what))
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+	int type = SOCK_STREAM;
+
+	if (proto == IPPROTO_UDP)
+		type = SOCK_DGRAM;
+
+	bfd->fd = socket(AF_INET, type, proto);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	//bfd->data = line;
+
+	if (bfd->fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n");
+		return -EIO;
+	}
+
+	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 l2 socket %s\n",
+			strerror(errno));
+		close(bfd->fd);
+		return -EIO;
+	}
+
+	if (proto != IPPROTO_UDP) {
+		ret = listen(bfd->fd, 1);
+		if (ret < 0) {
+			perror("listen");
+			return ret;
+		}
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		close(bfd->fd);
+		return ret;
+	}
+	return 0;
+}
diff --git a/openbsc/src/system_information.c b/openbsc/src/system_information.c
index 3f9d609..3bd833a 100644
--- a/openbsc/src/system_information.c
+++ b/openbsc/src/system_information.c
@@ -402,6 +402,16 @@
 		.t3192		= 500,
 		.drx_timer_max	= 3,
 		.bs_cv_max	= 15,
+		.ext_info_present = 0,
+		.ext_info = {
+			/* The values below are just guesses ! */
+			.egprs_supported = 0,
+			.use_egprs_p_ch_req = 1,
+			.bep_period = 4,
+			.pfc_supported = 0,
+			.dtm_supported = 0,
+			.bss_paging_coordination = 0,
+		},
 	},
 	.pwr_ctrl_pars = {
 		.alpha		= 10,	/* a = 1.0 */
@@ -448,7 +458,18 @@
 
 int gsm_generate_si(u_int8_t *output, struct gsm_bts *bts, int type)
 {
-	si_info.gprs_ind.present = bts->gprs.enabled;
+	switch (bts->gprs.mode) {
+	case BTS_GPRS_EGPRS:
+		si13_default.cell_opts.ext_info_present = 1;
+		si13_default.cell_opts.ext_info.egprs_supported = 1;
+		/* fallthrough */
+	case BTS_GPRS_GPRS:
+		si_info.gprs_ind.present = 1;
+		break;
+	case BTS_GPRS_NONE:
+		si_info.gprs_ind.present = 0;
+		break;
+	}
 
 	switch (type) {
 	case RSL_SYSTEM_INFO_1:
diff --git a/openbsc/src/vty/buffer.c b/openbsc/src/vty/buffer.c
index 195d062..0bc1760 100644
--- a/openbsc/src/vty/buffer.c
+++ b/openbsc/src/vty/buffer.c
@@ -65,11 +65,11 @@
 #define BUFFER_DATA_FREE(D) talloc_free((D))
 
 /* Make new buffer. */
-struct buffer *buffer_new(size_t size)
+struct buffer *buffer_new(void *ctx, size_t size)
 {
 	struct buffer *b;
 
-	b = talloc_zero(tall_vty_ctx, struct buffer);
+	b = talloc_zero(ctx, struct buffer);
 
 	if (size)
 		b->size = size;
@@ -138,7 +138,7 @@
 {
 	struct buffer_data *d;
 
-	d = _talloc_zero(tall_vty_ctx,
+	d = _talloc_zero(b,
 			 offsetof(struct buffer_data, data[b->size]),
 			 "buffer_add");
 	if (!d)
diff --git a/openbsc/src/vty/command.c b/openbsc/src/vty/command.c
index a38ed04..a1130b6 100644
--- a/openbsc/src/vty/command.c
+++ b/openbsc/src/vty/command.c
@@ -478,6 +478,13 @@
 	cmd->cmdsize = cmd_cmdsize(cmd->strvec);
 }
 
+/* Install a command into VIEW and ENABLE node */
+void install_element_ve(struct cmd_element *cmd)
+{
+	install_element(VIEW_NODE, cmd);
+	install_element(ENABLE_NODE, cmd);
+}
+
 #ifdef VTY_CRYPT_PW
 static unsigned char itoa64[] =
     "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -2311,7 +2318,7 @@
 }
 
 /* Down vty node level. */
-DEFUN(config_exit,
+gDEFUN(config_exit,
       config_exit_cmd, "exit", "Exit current mode and down to previous mode\n")
 {
 	switch (vty->node) {
@@ -2363,6 +2370,9 @@
 		vty->node = CONFIG_NODE;
 		break;
 	case MGCP_NODE:
+	case GBPROXY_NODE:
+	case SGSN_NODE:
+	case NS_NODE:
 		vty->node = CONFIG_NODE;
 		vty->index = NULL;
 	default:
@@ -2372,11 +2382,11 @@
 }
 
 /* quit is alias of exit. */
-ALIAS(config_exit,
+gALIAS(config_exit,
       config_quit_cmd, "quit", "Exit current mode and down to previous mode\n")
 
 /* End of configuration. */
-    DEFUN(config_end,
+    gDEFUN(config_end,
       config_end_cmd, "end", "End current mode and change to enable mode.")
 {
 	switch (vty->node) {
@@ -2407,7 +2417,7 @@
 }
 
 /* Help display function for all node. */
-DEFUN(config_help,
+gDEFUN(config_help,
       config_help_cmd, "help", "Description of the interactive help system\n")
 {
 	vty_out(vty,
@@ -2428,7 +2438,7 @@
 }
 
 /* Help display function for all node. */
-DEFUN(config_list, config_list_cmd, "list", "Print command list\n")
+gDEFUN(config_list, config_list_cmd, "list", "Print command list\n")
 {
 	unsigned int i;
 	struct cmd_node *cnode = vector_slot(cmdvec, vty->node);
diff --git a/openbsc/src/vty/utils.c b/openbsc/src/vty/utils.c
new file mode 100644
index 0000000..d8dfb8e
--- /dev/null
+++ b/openbsc/src/vty/utils.c
@@ -0,0 +1,50 @@
+/* utility routines for printing common objects in the Osmocom world */
+
+/* (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 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 <stdint.h>
+#include <inttypes.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rate_ctr.h>
+
+#include <vty/vty.h>
+
+void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
+			    struct rate_ctr_group *ctrg)
+{
+	unsigned int i;
+
+	vty_out(vty, "%s%s:%s", prefix, ctrg->desc->group_description, VTY_NEWLINE);
+	for (i = 0; i < ctrg->desc->num_ctr; i++) {
+		struct rate_ctr *ctr = &ctrg->ctr[i];
+		vty_out(vty, " %s%s: %8" PRIu64 " "
+			"(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s",
+			prefix, ctrg->desc->ctr_desc[i].description, ctr->current,
+			ctr->intv[RATE_CTR_INTV_SEC].rate,
+			ctr->intv[RATE_CTR_INTV_MIN].rate,
+			ctr->intv[RATE_CTR_INTV_HOUR].rate,
+			ctr->intv[RATE_CTR_INTV_DAY].rate,
+			VTY_NEWLINE);
+	};
+}
diff --git a/openbsc/src/vty/vty.c b/openbsc/src/vty/vty.c
index 0ac9530..0806856 100644
--- a/openbsc/src/vty/vty.c
+++ b/openbsc/src/vty/vty.c
@@ -51,10 +51,10 @@
 	if (!new)
 		goto out;
 
-	new->obuf = buffer_new(0);	/* Use default buffer size. */
+	new->obuf = buffer_new(new, 0);	/* Use default buffer size. */
 	if (!new->obuf)
 		goto out_new;
-	new->buf = _talloc_zero(tall_vty_ctx, VTY_BUFSIZ, "vty_new->buf");
+	new->buf = _talloc_zero(new, VTY_BUFSIZ, "vty_new->buf");
 	if (!new->buf)
 		goto out_obuf;
 
@@ -170,8 +170,7 @@
 	/* Check configure. */
 	vty_config_unlock(vty);
 
-	/* FIXME: memory leak. We need to call telnet_close_client() but don't
-	 * have bfd */
+	/* VTY_CLOSED is handled by the telnet_interface */
 	vty_event(VTY_CLOSED, vty->fd, vty);
 
 	/* OK free vty. */
@@ -211,7 +210,7 @@
 				else
 					size = size * 2;
 
-				p = talloc_realloc_size(tall_vty_ctx, p, size);
+				p = talloc_realloc_size(vty, p, size);
 				if (!p)
 					return -1;
 
@@ -358,7 +357,7 @@
 {
 	if (vty->max <= length) {
 		vty->max *= 2;
-		vty->buf = talloc_realloc_size(tall_vty_ctx, vty->buf, vty->max);
+		vty->buf = talloc_realloc_size(vty, vty->buf, vty->max);
 		// FIXME: check return
 	}
 }
@@ -459,7 +458,7 @@
 	/* Insert history entry. */
 	if (vty->hist[vty->hindex])
 		talloc_free(vty->hist[vty->hindex]);
-	vty->hist[vty->hindex] = talloc_strdup(tall_vty_ctx, vty->buf);
+	vty->hist[vty->hindex] = talloc_strdup(vty, vty->buf);
 
 	/* History index rotation. */
 	vty->hindex++;
@@ -916,7 +915,7 @@
 		vty_backward_pure_word(vty);
 		vty_insert_word_overwrite(vty, matched[0]);
 		vty_self_insert(vty, ' ');
-		//talloc_free(matched[0]);
+		talloc_free(matched[0]);
 		break;
 	case CMD_COMPLETE_MATCH:
 		vty_prompt(vty);
@@ -924,8 +923,6 @@
 		vty_backward_pure_word(vty);
 		vty_insert_word_overwrite(vty, matched[0]);
 		talloc_free(matched[0]);
-		vector_only_index_free(matched);
-		return;
 		break;
 	case CMD_COMPLETE_LIST_MATCH:
 		for (i = 0; matched[i] != NULL; i++) {
@@ -966,7 +963,7 @@
 		return;
 	}
 
-	buf = _talloc_zero(tall_vty_ctx, strlen(desc->str) + 1, "describe_fold");
+	buf = _talloc_zero(vty, strlen(desc->str) + 1, "describe_fold");
 	if (!buf)
 		return;
 
diff --git a/openbsc/src/vty_interface.c b/openbsc/src/vty_interface.c
index 956bf1e..2799706 100644
--- a/openbsc/src/vty_interface.c
+++ b/openbsc/src/vty_interface.c
@@ -39,9 +39,37 @@
 #include <osmocore/talloc.h>
 #include <openbsc/telnet_interface.h>
 #include <openbsc/vty.h>
+#include <openbsc/gprs_ns.h>
 
 static struct gsm_network *gsmnet;
 
+/* FIXME: this should go to some common file */
+static const struct value_string gprs_ns_timer_strs[] = {
+	{ 0, "tns-block" },
+	{ 1, "tns-block-retries" },
+	{ 2, "tns-reset" },
+	{ 3, "tns-reset-retries" },
+	{ 4, "tns-test" },
+	{ 5, "tns-alive" },
+	{ 6, "tns-alive-retries" },
+	{ 0, NULL }
+};
+
+static const struct value_string gprs_bssgp_cfg_strs[] = {
+	{ 0,	"blocking-timer" },
+	{ 1,	"blocking-retries" },
+	{ 2,	"unblocking-retries" },
+	{ 3,	"reset-timer" },
+	{ 4,	"reset-retries" },
+	{ 5,	"suspend-timer" },
+	{ 6,	"suspend-retries" },
+	{ 7,	"resume-timer" },
+	{ 8,	"resume-retries" },
+	{ 9,	"capability-update-timer" },
+	{ 10,	"capability-update-retries" },
+	{ 0,	NULL }
+};
+
 struct cmd_node net_node = {
 	GSMNET_NODE,
 	"%s(network)#",
@@ -266,6 +294,9 @@
 	int i;
 
 	vty_out(vty, "  trx %u%s", trx->nr, VTY_NEWLINE);
+	vty_out(vty, "   rf_locked %u%s",
+		trx->nm_state.administrative == NM_STATE_LOCKED ? 1 : 0,
+		VTY_NEWLINE);
 	vty_out(vty, "   arfcn %u%s", trx->arfcn, VTY_NEWLINE);
 	vty_out(vty, "   nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
 	vty_out(vty, "   max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
@@ -276,10 +307,48 @@
 		config_write_ts_single(vty, &trx->ts[i]);
 }
 
+static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts)
+{
+	unsigned int i;
+	vty_out(vty, "  gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
+		VTY_NEWLINE);
+	if (bts->gprs.mode == BTS_GPRS_NONE)
+		return;
+
+	vty_out(vty, "  gprs routing area %u%s", bts->gprs.rac,
+		VTY_NEWLINE);
+	vty_out(vty, "  gprs cell bvci %u%s", bts->gprs.cell.bvci,
+		VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++)
+		vty_out(vty, "  gprs cell timer %s %u%s",
+			get_value_string(gprs_bssgp_cfg_strs, i),
+			bts->gprs.cell.timer[i], VTY_NEWLINE);
+	vty_out(vty, "  gprs nsei %u%s", bts->gprs.nse.nsei,
+		VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++)
+		vty_out(vty, "  gprs ns timer %s %u%s",
+			get_value_string(gprs_ns_timer_strs, i),
+			bts->gprs.nse.timer[i], VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+		struct gsm_bts_gprs_nsvc *nsvc =
+					&bts->gprs.nsvc[i];
+		struct in_addr ia;
+
+		ia.s_addr = htonl(nsvc->remote_ip);
+		vty_out(vty, "  gprs nsvc %u nsvci %u%s", i,
+			nsvc->nsvci, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u local udp port %u%s", i,
+			nsvc->local_port, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u remote udp port %u%s", i,
+			nsvc->remote_port, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u remote ip %s%s", i,
+			inet_ntoa(ia), VTY_NEWLINE);
+	}
+}
+
 static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
 {
 	struct gsm_bts_trx *trx;
-	int i;
 
 	vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE);
 	vty_out(vty, "  type %s%s", btstype2str(bts->type), VTY_NEWLINE);
@@ -305,6 +374,13 @@
 	vty_out(vty, "  rach max transmission %u%s",
 		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
 		VTY_NEWLINE);
+
+	if (bts->rach_b_thresh != -1)
+		vty_out(vty, "  rach nm busy threshold %u%s",
+			bts->rach_b_thresh, VTY_NEWLINE);
+	if (bts->rach_ldavg_slots != -1)
+		vty_out(vty, "  rach nm load average %u%s",
+			bts->rach_ldavg_slots, VTY_NEWLINE);
 	if (bts->si_common.rach_control.cell_bar)
 		vty_out(vty, "  cell barred 1%s", VTY_NEWLINE);
 	if ((bts->si_common.rach_control.t2 & 0x4) == 0)
@@ -317,30 +393,7 @@
 		config_write_e1_link(vty, &bts->oml_e1_link, "  oml ");
 		vty_out(vty, "  oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
 	}
-	vty_out(vty, "  gprs enabled %u%s", bts->gprs.enabled, VTY_NEWLINE);
-	if (bts->gprs.enabled) {
-		vty_out(vty, "  gprs routing area %u%s", bts->gprs.rac,
-			VTY_NEWLINE);
-		vty_out(vty, "  gprs cell bvci %u%s", bts->gprs.cell.bvci,
-			VTY_NEWLINE);
-		vty_out(vty, "  gprs nsei %u%s", bts->gprs.nse.nsei,
-			VTY_NEWLINE);
-		for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
-			struct gsm_bts_gprs_nsvc *nsvc =
-						&bts->gprs.nsvc[i];
-			struct in_addr ia;
-
-			ia.s_addr = htonl(nsvc->remote_ip);
-			vty_out(vty, "  gprs nsvc %u nsvci %u%s", i,
-				nsvc->nsvci, VTY_NEWLINE);
-			vty_out(vty, "  gprs nsvc %u local udp port %u%s", i,
-				nsvc->local_port, VTY_NEWLINE);
-			vty_out(vty, "  gprs nsvc %u remote udp port %u%s", i,
-				nsvc->remote_port, VTY_NEWLINE);
-			vty_out(vty, "  gprs nsvc %u remote ip %s%s", i,
-				inet_ntoa(ia), VTY_NEWLINE);
-		}
-	}
+	config_write_bts_gprs(vty, bts);
 
 	llist_for_each_entry(trx, &bts->trx_list, list)
 		config_write_trx_single(vty, trx);
@@ -423,7 +476,9 @@
 DEFUN(show_trx,
       show_trx_cmd,
       "show trx [bts_nr] [trx_nr]",
-	SHOW_STR "Display information about a TRX\n")
+	SHOW_STR "Display information about a TRX\n"
+	"BTS Number\n"
+	"TRX Number\n")
 {
 	struct gsm_network *net = gsmnet;
 	struct gsm_bts *bts = NULL;
@@ -488,7 +543,8 @@
 DEFUN(show_ts,
       show_ts_cmd,
       "show timeslot [bts_nr] [trx_nr] [ts_nr]",
-	SHOW_STR "Display information about a TS\n")
+	SHOW_STR "Display information about a TS\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n")
 {
 	struct gsm_network *net = gsmnet;
 	struct gsm_bts *bts;
@@ -542,10 +598,6 @@
 
 static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr)
 {
-	int rc;
-	struct gsm_auth_info ainfo;
-	struct gsm_auth_tuple atuple;
-
 	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
 		subscr->authorized, VTY_NEWLINE);
 	if (subscr->name)
@@ -596,7 +648,7 @@
 	meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
 }
 
-static void lchan_dump_vty(struct vty *vty, struct gsm_lchan *lchan)
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
 {
 	int idx;
 
@@ -631,37 +683,28 @@
 	meas_rep_dump_vty(vty, &lchan->meas_rep[idx], "  ");
 }
 
-#if 0
-TODO: callref and remote callref of call must be resolved to get gsm_trans object
-static void call_dump_vty(struct vty *vty, struct gsm_call *call)
+static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
 {
-	vty_out(vty, "Call Type %u, State %u, Transaction ID %u%s",
-		call->type, call->state, call->transaction_id, VTY_NEWLINE);
+	struct gsm_meas_rep *mr;
+	int idx;
 
-	if (call->local_lchan) {
-		vty_out(vty, "Call Local Channel:%s", VTY_NEWLINE);
-		lchan_dump_vty(vty, call->local_lchan);
-	} else
-		vty_out(vty, "Call has no Local Channel%s", VTY_NEWLINE);
+	/* we want to report the last measurement report */
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+			       lchan->meas_rep_idx, 1);
+	mr =  &lchan->meas_rep[idx];
 
-	if (call->remote_lchan) {
-		vty_out(vty, "Call Remote Channel:%s", VTY_NEWLINE);
-		lchan_dump_vty(vty, call->remote_lchan);
-	} else
-		vty_out(vty, "Call has no Remote Channel%s", VTY_NEWLINE);
-
-	if (call->called_subscr) {
-		vty_out(vty, "Called Subscriber:%s", VTY_NEWLINE);
-		subscr_dump_vty(vty, call->called_subscr);
-	} else
-		vty_out(vty, "Call has no Called Subscriber%s", VTY_NEWLINE);
+	vty_out(vty, "Lchan: %u Timeslot: %u TRX: %u BTS: %u Type: %s - L1 MS Power: %u dBm "
+		     "RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
+		lchan->nr, lchan->ts->nr, lchan->ts->trx->nr,
+		lchan->ts->trx->bts->nr, gsm_lchant_name(lchan->type),
+		mr->ms_l1.pwr,
+		rxlev2dbm(mr->dl.full.rx_lev),
+		rxlev2dbm(mr->ul.full.rx_lev),
+		VTY_NEWLINE);
 }
-#endif
 
-DEFUN(show_lchan,
-      show_lchan_cmd,
-      "show lchan [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
-	SHOW_STR "Display information about a logical channel\n")
+static int lchan_summary(struct vty *vty, int argc, const char **argv,
+			 void (*dump_cb)(struct vty *, struct gsm_lchan *))
 {
 	struct gsm_network *net = gsmnet;
 	struct gsm_bts *bts;
@@ -706,7 +749,7 @@
 			return CMD_WARNING;
 		}
 		lchan = &ts->lchan[lchan_nr];
-		lchan_dump_vty(vty, lchan);
+		dump_cb(vty, lchan);
 		return CMD_SUCCESS;
 	}
 	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
@@ -720,7 +763,7 @@
 					lchan = &ts->lchan[lchan_nr];
 					if (lchan->type == GSM_LCHAN_NONE)
 						continue;
-					lchan_dump_vty(vty, lchan);
+					dump_cb(vty, lchan);
 				}
 			}
 		}
@@ -729,6 +772,28 @@
 	return CMD_SUCCESS;
 }
 
+
+DEFUN(show_lchan,
+      show_lchan_cmd,
+      "show lchan [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	"Logical Channel Number\n")
+
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+      show_lchan_summary_cmd,
+      "show lchan summary [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	"Logical Channel Number\n")
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
 static void e1drv_dump_vty(struct vty *vty, struct e1inp_driver *drv)
 {
 	vty_out(vty, "E1 Input Driver %s%s", drv->name, VTY_NEWLINE);
@@ -757,7 +822,8 @@
 DEFUN(show_e1line,
       show_e1line_cmd,
       "show e1_line [line_nr]",
-	SHOW_STR "Display information about a E1 line\n")
+	SHOW_STR "Display information about a E1 line\n"
+	"E1 Line Number\n")
 {
 	struct e1inp_line *line;
 
@@ -790,7 +856,8 @@
 DEFUN(show_e1ts,
       show_e1ts_cmd,
       "show e1_timeslot [line_nr] [ts_nr]",
-	SHOW_STR "Display information about a E1 timeslot\n")
+	SHOW_STR "Display information about a E1 timeslot\n"
+	"E1 Line Number\n" "E1 Timeslot Number\n")
 {
 	struct e1inp_line *line = NULL;
 	struct e1inp_ts *ts;
@@ -854,7 +921,8 @@
 DEFUN(show_paging,
       show_paging_cmd,
       "show paging [bts_nr]",
-	SHOW_STR "Display information about paging reuqests of a BTS\n")
+	SHOW_STR "Display information about paging reuqests of a BTS\n"
+	"BTS Number\n")
 {
 	struct gsm_network *net = gsmnet;
 	struct gsm_bts *bts;
@@ -881,50 +949,11 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(show_stats,
-      show_stats_cmd,
-      "show statistics",
-	SHOW_STR "Display network statistics\n")
-{
-	struct gsm_network *net = gsmnet;
-
-	vty_out(vty, "Channel Requests        : %lu total, %lu no channel%s",
-		counter_get(net->stats.chreq.total),
-		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
-	vty_out(vty, "Location Update         : %lu attach, %lu normal, %lu periodic%s",
-		counter_get(net->stats.loc_upd_type.attach),
-		counter_get(net->stats.loc_upd_type.normal),
-		counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE);
-	vty_out(vty, "IMSI Detach Indications : %lu%s",
-		counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE);
-	vty_out(vty, "Location Update Response: %lu accept, %lu reject%s",
-		counter_get(net->stats.loc_upd_resp.accept),
-		counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE);
-	vty_out(vty, "Paging                  : %lu attempted, %lu complete, %lu expired%s",
-		counter_get(net->stats.paging.attempted),
-		counter_get(net->stats.paging.completed),
-		counter_get(net->stats.paging.expired), VTY_NEWLINE);
-	vty_out(vty, "Handover                : %lu attempted, %lu no_channel, %lu timeout, "
-		"%lu completed, %lu failed%s",
-		counter_get(net->stats.handover.attempted),
-		counter_get(net->stats.handover.no_channel),
-		counter_get(net->stats.handover.timeout),
-		counter_get(net->stats.handover.completed),
-		counter_get(net->stats.handover.failed), VTY_NEWLINE);
-	vty_out(vty, "SMS MO                  : %lu submitted, %lu no receiver%s",
-		counter_get(net->stats.sms.submitted),
-		counter_get(net->stats.sms.no_receiver), VTY_NEWLINE);
-	vty_out(vty, "SMS MT                  : %lu delivered, %lu no memory, %lu other error%s",
-		counter_get(net->stats.sms.delivered),
-		counter_get(net->stats.sms.rp_err_mem),
-		counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE);
-	return CMD_SUCCESS;
-}
+#define NETWORK_STR "Configure the GSM network\n"
 
 DEFUN(cfg_net,
       cfg_net_cmd,
-      "network",
-      "Configure the GSM network")
+      "network", NETWORK_STR)
 {
 	vty->index = gsmnet;
 	vty->node = GSMNET_NODE;
@@ -982,7 +1011,11 @@
 DEFUN(cfg_net_auth_policy,
       cfg_net_auth_policy_cmd,
       "auth policy (closed|accept-all|token)",
-      "Set the GSM network authentication policy\n")
+	"Authentication (not cryptographic)\n"
+	"Set the GSM network authentication policy\n"
+	"Require the MS to be activated in HLR\n"
+	"Accept all MS, whether in HLR or not\n"
+	"Use SMS-token based authentication\n")
 {
 	enum gsm_auth_policy policy = gsm_auth_policy_parse(argv[0]);
 
@@ -1022,7 +1055,12 @@
 
 DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd,
       "rrlp mode (none|ms-based|ms-preferred|ass-preferred)",
-	"Set the Radio Resource Location Protocol Mode")
+	"Radio Resource Location Protocol\n"
+	"Set the Radio Resource Location Protocol Mode\n"
+	"Don't send RRLP request\n"
+	"Request MS-based location\n"
+	"Request any location, prefer MS-based\n"
+	"Request any location, prefer MS-assisted\n")
 {
 	gsmnet->rrlp.mode = rrlp_mode_parse(argv[0]);
 
@@ -1038,9 +1076,13 @@
 	return CMD_SUCCESS;
 }
 
+#define HANDOVER_STR	"Handover Options\n"
+
 DEFUN(cfg_net_handover, cfg_net_handover_cmd,
       "handover (0|1)",
-	"Whether or not to use in-call handover")
+	HANDOVER_STR
+	"Don't perform in-call handover\n"
+	"Perform in-call handover\n")
 {
 	int enable = atoi(argv[0]);
 
@@ -1055,8 +1097,14 @@
 	return CMD_SUCCESS;
 }
 
+#define HO_WIN_STR HANDOVER_STR "Measurement Window\n"
+#define HO_WIN_RXLEV_STR HO_WIN_STR "Received Level Averaging\n"
+#define HO_WIN_RXQUAL_STR HO_WIN_STR "Received Quality Averaging\n"
+#define HO_PBUDGET_STR HANDOVER_STR "Power Budget\n"
+
 DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd,
       "handover window rxlev averaging <1-10>",
+	HO_WIN_RXLEV_STR
 	"How many RxLev measurements are used for averaging")
 {
 	gsmnet->handover.win_rxlev_avg = atoi(argv[0]);
@@ -1065,6 +1113,7 @@
 
 DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd,
       "handover window rxqual averaging <1-10>",
+	HO_WIN_RXQUAL_STR
 	"How many RxQual measurements are used for averaging")
 {
 	gsmnet->handover.win_rxqual_avg = atoi(argv[0]);
@@ -1073,6 +1122,7 @@
 
 DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd,
       "handover window rxlev neighbor averaging <1-10>",
+	HO_WIN_RXLEV_STR
 	"How many RxQual measurements are used for averaging")
 {
 	gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]);
@@ -1081,6 +1131,7 @@
 
 DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd,
       "handover power budget interval <1-99>",
+	HO_PBUDGET_STR
 	"How often to check if we have a better cell (SACCH frames)")
 {
 	gsmnet->handover.pwr_interval = atoi(argv[0]);
@@ -1089,6 +1140,7 @@
 
 DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd,
       "handover power budget hysteresis <0-999>",
+	HO_PBUDGET_STR
 	"How many dB does a neighbor to be stronger to become a HO candidate")
 {
 	gsmnet->handover.pwr_hysteresis = atoi(argv[0]);
@@ -1097,6 +1149,7 @@
 
 DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd,
       "handover maximum distance <0-9999>",
+	HANDOVER_STR
 	"How big is the maximum timing advance before HO is forced")
 {
 	gsmnet->handover.max_distance = atoi(argv[0]);
@@ -1107,6 +1160,7 @@
     DEFUN(cfg_net_T##number,					\
       cfg_net_T##number##_cmd,					\
       "timer t" #number  " <0-65535>",				\
+      "Configure GSM Timers\n"					\
       doc)							\
 {								\
 	int value = atoi(argv[0]);				\
@@ -1138,7 +1192,8 @@
 DEFUN(cfg_bts,
       cfg_bts_cmd,
       "bts BTS_NR",
-      "Select a BTS to configure\n")
+      "Select a BTS to configure\n"
+	"BTS Number\n")
 {
 	int bts_nr = atoi(argv[0]);
 	struct gsm_bts *bts;
@@ -1301,9 +1356,13 @@
 	return CMD_SUCCESS;
 }
 
+#define OML_STR	"Organization & Maintenance Link\n"
+#define IPA_STR "ip.access Specific Options\n"
+
 DEFUN(cfg_bts_stream_id,
       cfg_bts_stream_id_cmd,
       "oml ip.access stream_id <0-255>",
+	OML_STR IPA_STR
       "Set the ip.access Stream ID of the OML link of this BTS\n")
 {
 	struct gsm_bts *bts = vty->index;
@@ -1319,10 +1378,12 @@
 	return CMD_SUCCESS;
 }
 
+#define OML_E1_STR OML_STR "E1 Line\n"
 
 DEFUN(cfg_bts_oml_e1,
       cfg_bts_oml_e1_cmd,
       "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+	OML_E1_STR
       "E1 interface to be used for OML\n")
 {
 	struct gsm_bts *bts = vty->index;
@@ -1336,6 +1397,7 @@
 DEFUN(cfg_bts_oml_e1_tei,
       cfg_bts_oml_e1_tei_cmd,
       "oml e1 tei <0-63>",
+	OML_E1_STR
       "Set the TEI to be used for OML")
 {
 	struct gsm_bts *bts = vty->index;
@@ -1347,7 +1409,9 @@
 
 DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd,
       "channel allocator (ascending|descending)",
-      "Should the channel allocator allocate in reverse TRX order?")
+	"Channnel Allocator\n" "Channel Allocator\n"
+	"Allocate Timeslots and Transceivers in ascending order\n"
+	"Allocate Timeslots and Transceivers in descending order\n")
 {
 	struct gsm_bts *bts = vty->index;
 
@@ -1359,9 +1423,12 @@
 	return CMD_SUCCESS;
 }
 
+#define RACH_STR "Random Access Control Channel\n"
+
 DEFUN(cfg_bts_rach_tx_integer,
       cfg_bts_rach_tx_integer_cmd,
       "rach tx integer <0-15>",
+	RACH_STR
       "Set the raw tx integer value in RACH Control parameters IE")
 {
 	struct gsm_bts *bts = vty->index;
@@ -1372,6 +1439,7 @@
 DEFUN(cfg_bts_rach_max_trans,
       cfg_bts_rach_max_trans_cmd,
       "rach max transmission (1|2|4|7)",
+	RACH_STR
       "Set the maximum number of RACH burst transmissions")
 {
 	struct gsm_bts *bts = vty->index;
@@ -1379,6 +1447,30 @@
 	return CMD_SUCCESS;
 }
 
+#define NM_STR "Network Management\n"
+
+DEFUN(cfg_bts_rach_nm_b_thresh,
+      cfg_bts_rach_nm_b_thresh_cmd,
+      "rach nm busy threshold <0-255>",
+	RACH_STR NM_STR
+      "Set the NM Busy Threshold in dB")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_b_thresh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_nm_ldavg,
+      cfg_bts_rach_nm_ldavg_cmd,
+      "rach nm load average <0-65535>",
+	RACH_STR NM_STR
+      "Set the NM Loadaverage Slots value")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_ldavg_slots = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd,
       "cell barred (0|1)",
       "Should this cell be barred from access?")
@@ -1448,13 +1540,17 @@
 	return CMD_SUCCESS;
 }
 
+#define GPRS_TEXT	"GPRS Packet Network\n"
+
 DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd,
-	"gprs cell bvci <0-65535>",
+	"gprs cell bvci <2-65535>",
+	GPRS_TEXT
+	"GPRS Cell Settings\n"
 	"GPRS BSSGP VC Identifier")
 {
 	struct gsm_bts *bts = vty->index;
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1466,11 +1562,12 @@
 
 DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd,
 	"gprs nsei <0-65535>",
+	GPRS_TEXT
 	"GPRS NS Entity Identifier")
 {
 	struct gsm_bts *bts = vty->index;
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1480,15 +1577,19 @@
 	return CMD_SUCCESS;
 }
 
+#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \
+		"NSVC Logical Number\n"
 
 DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd,
 	"gprs nsvc <0-1> nsvci <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"NS Virtual Connection Identifier\n"
 	"GPRS NS VC Identifier")
 {
 	struct gsm_bts *bts = vty->index;
 	int idx = atoi(argv[0]);
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1500,12 +1601,13 @@
 
 DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd,
 	"gprs nsvc <0-1> local udp port <0-65535>",
+	GPRS_TEXT NSVC_TEXT
 	"GPRS NS Local UDP Port")
 {
 	struct gsm_bts *bts = vty->index;
 	int idx = atoi(argv[0]);
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1517,12 +1619,13 @@
 
 DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd,
 	"gprs nsvc <0-1> remote udp port <0-65535>",
+	GPRS_TEXT NSVC_TEXT
 	"GPRS NS Remote UDP Port")
 {
 	struct gsm_bts *bts = vty->index;
 	int idx = atoi(argv[0]);
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1534,13 +1637,14 @@
 
 DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd,
 	"gprs nsvc <0-1> remote ip A.B.C.D",
+	GPRS_TEXT NSVC_TEXT
 	"GPRS NS Remote IP Address")
 {
 	struct gsm_bts *bts = vty->index;
 	int idx = atoi(argv[0]);
 	struct in_addr ia;
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1551,13 +1655,63 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd,
+	"gprs ns timer " NS_TIMERS " <0-255>",
+	GPRS_TEXT "Network Service\n"
+	"Network Service Timer\n"
+	NS_TIMERS_HELP "Timer Value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer))
+		return CMD_WARNING;
+
+	bts->gprs.nse.timer[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)"
+#define BSSGP_TIMERS_HELP	""
+
+DEFUN(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd,
+	"gprs cell timer " BSSGP_TIMERS " <0-255>",
+	GPRS_TEXT "Cell / BSSGP\n"
+	"Cell/BSSGP Timer\n"
+	BSSGP_TIMERS_HELP "Timer Value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer))
+		return CMD_WARNING;
+
+	bts->gprs.cell.timer[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd,
 	"gprs routing area <0-255>",
+	GPRS_TEXT
 	"GPRS Routing Area Code")
 {
 	struct gsm_bts *bts = vty->index;
 
-	if (!bts->gprs.enabled) {
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
 		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -1567,22 +1721,28 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_bts_gprs_enabled, cfg_bts_gprs_enabled_cmd,
-	"gprs enabled <0-1>",
-	"GPRS Enabled on this BTS")
+DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd,
+	"gprs mode (none|gprs|egprs)",
+	GPRS_TEXT
+	"GPRS Mode for this BTS\n"
+	"GPRS Disabled on this BTS\n"
+	"GPRS Enabled on this BTS\n"
+	"EGPRS (EDGE) Enabled on this BTS\n")
 {
 	struct gsm_bts *bts = vty->index;
 
-	bts->gprs.enabled = atoi(argv[0]);
+	bts->gprs.mode = bts_gprs_mode_parse(argv[0]);
 
 	return CMD_SUCCESS;
 }
 
+#define TRX_TEXT "Radio Transceiver\n"
 
 /* per TRX configuration */
 DEFUN(cfg_trx,
       cfg_trx_cmd,
       "trx TRX_NR",
+	TRX_TEXT
       "Select a TRX to configure")
 {
 	int trx_nr = atoi(argv[0]);
@@ -1756,6 +1916,8 @@
 	return CMD_SUCCESS;
 }
 
+extern int bsc_vty_init_extra(struct gsm_network *net);
+
 int bsc_vty_init(struct gsm_network *net)
 {
 	gsmnet = net;
@@ -1763,18 +1925,18 @@
 	cmd_init(1);
 	vty_init();
 
-	install_element(VIEW_NODE, &show_net_cmd);
-	install_element(VIEW_NODE, &show_bts_cmd);
-	install_element(VIEW_NODE, &show_trx_cmd);
-	install_element(VIEW_NODE, &show_ts_cmd);
-	install_element(VIEW_NODE, &show_lchan_cmd);
+	install_element_ve(&show_net_cmd);
+	install_element_ve(&show_bts_cmd);
+	install_element_ve(&show_trx_cmd);
+	install_element_ve(&show_ts_cmd);
+	install_element_ve(&show_lchan_cmd);
+	install_element_ve(&show_lchan_summary_cmd);
 
-	install_element(VIEW_NODE, &show_e1drv_cmd);
-	install_element(VIEW_NODE, &show_e1line_cmd);
-	install_element(VIEW_NODE, &show_e1ts_cmd);
+	install_element_ve(&show_e1drv_cmd);
+	install_element_ve(&show_e1line_cmd);
+	install_element_ve(&show_e1ts_cmd);
 
-	install_element(VIEW_NODE, &show_paging_cmd);
-	install_element(VIEW_NODE, &show_stats_cmd);
+	install_element_ve(&show_paging_cmd);
 
 	openbsc_vty_add_cmds();
 
@@ -1826,15 +1988,19 @@
 	install_element(BTS_NODE, &cfg_bts_challoc_cmd);
 	install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
 	install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
 	install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
 	install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd);
 	install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
 	install_element(BTS_NODE, &cfg_bts_per_loc_upd_cmd);
 	install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
 	install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
-	install_element(BTS_NODE, &cfg_bts_gprs_enabled_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd);
 	install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
 	install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd);
 	install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
 	install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
 	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
diff --git a/openbsc/src/vty_interface_cmds.c b/openbsc/src/vty_interface_cmds.c
index d494584..dd5e108 100644
--- a/openbsc/src/vty_interface_cmds.c
+++ b/openbsc/src/vty_interface_cmds.c
@@ -30,6 +30,8 @@
 
 #include <stdlib.h>
 
+#define LOGGING_STR	"Configure log message to this terminal\n"
+
 static void _vty_output(struct log_target *tgt, const char *line)
 {
 	struct vty *vty = tgt->tgt_vty.vty;
@@ -55,6 +57,7 @@
 DEFUN(enable_logging,
       enable_logging_cmd,
       "logging enable",
+	LOGGING_STR
       "Enables logging to this vty\n")
 {
 	struct telnet_connection *conn;
@@ -76,6 +79,7 @@
 DEFUN(logging_fltr_imsi,
       logging_fltr_imsi_cmd,
       "logging filter imsi IMSI",
+	LOGGING_STR
       "Print all messages related to a IMSI\n")
 {
 	struct telnet_connection *conn;
@@ -93,6 +97,7 @@
 DEFUN(logging_fltr_all,
       logging_fltr_all_cmd,
       "logging filter all <0-1>",
+	LOGGING_STR
       "Print all messages to the console\n")
 {
 	struct telnet_connection *conn;
@@ -110,6 +115,7 @@
 DEFUN(logging_use_clr,
       logging_use_clr_cmd,
       "logging color <0-1>",
+	LOGGING_STR
       "Use color for printing messages\n")
 {
 	struct telnet_connection *conn;
@@ -127,6 +133,7 @@
 DEFUN(logging_prnt_timestamp,
       logging_prnt_timestamp_cmd,
       "logging timestamp <0-1>",
+	LOGGING_STR
       "Print the timestamp of each message\n")
 {
 	struct telnet_connection *conn;
@@ -142,12 +149,48 @@
 }
 
 /* FIXME: those have to be kept in sync with the log levels and categories */
-#define VTY_DEBUG_CATEGORIES "(rll|cc|mm|rr|rsl|nm|sms|pag|mncc|inp|mi|mib|mux|meas|sccp|msc|mgcp|ho|db|ref)"
+#define VTY_DEBUG_CATEGORIES "(rll|cc|mm|rr|rsl|nm|sms|pag|mncc|inp|mi|mib|mux|meas|sccp|msc|mgcp|ho|db|ref|gprs|ns|bssgp|all)"
+#define CATEGORIES_HELP	\
+	"A-bis Radio Link Layer (RLL)\n"			\
+	"Layer3 Call Control (CC)\n"				\
+	"Layer3 Mobility Management (MM)\n"			\
+	"Layer3 Radio Resource (RR)\n"				\
+	"A-bis Radio Signalling Link (RSL)\n"			\
+	"A-bis Network Management / O&M (NM/OML)\n"		\
+	"Layer3 Short Messagaging Service (SMS)\n"		\
+	"Paging Subsystem\n"					\
+	"MNCC API for Call Control application\n"		\
+	"A-bis Input Subsystem\n"				\
+	"A-bis Input Driver for Signalling\n"			\
+	"A-bis Input Driver for B-Channel (voice data)\n"	\
+	"A-bis B-Channel / Sub-channel Multiplexer\n"		\
+	"Radio Measurements\n"					\
+	"SCCP\n"						\
+	"Mobile Switching Center\n"				\
+	"Media Gateway Control Protocol\n"			\
+	"Hand-over\n"						\
+	"Database Layer\n"					\
+	"Reference Counting\n"					\
+	"GPRS Core\n"						\
+	"GPRS Network Service (NS)\n"				\
+	"GPRS BSS Gateway Protocol (BSSGP)\n"			\
+	"Global setting for all subsytems\n"
+
 #define VTY_DEBUG_LEVELS "(everything|debug|info|notice|error|fatal)"
+#define LEVELS_HELP	\
+	"Log simply everything\n"				\
+	"Log debug messages and higher levels\n"		\
+	"Log informational messages and higher levels\n"	\
+	"Log noticable messages and higher levels\n"		\
+	"Log error messages and higher levels\n"		\
+	"Log only fatal messages\n"
 DEFUN(logging_level,
       logging_level_cmd,
       "logging level " VTY_DEBUG_CATEGORIES " " VTY_DEBUG_LEVELS,
-      "Set the log level for a specified category\n")
+      LOGGING_STR
+      "Set the log level for a specified category\n"
+      CATEGORIES_HELP
+      LEVELS_HELP)
 {
 	struct telnet_connection *conn;
 	int category = log_parse_category(argv[0]);
@@ -159,13 +202,19 @@
 		return CMD_WARNING;
 	}
 
-	if (category < 0) {
-		vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
+	if (level < 0) {
+		vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	if (level < 0) {
-		vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
+	/* Check for special case where we want to set global log level */
+	if (!strcmp(argv[0], "all")) {
+		log_set_log_level(conn->dbg, level);
+		return CMD_SUCCESS;
+	}
+
+	if (category < 0) {
+		vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
@@ -178,6 +227,7 @@
 DEFUN(logging_set_category_mask,
       logging_set_category_mask_cmd,
       "logging set log mask MASK",
+	LOGGING_STR
       "Decide which categories to output.\n")
 {
 	struct telnet_connection *conn;
@@ -192,26 +242,10 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(logging_set_log_level,
-      logging_set_log_level_cmd,
-      "logging set log level <0-8>",
-      "Set the global log level. The value 0 implies no filtering.\n")
-{
-	struct telnet_connection *conn;
-
-	conn = (struct telnet_connection *) vty->priv;
-	if (!conn->dbg) {
-		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-
-	log_set_log_level(conn->dbg, atoi(argv[0]));
-	return CMD_SUCCESS;
-}
-
 DEFUN(diable_logging,
       disable_logging_cmd,
       "logging disable",
+	LOGGING_STR
       "Disables logging to this vty\n")
 {
 	struct telnet_connection *conn;
@@ -228,16 +262,75 @@
 	return CMD_SUCCESS;
 }
 
+static void vty_print_logtarget(struct vty *vty, const struct log_info *info,
+				const struct log_target *tgt)
+{
+	unsigned int i;
+
+	vty_out(vty, " Global Loglevel: %s%s",
+		log_level_str(tgt->loglevel), VTY_NEWLINE);
+	vty_out(vty, " Use color: %s, Print Timestamp: %s%s",
+		tgt->use_color ? "On" : "Off",
+		tgt->print_timestamp ? "On" : "Off", VTY_NEWLINE);
+
+	vty_out(vty, " Log Level specific information:%s", VTY_NEWLINE);
+
+	for (i = 0; i < info->num_cat; i++) {
+		const struct log_category *cat = &tgt->categories[i];
+		vty_out(vty, "  %-10s %-10s %-8s %s%s",
+			info->cat[i].name+1, log_level_str(cat->loglevel),
+			cat->enabled ? "Enabled" : "Disabled",
+ 			info->cat[i].description,
+			VTY_NEWLINE);
+	}
+}
+
+#define SHOW_LOG_STR "Show current logging configuration\n"
+
+DEFUN(show_logging_vty,
+      show_logging_vty_cmd,
+      "show logging vty",
+	SHOW_STR SHOW_LOG_STR
+	"Show current logging configuration for this vty\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty_print_logtarget(vty, &log_info, conn->dbg);
+
+	return CMD_SUCCESS;
+}
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
+{
+	vty_out(vty, "Channel Requests        : %lu total, %lu no channel%s",
+		counter_get(net->stats.chreq.total),
+		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
+	vty_out(vty, "Channel Failures        : %lu rf_failures, %lu rll failures%s",
+		counter_get(net->stats.chan.rf_fail),
+		counter_get(net->stats.chan.rll_err), VTY_NEWLINE);
+	vty_out(vty, "Paging                  : %lu attempted, %lu complete, %lu expired%s",
+		counter_get(net->stats.paging.attempted),
+		counter_get(net->stats.paging.completed),
+		counter_get(net->stats.paging.expired), VTY_NEWLINE);
+	vty_out(vty, "BTS failures            : %lu OML, %lu RSL%s",
+		counter_get(net->stats.bts.oml_fail),
+		counter_get(net->stats.bts.rsl_fail), VTY_NEWLINE);
+}
+
 void openbsc_vty_add_cmds()
 {
-	install_element(VIEW_NODE, &enable_logging_cmd);
-	install_element(VIEW_NODE, &disable_logging_cmd);
-	install_element(VIEW_NODE, &logging_fltr_imsi_cmd);
-	install_element(VIEW_NODE, &logging_fltr_all_cmd);
-	install_element(VIEW_NODE, &logging_use_clr_cmd);
-	install_element(VIEW_NODE, &logging_prnt_timestamp_cmd);
-	install_element(VIEW_NODE, &logging_set_category_mask_cmd);
-	install_element(VIEW_NODE, &logging_level_cmd);
-	install_element(VIEW_NODE, &logging_set_log_level_cmd);
-
+	install_element_ve(&enable_logging_cmd);
+	install_element_ve(&disable_logging_cmd);
+	install_element_ve(&logging_fltr_imsi_cmd);
+	install_element_ve(&logging_fltr_all_cmd);
+	install_element_ve(&logging_use_clr_cmd);
+	install_element_ve(&logging_prnt_timestamp_cmd);
+	install_element_ve(&logging_set_category_mask_cmd);
+	install_element_ve(&logging_level_cmd);
+	install_element_ve(&show_logging_vty_cmd);
 }
diff --git a/openbsc/src/vty_interface_layer3.c b/openbsc/src/vty_interface_layer3.c
index b824c3d..1b2adbb 100644
--- a/openbsc/src/vty_interface_layer3.c
+++ b/openbsc/src/vty_interface_layer3.c
@@ -41,6 +41,7 @@
 #include <osmocore/talloc.h>
 #include <openbsc/signal.h>
 #include <openbsc/debug.h>
+#include <openbsc/vty.h>
 
 static struct gsm_network *gsmnet;
 
@@ -57,7 +58,7 @@
 
 static struct buffer *argv_to_buffer(int argc, const char *argv[], int base)
 {
-	struct buffer *b = buffer_new(1024);
+	struct buffer *b = buffer_new(NULL, 1024);
 	int i;
 
 	if (!b)
@@ -502,21 +503,57 @@
 	return 0;
 }
 
+DEFUN(show_stats,
+      show_stats_cmd,
+      "show statistics",
+	SHOW_STR "Display network statistics\n")
+{
+	struct gsm_network *net = gsmnet;
+
+	openbsc_vty_print_statistics(vty, net);
+	vty_out(vty, "Location Update         : %lu attach, %lu normal, %lu periodic%s",
+		counter_get(net->stats.loc_upd_type.attach),
+		counter_get(net->stats.loc_upd_type.normal),
+		counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE);
+	vty_out(vty, "IMSI Detach Indications : %lu%s",
+		counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE);
+	vty_out(vty, "Location Update Response: %lu accept, %lu reject%s",
+		counter_get(net->stats.loc_upd_resp.accept),
+		counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE);
+	vty_out(vty, "Handover                : %lu attempted, %lu no_channel, %lu timeout, "
+		"%lu completed, %lu failed%s",
+		counter_get(net->stats.handover.attempted),
+		counter_get(net->stats.handover.no_channel),
+		counter_get(net->stats.handover.timeout),
+		counter_get(net->stats.handover.completed),
+		counter_get(net->stats.handover.failed), VTY_NEWLINE);
+	vty_out(vty, "SMS MO                  : %lu submitted, %lu no receiver%s",
+		counter_get(net->stats.sms.submitted),
+		counter_get(net->stats.sms.no_receiver), VTY_NEWLINE);
+	vty_out(vty, "SMS MT                  : %lu delivered, %lu no memory, %lu other error%s",
+		counter_get(net->stats.sms.delivered),
+		counter_get(net->stats.sms.rp_err_mem),
+		counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+
 int bsc_vty_init_extra(struct gsm_network *net)
 {
 	gsmnet = net;
 
 	register_signal_handler(SS_SCALL, scall_cbfn, NULL);
 
-	install_element(VIEW_NODE, &show_subscr_cmd);
-	install_element(VIEW_NODE, &show_subscr_cache_cmd);
+	install_element_ve(&show_subscr_cmd);
+	install_element_ve(&show_subscr_cache_cmd);
 
-	install_element(VIEW_NODE, &sms_send_pend_cmd);
+	install_element_ve(&sms_send_pend_cmd);
 
-	install_element(VIEW_NODE, &subscriber_send_sms_cmd);
-	install_element(VIEW_NODE, &subscriber_silent_sms_cmd);
-	install_element(VIEW_NODE, &subscriber_silent_call_start_cmd);
-	install_element(VIEW_NODE, &subscriber_silent_call_stop_cmd);
+	install_element_ve(&subscriber_send_sms_cmd);
+	install_element_ve(&subscriber_silent_sms_cmd);
+	install_element_ve(&subscriber_silent_call_start_cmd);
+	install_element_ve(&subscriber_silent_call_stop_cmd);
+	install_element_ve(&show_stats_cmd);
 
 	install_element(CONFIG_NODE, &cfg_subscr_cmd);
 	install_node(&subscr_node, dummy_config_write);
diff --git a/openbsc/tests/sccp/sccp_test.c b/openbsc/tests/sccp/sccp_test.c
index 0c2adc8..31eb47f 100644
--- a/openbsc/tests/sccp/sccp_test.c
+++ b/openbsc/tests/sccp/sccp_test.c
@@ -264,6 +264,10 @@
 0x10, 0x01, 0x07, 
 0x94, 0x01, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00 };
 
+static const u_int8_t proto_err[] = {
+0x0f, 0x0c, 0x04, 0x00, 0x00,
+};
+
 static const struct sccp_parse_header_result parse_result[] = {
 	{
 		.msg_type	= SCCP_MSG_TYPE_IT,
@@ -287,6 +291,21 @@
 		.input		= it_test,
 		.input_len	= sizeof(it_test),
 	},
+	{
+		.msg_type	= SCCP_MSG_TYPE_ERR,
+		.wanted_len	= 0,
+		.src_ssn	= -1,
+		.dst_ssn	= -1,
+		.has_src_ref	= 0,
+		.has_dst_ref	= 1,
+		.dst_ref	= {
+			.octet1 = 0x0c,
+			.octet2 = 0x04,
+			.octet3 = 0x00,
+		},
+		.input		= proto_err,
+		.input_len	= sizeof(proto_err),
+	},
 };
 
 
@@ -777,7 +796,7 @@
 
 		memset(&result, 0, sizeof(result));
 		if (sccp_parse_header(msg, &result) != 0) {
-			fprintf(stderr, "Failed to parse test: %d\n", current_test);
+			fprintf(stderr, "Failed to sccp parse test: %d\n", current_test);
 		} else {
 			if (parse_result[current_test].wanted_len != result.data_len) {
 				fprintf(stderr, "Unexpected data length.\n");
diff --git a/wireshark/abis_oml.patch b/wireshark/abis_oml.patch
index 9f06b4d..4013211 100644
--- a/wireshark/abis_oml.patch
+++ b/wireshark/abis_oml.patch
@@ -1,8 +1,21 @@
-Index: wireshark/epan/dissectors/Makefile.common
-===================================================================
---- wireshark.orig/epan/dissectors/Makefile.common
-+++ wireshark/epan/dissectors/Makefile.common
-@@ -474,6 +474,7 @@
+From 5857518be87641fdab45e593bc9fd5ef5595e619 Mon Sep 17 00:00:00 2001
+From: Holger Hans Peter Freyther <zecke@selfish.org>
+Date: Mon, 19 Apr 2010 13:23:51 +0800
+Subject: [PATCH 1/2] Add the Abis OML patch.
+
+---
+ epan/dissectors/Makefile.common       |    1 +
+ epan/dissectors/packet-gsm_abis_oml.c | 1382 +++++++++++++++++++++++++++++++++
+ epan/dissectors/packet-gsm_abis_oml.h |  787 +++++++++++++++++++
+ 3 files changed, 2170 insertions(+), 0 deletions(-)
+ create mode 100644 epan/dissectors/packet-gsm_abis_oml.c
+ create mode 100644 epan/dissectors/packet-gsm_abis_oml.h
+
+diff --git a/epan/dissectors/Makefile.common b/epan/dissectors/Makefile.common
+index dbc3726..98dcdc3 100644
+--- a/epan/dissectors/Makefile.common
++++ b/epan/dissectors/Makefile.common
+@@ -481,6 +481,7 @@ DISSECTOR_SRC = \
  	packet-gsm_a_gm.c		\
  	packet-gsm_a_rp.c		\
  	packet-gsm_a_rr.c	\
@@ -12,7 +25,7 @@
  	packet-gsm_bssmap_le.c	\
 diff --git a/epan/dissectors/packet-gsm_abis_oml.c b/epan/dissectors/packet-gsm_abis_oml.c
 new file mode 100644
-index 0000000..2de9dca
+index 0000000..fa46ab5
 --- /dev/null
 +++ b/epan/dissectors/packet-gsm_abis_oml.c
 @@ -0,0 +1,1382 @@
@@ -1398,11 +1411,12 @@
 +	abis_oml_handle = create_dissector_handle(dissect_abis_oml, proto_abis_oml);
 +	dissector_add("lapd.gsm.sapi", LAPD_GSM_SAPI_OM_PROC, abis_oml_handle);
 +}
-Index: wireshark/epan/dissectors/packet-gsm_abis_oml.h
-===================================================================
+diff --git a/epan/dissectors/packet-gsm_abis_oml.h b/epan/dissectors/packet-gsm_abis_oml.h
+new file mode 100644
+index 0000000..d523e96
 --- /dev/null
-+++ wireshark/epan/dissectors/packet-gsm_abis_oml.h
-@@ -0,0 +1,786 @@
++++ b/epan/dissectors/packet-gsm_abis_oml.h
+@@ -0,0 +1,787 @@
 +/* GSM Network Management messages on the A-bis interface
 + * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
 +
@@ -2190,3 +2204,6 @@
 +};
 +
 +#endif /* _NM_H */
+-- 
+1.7.0.1
+
diff --git a/wireshark/rsl-ipaccess.patch b/wireshark/rsl-ipaccess.patch
index 36c09c5..29220b8 100644
--- a/wireshark/rsl-ipaccess.patch
+++ b/wireshark/rsl-ipaccess.patch
@@ -1,16 +1,25 @@
-Index: wireshark/epan/dissectors/packet-rsl.c
-===================================================================
---- wireshark.orig/epan/dissectors/packet-rsl.c	2009-10-21 23:03:41.000000000 +0200
-+++ wireshark/epan/dissectors/packet-rsl.c	2009-10-22 10:02:51.000000000 +0200
+From 8f35d623641dbba90e6186604c11e892bf515ecc Mon Sep 17 00:00:00 2001
+From: Holger Hans Peter Freyther <zecke@selfish.org>
+Date: Mon, 19 Apr 2010 13:32:58 +0800
+Subject: [PATCH 2/2] RSL patch
+
+---
+ epan/dissectors/packet-rsl.c |  522 +++++++++++++++++++++++++++++++++++++++++-
+ 1 files changed, 515 insertions(+), 7 deletions(-)
+
+diff --git a/epan/dissectors/packet-rsl.c b/epan/dissectors/packet-rsl.c
+index b10a671..a455cf3 100644
+--- a/epan/dissectors/packet-rsl.c
++++ b/epan/dissectors/packet-rsl.c
 @@ -2,6 +2,7 @@
   * Routines for Radio Signalling Link (RSL) dissection.
   *
   * Copyright 2007, Anders Broman <anders.broman@ericsson.com>
 + * Copyright 2009, Harald Welte <laforge@gnumonks.org>
   *
-  * $Id: packet-rsl.c 29944 2009-09-16 13:39:37Z morriss $
+  * $Id$
   *
-@@ -44,6 +45,8 @@
+@@ -42,6 +43,8 @@
  #include <epan/lapd_sapi.h>
  
  #include "packet-gsm_a_common.h"
@@ -19,7 +28,7 @@
  
  /* Initialize the protocol and registered fields */
  static int proto_rsl		= -1;
-@@ -117,6 +120,24 @@
+@@ -115,6 +118,24 @@ static int hf_rsl_emlpp_prio		= -1;
  static int hf_rsl_rtd				= -1;
  static int hf_rsl_delay_ind			= -1;
  static int hf_rsl_tfo				= -1;
@@ -44,7 +53,7 @@
  
  /* Initialize the subtree pointers */
  static int ett_rsl = -1;
-@@ -174,6 +195,15 @@
+@@ -172,6 +193,15 @@ static int ett_ie_cause = -1;
  static int ett_ie_meas_res_no = -1;
  static int ett_ie_message_id = -1;
  static int ett_ie_sys_info_type = -1;
@@ -60,7 +69,7 @@
  
  proto_tree *top_tree;
  dissector_handle_t gsm_a_ccch_handle;
-@@ -209,8 +239,11 @@
+@@ -207,8 +237,11 @@ static const value_string rsl_msg_disc_vals[] = {
  	{  0x06,		"Common Channel Management messages" },
  	{  0x08,		"TRX Management messages" },
  	{  0x16,		"Location Services messages" },
@@ -72,7 +81,7 @@
  /*
   * 9.2 MESSAGE TYPE
   */
-@@ -277,6 +310,49 @@
+@@ -275,6 +308,49 @@ static const value_string rsl_msg_disc_vals[] = {
  	/* 	0 1 - - - - - - Location Services messages: */
  #define RSL_MSG_LOC_INF					65	/* 8.7.1 */
  
@@ -90,16 +99,16 @@
 +#define RSL_MSG_TYPE_IPAC_PDCH_DEACT_ACK 0x4c
 +#define RSL_MSG_TYPE_IPAC_PDCH_DEACT_NACK 0x4d
 +
-+#define RSL_MSG_TYPE_IPAC_BIND		0x70
-+#define RSL_MSG_TYPE_IPAC_BIND_ACK	0x71
-+#define RSL_MSG_TYPE_IPAC_BIND_NACK	0x72
-+#define RSL_MSG_TYPE_IPAC_CONNECT	0x73
-+#define RSL_MSG_TYPE_IPAC_CONNECT_ACK	0x74
-+#define RSL_MSG_TYPE_IPAC_CONNECT_NACK	0x75
-+#define RSL_MSG_TYPE_IPAC_DISC_IND	0x76
-+#define RSL_MSG_TYPE_IPAC_DISC		0x77
-+#define RSL_MSG_TYPE_IPAC_DISC_ACK	0x78
-+#define RSL_MSG_TYPE_IPAC_DISC_NACK	0x79
++#define RSL_MSG_TYPE_IPAC_CRCX		0x70
++#define RSL_MSG_TYPE_IPAC_CRCX_ACK	0x71
++#define RSL_MSG_TYPE_IPAC_CRCX_NACK	0x72
++#define RSL_MSG_TYPE_IPAC_MDCX		0x73
++#define RSL_MSG_TYPE_IPAC_MDCX_ACK	0x74
++#define RSL_MSG_TYPE_IPAC_MDCX_NACK	0x75
++#define RSL_MSG_TYPE_IPAC_DLCX_IND	0x76
++#define RSL_MSG_TYPE_IPAC_DLCX		0x77
++#define RSL_MSG_TYPE_IPAC_DLCX_ACK	0x78
++#define RSL_MSG_TYPE_IPAC_DLCX_NACK	0x79
 +
 +#define RSL_IE_IPAC_SRTP_CONFIG		0xe0
 +#define RSL_IE_IPAC_PROXY_UDP		0xe1
@@ -122,7 +131,7 @@
  
  static const value_string rsl_msg_type_vals[] = {
  	  /* 	0 0 0 0 - - - - Radio Link Layer Management messages: */
-@@ -339,6 +415,26 @@
+@@ -337,6 +413,26 @@ static const value_string rsl_msg_type_vals[] = {
  	{  0x3f,	"TFO MODification REQuest" },					/* 8.4.31 */
  	/* 	0 1 - - - - - - Location Services messages: */
  	{  0x41,	"Location Information" },						/* 8.7.1 */
@@ -149,7 +158,7 @@
  	{ 0,		NULL }
  };
  
-@@ -372,10 +468,10 @@ static const value_string rsl_msg_type_vals[] = {
+@@ -370,10 +466,10 @@ static const value_string rsl_msg_type_vals[] = {
  #define RSL_IE_MESSAGE_ID		28
  
  #define RSL_IE_SYS_INFO_TYPE	30
@@ -164,7 +173,7 @@
  #define RSL_IE_FULL_IMM_ASS_INF			35
  #define RSL_IE_SMSCB_INF				36
  #define RSL_IE_FULL_MS_TIMING_OFFSET	37
-@@ -478,6 +574,24 @@
+@@ -476,6 +572,24 @@ static const value_string rsl_ie_type_vals[] = {
  			Not used
  
  	*/
@@ -189,7 +198,7 @@
  	{ 0,			NULL }
  };
  
-@@ -514,6 +628,96 @@
+@@ -512,6 +626,96 @@ static const value_string rsl_ch_no_Cbits_vals[] = {
  	{ 0,			NULL }
  };
  
@@ -286,7 +295,7 @@
  /* 9.3.1 Channel number			9.3.1	M TV 2 */
  static int
  dissect_rsl_ie_ch_no(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, gboolean is_mandatory)
-@@ -2044,7 +2248,6 @@
+@@ -2042,7 +2246,6 @@ dissect_rsl_ie_err_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int
  	proto_item_set_len(ti, length+2);
  
  	proto_tree_add_item(ie_tree, hf_rsl_ie_length, tvb, offset, 1, FALSE);
@@ -294,7 +303,7 @@
  
  	/* Received Message */
  	offset = dissct_rsl_msg(tvb, pinfo, ie_tree, offset);
-@@ -2909,12 +3112,183 @@
+@@ -2907,12 +3110,184 @@ dissect_rsl_ie_tfo_transp_cont(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree
  }
  
  static int
@@ -310,16 +319,16 @@
 +
 +#if 0
 +	switch (msg_type) {
-+	case RSL_MSG_TYPE_IPAC_BIND:
-+	case RSL_MSG_TYPE_IPAC_BIND_ACK:
-+	case RSL_MSG_TYPE_IPAC_BIND_NACK:
-+	case RSL_MSG_TYPE_IPAC_CONNECT:
-+	case RSL_MSG_TYPE_IPAC_CONNECT_ACK:
-+	case RSL_MSG_TYPE_IPAC_CONNECT_NACK:
-+	case RSL_MSG_TYPE_IPAC_DISC_IND:
-+	case RSL_MSG_TYPE_IPAC_DISC:
-+	case RSL_MSG_TYPE_IPAC_DISC_ACK:
-+	case RSL_MSG_TYPE_IPAC_DISC_NACK:
++	case RSL_MSG_TYPE_IPAC_CRCX:
++	case RSL_MSG_TYPE_IPAC_CRCX_ACK:
++	case RSL_MSG_TYPE_IPAC_CRCX_NACK:
++	case RSL_MSG_TYPE_IPAC_MDCX:
++	case RSL_MSG_TYPE_IPAC_MDCX_ACK:
++	case RSL_MSG_TYPE_IPAC_MDCX_NACK:
++	case RSL_MSG_TYPE_IPAC_DLCX_IND:
++	case RSL_MSG_TYPE_IPAC_DLCX:
++	case RSL_MSG_TYPE_IPAC_DLCX_ACK:
++	case RSL_MSG_TYPE_IPAC_DLCX_NACK:
 +	case RSL_MSG_TYPE_IPAC_PDCH_ACT:
 +	case RSL_MSG_TYPE_IPAC_PDCH_ACT_ACK:
 +	case RSL_MSG_TYPE_IPAC_PDCH_ACT_NACK:
@@ -449,7 +458,7 @@
 +	}
 +
 +	switch (msg_type) {
-+	case RSL_MSG_TYPE_IPAC_BIND_ACK:
++	case RSL_MSG_TYPE_IPAC_CRCX_ACK:
 +		/* Notify the RTP and RTCP dissectors about a new RTP stream */
 +		src_addr.type = AT_IPv4;
 +		src_addr.len = 4;
@@ -480,7 +489,7 @@
  	offset++;
  
  	switch (msg_type){
-@@ -3482,6 +3856,18 @@
+@@ -3480,6 +3855,18 @@ dissct_rsl_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
  		/* LLP APDU 9.3.58 M LV 2-N */
  		offset = dissect_rsl_ie_llp_apdu(tvb, pinfo, tree, offset, TRUE);
  		break;
@@ -499,7 +508,7 @@
  	default:
  		break;
  	}
-@@ -3489,6 +3875,40 @@
+@@ -3487,6 +3874,40 @@ dissct_rsl_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
  	return offset;
  
  }
@@ -540,7 +549,7 @@
  static void
  dissect_rsl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
  {
-@@ -3516,7 +3936,6 @@
+@@ -3514,7 +3935,6 @@ dissect_rsl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
  		/* 9.1 Message discriminator */
  		proto_tree_add_item(rsl_tree, hf_rsl_msg_dsc, tvb, offset, 1, FALSE);
  		proto_tree_add_item(rsl_tree, hf_rsl_T_bit, tvb, offset, 1, FALSE);
@@ -548,7 +557,7 @@
  
  		offset = dissct_rsl_msg(tvb, pinfo, rsl_tree, offset);
  
-@@ -3886,6 +4305,86 @@
+@@ -3884,6 +4304,86 @@ void proto_register_rsl(void)
  			FT_UINT8, BASE_DEC, VALS(rsl_emlpp_prio_vals), 0x03,
  			NULL, HFILL }
  		},
@@ -635,7 +644,7 @@
  	};
  	static gint *ett[] = {
  		&ett_rsl,
-@@ -3943,6 +4442,14 @@
+@@ -3941,6 +4441,14 @@ void proto_register_rsl(void)
  		&ett_ie_meas_res_no,
  		&ett_ie_message_id,
  		&ett_ie_sys_info_type,
@@ -650,3 +659,6 @@
  	};
  
  	/* Register the protocol name and description */
+-- 
+1.7.0.1
+