diff options
-rw-r--r-- | include/Makefile.am | 1 | ||||
-rw-r--r-- | include/osmocom/core/logging.h | 3 | ||||
-rw-r--r-- | include/osmocom/gsm/gsup.h | 139 | ||||
-rw-r--r-- | src/gsm/Makefile.am | 3 | ||||
-rw-r--r-- | src/gsm/gsup.c | 472 | ||||
-rw-r--r-- | src/gsm/libosmogsm.map | 3 | ||||
-rw-r--r-- | tests/Makefile.am | 7 | ||||
-rw-r--r-- | tests/gsup/gsup_test.c | 264 | ||||
-rw-r--r-- | tests/gsup/gsup_test.ok | 14 | ||||
-rw-r--r-- | tests/testsuite.at | 6 |
10 files changed, 908 insertions, 4 deletions
diff --git a/include/Makefile.am b/include/Makefile.am index af72bfca..2e58d7e2 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -70,6 +70,7 @@ nobase_include_HEADERS = \ osmocom/gsm/gsm48.h \ osmocom/gsm/gsm48_ie.h \ osmocom/gsm/gsm_utils.h \ + osmocom/gsm/gsup.h \ osmocom/gsm/ipa.h \ osmocom/gsm/lapd_core.h \ osmocom/gsm/lapdm.h \ diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h index 4b79c054..89d3f948 100644 --- a/include/osmocom/core/logging.h +++ b/include/osmocom/core/logging.h @@ -91,7 +91,8 @@ void logp(int subsys, const char *file, int line, int cont, const char *format, #define DLCTRL -8 /*!< Control Interface */ #define DLGTP -9 /*!< GTP (GPRS Tunneling Protocol */ #define DLSTATS -10 /*!< Statistics */ -#define OSMO_NUM_DLIB 10 /*!< Number of logging sub-systems in libraries */ +#define DLGSUP 11 /*!< Generic Subscriber Update Protocol */ +#define OSMO_NUM_DLIB 11 /*!< Number of logging sub-systems in libraries */ /*! Configuration of singgle log category / sub-system */ struct log_category { diff --git a/include/osmocom/gsm/gsup.h b/include/osmocom/gsm/gsup.h new file mode 100644 index 00000000..d966b2d2 --- /dev/null +++ b/include/osmocom/gsm/gsup.h @@ -0,0 +1,139 @@ +/* Osmocom Subscriber Update Protocol message encoder/decoder */ + +/* (C) 2014 by Sysmocom s.f.m.c. GmbH, Author: Jacob Erlbeck + * (C) 2016 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <stdint.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/gsm_23_003.h> +#include <osmocom/gsm/protocol/gsm_04_08_gprs.h> +#include <osmocom/crypt/auth.h> + +/*! Maximum nubmer of PDP inside \ref osmo_gsup_message */ +#define OSMO_GSUP_MAX_NUM_PDP_INFO 10 /* GSM 09.02 limits this to 50 */ +/*! Maximum number of auth info inside \ref osmo_gsup_message */ +#define OSMO_GSUP_MAX_NUM_AUTH_INFO 5 +/*! Maximum number of octets encoding MSISDN in BCD format */ +#define OSMO_GSUP_MAX_MSISDN_LEN 9 + +#define OSMO_GSUP_PDP_TYPE_SIZE 2 + +/*! Information Element Identifiers for GSUP IEs */ +enum osmo_gsup_iei { + OSMO_GSUP_IMSI_IE = 0x01, + OSMO_GSUP_CAUSE_IE = 0x02, + OSMO_GSUP_AUTH_TUPLE_IE = 0x03, + OSMO_GSUP_PDP_INFO_COMPL_IE = 0x04, + OSMO_GSUP_PDP_INFO_IE = 0x05, + OSMO_GSUP_CANCEL_TYPE_IE = 0x06, + OSMO_GSUP_FREEZE_PTMSI_IE = 0x07, + OSMO_GSUP_MSISDN_IE = 0x08, + OSMO_GSUP_HLR_NUMBER_IE = 0x09, + OSMO_GSUP_PDP_CONTEXT_ID_IE = 0x10, + OSMO_GSUP_PDP_TYPE_IE = 0x11, + OSMO_GSUP_ACCESS_POINT_NAME_IE = 0x12, + OSMO_GSUP_PDP_QOS_IE = 0x13, + OSMO_GSUP_RAND_IE = 0x20, + OSMO_GSUP_SRES_IE = 0x21, + OSMO_GSUP_KC_IE = 0x22, + /* 3G support */ + OSMO_GSUP_IK_IE = 0x23, + OSMO_GSUP_CK_IE = 0x24, + OSMO_GSUP_AUTN_IE = 0x25, + OSMO_GSUP_AUTS_IE = 0x26, + OSMO_GSUP_RES_IE = 0x27, +}; + +/*! GSUP message type */ +enum osmo_gsup_message_type { + OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST = 0b00000100, + OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR = 0b00000101, + OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT = 0b00000110, + + OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST = 0b00001000, + OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR = 0b00001001, + OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT = 0b00001010, + + OSMO_GSUP_MSGT_PURGE_MS_REQUEST = 0b00001100, + OSMO_GSUP_MSGT_PURGE_MS_ERROR = 0b00001101, + OSMO_GSUP_MSGT_PURGE_MS_RESULT = 0b00001110, + + OSMO_GSUP_MSGT_INSERT_DATA_REQUEST = 0b00010000, + OSMO_GSUP_MSGT_INSERT_DATA_ERROR = 0b00010001, + OSMO_GSUP_MSGT_INSERT_DATA_RESULT = 0b00010010, + + OSMO_GSUP_MSGT_DELETE_DATA_REQUEST = 0b00010100, + OSMO_GSUP_MSGT_DELETE_DATA_ERROR = 0b00010101, + OSMO_GSUP_MSGT_DELETE_DATA_RESULT = 0b00010110, + + OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST = 0b00011100, + OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR = 0b00011101, + OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT = 0b00011110, +}; + +#define OSMO_GSUP_IS_MSGT_REQUEST(msgt) (((msgt) & 0b00000011) == 0b00) +#define OSMO_GSUP_IS_MSGT_ERROR(msgt) (((msgt) & 0b00000011) == 0b01) +#define OSMO_GSUP_TO_MSGT_ERROR(msgt) (((msgt) & 0b11111100) | 0b01) + +enum osmo_gsup_cancel_type { + OSMO_GSUP_CANCEL_TYPE_UPDATE = 1, /* on wire: 0 */ + OSMO_GSUP_CANCEL_TYPE_WITHDRAW = 2, /* on wire: 1 */ +}; + +/*! parsed/decoded PDP context information */ +struct osmo_gsup_pdp_info { + unsigned int context_id; + int have_info; + /*! Type of PDP context */ + uint16_t pdp_type; + /*! APN information, still in encoded form. Can be NULL if no + * APN information included */ + const uint8_t *apn_enc; + /*! length (in octets) of apn_enc */ + size_t apn_enc_len; + /*! QoS information, still in encoded form. Can be NULL if no + * QoS information included */ + const uint8_t *qos_enc; + /*! length (in octets) of qos_enc */ + size_t qos_enc_len; +}; + +/*! parsed/decoded GSUP protocol message */ +struct osmo_gsup_message { + enum osmo_gsup_message_type message_type; + char imsi[GSM23003_IMSI_MAX_DIGITS+2]; + enum gsm48_gmm_cause cause; + enum osmo_gsup_cancel_type cancel_type; + int pdp_info_compl; + int freeze_ptmsi; + struct osmo_auth_vector auth_vectors[OSMO_GSUP_MAX_NUM_AUTH_INFO]; + size_t num_auth_vectors; + struct osmo_gsup_pdp_info pdp_infos[OSMO_GSUP_MAX_NUM_PDP_INFO]; + size_t num_pdp_infos; + const uint8_t *msisdn_enc; + size_t msisdn_enc_len; + const uint8_t *hlr_enc; + size_t hlr_enc_len; + const uint8_t *auts; +}; + +int osmo_gsup_decode(const uint8_t *data, size_t data_len, + struct osmo_gsup_message *gsup_msg); +void osmo_gsup_encode(struct msgb *msg, const struct osmo_gsup_message *gsup_msg); diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index 4326b357..781f153a 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -26,7 +26,8 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ auth_core.c auth_comp128v1.c auth_comp128v23.c \ auth_milenage.c milenage/aes-encblock.c \ milenage/aes-internal.c milenage/aes-internal-enc.c \ - milenage/milenage.c gan.c ipa.c gsm0341.c apn.c + milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \ + gsup.c libgsmint_la_LDFLAGS = -no-undefined libgsmint_la_LIBADD = ../libosmocore.la diff --git a/src/gsm/gsup.c b/src/gsm/gsup.c new file mode 100644 index 00000000..7a439d48 --- /dev/null +++ b/src/gsm/gsup.c @@ -0,0 +1,472 @@ +/* Osmocom Subscriber Update Protocol message encoder/decoder */ + +/* + * (C) 2014 by Sysmocom s.f.m.c. GmbH + * (C) 2015 by Holger Hans Peter Freyther + * (C) 2016 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved + * + * Author: Jacob Erlbeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/gsm48_ie.h> +#include <osmocom/gsm/gsup.h> + +#include <stdint.h> + +static int decode_pdp_info(uint8_t *data, size_t data_len, + struct osmo_gsup_pdp_info *pdp_info) +{ + int rc; + uint8_t tag; + uint8_t *value; + size_t value_len; + + /* specific parts */ + while (data_len > 0) { + enum osmo_gsup_iei iei; + + rc = osmo_shift_tlv(&data, &data_len, &tag, &value, &value_len); + if (rc < 0) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + + iei = tag; + + switch (iei) { + case OSMO_GSUP_PDP_CONTEXT_ID_IE: + pdp_info->context_id = osmo_decode_big_endian(value, value_len); + break; + + case OSMO_GSUP_PDP_TYPE_IE: + pdp_info->pdp_type = + osmo_decode_big_endian(value, value_len) & 0x0fff; + break; + + case OSMO_GSUP_ACCESS_POINT_NAME_IE: + pdp_info->apn_enc = value; + pdp_info->apn_enc_len = value_len; + break; + + case OSMO_GSUP_PDP_QOS_IE: + pdp_info->qos_enc = value; + pdp_info->qos_enc_len = value_len; + break; + + default: + LOGP(DLGSUP, LOGL_ERROR, + "GSUP IE type %d not expected in PDP info\n", iei); + continue; + } + } + + return 0; +} + +static int decode_auth_info(uint8_t *data, size_t data_len, + struct osmo_auth_vector *auth_vector) +{ + int rc; + uint8_t tag; + uint8_t *value; + size_t value_len; + enum osmo_gsup_iei iei; + uint8_t presence = 0; + + /* specific parts */ + while (data_len > 0) { + rc = osmo_shift_tlv(&data, &data_len, &tag, &value, &value_len); + if (rc < 0) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + + iei = tag; + + switch (iei) { + case OSMO_GSUP_RAND_IE: + if (value_len != sizeof(auth_vector->rand)) + goto parse_error; + + memcpy(auth_vector->rand, value, value_len); + presence |= (1 << 0); + break; + + case OSMO_GSUP_SRES_IE: + if (value_len != sizeof(auth_vector->sres)) + goto parse_error; + + memcpy(auth_vector->sres, value, value_len); + presence |= (1 << 1); + break; + + case OSMO_GSUP_KC_IE: + if (value_len != sizeof(auth_vector->kc)) + goto parse_error; + + memcpy(auth_vector->kc, value, value_len); + presence |= (1 << 2); + break; + + case OSMO_GSUP_IK_IE: + if (value_len != sizeof(auth_vector->ik)) + goto parse_error; + memcpy(auth_vector->ik, value, value_len); + presence |= (1 << 4); + break; + + case OSMO_GSUP_CK_IE: + if (value_len != sizeof(auth_vector->ck)) + goto parse_error; + memcpy(auth_vector->ck, value, value_len); + presence |= (1 << 5); + break; + + case OSMO_GSUP_AUTN_IE: + if (value_len != sizeof(auth_vector->autn)) + goto parse_error; + memcpy(auth_vector->autn, value, value_len); + presence |= (1 << 6); + break; + case OSMO_GSUP_RES_IE: + if (value_len > sizeof(auth_vector->res)) + goto parse_error; + memcpy(auth_vector->res, value, value_len); + auth_vector->res_len = value_len; + presence |= (1 << 7); + break; + + default: + LOGP(DLGSUP, LOGL_ERROR, + "GSUP IE type %d not expected in PDP info\n", iei); + continue; + } + } + + if (presence & 0x07) + auth_vector->auth_types |= OSMO_AUTH_TYPE_GSM; + if (presence & 0xf0) + auth_vector->auth_types |= OSMO_AUTH_TYPE_UMTS; + + return 0; + +parse_error: + LOGP(DLGSUP, LOGL_ERROR, + "GSUP IE type %d, length %zu invalid in PDP info\n", iei, value_len); + + return -1; +} + +/*! Decode (parse) a GSUP message + * \param[in] const_data input data to be parsed + * \param[in] data_len length of input (\a const_data) + * \param[out] gsup_msg callee-allocated output data structure + * \returns 0 on success; negative otherwise + */ +int osmo_gsup_decode(const uint8_t *const_data, size_t data_len, + struct osmo_gsup_message *gsup_msg) +{ + int rc; + uint8_t tag; + /* the shift/match functions expect non-const pointers, but we'll + * either copy the data or cast pointers back to const before returning + * them + */ + uint8_t *data = (uint8_t *)const_data; + uint8_t *value; + size_t value_len; + static const struct osmo_gsup_pdp_info empty_pdp_info = {0}; + static const struct osmo_auth_vector empty_auth_info = {0}; + static const struct osmo_gsup_message empty_gsup_message = {0}; + + *gsup_msg = empty_gsup_message; + + /* generic part */ + rc = osmo_shift_v_fixed(&data, &data_len, 1, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + + gsup_msg->message_type = osmo_decode_big_endian(value, 1); + + rc = osmo_match_shift_tlv(&data, &data_len, OSMO_GSUP_IMSI_IE, + &value, &value_len); + + if (rc <= 0) + return -GMM_CAUSE_INV_MAND_INFO; + + if (value_len * 2 + 1 > sizeof(gsup_msg->imsi)) + return -GMM_CAUSE_INV_MAND_INFO; + + /* Note that gsm48_decode_bcd_number expects the number of encoded IMSI + * octets in the first octet. By coincidence (the TLV encoding) the byte + * before the value part already contains this length so we can use it + * here. + */ + OSMO_ASSERT(value[-1] == value_len); + gsm48_decode_bcd_number(gsup_msg->imsi, sizeof(gsup_msg->imsi), + value - 1, 0); + + /* specific parts */ + while (data_len > 0) { + enum osmo_gsup_iei iei; + struct osmo_gsup_pdp_info pdp_info; + struct osmo_auth_vector auth_info; + + rc = osmo_shift_tlv(&data, &data_len, &tag, &value, &value_len); + if (rc < 0) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + + iei = tag; + + switch (iei) { + case OSMO_GSUP_IMSI_IE: + case OSMO_GSUP_PDP_TYPE_IE: + case OSMO_GSUP_ACCESS_POINT_NAME_IE: + case OSMO_GSUP_RAND_IE: + case OSMO_GSUP_SRES_IE: + case OSMO_GSUP_KC_IE: + LOGP(DLGSUP, LOGL_NOTICE, + "GSUP IE type %d not expected (ignored)\n", iei); + continue; + + case OSMO_GSUP_CAUSE_IE: + gsup_msg->cause = osmo_decode_big_endian(value, value_len); + break; + + case OSMO_GSUP_CANCEL_TYPE_IE: + gsup_msg->cancel_type = + osmo_decode_big_endian(value, value_len) + 1; + break; + + case OSMO_GSUP_PDP_INFO_COMPL_IE: + gsup_msg->pdp_info_compl = 1; + break; + + case OSMO_GSUP_FREEZE_PTMSI_IE: + gsup_msg->freeze_ptmsi = 1; + break; + + case OSMO_GSUP_PDP_CONTEXT_ID_IE: + /* When these IE appear in the top-level part of the + * message, they are used by Delete Subscr Info to delete + * single entries. We don't have an extra list for + * these but use the PDP info list instead */ + + /* fall through */ + + case OSMO_GSUP_PDP_INFO_IE: + if (gsup_msg->num_pdp_infos >= OSMO_GSUP_MAX_NUM_PDP_INFO) { + LOGP(DLGSUP, LOGL_ERROR, + "GSUP IE type %d (PDP_INFO) max exceeded\n", + iei); + return -GMM_CAUSE_COND_IE_ERR; + } + + pdp_info = empty_pdp_info; + + if (iei == OSMO_GSUP_PDP_INFO_IE) { + rc = decode_pdp_info(value, value_len, &pdp_info); + if (rc < 0) + return rc; + pdp_info.have_info = 1; + } else { + pdp_info.context_id = + osmo_decode_big_endian(value, value_len); + } + + gsup_msg->pdp_infos[gsup_msg->num_pdp_infos++] = + pdp_info; + break; + + case OSMO_GSUP_AUTH_TUPLE_IE: + if (gsup_msg->num_auth_vectors >= OSMO_GSUP_MAX_NUM_AUTH_INFO) { + LOGP(DLGSUP, LOGL_ERROR, + "GSUP IE type %d (AUTH_INFO) max exceeded\n", + iei); + return -GMM_CAUSE_INV_MAND_INFO; + } + + auth_info = empty_auth_info; + + rc = decode_auth_info(value, value_len, &auth_info); + if (rc < 0) + return rc; + + gsup_msg->auth_vectors[gsup_msg->num_auth_vectors++] = + auth_info; + break; + + case OSMO_GSUP_AUTS_IE: + if (value_len != 16) { + LOGP(DLGSUP, LOGL_ERROR, + "AUTS length != 16 received\n"); + return -GMM_CAUSE_COND_IE_ERR; + } + gsup_msg->auts = value; + break; + + case OSMO_GSUP_MSISDN_IE: + gsup_msg->msisdn_enc = value; + gsup_msg->msisdn_enc_len = value_len; + break; + + case OSMO_GSUP_HLR_NUMBER_IE: + gsup_msg->hlr_enc = value; + gsup_msg->hlr_enc_len = value_len; + break; + + default: + LOGP(DLGSUP, LOGL_NOTICE, + "GSUP IE type %d unknown\n", iei); + continue; + } + } + + return 0; +} + +static void encode_pdp_info(struct msgb *msg, enum osmo_gsup_iei iei, + const struct osmo_gsup_pdp_info *pdp_info) +{ + uint8_t *len_field; + size_t old_len; + uint8_t u8; + + len_field = msgb_tlv_put(msg, iei, 0, NULL) - 1; + old_len = msgb_length(msg); + + u8 = pdp_info->context_id; + msgb_tlv_put(msg, OSMO_GSUP_PDP_CONTEXT_ID_IE, sizeof(u8), &u8); + + if (pdp_info->pdp_type) { + msgb_tlv_put(msg, OSMO_GSUP_PDP_TYPE_IE, + OSMO_GSUP_PDP_TYPE_SIZE, + osmo_encode_big_endian(pdp_info->pdp_type | 0xf000, + OSMO_GSUP_PDP_TYPE_SIZE)); + } + + if (pdp_info->apn_enc) { + msgb_tlv_put(msg, OSMO_GSUP_ACCESS_POINT_NAME_IE, + pdp_info->apn_enc_len, pdp_info->apn_enc); + } + + if (pdp_info->qos_enc) { + msgb_tlv_put(msg, OSMO_GSUP_PDP_QOS_IE, + pdp_info->qos_enc_len, pdp_info->qos_enc); + } + + /* Update length field */ + *len_field = msgb_length(msg) - old_len; +} + +static void encode_auth_info(struct msgb *msg, enum osmo_gsup_iei iei, + const struct osmo_auth_vector *auth_vector) +{ + uint8_t *len_field; + size_t old_len; + + len_field = msgb_tlv_put(msg, iei, 0, NULL) - 1; + old_len = msgb_length(msg); + + msgb_tlv_put(msg, OSMO_GSUP_RAND_IE, + sizeof(auth_vector->rand), auth_vector->rand); + + msgb_tlv_put(msg, OSMO_GSUP_SRES_IE, + sizeof(auth_vector->sres), auth_vector->sres); + + msgb_tlv_put(msg, OSMO_GSUP_KC_IE, + sizeof(auth_vector->kc), auth_vector->kc); + + /* Update length field */ + *len_field = msgb_length(msg) - old_len; +} + +/*! Encode a GSUP message + * \param[out] msg message buffer to which encoded message is written + * \param[in] gsup_msg \ref osmo_gsup_message data to be encoded + */ +void osmo_gsup_encode(struct msgb *msg, const struct osmo_gsup_message *gsup_msg) +{ + uint8_t u8; + int idx; + uint8_t bcd_buf[GSM48_MI_SIZE] = {0}; + size_t bcd_len; + + /* generic part */ + OSMO_ASSERT(gsup_msg->message_type); + msgb_v_put(msg, gsup_msg->message_type); + + bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), 0, + gsup_msg->imsi); + + OSMO_ASSERT(bcd_len > 1); + + /* Note that gsm48_encode_bcd_number puts the length into the first + * octet. Since msgb_tlv_put will add this length byte, we'll have to + * skip it */ + msgb_tlv_put(msg, OSMO_GSUP_IMSI_IE, bcd_len - 1, &bcd_buf[1]); + + /* specific parts */ + if (gsup_msg->msisdn_enc) + msgb_tlv_put(msg, OSMO_GSUP_MSISDN_IE, + gsup_msg->msisdn_enc_len, gsup_msg->msisdn_enc); + if (gsup_msg->hlr_enc) + msgb_tlv_put(msg, OSMO_GSUP_HLR_NUMBER_IE, + gsup_msg->hlr_enc_len, gsup_msg->hlr_enc); + + if ((u8 = gsup_msg->cause)) + msgb_tlv_put(msg, OSMO_GSUP_CAUSE_IE, sizeof(u8), &u8); + + if ((u8 = gsup_msg->cancel_type)) { + u8 -= 1; + msgb_tlv_put(msg, OSMO_GSUP_CANCEL_TYPE_IE, sizeof(u8), &u8); + } + + if (gsup_msg->pdp_info_compl) + msgb_tlv_put(msg, OSMO_GSUP_PDP_INFO_COMPL_IE, 0, &u8); + + if (gsup_msg->freeze_ptmsi) + msgb_tlv_put(msg, OSMO_GSUP_FREEZE_PTMSI_IE, 0, &u8); + + for (idx = 0; idx < gsup_msg->num_pdp_infos; idx++) { + const struct osmo_gsup_pdp_info *pdp_info; + + pdp_info = &gsup_msg->pdp_infos[idx]; + + if (pdp_info->context_id == 0) + continue; + + if (pdp_info->have_info) { + encode_pdp_info(msg, OSMO_GSUP_PDP_INFO_IE, pdp_info); + } else { + u8 = pdp_info->context_id; + msgb_tlv_put(msg, OSMO_GSUP_PDP_CONTEXT_ID_IE, + sizeof(u8), &u8); + } + } + + for (idx = 0; idx < gsup_msg->num_auth_vectors; idx++) { + const struct osmo_auth_vector *auth_vector; + + auth_vector = &gsup_msg->auth_vectors[idx]; + + encode_auth_info(msg, OSMO_GSUP_AUTH_TUPLE_IE, auth_vector); + } + + if (gsup_msg->auts) + msgb_tlv_put(msg, OSMO_GSUP_AUTS_IE, 16, gsup_msg->auts); +} diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index 8bd0dbfe..e0d9dcbd 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -309,5 +309,8 @@ osmo_apn_qualify_from_imsi; osmo_apn_to_str; osmo_apn_from_str; +osmo_gsup_encode; +osmo_gsup_decode; + local: *; }; diff --git a/tests/Makefile.am b/tests/Makefile.am index fa814c72..6c9929b9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -13,7 +13,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ vty/vty_test comp128/comp128_test utils/utils_test \ smscb/gsm0341_test stats/stats_test \ bitvec/bitvec_test msgb/msgb_test bits/bitcomp_test \ - sim/sim_test tlv/tlv_test + sim/sim_test tlv/tlv_test gsup/gsup_test if ENABLE_MSGFILE check_PROGRAMS += msgfile/msgfile_test @@ -112,6 +112,9 @@ sim_sim_test_LDADD = $(top_builddir)/src/sim/libosmosim.la $(top_builddir)/src/l tlv_tlv_test_SOURCES = tlv/tlv_test.c tlv_tlv_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(top_builddir)/src/libosmocore.la +gsup_gsup_test_SOURCES = gsup/gsup_test.c +gsup_gsup_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(top_builddir)/src/libosmocore.la + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -146,7 +149,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ vty/vty_test.ok comp128/comp128_test.ok \ utils/utils_test.ok stats/stats_test.ok \ bitvec/bitvec_test.ok msgb/msgb_test.ok bits/bitcomp_test.ok \ - sim/sim_test.ok tlv/tlv_test.ok + sim/sim_test.ok tlv/tlv_test.ok gsup/gsup_test.ok DISTCLEANFILES = atconfig diff --git a/tests/gsup/gsup_test.c b/tests/gsup/gsup_test.c new file mode 100644 index 00000000..2b0a2938 --- /dev/null +++ b/tests/gsup/gsup_test.c @@ -0,0 +1,264 @@ +#include <string.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/application.h> +#include <osmocom/gsm/gsup.h> + +#define VERBOSE_FPRINTF(...) + +/* Tests for osmo_gsup_messages.c */ + +#define TEST_IMSI_IE 0x01, 0x08, 0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0xf5 +#define TEST_IMSI_STR "123456789012345" + +static void test_gsup_messages_dec_enc(void) +{ + int test_idx; + int rc; + uint8_t buf[1024]; + + static const uint8_t send_auth_info_req[] = { + 0x08, + TEST_IMSI_IE + }; + + static const uint8_t send_auth_info_err[] = { + 0x09, + TEST_IMSI_IE, + 0x02, 0x01, 0x07 /* GPRS no allowed */ + }; + + static const uint8_t send_auth_info_res[] = { + 0x0a, + TEST_IMSI_IE, + 0x03, 0x22, /* Auth tuple */ + 0x20, 0x10, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x21, 0x04, + 0x21, 0x22, 0x23, 0x24, + 0x22, 0x08, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x03, 0x22, /* Auth tuple */ + 0x20, 0x10, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x21, 0x04, + 0xa1, 0xa2, 0xa3, 0xa4, + 0x22, 0x08, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, + }; + + static const uint8_t update_location_req[] = { + 0x04, + TEST_IMSI_IE, + }; + + static const uint8_t update_location_err[] = { + 0x05, + TEST_IMSI_IE, + 0x02, 0x01, 0x07 /* GPRS no allowed */ + }; + + static const uint8_t update_location_res[] = { + 0x06, + TEST_IMSI_IE, + 0x08, 0x07, /* MSISDN of the subscriber */ + 0x91, 0x94, 0x61, 0x46, 0x32, 0x24, 0x43, + 0x09, 0x07, /* HLR-Number of the subscriber */ + 0x91, 0x83, 0x52, 0x38, 0x48, 0x83, 0x93, + 0x04, 0x00, /* PDP info complete */ + 0x05, 0x15, + 0x10, 0x01, 0x01, + 0x11, 0x02, 0xf1, 0x21, /* IPv4 */ + 0x12, 0x09, 0x04, 't', 'e', 's', 't', 0x03, 'a', 'p', 'n', + 0x13, 0x01, 0x02, + 0x05, 0x11, + 0x10, 0x01, 0x02, + 0x11, 0x02, 0xf1, 0x21, /* IPv4 */ + 0x12, 0x08, 0x03, 'f', 'o', 'o', 0x03, 'a', 'p', 'n', + }; + + static const uint8_t location_cancellation_req[] = { + 0x1c, + TEST_IMSI_IE, + 0x06, 0x01, 0x00, + }; + + static const uint8_t location_cancellation_err[] = { + 0x1d, + TEST_IMSI_IE, + 0x02, 0x01, 0x03 /* Illegal MS */ + }; + + static const uint8_t location_cancellation_res[] = { + 0x1e, + TEST_IMSI_IE, + }; + + static const uint8_t purge_ms_req[] = { + 0x0c, + TEST_IMSI_IE, + }; + + static const uint8_t purge_ms_err[] = { + 0x0d, + TEST_IMSI_IE, + 0x02, 0x01, 0x03, /* Illegal MS */ + }; + + static const uint8_t purge_ms_res[] = { + 0x0e, + TEST_IMSI_IE, + 0x07, 0x00, + }; + + static const struct test { + char *name; + const uint8_t *data; + size_t data_len; + } test_messages[] = { + {"Send Authentication Info Request", + send_auth_info_req, sizeof(send_auth_info_req)}, + {"Send Authentication Info Error", + send_auth_info_err, sizeof(send_auth_info_err)}, + {"Send Authentication Info Result", + send_auth_info_res, sizeof(send_auth_info_res)}, + {"Update Location Request", + update_location_req, sizeof(update_location_req)}, + {"Update Location Error", + update_location_err, sizeof(update_location_err)}, + {"Update Location Result", + update_location_res, sizeof(update_location_res)}, + {"Location Cancellation Request", + location_cancellation_req, sizeof(location_cancellation_req)}, + {"Location Cancellation Error", + location_cancellation_err, sizeof(location_cancellation_err)}, + {"Location Cancellation Result", + location_cancellation_res, sizeof(location_cancellation_res)}, + {"Purge MS Request", + purge_ms_req, sizeof(purge_ms_req)}, + {"Purge MS Error", + purge_ms_err, sizeof(purge_ms_err)}, + {"Purge MS Result", + purge_ms_res, sizeof(purge_ms_res)}, + }; + + printf("Test GSUP message decoding/encoding\n"); + + for (test_idx = 0; test_idx < ARRAY_SIZE(test_messages); test_idx++) { + const struct test *t = &test_messages[test_idx]; + struct osmo_gsup_message gm = {0}; + struct msgb *msg = msgb_alloc(4096, "gsup_test"); + + printf(" Testing %s\n", t->name); + + rc = osmo_gsup_decode(t->data, t->data_len, &gm); + OSMO_ASSERT(rc >= 0); + + osmo_gsup_encode(msg, &gm); + + fprintf(stderr, " generated message: %s\n", msgb_hexdump(msg)); + fprintf(stderr, " original message: %s\n", osmo_hexdump(t->data, t->data_len)); + fprintf(stderr, " IMSI: %s\n", gm.imsi); + OSMO_ASSERT(strcmp(gm.imsi, TEST_IMSI_STR) == 0); + OSMO_ASSERT(msgb_length(msg) == t->data_len); + OSMO_ASSERT(memcmp(msgb_data(msg), t->data, t->data_len) == 0); + + msgb_free(msg); + } + + /* simple truncation test */ + for (test_idx = 0; test_idx < ARRAY_SIZE(test_messages); test_idx++) { + int j; + const struct test *t = &test_messages[test_idx]; + int ie_end = t->data_len; + struct osmo_gsup_message gm = {0}; + int counter = 0; + int parse_err = 0; + + for (j = t->data_len - 1; j >= 0; --j) { + rc = osmo_gsup_decode(t->data, j, &gm); + counter += 1; + + VERBOSE_FPRINTF(stderr, + " partial message decoding: " + "orig_len = %d, trunc = %d, rc = %d, ie_end = %d\n", + t->data_len, j, rc, ie_end); + if (rc >= 0) { + VERBOSE_FPRINTF(stderr, + " remaing partial message: %s\n", + osmo_hexdump(t->data + j, ie_end - j)); + + OSMO_ASSERT(j <= ie_end - 2); + OSMO_ASSERT(t->data[j+0] <= OSMO_GSUP_KC_IE); + OSMO_ASSERT(t->data[j+1] <= ie_end - j - 2); + + ie_end = j; + } else { + parse_err += 1; + } + } + + fprintf(stderr, + " message %d: tested %d truncations, %d parse failures\n", + test_idx, counter, parse_err); + } + + /* message modification test (relies on ASAN or valgrind being used) */ + for (test_idx = 0; test_idx < ARRAY_SIZE(test_messages); test_idx++) { + int j; + const struct test *t = &test_messages[test_idx]; + struct osmo_gsup_message gm = {0}; + uint8_t val; + int counter = 0; + int parse_err = 0; + + OSMO_ASSERT(sizeof(buf) >= t->data_len); + + for (j = t->data_len - 1; j >= 0; --j) { + memcpy(buf, t->data, t->data_len); + val = 0; + do { + VERBOSE_FPRINTF(stderr, + "t = %d, len = %d, val = %d\n", + test_idx, j, val); + buf[j] = val; + rc = osmo_gsup_decode(buf, t->data_len, &gm); + counter += 1; + if (rc < 0) + parse_err += 1; + + val += 1; + } while (val != (uint8_t)256); + } + + fprintf(stderr, + " message %d: tested %d modifications, %d parse failures\n", + test_idx, counter, parse_err); + } +} + +const struct log_info_cat default_categories[] = { + [DLGSUP] = { + .name = "DLGSUP", + .description = "Generic Subscriber Update Protocol", + .enabled = 0, .loglevel = LOGL_DEBUG, + }, +}; + +static struct log_info info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +int main(int argc, char **argv) +{ + osmo_init_logging(&info); + + test_gsup_messages_dec_enc(); + + printf("Done.\n"); + return EXIT_SUCCESS; +} diff --git a/tests/gsup/gsup_test.ok b/tests/gsup/gsup_test.ok new file mode 100644 index 00000000..1285897c --- /dev/null +++ b/tests/gsup/gsup_test.ok @@ -0,0 +1,14 @@ +Test GSUP message decoding/encoding + Testing Send Authentication Info Request + Testing Send Authentication Info Error + Testing Send Authentication Info Result + Testing Update Location Request + Testing Update Location Error + Testing Update Location Result + Testing Location Cancellation Request + Testing Location Cancellation Error + Testing Location Cancellation Result + Testing Purge MS Request + Testing Purge MS Error + Testing Purge MS Result +Done. diff --git a/tests/testsuite.at b/tests/testsuite.at index 762b10a0..aa269afb 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -190,3 +190,9 @@ AT_KEYWORDS([tlv]) cat $abs_srcdir/tlv/tlv_test.ok > expout AT_CHECK([$abs_top_builddir/tests/tlv/tlv_test], [0], [expout], [ignore]) AT_CLEANUP + +AT_SETUP([gsup]) +AT_KEYWORDS([gsup]) +cat $abs_srcdir/gsup/gsup_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/gsup/gsup_test], [0], [expout], [ignore]) +AT_CLEANUP |