From c7da148423cedd864dfc6bc3e13b5226184a93ab Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Fri, 22 Dec 2023 17:09:38 +0100 Subject: [PATCH] WIP --- lib/crypt/ogs-kdf.c | 27 +++ lib/crypt/ogs-kdf.h | 4 + lib/gtp/v1/types.c | 377 ++++++++++++++++++++++++++++++++++++++- lib/gtp/v1/types.h | 11 ++ src/mme/emm-handler.c | 26 ++- src/mme/emm-handler.h | 3 + src/mme/emm-sm.c | 23 +++ src/mme/mme-context.c | 9 + src/mme/mme-gn-build.c | 167 ++++++++++++++++- src/mme/mme-gn-build.h | 6 + src/mme/mme-gn-handler.c | 252 +++++++++++++++++++++++++- src/mme/mme-gn-handler.h | 6 + src/mme/mme-gtp-path.c | 77 +++++++- src/mme/mme-gtp-path.h | 6 + src/mme/mme-s11-build.c | 6 +- src/mme/mme-sm.c | 7 + tests/common/nas-path.c | 2 + 17 files changed, 989 insertions(+), 20 deletions(-) diff --git a/lib/crypt/ogs-kdf.c b/lib/crypt/ogs-kdf.c index a1290a26f9..17ee7c7e3d 100644 --- a/lib/crypt/ogs-kdf.c +++ b/lib/crypt/ogs-kdf.c @@ -36,6 +36,7 @@ #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_KASME_DERIVATION_IDLE_MOBILITY 0x19 #define FC_FOR_CK_IK_DERIVATION_IDLE_MOBILITY 0x1B typedef struct kdf_param_s { @@ -415,6 +416,32 @@ void ogs_kdf_nas_token( memcpy(nas_token, output, 2); } +/* TS33.401 Annex A.11 : K’ASME from CK, IK derivation during idle mode mobility */ +void ogs_kdf_kasme_idle_mobility( + const uint8_t *ck, const uint8_t *ik, + uint32_t nonce_ue, uint32_t nonce_mme, + uint8_t *kasme) +{ + kdf_param_t param; + uint8_t key[OGS_KEY_LEN*2]; + + ogs_assert(ck); + ogs_assert(ik); + ogs_assert(kasme); + + memcpy(key, ck, OGS_KEY_LEN); + memcpy(key+OGS_KEY_LEN, ik, OGS_KEY_LEN); + + memset(param, 0, sizeof(param)); + param[0].buf = (uint8_t *)&nonce_ue; + param[0].len = sizeof(nonce_ue); + param[1].buf = (uint8_t *)&nonce_mme; + param[1].len = sizeof(nonce_mme); + + ogs_kdf_common(key, OGS_KEY_LEN*2, + FC_FOR_KASME_DERIVATION_IDLE_MOBILITY, param, kasme); +} + /* 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) diff --git a/lib/crypt/ogs-kdf.h b/lib/crypt/ogs-kdf.h index 139283436e..8bc2605823 100644 --- a/lib/crypt/ogs-kdf.h +++ b/lib/crypt/ogs-kdf.h @@ -109,6 +109,10 @@ void ogs_kdf_ck_ik_handover( void ogs_kdf_nas_token( uint32_t ul_count, const uint8_t *kasme, uint8_t *nas_token); +/* TS33.401 Annex A.11 : K’ASME from CK, IK derivation during idle mode mobility */ +void ogs_kdf_kasme_idle_mobility(const uint8_t *ck, const uint8_t *ik, + uint32_t nonce_ue, uint32_t nonce_mme, uint8_t *kasme); + /* 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); diff --git a/lib/gtp/v1/types.c b/lib/gtp/v1/types.c index 17667d3913..ba812d7a74 100644 --- a/lib/gtp/v1/types.c +++ b/lib/gtp/v1/types.c @@ -399,8 +399,154 @@ int16_t ogs_gtp1_build_qos_profile(ogs_tlv_octet_t *octet, return octet->len; } +/* 7.7.35 Authentication Quintuplet */ +static int decode_quintuple(ogs_gtp1_auth_quintuplet_t *decoded, uint8_t *data, unsigned int len) +{ + uint8_t *ptr = data; + +#define CHECK_SPACE_ERR(bytes) \ + if ((ptr - data) + (bytes) > len) \ + return OGS_ERROR + + CHECK_SPACE_ERR(sizeof(decoded->rand)); + memcpy(&decoded->rand, ptr, sizeof(decoded->rand)); + ptr += sizeof(decoded->rand); + + CHECK_SPACE_ERR(1); + decoded->xres_len = *ptr++; + CHECK_SPACE_ERR(decoded->xres_len); + memcpy(&decoded->xres[0], ptr, decoded->xres_len); + ptr += decoded->xres_len; + + CHECK_SPACE_ERR(sizeof(decoded->ck)); + memcpy(&decoded->ck, ptr, sizeof(decoded->ck)); + ptr += sizeof(decoded->ck); + + CHECK_SPACE_ERR(sizeof(decoded->ik)); + memcpy(&decoded->ik, ptr, sizeof(decoded->ik)); + ptr += sizeof(decoded->ik); + + CHECK_SPACE_ERR(1); + decoded->autn_len = *ptr++; + CHECK_SPACE_ERR(decoded->autn_len); + memcpy(&decoded->autn[0], ptr, decoded->autn_len); + ptr += decoded->autn_len; + + return (ptr - data); +#undef CHECK_SPACE_ERR +} + /* 7.7.28 MM Context */ -/* TODO: UMTS support, see Figure 41 and Figure 42. */ +int ogs_gtp1_parse_mm_context( + ogs_gtp1_mm_context_decoded_t *decoded, const ogs_tlv_octet_t *octet) +{ + uint8_t *ptr = octet->data; + unsigned int i; + uint16_t val16; + + ogs_assert(decoded); + ogs_assert(octet); + + memset(decoded, 0, sizeof(ogs_gtp1_mm_context_decoded_t)); + +#define CHECK_SPACE_ERR(bytes) \ + if ((ptr - (uint8_t *)octet->data) + (bytes) > octet->len) \ + return OGS_ERROR + + CHECK_SPACE_ERR(2); + decoded->sec_mode = ptr[1] >> 6; + switch (decoded->sec_mode) { + case OGS_GTP1_SEC_MODE_UMTS_KEY_AND_QUINTUPLETS: + case OGS_GTP1_SEC_MODE_USED_CIPHER_VALUE_UMTS_KEY_AND_QUINTUPLETS: + break; /* Handle below */ + case OGS_GTP1_SEC_MODE_GSM_KEY_AND_TRIPLETS: + case OGS_GTP1_SEC_MODE_GSM_KEY_AND_QUINTUPLETS: + ogs_error("[Gn] MM Context IE: Security Mode %u not supported!", decoded->sec_mode); + return OGS_ERROR; /* Not supported/expected here */ + } + /* Structure for both sec modes is the same here, only difference is that + OGS_GTP1_SEC_MODE_UMTS_KEY_AND_QUINTUPLETS has "Used Cipher" field encoded + as spare all 1s. */ + decoded->gupii = *ptr >> 7; + decoded->ugipai = (*ptr >> 6) & 0x01; + decoded->used_gprs_protection_algo = (*ptr >> 3) & 0x07; + decoded->ksi = *ptr & 0x07; + ptr++; + decoded->num_vectors = (*ptr >> 3) & 0x07; + decoded->used_cipher = *ptr & 0x07; + ptr++; + + CHECK_SPACE_ERR(sizeof(decoded->ck)); + memcpy(&decoded->ck[0], ptr, sizeof(decoded->ck)); + ptr += sizeof(decoded->ck); + CHECK_SPACE_ERR(sizeof(decoded->ik)); + memcpy(&decoded->ik[0], ptr, sizeof(decoded->ik)); + ptr += sizeof(decoded->ik); + + /* Quintuple length (in bytes) */ + CHECK_SPACE_ERR(2); + memcpy(&val16, &ptr[0], 2); + val16 = be16toh(val16); + ptr += 2; + + CHECK_SPACE_ERR(val16); + int remain = val16; + for (i = 0; i < decoded->num_vectors; i++) { + int rv = decode_quintuple(&decoded->auth_quintuplets[i], ptr + (val16 - remain), remain); + if (rv < 0) + return OGS_ERROR; + remain -= rv; + } + ptr += val16; + + CHECK_SPACE_ERR(sizeof(decoded->drx_param)); + memcpy(&decoded->drx_param, ptr, sizeof(decoded->drx_param)); + ptr += sizeof(decoded->drx_param); + + CHECK_SPACE_ERR(1); + decoded->ms_network_capability_len = *ptr++; + CHECK_SPACE_ERR(decoded->ms_network_capability_len); + if (decoded->ms_network_capability_len > 0) { + memcpy(&decoded->ms_network_capability[0], ptr, + ogs_min(decoded->ms_network_capability_len, sizeof(decoded->ms_network_capability))); + } + ptr += decoded->ms_network_capability_len; + + /* Container length (in bytes) */ + CHECK_SPACE_ERR(2); + memcpy(&val16, &ptr[0], 2); + val16 = be16toh(val16); + ptr += 2; + + /* Extract IMEISV from Container: */ + CHECK_SPACE_ERR(val16); + if (val16 > 0) { + CHECK_SPACE_ERR(2); + /* Validate Container (Mobile Identity IMEISV) IE, TS 29.060 Table 47A */ + if (ptr[0] != 0x23) + return OGS_ERROR; + decoded->imeisv_len = ptr[1]; + CHECK_SPACE_ERR(2 + decoded->imeisv_len); + memcpy(&decoded->imeisv[0], &ptr[2], + ogs_min(decoded->imeisv_len, sizeof(decoded->imeisv))); + } + ptr += val16; + + if ((ptr - (uint8_t *)octet->data) + 1 <= octet->len) { + CHECK_SPACE_ERR(*ptr); + if (*ptr > 0) { + /* ptr[0] = Length of Access Restriction Data */ + decoded->nrsrna = ptr[1] & 0x01; + } + ptr += 1 + *ptr; + } + /* else: Be tolerant and accept missing Length of Access Restriction Data field + * assuming the whole Access Restriction Data field is missing. */ + + return OGS_OK; +#undef CHECK_SPACE_ERR +} + 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) { @@ -443,6 +589,7 @@ int ogs_gtp1_build_mm_context(ogs_gtp1_tlv_mm_context_t *octet, len_ptr = (uint16_t *)ptr; /* will be filled later */ ptr += 2; + /* 7.7.35 Authentication Quintuplet */ for (i = 0; i < decoded->num_vectors; i++) { CHECK_SPACE_ERR(sizeof(decoded->auth_quintuplets[0])); @@ -504,6 +651,9 @@ int ogs_gtp1_build_mm_context(ogs_gtp1_tlv_mm_context_t *octet, CHECK_SPACE_ERR(2); *ptr++ = 1; *ptr++ = 0x01; + } else { + CHECK_SPACE_ERR(1); + *ptr++ = 0; } octet->len = (ptr - data); @@ -511,6 +661,69 @@ int ogs_gtp1_build_mm_context(ogs_gtp1_tlv_mm_context_t *octet, #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 decode the value part of the address, + * instead of using regular ogs_gtp1_eua_to_ip(). */ +static int dec_eua_for_pdp_ctx(uint8_t pdp_type_org, uint8_t pdp_type_num, uint8_t *data, int data_len, ogs_ip_t *ip) +{ + ogs_assert(data); + ogs_assert(ip); + + memset(ip, 0, sizeof *ip); + + switch (pdp_type_org) { + case OGS_PDP_EUA_ORG_IETF: + break; + case OGS_PDP_EUA_ORG_ETSI: + default: + ogs_error("Unsupported EUA organization %u", pdp_type_org); + return OGS_ERROR; + } + + switch (pdp_type_num) { + case OGS_PDP_EUA_IETF_IPV4: + if (data_len == OGS_IPV4_LEN) { + memcpy(&ip->addr, data, OGS_IPV4_LEN); + } else if (data_len != 0) { + ogs_error("Wrong IPv4 EUA length %u", data_len); + return OGS_ERROR; + } + ip->ipv4 = 1; + ip->ipv6 = 0; + break; + case OGS_PDP_EUA_IETF_IPV6: + if (data_len == OGS_IPV6_LEN) { + memcpy(ip->addr6, data, OGS_IPV6_LEN); + } else if (data_len != 0) { + ogs_error("Wrong IPv6 EUA length %u", data_len); + return OGS_ERROR; + } + ip->ipv4 = 0; + ip->ipv6 = 1; + break; + case OGS_PDP_EUA_IETF_IPV4V6: + if (data_len == OGS_IPV4_LEN) { + memcpy(&ip->addr, data, OGS_IPV4_LEN); + } else if (data_len == OGS_IPV6_LEN) { + memcpy(ip->addr6, data, OGS_IPV6_LEN); + } else if (data_len == OGS_IPV4_LEN + OGS_IPV6_LEN) { + memcpy(&ip->addr, data, OGS_IPV4_LEN); + memcpy(ip->addr6, data + OGS_IPV4_LEN, OGS_IPV6_LEN); + } else if (data_len != 0) { + ogs_error("Wrong IPv4v6 EUA length %u", data_len); + return OGS_ERROR; + } + ip->ipv4 = 1; + ip->ipv6 = 1; + break; + default: + ogs_error("No IPv4 or IPv6"); + return OGS_ERROR; + } + + return OGS_OK; +} + /* 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(). */ @@ -567,6 +780,168 @@ static int enc_pdp_ctx_as_eua(uint8_t pdu_session_type, const ogs_ip_t *ip, } /* TS 29.060 7.7.29 PDP Context */ +int ogs_gtp1_parse_pdp_context( + ogs_gtp1_pdp_context_decoded_t *decoded, const ogs_tlv_octet_t *octet) +{ + uint8_t *ptr = octet->data; + uint16_t val16; + uint32_t val32; + ogs_tlv_octet_t qos; + int rv; + + ogs_assert(decoded); + ogs_assert(octet); + + memset(decoded, 0, sizeof(ogs_gtp1_pdp_context_decoded_t)); + +#define CHECK_SPACE_ERR(bytes) \ + if ((ptr - (uint8_t *)octet->data) + (bytes) > octet->len) \ + return OGS_ERROR + + CHECK_SPACE_ERR(1); + decoded->ea = *ptr >> 7; + decoded->vaa = (*ptr >> 6) & 0x01; + decoded->asi = (*ptr >> 5) & 0x01; + decoded->order = (*ptr >> 4) & 0x01; + decoded->nsapi = *ptr & 0x0f; + ptr++; + + CHECK_SPACE_ERR(1); + decoded->sapi = *ptr++; + + /* QoS Sub */ + CHECK_SPACE_ERR(1); + qos.len = *ptr++; + CHECK_SPACE_ERR(qos.len); + qos.data = ptr; + rv = ogs_gtp1_parse_qos_profile(&decoded->qos_sub, &qos); + if (rv < 0) + return OGS_ERROR; + ptr += qos.len; + + /* QoS Req */ + CHECK_SPACE_ERR(1); + qos.len = *ptr++; + CHECK_SPACE_ERR(qos.len); + qos.data = ptr; + rv = ogs_gtp1_parse_qos_profile(&decoded->qos_req, &qos); + if (rv < 0) + return OGS_ERROR; + ptr += qos.len; + + /* QoS Neg */ + CHECK_SPACE_ERR(1); + qos.len = *ptr++; + CHECK_SPACE_ERR(qos.len); + qos.data = ptr; + rv = ogs_gtp1_parse_qos_profile(&decoded->qos_neg, &qos); + if (rv < 0) + return OGS_ERROR; + ptr += qos.len; + + CHECK_SPACE_ERR(2); + memcpy(&val16, &ptr[0], 2); + decoded->snd = be16toh(val16); + ptr += 2; + + CHECK_SPACE_ERR(2); + memcpy(&val16, &ptr[0], 2); + decoded->snu = be16toh(val16); + ptr += 2; + + CHECK_SPACE_ERR(1); + decoded->send_npdu_nr = *ptr++; + CHECK_SPACE_ERR(1); + decoded->receive_npdu_nr = *ptr++; + + CHECK_SPACE_ERR(4); + memcpy(&val32, &ptr[0], 4); + decoded->ul_teic = be32toh(val32); + ptr += 4; + + CHECK_SPACE_ERR(4); + memcpy(&val32, &ptr[0], 4); + decoded->ul_teid = be32toh(val32); + ptr += 4; + + CHECK_SPACE_ERR(1); + decoded->pdp_ctx_id = *ptr++; + + /* 'PDP Address' related fields */ + CHECK_SPACE_ERR(1); + decoded->pdp_type_org = *ptr & 0x0f; + ptr++; + CHECK_SPACE_ERR(2); /* PDP Address Type Number + PDP Address Length */ + decoded->pdp_type_num[0] = ogs_gtp1_eua_ietf_type_to_pdu_session_type(ptr[0]); + CHECK_SPACE_ERR(2 + ptr[1]); /* + PDP Address Length value */ + rv = dec_eua_for_pdp_ctx(decoded->pdp_type_org, ptr[0], &ptr[2], + ptr[1], &decoded->pdp_address[0]); + if (rv < 0) + return rv; + ptr += 2 + ptr[1]; + + /* GGSN Address for control plane Length */ + CHECK_SPACE_ERR(1); + CHECK_SPACE_ERR(1 + *ptr); + switch (*ptr) { + case OGS_GTP_GSN_ADDRESS_IPV4_LEN: + decoded->ggsn_address_c.ipv4 = 1; + memcpy((uint8_t *)&decoded->ggsn_address_c.addr, ptr + 1, *ptr); + break; + case OGS_GTP_GSN_ADDRESS_IPV6_LEN: + decoded->ggsn_address_c.ipv6 = 1; + memcpy((uint8_t *)&decoded->ggsn_address_c.addr6, ptr + 1, *ptr); + break; + default: + return OGS_ERROR; + } + decoded->ggsn_address_c.len = *ptr; + ptr += 1 + *ptr; + + /* GGSN Address for User Traffic Length */ + CHECK_SPACE_ERR(1); + CHECK_SPACE_ERR(1 + *ptr); + switch (*ptr) { + case OGS_GTP_GSN_ADDRESS_IPV4_LEN: + decoded->ggsn_address_u.ipv4 = 1; + memcpy((uint8_t *)&decoded->ggsn_address_u.addr, ptr + 1, *ptr); + break; + case OGS_GTP_GSN_ADDRESS_IPV6_LEN: + decoded->ggsn_address_u.ipv6 = 1; + memcpy((uint8_t *)&decoded->ggsn_address_u.addr6, ptr + 1, *ptr); + break; + default: + return OGS_ERROR; + } + decoded->ggsn_address_u.len = *ptr; + ptr += 1 + *ptr; + + /* APN length */ + CHECK_SPACE_ERR(1); + CHECK_SPACE_ERR(1 + *ptr); + rv = ogs_fqdn_parse(decoded->apn, (const char *)ptr + 1, + ogs_min(*ptr, sizeof(decoded->apn))); + ptr += 1 + *ptr; + + CHECK_SPACE_ERR(2); + decoded->trans_id = (((uint16_t)ptr[1]) << 4) | (ptr[0] & 0x0f); + ptr += 2; + + if (decoded->ea == OGS_GTP1_PDPCTX_EXT_EUA_YES) { + CHECK_SPACE_ERR(2); /* PDP Address Type Number + PDP Address Length */ + decoded->pdp_type_num[1] = ogs_gtp1_eua_ietf_type_to_pdu_session_type(ptr[0]); + CHECK_SPACE_ERR(2 + ptr[1]); /* + PDP Address Length value */ + rv = dec_eua_for_pdp_ctx(decoded->pdp_type_org, ptr[0], &ptr[2], + ptr[1], &decoded->pdp_address[1]); + if (rv < 0) + return rv; + ptr += 2 + ptr[1]; + } + + return OGS_OK; +#undef CHECK_SPACE_ERR +} + 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) { diff --git a/lib/gtp/v1/types.h b/lib/gtp/v1/types.h index 843d6c19a7..157f81102a 100644 --- a/lib/gtp/v1/types.h +++ b/lib/gtp/v1/types.h @@ -145,6 +145,13 @@ ED4(uint8_t spare:5;, uint8_t cs:1;) } __attribute__ ((packed)) ogs_gtp1_cause_t; +/* 7.7.15 Tunnel Endpoint Identifier Data II */ +typedef struct ogs_gtp1_teidII_s { +ED2(uint8_t spare:4;, /* Shall be set to 0 */ + uint8_t nsapi:4;) + uint32_t teid; +} __attribute__ ((packed)) ogs_gtp1_teidII_t; + /* TS 29.060 16.0.0 Table 7.7.50.1 RAT Type values */ #define OGS_GTP1_RAT_TYPE_RESERVED 0 @@ -359,6 +366,8 @@ typedef struct ogs_gtp1_mm_context_decoded_s { uint8_t nrsrna; } ogs_gtp1_mm_context_decoded_t; +int ogs_gtp1_parse_mm_context( + ogs_gtp1_mm_context_decoded_t *decoded, const ogs_tlv_octet_t *octet); 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); @@ -405,6 +414,8 @@ typedef struct ogs_gtp1_pdp_context_decoded_s { uint16_t trans_id:12; } ogs_gtp1_pdp_context_decoded_t; +int ogs_gtp1_parse_pdp_context( + ogs_gtp1_pdp_context_decoded_t *decoded, const ogs_tlv_octet_t *octet); 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); diff --git a/src/mme/emm-handler.c b/src/mme/emm-handler.c index 868167b41e..2d13b5988f 100644 --- a/src/mme/emm-handler.c +++ b/src/mme/emm-handler.c @@ -185,7 +185,7 @@ int emm_handle_attach_request(mme_ue_t *mme_ue, OGS_NAS_SECURITY_ALGORITHMS_EIA0) { ogs_warn("Encrypt[0x%x] can be skipped with EEA0, " "but Integrity[0x%x] cannot be bypassed with EIA0", - mme_selected_enc_algorithm(mme_ue), + mme_selected_enc_algorithm(mme_ue), mme_selected_int_algorithm(mme_ue)); r = nas_eps_send_attach_reject(mme_ue, OGS_NAS_EMM_CAUSE_UE_SECURITY_CAPABILITIES_MISMATCH, @@ -549,6 +549,26 @@ int emm_handle_service_request( return OGS_OK; } +bool emm_tau_request_guti_comes_from_sgsn(const ogs_nas_eps_tracking_area_update_request_t *tau_request) +{ + 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 shall be set to zero; + * and the most significant bit of shall be set to + * one. Based on this definition, the most significant bit of the + * 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) { @@ -679,6 +699,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); diff --git a/src/mme/emm-handler.h b/src/mme/emm-handler.h index 9ea14b93a4..b6c82a79ad 100644 --- a/src/mme/emm-handler.h +++ b/src/mme/emm-handler.h @@ -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_guti_comes_from_sgsn( + const ogs_nas_eps_tracking_area_update_request_t *tau_request); + #ifdef __cplusplus } #endif diff --git a/src/mme/emm-sm.c b/src/mme/emm-sm.c index b1ce78098f..130b0a3e6e 100644 --- a/src/mme/emm-sm.c +++ b/src/mme/emm-sm.c @@ -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" @@ -285,7 +286,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); @@ -488,6 +491,26 @@ static void common_register_state(ogs_fsm_t *s, mme_event_t *e, break; } + if (emm_tau_request_guti_comes_from_sgsn(&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); + OGS_FSM_TRAN(s, &emm_state_registered); /* FIXME: use a specific FSM state here? */ + break; + } + if (!MME_UE_HAVE_IMSI(mme_ue)) { ogs_info("TAU request : Unknown UE"); r = nas_eps_send_tau_reject(mme_ue, diff --git a/src/mme/mme-context.c b/src/mme/mme-context.c index 3b3a792a39..0a371c54b3 100644 --- a/src/mme/mme-context.c +++ b/src/mme/mme-context.c @@ -2488,6 +2488,15 @@ mme_sgsn_t *mme_sgsn_find_by_routing_address(const ogs_nas_rai_t *rai, uint16_t } } + /* If no exact match found, try using any with same RAI: */ + ogs_list_for_each(&self.sgsn_list, sgsn) { + mme_sgsn_route_t *rt = NULL; + ogs_list_for_each(&sgsn->route_list, rt) { + if (memcmp(&rt->rai, rai, sizeof(ogs_nas_rai_t)) == 0) + return sgsn; + } + } + /* No route found, return default route if available: */ return mme_sgsn_find_by_default_routing_address(); } diff --git a/src/mme/mme-gn-build.c b/src/mme/mme-gn-build.c index 76acd0fdbe..3882e0365e 100644 --- a/src/mme/mme-gn-build.c +++ b/src/mme/mme-gn-build.c @@ -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" */ @@ -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); @@ -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; } @@ -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 = >p1_message.sgsn_context_request; + memset(>p1_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(>p1_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) @@ -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(>p1_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 = >p1_message.sgsn_context_acknowledge; + memset(>p1_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(>p1_message); } diff --git a/src/mme/mme-gn-build.h b/src/mme/mme-gn-build.h index 6a7af32b5a..642ff174be 100644 --- a/src/mme/mme-gn-build.h +++ b/src/mme/mme-gn-build.h @@ -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); diff --git a/src/mme/mme-gn-handler.c b/src/mme/mme-gn-handler.c index 149ec76e99..884d2c18a4 100644 --- a/src/mme/mme-gn-handler.c +++ b/src/mme/mme-gn-handler.c @@ -30,6 +30,7 @@ #include "mme-gn-handler.h" #include "s1ap-path.h" +#include "nas-path.h" void mme_gn_handle_echo_request( ogs_gtp_xact_t *xact, ogs_gtp1_echo_request_t *req) @@ -68,12 +69,26 @@ 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 */ +/* 3GPP TS 23.003 2.8.2.1 Mapping from GUTI to RAI, P-TMSI and P-TMSI signature */ +void guti_to_rai_ptmsi(const ogs_nas_eps_guti_t *nas_guti, ogs_nas_rai_t *rai, mme_p_tmsi_t *ptmsi, uint32_t *ptmsi_sig) +{ + rai->lai.nas_plmn_id = nas_guti->nas_plmn_id; + rai->lai.lac = nas_guti->mme_gid; + rai->rac = nas_guti->mme_code; + if (ptmsi) + *ptmsi = 0xC0000000 | + (nas_guti->m_tmsi & 0x3f000000) | + (nas_guti->mme_code & 0x0ff) << 16 | + (nas_guti->m_tmsi & 0x0000ffff); + if (ptmsi_sig) + *ptmsi_sig = (nas_guti->m_tmsi & 0x00ff0000); +} + +/* 3GPP TS 23.003 2.8.2.2 Mapping from RAI and P-TMSI to GUTI */ 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->nas_plmn_id = rai->lai.nas_plmn_id; + nas_guti->mme_gid = rai->lai.lac; nas_guti->mme_code = rai->rac; nas_guti->m_tmsi = 0xC0000000 | (ptmsi & 0x3f000000) | (ptmsi_sig & 0x00ff0000) | (ptmsi & 0x0000ffff); } @@ -135,7 +150,7 @@ void mme_gn_handle_sgsn_context_request( 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); + rai_ptmsi_to_guti(rai, htobe16(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) { @@ -145,7 +160,7 @@ void mme_gn_handle_sgsn_context_request( /* 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); + rai_ptmsi_to_guti(rai, htobe16(req->temporary_logical_link_identifier.u32), req->p_tmsi_signature.u24, &nas_guti); mme_ue = mme_ue_find_by_guti(&nas_guti); } @@ -177,6 +192,231 @@ void mme_gn_handle_sgsn_context_request( mme_gtp1_send_sgsn_context_response(mme_ue, OGS_GTP1_CAUSE_REQUEST_ACCEPTED, xact); } +static mme_sess_t *mme_ue_session_from_gtp1_pdp_ctx(mme_ue_t *mme_ue, const ogs_gtp1_pdp_context_decoded_t *gtp1_pdp_ctx) +{ + mme_sess_t *sess = NULL; + mme_bearer_t *bearer = NULL; + const ogs_gtp1_qos_profile_decoded_t *qos_pdec = >p1_pdp_ctx->qos_sub; + uint8_t pti = gtp1_pdp_ctx->trans_id; + uint8_t qci = 0; + ogs_session_t *ogs_sess; + + ogs_sess = mme_session_find_by_apn(mme_ue, gtp1_pdp_ctx->apn); + if (!ogs_sess) { + ogs_assert(mme_ue->num_of_session < OGS_MAX_NUM_OF_SESS); + ogs_sess = &mme_ue->session[mme_ue->num_of_session]; + mme_ue->num_of_session++; + ogs_sess->name = ogs_strdup(gtp1_pdp_ctx->apn); + } + ogs_sess->smf_ip = gtp1_pdp_ctx->ggsn_address_c; + ogs_sess->context_identifier = gtp1_pdp_ctx->pdp_ctx_id; + ogs_sess->session_type = gtp1_pdp_ctx->pdp_type_num[0]; + ogs_sess->ue_ip = gtp1_pdp_ctx->pdp_address[0]; + /* TODO: sess->paa with gtp1_pdp_ctx->pdp_address[0], + using/implementing ogs_gtp2_ip_to_paa ? */ + ogs_ip_to_paa(&ogs_sess->ue_ip, &ogs_sess->paa); + + /* 3GPP TS 23.060 section 9.2.1A: "The QoS profiles of the PDP context and EPS bearer are mapped as specified in TS 23.401" + * 3GPP TS 23.401 Annex E: "Mapping between EPS and Release 99 QoS parameters" + */ + ogs_gtp1_qos_profile_to_qci(qos_pdec, &qci); + ogs_sess->qos.index = qci; + ogs_sess->qos.arp.priority_level = qos_pdec->qos_profile.arp; /* 3GPP TS 23.401 Annex E Table E.2 */ + ogs_sess->qos.arp.pre_emption_capability = 0; /* ignored as per 3GPP TS 23.401 Annex E */ + ogs_sess->qos.arp.pre_emption_vulnerability = 0; /* ignored as per 3GPP TS 23.401 Annex E */ + if (qos_pdec->data_octet6_to_13_present) { + ogs_sess->ambr.downlink = qos_pdec->dec_mbr_kbps_dl * 1000; + ogs_sess->ambr.uplink = qos_pdec->dec_mbr_kbps_ul * 1000; + } + + sess = mme_sess_find_by_pti(mme_ue, pti); + if (!sess) { + sess = mme_sess_add(mme_ue, pti); + ogs_assert(sess); + } + + sess->session = ogs_sess; + sess->pgw_s5c_teid = gtp1_pdp_ctx->ul_teic; + sess->pgw_s5c_ip = gtp1_pdp_ctx->ggsn_address_c; + switch (ogs_sess->session_type) { + case OGS_PDU_SESSION_TYPE_IPV4: + sess->request_type.type = OGS_NAS_EPS_PDN_TYPE_IPV4; + break; + case OGS_PDU_SESSION_TYPE_IPV6: + sess->request_type.type = OGS_NAS_EPS_PDN_TYPE_IPV6; + break; + case OGS_PDU_SESSION_TYPE_IPV4V6: + sess->request_type.type = OGS_NAS_EPS_PDN_TYPE_IPV4V6; + break; + } + sess->request_type.value = OGS_NAS_EPS_REQUEST_TYPE_INITIAL; + + bearer = mme_bearer_find_by_sess_ebi(sess, gtp1_pdp_ctx->nsapi); + if (!bearer) { + bearer = mme_default_bearer_in_sess(sess); + if (!bearer) { + bearer = mme_bearer_add(sess); + ogs_assert(bearer); + } + } + bearer->pgw_s5u_teid = gtp1_pdp_ctx->ul_teid; + bearer->pgw_s5u_ip = gtp1_pdp_ctx->ggsn_address_u; + /* Send invalid Remote Address and TEID since it makes no sense that SGW + * forwards GTPUv2 traffic to SGSN: */ + bearer->enb_s1u_ip.ipv4 = 1; + bearer->enb_s1u_ip.addr = 0; + bearer->enb_s1u_teid = 0xffffffff; + + return sess; +} + +/* TS 29.060 7.5.4 SGSN Context Response */ +int mme_gn_handle_sgsn_context_response( + ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_response_t *resp) +{ + int rv; + int gtp1_cause, emm_cause = OGS_NAS_EMM_CAUSE_NETWORK_FAILURE; + char imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1]; + ogs_gtp1_mm_context_decoded_t gtp1_mm_ctx; + ogs_gtp1_pdp_context_decoded_t gtp1_pdp_ctx; + mme_sess_t *sess = NULL; + + ogs_debug("[Gn] Rx SGSN Context Response"); + + rv = ogs_gtp_xact_commit(xact); + if (rv != OGS_OK) { + ogs_error("ogs_gtp_xact_commit() failed"); + return rv; + } + + if (!mme_ue) { + ogs_error("MME-UE Context has already been removed"); + return rv; + } + + switch (resp->cause.u8) { + case OGS_GTP1_CAUSE_REQUEST_ACCEPTED: + break; /* Handle below */ + case OGS_GTP1_CAUSE_TGT_ACC_RESTRICTED_SUBSCRIBER: + emm_cause = OGS_NAS_EMM_CAUSE_REQUESTED_SERVICE_OPTION_NOT_AUTHORIZED_IN_THIS_PLMN; + break; + case OGS_GTP1_CAUSE_IMSI_IMEI_NOT_KNOWN: + case OGS_GTP1_CAUSE_SYSTEM_FAILURE: + case OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT: + case OGS_GTP1_CAUSE_MANDATORY_IE_MISSING: + case OGS_GTP1_CAUSE_OPTIONAL_IE_INCORRECT: + case OGS_GTP1_CAUSE_INVALID_MESSAGE_FORMAT: + case OGS_GTP1_CAUSE_P_TMSI_SIGNATURE_MISMATCH: + default: + emm_cause = OGS_NAS_EMM_CAUSE_NETWORK_FAILURE; + break; + } + + if (resp->cause.u8 != OGS_GTP1_CAUSE_REQUEST_ACCEPTED) { + ogs_error("[Gn] Rx SGSN Context Response cause:%u", resp->cause.u8); + rv = nas_eps_send_tau_reject(mme_ue, emm_cause); + return rv; + } + + if (!resp->imsi.presence) { + ogs_error("[Gn] Rx SGSN Context Response with no IMSI!"); + gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; + goto nack_and_reject; + } + + ogs_buffer_to_bcd(resp->imsi.data, resp->imsi.len, imsi_bcd); + ogs_info(" IMSI[%s]", imsi_bcd); + mme_ue_set_imsi(mme_ue, imsi_bcd); + + if (!resp->tunnel_endpoint_identifier_control_plane.presence) { + ogs_error("[Gn] Rx SGSN Context Response with no Tunnel Endpoint Identifier Control Plane!"); + gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; + goto nack_and_reject; + } + + mme_ue->gn.sgsn_gn_teid = resp->tunnel_endpoint_identifier_control_plane.u32; + + if (!resp->mm_context.presence) { + ogs_error("[Gn] Rx SGSN Context Response with no MM Context!"); + gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; + goto nack_and_reject; + } + + if (!resp->pdp_context.presence) { + ogs_error("[Gn] Rx SGSN Context Response with no PDP Context!"); + gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; + goto nack_and_reject; + } + + rv = ogs_gtp1_parse_mm_context(>p1_mm_ctx, &resp->mm_context); + if (rv != OGS_OK) { + ogs_error("[Gn] Rx SGSN Context Response: Failed parsing MM Context"); + gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT; + goto nack_and_reject; + } + + rv = ogs_gtp1_parse_pdp_context(>p1_pdp_ctx, &resp->pdp_context); + if (rv != OGS_OK) { + ogs_error("[Gn] Rx SGSN Context Response: Failed parsing PDP Context"); + gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT; + goto nack_and_reject; + } + + if (gtp1_mm_ctx.imeisv_len > 0) + memcpy(&mme_ue->nas_mobile_identity_imeisv, >p1_mm_ctx.imeisv[0], + ogs_min(gtp1_mm_ctx.imeisv_len, sizeof(mme_ue->nas_mobile_identity_imeisv))); + /* else: TODO: 3GPP TS 23.401 D3.6: we need to request IMEI to the UE over EUTRAN */ + + mme_ue->ms_network_capability.length = gtp1_mm_ctx.ms_network_capability_len; + if (gtp1_mm_ctx.ms_network_capability_len > 0) + memcpy(((uint8_t*)&mme_ue->ms_network_capability)+1, >p1_mm_ctx.ms_network_capability[0], + ogs_min(gtp1_mm_ctx.ms_network_capability_len, sizeof(mme_ue->ms_network_capability) - 1)); + /* TODO: how to fill first byte of mme_ue->ms_network_capability ? */ + + mme_ue->nas_eps.ksi = gtp1_mm_ctx.ksi; + /* FIXME: 3GPP TS 33.401 A.11: "The generation of NONCE UE shall be + * sufficiently random such that both the probability of the UE generating equal + * values of NONCE UE and the probability of an attacker being able to predict + * future values of NONCEUE over the duration of practical eavesdropping attacks on + * a particular user are extremely low." + */ + uint32_t nonce_ue = 0x12345678; + /* FIXME: 3GPP TS 33.401 A.11: The generation of NONCE MME shall be as defined in clause A.10. + * 3GPP TS 33.401 A.10: The generation of NONCE MME shall be sufficiently + * random such that both the probability of the MME generating equal values of + * NONCE MME and the probability of an attacker being able to predict future + * values of NONCEMME over the duration of practical eavesdropping attacks on + * a particular user are extremely low.*/ + uint32_t nonce_mme = 0x12345678; + ogs_kdf_kasme_idle_mobility(gtp1_mm_ctx.ck, gtp1_mm_ctx.ik, nonce_ue, nonce_mme, mme_ue->kasme); + ogs_kdf_kenb(mme_ue->kasme, mme_ue->ul_count.i32, mme_ue->kenb); + ogs_kdf_nh_enb(mme_ue->kasme, mme_ue->kenb, mme_ue->nh); + mme_ue->nhcc = 1; + + if (gtp1_mm_ctx.num_vectors > 0) { + mme_ue->xres_len = gtp1_mm_ctx.auth_quintuplets[0].xres_len; + memcpy(mme_ue->xres, gtp1_mm_ctx.auth_quintuplets[0].xres, mme_ue->xres_len); + memcpy(mme_ue->rand, gtp1_mm_ctx.auth_quintuplets[0].rand, OGS_RAND_LEN); + memcpy(mme_ue->autn, gtp1_mm_ctx.auth_quintuplets[0].autn, OGS_AUTN_LEN); + } + + /* TODO: fill mme_ue with gtp1_mm_ctx, gtp1_pdp_ctx... */ + sess = mme_ue_session_from_gtp1_pdp_ctx(mme_ue, >p1_pdp_ctx); + + rv = mme_gtp1_send_sgsn_context_ack(mme_ue, OGS_GTP1_CAUSE_REQUEST_ACCEPTED, xact); + + GTP_COUNTER_INCREMENT(mme_ue, GTP_COUNTER_CREATE_SESSION_BY_PATH_SWITCH); + mme_gtp_send_create_session_request(sess, OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST); + + return OGS_OK; + +nack_and_reject: + rv = mme_gtp1_send_sgsn_context_ack(mme_ue, gtp1_cause, xact); + ogs_info("[%s] TAU Reject [OGS_NAS_EMM_CAUSE:%d]", mme_ue->imsi_bcd, emm_cause); + rv = nas_eps_send_tau_reject(mme_ue, emm_cause); + return OGS_ERROR; +} + /* 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) diff --git a/src/mme/mme-gn-handler.h b/src/mme/mme-gn-handler.h index d6138a4c59..0c3a6156f3 100644 --- a/src/mme/mme-gn-handler.h +++ b/src/mme/mme-gn-handler.h @@ -35,12 +35,18 @@ void mme_gn_handle_echo_response( void mme_gn_handle_sgsn_context_request( ogs_gtp_xact_t *xact, ogs_gtp1_sgsn_context_request_t *req); +int mme_gn_handle_sgsn_context_response( + ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_response_t *resp); + 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); +void guti_to_rai_ptmsi(const ogs_nas_eps_guti_t *nas_guti, ogs_nas_rai_t *rai, + mme_p_tmsi_t *ptmsi, uint32_t *ptmsi_sig); + #ifdef __cplusplus } #endif diff --git a/src/mme/mme-gtp-path.c b/src/mme/mme-gtp-path.c index f70ba66767..627a02e59b 100644 --- a/src/mme/mme-gtp-path.c +++ b/src/mme/mme-gtp-path.c @@ -248,8 +248,10 @@ int mme_gtp_send_create_session_request(mme_sess_t *sess, int create_action) ogs_assert(sgw_ue); if (create_action == OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST) { - sgw_ue = sgw_ue_cycle(sgw_ue->target_ue); - ogs_assert(sgw_ue); + sgw_ue_t *sgw_ue2 = sgw_ue_cycle(sgw_ue->target_ue); + if (sgw_ue2) + sgw_ue = sgw_ue2; + /* else: InterRAT to 2G/3G (SGSN) case: */ } memset(&h, 0, sizeof(ogs_gtp2_header_t)); @@ -758,6 +760,45 @@ int mme_gtp_send_bearer_resource_command( return rv; } +/************************* + * GTPv1C (Gn interface): + *************************/ + +int mme_gtp1_send_sgsn_context_request( + mme_sgsn_t *sgsn, mme_ue_t *mme_ue) +{ + int rv; + ogs_gtp1_header_t h; + ogs_pkbuf_t *pkbuf = NULL; + ogs_gtp_xact_t *xact = NULL; + + ogs_assert(sgsn); + + memset(&h, 0, sizeof(ogs_gtp1_header_t)); + h.type = OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE; + h.teid = 0; + + pkbuf = mme_gn_build_sgsn_context_request(mme_ue); + if (!pkbuf) { + ogs_error("mme_gn_build_ran_information_relay() failed"); + return OGS_ERROR; + } + + xact = ogs_gtp1_xact_local_create(&sgsn->gnode, &h, pkbuf, NULL, NULL); + if (!xact) { + ogs_error("ogs_gtp1_xact_local_create() failed"); + return OGS_ERROR; + } + /* TS 29.060 8.2: "The SGSN Context Request message, where the Tunnel + * Endpoint Identifier shall be set to all zeroes." */ + xact->local_teid = 0; + + rv = ogs_gtp_xact_commit(xact); + ogs_expect(rv == OGS_OK); + + return rv; +} + int mme_gtp1_send_sgsn_context_response( mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact) { @@ -788,6 +829,38 @@ int mme_gtp1_send_sgsn_context_response( return rv; } +int mme_gtp1_send_sgsn_context_ack( + 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; + + ogs_assert(mme_ue); + + memset(&h, 0, sizeof(ogs_gtp1_header_t)); + h.type = OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE; + h.teid = mme_ue->gn.sgsn_gn_teid; + + pkbuf = mme_gn_build_sgsn_context_ack(mme_ue, cause); + if (!pkbuf) { + ogs_error("mme_gn_build_sgsn_context_response() failed"); + return OGS_ERROR; + } + xact->local_teid = mme_ue->gn.mme_gn_teid; + + 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, const ogs_nas_rai_t *rai, uint16_t cell_id) diff --git a/src/mme/mme-gtp-path.h b/src/mme/mme-gtp-path.h index 1e41d34ffd..f741559c56 100644 --- a/src/mme/mme-gtp-path.h +++ b/src/mme/mme-gtp-path.h @@ -55,9 +55,15 @@ 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_request( + mme_sgsn_t *sgsn, mme_ue_t *mme_ue); + int mme_gtp1_send_sgsn_context_response( mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact); +int mme_gtp1_send_sgsn_context_ack( + 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); diff --git a/src/mme/mme-s11-build.c b/src/mme/mme-s11-build.c index 3b58c65b66..92bbbbfc47 100644 --- a/src/mme/mme-s11-build.c +++ b/src/mme/mme-s11-build.c @@ -61,8 +61,10 @@ ogs_pkbuf_t *mme_s11_build_create_session_request( ogs_assert(sgw_ue); if (create_action == OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST) { - sgw_ue = sgw_ue_cycle(sgw_ue->target_ue); - ogs_assert(sgw_ue); + sgw_ue_t *sgw_ue2 = sgw_ue_cycle(sgw_ue->target_ue); + if (sgw_ue2) + sgw_ue = sgw_ue2; + /* else: InterRAT to 2G/3G (SGSN) case: */ } ogs_debug("Create Session Request"); diff --git a/src/mme/mme-sm.c b/src/mme/mme-sm.c index f61f1465a1..d46a3b8b83 100644 --- a/src/mme/mme-sm.c +++ b/src/mme/mme-sm.c @@ -674,6 +674,13 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e) case OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE: mme_gn_handle_sgsn_context_request(xact, >p1_message.sgsn_context_request); break; + case OGS_GTP1_SGSN_CONTEXT_RESPONSE_TYPE: + rv = mme_gn_handle_sgsn_context_response(xact, mme_ue, >p1_message.sgsn_context_response); + if (rv == OGS_OK) { + //rv = nas_eps_send_tau_accept(mme_ue, S1AP_ProcedureCode_id_InitialContextSetup); + //OGS_FSM_TRAN(&mme_ue->sm, &emm_state_registered); + } + break; case OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE: mme_gn_handle_sgsn_context_acknowledge(xact, mme_ue, >p1_message.sgsn_context_acknowledge); break; diff --git a/tests/common/nas-path.c b/tests/common/nas-path.c index 8db4a2d02f..f3dc3cca96 100644 --- a/tests/common/nas-path.c +++ b/tests/common/nas-path.c @@ -171,6 +171,8 @@ void testemm_recv(test_ue_t *test_ue, ogs_pkbuf_t *pkbuf) break; case OGS_NAS_EPS_DOWNLINK_NAS_TRANSPORT: break; + case OGS_NAS_EPS_TRACKING_AREA_UPDATE_REJECT: + break; default: ogs_error("Unknown message[%d]", message.emm.h.message_type); break;