lynxis lazus has uploaded this change for review.

View Change

Add Gn support to allow MME->SGSN, SGSN->MME cell reselection

The Gn interface allow to transfer the state of MS/UE between
SGSNs and SGSN to MME and vice versa.
The code won't use the old key material from the old core network component.
Instead it will ask the HLR for new material.

Change-Id: Iffe83b31db2b6e6869fedf2351375184096cff6f
---
M include/osmocom/sgsn/gprs_rau_fsm.h
M include/osmocom/sgsn/gtp.h
M include/osmocom/sgsn/mmctx.h
M include/osmocom/sgsn/pdpctx.h
M src/sgsn/gprs_gmm.c
M src/sgsn/gprs_rau_fsm.c
M src/sgsn/mmctx.c
M src/sgsn/pdpctx.c
M src/sgsn/sgsn_libgtp.c
9 files changed, 927 insertions(+), 11 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-sgsn refs/changes/63/39563/1
diff --git a/include/osmocom/sgsn/gprs_rau_fsm.h b/include/osmocom/sgsn/gprs_rau_fsm.h
index 8cf47f4..7eae057 100644
--- a/include/osmocom/sgsn/gprs_rau_fsm.h
+++ b/include/osmocom/sgsn/gprs_rau_fsm.h
@@ -27,6 +27,7 @@
enum gmm_rau_state {
GMM_RAU_S_INIT,
GMM_RAU_S_WAIT_VLR_ANSWER,
+ GMM_RAU_S_WAIT_GGSN_UPDATE,
GMM_RAU_S_WAIT_UE_RAU_COMPLETE,
};

@@ -37,6 +38,7 @@
GMM_RAU_E_VLR_RAU_REJECT, /* Request to transmit Att/RAU Reject */
GMM_RAU_E_VLR_TERM_SUCCESS, /* VLR Lu FSM terminates. Inform GMM about Att/RAU Success (including Att/RAU complete) */
GMM_RAU_E_VLR_TERM_FAIL, /* VLR Lu FSM terminates. Inform GMM about Att/RAU fail */
+ GMM_RAU_E_GGSN_UPD_RESP,
};

/* To be used as data when terminating the fsm */
diff --git a/include/osmocom/sgsn/gtp.h b/include/osmocom/sgsn/gtp.h
index ed6cbf5..8cbad40 100644
--- a/include/osmocom/sgsn/gtp.h
+++ b/include/osmocom/sgsn/gtp.h
@@ -12,6 +12,7 @@
struct sgsn_pdp_ctx;
struct sgsn_mm_ctx;
struct sgsn_mme_ctx;
+struct gsn_t;

int sgsn_gtp_init(struct sgsn_instance *sgi);

@@ -27,3 +28,5 @@
struct msgb *msg, uint32_t npdu_len, uint8_t *npdu);
int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx);
int send_act_pdp_cont_acc(struct sgsn_pdp_ctx *pctx);
+
+int sgsn_context_ack(struct gsn_t *gsn, struct sgsn_mm_ctx *mmctx, uint8_t cause);
diff --git a/include/osmocom/sgsn/mmctx.h b/include/osmocom/sgsn/mmctx.h
index 1f1559e..d09212d 100644
--- a/include/osmocom/sgsn/mmctx.h
+++ b/include/osmocom/sgsn/mmctx.h
@@ -232,6 +232,15 @@
/* the current GGSN look-up operation */
struct sgsn_ggsn_lookup *ggsn_lookup;

+ /* When a GSN requests a SGSN Context, we need to keep a reference */
+ uint32_t gtp_local_ref;
+ bool gtp_local_ref_valid;
+
+ /* GUTI will be used when a inter RAT from EUTRAN happens */
+ struct osmo_guti guti;
+ uint8_t eutran_nri;
+ bool guti_valid;
+
struct gprs_subscr *subscr;
struct vlr_subscr *vsub;
/* to know if subscriber is doing an attach or location update */
@@ -280,6 +289,7 @@
const struct osmo_routing_area_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_gtp_local_ref(uint32_t local_ref);
struct sgsn_mm_ctx *sgsn_mm_ctx_by_ue_ctx(const void *uectx);
struct sgsn_mm_ctx *sgsn_mm_ctx_by_llme(const struct gprs_llc_llme *llme);

diff --git a/include/osmocom/sgsn/pdpctx.h b/include/osmocom/sgsn/pdpctx.h
index 39d744a..b61e643 100644
--- a/include/osmocom/sgsn/pdpctx.h
+++ b/include/osmocom/sgsn/pdpctx.h
@@ -31,6 +31,10 @@
PDP_STATE_CR_REQ,
PDP_STATE_CR_CONF,

+ /* when received via SGSN Context Req from another SGSN/MME,
+ * the PDP context needs to be updated with our own address */
+ PDP_STATE_NEED_UPDATE_GSN,
+
/* 04.08 / Figure 6.2 / 6.1.2.2 */
PDP_STATE_INACT_PEND,
PDP_STATE_INACTIVE = PDP_STATE_NONE,
@@ -93,6 +97,7 @@
uint8_t nsapi);
void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp);
void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp);
+int sgsn_pdp_ctx_gn_update(struct sgsn_pdp_ctx *pctx);

char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len, bool return_ipv6);

diff --git a/src/sgsn/gprs_gmm.c b/src/sgsn/gprs_gmm.c
index 83497d2..9f4343f 100644
--- a/src/sgsn/gprs_gmm.c
+++ b/src/sgsn/gprs_gmm.c
@@ -752,6 +752,10 @@
if (ctx->ran_type == MM_CTX_T_UTRAN_Iu)
ctx->iu.new_key = 1;

+ /* Gn: SGSN Context Req/Resp/Ack procedure */
+ if (ctx->gtp_local_ref_valid)
+ sgsn_context_ack(sgsn->gsn, ctx, GTPCAUSE_ACC_REQ);
+
/* FIXME: enable LLC cipheirng */
/* FIXME: This should _not_ trigger a FSM success */
osmo_fsm_inst_dispatch(ctx->gmm_fsm, E_GMM_COMMON_PROC_SUCCESS, NULL);
@@ -1472,6 +1476,41 @@
return gsm48_gmm_sendmsg(msg, 0, NULL, false);
}

+/*! Calculate the GUTI from a Routing Area Update Request, P-TMSI, P-TMSI signature
+ *
+ * \param[in] msg
+ * \param[in] req
+ * \param[in] new_ra_id
+ * \param[out] guti
+ * \param[out] nas_token
+ * \return 0 on success
+ */
+static int get_guti(struct msgb *msg, struct gprs_gmm_ra_upd_req *req, uint32_t ptmsi, struct osmo_guti *guti, uint16_t *nas_token)
+{
+ /* uint16_t nas_token; */
+ uint8_t ptmsi_mtmsi;
+
+ OSMO_ASSERT(guti);
+
+ if (!TLVP_PRES_LEN(&req->tlv, GSM48_IE_GMM_PTMSI_SIG, 3))
+ return GMM_CAUSE_IE_NOTEXIST_NOTIMPL;
+
+ ptmsi_mtmsi = tlvp_val8(&req->tlv, GSM48_IE_GMM_PTMSI_SIG, 0);
+ /* nas_token only valid for UTRAN */
+ *nas_token = osmo_load16be(TLVP_VAL(&req->tlv, GSM48_IE_GMM_PTMSI_SIG) + 1);
+
+ guti->gummei.plmn = req->old_rai.lac.plmn;
+ guti->gummei.mme.group_id = req->old_rai.lac.lac;
+ guti->gummei.mme.code = req->old_rai.rac;
+ /* bits 31 to 30 are always 11; P-TMSI bits29 to 24, 15 to 0 */
+ guti->mtmsi |= (1 << 31) | (1 << 30) | (ptmsi & 0x3f00ffff);
+ guti->mtmsi |= (ptmsi_mtmsi << 16);
+ /* bits 23 to 16 is the NRI */
+ /* eutran_nri = (ptmsi >> 16) & 0xff; */
+
+ return 0;
+}
+
/* Chapter 9.4.14: Routing area update request */
static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
struct gprs_llc_llme *llme)
@@ -1482,12 +1521,15 @@
enum gsm48_gmm_cause reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC;
struct gprs_gmm_ra_upd_req req;
struct osmo_routing_area_id new_ra_id = {};
+ struct osmo_guti guti = {};
enum vlr_lu_type vlr_rau_type = VLR_LU_TYPE_REGULAR;
bool foreign_ra = false;
enum gsm48_ptsmi_type ptmsi_type = PTMSI_TYPE_NATIVE;
int rc;
+ uint16_t nas_token;
uint32_t ptmsi = 0xffffffff;
uint32_t tlli = 0xffffffff;
+ struct sgsn_mme_ctx *mme;

rc = gprs_gmm_parse_ra_upd_req(msg, &req);
if (rc) {
@@ -1580,16 +1622,46 @@
/* the UE is transitioning from EUTRAN to GERAN/UTRAN */
foreign_ra = true;

- reject_cause = GMM_CAUSE_IMPL_DETACHED;
- LOGP(DGPRS, LOGL_ERROR, "UE has a mapped P-TMSI. MME-to-SGSN mobility not implemented. Rejecting\n");
- goto rejected;
+ rc = get_guti(msg, &req, ptmsi, &guti, &nas_token);
+ if (rc) {
+ reject_cause = GMM_CAUSE_IMPL_DETACHED;
+ LOGP(DGPRS, LOGL_ERROR, "Can't decode guti\n");
+ goto rejected;
+ }
+
+ mme = sgsn_mme_ctx_by_gummei(sgsn, &guti.gummei);
+ if (!mme) {
+ reject_cause = GMM_CAUSE_IMPL_DETACHED;
+ LOGP(DGPRS, LOGL_ERROR, "No MME found for Gummei %s", osmo_gummei_name(&guti.gummei));
+ /* FIXME: check for additional RAI + PTMSI */
+ goto rejected;
+ }
+
+ if (!mmctx) {
+ /* create a new MMCtx and ask for the context */
+ if (msgb_bcid(msg)) {
+ mmctx = sgsn_mm_ctx_alloc_gb(tlli, &req.old_rai);
+ if (mmctx)
+ mmctx->gb.llme = llme;
+ } else {
+ mmctx = sgsn_mm_ctx_alloc_iu(MSG_IU_UE_CTX(msg));
+ }
+
+ if (!mmctx)
+ goto rejected;
+
+ mmctx->p_tmsi = ptmsi;
+ }
+ mmctx->guti = guti;
+ mmctx->guti_valid = true;
} else { /* (ptmsi_type == PTMSI_TYPE_NATIVE) - 2G/3G PTMSI */
/* Check if this a local RA or a foreign */
struct sgsn_ra *ra;

ra = sgsn_ra_get_ra(&req.old_rai);
if (!ra) {
- LOGP(DGPRS, LOGL_ERROR, "Can't find RA for native PTMSI. SGSN-to-SGSN mobility not implemented. Rejecting\n");
+ LOGP(DGPRS, LOGL_ERROR, "Can't find RA for native PTMSI. No SGSN-to-SGSN mobility implemented. Rejecting\n");
+ /* FIXME: implement SGSN to SGSN routing */
foreign_ra = true;
reject_cause = GMM_CAUSE_IMPL_DETACHED;
goto rejected;
@@ -2491,12 +2563,73 @@
/* decide if the location/routing area id is within the VLR or not */
static bool vlr_location_served_cb(struct vlr_subscr *vsub, const struct osmo_routing_area_id *rai)
{
+ struct sgsn_mm_ctx *mmctx = vsub->msc_conn_ref;
+
+ if (mmctx) {
+ return !mmctx->attach_rau.foreign;
+ }
+
+ /* FIXME: check if rai is valid */
+
return false;
}

+/* VLR request the SGSN to ask the old SGSN/MME for more information */
int vlr_pvlr_request_cb(void *ref, const struct osmo_routing_area_id *old_rai)
{
- return 1;
+ struct sgsn_mm_ctx *ctx = ref;
+ int rc;
+ uint32_t local_ref;
+ struct sgsn_mme_ctx *mme = NULL;
+ struct sockaddr_in remote = {};
+
+ union gtpie_member *ie[GTPIE_SIZE] = {};
+ union gtpie_member ie_elem[3] = {};
+ unsigned int i = 0;
+
+ if (!ctx)
+ return -EINVAL;
+
+ if (!ctx->guti_valid)
+ return -EINVAL;
+
+ mme = sgsn_mme_ctx_by_gummei(sgsn, &ctx->guti.gummei);
+ if (!mme) {
+ return -EINVAL;
+ }
+
+ remote.sin_family = AF_INET;
+ remote.sin_addr = mme->remote_addr;
+
+ if (ctx->p_tmsi != 0x0 && ctx->p_tmsi != 0xffffffff) {
+ ie_elem[i].tv4.t = GTPIE_P_TMSI;
+ ie_elem[i].tv4.v = osmo_htonl(ctx->p_tmsi);
+ ie[GTPIE_P_TMSI] = &ie_elem[i];
+ i++;
+ }
+
+ if (ctx->attach_rau.p_tmsi_sig_valid) {
+ ie_elem[i].tv0.t = GTPIE_P_TMSI_S;
+ memcpy(ie_elem[i].tv0.v, ctx->attach_rau.p_tmsi_sig, sizeof(ctx->attach_rau.p_tmsi_sig));
+ ie[GTPIE_P_TMSI_S] = &ie_elem[i];
+ i++;
+ }
+
+ ie_elem[i].tv0.t = GTPIE_RAI;
+ osmo_routing_area_id_encode_buf(ie_elem[i].tv0.v, 6, &ctx->attach_rau.old_rai);
+ ie[GTPIE_RAI] = &ie_elem[i];
+ i++;
+
+ rc = gtp_sgsn_context_req(sgsn->gsn, &local_ref, &remote, ie, GTPIE_SIZE);
+ if (!rc) {
+ /* FIXME: reject with impl. */
+ return -1;
+ } else {
+ ctx->gtp_local_ref = local_ref;
+ ctx->gtp_local_ref_valid = true;
+ }
+
+ return 0;
}


diff --git a/src/sgsn/gprs_rau_fsm.c b/src/sgsn/gprs_rau_fsm.c
index 60ca6be..a9a79e6 100644
--- a/src/sgsn/gprs_rau_fsm.c
+++ b/src/sgsn/gprs_rau_fsm.c
@@ -44,6 +44,7 @@
OSMO_VALUE_STRING(GMM_RAU_E_UE_RAU_REQUEST),
OSMO_VALUE_STRING(GMM_RAU_E_VLR_RAU_ACCEPT),
OSMO_VALUE_STRING(GMM_RAU_E_VLR_RAU_REJECT),
+ OSMO_VALUE_STRING(GMM_RAU_E_GGSN_UPD_RESP),
OSMO_VALUE_STRING(GMM_RAU_E_UE_RAU_COMPLETE),
OSMO_VALUE_STRING(GMM_RAU_E_VLR_TERM_FAIL),
OSMO_VALUE_STRING(GMM_RAU_E_VLR_TERM_SUCCESS),
@@ -147,8 +148,13 @@
/* same RAU, a different RAU would terminate this FSM */
break;
case GMM_RAU_E_VLR_RAU_ACCEPT:
- transmit_rau_accept(mmctx);
- osmo_tdef_fsm_inst_state_chg(fi, GMM_RAU_S_WAIT_UE_RAU_COMPLETE, gmm_rau_tdef_states, gmm_rau_tdefs, 0);
+ /* delay forwarding it */
+ if (mmctx->attach_rau.foreign) {
+ osmo_tdef_fsm_inst_state_chg(fi, GMM_RAU_S_WAIT_GGSN_UPDATE, gmm_rau_tdef_states, gmm_rau_tdefs, 0);
+ } else {
+ transmit_rau_accept(mmctx);
+ osmo_tdef_fsm_inst_state_chg(fi, GMM_RAU_S_WAIT_UE_RAU_COMPLETE, gmm_rau_tdef_states, gmm_rau_tdefs, 0);
+ }
break;
case GMM_RAU_E_VLR_RAU_REJECT:
gmm_cause = (uint8_t) ((long) data & 0xff);
@@ -165,6 +171,54 @@
}
}

+static void transmit_update_pdp_req(struct sgsn_mm_ctx *mmctx)
+{
+ struct sgsn_pdp_ctx *pctx;
+
+ /* When receiving PDP context via Gn, all PDP context must taken cared off:
+ * if the UE still knows about them, update the GTP path
+ * or termiante the PDP context, when the UE states this has been dropped. */
+ llist_for_each_entry(pctx, &mmctx->pdp_list, list) {
+ if (pctx->ue_pdp_active)
+ sgsn_pdp_ctx_gn_update(pctx);
+ else
+ sgsn_pdp_ctx_terminate(pctx);
+ }
+}
+
+void gmm_rau_fsm_s_wait_ggsn_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct sgsn_mm_ctx *mmctx = gmm_rau_fsm_priv(fi);
+ /* transmit Update PDP Request when doing a Inter-SGSN handover (or 4G->2G/4G) */
+
+ /* FIXME: move Gn into a FSM and wait for a response before sending out the RAU Accept */
+ /* update the PDP Request should be done now */
+ transmit_update_pdp_req(mmctx);
+}
+
+static void gmm_rau_fsm_s_wait_ggsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct sgsn_mm_ctx *mmctx = gmm_rau_fsm_priv(fi);
+
+ switch (event) {
+ case GMM_RAU_E_GGSN_UPD_RESP:
+ transmit_rau_accept(mmctx);
+ // FIXME: transmit Routing Area Update Accpet OR inform the VLR to continue ULA */
+ /* FIXME: check for *all* pdp before going to next state */
+ osmo_tdef_fsm_inst_state_chg(fi, GMM_RAU_S_WAIT_UE_RAU_COMPLETE, gmm_rau_tdef_states, gmm_rau_tdefs, 0);
+ break;
+ case GMM_RAU_E_VLR_RAU_REJECT:
+ /* FIXME */
+ break;
+ case GMM_RAU_E_UE_RAU_REQUEST:
+ /* same RAU, a different RAU would terminate this FSM */
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
static void gmm_rau_fsm_s_wait_ue_rau_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct sgsn_mm_ctx *mmctx = gmm_rau_fsm_priv(fi);
@@ -302,10 +356,19 @@
},
[GMM_RAU_S_WAIT_VLR_ANSWER] = {
.in_event_mask = S(GMM_RAU_E_UE_RAU_REQUEST) | S(GMM_RAU_E_VLR_RAU_ACCEPT) | S(GMM_RAU_E_VLR_RAU_REJECT) | S(GMM_RAU_E_VLR_TERM_SUCCESS)| S(GMM_RAU_E_VLR_TERM_FAIL),
- .out_state_mask = S(GMM_RAU_S_WAIT_UE_RAU_COMPLETE),
+ .out_state_mask = S(GMM_RAU_S_WAIT_GGSN_UPDATE) |
+ S(GMM_RAU_S_WAIT_UE_RAU_COMPLETE),
.name = OSMO_STRINGIFY(GMM_RAU_S_WAIT_VLR_ANSWER),
.action = gmm_rau_fsm_s_wait_vlr,
},
+ [GMM_RAU_S_WAIT_GGSN_UPDATE] = {
+ .in_event_mask = S(GMM_RAU_E_UE_RAU_REQUEST) | S(GMM_RAU_E_GGSN_UPD_RESP) | S(GMM_RAU_E_VLR_RAU_REJECT) | S(GMM_RAU_E_VLR_TERM_SUCCESS)| S(GMM_RAU_E_VLR_TERM_FAIL),
+ .out_state_mask = S(GMM_RAU_S_WAIT_UE_RAU_COMPLETE),
+ .name = OSMO_STRINGIFY(GMM_RAU_S_WAIT_GGSN_UPDATE),
+ .action = gmm_rau_fsm_s_wait_ggsn,
+ .onenter = gmm_rau_fsm_s_wait_ggsn_onenter,
+ },
+ /* FIXME: add PVLR step here as well? */
[GMM_RAU_S_WAIT_UE_RAU_COMPLETE] = {
.in_event_mask = S(GMM_RAU_E_UE_RAU_COMPLETE) | S(GMM_RAU_E_VLR_RAU_REJECT) | S(GMM_RAU_E_VLR_RAU_ACCEPT) | S(GMM_RAU_E_UE_RAU_REQUEST) | S(GMM_RAU_E_VLR_TERM_SUCCESS)| S(GMM_RAU_E_VLR_TERM_FAIL),
.out_state_mask = 0,
diff --git a/src/sgsn/mmctx.c b/src/sgsn/mmctx.c
index f314425..4891826 100644
--- a/src/sgsn/mmctx.c
+++ b/src/sgsn/mmctx.c
@@ -187,6 +187,20 @@

}

+struct sgsn_mm_ctx *sgsn_mm_ctx_by_gtp_local_ref(uint32_t local_ref)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ llist_for_each_entry(ctx, &sgsn->mm_list, list) {
+ if (!ctx->gtp_local_ref_valid)
+ continue;
+
+ if (ctx->gtp_local_ref == local_ref)
+ return ctx;
+ }
+ return NULL;
+}
+
/* Allocate a new SGSN MM context, generic part */
struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t rate_ctr_id)
{
diff --git a/src/sgsn/pdpctx.c b/src/sgsn/pdpctx.c
index e779420..e45db95 100644
--- a/src/sgsn/pdpctx.c
+++ b/src/sgsn/pdpctx.c
@@ -33,10 +33,11 @@
#include <osmocom/sgsn/debug.h>
#include <osmocom/sgsn/signal.h>
#include <osmocom/sgsn/gtp_ggsn.h>
-#include <osmocom/sgsn/gprs_llc_xid.h>
-#include <osmocom/sgsn/gprs_sndcp.h>
#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/gprs_llc_xid.h>
+#include <osmocom/sgsn/gprs_ranap.h>
#include <osmocom/sgsn/gprs_sm.h>
+#include <osmocom/sgsn/gprs_sndcp.h>
#include <osmocom/sgsn/gtp.h>

static const struct rate_ctr_desc pdpctx_ctr_description[] = {
@@ -79,12 +80,39 @@
return NULL;
}
llist_add(&pdp->list, &mm->pdp_list);
- sgsn_ggsn_ctx_add_pdp(pdp->ggsn, pdp);
+ if (pdp->ggsn)
+ sgsn_ggsn_ctx_add_pdp(pdp->ggsn, pdp);
llist_add(&pdp->g_list, &sgsn->pdp_list);

return pdp;
}

+int sgsn_pdp_ctx_gn_update(struct sgsn_pdp_ctx *pctx)
+{
+ int rc = 0;
+
+ if (pctx->state == PDP_STATE_NEED_UPDATE_GSN) {
+ pctx->state = PDP_STATE_CR_CONF;
+ rc = gtp_update_context(pctx->ggsn->gsn, pctx->lib, pctx, &pctx->ggsn->remote_addr);
+ }
+
+ if (pctx->mm->ran_type == MM_CTX_T_GERAN_Gb) {
+ /* Activate the SNDCP layer */
+ sndcp_sm_activate_ind(&pctx->mm->gb.llme->lle[pctx->sapi], pctx->nsapi);
+ } else if (pctx->mm->ran_type == MM_CTX_T_UTRAN_Iu) {
+#ifdef BUILD_IU
+ /* Activate a radio bearer */
+ iu_rab_act_ps(pctx->lib->nsapi, pctx);
+ return 0;
+#else
+ return -ENOTSUP;
+#endif
+ }
+
+
+ return rc;
+}
+
/*
* This function will not trigger any GSM DEACT PDP ACK messages, so you
* probably want to call sgsn_delete_pdp_ctx() instead if the connection
diff --git a/src/sgsn/sgsn_libgtp.c b/src/sgsn/sgsn_libgtp.c
index 4650632..182e898 100644
--- a/src/sgsn/sgsn_libgtp.c
+++ b/src/sgsn/sgsn_libgtp.c
@@ -36,13 +36,23 @@

#include "config.h"

+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/gsm/gsm48.h>
+
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>

+#include <osmocom/gtp/gsn.h>
#include <osmocom/gtp/gtp.h>
+#include <osmocom/gtp/gtpie.h>
#include <osmocom/gtp/pdp.h>

#include <osmocom/sgsn/signal.h>
@@ -139,6 +149,91 @@
return imsi64;
}

+/* Import PDP Context which sent to the SGSN via SGSN Context Response */
+struct sgsn_pdp_ctx *sgsn_import_pdp_ctx(
+ struct sgsn_mm_ctx *mmctx,
+ uint16_t sapi,
+ struct pdp_t *pdp)
+{
+ struct sgsn_pdp_ctx *pctx;
+ struct pdp_t *lib_pdp;
+ uint64_t imsi_ui64;
+ int rc;
+ struct sgsn_ggsn_ctx *ggsn = NULL;
+ bool ggsn_created = false;
+ struct osmo_sockaddr addr = {};
+
+ rc = osmo_sockaddr_from_octets(&addr, pdp->gsnrc.v, pdp->gsnrc.l);
+ if (rc < 0 || rc != pdp->gsnrc.l) {
+ LOGP(DGPRS, LOGL_ERROR, "Invalid GSN address\n");
+ return NULL;
+ }
+
+ if (addr.u.sin.sin_family != AF_INET) {
+ LOGP(DGPRS, LOGL_ERROR, "SGSN only supports IPv4 towards GGSN\n");
+ return NULL;
+ }
+
+ ggsn = sgsn_ggsn_ctx_by_addr(sgsn, &addr.u.sin.sin_addr);
+ if (!ggsn) {
+ /* the ares code is also using UINT32_MAX, which results into multiple GGSN have uint max */
+ ggsn = sgsn_ggsn_ctx_alloc(sgsn, UINT32_MAX);
+ if (!ggsn) {
+ LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate GGSN Ctx\n");
+ return NULL;
+ }
+ ggsn_created = true;
+ ggsn->remote_addr = addr.u.sin.sin_addr;
+ }
+
+ /* FIXME: why can this NULL for an via vty configured GGSN? */
+ if (!ggsn->gsn) {
+ ggsn->gsn = sgsn->gsn;
+ }
+
+ pctx = sgsn_pdp_ctx_alloc(mmctx, ggsn, pdp->nsapi);
+ if (!pctx) {
+ LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n");
+ goto out;
+ }
+
+ imsi_ui64 = imsi_str2gtp(mmctx->imsi);
+ rc = gtp_pdp_newpdp(ggsn->gsn, &lib_pdp, imsi_ui64, pdp->nsapi, pdp);
+ if (rc) {
+ LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n");
+ return NULL;
+ }
+ pdp->priv = pctx;
+ pctx->nsapi = pdp->nsapi;
+ pctx->sapi = sapi;
+ pctx->lib = lib_pdp;
+ /* FIXME: should the ue pdp active here or not? */
+ pctx->ue_pdp_active = true;
+ pctx->ti = pdp->ti;
+ lib_pdp->priv = pctx;
+
+ /* SGSN address for control plane */
+ lib_pdp->gsnlc.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr);
+ memcpy(lib_pdp->gsnlc.v, &sgsn->cfg.gtp_listenaddr.sin_addr,
+ sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+
+ /* SGSN address for user plane */
+ lib_pdp->gsnlu.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr);
+ memcpy(lib_pdp->gsnlu.v, &sgsn->cfg.gtp_listenaddr.sin_addr,
+ sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+
+
+
+ pctx->state = PDP_STATE_NEED_UPDATE_GSN;
+
+ return pctx;
+out:
+ if (ggsn && ggsn_created)
+ sgsn_ggsn_ctx_free(ggsn);
+
+ return NULL;
+}
+
/* generate a PDP context based on the IE's from the 04.08 message,
* and send the GTP create pdp context request to the GGSN */
struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn,
@@ -535,6 +630,8 @@
* a Cause value "Non-existent", it shall delete the PDP Context."
*/
if (cause != GTPCAUSE_NON_EXIST) {
+ if (pctx->mm->attach_rau.rau_fsm)
+ osmo_fsm_inst_dispatch(pctx->mm->attach_rau.rau_fsm, GMM_RAU_E_GGSN_UPD_RESP, pctx);
return 0; /* Nothing to do */
}

@@ -542,6 +639,7 @@
"the GGSN anymore, deleting it locally.\n");

rc = gtp_freepdp(pctx->ggsn->gsn, pctx->lib);
+ osmo_fsm_inst_dispatch(pctx->mm->attach_rau.rau_fsm, GMM_RAU_E_GGSN_UPD_RESP, NULL);
/* related mmctx is torn down in cb_delete_context called by gtp_freepdp() */
return rc;
}
@@ -804,6 +902,562 @@
return -EINVAL;
}

+/* TS 29.060: Encode the MM Ctx TLV (7.7.28) of a SGSN Context Response (7.5.4) */
+static int gtp_mm_ctx(uint8_t *buf, unsigned int size, const struct sgsn_mm_ctx *mmctx)
+{
+ uint8_t sec_mode = 0, no_vecs = 0;
+ uint16_t *len_ptr;
+ uint8_t *ptr = buf;
+ uint32_t required_auth_type;
+
+#define CHECK_SPACE_ERR(bytes) \
+ if (ptr - buf + (bytes) > size) { \
+ LOGP(DGPRS, LOGL_ERROR, "Ran out of space encoding mm ctx: %lu, %lu\n", (ptr - buf), (unsigned long) bytes); \
+ return -1; \
+ }
+#define MEMCPY_CHK(dst, src, len) \
+ CHECK_SPACE_ERR((len)) \
+ memcpy((dst), (uint8_t *)(src), (len)); \
+ (dst) += (len);
+
+ // CKSN
+ if (mmctx->ran_type != MM_CTX_T_GERAN_Gb) {
+ LOGP(DGPRS, LOGL_ERROR, "SGSN Context Request: MM ctx doesn't support Iu/3G yet!"); \
+ return -1;
+ }
+
+ // FIXME: KSI/CKSN for Iu?;
+ *ptr++ = 0xf8 | (mmctx->auth_triplet.key_seq & 0x07);
+
+ // Sec Mode | No Vecs | Used Cipher
+ if (mmctx->auth_triplet.vec.auth_types & OSMO_AUTH_TYPE_UMTS) {
+ sec_mode = 0;
+ required_auth_type = OSMO_AUTH_TYPE_UMTS;
+ } else if (mmctx->auth_triplet.vec.auth_types & OSMO_AUTH_TYPE_GSM) {
+ sec_mode = 1;
+ required_auth_type = OSMO_AUTH_TYPE_GSM;
+ } else {
+ return -1;
+ }
+
+ if (mmctx->vsub) {
+ for (int i = 0; i < 5; i++) {
+ const struct vlr_auth_tuple *auth = &mmctx->vsub->auth_tuples[i];
+ if (auth->use_count == 0 && auth->vec.auth_types & required_auth_type)
+ no_vecs++;
+ }
+ }
+
+ *ptr++ = (sec_mode << 6) | (no_vecs << 3) | (mmctx->ciph_algo & 0x7);
+ // Kc or CK/IK
+ switch (sec_mode & 0x01) {
+ case 0:
+ /* UMTS keys */
+ MEMCPY_CHK(ptr, mmctx->auth_triplet.vec.ck, sizeof(mmctx->auth_triplet.vec.ck));
+ MEMCPY_CHK(ptr, mmctx->auth_triplet.vec.ik, sizeof(mmctx->auth_triplet.vec.ik));
+ break;
+ case 1:
+ /* GSM keys */
+ MEMCPY_CHK(ptr, mmctx->auth_triplet.vec.kc, sizeof(mmctx->auth_triplet.vec.kc));
+ }
+
+ /* 7.7.35 Authentication Triplet/Quintuplet */
+ if (mmctx->vsub) {
+ if ((sec_mode & 1) == 1) {
+ /* Triplets */
+ for (int i = 0; i < 5; i++) {
+ const struct vlr_auth_tuple *auth = &mmctx->vsub->auth_tuples[i];
+ if (!(auth->use_count == 0 && auth->vec.auth_types & required_auth_type))
+ continue;
+ MEMCPY_CHK(ptr, auth->vec.rand, sizeof(auth->vec.rand));
+ MEMCPY_CHK(ptr, auth->vec.sres, 4);
+ MEMCPY_CHK(ptr, auth->vec.kc, 8);
+ }
+ } else {
+ /* Quintuplets */
+ CHECK_SPACE_ERR(2);
+ len_ptr = (uint16_t *)ptr; /* size will be filled later */
+ ptr += 2;
+
+ for (int i = 0; i < 5; i++) {
+ const struct vlr_auth_tuple *auth = &mmctx->vsub->auth_tuples[i];
+ if (!(auth->use_count == 0 && auth->vec.auth_types & required_auth_type))
+ continue;
+ MEMCPY_CHK(ptr, auth->vec.rand, sizeof(auth->vec.rand));
+ *ptr++ = auth->vec.res_len;
+ MEMCPY_CHK(ptr, auth->vec.res, (unsigned long) auth->vec.res_len);
+
+ MEMCPY_CHK(ptr, auth->vec.ck, sizeof(auth->vec.ck));
+ MEMCPY_CHK(ptr, auth->vec.ik, sizeof(auth->vec.ik));
+
+ *ptr++ = sizeof(auth->vec.autn);
+ MEMCPY_CHK(ptr, auth->vec.autn, sizeof(auth->vec.autn));
+ }
+ *len_ptr = htobe16(ptr - (((uint8_t *)len_ptr) + 2));
+ }
+ }
+
+
+ // DRX
+ MEMCPY_CHK(ptr, &mmctx->drx_parms, sizeof(mmctx->drx_parms));
+
+ // MS Network Cap Len
+ *ptr++ = mmctx->ms_network_capa.len;
+ // MS Network Cap
+ MEMCPY_CHK(ptr, mmctx->ms_network_capa.buf, (unsigned long) mmctx->ms_network_capa.len);
+
+ // Container Len
+ *ptr++ = 0;
+ *ptr++ = 0;
+ // Container
+ // FIXME: Container
+
+ // Access Restriction Data Len
+ *ptr++ = 0;
+ // FIXME: NRSRA
+
+ return ptr - buf;
+#undef CHECK_SPACE_ERR
+#undef MEMCPY_CHK
+}
+
+#define GSM_MI_TYPE_TLLI 126
+#define RESP_MAX_IES 10
+
+static int cb_gtp_sgsn_context_request_ind(struct gsn_t *gsn, struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member **ie, unsigned int ie_size)
+{
+ struct sgsn_mm_ctx *mmctx = NULL;
+ struct sgsn_pdp_ctx *pdp;
+ struct osmo_mobile_identity mi = {};
+ struct osmo_routing_area_id rai = {};
+ uint8_t raiv[6];
+ uint8_t buf[512];
+ int buf_len;
+
+ char mi_str[40];
+ char rai_str[40];
+ uint64_t imsi;
+ union gtpie_member *resp_ie[GTPIE_SIZE] = {};
+ union gtpie_member resp_ie_elem[RESP_MAX_IES] = {};
+ unsigned resp_it = 0;
+
+ if (gtpie_gettv0(ie, GTPIE_RAI, 0, &raiv, 6)) {
+ //goto missing_ie;
+ return -1;
+ }
+
+ if (osmo_routing_area_id_decode(&rai, raiv, 6) < 0) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ //GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ // "Invalid RAI\n");
+ }
+
+ /* parse get the TMSI, IMSI, TMSI_SIG */
+ if (!gtpie_gettv4(ie, GTPIE_P_TMSI, 0, &mi.tmsi)) {
+ mi.type = GSM_MI_TYPE_TMSI;
+ } else if (!gtpie_gettv8(ie, GTPIE_IMSI, 0, &imsi)) {
+ mi.type = GSM_MI_TYPE_IMSI;
+ /* NOTE: gtpie_gettv8 already converts to host byte order, but imsi_gtp2str seems to prefer big endian */
+ imsi = ntoh64(imsi);
+ const char *imsi_str = imsi_gtp2str(&imsi);
+ memcpy(mi.imsi, imsi_str, sizeof(mi.imsi));
+ } else if (!gtpie_gettv4(ie, GTPIE_TLLI, 0, &mi.tmsi)) {
+ mi.type = GSM_MI_TYPE_TLLI;
+ }
+
+ osmo_mobile_identity_to_str_buf(mi_str, sizeof(mi_str), &mi);
+ osmo_rai_name2_buf(rai_str, sizeof(rai_str), &rai);
+
+ /* check if the subscribe is known to us */
+ LOGP(DGPRS, LOGL_NOTICE, "RAI: %s MI: %s\n", rai_str, mi_str);
+ if (mi.type == GSM_MI_TYPE_IMSI)
+ mmctx = sgsn_mm_ctx_by_imsi(mi.imsi);
+ else if (mi.type == GSM_MI_TYPE_TMSI)
+ mmctx = sgsn_mm_ctx_by_ptmsi(mi.tmsi);
+ else if (mi.type == GSM_MI_TYPE_TLLI)
+ mmctx = sgsn_mm_ctx_by_tlli(mi.tmsi, &rai);
+
+ if (!mmctx) {
+ LOGP(DGPRS, LOGL_NOTICE, "No context found\n");
+ return gtp_sgsn_context_resp_error(gsn, local_ref, GTPCAUSE_IMSI_NOT_KNOWN);
+ }
+
+ LOGMMCTXP(LOGL_INFO, mmctx, "Ctx will be transfered to another SGSN/MME\n");
+
+ mmctx->gtp_local_ref = local_ref;
+ mmctx->gtp_local_ref_valid = true;
+
+ /* 7.7.1: Cause Code */
+ resp_ie_elem[resp_it].tv1.t = GTPIE_CAUSE;
+ resp_ie_elem[resp_it].tv1.v = GTPCAUSE_ACC_REQ;
+ resp_ie[GTPIE_CAUSE] = &resp_ie_elem[resp_it];
+ resp_it++;
+
+ /* 7.7.2: IMSI */
+ imsi = imsi_str2gtp(mmctx->imsi);
+ resp_ie_elem[resp_it].tv8.t = GTPIE_IMSI;
+ resp_ie_elem[resp_it].tv8.v = imsi;
+ resp_ie[GTPIE_IMSI] = &resp_ie_elem[resp_it];
+ resp_it++;
+
+ /* 7.7.28: MM Context */
+ buf_len = gtp_mm_ctx(buf, sizeof(buf), mmctx);
+ if (buf_len <= 0)
+ return gtp_sgsn_context_resp_error(gsn, local_ref, GTPCAUSE_SYS_FAIL);
+
+ resp_ie_elem[resp_it].tlv.t = GTPIE_MM_CONTEXT;
+ resp_ie_elem[resp_it].tlv.l = htons(buf_len);
+ memcpy(&resp_ie_elem[resp_it].tlv.v[0], buf, buf_len);
+ resp_ie[GTPIE_MM_CONTEXT] = &resp_ie_elem[resp_it];
+ resp_it++;
+
+ // /* 7.7.99: UE network capability */
+ // resp_ie_elem[resp_it].tlv.t = GTPIE_UE_NET_CAPA;
+ // resp_ie_elem[resp_it].tlv.l = htons(sizeof(mmctx->ms_network_capa.len));
+ // memcpy(&resp_ie_elem[resp_it].tlv.v[0], mmctx->ms_network_capa.buf, mmctx->ms_network_capa.len);
+ // resp_ie[resp_it] = &resp_ie_elem[resp_it];
+ // resp_it++;
+
+ /* 7.7.29: PDP Context */
+ llist_for_each_entry(pdp, &mmctx->pdp_list, list) {
+ // use talloc here?
+ buf_len = gtp_encode_pdp_ctx(buf, sizeof(buf), pdp->lib, pdp->sapi);
+ if (buf_len <= 0) {
+ return gtp_sgsn_context_resp_error(gsn, local_ref, GTPCAUSE_SYS_FAIL);
+ }
+
+ resp_ie_elem[resp_it].tlv.t = GTPIE_PDP_CONTEXT;
+ resp_ie_elem[resp_it].tlv.l = htons(buf_len);
+ resp_ie[GTPIE_PDP_CONTEXT] = &resp_ie_elem[resp_it];
+ memcpy(&resp_ie_elem[resp_it].tlv.v[0], buf, buf_len);
+
+ resp_it++;
+ /* TODO: fix the duplicated PDP Context */
+ break;
+
+ // if (resp_it >= RESP_MAX_IES)
+ // break;
+ }
+
+ return gtp_sgsn_context_resp(gsn, local_ref, resp_ie, GTPIE_SIZE);
+}
+
+#define GTP_SEC_MODE_GSM_TRIPLETS 1
+#define GTP_SEC_MODE_GSM_QUINTLETS 3
+#define GTP_SEC_MODE_UMTS_QUINTLETS 2
+#define GTP_SEC_MODE_CIPHER_UMTS_QUINTLETS 0
+
+/*! validate the length of the quintlets, because of the variable AUTS */
+static int validate_quintlets(uint8_t *buf, unsigned int buf_len)
+{
+ uint8_t xres_len, autn_len;
+ unsigned int i = 0;
+ /* buf = Rand, XRes length, XRes, CK, IK, AUTN length, AUTN */
+
+ /* RAND */
+ i += 16;
+
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ xres_len = buf[i];
+ i++;
+
+ /* XRES */
+ i += xres_len;
+
+ /* CK */
+ i += 16;
+
+ /* IK */
+ i += 16;
+
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ autn_len = buf[i];
+ i++;
+
+ /* AUTN */
+ i += autn_len;
+
+ if (i != buf_len)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*! parse the GTP IE MM Context IE and save it into the local MM Ctx. TS 29.060 7.7.28
+ * @param[inout] mmctx The MM Context to save the GTP values into
+ * @param[in] buf A pointer to the GTP IE value octet 4 TS 29.060 7.7.28
+ * @param[in] buf_len The length of \a buf
+ * @return 0 on success, -ENOMEM when to short, -EINVAL for invalid encoding */
+static int gtp_mmctx_ie_to_mmctx(struct sgsn_mm_ctx *mmctx, uint8_t *buf, unsigned int buf_len)
+{
+ /* octet 4 */
+ uint8_t cksn_more;
+ /* octet 5 */
+ uint8_t sec_mode, no_vec;
+ /* octet 6.. */
+ uint8_t *kc = NULL, *ck = NULL, *ik = NULL;
+ uint8_t *quintlet = NULL;
+ uint16_t quintlet_len = 0;
+
+ uint8_t ms_net_cap_len = 0;
+
+ uint16_t container_len = 0;
+ unsigned int i;
+
+ if (buf_len <= 5)
+ return -ENOMEM;
+
+ /* validate length of mm ctx */
+ cksn_more = buf[0];
+ sec_mode = buf[1] >> 6;
+ no_vec = (buf[1] >> 3) & 0x7;
+ /* unsued: used_cipher = buf[1] & 0x7; */
+ i = 2;
+
+ if (no_vec > 5)
+ return -EINVAL;
+
+ switch (sec_mode) {
+ case GTP_SEC_MODE_GSM_TRIPLETS:
+ /* Kc */
+ kc = &buf[i];
+ i += 8;
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ /* triplet length is: 28 = 16 rand + 4 sres + 8 kc */
+ i += 28 * no_vec;
+ if (buf_len <= i)
+ return -ENOMEM;
+ break;
+ case GTP_SEC_MODE_GSM_QUINTLETS:
+ /* Kc */
+ kc = &buf[i];
+ i += 8;
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ quintlet_len = osmo_load16be(&buf[i]);
+ i += 2;
+ if (quintlet_len) {
+ quintlet = &buf[i];
+ i += quintlet_len;
+ }
+
+ if (buf_len <= i)
+ return -ENOMEM;
+ break;
+ case GTP_SEC_MODE_CIPHER_UMTS_QUINTLETS:
+ case GTP_SEC_MODE_UMTS_QUINTLETS:
+ /* CK, IK */
+ ck = &buf[i];
+ i += 16;
+ ik = &buf[i];
+ i += 16;
+
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ quintlet_len = osmo_load16be(&buf[i]);
+ i += 2;
+ if (quintlet_len) {
+ quintlet = &buf[i];
+ i += quintlet_len;
+ }
+
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ break;
+ }
+
+ /* DRX */
+ i += 2;
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ /* MS Network Capability */
+ ms_net_cap_len = buf[i];
+ i++;
+ i += ms_net_cap_len;
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ /* Container */
+ container_len = osmo_load16be(&buf[i]);
+ i += 2;
+
+ i += container_len;
+ if (buf_len <= i)
+ return -ENOMEM;
+
+ switch (sec_mode) {
+ case GTP_SEC_MODE_GSM_QUINTLETS:
+ case GTP_SEC_MODE_UMTS_QUINTLETS:
+ case GTP_SEC_MODE_CIPHER_UMTS_QUINTLETS:
+ if (validate_quintlets(quintlet, quintlet_len)) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: invalid quintlets length\n");
+ return -EINVAL;
+ }
+ break;
+ case GTP_SEC_MODE_GSM_TRIPLETS:
+ break;
+ }
+
+ /* TODO: parse Length of Access Restriction Data + following field */
+
+ /* Save data into the mmctx */
+ memset(&mmctx->auth_triplet, 0, sizeof(mmctx->auth_triplet));
+ mmctx->auth_triplet.key_seq = cksn_more & 0x7;
+ switch (sec_mode) {
+ case GTP_SEC_MODE_GSM_QUINTLETS:
+ case GTP_SEC_MODE_GSM_TRIPLETS:
+ memcpy(&mmctx->auth_triplet.vec.kc, kc, 8);
+ break;
+ case GTP_SEC_MODE_UMTS_QUINTLETS:
+ case GTP_SEC_MODE_CIPHER_UMTS_QUINTLETS:
+ memcpy(&mmctx->auth_triplet.vec.ck, ck, 16);
+ memcpy(&mmctx->auth_triplet.vec.ik, ik, 16);
+ break;
+ }
+
+ /* TODO: optional: pass triplets + quintlets to the VLR. Currently the SGSN will retrieve new quintlets from the HLR.
+ * This is still fine, but increases the time it takes to switch to 2G, but increases the security. */
+
+ return 0;
+}
+
+static int cb_gtp_sgsn_context_response_ind(struct gsn_t *gsn, struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member **ie, unsigned int ie_size)
+{
+ struct sgsn_mm_ctx *mmctx = NULL;
+ uint64_t imsi;
+ uint8_t buf[512];
+ unsigned int buf_len;
+ uint8_t cause;
+ int rc;
+
+ mmctx = sgsn_mm_ctx_by_gtp_local_ref(local_ref);
+ if (!mmctx) {
+ /* How can we loose the local reference? Most likely only when we release the whole subscriber. */
+ return gtp_sgsn_context_ack_error(gsn, local_ref, GTPCAUSE_NO_RESOURCES);
+ }
+
+ /* Check cause */
+ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) {
+ mmctx->gtp_local_ref_valid = false;
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Mandatory Cause IE not found\n");
+ return gtp_sgsn_context_ack_error(gsn, local_ref, GTPCAUSE_MAN_IE_MISSING);
+ }
+
+ if (!mmctx->vsub) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Mandatory Cause IE not found\n");
+ /* TODO: check if need to inform the other SGSN/MME with a different cause code */
+ return gtp_sgsn_context_ack_error(gsn, local_ref, GTPCAUSE_MS_NOT_RESP);
+ }
+
+ if (cause != GTPCAUSE_ACC_REQ) {
+ mmctx->gtp_local_ref_valid = false;
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Cause %d\n", cause);
+ vlr_subscr_rx_pvlr_id_nack(mmctx->vsub);
+ /* FIXME: inform FSM */
+ return -1;
+ }
+
+ if (gtpie_gettv8(ie, GTPIE_IMSI, 0, &imsi)) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Mandatory IMSI IE not found\n");
+ vlr_subscr_rx_pvlr_id_nack(mmctx->vsub);
+ return gtp_sgsn_context_ack_error(gsn, local_ref, GTPCAUSE_MAN_IE_MISSING);
+ }
+ imsi = ntoh64(imsi);
+ const char *imsi_str = imsi_gtp2str(&imsi);
+ /* move this into a different function? */
+ strncpy(mmctx->imsi, imsi_str, sizeof(mmctx->imsi));
+ if (mmctx->vsub)
+ vlr_subscr_set_imsi(mmctx->vsub, imsi_str);
+
+ if (gtpie_gettlv(ie, GTPIE_MM_CONTEXT, 0, &buf_len, buf, sizeof(buf))) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Mandatory MM context IE not found\n");
+ return gtp_sgsn_context_ack_error(gsn, local_ref, GTPCAUSE_MAN_IE_MISSING);
+ }
+
+ rc = gtp_mmctx_ie_to_mmctx(mmctx, buf, buf_len);
+ if (rc) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Mandatory MM context parsing failed. Ignoring, try to use RAU information\n");
+ }
+
+ rc = gtpie_gettlv(ie, GTPIE_PDP_CONTEXT, 0, &buf_len, buf, sizeof(buf));
+ if (rc) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "SGSN Context resp: Mandatory PDP context IE not found\n");
+ return gtp_sgsn_context_ack_error(gsn, local_ref, GTPCAUSE_MAN_IE_MISSING);
+ }
+
+ uint16_t sapi;
+ struct pdp_t new_pdp;
+
+ rc = gtp_decode_pdp_ctx(buf, buf_len, &new_pdp, &sapi);
+ if (rc) {
+ /* Ignore the failure and continue to work without taken the PDP context over.
+ * This way we communicate an Ack towards the SGSN/MME. The remote is responsible to close the PDP context not
+ * mentioned in the ack */
+ }
+
+ /* we save the pdps into the mmctx and inform the GGSN after we authenticated the client */
+ struct sgsn_pdp_ctx *pctx = sgsn_import_pdp_ctx(mmctx, sapi, &new_pdp);
+ if (!pctx) {
+ /* Ignore the failure and continue to work without taken the PDP context over.
+ * This way we communicate an Ack towards the SGSN/MME. The remote is responsible to close the PDP context not
+ * mentioned in the ack */
+ }
+
+ vlr_subscr_rx_pvlr_id_ack(mmctx->vsub);
+
+ return 0;
+}
+
+int sgsn_context_ack(struct gsn_t *gsn, struct sgsn_mm_ctx *mmctx, uint8_t cause)
+{
+ int rc;
+
+ if (!mmctx->gtp_local_ref_valid)
+ return -EINVAL;
+
+ if (!mmctx->gtp_local_ref)
+ return -EINVAL;
+
+ rc = gtp_sgsn_context_ack_error(gsn, mmctx->gtp_local_ref, cause);
+ mmctx->gtp_local_ref_valid = false;
+
+ return rc;
+}
+
+static int cb_gtp_sgsn_context_ack_ind(struct gsn_t *gsn, struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member **ie, unsigned int ie_size)
+{
+ /* The remote peer has verified/established a connection to the UE,
+ * Release local GGSN connection */
+
+ struct sgsn_mm_ctx *mmctx = NULL;
+ struct sgsn_pdp_ctx *pdp, *pdp2;
+
+ mmctx = sgsn_mm_ctx_by_gtp_local_ref(local_ref);
+ if (!mmctx) {
+ /* Can't do anything here. The Ack is the last message anyway. */
+ return 0;
+ }
+ mmctx->gtp_local_ref_valid = false;
+ /* FIXME: drop HLR relation? */
+ /* FIXME: drop local VLR relation? */
+ /* FIXME: Parse Tunnel Endpoint Identifier Data II IE */
+ llist_for_each_entry_safe(pdp, pdp2, &mmctx->pdp_list, list) {
+ sgsn_pdp_ctx_free(pdp);
+ }
+
+ return 0;
+}
+
/* Called whenever we receive a DATA packet */
static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len)
{
@@ -896,6 +1550,7 @@
pdp->nsapi, mm);
}

+
/* Called by SNDCP when it has received/re-assembled a N-PDU */
int sgsn_gtp_data_req(struct osmo_routing_area_id *ra_id, int32_t tlli, uint8_t nsapi,
struct msgb *msg, uint32_t npdu_len, uint8_t *npdu)
@@ -1006,6 +1661,9 @@
gtp_set_cb_unsup_ind(gsn, cb_unsup_ind);
gtp_set_cb_extheader_ind(gsn, cb_extheader_ind);
gtp_set_cb_ran_info_relay_ind(gsn, cb_gtp_ran_info_relay_ind);
+ gtp_set_cb_sgsn_context_request_ind(gsn, cb_gtp_sgsn_context_request_ind);
+ gtp_set_cb_sgsn_context_response_ind(gsn, cb_gtp_sgsn_context_response_ind);
+ gtp_set_cb_sgsn_context_ack_ind(gsn, cb_gtp_sgsn_context_ack_ind);

return 0;
}

To view, visit change 39563. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-sgsn
Gerrit-Branch: master
Gerrit-Change-Id: Iffe83b31db2b6e6869fedf2351375184096cff6f
Gerrit-Change-Number: 39563
Gerrit-PatchSet: 1
Gerrit-Owner: lynxis lazus <lynxis@fe80.eu>