diff options
-rw-r--r-- | openbsc/include/openbsc/gsm_sup.h | 16 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_sup.c | 394 |
2 files changed, 410 insertions, 0 deletions
diff --git a/openbsc/include/openbsc/gsm_sup.h b/openbsc/include/openbsc/gsm_sup.h new file mode 100644 index 000000000..d14b036b2 --- /dev/null +++ b/openbsc/include/openbsc/gsm_sup.h @@ -0,0 +1,16 @@ +#ifndef _GSM_SUP_H +#define _GSM_SUP_H + +#include <openbsc/debug.h> +#include <openbsc/gsm_subscriber.h> + +#define LOGGSUBSCRP(level, subscr, fmt, args...) \ + LOGP(DSUP, level, "SUBSCR(%s) " fmt, \ + (subscr) ? (subscr)->imsi : "---", \ + ## args) + +int subscr_query_auth_info(struct gsm_subscriber *subscr); +int subscr_location_update(struct gsm_subscriber *subscr); +int sup_init(struct gsm_network *net); + +#endif /* _GSM_SUP_H */ diff --git a/openbsc/src/libmsc/gsm_sup.c b/openbsc/src/libmsc/gsm_sup.c new file mode 100644 index 000000000..80352d3d0 --- /dev/null +++ b/openbsc/src/libmsc/gsm_sup.c @@ -0,0 +1,394 @@ +/* GSM Subscriber Update Protocol */ + +/* (C) 2015 by Ivan Klyuchnikov <kluchnikovi@gmail.com> + * + * All Rights Reserved + * + * 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 <openbsc/gsm_sup.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/debug.h> +#include <openbsc/db.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/gsm_04_08_gprs.h> +#include <openbsc/gprs_gsup_messages.h> +#include <openbsc/gprs_gsup_client.h> + +static int subscr_tx_sup_message(struct gprs_gsup_client *sup_client, + struct gsm_subscriber *subscr, + struct gprs_gsup_message *gsup_msg) +{ + struct msgb *msg = gprs_gsup_msgb_alloc(); + + if (strlen(gsup_msg->imsi) == 0 && subscr) + strncpy(gsup_msg->imsi, subscr->imsi, sizeof(gsup_msg->imsi) - 1); + + gprs_gsup_encode(msg, gsup_msg); + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Sending SUP, will send: %s\n", msgb_hexdump(msg)); + + if (!sup_client) { + msgb_free(msg); + return -ENOTSUP; + } + + return gprs_gsup_client_send(sup_client, msg); +} + +int subscr_query_auth_info(struct gsm_subscriber *subscr) +{ + struct gprs_gsup_message gsup_msg = {0}; + + LOGGSUBSCRP(LOGL_INFO, subscr, + "subscriber auth info is not available\n"); + + gsup_msg.message_type = GPRS_GSUP_MSGT_SEND_AUTH_INFO_REQUEST; + return subscr_tx_sup_message(subscr->group->net->sup_client, subscr, &gsup_msg); +} + +int subscr_location_update(struct gsm_subscriber *subscr) +{ + struct gprs_gsup_message gsup_msg = {0}; + + LOGGSUBSCRP(LOGL_INFO, subscr, + "subscriber data is not available\n"); + + gsup_msg.message_type = GPRS_GSUP_MSGT_UPDATE_LOCATION_REQUEST; + return subscr_tx_sup_message(subscr->group->net->sup_client, subscr, &gsup_msg); +} + +static int subscr_tx_sup_error_reply(struct gprs_gsup_client *sup_client, + struct gsm_subscriber *subscr, + struct gprs_gsup_message *gsup_orig, + enum gsm48_gmm_cause cause) +{ + struct gprs_gsup_message gsup_reply = {0}; + + strncpy(gsup_reply.imsi, gsup_orig->imsi, sizeof(gsup_reply.imsi) - 1); + gsup_reply.cause = cause; + gsup_reply.message_type = + GPRS_GSUP_TO_MSGT_ERROR(gsup_orig->message_type); + + return subscr_tx_sup_message(sup_client, subscr, &gsup_reply); +} + +static int subscr_handle_sup_auth_res(struct gprs_gsup_client *sup_client, + struct gsm_subscriber *subscr, + struct gprs_gsup_message *gsup_msg) +{ + struct gsm_subscriber_connection *conn = connection_for_subscr(subscr); + struct gsm_security_operation *op; + + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Got SendAuthenticationInfoResult, num_auth_tuples = %zu\n", + gsup_msg->num_auth_tuples); + + if (gsup_msg->num_auth_tuples > 0) { + op = conn->sec_operation; + memcpy(&op->atuple, gsup_msg->auth_tuples, sizeof(struct gsm_auth_tuple)); + db_sync_lastauthtuple_for_subscr(&op->atuple, subscr); + gsm48_tx_mm_auth_req(conn, op->atuple.rand, op->atuple.key_seq); + } + + return 0; +} + +static int subscr_handle_sup_upd_loc_res(struct gsm_subscriber *subscr, + struct gprs_gsup_message *gsup_msg) +{ + uint8_t msisdn_lv[10]; + + if (gsup_msg->msisdn_enc) { + if (gsup_msg->msisdn_enc_len > sizeof(msisdn_lv) - 1) { + LOGP(DSUP, LOGL_ERROR, "MSISDN too long (%zu)\n", + gsup_msg->msisdn_enc_len); + return -1; + } else { + msisdn_lv[0] = gsup_msg->msisdn_enc_len; + memcpy(msisdn_lv+1, gsup_msg->msisdn_enc, + gsup_msg->msisdn_enc_len); + gsm48_decode_bcd_number(subscr->extension, sizeof(subscr->extension), + msisdn_lv,1); + } + } + + db_sync_subscriber(subscr); + + struct gsm_subscriber_connection *conn = connection_for_subscr(subscr); + + if (conn->loc_operation) + conn->loc_operation->waiting_for_remote_accept = 0; + + gsm0408_authorize(conn,NULL); + + return 0; +} + +static int check_cause(int cause) +{ + switch (cause) { + case GMM_CAUSE_IMSI_UNKNOWN ... GMM_CAUSE_ILLEGAL_ME: + case GMM_CAUSE_GPRS_NOTALLOWED ... GMM_CAUSE_NO_GPRS_PLMN: + return EACCES; + + case GMM_CAUSE_MSC_TEMP_NOTREACH ... GMM_CAUSE_CONGESTION: + return EHOSTUNREACH; + + case GMM_CAUSE_SEM_INCORR_MSG ... GMM_CAUSE_PROTO_ERR_UNSPEC: + default: + return EINVAL; + } +} + +static int subscr_handle_sup_upd_loc_err(struct gsm_subscriber *subscr, + struct gprs_gsup_message *gsup_msg) +{ + int cause_err; + struct gsm_subscriber_connection *conn = connection_for_subscr(subscr); + + cause_err = check_cause(gsup_msg->cause); + + LOGGSUBSCRP(LOGL_DEBUG, subscr, + "Update location has failed with cause %d, handled as: %s\n", + gsup_msg->cause, strerror(cause_err)); + + switch (cause_err) { + case EACCES: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GSM update location failed, access denied, " + "MM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + gsm0408_loc_upd_rej(conn, gsup_msg->cause); + release_loc_updating_req(conn, 0); + break; + + case EHOSTUNREACH: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GSM update location failed, MM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + // TODO: Try to find subscriber in local HLR? + gsm0408_loc_upd_rej(conn, gsup_msg->cause); + release_loc_updating_req(conn, 0); + break; + + default: + case EINVAL: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "SUP protocol remote error, MM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + break; + } + + return -gsup_msg->cause; +} + +static int subscr_handle_sup_auth_err(struct gsm_subscriber *subscr, + struct gprs_gsup_message *gsup_msg) +{ + int cause_err; + struct gsm_subscriber_connection *conn = connection_for_subscr(subscr); + gsm_cbfn *cb = conn->sec_operation->cb; + + cause_err = check_cause(gsup_msg->cause); + + LOGGSUBSCRP(LOGL_DEBUG, subscr, + "Send authentication info has failed with cause %d, " + "handled as: %s\n", + gsup_msg->cause, strerror(cause_err)); + + switch (cause_err) { + case EACCES: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GSM send auth info req failed, access denied, " + "MM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + if (cb) + cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED, + NULL, conn, conn->sec_operation->cb_data); + release_security_operation(conn); + break; + + case EHOSTUNREACH: + LOGGSUBSCRP(LOGL_NOTICE, subscr, + "GSM send auth info req failed, MM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + // TODO: Try to resend auth request? + if (cb) + cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED, + NULL, conn, conn->sec_operation->cb_data); + release_security_operation(conn); + break; + + default: + case EINVAL: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "SUP protocol remote error, MM cause = '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + break; + } + + return -gsup_msg->cause; +} + +static int subscr_handle_unknown_imsi(struct gprs_gsup_client *sup_client, + struct gprs_gsup_message *gsup_msg) +{ + if (GPRS_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) { + subscr_tx_sup_error_reply(sup_client, NULL, gsup_msg, + GMM_CAUSE_IMSI_UNKNOWN); + LOGP(DSUP, LOGL_NOTICE, + "Unknown IMSI %s, discarding SUP request " + "of type 0x%02x\n", + gsup_msg->imsi, gsup_msg->message_type); + } else if (GPRS_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { + LOGP(DSUP, LOGL_NOTICE, + "Unknown IMSI %s, discarding SUP error " + "of type 0x%02x, cause '%s' (%d)\n", + gsup_msg->imsi, gsup_msg->message_type, + get_value_string(gsm48_gmm_cause_names, gsup_msg->cause), + gsup_msg->cause); + } else { + LOGP(DSUP, LOGL_NOTICE, + "Unknown IMSI %s, discarding SUP response " + "of type 0x%02x\n", + gsup_msg->imsi, gsup_msg->message_type); + } + + return -GMM_CAUSE_IMSI_UNKNOWN; +} + +int subscr_rx_sup_message(struct gprs_gsup_client *sup_client, struct msgb *msg) +{ + uint8_t *data = msgb_l2(msg); + size_t data_len = msgb_l2len(msg); + int rc = 0; + + struct gprs_gsup_message gsup_msg = {0}; + struct gsm_subscriber *subscr; + + rc = gprs_gsup_decode(data, data_len, &gsup_msg); + if (rc < 0) { + LOGP(DSUP, LOGL_ERROR, + "decoding SUP message fails with error '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, -rc), -rc); + return rc; + } + + if (!gsup_msg.imsi[0]) { + LOGP(DSUP, LOGL_ERROR, "Missing IMSI in SUP message\n"); + + if (GPRS_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type)) + subscr_tx_sup_error_reply(sup_client, NULL, &gsup_msg, + GMM_CAUSE_INV_MAND_INFO); + return -GMM_CAUSE_INV_MAND_INFO; + } + + if (!gsup_msg.cause && GPRS_GSUP_IS_MSGT_ERROR(gsup_msg.message_type)) + gsup_msg.cause = GMM_CAUSE_NET_FAIL; + + subscr = subscr_get_by_imsi(NULL, gsup_msg.imsi); + + if (!subscr) { + return subscr_handle_unknown_imsi(sup_client, &gsup_msg); + } + + LOGGSUBSCRP(LOGL_INFO, subscr, + "Received SUP message of type 0x%02x\n", gsup_msg.message_type); + + switch (gsup_msg.message_type) { + + case GPRS_GSUP_MSGT_SEND_AUTH_INFO_RESULT: + rc = subscr_handle_sup_auth_res(sup_client, subscr, &gsup_msg); + break; + + case GPRS_GSUP_MSGT_SEND_AUTH_INFO_ERROR: + rc = subscr_handle_sup_auth_err(subscr, &gsup_msg); + break; + + case GPRS_GSUP_MSGT_UPDATE_LOCATION_RESULT: + rc = subscr_handle_sup_upd_loc_res(subscr, &gsup_msg); + break; + + case GPRS_GSUP_MSGT_UPDATE_LOCATION_ERROR: + rc = subscr_handle_sup_upd_loc_err(subscr, &gsup_msg); + break; + + case GPRS_GSUP_MSGT_LOCATION_CANCEL_REQUEST: + case GPRS_GSUP_MSGT_PURGE_MS_ERROR: + case GPRS_GSUP_MSGT_PURGE_MS_RESULT: + case GPRS_GSUP_MSGT_INSERT_DATA_REQUEST: + case GPRS_GSUP_MSGT_DELETE_DATA_REQUEST: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "Rx SUP message type %d not yet implemented\n", + gsup_msg.message_type); + subscr_tx_sup_error_reply(sup_client, subscr, &gsup_msg, + GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; + break; + + default: + LOGGSUBSCRP(LOGL_ERROR, subscr, + "Rx SUP message type %d not valid at SGSN\n", + gsup_msg.message_type); + if (GPRS_GSUP_IS_MSGT_REQUEST(gsup_msg.message_type)) + subscr_tx_sup_error_reply(sup_client, subscr, &gsup_msg, + GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; + break; + }; + + subscr_put(subscr); + + return rc; +} + +static int sup_read_cb(struct gprs_gsup_client *sup_client, struct msgb *msg) +{ + int rc; + + rc = subscr_rx_sup_message(sup_client, msg); + msgb_free(msg); + if (rc < 0) + return -1; + + return rc; +} + +int sup_init(struct gsm_network *net) +{ + const char *addr_str; + + addr_str = "127.0.0.1"; + + net->sup_client = gprs_gsup_client_create( + addr_str, 8183, + &sup_read_cb); + + if (!net->sup_client) + return -1; + + return 1; +} |