Change in libosmocore[master]: WIP: Add support for GPRS NS IP Sub-Network-Service (SNS)

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Harald Welte gerrit-no-reply at lists.osmocom.org
Fri Feb 22 22:33:13 UTC 2019


Harald Welte has uploaded this change for review. ( https://gerrit.osmocom.org/13014


Change subject: WIP: Add support for GPRS NS IP Sub-Network-Service (SNS)
......................................................................

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

Related: OS#3372
Change-Id: I84786c3b43a8ae34ef3b3ba84b33c90042d234ea
---
M include/osmocom/gprs/gprs_msgb.h
M include/osmocom/gprs/gprs_ns.h
M include/osmocom/gprs/protocol/gsm_08_16.h
M src/gb/Makefile.am
M src/gb/gb_internal.h
M src/gb/gprs_ns.c
A src/gb/gprs_ns_sns.c
M src/gb/gprs_ns_vty.c
M src/gb/libosmogb.map
9 files changed, 895 insertions(+), 24 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/14/13014/1

diff --git a/include/osmocom/gprs/gprs_msgb.h b/include/osmocom/gprs/gprs_msgb.h
index 43471e2..3f48b5b 100644
--- a/include/osmocom/gprs/gprs_msgb.h
+++ b/include/osmocom/gprs/gprs_msgb.h
@@ -3,19 +3,19 @@
 #pragma once
 
 #include <stdint.h>
-/* the data structure stored in msgb->cb for libgb apps */
+/*! the data structure stored in msgb->cb for libgb apps */
 struct libgb_msgb_cb {
 	unsigned char *bssgph;
 	unsigned char *llch;
 
-	/* Cell Identifier */
+	/*! Cell Identifier */
 	unsigned char *bssgp_cell_id;
 
-	/* Identifiers of a BTS, equal to 'struct bssgp_bts_ctx' */
+	/*! Identifiers of a BTS, equal to 'struct bssgp_bts_ctx' */
 	uint16_t nsei;
 	uint16_t bvci;
 
-	/* Identifier of a MS (inside BTS), equal to 'struct sgsn_mm_ctx' */
+	/*! Identifier of a MS (inside BTS), equal to 'struct sgsn_mm_ctx' */
 	uint32_t tlli;
 } __attribute__((packed, may_alias));
 #define LIBGB_MSGB_CB(__msgb)	((struct libgb_msgb_cb *)&((__msgb)->cb[0]))
diff --git a/include/osmocom/gprs/gprs_ns.h b/include/osmocom/gprs/gprs_ns.h
index 1c99ed0..c62ef98 100644
--- a/include/osmocom/gprs/gprs_ns.h
+++ b/include/osmocom/gprs/gprs_ns.h
@@ -14,8 +14,8 @@
 
 #include <osmocom/gprs/protocol/gsm_08_16.h>
 
-#define NS_TIMERS_COUNT 7
-#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries)"
+#define NS_TIMERS_COUNT 8
+#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov)"
 #define NS_TIMERS_HELP	\
 	"(un)blocking Timer (Tns-block) timeout\n"		\
 	"(un)blocking Timer (Tns-block) number of retries\n"	\
@@ -23,7 +23,8 @@
 	"Reset Timer (Tns-reset) number of retries\n"		\
 	"Test Timer (Tns-test) timeout\n"			\
 	"Alive Timer (Tns-alive) timeout\n"			\
-	"Alive Timer (Tns-alive) number of retries\n"
+	"Alive Timer (Tns-alive) number of retries\n"		\
+	"SNS Provision Timer (Tsns-prov) timeout\n"
 
 /* Educated guess - LLC user payload is 1500 bytes plus possible headers */
 #define NS_ALLOC_SIZE	3072
@@ -37,6 +38,7 @@
 	NS_TOUT_TNS_TEST,
 	NS_TOUT_TNS_ALIVE,
 	NS_TOUT_TNS_ALIVE_RETRIES,
+	NS_TOUT_TSNS_PROV,
 };
 
 #define NSE_S_BLOCKED	0x0001
@@ -102,6 +104,8 @@
 		uint32_t local_ip;
 		unsigned int enabled:1;
 	} frgre;
+
+	struct osmo_fsm_inst *bss_sns_fi;
 };
 
 enum nsvc_timer_mode {
@@ -150,6 +154,10 @@
 			struct sockaddr_in bts_addr;
 		} frgre;
 	};
+	/*! signalling weight. 0 = don't use for signalling (BVCI == 0)*/
+	uint8_t sig_weight;
+	/*! signaling weight. 0 = don't use for user data (BVCI != 0) */
+	uint8_t data_weight;
 };
 
 /* Create a new NS protocol instance */
@@ -169,6 +177,9 @@
 					struct sockaddr_in *dest,
 					uint16_t nsei, uint16_t nsvci);
 
+/* Establish a connection (from the BSS) to the SGSN using IP SNS */
+struct gprs_nsvc *gprs_ns_nsip_connect_sns(struct gprs_ns_inst *nsi, struct sockaddr_in *dest,
+					   uint16_t nsei, uint16_t nsvci);
 
 struct sockaddr_in;
 
@@ -185,9 +196,12 @@
 int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi);
 
 struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci);
+struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
+				    uint8_t sig_weight, uint8_t data_weight);
 void gprs_nsvc_delete(struct gprs_nsvc *nsvc);
 struct gprs_nsvc *gprs_nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei);
 struct gprs_nsvc *gprs_nsvc_by_nsvci(struct gprs_ns_inst *nsi, uint16_t nsvci);
+struct gprs_nsvc *gprs_nsvc_by_rem_addr(struct gprs_ns_inst *nsi, const struct sockaddr_in *sin);
 
 /* Initiate a RESET procedure (including timer start, ...)*/
 int gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause);
@@ -213,6 +227,7 @@
 	S_NS_ALIVE_EXP,	/* Tns-alive expired more than N times */
 	S_NS_REPLACED, /* nsvc object is replaced (sets old_nsvc) */
 	S_NS_MISMATCH, /* got an unexpected IE (sets msg, pdu_type, ie_type) */
+	S_SNS_CONFIGURED, /* IP-SNS configuration completed */
 };
 
 extern const struct value_string gprs_ns_signal_ns_names[];
diff --git a/include/osmocom/gprs/protocol/gsm_08_16.h b/include/osmocom/gprs/protocol/gsm_08_16.h
index 15d92d3..95efcb6 100644
--- a/include/osmocom/gprs/protocol/gsm_08_16.h
+++ b/include/osmocom/gprs/protocol/gsm_08_16.h
@@ -66,6 +66,7 @@
 	NS_IE_IPv6_EP_NR	= 0x09,
 	NS_IE_RESET_FLAG	= 0x0a,
 	NS_IE_IP_ADDR		= 0x0b,
+	NS_IE_TRANS_ID	 	= 0xff,	/* osmocom. Spec has this IE but without IEI! */
 };
 
 /*! NS Cause (TS 08.16, Section 10.3.2, Table 13) */
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index d074092..3180f9c 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -17,7 +17,7 @@
 		$(top_builddir)/src/vty/libosmovty.la \
 		$(top_builddir)/src/gsm/libosmogsm.la
 
-libosmogb_la_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c \
+libosmogb_la_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c gprs_ns_sns.c \
 		  gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c \
 		  gprs_bssgp_bss.c common_vty.c
 endif
diff --git a/src/gb/gb_internal.h b/src/gb/gb_internal.h
index c1fa8e1..d9eb0e0 100644
--- a/src/gb/gb_internal.h
+++ b/src/gb/gb_internal.h
@@ -4,6 +4,16 @@
 #include <osmocom/gsm/tlv.h>
 #include <osmocom/gprs/gprs_ns.h>
 
+/* gprs_ns_sns.c */
+int gprs_ns_rx_sns(struct gprs_ns_inst *nsi, struct msgb *msg, struct tlv_parsed *tp);
+
+struct osmo_fsm_inst *gprs_sns_bss_fsm_alloc(void *ctx, struct gprs_nsvc *nsvc, const char *id);
+int gprs_sns_bss_fsm_start(struct gprs_ns_inst *nsi);
+
+int gprs_sns_init(void);
+
+/* gprs_ns.c */
+void gprs_start_alive_all_nsvcs(struct gprs_ns_inst *nsi);
 int gprs_ns_tx_sns_ack(struct gprs_nsvc *nsvc, uint8_t trans_id, uint8_t *cause,
 		       const struct gprs_ns_ie_ip4_elem *ip4_elems,unsigned int num_ip4_elems);
 
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index 816d2db..8be6146 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -71,6 +71,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>
@@ -92,6 +93,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 = {
@@ -106,6 +119,7 @@
 		[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 },
+		[NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 4 },
 	},
 };
 
@@ -170,6 +184,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 }
 };
 
@@ -236,11 +251,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)
@@ -250,9 +274,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) {
@@ -266,27 +292,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
  */
@@ -442,13 +493,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;
 
@@ -528,12 +582,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;
 
@@ -566,6 +623,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;
@@ -589,6 +648,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);
 
@@ -679,16 +741,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 */
@@ -725,11 +792,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;
 
@@ -769,6 +840,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));
@@ -807,6 +885,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));
@@ -839,6 +924,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));
@@ -872,6 +964,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));
@@ -904,6 +1003,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));
@@ -934,7 +1040,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))) {
@@ -1352,7 +1458,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;
@@ -1566,6 +1672,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;
@@ -1588,6 +1695,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));
@@ -1642,15 +1750,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
@@ -1659,6 +1820,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;
@@ -1668,6 +1834,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 */
@@ -1869,6 +2036,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);
 
@@ -1903,7 +2072,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;
@@ -1914,6 +2083,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;
@@ -1941,4 +2151,15 @@
 	return s;
 }
 
+/*! Start the ALIVE timer procedure in all NS-VCs part of this NS Instance */
+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_ns_tx_alive(nsvc);
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
+	}
+}
+
 /*! @} */
diff --git a/src/gb/gprs_ns_sns.c b/src/gb/gprs_ns_sns.c
new file mode 100644
index 0000000..159e136
--- /dev/null
+++ b/src/gb/gprs_ns_sns.c
@@ -0,0 +1,617 @@
+/* Implementation of 3GPP TS 48.016 NS IP Sub-Network Service */
+/* (C) 2018 by Harald Welte <laforge at gnumonks.org> */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights.  In theory, the BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns.h>
+
+#include "common_vty.h"
+#include "gb_internal.h"
+
+#define S(x)	(1 << (x))
+
+struct gprs_sns_state {
+	struct gprs_ns_inst *nsi;
+	struct gprs_nsvc *nsvc_hack;
+
+	/* local configuration to send to the remote end */
+	struct gprs_ns_ie_ip4_elem *ip4_local;
+	size_t num_ip4_local;
+
+	/* local configuration about our capabilities in terms of connections to
+	 * remote (SGSN) side */
+	size_t num_max_nsvcs;
+	size_t num_max_ip4_remote;
+
+	/* remote configuration as received */
+	struct gprs_ns_ie_ip4_elem *ip4_remote;
+	unsigned int num_ip4_remote;
+};
+
+static inline struct gprs_ns_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	return gss->nsi;
+}
+
+/* helper function to compute the sum of all (data or signaling) weights */
+static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
+			  bool data_weight)
+{
+	unsigned int i;
+	int weight_sum = 0;
+
+	for (i = 0; i < num; i++) {
+		if (data_weight)
+			weight_sum += ip4[i].data_weight;
+		else
+			weight_sum += ip4[i].sig_weight;
+	}
+	return weight_sum;
+}
+#define ip4_weight_sum_data(x,y)	ip4_weight_sum(x, y, true)
+#define ip4_weight_sum_sig(x,y)		ip4_weight_sum(x, y, false)
+
+static struct gprs_nsvc *nsvc_by_ip4_elem(struct gprs_ns_inst *nsi,
+					  const struct gprs_ns_ie_ip4_elem *ip4)
+{
+	struct sockaddr_in sin;
+	/* copy over. Both data structures use network byte order */
+	sin.sin_addr.s_addr = ip4->ip_addr;
+	sin.sin_port = ip4->udp_port;
+	return gprs_nsvc_by_rem_addr(nsi, &sin);
+}
+
+static struct gprs_nsvc *gprs_nsvc_create_ip4(struct gprs_ns_inst *nsi,
+					      const struct gprs_ns_ie_ip4_elem *ip4)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) nsi->bss_sns_fi->priv;
+	struct gprs_nsvc *nsvc;
+	struct sockaddr_in sin;
+	/* copy over. Both data structures use network byte order */
+	sin.sin_addr.s_addr = ip4->ip_addr;
+	sin.sin_port = ip4->udp_port;
+
+	nsvc = gprs_nsvc_create2(nsi, 0xFFFF, ip4->sig_weight, ip4->data_weight);
+	if (!nsvc)
+		return NULL;
+
+	nsvc->nsei = gss->nsvc_hack->nsei;
+	nsvc->nsvci_is_valid = 0;
+	nsvc->ip.bts_addr = sin;
+
+	return nsvc;
+}
+
+static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	unsigned int i;
+
+	for (i = 0; i < gss->num_ip4_remote; i++) {
+		const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[i];
+		struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+		if (!nsvc) {
+			/* create, if it doesn't exist */
+			nsvc = gprs_nsvc_create_ip4(nsi, ip4);
+			if (!nsvc) {
+				LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
+				continue;
+			}
+		} else {
+			/* update data / signalling weight */
+			nsvc->data_weight = ip4->data_weight;
+			nsvc->sig_weight = ip4->sig_weight;
+		}
+		LOGPFSML(fi, LOGL_INFO, "NS-VC %s data_weight=%u, sig_weight=%u\n",
+			 gprs_ns_ll_str(nsvc), nsvc->data_weight, nsvc->sig_weight);
+	}
+
+	return 0;
+}
+
+static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+	if (!nsvc)
+		return -ENODEV;
+	nsvc->data_weight = ip4->data_weight;
+	nsvc->sig_weight = ip4->sig_weight;
+	LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight=%u, sig_weight=%u\n",
+		 gprs_ns_ll_str(nsvc), nsvc->data_weight, nsvc->sig_weight);
+	return 0;
+}
+
+static int do_sns_add(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+	if (nsvc)
+		return -EEXIST;
+
+	nsvc = gprs_nsvc_create_ip4(nsi, ip4);
+	if (!nsvc) {
+		LOGPFSML(fi, LOGL_ERROR, "SNS-ADD: Failed to create NSVC\n");
+		return -1;
+	}
+
+	LOGPFSML(fi, LOGL_INFO, "ADD NS-VC %s data_weight=%u, sig_weight=%u\n",
+		 gprs_ns_ll_str(nsvc), nsvc->data_weight, nsvc->sig_weight);
+	return 0;
+}
+
+static int do_sns_delete(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+	if (!nsvc)
+		return -ENODEV;
+	LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns_ll_str(nsvc));
+	gprs_nsvc_delete(nsvc);
+	/* FIXME: remove from gss->ip4_remote */
+	return 0;
+}
+
+
+/***********************************************************************
+ * BSS-side FSM for IP Sub-Network Service
+ ***********************************************************************/
+
+enum gprs_sns_bss_state {
+	GPRS_SNS_ST_UNCONFIGURED,
+	GPRS_SNS_ST_SIZE,		/*!< SNS-SIZE procedure ongoing */
+	GPRS_SNS_ST_CONFIG_BSS,		/*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
+	GPRS_SNS_ST_CONFIG_SGSN,	/*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
+	GPRS_SNS_ST_CONFIGURED,
+};
+
+enum gprs_sns_event {
+	GPRS_SNS_EV_START,
+	GPRS_SNS_EV_SIZE,
+	GPRS_SNS_EV_SIZE_ACK,
+	GPRS_SNS_EV_CONFIG,
+	GPRS_SNS_EV_CONFIG_END,		/*!< SNS-CONFIG with end flag received */
+	GPRS_SNS_EV_CONFIG_ACK,
+	GPRS_SNS_EV_ADD,
+	GPRS_SNS_EV_DELETE,
+	GPRS_SNS_EV_CHANGE_WEIGHT,
+};
+
+static const struct value_string gprs_sns_event_names[] = {
+	{ GPRS_SNS_EV_START, 		"START" },
+	{ GPRS_SNS_EV_SIZE,		"SIZE" },
+	{ GPRS_SNS_EV_SIZE_ACK,		"SIZE_ACK" },
+	{ GPRS_SNS_EV_CONFIG,		"CONFIG" },
+	{ GPRS_SNS_EV_CONFIG_END,	"CONFIG_END" },
+	{ GPRS_SNS_EV_CONFIG_ACK,	"CONFIG_ACK" },
+	{ 0, NULL }
+};
+
+static void gprs_sns_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	switch (event) {
+	case GPRS_SNS_EV_START:
+		osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void gprs_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	struct tlv_parsed *tp = NULL;
+
+	switch (event) {
+	case GPRS_SNS_EV_SIZE_ACK:
+		tp = data;
+		if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+			LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n",
+				 gprs_ns_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+			/* FIXME: What to do? */
+		} else {
+			osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS,
+						nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+		}
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+static void gprs_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	uint16_t num_max_ip4_remote = gss->num_max_ip4_remote;
+
+	gprs_ns_tx_sns_size(gss->nsvc_hack, true, gss->num_max_nsvcs, &num_max_ip4_remote, NULL);
+}
+
+
+static void gprs_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	//struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	//struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	struct tlv_parsed *tp = NULL;
+
+	switch (event) {
+	case GPRS_SNS_EV_CONFIG_ACK:
+		tp = data;
+		if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+			LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
+				 gprs_ns_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+			/* FIXME: What to do? */
+		} else {
+			osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, 0, 0);
+		}
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+static void gprs_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	/* Transmit SNS-CONFIG */
+	gprs_ns_tx_sns_config(gss->nsvc_hack, true, gss->ip4_local, gss->num_ip4_local);
+}
+
+static void gprs_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	struct tlv_parsed *tp = NULL;
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+	const struct gprs_ns_ie_ip4_elem *v4_list;
+	unsigned int num_v4;
+	uint8_t cause;
+
+	switch (event) {
+	case GPRS_SNS_EV_CONFIG_END:
+	case GPRS_SNS_EV_CONFIG:
+		tp = data;
+#if 0		/* part of incoming SNS-SIZE (doesn't happen on BSS side */
+		if (TLVP_PRESENT(tp, NS_IE_RESET_FLAG)) {
+			/* reset all existing config */
+			if (gss->ip4_remote)
+				talloc_free(gss->ip4_remote);
+			gss->num_ip4_remote = 0;
+		}
+#endif
+		if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+			cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+			gprs_ns_tx_sns_config_ack(gss->nsvc_hack, &cause);
+			osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+			break;
+		}
+		v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+		num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+		/* realloc to the new size */
+		gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote,
+						 struct gprs_ns_ie_ip4_elem,
+						 gss->num_ip4_remote+num_v4);
+		/* append the new entries to the end of the list */
+		memcpy(&gss->ip4_remote[gss->num_ip4_remote], v4_list, num_v4*sizeof(*v4_list));
+		gss->num_ip4_remote += num_v4;
+
+		LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
+			 gss->num_ip4_remote);
+		if (event == GPRS_SNS_EV_CONFIG_END) {
+			/* check if sum of data / sig weights == 0 */
+			if (ip4_weight_sum_data(gss->ip4_remote, gss->num_ip4_remote) == 0 ||
+			    ip4_weight_sum_sig(gss->ip4_remote, gss->num_ip4_remote) == 0) {
+				cause = NS_CAUSE_INVAL_WEIGH;
+				gprs_ns_tx_sns_config_ack(gss->nsvc_hack, &cause);
+				osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+				break;
+			}
+			create_missing_nsvcs(fi);
+			gprs_ns_tx_sns_config_ack(gss->nsvc_hack, NULL);
+			/* start the test procedure on ALL NSVCs! */
+			gprs_start_alive_all_nsvcs(nsi);
+			osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+		} else {
+			/* just send CONFIG-ACK */
+			gprs_ns_tx_sns_config_ack(gss->nsvc_hack, NULL);
+		}
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void gprs_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+	struct tlv_parsed *tp = NULL;
+	const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+	unsigned int num_v4 = 0;
+	uint8_t trans_id;
+	uint8_t cause = 0xff;
+	unsigned int i;
+	int rc;
+
+	switch (event) {
+	case GPRS_SNS_EV_ADD:
+		tp = data;
+		trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+		if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+			v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+			num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+			for (i = 0; i < num_v4; i++) {
+				rc = do_sns_add(fi, &v4_list[i]);
+				if (rc < 0) {
+					cause = NS_CAUSE_EQUIP_FAIL;
+					/* continue to add others */
+				}
+			}
+			if (cause != 0xff) {
+				gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, NULL, 0);
+				break;
+			}
+		} else {
+			cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+			gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, NULL, 0);
+			break;
+		}
+		gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, NULL, v4_list, num_v4);
+		break;
+	case GPRS_SNS_EV_DELETE:
+		tp = data;
+		trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+		if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+			v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+			num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+			for (i = 0; i < num_v4; i++) {
+				rc = do_sns_delete(fi, &v4_list[i]);
+				if (rc < 0) {
+					cause = NS_CAUSE_EQUIP_FAIL;
+					/* continue to delete others */
+				}
+			}
+			if (cause != 0xff) {
+				gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, NULL, 0);
+				break;
+			}
+		} else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR)) {
+			/* FIXME */
+		} else {
+			cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+			gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, NULL, 0);
+			break;
+		}
+		gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, NULL, v4_list, num_v4);
+		break;
+	case GPRS_SNS_EV_CHANGE_WEIGHT:
+		tp = data;
+		trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+		if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+			v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+			num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+			for (i = 0; i < num_v4; i++) {
+				rc = do_sns_change_weight(fi, &v4_list[i]);
+				if (rc <  0) {
+					cause = NS_CAUSE_EQUIP_FAIL;
+					/* continue to others */
+				}
+			}
+			if (cause != 0xff) {
+				gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, NULL, 0);
+				break;
+			}
+		} else {
+			cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+			gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, NULL, 0);
+			break;
+		}
+		gprs_ns_tx_sns_ack(gss->nsvc_hack, trans_id, &cause, v4_list, num_v4);
+		break;
+	}
+}
+
+static void gprs_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+	struct ns_signal_data nssd = {0};
+	osmo_signal_dispatch(SS_L_NS, S_SNS_CONFIGURED, &nssd);
+}
+
+static const struct osmo_fsm_state gprs_sns_bss_states[] = {
+	[GPRS_SNS_ST_UNCONFIGURED] = {
+		.in_event_mask = S(GPRS_SNS_EV_START),
+		.out_state_mask = S(GPRS_SNS_ST_SIZE),
+		.name = "UNCONFIGURED",
+		.action = gprs_sns_st_unconfigured,
+	},
+	[GPRS_SNS_ST_SIZE] = {
+		.in_event_mask = S(GPRS_SNS_EV_SIZE_ACK),
+		.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+				  S(GPRS_SNS_ST_SIZE) |
+				  S(GPRS_SNS_ST_CONFIG_BSS),
+		.name = "SIZE",
+		.action = gprs_sns_st_size,
+		.onenter = gprs_sns_st_size_onenter,
+	},
+	[GPRS_SNS_ST_CONFIG_BSS] = {
+		.in_event_mask = S(GPRS_SNS_EV_CONFIG_ACK),
+		.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+				  S(GPRS_SNS_ST_CONFIG_BSS) |
+				  S(GPRS_SNS_ST_CONFIG_SGSN),
+		.name = "CONFIG_BSS",
+		.action = gprs_sns_st_config_bss,
+		.onenter = gprs_sns_st_config_bss_onenter,
+	},
+	[GPRS_SNS_ST_CONFIG_SGSN] = {
+		.in_event_mask = S(GPRS_SNS_EV_CONFIG) |
+				 S(GPRS_SNS_EV_CONFIG_END),
+		.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+				  S(GPRS_SNS_ST_CONFIG_SGSN) |
+				  S(GPRS_SNS_ST_CONFIGURED),
+		.name = "CONFIG_SGSN",
+		.action = gprs_sns_st_config_sgsn,
+	},
+	[GPRS_SNS_ST_CONFIGURED] = {
+		.in_event_mask = S(GPRS_SNS_EV_ADD) |
+				 S(GPRS_SNS_EV_DELETE) |
+				 S(GPRS_SNS_EV_CHANGE_WEIGHT),
+		.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED),
+		.name = "CONFIGURED",
+		.action = gprs_sns_st_configured,
+		.onenter = gprs_sns_st_configured_onenter,
+	},
+};
+
+static int gprs_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
+{
+	struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+
+	switch (fi->T) {
+	case 1:
+		osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+		break;
+	case 2:
+		osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+		break;
+	}
+	return 0;
+}
+
+static struct osmo_fsm gprs_sns_bss_fsm = {
+	.name = "GPRS-SNS-BSS",
+	.states = gprs_sns_bss_states,
+	.num_states = ARRAY_SIZE(gprs_sns_bss_states),
+	.allstate_event_mask = 0,
+	.allstate_action = NULL,
+	.cleanup = NULL,
+	.timer_cb = gprs_sns_fsm_bss_timer_cb,
+	/* .log_subsys = DNS, "is not constant" */
+	.event_names = gprs_sns_event_names,
+	.pre_term = NULL,
+};
+
+struct osmo_fsm_inst *gprs_sns_bss_fsm_alloc(void *ctx, struct gprs_nsvc *nsvc,
+					     const char *id)
+{
+	struct osmo_fsm_inst *fi;
+	struct gprs_sns_state *gss;
+	struct gprs_ns_ie_ip4_elem *ip4;
+	struct gprs_ns_inst *nsi = nsvc->nsi;
+
+	fi = osmo_fsm_inst_alloc(&gprs_sns_bss_fsm, ctx, NULL, LOGL_DEBUG, id);
+	if (!fi)
+		return fi;
+
+	gss = talloc_zero(fi, struct gprs_sns_state);
+	if (!gss)
+		goto err;
+
+	fi->priv = gss;
+	gss->nsi = nsi;
+	/* FIXME: we shouldn't use 'nsvc' here but only gprs_ns_inst */
+	gss->nsvc_hack = nsvc;
+
+	/* create IPv4 list from the one IP/port the NS instance has */
+	ip4 = talloc_zero(gss, struct gprs_ns_ie_ip4_elem);
+	if (!ip4)
+		goto err;
+	if (nsi->nsip.local_ip)
+		ip4->ip_addr = htonl(nsi->nsip.local_ip);
+	else {
+		/* unspecified local address. Figure out which address the kernel would use if we
+		 * wanted to send a packet to the remote_ip */
+		char local_ip[32];
+		struct in_addr in = { .s_addr = htonl(nsi->nsip.remote_ip) };
+		osmo_sock_local_ip(local_ip, inet_ntoa(in));
+		ip4->ip_addr = inet_addr(local_ip);
+	}
+	ip4->udp_port = htons(gss->nsi->nsip.local_port);
+	ip4->sig_weight = 2;
+	ip4->data_weight = 1;
+	gss->ip4_local = ip4;
+	gss->num_ip4_local = 1;
+	gss->num_max_nsvcs = 8;
+	gss->num_max_ip4_remote = 4;
+
+	return fi;
+err:
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+	return NULL;
+}
+
+int gprs_sns_bss_fsm_start(struct gprs_ns_inst *nsi)
+{
+	return osmo_fsm_inst_dispatch(nsi->bss_sns_fi, GPRS_SNS_EV_START, NULL);
+}
+
+/* main entry point for receiving SNS messages from the network */
+int gprs_ns_rx_sns(struct gprs_ns_inst *nsi, struct msgb *msg, struct tlv_parsed *tp)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	uint16_t nsei = msgb_nsei(msg);
+	struct osmo_fsm_inst *fi;
+
+	LOGP(DNS, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
+		get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+
+	/* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
+	fi = nsi->bss_sns_fi;
+
+	switch (nsh->pdu_type) {
+	case SNS_PDUT_SIZE:
+		osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE, tp);
+		break;
+	case SNS_PDUT_SIZE_ACK:
+		osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE_ACK, tp);
+		break;
+	case SNS_PDUT_CONFIG:
+		if (nsh->data[0] & 0x01)
+			osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_END, tp);
+		else
+			osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG, tp);
+		break;
+	case SNS_PDUT_CONFIG_ACK:
+		osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_ACK, tp);
+		break;
+	case SNS_PDUT_ADD:
+		osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_ADD, tp);
+		break;
+	case SNS_PDUT_DELETE:
+		osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_DELETE, tp);
+		break;
+	case SNS_PDUT_CHANGE_WEIGHT:
+		osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CHANGE_WEIGHT, tp);
+		break;
+	case SNS_PDUT_ACK:
+		LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx unsupported SNS PDU type %s\n", nsei,
+			get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+		break;
+	default:
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
+			get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int gprs_sns_init(void)
+{
+	/* "DNS" is not a constant/#define, but an integer variable set by the client app */
+	gprs_sns_bss_fsm.log_subsys = DNS;
+	return osmo_fsm_register(&gprs_sns_bss_fsm);
+}
diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c
index 667db7d..a108b71 100644
--- a/src/gb/gprs_ns_vty.c
+++ b/src/gb/gprs_ns_vty.c
@@ -555,6 +555,12 @@
 		return CMD_WARNING;
 	}
 
+	if (nsvc->nsi->bss_sns_fi) {
+		vty_out(vty, "A NS Instance using the IP Sub-Network doesn't use BLOCK/UNBLOCK/RESET%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
 	if (!strcmp(operation, "block"))
 		gprs_ns_tx_block(nsvc, NS_CAUSE_OM_INTERVENTION);
 	else if (!strcmp(operation, "unblock"))
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index ec69670..2ad3ff7 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -52,6 +52,7 @@
 gprs_ns_instantiate;
 gprs_ns_nsip_listen;
 gprs_ns_nsip_connect;
+gprs_ns_nsip_connect_sns;
 gprs_ns_rcvmsg;
 gprs_ns_sendmsg;
 gprs_ns_set_log_ss;

-- 
To view, visit https://gerrit.osmocom.org/13014
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: I84786c3b43a8ae34ef3b3ba84b33c90042d234ea
Gerrit-Change-Number: 13014
Gerrit-PatchSet: 1
Gerrit-Owner: Harald Welte <laforge at gnumonks.org>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20190222/c1521c64/attachment.htm>


More information about the gerrit-log mailing list