gprs_ns2_sns: rework tracking of NS-VC unblocked/alive state
The SNS must know when all NS-VC have failed. Further more
there might be a corner case when the SNS configuration succeeds but
no NS-VC comes up afterwards.
Related: OS#5355
Change-Id: Ie72da9adeefe0c2850d49a9208b2d0a4556f9101
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
index de27fb4..ca47934 100644
--- a/src/gb/gprs_ns2.c
+++ b/src/gb/gprs_ns2.c
@@ -577,7 +577,7 @@
ns2_nse_notify_unblocked(nsvc, false);
/* check if sns is using this VC */
- ns2_sns_free_nsvc(nsvc);
+ ns2_sns_replace_nsvc(nsvc);
osmo_fsm_inst_term(nsvc->fi, OSMO_FSM_TERM_REQUEST, NULL);
/* let the driver/bind clean up it's internal state */
@@ -1175,6 +1175,7 @@
{
struct gprs_ns2_nse *nse = nsvc->nse;
+ ns2_sns_notify_alive(nse, nsvc, unblocked);
ns2_nse_data_sum(nse);
if (unblocked == nse->alive)
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
index 6462907..ff95c81 100644
--- a/src/gb/gprs_ns2_internal.h
+++ b/src/gb/gprs_ns2_internal.h
@@ -350,7 +350,8 @@
int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
const char *id);
-void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc);
+void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc);
+void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive);
/* vc */
struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
index 427a153..b80a880 100644
--- a/src/gb/gprs_ns2_sns.c
+++ b/src/gb/gprs_ns2_sns.c
@@ -80,6 +80,7 @@
GPRS_SNS_EV_DELETE,
GPRS_SNS_EV_CHANGE_WEIGHT,
GPRS_SNS_EV_NO_NSVC,
+ GPRS_SNS_EV_NSVC_ALIVE, /*!< a NS-VC became alive */
};
static const struct value_string gprs_sns_event_names[] = {
@@ -93,6 +94,7 @@
{ GPRS_SNS_EV_DELETE, "DELETE" },
{ GPRS_SNS_EV_CHANGE_WEIGHT, "CHANGE_WEIGHT" },
{ GPRS_SNS_EV_NO_NSVC, "NO_NSVC" },
+ { GPRS_SNS_EV_NSVC_ALIVE, "NSVC_ALIVE"},
{ 0, NULL }
};
@@ -122,6 +124,8 @@
int bind_offset;
/* timer N */
int N;
+ /* true if at least one nsvc is alive */
+ bool alive;
/* local configuration to send to the remote end */
struct gprs_ns_ie_ip4_elem *ip4_local;
@@ -227,13 +231,13 @@
return &gss->initial->saddr;
}
-/*! called when a nsvc is beeing freed */
-void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
+/*! called when a nsvc is beeing freed or the nsvc became dead */
+void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc)
{
- struct gprs_ns2_nse *nse;
+ struct gprs_ns2_nse *nse = nsvc->nse;
struct gprs_ns2_vc *tmp;
+ struct osmo_fsm_inst *fi = nse->bss_sns_fi;
struct ns2_sns_state *gss;
- struct osmo_fsm_inst *fi = nsvc->nse->bss_sns_fi;
if (!fi)
return;
@@ -242,17 +246,26 @@
if (nsvc != gss->sns_nsvc)
return;
- nse = nsvc->nse;
- if (nse->alive) {
- /* choose a different sns nsvc */
+ gss->sns_nsvc = NULL;
+ if (gss->alive) {
llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (ns2_vc_is_unblocked(tmp))
+ if (ns2_vc_is_unblocked(tmp)) {
gss->sns_nsvc = tmp;
+ return;
+ }
}
} else {
- gss->sns_nsvc = NULL;
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_NO_NSVC, NULL);
+ /* the SNS is waiting for its first NS-VC to come up
+ * choose any other nsvc */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (nsvc != tmp) {
+ gss->sns_nsvc = tmp;
+ return;
+ }
+ }
}
+
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_NO_NSVC, NULL);
}
static void ns2_clear_ipv46_entries(struct ns2_sns_state *gss)
@@ -715,6 +728,7 @@
if (old_state != GPRS_SNS_ST_UNCONFIGURED)
ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_FAILURE);
+ gss->alive = false;
ns2_clear_ipv46_entries(gss);
/* no initial available */
@@ -873,6 +887,18 @@
}
}
+/* calculate the timeout of the configured state. the configured
+ * state will fail if not at least one NS-VC is alive within X second.
+ */
+static inline int ns_sns_configured_timeout(struct osmo_fsm_inst *fi)
+{
+ int secs;
+ struct gprs_ns2_inst *nsi = nse_inst_from_fi(fi)->nsi;
+ secs = nsi->timeout[NS_TOUT_TNS_ALIVE] * nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES];
+ secs += nsi->timeout[NS_TOUT_TNS_TEST];
+
+ return secs;
+}
static void ns_sns_st_config_sgsn_ip4(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
@@ -917,7 +943,7 @@
ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
/* start the test procedure on ALL NSVCs! */
gprs_ns2_start_alive_all_nsvcs(nse);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, ns_sns_configured_timeout(fi), 3);
} else {
/* just send CONFIG-ACK */
ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
@@ -1259,6 +1285,9 @@
case GPRS_SNS_EV_CHANGE_WEIGHT:
ns2_sns_st_configured_change(fi, gss, tp);
break;
+ case GPRS_SNS_EV_NSVC_ALIVE:
+ osmo_timer_del(&fi->timer);
+ break;
}
}
@@ -1307,7 +1336,8 @@
[GPRS_SNS_ST_CONFIGURED] = {
.in_event_mask = S(GPRS_SNS_EV_ADD) |
S(GPRS_SNS_EV_DELETE) |
- S(GPRS_SNS_EV_CHANGE_WEIGHT),
+ S(GPRS_SNS_EV_CHANGE_WEIGHT) |
+ S(GPRS_SNS_EV_NSVC_ALIVE),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
S(GPRS_SNS_ST_SIZE),
.name = "CONFIGURED",
@@ -1340,6 +1370,10 @@
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
}
break;
+ case 3:
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint.\n", nse->nsei);
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SELECT_ENDPOINT, NULL);
+ break;
}
return 0;
}
@@ -1709,6 +1743,46 @@
return count;
}
+void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive)
+{
+ struct ns2_sns_state *gss;
+ struct gprs_ns2_vc *tmp;
+
+ if (!nse->bss_sns_fi)
+ return;
+
+ gss = nse->bss_sns_fi->priv;
+ if(nse->bss_sns_fi->state != GPRS_SNS_ST_CONFIGURED)
+ return;
+
+ if (alive == gss->alive)
+ return;
+
+ /* check if this is the current SNS NS-VC */
+ if (nsvc == gss->sns_nsvc) {
+ /* only replace the SNS NS-VC if there are other alive NS-VC.
+ * There aren't any other alive NS-VC when the SNS fsm just reached CONFIGURED
+ * and couldn't confirm yet if the NS-VC comes up */
+ if (gss->alive && !alive)
+ ns2_sns_replace_nsvc(nsvc);
+ }
+
+ if (alive) {
+ gss->alive = true;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_NSVC_ALIVE, NULL);
+ } else {
+ /* is there at least another alive nsvc? */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(tmp))
+ return;
+ }
+
+ /* all NS-VC have failed */
+ gss->alive = false;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_NO_NSVC, NULL);
+ }
+}
+
/* initialize osmo_ctx on main tread */
static __attribute__((constructor)) void on_dso_load_ctx(void)
{