NS: Add support for GPRS NS IP Sub-Network-Service (SNS)

The NS implementation part of the Gb implementation libosmogb
so far implemented a rather classic dialect of Gb, with lots of
heritage to FR (Frame Relay) transports.  At least since Release 6
of the NS specification, there's an IP Sub-Network Service (SNS),
which
* permits for dynamic configuration of IP endpoints and their NS-VCs
* abandons the concept of a NSVCI on IP transport
* forbids the use of RESET/BLOCK/UNBLOCK procedures on IP transport

This commit introduces BSS-side IP-SNS support to libosmogb in a
minimally invasive way.  It adds a corresponding SNS FSM to each NS
instance, and implements the new SIZE/CONFIG/ADD/DELETE/CHANGE_WEIGHT
procedures very closely aligned with the spec.

In order to use the SNS flavor (rather than the classic one),
a BSS implementation should use gprs_ns_nsip_connect_sns() instead
of the existing gprs_ns_nsip_connect().

This implementation comes with a set of TTCN-3 tests in
PCU_Tests_RAW_SNS.ttcn, see Change-ID
I0fe3d4579960bab0494c294ec7ab8032feed4fb2 of osmo-ttcn3-hacks.git

Closes: OS#3372
Closes: OS#3617
Change-Id: I84786c3b43a8ae34ef3b3ba84b33c90042d234ea
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index ad68bc9..33de2c1 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -72,6 +72,7 @@
 #include <sys/socket.h>
 #include <arpa/inet.h>
 
+#include <osmocom/core/fsm.h>
 #include <osmocom/core/msgb.h>
 #include <osmocom/core/byteswap.h>
 #include <osmocom/gsm/tlv.h>
@@ -93,6 +94,18 @@
 #define ns_set_remote_state(ns_, st_) ns_set_state_with_log(ns_, st_, true, __FILE__, __LINE__)
 #define ns_mark_blocked(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_BLOCKED)
 #define ns_mark_unblocked(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_BLOCKED));
+#define ns_mark_alive(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_ALIVE)
+#define ns_mark_dead(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_ALIVE));
+
+#define ERR_IF_NSVC_USES_SNS(nsvc, reason) 							\
+	do {											\
+		if ((nsvc)->nsi->bss_sns_fi) {							\
+			LOGP(DNS, LOGL_ERROR, "NSEI=%u Asked to %s. Rejected on IP-SNS\n",	\
+				nsvc->nsei, reason);						\
+			osmo_log_backtrace(DNS, LOGL_ERROR);					\
+			return -EIO;								\
+		}										\
+	} while (0)
 
 static const struct tlv_definition ns_att_tlvdef = {
 	.def = {
@@ -107,6 +120,9 @@
 		[NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 },
 		[NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 },
 		[NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 },
+		/* TODO: IP_ADDR can be 5 or 17 bytes long, depending on first byte. This
+		 * cannot be expressed in our TLV parser.  However, we don't do IPv6 anyway */
+		[NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 },
 	},
 };
 
@@ -171,6 +187,7 @@
 	{ S_NS_ALIVE_EXP,	"NS-ALIVE expired" },
 	{ S_NS_REPLACED,	"NSVC replaced" },
 	{ S_NS_MISMATCH,	"Unexpected IE" },
+	{ S_SNS_CONFIGURED,	"SNS Configured" },
 	{ 0, NULL }
 };
 
@@ -237,11 +254,20 @@
 	return NULL;
 }
 
+/*! Determine active NS-VC for given NSEI + BVCI.
+ *  Use this function to determine which of the NS-VCs inside the NS Instance
+ *  shall be used to transmit data for given NSEI + BVCI */
 static struct gprs_nsvc *gprs_active_nsvc_by_nsei(struct gprs_ns_inst *nsi,
-						  uint16_t nsei)
+						  uint16_t nsei, uint16_t bvci)
 {
 	struct gprs_nsvc *nsvc;
 	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		/* if signalling BVCI, skip any NSVC with signalling weight == 0 */
+		if (bvci == 0 && nsvc->sig_weight == 0)
+			continue;
+		/* if point-to-point BVCI, skip any NSVC with data weight == 0 */
+		if (bvci != 0 && nsvc->data_weight == 0)
+			continue;
 		if (nsvc->nsei == nsei) {
 			if (!(nsvc->state & NSE_S_BLOCKED) &&
 			    nsvc->state & NSE_S_ALIVE)
@@ -251,9 +277,11 @@
 	return NULL;
 }
 
-/* Lookup struct gprs_nsvc based on remote peer socket addr */
-static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi,
-					  struct sockaddr_in *sin)
+/*! Lookup NS-VC based on specified remote peer socket addr.
+ *  \param[in] nsi NS Instance within which we shall look up the NS-VC
+ *  \param[in] sin Remote peer Socket Address (IP + UDP Port)
+ *  \returns NS-VC matching the given peer; NULL in case of none */
+struct gprs_nsvc *gprs_nsvc_by_rem_addr(struct gprs_ns_inst *nsi, const struct sockaddr_in *sin)
 {
 	struct gprs_nsvc *nsvc;
 	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
@@ -267,27 +295,52 @@
 
 static void gprs_ns_timer_cb(void *data);
 
-struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+/*! Create a new NS-VC (Virtual Circuit) within given instance
+ *  \param[in] nsi NS Instance in which to create the NSVC
+ *  \param[in] nsvci] NS Virtual Connection Identifier for this NSVC
+ *  \param[in] sig_weight Signalling Weight of this NS-VC. Use "0" for no signalling
+ *  \param[in] data_weight Data WEight of this NS-VC. Use "0" for no data
+ *  \returns newly-created gprs_nsvc within nsi. NULL on error. */
+struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
+				    uint8_t sig_weight, uint8_t data_weight)
 {
 	struct gprs_nsvc *nsvc;
 
+	if (gprs_nsvc_by_nsvci(nsi, nsvci)) {
+		LOGP(DNS, LOGL_ERROR, "Cannot create NS-VC for already-existing NSVCI=%u\n", nsvci);
+		return NULL;
+	}
+
 	LOGP(DNS, LOGL_INFO, "NSVCI=%u Creating NS-VC\n", nsvci);
 
 	nsvc = talloc_zero(nsi, struct gprs_nsvc);
+	if (!nsvc)
+		return NULL;
 	nsvc->nsvci = nsvci;
 	nsvc->nsvci_is_valid = 1;
 	/* before RESET procedure: BLOCKED and DEAD */
-	ns_set_state(nsvc, NSE_S_BLOCKED);
+	if (nsi->bss_sns_fi)
+		ns_set_state(nsvc, 0);
+	else
+		ns_set_state(nsvc, NSE_S_BLOCKED);
 	nsvc->nsi = nsi;
 	osmo_timer_setup(&nsvc->timer, gprs_ns_timer_cb, nsvc);
 	nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci);
 	nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, nsvci);
+	nsvc->sig_weight = sig_weight;
+	nsvc->data_weight = data_weight;
 
 	llist_add(&nsvc->list, &nsi->gprs_nsvcs);
 
 	return nsvc;
 }
 
+/*! Old API for creating a NS-VC. Uses gprs_nsvc_create2 with fixed weights. */
+struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+{
+	return gprs_nsvc_create2(nsi, nsvci, 1, 1);
+}
+
 /*! Delete given NS-VC
  *  \param[in] nsvc gprs_nsvc to be deleted
  */
@@ -450,13 +503,16 @@
  */
 int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
 {
-	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct msgb *msg;
 	struct gprs_ns_hdr *nsh;
 	uint16_t nsvci = osmo_htons(nsvc->nsvci);
 	uint16_t nsei = osmo_htons(nsvc->nsei);
 
 	log_set_context(LOG_CTX_GB_NSVC, nsvc);
 
+	ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
+
+	msg = gprs_ns_msgb_alloc();
 	if (!msg)
 		return -ENOMEM;
 
@@ -536,12 +592,15 @@
  */
 int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
 {
-	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct msgb *msg;
 	struct gprs_ns_hdr *nsh;
 	uint16_t nsvci = osmo_htons(nsvc->nsvci);
 
 	log_set_context(LOG_CTX_GB_NSVC, nsvc);
 
+	ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
+
+	msg = gprs_ns_msgb_alloc();
 	if (!msg)
 		return -ENOMEM;
 
@@ -574,6 +633,8 @@
 
 	log_set_context(LOG_CTX_GB_NSVC, nsvc);
 
+	ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
+
 	msg = gprs_ns_msgb_alloc();
 	if (!msg)
 		return -ENOMEM;
@@ -597,6 +658,9 @@
 int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc)
 {
 	log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+	ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK");
+
 	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
 		nsvc->nsei, nsvc->nsvci);
 
@@ -687,16 +751,21 @@
 		nsvc->alive_retries++;
 		if (nsvc->alive_retries >
 			nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
-			/* mark as dead and blocked */
-			ns_set_state(nsvc, NSE_S_BLOCKED);
-			rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+			/* mark as dead (and blocked unless IP-SNS) */
 			rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+			if (!nsvc->nsi->bss_sns_fi) {
+				ns_set_state(nsvc, NSE_S_BLOCKED);
+				rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+			} else
+				ns_set_state(nsvc, 0);
 			LOGP(DNS, LOGL_NOTICE,
 				"NSEI=%u Tns-alive expired more then "
 				"%u times, blocking NS-VC\n", nsvc->nsei,
 				nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]);
 			ns_osmo_signal_dispatch(nsvc, S_NS_ALIVE_EXP, 0);
-			ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
+			/* FIXME: should we send this signal in case of SNS? */
+			if (!nsvc->nsi->bss_sns_fi)
+				ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
 			return;
 		}
 		/* Tns-test case: send NS-ALIVE PDU */
@@ -733,11 +802,15 @@
 /* Section 9.2.6 */
 static int gprs_ns_tx_reset_ack(struct gprs_nsvc *nsvc)
 {
-	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct msgb *msg;
 	struct gprs_ns_hdr *nsh;
 	uint16_t nsvci, nsei;
 
 	log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+	ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
+
+	msg = gprs_ns_msgb_alloc();
 	if (!msg)
 		return -ENOMEM;
 
@@ -777,6 +850,13 @@
 	if (!msg)
 		return -ENOMEM;
 
+	if (!nsvc->nsi->bss_sns_fi) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+		     nsvc->nsei);
+		msgb_free(msg);
+		return -EIO;
+	}
+
 	nsei = osmo_htons(nsvc->nsei);
 
 	msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -815,6 +895,13 @@
 	if (!msg)
 		return -ENOMEM;
 
+	if (!nsvc->nsi->bss_sns_fi) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+		     nsvc->nsei);
+		msgb_free(msg);
+		return -EIO;
+	}
+
 	nsei = osmo_htons(nsvc->nsei);
 
 	msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -847,6 +934,13 @@
 	if (!msg)
 		return -ENOMEM;
 
+	if (!nsvc->nsi->bss_sns_fi) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+		     nsvc->nsei);
+		msgb_free(msg);
+		return -EIO;
+	}
+
 	nsei = osmo_htons(nsvc->nsei);
 
 	msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -880,6 +974,13 @@
 	if (!msg)
 		return -ENOMEM;
 
+	if (!nsvc->nsi->bss_sns_fi) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+		     nsvc->nsei);
+		msgb_free(msg);
+		return -EIO;
+	}
+
 	nsei = osmo_htons(nsvc->nsei);
 
 	msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -912,6 +1013,13 @@
 	if (!msg)
 		return -ENOMEM;
 
+	if (!nsvc->nsi->bss_sns_fi) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+		     nsvc->nsei);
+		msgb_free(msg);
+		return -EIO;
+	}
+
 	nsei = osmo_htons(nsvc->nsei);
 
 	msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -942,7 +1050,7 @@
 	struct gprs_ns_hdr *nsh;
 	uint16_t bvci = msgb_bvci(msg);
 
-	nsvc = gprs_active_nsvc_by_nsei(nsi, msgb_nsei(msg));
+	nsvc = gprs_active_nsvc_by_nsei(nsi, msgb_nsei(msg), msgb_bvci(msg));
 	if (!nsvc) {
 		int rc;
 		if (gprs_nsvc_by_nsei(nsi, msgb_nsei(msg))) {
@@ -1358,7 +1466,7 @@
 	int rc = 0;
 
 	/* look up the NSVC based on source address */
-	nsvc = nsvc_by_rem_addr(nsi, saddr);
+	nsvc = gprs_nsvc_by_rem_addr(nsi, saddr);
 
 	if (!nsvc) {
 		struct gprs_nsvc *fallback_nsvc;
@@ -1572,6 +1680,7 @@
 			struct gprs_nsvc **nsvc)
 {
 	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
 	int rc = 0;
 
 	msgb_nsei(msg) = (*nsvc)->nsei;
@@ -1594,6 +1703,7 @@
 			rc = gprs_ns_tx_alive_ack(*nsvc);
 		break;
 	case NS_PDUT_ALIVE_ACK:
+		ns_mark_alive(*nsvc);
 		if ((*nsvc)->timer_mode == NSVC_TIMER_TNS_ALIVE)
 			osmo_stat_item_set((*nsvc)->statg->items[NS_STAT_ALIVE_DELAY],
 				nsvc_timer_elapsed_ms(*nsvc));
@@ -1648,15 +1758,68 @@
 		/* mark remote NS-VC as blocked + active */
 		ns_set_remote_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
 		break;
+	case SNS_PDUT_CONFIG:
+		if (!nsi->bss_sns_fi)
+			goto unexpected_sns;
+		/* one additional byte ('end flag') before the TLV part starts */
+		rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data+1,
+				msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+		if (rc < 0) {
+			LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+			return rc;
+		}
+		/* All sub-network service related message types */
+		rc = gprs_ns_rx_sns(nsi, msg, &tp);
+		break;
+	case SNS_PDUT_ACK:
+	case SNS_PDUT_ADD:
+	case SNS_PDUT_CHANGE_WEIGHT:
+	case SNS_PDUT_DELETE:
+		if (!nsi->bss_sns_fi)
+			goto unexpected_sns;
+		/* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
+		rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data+5,
+				msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0);
+		if (rc < 0) {
+			LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+			return rc;
+		}
+		tp.lv[NS_IE_NSEI].val = nsh->data+2;
+		tp.lv[NS_IE_NSEI].len = 2;
+		tp.lv[NS_IE_TRANS_ID].val = nsh->data+4;
+		tp.lv[NS_IE_TRANS_ID].len = 1;
+		rc = gprs_ns_rx_sns(nsi, msg, &tp);
+		break;
+	case SNS_PDUT_CONFIG_ACK:
+	case SNS_PDUT_SIZE:
+	case SNS_PDUT_SIZE_ACK:
+		if (!nsi->bss_sns_fi)
+			goto unexpected_sns;
+		rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+				msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+		if (rc < 0) {
+			LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+			return rc;
+		}
+		/* All sub-network service related message types */
+		rc = gprs_ns_rx_sns(nsi, msg, &tp);
+		break;
 	default:
 		LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx Unknown NS PDU type 0x%02x\n",
 			(*nsvc)->nsei, nsh->pdu_type);
 		rc = -EINVAL;
 		break;
+unexpected_sns:
+		LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx %s for NS Instance that has no SNS!\n",
+			(*nsvc)->nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+		rc = -EINVAL;
+		break;
 	}
 	return rc;
 }
 
+static bool gprs_sns_fsm_registered = false;
+
 /*! Create a new GPRS NS instance
  *  \param[in] cb Call-back function for incoming BSSGP data
  *  \returns dynamically allocated gprs_ns_inst
@@ -1665,6 +1828,11 @@
 {
 	struct gprs_ns_inst *nsi = talloc_zero(ctx, struct gprs_ns_inst);
 
+	if (!gprs_sns_fsm_registered) {
+		gprs_sns_init();
+		gprs_sns_fsm_registered = true;
+	}
+
 	nsi->cb = cb;
 	INIT_LLIST_HEAD(&nsi->gprs_nsvcs);
 	nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
@@ -1674,6 +1842,7 @@
 	nsi->timeout[NS_TOUT_TNS_TEST] = 30;
 	nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
 	nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
+	nsi->timeout[NS_TOUT_TSNS_PROV] = 3; /* 1..10 */
 
 	/* Create the dummy NSVC that we use for sending
 	 * messages to non-existant/unknown NS-VC's */
@@ -1875,6 +2044,8 @@
 {
 	int rc;
 
+	ERR_IF_NSVC_USES_SNS(nsvc, "RESET procedure based on API request");
+
 	LOGP(DNS, LOGL_INFO, "NSEI=%u RESET procedure based on API request\n",
 		nsvc->nsei);
 
@@ -1909,7 +2080,7 @@
 {
 	struct gprs_nsvc *nsvc;
 
-	nsvc = nsvc_by_rem_addr(nsi, dest);
+	nsvc = gprs_nsvc_by_rem_addr(nsi, dest);
 	if (!nsvc)
 		nsvc = gprs_nsvc_create(nsi, nsvci);
 	nsvc->ip.bts_addr = *dest;
@@ -1920,6 +2091,47 @@
 	return nsvc;
 }
 
+/*! Establish a NS connection (from the BSS) to the SGSN using SNS auto-configuration
+ *  \param nsi NS-instance
+ *  \param[in] dest Destination IP/Port
+ *  \param[in] nsei NSEI of the to-be-established NS-VC
+ *  \param[in] nsvci NSVCI of the to-be-established NS-VC
+ *  \returns struct gprs_nsvc representing the new NS-VC
+ *
+ * This function will establish a single NS/UDP/IP connection in uplink
+ * (BSS to SGSN) direction.  It will start with the SNS-SIZE procedure,
+ * followed by BSS-originated SNS-CONFIG, then SGSN-originated SNS-CONFIG.
+ *
+ * Once configuration completes, the user will be notified by the S_SNS_CONFIGURED signal,
+ * at which point he typically would want to initiate NS-RESET by means of gprs_nsvc_reset().
+ */
+struct gprs_nsvc *gprs_ns_nsip_connect_sns(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	/* FIXME: We are getting the order wrong here.  Normally, one would want
+	 * to start the SNS FSM *before* creating any NS-VC and then create the NS-VC
+	 * after the SNS layer has established the IP/port/etc.  However, this would
+	 * require some massive code and API changes compared to existing libosmogb,
+	 * so let's keep the old logic. */
+	nsvc = gprs_nsvc_by_rem_addr(nsi, dest);
+	if (!nsvc)
+		nsvc = gprs_nsvc_create(nsi, nsvci);
+	nsvc->ip.bts_addr = *dest;
+	nsvc->nsei = nsei;
+	nsvc->remote_end_is_sgsn = 1;
+	/* NSVCs are always UNBLOCKED in IP-SNS */
+	ns_set_state(nsvc, 0);
+
+	if (nsi->bss_sns_fi)
+		osmo_fsm_inst_term(nsi->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+	nsi->bss_sns_fi = gprs_sns_bss_fsm_alloc(nsi, nsvc, "NSIP");
+	gprs_sns_bss_fsm_start(nsi);
+	return nsvc;
+}
+
 void gprs_ns_set_log_ss(int ss)
 {
 	DNS = ss;
@@ -1954,4 +2166,13 @@
 	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
 }
 
+void gprs_start_alive_all_nsvcs(struct gprs_ns_inst *nsi)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		/* start the test procedure */
+		gprs_nsvc_start_test(nsvc);
+	}
+}
+
 /*! @} */