[GPRS] More work on a real SGSN
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index b9ed1b2..8ad6ba1 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -2,6 +2,9 @@
 #define _GPRS_SGSN_H
 
 #include <stdint.h>
+#include <netinet/in.h>
+
+#include <osmocore/gsm48.h>
 
 #define GSM_IMSI_LENGTH 17
 #define GSM_IMEI_LENGTH 17
@@ -72,39 +75,6 @@
 	unsigned int		T;
 };
 
-enum pdp_ctx_state {
-	PDP_STAE_NONE,
-};
-
-enum pdp_type {
-	PDP_TYPE_NONE,
-};
-
-struct sgsn_pdp_ctx {
-	struct llist_head	list;
-
-	unsigned int		id;
-	enum pdp_ctx_state	state;
-	enum pdp_type		type;
-	uint32_t		address;
-	char 			*apn_subscribed;
-	char 			*apn_used;
-	uint16_t		nsapi;
-	uint8_t			ti;	/* transaction identifier */
-	uint32_t		ggsn_in_use;
-	int			vplmn_allowed;
-	uint32_t		qos_profile_subscr;
-	uint32_t		qos_profile_req;
-	uint32_t		qos_profile_neg;
-	uint8_t			radio_prio;
-	uint32_t		tx_npdu_nr;
-	uint32_t		rx_npdu_nr;
-	uint32_t		tx_gtp_snd;
-	uint32_t		rx_gtp_snu;
-	uint32_t		charging_id;
-	int			reordering_reqd;
-};
-
 /* look-up a SGSN MM context based on TLLI + RAI */
 struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
 					const struct gprs_ra_id *raid);
@@ -115,4 +85,72 @@
 struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
 					const struct gprs_ra_id *raid);
 
+
+enum pdp_ctx_state {
+	PDP_STATE_NONE,
+};
+
+enum pdp_type {
+	PDP_TYPE_NONE,
+};
+
+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 */
+	struct sgsn_ggsn_ctx	*ggsn;	/* which GGSN serves this PDP */
+
+	//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;
+	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		tx_npdu_nr;
+	uint32_t		rx_npdu_nr;
+	uint32_t		tx_gtp_snd;
+	uint32_t		rx_gtp_snu;
+	//uint32_t		charging_id;
+	int			reordering_reqd;
+};
+
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+					   uint8_t nsapi);
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+					uint8_t nsapi);
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp);
+
+
+struct ggsn_ctx {
+	struct llist_head list;
+	uint32_t id;
+	unsigned int gtp_version;
+	struct in_addr remote_addr;
+	struct gsn_t *gsn;
+};
+struct ggsn_ctx *ggsn_ctx_alloc(uint32_t id);
+struct ggsn_ctx *ggsn_ctx_by_id(uint32_t id);
+struct ggsn_ctx *ggsn_ctx_find_alloc(uint32_t id);
+
+struct apn_ctx {
+	struct llist_head list;
+	struct ggsn_ctx *ggsn;
+	char *name;
+	char *description;
+};
+
+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;
+
 #endif /* _GPRS_SGSN_H */
diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c
index fc9d007..e1a6dea 100644
--- a/openbsc/src/gprs/gprs_gmm.c
+++ b/openbsc/src/gprs/gprs_gmm.c
@@ -47,6 +47,9 @@
 #include <openbsc/gprs_bssgp.h>
 #include <openbsc/gprs_llc.h>
 #include <openbsc/gprs_sgsn.h>
+#include <openbsc/sgsn.h>
+
+extern struct sgsn_instance *sgsn;
 
 /* Protocol related stuff, should go into libosmocore */
 
@@ -266,20 +269,17 @@
 }
 
 /* Parse Chapter 9.4.13 Identity Response */
-static int gsm48_rx_gmm_id_resp(struct msgb *msg)
+static int gsm48_rx_gmm_id_resp(struct sgsn_mm_ctx *ctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
 	char mi_string[GSM48_MI_SIZE];
 	struct gprs_ra_id ra_id;
-	struct sgsn_mm_ctx *ctx;
 
 	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
 	DEBUGP(DMM, "-> GMM IDENTITY RESPONSE: mi_type=0x%02x MI(%s) ",
 		mi_type, mi_string);
 
-	bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
-	ctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
 	if (!ctx) {
 		DEBUGP(DMM, "from unknown TLLI 0x%08x?!?\n", msgb_tlli(msg));
 		return -EINVAL;
@@ -323,7 +323,7 @@
 }
 
 /* Section 9.4.1 Attach request */
-static int gsm48_rx_gmm_att_req(struct msgb *msg)
+static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	uint8_t *cur = gh->data, *msnc, *mi, *old_ra_info;
@@ -428,20 +428,10 @@
 }
 
 /* Section 4.7.4.1 / 9.4.5.2 MO Detach request */
-static int gsm48_rx_gmm_det_req(struct msgb *msg)
+static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
-	struct sgsn_mm_ctx *ctx;
 	uint8_t detach_type, power_off;
-	struct gprs_ra_id ra_id;
-
-	bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
-	ctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
-	if (!ctx) {
-		LOGP(DMM, LOGL_NOTICE, "-> GMM DETACH REQUEST for unknown "
-			"TLLI=0x%08x\n", msgb_tlli(msg));
-		/* FIXME: send detach reject */
-	}
 
 	detach_type = gh->data[0] & 0x7;
 	power_off = gh->data[0] & 0x8;
@@ -508,10 +498,9 @@
 }
 
 /* Chapter 9.4.14: Routing area update request */
-static int gsm48_rx_gmm_ra_upd_req(struct msgb *msg)
+static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
-	struct sgsn_mm_ctx *mmctx;
 	uint8_t *cur = gh->data;
 	struct gprs_ra_id old_ra_id;
 	uint8_t upd_type;
@@ -543,7 +532,6 @@
 	}
 
 	/* Look-up the MM context based on old RA-ID and TLLI */
-	mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &old_ra_id);
 	if (!mmctx || mmctx->mm_state == GMM_DEREGISTERED) {
 		/* The MS has to perform GPRS attach */
 		DEBUGPC(DMM, " REJECT\n");
@@ -561,7 +549,7 @@
 	return gsm48_tx_gmm_ra_upd_ack(msg);
 }
 
-static int gsm48_rx_gmm_status(struct msgb *msg)
+static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = msgb_l3(msg);
 
@@ -572,26 +560,34 @@
 }
 
 /* GPRS Mobility Management */
-static int gsm0408_rcv_gmm(struct msgb *msg)
+static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	int rc;
 
+	if (!mmctx &&
+	    gh->msg_type != GSM48_MT_GMM_ATTACH_REQ &&
+	    gh->msg_type != GSM48_MT_GMM_RA_UPD_REQ) {
+		LOGP(DMM, LOGL_NOTICE, "Cannot handle GMM for unknown MM CTX\n");
+		return -EINVAL;
+	}
+
+
 	switch (gh->msg_type) {
 	case GSM48_MT_GMM_RA_UPD_REQ:
-		rc = gsm48_rx_gmm_ra_upd_req(msg);
+		rc = gsm48_rx_gmm_ra_upd_req(mmctx, msg);
 		break;
 	case GSM48_MT_GMM_ATTACH_REQ:
-		rc = gsm48_rx_gmm_att_req(msg);
+		rc = gsm48_rx_gmm_att_req(mmctx, msg);
 		break;
 	case GSM48_MT_GMM_ID_RESP:
-		rc = gsm48_rx_gmm_id_resp(msg);
+		rc = gsm48_rx_gmm_id_resp(mmctx, msg);
 		break;
 	case GSM48_MT_GMM_STATUS:
-		rc = gsm48_rx_gmm_status(msg);
+		rc = gsm48_rx_gmm_status(mmctx, msg);
 		break;
 	case GSM48_MT_GMM_DETACH_REQ:
-		rc = gsm48_rx_gmm_det_req(msg);
+		rc = gsm48_rx_gmm_det_req(mmctx, msg);
 		break;
 	case GSM48_MT_GMM_RA_UPD_COMPL:
 		/* only in case SGSN offered new P-TMSI */
@@ -666,7 +662,8 @@
 }
 
 /* Section 9.5.9: Deactivate PDP Context Accept */
-static int gsm48_tx_gsm_deact_pdp_acc(struct msgb *old_msg)
+static int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mmctx,
+				      struct msgb *old_msg)
 {
 	struct gsm48_hdr *old_gh = (struct gsm48_hdr *) msgb_gmmh(old_msg);
 	struct msgb *msg = gsm48_msgb_alloc();
@@ -685,7 +682,8 @@
 }
 
 /* Section 9.5.1: Activate PDP Context Request */
-static int gsm48_rx_gsm_act_pdp_req(struct msgb *msg)
+static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx,
+				    struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data;
@@ -743,25 +741,31 @@
 	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {}
 	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)) {}
 
-#if 0
-	return sgsn_create_pdp_ctx(ggsn, &tp);
+#if 1
+	{
+		struct ggsn_ctx ggsn;
+		ggsn.gtp_version = 1;
+		inet_aton("192.168.100.239", &ggsn.remote_addr);
+		ggsn.gsn = sgsn->gsn;
+		return sgsn_create_pdp_ctx(ggsn, mmctx, 5, &tp);
+	}
 #else
 	return gsm48_tx_gsm_act_pdp_acc(msg, act_req);
 #endif
 }
 
 /* Section 9.5.8: Deactivate PDP Context Request */
-static int gsm48_rx_gsm_deact_pdp_req(struct msgb *msg)
+static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *ctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 
 	DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT REQ (cause: %s)\n",
 		get_value_string(gsm_cause_names, gh->data[0]));
 
-	return gsm48_tx_gsm_deact_pdp_acc(msg);
+	return gsm48_tx_gsm_deact_pdp_acc(ctx, msg);
 }
 
-static int gsm48_rx_gsm_status(struct msgb *msg)
+static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = msgb_l3(msg);
 
@@ -772,19 +776,24 @@
 }
 
 /* GPRS Session Management */
-static int gsm0408_rcv_gsm(struct msgb *msg)
+static int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	int rc;
 
+	if (!mmctx) {
+		LOGP(DMM, LOGL_NOTICE, "Cannot handle SM for unknown MM CTX\n");
+		return -EINVAL;
+	}
+
 	switch (gh->msg_type) {
 	case GSM48_MT_GSM_ACT_PDP_REQ:
-		rc = gsm48_rx_gsm_act_pdp_req(msg);
+		rc = gsm48_rx_gsm_act_pdp_req(mmctx, msg);
 		break;
 	case GSM48_MT_GSM_DEACT_PDP_REQ:
-		rc = gsm48_rx_gsm_deact_pdp_req(msg);
+		rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg);
 	case GSM48_MT_GSM_STATUS:
-		rc = gsm48_rx_gsm_status(msg);
+		rc = gsm48_rx_gsm_status(mmctx, msg);
 		break;
 	case GSM48_MT_GSM_REQ_PDP_ACT_REJ:
 	case GSM48_MT_GSM_ACT_AA_PDP_REQ:
@@ -807,14 +816,21 @@
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	uint8_t pdisc = gh->proto_discr & 0x0f;
+	struct sgsn_mm_ctx *mmctx;
+	struct gprs_ra_id ra_id;
 	int rc = -EINVAL;
 
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+	mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
+
+	/* MMCTX can be NULL */
+
 	switch (pdisc) {
 	case GSM48_PDISC_MM_GPRS:
-		rc = gsm0408_rcv_gmm(msg);
+		rc = gsm0408_rcv_gmm(mmctx, msg);
 		break;
 	case GSM48_PDISC_SM_GPRS:
-		rc = gsm0408_rcv_gsm(msg);
+		rc = gsm0408_rcv_gsm(mmctx, msg);
 		break;
 	default:
 		DEBUGP(DMM, "Unknown GSM 04.08 discriminator 0x%02x\n",
diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c
index 90ede76..4e5fb01 100644
--- a/openbsc/src/gprs/gprs_sgsn.c
+++ b/openbsc/src/gprs/gprs_sgsn.c
@@ -31,7 +31,10 @@
 #include <openbsc/gprs_ns.h>
 #include <openbsc/gprs_bssgp.h>
 
-static LLIST_HEAD(sgsn_mm_ctxts);
+LLIST_HEAD(sgsn_mm_ctxts);
+LLIST_HEAD(sgsn_ggsn_ctxts);
+LLIST_HEAD(sgsn_apn_ctxts);
+LLIST_HEAD(sgsn_pdp_ctxts);
 
 static int ra_id_equals(const struct gprs_ra_id *id1,
 			const struct gprs_ra_id *id2)
@@ -95,3 +98,118 @@
 
 	return ctx;
 }
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+					   uint8_t nsapi)
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	llist_for_each_entry(pdp, &mm->pdp_list, list) {
+		if (pdp->nsapi == nsapi)
+			return pdp;
+	}
+	return NULL;
+}
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+					uint8_t nsapi)
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi);
+	if (pdp)
+		return NULL;
+
+	pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx);
+	if (!pdp)
+		return NULL;
+
+	pdp->mm = mm;
+	pdp->nsapi = nsapi;
+	llist_add(&pdp->list, &mm->pdp_list);
+	llist_add(&pdp->g_list, &sgsn_pdp_ctxts);
+
+	return pdp;
+}
+
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp)
+{
+	llist_del(&pdp->list);
+	llist_del(&pdp->g_list);
+	talloc_free(pdp);
+}
+
+/* GGSN contexts */
+
+struct ggsn_ctx *ggsn_ctx_alloc(uint32_t id)
+{
+	struct ggsn_ctx *ggc;
+
+	ggc = talloc_zero(tall_bsc_ctx, struct ggsn_ctx);
+	if (!ggc)
+		return NULL;
+
+	ggc->id = id;
+	ggc->gtp_version = 1;
+
+	return ggc;
+}
+
+struct ggsn_ctx *ggsn_ctx_by_id(uint32_t id)
+{
+	struct ggsn_ctx *ggc;
+
+	llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+		if (id == ggc->id)
+			return ggc;
+	}
+	return NULL;
+}
+
+struct ggsn_ctx *ggsn_ctx_find_alloc(uint32_t id)
+{
+	struct ggsn_ctx *ggc;
+
+	ggc = ggsn_ctx_by_id(id);
+	if (!ggc)
+		ggc = ggsn_ctx_alloc(id);
+	return ggc;
+}
+
+/* APN contexts */
+
+#if 0
+struct apn_ctx *apn_ctx_alloc(const char *ap_name)
+{
+	struct apn_ctx *actx;
+
+	actx = talloc_zero(talloc_bsc_ctx, struct apn_ctx);
+	if (!actx)
+		return NULL;
+	actx->name = talloc_strdup(actx, ap_name);
+
+	return actx;
+}
+
+struct apn_ctx *apn_ctx_by_name(const char *name)
+{
+	struct apn_ctx *actx;
+
+	llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
+		if (!strcmp(name, actx->name))
+			return actx;
+	}
+	return NULL;
+}
+
+struct apn_ctx *apn_ctx_find_alloc(const char *name)
+{
+	struct apn_ctx *actx;
+
+	actx = apn_ctx_by_name(name);
+	if (!actx)
+		actx = apn_ctx_alloc(name);
+
+	return actx;
+}
+#endif
diff --git a/openbsc/src/gprs/sgsn_libgtp.c b/openbsc/src/gprs/sgsn_libgtp.c
index e99cb6c..f6331b9 100644
--- a/openbsc/src/gprs/sgsn_libgtp.c
+++ b/openbsc/src/gprs/sgsn_libgtp.c
@@ -49,12 +49,6 @@
 #include <gtp.h>
 #include <pdp.h>
 
-struct ggsn_ctx {
-	unsigned int gtp_version;
-	struct in_addr remote_addr;
-	struct gsn_t *gsn;
-};
-
 const struct value_string gtp_cause_strs[] = {
 	{ GTPCAUSE_REQ_IMSI, "Request IMSI" },
 	{ GTPCAUSE_REQ_IMEI, "Request IMEI" },
@@ -96,18 +90,30 @@
 
 /* generate a PDP context based on the IE's from the 04.08 message,
  * and send the GTP create pdp context request to the GGSN */
-int sgsn_create_pdp_ctx(struct ggsn_ctx *ggsn, struct sgsn_mm_ctx *mmctx,
-			uint16_t nsapi, struct tlv_parsed *tp)
+struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct ggsn_ctx *ggsn,
+					 struct sgsn_mm_ctx *mmctx,
+					 uint16_t nsapi,
+					 struct tlv_parsed *tp)
 {
+	struct sgsn_pdp_ctx *pctx;
 	struct pdp_t *pdp;
 	uint64_t imsi_ui64;
 	int rc;
 
+	pctx = sgsn_pdp_ctx_alloc(mmctx, nsapi);
+	if (!pctx) {
+		LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n");
+		return NULL;
+	}
+
 	rc = pdp_newpdp(&pdp, imsi_ui64, nsapi, NULL);
 	if (rc) {
-		LOGP(DGPRS, LOGL_ERROR, "Out of PDP Contexts\n");
-		return -ENOMEM;
+		LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n");
+		return NULL;
 	}
+	pctx->lib = pdp;
+	pctx->ggsn = ggsn;
+
 	//pdp->peer =	/* sockaddr_in of GGSN (receive) */
 	//pdp->ipif =	/* not used by library */
 	pdp->version = ggsn->gtp_version;
@@ -166,14 +172,16 @@
 
 	/* FIXME: change pdp state to 'requested' */
 
-	/* FIXME: pass along a pointer to the MM CTX */
-	return gtp_create_context_req(ggsn->gsn, pdp, mmctx);
+	rc = gtp_create_context_req(ggsn->gsn, pdp, pctx);
+	/* FIXME */
+
+	return pctx;
 }
 
 /* The GGSN has confirmed the creation of a PDP Context */
 static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
 {
-	struct sgsn_mm_ctx *mmctx = cbp;
+	struct sgsn_pdp_ctx *pctx = cbp;
 
 	DEBUGP(DGPRS, "Received CREATE PDP CTX CONF, cause=%d(%s)\n",
 		cause, get_value_string(gtp_cause_strs, cause));
@@ -197,7 +205,6 @@
 		return EOF;
 	}
 
-	/* FIXME: Determine MM ctx for the PDP ctx */
 	/* FIXME: Send PDP CTX ACT ACK/REJ to MS */
 	return 0;
 }
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index c16e191..b59529c 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -25,15 +25,19 @@
 #include <arpa/inet.h>
 
 #include <osmocore/talloc.h>
+#include <osmocore/utils.h>
 
 #include <openbsc/debug.h>
 #include <openbsc/sgsn.h>
 #include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_sgsn.h>
 #include <openbsc/vty.h>
 
 #include <vty/command.h>
 #include <vty/vty.h>
 
+#include <pdp.h>
+
 static struct sgsn_config *g_cfg = NULL;
 
 static struct cmd_node sgsn_node = {
@@ -45,6 +49,7 @@
 static int config_write_sgsn(struct vty *vty)
 {
 	struct in_addr ia;
+	struct ggsn_ctx *gctx;
 
 	vty_out(vty, "sgsn%s", VTY_NEWLINE);
 
@@ -56,27 +61,11 @@
 	vty_out(vty, "  nsip local port %u%s", g_cfg->nsip_listen_port,
 		VTY_NEWLINE);
 
-	return CMD_SUCCESS;
-}
-
-DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
-      SHOW_STR "Display information about the SGSN")
-{
-	/* FIXME: iterate over list of NS-VC's and display their state */
-	struct gprs_ns_inst *nsi = g_cfg->nsi;
-	struct gprs_nsvc *nsvc;
-
-	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
-		vty_out(vty, "NSEI %5u, NS-VC %5u, %s-mode, %s %s%s",
-			nsvc->nsei, nsvc->nsvci,
-			nsvc->remote_end_is_sgsn ? "BSS" : "SGSN",
-			nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
-			nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED",
-			VTY_NEWLINE);
-		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
-			vty_out(vty, "  remote peer %s:%u%s",
-				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
-				ntohs(nsvc->ip.bts_addr.sin_port), VTY_NEWLINE);
+	llist_for_each_entry(gctx, &sgsn_ggsn_ctxts, list) {
+		vty_out(vty, "  ggsn %u remote-ip %s%s", gctx->id,
+			inet_ntoa(gctx->remote_addr), VTY_NEWLINE);
+		vty_out(vty, "  ggsn %u gtp-version %u%s", gctx->id,
+			gctx->gtp_version, VTY_NEWLINE);
 	}
 
 	return CMD_SUCCESS;
@@ -113,15 +102,183 @@
 	unsigned int port = atoi(argv[0]);
 
 	g_cfg->nsip_listen_port = port;
+
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_ggsn_remote_ip, cfg_ggsn_remote_ip_cmd,
+	"ggsn <0-255> remote-ip A.B.C.D",
+	"")
+{
+	uint32_t id = atoi(argv[0]);
+	struct ggsn_ctx *ggc = ggsn_ctx_find_alloc(id);
 
+	inet_aton(argv[1], &ggc->remote_addr);
 
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_ggsn_remote_port, cfg_ggsn_remote_port_cmd,
+	"ggsn <0-255> remote-port <0-65535>",
+	"")
+{
+	uint32_t id = atoi(argv[0]);
+	struct ggsn_ctx *ggc = ggsn_ctx_find_alloc(id);
+	uint16_t port = atoi(argv[1]);
+
+}
+#endif
+
+DEFUN(cfg_ggsn_gtp_version, cfg_ggsn_gtp_version_cmd,
+	"ggsn <0-255> gtp-version (0|1)",
+	"")
+{
+	uint32_t id = atoi(argv[0]);
+	struct ggsn_ctx *ggc = ggsn_ctx_find_alloc(id);
+	uint16_t port = atoi(argv[1]);
+
+	if (atoi(argv[1]))
+		ggc->gtp_version = 1;
+	else
+		ggc->gtp_version = 0;
+
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_apn_ggsn, cfg_apn_ggsn_cmd,
+	"apn APNAME ggsn <0-255>",
+	"")
+{
+	struct apn_ctx **
+}
+#endif
+
+const struct value_string gprs_mm_st_strs[] = {
+	{ GMM_DEREGISTERED, "DEREGISTERED" },
+	{ GMM_COMMON_PROC_INIT, "COMMON PROCEDURE (INIT)" },
+	{ GMM_REGISTERED_NORMAL, "REGISTERED (NORMAL)" },
+	{ GMM_REGISTERED_SUSPENDED, "REGISTeRED (SUSPENDED)" },
+	{ GMM_DEREGISTERED_INIT, "DEREGISTERED (INIT)" },
+	{ 0, NULL }
+};
+
+static void vty_dump_pdp(struct vty *vty, const char *pfx,
+			 struct sgsn_pdp_ctx *pdp)
+{
+	vty_out(vty, "%sPDP Context IMSI: %s, SAPI: %u, NSAPI: %u%s",
+		pfx, pdp->mm->imsi, 2342 /* FIXME */, pdp->nsapi, VTY_NEWLINE);
+	vty_out(vty, "%s  APN: %s\n", pfx, pdp->lib->apn_use.v);
+	/* FIXME: statistics */
+}
+
+static void vty_dump_mmctx(struct vty *vty, const char *pfx,
+			   struct sgsn_mm_ctx *mm, int pdp)
+{
+	vty_out(vty, "%sMM Context for IMSI %s, IMEI %s, P-TMSI %08x%s",
+		pfx, mm->imsi, mm->imei, mm->p_tmsi, VTY_NEWLINE);
+	vty_out(vty, "%s  MSISDN: %s, TLLI: %08x%s", pfx, mm->msisdn,
+		mm->tlli, VTY_NEWLINE);
+	vty_out(vty, "%s  MM State: %s, Routeing Area: %u-%u-%u-%u, "
+		"Cell ID: %u%s", pfx,
+		get_value_string(gprs_mm_st_strs, mm->mm_state),
+		mm->ra.mcc, mm->ra.mnc, mm->ra.lac, mm->ra.rac,
+		mm->cell_id, VTY_NEWLINE);
+
+	if (pdp) {
+		struct sgsn_pdp_ctx *pdp;
+
+		llist_for_each_entry(pdp, &mm->pdp_list, list)
+			vty_dump_pdp(vty, "  ", pdp);
+	}
+}
+
+DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
+      SHOW_STR "Display information about the SGSN")
+{
+	/* FIXME: statistics */
+	return CMD_SUCCESS;
+}
+
+#define MMCTX_STR "MM Context\n"
+#define INCLUDE_PDP_STR "Include PDP Context Information\n"
+
+#if 0
+DEFUN(show_mmctx_tlli, show_mmctx_tlli_cmd,
+	"show mm-context tlli HEX [pdp]",
+	SHOW_STR MMCTX_STR "Identify by TLLI\n" "TLLI\n" INCLUDE_PDP_STR)
+{
+	uint32_t tlli;
+	struct sgsn_mm_ctx *mm;
+
+	tlli = strtoul(argv[0], NULL, 16);
+	mm = sgsn_mm_ctx_by_tlli(tlli);
+	if (!mm) {
+		vty_out(vty, "No MM context for TLLI %08x%s",
+			tlli, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0);
+	return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(swow_mmctx_imsi, show_mmctx_imsi_cmd,
+	"show mm-context imsi IMSI [pdp]",
+	SHOW_STR MMCTX_STR "Identify by IMSI\n" "IMSI of the MM Context\n"
+	INCLUDE_PDP_STR)
+{
+	struct sgsn_mm_ctx *mm;
+
+	mm = sgsn_mm_ctx_by_imsi(argv[0]);
+	if (!mm) {
+		vty_out(vty, "No MM context for IMSI %s%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(swow_mmctx_all, show_mmctx_all_cmd,
+	"show mm-context all [pdp]",
+	SHOW_STR MMCTX_STR "All MM Contexts\n" INCLUDE_PDP_STR)
+{
+	struct sgsn_mm_ctx *mm;
+
+	llist_for_each_entry(mm, &sgsn_mm_ctxts, list)
+		vty_dump_mmctx(vty, "", mm, argv[0] ? 1 : 0);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_ggsn, show_ggsn_cmd,
+	"show ggsn",
+	"")
+{
+
+}
+
+DEFUN(show_pdpctx_all, show_pdpctx_all_cmd,
+	"show pdp-context all",
+	SHOW_STR "Display information on PDP Context\n")
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	llist_for_each_entry(pdp, &sgsn_pdp_ctxts, g_list)
+		vty_dump_pdp(vty, "", pdp);
+
+	return CMD_SUCCESS;
+}
 
 int sgsn_vty_init(void)
 {
-	install_element(VIEW_NODE, &show_sgsn_cmd);
+	install_element_ve(&show_sgsn_cmd);
+	//install_element_ve(&show_mmctx_tlli_cmd);
+	install_element_ve(&show_mmctx_imsi_cmd);
+	install_element_ve(&show_mmctx_all_cmd);
+	install_element_ve(&show_pdpctx_all_cmd);
 
 	install_element(CONFIG_NODE, &cfg_sgsn_cmd);
 	install_node(&sgsn_node, config_write_sgsn);
@@ -130,6 +287,9 @@
 	install_element(SGSN_NODE, &ournode_end_cmd);
 	install_element(SGSN_NODE, &cfg_nsip_local_ip_cmd);
 	install_element(SGSN_NODE, &cfg_nsip_local_port_cmd);
+	install_element(SGSN_NODE, &cfg_ggsn_remote_ip_cmd);
+	//install_element(SGSN_NODE, &cfg_ggsn_remote_port_cmd);
+	install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd);
 
 	return 0;
 }