diff --git a/lib/crypt/ogs-kdf.c b/lib/crypt/ogs-kdf.c index 8d3d84607..9d80b8144 100644 --- a/lib/crypt/ogs-kdf.c +++ b/lib/crypt/ogs-kdf.c @@ -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 diff --git a/lib/crypt/ogs-kdf.h b/lib/crypt/ogs-kdf.h index 36286b07a..f64b26338 100644 --- a/lib/crypt/ogs-kdf.h +++ b/lib/crypt/ogs-kdf.h @@ -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 diff --git a/lib/gtp/v1/conv.c b/lib/gtp/v1/conv.c index 9951f702f..c4e7b78a5 100644 --- a/lib/gtp/v1/conv.c +++ b/lib/gtp/v1/conv.c @@ -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) { diff --git a/lib/gtp/v1/conv.h b/lib/gtp/v1/conv.h index 1b0e14a1d..ee95b6b37 100644 --- a/lib/gtp/v1/conv.h +++ b/lib/gtp/v1/conv.h @@ -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); diff --git a/lib/gtp/v1/types.c b/lib/gtp/v1/types.c index aabfb25e4..17667d391 100644 --- a/lib/gtp/v1/types.c +++ b/lib/gtp/v1/types.c @@ -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 +} diff --git a/lib/gtp/v1/types.h b/lib/gtp/v1/types.h index b3a4c8af5..843d6c19a 100644 --- a/lib/gtp/v1/types.h +++ b/lib/gtp/v1/types.h @@ -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 */ diff --git a/src/mme/mme-context.c b/src/mme/mme-context.c index 106e4e2d1..114823ba7 100644 --- a/src/mme/mme-context.c +++ b/src/mme/mme-context.c @@ -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); diff --git a/src/mme/mme-context.h b/src/mme/mme-context.h index 1016052c7..75dd155c0 100644 --- a/src/mme/mme-context.h +++ b/src/mme/mme-context.h @@ -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 { diff --git a/src/mme/mme-event.c b/src/mme/mme-event.c index 36c4bd624..483b02b12 100644 --- a/src/mme/mme-event.c +++ b/src/mme/mme-event.c @@ -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; } diff --git a/src/mme/mme-event.h b/src/mme/mme-event.h index 97869070f..f7f1af75c 100644 --- a/src/mme/mme-event.h +++ b/src/mme/mme-event.h @@ -51,6 +51,7 @@ typedef enum { MME_EVENT_SGSAP_LO_CONNREFUSED, MME_EVENT_GN_MESSAGE, + MME_EVENT_GN_TIMER, MAX_NUM_OF_MME_EVENT, diff --git a/src/mme/mme-gn-build.c b/src/mme/mme-gn-build.c index dc88063dd..76acd0fdb 100644 --- a/src/mme/mme-gn-build.c +++ b/src/mme/mme-gn-build.c @@ -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 = >p1_message.sgsn_context_response; + memset(>p1_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(>p1_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(>p1_message); -} \ No newline at end of file +} diff --git a/src/mme/mme-gn-build.h b/src/mme/mme-gn-build.h index fc66282c6..6a7af32b5 100644 --- a/src/mme/mme-gn-build.h +++ b/src/mme/mme-gn-build.h @@ -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); diff --git a/src/mme/mme-gn-handler.c b/src/mme/mme-gn-handler.c index b661d0de9..149ec76e9 100644 --- a/src/mme/mme-gn-handler.c +++ b/src/mme/mme-gn-handler.c @@ -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) diff --git a/src/mme/mme-gn-handler.h b/src/mme/mme-gn-handler.h index 3cf2e661b..d6138a4c5 100644 --- a/src/mme/mme-gn-handler.h +++ b/src/mme/mme-gn-handler.h @@ -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 */ diff --git a/src/mme/mme-gtp-path.c b/src/mme/mme-gtp-path.c index 423d9829e..f70ba6676 100644 --- a/src/mme/mme-gtp-path.c +++ b/src/mme/mme-gtp-path.c @@ -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, diff --git a/src/mme/mme-gtp-path.h b/src/mme/mme-gtp-path.h index 37773e115..1e41d34ff 100644 --- a/src/mme/mme-gtp-path.h +++ b/src/mme/mme-gtp-path.h @@ -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); diff --git a/src/mme/mme-s11-handler.c b/src/mme/mme-s11-handler.c index e73418fad..4b476317f 100644 --- a/src/mme/mme-s11-handler.c +++ b/src/mme/mme-s11-handler.c @@ -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; diff --git a/src/mme/mme-s6a-handler.c b/src/mme/mme-s6a-handler.c index e7b128ca8..308398256 100644 --- a/src/mme/mme-s6a-handler.c +++ b/src/mme/mme-s6a-handler.c @@ -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. diff --git a/src/mme/mme-sm.c b/src/mme/mme-sm.c index 37162a57d..47dfb3007 100644 --- a/src/mme/mme-sm.c +++ b/src/mme/mme-sm.c @@ -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, >p1_message.echo_response); break; + 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_ACKNOWLEDGE_TYPE: + mme_gn_handle_sgsn_context_acknowledge(xact, mme_ue, >p1_message.sgsn_context_acknowledge); + break; case OGS_GTP1_RAN_INFORMATION_RELAY_TYPE: mme_gn_handle_ran_information_relay(xact, >p1_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); diff --git a/src/mme/mme-timer.c b/src/mme/mme-timer.c index f2b6204f7..1f196c916 100644 --- a/src/mme/mme-timer.c +++ b/src/mme/mme-timer.c @@ -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); + } +} diff --git a/src/mme/mme-timer.h b/src/mme/mme-timer.h index f7cdf415c..a17931a16 100644 --- a/src/mme/mme-timer.h +++ b/src/mme/mme-timer.h @@ -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 }