From 47afc424c3b6f26abb9f42d9b2c3328a469ad9bf Mon Sep 17 00:00:00 2001 From: Alexander Couzens Date: Sun, 17 Jan 2021 20:12:46 +0100 Subject: [PATCH] gprs_ns2: implement BLOCK/UNBLOCK of a NSVC by vty The vty should be able to block or unblock a specific NSVC. Further more this case is special for the UNITDATA as those can be still received until the other side response to the BLOCK PDU. Related: OS#4939 Change-Id: Ic0ce3c5fabc8644cc1ee71a8f6dd783fadf7b84d --- src/gb/gprs_ns2_internal.h | 2 + src/gb/gprs_ns2_vc_fsm.c | 93 ++++++++++++++++++++++++++++++++++---- src/gb/gprs_ns2_vty2.c | 28 ++++++++++++ tests/gb/gprs_ns2_test.c | 86 +++++++++++++++++++++++++++++++++++ tests/gb/gprs_ns2_test.ok | 5 ++ 5 files changed, 206 insertions(+), 8 deletions(-) diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h index 5404ed36c..cb5c2bd12 100644 --- a/src/gb/gprs_ns2_internal.h +++ b/src/gb/gprs_ns2_internal.h @@ -318,6 +318,8 @@ int gprs_ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc); int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp); int gprs_ns2_vc_is_alive(struct gprs_ns2_vc *nsvc); int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc); +int ns2_vc_block(struct gprs_ns2_vc *nsvc); +int ns2_vc_unblock(struct gprs_ns2_vc *nsvc); /* nse */ void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked); diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c index 641fcc319..8604bbead 100644 --- a/src/gb/gprs_ns2_vc_fsm.c +++ b/src/gb/gprs_ns2_vc_fsm.c @@ -59,6 +59,10 @@ struct gprs_ns2_vc_priv { * It can change during runtime. The side which blocks an unblocked side.*/ bool initiate_block; bool initiate_reset; + /* if blocked by O&M/vty */ + bool om_blocked; + /* if unitdata is forwarded to the user */ + bool accept_unitdata; /* the alive counter is present in all states */ struct { @@ -111,7 +115,9 @@ enum gprs_ns2_vc_event { GPRS_NS2_EV_UNITDATA, - GPRS_NS2_EV_FORCE_UNCONFIGURED, /* called via vty for tests */ + GPRS_NS2_EV_FORCE_UNCONFIGURED, /* called via vty for tests */ + GPRS_NS2_EV_REQ_OM_BLOCK, /* vty cmd: block */ + GPRS_NS2_EV_REQ_OM_UNBLOCK, /* vty cmd: unblock*/ }; static const struct value_string gprs_ns2_vc_event_names[] = { @@ -127,6 +133,8 @@ static const struct value_string gprs_ns2_vc_event_names[] = { { GPRS_NS2_EV_STATUS, "STATUS" }, { GPRS_NS2_EV_UNITDATA, "UNITDATA" }, { GPRS_NS2_EV_FORCE_UNCONFIGURED, "FORCE_UNCONFIGURED" }, + { GPRS_NS2_EV_REQ_OM_BLOCK, "REQ-O&M-BLOCK"}, + { GPRS_NS2_EV_REQ_OM_UNBLOCK, "REQ-O&M-UNBLOCK"}, { 0, NULL } }; @@ -245,6 +253,7 @@ static void gprs_ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_sta if (old_state != GPRS_NS2_ST_RESET) priv->N = 0; + priv->accept_unitdata = false; if (priv->initiate_reset) ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION); @@ -283,8 +292,16 @@ static void gprs_ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_s if (old_state != GPRS_NS2_ST_BLOCKED) priv->N = 0; - if (priv->initiate_block) + if (priv->om_blocked) { + /* we are already blocked after a RESET */ + if (old_state == GPRS_NS2_ST_RESET) { + osmo_timer_del(&fi->timer); + } else { + ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION); + } + } else if (priv->initiate_block) { ns2_tx_unblock(priv->nsvc); + } start_test_procedure(priv); } @@ -293,7 +310,24 @@ static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void * { struct gprs_ns2_vc_priv *priv = fi->priv; - if (priv->initiate_block) { + if (priv->om_blocked) { + switch (event) { + case GPRS_NS2_EV_BLOCK_ACK: + priv->accept_unitdata = false; + osmo_timer_del(&fi->timer); + break; + case GPRS_NS2_EV_BLOCK: + priv->accept_unitdata = false; + ns2_tx_block_ack(priv->nsvc); + osmo_timer_del(&fi->timer); + break; + case GPRS_NS2_EV_UNBLOCK: + priv->accept_unitdata = false; + ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION); + osmo_timer_add(&fi->timer); + break; + } + } else if (priv->initiate_block) { switch (event) { case GPRS_NS2_EV_BLOCK: /* TODO: BLOCK is a UNBLOCK_NACK */ @@ -303,6 +337,7 @@ static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void * ns2_tx_unblock_ack(priv->nsvc); /* fall through */ case GPRS_NS2_EV_UNBLOCK_ACK: + priv->accept_unitdata = true; osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, NS_TOUT_TNS_TEST); break; @@ -325,6 +360,7 @@ static void gprs_ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t ol struct gprs_ns2_vc *nsvc = priv->nsvc; struct gprs_ns2_nse *nse = nsvc->nse; + priv->accept_unitdata = true; ns2_nse_notify_unblocked(nsvc, true); ns2_prim_status_ind(nse, nsvc, 0, NS_AFF_CAUSE_VC_RECOVERY); } @@ -446,10 +482,19 @@ static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi) case GPRS_NS2_ST_BLOCKED: if (priv->initiate_block) { priv->N++; - if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { - osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + if (priv->om_blocked) { + if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + } else { + /* 7.2 stop accepting data when BLOCK PDU not responded */ + priv->accept_unitdata = false; + } } else { - osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } } } break; @@ -550,7 +595,7 @@ static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi, switch (fi->state) { case GPRS_NS2_ST_BLOCKED: /* 7.2.1: the BLOCKED_ACK might be lost */ - if (priv->initiate_block) { + if (priv->accept_unitdata) { gprs_ns2_recv_unitdata(fi, msg); return; } @@ -576,6 +621,20 @@ static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi, return; } break; + case GPRS_NS2_EV_REQ_OM_BLOCK: + /* vty cmd: block */ + priv->initiate_block = true; + priv->om_blocked = true; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + break; + case GPRS_NS2_EV_REQ_OM_UNBLOCK: + /* vty cmd: unblock*/ + if (!priv->om_blocked) + return; + priv->om_blocked = false; + if (fi->state == GPRS_NS2_ST_BLOCKED) + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + break; } } @@ -595,7 +654,9 @@ static struct osmo_fsm gprs_ns2_vc_fsm = { S(GPRS_NS2_EV_RESET) | S(GPRS_NS2_EV_ALIVE) | S(GPRS_NS2_EV_ALIVE_ACK) | - S(GPRS_NS2_EV_FORCE_UNCONFIGURED), + S(GPRS_NS2_EV_FORCE_UNCONFIGURED) | + S(GPRS_NS2_EV_REQ_OM_BLOCK) | + S(GPRS_NS2_EV_REQ_OM_UNBLOCK), .allstate_action = gprs_ns2_vc_fsm_allstate_action, .cleanup = gprs_ns2_vc_fsm_clean, .timer_cb = gprs_ns2_vc_fsm_timer_cb, @@ -653,6 +714,22 @@ int gprs_ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc) return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_FORCE_UNCONFIGURED, NULL); } +/*! Block a NS-VC. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_block(struct gprs_ns2_vc *nsvc) +{ + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_BLOCK, NULL); +} + +/*! Unblock a NS-VC. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_unblock(struct gprs_ns2_vc *nsvc) +{ + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_UNBLOCK, NULL); +} + /*! entry point for messages from the driver/VL * \param nsvc virtual circuit on which the message was received * \param msg message that was received diff --git a/src/gb/gprs_ns2_vty2.c b/src/gb/gprs_ns2_vty2.c index 5af8fbce0..94302ef2b 100644 --- a/src/gb/gprs_ns2_vty2.c +++ b/src/gb/gprs_ns2_vty2.c @@ -1503,6 +1503,33 @@ DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd, return CMD_SUCCESS; } +DEFUN(nsvc_block, nsvc_block_cmd, + "nsvc <0-65535> (block|unblock)", + "NS Virtual Connection\n" + NSVCI_STR + "Block a NSVC. As cause code O&M intervention will be used.\n" + "Unblock a NSVC. As cause code O&M intervention will be used.\n") +{ + struct gprs_ns2_inst *nsi = vty_nsi; + struct gprs_ns2_vc *nsvc; + + uint16_t id = atoi(argv[0]); + + nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id); + if (!nsvc) { + vty_out(vty, "Could not find NSVCI %05u%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "block")) { + ns2_vc_block(nsvc); + } else { + ns2_vc_unblock(nsvc); + } + + return CMD_SUCCESS; +} + static void log_set_nse_filter(struct log_target *target, struct gprs_ns2_nse *nse) { @@ -1608,6 +1635,7 @@ int gprs_ns2_vty2_init(struct gprs_ns2_inst *nsi) install_lib_element_ve(&logging_fltr_nsvc_cmd); install_lib_element(ENABLE_NODE, &nsvc_force_unconf_cmd); + install_lib_element(ENABLE_NODE, &nsvc_block_cmd); install_lib_element(CFG_LOG_NODE, &logging_fltr_nse_cmd); install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd); diff --git a/tests/gb/gprs_ns2_test.c b/tests/gb/gprs_ns2_test.c index bcfd4606b..315a4d07d 100644 --- a/tests/gb/gprs_ns2_test.c +++ b/tests/gb/gprs_ns2_test.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,34 @@ static int ns_prim_cb(struct osmo_prim_hdr *oph, void *ctx) return 0; } +static struct msgb *get_pdu(struct gprs_ns2_vc_bind *bind, enum ns_pdu_type pdu_type) +{ + struct gprs_ns_hdr *nsh; + struct osmo_wqueue *queue = bind->priv; + + while (!llist_empty(&queue->msg_queue)) { + struct msgb *msg = msgb_dequeue(&queue->msg_queue); + nsh = (struct gprs_ns_hdr *) msg->l2h; + if (nsh->pdu_type == pdu_type) + return msg; + msgb_free(msg); + } + + return NULL; +} + +static bool find_pdu(struct gprs_ns2_vc_bind *bind, enum ns_pdu_type pdu_type) +{ + struct msgb *msg; + msg = get_pdu(bind, pdu_type); + if (msg) { + msgb_free(msg); + return true; + } + + return false; +} + static void clear_pdus(struct gprs_ns2_vc_bind *bind) { struct osmo_wqueue *queue = bind->priv; @@ -147,6 +176,62 @@ void test_nse_transfer_cap(void *ctx) } +/* setup NSE with 2x NSVCs. + * block 1x NSVC + * unblock 1x NSVC*/ +void test_block_unblock_nsvc(void *ctx) +{ + struct gprs_ns2_inst *nsi; + struct gprs_ns2_vc_bind *bind[2]; + struct gprs_ns2_nse *nse; + struct gprs_ns2_vc *nsvc[2]; + struct gprs_ns_hdr *nsh; + struct msgb *msg; + char idbuf[32]; + + printf("--- Testing NSE block unblock nsvc\n"); + printf("---- Create NSE + Binds\n"); + nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL); + bind[0] = dummy_bind(nsi, "bblock1"); + bind[1] = dummy_bind(nsi, "bblock2"); + nse = gprs_ns2_create_nse(nsi, 1001, GPRS_NS2_LL_UDP, NS2_DIALECT_STATIC_RESETBLOCK); + OSMO_ASSERT(nse); + + for (int i=0; i<2; i++) { + printf("---- Create NSVC[i]\n"); + snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i); + nsvc[i] = ns2_vc_alloc(bind[i], nse, false, NS2_VC_MODE_BLOCKRESET, idbuf); + OSMO_ASSERT(nsvc[i]); + nsvc[i]->fi->state = 3; /* HACK: 3 = GPRS_NS2_ST_UNBLOCKED */ + /* ensure the fi->state works correct */ + OSMO_ASSERT(gprs_ns2_vc_is_unblocked(nsvc[i])); + ns2_nse_notify_unblocked(nsvc[i], true); + } + + /* both nsvcs are unblocked and alive. Let's block it. */ + OSMO_ASSERT(!find_pdu(bind[0], NS_PDUT_BLOCK)); + clear_pdus(bind[0]); + ns2_vc_block(nsvc[0]); + OSMO_ASSERT(find_pdu(bind[0], NS_PDUT_BLOCK)); + /* state == BLOCKED */ + clear_pdus(bind[0]); + + /* now unblocking it */ + ns2_vc_unblock(nsvc[0]); + OSMO_ASSERT(find_pdu(bind[0], NS_PDUT_UNBLOCK)); + clear_pdus(bind[0]); + + msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, "test_unblock"); + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_UNBLOCK_ACK; + ns2_recv_vc(nsvc[0], msg); + + OSMO_ASSERT(gprs_ns2_vc_is_unblocked(nsvc[0])); + gprs_ns2_free(nsi); + printf("--- Finish NSE block unblock nsvc\n"); +} + int main(int argc, char **argv) { void *ctx = talloc_named_const(NULL, 0, "gprs_ns2_test"); @@ -159,6 +244,7 @@ int main(int argc, char **argv) printf("===== NS2 protocol test START\n"); test_nse_transfer_cap(ctx); + test_block_unblock_nsvc(ctx); printf("===== NS2 protocol test END\n\n"); talloc_free(ctx); diff --git a/tests/gb/gprs_ns2_test.ok b/tests/gb/gprs_ns2_test.ok index 62bbbfe78..27c72face 100644 --- a/tests/gb/gprs_ns2_test.ok +++ b/tests/gb/gprs_ns2_test.ok @@ -6,5 +6,10 @@ ---- Test with NSVC[2] ---- Test with NSVC[1] removed --- Finish NSE transfer cap +--- Testing NSE block unblock nsvc +---- Create NSE + Binds +---- Create NSVC[i] +---- Create NSVC[i] +--- Finish NSE block unblock nsvc ===== NS2 protocol test END