Skip to content

Commit

Permalink
[MME] Gn: Introduce initial support for 2G->4G cell reselection
Browse files Browse the repository at this point in the history
In an Inter-RAT setup a UE could perform a TAU coming from a 2G/3G network.
In that case the UE/MS is unknown to the MME and it should request the
SGSN context (MM, PDP) from the old SGSN. This is done through the following
GTPv1C message exchange on the Gn interface of SGSN and MME:
SGSN <- MME: SGSN Context Request
SGSN -> MME: SGSN Context Response
SGSN <- MME: SGSN Context Acknowledge

Diagram with full set of steps can be found at 3GPP TS 23.401 D.3.6.

This commit doesn't aim to be a complete implementation of the mentioned
procedure, since it's quite a complex one, with lots of fields and logic
required. This so far only implements in general the minimally
successful case by filling as much as possible the required set of
fields.
This will allow for a base onto which do incremental improvements and
fixes while testing against UEs and SGSNs (such as osmo-sgsn, which
doesn't yet support this procedure but will potentially earn it soon).

The reverse direction, aka UE issuing cell reselection 4G->2G was
already implemented (same as here, initial non-complete implementation)
in open5gs-mmed in commit 3d693da.

Related: https://osmocom.org/issues/6294
  • Loading branch information
pespin authored and acetcom committed Jan 17, 2024
1 parent 4088cdf commit 60691b0
Show file tree
Hide file tree
Showing 16 changed files with 641 additions and 19 deletions.
1 change: 1 addition & 0 deletions lib/gtp/xact.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ typedef struct ogs_gtp_xact_s {
#define OGS_GTP_CREATE_IN_ATTACH_REQUEST 1
#define OGS_GTP_CREATE_IN_UPLINK_NAS_TRANSPORT 2
#define OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST 3
#define OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE 4 /* 3GPP TS 33.401 9.1.2 */
int create_action;

#define OGS_GTP_MODIFY_IN_PATH_SWITCH_REQUEST 1
Expand Down
12 changes: 12 additions & 0 deletions src/mme/emm-build.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,18 @@ ogs_pkbuf_t *emm_build_security_mode_command(mme_ue_t *mme_ue)
imeisv_request->type = OGS_NAS_IMEISV_TYPE;
imeisv_request->value = OGS_NAS_IMEISV_REQUESTED;

if (mme_ue->nonceue) {
security_mode_command->presencemask |=
OGS_NAS_EPS_SECURITY_MODE_COMMAND_REPLAYED_NONCEUE_PRESENT;
security_mode_command->replayed_nonceue = mme_ue->nonceue;
}

if (mme_ue->noncemme) {
security_mode_command->presencemask |=
OGS_NAS_EPS_SECURITY_MODE_COMMAND_NONCEMME_PRESENT;
security_mode_command->noncemme = mme_ue->noncemme;
}

/*
* TS24.301
* 5.4.3.2 NAS security mode control initiation by the network
Expand Down
47 changes: 47 additions & 0 deletions src/mme/emm-handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,36 @@ int emm_handle_service_request(
return OGS_OK;
}

bool emm_tau_request_ue_comes_from_gb_or_iu(const ogs_nas_eps_tracking_area_update_request_t *tau_request)
{
/* "When the tracking area updating procedure is initiated in EMM-IDLE mode
* to perform an inter-system change from A/Gb mode or Iu mode to S1 mode
* and the TIN is set to "P-TMSI", the UE shall include the GPRS ciphering
* key sequence number applicable for A/Gb mode or Iu mode and a nonce UE in
* the TRACKING AREA UPDATE REQUEST message."
*/
if (!(tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_NONCEUE_PRESENT))
return false;

if (tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_OLD_GUTI_TYPE_PRESENT) {
/* 0 = Native, 1 = Mapped */
return tau_request->old_guti_type.guti_type;
} else {
/* TS 23.003 2.8.2.2.2:
* "The most significant bit of the <LAC> shall be set to zero;
* and the most significant bit of <MME group id> shall be set to
* one. Based on this definition, the most significant bit of the
* <MME group id> can be used to distinguish the node type, i.e.
* whether it is an MME or SGSN */
const ogs_nas_eps_mobile_identity_t *eps_mobile_identity = &tau_request->old_guti;
if (eps_mobile_identity->imsi.type != OGS_NAS_EPS_MOBILE_IDENTITY_GUTI)
return false;
return !(eps_mobile_identity->guti.mme_gid & 0x8000);
}
}

int emm_handle_tau_request(mme_ue_t *mme_ue,
ogs_nas_eps_tracking_area_update_request_t *tau_request, ogs_pkbuf_t *pkbuf)
{
Expand Down Expand Up @@ -660,6 +690,19 @@ int emm_handle_tau_request(mme_ue_t *mme_ue,
sizeof(tau_request->ms_network_capability));
}

if (tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_NONCEUE_PRESENT) {
mme_ue->gprs_ciphering_key_sequence_number = tau_request->gprs_ciphering_key_sequence_number.key_sequence;
} else {
/* Mark as unavailable, Table 10.5.2/3GPP TS 24.008 */
mme_ue->gprs_ciphering_key_sequence_number = OGS_NAS_CIPHERING_KEY_SEQUENCE_NUMBER_NO_KEY_FROM_MS;
}

if (tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_NONCEUE_PRESENT) {
mme_ue->nonceue = tau_request->nonceue;
}

/* TODO:
* 1) Consider if MME is changed or not.
* 2) Consider if SGW is changed or not.
Expand All @@ -679,6 +722,10 @@ int emm_handle_tau_request(mme_ue_t *mme_ue,
nas_guti.m_tmsi,
MME_UE_HAVE_IMSI(mme_ue)
? mme_ue->imsi_bcd : "Unknown");

memcpy(&mme_ue->next.guti,
&nas_guti, sizeof(ogs_nas_eps_guti_t));

break;
default:
ogs_error("Not implemented[%d]", eps_mobile_identity->imsi.type);
Expand Down
3 changes: 3 additions & 0 deletions src/mme/emm-handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ int emm_handle_extended_service_request(mme_ue_t *mme_ue,
int emm_handle_security_mode_complete(mme_ue_t *mme_ue,
ogs_nas_eps_security_mode_complete_t *security_mode_complete);

bool emm_tau_request_ue_comes_from_gb_or_iu(
const ogs_nas_eps_tracking_area_update_request_t *tau_request);

#ifdef __cplusplus
}
#endif
Expand Down
23 changes: 23 additions & 0 deletions src/mme/emm-sm.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "mme-event.h"
#include "mme-timer.h"
#include "s1ap-handler.h"
#include "mme-gn-handler.h"
#include "mme-fd-path.h"
#include "emm-handler.h"
#include "emm-build.h"
Expand Down Expand Up @@ -286,7 +287,9 @@ static void common_register_state(ogs_fsm_t *s, mme_event_t *e,

mme_ue_t *mme_ue = NULL;
enb_ue_t *enb_ue = NULL;
mme_sgsn_t *sgsn = NULL;
ogs_nas_eps_message_t *message = NULL;
ogs_nas_rai_t rai;
ogs_nas_security_header_type_t h;

ogs_assert(e);
Expand Down Expand Up @@ -489,6 +492,26 @@ static void common_register_state(ogs_fsm_t *s, mme_event_t *e,
break;
}

if (emm_tau_request_ue_comes_from_gb_or_iu(&message->emm.tracking_area_update_request)) {
ogs_info("TAU request : UE comes from SGSN, attempt retrieving context");
guti_to_rai_ptmsi(&mme_ue->next.guti, &rai, NULL, NULL);
sgsn = mme_sgsn_find_by_routing_address(&rai, 0xffff);
if (!sgsn) {
ogs_plmn_id_t plmn_id;
ogs_nas_to_plmn_id(&plmn_id, &rai.lai.nas_plmn_id);
ogs_warn("No SGSN route matching RAI[MCC:%u MNC:%u LAC:%u RAC:%u]",
ogs_plmn_id_mcc(&plmn_id), ogs_plmn_id_mnc(&plmn_id),
rai.lai.lac, rai.rac);
r = nas_eps_send_tau_reject(mme_ue,
OGS_NAS_EMM_CAUSE_UE_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK);
OGS_FSM_TRAN(s, &emm_state_exception);
break;
}
mme_gtp1_send_sgsn_context_request(sgsn, mme_ue);
/* FIXME: use a specific FSM state here to state we are waiting for resolution from Gn? */
break;
}

if (!MME_UE_HAVE_IMSI(mme_ue)) {
ogs_info("TAU request : Unknown UE");
r = nas_eps_send_tau_reject(mme_ue,
Expand Down
5 changes: 5 additions & 0 deletions src/mme/mme-context.c
Original file line number Diff line number Diff line change
Expand Up @@ -3184,6 +3184,11 @@ void mme_ue_confirm_guti(mme_ue_t *mme_ue)

/* Clear Next GUTI */
mme_ue->next.m_tmsi = NULL;

ogs_debug("Confirm GUTI[G:%d,C:%d,M_TMSI:0x%x]",
mme_ue->current.guti.mme_gid,
mme_ue->current.guti.mme_code,
mme_ue->current.guti.m_tmsi);
}

static bool compare_ue_info(mme_sgw_t *node, enb_ue_t *enb_ue)
Expand Down
2 changes: 2 additions & 0 deletions src/mme/mme-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ struct mme_ue_s {
} ul_count;
uint8_t kenb[OGS_SHA256_DIGEST_SIZE];
uint8_t hash_mme[OGS_HASH_MME_LEN];
uint32_t nonceue, noncemme;
uint8_t gprs_ciphering_key_sequence_number;

struct {
ED2(uint8_t nhcc_spare:5;,
Expand Down
167 changes: 159 additions & 8 deletions src/mme/mme-gn-build.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
#include "mme-context.h"

#include "mme-gn-build.h"
#include "mme-gn-handler.h"

static int sess_fill_mm_context_decoded(mme_sess_t *sess, ogs_gtp1_mm_context_decoded_t *mmctx_dec)
{
mme_ue_t *mme_ue = sess->mme_ue;
mme_bearer_t *bearer = NULL;
*mmctx_dec = (ogs_gtp1_mm_context_decoded_t) {
.gupii = 1, /* Integrity Protection not required */
.ugipai = 1, /* Ignore "Used GPRS integrity protection algorithm" field" */
Expand All @@ -40,7 +40,6 @@ static int sess_fill_mm_context_decoded(mme_sess_t *sess, ogs_gtp1_mm_context_de
.nrsrna = 0,
};

//TODO: derive cK Ki from mme_ue->kasme
ogs_kdf_ck_ik_idle_mobility(mme_ue->ul_count.i32, mme_ue->kasme, &mmctx_dec->ck[0], &mmctx_dec->ik[0]);

mmctx_dec->imeisv_len = sizeof(mme_ue->nas_mobile_identity_imeisv);
Expand All @@ -49,12 +48,6 @@ static int sess_fill_mm_context_decoded(mme_sess_t *sess, ogs_gtp1_mm_context_de
mmctx_dec->ms_network_capability_len = mme_ue->ms_network_capability.length;
memcpy(&mmctx_dec->ms_network_capability[0], ((uint8_t*)&mme_ue->ms_network_capability)+1, sizeof(mme_ue->ms_network_capability) - 1);

ogs_list_for_each(&sess->bearer_list, bearer) {

/* FIXME: only 1 PDP Context supported in the message so far. */
break;
}

return OGS_OK;
}

Expand Down Expand Up @@ -177,6 +170,103 @@ static int sess_fill_pdp_context_decoded(mme_sess_t *sess, ogs_gtp1_pdp_context_
return OGS_OK;
}

/* 3GPP TS 29.060 7.5.3 SGSN Context Request */
ogs_pkbuf_t *mme_gn_build_sgsn_context_request(
mme_ue_t *mme_ue)
{
ogs_gtp1_message_t gtp1_message;
ogs_gtp1_sgsn_context_request_t *req = NULL;
ogs_nas_rai_t rai;
mme_p_tmsi_t ptmsi;
uint32_t ptmsi_sig;
ogs_gtp1_gsn_addr_t mme_gnc_gsnaddr, mme_gnc_alt_gsnaddr;
int gsn_len;
int rv;

ogs_debug("[Gn] build SGSN Context Request");

ogs_assert(mme_ue);

req = &gtp1_message.sgsn_context_request;
memset(&gtp1_message, 0, sizeof(ogs_gtp1_message_t));

guti_to_rai_ptmsi(&mme_ue->next.guti, &rai, &ptmsi, &ptmsi_sig);

req->imsi.presence = 0;

req->routeing_area_identity.presence = 1;
req->routeing_area_identity.data = &rai;
req->routeing_area_identity.len = sizeof(ogs_nas_rai_t);

req->temporary_logical_link_identifier.presence = 0;

req->packet_tmsi.presence = 1;
req->packet_tmsi.u32 = be32toh(ptmsi);

req->p_tmsi_signature.presence = 1;
req->p_tmsi_signature.u24 = ptmsi_sig;

req->ms_validated.presence = 0;

req->tunnel_endpoint_identifier_control_plane.presence = 1;
req->tunnel_endpoint_identifier_control_plane.u32 = mme_ue->gn.mme_gn_teid;

/* SGSN Address for Control Plane */
if (ogs_gtp_self()->gtpc_addr && ogs_gtp_self()->gtpc_addr6) {
rv = ogs_gtp1_sockaddr_to_gsn_addr(NULL, ogs_gtp_self()->gtpc_addr6,
&mme_gnc_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;

rv = ogs_gtp1_sockaddr_to_gsn_addr(ogs_gtp_self()->gtpc_addr, NULL,
&mme_gnc_alt_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_alt_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
} else if (ogs_gtp_self()->gtpc_addr6) {
rv = ogs_gtp1_sockaddr_to_gsn_addr(NULL, ogs_gtp_self()->gtpc_addr6,
&mme_gnc_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
req->alternative_sgsn_address_for_control_plane.presence = 0;
} else {
rv = ogs_gtp1_sockaddr_to_gsn_addr(ogs_gtp_self()->gtpc_addr, NULL,
&mme_gnc_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
req->alternative_sgsn_address_for_control_plane.presence = 0;
}

req->sgsn_number.presence = 0;

req->rat_type.presence = 1;
req->rat_type.u8 = OGS_GTP1_RAT_TYPE_EUTRAN;

req->hop_counter.presence = 0;

gtp1_message.h.type = OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE;
return ogs_gtp1_build_msg(&gtp1_message);
}

/* 3GPP TS 29.060 7.5.4 SGSN Context Response */
ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause)
Expand Down Expand Up @@ -275,6 +365,67 @@ ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
rsp->sgsn_address_for_control_plane.len = gsn_len;


build_ret:
return ogs_gtp1_build_msg(&gtp1_message);
}

/* 3GPP TS 29.060 7.5.5 SGSN Context Acknowledge */
ogs_pkbuf_t *mme_gn_build_sgsn_context_ack(
mme_ue_t *mme_ue, uint8_t cause)
{
ogs_gtp1_message_t gtp1_message;
ogs_gtp1_sgsn_context_acknowledge_t *ack = NULL;
mme_sess_t *sess = NULL;
ogs_gtp1_gsn_addr_t reserved_gnc_gsnaddr;
ogs_gtp1_teidII_t teidII;

ogs_debug("[Gn] build SGSN Context Acknowledge");

ack = &gtp1_message.sgsn_context_acknowledge;
memset(&gtp1_message, 0, sizeof(ogs_gtp1_message_t));
gtp1_message.h.type = OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE;

/* 3GPP TS 29.060 7.7.1 Cause, Mandatory */
ack->cause.presence = 1;
ack->cause.u8 = cause;

if (cause != OGS_GTP1_CAUSE_REQUEST_ACCEPTED)
goto build_ret;

ogs_list_for_each(&mme_ue->sess_list, sess) {
mme_bearer_t *bearer = NULL;
if (!MME_HAVE_SGW_S1U_PATH(sess))
continue;
ogs_list_for_each(&sess->bearer_list, bearer) {
/* MME, acting as a new SGSN, shall send the following values in the SGSN Context
* Acknowledge message in order to discard the packets received from the old SGSN
* (because the MME and the S4-SGSN do not have user plane):
* - any reserved TEID (e.g. all 0's, or all 1's) for Tunnel Endpoint Identifier
* Data II value;
* - any reserved (implementation dependent) IP address for SGSN Address for user
traffic value.
*/
/* 3GPP TS 29.060 7.7.15 Tunnel Endpoint Identifier Data II, Conditional */
teidII.nsapi = bearer->ebi;
teidII.teid = 0xffffffff;
ack->tunnel_endpoint_identifier_data_ii.presence = 1;
ack->tunnel_endpoint_identifier_data_ii.data = &teidII;
ack->tunnel_endpoint_identifier_data_ii.len = sizeof(teidII);

/* Use IPv4 0.0.0.0 as reserved address: */
reserved_gnc_gsnaddr.addr = 0;
ack->sgsn_address_for_user_traffic.presence = 1;
ack->sgsn_address_for_user_traffic.data = &reserved_gnc_gsnaddr;
ack->sgsn_address_for_user_traffic.len = OGS_GTP_GSN_ADDRESS_IPV4_LEN;

/* FIXME: only 1 PDP Context supported in the message so far. */
break;
}
/* FIXME: right now we only support encoding 1 context in the message. */
break;
}


build_ret:
return ogs_gtp1_build_msg(&gtp1_message);
}
Expand Down
6 changes: 6 additions & 0 deletions src/mme/mme-gn-build.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ extern "C" {
}
#endif

ogs_pkbuf_t *mme_gn_build_sgsn_context_request(
mme_ue_t *mme_ue);

ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause);

ogs_pkbuf_t *mme_gn_build_sgsn_context_ack(
mme_ue_t *mme_ue, uint8_t cause);

ogs_pkbuf_t *mme_gn_build_ran_information_relay(
uint8_t type, const uint8_t *buf, size_t len,
const ogs_nas_rai_t *rai, uint16_t cell_id);
Expand Down
Loading

0 comments on commit 60691b0

Please sign in to comment.