move include/openbsc to include/osmocom/sgsn

Change-Id: I281ef585fffc2644682c8282224fb1c2da5ca795
diff --git a/include/osmocom/sgsn/Makefile.am b/include/osmocom/sgsn/Makefile.am
new file mode 100644
index 0000000..1502129
--- /dev/null
+++ b/include/osmocom/sgsn/Makefile.am
@@ -0,0 +1,28 @@
+noinst_HEADERS = \
+	common.h \
+	crc24.h \
+	debug.h \
+	gb_proxy.h \
+	gprs_gb_parse.h \
+	gprs_gmm.h \
+	gprs_llc.h \
+	gprs_llc_xid.h \
+	gprs_sgsn.h \
+	gprs_sndcp_comp.h \
+	gprs_sndcp_dcomp.h \
+	gprs_sndcp.h \
+	gprs_sndcp_pcomp.h \
+	gprs_sndcp_xid.h \
+	gprs_subscriber.h \
+	gprs_utils.h \
+	gsup_client.h \
+	gtphub.h \
+	oap_client.h \
+	rest_octets.h \
+	sgsn.h \
+	signal.h \
+	slhc.h \
+	v42bis.h \
+	v42bis_private.h \
+	vty.h \
+	$(NULL)
diff --git a/include/osmocom/sgsn/common.h b/include/osmocom/sgsn/common.h
new file mode 100644
index 0000000..d91b3d3
--- /dev/null
+++ b/include/osmocom/sgsn/common.h
@@ -0,0 +1,6 @@
+#pragma once
+
+enum nsap_addr_enc {
+	NSAP_ADDR_ENC_X213,
+	NSAP_ADDR_ENC_V4RAW,
+};
diff --git a/include/osmocom/sgsn/crc24.h b/include/osmocom/sgsn/crc24.h
new file mode 100644
index 0000000..756638c
--- /dev/null
+++ b/include/osmocom/sgsn/crc24.h
@@ -0,0 +1,10 @@
+#ifndef _CRC24_H
+#define _CRC24_H
+
+#include <stdint.h>
+
+#define INIT_CRC24	0xffffff
+
+uint32_t crc24_calc(uint32_t fcs, uint8_t *cp, unsigned int len);
+
+#endif
diff --git a/include/osmocom/sgsn/debug.h b/include/osmocom/sgsn/debug.h
new file mode 100644
index 0000000..65e197d
--- /dev/null
+++ b/include/osmocom/sgsn/debug.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <stdio.h>
+#include <osmocom/core/linuxlist.h>
+
+#define DEBUG
+#include <osmocom/core/logging.h>
+
+/* Debug Areas of the code */
+enum {
+	DRLL,
+	DCC,
+	DMM,
+	DRR,
+	DRSL,
+	DNM,
+	DMNCC,
+	DPAG,
+	DMEAS,
+	DSCCP,
+	DMSC,
+	DMGCP,
+	DHO,
+	DDB,
+	DREF,
+	DGPRS,
+	DNS,
+	DBSSGP,
+	DLLC,
+	DSNDCP,
+	DSLHC,
+	DNAT,
+	DCTRL,
+	DSMPP,
+	DFILTER,
+	DGTPHUB,
+	DRANAP,
+	DSUA,
+	DV42BIS,
+	DPCU,
+	DVLR,
+	DIUCS,
+	DSIGTRAN,
+	Debug_LastEntry,
+};
+
+extern const struct log_info log_info;
diff --git a/include/osmocom/sgsn/gb_proxy.h b/include/osmocom/sgsn/gb_proxy.h
new file mode 100644
index 0000000..e10894f
--- /dev/null
+++ b/include/osmocom/sgsn/gb_proxy.h
@@ -0,0 +1,288 @@
+#ifndef _GB_PROXY_H
+#define _GB_PROXY_H
+
+
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/vty/command.h>
+
+#include <sys/types.h>
+#include <regex.h>
+
+#define GBPROXY_INIT_VU_GEN_TX 256
+
+struct rate_ctr_group;
+struct gprs_gb_parse_context;
+struct tlv_parsed;
+
+enum gbproxy_global_ctr {
+	GBPROX_GLOB_CTR_INV_BVCI,
+	GBPROX_GLOB_CTR_INV_LAI,
+	GBPROX_GLOB_CTR_INV_RAI,
+	GBPROX_GLOB_CTR_INV_NSEI,
+	GBPROX_GLOB_CTR_PROTO_ERR_BSS,
+	GBPROX_GLOB_CTR_PROTO_ERR_SGSN,
+	GBPROX_GLOB_CTR_NOT_SUPPORTED_BSS,
+	GBPROX_GLOB_CTR_NOT_SUPPORTED_SGSN,
+	GBPROX_GLOB_CTR_RESTART_RESET_SGSN,
+	GBPROX_GLOB_CTR_TX_ERR_SGSN,
+	GBPROX_GLOB_CTR_OTHER_ERR,
+	GBPROX_GLOB_CTR_PATCH_PEER_ERR,
+};
+
+enum gbproxy_peer_ctr {
+	GBPROX_PEER_CTR_BLOCKED,
+	GBPROX_PEER_CTR_UNBLOCKED,
+	GBPROX_PEER_CTR_DROPPED,
+	GBPROX_PEER_CTR_INV_NSEI,
+	GBPROX_PEER_CTR_TX_ERR,
+	GBPROX_PEER_CTR_RAID_PATCHED_BSS,
+	GBPROX_PEER_CTR_RAID_PATCHED_SGSN,
+	GBPROX_PEER_CTR_APN_PATCHED,
+	GBPROX_PEER_CTR_TLLI_PATCHED_BSS,
+	GBPROX_PEER_CTR_TLLI_PATCHED_SGSN,
+	GBPROX_PEER_CTR_PTMSI_PATCHED_BSS,
+	GBPROX_PEER_CTR_PTMSI_PATCHED_SGSN,
+	GBPROX_PEER_CTR_PATCH_CRYPT_ERR,
+	GBPROX_PEER_CTR_PATCH_ERR,
+	GBPROX_PEER_CTR_ATTACH_REQS,
+	GBPROX_PEER_CTR_ATTACH_REJS,
+	GBPROX_PEER_CTR_ATTACH_ACKS,
+	GBPROX_PEER_CTR_ATTACH_COMPLS,
+	GBPROX_PEER_CTR_RA_UPD_REQS,
+	GBPROX_PEER_CTR_RA_UPD_REJS,
+	GBPROX_PEER_CTR_RA_UPD_ACKS,
+	GBPROX_PEER_CTR_RA_UPD_COMPLS,
+	GBPROX_PEER_CTR_GMM_STATUS_BSS,
+	GBPROX_PEER_CTR_GMM_STATUS_SGSN,
+	GBPROX_PEER_CTR_DETACH_REQS,
+	GBPROX_PEER_CTR_DETACH_ACKS,
+	GBPROX_PEER_CTR_PDP_ACT_REQS,
+	GBPROX_PEER_CTR_PDP_ACT_REJS,
+	GBPROX_PEER_CTR_PDP_ACT_ACKS,
+	GBPROX_PEER_CTR_PDP_DEACT_REQS,
+	GBPROX_PEER_CTR_PDP_DEACT_ACKS,
+	GBPROX_PEER_CTR_TLLI_UNKNOWN,
+	GBPROX_PEER_CTR_TLLI_CACHE_SIZE,
+	GBPROX_PEER_CTR_LAST,
+};
+
+enum gbproxy_keep_mode {
+	GBPROX_KEEP_NEVER,
+	GBPROX_KEEP_REATTACH,
+	GBPROX_KEEP_IDENTIFIED,
+	GBPROX_KEEP_ALWAYS,
+};
+
+enum gbproxy_match_id {
+	GBPROX_MATCH_PATCHING,
+	GBPROX_MATCH_ROUTING,
+	GBPROX_MATCH_LAST
+};
+
+struct gbproxy_match {
+	int   enable;
+	char *re_str;
+	regex_t re_comp;
+};
+
+struct gbproxy_config {
+	/* parsed from config file */
+	uint16_t nsip_sgsn_nsei;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+
+	/* Linked list of all Gb peers (except SGSN) */
+	struct llist_head bts_peers;
+
+	/* Counter */
+	struct rate_ctr_group *ctrg;
+
+	/* force mcc/mnc */
+	int core_mnc;
+	int core_mcc;
+	uint8_t* core_apn;
+	size_t core_apn_size;
+	int tlli_max_age;
+	int tlli_max_len;
+
+	/* Experimental config */
+	int patch_ptmsi;
+	int acquire_imsi;
+	int route_to_sgsn2;
+	uint16_t nsip_sgsn2_nsei;
+	enum gbproxy_keep_mode keep_link_infos;
+
+	/* IMSI checking/matching */
+	struct gbproxy_match matches[GBPROX_MATCH_LAST];
+};
+
+struct gbproxy_patch_state {
+	int local_mnc;
+	int local_mcc;
+
+	/* List of TLLIs for which patching is enabled */
+	struct llist_head logical_links;
+	int logical_link_count;
+};
+
+struct gbproxy_peer {
+	struct llist_head list;
+
+	/* point back to the config */
+	struct gbproxy_config *cfg;
+
+	/* NSEI of the peer entity */
+	uint16_t nsei;
+
+	/* BVCI used for Point-to-Point to this peer */
+	uint16_t bvci;
+	int blocked;
+
+	/* Routeing Area that this peer is part of (raw 04.08 encoding) */
+	uint8_t ra[6];
+
+	/* Counter */
+	struct rate_ctr_group *ctrg;
+
+	struct gbproxy_patch_state patch_state;
+};
+
+struct gbproxy_tlli_state {
+	uint32_t current;
+	uint32_t assigned;
+	int bss_validated;
+	int net_validated;
+
+	uint32_t ptmsi;
+};
+
+struct gbproxy_link_info {
+	struct llist_head list;
+
+	struct gbproxy_tlli_state tlli;
+	struct gbproxy_tlli_state sgsn_tlli;
+	uint32_t sgsn_nsei;
+
+	time_t timestamp;
+	uint8_t *imsi;
+	size_t imsi_len;
+
+	int imsi_acq_pending;
+	struct llist_head stored_msgs;
+	unsigned vu_gen_tx_bss;
+
+	int is_deregistered;
+
+	int is_matching[GBPROX_MATCH_LAST];
+};
+
+
+/* gb_proxy_vty .c */
+
+int gbproxy_vty_init(void);
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg);
+
+
+/* gb_proxy.c */
+int gbproxy_init_config(struct gbproxy_config *cfg);
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct gbproxy_config *cfg, struct msgb *msg, uint16_t nsei, uint16_t ns_bvci, uint16_t nsvci);
+
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+		  void *handler_data, void *signal_data);
+
+/* Reset all persistent NS-VC's */
+int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi);
+
+void gbprox_reset(struct gbproxy_config *cfg);
+
+/* TLLI info handling */
+void gbproxy_delete_link_infos(struct gbproxy_peer *peer);
+struct gbproxy_link_info *gbproxy_update_link_state_ul(
+	struct gbproxy_peer *peer, time_t now,
+	struct gprs_gb_parse_context *parse_ctx);
+struct gbproxy_link_info *gbproxy_update_link_state_dl(
+	struct gbproxy_peer *peer, time_t now,
+	struct gprs_gb_parse_context *parse_ctx);
+int gbproxy_update_link_state_after(
+	struct gbproxy_peer *peer, struct gbproxy_link_info *link_info,
+	time_t now, struct gprs_gb_parse_context *parse_ctx);
+int gbproxy_remove_stale_link_infos(struct gbproxy_peer *peer, time_t now);
+void gbproxy_delete_link_info(struct gbproxy_peer *peer,
+			 struct gbproxy_link_info *link_info);
+void gbproxy_link_info_discard_messages(struct gbproxy_link_info *link_info);
+
+void gbproxy_attach_link_info(struct gbproxy_peer *peer, time_t now,
+			      struct gbproxy_link_info *link_info);
+void gbproxy_update_link_info(struct gbproxy_link_info *link_info,
+			      const uint8_t *imsi, size_t imsi_len);
+void gbproxy_detach_link_info(struct gbproxy_peer *peer,
+			      struct gbproxy_link_info *link_info);
+struct gbproxy_link_info *gbproxy_link_info_alloc( struct gbproxy_peer *peer);
+
+struct gbproxy_link_info *gbproxy_link_info_by_tlli(
+	struct gbproxy_peer *peer, uint32_t tlli);
+struct gbproxy_link_info *gbproxy_link_info_by_imsi(
+	struct gbproxy_peer *peer, const uint8_t *imsi, size_t imsi_len);
+struct gbproxy_link_info *gbproxy_link_info_by_any_sgsn_tlli(
+	struct gbproxy_peer *peer, uint32_t tlli);
+struct gbproxy_link_info *gbproxy_link_info_by_sgsn_tlli(
+	struct gbproxy_peer *peer,
+	uint32_t tlli, uint32_t sgsn_nsei);
+struct gbproxy_link_info *gbproxy_link_info_by_ptmsi(
+	struct gbproxy_peer *peer,
+	uint32_t ptmsi);
+
+int gbproxy_imsi_matches(
+	struct gbproxy_config *cfg,
+	enum gbproxy_match_id match_id,
+	struct gbproxy_link_info *link_info);
+uint32_t gbproxy_map_tlli(
+	uint32_t other_tlli, struct gbproxy_link_info *link_info, int to_bss);
+
+/* needed by gb_proxy_tlli.h */
+uint32_t gbproxy_make_bss_ptmsi(struct gbproxy_peer *peer, uint32_t sgsn_ptmsi);
+uint32_t gbproxy_make_sgsn_tlli(
+	struct gbproxy_peer *peer, struct gbproxy_link_info *link_info,
+	uint32_t bss_tlli);
+void gbproxy_reset_link(struct gbproxy_link_info *link_info);
+int gbproxy_check_imsi(
+	struct gbproxy_match *match, const uint8_t *imsi, size_t imsi_len);
+
+/* Message patching */
+void gbproxy_patch_bssgp(
+	struct msgb *msg, uint8_t *bssgp, size_t bssgp_len,
+	struct gbproxy_peer *peer, struct gbproxy_link_info *link_info,
+	int *len_change, struct gprs_gb_parse_context *parse_ctx);
+
+int gbproxy_patch_llc(
+	struct msgb *msg, uint8_t *llc, size_t llc_len,
+	struct gbproxy_peer *peer, struct gbproxy_link_info *link_info,
+	int *len_change, struct gprs_gb_parse_context *parse_ctx);
+
+int gbproxy_set_patch_filter(
+	struct gbproxy_match *match, const char *filter, const char **err_msg);
+void gbproxy_clear_patch_filter(struct gbproxy_match *match);
+
+/* Peer handling */
+struct gbproxy_peer *gbproxy_peer_by_bvci(
+	struct gbproxy_config *cfg, uint16_t bvci);
+struct gbproxy_peer *gbproxy_peer_by_nsei(
+	struct gbproxy_config *cfg, uint16_t nsei);
+struct gbproxy_peer *gbproxy_peer_by_rai(
+	struct gbproxy_config *cfg, const uint8_t *ra);
+struct gbproxy_peer *gbproxy_peer_by_lai(
+	struct gbproxy_config *cfg, const uint8_t *la);
+struct gbproxy_peer *gbproxy_peer_by_lac(
+	struct gbproxy_config *cfg, const uint8_t *la);
+struct gbproxy_peer *gbproxy_peer_by_bssgp_tlv(
+	struct gbproxy_config *cfg, struct tlv_parsed *tp);
+struct gbproxy_peer *gbproxy_peer_alloc(struct gbproxy_config *cfg, uint16_t bvci);
+void gbproxy_peer_free(struct gbproxy_peer *peer);
+int gbproxy_cleanup_peers(struct gbproxy_config *cfg, uint16_t nsei, uint16_t bvci);
+
+#endif
diff --git a/include/osmocom/sgsn/gprs_gb_parse.h b/include/osmocom/sgsn/gprs_gb_parse.h
new file mode 100644
index 0000000..9f43fae
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_gb_parse.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <osmocom/sgsn/gprs_llc.h>
+
+#include <sys/types.h>
+
+struct gprs_gb_parse_context {
+	/* Pointer to protocol specific parts */
+	struct gsm48_hdr *g48_hdr;
+	struct bssgp_normal_hdr *bgp_hdr;
+	struct bssgp_ud_hdr *bud_hdr;
+	uint8_t *bssgp_data;
+	size_t bssgp_data_len;
+	uint8_t *llc;
+	size_t llc_len;
+
+	/* Extracted information */
+	struct gprs_llc_hdr_parsed llc_hdr_parsed;
+	struct tlv_parsed bssgp_tp;
+	int to_bss;
+	uint8_t *tlli_enc;
+	uint8_t *old_tlli_enc;
+	uint8_t *imsi;
+	size_t imsi_len;
+	uint8_t *apn_ie;
+	size_t apn_ie_len;
+	uint8_t *ptmsi_enc;
+	uint8_t *new_ptmsi_enc;
+	uint8_t *raid_enc;
+	uint8_t *old_raid_enc;
+	uint8_t *bssgp_raid_enc;
+	uint8_t *bssgp_ptmsi_enc;
+
+	/* General info */
+	const char *llc_msg_name;
+	int invalidate_tlli;
+	int await_reattach;
+	int need_decryption;
+	uint32_t tlli;
+	int pdu_type;
+	int old_raid_is_foreign;
+	int peer_nsei;
+};
+
+int gprs_gb_parse_dtap(uint8_t *data, size_t data_len,
+		       struct gprs_gb_parse_context *parse_ctx);
+
+int gprs_gb_parse_llc(uint8_t *llc, size_t llc_len,
+		      struct gprs_gb_parse_context *parse_ctx);
+
+int gprs_gb_parse_bssgp(uint8_t *bssgp, size_t bssgp_len,
+			struct gprs_gb_parse_context *parse_ctx);
+
+const char *gprs_gb_message_name(const struct gprs_gb_parse_context *parse_ctx,
+				 const char *default_msg_name);
+
+void gprs_gb_log_parse_context(int log_level,
+			       struct gprs_gb_parse_context *parse_ctx,
+			       const char *default_msg_name);
diff --git a/include/osmocom/sgsn/gprs_gmm.h b/include/osmocom/sgsn/gprs_gmm.h
new file mode 100644
index 0000000..6324c5d
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_gmm.h
@@ -0,0 +1,35 @@
+#ifndef _GPRS_GMM_H
+#define _GPRS_GMM_H
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+
+#include <stdbool.h>
+
+int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause);
+int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid,
+			     uint8_t cause, uint8_t pco_len, uint8_t *pco_v);
+int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp);
+int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp);
+
+int gsm0408_gprs_rcvmsg_gb(struct msgb *msg, struct gprs_llc_llme *llme,
+			   bool drop_cipherable);
+int gsm0408_gprs_rcvmsg_iu(struct msgb *msg, struct gprs_ra_id *ra_id,
+			   uint16_t *sai);
+int gsm0408_gprs_force_reattach(struct sgsn_mm_ctx *mmctx);
+int gsm0408_gprs_force_reattach_oldmsg(struct msgb *msg,
+				       struct gprs_llc_llme *llme);
+void gsm0408_gprs_access_granted(struct sgsn_mm_ctx *mmctx);
+void gsm0408_gprs_access_denied(struct sgsn_mm_ctx *mmctx, int gmm_cause);
+void gsm0408_gprs_access_cancelled(struct sgsn_mm_ctx *mmctx, int gmm_cause);
+void gsm0408_gprs_authenticate(struct sgsn_mm_ctx *mmctx);
+
+int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli);
+int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli,
+		       uint8_t suspend_ref);
+
+time_t gprs_max_time_to_idle(void);
+
+int iu_rab_act_ps(uint8_t rab_id, struct sgsn_pdp_ctx *pdp);
+
+#endif /* _GPRS_GMM_H */
diff --git a/include/osmocom/sgsn/gprs_llc.h b/include/osmocom/sgsn/gprs_llc.h
new file mode 100644
index 0000000..376ae9a
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_llc.h
@@ -0,0 +1,284 @@
+#ifndef _GPRS_LLC_H
+#define _GPRS_LLC_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gprs_llc_xid.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,
+};
+
+/* Section 6.4.1.6 / Table 6 */
+enum gprs_llc_xid_type {
+	GPRS_LLC_XID_T_VERSION		= 0,
+	GPRS_LLC_XID_T_IOV_UI		= 1,
+	GPRS_LLC_XID_T_IOV_I		= 2,
+	GPRS_LLC_XID_T_T200		= 3,
+	GPRS_LLC_XID_T_N200		= 4,
+	GPRS_LLC_XID_T_N201_U		= 5,
+	GPRS_LLC_XID_T_N201_I		= 6,
+	GPRS_LLC_XID_T_mD		= 7,
+	GPRS_LLC_XID_T_mU		= 8,
+	GPRS_LLC_XID_T_kD		= 9,
+	GPRS_LLC_XID_T_kU		= 10,
+	GPRS_LLC_XID_T_L3_PAR		= 11,
+	GPRS_LLC_XID_T_RESET		= 12,
+};
+
+extern const struct value_string gprs_llc_xid_type_names[];
+
+/* TS 04.64 Section 7.1.2 Table 7: LLC layer primitives (GMM/SNDCP/SMS/TOM) */
+/* TS 04.65 Section 5.1.2 Table 2: Service primitives used by SNDCP */
+enum gprs_llc_primitive {
+	/* GMM <-> LLME */
+	LLGMM_ASSIGN_REQ,	/* GMM tells us new TLLI: TLLI old, TLLI new, Kc, CiphAlg */
+	LLGMM_RESET_REQ,	/* GMM tells us to perform XID negotiation: TLLI */
+	LLGMM_RESET_CNF,	/* LLC informs GMM that XID has completed: TLLI */
+	LLGMM_SUSPEND_REQ,	/* GMM tells us MS has suspended: TLLI, Page */
+	LLGMM_RESUME_REQ,	/* GMM tells us MS has resumed: TLLI */
+	LLGMM_PAGE_IND,		/* LLC asks GMM to page MS: TLLI */
+	LLGMM_IOV_REQ,		/* GMM tells us to perform XID: TLLI */
+	LLGMM_STATUS_IND,	/* LLC informs GMM about error: TLLI, Cause */
+	/* LLE <-> (GMM/SNDCP/SMS/TOM) */
+	LL_RESET_IND,		/* TLLI */
+	LL_ESTABLISH_REQ,	/* TLLI, XID Req */
+	LL_ESTABLISH_IND,	/* TLLI, XID Req, N201-I, N201-U */
+	LL_ESTABLISH_RESP,	/* TLLI, XID Negotiated */
+	LL_ESTABLISH_CONF,	/* TLLI, XID Neg, N201-i, N201-U */
+	LL_RELEASE_REQ,		/* TLLI, Local */
+	LL_RELEASE_IND,		/* TLLI, Cause */
+	LL_RELEASE_CONF,	/* TLLI */
+	LL_XID_REQ,		/* TLLI, XID Requested */
+	LL_XID_IND,		/* TLLI, XID Req, N201-I, N201-U */
+	LL_XID_RESP,		/* TLLI, XID Negotiated */
+	LL_XID_CONF,		/* TLLI, XID Neg, N201-I, N201-U */
+	LL_DATA_REQ,		/* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */
+	LL_DATA_IND,		/* TLLI, SN-PDU */
+	LL_DATA_CONF,		/* TLLI, Ref */
+	LL_UNITDATA_REQ,	/* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */
+	LL_UNITDATA_IND,	/* TLLI, SN-PDU */
+	LL_STATUS_IND,		/* TLLI, Cause */
+};
+
+/* Section 4.5.2 Logical Link States + Annex C.2 */
+enum gprs_llc_lle_state {
+	GPRS_LLES_UNASSIGNED	= 1,	/* No TLLI yet */
+	GPRS_LLES_ASSIGNED_ADM	= 2,	/* TLLI assigned */
+	GPRS_LLES_LOCAL_EST	= 3,	/* Local Establishment */
+	GPRS_LLES_REMOTE_EST	= 4,	/* Remote Establishment */
+	GPRS_LLES_ABM		= 5,
+	GPRS_LLES_LOCAL_REL	= 6,	/* Local Release */
+	GPRS_LLES_TIMER_REC 	= 7,	/* Timer Recovery */
+};
+
+enum gprs_llc_llme_state {
+	GPRS_LLMS_UNASSIGNED	= 1,	/* No TLLI yet */
+	GPRS_LLMS_ASSIGNED	= 2,	/* TLLI assigned */
+};
+
+/* Section 8.9.9 LLC layer parameter default values */
+struct gprs_llc_params {
+	uint16_t iov_i_exp;
+	uint16_t t200_201;
+	uint16_t n200;
+	uint16_t n201_u;
+	uint16_t n201_i;
+	uint16_t mD;
+	uint16_t mU;
+	uint16_t kD;
+	uint16_t kU;
+};
+
+/* Section 4.7.1: Logical Link Entity: One per DLCI (TLLI + SAPI) */
+struct gprs_llc_lle {
+	struct llist_head list;
+
+	uint32_t sapi;
+
+	struct gprs_llc_llme *llme;
+
+	enum gprs_llc_lle_state state;
+
+	struct osmo_timer_list t200;
+	struct osmo_timer_list t201;	/* wait for acknowledgement */
+
+	uint16_t v_sent;
+	uint16_t v_ack;
+	uint16_t v_recv;
+
+	uint16_t vu_send;
+	uint16_t vu_recv;
+
+	/* non-standard LLC state */
+	uint16_t vu_recv_last;
+	uint16_t vu_recv_duplicates;
+
+	/* Overflow Counter for ABM */
+	uint32_t oc_i_send;
+	uint32_t oc_i_recv;
+
+	/* Overflow Counter for unconfirmed transfer */
+	uint32_t oc_ui_send;
+	uint32_t oc_ui_recv;
+
+	unsigned int retrans_ctr;
+
+	struct gprs_llc_params params;
+};
+
+#define NUM_SAPIS	16
+
+struct gprs_llc_llme {
+	struct llist_head list;
+
+	enum gprs_llc_llme_state state;
+
+	uint32_t tlli;
+	uint32_t old_tlli;
+
+	/* Crypto parameters */
+	enum gprs_ciph_algo algo;
+	uint8_t kc[16];
+	uint8_t cksn;
+	/* 3GPP TS 44.064 § 8.9.2: */
+	uint32_t iov_ui;
+
+	/* over which BSSGP BTS ctx do we need to transmit */
+	uint16_t bvci;
+	uint16_t nsei;
+	struct gprs_llc_lle lle[NUM_SAPIS];
+
+	/* Copy of the XID fields we have sent with the last
+	 * network originated XID-Request. Since the phone
+	 * may strip the optional fields in the confirmation
+	 * we need to remeber those fields in order to be
+	 * able to create the compression entity. */
+	struct llist_head *xid;
+
+	/* Compression entities */
+	struct {
+		/* In these two list_heads we will store the
+		 * data and protocol compression entities,
+		 * together with their compression states */
+		struct llist_head *proto;
+		struct llist_head *data;
+	} comp;
+
+	/* Internal management */
+	uint32_t age_timestamp;
+};
+
+#define GPRS_LLME_RESET_AGE (0)
+
+extern struct llist_head gprs_llc_llmes;
+
+/* LLC low level types */
+
+enum gprs_llc_cmd {
+	GPRS_LLC_NULL,
+	GPRS_LLC_RR,
+	GPRS_LLC_ACK,
+	GPRS_LLC_RNR,
+	GPRS_LLC_SACK,
+	GPRS_LLC_DM,
+	GPRS_LLC_DISC,
+	GPRS_LLC_UA,
+	GPRS_LLC_SABM,
+	GPRS_LLC_FRMR,
+	GPRS_LLC_XID,
+	GPRS_LLC_UI,
+};
+
+struct gprs_llc_hdr_parsed {
+	uint8_t sapi;
+	uint8_t is_cmd:1,
+		 ack_req:1,
+		 is_encrypted:1;
+	uint32_t seq_rx;
+	uint32_t seq_tx;
+	uint32_t fcs;
+	uint32_t fcs_calc;
+	uint8_t *data;
+	uint16_t data_len;
+	uint16_t crc_length;
+	enum gprs_llc_cmd cmd;
+};
+
+
+/* BSSGP-UL-UNITDATA.ind */
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv);
+
+/* LL-UNITDATA.req */
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command,
+		   struct sgsn_mm_ctx *mmctx, bool encryptable);
+
+/* Chapter 7.2.1.2 LLGMM-RESET.req */
+int gprs_llgmm_reset(struct gprs_llc_llme *llme);
+int gprs_llgmm_reset_oldmsg(struct msgb* oldmsg, uint8_t sapi,
+			    struct gprs_llc_llme *llme);
+
+/* Set of LL-XID negotiation (See also: TS 101 351, Section 7.2.2.4) */
+int gprs_ll_xid_req(struct gprs_llc_lle *lle,
+		    struct gprs_llc_xid_field *l3_xid_field);
+
+/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */
+int gprs_llgmm_assign(struct gprs_llc_llme *llme,
+		      uint32_t old_tlli, uint32_t new_tlli);
+int gprs_llgmm_unassign(struct gprs_llc_llme *llme);
+
+int gprs_llc_init(const char *cipher_plugin_path);
+int gprs_llc_vty_init(void);
+
+/**
+ * \short Check if N(U) should be considered a retransmit
+ *
+ * Implements the range check as of GSM 04.64 8.4.2
+ * Receipt of unacknowledged information.
+ *
+ * @returns Returns 1 if  (V(UR)-32) <= N(U) < V(UR)
+ * @param nu N(U) unconfirmed sequence number of the UI frame
+ * @param vur V(UR) unconfirmend received state variable
+ */
+static inline int gprs_llc_is_retransmit(uint16_t nu, uint16_t vur)
+{
+	int delta = (vur - nu) & 0x1ff;
+	return 0 < delta && delta < 32;
+}
+
+/* LLC low level functions */
+void gprs_llme_copy_key(struct sgsn_mm_ctx *mm, struct gprs_llc_llme *llme);
+
+/* parse a GPRS LLC header, also check for invalid frames */
+int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp,
+		       uint8_t *llc_hdr, int len);
+void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph, struct gprs_llc_lle *lle);
+int gprs_llc_fcs(uint8_t *data, unsigned int len);
+
+
+/* LLME handling routines */
+struct llist_head *gprs_llme_list(void);
+struct gprs_llc_lle *gprs_lle_get_or_create(const uint32_t tlli, uint8_t sapi);
+
+
+#endif
diff --git a/include/osmocom/sgsn/gprs_llc_xid.h b/include/osmocom/sgsn/gprs_llc_xid.h
new file mode 100644
index 0000000..d340d40
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_llc_xid.h
@@ -0,0 +1,57 @@
+/* GPRS LLC XID field encoding/decoding as per 3GPP TS 44.064 */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+
+/* 3GPP TS 44.064 6.4.1.6 Exchange Identification (XID)
+   command/response parameter field */
+struct gprs_llc_xid_field {
+	struct llist_head list;
+	uint8_t type;		/* See also Table 6: LLC layer parameter
+				   negotiation */
+	uint8_t *data;		/* Payload data (memory is owned by the
+				 * creator of the struct) */
+	unsigned int data_len;	/* Payload length */
+};
+
+/* Transform a list with XID fields into a XID message (dst) */
+int gprs_llc_compile_xid(uint8_t *dst, int dst_maxlen,
+			 const struct llist_head *xid_fields);
+
+/* Transform a XID message (dst) into a list of XID fields */
+struct llist_head *gprs_llc_parse_xid(const void *ctx, const uint8_t *src,
+				      int src_len);
+
+/* Create a duplicate of an XID-Field */
+struct gprs_llc_xid_field *gprs_llc_dup_xid_field(const void *ctx,
+				const struct gprs_llc_xid_field *xid_field);
+
+/* Copy an llist with xid fields */
+struct llist_head *gprs_llc_copy_xid(const void *ctx,
+				     const struct llist_head *xid_fields);
+
+/* Dump a list with XID fields (Debug) */
+void gprs_llc_dump_xid_fields(const struct llist_head *xid_fields,
+			      unsigned int logl);
+
diff --git a/include/osmocom/sgsn/gprs_sgsn.h b/include/osmocom/sgsn/gprs_sgsn.h
new file mode 100644
index 0000000..c47fb09
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_sgsn.h
@@ -0,0 +1,484 @@
+#ifndef _GPRS_SGSN_H
+#define _GPRS_SGSN_H
+
+#include <stdint.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/timer.h>
+
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/crypt/gprs_cipher.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/crypt/auth.h>
+
+#define GSM_EXTENSION_LENGTH 15
+#define GSM_APN_LENGTH 102
+
+struct gprs_llc_lle;
+struct ctrl_handle;
+struct gprs_subscr;
+
+enum gsm48_gsm_cause;
+
+/* TS 04.08 4.1.3.3 GMM mobility management states on the network side */
+enum gprs_gmm_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 */
+};
+
+/* TS 23.060 6.1.1 and 6.1.2 Mobility management states A/Gb and Iu mode */
+enum gprs_pmm_state {
+	PMM_DETACHED,
+	PMM_CONNECTED,
+	PMM_IDLE,
+	MM_IDLE,
+	MM_READY,
+	MM_STANDBY,
+};
+
+enum gprs_mm_ctr {
+	GMM_CTR_PKTS_SIG_IN,
+	GMM_CTR_PKTS_SIG_OUT,
+	GMM_CTR_PKTS_UDATA_IN,
+	GMM_CTR_PKTS_UDATA_OUT,
+	GMM_CTR_BYTES_UDATA_IN,
+	GMM_CTR_BYTES_UDATA_OUT,
+	GMM_CTR_PDP_CTX_ACT,
+	GMM_CTR_SUSPEND,
+	GMM_CTR_PAGING_PS,
+	GMM_CTR_PAGING_CS,
+	GMM_CTR_RA_UPDATE,
+};
+
+enum gprs_pdp_ctx {
+	PDP_CTR_PKTS_UDATA_IN,
+	PDP_CTR_PKTS_UDATA_OUT,
+	PDP_CTR_BYTES_UDATA_IN,
+	PDP_CTR_BYTES_UDATA_OUT,
+};
+
+enum gprs_t3350_mode {
+	GMM_T3350_MODE_NONE,
+	GMM_T3350_MODE_ATT,
+	GMM_T3350_MODE_RAU,
+	GMM_T3350_MODE_PTMSI_REALL,
+};
+
+/* Authorization/ACL handling */
+enum sgsn_auth_state {
+	SGSN_AUTH_UNKNOWN,
+	SGSN_AUTH_AUTHENTICATE,
+	SGSN_AUTH_UMTS_RESYNC,
+	SGSN_AUTH_ACCEPTED,
+	SGSN_AUTH_REJECTED
+};
+
+#define MS_RADIO_ACCESS_CAPA
+
+enum sgsn_ggsn_lookup_state {
+	SGSN_GGSN_2DIGIT,
+	SGSN_GGSN_3DIGIT,
+};
+
+struct sgsn_ggsn_lookup {
+	int state;
+
+	struct sgsn_mm_ctx *mmctx;
+
+	/* APN string */
+	char apn_str[GSM_APN_LENGTH];
+
+	/* the original data */
+	struct msgb *orig_msg;
+	struct tlv_parsed tp;
+
+	/* for dealing with re-transmissions */
+	uint8_t nsapi;
+	uint8_t sapi;
+	uint8_t ti;
+};
+
+enum sgsn_ran_type {
+	/* GPRS/EDGE via Gb */
+	MM_CTX_T_GERAN_Gb,
+	/* UMTS via Iu */
+	MM_CTX_T_UTRAN_Iu,
+	/* GPRS/EDGE via Iu */
+	MM_CTX_T_GERAN_Iu,
+};
+
+struct service_info {
+	uint8_t type;
+	uint16_t pdp_status;
+};
+
+struct ranap_ue_conn_ctx;
+
+struct gsm_auth_tuple {
+        int use_count;
+        int key_seq;
+        struct osmo_auth_vector vec;
+};
+#define GSM_KEY_SEQ_INVAL       7       /* GSM 04.08 - 10.5.1.2 */
+
+/* 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;
+
+	enum sgsn_ran_type	ran_type;
+
+	char 			imsi[GSM23003_IMSI_MAX_DIGITS+1];
+	enum gprs_gmm_state	gmm_state;
+	enum gprs_pmm_state	pmm_state;	/* Iu: page when in PMM-IDLE mode */
+	uint32_t 		p_tmsi;
+	uint32_t 		p_tmsi_old;	/* old P-TMSI before new is confirmed */
+	uint32_t 		p_tmsi_sig;
+	char 			imei[GSM23003_IMEISV_NUM_DIGITS+1];
+	/* Opt: Software Version Numbber / TS 23.195 */
+	char 			msisdn[GSM_EXTENSION_LENGTH];
+	struct gprs_ra_id	ra;
+	struct {
+		uint16_t		cell_id;	/* Gb only */
+		uint32_t		cell_id_age;	/* Gb only */
+		uint8_t			radio_prio_sms;
+
+		/* Additional bits not present in the GSM TS */
+		uint16_t		nsei;
+		uint16_t		bvci;
+		struct gprs_llc_llme	*llme;
+		uint32_t		tlli;
+		uint32_t		tlli_new;
+	} gb;
+	struct {
+		int			new_key;
+		uint16_t		sac;		/* Iu: Service Area Code */
+		uint32_t		sac_age;	/* Iu: Service Area Code age */
+		/* CSG ID */
+		/* CSG Membership */
+		/* Access Mode */
+		/* Seelected CN Operator ID (TS 23.251) */
+		/* CSG Subscription Data */
+		/* LIPA Allowed */
+		/* Voice Support Match Indicator */
+		struct ranap_ue_conn_ctx	*ue_ctx;
+		struct service_info	service;
+	} iu;
+	/* VLR number */
+	uint32_t		new_sgsn_addr;
+	/* Authentication Triplet */
+	struct gsm_auth_tuple	auth_triplet;
+	/* Kc */
+	/* Iu: CK, IK, KSI */
+	/* CKSN */
+	enum gprs_ciph_algo	ciph_algo;
+	/* Auth & Ciphering Request reference from 3GPP TS 24.008 § 10.5.5.19: */
+	uint8_t ac_ref_nr_used;
+
+	struct {
+		uint8_t	len;
+		uint8_t	buf[50];	/* GSM 04.08 10.5.5.12a, extended in TS 24.008 */
+	} ms_radio_access_capa;
+	/* Supported Codecs (SRVCC) */
+	struct {
+		uint8_t	len;
+		uint8_t	buf[8];		/* GSM 04.08 10.5.5.12, extended in TS 24.008 */
+	} ms_network_capa;
+	/* UE Netowrk Capability (E-UTRAN) */
+	uint16_t		drx_parms;
+	/* Active Time value for PSM */
+	int			mnrg;	/* MS reported to HLR? */
+	int			ngaf;	/* MS reported to MSC/VLR? */
+	int			ppf;	/* paging for GPRS + non-GPRS? */
+	/* Subscribed Charging Characteristics */
+	/* Trace Reference */
+	/* Trace Type */
+	/* Trigger ID */
+	/* OMC Identity */
+	/* SMS Parameters */
+	int			recovery;
+	/* Access Restriction */
+	/* GPRS CSI (CAMEL) */
+	/* MG-CSI (CAMEL) */
+	/* Subscribed UE-AMBR */
+	/* UE-AMBR */
+	/* APN Subscribed */
+
+	struct llist_head	pdp_list;
+
+	struct rate_ctr_group	*ctrg;
+	struct osmo_timer_list	timer;
+	unsigned int		T;		/* Txxxx number */
+	unsigned int		num_T_exp;	/* number of consecutive T expirations */
+
+	enum gprs_t3350_mode	t3350_mode;
+	uint8_t			t3370_id_type;
+	uint8_t			pending_req;	/* the request's message type */
+	/* TODO: There isn't much semantic difference between t3350_mode
+	 * (refers to the timer) and pending_req (refers to the procedure),
+	 * where mm->T == 3350 => mm->t3350_mode == f(mm->pending_req). Check
+	 * whether one of them can be dropped. */
+
+	enum sgsn_auth_state	auth_state;
+	int			is_authenticated;
+
+	/* the string representation of the current hlr */
+	char 			hlr[GSM_EXTENSION_LENGTH];
+
+	/* the current GGSN look-up operation */
+	struct sgsn_ggsn_lookup *ggsn_lookup;
+
+	struct gprs_subscr	*subscr;
+};
+
+#define LOGMMCTXP(level, mm, fmt, args...) \
+	LOGP(DMM, level, "MM(%s/%08x) " fmt, (mm) ? (mm)->imsi : "---", \
+	     (mm) ? (mm)->p_tmsi : GSM_RESERVED_TMSI, ## args)
+
+/* 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);
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ue_ctx(const void *uectx);
+
+/* look-up by matching TLLI and P-TMSI (think twice before using this) */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli_and_ptmsi(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_gb(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_iu(void *uectx);
+
+void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *ctx);
+
+struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx,
+						struct tlv_parsed *tp,
+						enum gsm48_gsm_cause *gsm_cause,
+						char *apn_str);
+
+enum pdp_ctx_state {
+	PDP_STATE_NONE,
+	PDP_STATE_CR_REQ,
+	PDP_STATE_CR_CONF,
+
+	/* 04.08 / Figure 6.2 / 6.1.2.2 */
+	PDP_STATE_INACT_PEND,
+	PDP_STATE_INACTIVE = PDP_STATE_NONE,
+};
+
+enum pdp_type {
+	PDP_TYPE_NONE,
+	PDP_TYPE_ETSI_PPP,
+	PDP_TYPE_IANA_IPv4,
+	PDP_TYPE_IANA_IPv6,
+};
+
+struct sgsn_pdp_ctx {
+	struct llist_head	list;	/* list_head for mmctx->pdp_list */
+	struct llist_head	g_list;	/* list_head for global list */
+	struct sgsn_mm_ctx	*mm;	/* back pointer to MM CTX */
+	int			destroy_ggsn; /* destroy it on destruction */
+	struct sgsn_ggsn_ctx	*ggsn;	/* which GGSN serves this PDP */
+	struct rate_ctr_group	*ctrg;
+
+	//unsigned int		id;
+	struct pdp_t		*lib;	/* pointer to libgtp PDP ctx */
+	enum pdp_ctx_state	state;
+	enum pdp_type		type;
+	uint32_t		address;
+	char 			*apn_subscribed;
+	//char 			*apn_used;
+	uint16_t		nsapi;	/* SNDCP */
+	uint16_t		sapi;	/* LLC */
+	uint8_t			ti;	/* transaction identifier */
+	int			vplmn_allowed;
+	uint32_t		qos_profile_subscr;
+	//uint32_t		qos_profile_req;
+	//uint32_t		qos_profile_neg;
+	uint8_t			radio_prio;
+	//uint32_t		charging_id;
+
+	struct osmo_timer_list	timer;
+	unsigned int		T;		/* Txxxx number */
+	unsigned int		num_T_exp;	/* number of consecutive T expirations */
+
+	struct osmo_timer_list	cdr_timer;	/* CDR record wird timer */
+	struct timespec		cdr_start;	/* The start of the CDR */
+	uint64_t		cdr_bytes_in;
+	uint64_t		cdr_bytes_out;
+	uint32_t		cdr_charging_id;
+};
+
+#define LOGPDPCTXP(level, pdp, fmt, args...) \
+	LOGP(DGPRS, level, "PDP(%s/%u) " \
+	     fmt, (pdp)->mm ? (pdp)->mm->imsi : "---", (pdp)->ti, ## args)
+
+/* look up PDP context by MM context and NSAPI */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+					   uint8_t nsapi);
+/* look up PDP context by MM context and transaction ID */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm,
+					 uint8_t tid);
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+					uint8_t nsapi);
+void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp);
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp);
+
+
+struct sgsn_ggsn_ctx {
+	struct llist_head list;
+	uint32_t id;
+	unsigned int gtp_version;
+	struct in_addr remote_addr;
+	int remote_restart_ctr;
+	struct gsn_t *gsn;
+};
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id);
+void sgsn_ggsn_ctx_free(struct sgsn_ggsn_ctx *ggc);
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id);
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr);
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id);
+
+struct apn_ctx {
+	struct llist_head list;
+	struct sgsn_ggsn_ctx *ggsn;
+	char *name;
+	char *imsi_prefix;
+	char *description;
+};
+
+struct apn_ctx *sgsn_apn_ctx_find_alloc(const char *name, const char *imsi_prefix);
+void sgsn_apn_ctx_free(struct apn_ctx *actx);
+struct apn_ctx *sgsn_apn_ctx_by_name(const char *name, const char *imsi_prefix);
+struct apn_ctx *sgsn_apn_ctx_match(const char *name, const char *imsi_prefix);
+
+extern struct llist_head sgsn_mm_ctxts;
+extern struct llist_head sgsn_ggsn_ctxts;
+extern struct llist_head sgsn_apn_ctxts;
+extern struct llist_head sgsn_pdp_ctxts;
+
+uint32_t sgsn_alloc_ptmsi(void);
+void sgsn_inst_init(void);
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * ottherwise lost state (recovery procedure) */
+int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn);
+
+char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len);
+
+/*
+ * ctrl interface related work
+ */
+struct gsm_network;
+struct ctrl_handle *sgsn_controlif_setup(struct gsm_network *,
+					 const char *bind_addr, uint16_t port);
+int sgsn_ctrl_cmds_install(void);
+
+/*
+ * Authorization/ACL handling
+ */
+struct imsi_acl_entry {
+	struct llist_head list;
+	char imsi[16+1];
+};
+
+/* see GSM 09.02, 17.7.1, PDP-Context and GPRSSubscriptionData */
+/* see GSM 09.02, B.1, gprsSubscriptionData */
+struct sgsn_subscriber_pdp_data {
+	struct llist_head	list;
+
+	unsigned int		context_id;
+	uint16_t		pdp_type;
+	char			apn_str[GSM_APN_LENGTH];
+	uint8_t			qos_subscribed[20];
+	size_t			qos_subscribed_len;
+	uint8_t			pdp_charg[2];
+	bool			has_pdp_charg;
+};
+
+struct sgsn_subscriber_data {
+	struct sgsn_mm_ctx	*mm;
+	struct gsm_auth_tuple	auth_triplets[5];
+	int			auth_triplets_updated;
+	struct llist_head	pdp_list;
+	int			error_cause;
+
+	uint8_t			msisdn[9];
+	size_t			msisdn_len;
+
+	uint8_t			hlr[9];
+	size_t			hlr_len;
+
+	uint8_t			pdp_charg[2];
+	bool			has_pdp_charg;
+};
+
+#define SGSN_ERROR_CAUSE_NONE (-1)
+
+#define LOGGSUBSCRP(level, subscr, fmt, args...) \
+	LOGP(DGPRS, level, "SUBSCR(%s) " fmt, \
+	     (subscr) ? (subscr)->imsi : "---", \
+	     ## args)
+
+struct sgsn_config;
+struct sgsn_instance;
+extern const struct value_string *sgsn_auth_state_names;
+
+void sgsn_auth_init(void);
+struct imsi_acl_entry *sgsn_acl_lookup(const char *imsi, struct sgsn_config *cfg);
+int sgsn_acl_add(const char *imsi, struct sgsn_config *cfg);
+int sgsn_acl_del(const char *imsi, struct sgsn_config *cfg);
+/* Request authorization */
+int sgsn_auth_request(struct sgsn_mm_ctx *mm);
+enum sgsn_auth_state sgsn_auth_state(struct sgsn_mm_ctx *mm);
+void sgsn_auth_update(struct sgsn_mm_ctx *mm);
+struct gsm_auth_tuple *sgsn_auth_get_tuple(struct sgsn_mm_ctx *mmctx,
+					   unsigned key_seq);
+
+/*
+ * GPRS subscriber data
+ */
+#define GPRS_SUBSCRIBER_FIRST_CONTACT	0x00000001
+#define GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING	(1 << 16)
+#define GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING		(1 << 17)
+#define GPRS_SUBSCRIBER_CANCELLED			(1 << 18)
+#define GPRS_SUBSCRIBER_ENABLE_PURGE			(1 << 19)
+
+#define GPRS_SUBSCRIBER_UPDATE_PENDING_MASK ( \
+		GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING | \
+		GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING  \
+)
+
+int gprs_subscr_init(struct sgsn_instance *sgi);
+int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx);
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx,
+				  const uint8_t *auts,
+				  const uint8_t *auts_rand);
+int gprs_subscr_auth_sync(struct gprs_subscr *subscr,
+			  const uint8_t *auts, const uint8_t *auts_rand);
+void gprs_subscr_cleanup(struct gprs_subscr *subscr);
+struct gprs_subscr *gprs_subscr_get_or_create(const char *imsi);
+struct gprs_subscr *gprs_subscr_get_or_create_by_mmctx( struct sgsn_mm_ctx *mmctx);
+struct gprs_subscr *gprs_subscr_get_by_imsi(const char *imsi);
+void gprs_subscr_cancel(struct gprs_subscr *subscr);
+void gprs_subscr_update(struct gprs_subscr *subscr);
+void gprs_subscr_update_auth_info(struct gprs_subscr *subscr);
+int gprs_subscr_rx_gsup_message(struct msgb *msg);
+
+/* Called on subscriber data updates */
+void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx);
+
+int gprs_sndcp_vty_init(void);
+struct sgsn_instance;
+int sgsn_gtp_init(struct sgsn_instance *sgi);
+
+void sgsn_rate_ctr_init();
+
+#endif /* _GPRS_SGSN_H */
diff --git a/include/osmocom/sgsn/gprs_sndcp.h b/include/osmocom/sgsn/gprs_sndcp.h
new file mode 100644
index 0000000..d970240
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_sndcp.h
@@ -0,0 +1,79 @@
+#ifndef _INT_SNDCP_H
+#define _INT_SNDCP_H
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+
+/* A fragment queue header, maintaining list of fragments for one N-PDU */
+struct defrag_state {
+	/* PDU number for which the defragmentation state applies */
+	uint16_t npdu;
+	/* highest segment number we have received so far */
+	uint8_t highest_seg;
+	/* bitmask of the segments we already have */
+	uint32_t seg_have;
+	/* do we still expect more segments? */
+	unsigned int no_more;
+	/* total length of all segments together */
+	unsigned int tot_len;
+
+	/* linked list of defrag_queue_entry: one for each fragment  */
+	struct llist_head frag_list;
+
+	struct osmo_timer_list timer;
+
+	/* Holds state to know which compression mode is used
+	 * when the packet is re-assembled */
+	uint8_t pcomp;
+	uint8_t dcomp;
+
+	/* Holds the pointers to the compression entity list
+	 * that is used when the re-assembled packet is decompressed */
+	struct llist_head *proto;
+	struct llist_head *data;
+};
+
+/* See 6.7.1.2 Reassembly */
+enum sndcp_rx_state {
+	SNDCP_RX_S_FIRST,
+	SNDCP_RX_S_SUBSEQ,
+	SNDCP_RX_S_DISCARD,
+};
+
+struct gprs_sndcp_entity {
+	struct llist_head list;
+
+	/* FIXME: move this RA_ID up to the LLME or even higher */
+	struct gprs_ra_id ra_id;
+	/* reference to the LLC Entity below this SNDCP entity */
+	struct gprs_llc_lle *lle;
+	/* The NSAPI we shall use on top of LLC */
+	uint8_t nsapi;
+
+	/* NPDU number for the GTP->SNDCP side */
+	uint16_t tx_npdu_nr;
+	/* SNDCP eeceiver state */
+	enum sndcp_rx_state rx_state;
+	/* The defragmentation queue */
+	struct defrag_state defrag;
+};
+
+extern struct llist_head gprs_sndcp_entities;
+
+/* Set of SNDCP-XID negotiation (See also: TS 144 065,
+ * Section 6.8 XID parameter negotiation) */
+int sndcp_sn_xid_req(struct gprs_llc_lle *lle, uint8_t nsapi);
+
+/* Process SNDCP-XID indication (See also: TS 144 065,
+ * Section 6.8 XID parameter negotiation) */
+int sndcp_sn_xid_ind(struct gprs_llc_xid_field *xid_field_indication,
+		     struct gprs_llc_xid_field *xid_field_response,
+		     struct gprs_llc_lle *lle);
+
+/* Process SNDCP-XID indication
+ * (See also: TS 144 065, Section 6.8 XID parameter negotiation) */
+int sndcp_sn_xid_conf(struct gprs_llc_xid_field *xid_field_conf,
+		      struct gprs_llc_xid_field *xid_field_request,
+		      struct gprs_llc_lle *lle);
+
+#endif	/* INT_SNDCP_H */
diff --git a/include/osmocom/sgsn/gprs_sndcp_comp.h b/include/osmocom/sgsn/gprs_sndcp_comp.h
new file mode 100644
index 0000000..c04f7d4
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_sndcp_comp.h
@@ -0,0 +1,82 @@
+/* GPRS SNDCP header compression entity management tools */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/sgsn/gprs_sndcp_xid.h>
+
+/* Header / Data compression entity */
+struct gprs_sndcp_comp {
+	struct llist_head list;
+
+	/* Serves as an ID in case we want to delete this entity later */
+	unsigned int entity;	/* see also: 6.5.1.1.3 and 6.6.1.1.3 */
+
+	/* Specifies to which NSAPIs the compression entity is assigned */
+	uint8_t nsapi_len;	/* Number of applicable NSAPIs (default 0) */
+	uint8_t nsapi[MAX_NSAPI];	/* Applicable NSAPIs (default 0) */
+
+	/* Assigned pcomp values */
+	uint8_t comp_len;	/* Number of contained PCOMP / DCOMP values */
+	uint8_t comp[MAX_COMP];	/* see also: 6.5.1.1.5 and 6.6.1.1.5 */
+
+	/* Algorithm parameters */
+	int algo;		/* Algorithm type (see gprs_sndcp_xid.h) */
+	int compclass;		/* See gprs_sndcp_xid.h/c */
+	void *state;		/* Algorithm status and parameters */
+};
+
+#define MAX_COMP 16	/* Maximum number of possible pcomp/dcomp values */
+#define MAX_NSAPI 11	/* Maximum number usable NSAPIs */
+
+/* Allocate a compression enitiy list */
+struct llist_head *gprs_sndcp_comp_alloc(const void *ctx);
+
+/* Free a compression entitiy list */
+void gprs_sndcp_comp_free(struct llist_head *comp_entities);
+
+/* Delete a compression entity */
+void gprs_sndcp_comp_delete(struct llist_head *comp_entities, unsigned int entity);
+
+/* Create and Add a new compression entity
+ * (returns a pointer to the compression entity that has just been created) */
+struct gprs_sndcp_comp *gprs_sndcp_comp_add(const void *ctx,
+					    struct llist_head *comp_entities,
+					    const struct gprs_sndcp_comp_field
+					    *comp_field);
+
+/* Find which compression entity handles the specified pcomp/dcomp */
+struct gprs_sndcp_comp *gprs_sndcp_comp_by_comp(const struct llist_head
+						*comp_entities, uint8_t comp);
+
+/* Find which compression entity handles the specified nsapi */
+struct gprs_sndcp_comp *gprs_sndcp_comp_by_nsapi(const struct llist_head
+						 *comp_entities, uint8_t nsapi);
+
+/* Find a comp_index for a given pcomp/dcomp value */
+uint8_t gprs_sndcp_comp_get_idx(const struct gprs_sndcp_comp *comp_entity,
+				uint8_t comp);
+
+/* Find a pcomp/dcomp value for a given comp_index */
+uint8_t gprs_sndcp_comp_get_comp(const struct gprs_sndcp_comp *comp_entity,
+			         uint8_t comp_index);
diff --git a/include/osmocom/sgsn/gprs_sndcp_dcomp.h b/include/osmocom/sgsn/gprs_sndcp_dcomp.h
new file mode 100644
index 0000000..3e85142
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_sndcp_dcomp.h
@@ -0,0 +1,53 @@
+/* GPRS SNDCP data compression handler */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+
+/* Note: The decompressed packet may have a maximum size of:
+ * Return value * MAX_DATADECOMPR_FAC */
+#define MAX_DATADECOMPR_FAC 10
+
+/* Note: In unacknowledged mode (SN_UNITDATA), the comression state is reset
+ * for every NPDU. The compressor needs a reasonably large payload to operate
+ * effectively (yield positive compression gain). For packets shorter than 100
+ * byte, no positive compression gain can be expected so we will skip the
+ * compression for short packets. */
+#define MIN_COMPR_PAYLOAD 100
+
+/* Initalize data compression */
+int gprs_sndcp_dcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity,
+			  const struct gprs_sndcp_comp_field *comp_field);
+
+/* Terminate data compression */
+void gprs_sndcp_dcomp_term(struct gprs_sndcp_comp *comp_entity);
+
+/* Expand packet */
+int gprs_sndcp_dcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp,
+			    const struct llist_head *comp_entities);
+
+/* Compress packet */
+int gprs_sndcp_dcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp,
+			      const struct llist_head *comp_entities,
+			      uint8_t nsapi);
diff --git a/include/osmocom/sgsn/gprs_sndcp_pcomp.h b/include/osmocom/sgsn/gprs_sndcp_pcomp.h
new file mode 100644
index 0000000..3e3131b
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_sndcp_pcomp.h
@@ -0,0 +1,46 @@
+/* GPRS SNDCP header compression handler */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/sgsn/gprs_sndcp_comp.h>
+
+/* Note: The decompressed packet may have a maximum size of:
+ * Return value + MAX_DECOMPR_INCR */
+#define MAX_HDRDECOMPR_INCR 64
+
+/* Initalize header compression */
+int gprs_sndcp_pcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity,
+			  const struct gprs_sndcp_comp_field *comp_field);
+
+/* Terminate header compression */
+void gprs_sndcp_pcomp_term(struct gprs_sndcp_comp *comp_entity);
+
+/* Expand packet header */
+int gprs_sndcp_pcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp,
+			    const struct llist_head *comp_entities);
+
+/* Compress packet header */
+int gprs_sndcp_pcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp,
+			      const struct llist_head *comp_entities,
+			      uint8_t nsapi);
diff --git a/include/osmocom/sgsn/gprs_sndcp_xid.h b/include/osmocom/sgsn/gprs_sndcp_xid.h
new file mode 100644
index 0000000..e64bc52
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_sndcp_xid.h
@@ -0,0 +1,218 @@
+/* GPRS SNDCP XID field encoding/decoding as per 3GPP TS 44.065 */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+
+#define DEFAULT_SNDCP_VERSION 0	/* See 3GPP TS 44.065, clause 8 */
+#define MAX_ENTITIES 32		/* 3GPP TS 44.065 reserves 5 bit
+				 * for compression enitity number */
+
+#define MAX_COMP 16	/* Maximum number of possible pcomp/dcomp values */
+#define MAX_NSAPI 11	/* Maximum number usable NSAPIs */
+#define MAX_ROHC 16	/* Maximum number of ROHC compression profiles */
+
+/* According to: 3GPP TS 44.065, 6.5.1.1 Format of the protocol control
+ * information compression field (Figure 7) and 3GPP TS 44.065, 
+ * 6.6.1.1 Format of the data compression field (Figure 9) */
+struct gprs_sndcp_comp_field {
+	struct llist_head list;
+
+	/* Propose bit (P), see also: 6.5.1.1.2 and 6.6.1.1.2 */
+	unsigned int p;
+
+	/* Entity number, see also: 6.5.1.1.3 and 6.6.1.1.3 */
+	unsigned int entity;
+
+	/* Algorithm identifier, see also: 6.5.1.1.4 and 6.6.1.1.4 */
+	int algo;
+
+	/* Number of contained PCOMP / DCOMP values */
+	uint8_t comp_len;
+
+	/* PCOMP / DCOMP values, see also: 6.5.1.1.5 and 6.6.1.1.5 */
+	uint8_t comp[MAX_COMP];
+
+	/* Note: Only one of the following struct pointers may,
+	   be used. Unused pointers must be set to NULL! */
+	struct gprs_sndcp_pcomp_rfc1144_params *rfc1144_params;
+	struct gprs_sndcp_pcomp_rfc2507_params *rfc2507_params;
+	struct gprs_sndcp_pcomp_rohc_params *rohc_params;
+	struct gprs_sndcp_dcomp_v42bis_params *v42bis_params;
+	struct gprs_sndcp_dcomp_v44_params *v44_params;
+};
+
+/* According to: 3GPP TS 44.065, 6.5.1.1.4 Algorithm identifier */
+enum gprs_sndcp_hdr_comp_algo {
+	RFC_1144,		/* TCP/IP header compression, see also 6.5.2 */
+	RFC_2507,		/* TCP/UDP/IP header compression, see also: 6.5.3 */
+	ROHC			/* Robust Header Compression, see also 6.5.4 */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.1.1.4 Algorithm identifier */
+enum gprs_sndcp_data_comp_algo {
+	V42BIS,			/* V.42bis data compression, see also 6.6.2 */
+	V44			/* V44 data compression, see also: 6.6.3 */
+};
+
+/* According to: 3GPP TS 44.065, 8 SNDCP XID parameters */
+enum gprs_sndcp_xid_param_types {
+	SNDCP_XID_VERSION_NUMBER,
+	SNDCP_XID_DATA_COMPRESSION,	/* See also: subclause 6.6.1 */
+	SNDCP_XID_PROTOCOL_COMPRESSION,	/* See also: subclause 6.5.1 */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.2.1 Parameters (Table 5) */
+struct gprs_sndcp_pcomp_rfc1144_params {
+	uint8_t nsapi_len;		/* Number of applicable NSAPIs
+					 * (default 0) */
+	uint8_t nsapi[MAX_NSAPI];	/* Applicable NSAPIs (default 0) */
+	int s01;			/* (default 15) */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.2.2 Assignment of PCOMP values */
+enum gprs_sndcp_pcomp_rfc1144_pcomp {
+	RFC1144_PCOMP1,			/* Uncompressed TCP */
+	RFC1144_PCOMP2,			/* Compressed TCP */
+	RFC1144_PCOMP_NUM		/* Number of pcomp values */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.3.1 Parameters (Table 6) */
+struct gprs_sndcp_pcomp_rfc2507_params {
+	uint8_t nsapi_len;		/* Number of applicable NSAPIs
+					 * (default 0) */
+	uint8_t nsapi[MAX_NSAPI];	/* Applicable NSAPIs (default 0) */
+	int f_max_period;		/* (default 256) */
+	int f_max_time;			/* (default 5) */
+	int max_header;			/* (default 168) */
+	int tcp_space;			/* (default 15) */
+	int non_tcp_space;		/* (default 15) */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.3.2 Assignment of PCOMP values for RFC2507 */
+enum gprs_sndcp_pcomp_rfc2507_pcomp {
+	RFC2507_PCOMP1,			/* Full Header */
+	RFC2507_PCOMP2,			/* Compressed TCP */
+	RFC2507_PCOMP3,			/* Compressed TCP non delta */
+	RFC2507_PCOMP4,			/* Compressed non TCP */
+	RFC2507_PCOMP5,			/* Context state */
+	RFC2507_PCOMP_NUM		/* Number of pcomp values */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.4.1 Parameter (Table 10) */
+struct gprs_sndcp_pcomp_rohc_params {
+	uint8_t nsapi_len;		/* Number of applicable NSAPIs
+					 * (default 0) */
+	uint8_t nsapi[MAX_NSAPI];	/* Applicable NSAPIs (default 0) */
+	int max_cid;			/* (default 15) */
+	int max_header;			/* (default 168) */
+	uint8_t profile_len;		/* (default 1) */
+	uint16_t profile[MAX_ROHC];	/* (default 0, ROHC uncompressed) */
+};
+
+/* According to: 3GPP TS 44.065, 6.5.4.2 Assignment of PCOMP values for ROHC */
+enum gprs_sndcp_pcomp_rohc_pcomp {
+	ROHC_PCOMP1,			/* ROHC small CIDs */
+	ROHC_PCOMP2,			/* ROHC large CIDs */
+	ROHC_PCOMP_NUM			/* Number of pcomp values */
+};
+
+/* ROHC compression profiles, see also:
+   http://www.iana.org/assignments/rohc-pro-ids/rohc-pro-ids.xhtml */
+enum gprs_sndcp_xid_rohc_profiles {
+	ROHC_UNCOMPRESSED = 0x0000,	/* ROHC uncompressed    [RFC5795] */
+	ROHC_RTP = 0x0001,		/* ROHC RTP             [RFC3095] */
+	ROHCV2_RTP = 0x0101,		/* ROHCv2 RTP           [RFC5225] */
+	ROHC_UDP = 0x0002,		/* ROHC UDP             [RFC3095] */
+	ROHCv2_UDP = 0x0102,		/* ROHCv2 UDP           [RFC5225] */
+	ROHC_ESP = 0x0003,		/* ROHC ESP             [RFC3095] */
+	ROHCV2_ESP = 0x0103,		/* ROHCv2 ESP           [RFC5225] */
+	ROHC_IP = 0x0004,		/* ROHC IP              [RFC3843] */
+	ROHCV2_IP = 0x0104,		/* ROHCv2 IP            [RFC5225] */
+	ROHC_LLA = 0x0005,		/* ROHC LLA             [RFC4362] */
+	ROHC_LLA_WITH_R_MODE = 0x0105,	/* ROHC LLA with R-mode [RFC3408] */
+	ROHC_TCP = 0x0006,		/* ROHC TCP             [RFC6846] */
+	ROHC_RTP_UDP_LITE = 0x0007,	/* ROHC RTP/UDP-Lite    [RFC4019] */
+	ROHCV2_RTP_UDP_LITE = 0x0107,	/* ROHCv2 RTP/UDP-Lite  [RFC5225] */
+	ROHC_UDP_LITE = 0x0008,		/* ROHC UDP-Lite        [RFC4019] */
+	ROHCV2_UDP_LITE = 0x0108,	/* ROHCv2 UDP-Lite      [RFC5225] */
+};
+
+/* According to: 3GPP TS 44.065, 6.6.2.1 Parameters (Table 7a) */
+struct gprs_sndcp_dcomp_v42bis_params {
+	uint8_t nsapi_len;		/* Number of applicable NSAPIs
+					 * (default 0) */
+	uint8_t nsapi[MAX_NSAPI];	/* Applicable NSAPIs (default 0) */
+	int p0;				/* (default 3) */
+	int p1;				/* (default 2048) */
+	int p2;				/* (default 20) */
+
+};
+
+/* According to: 3GPP TS 44.065, 6.6.2.2 Assignment of DCOMP values */
+enum gprs_sndcp_dcomp_v42bis_dcomp {
+	V42BIS_DCOMP1,			/* V.42bis enabled */
+	V42BIS_DCOMP_NUM		/* Number of dcomp values */
+};
+
+/* According to: 3GPP TS 44.065, 6.6.3.1 Parameters (Table 7c) */
+struct gprs_sndcp_dcomp_v44_params {
+	uint8_t nsapi_len;		/* Number of applicable NSAPIs
+					 * (default 0) */
+	uint8_t nsapi[MAX_NSAPI];	/* Applicable NSAPIs (default 0) */
+	int c0;				/* (default 10000000) */
+	int p0;				/* (default 3) */
+	int p1t;			/* Refer to subclause 6.6.3.1.4 */
+	int p1r;			/* Refer to subclause 6.6.3.1.5 */
+	int p3t;			/* (default 3 x p1t) */
+	int p3r;			/* (default 3 x p1r) */
+};
+
+/* According to: 3GPP TS 44.065, 6.6.3.2 Assignment of DCOMP values */
+enum gprs_sndcp_dcomp_v44_dcomp {
+	V44_DCOMP1,			/* Packet method compressed */
+	V44_DCOMP2,			/* Multi packet method compressed */
+	V44_DCOMP_NUM			/* Number of dcomp values */
+};
+
+/* Transform a list with compression fields into an SNDCP-XID message (dst) */
+int gprs_sndcp_compile_xid(uint8_t *dst, unsigned int dst_maxlen,
+			   const struct llist_head *comp_fields, int version);
+
+/* Transform an SNDCP-XID message (src) into a list of SNDCP-XID fields */
+struct llist_head *gprs_sndcp_parse_xid(int *version,
+					const void *ctx,
+					const uint8_t *src,
+					unsigned int src_len,
+					const struct llist_head
+					*comp_fields_req);
+
+/* Find out to which compression class the specified comp-field belongs
+ * (header compression or data compression?) */
+int gprs_sndcp_get_compression_class(
+				const struct gprs_sndcp_comp_field *comp_field);
+
+/* Dump a list with SNDCP-XID fields (Debug) */
+void gprs_sndcp_dump_comp_fields(const struct llist_head *comp_fields,
+				 unsigned int logl);
+
diff --git a/include/osmocom/sgsn/gprs_subscriber.h b/include/osmocom/sgsn/gprs_subscriber.h
new file mode 100644
index 0000000..be78feb
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_subscriber.h
@@ -0,0 +1,31 @@
+/* GPRS subscriber details for use in SGSN land */
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+
+extern struct llist_head * const gprs_subscribers;
+
+struct gprs_subscr {
+	struct llist_head entry;
+	int use_count;
+
+	char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+	uint32_t tmsi;
+	char imei[GSM23003_IMEISV_NUM_DIGITS+1];
+	bool authorized;
+	bool keep_in_ram;
+	uint32_t flags;
+	uint16_t lac;
+
+	struct sgsn_subscriber_data *sgsn_data;
+};
+
+struct gprs_subscr *_gprs_subscr_get(struct gprs_subscr *gsub,
+				     const char *file, int line);
+struct gprs_subscr *_gprs_subscr_put(struct gprs_subscr *gsub,
+				     const char *file, int line);
+#define gprs_subscr_get(gsub) _gprs_subscr_get(gsub, __BASE_FILE__, __LINE__)
+#define gprs_subscr_put(gsub) _gprs_subscr_put(gsub, __BASE_FILE__, __LINE__)
diff --git a/include/osmocom/sgsn/gprs_utils.h b/include/osmocom/sgsn/gprs_utils.h
new file mode 100644
index 0000000..e06364d
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_utils.h
@@ -0,0 +1,55 @@
+/* GPRS utility functions */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2014 by On-Waves
+ * (C) 2013 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <osmocom/core/msgb.h>
+
+struct msgb;
+struct gprs_ra_id;
+
+struct msgb *gprs_msgb_copy(const struct msgb *msg, const char *name);
+int gprs_msgb_resize_area(struct msgb *msg, uint8_t *area,
+			    size_t old_size, size_t new_size);
+int gprs_str_to_apn(uint8_t *apn_enc, size_t max_len, const char *str);
+
+/* GSM 04.08, 10.5.7.3 GPRS Timer */
+int gprs_tmr_to_secs(uint8_t tmr);
+uint8_t gprs_secs_to_tmr_floor(int secs);
+
+int gprs_is_mi_tmsi(const uint8_t *value, size_t value_len);
+int gprs_is_mi_imsi(const uint8_t *value, size_t value_len);
+int gprs_parse_mi_tmsi(const uint8_t *value, size_t value_len, uint32_t *tmsi);
+void gprs_parse_tmsi(const uint8_t *value, uint32_t *tmsi);
+
+int gprs_ra_id_equals(const struct gprs_ra_id *id1, const struct gprs_ra_id *id2);
+
+#define GSM48_ALLOC_SIZE        2048
+#define GSM48_ALLOC_HEADROOM    256
+
+static inline struct msgb *gsm48_msgb_alloc_name(const char *name)
+{
+        return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
+                                   name);
+}
diff --git a/include/osmocom/sgsn/gsup_client.h b/include/osmocom/sgsn/gsup_client.h
new file mode 100644
index 0000000..29092ad
--- /dev/null
+++ b/include/osmocom/sgsn/gsup_client.h
@@ -0,0 +1,63 @@
+/* GPRS Subscriber Update Protocol client */
+
+/* (C) 2014 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Jacob Erlbeck
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <osmocom/core/timer.h>
+
+#include <osmocom/sgsn/oap_client.h>
+
+#define GSUP_CLIENT_RECONNECT_INTERVAL 10
+#define GSUP_CLIENT_PING_INTERVAL 20
+
+struct msgb;
+struct ipa_client_conn;
+struct gsup_client;
+
+/* Expects message in msg->l2h */
+typedef int (*gsup_client_read_cb_t)(struct gsup_client *gsupc,
+				     struct msgb *msg);
+
+struct gsup_client {
+	const char *unit_name;
+
+	struct ipa_client_conn *link;
+	gsup_client_read_cb_t read_cb;
+	void *data;
+
+	struct oap_client_state oap_state;
+
+	struct osmo_timer_list ping_timer;
+	struct osmo_timer_list connect_timer;
+	int is_connected;
+	int got_ipa_pong;
+};
+
+struct gsup_client *gsup_client_create(const char *unit_name,
+				       const char *ip_addr,
+				       unsigned int tcp_port,
+				       gsup_client_read_cb_t read_cb,
+				       struct oap_client_config *oapc_config);
+
+void gsup_client_destroy(struct gsup_client *gsupc);
+int gsup_client_send(struct gsup_client *gsupc, struct msgb *msg);
+struct msgb *gsup_client_msgb_alloc(void);
+
diff --git a/include/osmocom/sgsn/gtphub.h b/include/osmocom/sgsn/gtphub.h
new file mode 100644
index 0000000..8fd9f38
--- /dev/null
+++ b/include/osmocom/sgsn/gtphub.h
@@ -0,0 +1,523 @@
+/* GTP Hub Implementation */
+
+/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/sgsn/gprs_sgsn.h>
+
+
+/* support */
+
+/* TODO move to osmocom/core/socket.c ? */
+#include <netdb.h> /* for IPPROTO_* etc */
+struct osmo_sockaddr {
+	struct sockaddr_storage a;
+	socklen_t l;
+};
+
+/* TODO move to osmocom/core/socket.c ? */
+/*! \brief Initialize a sockaddr
+ * \param[out] addr  Valid osmo_sockaddr pointer to write result to
+ * \param[in] family  Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ * \param[in] type  Socket type like SOCK_DGRAM, SOCK_STREAM
+ * \param[in] proto  Protocol like IPPROTO_TCP, IPPROTO_UDP
+ * \param[in] host Remote host name or IP address in string form
+ * \param[in] port Remote port number in host byte order
+ * \returns 0 on success, otherwise an error code (from getaddrinfo()).
+ *
+ * Copy the first result from a getaddrinfo() call with the given parameters to
+ * *addr and *addr_len. On error, do not change *addr and return nonzero.
+ */
+int osmo_sockaddr_init(struct osmo_sockaddr *addr,
+		       uint16_t family, uint16_t type, uint8_t proto,
+		       const char *host, uint16_t port);
+
+/* Conveniently pass AF_UNSPEC, SOCK_DGRAM and IPPROTO_UDP to
+ * osmo_sockaddr_init(). */
+static inline int osmo_sockaddr_init_udp(struct osmo_sockaddr *addr,
+					 const char *host, uint16_t port)
+{
+	return osmo_sockaddr_init(addr, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
+				  host, port);
+}
+
+/*! \brief convert sockaddr to human readable string.
+ * \param[out] addr_str  Valid pointer to a buffer of length addr_str_len.
+ * \param[in] addr_str_len  Size of buffer addr_str points at.
+ * \param[out] port_str  Valid pointer to a buffer of length port_str_len.
+ * \param[in] port_str_len  Size of buffer port_str points at.
+ * \param[in] addr  Binary representation as returned by osmo_sockaddr_init().
+ * \param[in] flags  flags as passed to getnameinfo().
+ * \returns  0 on success, an error code on error.
+ *
+ * Return the IPv4 or IPv6 address string and the port (a.k.a. service) string
+ * representations of the given struct osmo_sockaddr in two caller provided
+ * char buffers. Flags of (NI_NUMERICHOST | NI_NUMERICSERV) return numeric
+ * address and port. Either one of addr_str or port_str may be NULL, in which
+ * case nothing is returned there.
+ *
+ * See also osmo_sockaddr_to_str() (less flexible, but much more convenient). */
+int osmo_sockaddr_to_strs(char *addr_str, size_t addr_str_len,
+			  char *port_str, size_t port_str_len,
+			  const struct osmo_sockaddr *addr,
+			  int flags);
+
+
+/*! \brief concatenate the parts returned by osmo_sockaddr_to_strs().
+ * \param[in] addr  Binary representation as returned by osmo_sockaddr_init().
+ * \param[in] buf  A buffer to use for string operations.
+ * \param[in] buf_len  Length of the buffer.
+ * \returns  Address string (in buffer).
+ *
+ * Compose a string of the numeric IP-address and port represented by *addr of
+ * the form "<ip-addr> port <port>". The returned string is valid until the
+ * next invocation of this function.
+ */
+const char *osmo_sockaddr_to_strb(const struct osmo_sockaddr *addr,
+				  char *buf, size_t buf_len);
+
+/*! \brief conveniently return osmo_sockaddr_to_strb() in a static buffer.
+ * \param[in] addr  Binary representation as returned by osmo_sockaddr_init().
+ * \returns  Address string in static buffer.
+ *
+ * See osmo_sockaddr_to_strb().
+ *
+ * Note: only one osmo_sockaddr_to_str() call will work per print/log
+ * statement. For two or more, use osmo_sockaddr_to_strb() with a separate
+ * buffer each.
+ */
+const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *addr);
+
+/*! \brief compare two osmo_sockaddr.
+ * \param[in] a  The first address to compare.
+ * \param[in] b  The other address to compare.
+ * \returns 0 if equal, otherwise -1 or 1.
+ */
+int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
+		      const struct osmo_sockaddr *b);
+
+/*! \brief Overwrite *dst with *src.
+ * Like memcpy(), but copy only the valid bytes. */
+void osmo_sockaddr_copy(struct osmo_sockaddr *dst,
+			const struct osmo_sockaddr *src);
+
+
+/* general */
+
+enum gtphub_plane_idx {
+	GTPH_PLANE_CTRL = 0,
+	GTPH_PLANE_USER = 1,
+	GTPH_PLANE_N
+};
+
+enum gtphub_side_idx {
+	GTPH_SIDE_SGSN = 0,
+	GTPH_SIDE_GGSN = 1,
+	GTPH_SIDE_N
+};
+
+#define for_each_side(I)	for (I = 0; I < GTPH_SIDE_N; I++)
+#define for_each_plane(I)	for (I = 0; I < GTPH_PLANE_N; I++)
+#define for_each_side_and_plane(I,J)	for_each_side(I) for_each_plane(J)
+
+static inline int other_side_idx(int side_idx)
+{
+	return (side_idx + 1) & 1;
+}
+
+extern const char* const gtphub_plane_idx_names[GTPH_PLANE_N];
+extern const uint16_t gtphub_plane_idx_default_port[GTPH_PLANE_N];
+
+extern const char* const gtphub_side_idx_names[GTPH_SIDE_N];
+
+/* A host address in the form that is expected in the 7.7.32 GSN Address IE.
+ * len is either 4 (IPv4) or 16 (IPv6), any other value is invalid. If no
+ * address is set, len shall be 0. */
+struct gsn_addr {
+	uint16_t len;
+	uint8_t buf[16];
+};
+
+void gsn_addr_copy(struct gsn_addr *gsna, const struct gsn_addr *src);
+int gsn_addr_from_str(struct gsn_addr *gsna, const char *numeric_addr_str);
+
+/* Return gsna in numeric string form, in a static buffer. */
+const char *gsn_addr_to_str(const struct gsn_addr *gsna);
+
+/* note: strbuf_len doesn't need to be larger than INET6_ADDRSTRLEN + 1. */
+const char *gsn_addr_to_strb(const struct gsn_addr *gsna,
+			     char *strbuf, int strbuf_len);
+
+/* Return 1 on match, zero otherwise. */
+int gsn_addr_same(const struct gsn_addr *a, const struct gsn_addr *b);
+
+/* Decode sa to gsna. Return 0 on success. If port is non-NULL, the port number
+ * from sa is also returned. */
+int gsn_addr_from_sockaddr(struct gsn_addr *gsna, uint16_t *port,
+			   const struct osmo_sockaddr *sa);
+
+/* expiry */
+
+struct expiring_item;
+typedef void (*del_cb_t)(struct expiring_item *);
+
+struct expiring_item {
+	struct llist_head entry;
+	time_t expiry;
+	del_cb_t del_cb;
+};
+
+struct expiry {
+	int expiry_in_seconds;
+	struct llist_head items;
+};
+
+/* Initialize an expiry queue. */
+void expiry_init(struct expiry *exq, int expiry_in_seconds);
+
+/* Add a new mapping, or restart the expiry timeout for an already listed
+ * mapping. */
+void expiry_add(struct expiry *exq, struct expiring_item *item, time_t now);
+
+/* Initialize to all-empty; must be called before using the item in any way. */
+void expiring_item_init(struct expiring_item *item);
+
+/* Remove the given item from its expiry queue, and call item->del_cb, if set.
+ * This sets item->del_cb to NULL and is harmless when run a second time on the
+ * same item, so the del_cb may choose to call this function, too, to allow
+ * deleting items from several code paths. */
+void expiring_item_del(struct expiring_item *item);
+
+/* Carry out due expiry of mappings. Must be invoked regularly.
+ * 'now' is the current clock count in seconds and must correspond to the clock
+ * count passed to nr_map_add(). A monotonous clock counter should be used. */
+int expiry_tick(struct expiry *exq, time_t now);
+
+/* Expire all items. */
+void expiry_clear(struct expiry *exq);
+
+
+/* number map */
+
+/* A number map assigns a "random" mapped number to each user provided number.
+ * If the same number is requested multiple times, the same mapped number is
+ * returned.
+ *
+ * Number maps plug into possibly shared pools and expiry queues, for example:
+ *
+ *     mapA -----------+-> pool1           <-+-- mapB
+ *     {10->1, 11->5}  |   {1, 2, 3, ...}    |   {10->2, 11->3}
+ *                     |                     |
+ *                     |                     |
+ *                 /-> \-> expiry1         <-/
+ *                 |       (30 seconds)
+ *                 |
+ *     mapC -------+-----> pool2           <-+-- mapD
+ *     {10->1, 11->3}      {1, 2, 3, ...}    |   {10->2, 11->5}
+ *                                           |
+ *                         expiry2         <-/
+ *                         (60 seconds)
+ *
+ * A map contains mappings ("10->1"). Each map needs a number pool, which can
+ * be shared with other maps. Each new mapping receives a number from the pool,
+ * which is then unavailable to any other map using the same pool.
+ *
+ * A map may point at an expiry queue, in which case all mappings added to it
+ * are also appended to the expiry queue (using a separate llist entry in the
+ * mapping). Any number of maps may submit to the same expiry queue, if they
+ * desire the same expiry timeout. An expiry queue stores the mappings in
+ * chronological order, so that expiry checking is needed only from the start
+ * of the queue; hence only mappings with identical expiry timeout can be added
+ * to the same expiry queue. Upon expiry, a mapping is dropped from the map it
+ * was submitted at. expiry_tick() needs to be called regularly for each expiry
+ * queue.
+ *
+ * A nr_mapping can be embedded in a larger struct: each mapping can have a
+ * distinct destructor (del_cb), and each del_cb can figure out the container
+ * struct's address and free that upon expiry or manual deletion. So in expiry
+ * queues (and even maps), mappings of different container types can be mixed.
+ * This can help to drastically reduce the amount of unnecessary visits during
+ * expiry checking, for the case that no expiry is pending. An expiry queue
+ * always knows which mappings to expire next, because they are right at the
+ * start of its list.
+ *
+ * Mapping allocation and a del_cb are provided by the caller. If del_cb is
+ * NULL, no deallocation will be done (allowing statically allocated entries).
+ */
+
+typedef unsigned int nr_t;
+
+/* Generator for unused numbers. So far this counts upwards from zero, but the
+ * implementation may change in the future. Treat this like an opaque struct.
+ * If this becomes random, the tests need to be fixed. */
+struct nr_pool {
+	nr_t last_nr;
+	nr_t nr_min;
+	nr_t nr_max;
+};
+
+struct nr_mapping {
+	struct llist_head entry;
+	struct expiring_item expiry_entry;
+
+	void *origin;
+	nr_t orig;
+	nr_t repl;
+};
+
+struct nr_map {
+	struct nr_pool *pool; /* multiple nr_maps can share a nr_pool. */
+	struct expiry *add_items_to_expiry;
+	struct llist_head mappings;
+};
+
+
+void nr_pool_init(struct nr_pool *pool, nr_t nr_min, nr_t nr_max);
+
+/* Return the next unused number from the nr_pool. */
+nr_t nr_pool_next(struct nr_pool *pool);
+
+/* Initialize the nr_mapping to zero/empty values. */
+void nr_mapping_init(struct nr_mapping *mapping);
+
+/* Remove the given mapping from its parent map and expiry queue, and call
+ * mapping->del_cb, if set. */
+void nr_mapping_del(struct nr_mapping *mapping);
+
+/* Initialize an (already allocated) nr_map, and set the map's number pool.
+ * Multiple nr_map instances may use the same nr_pool. Set the nr_map's expiry
+ * queue to exq, so that all added mappings are automatically expired after the
+ * time configured in exq. exq may be NULL to disable automatic expiry. */
+void nr_map_init(struct nr_map *map, struct nr_pool *pool,
+		 struct expiry *exq);
+
+/* Add a new entry to the map. mapping->orig, mapping->origin and
+ * mapping->del_cb must be set before calling this function. The remaining
+ * fields of *mapping will be overwritten. mapping->repl is set to the next
+ * available mapped number from map->pool. 'now' is the current clock count in
+ * seconds; if no map->expiry is used, just pass 0 for 'now'. */
+void nr_map_add(struct nr_map *map, struct nr_mapping *mapping,
+		time_t now);
+
+/* Restart the timeout for the given mapping. mapping must be a member of map.
+ */
+void nr_map_refresh(struct nr_map *map, struct nr_mapping *mapping,
+		    time_t now);
+
+/* Return a known mapping from nr_orig and the given origin. If nr_orig is
+ * unknown, return NULL. */
+struct nr_mapping *nr_map_get(const struct nr_map *map,
+			      void *origin, nr_t nr_orig);
+
+/* Return a known mapping to nr_repl. If nr_repl is unknown, return NULL. */
+struct nr_mapping *nr_map_get_inv(const struct nr_map *map, nr_t nr_repl);
+
+/* Remove all mappings from map. */
+void nr_map_clear(struct nr_map *map);
+
+/* Return 1 if map has no entries, 0 otherwise. */
+int nr_map_empty(const struct nr_map *map);
+
+
+/* config */
+
+static const int GTPH_EXPIRE_QUICKLY_SECS = 30; /* TODO is there a spec for this? */
+static const int GTPH_EXPIRE_SLOWLY_MINUTES = 6 * 60; /* TODO is there a spec for this? */
+
+struct gtphub_cfg_addr {
+	const char *addr_str;
+	uint16_t port;
+};
+
+struct gtphub_cfg_bind {
+	struct gtphub_cfg_addr bind;
+};
+
+struct gtphub_cfg {
+	struct gtphub_cfg_bind to_gsns[GTPH_SIDE_N][GTPH_PLANE_N];
+	struct gtphub_cfg_addr proxy[GTPH_SIDE_N][GTPH_PLANE_N];
+	int sgsn_use_sender; /* Use sender, not GSN addr IE with std ports */
+};
+
+
+/* state */
+
+struct gtphub_peer {
+	struct llist_head entry;
+
+	struct llist_head addresses; /* Alternatives, not load balancing. */
+	struct nr_pool seq_pool;
+	struct nr_map seq_map;
+};
+
+struct gtphub_peer_addr {
+	struct llist_head entry;
+
+	struct gtphub_peer *peer;
+	struct gsn_addr addr;
+	struct llist_head ports;
+};
+
+struct gtphub_peer_port {
+	struct llist_head entry;
+
+	struct gtphub_peer_addr *peer_addr;
+	uint16_t port;
+	unsigned int ref_count; /* references from other peers' seq_maps */
+	struct osmo_sockaddr sa; /* a "cache" for (peer_addr->addr, port) */
+	int last_restart_count; /* 0..255 = valid, all else means unknown */
+
+	struct rate_ctr_group *counters_io;
+};
+
+struct gtphub_tunnel_endpoint {
+	struct gtphub_peer_port *peer;
+	uint32_t tei_orig; /* from/to peer */
+
+	struct rate_ctr_group *counters_io;
+};
+
+struct gtphub_tunnel {
+	struct llist_head entry;
+	struct expiring_item expiry_entry;
+
+	uint32_t tei_repl; /* unique TEI to replace peers' TEIs */
+	struct gtphub_tunnel_endpoint endpoint[GTPH_SIDE_N][GTPH_PLANE_N];
+};
+
+struct gtphub_bind {
+	struct gsn_addr local_addr;
+	uint16_t local_port;
+	struct osmo_fd ofd;
+
+	/* list of struct gtphub_peer */
+	struct llist_head peers;
+
+	const char *label; /* For logging */
+	struct rate_ctr_group *counters_io;
+};
+
+struct gtphub_resolved_ggsn {
+	struct llist_head entry;
+	struct expiring_item expiry_entry;
+
+	/* The APN OI, the Operator Identifier, is the combined address,
+	 * including parts of the IMSI and APN NI, and ending with ".gprs". */
+	char apn_oi_str[GSM_APN_LENGTH];
+
+	/* Which address and port we resolved that to. */
+	struct gtphub_peer_port *peer;
+};
+
+struct gtphub {
+	struct gtphub_bind to_gsns[GTPH_SIDE_N][GTPH_PLANE_N];
+
+	/* pointers to an entry of to_gsns[s][p].peers */
+	struct gtphub_peer_port *proxy[GTPH_SIDE_N][GTPH_PLANE_N];
+
+	/* The TEI numbers will simply wrap and be reused, which will work out
+	 * in practice. Problems would arise if one given peer maintained the
+	 * same TEI for a time long enough for the TEI nr map to wrap an entire
+	 * uint32_t; if a new TEI were mapped every second, this would take
+	 * more than 100 years (in which a single given TEI must not time out)
+	 * to cause a problem. */
+	struct nr_pool tei_pool;
+
+	struct llist_head tunnels; /* struct gtphub_tunnel */
+	struct llist_head pending_deletes; /* opaque (gtphub.c) */
+
+	struct llist_head ggsn_lookups; /* opaque (gtphub_ares.c) */
+	struct llist_head resolved_ggsns; /* struct gtphub_resolved_ggsn */
+
+	struct osmo_timer_list gc_timer;
+	struct expiry expire_quickly;
+	struct expiry expire_slowly;
+
+	uint8_t restart_counter;
+
+	int sgsn_use_sender;
+};
+
+struct gtp_packet_desc;
+
+
+/* api */
+
+int gtphub_vty_init(struct gtphub *global_hub, struct gtphub_cfg *global_cfg);
+int gtphub_cfg_read(struct gtphub_cfg *cfg, const char *config_file);
+
+/* Initialize and start gtphub: bind to ports, run expiry timers. */
+int gtphub_start(struct gtphub *hub, struct gtphub_cfg *cfg,
+		 uint8_t restart_counter);
+
+/* Close all sockets, expire all maps and peers and free all allocations. The
+ * struct is then unusable, unless gtphub_start() is run on it again. */
+void gtphub_stop(struct gtphub *hub);
+
+time_t gtphub_now(void);
+
+/* Remove expired items, empty peers, ... */
+void gtphub_gc(struct gtphub *hub, time_t now);
+
+/* Return the string of the first address for this peer. */
+const char *gtphub_peer_str(struct gtphub_peer *peer);
+
+/* Return a human readable description of tun in a static buffer. */
+const char *gtphub_tunnel_str(struct gtphub_tunnel *tun);
+
+/* Return 1 if all of tun's endpoints are fully established, 0 otherwise. */
+int gtphub_tunnel_complete(struct gtphub_tunnel *tun);
+
+int gtphub_handle_buf(struct gtphub *hub,
+		      unsigned int side_idx,
+		      unsigned int port_idx,
+		      const struct osmo_sockaddr *from_addr,
+		      uint8_t *buf,
+		      size_t received,
+		      time_t now,
+		      uint8_t **reply_buf,
+		      struct osmo_fd **to_ofd,
+		      struct osmo_sockaddr *to_addr);
+
+struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub,
+					  struct gtphub_bind *bind,
+					  const struct gsn_addr *addr,
+					  uint16_t port);
+
+struct gtphub_peer_port *gtphub_port_find_sa(const struct gtphub_bind *bind,
+					     const struct osmo_sockaddr *addr);
+
+void gtphub_resolved_ggsn(struct gtphub *hub, const char *apn_oi_str,
+			  struct gsn_addr *resolved_addr,
+			  time_t now);
+
+const char *gtphub_port_str(struct gtphub_peer_port *port);
+
+int gtphub_write(const struct osmo_fd *to,
+		 const struct osmo_sockaddr *to_addr,
+		 const uint8_t *buf, size_t buf_len);
diff --git a/include/osmocom/sgsn/oap_client.h b/include/osmocom/sgsn/oap_client.h
new file mode 100644
index 0000000..80c86d5
--- /dev/null
+++ b/include/osmocom/sgsn/oap_client.h
@@ -0,0 +1,82 @@
+/* Osmocom Authentication Protocol API */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct msgb;
+struct osmo_oap_message;
+
+/* This is the config part for vty. It is essentially copied in
+ * oap_client_state, where values are copied over once the config is
+ * considered valid. */
+struct oap_client_config {
+	uint16_t client_id;
+	int secret_k_present;
+	uint8_t secret_k[16];
+	int secret_opc_present;
+	uint8_t secret_opc[16];
+};
+
+/* The runtime state of the OAP client. client_id and the secrets are in fact
+ * duplicated from oap_client_config, so that a separate validation of the
+ * config data is possible, and so that only a struct oap_client_state* is
+ * passed around. */
+struct oap_client_state {
+	enum {
+		OAP_UNINITIALIZED = 0,	/* just allocated. */
+		OAP_DISABLED,		/* disabled by config. */
+		OAP_INITIALIZED,	/* enabled, config is valid. */
+		OAP_REQUESTED_CHALLENGE,
+		OAP_SENT_CHALLENGE_RESULT,
+		OAP_REGISTERED
+	} state;
+	uint16_t client_id;
+	uint8_t secret_k[16];
+	uint8_t secret_opc[16];
+	int registration_failures;
+};
+
+/* From config, initialize state. Return 0 on success. */
+int oap_client_init(struct oap_client_config *config,
+		    struct oap_client_state *state);
+
+/* Construct an OAP registration message and return in *msg_tx. Use
+ * state->client_id and update state->state.
+ * Return 0 on success, or a negative value on error.
+ * If an error is returned, *msg_tx is guaranteed to be NULL. */
+int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx);
+
+/* Decode and act on a received OAP message msg_rx. Update state->state.  If a
+ * non-NULL pointer is returned in *msg_tx, that msgb should be sent to the OAP
+ * server (and freed) by the caller. The received msg_rx is not freed.
+ * Return 0 on success, or a negative value on error.
+ * If an error is returned, *msg_tx is guaranteed to be NULL. */
+int oap_client_handle(struct oap_client_state *state,
+		      const struct msgb *msg_rx, struct msgb **msg_tx);
+
+/* Allocate a msgb and in it, return the encoded oap_client_msg. Return
+ * NULL on error. (Like oap_client_encode(), but also allocates a msgb.)
+ * About the name: the idea is do_something(oap_client_encoded(my_struct))
+ */
+struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_client_msg);
diff --git a/include/osmocom/sgsn/rest_octets.h b/include/osmocom/sgsn/rest_octets.h
new file mode 100644
index 0000000..0dbe7ae
--- /dev/null
+++ b/include/osmocom/sgsn/rest_octets.h
@@ -0,0 +1,139 @@
+#ifndef _REST_OCTETS_H
+#define _REST_OCTETS_H
+
+#include <stdbool.h>
+#include <osmocom/sgsn/gsm_04_08.h>
+#include <osmocom/gsm/sysinfo.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net);
+int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts);
+int rest_octets_si6(uint8_t *data, bool is1800_net);
+
+struct gsm48_si_selection_params {
+	uint16_t penalty_time:5,
+		  temp_offs:3,
+		  cell_resel_off:6,
+		  cbq:1,
+		  present:1;
+};
+
+struct gsm48_si_power_offset {
+	uint8_t power_offset:2,
+		 present:1;
+};
+
+struct gsm48_si3_gprs_ind {
+	uint8_t si13_position:1,
+		 ra_colour:3,
+		 present:1;
+};
+
+struct gsm48_lsa_params {
+	uint32_t prio_thr:3,
+		 lsa_offset:3,
+		 mcc:12,
+		 mnc:12;
+	unsigned int present;
+};
+
+struct gsm48_si_ro_info {
+	struct gsm48_si_selection_params selection_params;
+	struct gsm48_si_power_offset power_offset;
+	uint8_t si2ter_indicator;
+	uint8_t early_cm_ctrl;
+	struct {
+		uint8_t where:3,
+			 present:1;
+	} scheduling;
+	struct gsm48_si3_gprs_ind gprs_ind;
+	/* SI 3 specific */
+	uint8_t si2quater_indicator;
+	/* SI 4 specific */
+	struct gsm48_lsa_params lsa_params;
+	uint16_t cell_id;
+	uint8_t break_ind;	/* do we have SI7 + SI8 ? */
+};
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(uint8_t *data, const struct gsm48_si_ro_info *si3);
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len);
+
+enum pbcch_carrier_type {
+	PBCCH_BCCH,
+	PBCCH_ARFCN,
+	PBCCH_MAIO
+};
+
+/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
+enum gprs_nmo {
+	GPRS_NMO_I	= 0,	/* CS pagin on GPRS paging or traffic channel */
+	GPRS_NMO_II	= 1,	/* all paging on CCCH */
+	GPRS_NMO_III	= 2,	/* no paging coordination */
+};
+
+/* TS 04.60 12.24 */
+struct gprs_cell_options {
+	enum gprs_nmo nmo;
+	/* T3168: wait for packet uplink assignment message */
+	uint32_t t3168;	/* in milliseconds */
+	/* T3192: wait for release of the TBF after reception of the final block */
+	uint32_t t3192;	/* in milliseconds */
+	uint32_t drx_timer_max;/* in seconds */
+	uint32_t bs_cv_max;
+	uint8_t  supports_egprs_11bit_rach;
+	bool ctrl_ack_type_use_block; /* use PACKET CONTROL ACKNOWLEDGMENT */
+
+	uint8_t ext_info_present;
+	struct {
+		uint8_t egprs_supported;
+			uint8_t use_egprs_p_ch_req;
+			uint8_t bep_period;
+		uint8_t pfc_supported;
+		uint8_t dtm_supported;
+		uint8_t bss_paging_coordination;
+	} ext_info;
+};
+
+/* TS 04.60 Table 12.9.2 */
+struct gprs_power_ctrl_pars {
+	uint8_t alpha;
+	uint8_t t_avg_w;
+	uint8_t t_avg_t;
+	uint8_t pc_meas_chan;
+	uint8_t n_avg_i;
+};
+
+struct gsm48_si13_info {
+	struct gprs_cell_options cell_opts;
+	struct gprs_power_ctrl_pars pwr_ctrl_pars;
+	uint8_t bcch_change_mark;
+	uint8_t si_change_field;
+	uint8_t pbcch_present;
+
+	union {
+		struct {
+			uint8_t rac;
+			uint8_t spgc_ccch_sup;
+			uint8_t net_ctrl_ord;
+			uint8_t prio_acc_thr;
+		} no_pbcch;
+		struct {
+			uint8_t psi1_rep_per;
+			uint8_t pb;
+			uint8_t tsc;
+			uint8_t tn;
+			enum pbcch_carrier_type carrier_type;
+			uint16_t arfcn;
+			uint8_t maio;
+		} pbcch;
+	};
+};
+
+/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13);
+
+#endif /* _REST_OCTETS_H */
diff --git a/include/osmocom/sgsn/sgsn.h b/include/osmocom/sgsn/sgsn.h
new file mode 100644
index 0000000..e4eda17
--- /dev/null
+++ b/include/osmocom/sgsn/sgsn.h
@@ -0,0 +1,191 @@
+#ifndef _SGSN_H
+#define _SGSN_H
+
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/crypt/gprs_cipher.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/oap_client.h>
+#include <osmocom/sgsn/common.h>
+
+#include <ares.h>
+
+struct gprs_gsup_client;
+struct hostent;
+
+enum sgsn_auth_policy {
+	SGSN_AUTH_POLICY_OPEN,
+	SGSN_AUTH_POLICY_CLOSED,
+	SGSN_AUTH_POLICY_ACL_ONLY,
+	SGSN_AUTH_POLICY_REMOTE
+};
+
+
+enum sgsn_rate_ctr_keys {
+	CTR_LLC_DL_BYTES,
+	CTR_LLC_UL_BYTES,
+	CTR_LLC_DL_PACKETS,
+	CTR_LLC_UL_PACKETS,
+	CTR_GPRS_ATTACH_REQUEST,
+	CTR_GPRS_ATTACH_ACKED,
+	CTR_GPRS_ATTACH_REJECTED,
+	CTR_GPRS_DETACH_REQUEST,
+	CTR_GPRS_DETACH_ACKED,
+	CTR_GPRS_ROUTING_AREA_REQUEST,
+	CTR_GPRS_ROUTING_AREA_ACKED,
+	CTR_GPRS_ROUTING_AREA_REJECT,
+	/* PDP single packet counter / GSM 04.08 9.5.1 - 9.5.9 */
+	CTR_PDP_ACTIVATE_REQUEST,
+	CTR_PDP_ACTIVATE_REJECT,
+	CTR_PDP_ACTIVATE_ACCEPT,
+	CTR_PDP_REQUEST_ACTIVATE, /* unused */
+	CTR_PDP_REQUEST_ACTIVATE_REJ, /* unused */
+	CTR_PDP_MODIFY_REQUEST, /* unsued */
+	CTR_PDP_MODIFY_ACCEPT, /* unused */
+	CTR_PDP_DL_DEACTIVATE_REQUEST,
+	CTR_PDP_DL_DEACTIVATE_ACCEPT,
+	CTR_PDP_UL_DEACTIVATE_REQUEST,
+	CTR_PDP_UL_DEACTIVATE_ACCEPT,
+};
+
+struct sgsn_cdr {
+	char *filename;
+	int interval;
+};
+
+struct sgsn_config {
+	/* parsed from config file */
+
+	char *gtp_statedir;
+	struct sockaddr_in gtp_listenaddr;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+
+	enum sgsn_auth_policy auth_policy;
+	enum gprs_ciph_algo cipher;
+	struct llist_head imsi_acl;
+
+	struct sockaddr_in gsup_server_addr;
+	int gsup_server_port;
+
+	int require_authentication;
+	int require_update_location;
+
+	/* CDR configuration */
+	struct sgsn_cdr cdr;
+
+	struct {
+		int T3312;
+		int T3322;
+		int T3350;
+		int T3360;
+		int T3370;
+		int T3313;
+		int T3314;
+		int T3316;
+		int T3385;
+		int T3386;
+		int T3395;
+		int T3397;
+	} timers;
+
+	int dynamic_lookup;
+
+	struct oap_client_config oap;
+
+	/* RFC1144 TCP/IP header compression */
+	struct {
+		int active;
+		int passive;
+		int s01;
+	} pcomp_rfc1144;
+
+	/* V.42vis data compression */
+	struct {
+		int active;
+		int passive;
+		int p0;
+		int p1;
+		int p2;
+	} dcomp_v42bis;
+
+	struct {
+		int rab_assign_addr_enc;
+	} iu;
+};
+
+struct sgsn_instance {
+	char *config_file;
+	struct sgsn_config cfg;
+	/* File descriptor wrappers for LibGTP */
+	struct osmo_fd gtp_fd0;
+	struct osmo_fd gtp_fd1c;
+	struct osmo_fd gtp_fd1u;
+	/* Timer for libGTP */
+	struct osmo_timer_list gtp_timer;
+	/* GSN instance for libgtp */
+	struct gsn_t *gsn;
+	/* Subscriber */
+	struct gsup_client *gsup_client;
+	/* LLME inactivity timer */
+	struct osmo_timer_list llme_timer;
+
+	/* c-ares event loop integration */
+	struct osmo_timer_list ares_timer;
+	struct llist_head ares_fds;
+	ares_channel ares_channel;
+	struct ares_addr_node *ares_servers;
+
+	struct rate_ctr_group *rate_ctrs;
+};
+
+extern struct sgsn_instance *sgsn;
+
+/* sgsn_vty.c */
+
+int sgsn_vty_init(struct sgsn_config *cfg);
+int sgsn_parse_config(const char *config_file);
+
+/* sgsn.c */
+
+/* Main input function for Gb proxy */
+int sgsn_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+
+struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn,
+					 struct sgsn_mm_ctx *mmctx,
+					 uint16_t nsapi,
+					 struct tlv_parsed *tp);
+int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx);
+void sgsn_pdp_upd_gtp_u(struct sgsn_pdp_ctx *pdp, void *addr, size_t alen);
+
+/* gprs_sndcp.c */
+
+/* Entry point for the SNSM-ACTIVATE.indication */
+int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi);
+/* Entry point for the SNSM-DEACTIVATE.indication */
+int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi);
+/* Called by SNDCP when it has received/re-assembled a N-PDU */
+int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi,
+			 struct msgb *msg, uint32_t npdu_len, uint8_t *npdu);
+int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi,
+			void *mmcontext);
+int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle,
+			 uint8_t *hdr, uint16_t len);
+
+
+/*
+ * CDR related functionality
+ */
+int sgsn_cdr_init(struct sgsn_instance *sgsn);
+
+
+/*
+ * C-ARES related functionality
+ */
+int sgsn_ares_init(struct sgsn_instance *sgsn);
+int sgsn_ares_query(struct sgsn_instance *sgsm, const char *name, ares_host_callback cb, void *data);
+
+#endif
diff --git a/include/osmocom/sgsn/signal.h b/include/osmocom/sgsn/signal.h
new file mode 100644
index 0000000..4b6ba56
--- /dev/null
+++ b/include/osmocom/sgsn/signal.h
@@ -0,0 +1,52 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2009-2010, 2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_SIGNAL_H
+#define OPENBSC_SIGNAL_H
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/signal.h>
+
+enum signal_subsystems {
+	SS_SGSN,
+};
+
+/* GPRS SGSN signals SS_SGSN */
+enum signal_sgsn {
+	S_SGSN_ATTACH,
+	S_SGSN_DETACH,
+	S_SGSN_UPDATE,
+	S_SGSN_PDP_ACT,
+	S_SGSN_PDP_DEACT,
+	S_SGSN_PDP_TERMINATE,
+	S_SGSN_PDP_FREE,
+	S_SGSN_MM_FREE,
+};
+
+struct sgsn_mm_ctx;
+struct sgsn_signal_data {
+	struct sgsn_mm_ctx *mm;
+	struct sgsn_pdp_ctx *pdp;	/* non-NULL for PDP_ACT, PDP_DEACT, PDP_FREE */
+};
+
+#endif
diff --git a/include/osmocom/sgsn/slhc.h b/include/osmocom/sgsn/slhc.h
new file mode 100644
index 0000000..cd5a47c
--- /dev/null
+++ b/include/osmocom/sgsn/slhc.h
@@ -0,0 +1,187 @@
+#ifndef _SLHC_H
+#define _SLHC_H
+/*
+ * Definitions for tcp compression routines.
+ *
+ * $Header: slcompress.h,v 1.10 89/12/31 08:53:02 van Exp $
+ *
+ * Copyright (c) 1989 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ *	Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
+ *	- Initial distribution.
+ *
+ *
+ * modified for KA9Q Internet Software Package by
+ * Katie Stevens (dkstevens@ucdavis.edu)
+ * University of California, Davis
+ * Computing Services
+ *	- 01-31-90	initial adaptation
+ *
+ *	- Feb 1991	Bill_Simpson@um.cc.umich.edu
+ *			variable number of conversation slots
+ *			allow zero or one slots
+ *			separate routines
+ *			status display
+ */
+
+/*
+ * Compressed packet format:
+ *
+ * The first octet contains the packet type (top 3 bits), TCP
+ * 'push' bit, and flags that indicate which of the 4 TCP sequence
+ * numbers have changed (bottom 5 bits).  The next octet is a
+ * conversation number that associates a saved IP/TCP header with
+ * the compressed packet.  The next two octets are the TCP checksum
+ * from the original datagram.  The next 0 to 15 octets are
+ * sequence number changes, one change per bit set in the header
+ * (there may be no changes and there are two special cases where
+ * the receiver implicitly knows what changed -- see below).
+ *
+ * There are 5 numbers which can change (they are always inserted
+ * in the following order): TCP urgent pointer, window,
+ * acknowledgment, sequence number and IP ID.  (The urgent pointer
+ * is different from the others in that its value is sent, not the
+ * change in value.)  Since typical use of SLIP links is biased
+ * toward small packets (see comments on MTU/MSS below), changes
+ * use a variable length coding with one octet for numbers in the
+ * range 1 - 255 and 3 octets (0, MSB, LSB) for numbers in the
+ * range 256 - 65535 or 0.  (If the change in sequence number or
+ * ack is more than 65535, an uncompressed packet is sent.)
+ */
+
+/*
+ * Packet types (must not conflict with IP protocol version)
+ *
+ * The top nibble of the first octet is the packet type.  There are
+ * three possible types: IP (not proto TCP or tcp with one of the
+ * control flags set); uncompressed TCP (a normal IP/TCP packet but
+ * with the 8-bit protocol field replaced by an 8-bit connection id --
+ * this type of packet syncs the sender & receiver); and compressed
+ * TCP (described above).
+ *
+ * LSB of 4-bit field is TCP "PUSH" bit (a worthless anachronism) and
+ * is logically part of the 4-bit "changes" field that follows.  Top
+ * three bits are actual packet type.  For backward compatibility
+ * and in the interest of conserving bits, numbers are chosen so the
+ * IP protocol version number (4) which normally appears in this nibble
+ * means "IP packet".
+ */
+
+
+#include <linux/ip.h>
+#include <linux/tcp.h>
+
+/* SLIP compression masks for len/vers byte */
+#define SL_TYPE_IP 0x40
+#define SL_TYPE_UNCOMPRESSED_TCP 0x70
+#define SL_TYPE_COMPRESSED_TCP 0x80
+#define SL_TYPE_ERROR 0x00
+
+/* Bits in first octet of compressed packet */
+#define NEW_C	0x40	/* flag bits for what changed in a packet */
+#define NEW_I	0x20
+#define NEW_S	0x08
+#define NEW_A	0x04
+#define NEW_W	0x02
+#define NEW_U	0x01
+
+/* reserved, special-case values of above */
+#define SPECIAL_I (NEW_S|NEW_W|NEW_U)		/* echoed interactive traffic */
+#define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U)	/* unidirectional data */
+#define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U)
+
+#define TCP_PUSH_BIT 0x10
+
+/*
+ * data type and sizes conversion assumptions:
+ *
+ *	VJ code		KA9Q style	generic
+ *	u_char		byte_t		unsigned char	 8 bits
+ *	u_short		int16		unsigned short	16 bits
+ *	u_int		int16		unsigned short	16 bits
+ *	u_long		unsigned long	unsigned long	32 bits
+ *	int		int32		long		32 bits
+ */
+
+typedef __u8 byte_t;
+typedef __u32 int32;
+
+/*
+ * "state" data for each active tcp conversation on the wire.  This is
+ * basically a copy of the entire IP/TCP header from the last packet
+ * we saw from the conversation together with a small identifier
+ * the transmit & receive ends of the line use to locate saved header.
+ */
+struct cstate {
+	byte_t	cs_this;	/* connection id number (xmit) */
+	struct cstate *next;	/* next in ring (xmit) */
+	struct iphdr cs_ip;	/* ip/tcp hdr from most recent packet */
+	struct tcphdr cs_tcp;
+	unsigned char cs_ipopt[64];
+	unsigned char cs_tcpopt[64];
+	int cs_hsize;
+};
+#define NULLSLSTATE	(struct cstate *)0
+
+/*
+ * all the state data for one serial line (we need one of these per line).
+ */
+struct slcompress {
+	struct cstate *tstate;	/* transmit connection states (array)*/
+	struct cstate *rstate;	/* receive connection states (array)*/
+
+	byte_t tslot_limit;	/* highest transmit slot id (0-l)*/
+	byte_t rslot_limit;	/* highest receive slot id (0-l)*/
+
+	byte_t xmit_oldest;	/* oldest xmit in ring */
+	byte_t xmit_current;	/* most recent xmit id */
+	byte_t recv_current;	/* most recent rcvd id */
+
+	byte_t flags;
+#define SLF_TOSS	0x01	/* tossing rcvd frames until id received */
+
+	int32 sls_o_nontcp;	/* outbound non-TCP packets */
+	int32 sls_o_tcp;	/* outbound TCP packets */
+	int32 sls_o_uncompressed;	/* outbound uncompressed packets */
+	int32 sls_o_compressed;	/* outbound compressed packets */
+	int32 sls_o_searches;	/* searches for connection state */
+	int32 sls_o_misses;	/* times couldn't find conn. state */
+
+	int32 sls_i_uncompressed;	/* inbound uncompressed packets */
+	int32 sls_i_compressed;	/* inbound compressed packets */
+	int32 sls_i_error;	/* inbound error packets */
+	int32 sls_i_tossed;	/* inbound packets tossed because of error */
+
+	int32 sls_i_runt;
+	int32 sls_i_badcheck;
+};
+#define NULLSLCOMPR	(struct slcompress *)0
+
+/* In slhc.c: */
+struct slcompress *slhc_init(const void *ctx, int rslots, int tslots);
+
+void slhc_free(struct slcompress *comp);
+
+int slhc_compress(struct slcompress *comp, unsigned char *icp, int isize,
+		  unsigned char *ocp, unsigned char **cpp, int compress_cid);
+int slhc_uncompress(struct slcompress *comp, unsigned char *icp, int isize);
+int slhc_remember(struct slcompress *comp, unsigned char *icp, int isize);
+int slhc_toss(struct slcompress *comp);
+
+void slhc_i_status(struct slcompress *comp);
+void slhc_o_status(struct slcompress *comp);
+
+#endif	/* _SLHC_H */
diff --git a/include/osmocom/sgsn/v42bis.h b/include/osmocom/sgsn/v42bis.h
new file mode 100644
index 0000000..607a58e
--- /dev/null
+++ b/include/osmocom/sgsn/v42bis.h
@@ -0,0 +1,147 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * v42bis.h
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2011 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation.
+ *
+ * This 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*! \page v42bis_page V.42bis modem data compression
+\section v42bis_page_sec_1 What does it do?
+The v.42bis specification defines a data compression scheme, to work in
+conjunction with the error correction scheme defined in V.42.
+
+\section v42bis_page_sec_2 How does it work?
+*/
+
+#include <stdint.h>
+
+#if !defined(_SPANDSP_V42BIS_H_)
+#define _SPANDSP_V42BIS_H_
+
+#define SPAN_DECLARE(x) x
+
+#define V42BIS_MIN_STRING_SIZE      6
+#define V42BIS_MAX_STRING_SIZE      250
+#define V42BIS_MIN_DICTIONARY_SIZE  512
+#define V42BIS_MAX_BITS             12
+#define V42BIS_MAX_CODEWORDS        4096    /* 2^V42BIS_MAX_BITS */
+#define V42BIS_MAX_OUTPUT_LENGTH    1024
+
+enum
+{
+    V42BIS_P0_NEITHER_DIRECTION = 0,
+    V42BIS_P0_INITIATOR_RESPONDER,
+    V42BIS_P0_RESPONDER_INITIATOR,
+    V42BIS_P0_BOTH_DIRECTIONS
+};
+
+enum
+{
+    V42BIS_COMPRESSION_MODE_DYNAMIC = 0,
+    V42BIS_COMPRESSION_MODE_ALWAYS,
+    V42BIS_COMPRESSION_MODE_NEVER
+};
+
+typedef void (*put_msg_func_t)(void *user_data, const uint8_t *msg, int len);
+
+/*!
+    V.42bis compression/decompression descriptor. This defines the working state for a
+    single instance of V.42bis compress/decompression.
+*/
+typedef struct v42bis_state_s v42bis_state_t;
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! Compress a block of octets.
+    \param s The V.42bis context.
+    \param buf The data to be compressed.
+    \param len The length of the data buffer.
+    \return 0 */
+SPAN_DECLARE(int) v42bis_compress(v42bis_state_t *s, const uint8_t buf[], int len);
+
+/*! Flush out any data remaining in a compression buffer.
+    \param s The V.42bis context.
+    \return 0 */
+SPAN_DECLARE(int) v42bis_compress_flush(v42bis_state_t *s);
+
+/*! Decompress a block of octets.
+    \param s The V.42bis context.
+    \param buf The data to be decompressed.
+    \param len The length of the data buffer.
+    \return 0 */
+SPAN_DECLARE(int) v42bis_decompress(v42bis_state_t *s, const uint8_t buf[], int len);
+    
+/*! Flush out any data remaining in the decompression buffer.
+    \param s The V.42bis context.
+    \return 0 */
+SPAN_DECLARE(int) v42bis_decompress_flush(v42bis_state_t *s);
+
+/*! Set the compression mode.
+    \param s The V.42bis context.
+    \param mode One of the V.42bis compression modes -
+            V42BIS_COMPRESSION_MODE_DYNAMIC,
+            V42BIS_COMPRESSION_MODE_ALWAYS,
+            V42BIS_COMPRESSION_MODE_NEVER */
+SPAN_DECLARE(void) v42bis_compression_control(v42bis_state_t *s, int mode);
+
+/*! Initialise a V.42bis context.
+    \param s The V.42bis context.
+    \param negotiated_p0 The negotiated P0 parameter, from the V.42bis spec.
+    \param negotiated_p1 The negotiated P1 parameter, from the V.42bis spec.
+    \param negotiated_p2 The negotiated P2 parameter, from the V.42bis spec.
+    \param encode_handler Encode callback handler.
+    \param encode_user_data An opaque pointer passed to the encode callback handler.
+    \param max_encode_len The maximum length that should be passed to the encode handler.
+    \param decode_handler Decode callback handler.
+    \param decode_user_data An opaque pointer passed to the decode callback handler.
+    \param max_decode_len The maximum length that should be passed to the decode handler.
+    \return The V.42bis context. */
+SPAN_DECLARE(v42bis_state_t *) v42bis_init(const void *ctx,
+                                           v42bis_state_t *s,
+                                           int negotiated_p0,
+                                           int negotiated_p1,
+                                           int negotiated_p2,
+                                           put_msg_func_t encode_handler,
+                                           void *encode_user_data,
+                                           int max_encode_len,
+                                           put_msg_func_t decode_handler,
+                                           void *decode_user_data,
+                                           int max_decode_len);
+
+/*! Release a V.42bis context.
+    \param s The V.42bis context.
+    \return 0 if OK */
+SPAN_DECLARE(int) v42bis_release(v42bis_state_t *s);
+
+/*! Free a V.42bis context.
+    \param s The V.42bis context.
+    \return 0 if OK */
+SPAN_DECLARE(int) v42bis_free(v42bis_state_t *s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/include/osmocom/sgsn/v42bis_private.h b/include/osmocom/sgsn/v42bis_private.h
new file mode 100644
index 0000000..daa5ea3
--- /dev/null
+++ b/include/osmocom/sgsn/v42bis_private.h
@@ -0,0 +1,126 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/v42bis.h
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation.
+ *
+ * This 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_V42BIS_H_)
+#define _SPANDSP_PRIVATE_V42BIS_H_
+
+/*!
+    V.42bis dictionary node.
+    Note that 0 is not a valid node to point to (0 is always a control code), so 0 is used
+    as a "no such value" marker in this structure.
+*/
+typedef struct
+{
+    /*! \brief The value of the octet represented by the current dictionary node */
+    uint8_t node_octet;
+    /*! \brief The parent of this node */
+    uint16_t parent;
+    /*! \brief The first child of this node */
+    uint16_t child;
+    /*! \brief The next node at the same depth */
+    uint16_t next;
+} v42bis_dict_node_t;
+
+/*!
+    V.42bis compression or decompression. This defines the working state for a single instance
+    of V.42bis compression or decompression.
+*/
+typedef struct
+{
+    /*! \brief Compression enabled. */
+    int v42bis_parm_p0;
+    /*! \brief Compression mode. */
+    int compression_mode;
+    /*! \brief Callback function to handle output data. */
+    put_msg_func_t handler;
+    /*! \brief An opaque pointer passed in calls to the data handler. */
+    void *user_data;
+    /*! \brief The maximum amount to be passed to the data handler. */
+    int max_output_len;
+
+    /*! \brief TRUE if we are in transparent (i.e. uncompressable) mode */
+    int transparent;
+    /*! \brief Next empty dictionary entry */
+    uint16_t v42bis_parm_c1;
+    /*! \brief Current codeword size */
+    uint16_t v42bis_parm_c2;
+    /*! \brief Threshold for codeword size change */
+    uint16_t v42bis_parm_c3;
+    /*! \brief The current update point in the dictionary */
+    uint16_t update_at;
+    /*! \brief The last entry matched in the dictionary */
+    uint16_t last_matched;
+    /*! \brief The last entry added to the dictionary */
+    uint16_t last_added;
+    /*! \brief Total number of codewords in the dictionary */
+    int v42bis_parm_n2;
+    /*! \brief Maximum permitted string length */
+    int v42bis_parm_n7;
+    /*! \brief The dictionary */
+    v42bis_dict_node_t dict[V42BIS_MAX_CODEWORDS];
+
+    /*! \brief The octet string in progress */
+    uint8_t string[V42BIS_MAX_STRING_SIZE];
+    /*! \brief The current length of the octet string in progress */
+    int string_length;
+    /*! \brief The amount of the octet string in progress which has already
+        been flushed out of the buffer */
+    int flushed_length;
+
+    /*! \brief Compression performance metric */
+    uint16_t compression_performance;
+
+    /*! \brief Outgoing bit buffer (compression), or incoming bit buffer (decompression) */ 
+    uint32_t bit_buffer;
+    /*! \brief Outgoing bit count (compression), or incoming bit count (decompression) */ 
+    int bit_count;
+
+    /*! \brief The output composition buffer */
+    uint8_t output_buf[V42BIS_MAX_OUTPUT_LENGTH];
+    /*! \brief The length of the contents of the output composition buffer */    
+    int output_octet_count;
+
+    /*! \brief The current value of the escape code */
+    uint8_t escape_code;
+    /*! \brief TRUE if we just hit an escape code, and are waiting for the following octet */
+    int escaped;
+} v42bis_comp_state_t;
+
+/*!
+    V.42bis compression/decompression descriptor. This defines the working state for a
+    single instance of V.42bis compress/decompression.
+*/
+struct v42bis_state_s
+{
+    /*! \brief Compression state. */
+    v42bis_comp_state_t compress;
+    /*! \brief Decompression state. */
+    v42bis_comp_state_t decompress;
+
+    /*! \brief Error and flow logging control */
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/include/osmocom/sgsn/vty.h b/include/osmocom/sgsn/vty.h
new file mode 100644
index 0000000..d2e3d9a
--- /dev/null
+++ b/include/osmocom/sgsn/vty.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <osmocom/vty/command.h>
+
+enum bsc_vty_node {
+	GBPROXY_NODE = _LAST_OSMOVTY_NODE + 1,
+	SGSN_NODE,
+	GTPHUB_NODE,
+};