diff --git a/include/osmocom/bsc/bsc_subscr_conn_fsm.h b/include/osmocom/bsc/bsc_subscr_conn_fsm.h index d7deb06a8..0e495aae2 100644 --- a/include/osmocom/bsc/bsc_subscr_conn_fsm.h +++ b/include/osmocom/bsc/bsc_subscr_conn_fsm.h @@ -9,6 +9,7 @@ enum gscon_fsm_event { /* local SCCP stack tells us incoming conn from MSC */ GSCON_EV_A_CONN_IND, + GSCON_EV_A_INITIAL_USER_DATA, /* RSL side requests CONNECT to MSC */ GSCON_EV_MO_COMPL_L3, /* MSC confirms the SCCP connection */ diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c index 65c23d70d..2af333816 100644 --- a/src/osmo-bsc/bsc_subscr_conn_fsm.c +++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c @@ -60,6 +60,8 @@ enum gscon_fsm_states { ST_INIT, + /* wait for initial BSSMAP after the MSC opened a new SCCP connection */ + ST_WAIT_INITIAL_USER_DATA, /* waiting for CC from MSC */ ST_WAIT_CC, /* active connection */ @@ -73,6 +75,7 @@ enum gscon_fsm_states { static const struct value_string gscon_fsm_event_names[] = { {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, + {GSCON_EV_A_INITIAL_USER_DATA, "A_INITIAL_USER_DATA"}, {GSCON_EV_MO_COMPL_L3, "MO_COMPL_L3"}, {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, @@ -95,6 +98,7 @@ static const struct value_string gscon_fsm_event_names[] = { }; struct osmo_tdef_state_timeout conn_fsm_timeouts[32] = { + [ST_WAIT_INITIAL_USER_DATA] = { .T = -25 }, [ST_WAIT_CC] = { .T = -3210 }, [ST_WAIT_CLEAR_CMD] = { .T = -4 }, [ST_WAIT_SCCP_RLSD] = { .T = -4 }, @@ -258,23 +262,21 @@ void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_rel gscon_release_lchan(conn, conn->lchan, do_rr_release, false, cause_rr); } -static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim) +static int validate_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg) { - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *msg = scu_prim->oph.msg; struct bssmap_header *bs; - uint8_t bssmap_type; + enum BSS_MAP_MSG_TYPE bssmap_type; msg->l3h = msgb_l2(msg); if (!msgb_l3(msg)) { LOGPFSML(fi, LOGL_ERROR, "internal error: no l3 in msg\n"); - goto refuse; + return -EINVAL; } if (msgb_l3len(msg) < sizeof(*bs)) { LOGPFSML(fi, LOGL_ERROR, "message too short for BSSMAP header (%u < %zu)\n", msgb_l3len(msg), sizeof(*bs)); - goto refuse; + return -EINVAL; } bs = (struct bssmap_header*)msgb_l3(msg); @@ -282,7 +284,7 @@ static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_pri LOGPFSML(fi, LOGL_ERROR, "message too short for length indicated in BSSMAP header (%u < %u)\n", msgb_l3len(msg), bs->length); - goto refuse; + return -EINVAL; } switch (bs->type) { @@ -290,36 +292,42 @@ static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_pri break; default: LOGPFSML(fi, LOGL_ERROR, - "message type not allowed for N-CONNECT: %s\n", gsm0808_bssap_name(bs->type)); - goto refuse; + "message type not allowed for initial BSSMAP: %s\n", gsm0808_bssap_name(bs->type)); + return -EINVAL; } msg->l4h = &msg->l3h[sizeof(*bs)]; + + /* Validate initial message type. See also BSC_Tests.TC_outbound_connect. */ bssmap_type = msg->l4h[0]; - - LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type), - gsm0808_bssmap_name(bssmap_type)); - switch (bssmap_type) { case BSS_MAP_MSG_HANDOVER_RQST: case BSS_MAP_MSG_PERFORM_LOCATION_RQST: - break; + return 0; default: - LOGPFSML(fi, LOGL_ERROR, "No support for N-CONNECT: %s: %s\n", + LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); - goto refuse; + return -EINVAL; } +} - /* First off, accept the new conn. */ - if (osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, - &scu_prim->u.connect.called_addr, NULL, 0)) { - LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n"); - goto refuse; - } +static void handle_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct bssmap_header *bs; + enum BSS_MAP_MSG_TYPE bssmap_type; - /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ - conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + /* validate_initial_user_data() must be called before this */ + OSMO_ASSERT(msgb_l4(msg)); + + bs = msgb_l3(msg); + bssmap_type = msg->l4h[0]; + + /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() (OS#2969) */ + + LOGPFSML(fi, LOGL_DEBUG, "Rx initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type), + gsm0808_bssmap_name(bssmap_type)); switch (bssmap_type) { case BSS_MAP_MSG_HANDOVER_RQST: @@ -336,13 +344,51 @@ static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_pri return; default: - OSMO_ASSERT(false); + LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n", + gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } +} + +static void handle_sccp_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *msg = scu_prim->oph.msg; + + /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ + conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + + msg->l3h = msgb_l2(msg); + + /* If (BSSMAP) user data is included, validate it before accepting the connection */ + if (msgb_l3(msg) && msgb_l3len(msg)) { + if (validate_initial_user_data(fi, msg)) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } } -refuse: - osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, - &scu_prim->u.connect.called_addr, 0); - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + /* accept the new conn. */ + if (osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, NULL, 0)) { + LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + + /* The initial user data may already be included in this N-Connect, or it may come later in a separate message. + * If it is already included, also go to ST_WAIT_INITIAL_USER_DATA now, so that we don't have to tend to two + * separate code paths doing the same thing (handling of HANDOVER_END). */ + OSMO_ASSERT(conn_fsm_state_chg(ST_WAIT_INITIAL_USER_DATA) == 0); + + /* It is usually a bad idea to continue using a fi after a state change, because the fi might terminate during + * the state change. In this case it is certain that the fi stays around for the initial user data. */ + if (msgb_l3(msg) && msgb_l3len(msg)) { + handle_initial_user_data(fi, msg); + } else { + LOGPFSML(fi, LOGL_DEBUG, "N-Connect does not contain user data (no BSSMAP message included)\n"); + } } static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -351,7 +397,6 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) struct osmo_scu_prim *scu_prim = NULL; struct msgb *msg = NULL; int rc; - enum handover_result ho_result; switch (event) { case GSCON_EV_MO_COMPL_L3: @@ -379,11 +424,28 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); return; } - /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() - * related: OS2969 (same as above) */ - - handle_bssap_n_connect(fi, scu_prim); + handle_sccp_n_connect(fi, scu_prim); break; + default: + OSMO_ASSERT(false); + } +} + +static void gscon_fsm_wait_initial_user_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *msg = data; + enum handover_result ho_result; + + switch (event) { + case GSCON_EV_A_INITIAL_USER_DATA: + if (validate_initial_user_data(fi, msg)) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + handle_initial_user_data(fi, msg); + break; + case GSCON_EV_HANDOVER_END: ho_result = HO_RESULT_ERROR; if (data) @@ -715,10 +777,20 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, static const struct osmo_fsm_state gscon_fsm_states[] = { [ST_INIT] = { .name = "INIT", - .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND) - | S(GSCON_EV_HANDOVER_END), - .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), + .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND), + .out_state_mask = 0 + | S(ST_WAIT_INITIAL_USER_DATA) + | S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_init, + }, + [ST_WAIT_INITIAL_USER_DATA] = { + .name = "WAIT_INITIAL_USER_DATA", + .in_event_mask = 0 + | S(GSCON_EV_A_INITIAL_USER_DATA) + | S(GSCON_EV_HANDOVER_END) + , + .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), + .action = gscon_fsm_wait_initial_user_data, }, [ST_WAIT_CC] = { .name = "WAIT_CC", diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c index adcffc7a2..8a8c0a674 100644 --- a/src/osmo-bsc/net_init.c +++ b/src/osmo-bsc/net_init.c @@ -71,6 +71,7 @@ static struct osmo_tdef gsm_network_T_defs[] = { .desc = "Forget-sum period for all_allocated:* rate counters:" " after this amount of idle time, forget internally cumulated time remainders. Zero to always" " keep remainders. See also X16, X17." }, + { .T=-25, .default_val=5, .desc="Timeout for initial user data after an MSC initiated an SCCP connection to the BSS" }, { .T=-3111, .default_val=4, .desc="Wait time after lchan was released in error (should be T3111 + 2s)" }, { .T=-3210, .default_val=20, .desc="After L3 Complete, wait for MSC to confirm" }, {} diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c index aba652a55..311b0faf0 100644 --- a/src/osmo-bsc/osmo_bsc_bssap.c +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -981,6 +981,24 @@ reject: return -1; } +/* Handle Handover Request message, part of inter-BSC handover: + * The MSC opened a new SCCP connection and is asking this BSS to accept an inter-BSC incoming handover. + * If we accept, we'll send a Handover Request Acknowledge. + * This function is only called when the Handover Request is *not* included in the initial SCCP N-Connect message, but + * follows an "empty" N-Connect in a separate DT1 message. + */ +static int bssmap_handle_handover_request(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + if (osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_INITIAL_USER_DATA, msg)) { + /* A Handover Request message should come in on a newly opened SCCP conn. Apparently the MSC has sent a + * Handover Request on an already busy SCCP conn, and naturally we cannot accept another subscriber + * here. This is unlikely to ever happen in practice. Respond in the only possible way: */ + bsc_tx_bssmap_ho_failure(conn); + return -EINVAL; + } + return 0; +} + /* Handle Handover Command message, part of inter-BSC handover: * This BSS sent a Handover Required message. * The MSC contacts the remote BSS and receives from it an RR Handover Command; this BSSMAP Handover @@ -1165,6 +1183,10 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn, rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL]); ret = bssmap_handle_lcls_connect_ctrl(conn, msg, length); break; + case BSS_MAP_MSG_HANDOVER_RQST: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST]); + ret = bssmap_handle_handover_request(conn, msg); + break; case BSS_MAP_MSG_HANDOVER_CMD: rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD]); ret = bssmap_handle_handover_cmd(conn, msg, length); diff --git a/tests/timer.vty b/tests/timer.vty index e83207015..04c987274 100644 --- a/tests/timer.vty +++ b/tests/timer.vty @@ -33,6 +33,7 @@ net: X14 = 5 s Timeout for RSL Channel Mode Modify ACK (BSC <-> BTS) (default: 5 net: X16 = 1000 ms Granularity for all_allocated:* rate counters: amount of milliseconds that one counter increment represents. See also X17, X18 (default: 1000 ms) net: X17 = 0 ms Rounding threshold for all_allocated:* rate counters: round up to the next counter increment after this many milliseconds. If set to half of X16 (or 0), employ the usual round() behavior: round up after half of a granularity period. If set to 1, behave like ceil(): already increment the counter immediately when all channels are allocated. If set >= X16, behave like floor(): only increment after a full X16 period of all channels being occupied. See also X16, X18 (default: 0 ms) net: X18 = 60000 ms Forget-sum period for all_allocated:* rate counters: after this amount of idle time, forget internally cumulated time remainders. Zero to always keep remainders. See also X16, X17. (default: 60000 ms) +net: X25 = 5 s Timeout for initial user data after an MSC initiated an SCCP connection to the BSS (default: 5 s) net: X3111 = 4 s Wait time after lchan was released in error (should be T3111 + 2s) (default: 4 s) net: X3210 = 20 s After L3 Complete, wait for MSC to confirm (default: 20 s) mgw: X2427 = 5 s timeout for MGCP response from MGW (default: 5 s) @@ -86,6 +87,7 @@ net: X14 = 5 s Timeout for RSL Channel Mode Modify ACK (BSC <-> BTS) (default: 5 net: X16 = 1000 ms Granularity for all_allocated:* rate counters: amount of milliseconds that one counter increment represents. See also X17, X18 (default: 1000 ms) net: X17 = 0 ms Rounding threshold for all_allocated:* rate counters: round up to the next counter increment after this many milliseconds. If set to half of X16 (or 0), employ the usual round() behavior: round up after half of a granularity period. If set to 1, behave like ceil(): already increment the counter immediately when all channels are allocated. If set >= X16, behave like floor(): only increment after a full X16 period of all channels being occupied. See also X16, X18 (default: 0 ms) net: X18 = 60000 ms Forget-sum period for all_allocated:* rate counters: after this amount of idle time, forget internally cumulated time remainders. Zero to always keep remainders. See also X16, X17. (default: 60000 ms) +net: X25 = 5 s Timeout for initial user data after an MSC initiated an SCCP connection to the BSS (default: 5 s) net: X3111 = 4 s Wait time after lchan was released in error (should be T3111 + 2s) (default: 4 s) net: X3210 = 20 s After L3 Complete, wait for MSC to confirm (default: 20 s) mgw: X2427 = 5 s timeout for MGCP response from MGW (default: 5 s)