diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libmsc/a_iface.c | 10 | ||||
-rw-r--r-- | src/libmsc/gsm_04_08.c | 134 | ||||
-rw-r--r-- | src/libmsc/osmo_msc.c | 4 | ||||
-rw-r--r-- | src/libmsc/subscr_conn.c | 74 |
4 files changed, 175 insertions, 47 deletions
diff --git a/src/libmsc/a_iface.c b/src/libmsc/a_iface.c index 75fa438e6..bd9b89035 100644 --- a/src/libmsc/a_iface.c +++ b/src/libmsc/a_iface.c @@ -422,6 +422,16 @@ int a_iface_tx_clear_cmd(struct gsm_subscriber_connection *conn) return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg); } +int a_iface_tx_classmark_request(const struct gsm_subscriber_connection *conn) +{ + struct msgb *msg; + + LOGPCONN(conn, LOGL_INFO, "Tx BSSMAP CLASSMARK REQUEST to BSC\n"); + + msg = gsm0808_create_classmark_request(); + return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg); +} + /* Callback function: Close all open connections */ static void a_reset_cb(const void *priv) { diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c index b942a0391..564e90bff 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -119,13 +119,16 @@ static bool classmark_is_r99(struct gsm_classmark *cm) return classmark2_is_r99(cm->classmark2, cm->classmark2_len); } -/* Determine if the given CLASSMARK (1/2/3) value permits a given A5/n cipher */ -static bool classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5) +/* Determine if the given CLASSMARK (1/2/3) value permits a given A5/n cipher. + * Return 1 when the given A5/n is permitted, 0 when not, and negative if the respective MS CLASSMARK is + * not known, where the negative number indicates the classmark type: -2 means Classmark 2 is not + * available. */ +static int classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5) { switch (a5) { case 0: /* all phones must implement A5/0, see 3GPP TS 43.020 4.9 */ - return true; + return 1; case 1: /* 3GPP TS 43.020 4.9 requires A5/1 to be suppored by all phones and actually states: * "The network shall not provide service to an MS which indicates that it does not @@ -134,25 +137,24 @@ static bool classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5) /* See 3GPP TS 24.008 10.5.1.7 */ if (!cm->classmark1_set) { DEBUGP(DMSC, "CLASSMARK 1 unknown, assuming MS supports A5/1\n"); - return true; + return -1; } else { if (cm->classmark1.a5_1) - return false; /* Inverted logic for this bit! */ + return 0; /* Inverted logic for this bit! */ else - return true; + return 1; } break; case 2: case 3: /* See 3GPP TS 24.008 10.5.1.6 */ if (cm->classmark2_len < 3) { - DEBUGP(DMSC, "CLASSMARK 2 unknown, assuming MS doesn't support A5/%u\n", a5); - return false; + return -2; } else { if (cm->classmark2[2] & (1 << (a5-2))) - return true; + return 1; else - return false; + return 0; } break; case 4: @@ -161,13 +163,12 @@ static bool classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5) case 7: /* See 3GPP TS 24.008 10.5.1.7 */ if (cm->classmark3_len < 1) { - DEBUGP(DMSC, "CLASSMARK 3 unknown, assuming MS doesn't support A5/%u\n", a5); - return false; + return -3; } else { if (cm->classmark3[0] & (1 << (a5-4))) - return true; + return 1; else - return false; + return 0; } break; default: @@ -1592,6 +1593,78 @@ static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum gsm48_reject_value ca osmo_static_assert(sizeof(((struct gsm0808_encrypt_info*)0)->key) >= sizeof(((struct osmo_auth_vector*)0)->kc), gsm0808_encrypt_info_key_fits_osmo_auth_vec_kc); +int msc_geran_set_cipher_mode(struct gsm_subscriber_connection *conn, bool umts_aka, bool retrieve_imeisv) +{ + struct gsm_network *net = conn->network; + struct gsm0808_encrypt_info ei; + int i, j = 0; + int request_classmark = 0; + int request_classmark_for_a5_n = 0; + struct gsm_auth_tuple *tuple = conn->vsub->last_tuple; + + if (!conn || !conn->vsub || !conn->vsub->last_tuple) { + /* This should really never happen, because we checked this in msc_vlr_set_ciph_mode() + * already. */ + LOGP(DMM, LOGL_ERROR, "Internal error: missing state during Ciphering Mode Command\n"); + return -EINVAL; + } + + for (i = 0; i < 8; i++) { + int supported; + + /* A5/n permitted by osmo-msc.cfg? */ + if (!(net->a5_encryption_mask & (1 << i))) + continue; + + /* A5/n supported by MS? */ + supported = classmark_supports_a5(&conn->vsub->classmark, i); + if (supported == 1) { + ei.perm_algo[j++] = vlr_ciph_to_gsm0808_alg_id(i); + /* A higher A5/n is supported, so no need to request a Classmark + * for support of a lesser A5/n. */ + request_classmark = 0; + } else if (supported < 0) { + request_classmark = -supported; + request_classmark_for_a5_n = i; + } + } + ei.perm_algo_len = j; + + if (request_classmark) { + /* The highest A5/n as from osmo-msc.cfg might be available, but we are + * still missing the Classmark information for that from the MS. First + * ask for that. */ + LOGP(DMM, LOGL_DEBUG, "%s: to determine whether A5/%d is supported," + " first ask for a Classmark Update to obtain Classmark %d\n", + vlr_subscr_name(conn->vsub), request_classmark_for_a5_n, + request_classmark); + + return msc_classmark_request_then_cipher_mode_cmd(conn, umts_aka, retrieve_imeisv); + } + + if (ei.perm_algo_len == 0) { + LOGP(DMM, LOGL_ERROR, "%s: cannot start ciphering, no intersection " + "between MSC-configured and MS-supported A5 algorithms\n", + vlr_subscr_name(conn->vsub)); + return -ENOTSUP; + } + + DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n", vlr_subscr_name(conn->vsub)); + + tuple = conn->vsub->last_tuple; + + /* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth + * tokens. tuple->vec.kc was calculated from the GSM algorithm and is not + * necessarily a match for the UMTS AKA tokens. */ + if (umts_aka) + osmo_auth_c3(ei.key, tuple->vec.ck, tuple->vec.ik); + else + memcpy(ei.key, tuple->vec.kc, sizeof(tuple->vec.kc)); + ei.key_len = sizeof(tuple->vec.kc); + + return a_iface_tx_cipher_mode(conn, &ei, retrieve_imeisv); +} + /* VLR asks us to start using ciphering. * (Keep non-static to allow regression testing on this function.) */ int msc_vlr_set_ciph_mode(void *msc_conn_ref, @@ -1620,38 +1693,7 @@ int msc_vlr_set_ciph_mode(void *msc_conn_ref, switch (conn->via_ran) { case RAN_GERAN_A: - DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n", - vlr_subscr_name(conn->vsub)); - { - struct gsm_network *net = conn->network; - struct gsm0808_encrypt_info ei; - int i, j = 0; - - for (i = 0; i < 8; i++) { - if (net->a5_encryption_mask & (1 << i) && - classmark_supports_a5(&conn->vsub->classmark, i)) - ei.perm_algo[j++] = vlr_ciph_to_gsm0808_alg_id(i); - } - ei.perm_algo_len = j; - - if (ei.perm_algo_len == 0) { - LOGP(DMM, LOGL_ERROR, "%s: cannot start ciphering, no intersection " - "between MSC-configured and MS-supported A5 algorithms\n", - vlr_subscr_name(conn->vsub)); - return -ENOTSUP; - } - - /* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth - * tokens. tuple->vec.kc was calculated from the GSM algorithm and is not - * necessarily a match for the UMTS AKA tokens. */ - if (umts_aka) - osmo_auth_c3(ei.key, tuple->vec.ck, tuple->vec.ik); - else - memcpy(ei.key, tuple->vec.kc, sizeof(tuple->vec.kc)); - ei.key_len = sizeof(tuple->vec.kc); - - return a_iface_tx_cipher_mode(conn, &ei, retrieve_imeisv); - } + return msc_geran_set_cipher_mode(conn, umts_aka, retrieve_imeisv); case RAN_UTRAN_IU: #ifdef BUILD_IU diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c index 1966043fd..8efa16ced 100644 --- a/src/libmsc/osmo_msc.c +++ b/src/libmsc/osmo_msc.c @@ -167,6 +167,10 @@ void msc_classmark_chg(struct gsm_subscriber_connection *conn, cm->classmark3_len = cm3_len; memcpy(cm->classmark3, cm3, cm3_len); } + + /* bump subscr conn FSM in case it is waiting for a Classmark Update */ + if (conn->fi->state == SUBSCR_CONN_S_WAIT_CLASSMARK_UPDATE) + osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_CLASSMARK_UPDATE, NULL); } /* Receive a CIPHERING MODE COMPLETE from BSC */ diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c index 2040e369b..e6fa7e11f 100644 --- a/src/libmsc/subscr_conn.c +++ b/src/libmsc/subscr_conn.c @@ -45,6 +45,7 @@ static const struct value_string subscr_conn_fsm_event_names[] = { OSMO_VALUE_STRING(SUBSCR_CONN_E_INVALID), OSMO_VALUE_STRING(SUBSCR_CONN_E_COMPLETE_LAYER_3), + OSMO_VALUE_STRING(SUBSCR_CONN_E_CLASSMARK_UPDATE), OSMO_VALUE_STRING(SUBSCR_CONN_E_ACCEPTED), OSMO_VALUE_STRING(SUBSCR_CONN_E_COMMUNICATING), OSMO_VALUE_STRING(SUBSCR_CONN_E_RELEASE_WHEN_UNUSED), @@ -163,6 +164,66 @@ void subscr_conn_fsm_auth_ciph(struct osmo_fsm_inst *fi, uint32_t event, void *d } } +int msc_classmark_request_then_cipher_mode_cmd(struct gsm_subscriber_connection *conn, bool umts_aka, + bool retrieve_imeisv) +{ + int rc; + conn->geran_set_cipher_mode.umts_aka = umts_aka; + conn->geran_set_cipher_mode.retrieve_imeisv = retrieve_imeisv; + + rc = a_iface_tx_classmark_request(conn); + if (rc) { + LOGP(DMM, LOGL_ERROR, "%s: cannot send BSSMAP Classmark Request\n", + vlr_subscr_name(conn->vsub)); + return -EIO; + } + + osmo_fsm_inst_state_chg(conn->fi, SUBSCR_CONN_S_WAIT_CLASSMARK_UPDATE, SUBSCR_CONN_TIMEOUT, 0); + return 0; +} + +static void subscr_conn_fsm_wait_classmark_update(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + switch (event) { + case SUBSCR_CONN_E_CLASSMARK_UPDATE: + /* Theoretically, this event can be used for requesting Classmark in various situations. + * So far though, the only time we send a Classmark Request is during Ciphering. As soon + * as more such situations arise, we need to add state to indicate what action should + * follow after a Classmark Update is received (e.g. + * msc_classmark_request_then_cipher_mode_cmd() sets an enum value to indicate that + * Ciphering should continue afterwards). But right now, it is accurate to always + * continue with Ciphering: */ + + /* During Ciphering, we needed Classmark information. The Classmark Update has come in, + * go back into the Set Ciphering Command procedure. */ + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_AUTH_CIPH, SUBSCR_CONN_TIMEOUT, 0); + if (msc_geran_set_cipher_mode(conn, conn->geran_set_cipher_mode.umts_aka, + conn->geran_set_cipher_mode.retrieve_imeisv)) { + LOGPFSML(fi, LOGL_ERROR, + "Sending Cipher Mode Command failed, aborting attach\n"); + vlr_subscr_cancel_attach_fsm(conn->vsub, OSMO_FSM_TERM_ERROR, + GSM48_REJECT_NETWORK_FAILURE); + } + return; + + case SUBSCR_CONN_E_UNUSED: + LOGPFSML(fi, LOGL_DEBUG, "Awaiting results for Auth+Ciph, overruling event %s\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + + case SUBSCR_CONN_E_MO_CLOSE: + case SUBSCR_CONN_E_CN_CLOSE: + log_close_event(fi, event, data); + evaluate_acceptance_outcome(fi, false); + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASING, SUBSCR_CONN_TIMEOUT, 0); + return; + + default: + OSMO_ASSERT(false); + } +} + static bool subscr_conn_fsm_has_active_transactions(struct osmo_fsm_inst *fi) { struct gsm_subscriber_connection *conn = fi->priv; @@ -362,10 +423,21 @@ static const struct osmo_fsm_state subscr_conn_fsm_states[] = { S(SUBSCR_CONN_E_MO_CLOSE) | S(SUBSCR_CONN_E_CN_CLOSE) | S(SUBSCR_CONN_E_UNUSED), - .out_state_mask = S(SUBSCR_CONN_S_ACCEPTED) | + .out_state_mask = S(SUBSCR_CONN_S_WAIT_CLASSMARK_UPDATE) | + S(SUBSCR_CONN_S_ACCEPTED) | S(SUBSCR_CONN_S_RELEASING), .action = subscr_conn_fsm_auth_ciph, }, + [SUBSCR_CONN_S_WAIT_CLASSMARK_UPDATE] = { + .name = OSMO_STRINGIFY(SUBSCR_CONN_S_WAIT_CLASSMARK_UPDATE), + .in_event_mask = S(SUBSCR_CONN_E_CLASSMARK_UPDATE) | + S(SUBSCR_CONN_E_MO_CLOSE) | + S(SUBSCR_CONN_E_CN_CLOSE) | + S(SUBSCR_CONN_E_UNUSED), + .out_state_mask = S(SUBSCR_CONN_S_AUTH_CIPH) | + S(SUBSCR_CONN_S_RELEASING), + .action = subscr_conn_fsm_wait_classmark_update, + }, [SUBSCR_CONN_S_ACCEPTED] = { .name = OSMO_STRINGIFY(SUBSCR_CONN_S_ACCEPTED), /* allow everything to release for any odd behavior */ |