[GTPv1] Introduce APIs to decode MM Context and PDP Context IEs

They will be used in a follow-up patch implementing GERAN->EUTRAN idle
mobility.
This commit is contained in:
Pau Espin 2024-01-15 16:27:49 +01:00 committed by Sukchan Lee
parent feaa86fc9c
commit 078bfc90da
2 changed files with 384 additions and 1 deletions

View File

@ -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]));
@ -514,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(). */
@ -570,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)
{

View File

@ -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);