[MME] Gn: Introduce initial support for 4G->2G cell reselection

In an Inter-RAT setup a UE could perform a RAU coming from a 4G network.
In that case the UE/MS is unknown to the SGSN and it should request the
SGSN context (MM, PDP) from the MME. 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

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).

This commit doesn't implement the reverse direction, aka UE issuing cell
reselection 2G->4G. Initial support for this scenario will hopefully be
added soon as a follow-up patch, similar to this one.

Related: https://osmocom.org/issues/6294
(cherry picked from commit 3d693da73e)
This commit is contained in:
Pau Espin 2023-12-11 17:18:11 +01:00 committed by Oliver Smith
parent 4e08baa633
commit e84450bfc1
21 changed files with 1072 additions and 2 deletions

View File

@ -34,6 +34,9 @@
#define FC_FOR_KENB_DERIVATION 0x11
#define FC_FOR_NH_ENB_DERIVATION 0x12
#define FC_FOR_EPS_ALGORITHM_KEY_DERIVATION 0x15
#define FC_FOR_CK_IK_DERIVATION_HANDOVER 0x16
#define FC_FOR_NAS_TOKEN_DERIVATION 0x17
#define FC_FOR_CK_IK_DERIVATION_IDLE_MOBILITY 0x1B
typedef struct kdf_param_s {
uint8_t *buf;
@ -377,6 +380,56 @@ void ogs_kdf_nas_eps(uint8_t algorithm_type_distinguishers,
memcpy(knas, output+16, 16);
}
/* TS33.401 Annex A.8: KASME to CK', IK' derivation at handover */
void ogs_kdf_ck_ik_handover(
uint32_t dl_count, const uint8_t *kasme, uint8_t *ck, uint8_t *ik)
{
kdf_param_t param;
uint8_t output[OGS_SHA256_DIGEST_SIZE];
memset(param, 0, sizeof(param));
param[0].buf = (uint8_t *)&dl_count;
param[0].len = 4;
ogs_kdf_common(kasme, OGS_SHA256_DIGEST_SIZE,
FC_FOR_CK_IK_DERIVATION_HANDOVER, param, output);
memcpy(ck, output, 16);
memcpy(ik, output+16, 16);
}
/* TS33.401 Annex A.9: NAS token derivation for inter-RAT mobility */
void ogs_kdf_nas_token(
uint32_t ul_count, const uint8_t *kasme, uint8_t *nas_token)
{
kdf_param_t param;
uint8_t output[OGS_SHA256_DIGEST_SIZE];
memset(param, 0, sizeof(param));
param[0].buf = (uint8_t *)&ul_count;
param[0].len = 4;
ogs_kdf_common(kasme, OGS_SHA256_DIGEST_SIZE,
FC_FOR_NAS_TOKEN_DERIVATION, param, output);
memcpy(nas_token, output, 2);
}
/* TS33.401 Annex A.13: KASME to CK', IK' derivation at idle mobility */
void ogs_kdf_ck_ik_idle_mobility(
uint32_t ul_count, const uint8_t *kasme, uint8_t *ck, uint8_t *ik)
{
kdf_param_t param;
uint8_t output[OGS_SHA256_DIGEST_SIZE];
memset(param, 0, sizeof(param));
param[0].buf = (uint8_t *)&ul_count;
param[0].len = 4;
ogs_kdf_common(kasme, OGS_SHA256_DIGEST_SIZE,
FC_FOR_CK_IK_DERIVATION_IDLE_MOBILITY, param, output);
memcpy(ck, output, 16);
memcpy(ik, output+16, 16);
}
/*
* TS33.401 Annex I Hash Functions
* Use the KDF given in TS33.220

View File

@ -101,6 +101,18 @@ void ogs_kdf_nh_enb(uint8_t *kasme, uint8_t *sync_input, uint8_t *kenb);
void ogs_kdf_nas_eps(uint8_t algorithm_type_distinguishers,
uint8_t algorithm_identity, uint8_t *kasme, uint8_t *knas);
/* TS33.401 Annex A.8: KASME to CK', IK' derivation at handover */
void ogs_kdf_ck_ik_handover(
uint32_t dl_count, const uint8_t *kasme, uint8_t *ck, uint8_t *ik);
/* TS33.401 Annex A.9: NAS token derivation for inter-RAT mobility */
void ogs_kdf_nas_token(
uint32_t ul_count, const uint8_t *kasme, uint8_t *nas_token);
/* TS33.401 Annex A.13: KASME to CK', IK' derivation at idle mobility */
void ogs_kdf_ck_ik_idle_mobility(
uint32_t ul_count, const uint8_t *kasme, uint8_t *ck, uint8_t *ik);
/*
* TS33.401 Annex I Hash Functions
* Use the KDF given in TS33.220

View File

@ -104,6 +104,34 @@ int ogs_gtp1_gsn_addr_to_ip(const ogs_gtp1_gsn_addr_t *gsnaddr, uint16_t gsnaddr
return OGS_OK;
}
int ogs_gtp1_pdu_session_type_to_eua_ietf_type(uint8_t session_type)
{
switch (session_type) {
case OGS_PDU_SESSION_TYPE_IPV4:
return OGS_PDP_EUA_IETF_IPV4;
case OGS_PDU_SESSION_TYPE_IPV6:
return OGS_PDP_EUA_IETF_IPV6;
case OGS_PDU_SESSION_TYPE_IPV4V6:
return OGS_PDP_EUA_IETF_IPV4V6;
default:
return OGS_ERROR;
}
}
int ogs_gtp1_eua_ietf_type_to_pdu_session_type(uint8_t eua_ietf_type)
{
switch (eua_ietf_type) {
case OGS_PDP_EUA_IETF_IPV4:
return OGS_PDU_SESSION_TYPE_IPV4;
case OGS_PDP_EUA_IETF_IPV6:
return OGS_PDU_SESSION_TYPE_IPV6;
case OGS_PDP_EUA_IETF_IPV4V6:
return OGS_PDU_SESSION_TYPE_IPV4V6;
default:
return OGS_ERROR;
}
}
int ogs_gtp1_eua_to_ip(const ogs_eua_t *eua, uint16_t eua_len, ogs_ip_t *ip,
uint8_t *pdu_session_type)
{

View File

@ -35,6 +35,9 @@ int ogs_gtp1_sockaddr_to_gsn_addr(const ogs_sockaddr_t *addr,
int ogs_gtp1_gsn_addr_to_ip(const ogs_gtp1_gsn_addr_t *gsnaddr, uint16_t gsnaddr_len,
ogs_ip_t *ip);
int ogs_gtp1_pdu_session_type_to_eua_ietf_type(uint8_t session_type);
int ogs_gtp1_eua_ietf_type_to_pdu_session_type(uint8_t eua_ietf_type);
int ogs_gtp1_eua_to_ip(const ogs_eua_t *eua, uint16_t eua_len, ogs_ip_t *ip,
uint8_t *pdu_session_type);

View File

@ -398,3 +398,318 @@ int16_t ogs_gtp1_build_qos_profile(ogs_tlv_octet_t *octet,
octet->len = 6;
return octet->len;
}
/* 7.7.28 MM Context */
/* TODO: UMTS support, see Figure 41 and Figure 42. */
int ogs_gtp1_build_mm_context(ogs_gtp1_tlv_mm_context_t *octet,
const ogs_gtp1_mm_context_decoded_t *decoded, uint8_t *data, int data_len)
{
uint8_t *ptr = data;
unsigned int i;
uint16_t *len_ptr;
uint16_t val16;
ogs_assert(octet);
ogs_assert(data);
ogs_assert((size_t)data_len >= 1);
octet->data = data;
#define CHECK_SPACE_ERR(bytes) \
if ((ptr - data) + (bytes) > data_len) \
return OGS_ERROR
CHECK_SPACE_ERR(1);
*ptr++ = (decoded->gupii & 0x01) << 7 |
(decoded->ugipai & 0x01) << 6 |
(decoded->used_gprs_protection_algo & 0x07) << 3 |
(decoded->ksi & 0x07);
CHECK_SPACE_ERR(1);
*ptr++ = (decoded->sec_mode & 0x03) << 6 |
(decoded->num_vectors & 0x07) << 3 |
(decoded->sec_mode == OGS_GTP1_SEC_MODE_USED_CIPHER_VALUE_UMTS_KEY_AND_QUINTUPLETS ?
decoded->used_cipher & 0x07 : 0x07);
CHECK_SPACE_ERR(sizeof(decoded->ck));
memcpy(ptr, &decoded->ck[0], sizeof(decoded->ck));
ptr += sizeof(decoded->ck);
CHECK_SPACE_ERR(sizeof(decoded->ik));
memcpy(ptr, &decoded->ik[0], sizeof(decoded->ik));
ptr += sizeof(decoded->ik);
/* Quintuplet Length */
CHECK_SPACE_ERR(2);
len_ptr = (uint16_t *)ptr; /* will be filled later */
ptr += 2;
for (i = 0; i < decoded->num_vectors; i++) {
CHECK_SPACE_ERR(sizeof(decoded->auth_quintuplets[0]));
memcpy(ptr, &decoded->auth_quintuplets[i].rand, sizeof(decoded->auth_quintuplets[i].rand));
ptr += sizeof(decoded->auth_quintuplets[i].rand);
*ptr++ = decoded->auth_quintuplets[i].xres_len;
memcpy(ptr, &decoded->auth_quintuplets[i].xres[0], decoded->auth_quintuplets[i].xres_len);
ptr += decoded->auth_quintuplets[i].xres_len;
memcpy(ptr, &decoded->auth_quintuplets[i].ck, sizeof(decoded->auth_quintuplets[i].ck));
ptr += sizeof(decoded->auth_quintuplets[i].ck);
memcpy(ptr, &decoded->auth_quintuplets[i].ik, sizeof(decoded->auth_quintuplets[i].ik));
ptr += sizeof(decoded->auth_quintuplets[i].ik);
*ptr++ = decoded->auth_quintuplets[i].autn_len;
memcpy(ptr, &decoded->auth_quintuplets[i].autn[0], decoded->auth_quintuplets[i].autn_len);
ptr += decoded->auth_quintuplets[i].autn_len;
}
*len_ptr = htobe16(ptr - (((uint8_t *)len_ptr) + 2));
CHECK_SPACE_ERR(sizeof(decoded->drx_param));
memcpy(ptr, &decoded->drx_param, sizeof(decoded->drx_param));
ptr += sizeof(decoded->drx_param);
if (decoded->ms_network_capability_len != 0) {
/* MS Network Capability Length */
CHECK_SPACE_ERR(1 + decoded->ms_network_capability_len);
*ptr++ = decoded->ms_network_capability_len;
memcpy(ptr, &decoded->ms_network_capability, decoded->ms_network_capability_len);
ptr += decoded->ms_network_capability_len;
} else {
CHECK_SPACE_ERR(1);
*ptr++ = 0;
}
if (decoded->imeisv_len != 0) {
/* Container Len */
CHECK_SPACE_ERR(2);
val16 = htobe16(2 + decoded->imeisv_len);
memcpy(ptr, &val16, 2);
ptr += 2;
/* Container (Mobile Identity IMEISV), TS 29.060 Table 47A */
CHECK_SPACE_ERR(2 + decoded->imeisv_len);
*ptr++ = 0x23;
*ptr++ = decoded->imeisv_len; /* Length of mobile identity contents */
memcpy(ptr, &decoded->imeisv[0], decoded->imeisv_len);
ptr += decoded->imeisv_len;
} else {
/* Container Len */
CHECK_SPACE_ERR(2);
*ptr++ = 0;
*ptr++ = 0;
}
if (decoded->nrsrna) {
CHECK_SPACE_ERR(2);
*ptr++ = 1;
*ptr++ = 0x01;
}
octet->len = (ptr - data);
return OGS_OK;
#undef CHECK_SPACE_ERR
}
/* The format of EUA in PDP Context is not exactly the same for the entire EUA,
* hence a separate function is required to encode the value part of the address,
* instead of using regular ogs_gtp1_ip_to_eua(). */
static int enc_pdp_ctx_as_eua(uint8_t pdu_session_type, const ogs_ip_t *ip,
uint8_t *data, int data_len)
{
switch (pdu_session_type)
{
case OGS_PDU_SESSION_TYPE_IPV4:
if (!ip->ipv4) {
ogs_error("EUA type IPv4 but no IPv4 address available");
return OGS_ERROR;
}
if (data_len < OGS_IPV4_LEN)
return OGS_ERROR;
memcpy(data, &ip->addr, OGS_IPV4_LEN);
return OGS_IPV4_LEN;
case OGS_PDU_SESSION_TYPE_IPV6:
if (!ip->ipv6) {
ogs_error("EUA type IPv4 but no IPv6 address available");
return OGS_ERROR;
}
if (data_len < OGS_IPV6_LEN)
return OGS_ERROR;
memcpy(data, ip->addr6, OGS_IPV6_LEN);
return OGS_IPV6_LEN;
case OGS_PDU_SESSION_TYPE_IPV4V6:
if (ip->ipv4 && ip->ipv6) {
if (data_len < OGS_IPV4_LEN + OGS_IPV6_LEN)
return OGS_ERROR;
memcpy(data, &ip->addr, OGS_IPV4_LEN);
memcpy(data + OGS_IPV4_LEN, ip->addr6, OGS_IPV6_LEN);
return OGS_IPV4_LEN + OGS_IPV6_LEN;
} else if (ip->ipv4) {
if (data_len < OGS_IPV4_LEN)
return OGS_ERROR;
memcpy(data, &ip->addr, OGS_IPV4_LEN);
return OGS_IPV4_LEN;
} else if (ip->ipv6) {
if (data_len < OGS_IPV6_LEN)
return OGS_ERROR;
memcpy(data, ip->addr6, OGS_IPV6_LEN);
return OGS_IPV6_LEN;
} else {
ogs_error("EUA type IPv4 but no IPv4 nor IPv6 address available");
return OGS_ERROR;
}
break;
default:
ogs_error("Unexpected session type");
return OGS_ERROR;
}
return OGS_OK;
}
/* TS 29.060 7.7.29 PDP Context */
int ogs_gtp1_build_pdp_context(ogs_tlv_octet_t *octet,
const ogs_gtp1_pdp_context_decoded_t *decoded, uint8_t *data, int data_len)
{
uint8_t *ptr = data;
uint16_t val16;
uint32_t val32;
int rv;
ogs_tlv_octet_t qos_sub_tlv_unused;
ogs_assert(octet);
ogs_assert(data);
ogs_assert((size_t)data_len >= 1);
octet->data = data;
#define CHECK_SPACE_ERR(bytes) \
if ((ptr - data) + (bytes) > data_len) \
return OGS_ERROR
CHECK_SPACE_ERR(1);
*ptr++ = (decoded->ea << 7) | (decoded->vaa << 6) |
(decoded->asi << 5)| (decoded->order << 4) |
(decoded->nsapi & 0x0f);
CHECK_SPACE_ERR(1);
*ptr++ = (decoded->sapi & 0x0f);
/* Quality of Service Subscribed */
CHECK_SPACE_ERR(1 + OGS_GTP1_QOS_PROFILE_MAX_LEN);
rv = ogs_gtp1_build_qos_profile(&qos_sub_tlv_unused, &decoded->qos_sub,
ptr, (data + data_len) - (ptr + 1));
if (rv < 0)
return rv;
*ptr = rv;
ptr += 1 + rv;
/* Quality of Service Requested */
CHECK_SPACE_ERR(1 + OGS_GTP1_QOS_PROFILE_MAX_LEN);
rv = ogs_gtp1_build_qos_profile(&qos_sub_tlv_unused, &decoded->qos_req,
ptr, (data + data_len) - (ptr + 1));
if (rv < 0)
return rv;
*ptr = rv;
ptr += 1 + rv;
/* Quality of Service Negotiated */
CHECK_SPACE_ERR(1 + OGS_GTP1_QOS_PROFILE_MAX_LEN);
rv = ogs_gtp1_build_qos_profile(&qos_sub_tlv_unused, &decoded->qos_neg,
ptr, (data + data_len) - (ptr + 1));
if (rv < 0)
return rv;
*ptr = rv;
ptr += 1 + rv;
CHECK_SPACE_ERR(2);
val16 = htobe16(decoded->snd);
memcpy(ptr, &val16, 2);
ptr += 2;
CHECK_SPACE_ERR(2);
val16 = htobe16(decoded->snu);
memcpy(ptr, &val16, 2);
ptr += 2;
CHECK_SPACE_ERR(1);
*ptr++ = decoded->send_npdu_nr;
CHECK_SPACE_ERR(1);
*ptr++ = decoded->receive_npdu_nr;
CHECK_SPACE_ERR(4);
val32 = htobe32(decoded->ul_teic);
memcpy(ptr, &val32, 4);
ptr += 4;
CHECK_SPACE_ERR(4);
val32 = htobe32(decoded->ul_teid);
memcpy(ptr, &val32, 4);
ptr += 4;
CHECK_SPACE_ERR(1);
*ptr++ = decoded->pdp_ctx_id;
CHECK_SPACE_ERR(1);
*ptr++ = 0xf0 | decoded->pdp_type_org;
CHECK_SPACE_ERR(1);
*ptr++ = ogs_gtp1_pdu_session_type_to_eua_ietf_type(decoded->pdp_type_num[0]);
/* PDP Address Length filled after PDP Address */
CHECK_SPACE_ERR(1);
rv = enc_pdp_ctx_as_eua(decoded->pdp_type_num[0], &decoded->pdp_address[0],
ptr + 1, (data + data_len) - (ptr + 1));
if (rv < 0)
return rv;
*ptr = rv;
ptr += 1 + rv;
/* GGSN Address for control plane Length */
CHECK_SPACE_ERR(1);
*ptr = decoded->ggsn_address_c.ipv6 ? OGS_GTP_GSN_ADDRESS_IPV6_LEN : OGS_GTP_GSN_ADDRESS_IPV4_LEN;
CHECK_SPACE_ERR(1 + (*ptr));
memcpy(ptr + 1,
decoded->ggsn_address_c.ipv6 ?
(uint8_t *)decoded->ggsn_address_c.addr6 :
(uint8_t *)&decoded->ggsn_address_c.addr,
*ptr);
ptr += 1 + *ptr;
/* GGSN Address for User Traffic Length */
CHECK_SPACE_ERR(1);
*ptr = decoded->ggsn_address_u.ipv6 ? OGS_GTP_GSN_ADDRESS_IPV6_LEN : OGS_GTP_GSN_ADDRESS_IPV4_LEN;
CHECK_SPACE_ERR(1 + (*ptr));
memcpy(ptr + 1,
decoded->ggsn_address_u.ipv6 ?
(uint8_t *)decoded->ggsn_address_u.addr6 :
(uint8_t *)&decoded->ggsn_address_u.addr,
*ptr);
ptr += 1 + *ptr;
/* APN */
rv = strlen(decoded->apn);
CHECK_SPACE_ERR(1 + rv + 1);
*ptr = ogs_fqdn_build(
(char *)ptr + 1, decoded->apn, rv);
ptr += 1 + *ptr;
CHECK_SPACE_ERR(2);
*ptr++ = (decoded->trans_id >> 8) & 0x0f;
*ptr++ = decoded->trans_id & 0xff;
if (decoded->ea == OGS_GTP1_PDPCTX_EXT_EUA_YES) {
CHECK_SPACE_ERR(1);
*ptr++ = decoded->pdp_type_num[1];
/* PDP Address Length filled after PDP Address */
CHECK_SPACE_ERR(1);
rv = enc_pdp_ctx_as_eua(decoded->pdp_type_num[1], &decoded->pdp_address[1],
ptr + 1, (data + data_len) - (ptr + 1));
if (rv < 0)
return rv;
*ptr = rv;
ptr += 1 + rv;
}
octet->len = (ptr - data);
return OGS_OK;
#undef CHECK_SPACE_ERR
}

View File

@ -300,4 +300,112 @@ int16_t ogs_gtp1_parse_qos_profile(
int16_t ogs_gtp1_build_qos_profile(ogs_tlv_octet_t *octet,
const ogs_gtp1_qos_profile_decoded_t *decoded, void *data, int data_len);
/* 7.7.7 Authentication Triplet. FIXME: Not used in MME Gn scenario? */
# if 0
struct ogs_gtp1_auth_triplet_s {
uint8_t rand[16];
uint8_t sres[4];
uint8_t kc[8];
} __attribute__ ((packed)) ogs_gtp1_auth_triplet_t;
#endif
/* 7.7.35 Authentication Quintuplet */
typedef struct ogs_gtp1_auth_quintuplet_s {
uint8_t rand[OGS_RAND_LEN];
uint8_t xres_len;
uint8_t xres[OGS_MAX_RES_LEN];
uint8_t ck[128/8];
uint8_t ik[128/8];
uint8_t autn_len;
uint8_t autn[OGS_AUTN_LEN];
} ogs_gtp1_auth_quintuplet_t;
/* TS 24.008 10.5.5.6 DRX parameter (value part only) */
typedef struct ogs_gtp1_drx_param_val_s {
uint8_t split_pg_cycle_code; /* 0 = equivalent to no DRX */
ED3(uint8_t cn_drx_cycle_len_coeff:4;,
uint8_t split_on_ccch:1;,
uint8_t non_drx_timer:3;)
} __attribute__ ((packed)) ogs_gtp1_drx_param_val_t;
/* 7.7.28 MM Context (Figure 41) */
/* Table 47: Security Mode Values */
#define OGS_GTP1_SEC_MODE_GSM_KEY_AND_TRIPLETS 1
#define OGS_GTP1_SEC_MODE_GSM_KEY_AND_QUINTUPLETS 3
#define OGS_GTP1_SEC_MODE_UMTS_KEY_AND_QUINTUPLETS 2
#define OGS_GTP1_SEC_MODE_USED_CIPHER_VALUE_UMTS_KEY_AND_QUINTUPLETS 0
/* Table 47B: Used GPRS integrity protection algorithm Values */
#define OGS_GTP1_USED_GPRS_IP_NO_IP 0
#define OGS_GTP1_USED_GPRS_IP_GIA4 4
#define OGS_GTP1_USED_GPRS_IP_GIA5 5
typedef struct ogs_gtp1_mm_context_decoded_s {
uint8_t gupii:1;
uint8_t ugipai:1;
uint8_t used_gprs_protection_algo:3; /* OGS_GTP1_USED_GPRS_IP */
uint8_t ksi:3;
uint8_t sec_mode:2; /* OGS_GTP1_SEC_MODE_* */
uint8_t num_vectors:3;
uint8_t used_cipher:3; /* 0..7 -> GEA/... */
uint8_t ck[OGS_SHA256_DIGEST_SIZE/2];
uint8_t ik[OGS_SHA256_DIGEST_SIZE/2];
ogs_gtp1_auth_quintuplet_t auth_quintuplets[5];
ogs_gtp1_drx_param_val_t drx_param;
uint8_t ms_network_capability_len;
uint8_t ms_network_capability[6]; /* ogs_nas_ms_network_capability_t */
uint8_t imeisv_len;
uint8_t imeisv[10]; /* ogs_nas_mobile_identity_imeisv_t */
uint8_t nrsrna;
} ogs_gtp1_mm_context_decoded_t;
int ogs_gtp1_build_mm_context(ogs_gtp1_tlv_mm_context_t *octet,
const ogs_gtp1_mm_context_decoded_t *decoded, uint8_t *data, int data_len);
/* Extended End User Address. Not explicitly defined in a table: */
#define OGS_GTP1_PDPCTX_EXT_EUA_NO 0
#define OGS_GTP1_PDPCTX_EXT_EUA_YES 1
/* 7.7.29 Table 48 Reordering Required Values */
#define OGS_GTP1_PDPCTX_REORDERING_REQUIRED_NO 0
#define OGS_GTP1_PDPCTX_REORDERING_REQUIRED_YES 1
/* 7.7.29 Table 49 VPLMN Address Allowed Values */
#define OGS_GTP1_PDPCTX_VLPMN_ADDR_ALLOWED_NO 0
#define OGS_GTP1_PDPCTX_VLPMN_ADDR_ALLOWED_YES 1
/* 7.7.29 Table 49A Activity Status Indicator Value */
#define OGS_GTP1_PDPCTX_ACTIVITY_STATUS_IND_YES 0
#define OGS_GTP1_PDPCTX_ACTIVITY_STATUS_IND_NO 1
/* 7.7.29 PDP Context */
typedef struct ogs_gtp1_pdp_context_decoded_s {
uint8_t ea:1; /* OGS_GTP1_PDPCTX_EXT_EUA_* */
uint8_t vaa:1; /* OGS_GTP1_PDPCTX_VLPMN_ADDR_ALLOWED_* */
uint8_t asi:1; /* OGS_GTP1_PDPCTX_ACTIVITY_STATUS_IND_* */
uint8_t order:1; /* OGS_GTP1_PDPCTX_REORDERING_REQUIRED_* */
uint8_t nsapi:4;
uint8_t sapi:4;
ogs_gtp1_qos_profile_decoded_t qos_sub;
ogs_gtp1_qos_profile_decoded_t qos_req;
ogs_gtp1_qos_profile_decoded_t qos_neg;
uint16_t snd;
uint16_t snu;
uint8_t send_npdu_nr;
uint8_t receive_npdu_nr;
uint32_t ul_teic;
uint32_t ul_teid;
uint8_t pdp_ctx_id;
uint8_t pdp_type_org;
uint8_t pdp_type_num[2];
ogs_ip_t pdp_address[2];
ogs_ip_t ggsn_address_c;
ogs_ip_t ggsn_address_u;
char apn[OGS_MAX_APN_LEN+1];
uint16_t trans_id:12;
} ogs_gtp1_pdp_context_decoded_t;
int ogs_gtp1_build_pdp_context(ogs_gtp1_tlv_pdp_context_t *octet,
const ogs_gtp1_pdp_context_decoded_t *decoded, uint8_t *data, int data_len);
#endif /* OGS_GTP1_TYPES_H */

View File

@ -3055,6 +3055,14 @@ mme_ue_t *mme_ue_add(enb_ue_t *enb_ue)
}
mme_ue->t_implicit_detach.pkbuf = NULL;
mme_ue->gn.t_gn_holding = ogs_timer_add(
ogs_app()->timer_mgr, mme_timer_gn_holding_timer_expire, mme_ue);
if (! mme_ue->gn.t_gn_holding) {
ogs_error("ogs_timer_add() failed");
ogs_pool_free(&mme_ue_pool, mme_ue);
return NULL;
}
mme_ebi_pool_init(mme_ue);
ogs_list_init(&mme_ue->sess_list);
@ -3156,6 +3164,7 @@ void mme_ue_remove(mme_ue_t *mme_ue)
ogs_timer_delete(mme_ue->t3470.timer);
ogs_timer_delete(mme_ue->t_mobile_reachable.timer);
ogs_timer_delete(mme_ue->t_implicit_detach.timer);
ogs_timer_delete(mme_ue->gn.t_gn_holding);
enb_ue_unlink(mme_ue);

View File

@ -427,6 +427,11 @@ struct mme_ue_s {
struct {
ogs_pool_id_t *mme_gn_teid_node; /* A node of MME-Gn-TEID */
uint32_t mme_gn_teid; /* MME-Gn-TEID is derived from NODE */
uint32_t sgsn_gn_teid;
ogs_ip_t sgsn_gn_ip;
ogs_ip_t sgsn_gn_ip_alt;
/* Unnamed timer in 3GPP TS 23.401 D.3.5 step 2), see also 3GPP TS 23.060 6.9.1.2.2 */
ogs_timer_t *t_gn_holding;
} gn;
struct {

View File

@ -99,6 +99,8 @@ const char *mme_event_get_name(mme_event_t *e)
case MME_EVENT_GN_MESSAGE:
return "MME_EVENT_GN_MESSAGE";
case MME_EVENT_GN_TIMER:
return "MME_EVENT_GN_TIMER";
default:
break;
}

View File

@ -51,6 +51,7 @@ typedef enum {
MME_EVENT_SGSAP_LO_CONNREFUSED,
MME_EVENT_GN_MESSAGE,
MME_EVENT_GN_TIMER,
MAX_NUM_OF_MME_EVENT,

View File

@ -21,6 +21,264 @@
#include "mme-gn-build.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" */
.ksi = mme_ue->nas_eps.ksi,
.sec_mode = OGS_GTP1_SEC_MODE_UMTS_KEY_AND_QUINTUPLETS,
.num_vectors = 0, /* TODO: figure out how to fill the quintuplets */
.drx_param = {
.split_pg_cycle_code = 0, /* equivalent to no DRX */
.cn_drx_cycle_len_coeff = 0,
.split_on_ccch = 0,
.non_drx_timer = 0,
},
.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);
memcpy(&mmctx_dec->imeisv[0], &mme_ue->nas_mobile_identity_imeisv, sizeof(mme_ue->nas_mobile_identity_imeisv));
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;
}
/* 3GPP TS 23.401 Annex E */
static void build_qos_profile_from_session(ogs_gtp1_qos_profile_decoded_t *qos_pdec,
const mme_sess_t *sess, const mme_bearer_t *bearer)
{
const mme_ue_t *mme_ue = sess->mme_ue;
const ogs_session_t *session = sess->session;
/* FIXME: Initialize with defaults: */
memset(qos_pdec, 0, sizeof(*qos_pdec));
qos_pdec->qos_profile.arp = session->qos.arp.priority_level;
/* 3GPP TS 23.401 Annex E table Table E.3 */
/* Also take into account table 7 in 3GPP TS 23.107 9.1.2.2 */
switch (session->qos.index) { /* QCI */
case 1:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_CONVERSATIONAL;
qos_pdec->qos_profile.data.source_statistics_descriptor = OGS_GTP1_QOS_SRC_STATS_DESC_SPEECH;
qos_pdec->dec_transfer_delay = 150;
qos_pdec->qos_profile.data.delay_class = 1;
break;
case 2:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_CONVERSATIONAL;
qos_pdec->qos_profile.data.source_statistics_descriptor = OGS_GTP1_QOS_SRC_STATS_DESC_UNKNOWN;
qos_pdec->dec_transfer_delay = 150;
qos_pdec->qos_profile.data.delay_class = 1;
break;
case 3:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_CONVERSATIONAL;
qos_pdec->qos_profile.data.source_statistics_descriptor = OGS_GTP1_QOS_SRC_STATS_DESC_UNKNOWN;
qos_pdec->dec_transfer_delay = 80;
qos_pdec->qos_profile.data.delay_class = 1;
break;
case 4:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_STREAMING;
qos_pdec->qos_profile.data.source_statistics_descriptor = OGS_GTP1_QOS_SRC_STATS_DESC_UNKNOWN;
qos_pdec->qos_profile.data.sdu_error_ratio = 5; /* 10^-5*/
qos_pdec->qos_profile.data.delay_class = 1;
break;
case 5:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_INTERACTIVE;
qos_pdec->qos_profile.data.traffic_handling_priority = 1;
qos_pdec->qos_profile.data.signalling_indication = 1;
qos_pdec->qos_profile.data.delay_class = qos_pdec->qos_profile.data.traffic_handling_priority;
break;
case 6:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_INTERACTIVE;
qos_pdec->qos_profile.data.traffic_handling_priority = 1;
qos_pdec->qos_profile.data.delay_class = qos_pdec->qos_profile.data.traffic_handling_priority;
break;
case 7:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_INTERACTIVE;
qos_pdec->qos_profile.data.traffic_handling_priority = 2;
qos_pdec->qos_profile.data.delay_class = qos_pdec->qos_profile.data.traffic_handling_priority;
break;
case 8:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_INTERACTIVE;
qos_pdec->qos_profile.data.traffic_handling_priority = 3;
qos_pdec->qos_profile.data.delay_class = qos_pdec->qos_profile.data.traffic_handling_priority;
break;
case 9:
default:
qos_pdec->qos_profile.data.traffic_class = OGS_GTP1_QOS_TRAFFIC_CLASS_BACKGROUND;
qos_pdec->qos_profile.data.delay_class = 4;
break;
}
qos_pdec->data_octet6_to_13_present = true;
qos_pdec->data_octet14_present = true;
qos_pdec->dec_mbr_kbps_dl = mme_ue->ambr.downlink / 1000;
qos_pdec->dec_mbr_kbps_ul = mme_ue->ambr.uplink / 1000;
qos_pdec->dec_gbr_kbps_dl = bearer->qos.gbr.downlink / 1000;
qos_pdec->dec_gbr_kbps_ul = bearer->qos.gbr.uplink / 1000;
}
static int sess_fill_pdp_context_decoded(mme_sess_t *sess, ogs_gtp1_pdp_context_decoded_t *pdpctx_dec)
{
mme_bearer_t *bearer = NULL;
*pdpctx_dec = (ogs_gtp1_pdp_context_decoded_t){
.ea = OGS_GTP1_PDPCTX_EXT_EUA_NO,
.vaa = OGS_GTP1_PDPCTX_VLPMN_ADDR_ALLOWED_YES,
.asi = OGS_GTP1_PDPCTX_ACTIVITY_STATUS_IND_NO,
.order = OGS_GTP1_PDPCTX_REORDERING_REQUIRED_NO,
/* 3GPP TS 23.401 Annex D3.5.5 2b.:
* "The GTP equence numbers received from the old 3G-SGSN are only relevant if
* delivery order is required for the PDP context (QoS profile)."
* NOTE 4: "The GTP and PDCP sequence numbers are not relevant" */
.snd = 0,
.snu = 0,
.send_npdu_nr = 0,
.receive_npdu_nr = 0,
.ul_teic = sess->pgw_s5c_teid,
.pdp_type_org = OGS_PDP_EUA_ORG_IETF,
.pdp_type_num = {sess->session->session_type, },
.pdp_address = {sess->session->ue_ip, },
.ggsn_address_c = sess->pgw_s5c_ip,
.trans_id = sess->pti,
};
ogs_cpystrn(pdpctx_dec->apn, sess->session->name, sizeof(pdpctx_dec->apn));
ogs_list_for_each(&sess->bearer_list, bearer) {
pdpctx_dec->nsapi = bearer->ebi;
pdpctx_dec->sapi = 3; /* FIXME. Using 3 = default for now. Maybe use 0 = UNASSIGNED ?*/
build_qos_profile_from_session(&pdpctx_dec->qos_sub, sess, bearer);
//FIXME: sort out where to get each one:
memcpy(&pdpctx_dec->qos_req, &pdpctx_dec->qos_sub, sizeof(pdpctx_dec->qos_sub));
memcpy(&pdpctx_dec->qos_neg, &pdpctx_dec->qos_sub, sizeof(pdpctx_dec->qos_sub));
pdpctx_dec->ul_teid = bearer->pgw_s5u_teid;
pdpctx_dec->pdp_ctx_id = 0; /* FIXME. */
pdpctx_dec->ggsn_address_u = bearer->pgw_s5u_ip;
/* FIXME: only 1 PDP Context supported in the message so far. */
break;
}
return OGS_OK;
}
/* 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)
{
ogs_gtp1_message_t gtp1_message;
ogs_gtp1_sgsn_context_response_t *rsp = NULL;
mme_sess_t *sess = NULL;
ogs_gtp1_gsn_addr_t mme_gnc_gsnaddr;
int gsn_len;
int rv;
ogs_gtp1_mm_context_decoded_t mmctx_dec;
uint8_t mmctx_dec_buf[512];
ogs_gtp1_pdp_context_decoded_t pdpctx_dec;
uint8_t pdpctx_dec_buf[1024];
ogs_debug("[Gn] build SGSN Context Response");
rsp = &gtp1_message.sgsn_context_response;
memset(&gtp1_message, 0, sizeof(ogs_gtp1_message_t));
gtp1_message.h.type = OGS_GTP1_SGSN_CONTEXT_RESPONSE_TYPE;
/* 3GPP TS 29.060 7.7.1 Cause, Mandatory */
rsp->cause.presence = 1;
rsp->cause.u8 = cause;
/* 3GPP TS 29.060 7.7.2 IMSI, Conditional */
rsp->imsi.presence = !!mme_ue;
if (rsp->imsi.presence) {
rsp->imsi.data = mme_ue->imsi;
rsp->imsi.len = mme_ue->imsi_len;
}
if (cause != OGS_GTP1_CAUSE_REQUEST_ACCEPTED)
goto build_ret;
ogs_assert(mme_ue);
rsp->tunnel_endpoint_identifier_control_plane.presence = 1;
rsp->tunnel_endpoint_identifier_control_plane.u32 = mme_ue->gn.mme_gn_teid;
ogs_list_for_each(&mme_ue->sess_list, sess) {
if (!MME_HAVE_SGW_S1U_PATH(sess))
continue;
/* 7.7.28 MM Context */
rv = sess_fill_mm_context_decoded(sess, &mmctx_dec);
if (rv != OGS_OK) {
ogs_error("sess_fill_mm_context_decoded() failed");
return NULL;
}
rsp->mm_context.presence = 1;
rv = ogs_gtp1_build_mm_context(&rsp->mm_context, &mmctx_dec,
&mmctx_dec_buf[0], sizeof(mmctx_dec_buf));
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_build_mm_context() failed");
return NULL;
}
/* 7.7.29 PDP Context */
rv = sess_fill_pdp_context_decoded(sess, &pdpctx_dec);
if (rv != OGS_OK) {
ogs_error("sess_fill_pdp_context_decoded() failed");
return NULL;
}
rsp->pdp_context.presence = 1;
rv = ogs_gtp1_build_pdp_context(&rsp->pdp_context, &pdpctx_dec,
&pdpctx_dec_buf[0], sizeof(pdpctx_dec_buf));
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_build_pdp_context() failed");
return NULL;
}
/* FIXME: right now we only support encoding 1 context in the message. */
break;
}
/* SGSN Address for Control Plane */
if (ogs_gtp_self()->gtpc_addr6 &&
(mme_ue->gn.sgsn_gn_ip.ipv6 || mme_ue->gn.sgsn_gn_ip_alt.ipv6)) {
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;
}
} 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;
}
}
rsp->sgsn_address_for_control_plane.presence = 1;
rsp->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
rsp->sgsn_address_for_control_plane.len = gsn_len;
build_ret:
return ogs_gtp1_build_msg(&gtp1_message);
}
/* 3GPP TS 29.060 7.5.14.1 RAN Information Relay */
ogs_pkbuf_t *mme_gn_build_ran_information_relay(
uint8_t type, const uint8_t *buf, size_t len,
@ -59,4 +317,4 @@ ogs_pkbuf_t *mme_gn_build_ran_information_relay(
gtp1_message.h.type = type;
return ogs_gtp1_build_msg(&gtp1_message);
}
}

View File

@ -30,6 +30,9 @@ extern "C" {
}
#endif
ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
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);

View File

@ -68,6 +68,164 @@ static int decode_global_enb_id(S1AP_Global_ENB_ID_t *glob_enb_id, const uint8_t
return OGS_OK;
}
/* 3GPP TS 23.003 2.8.2.2 Mapping from RAI and P-TMSI to GUT */
static void rai_ptmsi_to_guti(const ogs_nas_rai_t *rai, mme_p_tmsi_t ptmsi, uint32_t ptmsi_sig, ogs_nas_eps_guti_t *nas_guti)
{
uint16_t lac = be16toh(rai->lai.lac);;
nas_guti->nas_plmn_id =rai->lai.nas_plmn_id;
nas_guti->mme_gid = lac;
nas_guti->mme_code = rai->rac;
nas_guti->m_tmsi = 0xC0000000 | (ptmsi & 0x3f000000) | (ptmsi_sig & 0x00ff0000) | (ptmsi & 0x0000ffff);
}
/* TS 29.060 7.5.3 SGSN Context Request */
void mme_gn_handle_sgsn_context_request(
ogs_gtp_xact_t *xact, ogs_gtp1_sgsn_context_request_t *req)
{
ogs_nas_eps_guti_t nas_guti;
ogs_nas_rai_t *rai;
mme_ue_t *mme_ue = NULL;
int rv;
ogs_debug("[Gn] Rx SGSN Context Request");
if (!req->routeing_area_identity.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with no RAI!");
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_MISSING, xact);
return;
}
if (req->routeing_area_identity.len != sizeof(*rai)) {
ogs_warn("[Gn] Rx SGSN Context Request RAI wrong size %u vs exp %zu!",
req->routeing_area_identity.len, sizeof(*rai));
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT, xact);
return;
}
if (!req->tunnel_endpoint_identifier_control_plane.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with no Tunnel Endpoint Identifier Control Plane!");
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_MISSING, xact);
return;
}
if (!req->sgsn_address_for_control_plane.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with no SGSN Address for Control Plane!");
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_MISSING, xact);
return;
}
if (!req->imsi.presence &&
!req->temporary_logical_link_identifier.presence &&
!req->packet_tmsi.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with no IMSI/TLLI/P-TMSI!");
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_MISSING, xact);
return;
}
if (req->ms_validated.presence &&
(req->ms_validated.u8 & 0x01) /* 1=> "Yes" */
&& !req->imsi.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with 'MS Validated' but no IMSI!");
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_MISSING, xact);
return;
}
rai = req->routeing_area_identity.data;
if (req->imsi.presence) {
mme_ue = mme_ue_find_by_imsi(req->imsi.data, req->imsi.len);
} else if (req->packet_tmsi.presence) { /* P-TMSI */
if (!req->p_tmsi_signature.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with 'P-TMSI' but no P-TMSI Signature! Assuming value 0.");
req->p_tmsi_signature.u24 = 0;
}
rai_ptmsi_to_guti(rai, req->packet_tmsi.u32, req->p_tmsi_signature.u24, &nas_guti);
mme_ue = mme_ue_find_by_guti(&nas_guti);
} else if (req->temporary_logical_link_identifier.presence) {
if (!req->p_tmsi_signature.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with 'TLLI' but no P-TMSI Signature! Assuming value 0.");
req->p_tmsi_signature.u24 = 0;
}
/* TS 29.060 7.5.3 "The TLLI/P-TMSI and RAI is a foreign TLLI/P-TMSI and an RAI in the old SGSN."
* A foregin TLLI is "tlli = (p_tmsi & 0x3fffffff) | 0x80000000", and since we only use 0x3fffffff
* bits of P-TMSI to derive the GUTI, it's totally fine passing the TLLI as P-TMSI. */
rai_ptmsi_to_guti(rai, req->temporary_logical_link_identifier.u32, req->p_tmsi_signature.u24, &nas_guti);
mme_ue = mme_ue_find_by_guti(&nas_guti);
}
if (!mme_ue) {
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_IMSI_IMEI_NOT_KNOWN, xact);
return;
}
mme_ue->gn.sgsn_gn_teid = req->tunnel_endpoint_identifier_control_plane.u32;
rv = ogs_gtp1_gsn_addr_to_ip(req->sgsn_address_for_control_plane.data,
req->sgsn_address_for_control_plane.len,
&mme_ue->gn.sgsn_gn_ip);
ogs_assert(rv == OGS_OK);
if (req->alternative_sgsn_address_for_control_plane.presence) {
rv = ogs_gtp1_gsn_addr_to_ip(req->alternative_sgsn_address_for_control_plane.data,
req->alternative_sgsn_address_for_control_plane.len,
&mme_ue->gn.sgsn_gn_ip_alt);
ogs_assert(rv == OGS_OK);
}
/* 3GPP TS 23.401 Annex D.3.5 "Routing Area Update":
* Step 2. "If the old P-TMSI Signature was valid or if the new SGSN indicates that it has authenticated the MS,
* the old SGSN starts a timer."
*/
ogs_timer_start(mme_ue->gn.t_gn_holding, mme_timer_cfg(MME_TIMER_GN_HOLDING)->duration);
mme_gtp1_send_sgsn_context_response(mme_ue, OGS_GTP1_CAUSE_REQUEST_ACCEPTED, xact);
}
/* TS 29.060 7.5.5 SGSN Context Acknowledge */
void mme_gn_handle_sgsn_context_acknowledge(
ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_acknowledge_t *req)
{
int rv;
ogs_debug("[Gn] Rx SGSN Context Acknowledge");
rv = ogs_gtp_xact_commit(xact);
if (rv != OGS_OK) {
ogs_error("ogs_gtp_xact_commit() failed");
return;
}
if (!mme_ue) {
ogs_error("MME-UE Context has already been removed");
return;
}
/* 3GPP TS 23.060 6.9.1.2.2 Step 4), 3GPP TS 23.401 D.3.5 Step 4)
* The new SGSN sends an SGSN Context Acknowledge message to the old SGSN. The old MME (which is the old
* SGSN from the new SGSN's point of view) marks in its context that the information in the GWs and the HSS are
* invalid. This triggers the GWs, and the HSS to be updated if the UE initiates a Tracking Area Update procedure
* back to the old MME before completing the ongoing Routing Area Update procedure. If the security functions do
* not authenticate the MS correctly, then the routing area update shall be rejected, and the new SGSN shall send a
* reject indication to the old SGSN. The old MME shall continue as if the SGSN Context Request was never
* received.
* "NOTE 6: The new SGSN's operation is unmodified compared to pre-Rel-8. The old MME/S-GW (old SGSN from
* the new SGSN's point of view) does not forward any data towards the new SGSN." "*/
if (req->cause.u8 != OGS_GTP1_CAUSE_REQUEST_ACCEPTED) {
ogs_timer_stop(mme_ue->gn.t_gn_holding);
return;
}
/* 3GPP TS 23.060 6.9.1.2.2 Step 13)
* "If the old MME has an S1-MME association for the UE, the source MME sends a S1-U Release Command to the
* source eNodeB when receiving the SGSN Context Acknowledge message from the new SGSN. The RRC
* connection is released by the source eNodeB. The source eNodeB confirms the release of the RRC connection
* and of the S1-U connection by sending a S1-U Release Complete message to the source MME."
*/
if (mme_ue->enb_ue) {
rv = s1ap_send_ue_context_release_command(mme_ue->enb_ue,
S1AP_Cause_PR_nas, S1AP_CauseNas_normal_release,
S1AP_UE_CTX_REL_S1_REMOVE_AND_UNLINK, 0);
ogs_expect(rv == OGS_OK);
}
}
/* TS 29.060 7.5.14.1 */
void mme_gn_handle_ran_information_relay(
ogs_gtp_xact_t *xact, ogs_gtp1_ran_information_relay_t *req)

View File

@ -32,6 +32,12 @@ void mme_gn_handle_echo_request(
void mme_gn_handle_echo_response(
ogs_gtp_xact_t *xact, ogs_gtp1_echo_response_t *req);
void mme_gn_handle_sgsn_context_request(
ogs_gtp_xact_t *xact, ogs_gtp1_sgsn_context_request_t *req);
void mme_gn_handle_sgsn_context_acknowledge(
ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_acknowledge_t *req);
void mme_gn_handle_ran_information_relay(
ogs_gtp_xact_t *xact, ogs_gtp1_ran_information_relay_t *req);
@ -39,4 +45,4 @@ void mme_gn_handle_ran_information_relay(
}
#endif
#endif /* MME_S11_HANDLER_H */
#endif /* MME_GN_HANDLER_H */

View File

@ -758,6 +758,35 @@ int mme_gtp_send_bearer_resource_command(
return rv;
}
int mme_gtp1_send_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact)
{
int rv;
ogs_gtp1_header_t h;
ogs_pkbuf_t *pkbuf = NULL;
memset(&h, 0, sizeof(ogs_gtp1_header_t));
h.type = OGS_GTP1_SGSN_CONTEXT_RESPONSE_TYPE;
h.teid = mme_ue ? mme_ue->gn.sgsn_gn_teid : 0;
pkbuf = mme_gn_build_sgsn_context_response(mme_ue, cause);
if (!pkbuf) {
ogs_error("mme_gn_build_sgsn_context_response() failed");
return OGS_ERROR;
}
xact->local_teid = mme_ue ? mme_ue->gn.mme_gn_teid : 0;
rv = ogs_gtp1_xact_update_tx(xact, &h, pkbuf);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_xact_update_tx() failed");
return OGS_ERROR;
}
rv = ogs_gtp_xact_commit(xact);
ogs_expect(rv == OGS_OK);
return rv;
}
int mme_gtp1_send_ran_information_relay(
mme_sgsn_t *sgsn, const uint8_t *buf, size_t len,

View File

@ -55,6 +55,9 @@ int mme_gtp_send_delete_indirect_data_forwarding_tunnel_request(
int mme_gtp_send_bearer_resource_command(
mme_bearer_t *bearer, ogs_nas_eps_message_t *nas_message);
int mme_gtp1_send_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact);
int mme_gtp1_send_ran_information_relay(
mme_sgsn_t *sgsn, const uint8_t *buf, size_t len,
const ogs_nas_rai_t *rai, uint16_t cell_id);

View File

@ -601,6 +601,8 @@ void mme_s11_handle_delete_session_response(
if (action == OGS_GTP_DELETE_IN_PATH_SWITCH_REQUEST) {
source_ue = sgw_ue_cycle(target_ue->source_ue);
if (!source_ue) /* InterRAT to 2G/3G (SGSN) case: */
source_ue = target_ue;
ogs_assert(source_ue);
} else {
source_ue = target_ue;

View File

@ -279,8 +279,19 @@ void mme_s6a_handle_clr(mme_ue_t *mme_ue, ogs_diam_s6a_message_t *s6a_message)
}
break;
case OGS_DIAM_S6A_CT_MME_UPDATE_PROCEDURE:
case OGS_DIAM_S6A_CT_SGSN_UPDATE_PROCEDURE:
mme_ue->detach_type = MME_DETACH_TYPE_HSS_IMPLICIT;
/* 3GPP TS 23.401 D.3.5.5 8), 3GPP TS 23.060 6.9.1.2.2 8):
* "When the timer described in step 2 is running, the MM and PDP/EPS
* Bearer Contexts and any affected S-GW resources are removed when the
* timer expires and the SGSN received a Cancel Location".
*/
if (mme_ue->gn.t_gn_holding->running) {
ogs_debug("Gn Holding Timer is running, delay removing UE resources");
break;
}
/*
* There is no need to send NAS or S1AP message to the UE.
* So, we don't have to check whether UE is IDLE or not.

View File

@ -667,6 +667,12 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
case OGS_GTP1_ECHO_RESPONSE_TYPE:
mme_gn_handle_echo_response(xact, &gtp1_message.echo_response);
break;
case OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE:
mme_gn_handle_sgsn_context_request(xact, &gtp1_message.sgsn_context_request);
break;
case OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE:
mme_gn_handle_sgsn_context_acknowledge(xact, mme_ue, &gtp1_message.sgsn_context_acknowledge);
break;
case OGS_GTP1_RAN_INFORMATION_RELAY_TYPE:
mme_gn_handle_ran_information_relay(xact, &gtp1_message.ran_information_relay);
break;
@ -677,6 +683,37 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
ogs_pkbuf_free(pkbuf);
break;
case MME_EVENT_GN_TIMER:
mme_ue = e->mme_ue;
ogs_assert(mme_ue);
sgw_ue = mme_ue->sgw_ue;
ogs_assert(sgw_ue);
switch (e->timer_id) {
case MME_TIMER_GN_HOLDING:
/* 3GPP TS 23.401 Annex D.3.5 "Routing Area Update":
* Step 13. "When the timer started in step 2) (see mme_gn_handle_sgsn_context_request()) expires the old MME
* releases any RAN and Serving GW resources. If the PLMN has configured Secondary RAT usage data reporting,
* the MME first releases RAN resource before releasing Serving GW resources."
*/
GTP_COUNTER_CLEAR(mme_ue,
GTP_COUNTER_DELETE_SESSION_BY_PATH_SWITCH);
ogs_list_for_each(&mme_ue->sess_list, sess) {
GTP_COUNTER_INCREMENT(
mme_ue, GTP_COUNTER_DELETE_SESSION_BY_PATH_SWITCH);
ogs_assert(OGS_OK ==
mme_gtp_send_delete_session_request(
sgw_ue, sess,
OGS_GTP_DELETE_IN_PATH_SWITCH_REQUEST));
}
break;
default:
ogs_error("Unknown timer[%s:%d]",
mme_timer_get_name(e->timer_id), e->timer_id);
}
break;
case MME_EVENT_SGSAP_LO_SCTP_COMM_UP:
sock = e->sock;
ogs_assert(sock);

View File

@ -58,6 +58,9 @@ static mme_timer_cfg_t g_mme_timer_cfg[MAX_NUM_OF_MME_TIMER] = {
[MME_TIMER_S11_HOLDING] =
{ .have = true, .duration = ogs_time_from_msec(300) },
[MME_TIMER_GN_HOLDING] =
{ .have = true, .duration = ogs_time_from_sec(20) },
};
static void emm_timer_event_send(
@ -258,3 +261,24 @@ void mme_timer_s11_holding_timer_expire(void *data)
mme_event_free(e);
}
}
void mme_timer_gn_holding_timer_expire(void *data)
{
int rv;
mme_event_t *e = NULL;
mme_ue_t *mme_ue;
ogs_assert(data);
mme_ue = data;
e = mme_event_new(MME_EVENT_GN_TIMER);
e->timer_id = MME_TIMER_GN_HOLDING;
e->mme_ue = mme_ue;
rv = ogs_queue_push(ogs_app()->queue, e);
if (rv != OGS_OK) {
ogs_error("ogs_queue_push() failed:%d", (int)rv);
mme_event_free(e);
}
}

View File

@ -45,6 +45,8 @@ typedef enum {
MME_TIMER_S11_HOLDING,
MME_TIMER_GN_HOLDING,
MME_TIMER_SGS_CLI_CONN_TO_SRV,
MAX_NUM_OF_MME_TIMER,
@ -76,6 +78,7 @@ void mme_timer_implicit_detach_expire(void *data);
void mme_timer_sgs_cli_conn_to_srv(void *data);
void mme_timer_s1_holding_timer_expire(void *data);
void mme_timer_s11_holding_timer_expire(void *data);
void mme_timer_gn_holding_timer_expire(void *data);
#ifdef __cplusplus
}