gprs_ns2: inform the NS user (BSSGP) about the MTU of a NSE

The BSSGP layer needs to know the MTU of the NS UNIDATA payload.
The MTU can be 0 if the NSE doesn't contain any NSVC.
Every status indication will contain the mtu value.
The MTU in the status indication contains the maximum transfer
unit of a BSSGP message. From NS side the maximum SDU.

Related: OS#4889
Change-Id: I5016b295db6185ec131d83089cf6c806e34ef1b6
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
index 99a7415..9302a16 100644
--- a/src/gb/gprs_ns2.c
+++ b/src/gb/gprs_ns2.c
@@ -217,6 +217,7 @@
 	{ GPRS_NS2_AFF_CAUSE_RECOVERY,		"NSE recovery" },
 	{ GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED,	"NSE SNS configured" },
 	{ GPRS_NS2_AFF_CAUSE_SNS_FAILURE,	"NSE SNS failure" },
+	{ GPRS_NS2_AFF_CAUSE_MTU_CHANGE,	"NSE MTU changed" },
 	{ 0, NULL }
 };
 
@@ -496,15 +497,20 @@
 	nsp.u.status.transfer = ns2_count_transfer_cap(nse, bvci);
 	nsp.u.status.first = nse->first;
 	nsp.u.status.persistent = nse->persistent;
+	if (nse->mtu < 4)
+		nsp.u.status.mtu = 0;
+	else
+		nsp.u.status.mtu = nse->mtu - 4; /* 1 Byte NS PDU type, 1 Byte NS SDU control, 2 Byte BVCI */
+
 	if (nsvc) {
 		nsp.u.status.nsvc = gprs_ns2_ll_str_buf(nsvc_str, sizeof(nsvc_str), nsvc);
-		LOGNSVC(nsvc, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d\n",
+		LOGNSVC(nsvc, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n",
 			nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause),
-			nsp.u.status.transfer, nsp.u.status.first);
+			nsp.u.status.transfer, nsp.u.status.first, nse->mtu);
 	} else {
-		LOGNSE(nse, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d\n",
+		LOGNSE(nse, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n",
 			nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause),
-			nsp.u.status.transfer, nsp.u.status.first);
+			nsp.u.status.transfer, nsp.u.status.first, nse->mtu);
 	}
 
 	osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_STATUS, PRIM_OP_INDICATION, NULL);
@@ -548,6 +554,7 @@
 
 	llist_add(&nsvc->list, &nse->nsvc);
 	llist_add(&nsvc->blist, &bind->nsvc);
+	ns2_nse_update_mtu(nse);
 
 	return nsvc;
 
@@ -746,6 +753,7 @@
 	nse->nsei = nsei;
 	nse->nsi = nsi;
 	nse->first = true;
+	nse->mtu = 0;
 	llist_add(&nse->list, &nsi->nse);
 	INIT_LLIST_HEAD(&nse->nsvc);
 
@@ -1334,6 +1342,31 @@
 	array[i] = bind;
 }
 
+void ns2_nse_update_mtu(struct gprs_ns2_nse *nse)
+{
+	struct gprs_ns2_vc *nsvc;
+	int mtu = 0;
+
+	if (llist_empty(&nse->nsvc)) {
+		nse->mtu = 0;
+		return;
+	}
+
+	llist_for_each_entry(nsvc, &nse->nsvc, list) {
+		if (mtu == 0)
+			mtu = nsvc->bind->mtu;
+		else if (mtu > nsvc->bind->mtu)
+			mtu = nsvc->bind->mtu;
+	}
+
+	if (nse->mtu == mtu)
+		return;
+
+	nse->mtu = mtu;
+	if (nse->alive)
+		ns2_prim_status_ind(nsvc->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_MTU_CHANGE);
+}
+
 /*! calculate the transfer capabilities for a nse
  *  \param nse the nse to count the transfer capability
  *  \param bvci a bvci - unused
diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c
index 7e6db2a..445f00a 100644
--- a/src/gb/gprs_ns2_fr.c
+++ b/src/gb/gprs_ns2_fr.c
@@ -801,6 +801,7 @@
 	bind->send_vc = fr_vc_sendmsg;
 	bind->free_vc = free_vc;
 	bind->dump_vty = dump_vty;
+	bind->mtu = FRAME_RELAY_SDU;
 	priv = bind->priv = talloc_zero(bind, struct priv_bind);
 	if (!priv) {
 		rc = -ENOMEM;
diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c
index 177aeb2..62d87a4 100644
--- a/src/gb/gprs_ns2_frgre.c
+++ b/src/gb/gprs_ns2_frgre.c
@@ -572,6 +572,10 @@
 	bind->send_vc = frgre_vc_sendmsg;
 	bind->free_vc = free_vc;
 	bind->nsi = nsi;
+	/* TODO: allow to set the MTU via vty. It can not be automatic detected because it goes over an
+	 * ethernet device and the MTU here must match the FR side of the FR-to-GRE gateway.
+	 */
+	bind->mtu = FRAME_RELAY_SDU;
 
 	priv = bind->priv = talloc_zero(bind, struct priv_bind);
 	if (!priv) {
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
index d4764f6..7e235be 100644
--- a/src/gb/gprs_ns2_internal.h
+++ b/src/gb/gprs_ns2_internal.h
@@ -179,6 +179,9 @@
 
 	/*! sum of all the signalling weight of _alive_ NS-VCs */
 	uint32_t sum_sig_weight;
+
+	/*! MTU of a NS PDU. This is the lowest MTU of all NSVCs */
+	uint16_t mtu;
 };
 
 /*! Structure representing a single NS-VC */
@@ -244,6 +247,9 @@
 	/*! transfer capability in mbit */
 	int transfer_capability;
 
+	/*! MTU of a NS PDU on this bind. */
+	uint16_t mtu;
+
 	/*! which link-layer are we based on? */
 	enum gprs_ns2_ll ll;
 
@@ -298,6 +304,7 @@
 			 uint16_t bvci,
 			 enum gprs_ns2_affecting_cause cause);
 void ns2_nse_notify_alive(struct gprs_ns2_vc *nsvc, bool alive);
+void ns2_nse_update_mtu(struct gprs_ns2_nse *nse);
 
 /* message */
 int ns2_validate(struct gprs_ns2_vc *nsvc,
diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c
index 0f22458..36f6a97 100644
--- a/src/gb/gprs_ns2_udp.c
+++ b/src/gb/gprs_ns2_udp.c
@@ -368,6 +368,10 @@
 				dscp, rc, errno);
 	}
 
+	/* IPv4: max fragmented payload can be (13 bit) * 8 byte => 65535.
+	 * IPv6: max payload can be 65535 (RFC 2460).
+	 * UDP header = 8 byte */
+	bind->mtu = 65535 - 8;
 	if (result)
 		*result = bind;