#include #include #include #include #include #include #include #include #include #include #include #define S(x) (1 << (x)) /* * TODO: * * test FSM for all testvlr_mode (and more) * * test also the time-outs in the vlr code * * test for memory leaks * * how to get the HLR running? Or test against stub? * * test disappearing MS connection * * test absence of HLR */ void *tall_bsc_ctx; static struct vlr_instance *g_vlr; /*********************************************************************** * Finite State Machine simulating MS and MSC towards VLR ***********************************************************************/ static int timer_error_cb(struct osmo_fsm_inst *fi) { LOGPFSM(fi, "timer expired waiting for completion\n"); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return 1; } enum testvlr_mode { MODE_SUCCESS, MODE_SUCCESS_TMSI, MODE_AUTH_FAIL, MODE_AUTH_RESYNC, }; struct testvlr_priv { enum testvlr_mode mode; uint32_t tmsi; char imsi[16]; char imei[16]; struct osmo_location_area_id old_lai; struct osmo_location_area_id new_lai; struct vlr_subscr *subscr; struct osmo_fsm_inst *lu_fsm; }; #define fsi_priv(x) (struct testvlr_priv *)(x)->priv enum f_state { /*! initial state */ ST_NULL, /*! LU was sent by MS */ ST_LU_SENT, /*! waiting for auth re-sync */ ST_RESYNC_SENT, /* Waiting for LU ACK */ ST_WAIT_LU_ACK, ST_DONE, ST_FAILED, }; enum f_event { /* events from MS */ EVT_MS_TX_LU, /* transmit LU REQ to network */ EVT_MS_TX_ID_RESP, /* tranmit ID RSP to network */ EVT_MS_TX_AUTH_RESP, /* transmit AUTH RESP to network */ EVT_MS_TX_AUTH_FAIL, /* transmit AUTH FAIL to network */ EVT_MS_CONN_LOST, /* connection to MS was lost */ /* events from VLR */ EVT_VLR_AUTH_REQ, /* transmit AUTH REQ to MS */ EVT_VLR_ID_REQ_IMSI, /* transmit ID REQ(IMSI) to MS */ EVT_VLR_ID_REQ_IMEI, /* tramsmit ID REQ(IMEI) to MS */ EVT_VLR_ID_REQ_IMEISV, /* trasnmit ID REQ(IMEISV) to MS */ EVT_VLR_AUTH_REJ, /* transmit AUTH REJ to MS */ EVT_VLR_SET_CIPH, /* transmit SET CIPH to MS */ EVT_VLR_LU_ACK, /* transmit LU ACK to MS */ EVT_VLR_LU_REJ, /* transmit LU REJ to MS */ }; static struct value_string f_event_names[] = { { EVT_MS_TX_LU, "MS-TX-LU" }, { EVT_MS_TX_ID_RESP, "MS-TX-ID-RESP" }, { EVT_MS_TX_AUTH_RESP, "MS-TX-AUTH-RESP" }, { EVT_MS_TX_AUTH_FAIL, "MS-TX-AUTH-FAIL" }, { EVT_MS_CONN_LOST, "MS-CONN-LOST" }, { EVT_VLR_AUTH_REQ, "VLR-AUTH-REQ" }, { EVT_VLR_ID_REQ_IMSI, "VLR-ID-REQ-IMSI" }, { EVT_VLR_ID_REQ_IMEI, "VLR-ID-REQ-IMEI" }, { EVT_VLR_ID_REQ_IMEISV,"VLR-ID-REQ-IMEISV" }, { EVT_VLR_AUTH_REJ, "VLR-AUTH-REJ" }, { EVT_VLR_SET_CIPH, "VLR-SET-CIPH" }, { EVT_VLR_LU_ACK, "VLR-LU-ACK" }, { EVT_VLR_LU_REJ, "VLR-LU-REJ" }, { 0, NULL } }; static void fsm_f_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct testvlr_priv *priv = fsi_priv(fi); uint8_t mi[16]; unsigned int mi_len; switch (event) { case EVT_VLR_ID_REQ_IMSI: if (priv->mode != MODE_SUCCESS_TMSI) { LOGP(DGPRS, LOGL_NOTICE, "Unexpected ID REQ " "(IMSI)\n"); } mi_len = gsm48_generate_mid_from_imsi(mi, priv->imsi); vlr_subscr_rx_id_resp(priv->subscr, mi+2, mi_len-2); break; case EVT_VLR_ID_REQ_IMEI: mi_len = gsm48_generate_mid_from_imsi(mi, priv->imei); mi[0] = (mi[0] & 0xf8) | GSM_MI_TYPE_IMEI; vlr_subscr_rx_id_resp(priv->subscr+2, mi, mi_len-2); break; case EVT_VLR_ID_REQ_IMEISV: mi_len = gsm48_generate_mid_from_imsi(mi, priv->imei); mi[0] = (mi[0] & 0xf8) | GSM_MI_TYPE_IMEISV; vlr_subscr_rx_id_resp(priv->subscr, mi+2, mi_len-2); break; case EVT_MS_CONN_LOST: vlr_subscr_disconnected(priv->subscr); /* IDEA: not release but keep around in extra state to * see if VLR still sends us anything? */ osmo_fsm_inst_free(fi); break; } } static void fsm_f_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct testvlr_priv *priv = fsi_priv(fi); uint32_t tmsi = 0; const char *imsi = NULL; switch (event) { case EVT_MS_TX_LU: /* send LU to VLR */ if (priv->mode == MODE_SUCCESS) imsi = priv->imsi; else tmsi = priv->tmsi; priv->lu_fsm = vlr_loc_update(fi, EVT_VLR_LU_ACK, EVT_VLR_LU_REJ, NULL, g_vlr, NULL, VLR_LU_TYPE_IMSI_ATTACH, tmsi, imsi, &priv->old_lai, &priv->new_lai, true, true, false, false, true); OSMO_ASSERT(priv->subscr); osmo_fsm_inst_state_chg(fi, ST_LU_SENT, 0, 0); break; default: break; } } static void fsm_f_lu_sent(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_auth_tuple *at = NULL; struct testvlr_priv *priv = fsi_priv(fi); uint8_t res_fail[4]; uint8_t auts[14]; switch (event) { case EVT_VLR_AUTH_REQ: at = data; OSMO_ASSERT(at); DEBUGP(DGPRS, "%s: at->res=%s\n", __func__, osmo_hexdump(at->vec.res, at->vec.res_len)); switch (priv->mode) { case MODE_SUCCESS: case MODE_SUCCESS_TMSI: /* return matching SRES/AUTS */ vlr_subscr_rx_auth_resp(priv->subscr, true, false, at->vec.res, at->vec.res_len); break; case MODE_AUTH_FAIL: /* return not matching SRES/AUTS */ vlr_subscr_rx_auth_resp(priv->subscr, true, false, res_fail, sizeof(res_fail)); /* FIXME: state transition? */ break; case MODE_AUTH_RESYNC: /* return SRES/AUTS requesting re-sync */ /* FIXME: generate a proper authenticating * re-sync request */ vlr_subscr_rx_auth_fail(priv->subscr, auts); /* FIXME: state transition? */ osmo_fsm_inst_state_chg(fi, ST_RESYNC_SENT, 0, 0); break; } osmo_fsm_inst_state_chg(fi, ST_WAIT_LU_ACK, 0, 0); break; case EVT_VLR_LU_REJ: { uint8_t cause = *(uint8_t *)data; LOGP(DGPRS, LOGL_NOTICE, "LU(%s): Rejected; cause=0x%02x\n", priv->imsi, cause); } break; default: break; } } #if 0 static void fsm_f_resync_sent(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct testvlr_priv *priv = fsi_priv(fi); struct gsm_auth_tuple *at = NULL; /* second auth request is supposed to succed after the * re-sync procedure before */ switch (event) { case EVT_VLR_AUTH_REQ: at = data; /* return matching SRES/AUTS now */ vlr_subscr_rx_auth_resp(priv->subscr, true, false, at->vec.res, at->vec.res_len); osmo_fsm_inst_state_chg(fi, ST_WAIT_LU_ACK, 0, 0); break; } } #endif static void fsm_f_wait_lu_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct testvlr_priv *priv = fsi_priv(fi); switch (event) { case EVT_VLR_LU_ACK: if (priv->subscr->tmsi != GSM_RESERVED_TMSI) { /* we need to send an TMSI REALLOC COMPL */ vlr_subscr_rx_tmsi_reall_compl(priv->subscr); } osmo_fsm_inst_state_chg(fi, ST_DONE, 0, 0); break; case EVT_VLR_LU_REJ: osmo_fsm_inst_state_chg(fi, ST_FAILED, 0, 0); break; } } static void fsm_f_imsi_sent(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case EVT_MS_TX_ID_RESP: break; } } #if 0 static void fsm_f_areq_sent(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case EVT_MS_TX_AUTH_RESP: break; case EVT_MS_TX_AUTH_FAIL: break; } } #endif static struct osmo_fsm_state fsm_success_states[] = { [ST_NULL] = { .in_event_mask = S(EVT_MS_TX_LU), .out_state_mask = S(ST_LU_SENT), .name = "NULL", .action = fsm_f_null, }, [ST_LU_SENT] = { .in_event_mask = S(EVT_VLR_AUTH_REQ) | S(EVT_VLR_LU_REJ), //.out_state_mask = S(ST_IDREQ_IMSI_SENT) | S(ST_AUTH_REQ_SENT), .out_state_mask = S(ST_WAIT_LU_ACK), .name = "LU Sent", .action = fsm_f_lu_sent, }, [ST_RESYNC_SENT] = { .in_event_mask = S(EVT_VLR_AUTH_REQ), .out_state_mask = S(ST_WAIT_LU_ACK), .name = "AUTH-RESYNC sent", .action = fsm_f_imsi_sent, }, [ST_WAIT_LU_ACK] = { .in_event_mask = S(EVT_VLR_LU_ACK) | S(EVT_VLR_SET_CIPH) | S(EVT_VLR_LU_REJ), .out_state_mask = S(ST_DONE), .name = "WAIT-LU-ACK", .action = fsm_f_wait_lu_ack, }, [ST_DONE] = { .name = "DONE" }, }; static struct osmo_fsm vlr_test_fsm = { .name = "VLR Test FSM", .states = fsm_success_states, .num_states = ARRAY_SIZE(fsm_success_states), .log_subsys = DGPRS, .event_names = f_event_names, .allstate_event_mask = S(EVT_MS_CONN_LOST) | S(EVT_VLR_ID_REQ_IMSI) | S(EVT_VLR_ID_REQ_IMEI) | S(EVT_VLR_ID_REQ_IMEISV), .allstate_action = fsm_f_allstate, }; /* Testing of Subscriber_Present_VLR */ enum test_sub_pres_state { TSPV_S_INIT, TSPV_S_RUNNING, }; enum test_sub_pres_evt { TSPV_E_START, TSPV_E_COMPL, }; static void tspv_f_running(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct vlr_subscr *vsub = fi->priv; switch (event) { case TSPV_E_COMPL: OSMO_ASSERT(vsub); OSMO_ASSERT(vsub->ms_not_reachable_flag == false); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); break; } } static void tspv_f_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct osmo_fsm_inst *spv; struct vlr_subscr *vsub = fi->priv; switch (event) { case TSPV_E_START: OSMO_ASSERT(vsub); vsub->ms_not_reachable_flag = true; spv = sub_pres_vlr_fsm_start(fi, vsub, TSPV_E_COMPL); OSMO_ASSERT(spv); osmo_fsm_inst_state_chg(fi, TSPV_S_RUNNING, 4, 0); break; } } static const struct osmo_fsm_state test_sub_pres_vlr_states[] = { [TSPV_S_INIT] = { .in_event_mask = S(TSPV_E_START), .out_state_mask = S(TSPV_S_RUNNING), .name = "INIT", .action = tspv_f_init, }, [TSPV_S_RUNNING] = { .in_event_mask = S(TSPV_E_COMPL), .out_state_mask = 0, .name = "RUNNING", .action = tspv_f_running, }, }; static struct osmo_fsm test_sub_pres_vlr_fsm = { .name = "Test Subscriber_Present_VLR", .states = test_sub_pres_vlr_states, .num_states = ARRAY_SIZE(test_sub_pres_vlr_states), .log_subsys = DGPRS, .event_names = f_event_names, .timer_cb = timer_error_cb, }; #if 0 static void start_sub_pres_vlr(void *ctx, uint32_t tmsi, const char *imsi) { struct osmo_fsm_inst *fi; struct vlr_subscr *vsub = vlr_subscr_alloc(g_vlr); vsub->tmsi = tmsi; strncpy(vsub->imsi, imsi, sizeof(vsub->imsi)); fi = osmo_fsm_inst_alloc(&test_sub_pres_vlr_fsm, ctx, vsub, LOGL_DEBUG, vsub->imsi); osmo_fsm_inst_dispatch(fi, TSPV_E_START, NULL); } #endif /* Testing of Update_HLR_VLR */ enum test_update_hlr_vlr_state { TUHV_S_INIT, TUHV_S_RUNNING, }; enum test_update_hlr_vlr_event { TUHV_E_START, TUHV_E_COMPL, }; static void tuhv_f_running(struct osmo_fsm_inst *fi, uint32_t event, void *data) { enum gsm48_gmm_cause *res = data; switch (event) { case TUHV_E_COMPL: if (!res) { /* Success */ LOGPFSM(fi, "success\n"); } else { /* error */ LOGPFSM(fi, "errror cause=0x%u\n", *res); } osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); break; } } static void tuhv_f_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct osmo_fsm_inst *child; struct vlr_subscr *vsub = fi->priv; switch (event) { case TUHV_E_START: child = upd_hlr_vlr_proc_start(fi, vsub, TUHV_E_COMPL); OSMO_ASSERT(child); osmo_fsm_inst_state_chg(fi, TUHV_S_RUNNING, 4, 0); break; } } static const struct osmo_fsm_state test_upd_hlr_vlr_states[] = { [TUHV_S_INIT] = { .in_event_mask = S(TUHV_E_START), .out_state_mask = S(TUHV_S_RUNNING), .name = "INIT", .action = tuhv_f_init, }, [TUHV_S_RUNNING] = { .in_event_mask = S(TUHV_E_COMPL), .out_state_mask = 0, .name = "RUNNING", .action = tuhv_f_running, }, }; static struct osmo_fsm test_upd_hlr_vlr_fsm = { .name = "Test Update_HLR_VLR", .states = test_upd_hlr_vlr_states, .num_states = ARRAY_SIZE(test_upd_hlr_vlr_states), .log_subsys = DGPRS, .event_names = f_event_names, .timer_cb = timer_error_cb, }; #if 0 static void start_upd_hlr_vlr(void *ctx, uint32_t tmsi, const char *imsi) { struct osmo_fsm_inst *fi; struct vlr_subscr *vsub = vlr_subscr_alloc(g_vlr); vsub->tmsi = tmsi; strncpy(vsub->imsi, imsi, sizeof(vsub->imsi)); fi = osmo_fsm_inst_alloc(&test_upd_hlr_vlr_fsm, ctx, vsub, LOGL_DEBUG, vsub->imsi); /* we need to set this to fool vlr.c in an ongoing LU */ vsub->lu_fsm = fi; osmo_fsm_inst_dispatch(fi, TUHV_E_START, NULL); } #endif /*********************************************************************** * Integration with VLR code ***********************************************************************/ static struct vlr_instance *g_vlr; /* VLR asks us to send an authentication request */ static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct gsm_auth_tuple *at, bool send_autn) { struct osmo_fsm_inst *fi = msc_conn_ref; OSMO_ASSERT(at); DEBUGP(DGPRS, "%s: RES=%s\n", __func__, osmo_hexdump_nospc(at->vec.res, at->vec.res_len)); osmo_fsm_inst_dispatch(fi, EVT_VLR_AUTH_REQ, at); return 0; } /* VLR asks us to send an authentication reject */ static int msc_vlr_tx_auth_rej(void *msc_conn_ref) { struct osmo_fsm_inst *fi = msc_conn_ref; DEBUGP(DGPRS, "%s\n", __func__); osmo_fsm_inst_dispatch(fi, EVT_VLR_AUTH_REJ, NULL); return 0; } /* VLR asks us to transmit an Identity Request of given type */ static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type) { struct osmo_fsm_inst *fi = msc_conn_ref; uint32_t event; DEBUGP(DGPRS, "%s (%u)\n", __func__, mi_type); switch (mi_type) { case GSM_MI_TYPE_IMSI: event = EVT_VLR_ID_REQ_IMSI; break; case GSM_MI_TYPE_IMEI: event = EVT_VLR_ID_REQ_IMEI; break; case GSM_MI_TYPE_IMEISV: event = EVT_VLR_ID_REQ_IMEISV; break; default: LOGP(DGPRS, LOGL_ERROR, "Unknown identity 0x%02x\n", mi_type); return -1; } osmo_fsm_inst_dispatch(fi, event, NULL); return 0; } /* VLR asks us to transmit a Location Update Accept */ static int msc_vlr_tx_lu_ack(void *msc_conn_ref, uint32_t send_tmsi) { struct osmo_fsm_inst *fi = msc_conn_ref; DEBUGP(DGPRS, "%s\n", __func__); osmo_fsm_inst_dispatch(fi, EVT_VLR_LU_ACK, NULL); return 0; } /* VLR asks us to transmit a Location Update Reject */ static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause) { struct osmo_fsm_inst *fi = msc_conn_ref; DEBUGP(DGPRS, "%s\n", __func__); osmo_fsm_inst_dispatch(fi, EVT_VLR_LU_REJ, (void *) &cause); return 0; } static int msc_vlr_set_ciph_mode(void *msc_conn_ref, enum vlr_ciph mode, bool retrieve_imeisv) { struct osmo_fsm_inst *fi = msc_conn_ref; DEBUGP(DGPRS, "%s\n", __func__); osmo_fsm_inst_dispatch(fi, EVT_VLR_SET_CIPH, NULL); return 0; } /* VLR informs us that the subscriber data has somehow been modified */ static void msc_vlr_subscr_update(struct vlr_subscr *subscr) { DEBUGP(DGPRS, "%s\n", __func__); /* FIXME */ } static void msc_vlr_subscr_assoc(void *msc_conn_ref, struct vlr_subscr *vsub) { struct osmo_fsm_inst *fi = msc_conn_ref; struct testvlr_priv *priv = fsi_priv(fi); DEBUGP(DGPRS, "%s(%p, %s)\n", __func__, msc_conn_ref, vlr_subscr_name(vsub)); priv->subscr = vsub; } /* operations that we need to implement for libvlr */ static const struct vlr_ops test_vlr_ops = { .tx_auth_req = msc_vlr_tx_auth_req, .tx_auth_rej = msc_vlr_tx_auth_rej, .tx_id_req = msc_vlr_tx_id_req, .tx_lu_acc = msc_vlr_tx_lu_ack, .tx_lu_rej = msc_vlr_tx_lu_rej, .set_ciph_mode = msc_vlr_set_ciph_mode, .subscr_update = msc_vlr_subscr_update, .subscr_assoc = msc_vlr_subscr_assoc, }; /*********************************************************************** * Actual test cases ***********************************************************************/ static struct osmo_fsm_inst * start_lu(enum testvlr_mode mode, uint32_t tmsi, const char *imsi, const char *imei) { struct testvlr_priv *vp; struct osmo_fsm_inst *fi; vp = talloc_zero(tall_bsc_ctx, struct testvlr_priv); vp->mode = mode; vp->tmsi = tmsi; strncpy(vp->imsi, imsi, sizeof(vp->imsi)); strncpy(vp->imei, imei, sizeof(vp->imei)); fi = osmo_fsm_inst_alloc(&vlr_test_fsm, vp, vp, LOGL_DEBUG, vp->imsi); osmo_fsm_inst_dispatch(fi, EVT_MS_TX_LU, NULL); return fi; } /*********************************************************************** * Main / Misc ***********************************************************************/ static struct osmo_timer_list tmr; static void timer_cb(void *data) { uint32_t tmsi = rand() % 1000000; uint64_t imsi = 901790000000000 + tmsi; char imsi_str[32]; snprintf(imsi_str, sizeof(imsi_str), "%lu", imsi); //start_lu(MODE_AUTH_FAIL, tmsi, imsi_str, "23422342"); start_lu(MODE_SUCCESS_TMSI, tmsi, imsi_str, "23422342"); //start_lu(MODE_SUCCESS, tmsi, imsi_str, "23422342"); //start_upd_hlr_vlr(tall_bsc_ctx, tmsi, imsi_str); //start_sub_pres_vlr(tall_bsc_ctx); osmo_timer_schedule(&tmr, 8, 0); } static void sighdlr(int sig) { switch (sig) { case SIGUSR1: talloc_report_full(tall_bsc_ctx, stderr); break; } } int main(int argc, char **argv) { tall_bsc_ctx = talloc_named_const(NULL, 1, "tall_bsc_ctx"); signal(SIGUSR1, sighdlr); osmo_init_logging(&log_info); g_vlr = vlr_alloc(NULL, &test_vlr_ops); vlr_start("VLRTEST", g_vlr, "localhost", 2222); OSMO_ASSERT(g_vlr); osmo_fsm_register(&vlr_test_fsm); osmo_fsm_register(&test_sub_pres_vlr_fsm); osmo_fsm_register(&test_upd_hlr_vlr_fsm); g_vlr->cfg.assign_tmsi = true; tmr.cb = timer_cb; timer_cb(NULL); while (1) { osmo_select_main(0); } exit(0); } struct gsm_subscriber_connection; int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn) { return 0; }