neels has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-bsc/+/27373 )


Change subject: support "empty" SCCP N-Connect from MSC
......................................................................

support "empty" SCCP N-Connect from MSC

Teach osmo-bsc to handle empty N-Connect. So far we were always
expecting user data in an SCCP N-Connect from an MSC. However, it is
perfectly valid for an initial BSSMAP request to follow later.

This is relevant for:
- Handover Request (incoming inter-BSC handover)
- Perform Location Request (query physical location of the MS)

Add state WAIT_INITIAL_USER_DATA with new timeout net X25. Always enter
this state so that we don't have two separate code paths for handling
initial user data.

Related: SYS#5864
Change-Id: I535c791fa01e99a2226392eb05f676ba6c3cc16e
---
M include/osmocom/bsc/bsc_subscr_conn_fsm.h
M src/osmo-bsc/bsc_subscr_conn_fsm.c
M src/osmo-bsc/net_init.c
M src/osmo-bsc/osmo_bsc_bssap.c
M src/osmo-bsc/osmo_bsc_msc.c
M tests/timer.vty
6 files changed, 137 insertions(+), 37 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-bsc refs/changes/73/27373/1

diff --git a/include/osmocom/bsc/bsc_subscr_conn_fsm.h 
b/include/osmocom/bsc/bsc_subscr_conn_fsm.h
index d7deb06..0e495aa 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 3da13d2..a912663 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 @@

 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 @@
 };

 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 @@
        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 @@
                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,43 @@
                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;
+       struct rate_ctr *ctrs = conn->sccp.msc->msc_ctrs->ctr;

-       /* 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 +345,51 @@
                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 +398,6 @@
        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 +425,28 @@
                        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 +778,20 @@
 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 adcffc7..8a8c0a6 100644
--- a/src/osmo-bsc/net_init.c
+++ b/src/osmo-bsc/net_init.c
@@ -71,6 +71,7 @@
                .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 aba652a..311b0fa 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -981,6 +981,24 @@
        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 @@
                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/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
index baef4e5..4d1d85b 100644
--- a/src/osmo-bsc/osmo_bsc_msc.c
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -55,6 +55,7 @@
        [MSC_CTR_BSSMAP_RX_DT1_CIPHER_MODE_CMD] =   
{"bssmap:rx:dt1:cipher_mode:cmd", "Number of received BSSMAP DT1 CIPHER MODE 
CMD messages"},
        [MSC_CTR_BSSMAP_RX_DT1_ASSIGMENT_RQST] =    
{"bssmap:rx:dt1:assignment:rqst", "Number of received BSSMAP DT1 ASSIGMENT RQST 
messages"},
        [MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL] = 
{"bssmap:rx:dt1:lcls_connect_ctrl:cmd", "Number of received BSSMAP DT1 LCLS 
CONNECT CTRL messages"},
+       [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST] =     
{"bssmap:rx:dt1:handover:rqst", "Number of received BSSMAP DT1 HANDOVER RQST 
messages"},
        [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD] =      
{"bssmap:rx:dt1:handover:cmd", "Number of received BSSMAP DT1 HANDOVER CMD 
messages"},
        [MSC_CTR_BSSMAP_RX_DT1_CLASSMARK_RQST] =    
{"bssmap:rx:dt1:classmark:rqst", "Number of received BSSMAP DT1 CLASSMARK RQST 
messages"},
        [MSC_CTR_BSSMAP_RX_DT1_CONFUSION] =         {"bssmap:rx:dt1:confusion", 
"Number of received BSSMAP DT1 CONFUSION messages"},
diff --git a/tests/timer.vty b/tests/timer.vty
index e832070..04c9872 100644
--- a/tests/timer.vty
+++ b/tests/timer.vty
@@ -33,6 +33,7 @@
 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: 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)

--
To view, visit https://gerrit.osmocom.org/c/osmo-bsc/+/27373
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-bsc
Gerrit-Branch: master
Gerrit-Change-Id: I535c791fa01e99a2226392eb05f676ba6c3cc16e
Gerrit-Change-Number: 27373
Gerrit-PatchSet: 1
Gerrit-Owner: neels <[email protected]>
Gerrit-MessageType: newchange

Reply via email to