aboutsummaryrefslogtreecommitdiffstats
path: root/src/gb
diff options
context:
space:
mode:
Diffstat (limited to 'src/gb')
-rw-r--r--src/gb/Makefile.am33
-rw-r--r--src/gb/bssgp_bvc_fsm.c857
-rw-r--r--src/gb/common_vty.c14
-rw-r--r--src/gb/common_vty.h2
-rw-r--r--src/gb/frame_relay.c1051
-rw-r--r--src/gb/gprs_bssgp.c353
-rw-r--r--src/gb/gprs_bssgp2.c486
-rw-r--r--src/gb/gprs_bssgp_bss.c100
-rw-r--r--src/gb/gprs_bssgp_internal.h9
-rw-r--r--src/gb/gprs_bssgp_rim.c1261
-rw-r--r--src/gb/gprs_bssgp_util.c350
-rw-r--r--src/gb/gprs_bssgp_vty.c16
-rw-r--r--src/gb/gprs_ns.c69
-rw-r--r--src/gb/gprs_ns2.c1695
-rw-r--r--src/gb/gprs_ns2_fr.c988
-rw-r--r--src/gb/gprs_ns2_frgre.c616
-rw-r--r--src/gb/gprs_ns2_internal.h503
-rw-r--r--src/gb/gprs_ns2_message.c848
-rw-r--r--src/gb/gprs_ns2_sns.c3106
-rw-r--r--src/gb/gprs_ns2_udp.c597
-rw-r--r--src/gb/gprs_ns2_vc_fsm.c992
-rw-r--r--src/gb/gprs_ns2_vty.c2351
-rw-r--r--src/gb/gprs_ns_vty.c46
-rw-r--r--src/gb/libosmogb.map132
24 files changed, 16230 insertions, 245 deletions
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index 7248413a..0aa5b131 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -1,26 +1,43 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=10:0:1
+LIBVERSION=16:1:2
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall -fno-strict-aliasing \
+ $(TALLOC_CFLAGS) \
+ $(NULL)
# FIXME: this should eventually go into a milenage/Makefile.am
-noinst_HEADERS = common_vty.h gb_internal.h
+noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_internal.h
if ENABLE_GB
lib_LTLIBRARIES = libosmogb.la
-libosmogb_la_LDFLAGS = $(LTLDFLAGS_OSMOGB) -version-info $(LIBVERSION)
+libosmogb_la_LDFLAGS = \
+ $(LTLDFLAGS_OSMOGB) \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmogb_la_LIBADD = $(TALLOC_LIBS) \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(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 gprs_ns_sns.c \
- gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c \
- gprs_bssgp_bss.c common_vty.c
+ gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c gprs_bssgp_rim.c \
+ gprs_bssgp_bss.c \
+ gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_fr.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \
+ gprs_ns2_message.c gprs_ns2_vty.c \
+ gprs_bssgp2.c bssgp_bvc_fsm.c \
+ common_vty.c frame_relay.c
+
+# convenience library for testing with access to all non-static symbols
+noinst_LTLIBRARIES = libosmogb-test.la
+libosmogb_test_la_LIBADD = $(libosmogb_la_LIBADD)
+libosmogb_test_la_SOURCES= $(libosmogb_la_SOURCES)
+
endif
EXTRA_DIST = libosmogb.map
+EXTRA_libosmogb_la_DEPENDENCIES = libosmogb.map
diff --git a/src/gb/bssgp_bvc_fsm.c b/src/gb/bssgp_bvc_fsm.c
new file mode 100644
index 00000000..3a36c7dc
--- /dev/null
+++ b/src/gb/bssgp_bvc_fsm.c
@@ -0,0 +1,857 @@
+/* BSSGP per-BVC Finite State Machine */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp2.h>
+#include <osmocom/gprs/bssgp_bvc_fsm.h>
+
+#include "common_vty.h"
+
+#define S(x) (1 << (x))
+
+/* TODO: Those are not made cofnigurable via a VTY yet */
+struct osmo_tdef bssgp_bvc_fsm_tdefs[] = {
+ {
+ .T = 1,
+ .default_val = 5,
+ .min_val = 1,
+ .max_val = 30,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP BVC (un)blocking procedure",
+ }, {
+ .T = 2,
+ .default_val = 10,
+ .min_val = 1,
+ .max_val = 120,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP BVC reset procedure",
+ }, {
+ .T = 3,
+ .default_val = 500,
+ .min_val = 100,
+ .max_val = 10000,
+ .unit = OSMO_TDEF_MS,
+ .desc = "Guards the BSSGP SUSPEND procedure",
+ }, {
+ .T = 4,
+ .default_val = 500,
+ .min_val = 100,
+ .max_val = 10000,
+ .unit = OSMO_TDEF_MS,
+ .desc = "Guards the BSSGP RESUME procedure",
+ }, {
+ .T = 5,
+ .default_val = 15,
+ .min_val = 1,
+ .max_val = 30,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP Radio Access Capability Update procedure",
+ },
+ {}
+};
+
+#define T1 1
+#define T2 2
+
+/* We cannot use osmo_tdef_fsm_* as it makes hard-coded assumptions that
+ * each new/target state will always use the same timer and timeout - or
+ * a timeout at all */
+#define T1_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 1, OSMO_TDEF_S, 5)
+#define T2_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 2, OSMO_TDEF_S, 10)
+
+/* forward declaration */
+static struct osmo_fsm bssgp_bvc_fsm;
+
+static const struct value_string ptp_bvc_event_names[] = {
+ { BSSGP_BVCFSM_E_RX_BLOCK, "RX-BVC-BLOCK" },
+ { BSSGP_BVCFSM_E_RX_BLOCK_ACK, "RX-BVC-BLOCK-ACK" },
+ { BSSGP_BVCFSM_E_RX_UNBLOCK, "RX-BVC-UNBLOCK" },
+ { BSSGP_BVCFSM_E_RX_UNBLOCK_ACK, "RX-BVC-UNBLOCK-ACK" },
+ { BSSGP_BVCFSM_E_RX_RESET, "RX-BVC-RESET" },
+ { BSSGP_BVCFSM_E_RX_RESET_ACK, "RX-BVC-RESET-ACK" },
+ { BSSGP_BVCFSM_E_RX_FC_BVC, "RX-FLOW-CONTROL-BVC" },
+ { BSSGP_BVCFSM_E_RX_FC_BVC_ACK, "RX-FLOW-CONTROL-BVC-ACK" },
+ { BSSGP_BVCFSM_E_REQ_BLOCK, "REQ-BLOCK" },
+ { BSSGP_BVCFSM_E_REQ_UNBLOCK, "REQ-UNBLOCK" },
+ { BSSGP_BVCFSM_E_REQ_RESET, "REQ-RESET" },
+ { BSSGP_BVCFSM_E_REQ_FC_BVC, "REQ-FLOW-CONTROL-BVC" },
+ { 0, NULL }
+};
+
+struct bvc_fsm_priv {
+ /* NS-instance; defining the scope for NSEI below */
+ struct gprs_ns2_inst *nsi;
+
+ /* NSEI of the underlying NS Entity */
+ uint16_t nsei;
+ /* Maximum size of the BSSGP PDU */
+ uint16_t max_pdu_len;
+
+ /* BVCI of this BVC */
+ uint16_t bvci;
+
+ /* are we the SGSN (true) or the BSS (false) */
+ bool role_sgsn;
+
+ /* BSS side: are we locally marked blocked? */
+ bool locally_blocked;
+ uint8_t block_cause;
+
+ /* cause value of the last outbound BVC-RESET (for re-transmissions) */
+ uint8_t last_reset_cause;
+
+ struct {
+ /* Bit 0..7: Features; Bit 8..15: Extended Features */
+ uint32_t advertised;
+ uint32_t received;
+ uint32_t negotiated;
+ /* only used if BSSGP_XFEAT_GBIT is negotiated */
+ enum bssgp_fc_granularity fc_granularity;
+ } features;
+
+ /* Cell Identification used by BSS when
+ * transmitting BVC-RESET / BVC-RESET-ACK, or those received
+ * from BSS in SGSN role */
+ struct gprs_ra_id ra_id;
+ uint16_t cell_id;
+
+ /* call-backs provided by the user */
+ const struct bssgp_bvc_fsm_ops *ops;
+ /* private data pointer passed to each call-back invocation */
+ void *ops_priv;
+};
+
+static int fi_tx_ptp(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type));
+
+ return bssgp2_nsi_tx_ptp(bfp->nsi, bfp->nsei, bfp->bvci, msg, 0);
+}
+
+static int fi_tx_sig(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type));
+
+ return bssgp2_nsi_tx_sig(bfp->nsi, bfp->nsei, msg, 0);
+}
+
+/* helper function to transmit BVC-RESET with right combination of conditional/optional IEs */
+static void _tx_bvc_reset(struct osmo_fsm_inst *fi, uint8_t cause)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const uint8_t *features = NULL;
+ const uint8_t *features_ext = NULL;
+ uint8_t _features[2] = {
+ (bfp->features.advertised >> 0) & 0xff,
+ (bfp->features.advertised >> 8) & 0xff,
+ };
+ struct msgb *tx;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ /* transmit BVC-RESET to peer; RA-ID only present for PTP from BSS */
+ if (bfp->bvci == 0) {
+ features = &_features[0];
+ features_ext = &_features[1];
+ }
+ tx = bssgp2_enc_bvc_reset(bfp->bvci, cause,
+ bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL,
+ bfp->cell_id, features, features_ext);
+ fi_tx_sig(fi, tx);
+}
+
+/* helper function to transmit BVC-RESET-ACK with right combination of conditional/optional IEs */
+static void _tx_bvc_reset_ack(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const uint8_t *features = NULL;
+ const uint8_t *features_ext = NULL;
+ uint8_t _features[2] = {
+ (bfp->features.advertised >> 0) & 0xff,
+ (bfp->features.advertised >> 8) & 0xff,
+ };
+ struct msgb *tx;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ /* transmit BVC-RESET-ACK to peer; RA-ID only present for PTP from BSS -> SGSN */
+ if (bfp->bvci == 0) {
+ features = &_features[0];
+ features_ext = &_features[1];
+ }
+ tx = bssgp2_enc_bvc_reset_ack(bfp->bvci, bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL,
+ bfp->cell_id, features, features_ext);
+ fi_tx_sig(fi, tx);
+}
+
+/* helper function to transmit BVC-STATUS with right combination of conditional/optional IEs */
+static void _tx_status(struct osmo_fsm_inst *fi, enum gprs_bssgp_cause cause, const struct msgb *rx)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *tx;
+ uint16_t *bvci = NULL;
+
+ /* GSM 08.18, 10.4.14.1: The BVCI must be included if (and only if) the
+ * cause is either "BVCI blocked" or "BVCI unknown" */
+ if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED)
+ bvci = &bfp->bvci;
+
+ tx = bssgp2_enc_status(cause, bvci, rx, bfp->max_pdu_len);
+
+ if (msgb_bvci(rx) == 0)
+ fi_tx_sig(fi, tx);
+ else
+ fi_tx_ptp(fi, tx);
+}
+
+/* Update the features by bit-wise AND of advertised + received features */
+static void update_negotiated_features(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ bfp->features.received = 0;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_FEATURE_BITMAP, 1))
+ bfp->features.received |= *TLVP_VAL(tp, BSSGP_IE_FEATURE_BITMAP);
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_EXT_FEATURE_BITMAP, 1))
+ bfp->features.received |= (*TLVP_VAL(tp, BSSGP_IE_EXT_FEATURE_BITMAP) << 8);
+
+ bfp->features.negotiated = bfp->features.advertised & bfp->features.received;
+
+ LOGPFSML(fi, LOGL_NOTICE, "Updating features: Advertised 0x%04x, Received 0x%04x, Negotiated 0x%04x\n",
+ bfp->features.advertised, bfp->features.received, bfp->features.negotiated);
+}
+
+/* "tail" of each onenter() handler: Calling the state change notification call-back */
+static void _onenter_tail(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ if (prev_state == fi->state)
+ return;
+
+ if (bfp->ops && bfp->ops->state_chg_notification)
+ bfp->ops->state_chg_notification(bfp->nsei, bfp->bvci, prev_state, fi->state, bfp->ops_priv);
+}
+
+static void bssgp_bvc_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ /* we don't really expect anything in this state; all handled via allstate */
+ OSMO_ASSERT(0);
+}
+
+static void bssgp_bvc_fsm_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ /* signaling BVC can never be blocked */
+ OSMO_ASSERT(bfp->bvci != 0);
+ _onenter_tail(fi, prev_state);
+}
+
+static void bssgp_bvc_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *rx = NULL, *tx;
+ const struct tlv_parsed *tp = NULL;
+ uint8_t cause;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_BLOCK_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If a BVC-BLOCK-ACK PDU is received by a BSS for the signalling BVC, the PDU is ignored. */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK-ACK on BVCI=0 is illegal\n");
+ if (!bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* stop T1 timer */
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_RX_BLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n", bssgp_cause_str(cause));
+ /* If a BVC-BLOCK PDU is received by an SGSN for a blocked BVC, a BVC-BLOCK-ACK
+ * PDU shall be returned. */
+ if (bfp->role_sgsn) {
+ /* If a BVC-BLOCK PDU is received by an SGSN for
+ * the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0)
+ break;
+ tx = bssgp2_enc_bvc_block_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ }
+ break;
+ case BSSGP_BVCFSM_E_RX_UNBLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-UNBLOCK\n");
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BVCI=0 is illegal\n");
+ /* If BVC-UNBLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored.*/
+ if (bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (!bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BSS is illegal\n");
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ tx = bssgp2_enc_bvc_unblock_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_E_REQ_UNBLOCK:
+ if (bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "SGSN side cannot initiate BVC unblock\n");
+ break;
+ }
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be unblocked\n");
+ break;
+ }
+ bfp->locally_blocked = false;
+ tx = bssgp2_enc_bvc_unblock(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ break;
+ }
+}
+
+/* Waiting for RESET-ACK: Receive PDUs but don't transmit */
+static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL, *tx;
+ uint8_t cause;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_RESET:
+ /* 48.018 Section 8.4.3: If the BSS (or SGSN) has sent a BVC-RESET PDU for a BVCI to
+ * the SGSN (or BSS) and is awaiting a BVC-RESET-ACK PDU in response, but instead
+ * receives a BVC-RESET PDU indicating the same BVCI, then this shall be interpreted
+ * as a BVC-RESET ACK PDU and the T2 timer shall be stopped. */
+ /* fall-through */
+ case BSSGP_BVCFSM_E_RX_RESET_ACK:
+ rx = data;
+ cause = bfp->last_reset_cause;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ if (bfp->bvci == 0)
+ update_negotiated_features(fi, tp);
+ if (bfp->role_sgsn && bfp->bvci != 0)
+ bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+ if (!bfp->role_sgsn && bfp->bvci != 0 && bfp->locally_blocked) {
+ /* initiate the blocking procedure */
+ /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ } else
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ if (bfp->ops && bfp->ops->reset_ack_notification)
+ bfp->ops->reset_ack_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id, cause, bfp->ops_priv);
+ break;
+ }
+}
+
+static void bssgp_bvc_fsm_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bssgp2_flow_ctrl rx_fc, *tx_fc;
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL, *tx;
+ int rc;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_UNBLOCK_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If BVC-UNBLOCK-ACK PDU is received by an BSS for the signalling BVC, the PDU is ignored. */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK-ACK on BVCI=0 is illegal\n");
+ if (!bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* stop T1 timer */
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_RX_UNBLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If a BVC-UNBLOCK PDU is received by an SGSN for a blocked BVC, a BVC-UNBLOCK-ACK
+ * PDU shall be returned. */
+ if (bfp->role_sgsn) {
+ /* If a BVC-UNBLOCK PDU is received by an SGSN for
+ * the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0)
+ break;
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, bfp->nsei, bfp->bvci, 0);
+ }
+ break;
+ case BSSGP_BVCFSM_E_RX_BLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n",
+ bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
+ /* If a BVC-BLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BVCI=0 is illegal\n");
+ if (bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (!bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BSS is illegal\n");
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* transmit BVC-BLOCK-ACK, transition to BLOCKED state */
+ tx = bssgp2_enc_bvc_block_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_REQ_BLOCK:
+ if (bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "SGSN may not initiate BVC-BLOCK\n");
+ break;
+ }
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be blocked\n");
+ break;
+ }
+ bfp->locally_blocked = true;
+ bfp->block_cause = *(uint8_t *)data;
+ /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_E_RX_FC_BVC:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */
+ OSMO_ASSERT(bfp->role_sgsn);
+ rc = bssgp2_dec_fc_bvc(&rx_fc, tp);
+ if (rc < 0) {
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (bfp->ops->rx_fc_bvc)
+ bfp->ops->rx_fc_bvc(bfp->nsei, bfp->bvci, &rx_fc, bfp->ops_priv);
+ tx = bssgp2_enc_fc_bvc_ack(rx_fc.tag);
+ fi_tx_ptp(fi, tx);
+ break;
+ case BSSGP_BVCFSM_E_RX_FC_BVC_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */
+ OSMO_ASSERT(!bfp->role_sgsn);
+ break;
+ case BSSGP_BVCFSM_E_REQ_FC_BVC:
+ tx_fc = data;
+ tx = bssgp2_enc_fc_bvc(tx_fc, bfp->features.negotiated & (BSSGP_XFEAT_GBIT << 8) ?
+ &bfp->features.fc_granularity : NULL);
+ fi_tx_ptp(fi, tx);
+ break;
+ }
+}
+
+static void bssgp_bvc_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ uint8_t cause;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_REQ_RESET:
+ bfp->locally_blocked = false;
+ cause = bfp->last_reset_cause = *(uint8_t *) data;
+ _tx_bvc_reset(fi, cause);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2);
+#if 0 /* not sure if we really should notify the application if itself has requested the reset? */
+ if (bfp->ops && bfp->ops->reset_notification)
+ bfp->ops->reset_notification(bfp->nsei, bfp->bvci, NULL, 0, cause, bfp->ops_priv);
+#endif
+ break;
+ case BSSGP_BVCFSM_E_RX_RESET:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
+ if (bfp->role_sgsn && bfp->bvci != 0)
+ bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-RESET (cause=%s)\n", bssgp_cause_str(cause));
+ if (bfp->bvci == 0)
+ update_negotiated_features(fi, tp);
+ _tx_bvc_reset_ack(fi);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ if (bfp->ops && bfp->ops->reset_notification) {
+ bfp->ops->reset_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id,
+ cause, bfp->ops_priv);
+ }
+ break;
+ }
+}
+
+static int bssgp_bvc_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *tx;
+
+ switch (fi->T) {
+ case T1:
+ switch (fi->state) {
+ case BSSGP_BVCFSM_S_BLOCKED:
+ /* re-transmit BVC-BLOCK */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_S_UNBLOCKED:
+ /* re-transmit BVC-UNBLOCK */
+ tx = bssgp2_enc_bvc_unblock(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1);
+ break;
+ }
+ break;
+ case T2:
+ switch (fi->state) {
+ case BSSGP_BVCFSM_S_WAIT_RESET_ACK:
+ /* re-transmit BVC-RESET */
+ _tx_bvc_reset(fi, bfp->last_reset_cause);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2);
+ break;
+ case BSSGP_BVCFSM_S_UNBLOCKED:
+ /* re-transmit BVC-RESET-ACK */
+ _tx_bvc_reset_ack(fi);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T2_SECS, T2);
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+ return 0;
+}
+
+
+
+static const struct osmo_fsm_state bssgp_bvc_fsm_states[] = {
+ [BSSGP_BVCFSM_S_NULL] = {
+ /* initial state from which we must do a RESET */
+ .name = "NULL",
+ .in_event_mask = 0,
+ .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED),
+ .action = bssgp_bvc_fsm_null,
+ },
+ [BSSGP_BVCFSM_S_BLOCKED] = {
+ .name = "BLOCKED",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_UNBLOCK) |
+ S(BSSGP_BVCFSM_E_RX_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_BLOCK_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_UNBLOCK),
+ .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED) |
+ S(BSSGP_BVCFSM_S_BLOCKED),
+ .action = bssgp_bvc_fsm_blocked,
+ .onenter = bssgp_bvc_fsm_blocked_onenter,
+ },
+ [BSSGP_BVCFSM_S_WAIT_RESET_ACK]= {
+ .name = "WAIT_RESET_ACK",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_RESET_ACK) |
+ S(BSSGP_BVCFSM_E_RX_RESET),
+ .out_state_mask = S(BSSGP_BVCFSM_S_UNBLOCKED) |
+ S(BSSGP_BVCFSM_S_BLOCKED) |
+ S(BSSGP_BVCFSM_S_WAIT_RESET_ACK),
+ .action = bssgp_bvc_fsm_wait_reset_ack,
+ .onenter = _onenter_tail,
+ },
+
+ [BSSGP_BVCFSM_S_UNBLOCKED] = {
+ .name = "UNBLOCKED",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_UNBLOCK) |
+ S(BSSGP_BVCFSM_E_RX_UNBLOCK_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_FC_BVC) |
+ S(BSSGP_BVCFSM_E_RX_FC_BVC_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_FC_BVC),
+ .out_state_mask = S(BSSGP_BVCFSM_S_BLOCKED) |
+ S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED),
+ .action = bssgp_bvc_fsm_unblocked,
+ .onenter = _onenter_tail,
+ },
+};
+
+static struct osmo_fsm bssgp_bvc_fsm = {
+ .name = "BSSGP-BVC",
+ .states = bssgp_bvc_fsm_states,
+ .num_states = ARRAY_SIZE(bssgp_bvc_fsm_states),
+ .allstate_event_mask = S(BSSGP_BVCFSM_E_REQ_RESET) |
+ S(BSSGP_BVCFSM_E_RX_RESET),
+ .allstate_action = bssgp_bvc_fsm_allstate,
+ .timer_cb = bssgp_bvc_fsm_timer_cb,
+ .log_subsys = DLBSSGP,
+ .event_names = ptp_bvc_event_names,
+};
+
+static struct osmo_fsm_inst *
+_bvc_fsm_alloc(void *ctx, struct gprs_ns2_inst *nsi, bool role_sgsn, uint16_t nsei, uint16_t bvci)
+{
+ struct osmo_fsm_inst *fi;
+ struct bvc_fsm_priv *bfp;
+ char idbuf[64];
+
+ /* TODO: encode our role in the id string? */
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-BVC%05u", nsei, bvci);
+
+ fi = osmo_fsm_inst_alloc(&bssgp_bvc_fsm, ctx, NULL, LOGL_INFO, idbuf);
+ if (!fi)
+ return NULL;
+
+ bfp = talloc_zero(fi, struct bvc_fsm_priv);
+ if (!bfp) {
+ osmo_fsm_inst_free(fi);
+ return NULL;
+ }
+ fi->priv = bfp;
+
+ bfp->nsi = nsi;
+ bfp->role_sgsn = role_sgsn;
+ bfp->nsei = nsei;
+ bfp->bvci = bvci;
+ bfp->max_pdu_len = UINT16_MAX;
+
+ return fi;
+}
+
+/*! Allocate a SIGNALING-BVC FSM for the BSS role (facing a remote SGSN).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features)
+{
+ struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, 0);
+ struct bvc_fsm_priv *bfp;
+
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->features.advertised = features;
+
+ return fi;
+}
+
+/*! Allocate a PTP-BVC FSM for the BSS role (facing a remote SGSN).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] bvci BVCI of this FSM
+ * \param[in] ra_id Routing Area Identity of the cell (reported to SGSN)
+ * \param[in] cell_id Cell Identifier of the cell (reported to SGSN)
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei,
+ uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ struct osmo_fsm_inst *fi;
+ struct bvc_fsm_priv *bfp;
+
+ OSMO_ASSERT(bvci >= 2);
+ OSMO_ASSERT(ra_id);
+
+ fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, bvci);
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->ra_id = *ra_id;
+ bfp->cell_id = cell_id;
+
+ return fi;
+}
+
+/*! Allocate a SIGNALING-BVC FSM for the SGSN role (facing a remote BSS).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features)
+{
+ struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, 0);
+ struct bvc_fsm_priv *bfp;
+
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->features.advertised = features;
+
+ return fi;
+}
+
+/*! Allocate a PTP-BVC FSM for the SGSN role (facing a remote BSS).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] bvci BVCI of this FSM
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci)
+{
+ struct osmo_fsm_inst *fi;
+
+ OSMO_ASSERT(bvci >= 2);
+
+ fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, bvci);
+ if (!fi)
+ return NULL;
+
+ return fi;
+}
+
+/*! Set the 'operations' callbacks + private data.
+ * \param[in] fi FSM instance for which the data shall be set
+ * \param[in] ops BSSGP BVC FSM operations (call-back functions) to register
+ * \param[in] ops_priv opaque/private data pointer passed through to call-backs */
+void bssgp_bvc_fsm_set_ops(struct osmo_fsm_inst *fi, const struct bssgp_bvc_fsm_ops *ops, void *ops_priv)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ bfp->ops = ops;
+ bfp->ops_priv = ops_priv;
+}
+
+/*! Return if the given BVC FSM is in UNBLOCKED state. */
+bool bssgp_bvc_fsm_is_unblocked(struct osmo_fsm_inst *fi)
+{
+ return fi->state == BSSGP_BVCFSM_S_UNBLOCKED;
+}
+
+/*! Determine the cause value why given BVC FSM is blocked. */
+uint8_t bssgp_bvc_fsm_get_block_cause(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->block_cause;
+}
+
+/*! Return the advertised features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_advertised(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.advertised;
+}
+
+/*! Return the received features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_received(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.received;
+}
+
+/*! Return the negotiated features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_negotiated(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.negotiated;
+}
+
+/*! Set the maximum size of a BSSGP PDU.
+ *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */
+void bssgp_bvc_fsm_set_max_pdu_len(struct osmo_fsm_inst *fi, uint16_t max_pdu_len) {
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ bfp->max_pdu_len = max_pdu_len;
+}
+
+/*! Return the maximum size of a BSSGP PDU
+ *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */
+uint16_t bssgp_bvc_fsm_get_max_pdu_len(const struct osmo_fsm_inst *fi)
+{
+ const struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->max_pdu_len;
+}
+
+
+static __attribute__((constructor)) void on_dso_load_bvc_fsm(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&bssgp_bvc_fsm) == 0);
+}
diff --git a/src/gb/common_vty.c b/src/gb/common_vty.c
index a47294b5..ad3dea23 100644
--- a/src/gb/common_vty.c
+++ b/src/gb/common_vty.c
@@ -40,15 +40,21 @@
int gprs_log_filter_fn(const struct log_context *ctx,
struct log_target *tar)
{
- const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
- const struct gprs_bvc *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+ const void *nse = ctx->ctx[LOG_CTX_GB_NSE];
+ const void *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
+ const void *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+
+ /* Filter on the NS Entity */
+ if ((tar->filter_map & (1 << LOG_FLT_GB_NSE)) != 0
+ && nse && (nse == tar->filter_data[LOG_FLT_GB_NSE]))
+ return 1;
/* Filter on the NS Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0
&& nsvc && (nsvc == tar->filter_data[LOG_FLT_GB_NSVC]))
return 1;
- /* Filter on the NS Virtual Connection */
+ /* Filter on the BSSGP Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_BVC)) != 0
&& bvc && (bvc == tar->filter_data[LOG_FLT_GB_BVC]))
return 1;
@@ -57,4 +63,4 @@ int gprs_log_filter_fn(const struct log_context *ctx,
}
-int DNS, DBSSGP;
+int DNS;
diff --git a/src/gb/common_vty.h b/src/gb/common_vty.h
index 801d2dab..8e883319 100644
--- a/src/gb/common_vty.h
+++ b/src/gb/common_vty.h
@@ -3,5 +3,5 @@
#include <osmocom/vty/command.h>
#include <osmocom/core/logging.h>
-extern int DNS, DBSSGP;
+extern int DNS;
diff --git a/src/gb/frame_relay.c b/src/gb/frame_relay.c
new file mode 100644
index 00000000..e973b915
--- /dev/null
+++ b/src/gb/frame_relay.c
@@ -0,0 +1,1051 @@
+/*! \file frame_relay.c
+ * Implement frame relay/PVC by Q.933
+ */
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/core/endian.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#define LOGPFRL(frl, lvl, fmt, args ...) \
+ LOGP(DFR, lvl, "%s: " fmt, (frl)->name, ## args)
+
+#define DFR DLNS
+
+/* Table 4-2/Q.931 */
+enum q931_msgtype {
+ /* Call establishment message */
+ Q931_MSGT_ALERTING = 0x01,
+ Q931_MSGT_CALL_PROCEEDING = 0x02,
+ Q931_MSGT_CONNECT = 0x07,
+ Q931_MSGT_CONNECT_ACK = 0x0f,
+ Q931_MSGT_PROGRESS = 0x03,
+ Q931_MSGT_SETUP = 0x05,
+ Q931_MSGT_SETUP_ACK = 0x0d,
+ /* Call information phase message */
+ Q931_MSGT_RESUME = 0x26,
+ Q931_MSGT_RESUME_ACK = 0x2e,
+ Q931_MSGT_RESUME_REJ = 0x22,
+ Q931_MSGT_SUSPEND = 0x25,
+ Q931_MSGT_SUSPEND_ACK = 0x2d,
+ Q931_MSGT_USER_INFO = 0x20,
+ /* Call clearing message */
+ Q931_MSGT_DISCONNECT = 0x45,
+ Q931_MSGT_RELEASE = 0x4d,
+ Q931_MSGT_RELEASE_COMPLETE = 0x5a,
+ Q931_MSGT_RESTART = 0x46,
+ Q931_MSGT_RESTART_ACK = 0x4e,
+ /* Miscellaneous messages */
+ Q931_MSGT_SEGMENT = 0x60,
+ Q931_MSGT_CONGESTION_CONTROL = 0x79,
+ Q931_MSGT_IFORMATION = 0x7b,
+ Q931_MSGT_NOTIFY = 0x6e,
+ Q931_MSGT_STATUS = 0x7d,
+ Q931_MSGT_STATUS_ENQUIRY = 0x75,
+};
+
+
+/* Figure A.1/Q.933 Report type information element */
+enum q933_type_of_report {
+ Q933_REPT_FULL_STATUS = 0x00,
+ Q933_REPT_LINK_INTEGRITY_VERIF = 0x01,
+ Q933_REPT_SINGLE_PVC_ASYNC_STS = 0x02,
+};
+
+/* Q.933 Section A.3 */
+enum q933_iei {
+ Q933_IEI_REPORT_TYPE = 0x51,
+ Q933_IEI_LINK_INT_VERIF = 0x53,
+ Q933_IEI_PVC_STATUS = 0x57,
+};
+
+/* Q.933 Section A.3.3 */
+enum q933_pvc_status {
+ Q933_PVC_STATUS_DLC_ACTIVE = 0x02,
+ Q933_PVC_STATUS_DLC_DELETE = 0x04,
+ Q933_PVC_STATUS_DLC_NEW = 0x08,
+};
+
+
+
+#define LAPF_UI 0x03 /* UI control word */
+#define Q931_PDISC_CC 0x08 /* protocol discriminator */
+#define LMI_Q933A_CALLREF 0x00 /* NULL call-ref */
+
+/* LMI DLCI values */
+#define LMI_Q933A_DLCI 0 /* Q.933A DLCI */
+#define LMI_CISCO_DLCI 1023 /* Cisco DLCI */
+
+/* maximum of supported */
+#define MAX_SUPPORTED_PVC 10
+
+/* TODO: add counters since good connection */
+
+/* Message header of the L3 payload of a Q.933 Annex A message */
+struct q933_a_hdr {
+ uint8_t prot_disc;
+ uint8_t call_ref;
+ uint8_t msg_type;
+} __attribute__((packed));
+
+/* Value part of the Q.933 Annex A.3.3 IE */
+struct q933_a_pvc_sts {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t dlci_msb:6,
+ spare:1,
+ ext0:1;
+ uint8_t space1:3,
+ dlci_lsb:4,
+ ext1:1;
+ uint8_t reserved:1,
+ active:1,
+ delete:1,
+ new:1,
+ spare2:3,
+ ext2:1;
+
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t ext0:1, spare:1, dlci_msb:6;
+ uint8_t ext1:1, dlci_lsb:4, space1:3;
+ uint8_t ext2:1, spare2:3, new:1, delete:1, active:1, reserved:1;
+#endif
+} __attribute__((packed));
+
+/* RX Message: 14 [ 00 01 03 08 00 75 95 01 01 00 03 02 01 00 ] */
+/* RX Message: 13 [ 00 01 03 08 00 75 51 01 00 53 02 01 00 ] */
+
+const struct value_string osmo_fr_role_names[] = {
+ { FR_ROLE_USER_EQUIPMENT, "USER" },
+ { FR_ROLE_NETWORK_EQUIPMENT, "NETWORK" },
+ { 0, NULL }
+};
+
+/* Table A.4/Q.933 */
+struct osmo_tdef fr_tdefs[] = {
+ {
+ .T=391,
+ .default_val = 10,
+ .min_val = 5,
+ .max_val = 30,
+ .desc = "Link integrity verification polling timer",
+ .unit = OSMO_TDEF_S,
+ }, {
+ .T=392,
+ .default_val = 15,
+ .min_val = 5,
+ .max_val = 30,
+ .desc = "Polling verification timer",
+ .unit = OSMO_TDEF_S,
+ },
+ {}
+};
+
+static const struct tlv_definition q933_att_tlvdef = {
+ .def = {
+ [Q933_IEI_REPORT_TYPE] = { TLV_TYPE_TLV },
+ [Q933_IEI_LINK_INT_VERIF] = { TLV_TYPE_TLV },
+ [Q933_IEI_PVC_STATUS] = { TLV_TYPE_TLV },
+ },
+};
+
+static void check_link_state(struct osmo_fr_link *link, bool valid);
+
+static inline uint16_t q922_to_dlci(const uint8_t *hdr)
+{
+ return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);
+}
+
+
+static inline void dlci_to_q922(uint8_t *hdr, uint16_t dlci)
+{
+ hdr[0] = (dlci >> 2) & 0xFC;
+ hdr[1] = ((dlci << 4) & 0xF0) | 0x01;
+}
+
+static void dlc_set_active(struct osmo_fr_dlc *dlc, bool active)
+{
+ if (active == dlc->active)
+ return;
+
+ dlc->active = active;
+
+ LOGPFRL(dlc->link, LOGL_NOTICE, "DLCI %u became %s\n", dlc->dlci, active ? "active" : "inactive");
+ if (dlc->status_cb)
+ dlc->status_cb(dlc, dlc->cb_data, active);
+}
+
+/* allocate a message buffer and put Q.933 Annex A headers (L2 + L3) */
+static struct msgb *q933_msgb_alloc(uint16_t dlci, uint8_t prot_disc, uint8_t msg_type)
+{
+ struct msgb *msg = msgb_alloc_headroom(1600+64, 64, "FR Q.933 Tx");
+ struct q933_a_hdr *qh;
+
+ if (!msg)
+ return NULL;
+
+ msg->l1h = msgb_put(msg, 2);
+ dlci_to_q922(msg->l1h, dlci);
+
+ /* LAPF UI control */
+ msg->l2h = msgb_put(msg, 1);
+ *msg->l2h = LAPF_UI;
+
+ msg->l3h = msgb_put(msg, sizeof(*qh));
+ qh = (struct q933_a_hdr *) msg->l3h;
+ qh->prot_disc = prot_disc;
+ qh->call_ref = LMI_Q933A_CALLREF;
+ qh->msg_type = msg_type;
+
+ return msg;
+}
+
+/* obtain the [next] transmit sequence number */
+static uint8_t link_get_tx_seq(struct osmo_fr_link *link)
+{
+ /* The {user equipment, network} increments the send sequence
+ * counter using modulo 256. The value zero is skipped. */
+ link->last_tx_seq++;
+ if (link->last_tx_seq == 0)
+ link->last_tx_seq++;
+
+ return link->last_tx_seq;
+}
+
+/* Append PVC Status IE according to Q.933 A.3.2 */
+static void msgb_put_link_int_verif(struct msgb *msg, struct osmo_fr_link *link)
+{
+ uint8_t link_int_tx[2];
+ link_int_tx[0] = link_get_tx_seq(link);
+ link_int_tx[1] = link->last_rx_seq;
+ msgb_tlv_put(msg, Q933_IEI_LINK_INT_VERIF, 2, link_int_tx);
+}
+
+static void dlc_destroy(struct osmo_fr_dlc *dlc)
+{
+ llist_del(&dlc->list);
+ talloc_free(dlc);
+}
+
+/* Append PVC Status IE according to Q.933 A.3.3 */
+static void msgb_put_pvc_status(struct msgb *msg, struct osmo_fr_dlc *dlc)
+{
+ uint8_t ie[3];
+
+ ie[0] = (dlc->dlci >> 4) & 0x3f;
+ /* extension bits */
+ ie[1] = 0x80 | ((dlc->dlci & 0xf) << 3);
+ /* extension bits */
+ ie[2] = 0x80;
+
+ /* FIXME: validate: this status should be added as long it's not yet acked by the remote */
+ if (dlc->active)
+ ie[2] |= Q933_PVC_STATUS_DLC_ACTIVE;
+
+ if (dlc->add) {
+ ie[2] |= Q933_PVC_STATUS_DLC_NEW;
+ /* we've reported it as new once, reset the status */
+ }
+
+ if (dlc->del) {
+ ie[2] |= Q933_PVC_STATUS_DLC_DELETE;
+ /* we've reported it as deleted once, destroy it */
+ dlc_destroy(dlc);
+ }
+
+ msgb_tlv_put(msg, Q933_IEI_PVC_STATUS, 3, ie);
+}
+
+/* Send a Q.933 STATUS ENQUIRY given type over given link */
+static int tx_lmi_q933_status_enq(struct osmo_fr_link *link, uint8_t rep_type)
+{
+ struct msgb *resp;
+
+ resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS_ENQUIRY);
+ if (!resp)
+ return -1;
+ resp->dst = link;
+ link->expected_rep = rep_type;
+
+ /* Table A.2/Q.933 */
+ msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type);
+ msgb_put_link_int_verif(resp, link);
+
+ return link->tx_cb(link->cb_data, resp);
+}
+
+/* Send a Q.933 STATUS of given type over given link */
+static int tx_lmi_q933_status(struct osmo_fr_link *link, uint8_t rep_type)
+{
+ struct osmo_fr_dlc *dlc;
+ struct msgb *resp;
+
+ resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS);
+ if (!resp)
+ return -1;
+
+ resp->dst = link;
+
+ /* Table A.1/Q.933 */
+ msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type);
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ msgb_put_link_int_verif(resp, link);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->add || dlc->del)
+ dlc->state_send = true;
+
+ msgb_put_pvc_status(resp, dlc);
+ }
+ break;
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ msgb_put_link_int_verif(resp, link);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->add || dlc->del) {
+ msgb_put_pvc_status(resp, dlc);
+ dlc->state_send = true;
+ }
+ }
+ break;
+ case Q933_REPT_SINGLE_PVC_ASYNC_STS:
+ llist_for_each_entry(dlc, &link->dlc_list, list)
+ msgb_put_pvc_status(resp, dlc);
+ break;
+ }
+
+ return link->tx_cb(link->cb_data, resp);
+}
+
+
+static void link_set_failed(struct osmo_fr_link *link)
+{
+ struct osmo_fr_dlc *dlc;
+
+ LOGPFRL(link, LOGL_NOTICE, "Link failed\n");
+ link->state = false;
+ if (link->status_cb)
+ link->status_cb(link, link->cb_data, link->state);
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ dlc_set_active(dlc, false);
+ }
+}
+
+/* Q.933 */
+static int rx_lmi_q933_status_enq(struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct osmo_fr_link *link = msg->dst;
+ struct osmo_fr_dlc *dlc;
+ const uint8_t *link_int_rx;
+ uint8_t rep_type;
+
+ OSMO_ASSERT(link);
+
+ if (link->role == FR_ROLE_USER_EQUIPMENT) {
+ LOGPFRL(link, LOGL_ERROR, "STATUS-ENQ aren't supported in role user\n");
+ return -1;
+ }
+
+ /* check for mandatory IEs */
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1) ||
+ !TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2))
+ return -1;
+
+ rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE);
+
+ link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF);
+ link->last_rx_seq = link_int_rx[0];
+
+ /* this is a bit of a hack. Q.933 explicitly forbids either side from ever
+ * sending a sequence number of '0'. Values start from '1' and are modulo 256,
+ * but '0' is always skipped. So if the peer is sending us a "last received
+ * sequence number of '0' it means it has not yet received any packets from us,
+ * which in turn can only mean that it has just been restarted. Let's treat
+ * this as "service affecting condition" and notify upper layers. This helps
+ * particularly in recovering from rapidly re-starting peers, where the Q.933
+ * nor NS have time to actually detect the connection was lost. Se OS#4974 */
+ if (link_int_rx[1] == 0) {
+ link_set_failed(link);
+ /* the network checks the receive sequence number received from
+ * the user equipment against its send sequence counter */
+ } else if (link_int_rx[1] != link->last_tx_seq) {
+ check_link_state(link, false);
+ link->err_count++;
+ } else {
+ check_link_state(link, true);
+ /* confirm DLC state changes */
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (!dlc->state_send)
+ continue;
+
+ if (dlc->add) {
+ dlc_set_active(dlc, link->state);
+ dlc->add = false;
+ }
+
+ if (dlc->del) {
+ dlc->del = false;
+ }
+
+ dlc->state_send = false;
+ }
+ }
+
+
+ /* The network responds to each STATUS ENQUIRY message with a
+ * STATUS message and resets the T392 timer */
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+
+ return tx_lmi_q933_status(link, rep_type);
+}
+
+/* check if the link become active.
+ * The link becomes active when enough times a STATUS/STATUS ENQUIRY arrives without any loss.
+ * Look at the last N393 STATUS/STATUS ENQUIRY PDUs. The link is valid if at least N392
+ * got received.
+ * param[in] valid contains the status of the last packet */
+static void check_link_state(struct osmo_fr_link *link, bool valid)
+{
+ unsigned int last, i;
+ unsigned int carry = 0;
+ struct osmo_fr_dlc *dlc;
+
+ link->succeed <<= 1;
+ if (valid)
+ link->succeed |= 1;
+
+ /* count the bits */
+ last = link->succeed & ((1 << link->net->n393) - 1);
+ for (i = 0; i < link->net->n393; i++)
+ if (last & (1 << i))
+ carry++;
+
+ if (link->net->n393 - carry >= link->net->n392) {
+ /* failing link */
+ if (!link->state)
+ return;
+
+ link_set_failed(link);
+ } else {
+ /* good link */
+ if (link->state)
+ return;
+
+ LOGPFRL(link, LOGL_NOTICE, "Link recovered\n");
+ link->state = true;
+ if (link->status_cb)
+ link->status_cb(link, link->cb_data, link->state);
+
+ if (link->role == FR_ROLE_USER_EQUIPMENT) {
+ /* make sure the next STATUS ENQUIRY is for a full
+ * status report to get the configred DLCs ASAP */
+ link->polling_count = 0;
+ /* we must not proceed further below if we're in user role,
+ * as otherwise link recovery would set all DLCs as active */
+ return;
+ }
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (!dlc->add && !dlc->del)
+ dlc_set_active(dlc, true);
+ }
+ }
+}
+
+static int validate_pvc_status(struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i;
+ uint16_t len = 0;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* PVC status can be 2 or 3 bytes. If the PVC is bigger
+ * ignore this to be compatible to future extensions. */
+ len = TLVP_LEN(&tp[i], Q933_IEI_PVC_STATUS);
+ if (len <= 1) {
+ return -EINVAL;
+ }
+ /* FIXME: validate correct flags: are some flags invalid at the same time? */
+ }
+
+ return 0;
+}
+
+static int parse_full_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i;
+ int err = 0;
+ struct osmo_fr_dlc *dlc, *tmp;
+ struct q933_a_pvc_sts *pvc;
+ uint16_t dlci = 0;
+ uint16_t *dlcis = talloc_zero_array(link, uint16_t, tp_len);
+ if (!dlcis)
+ return -ENOMEM;
+
+ /* first run validate all PVCs */
+ err = validate_pvc_status(tp, tp_len);
+ if (err < 0)
+ goto out;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* parse only 3 byte PVCs */
+ pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN(
+ &tp[i],
+ Q933_IEI_PVC_STATUS,
+ sizeof(struct q933_a_pvc_sts));
+ if (!pvc)
+ continue;
+
+ dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf);
+ dlcis[i] = dlci;
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (!dlc) {
+ dlc = osmo_fr_dlc_alloc(link, dlci);
+ if (!dlc) {
+ LOGPFRL(link, LOGL_ERROR, "Could not create DLC %d\n", dlci);
+ continue;
+ }
+ }
+
+ /* Figure A.3/Q.933: The delete bit is only applicable for timely notification
+ * using the optional single PVC asynchronous status report.
+ * Ignoring the delete. */
+ dlc->add = pvc->new;
+ dlc_set_active(dlc, pvc->active);
+ dlc->del = 0;
+ }
+
+ /* check if all dlc are present in PVC Status */
+ llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) {
+ bool found = false;
+ for (i = 0; i < tp_len; i++) {
+ if (dlcis[i] == dlc->dlci) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dlc_set_active(dlc, false);
+ dlc->del = true;
+ }
+ }
+
+ return 0;
+out:
+ talloc_free(dlcis);
+ return err;
+}
+
+static int parse_link_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len)
+{
+ int err;
+ size_t i;
+ struct q933_a_pvc_sts *pvc;
+ struct osmo_fr_dlc *dlc;
+ uint16_t dlci = 0;
+
+ err = validate_pvc_status(tp, tp_len);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* parse only 3 byte PVCs */
+ pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN(
+ &tp[i],
+ Q933_IEI_PVC_STATUS,
+ sizeof(struct q933_a_pvc_sts));
+ if (!pvc)
+ continue;
+
+ dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf);
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (!dlc) {
+ /* don't create dlc's for the ones which are about to be deleted. */
+ if (pvc->delete)
+ continue;
+
+ dlc = osmo_fr_dlc_alloc(link, dlci);
+ if (!dlc) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Could not create DLC %d\n", dlci);
+ continue;
+ }
+ }
+
+ if (pvc->delete) {
+ dlc->del = 1;
+ } else {
+ dlc->add = pvc->new;
+ dlc_set_active(dlc, pvc->active);
+ dlc->del = 0;
+ }
+ }
+
+ return 0;
+}
+
+static size_t count_pvc_status(struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i, count = 0;
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+ count++;
+ }
+
+ return count;
+}
+
+static int rx_lmi_q933_status(struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct osmo_fr_link *link = msg->dst;
+ const uint8_t *link_int_rx;
+ uint8_t rep_type;
+
+ OSMO_ASSERT(link);
+
+ if (link->role == FR_ROLE_NETWORK_EQUIPMENT) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: STATUS aren't supported in role network\n");
+ return -1;
+ }
+
+ /* check for mandatory IEs */
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1)) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Report Type\n");
+ return -1;
+ }
+
+ rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE);
+
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ if (rep_type != link->expected_rep) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Unexpected Q933 report type (got 0x%x != exp 0x%x)\n",
+ rep_type, link->expected_rep);
+ return -1;
+ }
+
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Link Integrety Verification\n");
+ return -1;
+ }
+ link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF);
+ link->last_rx_seq = link_int_rx[0];
+ /* The received receive sequence number is not valid if
+ * it is not equal to the last transmitted send sequence
+ * number. Ignore messages containing this error. As a
+ * result, timer T391 expires and the user then
+ * increments the error count. */
+ if (link_int_rx[1] != link->last_tx_seq)
+ return 0;
+ break;
+ case Q933_REPT_SINGLE_PVC_ASYNC_STS:
+ default:
+ return -1;
+ }
+
+ check_link_state(link, true);
+ if (count_pvc_status(tp, MAX_SUPPORTED_PVC + 1) > MAX_SUPPORTED_PVC) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Too many PVC! Only %d are supported!\n", MAX_SUPPORTED_PVC);
+ }
+
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ parse_full_pvc_status(link, tp, MAX_SUPPORTED_PVC);
+ break;
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ parse_link_pvc_status(link, tp, MAX_SUPPORTED_PVC);
+ break;
+ default:
+ break;
+ }
+
+ /* The network responds to each STATUS ENQUIRY message with a
+ * STATUS message and resets the T392 timer */
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+
+ return 0;
+}
+
+static int rx_lmi_q922(struct msgb *msg)
+{
+ struct osmo_fr_link *link = msg->dst;
+ struct q933_a_hdr *qh;
+ /* the + 1 is used to detect more than MAX_SUPPORTED_PVC */
+ struct tlv_parsed tp[MAX_SUPPORTED_PVC + 1];
+ uint8_t *lapf;
+ int rc;
+
+ OSMO_ASSERT(link);
+
+ if (msgb_l2len(msg) < 1)
+ return -1;
+ lapf = msgb_l2(msg);
+
+ /* we only support LAPF UI frames */
+ if (lapf[0] != LAPF_UI)
+ return -1;
+
+ msg->l3h = msg->l2h + 1;
+ if (msgb_l3len(msg) < 3)
+ return -1;
+
+ qh = (struct q933_a_hdr *) msgb_l3(msg);
+ if (qh->prot_disc != Q931_PDISC_CC) {
+ LOGPFRL(link, LOGL_NOTICE,
+ "Rx unsupported LMI protocol discriminator %u\n", qh->prot_disc);
+ return -1;
+ }
+
+ rc = tlv_parse2(tp, MAX_SUPPORTED_PVC + 1, &q933_att_tlvdef,
+ msgb_l3(msg) + sizeof(*qh),
+ msgb_l3len(msg) - sizeof(*qh), 0, 0);
+ if (rc < 0) {
+ LOGPFRL(link, LOGL_NOTICE,
+ "Failed to parse TLVs in LMI message type %u\n", qh->msg_type);
+ return rc;
+ }
+
+ switch (qh->msg_type) {
+ case Q931_MSGT_STATUS_ENQUIRY:
+ rc = rx_lmi_q933_status_enq(msg, tp);
+ break;
+ case Q931_MSGT_STATUS:
+ rc = rx_lmi_q933_status(msg, tp);
+ break;
+ default:
+ LOGPFRL(link, LOGL_NOTICE,
+ "Rx unsupported LMI message type %u\n", qh->msg_type);
+ rc = -1;
+ break;
+ }
+ msgb_free(msg);
+
+ return rc;
+}
+
+int osmo_fr_rx(struct msgb *msg)
+{
+ int rc = 0;
+ uint8_t *frh;
+ uint16_t dlci;
+ struct osmo_fr_dlc *dlc;
+ struct osmo_fr_link *link = msg->dst;
+
+ OSMO_ASSERT(link);
+
+ if (msgb_length(msg) < 2) {
+ LOGPFRL(link, LOGL_ERROR, "Rx short FR header: %u bytes\n", msgb_length(msg));
+ rc = -1;
+ goto out;
+ }
+
+ frh = msg->l1h = msgb_data(msg);
+ if (frh[0] & 0x01) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx Unsupported single-byte FR address\n");
+ rc = -1;
+ goto out;
+ }
+ if ((frh[1] & 0x0f) != 0x01) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx Unknown second FR octet 0x%02x\n", frh[1]);
+ rc = -1;
+ goto out;
+ }
+ dlci = q922_to_dlci(frh);
+ msg->l2h = frh + 2;
+
+ switch (dlci) {
+ case LMI_Q933A_DLCI:
+ return rx_lmi_q922(msg);
+ case LMI_CISCO_DLCI:
+ LOGPFRL(link, LOGL_ERROR, "Rx Unsupported FR DLCI %u\n", dlci);
+ goto out;
+ }
+
+ if (!link->state) {
+ LOGPFRL(link, LOGL_NOTICE, "Link is not reliable. Discarding Rx PDU on DLCI %d\n", dlci);
+ goto out;
+ }
+
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (dlc) {
+ if (dlc->active) {
+ /* dispatch to handler of respective DLC */
+ msg->dst = dlc;
+ return dlc->rx_cb(dlc->cb_data, msg);
+ } else {
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u not yet active. Discarding Rx PDU\n", dlci);
+ }
+ } else {
+ if (link->unknown_dlc_rx_cb)
+ return link->unknown_dlc_rx_cb(link->unknown_dlc_rx_cb_data, msg);
+ else
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u doesn't exist. Discarding Rx PDU\n", dlci);
+ }
+
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+int osmo_fr_tx_dlc(struct msgb *msg)
+{
+ uint8_t *frh;
+ struct osmo_fr_dlc *dlc = msg->dst;
+ struct osmo_fr_link *link = dlc->link;
+
+ OSMO_ASSERT(dlc);
+ OSMO_ASSERT(link);
+
+ if (!link->state) {
+ LOGPFRL(link, LOGL_NOTICE, "Link is not reliable (yet), discarding Tx\n");
+ msgb_free(msg);
+ return -1;
+ }
+ if (!dlc->active) {
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u is not active (yet), discarding Tx\n", dlc->dlci);
+ msgb_free(msg);
+ return -1;
+ }
+ LOGPFRL(link, LOGL_DEBUG, "DLCI %u is active, sending message\n", dlc->dlci);
+
+ if (msgb_headroom(msg) < 2) {
+ msgb_free(msg);
+ return -ENOSPC;
+ }
+
+ frh = msgb_push(msg, 2);
+ dlci_to_q922(frh, dlc->dlci);
+
+ msg->dst = link;
+ return link->tx_cb(link->cb_data, msg);
+}
+
+/* Every T391 seconds, the user equipment sends a STATUS ENQUIRY
+ * message to the network and resets its polling timer (T391). */
+static void fr_t391_cb(void *data)
+{
+ struct osmo_fr_link *link = data;
+
+ OSMO_ASSERT(link);
+
+ if (link->polling_count % link->net->n391 == 0)
+ tx_lmi_q933_status_enq(link, Q933_REPT_FULL_STATUS);
+ else
+ tx_lmi_q933_status_enq(link, Q933_REPT_LINK_INTEGRITY_VERIF);
+ link->polling_count++;
+ osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 10), 0);
+}
+
+static void fr_t392_cb(void *data)
+{
+ struct osmo_fr_link *link = data;
+
+ OSMO_ASSERT(link);
+
+ /* A.5 The network increments the error count .. Non-receipt of
+ * a STATUS ENQUIRY within T392, which results in restarting
+ * T392 */
+ link->err_count++;
+ check_link_state(link, false);
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+}
+
+/* allocate a frame relay network */
+struct osmo_fr_network *osmo_fr_network_alloc(void *ctx)
+{
+ struct osmo_fr_network *net = talloc_zero(ctx, struct osmo_fr_network);
+ if (!net)
+ return NULL;
+
+ INIT_LLIST_HEAD(&net->links);
+ net->T_defs = fr_tdefs;
+ osmo_tdefs_reset(net->T_defs);
+ net->n391 = 6;
+ net->n392 = 3;
+ net->n393 = 4;
+
+ return net;
+}
+
+void osmo_fr_network_free(struct osmo_fr_network *net)
+{
+ struct osmo_fr_link *link, *tmp;
+
+ if (!net)
+ return;
+
+ llist_for_each_entry_safe(link, tmp, &net->links, list) {
+ osmo_fr_link_free(link);
+ }
+}
+
+/* allocate a frame relay link in a given network */
+struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name)
+{
+ struct osmo_fr_link *link = talloc_zero(net, struct osmo_fr_link);
+ if (!link)
+ return NULL;
+ link->role = role;
+ link->net = net;
+ link->name = talloc_strdup(link, name);
+ INIT_LLIST_HEAD(&link->dlc_list);
+ llist_add_tail(&link->list, &net->links);
+
+ osmo_timer_setup(&link->t391, fr_t391_cb, link);
+ osmo_timer_setup(&link->t392, fr_t392_cb, link);
+
+ switch (role) {
+ case FR_ROLE_USER_EQUIPMENT:
+ osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 15), 0);
+ break;
+ case FR_ROLE_NETWORK_EQUIPMENT:
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+ break;
+ }
+
+ LOGPFRL(link, LOGL_INFO, "Creating frame relay link with role %s\n", osmo_fr_role_str(role));
+
+ return link;
+}
+
+void osmo_fr_link_free(struct osmo_fr_link *link)
+{
+ struct osmo_fr_dlc *dlc, *tmp;
+
+ if (!link)
+ return;
+
+ osmo_timer_del(&link->t391);
+ osmo_timer_del(&link->t392);
+
+ llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) {
+ osmo_fr_dlc_free(dlc);
+ }
+
+ llist_del(&link->list);
+ talloc_free(link);
+}
+
+/* allocate a data link connectoin on a given framerelay link */
+struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci)
+{
+ struct osmo_fr_dlc *dlc = talloc_zero(link, struct osmo_fr_dlc);
+ if (!dlc)
+ return NULL;
+
+ dlc->link = link;
+ dlc->dlci = dlci;
+ dlc->active = false;
+
+ llist_add_tail(&dlc->list, &link->dlc_list);
+
+ dlc->add = true;
+ tx_lmi_q933_status(link, Q933_REPT_SINGLE_PVC_ASYNC_STS);
+
+ return dlc;
+}
+
+void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc)
+{
+ llist_del(&dlc->list);
+ talloc_free(dlc);
+}
+
+/* TODO: rework osmo_fr_dlc_alloc/free with handling it's own memory.
+ * For network role: The dlc have to created by the application (e.g. vty).
+ * The dlc shouldn't free'd directly. It should be communicated to the
+ * other side and wait until it's confirmed OR the link go off and free it afterwards.
+ * For user equpment role: The dlc can be created by the application or the dlc will be created
+ * by the frame relay because the network is configuring the dlc.
+ * The dlc shouldn't be free'd. Only the handler should be set to NULL.
+ */
+
+struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci)
+{
+ struct osmo_fr_dlc *dlc;
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->dlci == dlci)
+ return dlc;
+ }
+ return NULL;
+}
+
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/tdef_vty.h>
+
+static void fr_dlc_dump_vty(struct vty *vty, const struct osmo_fr_dlc *dlc)
+{
+ vty_out(vty, " FR DLC %05u: %s%s%s%s", dlc->dlci,
+ dlc->active ? "ACTIVE" : "INACTIVE",
+ dlc->add ? " ADDED" : "", dlc->del ? " DELETED" : "", VTY_NEWLINE);
+}
+
+static void fr_link_dump_vty(struct vty *vty, const struct osmo_fr_link *link)
+{
+ const struct osmo_fr_dlc *dlc;
+
+ vty_out(vty, "FR Link '%s': Role %s, LastRxSeq %u, LastTxSeq %u%s",
+ link->name, link->role == FR_ROLE_USER_EQUIPMENT ? "USER" : "NETWORK",
+ link->last_rx_seq, link->last_tx_seq, VTY_NEWLINE);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ fr_dlc_dump_vty(vty, dlc);
+ }
+}
+
+void osmo_fr_network_dump_vty(struct vty *vty, const struct osmo_fr_network *net)
+{
+ struct osmo_fr_link *link;
+
+ vty_out(vty, "FR Network: N391 %u, N392 %u, N393 %u%s",
+ net->n391, net->n392, net->n393, VTY_NEWLINE);
+ osmo_tdef_vty_out_all(vty, net->T_defs, " ");
+ llist_for_each_entry(link, &net->links, list) {
+ fr_link_dump_vty(vty, link);
+ }
+}
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index 38794c28..d436f0aa 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -40,10 +40,16 @@
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/gprs_ns.h>
-#include "common_vty.h"
+#include "osmocom/gsm/gsm48.h"
+#include "gprs_bssgp_internal.h"
void *bssgp_tall_ctx = NULL;
+static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg);
+
+bssgp_bvc_send bssgp_ns_send = _gprs_ns_sendmsg;
+void *bssgp_ns_send_data = NULL;
+
static const struct rate_ctr_desc bssgp_ctr_description[] = {
{ "packets:in", "Packets at BSSGP Level ( In)" },
{ "packets:out","Packets at BSSGP Level (Out)" },
@@ -67,6 +73,14 @@ LLIST_HEAD(bssgp_bvc_ctxts);
static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv);
+
+/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */
+static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg)
+{
+ OSMO_ASSERT(bssgp_nsi);
+ return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
/* Find a BTS Context based on parsed RA ID and Cell ID */
struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid)
{
@@ -80,6 +94,75 @@ struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t
return NULL;
}
+/* Transmit a BVC-RESET or BVC-RESET-ACK with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] pdu Either BSSGP_PDUT_BVC_RESET or BSSGP_PDUT_BVC_RESET_ACK
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] cause The cause of the reset only valid for BSSGP_PDUT_BVC_RESET.
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+static int tx_bvc_reset_nsei_bvci(enum bssgp_pdu_type pdu, uint16_t nsei, uint16_t bvci,
+ enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph =
+ (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ uint16_t _bvci = osmo_htons(bvci);
+
+ OSMO_ASSERT(pdu == BSSGP_PDUT_BVC_RESET || pdu == BSSGP_PDUT_BVC_RESET_ACK);
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = BVCI_SIGNALLING;
+ bgph->pdu_type = pdu;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ if (pdu == BSSGP_PDUT_BVC_RESET) {
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
+ "CAUSE=%s\n", bvci, bssgp_cause_str(cause));
+ } else {
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET-ACK\n", bvci);
+ }
+
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ /* Optional: Feature Bitmap */
+
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/*! Transmit a BVC-RESET message with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] cause The cause of the reset
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET, nsei, bvci, cause, ra_id, cell_id);
+}
+
+/*! Transmit a BVC-RESET-ACK message with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+int bssgp_tx_bvc_reset_ack_nsei_bvci(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET_ACK, nsei, bvci, 0, ra_id, cell_id);
+}
+
/*! Initiate reset procedure for all PTP BVC on a given NSEI.
*
* This function initiates reset procedure for all PTP BVC with a given cause.
@@ -94,7 +177,7 @@ int bssgp_tx_bvc_ptp_reset(uint16_t nsei, enum gprs_bssgp_cause cause)
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (bctx->nsei == nsei && bctx->bvci != BVCI_SIGNALLING) {
- LOGP(DBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
+ LOGP(DLBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
nsei, bctx->bvci, bssgp_cause_str(cause));
rc = bssgp_tx_bvc_reset(bctx, bctx->bvci, cause);
if (rc < 0)
@@ -117,6 +200,12 @@ struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei)
return NULL;
}
+void bssgp_set_bssgp_callback(bssgp_bvc_send ns_send, void *data)
+{
+ bssgp_ns_send = ns_send;
+ bssgp_ns_send_data = data;
+}
+
struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
{
struct bssgp_bvc_ctx *ctx;
@@ -126,25 +215,36 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
return NULL;
ctx->bvci = bvci;
ctx->nsei = nsei;
+ ctx->is_sgsn = true;
/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
- if (!ctx->ctrg) {
- talloc_free(ctx);
- return NULL;
- }
+ if (!ctx->ctrg)
+ goto err_ctrg;
+
ctx->fc = talloc_zero(ctx, struct bssgp_flow_control);
+ if (!ctx->fc)
+ goto err_fc;
+
/* cofigure for 2Mbit, 30 packets in queue */
bssgp_fc_init(ctx->fc, 100000, 2*1024*1024/8, 30, &_bssgp_tx_dl_ud);
llist_add(&ctx->list, &bssgp_bvc_ctxts);
return ctx;
+
+err_fc:
+ rate_ctr_group_free(ctx->ctrg);
+err_ctrg:
+ talloc_free(ctx);
+ return NULL;
}
void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx)
{
if (!ctx)
return;
+
+ osmo_timer_del(&ctx->fc->timer);
rate_ctr_group_free(ctx->ctrg);
llist_del(&ctx->list);
talloc_free(ctx);
@@ -163,7 +263,7 @@ static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.7 SUSPEND-ACK PDU */
@@ -182,7 +282,7 @@ int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli,
bssgp_msgb_ra_put(msg, ra_id);
msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.8 SUSPEND-NACK PDU */
@@ -204,7 +304,7 @@ int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli,
if (cause)
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.10 RESUME-ACK PDU */
@@ -222,7 +322,7 @@ int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli,
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.11 RESUME-NACK PDU */
@@ -243,7 +343,7 @@ int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli,
if (cause)
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
@@ -254,6 +354,24 @@ uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
return osmo_load16be(buf+6);
}
+/*! Parse the value of a BSSGP Cell identity (04.08 RAI + Cell Id) */
+int bssgp_parse_cell_id2(struct osmo_routing_area_id *raid, uint16_t *cid,
+ const uint8_t *buf, size_t buf_len)
+{
+ if (buf_len < 8)
+ return -EINVAL;
+
+ /* 6 octets RAC */
+ if (raid)
+ osmo_routing_area_id_decode(raid, buf, buf_len);
+
+ /* 2 octets CID */
+ if (cid)
+ *cid = osmo_load16be(buf + sizeof(struct gsm48_ra_id));
+
+ return 0;
+}
+
int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid,
uint16_t cid)
{
@@ -265,8 +383,25 @@ int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid,
return 8;
}
+/*! Encode the 04.08 RAI, Cell Id into BSSGP Cell identity */
+int bssgp_create_cell_id2(uint8_t *buf, size_t buf_len,
+ const struct osmo_routing_area_id *raid,
+ const uint16_t cid)
+{
+ if (buf_len < 8)
+ return -ENOMEM;
+
+ /* 6 octets RAC */
+ osmo_routing_area_id_encode_buf(buf, buf_len, raid);
+
+ /* 2 octets CID */
+ osmo_store16be(cid, buf + sizeof(struct gsm48_ra_id));
+
+ return 8;
+}
+
/* Chapter 8.4 BVC-Reset Procedure */
-static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
+static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
uint16_t ns_bvci)
{
struct osmo_bssgp_prim nmp;
@@ -275,7 +410,7 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
uint16_t bvci;
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
/* look-up or create the BTS context for this BVC */
@@ -288,19 +423,26 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
* informs us about its RAC + Cell ID, so we can create a mapping */
- if (bvci != 0 && bvci != 1) {
- if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
+ if (bctx->is_sgsn && bvci != BVCI_SIGNALLING && bvci != BVCI_PTM) {
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
"missing mandatory IE\n", bvci);
return -EINVAL;
}
/* actually extract RAC / CID */
bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
TLVP_VAL(tp, BSSGP_IE_CELL_ID));
- LOGP(DBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
osmo_rai_name(&bctx->ra_id), bctx->cell_id, bvci);
}
+ /* Acknowledge the RESET to the BTS */
+ if (bvci == BVCI_SIGNALLING || bvci == BVCI_PTM || bctx->is_sgsn)
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+ nsei, bvci, ns_bvci);
+ else
+ bssgp_tx_bvc_reset_ack_nsei_bvci(nsei, bvci, &bctx->ra_id, bctx->cell_id);
+
/* Send NM_BVC_RESET.ind to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
@@ -310,10 +452,6 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_RESET,
PRIM_OP_INDICATION, msg);
bssgp_prim_cb(&nmp.oph, NULL);
-
- /* Acknowledge the RESET to the BTS */
- bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
- nsei, bvci, ns_bvci);
return 0;
}
@@ -326,20 +464,20 @@ static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp)
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
- LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+ LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received block for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
- LOGP(DBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
+ LOGP(DLBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
ptp_ctx->state |= BVC_S_BLOCKED;
- rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ptp_ctx->ctrg, BSSGP_CTR_BLOCKED));
/* Send NM_BVC_BLOCK.ind to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -364,13 +502,13 @@ static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp)
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
- LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+ LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received unblock for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
@@ -402,12 +540,12 @@ static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp,
/* extract TLLI and parse TLV IEs */
msgb_tlli(msg) = osmo_ntohl(budh->tlli);
- DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
+ DEBUGP(DLBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
/* Cell ID and LLC_PDU are the only mandatory IE */
- if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) ||
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8) ||
!TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
"missing mandatory IE\n", msgb_tlli(msg));
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -435,16 +573,16 @@ static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp)
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
@@ -476,10 +614,10 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) ||
- !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4 ) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_SUSPEND_REF_NR, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -487,7 +625,7 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
@@ -518,21 +656,21 @@ static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp,
uint32_t tlli = 0;
uint16_t nsei = msgb_nsei(msg);
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_LLC_FRAMES_DISCARDED, 1) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_NUM_OCT_AFF, 3)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
"missing mandatory IE\n", ctx->bvci);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
- if (TLVP_PRESENT(tp, BSSGP_IE_TLLI))
- tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+ tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
ctx->bvci, tlli);
- rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctx->ctrg, BSSGP_CTR_DISCARDED));
/* send NM_LLC_DISCARDED to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -553,27 +691,27 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
struct osmo_bssgp_prim nmp;
enum gprs_bssgp_cause cause;
- if (!TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
"missing mandatory IE\n", bvci);
cause = BSSGP_CAUSE_PROTO_ERR_UNSPEC;
} else {
cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
}
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
bvci, bssgp_cause_str(cause));
if (cause == BSSGP_CAUSE_BVCI_BLOCKED || cause == BSSGP_CAUSE_UNKNOWN_BVCI) {
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI))
- LOGP(DBSSGP, LOGL_ERROR,
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2))
+ LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP BVCI=%u Rx STATUS cause=%s "
"missing conditional BVCI IE\n",
bvci, bssgp_cause_str(cause));
}
if (bctx)
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_STATUS]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_STATUS));
/* send NM_STATUS to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -586,7 +724,6 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
return bssgp_prim_cb(&nmp.oph, NULL);
}
-
/* One element (msgb) in a BSSGP Flow Control queue */
struct bssgp_fc_queue_element {
/* linked list of queue elements */
@@ -618,7 +755,7 @@ static void fc_timer_cb(void *data)
list);
if (bssgp_fc_needs_queueing(fc, fcqe->llc_pdu_len)) {
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
"not able to send PDU of %u bytes\n", fcqe->llc_pdu_len);
/* make sure we re-start the timer */
fc_queue_timer_cfg(fc);
@@ -744,7 +881,7 @@ static int bssgp_fc_needs_queueing(struct bssgp_flow_control *fc, uint32_t pdu_l
static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv)
{
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* input function of the flow control implementation, called first
@@ -756,7 +893,7 @@ int bssgp_fc_in(struct bssgp_flow_control *fc, struct msgb *msg,
struct timeval time_now;
if (llc_pdu_len > fc->bucket_size_max) {
- LOGP(DBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
+ LOGP(DLBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
"than maximum bucket size (%u)!\n", llc_pdu_len,
fc->bucket_size_max);
msgb_free(msg);
@@ -817,15 +954,15 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
uint32_t old_leak_rate = bctx->fc->bucket_leak_rate;
uint32_t old_r_def_ms = bctx->r_default_ms;
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
bctx->bvci);
- if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) ||
- !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TAG, 1) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BVC_BUCKET_SIZE, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BUCKET_LEAK_RATE, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BMAX_DEFAULT_MS, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_R_DEFAULT_MS,2)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
"missing mandatory IE\n", bctx->bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -840,17 +977,17 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
bctx->r_default_ms = 100 * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS) / 8;
if (old_leak_rate != 0 && bctx->fc->bucket_leak_rate == 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of 0, stopping all DL GPRS!\n");
else if (old_leak_rate == 0 && bctx->fc->bucket_leak_rate != 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of != 0, restarting all DL GPRS!\n");
if (old_r_def_ms != 0 && bctx->r_default_ms == 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate of 0, stopping DL GPRS!\n");
else if (old_r_def_ms == 0 && bctx->r_default_ms != 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate != 0, restarting DL GPRS!\n");
/* reconfigure the timer for flow control based on new values */
@@ -887,13 +1024,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_RA_CAPABILITY:
/* BSS requests RA capability or IMSI */
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
bctx->bvci);
/* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */
/* FIXME: send RA_CAPA_UPDATE_ACK */
break;
case BSSGP_PDUT_RADIO_STATUS:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
/* BSS informs us of some exception */
/* FIXME: send GMM_RADIO_STATUS.ind to GMM */
break;
@@ -903,7 +1040,7 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_FLOW_CONTROL_MS:
/* BSS informs us of available bandwidth to one MS */
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
bctx->bvci);
/* FIXME: actually implement flow control */
/* FIXME: Send FLOW_CONTROL_MS_ACK */
@@ -911,12 +1048,15 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
+ case BSSGP_PDUT_BVC_RESET:
+ rc = bssgp_rx_bvc_reset(msg, tp, bctx->bvci);
+ break;
case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
case BSSGP_PDUT_MODIFY_BSS_PFC:
case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
"implemented\n", bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
break;
@@ -927,13 +1067,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
@@ -964,13 +1104,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_FLUSH_LL_ACK:
/* BSS informs us it has performed LL FLUSH */
- DEBUGP(DBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
+ DEBUGP(DLBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
/* FIXME: send NM_FLUSH_LL.res to NM */
break;
case BSSGP_PDUT_LLC_DISCARD:
/* BSS informs that some LLC PDU's have been discarded */
if (!bctx) {
- LOGP(DBSSGP, LOGL_ERROR,
+ LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP Rx LLC-DISCARD missing mandatory BVCI\n");
goto err_mand_ie;
}
@@ -978,9 +1118,9 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_BVC_BLOCK:
/* BSS tells us that BVC shall be blocked */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
@@ -988,21 +1128,21 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_BVC_UNBLOCK:
/* BSS tells us that BVC shall be unblocked */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
rc = bssgp_rx_bvc_unblock(msg, tp);
break;
case BSSGP_PDUT_BVC_RESET_ACK:
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
break;
case BSSGP_PDUT_BVC_RESET:
- /* BSS tells us that BVC init is required */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
+ /* SGSN or BSS tells us that BVC init is required */
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
"missing mandatory IE\n");
goto err_mand_ie;
}
@@ -1011,6 +1151,15 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
+
+ case BSSGP_PDUT_RAN_INFO:
+ case BSSGP_PDUT_RAN_INFO_REQ:
+ case BSSGP_PDUT_RAN_INFO_ACK:
+ case BSSGP_PDUT_RAN_INFO_ERROR:
+ case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+ rc = bssgp_rx_rim(msg, tp, bvci);
+ break;
+
/* those only exist in the SGSN -> BSS direction */
case BSSGP_PDUT_PAGING_PS:
case BSSGP_PDUT_PAGING_CS:
@@ -1022,13 +1171,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_BVC_BLOCK_ACK:
case BSSGP_PDUT_BVC_UNBLOCK_ACK:
case BSSGP_PDUT_SGSN_INVOKE_TRACE:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
@@ -1066,14 +1215,14 @@ int bssgp_rcvmsg(struct msgb *msg)
rc = bssgp_tlv_parse(&tp, budh->data, data_len);
}
if (rc < 0) {
- LOGP(DBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
bssgp_pdu_str(pdu_type), msgb_hexdump(msg));
if (pdu_type != BSSGP_PDUT_STATUS)
return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg);
return rc;
}
- if (bvci == BVCI_SIGNALLING && TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ if (bvci == BVCI_SIGNALLING && TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2))
bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
/* look-up or create the BTS context for this BVC */
@@ -1081,8 +1230,8 @@ int bssgp_rcvmsg(struct msgb *msg)
if (bctx) {
log_set_context(LOG_CTX_GB_BVC, bctx);
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN],
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_IN));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_IN),
msgb_bssgp_len(msg));
}
@@ -1096,7 +1245,7 @@ int bssgp_rcvmsg(struct msgb *msg)
* registered if a BVCI is given. */
if (!bctx && bvci != BVCI_SIGNALLING &&
pdu_type != BSSGP_PDUT_BVC_RESET) {
- LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
+ LOGP(DLBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
bssgp_pdu_str(pdu_type));
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
}
@@ -1108,7 +1257,7 @@ int bssgp_rcvmsg(struct msgb *msg)
else if (bctx)
rc = bssgp_rx_ptp(msg, &tp, bctx);
else
- LOGP(DBSSGP, LOGL_NOTICE,
+ LOGP(DLBSSGP, LOGL_NOTICE,
"NSEI=%u/BVCI=%u Cannot handle PDU type %s for unknown BVCI, NS BVCI %u\n", nsei, bvci,
bssgp_pdu_str(pdu_type), ns_bvci);
@@ -1132,7 +1281,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
if (bvci <= BVCI_PTM ) {
- LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
bvci);
msgb_free(msg);
return -EINVAL;
@@ -1140,7 +1289,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
bctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!bctx) {
- LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
bvci);
msgb_free(msg);
return -ENODEV;
@@ -1206,8 +1355,8 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
budh->tlli = osmo_htonl(msgb_tlli(msg));
budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
/* Identifiers down: BVCI, NSEI (in msgb->cb) */
@@ -1286,12 +1435,14 @@ int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *) &ptmsi);
}
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
void bssgp_set_log_ss(int ss)
{
- DBSSGP = ss;
+ /* BSSGP has moved from DGPRS to DLGPRS, please update your code if it's
+ * still calling this function
+ */
}
/*!
@@ -1312,7 +1463,7 @@ void bssgp_fc_flush_queue(struct bssgp_flow_control *fc)
/*!
* \brief Flush the queues of all BSSGP contexts.
*/
-void bssgp_flush_all_queues()
+void bssgp_flush_all_queues(void)
{
struct bssgp_bvc_ctx *bctx;
diff --git a/src/gb/gprs_bssgp2.c b/src/gb/gprs_bssgp2.c
new file mode 100644
index 00000000..104fe08e
--- /dev/null
+++ b/src/gb/gprs_bssgp2.c
@@ -0,0 +1,486 @@
+/* BSSGP2 - second generation of BSSGP library */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp2.h>
+
+
+/*! transmit BSSGP PDU over NS (PTP BVC)
+ * \param[in] nsi NS Instance through which to transmit
+ * \param[in] nsei NSEI of NSE through which to transmit
+ * \param[in] bvci BVCI through which to transmit
+ * \param[in] msg BSSGP PDU to transmit
+ * \returns 0 on success; negative on error */
+int bssgp2_nsi_tx_ptp(struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci,
+ struct msgb *msg, uint32_t lsp)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ int rc;
+
+ if (!msg)
+ return 0;
+
+ nsp.bvci = bvci;
+ nsp.nsei = nsei;
+ nsp.u.unitdata.link_selector = lsp;
+
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg);
+ rc = gprs_ns2_recv_prim(nsi, &nsp.oph);
+
+ return rc;
+}
+
+/*! transmit BSSGP PDU over NS (SIGNALING BVC)
+ * \param[in] nsi NS Instance through which to transmit
+ * \param[in] nsei NSEI of NSE through which to transmit
+ * \param[in] msg BSSGP PDU to transmit
+ * \returns 0 on success; negative on error */
+int bssgp2_nsi_tx_sig(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg, uint32_t lsp)
+{
+ return bssgp2_nsi_tx_ptp(nsi, nsei, 0, msg, lsp);
+}
+
+/*! Encode BSSGP BVC-BLOCK PDU as per TS 48.018 Section 10.4.8. */
+struct msgb *bssgp2_enc_bvc_block(uint16_t bvci, enum gprs_bssgp_cause cause)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-BLOCK-ACK PDU as per TS 48.018 Section 10.4.9. */
+struct msgb *bssgp2_enc_bvc_block_ack(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-UNBLOCK PDU as per TS 48.018 Section 10.4.10. */
+struct msgb *bssgp2_enc_bvc_unblock(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-UNBLOCK-ACK PDU as per TS 48.018 Section 10.4.11. */
+struct msgb *bssgp2_enc_bvc_unblock_ack(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-RESET PDU as per TS 48.018 Section 10.4.12.
+ * \param[in] bvci PTP BVCI to encode into the BVCI IE
+ * \param[in] cause BSSGP Cause value (reason for reset)
+ * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
+ * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
+ * \param[in] feat_bm Feature Bitmap (optional)
+ * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
+struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause,
+ const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ if (feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
+
+ if (ext_feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-RESET-ACK PDU as per TS 48.018 Section 10.4.13.
+ * \param[in] bvci PTP BVCI to encode into the BVCI IE
+ * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
+ * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
+ * \param[in] feat_bm Feature Bitmap (optional)
+ * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
+struct msgb *bssgp2_enc_bvc_reset_ack(uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_RESET_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ if (feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
+
+ if (ext_feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
+
+ return msg;
+}
+
+/*! Encode BSSGP STATUS PDU as per TS 48.018 Section 10.4.14.
+ * \param[in] cause BSSGP Cause value
+ * \param[in] bvci optional BVCI - only encoded if non-NULL
+ * \param[in] msg optional message buffer containing PDU in error - only encoded if non-NULL
+ * \param[in] max_pdu_len Maximum BSSGP PDU size the NS layer accepts */
+struct msgb *bssgp2_enc_status(uint8_t cause, const uint16_t *bvci, const struct msgb *orig_msg, uint16_t max_pdu_len)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_STATUS;
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
+ /* FIXME: Require/encode BVCI only if cause is BVCI unknown/blocked
+ * See 3GPP TS 48.018 Ch. 10.4.14 */
+ if (bvci) {
+ uint16_t _bvci = osmo_htons(*bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ }
+ if (orig_msg) {
+ uint32_t orig_len, max_orig_len;
+ /* Calculate how big the reply would be: the BSSGP msg so far + size of the PDU IN ERROR including tvl */
+ orig_len = msgb_bssgp_len(orig_msg);
+ max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
+ /* Truncate the difference between max_orig_len and mtu */
+ if (max_orig_len > max_pdu_len)
+ orig_len -= max_orig_len - max_pdu_len;
+ msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, orig_len, msgb_bssgph(orig_msg));
+ }
+
+ return msg;
+}
+
+static const unsigned int bssgp_fc_gran_tbl[] = {
+ [BSSGP_FC_GRAN_100] = 100,
+ [BSSGP_FC_GRAN_1000] = 1000,
+ [BSSGP_FC_GRAN_10000] = 10000,
+ [BSSGP_FC_GRAN_100000] = 100000,
+};
+
+/*! Decode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ * \param[out] fc caller-allocated memory for parsed output
+ * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ * \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp)
+{
+ unsigned int granularity = 100;
+
+ /* optional "Flow Control Granularity IE" (11.3.102); applies to
+ * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+ if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+ uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+ granularity = bssgp_fc_gran_tbl[gran & 3];
+ }
+
+ /* mandatory IEs */
+ fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+ fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE);
+ fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+ fc->u.bvc.bmax_default_ms = granularity * tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS);
+ fc->u.bvc.r_default_ms = (granularity * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS)) / 8;
+
+ /* optional / conditional */
+ if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+ fc->bucket_full_ratio_present = true;
+ fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+ } else {
+ fc->bucket_full_ratio_present = false;
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_BVC_MEASUREMENT)) {
+ uint16_t val = tlvp_val16be(tp, BSSGP_IE_BVC_MEASUREMENT);
+ fc->u.bvc.measurement_present = true;
+ /* convert from centi-seconds to milli-seconds */
+ if (val == 0xffff)
+ fc->u.bvc.measurement = 0xffffffff;
+ else
+ fc->u.bvc.measurement = val * 10;
+ } else {
+ fc->u.bvc.measurement_present = false;
+ }
+
+ return 0;
+
+}
+
+/*! Encode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ * \param[in] fc structure describing to-be-encoded FC parameters
+ * \param[in] gran if non-NULL: Encode using specified unit granularity
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ unsigned int granularity = 100;
+
+ if (gran)
+ granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC;
+
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_BUCKET_SIZE, fc->bucket_size_max / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BMAX_DEFAULT_MS, fc->u.bvc.bmax_default_ms / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_R_DEFAULT_MS, fc->u.bvc.r_default_ms * 8 / granularity);
+
+ if (fc->bucket_full_ratio_present)
+ msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+ if (fc->u.bvc.measurement_present) {
+ uint16_t val;
+ /* convert from ms to cs */
+ if (fc->u.bvc.measurement == 0xffffffff)
+ val = 0xffff;
+ else
+ val = fc->u.bvc.measurement / 10;
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_MEASUREMENT, val);
+ }
+
+ if (gran) {
+ uint8_t val = *gran & 3;
+ msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+ }
+
+ return msg;
+}
+
+/*! Encode BSSGP FLUSH-LL PDU as per TS 48.018 Section 10.4.1.
+ * \param[in] tlli - the TLLI of the MS
+ * \param[in] old_bvci BVCI
+ * \param[in] new_bvci2 optional BVCI - only encoded if non-NULL
+ * \param[in] nsei optional - only encoded if non-NULL
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci,
+ const uint16_t *new_bvci, const uint16_t *nsei)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLUSH_LL;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, old_bvci);
+ if (new_bvci)
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *new_bvci);
+
+ if (nsei)
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *nsei);
+
+ return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4.
+ * \param[in] tag the tag IE value to encode
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+ return msg;
+}
+
+/*! Decode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ * \param[out] fc caller-allocated memory for parsed output
+ * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ * \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp)
+{
+ unsigned int granularity = 100;
+
+ /* optional "Flow Control Granularity IE" (11.3.102); applies to
+ * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+ if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+ uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+ granularity = bssgp_fc_gran_tbl[gran & 3];
+ }
+
+ /* mandatory IEs */
+ fc->u.ms.tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+ fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+ fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_MS_BUCKET_SIZE);
+ fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+
+ /* optional / conditional */
+ if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+ fc->bucket_full_ratio_present = true;
+ fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+ } else {
+ fc->bucket_full_ratio_present = false;
+ }
+
+ return 0;
+}
+
+/*! Encode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ * \param[in] fc structure describing to-be-encoded FC parameters
+ * \param[in] gran if non-NULL: Encode using specified unit granularity
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ unsigned int granularity = 100;
+
+ if (gran)
+ granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, fc->u.ms.tlli);
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_MS_BUCKET_SIZE, fc->bucket_size_max / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+
+ if (fc->bucket_full_ratio_present)
+ msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+ if (gran) {
+ uint8_t val = *gran & 3;
+ msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+ }
+
+ return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.7.
+ * \param[in] tlli the TLLI IE value to encode
+ * \param[in] tag the tag IE value to encode
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS_ACK;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+ return msg;
+}
diff --git a/src/gb/gprs_bssgp_bss.c b/src/gb/gprs_bssgp_bss.c
index 5a1ffb92..8230d871 100644
--- a/src/gb/gprs_bssgp_bss.c
+++ b/src/gb/gprs_bssgp_bss.c
@@ -34,7 +34,7 @@
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/gprs_ns.h>
-#include "common_vty.h"
+#include "gprs_bssgp_internal.h"
#define GSM_IMSI_LENGTH 17
@@ -60,7 +60,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli,
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n",
tlli);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -69,7 +69,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli,
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! GMM-RESUME.req (Chapter 10.3.9) */
@@ -80,7 +80,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli,
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n",
tlli);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -91,7 +91,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli,
msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit RA-CAPABILITY-UPDATE (10.3.3) */
@@ -101,7 +101,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag)
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n",
bctx->bvci, tlli);
/* set NSEI and BVCI in msgb cb */
@@ -113,7 +113,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag)
msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* first common part of RADIO-STATUS */
@@ -123,7 +123,7 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx)
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ",
bctx->bvci);
/* set NSEI and BVCI in msgb cb */
@@ -139,9 +139,9 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx)
static int common_tx_radio_status2(struct msgb *msg, uint8_t cause)
{
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- LOGPC(DBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause));
+ LOGPC(DLBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause));
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit RADIO-STATUS for TLLI (10.3.5) */
@@ -153,7 +153,7 @@ int bssgp_tx_radio_status_tlli(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (!msg)
return -ENOMEM;
bssgp_msgb_tlli_put(msg, tlli);
- LOGPC(DBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli);
return common_tx_radio_status2(msg, cause);
}
@@ -168,7 +168,7 @@ int bssgp_tx_radio_status_tmsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (!msg)
return -ENOMEM;
msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *)&_tmsi);
- LOGPC(DBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi);
return common_tx_radio_status2(msg, cause);
}
@@ -194,7 +194,7 @@ int bssgp_tx_radio_status_imsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (imsi_len > 2)
msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
#pragma GCC diagnostic pop
- LOGPC(DBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi);
return common_tx_radio_status2(msg, cause);
}
@@ -220,7 +220,7 @@ int bssgp_tx_flush_ll_ack(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci_new);
msgb_tvlv_put(msg, BSSGP_IE_NUM_OCT_AFF, 3, (uint8_t *) &_oct_aff);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit LLC-DISCARDED (Chapter 10.4.3) */
@@ -233,7 +233,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
uint16_t _bvci = osmo_htons(bctx->bvci);
uint32_t _oct_aff = osmo_htonl(num_octets & 0xFFFFFF);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED "
"TLLI=0x%04x, FRAMES=%u, OCTETS=%u\n", bctx->bvci, tlli,
num_frames, num_octets);
msgb_nsei(msg) = bctx->nsei;
@@ -246,7 +246,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
msgb_tvlv_put(msg, BSSGP_IE_NUM_OCT_AFF, 3, ((uint8_t *) &_oct_aff) + 1);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-BLOCK message (Chapter 10.4.8) */
@@ -257,7 +257,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause)
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bctx->bvci);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK "
"CAUSE=%s\n", bctx->bvci, bssgp_cause_str(cause));
msgb_nsei(msg) = bctx->nsei;
@@ -267,7 +267,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause)
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-UNBLOCK message (Chapter 10.4.10) */
@@ -278,7 +278,7 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx)
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bctx->bvci);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci);
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci);
msgb_nsei(msg) = bctx->nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -286,38 +286,29 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx)
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-RESET message (Chapter 10.4.12) */
int bssgp_tx_bvc_reset2(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause, bool add_cell_id)
{
- struct msgb *msg = bssgp_msgb_alloc();
- struct bssgp_normal_hdr *bgph =
- (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- uint16_t _bvci = osmo_htons(bvci);
-
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
- "CAUSE=%s\n", bvci, bssgp_cause_str(cause));
-
- msgb_nsei(msg) = bctx->nsei;
- msgb_bvci(msg) = 0; /* Signalling */
- bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
-
- msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- if (add_cell_id) {
- uint8_t bssgp_cid[8];
- bssgp_create_cell_id(bssgp_cid, &bctx->ra_id, bctx->cell_id);
- msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
- }
- /* Optional: Feature Bitmap */
-
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ if (add_cell_id)
+ return bssgp_tx_bvc_reset_nsei_bvci(bctx->nsei, bvci, cause, &bctx->ra_id, bctx->cell_id);
+ else
+ return bssgp_tx_bvc_reset_nsei_bvci(bctx->nsei, bvci, cause, NULL, 0);
}
int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause)
{
- return bssgp_tx_bvc_reset2(bctx, bvci, cause, bvci != BVCI_PTM);
+ /* The Cell Identifier IE is mandatory in the BVC-RESET PDU sent from BSS to SGSN in order to reset a
+ * BVC corresponding to a PTP functional entity. The Cell Identifier IE shall not be used in any other
+ * BVC-RESET PDU. */
+ switch (bvci) {
+ case BVCI_SIGNALLING:
+ case BVCI_PTM:
+ return bssgp_tx_bvc_reset2(bctx, bvci, cause, false);
+ default:
+ return bssgp_tx_bvc_reset2(bctx, bvci, cause, true);
+ }
}
/*! Transmit a FLOW_CONTROL-BVC (Chapter 10.4.4)
@@ -389,7 +380,7 @@ int bssgp_tx_fc_bvc(struct bssgp_bvc_ctx *bctx, uint8_t tag,
sizeof(e_queue_delay),
(uint8_t *) &e_queue_delay);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a FLOW_CONTROL-MS (Chapter 10.4.6)
@@ -432,7 +423,7 @@ int bssgp_tx_fc_ms(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag,
msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO,
1, bucket_full_ratio);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! RL-UL-UNITDATA.req (Chapter 10.2.2)
@@ -475,10 +466,10 @@ int bssgp_tx_ul_ud(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_nsei(msg) = bctx->nsei;
msgb_bvci(msg) = bctx->bvci;
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* Parse a single GMM-PAGING.req to a given NSEI/NS-BVCI */
@@ -519,24 +510,24 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo,
TLVP_LEN(&tp, BSSGP_IE_IMSI));
/* DRX Parameters */
- if (!TLVP_PRESENT(&tp, BSSGP_IE_DRX_PARAMS))
+ if (!TLVP_PRES_LEN(&tp, BSSGP_IE_DRX_PARAMS, 2))
goto err_mand_ie;
pinfo->drx_params = tlvp_val16be(&tp, BSSGP_IE_DRX_PARAMS);
/* Scope */
- if (TLVP_PRESENT(&tp, BSSGP_IE_BSS_AREA_ID)) {
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_BSS_AREA_ID, 1)) {
pinfo->scope = BSSGP_PAGING_BSS_AREA;
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_LOCATION_AREA)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_LOCATION_AREA, 5)) {
pinfo->scope = BSSGP_PAGING_LOCATION_AREA;
memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_LOCATION_AREA),
TLVP_LEN(&tp, BSSGP_IE_LOCATION_AREA));
gsm48_parse_ra(&pinfo->raid, ra);
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_ROUTEING_AREA, 6)) {
pinfo->scope = BSSGP_PAGING_ROUTEING_AREA;
memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
TLVP_LEN(&tp, BSSGP_IE_ROUTEING_AREA));
gsm48_parse_ra(&pinfo->raid, ra);
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) {
pinfo->scope = BSSGP_PAGING_BVCI;
pinfo->bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
} else
@@ -554,8 +545,7 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo,
}
/* Optional (P-)TMSI */
- if (TLVP_PRESENT(&tp, BSSGP_IE_TMSI) &&
- TLVP_LEN(&tp, BSSGP_IE_TMSI) >= 4) {
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_TMSI, 4)) {
if (!pinfo->ptmsi)
pinfo->ptmsi = talloc_zero_size(pinfo, sizeof(uint32_t));
*(pinfo->ptmsi) = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TMSI));
diff --git a/src/gb/gprs_bssgp_internal.h b/src/gb/gprs_bssgp_internal.h
new file mode 100644
index 00000000..5022d32d
--- /dev/null
+++ b/src/gb/gprs_bssgp_internal.h
@@ -0,0 +1,9 @@
+
+#pragma once
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+extern bssgp_bvc_send bssgp_ns_send;
+extern void *bssgp_ns_send_data;
+
+int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci);
diff --git a/src/gb/gprs_bssgp_rim.c b/src/gb/gprs_bssgp_rim.c
new file mode 100644
index 00000000..9c09e1ef
--- /dev/null
+++ b/src/gb/gprs_bssgp_rim.c
@@ -0,0 +1,1261 @@
+/*! \file gprs_bssgp.c
+ * GPRS BSSGP RIM protocol implementation as per 3GPP TS 48.018. */
+/*
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp_rim.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include "gprs_bssgp_internal.h"
+
+/* TVLV IEs use a variable length field. To be sure we will do all buffer
+ * length checks with the maximum possible header length, which is
+ * 1 octet tag + 2 octets length = 3 */
+#define TVLV_HDR_MAXLEN 3
+
+/* Usually RIM application containers and their surrounding RIM containers
+ * are not likely to exceed 128 octets, so the usual header length will be 2 */
+#define TVLV_HDR_LEN 2
+
+/* The reporting cell identifier is encoded as a cell identifier IE
+ * (3GPP TS 48.018, sub-clause 11.3.9) but without IE and length octets. */
+#define REP_CELL_ID_LEN 8
+
+const struct value_string bssgp_rim_routing_info_discr_strs[] = {
+ { BSSGP_RIM_ROUTING_INFO_GERAN, "GERAN-cell" },
+ { BSSGP_RIM_ROUTING_INFO_UTRAN, "UTRAN-RNC" },
+ { BSSGP_RIM_ROUTING_INFO_EUTRAN, "E-UTRAN-eNodeB/HeNB" },
+ { 0, NULL }
+};
+
+/*! Parse a RIM Routing address IE (3GPP TS 29.060, chapter 7.7.57 and 7.7.77).
+ * \param[out] ri user provided memory to store the parsed results.
+ * \param[in] buf input buffer of the value part of the RIM Routing address IE.
+ * \discr[in] discr value part (one byte) of the RIM Routing Address Discriminator IE.
+ * \returns length of parsed octets, -EINVAL on error. */
+int bssgp_parse_rim_ra(struct bssgp_rim_routing_info *ri, const uint8_t *buf,
+ unsigned int len, uint8_t discr)
+{
+ struct gprs_ra_id raid_temp;
+
+ memset(ri, 0, sizeof(*ri));
+ if (len < 2)
+ return -EINVAL;
+
+ ri->discr = discr;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ if (len < 8)
+ return -EINVAL;
+ ri->geran.cid = bssgp_parse_cell_id(&ri->geran.raid, buf);
+ return 8;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ if (len < 8)
+ return -EINVAL;
+ gsm48_parse_ra(&ri->utran.raid, buf);
+ ri->utran.rncid = osmo_load16be(buf + 6);
+ return 8;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ if (len < 6 || len > 13)
+ return -EINVAL;
+ /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
+ * Figure 10.5.130 specify MCC/MNC encoding in the same way,
+ * so we can re-use gsm48_parse_ra() for that. */
+ gsm48_parse_ra(&raid_temp, buf);
+ ri->eutran.tai.mcc = raid_temp.mcc;
+ ri->eutran.tai.mnc = raid_temp.mnc;
+ ri->eutran.tai.mnc_3_digits = raid_temp.mnc_3_digits;
+ ri->eutran.tai.tac = osmo_load16be(buf + 3);
+ memcpy(ri->eutran.global_enb_id, buf + 5, len - 5);
+ ri->eutran.global_enb_id_len = len - 5;
+ return len;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*! Parse a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70).
+ * \param[out] ri user provided memory to store the parsed results.
+ * \param[in] buf input buffer of the value part of the IE.
+ * \returns length of parsed octets, -EINVAL on error. */
+int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf,
+ unsigned int len)
+{
+ uint8_t discr;
+ int rc;
+
+ if (len < 1)
+ return -EINVAL;
+
+ discr = buf[0] & 0x0f;
+
+ rc = bssgp_parse_rim_ra(ri, buf + 1, len - 1, discr);
+ if (rc < 0)
+ return rc;
+ return rc + 1;
+}
+
+/*! Encode a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70).
+ * \param[out] buf user provided memory (at least 14 byte) for the generated value part of the IE.
+ * \param[in] ri user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_create_rim_ri(uint8_t *buf, const struct bssgp_rim_routing_info *ri)
+{
+ int rc;
+ struct gprs_ra_id raid_temp;
+ int len;
+
+ buf[0] = ri->discr & 0x0f;
+ buf++;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ rc = bssgp_create_cell_id(buf, &ri->geran.raid, ri->geran.cid);
+ if (rc < 0)
+ return -EINVAL;
+ len = rc + 1;
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ gsm48_encode_ra((struct gsm48_ra_id *)buf, &ri->utran.raid);
+ osmo_store16be(ri->utran.rncid, buf + 6);
+ len = 9;
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
+ * Figure 10.5.130 specify MCC/MNC encoding in the same way,
+ * so we can re-use gsm48_encode_ra() for that. */
+ raid_temp = (struct gprs_ra_id) {
+ .mcc = ri->eutran.tai.mcc,
+ .mnc = ri->eutran.tai.mnc,
+ .mnc_3_digits = ri->eutran.tai.mnc_3_digits,
+ };
+
+ gsm48_encode_ra((struct gsm48_ra_id *)buf, &raid_temp);
+ osmo_store16be(ri->eutran.tai.tac, buf + 3);
+ OSMO_ASSERT(ri->eutran.global_enb_id_len <=
+ sizeof(ri->eutran.global_enb_id));
+ memcpy(buf + 5, ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len);
+ len = ri->eutran.global_enb_id_len + 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(len <= BSSGP_RIM_ROUTING_INFO_MAXLEN);
+ return len;
+}
+
+/*! Encode a RIM Routing information into a human readable string.
+ * \param[buf] user provided string buffer to store the resulting string.
+ * \param[buf_len] maximum length of string buffer.
+ * \param[in] ri user provided input data struct.
+ * \returns pointer to the beginning of the resulting string stored in string buffer. */
+char *bssgp_rim_ri_name_buf(char *buf, size_t buf_len, const struct bssgp_rim_routing_info *ri)
+{
+ char plmn_str[16];
+ char enb_id_str[16];
+ char g_id_ps_str[32];
+ struct osmo_plmn_id plmn;
+ struct osmo_cell_global_id_ps g_id_ps;
+
+ if (!ri)
+ return NULL;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ g_id_ps.rai.rac = ri->geran.raid.rac;
+ g_id_ps.rai.lac.lac = ri->geran.raid.lac;
+ g_id_ps.rai.lac.plmn.mcc = ri->geran.raid.mcc;
+ g_id_ps.rai.lac.plmn.mnc_3_digits = ri->geran.raid.mnc_3_digits;
+ g_id_ps.rai.lac.plmn.mnc = ri->geran.raid.mnc;
+ g_id_ps.cell_identity = ri->geran.cid;
+ snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps));
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ g_id_ps.rai.rac = ri->utran.raid.rac;
+ g_id_ps.rai.lac.lac = ri->utran.raid.lac;
+ g_id_ps.rai.lac.plmn.mcc = ri->utran.raid.mcc;
+ g_id_ps.rai.lac.plmn.mnc_3_digits = ri->utran.raid.mnc_3_digits;
+ g_id_ps.rai.lac.plmn.mnc = ri->utran.raid.mnc;
+ g_id_ps.cell_identity = ri->utran.rncid;
+ snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps));
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ plmn.mcc = ri->eutran.tai.mcc;
+ plmn.mnc = ri->eutran.tai.mnc;
+ plmn.mnc_3_digits = ri->eutran.tai.mnc_3_digits;
+ snprintf(buf, buf_len, "%s-%s-%u-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_plmn_name_buf(plmn_str, sizeof(plmn_str), &plmn), ri->eutran.tai.tac,
+ osmo_hexdump_buf(enb_id_str, sizeof(enb_id_str), ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len, "", false));
+ break;
+ default:
+ snprintf(buf, buf_len, "invalid");
+ }
+
+ return buf;
+}
+
+/*! Encode a RIM Routing information into a human readable string.
+ * \param[in] ri user provided input data struct.
+ * \returns pointer to the resulting string. */
+const char *bssgp_rim_ri_name(const struct bssgp_rim_routing_info *ri)
+{
+ static __thread char rim_ri_buf[64];
+ return bssgp_rim_ri_name_buf(rim_ri_buf, sizeof(rim_ri_buf), ri);
+}
+
+/*! Decode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_req_app_cont_nacc(struct bssgp_ran_inf_req_app_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+
+ if (len < REP_CELL_ID_LEN)
+ return -EINVAL;
+
+ rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell,
+ CELL_IDENT_WHOLE_GLOBAL_PS, buf, len);
+ if (rc < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*! Encode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_req_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_app_cont_nacc *cont)
+{
+ int rc;
+ struct gprs_ra_id *raid;
+
+ if (len < REP_CELL_ID_LEN)
+ return -EINVAL;
+
+ raid = (struct gprs_ra_id *)&cont->reprt_cell.rai;
+ rc = bssgp_create_cell_id(buf, raid, cont->reprt_cell.cell_identity);
+ if (rc < 0)
+ return -EINVAL;
+ return rc;
+}
+
+/*! Decode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_app_cont_nacc(struct bssgp_ran_inf_app_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ unsigned int i;
+ int remaining_buf_len;
+ int rc;
+
+ /* The given buffer must at least contain a reporting cell identifer
+ * plus one octet that defines number/type of attached sysinfo messages. */
+ if (len < REP_CELL_ID_LEN + 1)
+ return -EINVAL;
+
+ rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell,
+ CELL_IDENT_WHOLE_GLOBAL_PS, buf, len);
+ if (rc < 0)
+ return -EINVAL;
+
+ buf += REP_CELL_ID_LEN;
+
+ cont->type_psi = buf[0] & 1;
+ cont->num_si = buf[0] >> 1;
+ buf++;
+
+ /* The number of sysinfo messages may be zero */
+ if (cont->num_si == 0)
+ return 0;
+
+ /* Check if the prospected system information messages fit in the
+ * remaining buffer space */
+ remaining_buf_len = len - REP_CELL_ID_LEN - 1;
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+ if (cont->type_psi && remaining_buf_len / BSSGP_RIM_PSI_LEN < cont->num_si)
+ return -EINVAL;
+ else if (remaining_buf_len / BSSGP_RIM_SI_LEN < cont->num_si)
+ return -EINVAL;
+
+ for (i = 0; i < cont->num_si; i++) {
+ cont->si[i] = buf;
+ if (cont->type_psi)
+ buf += BSSGP_RIM_PSI_LEN;
+ else
+ buf += BSSGP_RIM_SI_LEN;
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_cont_nacc *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int rc;
+ unsigned int silen;
+ unsigned int i;
+ struct gprs_ra_id *raid;
+
+ if (cont->type_psi)
+ silen = BSSGP_RIM_PSI_LEN;
+ else
+ silen = BSSGP_RIM_SI_LEN;
+
+ /* The buffer must accept the reporting cell id, plus 1 byte to define
+ * the type and number of sysinfo messages. */
+ if (len < REP_CELL_ID_LEN + 1 + silen * cont->num_si)
+ return -EINVAL;
+
+ raid = (struct gprs_ra_id *)&cont->reprt_cell.rai;
+ rc = bssgp_create_cell_id(buf_ptr, raid, cont->reprt_cell.cell_identity);
+ if (rc < 0)
+ return -EINVAL;
+ buf_ptr += rc;
+
+ buf_ptr[0] = 0x00;
+ if (cont->type_psi)
+ buf_ptr[0] |= 0x01;
+ buf_ptr[0] |= (cont->num_si << 1);
+ buf_ptr++;
+
+ for (i = 0; i < cont->num_si; i++) {
+ memcpy(buf_ptr, cont->si[i], silen);
+ buf_ptr += silen;
+ }
+
+ return (int)(buf_ptr - buf);
+}
+
+/* 3GPP TS 48.018, table 11.3.64.1.b, NACC Cause coding */
+const struct value_string bssgp_nacc_cause_strs[] = {
+ { BSSGP_NACC_CAUSE_UNSPEC, "unspecified error" },
+ { BSSGP_NACC_CAUSE_SYNTAX_ERR, "syntax error in app container" },
+ { BSSGP_NACC_CAUSE_RPRT_CELL_MISSMTCH, "reporting cell id mismatch" },
+ { BSSGP_NACC_CAUSE_SIPSI_TYPE_ERR, "SI/PSI type error" },
+ { BSSGP_NACC_CAUSE_SIPSI_LEN_ERR, "SI/PSI inconsistent length" },
+ { BSSGP_NACC_CAUSE_SIPSI_SET_ERR, "inconsistent set of msg" },
+ { 0, NULL }
+};
+
+/*! Decode a Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_app_err_cont_nacc(struct bssgp_app_err_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ /* The buffer must at least contain the NACC cause code, it should also
+ * contain the application container, but we won't error if it is missing. */
+ if (len < 1)
+ return -EINVAL;
+
+ cont->nacc_cause = buf[0];
+
+ if (len > 1) {
+ cont->err_app_cont = buf + 1;
+ cont->err_app_cont_len = len - 1;
+ } else {
+ cont->err_app_cont = NULL;
+ cont->err_app_cont_len = 0;
+ }
+
+ return 0;
+}
+
+/*! Encode Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_app_err_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_app_err_cont_nacc *cont)
+{
+ uint8_t *buf_ptr = buf;
+
+ /* The buffer must accept the length of the application container and the NACC
+ * cause code, which is one octet in length. */
+ if (len < cont->err_app_cont_len + 1)
+ return -EINVAL;
+
+ buf_ptr[0] = cont->nacc_cause;
+ buf_ptr++;
+
+ memcpy(buf_ptr, cont->err_app_cont, cont->err_app_cont_len);
+ buf_ptr += cont->err_app_cont_len;
+
+ return (int)(buf_ptr - buf);
+}
+
+/* The structs bssgp_ran_inf_req_rim_cont, bssgp_ran_inf_rim_cont and bssgp_ran_inf_app_err_rim_cont *cont
+ * share four common fields at the beginning, we use the following struct as parameter type for the common
+ * encoder/decoder functions. (See also 3GPP TS 48.018 table 11.3.62a.1.b, table 11.3.62a.2.b, and
+ * table 11.3.62a.5.b) */
+struct bssgp_ran_inf_x_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ struct bssgp_rim_pdu_ind pdu_ind;
+ uint8_t prot_ver;
+};
+
+static int dec_rim_cont_common(struct bssgp_ran_inf_x_cont *cont, struct tlv_parsed *tp)
+{
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num)))
+ cont->seq_num = tlvp_val32be(tp, BSSGP_IE_RIM_SEQ_NR);
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind)))
+ memcpy(&cont->pdu_ind, TLVP_VAL(tp, BSSGP_IE_RIM_PDU_INDICATIONS), sizeof(cont->pdu_ind));
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ return 0;
+}
+
+static uint8_t *enc_rim_cont_common(uint8_t *buf, size_t len, const struct bssgp_ran_inf_x_cont *cont)
+{
+
+ uint32_t seq_num = osmo_htonl(cont->seq_num);
+ uint8_t app_id_temp;
+ uint8_t *buf_ptr = buf;
+
+ if (len <
+ TVLV_HDR_MAXLEN * 4 + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->pdu_ind) +
+ sizeof(cont->prot_ver))
+ return NULL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind), (uint8_t *) & cont->pdu_ind);
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ return buf_ptr;
+}
+
+/* 3GPP TS 48.018, table 11.3.61.b: RIM Application Identity coding */
+const struct value_string bssgp_ran_inf_app_id_strs[] = {
+ { BSSGP_RAN_INF_APP_ID_NACC, "Network Assisted Cell Change (NACC)" },
+ { BSSGP_RAN_INF_APP_ID_SI3, "System Information 3 (SI3)" },
+ { BSSGP_RAN_INF_APP_ID_MBMS, "MBMS data channel" },
+ { BSSGP_RAN_INF_APP_ID_SON, "SON Transfer" },
+ { BSSGP_RAN_INF_APP_ID_UTRA_SI, "UTRA System Information (UTRA SI)" },
+ { 0, NULL }
+};
+
+/*! Decode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_ran_inf_req_app_cont_nacc(&cont->u.app_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER),
+ TLVP_LEN(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/* Dub a TLVP header into a given buffer. The value part of the IE must start
+ * at the 2nd octet. Should the length field make a 3 octet TLVP header
+ * necessary (unlikely, but possible) the value part is moved ahead by one
+ * octet. The function returns a pointer to the end of value part. */
+static uint8_t *dub_tlvp_header(uint8_t *buf, uint8_t iei, uint16_t len)
+{
+ uint8_t *buf_ptr = buf;
+
+ buf_ptr[0] = iei;
+ if (len <= TVLV_MAX_ONEBYTE) {
+ buf_ptr[1] = (uint8_t) len;
+ buf_ptr[1] |= 0x80;
+ buf_ptr += TVLV_HDR_LEN;
+ } else {
+ memmove(buf_ptr + 1, buf_ptr, len);
+ buf_ptr[1] = len >> 8;
+ buf_ptr[2] = len & 0xff;
+ buf_ptr += TVLV_HDR_MAXLEN;
+ }
+ buf_ptr += len;
+
+ return buf_ptr;
+}
+
+/*! Encode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_req_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_ran_inf_req_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RIM_REQ_APP_CONTAINER, app_cont_len);
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len < 0)
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) {
+ if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN)
+ return -EINVAL;
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+ }
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_ran_inf_app_cont_nacc(&cont->u.app_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER),
+ TLVP_LEN(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+ } else if (TLVP_PRESENT(&tp, BSSGP_IE_APP_ERROR_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp,
+ BSSGP_IE_APP_ERROR_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+ cont->app_err = true;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ if (cont->app_err) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len);
+ } else {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_ran_inf_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RAN_INFO_APP_CONTAINER, app_cont_len);
+ }
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len < 0)
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) {
+ if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN)
+ return -EINVAL;
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+ }
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num)))
+ cont->seq_num = tlvp_val32be(&tp, BSSGP_IE_RIM_SEQ_NR);
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_ack_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_ack_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ uint32_t seq_num = osmo_htonl(cont->seq_num);
+ uint8_t app_id_temp;
+
+ if (len <
+ 4 * TVLV_HDR_MAXLEN + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->prot_ver) +
+ cont->son_trans_app_id_len)
+ return -EINVAL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num);
+
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0)
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_CAUSE, sizeof(cont->cause)))
+ cont->cause = TLVP_VAL(&tp, BSSGP_IE_CAUSE)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_PDU_IN_ERROR)) {
+ cont->err_pdu = TLVP_VAL(&tp, BSSGP_IE_PDU_IN_ERROR);
+ cont->err_pdu_len = TLVP_LEN(&tp, BSSGP_IE_PDU_IN_ERROR);
+ } else {
+ return -EINVAL;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_err_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ uint8_t app_id_temp;
+
+ if (len <
+ TVLV_HDR_MAXLEN * 5 + sizeof(app_id_temp) + sizeof(cont->cause) + sizeof(cont->prot_ver) +
+ cont->err_pdu_len + cont->son_trans_app_id_len)
+ return -EINVAL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_CAUSE, sizeof(cont->cause), &cont->cause);
+
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ if (cont->err_pdu && cont->err_pdu_len > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_PDU_IN_ERROR, cont->err_pdu_len, cont->err_pdu);
+ else
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0)
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp,
+ BSSGP_IE_APP_ERROR_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/*! Encode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_app_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_err_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Parse a given message buffer into a rim-pdu struct.
+ * \param[out] pdu user provided memory for the resulting RAN INFORMATION PDU.
+ * \param[in] msg BSSGP message buffer that contains the encoded RAN INFORMATION PDU.
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb *msg)
+{
+ struct tlv_parsed tp[2];
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ int data_len;
+ int rc;
+ uint16_t nsei = msgb_nsei(msg);
+
+ memset(pdu, 0, sizeof(*pdu));
+
+ data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+ if (data_len < 0)
+ return -EINVAL;
+
+ rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, tp, ARRAY_SIZE(tp), bgph->pdu_type, bgph->data, data_len, 0, 0,
+ DLBSSGP, __func__);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp[0], BSSGP_IE_RIM_ROUTING_INFO)) {
+ rc = bssgp_parse_rim_ri(&pdu->routing_info_dest, TLVP_VAL(&tp[0], BSSGP_IE_RIM_ROUTING_INFO),
+ TLVP_LEN(&tp[0], BSSGP_IE_RIM_ROUTING_INFO));
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+ } else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp[1], BSSGP_IE_RIM_ROUTING_INFO)) {
+ rc = bssgp_parse_rim_ri(&pdu->routing_info_src, TLVP_VAL(&tp[1], BSSGP_IE_RIM_ROUTING_INFO),
+ TLVP_LEN(&tp[1], BSSGP_IE_RIM_ROUTING_INFO));
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+ } else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Source Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_REQ_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_REQ_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_APP_ERROR_RIM_CONT))
+ pdu->rim_cont_iei = BSSGP_IE_RI_APP_ERROR_RIM_CONT;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ACK_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_ACK_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ERROR_RIM_COINTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_ERROR_RIM_COINTAINER;
+ else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing or wrong RIM Container IE\n", nsei);
+ return -EINVAL;
+ }
+
+ pdu->rim_cont = TLVP_VAL(&tp[0], pdu->rim_cont_iei);
+ pdu->rim_cont_len = TLVP_LEN(&tp[0], pdu->rim_cont_iei);
+
+ /* Make sure the rim container field is not empty */
+ if (pdu->rim_cont_len < 1)
+ return -EINVAL;
+ if (!pdu->rim_cont)
+ return -EINVAL;
+
+ /* Note: It is not an error if we fail to parse the RIM container,
+ * since there are applications where parsing the RIM container
+ * is not necessary (routing). It is up to the API user to check
+ * the results. */
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_req_rim_cont(&pdu->decoded.req_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_rim_cont(&pdu->decoded.rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ rc = bssgp_dec_ran_inf_app_err_rim_cont(&pdu->decoded.app_err_rim_cont, pdu->rim_cont,
+ pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_ack_rim_cont(&pdu->decoded.ack_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ rc = bssgp_dec_ran_inf_err_rim_cont(&pdu->decoded.err_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ default:
+ LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) cannot parse unknown RIM container.\n", nsei);
+ return 0;
+ }
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) unable to parse RIM container.\n", nsei);
+ return 0;
+ }
+ pdu->decoded_present = true;
+
+ return 0;
+}
+
+/*! Encode a given rim-pdu struct into a message buffer.
+ * \param[out] pdu user provided memory that contains the RAN INFORMATION PDU to encode.
+ * \returns BSSGP message buffer on sccess, NULL on error. */
+struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint8_t rim_ri_buf[BSSGP_RIM_ROUTING_INFO_MAXLEN];
+ int rc;
+
+ if (!msg)
+ return NULL;
+ bgph = (struct bssgp_normal_hdr *)msgb_put(msg, sizeof(*bgph));
+
+ /* Set PDU type based on RIM container type */
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_REQ;
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO;
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_APP_ERROR;
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ACK;
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ERROR;
+ break;
+ default:
+ /* The caller must correctly specify the container type! */
+ OSMO_ASSERT(false);
+ }
+
+ /* Put RIM routing information */
+ rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_dest);
+ if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+ goto error;
+ msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+ rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_src);
+ if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+ goto error;
+ msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+
+ /* Put RIM container */
+ if (pdu->decoded_present) {
+ uint8_t *rim_cont_buf = talloc_zero_size(msg, msg->data_len);
+ if (!rim_cont_buf)
+ goto error;
+
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_req_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.req_rim_cont);
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.rim_cont);
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ rc = bssgp_enc_ran_inf_app_err_rim_cont(rim_cont_buf, msg->data_len,
+ &pdu->decoded.app_err_rim_cont);
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_ack_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.ack_rim_cont);
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ rc = bssgp_enc_ran_inf_err_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.err_rim_cont);
+ break;
+ default:
+ /* The API user must set the iei properly! */
+ OSMO_ASSERT(false);
+ }
+ if (rc < 0) {
+ talloc_free(rim_cont_buf);
+ goto error;
+ }
+
+ msgb_tvlv_put(msg, pdu->rim_cont_iei, rc, rim_cont_buf);
+ talloc_free(rim_cont_buf);
+ } else {
+ /* Make sure the RIM container is actually present. */
+ OSMO_ASSERT(pdu->rim_cont_iei != 0 && pdu->rim_cont_len > 0 && pdu->rim_cont);
+ msgb_tvlv_put(msg, pdu->rim_cont_iei, pdu->rim_cont_len, pdu->rim_cont);
+ }
+
+ return msg;
+error:
+ msgb_free(msg);
+ return 0;
+}
+
+/*! Send RIM RAN INFORMATION REQUEST via BSSGP (3GPP TS 48.018, section 10.6.1).
+ * \param[in] pdu user provided memory for the RAN INFORMATION PDU to be sent.
+ * \param[in] nsei BSSGP network service entity identifier (NSEI).
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei)
+{
+ struct msgb *msg;
+ struct bssgp_normal_hdr *bgph;
+ char ri_src_str[64];
+ char ri_dest_str[64];
+
+ /* Encode RIM PDU into mesage buffer */
+ msg = bssgp_encode_rim_pdu(pdu);
+ if (!msg) {
+ LOGP(DLBSSGP, LOGL_ERROR,
+ "BSSGP RIM (NSEI=%u) unable to encode BSSGP RIM PDU\n", nsei);
+ return -EINVAL;
+ }
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = 0; /* Signalling */
+
+ bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s, src=%s, dest=%s\n",
+ nsei, bssgp_pdu_str(bgph->pdu_type),
+ bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src),
+ bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &pdu->routing_info_dest));
+
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/*! Send encoded RAN TRANSPARENT CONTAINER via BSSGP (3GPP TS 29.060, section 7.7.43).
+ * \param[in] msg user provided memory for the encoded RAN TRANSPARENT CONTAINER to be sent.
+ * (this function will take ownership of msg).
+ * \param[in] nsei BSSGP network service entity identifier (NSEI).
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_tx_rim_encoded(struct msgb *msg, uint16_t nsei)
+{
+ struct bssgp_normal_hdr *bgph;
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = 0; /* Signalling */
+
+ bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s\n",
+ nsei, bssgp_pdu_str(bgph->pdu_type));
+
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/* For internal use only (called from gprs_bssgp.c) */
+int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci)
+{
+ struct osmo_bssgp_prim nmp;
+ uint16_t nsei = msgb_nsei(msg);
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ enum bssgp_prim prim;
+ char ri_src_str[64];
+ char ri_dest_str[64];
+
+ /* Specify PRIM type based on the RIM PDU */
+ switch (bgph->pdu_type) {
+ case BSSGP_PDUT_RAN_INFO:
+ case BSSGP_PDUT_RAN_INFO_REQ:
+ case BSSGP_PDUT_RAN_INFO_ACK:
+ case BSSGP_PDUT_RAN_INFO_ERROR:
+ case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+ prim = PRIM_BSSGP_RIM_PDU_TRANSFER;
+ break;
+ default:
+ /* Caller already makes sure that this can't happen. */
+ OSMO_ASSERT(false);
+ }
+
+ /* Send BSSGP RIM indication to NM */
+ memset(&nmp, 0, sizeof(nmp));
+ nmp.nsei = nsei;
+ nmp.bvci = bvci;
+ nmp.tp = tp;
+ if (bssgp_parse_rim_pdu(&nmp.u.rim_pdu, msg) < 0)
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RIM-PDU:%s, src=%s, dest=%s\n",
+ bvci, bssgp_pdu_str(bgph->pdu_type),
+ bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &nmp.u.rim_pdu.routing_info_src),
+ bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &nmp.u.rim_pdu.routing_info_dest));
+ osmo_prim_init(&nmp.oph, SAP_BSSGP_RIM, prim, PRIM_OP_INDICATION, msg);
+ bssgp_prim_cb(&nmp.oph, NULL);
+
+ return 0;
+}
diff --git a/src/gb/gprs_bssgp_util.c b/src/gb/gprs_bssgp_util.c
index 77089491..92896c1f 100644
--- a/src/gb/gprs_bssgp_util.c
+++ b/src/gb/gprs_bssgp_util.c
@@ -32,7 +32,7 @@
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/gprs/gprs_ns.h>
-#include "common_vty.h"
+#include "gprs_bssgp_internal.h"
struct gprs_ns_inst *bssgp_nsi;
@@ -106,6 +106,8 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_UL_UNITDATA, "UL-UNITDATA" },
{ BSSGP_PDUT_RA_CAPABILITY, "RA-CAPABILITY" },
{ BSSGP_PDUT_PTM_UNITDATA, "PTM-UNITDATA" },
+ { BSSGP_PDUT_DL_MMBS_UNITDATA, "DL-MBMS-UNITDATA" },
+ { BSSGP_PDUT_UL_MMBS_UNITDATA, "UL-MBMS-UNITDATA" },
{ BSSGP_PDUT_PAGING_PS, "PAGING-PS" },
{ BSSGP_PDUT_PAGING_CS, "PAGING-CS" },
{ BSSGP_PDUT_RA_CAPA_UDPATE, "RA-CAPABILITY-UPDATE" },
@@ -117,6 +119,10 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_RESUME, "RESUME" },
{ BSSGP_PDUT_RESUME_ACK, "RESUME-ACK" },
{ BSSGP_PDUT_RESUME_NACK, "RESUME-NACK" },
+ { BSSGP_PDUT_DUMMY_PAGING_PS, "DUMMY-PAGING-PS" },
+ { BSSGP_PDUT_DUMMY_PAGING_PS_RESP, "DUMMY-PAGING-PS-RESP" },
+ { BSSGP_PDUT_MS_REGISTR_ENQ, "MS-REGISTRATION-ENQ" },
+ { BSSGP_PDUT_MS_REGISTR_ENQ_RESP, "MS-REGISTRATION-ENQ-RESP" },
{ BSSGP_PDUT_BVC_BLOCK, "BVC-BLOCK" },
{ BSSGP_PDUT_BVC_BLOCK_ACK, "BVC-BLOCK-ACK" },
{ BSSGP_PDUT_BVC_RESET, "BVC-RESET" },
@@ -130,8 +136,11 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_FLUSH_LL, "FLUSH-LL" },
{ BSSGP_PDUT_FLUSH_LL_ACK, "FLUSH-LL-ACK" },
{ BSSGP_PDUT_LLC_DISCARD, "LLC DISCARDED" },
+ { BSSGP_PDUT_FLOW_CONTROL_PFC, "FLOW-CONTROL-PFC" },
+ { BSSGP_PDUT_FLOW_CONTROL_PFC_ACK, "FLOW-CONTROL-PFC-ACK" },
{ BSSGP_PDUT_SGSN_INVOKE_TRACE, "SGSN-INVOKE-TRACE" },
{ BSSGP_PDUT_STATUS, "STATUS" },
+ { BSSGP_PDUT_OVERLOAD, "OVERLOAD" },
{ BSSGP_PDUT_DOWNLOAD_BSS_PFC, "DOWNLOAD-BSS-PFC" },
{ BSSGP_PDUT_CREATE_BSS_PFC, "CREATE-BSS-PFC" },
{ BSSGP_PDUT_CREATE_BSS_PFC_ACK, "CREATE-BSS-PFC-ACK" },
@@ -140,9 +149,338 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_MODIFY_BSS_PFC_ACK, "MODIFY-BSS-PFC-ACK" },
{ BSSGP_PDUT_DELETE_BSS_PFC, "DELETE-BSS-PFC" },
{ BSSGP_PDUT_DELETE_BSS_PFC_ACK, "DELETE-BSS-PFC-ACK" },
+ { BSSGP_PDUT_DELETE_BSS_PFC_REQ, "DELETE-BSS-PFC-REQ" },
+ { BSSGP_PDUT_PS_HO_REQUIRED, "PS-HO-REQUIRED" },
+ { BSSGP_PDUT_PS_HO_REQUIRED_ACK, "PS-HO-REQUIRED-ACK" },
+ { BSSGP_PDUT_PS_HO_REQUIRED_NACK, "PS-HO-REQUIRED-NACK" },
+ { BSSGP_PDUT_PS_HO_REQUEST, "PS-HO-REQUEST" },
+ { BSSGP_PDUT_PS_HO_REQUEST_ACK, "PS-HO-REQUEST-ACK" },
+ { BSSGP_PDUT_PS_HO_REQUEST_NACK, "PS-HO-REQUEST-NACK" },
+ { BSSGP_PDUT_PS_HO_COMPLETE, "PS-HO-COMPLETE" },
+ { BSSGP_PDUT_PS_HO_CANCEL, "PS-HO-CANCEL" },
+ { BSSGP_PDUT_PS_HO_COMPLETE_ACK, "PS-HO-COMPLETE-ACK" },
+ { BSSGP_PDUT_PERFORM_LOC_REQ, "PERFORM-LOC-REQ" },
+ { BSSGP_PDUT_PERFORM_LOC_RESP, "PERFORM-LOC-RESP" },
+ { BSSGP_PDUT_PERFORM_LOC_ABORT, "PERFORM-LOC-ABORT" },
+ { BSSGP_PDUT_POSITION_COMMAND, "POSITION-COMMAND" },
+ { BSSGP_PDUT_POSITION_RESPONSE, "POSITION-RESPONSE" },
+ { BSSGP_PDUT_RAN_INFO, "RAN-INFO" },
+ { BSSGP_PDUT_RAN_INFO_REQ, "RAN-INFO-REQ" },
+ { BSSGP_PDUT_RAN_INFO_ACK, "RAN-INFO-ACK" },
+ { BSSGP_PDUT_RAN_INFO_ERROR, "RAN-INFO-ERROR" },
+ { BSSGP_PDUT_RAN_INFO_APP_ERROR, "RAN-INFO-APP-ERROR" },
+ { BSSGP_PDUT_MBMS_START_REQ, "MBMS-START-REQ" },
+ { BSSGP_PDUT_MBMS_START_RESP, "MBMS-START-RESP" },
+ { BSSGP_PDUT_MBMS_STOP_REQ, "MBMS-STOP-REQ" },
+ { BSSGP_PDUT_MBMS_STOP_RESP, "MBMS-STOP-RESP" },
+ { BSSGP_PDUT_MBMS_UPDATE_REQ, "MBMS-UPDATE-REQ" },
+ { BSSGP_PDUT_MBMS_UPDATE_RESP, "MBMS-UPDATE-RESP" },
{ 0, NULL },
};
+static const uint8_t dl_ud_ies[] = { BSSGP_IE_PDU_LIFETIME };
+static const uint8_t ul_ud_ies[] = { BSSGP_IE_CELL_ID };
+static const uint8_t ra_cap_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_MS_RADIO_ACCESS_CAP };
+static const uint8_t dl_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU };
+static const uint8_t ul_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU };
+static const uint8_t pag_ps_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_QOS_PROFILE };
+static const uint8_t pag_cs_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_DRX_PARAMS };
+static const uint8_t ra_cap_upd_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t ra_cap_upd_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_RA_CAP_UPD_CAUSE };
+static const uint8_t rad_sts_ies[] = { BSSGP_IE_RADIO_CAUSE };
+static const uint8_t suspend_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t suspend_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR };
+static const uint8_t suspend_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t resume_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR };
+static const uint8_t resume_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t resume_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t d_pag_ps_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t d_pag_ps_resp_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING };
+static const uint8_t d_pag_ps_rej_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING };
+static const uint8_t ms_reg_enq_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t ms_reg_enq_res_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t flush_ll_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_BVCI };
+static const uint8_t flush_ll_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_FLUSH_ACTION };
+static const uint8_t llc_disc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LLC_FRAMES_DISCARDED, BSSGP_IE_BVCI,
+ BSSGP_IE_NUM_OCT_AFF };
+static const uint8_t fc_bvc_ies[] = { BSSGP_IE_TAG, BSSGP_IE_BVC_BUCKET_SIZE, BSSGP_IE_BUCKET_LEAK_RATE,
+ BSSGP_IE_BMAX_DEFAULT_MS, BSSGP_IE_R_DEFAULT_MS };
+static const uint8_t fc_bvc_ack_ies[] = { BSSGP_IE_TAG };
+static const uint8_t fc_ms_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_MS_BUCKET_SIZE,
+ BSSGP_IE_BUCKET_LEAK_RATE };
+static const uint8_t fc_ms_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t block_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE };
+static const uint8_t block_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t unblock_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t unblock_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t reset_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE };
+static const uint8_t reset_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t status_ies[] = { BSSGP_IE_CAUSE };
+static const uint8_t inv_trc_ies[] = { BSSGP_IE_TRACE_TYPE, BSSGP_IE_TRACE_REFERENC };
+static const uint8_t dl_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t crt_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t crt_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t crt_bss_pfc_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE };
+static const uint8_t mod_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t mod_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t del_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t del_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t fc_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_PFC_FLOW_CTRL_PARAMS };
+static const uint8_t fc_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t del_bss_pfc_req_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_required_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID,
+ BSSGP_IE_ACTIVE_PFC_LIST };
+static const uint8_t ps_ho_required_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC };
+static const uint8_t ps_ho_required_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_request_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI, BSSGP_IE_CAUSE,
+ BSSGP_IE_CELL_ID, BSSGP_IE_SBSS_TO_TBSS_TR_CONT,
+ BSSGP_IE_PFC_TO_BE_SETUP_LIST };
+static const uint8_t ps_ho_request_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC,
+ BSSGP_IE_TBSS_TO_SBSS_TR_CONT };
+static const uint8_t ps_ho_request_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_compl_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI };
+static const uint8_t ps_ho_cancel_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID };
+static const uint8_t ps_ho_compl_ack_ies[] = { BSSGP_IE_TLLI };
+static const uint8_t overload_ies[] = { BSSGP_IE_PRIO_CLASS_IND };
+static const uint8_t rinfo_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_RIM_CONTAINER };
+static const uint8_t rinfo_req_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_REQ_RIM_CONTAINER };
+static const uint8_t rinfo_ack_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ACK_RIM_CONTAINER };
+static const uint8_t rinfo_err_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ERROR_RIM_COINTAINER };
+static const uint8_t rinfo_aerr_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_APP_ERROR_RIM_CONT };
+
+#define DL BSSGP_PDUF_DL
+#define UL BSSGP_PDUF_UL
+#define SIG BSSGP_PDUF_SIG
+#define PTP BSSGP_PDUF_PTP
+#define PTM BSSGP_PDUF_PTM
+
+const struct osmo_tlv_prot_def osmo_pdef_bssgp = {
+ .name = "BSSGP",
+ .tlv_def = &tvlv_att_def,
+ .msg_def = {
+ [BSSGP_PDUT_DL_UNITDATA] = MSG_DEF("DL-UNITDATA", dl_ud_ies, DL|PTP),
+ [BSSGP_PDUT_UL_UNITDATA] = MSG_DEF("UL-UNITDATA", ul_ud_ies, UL|PTP),
+ [BSSGP_PDUT_RA_CAPABILITY] = MSG_DEF("RA-CAPABILITY", ra_cap_ies, DL|PTP),
+ [BSSGP_PDUT_DL_MMBS_UNITDATA] = MSG_DEF("DL-MBMS-UNITDATA", dl_mb_ud_ies, DL|PTM),
+ [BSSGP_PDUT_UL_MMBS_UNITDATA] = MSG_DEF("UL-MBMS-UNITDATA", ul_mb_ud_ies, UL|PTM),
+ [BSSGP_PDUT_PAGING_PS] = MSG_DEF("PAGING-PS", pag_ps_ies, DL|PTP|SIG),
+ [BSSGP_PDUT_PAGING_CS] = MSG_DEF("PAGING-CS", pag_cs_ies, DL|PTP|SIG),
+ [BSSGP_PDUT_RA_CAPA_UDPATE] = MSG_DEF("RA-CAPABILITY-UPDATE", ra_cap_upd_ies, UL|PTP),
+ [BSSGP_PDUT_RA_CAPA_UPDATE_ACK] = MSG_DEF("RA-CAPABILITY-UPDATE-ACK", ra_cap_upd_ack_ies, DL|PTP),
+ [BSSGP_PDUT_RADIO_STATUS] = MSG_DEF("RADIO-STATUS", rad_sts_ies, UL|PTP),
+ [BSSGP_PDUT_SUSPEND] = MSG_DEF("SUSPEND", suspend_ies, UL|SIG),
+ [BSSGP_PDUT_SUSPEND_ACK] = MSG_DEF("SUSPEND-ACK", suspend_ack_ies, DL|SIG),
+ [BSSGP_PDUT_SUSPEND_NACK] = MSG_DEF("SUSPEND-NACK", suspend_nack_ies, DL|SIG),
+ [BSSGP_PDUT_RESUME] = MSG_DEF("RESUME", resume_ies, UL|SIG),
+ [BSSGP_PDUT_RESUME_ACK] = MSG_DEF("RESUME-ACK", resume_ack_ies, DL|SIG),
+ [BSSGP_PDUT_RESUME_NACK] = MSG_DEF("RESUME-NACK", resume_nack_ies, DL|SIG),
+ [BSSGP_PDUT_DUMMY_PAGING_PS] = MSG_DEF("DUMMY-PAGING-PS", d_pag_ps_ies, DL|SIG|PTP),
+ [BSSGP_PDUT_DUMMY_PAGING_PS_RESP] = MSG_DEF("DUMMY-PAGING-PS-RESP", d_pag_ps_resp_ies, UL|SIG|PTP),
+ [BSSGP_PDUT_PAGING_PS_REJECT] = MSG_DEF("PAGING-PS-REJ", d_pag_ps_rej_ies, UL|SIG|PTP),
+ [BSSGP_PDUT_MS_REGISTR_ENQ] = MSG_DEF("MS-REGISRATION-ENQ", ms_reg_enq_ies, UL|SIG),
+ [BSSGP_PDUT_MS_REGISTR_ENQ_RESP] = MSG_DEF("MS-REGISRATION-ENQ-RESP", ms_reg_enq_res_ies, DL|SIG),
+ [BSSGP_PDUT_FLUSH_LL] = MSG_DEF("FLUSH-LL", flush_ll_ies, DL|SIG),
+ [BSSGP_PDUT_FLUSH_LL_ACK] = MSG_DEF("FLUSH-LL-ACK", flush_ll_ack_ies, UL|SIG),
+ [BSSGP_PDUT_LLC_DISCARD] = MSG_DEF("LLC-DISCARDED", llc_disc_ies, UL|SIG),
+ [BSSGP_PDUT_FLOW_CONTROL_BVC] = MSG_DEF("FC-BVC", fc_bvc_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_BVC_ACK] = MSG_DEF("FC-BVC-ACK", fc_bvc_ack_ies, DL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_MS] = MSG_DEF("FC-MS", fc_ms_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_MS_ACK] = MSG_DEF("FC-MS-ACK", fc_ms_ack_ies, DL|PTP),
+ [BSSGP_PDUT_BVC_BLOCK] = MSG_DEF("BVC-BLOCK", block_ies, UL|SIG),
+ [BSSGP_PDUT_BVC_BLOCK_ACK] = MSG_DEF("BVC-BLOCK-ACK", block_ack_ies, DL|SIG),
+ [BSSGP_PDUT_BVC_UNBLOCK] = MSG_DEF("BVC-UNBLOCK", unblock_ies, UL|SIG),
+ [BSSGP_PDUT_BVC_UNBLOCK_ACK] = MSG_DEF("BVC-UNBLOCK-ACK", unblock_ack_ies, DL|SIG),
+ [BSSGP_PDUT_BVC_RESET] = MSG_DEF("BVC-RESET", reset_ies, UL|DL|SIG|PTP),
+ [BSSGP_PDUT_BVC_RESET_ACK] = MSG_DEF("BVC-RESET-ACK", reset_ack_ies, UL|DL|SIG|PTP),
+ [BSSGP_PDUT_STATUS] = MSG_DEF("STATUS", status_ies, UL|DL|PTP|SIG|PTM),
+ [BSSGP_PDUT_SGSN_INVOKE_TRACE] = MSG_DEF("SGSN-INVOKE-TRACE", inv_trc_ies, DL|SIG),
+ [BSSGP_PDUT_DOWNLOAD_BSS_PFC] = MSG_DEF("DOWNLOAD-BSS-PFC", dl_bss_pfc_ies, UL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC] = MSG_DEF("CREATE-BSS-PFC", crt_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC_ACK] = MSG_DEF("CREATE-BSS-PFC-ACK", crt_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC_NACK] = MSG_DEF("CREATE-BSS-PFC-NACK", crt_bss_pfc_nack_ies, UL|PTP),
+ [BSSGP_PDUT_MODIFY_BSS_PFC] = MSG_DEF("MODIFY-BSS-PFC", mod_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_MODIFY_BSS_PFC_ACK] = MSG_DEF("MODIFY-BSS-PFC-ACK", mod_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC] = MSG_DEF("DELETE-BSS-PFC", del_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC_ACK] = MSG_DEF("DELETE-BSS-PFC-ACK", del_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_PFC] = MSG_DEF("FC-PFC", fc_pfc_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_PFC_ACK] = MSG_DEF("FC-PFC-ACK", fc_pfc_ack_ies, DL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC_REQ] = MSG_DEF("DELETE-BSS-PFC-REQ", del_bss_pfc_req_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED] = MSG_DEF("PS-HO-REQUIRED", ps_ho_required_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED_ACK] = MSG_DEF("PS-HO-REQUIRED-ACK", ps_ho_required_ack_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED_NACK] = MSG_DEF("PS-HO-REQUIRED-NACK", ps_ho_required_nack_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST] = MSG_DEF("PS-HO-REQUEST", ps_ho_request_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST_ACK] = MSG_DEF("PS-HO-REQUEST-ACK", ps_ho_request_ack_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST_NACK] = MSG_DEF("PS-HO-REQUEST-NACK", ps_ho_request_nack_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_COMPLETE] = MSG_DEF("PS-HO-COMPLETE", ps_ho_compl_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_CANCEL] = MSG_DEF("PS-HO-CANCEL", ps_ho_cancel_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_COMPLETE_ACK] = MSG_DEF("PS-HO-COMPLETE-ACK", ps_ho_compl_ack_ies, DL|PTP),
+ [BSSGP_PDUT_OVERLOAD] = MSG_DEF("OVERLOAD", overload_ies, DL|SIG),
+ /* TODO: Messages on LCS SAP */
+ /* Messages on RIM SAP */
+ [BSSGP_PDUT_RAN_INFO] = MSG_DEF("RAN-INFORMATION", rinfo_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_REQ] = MSG_DEF("RAN-INFORMATION-REQUEST", rinfo_req_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_ACK] = MSG_DEF("RAN-INFORMATION-ACK", rinfo_ack_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_ERROR] = MSG_DEF("RAN-INFORMATION-ERROR", rinfo_err_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_APP_ERROR] = MSG_DEF("RAN-INFORMATION-APP-ERROR", rinfo_aerr_ies, DL|UL|SIG),
+ /* TODO: Messages on MBMS SAP */
+ },
+ .ie_def = {
+ [BSSGP_IE_ALIGNMENT] = { 0, "Alignment Octets" },
+ [BSSGP_IE_BMAX_DEFAULT_MS] = { 2, "Bmax default MS" },
+ [BSSGP_IE_BSS_AREA_ID] = { 1, "BSS Area Indication" },
+ [BSSGP_IE_BUCKET_LEAK_RATE] = { 2, "Bucket Leak Rate (R)" },
+ [BSSGP_IE_BVC_BUCKET_SIZE] = { 2, "BVC Bucket Size" },
+ [BSSGP_IE_BVCI] = { 2, "BVCI" },
+ [BSSGP_IE_BVC_MEASUREMENT] = {2, "BVC Measurement" },
+ [BSSGP_IE_CAUSE] = { 1, "Cause" },
+ [BSSGP_IE_CELL_ID] = { 8, "Cell Identifier" },
+ [BSSGP_IE_CHAN_NEEDED] = { 1, "Channel Needed" },
+ [BSSGP_IE_DRX_PARAMS] = { 2, "DRX Parameters" },
+ [BSSGP_IE_EMLPP_PRIO] = { 3, "eMLPP Priority" },
+ [BSSGP_IE_FLUSH_ACTION] = { 1, "Flush Action" },
+ [BSSGP_IE_IMSI] = { 1, "Mobile Identity" },
+ [BSSGP_IE_LLC_PDU] = { 0, "LLC-PDU" },
+ [BSSGP_IE_LLC_FRAMES_DISCARDED] = { 1, "LLC Frames Discarded" },
+ [BSSGP_IE_LOCATION_AREA] = { 5, "Location Area" },
+ [BSSGP_IE_LSA_ID_LIST] = { 3, "LSA Identifier List" },
+ [BSSGP_IE_LSA_INFORMATION] = { 5, "LSA Information" },
+ [BSSGP_IE_MOBILE_ID] = { 1, "Mobile Identity" },
+ [BSSGP_IE_MS_BUCKET_SIZE] = { 2, "MS Bucket Size" },
+ [BSSGP_IE_MS_RADIO_ACCESS_CAP] = { 1, "MS Radio Access Capability" },
+ [BSSGP_IE_OMC_ID] = { 1, "OMC Id" },
+ [BSSGP_IE_PDU_IN_ERROR] = { 0, "PDU In Error" },
+ [BSSGP_IE_PDU_LIFETIME] = { 2, "PDU Lifetime" },
+ [BSSGP_IE_PRIORITY] = { 1, "Priority" },
+ [BSSGP_IE_QOS_PROFILE] = { 3, "QoS Profile" },
+ [BSSGP_IE_RADIO_CAUSE] = { 1, "Radio Cause" },
+ [BSSGP_IE_RA_CAP_UPD_CAUSE] = { 1, "RA-Cap-UPD-Cause" },
+ [BSSGP_IE_ROUTEING_AREA] = { 6, "Routeing Area" },
+ [BSSGP_IE_R_DEFAULT_MS] = { 2, "R_default_MS" },
+ [BSSGP_IE_SUSPEND_REF_NR] = { 1, "Suspend Reference Number" },
+ [BSSGP_IE_TAG] = { 1, "Tag" },
+ [BSSGP_IE_TLLI] = { 4, "TLLI" },
+ [BSSGP_IE_TMSI] = { 4, "TMSI" },
+ [BSSGP_IE_TRACE_REFERENC] = { 2, "Trace Reference" },
+ [BSSGP_IE_TRACE_TYPE] = { 1, "Trace Type" },
+ [BSSGP_IE_TRANSACTION_ID] = { 2, "Transaction Id" },
+ [BSSGP_IE_TRIGGER_ID] = { 1, "Trigger Id" },
+ [BSSGP_IE_NUM_OCT_AFF] = { 3, "Number of octets affected" },
+ [BSSGP_IE_PACKET_FLOW_ID] = { 1, "Packet Flow Identifier (PFI)" },
+ [BSSGP_IE_AGG_BSS_QOS_PROFILE] = { 14, "Aggregate BSS QoS Profile" },
+ [BSSGP_IE_PACKET_FLOW_TIMER] = { 1, "GPRS Timer" },
+ [BSSGP_IE_FEATURE_BITMAP] = { 1, "Feature Bitmap" },
+ [BSSGP_IE_BUCKET_FULL_RATIO] = { 1, "Bucket Full Ratio" },
+ [BSSGP_IE_SERVICE_UTRAN_CCO] = { 1, "Service UTRAN COO" },
+ [BSSGP_IE_NSEI] = { 2, "NSEI" },
+ [BSSGP_IE_RRLP_APDU] = { 1, "RLLP APDU" },
+ [BSSGP_IE_LCS_QOS] = { 4, "LCS QoS" },
+ [BSSGP_IE_LCS_CLIENT_TYPE] = { 1, "LCS Client Type" },
+ [BSSGP_IE_REQUESTED_GPS_AST_DATA] = { 4, "Requested GPS Assistance Data" },
+ [BSSGP_IE_LOCATION_TYPE] = { 2, "Location Type" },
+ [BSSGP_IE_LOCATION_ESTIMATE] = { 1, "Location Estimate" },
+ [BSSGP_IE_POSITIONING_DATA] = { 1, "Positioning Data" },
+ [BSSGP_IE_DECIPHERING_KEYS] = { 15, "Deciphering Keys" },
+ [BSSGP_IE_LCS_PRIORITY] = { 1, "LCS Priority" },
+ [BSSGP_IE_LCS_CAUSE] = { 1, "LCS Cause" },
+ [BSSGP_IE_LCS_CAPABILITY] = { 1, "LCS Capability" },
+ [BSSGP_IE_RRLP_FLAGS] = { 1, "RRLP Flags" },
+ [BSSGP_IE_RIM_APP_IDENTITY] = { 1, "RIM Application Identity" },
+ [BSSGP_IE_RIM_SEQ_NR] = { 4, "RIM Sequence Number" },
+ [BSSGP_IE_RIM_REQ_APP_CONTAINER] = { 12, "RIM-REQUEST RIM Container" },
+ [BSSGP_IE_RAN_INFO_APP_CONTAINER] = { 12, "RAN-INFORMATION RIM Container" },
+ [BSSGP_IE_RI_ACK_RIM_CONTAINER] = { 9, "RAN-INFORMATION-ACK RIM Container" },
+ [BSSGP_IE_RI_ERROR_RIM_COINTAINER] = { 9, "RAN-INFOIRMATION-ERROR RIM Container" },
+ [BSSGP_IE_RI_APP_ERROR_RIM_CONT] = { 14, "RAN-INFORMATION-APP-ERROR RIM Container" },
+ [BSSGP_IE_RIM_PDU_INDICATIONS] = { 1, "RIM PDU Indications" },
+ [BSSGP_IE_RIM_PROTOCOL_VERSION] = { 1, "RIM Protocol Version Number" },
+ [BSSGP_IE_PFC_FLOW_CTRL_PARAMS] = { 7, "PFC FLow Control Parameters" },
+ [BSSGP_IE_GLOBAL_CN_ID] = { 5, "Global CN-Id" },
+ [BSSGP_IE_RIM_ROUTING_INFO] = { 1, "RIM Routing Information" },
+ [BSSGP_IE_MBMS_SESSION_ID] = { 0, "MBMS Session Identity" },
+ [BSSGP_IE_MBMS_SESSION_DURATION] = { 0, "MBMS Session Duration" },
+ [BSSGP_IE_MBMS_SA_ID_LIST] = { 3, "MBMS Service Area Identity List" },
+ [BSSGP_IE_MBMS_RESPONSE] = { 1, "MBMS Response" },
+ [BSSGP_IE_MBMS_RA_LIST] = { 9, "MBMS Routing Area List" },
+ [BSSGP_IE_MBMS_SESSION_INFO] = { 1, "MBMS Session Information" },
+ [BSSGP_IE_TMGI] = { 6, "TMGI" },
+ [BSSGP_IE_MBMS_STOP_CAUSE] = { 1, "MBM Stop Cause" },
+ [BSSGP_IE_SBSS_TO_TBSS_TR_CONT] = { 7, "Source BSS to Target BSS Transparent Container" },
+ [BSSGP_IE_TBSS_TO_SBSS_TR_CONT] = { 0, "Target BSS to Source BSS Transparent Container" },
+ [BSSGP_IE_NAS_CONT_FOR_PS_HO] = { 0, "NAS container for PS Handover" },
+ [BSSGP_IE_PFC_TO_BE_SETUP_LIST] = { 9, "PFCs to be set-up list" },
+ [BSSGP_IE_LIST_OF_SETUP_PFC] = { 1, "List of set-up PFCs" },
+ [BSSGP_IE_EXT_FEATURE_BITMAP] = { 1, "Extended Feature Bitmap" },
+ [BSSGP_IE_SRC_TO_TGT_TR_CONT] = { 0, "Source to Target Transparent Container" },
+ [BSSGP_IE_TGT_TO_SRC_TR_CONT] = { 0, "Target to Source Transparent Container" },
+ [BSSGP_IE_NC_ID] = { 8, "RNC Identifier" },
+ [BSSGP_IE_PAGE_MODE] = { 1, "Page Mode" },
+ [BSSGP_IE_CONTAINER_ID] = { 1, "Container ID" },
+ [BSSGP_IE_GLOBAL_TFI] = { 1, "Global TFI" },
+ [BSSGP_IE_IMEI] = { 1, "IMEI" },
+ [BSSGP_IE_TIME_TO_MBMS_DATA_XFR] = { 1, "Time to MBMS Data Transfer" },
+ [BSSGP_IE_MBMS_SESSION_REP_NR] = { 1, "MBMS Session Repetition Number" },
+ [BSSGP_IE_INTER_RAT_HO_INFO] = { 0, "Inter RAT Handover Info" },
+ [BSSGP_IE_PS_HO_COMMAND] = { 0, "PS Handover Command" },
+ [BSSGP_IE_PS_HO_INDICATIONS] = { 1, "PS Handover Indications" },
+ [BSSGP_IE_SI_PSI_CONTAINER] = { 1, "SI/PSI Container" },
+ [BSSGP_IE_ACTIVE_PFC_LIST] = { 2, "Active PFCs List" },
+ [BSSGP_IE_VELOCITY_DATA] = { 0, "Velocity Data" },
+ [BSSGP_IE_DTM_HO_COMMAND] = { 0, "DTM Handover Command" },
+ [BSSGP_IE_CS_INDICATION] = { 1, "CS Indication" },
+ [BSSGP_IE_RQD_GANNS_AST_DATA] = { 0, "Requested GANSS Assistance Data" },
+ [BSSGP_IE_GANSS_LOCATION_TYPE] = { 1, "GANSS Location Type" },
+ [BSSGP_IE_GANSS_POSITIONING_DATA] = { 0, "GANSS Positioning Data" },
+ [BSSGP_IE_FLOW_CTRL_GRANULARITY] = { 1, "Flow Control Granularity" },
+ [BSSGP_IE_ENB_ID] = { 6, "eNB Identifier" },
+ [BSSGP_IE_EUTRAN_IRAT_HO_INFO] = { 0, "E-UTRAN Inter RAT Handover Info" },
+ [BSSGP_IE_SUB_PID4RAT_FREQ_PRIO] = { 1, "Subscriber Profile ID for RAT/Frequency priority" },
+ [BSSGP_IE_REQ4IRAT_HO_INFO] = { 1, "Request for Inter-RAT Handover Info" },
+ [BSSGP_IE_RELIABLE_IRAT_HO_INFO] = { 1, "Reliable Inter-RAT Handover Info" },
+ [BSSGP_IE_SON_TRANSFER_APP_ID] = { 0, "SON Transfer Application Identity" },
+ [BSSGP_IE_CSG_ID] = { 5, "CSG Identifier" },
+ [BSSGP_IE_TAC] = { 3, "Tracking Area Code" },
+ [BSSGP_IE_REDIRECT_ATTEMPT_FLAG] = { 1, "Redirect Attempt Flag" },
+ [BSSGP_IE_REDIRECTION_INDICATION] = { 1, "Redirection Indication" },
+ [BSSGP_IE_REDIRECTION_COMPLETED] = { 1, "Redirection Completed" },
+ [BSSGP_IE_UNCONF_SEND_STATE_VAR] = { 2, "Unconfirmed send state variable" },
+ [BSSGP_IE_IRAT_MEASUREMENT_CONF] = { 10, "IRAT Measurement Configuration" },
+ [BSSGP_IE_SCI] = { 1, "SCI" },
+ [BSSGP_IE_GGSN_PGW_LOCATION] = { 1, "GGSN/P-GW Location" },
+ [BSSGP_IE_SELECTED_PLMN_ID] = { 3, "Selected PLMN ID" },
+ [BSSGP_IE_PRIO_CLASS_IND] = { 1, "Priority Class Indication" },
+ [BSSGP_IE_SOURCE_CELL_ID] = { 6, "Source Cell ID" },
+ [BSSGP_IE_IRAT_MEAS_CFG_E_EARFCN] = { 10, "IRAT Measurement Configuration (extended E-ARFCNs)" },
+ [BSSGP_IE_EDRX_PARAMETERS] = { 1, "eDRX Parameters" },
+ [BSSGP_IE_T_UNTIL_NEXT_PAGING] = { 2, "Time Until Next Paging Occasion" },
+ [BSSGP_IE_COVERAGE_CLASS] = { 1, "Coverage Class" },
+ [BSSGP_IE_PAGING_ATTEMPT_INFO] = { 1, "Paging Attempt Information" },
+ [BSSGP_IE_EXCEPTION_REPORT_FLAG] = { 1, "Exception Report Flag" },
+ [BSSGP_IE_OLD_RA_ID] = { 6, "Old Routing Area Identification" },
+ [BSSGP_IE_ATTACH_IND] = { 1, "Attach Indicator" },
+ [BSSGP_IE_PLMN_ID] = { 3, "PLMN Identity" },
+ [BSSGP_IE_MME_QUERY] = { 1, "MME Query" },
+ [BSSGP_IE_SGSN_GROUP_ID] = { 3, "SGSN Group Identity" },
+ [BSSGP_IE_ADDITIONAL_PTMSI] = { 4, "Additional P-TMSI" },
+ [BSSGP_IE_UE_USAGE_TYPE] = { 1, "UE Usage Type" },
+ [BSSGP_IE_MLAT_TIMER] = { 1, "Multilateration Timer" },
+ [BSSGP_IE_MLAT_TA] = { 2, "Multilateration Timing Advance" },
+ [BSSGP_IE_MS_SYNC_ACCURACY] = { 1, "MS Sync Accuracy" },
+ [BSSGP_IE_BTS_RX_ACCURACY_LVL] = { 1, "BTS Reception Accuracy Level" },
+ [BSSGP_IE_TA_REQ] = { 1, "Timing Advance Request (TAR)" },
+ },
+};
+
+#undef DL
+#undef UL
+#undef SIG
+#undef PTP
+#undef PTM
+
+
const char *bssgp_cause_str(enum gprs_bssgp_cause cause)
{
return get_value_string(bssgp_cause_strings, cause);
@@ -210,7 +548,7 @@ int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
_bvci = osmo_htons(bvci);
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* Chapter 10.4.14: Status */
@@ -224,17 +562,17 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
cause is either "BVCI blocked" or "BVCI unknown" */
if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED) {
if (bvci == NULL)
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
"missing conditional BVCI\n",
bssgp_cause_str(cause));
} else {
if (bvci != NULL)
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
"unexpected conditional BVCI\n",
bssgp_cause_str(cause));
}
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
bvci ? *bvci : 0, bssgp_cause_str(cause));
msgb_nsei(msg) = msgb_nsei(orig_msg);
msgb_bvci(msg) = 0;
@@ -248,5 +586,5 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR,
msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg));
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
diff --git a/src/gb/gprs_bssgp_vty.c b/src/gb/gprs_bssgp_vty.c
index 5dab94e7..d04641d9 100644
--- a/src/gb/gprs_bssgp_vty.c
+++ b/src/gb/gprs_bssgp_vty.c
@@ -44,8 +44,6 @@
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
-#include "common_vty.h"
-
static void log_set_bvc_filter(struct log_target *target,
struct bssgp_bvc_ctx *bctx)
{
@@ -207,15 +205,15 @@ DEFUN(logging_fltr_bvc,
int bssgp_vty_init(void)
{
- install_element_ve(&show_bssgp_cmd);
- install_element_ve(&show_bssgp_stats_cmd);
- install_element_ve(&show_bvc_cmd);
- install_element_ve(&logging_fltr_bvc_cmd);
- install_element_ve(&bvc_reset_cmd);
+ install_lib_element_ve(&show_bssgp_cmd);
+ install_lib_element_ve(&show_bssgp_stats_cmd);
+ install_lib_element_ve(&show_bvc_cmd);
+ install_lib_element_ve(&logging_fltr_bvc_cmd);
+ install_lib_element_ve(&bvc_reset_cmd);
- install_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
- install_element(CONFIG_NODE, &cfg_bssgp_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_bssgp_cmd);
install_node(&bssgp_node, config_write_bssgp);
return 0;
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index 14fe6614..304fe7c1 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -29,7 +29,7 @@
* @{
*
* GPRS Networks Service (NS) messages on the Gb interface
- * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
*
* Some introduction into NS: NS is used typically on top of frame relay,
* but in the ip.access world it is encapsulated in UDP packets. It serves
@@ -352,8 +352,7 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
*/
void gprs_nsvc_delete(struct gprs_nsvc *nsvc)
{
- if (osmo_timer_pending(&nsvc->timer))
- osmo_timer_del(&nsvc->timer);
+ osmo_timer_del(&nsvc->timer);
llist_del(&nsvc->list);
rate_ctr_group_free(nsvc->ctrg);
osmo_stat_item_group_free(nsvc->statg);
@@ -486,8 +485,8 @@ static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
}
/* Increment number of Uplink bytes */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]);
- rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BYTES_OUT), msgb_l2len(msg));
switch (nsvc->ll) {
case GPRS_NS_LL_UDP:
@@ -533,7 +532,7 @@ static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
/*! Transmit a NS-RESET on a given NSVC
* \param[in] nsvc NS-VC used for transmission
- * \paam[in] cause Numeric NS cause value
+ * \param[in] cause Numeric NS cause value
*/
int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
{
@@ -619,7 +618,7 @@ int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause,
return gprs_ns_tx(nsvc, msg);
}
-/*! Transmit a NS-BLOCK on a tiven NS-VC
+/*! Transmit a NS-BLOCK on a given NS-VC
* \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted
* \param[in] cause Numeric NS Cause value
* \returns 0 in case of success
@@ -643,7 +642,7 @@ int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
/* be conservative and mark it as blocked even now! */
ns_mark_blocked(nsvc);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
@@ -750,8 +749,7 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
nsvc->nsei, get_value_string(timer_mode_strs, mode),
seconds);
- if (osmo_timer_pending(&nsvc->timer))
- osmo_timer_del(&nsvc->timer);
+ osmo_timer_del(&nsvc->timer);
osmo_gettimeofday(&nsvc->timer_started, NULL);
nsvc->timer_mode = mode;
@@ -781,15 +779,15 @@ static void gprs_ns_timer_cb(void *data)
switch (nsvc->timer_mode) {
case NSVC_TIMER_TNS_ALIVE:
/* Tns-alive case: we expired without response ! */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_ALIVE]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_ALIVE));
nsvc->alive_retries++;
if (nsvc->alive_retries >
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
/* mark as dead (and blocked unless IP-SNS) */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_DEAD));
if (!nsvc->nsi->bss_sns_fi && nsvc->nsi->nsip.use_reset_block_unblock) {
ns_set_state(nsvc, NSE_S_BLOCKED);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
} else
ns_set_state(nsvc, 0);
LOGP(DNS, LOGL_NOTICE,
@@ -816,7 +814,7 @@ static void gprs_ns_timer_cb(void *data)
nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
break;
case NSVC_TIMER_TNS_RESET:
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_RESET]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_RESET));
if (!(nsvc->state & NSE_S_RESET))
LOGP(DNS, LOGL_NOTICE,
"NSEI=%u Reset timed out but RESET flag is not set\n",
@@ -1076,7 +1074,7 @@ int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause)
* if the NS-VC is ALIVE and not BLOCKED. After that, it adds a NS
* header for the NS-UNITDATA message type and sends it off.
*
- * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive
+ * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive
*/
int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
{
@@ -1251,7 +1249,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET,
NS_IE_VCI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI));
gprs_ns_tx_reset_ack(*nsvc);
return 0;
}
@@ -1277,14 +1275,14 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET,
NS_IE_NSEI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI));
rc = gprs_ns_tx_reset_ack(*nsvc);
CHECK_TX_RC(rc, *nsvc);
return 0;
}
/* NSEI has changed */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG));
(*nsvc)->nsei = nsei;
}
@@ -1292,7 +1290,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
if (orig_nsvc) {
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED));
ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc);
/* Update the ll info fields */
@@ -1390,7 +1388,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET_ACK,
NS_IE_VCI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI));
LOGP(DNS, LOGL_ERROR,
"NS RESET ACK Unknown NS-VCI %d (%s NSEI=%d) "
"from %s\n",
@@ -1401,7 +1399,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
}
/* Notify others */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED));
ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc);
/* Update the ll info fields */
@@ -1415,7 +1413,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET_ACK,
NS_IE_NSEI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI));
LOGP(DNS, LOGL_ERROR,
"NS RESET ACK Unknown NSEI %d (NS-VCI=%u) from %s\n",
nsei, nsvci, gprs_ns_ll_str(*nsvc));
@@ -1423,14 +1421,14 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
}
/* NSEI has changed */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG));
(*nsvc)->nsei = nsei;
}
/* Mark NS-VC as blocked and alive */
ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
ns_set_remote_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BLOCKED));
if ((*nsvc)->persistent || (*nsvc)->remote_end_is_sgsn) {
/* stop RESET timer */
osmo_timer_del(&(*nsvc)->timer);
@@ -1471,7 +1469,7 @@ static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg)
//nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, *cause);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
return gprs_ns_tx_block_ack(nsvc);
}
@@ -1705,7 +1703,7 @@ int gprs_ns_vc_create(struct gprs_ns_inst *nsi, struct msgb *msg,
existing_nsvc->nsei = nsei;
/* Do statistics */
- rate_ctr_inc(&existing_nsvc->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(existing_nsvc->ctrg, NS_CTR_NSEI_CHG));
}
*new_nsvc = existing_nsvc;
@@ -1734,8 +1732,8 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
log_set_context(LOG_CTX_GB_NSVC, *nsvc);
/* Increment number of Incoming bytes */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_PKTS_IN]);
- rate_ctr_add(&(*nsvc)->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_PKTS_IN));
+ rate_ctr_add(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BYTES_IN), msgb_l2len(msg));
if (nsvc_is_not_used(*nsvc) && !ns_is_sns(nsh->pdu_type) && nsh->pdu_type != NS_PDUT_STATUS) {
LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx %s on unused/pre-configured endpoint, discarding\n",
@@ -1762,7 +1760,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
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],
+ osmo_stat_item_set(osmo_stat_item_group_get_item((*nsvc)->statg, NS_STAT_ALIVE_DELAY),
nsvc_timer_elapsed_ms(*nsvc));
/* stop Tns-alive and start Tns-test */
nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST);
@@ -2068,7 +2066,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
osmo_sock_init2_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM,
IPPROTO_UDP, inet_ntoa(in),
nsi->nsip.local_port, remote_str,
- nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT |
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp));
LOGP(DNS, LOGL_NOTICE,
"Listening for nsip packets from %s:%u on %s:%u\n",
@@ -2076,7 +2075,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
} else {
/* Accept UDP packets from any source IP/Port */
ret = osmo_sock_init_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM,
- IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port, OSMO_SOCK_F_BIND);
+ IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp));
LOGP(DNS, LOGL_NOTICE, "Listening for nsip packets on %s:%u\n", inet_ntoa(in), nsi->nsip.local_port);
}
@@ -2087,13 +2087,6 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
return ret;
}
- ret = setsockopt(nsi->nsip.fd.fd, IPPROTO_IP, IP_TOS,
- &nsi->nsip.dscp, sizeof(nsi->nsip.dscp));
- if (ret < 0)
- LOGP(DNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- nsi->nsip.dscp, ret, errno);
-
LOGP(DNS, LOGL_NOTICE, "NS UDP socket at %s:%d\n", inet_ntoa(in), nsi->nsip.local_port);
return ret;
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
new file mode 100644
index 00000000..4e496c1f
--- /dev/null
+++ b/src/gb/gprs_ns2.c
@@ -0,0 +1,1695 @@
+/*! \file gprs_ns2.c
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2016-2017,2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*! \addtogroup libgb
+ * @{
+ *
+ * GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ *
+ * Some introduction into NS: NS is used typically on top of frame relay,
+ * but in the ip.access world it is encapsulated in UDP packets. It serves
+ * as an intermediate shim betwen BSSGP and the underlying medium. It doesn't
+ * do much, apart from providing congestion notification and status indication.
+ *
+ * Terms:
+ *
+ * NS Network Service
+ * NSVC NS Virtual Connection
+ * NSEI NS Entity Identifier
+ * NSVL NS Virtual Link
+ * NSVLI NS Virtual Link Identifier
+ * BVC BSSGP Virtual Connection
+ * BVCI BSSGP Virtual Connection Identifier
+ * NSVCG NS Virtual Connection Goup
+ * Blocked NS-VC cannot be used for user traffic
+ * Alive Ability of a NS-VC to provide communication
+ *
+ * There can be multiple BSSGP virtual connections over one (group of) NSVC's. BSSGP will
+ * therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
+ * NS then has to figure out which NSVC's are responsible for this BVCI.
+ * Those mappings are administratively configured.
+ *
+ * This implementation has the following limitations:
+ * - NSVCI 65535 and 65534 are reserved for internal use
+ * - There are no BLOCK and UNBLOCK timers (yet?)
+ *
+ * \file gprs_ns2.c */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "gprs_ns2_internal.h"
+
+#define ns_set_state(ns_, st_) ns_set_state_with_log(ns_, st_, false, __FILE__, __LINE__)
+#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));
+
+/* HACK: The NS_IE_IP_ADDR does not follow any known TLV rules.
+ * Since it's a hard ABI break to implement 16 bit tag with fixed length entries to workaround it,
+ * the parser will be called with ns_att_tlvdef1 and if it's failed with ns_att_tlvdef2.
+ * The TLV parser depends on 8bit tag in many places.
+ * The NS_IE_IP_ADDR is only valid for SNS_ACK SNS_ADD and SNS_DELETE.
+ */
+static const struct tlv_definition ns_att_tlvdef1 = {
+ .def = {
+ [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 },
+ [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 in the IPv4 version */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 },
+ },
+};
+
+static const struct tlv_definition ns_att_tlvdef2 = {
+ .def = {
+ [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 },
+ [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 in the IPv6 version */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 17 },
+ },
+};
+
+
+/* Section 10.3.2, Table 13 */
+const struct value_string gprs_ns2_cause_strs[] = {
+ { NS_CAUSE_TRANSIT_FAIL, "Transit network failure" },
+ { NS_CAUSE_OM_INTERVENTION, "O&M intervention" },
+ { NS_CAUSE_EQUIP_FAIL, "Equipment failure" },
+ { NS_CAUSE_NSVC_BLOCKED, "NS-VC blocked" },
+ { NS_CAUSE_NSVC_UNKNOWN, "NS-VC unknown" },
+ { NS_CAUSE_BVCI_UNKNOWN, "BVCI unknown" },
+ { NS_CAUSE_SEM_INCORR_PDU, "Semantically incorrect PDU" },
+ { NS_CAUSE_PDU_INCOMP_PSTATE, "PDU not compatible with protocol state" },
+ { NS_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+ { NS_CAUSE_INVAL_ESSENT_IE, "Invalid essential IE" },
+ { NS_CAUSE_MISSING_ESSENT_IE, "Missing essential IE" },
+ { NS_CAUSE_INVAL_NR_IPv4_EP, "Invalid Number of IPv4 Endpoints" },
+ { NS_CAUSE_INVAL_NR_IPv6_EP, "Invalid Number of IPv6 Endpoints" },
+ { NS_CAUSE_INVAL_NR_NS_VC, "Invalid Number of NS-VCs" },
+ { NS_CAUSE_INVAL_WEIGH, "Invalid Weights" },
+ { NS_CAUSE_UNKN_IP_EP, "Unknown IP Endpoint" },
+ { NS_CAUSE_UNKN_IP_ADDR, "Unknown IP Address" },
+ { NS_CAUSE_UNKN_IP_TEST_FAILED, "IP Test Failed" },
+ { 0, NULL }
+};
+
+static const struct rate_ctr_desc ns_ctr_description[] = {
+ [NS_CTR_PKTS_IN] = { "packets:in", "Packets at NS Level ( In)" },
+ [NS_CTR_PKTS_OUT] = { "packets:out", "Packets at NS Level (Out)" },
+ [NS_CTR_PKTS_OUT_DROP] = { "packets:out:drop", "Dropped Packets (Out)" },
+ [NS_CTR_BYTES_IN] = { "bytes:in", "Bytes at NS Level ( In)" },
+ [NS_CTR_BYTES_OUT] = { "bytes:out", "Bytes at NS Level (Out)" },
+ [NS_CTR_BYTES_OUT_DROP] = { "bytes:out:drop", "Dropped Bytes (Out)" },
+ [NS_CTR_BLOCKED] = { "blocked", "NS-VC Block count " },
+ [NS_CTR_UNBLOCKED] = { "unblocked", "NS-VC Unblock count " },
+ [NS_CTR_DEAD] = { "dead", "NS-VC gone dead count " },
+ [NS_CTR_REPLACED] = { "replaced", "NS-VC replaced other count" },
+ [NS_CTR_NSEI_CHG] = { "nsei-chg", "NS-VC changed NSEI count " },
+ [NS_CTR_INV_VCI] = { "inv-nsvci", "NS-VCI was invalid count " },
+ [NS_CTR_INV_NSEI] = { "inv-nsei", "NSEI was invalid count " },
+ [NS_CTR_LOST_ALIVE] = { "lost:alive", "ALIVE ACK missing count " },
+ [NS_CTR_LOST_RESET] = { "lost:reset", "RESET ACK missing count " },
+};
+
+static const struct rate_ctr_group_desc nse_ctrg_desc = {
+ .group_name_prefix = "ns:nse",
+ .group_description = "NSE Peer Statistics",
+ .num_ctr = ARRAY_SIZE(ns_ctr_description),
+ .ctr_desc = ns_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
+ .group_name_prefix = "ns:nsvc",
+ .group_description = "NSVC Peer Statistics",
+ .num_ctr = ARRAY_SIZE(ns_ctr_description),
+ .ctr_desc = ns_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+
+static const struct osmo_stat_item_desc nsvc_stat_description[] = {
+ [NS_STAT_ALIVE_DELAY] = { "alive.delay", "ALIVE response time ", "ms", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
+ .group_name_prefix = "ns.nsvc",
+ .group_description = "NSVC Peer Statistics",
+ .num_items = ARRAY_SIZE(nsvc_stat_description),
+ .item_desc = nsvc_stat_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+const struct osmo_stat_item_desc nsbind_stat_description[] = {
+ [NS2_BIND_STAT_BACKLOG_LEN] = { "tx_backlog_length", "Transmit backlog length", "packets", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc nsbind_statg_desc = {
+ .group_name_prefix = "ns.bind",
+ .group_description = "NS Bind Statistics",
+ .num_items = ARRAY_SIZE(nsbind_stat_description),
+ .item_desc = nsbind_stat_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+const struct value_string gprs_ns2_aff_cause_prim_strs[] = {
+ { GPRS_NS2_AFF_CAUSE_VC_FAILURE, "NSVC failure" },
+ { GPRS_NS2_AFF_CAUSE_VC_RECOVERY, "NSVC recovery" },
+ { GPRS_NS2_AFF_CAUSE_FAILURE, "NSE failure" },
+ { 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_SNS_NO_ENDPOINTS, "NSE SNS no endpoints"},
+ { GPRS_NS2_AFF_CAUSE_MTU_CHANGE, "NSE MTU changed" },
+ { 0, NULL }
+};
+
+const struct value_string gprs_ns2_prim_strs[] = {
+ { GPRS_NS2_PRIM_UNIT_DATA, "UNIT DATA" },
+ { GPRS_NS2_PRIM_CONGESTION, "CONGESTION" },
+ { GPRS_NS2_PRIM_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string gprs_ns2_lltype_strs[] = {
+ { GPRS_NS2_LL_UDP, "UDP" },
+ { GPRS_NS2_LL_FR_GRE, "FR_GRE" },
+ { GPRS_NS2_LL_FR, "FR" },
+ { 0, NULL }
+};
+
+/*! string-format a given NS-VC into a user-supplied buffer.
+ * \param[in] buf user-allocated output buffer
+ * \param[in] buf_len size of user-allocated output buffer in bytes
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to buf on success; NULL on error */
+char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
+{
+ const struct osmo_sockaddr *local;
+ const struct osmo_sockaddr *remote;
+ struct osmo_sockaddr_str local_str;
+ struct osmo_sockaddr_str remote_str;
+
+ if (!buf_len)
+ return NULL;
+
+ switch (nsvc->nse->ll) {
+ case GPRS_NS2_LL_UDP:
+ if (!gprs_ns2_is_ip_bind(nsvc->bind)) {
+ buf[0] = '\0';
+ return buf;
+ }
+
+ local = gprs_ns2_ip_bind_sockaddr(nsvc->bind);
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ if (osmo_sockaddr_str_from_sockaddr(&local_str, &local->u.sas))
+ strcpy(local_str.ip, "invalid");
+ if (osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas))
+ strcpy(remote_str.ip, "invalid");
+
+ if (nsvc->nsvci_is_valid)
+ snprintf(buf, buf_len, "udp)[%s]:%u<%u>[%s]:%u",
+ local_str.ip, local_str.port,
+ nsvc->nsvci,
+ remote_str.ip, remote_str.port);
+ else
+ snprintf(buf, buf_len, "udp)[%s]:%u<>[%s]:%u",
+ local_str.ip, local_str.port,
+ remote_str.ip, remote_str.port);
+ break;
+ case GPRS_NS2_LL_FR_GRE:
+ snprintf(buf, buf_len, "frgre)");
+ break;
+ case GPRS_NS2_LL_FR:
+ snprintf(buf, buf_len, "fr)netif: %s dlci: %u", gprs_ns2_fr_bind_netif(nsvc->bind),
+ gprs_ns2_fr_nsvc_dlci(nsvc));
+ break;
+ default:
+ snprintf(buf, buf_len, "unknown)");
+ break;
+ }
+
+ buf[buf_len - 1] = '\0';
+
+ return buf;
+}
+
+/* udp is the longest: udp)[IP6]:65536<65536>[IP6]:65536 */
+#define NS2_LL_MAX_STR 4+2*(INET6_ADDRSTRLEN+9)+8
+
+/*! string-format a given NS-VC to a thread-local static buffer.
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to the string on success; NULL on error */
+const char *gprs_ns2_ll_str(struct gprs_ns2_vc *nsvc)
+{
+ static __thread char buf[NS2_LL_MAX_STR];
+ return gprs_ns2_ll_str_buf(buf, sizeof(buf), nsvc);
+}
+
+/*! string-format a given NS-VC to a dynamically allocated string.
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to the string on success; NULL on error */
+char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc)
+{
+ char *buf = talloc_size(ctx, NS2_LL_MAX_STR);
+ if (!buf)
+ return buf;
+ return gprs_ns2_ll_str_buf(buf, NS2_LL_MAX_STR, nsvc);
+}
+
+/*! Return the current state name of a given NS-VC to a thread-local static buffer.
+ * \param[in] nsvc NS-VC to return the state of
+ * \return pointer to the string on success; NULL on error */
+const char *gprs_ns2_nsvc_state_name(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_state_name(nsvc->fi);
+}
+
+/* select a signalling NSVC and respect sig_counter
+ * param[out] reset_counter - all counter has to be resetted to their signal weight
+ * return the chosen nsvc or NULL
+ */
+static struct gprs_ns2_vc *ns2_load_sharing_signal(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc = NULL, *last = NULL, *tmp;
+
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp->sig_weight == 0)
+ continue;
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (tmp->sig_counter == 0) {
+ last = tmp;
+ continue;
+ }
+
+ tmp->sig_counter--;
+ nsvc = tmp;
+ break;
+ }
+
+ /* all counter were zero, but there are valid nsvc */
+ if (!nsvc && last) {
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ tmp->sig_counter = tmp->sig_weight;
+ }
+
+ last->sig_counter--;
+ return last;
+ } else {
+ return nsvc;
+ }
+}
+
+/* 4.4.1 Load Sharing function for the Frame Relay Sub-Network */
+static struct gprs_ns2_vc *ns2_load_sharing_modulo(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t load_selector)
+{
+ struct gprs_ns2_vc *tmp;
+ uint32_t mod;
+ uint32_t i = 0;
+
+ if (nse->nsvc_count == 0)
+ return NULL;
+
+ mod = (bvci + load_selector) % nse->nsvc_count;
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (i == mod)
+ return tmp;
+ i++;
+ }
+
+ return NULL;
+}
+
+/* 4.4.2 Load Sharing function for the IP Sub-Network
+ *
+ * Implement a simple approach for UDP load sharing of data weight based on the modulo of the lsp.
+ *
+ * E.g. 3 NSVC: 1st weight 5, 2nd weight 3, 3rd weight 1, lsp = 3.
+ * sum all weights = 9
+ * target_weight = lsp % sum = 3
+ *
+ * 1st NSVC will be the target for 0-4
+ * 2nd NSVC will be the target for 5-7
+ * 3rd NSVC will be the target for 8
+ *
+ * The 1st NSVC will be used.
+ * E.g. lsp = 7. The 2nd NSVC will used.
+ */
+static struct gprs_ns2_vc *ns2_load_sharing_weight_modulo(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t load_selector)
+{
+ struct gprs_ns2_vc *tmp;
+ uint32_t mod;
+ uint32_t i = 0;
+
+ if (nse->nsvc_count == 0)
+ return NULL;
+
+ mod = (bvci + load_selector) % nse->sum_data_weight;
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp->data_weight == 0)
+ continue;
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (i == mod || mod < i + tmp->data_weight)
+ return tmp;
+ i += tmp->data_weight;
+ }
+
+ return NULL;
+}
+
+/* pick the first available data NSVC - no load sharing */
+struct gprs_ns2_vc *ns2_load_sharing_first(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc = NULL, *tmp;
+
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (tmp->data_weight == 0)
+ continue;
+
+ nsvc = tmp;
+ break;
+ }
+
+ return nsvc;
+}
+
+
+static struct gprs_ns2_vc *ns2_load_sharing(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t link_selector)
+{
+ struct gprs_ns2_vc *nsvc = NULL;
+
+ switch (nse->ll) {
+ case GPRS_NS2_LL_FR:
+ nsvc = ns2_load_sharing_modulo(nse, bvci, link_selector);
+ break;
+ case GPRS_NS2_LL_UDP:
+ default:
+ if (bvci == 0) {
+ /* signalling */
+ nsvc = ns2_load_sharing_signal(nse);
+ } else {
+ /* data with load sharing parameter */
+ nsvc = ns2_load_sharing_weight_modulo(nse, bvci, link_selector);
+ }
+ break;
+ }
+
+ return nsvc;
+}
+
+/*! Receive a primitive from the NS User (Gb).
+ * \param[in] nsi NS instance to which the primitive is issued
+ * \param[in] oph The primitive
+ * \return 0 on success; negative on error */
+int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph)
+{
+ /* TODO: implement resource distribution */
+ /* TODO: check for empty PDUs which can be sent to Request/Confirm
+ * the IP endpoint */
+ struct osmo_gprs_ns2_prim *nsp;
+ struct gprs_ns2_nse *nse = NULL;
+ struct gprs_ns2_vc *nsvc = NULL;
+ uint16_t bvci, nsei;
+ uint8_t sducontrol = 0;
+ int rc = 0;
+
+ if (oph->sap != SAP_NS) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
+
+ if (oph->operation != PRIM_OP_REQUEST || oph->primitive != GPRS_NS2_PRIM_UNIT_DATA) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (!oph->msg) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ bvci = nsp->bvci;
+ nsei = nsp->nsei;
+
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+ if (!nse) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (!nse->alive) {
+ goto out;
+ }
+
+ nsvc = ns2_load_sharing(nse, bvci, nsp->u.unitdata.link_selector);
+
+ /* TODO: send a status primitive back */
+ if (!nsvc)
+ goto out;
+
+ if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_REQUEST_CHANGE)
+ sducontrol = 1;
+ else if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_CONFIRM_CHANGE)
+ sducontrol = 2;
+
+ return ns2_tx_unit_data(nsvc, bvci, sducontrol, oph->msg);
+
+out:
+ msgb_free(oph->msg);
+ return rc;
+}
+
+/*! Send a STATUS.ind primitive to the specified NS instance user.
+ * \param[in] nsi NS instance on which we operate
+ * \param[in] nsei NSEI to which the statue relates
+ * \param[in] bvci BVCI to which the status relates
+ * \param[in] cause The cause of the status */
+void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc *nsvc,
+ uint16_t bvci,
+ enum gprs_ns2_affecting_cause cause)
+{
+ char nsvc_str[NS2_LL_MAX_STR];
+ struct osmo_gprs_ns2_prim nsp = {};
+ nsp.nsei = nse->nsei;
+ nsp.bvci = bvci;
+ nsp.u.status.cause = cause;
+ 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, 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.mtu);
+ } else {
+ 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.mtu);
+ }
+
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_STATUS, PRIM_OP_INDICATION, NULL);
+ nse->nsi->cb(&nsp.oph, nse->nsi->cb_data);
+}
+
+/*! Allocate a NS-VC within the given bind + NSE.
+ * \param[in] bind The 'bind' on which we operate
+ * \param[in] nse The NS Entity on which we operate
+ * \param[in] initiater - if this is an incoming remote (!initiater) or a local outgoing connection (initater)
+ * \param[in] id - human-readable identifier
+ * \return newly allocated NS-VC on success; NULL on error */
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater,
+ enum gprs_ns2_vc_mode vc_mode, const char *id)
+{
+ /* Sanity check */
+ OSMO_ASSERT(bind->ll == nse->ll);
+
+ struct gprs_ns2_vc *nsvc = talloc_zero(bind, struct gprs_ns2_vc);
+
+ if (!nsvc)
+ return NULL;
+
+ nsvc->bind = bind;
+ nsvc->nse = nse;
+ nsvc->mode = vc_mode;
+ nsvc->sig_weight = 1;
+ nsvc->data_weight = 1;
+
+ nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->nsvc_rate_ctr_idx);
+ if (!nsvc->ctrg) {
+ goto err;
+ }
+ nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->nsvc_rate_ctr_idx);
+ if (!nsvc->statg)
+ goto err_group;
+ if (!ns2_vc_fsm_alloc(nsvc, id, initiater))
+ goto err_statg;
+
+ bind->nsi->nsvc_rate_ctr_idx++;
+
+ rate_ctr_group_set_name(nsvc->ctrg, id);
+ osmo_stat_item_group_set_name(nsvc->statg, id);
+
+ llist_add_tail(&nsvc->list, &nse->nsvc);
+ llist_add_tail(&nsvc->blist, &bind->nsvc);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change);
+ ns2_nse_update_mtu(nse);
+
+ return nsvc;
+
+err_statg:
+ osmo_stat_item_group_free(nsvc->statg);
+err_group:
+ rate_ctr_group_free(nsvc->ctrg);
+err:
+ talloc_free(nsvc);
+
+ return NULL;
+}
+
+/*! Destroy/release given NS-VC.
+ * \param[in] nsvc NS-VC to destroy */
+void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc || nsvc->freed)
+ return;
+ nsvc->freed = true;
+ ns2_prim_status_ind(nsvc->nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_FAILURE);
+
+ llist_del(&nsvc->list);
+ llist_del(&nsvc->blist);
+
+ /* notify nse this nsvc is unavailable */
+ ns2_nse_notify_unblocked(nsvc, false);
+
+ /* check if sns is using this VC */
+ 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 */
+ if (nsvc->priv && nsvc->bind->free_vc)
+ nsvc->bind->free_vc(nsvc);
+
+ osmo_stat_item_group_free(nsvc->statg);
+ rate_ctr_group_free(nsvc->ctrg);
+
+ talloc_free(nsvc);
+}
+
+void ns2_free_nsvcs(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nse->nsvc)) {
+ nsvc = llist_first_entry(&nse->nsvc, struct gprs_ns2_vc, list);
+ gprs_ns2_free_nsvc(nsvc);
+ }
+}
+
+/*! Destroy/release all NS-VC of given NSE
+ * \param[in] nse NSE
+ */
+void gprs_ns2_free_nsvcs(struct gprs_ns2_nse *nse)
+{
+ if (!nse || nse->freed)
+ return;
+
+ if (nse->bss_sns_fi) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_FREE_NSVCS, NULL);
+ } else {
+ ns2_free_nsvcs(nse);
+ }
+}
+
+/*! Allocate a message buffer for use with the NS2 stack. */
+struct msgb *ns2_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM,
+ "GPRS/NS");
+ if (!msg) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to allocate NS message of size %d\n",
+ NS_ALLOC_SIZE);
+ }
+ return msg;
+}
+
+/*! Create a status message to be sent over a new connection.
+ * \param[in] orig_msg the original message
+ * \param[in] tp TLVP parsed of the original message
+ * \param[out] reject callee-allocated message buffer of the generated NS-STATUS
+ * \param[in] cause Cause for the rejection
+ * \return 0 on success */
+static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struct msgb **reject, enum ns_cause cause)
+{
+ struct msgb *msg = ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ bool have_vci = false;
+ uint8_t _cause = cause;
+ uint16_t nsei = 0;
+
+ if (!msg)
+ return -ENOMEM;
+
+ if (TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
+ nsei = tlvp_val16be(tp, NS_IE_NSEI);
+
+ LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rejecting message without NSVCI. Tx NS STATUS (cause=%s)\n",
+ nsei, gprs_ns2_cause_str(cause));
+ }
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_STATUS;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &_cause);
+ have_vci = TLVP_PRES_LEN(tp, NS_IE_VCI, 2);
+
+ /* Section 9.2.7.1: Static conditions for NS-VCI */
+ if (cause == NS_CAUSE_NSVC_BLOCKED ||
+ cause == NS_CAUSE_NSVC_UNKNOWN) {
+ if (!have_vci) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, TLVP_VAL(tp, NS_IE_VCI));
+ }
+
+ /* Section 9.2.7.2: Static conditions for NS PDU */
+ switch (cause) {
+ case NS_CAUSE_SEM_INCORR_PDU:
+ case NS_CAUSE_PDU_INCOMP_PSTATE:
+ case NS_CAUSE_PROTO_ERR_UNSPEC:
+ case NS_CAUSE_INVAL_ESSENT_IE:
+ case NS_CAUSE_MISSING_ESSENT_IE:
+ msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg),
+ orig_msg->l2h);
+ break;
+ default:
+ break;
+ }
+
+ *reject = msg;
+ return 0;
+}
+
+/*! Resolve a NS Entity based on its NSEI.
+ * \param[in] nsi NS Instance in which we do the look-up
+ * \param[in] nsei NSEI to look up
+ * \return NS Entity in successful case; NULL if none found */
+struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ if (nse->nsei == nsei)
+ return nse;
+ }
+
+ return NULL;
+}
+
+/*! Resolve a NS-VC Entity based on its NS-VCI.
+ * \param[in] nsi NS Instance in which we do the look-up
+ * \param[in] nsvci NS-VCI to look up
+ * \return NS-VC Entity in successful case; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci)
+{
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->nsvci_is_valid && nsvc->nsvci == nsvci)
+ return nsvc;
+ }
+ }
+
+ return NULL;
+}
+
+/*! Create a NS Entity within given NS instance.
+ * \param[in] nsi NS instance in which to create NS Entity
+ * \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \param[in] ip_sns_role_sgsn Does local side implement SGSN role?
+ * \returns newly-allocated NS-E in successful case; NULL on error */
+struct gprs_ns2_nse *gprs_ns2_create_nse2(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect,
+ bool ip_sns_role_sgsn)
+{
+ struct gprs_ns2_nse *nse;
+
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+ if (nse) {
+ LOGNSE(nse, LOGL_ERROR, "Can not create a NSE with already taken NSEI\n");
+ return nse;
+ }
+
+ nse = talloc_zero(nsi, struct gprs_ns2_nse);
+ if (!nse)
+ return NULL;
+ nse->dialect = GPRS_NS2_DIALECT_UNDEF;
+ nse->ip_sns_role_sgsn = ip_sns_role_sgsn;
+
+ if (ns2_nse_set_dialect(nse, dialect) < 0) {
+ talloc_free(nse);
+ return NULL;
+ }
+
+ nse->ctrg = rate_ctr_group_alloc(nse, &nse_ctrg_desc, nsei);
+ if (!nse->ctrg) {
+ talloc_free(nse);
+ return NULL;
+ }
+
+ nse->ll = linklayer;
+ nse->nsei = nsei;
+ nse->nsi = nsi;
+ nse->first = true;
+ nse->mtu = 0;
+ llist_add_tail(&nse->list, &nsi->nse);
+ INIT_LLIST_HEAD(&nse->nsvc);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+
+ return nse;
+}
+
+int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect)
+{
+ char sns[16];
+
+ if (nse->dialect == dialect)
+ return 0;
+
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_UNDEF:
+ if (dialect == GPRS_NS2_DIALECT_SNS) {
+ snprintf(sns, sizeof(sns), "NSE%05u-SNS", nse->nsei);
+ if (nse->ip_sns_role_sgsn)
+ nse->bss_sns_fi = ns2_sns_sgsn_fsm_alloc(nse, sns);
+ else
+ nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, sns);
+ if (!nse->bss_sns_fi)
+ return -1;
+ }
+ nse->dialect = dialect;
+ break;
+ default:
+ if (dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (nse->bss_sns_fi)
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nse->bss_sns_fi = NULL;
+ nse->dialect = GPRS_NS2_DIALECT_UNDEF;
+ } else {
+ /* we don't support arbitrary changes without going through UNDEF first */
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
+/*! Create a NS Entity within given NS instance.
+ * \param[in] nsi NS instance in which to create NS Entity
+ * \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \returns newly-allocated NS-E in successful case; NULL on error */
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect)
+{
+ return gprs_ns2_create_nse2(nsi, nsei, linklayer, dialect, false);
+}
+
+/*! Return the NSEI
+ * \param[in] nse NS Entity
+ * \return the nsei.
+ */
+uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse)
+{
+ return nse->nsei;
+}
+
+/*! Destroy given NS Entity.
+ * \param[in] nse NS Entity to destroy */
+void gprs_ns2_free_nse(struct gprs_ns2_nse *nse)
+{
+ if (!nse || nse->freed)
+ return;
+
+ nse->freed = true;
+ nse->alive = false;
+ if (nse->bss_sns_fi) {
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nse->bss_sns_fi = NULL;
+ }
+
+ gprs_ns2_free_nsvcs(nse);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE);
+ rate_ctr_group_free(nse->ctrg);
+ ns2_free_nsvcs(nse);
+
+ llist_del(&nse->list);
+ talloc_free(nse);
+}
+
+void gprs_ns2_free_nses(struct gprs_ns2_inst *nsi)
+{
+ struct gprs_ns2_nse *nse;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nsi->nse)) {
+ nse = llist_first_entry(&nsi->nse, struct gprs_ns2_nse, list);
+ gprs_ns2_free_nse(nse);
+ }
+}
+
+static inline int ns2_tlv_parse(struct tlv_parsed *dec,
+ const uint8_t *buf, int buf_len, uint8_t lv_tag,
+ uint8_t lv_tag2)
+{
+ /* workaround for NS_IE_IP_ADDR not following any known TLV rules.
+ * See comment of ns_att_tlvdef1. */
+ int rc = tlv_parse(dec, &ns_att_tlvdef1, buf, buf_len, lv_tag, lv_tag2);
+ if (rc < 0)
+ return tlv_parse(dec, &ns_att_tlvdef2, buf, buf_len, lv_tag, lv_tag2);
+ return rc;
+}
+
+static enum ns2_cs ns2_create_vc_sns(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_vc **success, uint16_t nsei)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, remote);
+ /* ns2_create_vc() is only called if no NS-VC could be found */
+ OSMO_ASSERT(!nsvc);
+
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ if (!bind->accept_sns) {
+ struct osmo_sockaddr_str remote_str;
+ osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas);
+ /* no dynamic creation of IP-SNS NSE permitted */
+ LOGP(DLNS, LOGL_ERROR, "[%s]:%u: Dynamic creation of NSE(%05u) via IP-SNS not "
+ "permitted. Check your config.\n", remote_str.ip, remote_str.port, nsei);
+ return NS2_CS_ERROR;
+ }
+ nse = gprs_ns2_create_nse2(bind->nsi, nsei, bind->ll, GPRS_NS2_DIALECT_SNS, true);
+ if (!nse) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei);
+ return NS2_CS_ERROR;
+ }
+ /* add configured list of default binds; if that fails, use only current bind */
+ if (!ns2_sns_add_sns_default_binds(nse))
+ gprs_ns2_sns_add_bind(nse, bind);
+ } else {
+ /* nsei already known */
+ if (nse->ll != bind->ll) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET with wrong linklayer(%s)"
+ " for already known NSE(%s)\n", gprs_ns2_lltype_str(bind->ll),
+ gprs_ns2_lltype_str(nse->ll));
+ return NS2_CS_SKIPPED;
+ }
+ }
+
+ nsvc = ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return NS2_CS_SKIPPED;
+
+ nsvc->nsvci_is_valid = false;
+
+ *success = nsvc;
+
+ return NS2_CS_CREATED;
+}
+
+/*! Create a new NS-VC based on a [received] message. Depending on the bind it might create a NSE.
+ * \param[in] bind the bind through which msg was received
+ * \param[in] msg the actual received message
+ * \param[in] remote address of remote peer sending message
+ * \param[in] logname A name to describe the VC. E.g. ip address pair
+ * \param[out] reject A message filled to be sent back. Only used in failure cases.
+ * \param[out] success A pointer which will be set to the new VC on success
+ * \return enum value indicating the status, e.g. GPRS_NS2_CS_CREATED */
+enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const struct osmo_sockaddr *remote,
+ const char *logname,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
+ struct tlv_parsed tp;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+ enum gprs_ns2_dialect dialect;
+ enum gprs_ns2_vc_mode vc_mode;
+ uint16_t nsvci;
+ uint16_t nsei;
+ const struct osmo_sockaddr *local;
+ char idbuf[256], tmp[INET6_ADDRSTRLEN + 8];
+
+ int rc, tlv;
+
+ if (msg->len < sizeof(struct gprs_ns_hdr))
+ return NS2_CS_ERROR;
+
+ /* parse the tlv early to allow reject status msg to
+ * work with valid tp.
+ * Ignore the return code until the pdu type is parsed because
+ * an unknown pdu type should be ignored */
+ tlv = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+
+ if (bind->ll == GPRS_NS2_LL_UDP && nsh->pdu_type == SNS_PDUT_SIZE && tlv >= 0) {
+ uint16_t nsei;
+
+ if (!TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) {
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ }
+ nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ /* Create NS-VC, and if required, even NSE dynamically */
+ return ns2_create_vc_sns(bind, remote, success, nsei);
+ }
+
+ switch (nsh->pdu_type) {
+ case NS_PDUT_STATUS:
+ /* Do not respond, see 3GPP TS 08.16, 7.5.1 */
+ LOGP(DLNS, LOGL_INFO, "Ignoring NS STATUS from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_ALIVE_ACK:
+ /* Ignore this, see 3GPP TS 08.16, 7.4.1 */
+ LOGP(DLNS, LOGL_INFO, "Ignoring NS ALIVE ACK from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_RESET_ACK:
+ /* Ignore this, see 3GPP TS 08.16, 7.3.1 */
+ LOGP(DLNS, LOGL_INFO, "Ignoring NS RESET ACK from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_RESET:
+ /* accept PDU RESET when vc_mode matches */
+ if (bind->accept_ipaccess) {
+ dialect = GPRS_NS2_DIALECT_IPACCESS;
+ break;
+ }
+
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ default:
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ }
+
+ if (tlv < 0) {
+ /* TODO: correct behaviour would checking what's wrong.
+ * If it's an essential TLV for the PDU return NS_CAUSE_INVAL_ESSENT_IE.
+ * Otherwise ignore the non-essential TLV. */
+ LOGP(DLNS, LOGL_ERROR, "Rx NS RESET Error %d during "
+ "TLV Parse\n", tlv);
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PROTO_ERR_UNSPEC);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ }
+
+ if (!TLVP_PRES_LEN(&tp, NS_IE_CAUSE, 1) ||
+ !TLVP_PRES_LEN(&tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) {
+ LOGP(DLNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ }
+
+ nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+
+ /* find or create NSE */
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ /* only create nse for udp & ipaccess */
+ if (bind->ll != GPRS_NS2_LL_UDP || dialect != GPRS_NS2_DIALECT_IPACCESS)
+ return NS2_CS_SKIPPED;
+
+ if (!bind->accept_ipaccess)
+ return NS2_CS_SKIPPED;
+
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, bind->ll, dialect);
+ if (!nse) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei);
+ return NS2_CS_ERROR;
+ }
+ } else {
+ /* nsei already known */
+ if (nse->ll != bind->ll) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET NS-VCI(%05u) with wrong linklayer(%s)"
+ " for already known NSE(%s)\n", nsvci, gprs_ns2_lltype_str(bind->ll),
+ gprs_ns2_lltype_str(nse->ll));
+ return NS2_CS_SKIPPED;
+ }
+ }
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(bind->nsi, nsvci);
+ if (nsvc) {
+ if (nsvc->persistent) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Received NS-RESET for a persistent NSE over wrong connection.\n");
+ return NS2_CS_SKIPPED;
+ }
+ /* destroy old dynamic nsvc */
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ /* do nse persistent check late to be more precise on the error message */
+ if (nse->persistent) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET for a persistent NSE but the unknown "
+ "NS-VCI(%05u)\n", nsvci);
+ return NS2_CS_SKIPPED;
+ }
+
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+ vc_mode = ns2_dialect_to_vc_mode(dialect);
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local);
+ snprintf(idbuf, sizeof(idbuf), "%s-NSE%05u-NSVC%05u-%s-%s", gprs_ns2_lltype_str(nse->ll),
+ nse->nsei, nsvci, tmp, osmo_sockaddr_to_str(remote));
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+ nsvc = ns2_vc_alloc(bind, nse, false, vc_mode, idbuf);
+ if (!nsvc)
+ return NS2_CS_SKIPPED;
+
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+
+ *success = nsvc;
+
+ return NS2_CS_CREATED;
+}
+
+/*! Create, and connect an inactive, new IP-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nse NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and inactive NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ nsvc = ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return NULL;
+
+ if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) {
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+ }
+
+ return nsvc;
+}
+
+/*! Create, connect and activate a new IP-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nse NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci)
+{
+ struct gprs_ns2_vc *nsvc;
+ nsvc = gprs_ns2_ip_connect_inactive(bind, remote, nse, nsvci);
+ if (!nsvc)
+ return NULL;
+
+ ns2_vc_fsm_start(nsvc);
+
+ return nsvc;
+}
+
+/*! Create, connect and activate a new IP-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsei,
+ uint16_t nsvci,
+ enum gprs_ns2_dialect dialect)
+{
+ struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_UDP, dialect);
+ if (!nse)
+ return NULL;
+ }
+
+ return gprs_ns2_ip_connect(bind, remote, nse, nsvci);
+}
+
+/*! Find NS-VC for given socket address.
+ * \param[in] nse NS Entity in which to search
+ * \param[in] sockaddr socket address to search for
+ * \return NS-VC matching sockaddr; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *sockaddr)
+{
+ struct gprs_ns2_vc *nsvc;
+ const struct osmo_sockaddr *remote;
+
+ OSMO_ASSERT(nse);
+ OSMO_ASSERT(sockaddr);
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ if (!osmo_sockaddr_cmp(sockaddr, remote))
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+/*!
+ * Iterate over all nsvc of a NS Entity and call the callback.
+ * If the callback returns < 0 it aborts the loop and returns the callback return code.
+ * \param[in] nse NS Entity to iterate over all nsvcs
+ * \param[in] cb the callback to call
+ * \param[inout] cb_data the private data of the callback
+ * \return 0 if the loop completes. If a callback returns < 0 it will returns this value.
+ */
+int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse, gprs_ns2_foreach_nsvc_cb cb, void *cb_data)
+{
+ struct gprs_ns2_vc *nsvc, *tmp;
+ int rc = 0;
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ rc = cb(nsvc, cb_data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+
+
+/*! Bottom-side entry-point for received NS PDU from the driver/bind
+ * \param[in] nsvc NS-VC for which the message was received
+ * \param msg the received message. Ownership is transferred, caller must not free it!
+ * \return 0 on success; negative on error */
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
+ struct msgb *msg)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct tlv_parsed tp = { };
+ int rc = 0;
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_IN);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_IN, msg->len);
+
+ if (msg->len < sizeof(struct gprs_ns_hdr)) {
+ rc = -EINVAL;
+ goto freemsg;
+ }
+
+ if (nsh->pdu_type != NS_PDUT_UNITDATA)
+ LOG_NS_RX_SIGNAL(nsvc, nsh->pdu_type);
+ else
+ LOG_NS_DATA(nsvc, "Rx", nsh->pdu_type, LOGL_INFO, "\n");
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_CONFIG:
+ /* one additional byte ('end flag') before the TLV part starts */
+ rc = ns2_tlv_parse(&tp, nsh->data+1,
+ msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ goto freemsg;
+ }
+ /* All sub-network service related message types */
+ return ns2_sns_rx(nsvc, msg, &tp);
+ case SNS_PDUT_ACK:
+ case SNS_PDUT_ADD:
+ case SNS_PDUT_CHANGE_WEIGHT:
+ case SNS_PDUT_DELETE:
+ /* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
+ rc = ns2_tlv_parse(&tp, nsh->data+5,
+ msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ goto freemsg;
+ }
+ 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;
+ return ns2_sns_rx(nsvc, msg, &tp);
+ case SNS_PDUT_CONFIG_ACK:
+ case SNS_PDUT_SIZE:
+ case SNS_PDUT_SIZE_ACK:
+ rc = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ goto freemsg;
+ }
+ /* All sub-network service related message types */
+ return ns2_sns_rx(nsvc, msg, &tp);
+ case NS_PDUT_UNITDATA:
+ return ns2_vc_rx(nsvc, msg, &tp);
+ default:
+ rc = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse\n");
+ if (nsh->pdu_type != NS_PDUT_STATUS)
+ ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg, NULL);
+ return rc;
+ }
+ return ns2_vc_rx(nsvc, msg, &tp);
+ }
+freemsg:
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* summarize all active data nsvcs */
+void ns2_nse_data_sum(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ nse->nsvc_count = 0;
+ nse->sum_data_weight = 0;
+ nse->sum_sig_weight = 0;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(nsvc))
+ continue;
+
+ nse->nsvc_count++;
+ nse->sum_data_weight += nsvc->data_weight;
+ nse->sum_sig_weight += nsvc->sig_weight;
+ }
+}
+
+/*! Notify a nse about the change of a NS-VC.
+ * \param[in] nsvc NS-VC which has detected the change (and shall not be notified).
+ * \param[in] unblocked whether the NSE should be marked as unblocked (true) or blocked (false) */
+void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked)
+{
+ struct gprs_ns2_nse *nse = nsvc->nse;
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ uint16_t nsei = nse->nsei;
+
+ ns2_nse_data_sum(nse);
+ ns2_sns_notify_alive(nse, nsvc, unblocked);
+
+ /* NSE could have been freed, try to get it again */
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+
+ if (!nse || unblocked == nse->alive)
+ return;
+
+ /* wait until both data_weight and sig_weight are != 0 before declaring NSE as alive */
+ if (unblocked && nse->sum_data_weight && nse->sum_sig_weight) {
+ nse->alive = true;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_RECOVERY);
+ nse->first = false;
+ return;
+ }
+
+ if (nse->alive && (nse->sum_data_weight == 0 || nse->sum_sig_weight == 0)) {
+ /* nse became unavailable */
+ nse->alive = false;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE);
+ }
+}
+
+/*! Create a new GPRS NS instance
+ * \param[in] ctx a talloc context to allocate NS instance from
+ * \param[in] cb Call-back function for dispatching primitives to the user. The Call-back must free all msgb* given in the primitive.
+ * \param[in] cb_data transparent user data passed to Call-back
+ * \returns dynamically allocated gprs_ns_inst; NULL on error */
+struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data)
+{
+ struct gprs_ns2_inst *nsi;
+
+ nsi = talloc_zero(ctx, struct gprs_ns2_inst);
+ if (!nsi)
+ return NULL;
+
+ nsi->cb = cb;
+ nsi->cb_data = cb_data;
+ INIT_LLIST_HEAD(&nsi->binding);
+ INIT_LLIST_HEAD(&nsi->nse);
+
+ nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
+ nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TNS_RESET] = 3;
+ nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3;
+ 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 */
+ nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES] = 3;
+
+ nsi->txqueue_max_length = NS_DEFAULT_TXQUEUE_MAX_LENGTH;
+
+ return nsi;
+}
+
+/*! Destroy a NS Instance (including all its NSEs, binds, ...).
+ * \param[in] nsi NS instance to destroy */
+void gprs_ns2_free(struct gprs_ns2_inst *nsi)
+{
+ if (!nsi)
+ return;
+
+ gprs_ns2_free_nses(nsi);
+ gprs_ns2_free_binds(nsi);
+
+ talloc_free(nsi);
+}
+
+/*! Start the NS-ALIVE FSM in all NS-VCs of given NSE.
+ * \param[in] nse NS Entity in whihc to start NS-ALIVE FSMs */
+void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ OSMO_ASSERT(nse);
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ /* A pre-configured endpoint shall not be used for NSE data or signalling traffic
+ * (with the exception of Size and Configuration procedures) unless it is
+ * configured by the SGSN using the auto-configuration procedures */
+ if (nsvc->sns_only)
+ continue;
+
+ ns2_vc_fsm_start(nsvc);
+ }
+}
+
+/*! Destroy a given bind.
+ * \param[in] bind the bind we want to destroy */
+void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+ if (!bind || bind->freed)
+ return;
+ bind->freed = true;
+
+ if (gprs_ns2_is_ip_bind(bind)) {
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ gprs_ns2_sns_del_bind(nse, bind);
+ }
+ }
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&bind->nsvc)) {
+ nsvc = llist_first_entry(&bind->nsvc, struct gprs_ns2_vc, blist);
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ if (bind->driver->free_bind)
+ bind->driver->free_bind(bind);
+
+ llist_del(&bind->list);
+ osmo_stat_item_group_free(bind->statg);
+ talloc_free((char *)bind->name);
+ talloc_free(bind);
+}
+
+void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nsi->binding)) {
+ bind = llist_first_entry(&nsi->binding, struct gprs_ns2_vc_bind, list);
+ gprs_ns2_free_bind(bind);
+ }
+}
+
+/*! Search for a bind with a unique name
+ * \param[in] nsi NS instance on which we operate
+ * \param[in] name The unique bind name to search for
+ * \return the bind or NULL if not found
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_bind_by_name(struct gprs_ns2_inst *nsi, const char *name)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!strcmp(bind->name, name))
+ return bind;
+ }
+
+ return NULL;
+}
+
+enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect)
+{
+ switch (dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ return GPRS_NS2_VC_MODE_ALIVE;
+ case GPRS_NS2_DIALECT_STATIC_RESETBLOCK:
+ case GPRS_NS2_DIALECT_IPACCESS:
+ return GPRS_NS2_VC_MODE_BLOCKRESET;
+ default:
+ return -1;
+ }
+}
+
+static void add_bind_array(struct gprs_ns2_vc_bind **array,
+ struct gprs_ns2_vc_bind *bind, int size)
+{
+ int i;
+ for (i=0; i < size; i++) {
+ if (array[i] == bind)
+ return;
+ if (!array[i])
+ break;
+ }
+
+ if (i == size)
+ return;
+
+ 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(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
+ * \return the transfer capability in mbit. On error < 0.
+ */
+int ns2_count_transfer_cap(struct gprs_ns2_nse *nse,
+ uint16_t bvci)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind **active_binds;
+ int i, active_nsvcs = 0, transfer_cap = 0;
+
+ /* calculate the transfer capabilities based on the binds.
+ * A bind has a transfer capability which is shared across all NSVCs.
+ * Take care the bind cap is not counted twice within a NSE.
+ * This should be accurate for FR and UDP but not for FR/GRE. */
+
+ if (!nse->alive)
+ return 0;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(nsvc))
+ active_nsvcs++;
+ }
+
+ if (!active_nsvcs)
+ return 0;
+
+ active_binds = talloc_zero_array(nse, struct gprs_ns2_vc_bind*, active_nsvcs);
+ if (!active_binds)
+ return -ENOMEM;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(nsvc))
+ continue;
+ add_bind_array(active_binds, nsvc->bind, active_nsvcs);
+ }
+
+ /* TODO: change calcuation for FR/GRE */
+ for (i = 0; i < active_nsvcs; i++) {
+ if (active_binds[i])
+ transfer_cap += active_binds[i]->transfer_capability;
+ }
+
+ talloc_free(active_binds);
+ return transfer_cap;
+}
+
+/*! common allocation + low-level initialization of a bind. Called by vc-drivers */
+int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ if (!name)
+ return -EINVAL;
+
+ if (gprs_ns2_bind_by_name(nsi, name))
+ return -EALREADY;
+
+ bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ if (!bind)
+ return -ENOMEM;
+
+ bind->name = talloc_strdup(bind, name);
+ if (!bind->name) {
+ talloc_free(bind);
+ return -ENOMEM;
+ }
+
+ bind->statg = osmo_stat_item_group_alloc(bind, &nsbind_statg_desc, nsi->bind_rate_ctr_idx);
+ if (!bind->statg) {
+ talloc_free(bind);
+ return -ENOMEM;
+ }
+
+ bind->sns_sig_weight = 1;
+ bind->sns_data_weight = 1;
+ bind->nsi = nsi;
+ INIT_LLIST_HEAD(&bind->nsvc);
+ llist_add_tail(&bind->list, &nsi->binding);
+
+ nsi->bind_rate_ctr_idx++;
+
+ if (result)
+ *result = bind;
+
+ return 0;
+}
+
+/*! @} */
diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c
new file mode 100644
index 00000000..f6bee39c
--- /dev/null
+++ b/src/gb/gprs_ns2_fr.c
@@ -0,0 +1,988 @@
+/*! \file gprs_ns2_fr.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2021 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <arpa/inet.h>
+#include <linux/if.h>
+
+#include <sys/ioctl.h>
+#include <netpacket/packet.h>
+#include <linux/if_ether.h>
+#include <linux/hdlc.h>
+#include <linux/hdlc/ioctl.h>
+#include <linux/sockios.h>
+
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/core/netdev.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+
+#include "config.h"
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+
+#define GRE_PTYPE_FR 0x6559
+#define GRE_PTYPE_IPv4 0x0800
+#define GRE_PTYPE_IPv6 0x86dd
+#define GRE_PTYPE_KAR 0x0000 /* keepalive response */
+
+#ifndef IPPROTO_GRE
+# define IPPROTO_GRE 47
+#endif
+
+#define E1_LINERATE 2048000
+#define E1_SLOTS_TOTAL 32
+#define E1_SLOTS_USED 31
+/* usable bitrate of the E1 superchannel with 31 of 32 timeslots */
+#define SUPERCHANNEL_LINERATE (E1_LINERATE*E1_SLOTS_USED)/E1_SLOTS_TOTAL
+/* nanoseconds per bit (504) */
+#define BIT_DURATION_NS (1000000000 / SUPERCHANNEL_LINERATE)
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg);
+
+struct gprs_ns2_vc_driver vc_driver_fr = {
+ .name = "GB frame relay",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_netdev *netdev;
+ char netif[IFNAMSIZ];
+ struct osmo_fr_link *link;
+ int ifindex;
+ bool if_running;
+ /* backlog queue for AF_PACKET / ENOBUFS handling (see OS#4995) */
+ struct {
+ /* file-descriptor for AF_PACKET socket */
+ struct osmo_fd ofd;
+ /* LMI bucket (we only store the last LMI message, no need to queue */
+ struct msgb *lmi_msg;
+ /* list of NS msgb (backlog) */
+ struct llist_head list;
+ /* timer to trigger next attempt of AF_PACKET write */
+ struct osmo_timer_list timer;
+ /* re-try after that many micro-seconds */
+ uint32_t retry_us;
+ } backlog;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+ uint16_t dlci;
+ struct osmo_fr_dlc *dlc;
+};
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ if (!nsvc->priv)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(nsvc->bind));
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+static void dump_vty(const struct gprs_ns2_vc_bind *bind, struct vty *vty, bool stats)
+{
+ struct priv_bind *priv;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_fr_link *fr_link;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+ fr_link = priv->link;
+
+ vty_out(vty, "FR bind: %s, role: %s, link: %s%s", priv->netif,
+ osmo_fr_role_str(fr_link->role), priv->if_running ? "UP" : "DOWN", VTY_NEWLINE);
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+ }
+
+ priv = bind->priv;
+}
+
+/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */
+static void free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+ struct msgb *msg, *msg2;
+
+ if (!bind)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->nsvc));
+
+ osmo_timer_del(&priv->backlog.timer);
+ llist_for_each_entry_safe(msg, msg2, &priv->backlog.list, list) {
+ msgb_free(msg);
+ }
+ msgb_free(priv->backlog.lmi_msg);
+
+ osmo_netdev_free(priv->netdev);
+ osmo_fr_link_free(priv->link);
+ osmo_fd_close(&priv->backlog.ofd);
+ talloc_free(priv);
+}
+
+static void fr_dlci_status_cb(struct osmo_fr_dlc *dlc, void *cb_data, bool active)
+{
+ struct gprs_ns2_vc *nsvc = cb_data;
+
+ if (active) {
+ ns2_vc_fsm_start(nsvc);
+ } else {
+ ns2_vc_force_unconfigured(nsvc);
+ }
+}
+
+static struct priv_vc *fr_alloc_vc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_vc *nsvc,
+ uint16_t dlci)
+{
+ struct priv_bind *privb = bind->priv;
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nsvc->priv = priv;
+ priv->dlci = dlci;
+ priv->dlc = osmo_fr_dlc_alloc(privb->link, dlci);
+ if (!priv->dlc) {
+ nsvc->priv = NULL;
+ talloc_free(priv);
+ return NULL;
+ }
+
+ priv->dlc->cb_data = nsvc;
+ priv->dlc->rx_cb = fr_dlci_rx_cb;
+ priv->dlc->status_cb = fr_dlci_status_cb;
+
+ return priv;
+}
+
+int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ if (!result)
+ return -EINVAL;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->dlci != dlci) {
+ *result = nsvc;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* PDU from the network interface towards the fr layer (upwards) */
+static int fr_netif_ofd_cb(struct osmo_fd *bfd, uint32_t what)
+{
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ struct msgb *msg;
+ struct sockaddr_ll sll;
+ socklen_t sll_len = sizeof(sll);
+ int rc = 0;
+
+ /* we only handle read here. write to AF_PACKET sockets cannot be triggered
+ * by select or poll, see OS#4995 */
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR Rx");
+ if (!msg)
+ return -ENOMEM;
+
+ rc = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, (struct sockaddr *)&sll, &sll_len);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR recv\n", strerror(errno));
+ goto out_err;
+ } else if (rc == 0) {
+ goto out_err;
+ }
+
+ /* ignore any packets that we might have received for a different interface, between
+ * the socket() and the bind() call */
+ if (sll.sll_ifindex != priv->ifindex)
+ goto out_err;
+
+ msgb_put(msg, rc);
+ msg->dst = priv->link;
+ return osmo_fr_rx(msg);
+
+out_err:
+ msgb_free(msg);
+ return rc;
+}
+
+/* PDU from the frame relay towards the NS-VC (upwards) */
+static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc *nsvc = cb_data;
+
+ rc = ns2_recv_vc(nsvc, msg);
+
+ return rc;
+}
+
+static int fr_netif_write_one(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ unsigned int len = msgb_length(msg);
+ int rc;
+
+ /* estimate the retry time based on the data rate it takes to transmit */
+ priv->backlog.retry_us = (BIT_DURATION_NS * 8 * len) / 1000;
+
+ rc = write(priv->backlog.ofd.fd, msgb_data(msg), len);
+ if (rc == len) {
+ msgb_free(msg);
+ return 0;
+ } else if (rc < 0) {
+ /* don't free, the caller might want to re-transmit */
+ switch (errno) {
+ case EAGAIN:
+ case ENOBUFS:
+ /* not a real error, but more a normal event on AF_PACKET */
+ /* don't free the message and let the caller re-enqueue */
+ return -errno;
+ default:
+ /* an actual error, like -ENETDOWN, -EMSGSIZE */
+ LOGBIND(bind, LOGL_ERROR, "error during write to AF_PACKET: %s\n", strerror(errno));
+ msgb_free(msg);
+ return 0;
+ }
+ } else {
+ /* short write */
+ LOGBIND(bind, LOGL_ERROR, "short write on AF_PACKET: %d < %d\n", rc, len);
+ msgb_free(msg);
+ return 0;
+ }
+}
+
+/*! determine if given bind is for FR-GRE encapsulation. */
+int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_fr);
+}
+
+/* PDU from the NS-VC towards the frame relay layer (downwards) */
+static int fr_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct priv_vc *vcpriv = nsvc->priv;
+ unsigned int vc_len = msgb_length(msg);
+ int rc;
+
+ msg->dst = vcpriv->dlc;
+ rc = osmo_fr_tx_dlc(msg);
+ if (OSMO_LIKELY(rc >= 0)) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, vc_len);
+ } else {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len);
+ }
+ return rc;
+}
+
+static void enqueue_at_head(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ llist_add(&msg->list, &priv->backlog.list);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+static void enqueue_at_tail(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ llist_add_tail(&msg->list, &priv->backlog.list);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+#define LMI_Q933A_DLCI 0
+
+/* enqueue to backlog (LMI, signaling) or drop (userdata msg) */
+static int backlog_enqueue_or_free(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ uint8_t dlci = msg->data[0];
+ uint8_t ns_pdu_type;
+ uint16_t bvci;
+
+ if (msgb_length(msg) < 1)
+ goto out_free;
+
+ /* we want to enqueue only Q.933 LMI traffic or NS signaling; NOT user traffic */
+ switch (dlci) {
+ case LMI_Q933A_DLCI:
+ /* always store only the last LMI message in the lmi_msg bucket */
+ msgb_free(priv->backlog.lmi_msg);
+ priv->backlog.lmi_msg = msg;
+ return 0;
+ default:
+ /* there's no point in trying to enqueue messages if the interface is down */
+ if (!priv->if_running)
+ break;
+
+ if (msgb_length(msg) < 3)
+ break;
+ ns_pdu_type = msg->data[2];
+ switch (ns_pdu_type) {
+ case NS_PDUT_UNITDATA:
+ if (msgb_length(msg) < 6)
+ break;
+ bvci = osmo_load16be(msg->data + 4);
+ /* enqueue BVCI=0 traffic at tail of queue */
+ if (bvci == BVCI_SIGNALLING) {
+ enqueue_at_tail(bind, msg);
+ return 0;
+ }
+ break;
+ default:
+ /* enqueue NS signaling traffic at head of queue */
+ enqueue_at_head(bind, msg);
+ return 0;
+ }
+ break;
+ }
+
+out_free:
+ /* drop everything that is not LMI, NS-signaling or BVCI-0 */
+ msgb_free(msg);
+ return -1;
+}
+
+static void fr_backlog_timer_cb(void *data)
+{
+ struct gprs_ns2_vc_bind *bind = data;
+ struct priv_bind *priv = bind->priv;
+ int i, rc;
+
+ /* first try to get rid of the LMI message, if any */
+ if (priv->backlog.lmi_msg) {
+ rc = fr_netif_write_one(bind, priv->backlog.lmi_msg);
+ if (rc < 0)
+ goto restart_timer;
+ /* fr_netif_write_one() has just free'd it */
+ priv->backlog.lmi_msg = NULL;
+ }
+
+ /* attempt to send up to 10 messages in every timer */
+ for (i = 0; i < 10; i++) {
+ struct msgb *msg = msgb_dequeue(&priv->backlog.list);
+ if (!msg)
+ break;
+
+ rc = fr_netif_write_one(bind, msg);
+ if (rc < 0) {
+ /* re-add at head of list */
+ llist_add(&msg->list, &priv->backlog.list);
+ break;
+ }
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ }
+
+restart_timer:
+ /* re-start timer if we still have data in the queue */
+ if (!llist_empty(&priv->backlog.list))
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+/* PDU from the frame relay layer towards the network interface (downwards) */
+int fr_tx_cb(void *data, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = data;
+ struct priv_bind *priv = bind->priv;
+ int rc;
+
+ if (llist_empty(&priv->backlog.list)) {
+ /* attempt to transmit right now */
+ rc = fr_netif_write_one(bind, msg);
+ if (rc < 0) {
+ /* enqueue to backlog in case it fails */
+ return backlog_enqueue_or_free(bind, msg);
+ }
+ } else {
+ /* enqueue to backlog */
+ return backlog_enqueue_or_free(bind, msg);
+ }
+
+ return 0;
+}
+
+static int devname2ifindex(const char *ifname)
+{
+ struct ifreq ifr;
+ int sk, rc;
+
+ sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sk < 0)
+ return sk;
+
+
+ memset(&ifr, 0, sizeof(ifr));
+ OSMO_STRLCPY_ARRAY(ifr.ifr_name, ifname);
+
+ rc = ioctl(sk, SIOCGIFINDEX, &ifr);
+ close(sk);
+ if (rc < 0)
+ return rc;
+
+ return ifr.ifr_ifindex;
+}
+
+static int open_socket(int ifindex, const struct gprs_ns2_vc_bind *nsbind)
+{
+ struct sockaddr_ll addr;
+ int fd, rc;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sll_family = AF_PACKET;
+ addr.sll_protocol = htons(ETH_P_ALL);
+ addr.sll_ifindex = ifindex;
+
+ fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_HDLC));
+ if (fd < 0) {
+ LOGBIND(nsbind, LOGL_ERROR, "Can not create AF_PACKET socket. Are you root or have CAP_NET_RAW?\n");
+ return fd;
+ }
+
+ /* there's a race condition between the above syscall and the bind() call below,
+ * causing other packets to be received in between */
+
+ rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc < 0) {
+ LOGBIND(nsbind, LOGL_ERROR, "Can not bind AF_PACKET socket to ifindex %d\n", ifindex);
+ close(fd);
+ return rc;
+ }
+
+ return fd;
+}
+
+static int gprs_n2_fr_ifupdown_ind_cb(struct osmo_netdev *netdev, bool if_running)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev);
+ struct priv_bind *bpriv = bind->priv;
+ struct msgb *msg, *msg2;
+
+ if (bpriv->if_running == if_running)
+ return 0;
+
+ LOGBIND(bind, LOGL_NOTICE, "FR net-device '%s': Physical link state changed: %s\n",
+ bpriv->netif, if_running ? "UP" : "DOWN");
+
+ /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes
+ * sense to get one out of the door ASAP. */
+ llist_for_each_entry_safe(msg, msg2, &bpriv->backlog.list, list) {
+ msgb_free(msg);
+ }
+
+ if (if_running) {
+ /* interface just came up */
+ if (bpriv->backlog.lmi_msg)
+ osmo_timer_schedule(&bpriv->backlog.timer, 0, bpriv->backlog.retry_us);
+ } else {
+ /* interface just went down; no need to retransmit */
+ osmo_timer_del(&bpriv->backlog.timer);
+ }
+
+ bpriv->if_running = if_running;
+ return 0;
+}
+
+static int gprs_n2_fr_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev);
+ struct priv_bind *bpriv = bind->priv;
+ struct gprs_ns2_nse *nse;
+
+ /* 2 byte DLCI header */
+ if (new_mtu <= 2)
+ return 0;
+ new_mtu -= 2;
+
+ if (new_mtu == bind->mtu)
+ return 0;
+
+ LOGBIND(bind, LOGL_INFO, "MTU changed from %d to %d.\n",
+ bind->mtu + 2, new_mtu + 2);
+
+ bind->mtu = new_mtu;
+ if (!bpriv->if_running)
+ return 0;
+
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ ns2_nse_update_mtu(nse);
+ }
+ return 0;
+}
+
+static int set_ifupdown(const char *netif, bool up)
+{
+ int sock, rc;
+ struct ifreq req;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return sock;
+
+ memset(&req, 0, sizeof req);
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+
+ rc = ioctl(sock, SIOCGIFFLAGS, &req);
+ if (rc < 0) {
+ close(sock);
+ return rc;
+ }
+
+ if ((req.ifr_flags & IFF_UP) == up) {
+ close(sock);
+ return 0;
+ }
+
+ if (up)
+ req.ifr_flags |= IFF_UP;
+
+ rc = ioctl(sock, SIOCSIFFLAGS, &req);
+ close(sock);
+ return rc;
+}
+
+static int setup_device(const char *netif, const struct gprs_ns2_vc_bind *bind)
+{
+ int sock, rc;
+ char buffer[128];
+ fr_proto *fr = (void*)buffer;
+ struct ifreq req;
+
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to create socket: %s\n",
+ netif, strerror(errno));
+ return sock;
+ }
+
+ memset(&req, 0, sizeof(struct ifreq));
+ memset(&buffer, 0, sizeof(buffer));
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+ req.ifr_settings.ifs_ifsu.sync = (void*)buffer;
+ req.ifr_settings.size = sizeof(buffer);
+ req.ifr_settings.type = IF_GET_PROTO;
+
+ /* EINVAL is returned when no protocol has been set */
+ rc = ioctl(sock, SIOCWANDEV, &req);
+ if (rc < 0 && errno != EINVAL) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to get FR protocol information: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ /* check if the device is good */
+ if (rc == 0 && req.ifr_settings.type == IF_PROTO_FR && fr->lmi == LMI_NONE) {
+ LOGBIND(bind, LOGL_NOTICE, "%s: has correct frame relay mode and lmi\n", netif);
+ goto ifup;
+ }
+
+ /* modify the device to match */
+ rc = set_ifupdown(netif, false);
+ if (rc) {
+ LOGBIND(bind, LOGL_ERROR, "Unable to bring down the device %s: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ memset(&req, 0, sizeof(struct ifreq));
+ memset(fr, 0, sizeof(fr_proto));
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+ req.ifr_settings.type = IF_PROTO_FR;
+ req.ifr_settings.size = sizeof(fr_proto);
+ req.ifr_settings.ifs_ifsu.fr = fr;
+ fr->lmi = LMI_NONE;
+ /* even those settings aren't used, they must be in the range */
+ /* polling verification timer*/
+ fr->t391 = 10;
+ /* link integrity verification polling timer */
+ fr->t392 = 15;
+ /* full status polling counter*/
+ fr->n391 = 6;
+ /* error threshold */
+ fr->n392 = 3;
+ /* monitored events count */
+ fr->n393 = 4;
+
+ LOGBIND(bind, LOGL_INFO, "%s: Setting frame relay related parameters\n", netif);
+ rc = ioctl(sock, SIOCWANDEV, &req);
+ if (rc) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to set FR protocol on information: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ifup:
+ rc = set_ifupdown(netif, true);
+ if (rc)
+ LOGBIND(bind, LOGL_ERROR, "Unable to bring up the device %s: %s\n",
+ netif, strerror(errno));
+err:
+ close(sock);
+ return rc;
+}
+
+/*! Create a new bind for NS over FR.
+ * \param[in] nsi NS instance in which to create the bind
+ * \param[in] netif Network interface to bind to
+ * \param[in] fr_network
+ * \param[in] fr_role
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
+int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
+ const char *netif,
+ struct osmo_fr_network *fr_network,
+ enum osmo_fr_role fr_role,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct priv_bind *priv;
+ struct osmo_fr_link *fr_link;
+ int rc = 0;
+
+ if (strlen(netif) > IFNAMSIZ)
+ return -EINVAL;
+
+ bind = gprs_ns2_bind_by_name(nsi, name);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EALREADY;
+ }
+
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
+ bind->driver = &vc_driver_fr;
+ bind->ll = GPRS_NS2_LL_FR;
+ /* 2 mbit */
+ bind->transfer_capability = 2;
+ 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;
+ goto err_bind;
+ }
+
+ INIT_LLIST_HEAD(&priv->backlog.list);
+ OSMO_STRLCPY_ARRAY(priv->netif, netif);
+
+ /* FIXME: move fd handling into socket.c */
+ fr_link = osmo_fr_link_alloc(fr_network, fr_role, netif);
+ if (!fr_link) {
+ rc = -EINVAL;
+ goto err_bind;
+ }
+
+ fr_link->tx_cb = fr_tx_cb;
+ fr_link->cb_data = bind;
+ priv->link = fr_link;
+
+ priv->ifindex = rc = devname2ifindex(netif);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Can not get interface index for interface %s\n", netif);
+ goto err_fr;
+ }
+
+ priv->netdev = osmo_netdev_alloc(bind, name);
+ if (!priv->netdev) {
+ rc = -ENOENT;
+ goto err_fr;
+ }
+ osmo_netdev_set_priv_data(priv->netdev, bind);
+ osmo_netdev_set_ifupdown_ind_cb(priv->netdev, gprs_n2_fr_ifupdown_ind_cb);
+ osmo_netdev_set_mtu_chg_cb(priv->netdev, gprs_n2_fr_mtu_chg_cb);
+ rc = osmo_netdev_set_ifindex(priv->netdev, priv->ifindex);
+ if (rc < 0)
+ goto err_free_netdev;
+ rc = osmo_netdev_register(priv->netdev);
+ if (rc < 0)
+ goto err_free_netdev;
+
+ /* set protocol frame relay and lmi */
+ rc = setup_device(priv->netif, bind);
+ if(rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to setup the interface %s for frame relay and lmi\n", netif);
+ goto err_free_netdev;
+ }
+
+ rc = open_socket(priv->ifindex, bind);
+ if (rc < 0)
+ goto err_free_netdev;
+ priv->backlog.retry_us = 2500; /* start with some non-zero value; this corrsponds to 496 bytes */
+ osmo_timer_setup(&priv->backlog.timer, fr_backlog_timer_cb, bind);
+ osmo_fd_setup(&priv->backlog.ofd, rc, OSMO_FD_READ, fr_netif_ofd_cb, bind, 0);
+ rc = osmo_fd_register(&priv->backlog.ofd);
+ if (rc < 0)
+ goto err_fd;
+
+ if (result)
+ *result = bind;
+
+ return rc;
+
+err_fd:
+ close(priv->backlog.ofd.fd);
+err_free_netdev:
+ osmo_netdev_free(priv->netdev);
+ priv->netdev = NULL;
+err_fr:
+ osmo_fr_link_free(fr_link);
+ priv->link = NULL;
+err_bind:
+ gprs_ns2_free_bind(bind);
+
+ return rc;
+}
+
+/*! Return the frame relay role of a bind
+ * \param[in] bind The bind
+ * \return the frame relay role or -EINVAL if bind is not frame relay
+ */
+enum osmo_fr_role gprs_ns2_fr_bind_role(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (bind->driver != &vc_driver_fr)
+ return -EINVAL;
+
+ priv = bind->priv;
+ return priv->link->role;
+}
+
+/*! Return the network interface of the bind
+ * \param[in] bind The bind
+ * \return the network interface
+ */
+const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (bind->driver != &vc_driver_fr)
+ return NULL;
+
+ priv = bind->priv;
+ return priv->netif;
+}
+
+/*! Find NS bind for a given network interface
+ * \param[in] nsi NS instance
+ * \param[in] netif the network interface to search for
+ * \return the bind or NULL if not found
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif(
+ struct gprs_ns2_inst *nsi,
+ const char *netif)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const char *_netif;
+
+ OSMO_ASSERT(nsi);
+ OSMO_ASSERT(netif);
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_fr_bind(bind))
+ continue;
+
+ _netif = gprs_ns2_fr_bind_netif(bind);
+ if (!strncmp(_netif, netif, IFNAMSIZ))
+ return bind;
+ }
+
+ return NULL;
+}
+
+/*! Create, connect and activate a new FR-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] dlci Data Link connection identifier
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci,
+ uint16_t dlci)
+{
+ struct gprs_ns2_vc *nsvc = NULL;
+ struct priv_vc *priv = NULL;
+ struct priv_bind *bpriv = bind->priv;
+ char idbuf[64];
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci);
+ if (nsvc) {
+ goto err;
+ }
+
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC%05u-%s-%s-DLCI%u", nse->nsei, nsvci,
+ gprs_ns2_lltype_str(nse->ll), bpriv->netif, dlci);
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+ nsvc = ns2_vc_alloc(bind, nse, true, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ if (!nsvc)
+ goto err;
+
+ nsvc->priv = priv = fr_alloc_vc(bind, nsvc, dlci);
+ if (!priv)
+ goto err;
+
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+
+ return nsvc;
+
+err:
+ gprs_ns2_free_nsvc(nsvc);
+ return NULL;
+}
+
+
+/*! Create, connect and activate a new FR-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] dlci Data Link connection identifier
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_fr_connect2(struct gprs_ns2_vc_bind *bind,
+ uint16_t nsei,
+ uint16_t nsvci,
+ uint16_t dlci)
+{
+ bool created_nse = false;
+ struct gprs_ns2_vc *nsvc = NULL;
+ struct gprs_ns2_nse *nse;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_FR, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ if (!nse)
+ return NULL;
+ created_nse = true;
+ }
+
+ nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci);
+ if (!nsvc)
+ goto err_nse;
+
+ return nsvc;
+
+err_nse:
+ if (created_nse)
+ gprs_ns2_free_nse(nse);
+
+ return NULL;
+}
+
+/*! Return the nsvc by dlci.
+ * \param[in] bind
+ * \param[in] dlci Data Link connection identifier
+ * \return the nsvc or NULL if not found
+ */
+struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+
+ if (dlci == vcpriv->dlci)
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+/*! Return the dlci of the nsvc
+ * \param[in] nsvc
+ * \return the dlci or 0 on error. 0 is not a valid dlci.
+ */
+uint16_t gprs_ns2_fr_nsvc_dlci(const struct gprs_ns2_vc *nsvc)
+{
+ struct priv_vc *vcpriv;
+
+ if (!nsvc->bind)
+ return 0;
+
+ if (nsvc->bind->driver != &vc_driver_fr)
+ return 0;
+
+ vcpriv = nsvc->priv;
+ return vcpriv->dlci;
+}
diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c
new file mode 100644
index 00000000..b99761eb
--- /dev/null
+++ b/src/gb/gprs_ns2_frgre.c
@@ -0,0 +1,616 @@
+/*! \file gprs_ns2_frgre.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2010,2014,2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include "gprs_ns2_internal.h"
+
+#define GRE_PTYPE_FR 0x6559
+#define GRE_PTYPE_IPv4 0x0800
+#define GRE_PTYPE_IPv6 0x86dd
+#define GRE_PTYPE_KAR 0x0000 /* keepalive response */
+
+#ifndef IPPROTO_GRE
+# define IPPROTO_GRE 47
+#endif
+
+struct gre_hdr {
+ uint16_t flags;
+ uint16_t ptype;
+} __attribute__ ((packed));
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__CYGWIN__)
+/**
+ * On BSD the IPv4 struct is called struct ip and instead of iXX
+ * the members are called ip_XX. One could change this code to use
+ * struct ip but that would require to define _BSD_SOURCE and that
+ * might have other complications. Instead make sure struct iphdr
+ * is present on FreeBSD. The below is taken from GLIBC.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+struct iphdr
+ {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned int ihl:4;
+ unsigned int version:4;
+#elif BYTE_ORDER == BIG_ENDIAN
+ unsigned int version:4;
+ unsigned int ihl:4;
+#endif
+ u_int8_t tos;
+ u_int16_t tot_len;
+ u_int16_t id;
+ u_int16_t frag_off;
+ u_int8_t ttl;
+ u_int8_t protocol;
+ u_int16_t check;
+ u_int32_t saddr;
+ u_int32_t daddr;
+ /*The options start here. */
+ };
+#endif
+
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ struct osmo_sockaddr *dest);
+
+struct gprs_ns2_vc_driver vc_driver_frgre = {
+ .name = "GB frame relay over GRE",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_fd fd;
+ struct osmo_sockaddr addr;
+ uint16_t dlci;
+ int dscp;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+ uint16_t dlci;
+};
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ OSMO_ASSERT(nsvc);
+
+ if (!nsvc->priv)
+ return;
+
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+
+/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */
+static void free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->nsvc));
+
+ osmo_fd_close(&priv->fd);
+ talloc_free(priv);
+}
+
+static struct priv_vc *frgre_alloc_vc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_vc *nsvc,
+ struct osmo_sockaddr *remote,
+ uint16_t dlci)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ nsvc->priv = priv;
+ priv->remote = *remote;
+ priv->dlci = dlci;
+
+ return priv;
+}
+
+static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
+ struct ip6_hdr *ip6hdr, struct gre_hdr *greh)
+{
+ /* RFC 7676 IPv6 Support for Generic Routing Encapsulation (GRE) */
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ int gre_payload_len;
+ struct ip6_hdr *inner_ip6h;
+ struct gre_hdr *inner_greh;
+ struct sockaddr_in6 daddr;
+ struct in6_addr ia6;
+
+ gre_payload_len = msg->len - (sizeof(*ip6hdr) + sizeof(*greh));
+
+ inner_ip6h = (struct ip6_hdr *) ((uint8_t *)greh + sizeof(*greh));
+
+ if (gre_payload_len < sizeof(*ip6hdr) + sizeof(*inner_greh)) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n");
+ return -EIO;
+ }
+
+ if (!memcmp(&inner_ip6h->ip6_src, &ip6hdr->ip6_src, sizeof(struct in6_addr)) ||
+ !memcmp(&inner_ip6h->ip6_dst, &ip6hdr->ip6_dst, sizeof(struct in6_addr))) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n");
+ return -EIO;
+ }
+
+ /* Are IPv6 extensions header are allowed in the *inner*? In the outer they are */
+ if (inner_ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_GRE) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ return -EIO;
+ }
+
+ inner_greh = (struct gre_hdr *) ((uint8_t *)inner_ip6h + sizeof(struct ip6_hdr));
+ if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ return -EIO;
+ }
+
+ /* Actually send the response back */
+
+ daddr.sin6_family = AF_INET6;
+ daddr.sin6_addr = inner_ip6h->ip6_dst;
+ daddr.sin6_port = IPPROTO_GRE;
+
+ ia6 = ip6hdr->ip6_src;
+ char ip6str[INET6_ADDRSTRLEN] = {};
+ inet_ntop(AF_INET6, &ia6, ip6str, INET6_ADDRSTRLEN);
+ LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str);
+
+ /* why does it reduce the gre_payload_len by the ipv6 header?
+ * make it similiar to ipv4 even this seems to be wrong */
+ return sendto(priv->fd.fd, inner_greh,
+ gre_payload_len - sizeof(*inner_ip6h), 0,
+ (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+/* IPv4 messages inside the GRE tunnel might be GRE keepalives */
+static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
+ struct iphdr *iph, struct gre_hdr *greh)
+{
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ int gre_payload_len;
+ struct iphdr *inner_iph;
+ struct gre_hdr *inner_greh;
+ struct sockaddr_in daddr;
+ struct in_addr ia;
+
+ gre_payload_len = msg->len - (iph->ihl*4 + sizeof(*greh));
+
+ inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh));
+
+ if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n");
+ return -EIO;
+ }
+
+ if (inner_iph->saddr != iph->daddr ||
+ inner_iph->daddr != iph->saddr) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n");
+ return -EIO;
+ }
+
+ if (inner_iph->protocol != IPPROTO_GRE) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ return -EIO;
+ }
+
+ inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4);
+ if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ return -EIO;
+ }
+
+ /* Actually send the response back */
+
+ daddr.sin_family = AF_INET;
+ daddr.sin_addr.s_addr = inner_iph->daddr;
+ daddr.sin_port = IPPROTO_GRE;
+
+ ia.s_addr = iph->saddr;
+ LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", inet_ntoa(ia));
+
+ /* why does it reduce the gre_payload_len by the ipv4 header? */
+ return sendto(priv->fd.fd, inner_greh,
+ gre_payload_len - inner_iph->ihl*4, 0,
+ (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
+ struct osmo_sockaddr *saddr, uint16_t *dlci,
+ const struct gprs_ns2_vc_bind *bind)
+{
+ struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
+ int ret = 0;
+ socklen_t saddr_len = sizeof(*saddr);
+ struct iphdr *iph = NULL;
+ struct ip6_hdr *ip6h = NULL;
+ size_t ip46hdr;
+ struct gre_hdr *greh;
+ uint8_t *frh;
+
+ if (!msg) {
+ *error = -ENOMEM;
+ return NULL;
+ }
+
+ ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
+ &saddr->u.sa, &saddr_len);
+ if (ret < 0) {
+ LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", strerror(errno));
+ *error = ret;
+ goto out_err;
+ } else if (ret == 0) {
+ *error = ret;
+ goto out_err;
+ }
+
+ msgb_put(msg, ret);
+
+ /* we've received a raw packet including the IPv4 or IPv6 header */
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ ip46hdr = sizeof(struct iphdr);
+ break;
+ case AF_INET6:
+ ip46hdr = sizeof(struct ip6_hdr);
+ break;
+ default:
+ *error = -EIO;
+ goto out_err;
+ break;
+ }
+
+ /* TODO: add support for the extension headers */
+ if (msg->len < ip46hdr + sizeof(*greh) + 2) {
+ LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ *error = -EIO;
+ goto out_err;
+ }
+
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ iph = (struct iphdr *) msg->data;
+ if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) {
+ LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ *error = -EIO;
+ goto out_err;
+ }
+ break;
+ case AF_INET6:
+ ip6h = (struct ip6_hdr *) msg->data;
+ break;
+ }
+
+ if (iph)
+ greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ else
+ greh = (struct gre_hdr *) (msg->data + sizeof(struct ip6_hdr));
+
+ if (greh->flags) {
+ LOGBIND(bind, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n", osmo_ntohs(greh->flags));
+ }
+
+ switch (osmo_ntohs(greh->ptype)) {
+ case GRE_PTYPE_IPv4:
+ /* IPv4 messages might be GRE keepalives */
+ if (iph)
+ *error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+ else
+ *error = -EIO;
+ goto out_err;
+ break;
+ case GRE_PTYPE_IPv6:
+ if (ip6h)
+ *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ else
+ *error = -EIO;
+ goto out_err;
+ break;
+ case GRE_PTYPE_FR:
+ /* continue as usual */
+ break;
+ default:
+ LOGBIND(bind, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n", osmo_ntohs(greh->ptype));
+ *error = -EIO;
+ goto out_err;
+ break;
+ }
+
+ if (msg->len < sizeof(*greh) + 2) {
+ LOGBIND(bind, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
+ *error = -EIO;
+ goto out_err;
+ }
+
+ frh = (uint8_t *)greh + sizeof(*greh);
+ if (frh[0] & 0x01) {
+ LOGBIND(bind, LOGL_NOTICE, "Unsupported single-byte FR address\n");
+ *error = -EIO;
+ goto out_err;
+ }
+ *dlci = ((frh[0] & 0xfc) << 2);
+ if ((frh[1] & 0x0f) != 0x01) {
+ LOGBIND(bind, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n", frh[1]);
+ *error = -EIO;
+ goto out_err;
+ }
+ *dlci |= (frh[1] >> 4);
+
+ msg->l2h = frh+2;
+
+ return msg;
+
+out_err:
+ msgb_free(msg);
+ return NULL;
+}
+
+static int ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ if (!result)
+ return -EINVAL;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->dlci != dlci) {
+ *result = nsvc;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int handle_nsfrgre_read(struct osmo_fd *bfd)
+{
+ int rc;
+ struct osmo_sockaddr saddr;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct msgb *msg;
+ struct msgb *reject;
+ uint16_t dlci;
+
+ msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci, bind);
+ if (!msg)
+ return rc;
+
+ if (dlci == 0 || dlci == 1023) {
+ LOGBIND(bind, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n", dlci);
+ rc = 0;
+ goto out;
+ }
+
+ rc = ns2_find_vc_by_dlci(bind, dlci, &nsvc);
+ if (rc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, &saddr, "newconnection", &reject, &nsvc);
+ switch (rc) {
+ case NS2_CS_FOUND:
+ break;
+ case NS2_CS_ERROR:
+ case NS2_CS_SKIPPED:
+ rc = 0;
+ goto out;
+ case NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ rc = frgre_sendmsg(bind, reject, &saddr);
+ goto out;
+ case NS2_CS_CREATED:
+ frgre_alloc_vc(bind, nsvc, &saddr, dlci);
+ ns2_vc_fsm_start(nsvc);
+ break;
+ }
+ }
+
+ rc = ns2_recv_vc(nsvc, msg);
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_nsfrgre_write(struct osmo_fd *bfd)
+{
+ /* FIXME: actually send the data here instead of nsip_sendmsg() */
+ return -EIO;
+}
+
+static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ struct osmo_sockaddr *dest)
+{
+ int rc;
+ struct priv_bind *priv = bind->priv;
+
+ rc = sendto(priv->fd.fd, msg->data, msg->len, 0,
+ &dest->u.sa, sizeof(*dest));
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct priv_vc *vcpriv = nsvc->priv;
+ struct priv_bind *bindpriv = bind->priv;
+
+ uint16_t dlci = osmo_htons(bindpriv->dlci);
+ uint8_t *frh;
+ struct gre_hdr *greh;
+ unsigned int vc_len = msgb_length(msg);
+ int rc;
+
+ /* Prepend the FR header */
+ frh = msgb_push(msg, 2);
+ frh[0] = (dlci >> 2) & 0xfc;
+ frh[1] = ((dlci & 0xf)<<4) | 0x01;
+
+ /* Prepend the GRE header */
+ greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh));
+ greh->flags = 0;
+ greh->ptype = osmo_htons(GRE_PTYPE_FR);
+
+ rc = frgre_sendmsg(bind, msg, &vcpriv->remote);
+ if (OSMO_LIKELY(rc >= 0)) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, rc);
+ } else {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len);
+ }
+ return rc;
+}
+
+static int frgre_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ int rc = 0;
+
+ if (what & OSMO_FD_READ)
+ rc = handle_nsfrgre_read(bfd);
+ if (what & OSMO_FD_WRITE)
+ rc = handle_nsfrgre_write(bfd);
+
+ return rc;
+}
+
+/*! determine if given bind is for FR-GRE encapsulation. */
+int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_frgre);
+}
+
+/*! Create a new bind for NS over FR-GRE.
+ * \param[in] nsi NS instance in which to create the bind
+ * \param[in] local local address on which to bind
+ * \param[in] dscp DSCP/TOS bits to use for transmitted data on this bind
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
+int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
+ const struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct priv_bind *priv;
+ int rc;
+
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6)
+ return -EINVAL;
+
+ if (dscp < 0 || dscp > 63)
+ return -EINVAL;
+
+ bind = gprs_ns2_bind_by_name(nsi, name);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EALREADY;
+ }
+
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
+ bind->driver = &vc_driver_frgre;
+ bind->ll = GPRS_NS2_LL_FR_GRE;
+ /* 2 mbit transfer capability. Counting should be done different for this. */
+ bind->transfer_capability = 2;
+ 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) {
+ gprs_ns2_free_bind(bind);
+ return -ENOMEM;
+ }
+ priv->fd.cb = frgre_fd_cb;
+ priv->fd.data = bind;
+ priv->addr = *local;
+ INIT_LLIST_HEAD(&bind->nsvc);
+ priv->dscp = dscp;
+
+ rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_RAW, IPPROTO_GRE,
+ local, NULL,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp));
+ if (rc < 0) {
+ gprs_ns2_free_bind(bind);
+ return rc;
+ }
+
+ if (result)
+ *result = bind;
+
+ return rc;
+}
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
new file mode 100644
index 00000000..2e7dac3f
--- /dev/null
+++ b/src/gb/gprs_ns2_internal.h
@@ -0,0 +1,503 @@
+/*! \file gprs_ns2_internal.h */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#define LOGNSE(nse, lvl, fmt, args ...) \
+ LOGP(DLNS, lvl, "NSE(%05u) " fmt, (nse)->nsei, ## args)
+
+#define LOGBIND(bind, lvl, fmt, args ...) \
+ LOGP(DLNS, lvl, "BIND(%s) " fmt, (bind)->name, ## args)
+
+#define LOGNSVC_SS(ss, nsvc, lvl, fmt, args ...) \
+ do { \
+ if ((nsvc)->nsvci_is_valid) { \
+ LOGP(ss, lvl, "NSE(%05u)-NSVC(%05u) " fmt, \
+ (nsvc)->nse->nsei, (nsvc)->nsvci, ## args); \
+ } else { \
+ LOGP(ss, lvl, "NSE(%05u)-NSVC(none) " fmt, \
+ (nsvc)->nse->nsei, ## args); \
+ } \
+ } while (0)
+
+#define LOGNSVC(nsvc, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNS, nsvc, lvl, fmt, ## args)
+
+#define LOG_NS_SIGNAL(nsvc, direction, pdu_type, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNSSIGNAL, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args)
+
+#define LOG_NS_DATA(nsvc, direction, pdu_type, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNSDATA, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args)
+
+#define LOG_NS_RX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Rx", pdu_type, LOGL_INFO, "\n")
+#define LOG_NS_TX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Tx", pdu_type, LOGL_INFO, "\n")
+
+#define RATE_CTR_INC_NS(nsvc, ctr) \
+ do { \
+ struct gprs_ns2_vc *_nsvc = (nsvc); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr)); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr)); \
+ } while (0)
+
+#define RATE_CTR_ADD_NS(nsvc, ctr, val) \
+ do { \
+ struct gprs_ns2_vc *_nsvc = (nsvc); \
+ rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr), val); \
+ rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr), val); \
+ } while (0)
+
+
+struct osmo_fsm_inst;
+struct tlv_parsed;
+struct vty;
+
+struct gprs_ns2_vc_driver;
+struct gprs_ns2_vc_bind;
+
+#define NS_TIMERS_COUNT 11
+
+#define TNS_BLOCK_STR "tns-block"
+#define TNS_BLOCK_RETRIES_STR "tns-block-retries"
+#define TNS_RESET_STR "tns-reset"
+#define TNS_RESET_RETRIES_STR "tns-reset-retries"
+#define TNS_TEST_STR "tns-test"
+#define TNS_ALIVE_STR "tns-alive"
+#define TNS_ALIVE_RETRIES_STR "tns-alive-retries"
+#define TSNS_PROV_STR "tsns-prov"
+#define TSNS_SIZE_RETRIES_STR "tsns-size-retries"
+#define TSNS_CONFIG_RETRIES_STR "tsns-config-retries"
+#define TSNS_PROCEDURES_RETRIES_STR "tsns-procedures-retries"
+#define NS_TIMERS "(" TNS_BLOCK_STR "|" TNS_BLOCK_RETRIES_STR "|" TNS_RESET_STR "|" TNS_RESET_RETRIES_STR "|" TNS_TEST_STR "|"\
+ TNS_ALIVE_STR "|" TNS_ALIVE_RETRIES_STR "|" TSNS_PROV_STR "|" TSNS_SIZE_RETRIES_STR "|" TSNS_CONFIG_RETRIES_STR "|"\
+ TSNS_PROCEDURES_RETRIES_STR ")"
+
+#define NS_TIMERS_HELP \
+ "(un)blocking Timer (Tns-block) timeout\n" \
+ "(un)blocking Timer (Tns-block) number of retries\n" \
+ "Reset Timer (Tns-reset) timeout\n" \
+ "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" \
+ "SNS Provision Timer (Tsns-prov) timeout\n" \
+ "SNS Size number of retries\n" \
+ "SNS Config number of retries\n" \
+ "SNS Procedures number of retries\n" \
+
+/* Educated guess - LLC user payload is 1500 bytes plus possible headers */
+#define NS_ALLOC_SIZE 3072
+#define NS_ALLOC_HEADROOM 20
+
+#define NS_DEFAULT_TXQUEUE_MAX_LENGTH 128
+
+enum ns2_timeout {
+ NS_TOUT_TNS_BLOCK,
+ NS_TOUT_TNS_BLOCK_RETRIES,
+ NS_TOUT_TNS_RESET,
+ NS_TOUT_TNS_RESET_RETRIES,
+ NS_TOUT_TNS_TEST,
+ NS_TOUT_TNS_ALIVE,
+ NS_TOUT_TNS_ALIVE_RETRIES,
+ NS_TOUT_TSNS_PROV,
+ NS_TOUT_TSNS_SIZE_RETRIES,
+ NS_TOUT_TSNS_CONFIG_RETRIES,
+ NS_TOUT_TSNS_PROCEDURES_RETRIES,
+};
+
+enum nsvc_timer_mode {
+ /* standard timers */
+ NSVC_TIMER_TNS_TEST,
+ NSVC_TIMER_TNS_ALIVE,
+ NSVC_TIMER_TNS_RESET,
+ _NSVC_TIMER_NR,
+};
+
+enum ns2_vc_stat {
+ NS_STAT_ALIVE_DELAY,
+};
+
+enum ns2_bind_stat {
+ NS2_BIND_STAT_BACKLOG_LEN,
+};
+
+/*! Osmocom NS2 VC create status */
+enum ns2_cs {
+ NS2_CS_CREATED, /*!< A NSVC object has been created */
+ NS2_CS_FOUND, /*!< A NSVC object has been found */
+ NS2_CS_REJECTED, /*!< Rejected and answered message */
+ NS2_CS_SKIPPED, /*!< Skipped message */
+ NS2_CS_ERROR, /*!< Failed to process message */
+};
+
+enum ns_ctr {
+ NS_CTR_PKTS_IN,
+ NS_CTR_PKTS_OUT,
+ NS_CTR_PKTS_OUT_DROP,
+ NS_CTR_BYTES_IN,
+ NS_CTR_BYTES_OUT,
+ NS_CTR_BYTES_OUT_DROP,
+ NS_CTR_BLOCKED,
+ NS_CTR_UNBLOCKED,
+ NS_CTR_DEAD,
+ NS_CTR_REPLACED,
+ NS_CTR_NSEI_CHG,
+ NS_CTR_INV_VCI,
+ NS_CTR_INV_NSEI,
+ NS_CTR_LOST_ALIVE,
+ NS_CTR_LOST_RESET,
+};
+
+#define NSE_S_BLOCKED 0x0001
+#define NSE_S_ALIVE 0x0002
+#define NSE_S_RESET 0x0004
+
+#define NS_DESC_B(st) ((st) & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED")
+#define NS_DESC_A(st) ((st) & NSE_S_ALIVE ? "ALIVE" : "DEAD")
+#define NS_DESC_R(st) ((st) & NSE_S_RESET ? "RESET" : "UNRESET")
+
+/*! An instance of the NS protocol stack */
+struct gprs_ns2_inst {
+ /*! callback to the user for incoming UNIT DATA IND */
+ osmo_prim_cb cb;
+
+ /*! callback data */
+ void *cb_data;
+
+ /*! linked lists of all NSVC binds (e.g. IPv4 bind, but could be also E1 */
+ struct llist_head binding;
+
+ /*! linked lists of all NSVC in this instance */
+ struct llist_head nse;
+
+ uint16_t timeout[NS_TIMERS_COUNT];
+
+ /*! workaround for rate counter until rate counter accepts char str as index */
+ uint32_t nsvc_rate_ctr_idx;
+ uint32_t bind_rate_ctr_idx;
+
+ uint32_t txqueue_max_length;
+};
+
+
+/*! Structure repesenting a NSE. The BSS/PCU will only have a single NSE, while SGSN has one for each BSS/PCU */
+struct gprs_ns2_nse {
+ uint16_t nsei;
+
+ /*! entry back to ns2_inst */
+ struct gprs_ns2_inst *nsi;
+
+ /*! llist entry for gprs_ns2_inst */
+ struct llist_head list;
+
+ /*! llist head to hold all nsvc */
+ struct llist_head nsvc;
+
+ /*! count all active NSVCs */
+ int nsvc_count;
+
+ /*! true if this NSE was created by VTY or pcu socket) */
+ bool persistent;
+
+ /*! true if this NSE wasn't yet alive at all.
+ * Will be true after the first status ind with NS_AFF_CAUSE_RECOVERY */
+ bool first;
+
+ /*! true if this NSE has at least one alive VC */
+ bool alive;
+
+ /*! which link-layer are we based on? */
+ enum gprs_ns2_ll ll;
+
+ /*! which dialect does this NSE speaks? */
+ enum gprs_ns2_dialect dialect;
+
+ struct osmo_fsm_inst *bss_sns_fi;
+
+ /*! sum of all the data weight of _alive_ NS-VCs */
+ uint32_t sum_data_weight;
+
+ /*! 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;
+
+ /*! are we implementing the SGSN role? */
+ bool ip_sns_role_sgsn;
+
+ /*! NSE-wide statistics */
+ struct rate_ctr_group *ctrg;
+
+ /*! recursive anchor */
+ bool freed;
+
+ /*! when the NSE became alive or dead */
+ struct timespec ts_alive_change;
+};
+
+/*! Structure representing a single NS-VC */
+struct gprs_ns2_vc {
+ /*! list of NS-VCs within NSE */
+ struct llist_head list;
+
+ /*! list of NS-VCs within bind, bind is the owner! */
+ struct llist_head blist;
+
+ /*! pointer to NS Instance */
+ struct gprs_ns2_nse *nse;
+
+ /*! pointer to NS VL bind. bind own the memory of this instance */
+ struct gprs_ns2_vc_bind *bind;
+
+ /*! true if this NS was created by VTY or pcu socket) */
+ bool persistent;
+
+ /*! uniquely identifies NS-VC if VC contains nsvci */
+ uint16_t nsvci;
+
+ /*! signalling weight. 0 = don't use for signalling (BVCI == 0)*/
+ uint8_t sig_weight;
+
+ /*! signalling packet counter for the load sharing function */
+ uint8_t sig_counter;
+
+ /*! data weight. 0 = don't use for user data (BVCI != 0) */
+ uint8_t data_weight;
+
+ /*! can be used by the bind/driver of the virtual circuit. e.g. ipv4/ipv6/frgre/e1 */
+ void *priv;
+
+ bool nsvci_is_valid;
+ /*! should this NS-VC only be used for SNS-SIZE and SNS-CONFIG? */
+ bool sns_only;
+
+ struct rate_ctr_group *ctrg;
+ struct osmo_stat_item_group *statg;
+
+ enum gprs_ns2_vc_mode mode;
+
+ struct osmo_fsm_inst *fi;
+
+ /*! recursive anchor */
+ bool freed;
+
+ /*! if blocked by O&M/vty */
+ bool om_blocked;
+
+ /*! when the NSVC became alive or dead */
+ struct timespec ts_alive_change;
+};
+
+/*! Structure repesenting a bind instance. E.g. IPv4 listen port. */
+struct gprs_ns2_vc_bind {
+ /*! unique name */
+ const char *name;
+ /*! list entry in nsi */
+ struct llist_head list;
+ /*! list of all VC */
+ struct llist_head nsvc;
+ /*! driver private structure */
+ void *priv;
+ /*! a pointer back to the nsi */
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_driver *driver;
+
+ bool accept_ipaccess;
+ bool accept_sns;
+
+ /*! 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;
+
+ /*! send a msg over a VC */
+ int (*send_vc)(struct gprs_ns2_vc *nsvc, struct msgb *msg);
+
+ /*! free the vc priv data */
+ void (*free_vc)(struct gprs_ns2_vc *nsvc);
+
+ /*! allow to show information for the vty */
+ void (*dump_vty)(const struct gprs_ns2_vc_bind *bind,
+ struct vty *vty, bool stats);
+
+ /*! the IP-SNS signalling weight when doing dynamic configuration */
+ uint8_t sns_sig_weight;
+ /*! the IP-SNS data weight when doing dynamic configuration */
+ uint8_t sns_data_weight;
+
+ struct osmo_stat_item_group *statg;
+
+ /*! recursive anchor */
+ bool freed;
+};
+
+struct gprs_ns2_vc_driver {
+ const char *name;
+ void *priv;
+ void (*free_bind)(struct gprs_ns2_vc_bind *driver);
+};
+
+enum ns2_sns_event {
+ NS2_SNS_EV_REQ_SELECT_ENDPOINT, /*!< Select a SNS endpoint from the list */
+ NS2_SNS_EV_RX_SIZE,
+ NS2_SNS_EV_RX_SIZE_ACK,
+ NS2_SNS_EV_RX_CONFIG,
+ NS2_SNS_EV_RX_CONFIG_END, /*!< SNS-CONFIG with end flag received */
+ NS2_SNS_EV_RX_CONFIG_ACK,
+ NS2_SNS_EV_RX_ADD,
+ NS2_SNS_EV_RX_DELETE,
+ NS2_SNS_EV_RX_CHANGE_WEIGHT,
+ NS2_SNS_EV_RX_ACK, /*!< Rx of SNS-ACK (response to ADD/DELETE/CHG_WEIGHT */
+ NS2_SNS_EV_REQ_NO_NSVC, /*!< no more NS-VC remaining (all dead) */
+ NS2_SNS_EV_REQ_FREE_NSVCS, /*!< free all NS-VCs */
+ NS2_SNS_EV_REQ_NSVC_ALIVE, /*!< a NS-VC became alive */
+ NS2_SNS_EV_REQ_ADD_BIND, /*!< add a new local bind to this NSE */
+ NS2_SNS_EV_REQ_DELETE_BIND, /*!< remove a local bind from this NSE */
+ NS2_SNS_EV_REQ_CHANGE_WEIGHT, /*!< a bind changed its weight */
+};
+
+enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const struct osmo_sockaddr *remote,
+ const char *logname,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success);
+
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
+ struct msgb *msg);
+
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ bool initiater,
+ enum gprs_ns2_vc_mode vc_mode,
+ const char *id);
+
+void ns2_free_nsvcs(struct gprs_ns2_nse *nse);
+int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name,
+ struct gprs_ns2_vc_bind **result);
+
+struct msgb *ns2_msgb_alloc(void);
+
+void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse);
+void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats);
+void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc *nsvc,
+ 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);
+int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect);
+
+/* message */
+int ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause);
+
+/* SNS messages */
+int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
+ int ip4_ep_nr, int ip6_ep_nr);
+int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+
+int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+
+/* transmit message over a VC */
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci);
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci);
+
+int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause);
+int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_unblock(struct gprs_ns2_vc *nsvc);
+int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_alive(struct gprs_ns2_vc *nsvc);
+int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
+ uint16_t bvci, uint8_t sducontrol,
+ struct msgb *msg);
+
+int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
+ uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci);
+
+/* driver */
+struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *remote);
+int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote);
+struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *remote,
+ int index);
+void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length);
+
+/* sns */
+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);
+struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id);
+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);
+void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind);
+
+/* vc */
+struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+ const char *id, bool initiate);
+int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc);
+int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc);
+int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+int ns2_vc_is_alive(struct gprs_ns2_vc *nsvc);
+int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc);
+int ns2_vc_block(struct gprs_ns2_vc *nsvc);
+int ns2_vc_reset(struct gprs_ns2_vc *nsvc);
+int ns2_vc_unblock(struct gprs_ns2_vc *nsvc);
+void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats);
+
+/* nse */
+void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked);
+enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect);
+int ns2_count_transfer_cap(struct gprs_ns2_nse *nse,
+ uint16_t bvci);
+
+/* vty */
+int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse);
diff --git a/src/gb/gprs_ns2_message.c b/src/gb/gprs_ns2_message.c
new file mode 100644
index 00000000..de63b7aa
--- /dev/null
+++ b/src/gb/gprs_ns2_message.c
@@ -0,0 +1,848 @@
+/*! \file gprs_ns2_message.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define ERR_IF_NSVC_USES_SNS(nsvc, reason) \
+ do { \
+ if (!nsvc->nse->bss_sns_fi) \
+ break; \
+ LOGNSVC(nsvc, LOGL_DEBUG, "invalid packet %s with SNS\n", reason); \
+ } while (0)
+
+static int ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1) ||
+ !TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+
+ if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ uint8_t _cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ switch (_cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ case NS_CAUSE_NSVC_UNKNOWN:
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 1)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ if (nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) {
+ *cause = NS_CAUSE_PDU_INCOMP_PSTATE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_SEM_INCORR_PDU:
+ case NS_CAUSE_PDU_INCOMP_PSTATE:
+ case NS_CAUSE_PROTO_ERR_UNSPEC:
+ case NS_CAUSE_INVAL_ESSENT_IE:
+ case NS_CAUSE_MISSING_ESSENT_IE:
+ if (!TLVP_PRES_LEN(tp, NS_IE_PDU, 1)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_BVCI_UNKNOWN:
+ if (!TLVP_PRES_LEN(tp, NS_IE_BVCI, 2)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_UNKN_IP_TEST_FAILED:
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause)
+{
+ switch (pdu_type) {
+ case NS_PDUT_RESET:
+ return ns2_validate_reset(nsvc, msg, tp, cause);
+ case NS_PDUT_RESET_ACK:
+ return ns2_validate_reset_ack(nsvc, msg, tp, cause);
+ case NS_PDUT_BLOCK:
+ return ns2_validate_block(nsvc, msg, tp, cause);
+ case NS_PDUT_BLOCK_ACK:
+ return ns2_validate_block_ack(nsvc, msg, tp, cause);
+ case NS_PDUT_STATUS:
+ return ns2_validate_status(nsvc, msg, tp, cause);
+
+ /* following PDUs doesn't have any payloads */
+ case NS_PDUT_ALIVE:
+ case NS_PDUT_ALIVE_ACK:
+ case NS_PDUT_UNBLOCK:
+ case NS_PDUT_UNBLOCK_ACK:
+ if (msgb_l2len(msg) != sizeof(struct gprs_ns_hdr)) {
+ *cause = NS_CAUSE_PROTO_ERR_UNSPEC;
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int ns_vc_tx(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/* transmit functions */
+static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
+{
+ struct msgb *msg = ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = pdu_type;
+
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Transmit a NS-BLOCK on a given NS-VC.
+ * \param[in] vc NS-VC on which the NS-BLOCK is to be transmitted
+ * \param[in] cause Numeric NS Cause value
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
+ * \returns 0 in case of success */
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t encoded_nsvci;
+
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
+
+ msg = ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_BLOCK;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci);
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Transmit a NS-BLOCK-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
+ * \returns 0 in case of success */
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t encoded_nsvci;
+
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
+
+ msg = ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_BLOCK_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci);
+
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Transmit a NS-RESET on a given NS-VC.
+ * \param[in] nsvc NS-VC used for transmission
+ * \param[in] cause Numeric NS cause value
+ * \returns 0 in case of success */
+int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t nsei = osmo_htons(nsvc->nse->nsei);
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
+
+ msg = ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_RESET;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Transmit a NS-RESET-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC used for transmission
+ * \returns 0 in case of success */
+int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci, nsei;
+
+ /* Section 9.2.6 */
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
+
+ msg = ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ nsvci = osmo_htons(nsvc->nsvci);
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = NS_PDUT_RESET_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Transmit a NS-UNBLOCK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNBLOCK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK");
+
+ return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK);
+}
+
+
+/*! Transmit a NS-UNBLOCK-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNBLOCK-ACK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK ACK");
+
+ return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
+}
+
+/*! Transmit a NS-ALIVE on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-ALIVE is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ return ns2_tx_simple(nsvc, NS_PDUT_ALIVE);
+}
+
+/*! Transmit a NS-ALIVE-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-ALIVE-ACK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ return ns2_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
+}
+
+/*! Transmit NS-UNITDATA on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNITDATA is to be transmitted
+ * \param[in] bvci BVCI to encode in NS-UNITDATA header
+ * \param[in] sducontrol SDU control octet of NS header
+ * \param[in] msg message buffer containing payload
+ * \returns 0 in case of success */
+int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
+ uint16_t bvci, uint8_t sducontrol,
+ struct msgb *msg)
+{
+ struct gprs_ns_hdr *nsh;
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ msg->l2h = msgb_push(msg, sizeof(*nsh) + 3);
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ if (!nsh) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Not enough headroom for NS header\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsh->pdu_type = NS_PDUT_UNITDATA;
+ nsh->data[0] = sducontrol;
+ nsh->data[1] = bvci >> 8;
+ nsh->data[2] = bvci & 0xff;
+
+ LOG_NS_DATA(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, "\n");
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Transmit a NS-STATUS on a given NS-VC.
+ * \param[in] nsvc NS-VC to be used for transmission
+ * \param[in] cause Numeric NS cause value
+ * \param[in] bvci BVCI to be reset within NSVC
+ * \param[in] orig_msg message causing the STATUS
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
+ * \returns 0 in case of success */
+int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
+ uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci)
+{
+ struct msgb *msg = ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t encoded_nsvci;
+ unsigned int orig_len, max_orig_len;
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_STATUS;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+
+ switch (cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ case NS_CAUSE_NSVC_UNKNOWN:
+ /* Section 9.2.7.1: Static conditions for NS-VCI */
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&encoded_nsvci);
+ break;
+ case NS_CAUSE_SEM_INCORR_PDU:
+ case NS_CAUSE_PDU_INCOMP_PSTATE:
+ case NS_CAUSE_PROTO_ERR_UNSPEC:
+ case NS_CAUSE_INVAL_ESSENT_IE:
+ case NS_CAUSE_MISSING_ESSENT_IE:
+ /* Section 9.2.7.2: Static conditions for NS PDU */
+ /* ensure the PDU doesn't exceed the MTU */
+ orig_len = msgb_l2len(orig_msg);
+ max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
+ if (max_orig_len > nsvc->bind->mtu)
+ orig_len -= max_orig_len - nsvc->bind->mtu;
+ msgb_tvlv_put(msg, NS_IE_PDU, orig_len, orig_msg->l2h);
+ break;
+ case NS_CAUSE_BVCI_UNKNOWN:
+ /* Section 9.2.7.3: Static conditions for BVCI */
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+ break;
+
+ default:
+ break;
+ }
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-ADD/SNS-CHANGE-WEIGHT as per Section 9.3.2/9.3.3.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] pdu The PDU type to send out
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+static int ns2_tx_sns_procedure(struct gprs_ns2_vc *nsvc,
+ enum ns_pdu_type pdu,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -EINVAL;
+
+ if (!ip4_elems && !ip6_elems)
+ return -EINVAL;
+
+ msg = ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = pdu;
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_v_put(msg, trans_id);
+
+ /* List of IP4 Elements 10.3.2c */
+ if (ip4_elems) {
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ } else if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-ADD as per Section 9.3.2.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_ADD, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+/*! Encode + Transmit a SNS-CHANGE-WEIGHT as per Section 9.3.3.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_CHANGE_WEIGHT, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+/*! Encode + Transmit a SNS-DEL as per Section 9.3.6.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ /* TODO: IP Address field */
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_DELETE, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+
+/*! Encode + Transmit a SNS-ACK as per Section 9.3.1.
+ * \param[in] nsvc NS-VC through which to transmit the ACK
+ * \param[in] trans_id Transaction ID which to acknowledge
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_ACK;
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_v_put(msg, trans_id);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+ if (ip4_elems) {
+ /* List of IP4 Elements 10.3.2c */
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST,
+ num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ }
+ if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST,
+ num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (trans_id=%u, cause=%s, num_ip4=%u, num_ip6=%u)\n",
+ trans_id, cause ? gprs_ns2_cause_str(*cause) : "NULL", num_ip4_elems, num_ip6_elems);
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-CONFIG as per Section 9.3.4.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] end_flag Whether or not this is the last SNS-CONFIG
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_CONFIG;
+
+ msgb_v_put(msg, end_flag ? 0x01 : 0x00);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+ /* List of IP4 Elements 10.3.2c */
+ if (ip4_elems) {
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ } else if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (end_flag=%u, num_ip4=%u, num_ip6=%u)\n",
+ end_flag, num_ip4_elems, num_ip6_elems);
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-CONFIG-ACK as per Section 9.3.5.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG-ACK
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_CONFIG_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+
+ LOGNSVC(nsvc, LOGL_INFO, "Tx SNS-CONFIG-ACK (cause=%s)\n",
+ cause ? gprs_ns2_cause_str(*cause) : "NULL");
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
+}
+
+
+/*! Encode + transmit a SNS-SIZE as per Section 9.3.7.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE
+ * \param[in] reset_flag Whether or not to add a RESET flag
+ * \param[in] max_nr_nsvc Maximum number of NS-VCs
+ * \param[in] ip4_ep_nr Number of IPv4 endpoints (< 0 will omit the TLV)
+ * \param[in] ip6_ep_nr Number of IPv6 endpoints (< 0 will omit the TLV)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
+ int ip4_ep_nr, int ip6_ep_nr)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_SIZE;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_tv_put(msg, NS_IE_RESET_FLAG, reset_flag ? 0x01 : 0x00);
+ msgb_tv16_put(msg, NS_IE_MAX_NR_NSVC, max_nr_nsvc);
+ if (ip4_ep_nr >= 0)
+ msgb_tv16_put(msg, NS_IE_IPv4_EP_NR, ip4_ep_nr);
+ if (ip6_ep_nr >= 0)
+ msgb_tv16_put(msg, NS_IE_IPv6_EP_NR, ip6_ep_nr);
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (reset=%u, max_nr_nsvc=%u, num_ip4=%d, num_ip6=%d)\n",
+ reset_flag, max_nr_nsvc, ip4_ep_nr, ip6_ep_nr);
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-SIZE-ACK as per Section 9.3.8.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE-ACK
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
+{
+ struct msgb *msg = ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_SIZE_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n",
+ cause ? gprs_ns2_cause_str(*cause) : "NULL");
+ return ns_vc_tx(nsvc, msg);
+}
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
new file mode 100644
index 00000000..0afc06ed
--- /dev/null
+++ b/src/gb/gprs_ns2_sns.c
@@ -0,0 +1,3106 @@
+/*! \file gprs_ns2_sns.c
+ * NS Sub-Network Service Protocol implementation
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2018-2021 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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. The BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports.
+ *
+ * Known limitation/expectation/bugs:
+ * - No concurrent dual stack. It supports either IPv4 or IPv6, but not both at the same time.
+ * - SNS Add/Change/Delete: Doesn't answer on the same NSVC as received SNS ADD/CHANGE/DELETE PDUs.
+ * - SNS Add/Change/Delete: Doesn't communicated the failed IPv4/IPv6 entries on the SNS_ACK.
+ */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define S(x) (1 << (x))
+
+enum ns2_sns_role {
+ GPRS_SNS_ROLE_BSS,
+ GPRS_SNS_ROLE_SGSN,
+};
+
+/* BSS-side-only states _ST_BSS_; SGSN-side only states _ST_SGSN_; others shared */
+enum gprs_sns_bss_state {
+ GPRS_SNS_ST_UNCONFIGURED,
+ GPRS_SNS_ST_BSS_SIZE, /*!< SNS-SIZE procedure ongoing */
+ GPRS_SNS_ST_BSS_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
+ GPRS_SNS_ST_BSS_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
+ GPRS_SNS_ST_CONFIGURED,
+ GPRS_SNS_ST_SGSN_WAIT_CONFIG, /* !< SGSN role: Wait for CONFIG from BSS */
+ GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, /* !< SGSN role: Wait for CONFIG-ACK from BSS */
+ GPRS_SNS_ST_LOCAL_PROCEDURE, /*!< in process of a ADD/DEL/CHANGE procedure towards SGSN (BSS->SGSN) */
+};
+
+static const struct value_string gprs_sns_event_names[] = {
+ { NS2_SNS_EV_REQ_SELECT_ENDPOINT, "REQ_SELECT_ENDPOINT" },
+ { NS2_SNS_EV_RX_SIZE, "RX_SIZE" },
+ { NS2_SNS_EV_RX_SIZE_ACK, "RX_SIZE_ACK" },
+ { NS2_SNS_EV_RX_CONFIG, "RX_CONFIG" },
+ { NS2_SNS_EV_RX_CONFIG_END, "RX_CONFIG_END" },
+ { NS2_SNS_EV_RX_CONFIG_ACK, "RX_CONFIG_ACK" },
+ { NS2_SNS_EV_RX_ADD, "RX_ADD" },
+ { NS2_SNS_EV_RX_DELETE, "RX_DELETE" },
+ { NS2_SNS_EV_RX_ACK, "RX_ACK" },
+ { NS2_SNS_EV_RX_CHANGE_WEIGHT, "RX_CHANGE_WEIGHT" },
+ { NS2_SNS_EV_REQ_NO_NSVC, "REQ_NO_NSVC" },
+ { NS2_SNS_EV_REQ_FREE_NSVCS, "REQ_FREE_NSVCS" },
+ { NS2_SNS_EV_REQ_NSVC_ALIVE, "REQ_NSVC_ALIVE"},
+ { NS2_SNS_EV_REQ_ADD_BIND, "REQ_ADD_BIND"},
+ { NS2_SNS_EV_REQ_DELETE_BIND, "REQ_DELETE_BIND"},
+ { NS2_SNS_EV_REQ_CHANGE_WEIGHT, "REQ_CHANGE_WEIGHT"},
+ { 0, NULL }
+};
+
+#define GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER (void *) 1
+
+enum sns_procedure {
+ SNS_PROC_NONE, /*!< used as invalid/idle value */
+ SNS_PROC_ADD,
+ SNS_PROC_DEL,
+ SNS_PROC_CHANGE_WEIGHT,
+};
+
+struct sns_endpoint {
+ struct llist_head list;
+ struct osmo_sockaddr saddr;
+};
+
+struct ns2_sns_bind {
+ struct llist_head list;
+ struct gprs_ns2_vc_bind *bind;
+ uint8_t change_weight_state;
+};
+
+struct ns2_sns_procedure {
+ struct llist_head list;
+ struct ns2_sns_bind *sbind;
+ uint16_t sig_weight;
+ uint16_t data_weight;
+ /* copy entry to protect against changes of gss->local */
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ enum sns_procedure procedure;
+ uint8_t trans_id;
+ /* is the procedure in process */
+ bool running;
+};
+
+struct ns2_sns_elems {
+ struct gprs_ns_ie_ip4_elem *ip4;
+ unsigned int num_ip4;
+ struct gprs_ns_ie_ip6_elem *ip6;
+ unsigned int num_ip6;
+};
+
+struct ns2_sns_state {
+ struct gprs_ns2_nse *nse;
+
+ /* containing the address family AF_* */
+ int family;
+ enum ns2_sns_role role; /* local role: BSS or SGSN */
+
+ /* holds the list of initial SNS endpoints */
+ struct llist_head sns_endpoints;
+ /* list of used struct ns2_sns_bind */
+ struct llist_head binds;
+ /* pointer to the bind which was used to initiate the SNS connection */
+ struct ns2_sns_bind *initial_bind;
+ /* prevent recursive reselection */
+ bool reselection_running;
+
+ /* protection against recursive free() */
+ bool block_no_nsvc_events;
+
+ /* The current initial SNS endpoints.
+ * The initial connection will be moved into the NSE
+ * if configured via SNS. Otherwise it will be removed
+ * in configured state. */
+ struct sns_endpoint *initial;
+ /* all SNS PDU will be sent over this nsvc */
+ struct gprs_ns2_vc *sns_nsvc;
+ /* timer N */
+ int N;
+ /* true if at least one nsvc is alive */
+ bool alive;
+
+ /* local configuration to send to the remote end */
+ struct ns2_sns_elems local;
+
+ /* local configuration after all local procedures applied */
+ struct ns2_sns_elems local_procedure;
+
+ /* remote configuration as received */
+ struct ns2_sns_elems remote;
+
+ /* local configuration about our capabilities in terms of connections to
+ * remote (SGSN) side */
+ size_t num_max_nsvcs;
+ size_t num_max_ip4_remote;
+ size_t num_max_ip6_remote;
+
+ struct llist_head procedures;
+ struct ns2_sns_procedure *current_procedure;
+ uint8_t trans_id;
+};
+
+static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ return gss->nse;
+}
+
+/* The SNS has failed. Etither restart the SNS (BSS) or remove the SNS (SGSN) */
+#define sns_failed(fi, reason) \
+ _sns_failed(fi, reason, __FILE__, __LINE__)
+static void _sns_failed(struct osmo_fsm_inst *fi, const char *reason, const char *file, int line)
+{
+ struct ns2_sns_state *gss = fi->priv;
+
+ if (reason)
+ LOGPFSMLSRC(fi, LOGL_ERROR, file, line, "NSE %d: SNS failed: %s\n", gss->nse->nsei, reason);
+
+ gss->alive = false;
+ if (gss->role == GPRS_SNS_ROLE_SGSN) {
+ if (!gss->nse->persistent)
+ gprs_ns2_free_nse(gss->nse);
+ else
+ _osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0, file, line);
+ } else {
+ _osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL, file, line);
+ }
+}
+
+/* helper function to compute the sum of all (data or signaling) weights */
+static int ip4_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (data_weight)
+ weight_sum += elems->ip4[i].data_weight;
+ else
+ weight_sum += elems->ip4[i].sig_weight;
+ }
+ return weight_sum;
+}
+#define ip4_weight_sum_data(elems) ip4_weight_sum(elems, true)
+#define ip4_weight_sum_sig(elems) ip4_weight_sum(elems, false)
+
+/* helper function to compute the sum of all (data or signaling) weights */
+static int ip6_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (data_weight)
+ weight_sum += elems->ip6[i].data_weight;
+ else
+ weight_sum += elems->ip6[i].sig_weight;
+ }
+ return weight_sum;
+}
+#define ip6_weight_sum_data(elems) ip6_weight_sum(elems, true)
+#define ip6_weight_sum_sig(elems) ip6_weight_sum(elems, false)
+
+static int ip46_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
+{
+ return ip4_weight_sum(elems, data_weight) +
+ ip6_weight_sum(elems, data_weight);
+}
+#define ip46_weight_sum_data(elems) ip46_weight_sum(elems, true)
+#define ip46_weight_sum_sig(elems) ip46_weight_sum(elems, false)
+
+static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct osmo_sockaddr sa;
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ sa.u.sin.sin_port = ip4->udp_port;
+ sa.u.sin.sin_family = AF_INET;
+
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
+}
+
+static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct osmo_sockaddr sa;
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET;
+
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
+}
+
+/*! Return the initial SNS remote socket address
+ * \param nse NS Entity
+ * \return address of the initial SNS connection; NULL in case of error
+ */
+const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+
+ if (!nse->bss_sns_fi)
+ return NULL;
+
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ return &gss->initial->saddr;
+}
+
+/*! 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 = nsvc->nse;
+ struct gprs_ns2_vc *tmp;
+ struct osmo_fsm_inst *fi = nse->bss_sns_fi;
+ struct ns2_sns_state *gss;
+
+ if (!fi)
+ return;
+
+ gss = (struct ns2_sns_state *) fi->priv;
+ if (nsvc != gss->sns_nsvc)
+ return;
+
+ gss->sns_nsvc = NULL;
+ if (gss->alive) {
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(tmp)) {
+ gss->sns_nsvc = tmp;
+ return;
+ }
+ }
+ } else {
+ /* 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;
+ }
+ }
+ }
+
+ if (gss->block_no_nsvc_events)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_NO_NSVC, NULL);
+}
+
+static void ns2_clear_elems(struct ns2_sns_elems *elems)
+{
+ TALLOC_FREE(elems->ip4);
+ TALLOC_FREE(elems->ip6);
+
+ elems->num_ip4 = 0;
+ elems->num_ip6 = 0;
+}
+
+static void ns2_clear_procedures(struct ns2_sns_state *gss)
+{
+ struct ns2_sns_procedure *procedure, *tmp;
+ gss->current_procedure = NULL;
+ llist_for_each_entry_safe(procedure, tmp, &gss->procedures, list) {
+ llist_del(&procedure->list);
+ talloc_free(procedure);
+ }
+}
+
+static void ns2_vc_create_ip(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct osmo_sockaddr *remote,
+ uint8_t sig_weight, uint8_t data_weight)
+{
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind *bind;
+
+ /* for every bind, create a connection if bind type == IP */
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
+ /* ignore failed connection */
+ nsvc = gprs_ns2_ip_connect_inactive(bind,
+ remote,
+ nse, 0);
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
+ continue;
+ }
+
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
+ }
+}
+
+static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi,
+ struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct osmo_sockaddr remote = { };
+ /* copy over. Both data structures use network byte order */
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+
+ ns2_vc_create_ip(fi, nse, &remote, ip4->sig_weight, ip4->data_weight);
+}
+
+static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi,
+ struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct osmo_sockaddr remote = {};
+ /* copy over. Both data structures use network byte order */
+ remote.u.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->udp_port;
+
+ ns2_vc_create_ip(fi, nse, &remote, ip6->sig_weight, ip6->data_weight);
+}
+
+static struct gprs_ns2_vc *nsvc_for_bind_and_remote(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->bind != bind)
+ continue;
+
+ if (!osmo_sockaddr_cmp(remote, gprs_ns2_ip_vc_remote(nsvc)))
+ return nsvc;
+ }
+ return NULL;
+}
+
+static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ struct ns2_sns_bind *sbind;
+ struct osmo_sockaddr remote = { };
+ unsigned int i;
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i];
+
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+
+ /* iterate over all local binds within this SNS */
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
+
+ /* we only care about UDP binds */
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
+
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+ nsvc->sns_only = false;
+ }
+ }
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i];
+
+ remote.u.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->udp_port;
+
+ /* iterate over all local binds within this SNS */
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
+
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
+
+ /* we only care about UDP binds */
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip6->data_weight;
+ nsvc->sig_weight = ip6->sig_weight;
+ nsvc->sns_only = false;
+ }
+ }
+
+
+ return 0;
+}
+
+/* Add a given remote IPv4 element to gprs_sns_state */
+static int add_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ /* check for duplicates */
+ for (unsigned int i = 0; i < elems->num_ip4; i++) {
+ if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4)))
+ continue;
+ return -1;
+ }
+
+ elems->ip4 = talloc_realloc(gss, elems->ip4, struct gprs_ns_ie_ip4_elem,
+ elems->num_ip4+1);
+ elems->ip4[elems->num_ip4] = *ip4;
+ elems->num_ip4 += 1;
+ return 0;
+}
+
+/* Remove a given remote IPv4 element from gprs_sns_state */
+static int remove_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4)))
+ continue;
+ /* all array elements < i remain as they are; all > i are shifted left by one */
+ memmove(&elems->ip4[i], &elems->ip4[i+1], elems->num_ip4-i-1);
+ elems->num_ip4 -= 1;
+ return 0;
+ }
+ return -1;
+}
+
+/* update the weights for specified remote IPv4 */
+static int update_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (elems->ip4[i].ip_addr != ip4->ip_addr ||
+ elems->ip4[i].udp_port != ip4->udp_port)
+ continue;
+
+ elems->ip4[i].sig_weight = ip4->sig_weight;
+ elems->ip4[i].data_weight = ip4->data_weight;
+ return 0;
+ }
+ return -1;
+}
+
+/* Add a given remote IPv6 element to gprs_sns_state */
+static int add_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ /* check for duplicates */
+ for (unsigned int i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ elems->ip6[i].udp_port != ip6->udp_port)
+ continue;
+ return -1;
+ }
+
+ elems->ip6 = talloc_realloc(gss, elems->ip6, struct gprs_ns_ie_ip6_elem,
+ elems->num_ip6+1);
+ elems->ip6[elems->num_ip6] = *ip6;
+ elems->num_ip6 += 1;
+ return 0;
+}
+
+/* Remove a given remote IPv6 element from gprs_sns_state */
+static int remove_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ unsigned int i;
+
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i], ip6, sizeof(*ip6)))
+ continue;
+ /* all array elements < i remain as they are; all > i are shifted left by one */
+ memmove(&elems->ip6[i], &elems->ip6[i+1], elems->num_ip6-i-1);
+ elems->num_ip6 -= 1;
+ return 0;
+ }
+ return -1;
+}
+
+/* update the weights for specified remote IPv6 */
+static int update_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ unsigned int i;
+
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ elems->ip6[i].udp_port != ip6->udp_port)
+ continue;
+ elems->ip6[i].sig_weight = ip6->sig_weight;
+ elems->ip6[i].data_weight = ip6->data_weight;
+ return 0;
+ }
+ return -1;
+}
+
+static int remove_bind_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, struct ns2_sns_bind *sbind)
+{
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ const struct osmo_sockaddr *saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ ip4.udp_port = saddr->u.sin.sin_port;
+ ip4.sig_weight = sbind->bind->sns_sig_weight;
+ ip4.data_weight = sbind->bind->sns_data_weight;
+ return remove_ip4_elem(gss, elems, &ip4);
+ case AF_INET6:
+ memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ ip6.udp_port = saddr->u.sin.sin_port;
+ ip6.sig_weight = sbind->bind->sns_sig_weight;
+ ip6.data_weight = sbind->bind->sns_data_weight;
+ return remove_ip6_elem(gss, elems, &ip6);
+ default:
+ return -1;
+ }
+
+ return -1;
+}
+
+static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr sa = {};
+ const struct osmo_sockaddr *remote;
+ uint8_t new_signal;
+ uint8_t new_data;
+
+ /* TODO: Upon receiving an SNS-CHANGEWEIGHT PDU, if the resulting sum of the
+ * signalling weights of all the peer IP endpoints configured for this NSE is
+ * equal to zero or if the resulting sum of the data weights of all the peer IP
+ * endpoints configured for this NSE is equal to zero, the BSS/SGSN shall send an
+ * SNS-ACK PDU with a cause code of "Invalid weights". */
+
+ if (ip4) {
+ if (update_ip4_elem(gss, &gss->remote, ip4))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ sa.u.sin.sin_port = ip4->udp_port;
+ sa.u.sin.sin_family = AF_INET;
+ new_signal = ip4->sig_weight;
+ new_data = ip4->data_weight;
+ } else if (ip6) {
+ if (update_ip6_elem(gss, &gss->remote, ip6))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET6;
+ new_signal = ip6->sig_weight;
+ new_data = ip6->data_weight;
+ } else {
+ OSMO_ASSERT(false);
+ }
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ /* all nsvc in NSE should be IP/UDP nsvc */
+ OSMO_ASSERT(remote);
+
+ if (osmo_sockaddr_cmp(&sa, remote))
+ continue;
+
+ LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight %u->%u, sig_weight %u->%u\n",
+ gprs_ns2_ll_str(nsvc), nsvc->data_weight, new_data,
+ nsvc->sig_weight, new_signal);
+
+ nsvc->data_weight = new_data;
+ nsvc->sig_weight = new_signal;
+ }
+
+ return 0;
+}
+
+static int do_sns_delete(struct osmo_fsm_inst *fi,
+ const struct gprs_ns_ie_ip4_elem *ip4,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc, *tmp;
+ const struct osmo_sockaddr *remote;
+ struct osmo_sockaddr sa = {};
+
+ if (ip4) {
+ if (remove_ip4_elem(gss, &gss->remote, ip4) < 0)
+ return -NS_CAUSE_UNKN_IP_EP;
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ sa.u.sin.sin_port = ip4->udp_port;
+ sa.u.sin.sin_family = AF_INET;
+ } else if (ip6) {
+ if (remove_ip6_elem(gss, &gss->remote, ip6))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET6;
+ } else {
+ OSMO_ASSERT(false);
+ }
+
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ /* all nsvc in NSE should be IP/UDP nsvc */
+ OSMO_ASSERT(remote);
+ if (osmo_sockaddr_cmp(&sa, remote))
+ continue;
+
+ LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns2_ll_str(nsvc));
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ return 0;
+}
+
+static int do_sns_add(struct osmo_fsm_inst *fi,
+ const struct gprs_ns_ie_ip4_elem *ip4,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ int rc = 0;
+
+ /* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints
+ * exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send
+ * an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->remote.num_ip4 >= gss->num_max_ip4_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* TODO: log message duplicate */
+ rc = add_ip4_elem(gss, &gss->remote, ip4);
+ break;
+ case AF_INET6:
+ if (gss->remote.num_ip6 >= gss->num_max_ip6_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* TODO: log message duplicate */
+ rc = add_ip6_elem(gss, &gss->remote, ip6);
+ break;
+ default:
+ /* the gss->ip is initialized with the bss */
+ OSMO_ASSERT(false);
+ }
+
+ if (rc)
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+
+ /* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the
+ * NSE shall send an SNS-ACK PDU with the cause code "Protocol error -
+ * unspecified" */
+ switch (gss->family) {
+ case AF_INET:
+ nsvc = nsvc_by_ip4_elem(nse, ip4);
+ if (nsvc) {
+ /* the nsvc should be already in sync with the ip4 / ip6 elements */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip4(fi, nse, ip4);
+ break;
+ case AF_INET6:
+ nsvc = nsvc_by_ip6_elem(nse, ip6);
+ if (nsvc) {
+ /* the nsvc should be already in sync with the ip4 / ip6 elements */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip6(fi, nse, ip6);
+ break;
+ }
+
+ gprs_ns2_start_alive_all_nsvcs(nse);
+
+ return 0;
+}
+
+
+static void ns2_sns_st_bss_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+ /* empty state - SNS Select will start by ns2_sns_st_all_action() */
+}
+
+static void ns2_sns_st_bss_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ struct tlv_parsed *tp = NULL;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_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_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* TODO: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS,
+ nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static int ns2_sns_count_num_local_ep(struct osmo_fsm_inst *fi, int ip_proto)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct ns2_sns_bind *sbind;
+ int count = 0;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ const struct osmo_sockaddr *sa = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (!sa)
+ continue;
+
+ switch (ip_proto) {
+ case AF_INET:
+ if (sa->u.sas.ss_family == AF_INET)
+ count++;
+ break;
+ case AF_INET6:
+ if (sa->u.sas.ss_family == AF_INET6)
+ count++;
+ break;
+ }
+ }
+ return count;
+}
+
+static int ns2_sns_copy_local_endpoints(struct ns2_sns_state *gss)
+{
+ switch (gss->family) {
+ case AF_INET:
+ gss->local_procedure.ip4 = talloc_realloc(gss, gss->local_procedure.ip4, struct gprs_ns_ie_ip4_elem,
+ gss->local.num_ip4);
+ if (!gss->local_procedure.ip4)
+ return -ENOMEM;
+
+ gss->local_procedure.num_ip4 = gss->local.num_ip4;
+ memcpy(gss->local_procedure.ip4, gss->local.ip4,
+ sizeof(struct gprs_ns_ie_ip4_elem) * gss->local.num_ip4);
+ break;
+ case AF_INET6:
+ gss->local_procedure.ip6 = talloc_realloc(gss, gss->local_procedure.ip6, struct gprs_ns_ie_ip6_elem,
+ gss->local.num_ip6);
+ if (!gss->local_procedure.ip6)
+ return -ENOMEM;
+
+ gss->local_procedure.num_ip6 = gss->local.num_ip6;
+ memcpy(gss->local_procedure.ip6, gss->local.ip6,
+ sizeof(struct gprs_ns_ie_ip6_elem) * gss->local.num_ip6);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ return 0;
+}
+
+static void ns2_sns_compute_local_ep_from_binds(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns_ie_ip4_elem *ip4_elems;
+ struct gprs_ns_ie_ip6_elem *ip6_elems;
+ struct gprs_ns2_vc_bind *bind;
+ struct ns2_sns_bind *sbind;
+ const struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *sa;
+ struct osmo_sockaddr local;
+ int count;
+
+ ns2_clear_elems(&gss->local);
+
+ /* no initial available */
+ if (gss->role == GPRS_SNS_ROLE_BSS) {
+ if (!gss->initial)
+ return;
+ remote = &gss->initial->saddr;
+ } else
+ remote = gprs_ns2_ip_vc_remote(gss->sns_nsvc);
+
+ /* count how many bindings are available (only UDP binds) */
+ count = llist_count(&gss->binds);
+ if (count == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "No local binds for this NSE -> cannot determine IP endpoints\n");
+ return;
+ }
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4_elems = talloc_realloc(fi, gss->local.ip4, struct gprs_ns_ie_ip4_elem, count);
+ if (!ip4_elems)
+ return;
+
+ gss->local.ip4 = ip4_elems;
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ bind = sbind->bind;
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET)
+ continue;
+
+ /* check if this is an specific bind */
+ if (sa->u.sin.sin_addr.s_addr == 0) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
+ } else {
+ ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
+ }
+
+ ip4_elems->udp_port = sa->u.sin.sin_port;
+ ip4_elems->sig_weight = bind->sns_sig_weight;
+ ip4_elems->data_weight = bind->sns_data_weight;
+ ip4_elems++;
+ }
+
+ gss->local.num_ip4 = count;
+ gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip4_remote * gss->local.num_ip4, 8);
+ break;
+ case AF_INET6:
+ /* IPv6 */
+ ip6_elems = talloc_realloc(fi, gss->local.ip6, struct gprs_ns_ie_ip6_elem, count);
+ if (!ip6_elems)
+ return;
+
+ gss->local.ip6 = ip6_elems;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ bind = sbind->bind;
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET6)
+ continue;
+
+ /* check if this is an specific bind */
+ if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip6_elems->ip_addr = local.u.sin6.sin6_addr;
+ } else {
+ ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
+ }
+
+ ip6_elems->udp_port = sa->u.sin.sin_port;
+ ip6_elems->sig_weight = bind->sns_sig_weight;
+ ip6_elems->data_weight = bind->sns_data_weight;
+
+ ip6_elems++;
+ }
+ gss->local.num_ip6 = count;
+ gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->local.num_ip6, 8);
+ break;
+ }
+
+ ns2_sns_copy_local_endpoints(gss);
+}
+
+static void ns2_sns_choose_next_bind(struct ns2_sns_state *gss)
+{
+ /* take the first bind or take the next bind */
+ if (!gss->initial_bind || gss->initial_bind->list.next == &gss->binds)
+ gss->initial_bind = llist_first_entry_or_null(&gss->binds, struct ns2_sns_bind, list);
+ else
+ gss->initial_bind = llist_entry(gss->initial_bind->list.next, struct ns2_sns_bind, list);
+}
+
+/* setup all dynamic SNS settings, create a new nsvc and send the SIZE */
+static void ns2_sns_st_bss_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ /* on a generic failure, the timer callback will recover */
+ if (old_state != GPRS_SNS_ST_UNCONFIGURED)
+ ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_FAILURE);
+ if (old_state != GPRS_SNS_ST_BSS_SIZE)
+ gss->N = 0;
+
+ ns2_clear_procedures(gss);
+ gss->alive = false;
+
+ ns2_sns_compute_local_ep_from_binds(fi);
+ ns2_sns_choose_next_bind(gss);
+
+ /* setup the NSVC */
+ if (!gss->sns_nsvc) {
+ struct gprs_ns2_vc_bind *bind = gss->initial_bind->bind;
+ struct osmo_sockaddr *remote = &gss->initial->saddr;
+ gss->sns_nsvc = ns2_ip_bind_connect(bind, gss->nse, remote);
+ if (!gss->sns_nsvc)
+ return;
+ /* A pre-configured endpoint shall not be used for NSE data or signalling traffic
+ * (with the exception of Size and Configuration procedures) unless it is configured
+ * by the SGSN using the auto-configuration procedures */
+ gss->sns_nsvc->sns_only = true;
+ }
+
+ if (gss->num_max_ip4_remote > 0)
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->local.num_ip4, -1);
+ else
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->local.num_ip6);
+}
+
+static void ns2_sns_st_bss_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG_ACK:
+ tp = (struct tlv_parsed *) data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* TODO: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void ns2_sns_st_bss_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ if (old_state != GPRS_SNS_ST_BSS_CONFIG_BSS)
+ gss->N = 0;
+
+ /* Transmit SNS-CONFIG */
+ switch (gss->family) {
+ case AF_INET:
+ ns2_tx_sns_config(gss->sns_nsvc, true,
+ gss->local.ip4, gss->local.num_ip4,
+ NULL, 0);
+ break;
+ case AF_INET6:
+ ns2_tx_sns_config(gss->sns_nsvc, true,
+ NULL, 0,
+ gss->local.ip6, gss->local.num_ip6);
+ break;
+ }
+}
+
+/* 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;
+}
+
+/* append the remote endpoints from the parsed TLV array to the ns2_sns_state */
+static int ns_sns_append_remote_eps(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ 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);
+
+ if (num_v4 && gss->remote.ip6)
+ return -NS_CAUSE_INVAL_NR_IPv4_EP;
+
+ /* realloc to the new size */
+ gss->remote.ip4 = talloc_realloc(gss, gss->remote.ip4,
+ struct gprs_ns_ie_ip4_elem,
+ gss->remote.num_ip4 + num_v4);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->remote.ip4[gss->remote.num_ip4], v4_list, num_v4*sizeof(*v4_list));
+ gss->remote.num_ip4 += num_v4;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
+ gss->remote.num_ip4);
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ const struct gprs_ns_ie_ip6_elem *v6_list;
+ unsigned int num_v6;
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+
+ if (num_v6 && gss->remote.ip4)
+ return -NS_CAUSE_INVAL_NR_IPv6_EP;
+
+ /* realloc to the new size */
+ gss->remote.ip6 = talloc_realloc(gss, gss->remote.ip6,
+ struct gprs_ns_ie_ip6_elem,
+ gss->remote.num_ip6 + num_v6);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->remote.ip6[gss->remote.num_ip6], v6_list, num_v6*sizeof(*v6_list));
+ gss->remote.num_ip6 += num_v6;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %d entries\n",
+ gss->remote.num_ip6);
+ }
+
+ return 0;
+}
+
+static void ns2_sns_st_bss_config_sgsn_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ if (old_state != GPRS_SNS_ST_BSS_CONFIG_SGSN)
+ gss->N = 0;
+}
+
+static void ns2_sns_st_bss_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ uint8_t cause;
+ int rc;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG_END:
+ case NS2_SNS_EV_RX_CONFIG:
+ rc = ns_sns_append_remote_eps(fi, data);
+ if (rc < 0) {
+ cause = -rc;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ if (event == NS2_SNS_EV_RX_CONFIG_END) {
+ /* check if sum of data / sig weights == 0 */
+ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ create_missing_nsvcs(fi);
+ 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);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* called when receiving NS2_SNS_EV_RX_ADD in state configure */
+static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ unsigned int i;
+ int rc = 0;
+
+ /* TODO: refactor EV_ADD/CHANGE/REMOVE by
+ * check uniqueness within the lists (no doublicate entries)
+ * check not-known-by-us and sent back a list of unknown/known values
+ * (abnormal behaviour according to 48.016)
+ */
+
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (gss->family == AF_INET) {
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ 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++) {
+ unsigned int j;
+ rc = do_sns_add(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, &v4_list[j], NULL);
+ cause = -rc;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ break;
+ }
+ }
+ } else { /* IPv6 */
+ if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ for (i = 0; i < num_v6; i++) {
+ unsigned int j;
+ rc = do_sns_add(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, NULL, &v6_list[j]);
+ cause = -rc;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ break;
+ }
+ }
+ }
+
+ /* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ unsigned int i;
+ int rc = 0;
+
+ /* TODO: split up delete into v4 + v6
+ * TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa
+ * TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time
+ */
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (gss->family == AF_INET) {
+ 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], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ } else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) {
+ /* delete all NS-VCs for given IPv4 address */
+ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
+ struct gprs_ns_ie_ip4_elem *ip4_remote;
+ uint32_t ip_addr = *(uint32_t *)(ie+1);
+ if (ie[0] != 0x01) { /* Address Type != IPv4 */
+ cause = NS_CAUSE_UNKN_IP_ADDR;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ /* make a copy as do_sns_delete() will change the array underneath us */
+ ip4_remote = talloc_memdup(fi, gss->remote.ip4,
+ gss->remote.num_ip4 * sizeof(*v4_list));
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ if (ip4_remote[i].ip_addr == ip_addr) {
+ rc = do_sns_delete(fi, &ip4_remote[i], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ }
+ talloc_free(ip4_remote);
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else { /* IPv6 */
+ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ for (i = 0; i < num_v6; i++) {
+ rc = do_sns_delete(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) {
+ /* delete all NS-VCs for given IPv4 address */
+ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
+ struct gprs_ns_ie_ip6_elem *ip6_remote;
+ struct in6_addr ip6_addr;
+ unsigned int i;
+ if (ie[0] != 0x02) { /* Address Type != IPv6 */
+ cause = NS_CAUSE_UNKN_IP_ADDR;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr));
+ /* make a copy as do_sns_delete() will change the array underneath us */
+ ip6_remote = talloc_memdup(fi, gss->remote.ip6,
+ gss->remote.num_ip6 * sizeof(*v4_list));
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) {
+ rc = do_sns_delete(fi, NULL, &ip6_remote[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ }
+
+ talloc_free(ip6_remote);
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ }
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ int rc = 0;
+ unsigned int i;
+
+ 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], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ for (i = 0; i < num_v6; i++) {
+ rc = do_sns_change_weight(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct tlv_parsed *tp = data;
+
+ switch (event) {
+ case NS2_SNS_EV_RX_ADD:
+ ns2_sns_st_configured_add(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_DELETE:
+ ns2_sns_st_configured_delete(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_CHANGE_WEIGHT:
+ ns2_sns_st_configured_change(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_REQ_NSVC_ALIVE:
+ osmo_timer_del(&fi->timer);
+ break;
+ }
+}
+
+static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ /* NS-VC status updates are only parsed in ST_CONFIGURED.
+ * Do an initial check if there are any nsvc alive atm */
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(nsvc)) {
+ gss->alive = true;
+ osmo_timer_del(&fi->timer);
+ break;
+ }
+ }
+
+ /* remove the initial NSVC if the NSVC isn't part of the configuration */
+ if (gss->sns_nsvc->sns_only)
+ gprs_ns2_free_nsvc(gss->sns_nsvc);
+
+ if (old_state != GPRS_SNS_ST_LOCAL_PROCEDURE)
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED);
+
+ if (!llist_empty(&gss->procedures)) {
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+}
+
+static void ns2_sns_st_local_procedure_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ /* check if resend or not */
+ if (!gss->current_procedure) {
+ /* take next procedure */
+ gss->current_procedure = llist_first_entry_or_null(&gss->procedures,
+ struct ns2_sns_procedure, list);
+ if (!gss->current_procedure) {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ return;
+ }
+ gss->N = 0;
+ gss->current_procedure->running = true;
+ gss->current_procedure->trans_id = ++gss->trans_id;
+ if (gss->trans_id == 0)
+ gss->trans_id = gss->current_procedure->trans_id = 1;
+
+ }
+
+ /* also takes care of retransmitting */
+ switch (gss->current_procedure->procedure) {
+ case SNS_PROC_ADD:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ case SNS_PROC_DEL:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+static void create_nsvc_for_new_sbind(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind)
+{
+ struct gprs_ns2_nse *nse = gss->nse;
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr remote = { };
+ unsigned int i;
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i];
+
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+ /* we only care about UDP binds */
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
+
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+ nsvc->sns_only = false;
+ }
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i];
+
+ remote.u.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->udp_port;
+
+ /* we only care about UDP binds */
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip6->data_weight;
+ nsvc->sig_weight = ip6->sig_weight;
+ nsvc->sns_only = false;
+ }
+}
+
+static void ns2_sns_st_local_procedure(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns_ie_ip4_elem *ip4, *proc4;
+ struct gprs_ns_ie_ip6_elem *ip6, *proc6;
+ struct tlv_parsed *tp = data;
+ uint8_t trans_id;
+ uint8_t cause;
+
+ switch (event) {
+ case NS2_SNS_EV_RX_ADD:
+ ns2_sns_st_configured_add(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_DELETE:
+ ns2_sns_st_configured_delete(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_CHANGE_WEIGHT:
+ ns2_sns_st_configured_change(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_ACK:
+ /* presence of trans_id is already checked here */
+ trans_id = tlvp_val8(tp, NS_IE_TRANS_ID, 0);
+ if (trans_id != gss->current_procedure->trans_id) {
+ LOGPFSML(fi, LOGL_INFO, "NSEI=%u Rx SNS ACK with invalid transaction id %d. Valid %d\n",
+ nse->nsei, trans_id, gss->current_procedure->trans_id);
+ break;
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ /* what happend on error cause? return to size? */
+ cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx SNS ACK trans %d with cause code %d.\n",
+ nse->nsei, trans_id, cause);
+ sns_failed(fi, NULL);
+ break;
+ }
+
+ switch (gss->current_procedure->procedure) {
+ case SNS_PROC_ADD:
+ switch (gss->family) {
+ case AF_INET:
+ add_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4);
+ break;
+ case AF_INET6:
+ add_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6);
+ break;
+ }
+ /* the sbind can be NULL if the bind has been released by del_bind */
+ if (gss->current_procedure->sbind) {
+ create_nsvc_for_new_sbind(gss, gss->current_procedure->sbind);
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ }
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ switch (gss->family) {
+ case AF_INET:
+ proc4 = &gss->current_procedure->ip4;
+ for (unsigned int i=0; i<gss->local.num_ip4; i++) {
+ ip4 = &gss->local.ip4[i];
+ if (ip4->ip_addr != proc4->ip_addr ||
+ ip4->udp_port != proc4->udp_port)
+ continue;
+ ip4->sig_weight = proc4->sig_weight;
+ ip4->data_weight = proc4->data_weight;
+ break;
+ }
+ break;
+ case AF_INET6:
+ proc6 = &gss->current_procedure->ip6;
+ for (unsigned int i=0; i<gss->local.num_ip6; i++) {
+ ip6 = &gss->local.ip6[i];
+ if (memcmp(&ip6->ip_addr, &proc6->ip_addr, sizeof(proc6->ip_addr)) ||
+ ip6->udp_port != proc6->udp_port) {
+ continue;
+ }
+ ip6->sig_weight = proc6->sig_weight;
+ ip6->data_weight = proc6->data_weight;
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ break;
+ case SNS_PROC_DEL:
+ switch (gss->family) {
+ case AF_INET:
+ remove_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4);
+ break;
+ case AF_INET6:
+ remove_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ llist_del(&gss->current_procedure->list);
+ talloc_free(gss->current_procedure);
+ gss->current_procedure = NULL;
+
+ if (llist_empty(&gss->procedures))
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_CONFIGURED,
+ 0, 0);
+ else
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state ns2_sns_bss_states[] = {
+ [GPRS_SNS_ST_UNCONFIGURED] = {
+ .in_event_mask = 0, /* handled by all_state_action */
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "UNCONFIGURED",
+ .action = ns2_sns_st_bss_unconfigured,
+ },
+ [GPRS_SNS_ST_BSS_SIZE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_SIZE_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_BSS_CONFIG_BSS),
+ .name = "BSS_SIZE",
+ .action = ns2_sns_st_bss_size,
+ .onenter = ns2_sns_st_bss_size_onenter,
+ },
+ [GPRS_SNS_ST_BSS_CONFIG_BSS] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_CONFIG_BSS) |
+ S(GPRS_SNS_ST_BSS_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "BSS_CONFIG_BSS",
+ .action = ns2_sns_st_bss_config_bss,
+ .onenter = ns2_sns_st_bss_config_bss_onenter,
+ },
+ [GPRS_SNS_ST_BSS_CONFIG_SGSN] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) |
+ S(NS2_SNS_EV_RX_CONFIG_END),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "BSS_CONFIG_SGSN",
+ .action = ns2_sns_st_bss_config_sgsn,
+ .onenter = ns2_sns_st_bss_config_sgsn_onenter,
+ },
+ [GPRS_SNS_ST_CONFIGURED] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "CONFIGURED",
+ .action = ns2_sns_st_configured,
+ .onenter = ns2_sns_st_configured_onenter,
+ },
+ [GPRS_SNS_ST_LOCAL_PROCEDURE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_RX_ACK) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "LOCAL_PROCEDURE",
+ .action = ns2_sns_st_local_procedure,
+ .onenter = ns2_sns_st_local_procedure_onenter,
+ },
+
+};
+
+static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ gss->N++;
+ switch (fi->T) {
+ case 1:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES]) {
+ sns_failed(fi, "Size retries failed. Selecting next IP-SNS endpoint.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ }
+ break;
+ case 2:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "BSS Config retries failed. Selecting next IP-SNS endpoint");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ case 3:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "SGSN Config retries failed. Selecting next IP-SNS endpoint.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ case 4:
+ sns_failed(fi, "Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint.");
+ break;
+ case 5:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "SNS Procedure retries failed.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+ break;
+ }
+ return 0;
+}
+
+static struct gprs_ns_ie_ip4_elem *ns2_get_sbind_ip4_entry(struct ns2_sns_state *gss,
+ struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
+{
+ const struct osmo_sockaddr *addr;
+ struct gprs_ns_ie_ip4_elem *ip4;
+
+ if (gss->family != AF_INET)
+ return NULL;
+
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (addr->u.sa.sa_family != AF_INET)
+ return NULL;
+
+ for (unsigned int i=0; i<endpoints->num_ip4; i++) {
+ ip4 = &endpoints->ip4[i];
+ if (ip4->ip_addr == addr->u.sin.sin_addr.s_addr &&
+ ip4->udp_port == addr->u.sin.sin_port)
+ return ip4;
+ }
+
+ return NULL;
+}
+
+static struct gprs_ns_ie_ip6_elem *ns2_get_sbind_ip6_entry(struct ns2_sns_state *gss,
+ struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
+{
+ const struct osmo_sockaddr *addr;
+ struct gprs_ns_ie_ip6_elem *ip6;
+
+ if (gss->family != AF_INET6)
+ return NULL;
+
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (addr->u.sa.sa_family != AF_INET6)
+ return NULL;
+
+ for (unsigned int i=0; i<endpoints->num_ip6; i++) {
+ ip6 = &endpoints->ip6[i];
+ if (memcmp(&ip6->ip_addr, &addr->u.sin6.sin6_addr, sizeof(ip6->ip_addr)) ||
+ ip6->udp_port != addr->u.sin6.sin6_port)
+ return ip6;
+ }
+
+ return NULL;
+}
+
+/* return != 0 if the resulting weight is invalid. return 1 if sbind doesn't have an entry */
+static int ns2_update_weight_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
+{
+ struct gprs_ns_ie_ip4_elem *ip4;
+ struct gprs_ns_ie_ip6_elem *ip6;
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4 = ns2_get_sbind_ip4_entry(gss, sbind, endpoints);
+ if (!ip4)
+ return 1;
+ ip4->sig_weight = sbind->bind->sns_sig_weight;
+ ip4->data_weight = sbind->bind->sns_data_weight;
+ return (ip4_weight_sum_sig(endpoints) != 0 && ip4_weight_sum_data(endpoints) != 0);
+ break;
+ case AF_INET6:
+ ip6 = ns2_get_sbind_ip6_entry(gss, sbind, endpoints);
+ if (!ip6)
+ return 1;
+ ip6->sig_weight = sbind->bind->sns_sig_weight;
+ ip6->data_weight = sbind->bind->sns_data_weight;
+ return (ip6_weight_sum_sig(endpoints) != 0 && ip6_weight_sum_data(endpoints) != 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void ns2_add_procedure(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ enum sns_procedure procedure_type)
+{
+ struct ns2_sns_procedure *procedure = NULL;
+ const struct osmo_sockaddr *saddr;
+ saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+
+ OSMO_ASSERT(saddr->u.sa.sa_family == gss->family);
+
+ switch (procedure_type) {
+ case SNS_PROC_ADD:
+ break;
+ case SNS_PROC_DEL:
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ llist_for_each_entry(procedure, &gss->procedures, list) {
+ if (procedure->sbind == sbind && procedure->procedure == procedure_type &&
+ !procedure->running) {
+ switch(gss->family) {
+ case AF_INET:
+ /* merge it with a previous procedure */
+ procedure->ip4.ip_addr = sbind->bind->sns_sig_weight;
+ procedure->ip4.data_weight = sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ /* merge it with a previous procedure */
+ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip6.data_weight = sbind->bind->sns_data_weight;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ return;
+ }
+ }
+ break;
+ default:
+ return;
+ }
+
+ procedure = talloc_zero(gss, struct ns2_sns_procedure);
+ if (!procedure)
+ return;
+
+ switch (procedure_type) {
+ case SNS_PROC_ADD:
+ case SNS_PROC_CHANGE_WEIGHT:
+ procedure->sbind = sbind;
+ break;
+ default:
+ break;
+ }
+
+ llist_add_tail(&procedure->list, &gss->procedures);
+ procedure->procedure = procedure_type;
+ procedure->sig_weight = sbind->bind->sns_sig_weight;
+ procedure->data_weight = sbind->bind->sns_data_weight;
+
+ switch(gss->family) {
+ case AF_INET:
+ procedure->ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ procedure->ip4.udp_port = saddr->u.sin.sin_port;
+ procedure->ip4.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip4.data_weight = sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ memcpy(&procedure->ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ procedure->ip6.udp_port = saddr->u.sin.sin_port;
+ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip6.data_weight = sbind->bind->sns_data_weight;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ if (gss->nse->bss_sns_fi->state == GPRS_SNS_ST_CONFIGURED) {
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+}
+
+/* add an entrypoint to sns_endpoints */
+static int ns2_sns_add_elements(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *elems)
+{
+ const struct osmo_sockaddr *saddr;
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ int rc = -1;
+
+ saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ OSMO_ASSERT(saddr->u.sa.sa_family == gss->family);
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ ip4.udp_port= saddr->u.sin.sin_port;
+ ip4.sig_weight = sbind->bind->sns_sig_weight;
+ ip4.data_weight = sbind->bind->sns_data_weight;
+ rc = add_ip4_elem(gss, elems, &ip4);
+ break;
+ case AF_INET6:
+ memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ ip6.udp_port= saddr->u.sin.sin_port;
+ ip6.sig_weight = sbind->bind->sns_sig_weight;
+ ip6.data_weight = sbind->bind->sns_data_weight;
+ rc = add_ip6_elem(gss, elems, &ip6);
+ break;
+ }
+
+ return rc;
+}
+
+/* common allstate-action for both roles */
+static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct ns2_sns_bind *sbind;
+ struct gprs_ns2_vc *nsvc, *nsvc2;
+ struct ns2_sns_procedure *procedure;
+
+ switch (event) {
+ case NS2_SNS_EV_REQ_ADD_BIND:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ if (gss->role == GPRS_SNS_ROLE_BSS)
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->num_max_ip4_remote <= gss->local.num_ip4 ||
+ gss->num_max_ip4_remote * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER);
+ return;
+ }
+ break;
+ case AF_INET6:
+ if (gss->num_max_ip6_remote <= gss->local.num_ip6 ||
+ gss->num_max_ip6_remote * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER);
+ return;
+ }
+ break;
+ }
+ ns2_sns_add_elements(gss, sbind, &gss->local);
+ break;
+ case GPRS_SNS_ST_BSS_CONFIG_BSS:
+ case GPRS_SNS_ST_BSS_CONFIG_SGSN:
+ case GPRS_SNS_ST_CONFIGURED:
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->num_max_ip4_remote <= gss->local.num_ip4) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ if (gss->remote.num_ip4 * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ break;
+ case AF_INET6:
+ if (gss->num_max_ip6_remote <= gss->local.num_ip6) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ if (gss->remote.num_ip6 * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ break;
+ }
+ ns2_sns_add_elements(gss, sbind, &gss->local_procedure);
+ ns2_add_procedure(gss, sbind, SNS_PROC_ADD);
+ break;
+ }
+ break;
+ case NS2_SNS_EV_REQ_DELETE_BIND:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
+ if (nsvc->bind == sbind->bind) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ }
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_CONFIG_BSS:
+ case GPRS_SNS_ST_BSS_CONFIG_SGSN:
+ case GPRS_SNS_ST_CONFIGURED:
+ case GPRS_SNS_ST_LOCAL_PROCEDURE:
+ remove_bind_elem(gss, &gss->local_procedure, sbind);
+ if (ip46_weight_sum(&gss->local_procedure, true) == 0 ||
+ ip46_weight_sum(&gss->local_procedure, false) == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: weight has become invalid because of removing bind %s. Resetting the configuration\n",
+ nse->nsei, sbind->bind->name);
+ sns_failed(fi, NULL);
+ break;
+ }
+ gss->block_no_nsvc_events = true;
+ llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
+ if (nsvc->bind == sbind->bind) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ }
+ gss->block_no_nsvc_events = false;
+ if (nse->sum_sig_weight == 0 || !nse->alive || !gss->alive) {
+ sns_failed(fi, "While deleting a bind the current state became invalid (no signalling weight)");
+ break;
+ }
+
+ /* ensure other procedures doesn't use the sbind */
+ llist_for_each_entry(procedure, &gss->procedures, list) {
+ if (procedure->sbind == sbind)
+ procedure->sbind = NULL;
+ }
+ ns2_add_procedure(gss, sbind, SNS_PROC_DEL);
+ break;
+ }
+
+ /* if this is the last bind, the free_nsvc() will trigger a reselection */
+ talloc_free(sbind);
+ break;
+ case NS2_SNS_EV_REQ_CHANGE_WEIGHT:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ /* select_endpoint will check if this is a valid configuration */
+ if (gss->role == GPRS_SNS_ROLE_BSS)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ /* invalid weight? */
+ if (!ns2_update_weight_entry(gss, sbind, &gss->local))
+ sns_failed(fi, "updating weights results in an invalid configuration.");
+ break;
+ default:
+ if (!ns2_update_weight_entry(gss, sbind, &gss->local_procedure)) {
+ sns_failed(fi, "updating weights results in an invalid configuration.");
+ break;
+ }
+ ns2_add_procedure(gss, sbind, SNS_PROC_CHANGE_WEIGHT);
+ break;
+ }
+ }
+}
+
+/* validate the bss configuration (sns endpoint and binds)
+ * - no endpoints -> invalid
+ * - no binds -> invalid
+ * - only v4 sns endpoints, only v6 binds -> invalid
+ * - only v4 sns endpoints, but v4 sig weights == 0 -> invalid ...
+ */
+static int ns2_sns_bss_valid_configuration(struct ns2_sns_state *gss)
+{
+ struct ns2_sns_bind *sbind;
+ struct sns_endpoint *endpoint;
+ const struct osmo_sockaddr *addr;
+ int v4_sig = 0, v4_data = 0, v6_sig = 0, v6_data = 0;
+ bool v4_endpoints = false;
+ bool v6_endpoints = false;
+
+ if (llist_empty(&gss->sns_endpoints) || llist_empty(&gss->binds))
+ return 0;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (!addr)
+ continue;
+ switch (addr->u.sa.sa_family) {
+ case AF_INET:
+ v4_sig += sbind->bind->sns_sig_weight;
+ v4_data += sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ v6_sig += sbind->bind->sns_sig_weight;
+ v6_data += sbind->bind->sns_data_weight;
+ break;
+ }
+ }
+
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list) {
+ switch (endpoint->saddr.u.sa.sa_family) {
+ case AF_INET:
+ v4_endpoints = true;
+ break;
+ case AF_INET6:
+ v6_endpoints = true;
+ break;
+ }
+ }
+
+ return (v4_endpoints && v4_sig && v4_data) || (v6_endpoints && v6_sig && v6_data);
+}
+
+/* allstate-action for BSS role */
+static void ns2_sns_st_all_action_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+
+ /* reset when receiving NS2_SNS_EV_REQ_NO_NSVC */
+ switch (event) {
+ case NS2_SNS_EV_REQ_NO_NSVC:
+ /* ignore reselection running */
+ if (gss->reselection_running || gss->block_no_nsvc_events)
+ break;
+
+ sns_failed(fi, "no remaining NSVC, resetting SNS FSM");
+ break;
+ case NS2_SNS_EV_REQ_FREE_NSVCS:
+ case NS2_SNS_EV_REQ_SELECT_ENDPOINT:
+ /* TODO: keep the order of binds when data == GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER */
+ /* tear down previous state
+ * gprs_ns2_free_nsvcs() will trigger NO_NSVC, prevent this from triggering a reselection */
+ if (gss->reselection_running || gss->block_no_nsvc_events)
+ break;
+
+ gss->reselection_running = true;
+ ns2_free_nsvcs(nse);
+ ns2_clear_elems(&gss->local);
+ ns2_clear_elems(&gss->remote);
+
+ /* Choose the next sns endpoint. */
+ if (!ns2_sns_bss_valid_configuration(gss)) {
+ gss->initial = NULL;
+ ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 3);
+ gss->reselection_running = false;
+ return;
+ } else if (!gss->initial) {
+ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list);
+ } else if (gss->initial->list.next == &gss->sns_endpoints) {
+ /* last entry, continue with first */
+ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list);
+ } else {
+ /* next element is an entry */
+ gss->initial = llist_entry(gss->initial->list.next, struct sns_endpoint, list);
+ }
+
+ gss->family = gss->initial->saddr.u.sa.sa_family;
+ gss->reselection_running = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ break;
+ default:
+ ns2_sns_st_all_action(fi, event, data);
+ break;
+ }
+}
+
+static struct osmo_fsm gprs_ns2_sns_bss_fsm = {
+ .name = "GPRS-NS2-SNS-BSS",
+ .states = ns2_sns_bss_states,
+ .num_states = ARRAY_SIZE(ns2_sns_bss_states),
+ .allstate_event_mask = S(NS2_SNS_EV_REQ_NO_NSVC) |
+ S(NS2_SNS_EV_REQ_FREE_NSVCS) |
+ S(NS2_SNS_EV_REQ_SELECT_ENDPOINT) |
+ S(NS2_SNS_EV_REQ_ADD_BIND) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_DELETE_BIND),
+ .allstate_action = ns2_sns_st_all_action_bss,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_bss_timer_cb,
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
+
+/*! Allocate an IP-SNS FSM for the BSS side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \returns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
+ const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+ gss->role = GPRS_SNS_ROLE_BSS;
+ /* The SGSN doesn't tell the BSS, so we assume there's always sufficient */
+ gss->num_max_ip4_remote = 8192;
+ gss->num_max_ip6_remote = 8192;
+ INIT_LLIST_HEAD(&gss->sns_endpoints);
+ INIT_LLIST_HEAD(&gss->binds);
+ INIT_LLIST_HEAD(&gss->procedures);
+
+ return fi;
+err:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
+}
+
+/*! main entry point for receiving SNS messages from the network.
+ * \param[in] nsvc NS-VC on which the message was received
+ * \param[in] msg message buffer of the IP-SNS message
+ * \param[in] tp parsed TLV structure of message
+ * \returns 0 on success; negative on error */
+int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct gprs_ns2_nse *nse = nsvc->nse;
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ uint16_t nsei = nsvc->nse->nsei;
+ struct ns2_sns_state *gss;
+ struct osmo_fsm_inst *fi;
+ int rc = 0;
+
+ if (!nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_NOTICE, "Rx %s for NS Instance that has no SNS!\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ goto out;
+ }
+
+ /* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
+ fi = nse->bss_sns_fi;
+ gss = (struct ns2_sns_state *) fi->priv;
+ gss->sns_nsvc = nsvc;
+
+ LOGPFSML(fi, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_SIZE:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE, tp);
+ break;
+ case SNS_PDUT_SIZE_ACK:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE_ACK, tp);
+ break;
+ case SNS_PDUT_CONFIG:
+ if (nsh->data[0] & 0x01)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_END, tp);
+ else
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG, tp);
+ break;
+ case SNS_PDUT_CONFIG_ACK:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_ACK, tp);
+ break;
+ case SNS_PDUT_ADD:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ADD, tp);
+ break;
+ case SNS_PDUT_DELETE:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_DELETE, tp);
+ break;
+ case SNS_PDUT_CHANGE_WEIGHT:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CHANGE_WEIGHT, tp);
+ break;
+ case SNS_PDUT_ACK:
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ACK, tp);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ }
+
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+
+static void vty_dump_sns_ip4(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct in_addr in = { .s_addr = ip4->ip_addr };
+ vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix,
+ inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE);
+}
+
+static void vty_dump_sns_ip6(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ char ip_addr[INET6_ADDRSTRLEN] = {};
+ if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN)))
+ strcpy(ip_addr, "Invalid IPv6");
+
+ vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix,
+ ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE);
+}
+
+/*! Dump the IP-SNS state to a vty.
+ * \param[in] vty VTY to which the state shall be printed
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
+ * \param[in] nse NS Entity whose IP-SNS state shall be printed
+ * \param[in] stats Whether or not statistics shall also be printed */
+void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats)
+{
+ struct ns2_sns_state *gss;
+ unsigned int i;
+
+ if (!nse->bss_sns_fi)
+ return;
+
+ vty_out_fsm_inst2(vty, prefix, nse->bss_sns_fi);
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+
+ vty_out(vty, "%sMaximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s",
+ prefix, gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
+
+ if (gss->local.num_ip4 && gss->remote.num_ip4) {
+ vty_out(vty, "%sLocal IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->local.num_ip4; i++)
+ vty_dump_sns_ip4(vty, prefix, &gss->local.ip4[i]);
+
+ vty_out(vty, "%sRemote IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->remote.num_ip4; i++)
+ vty_dump_sns_ip4(vty, prefix, &gss->remote.ip4[i]);
+ }
+
+ if (gss->local.num_ip6 && gss->remote.num_ip6) {
+ vty_out(vty, "%sLocal IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->local.num_ip6; i++)
+ vty_dump_sns_ip6(vty, prefix, &gss->local.ip6[i]);
+
+ vty_out(vty, "%sRemote IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->remote.num_ip6; i++)
+ vty_dump_sns_ip6(vty, prefix, &gss->remote.ip6[i]);
+ }
+}
+
+/*! write IP-SNS to a vty
+ * \param[in] vty VTY to which the state shall be printed
+ * \param[in] nse NS Entity whose IP-SNS state shall be printed */
+void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+ struct osmo_sockaddr_str addr_str;
+ struct sns_endpoint *endpoint;
+
+ if (!nse->bss_sns_fi)
+ return;
+
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list) {
+ /* It's unlikely that an error happens, but let's better be safe. */
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &endpoint->saddr.u.sas) != 0)
+ addr_str = (struct osmo_sockaddr_str) { .ip = "<INVALID>" };
+ vty_out(vty, " ip-sns-remote %s %u%s", addr_str.ip, addr_str.port, VTY_NEWLINE);
+ }
+}
+
+static struct sns_endpoint *ns2_get_sns_endpoint(struct ns2_sns_state *state,
+ const struct osmo_sockaddr *saddr)
+{
+ struct sns_endpoint *endpoint;
+
+ llist_for_each_entry(endpoint, &state->sns_endpoints, list) {
+ if (!osmo_sockaddr_cmp(saddr, &endpoint->saddr))
+ return endpoint;
+ }
+
+ return NULL;
+}
+
+/*! gprs_ns2_sns_add_endpoint
+ * \param[in] nse
+ * \param[in] sockaddr
+ * \return
+ */
+int gprs_ns2_sns_add_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+ bool do_selection = false;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+
+ if (ns2_get_sns_endpoint(gss, saddr))
+ return -EADDRINUSE;
+
+ endpoint = talloc_zero(nse->bss_sns_fi->priv, struct sns_endpoint);
+ if (!endpoint)
+ return -ENOMEM;
+
+ endpoint->saddr = *saddr;
+ if (llist_empty(&gss->sns_endpoints))
+ do_selection = true;
+
+ llist_add_tail(&endpoint->list, &gss->sns_endpoints);
+ if (do_selection)
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+
+ return 0;
+}
+
+/*! gprs_ns2_sns_del_endpoint
+ * \param[in] nse
+ * \param[in] sockaddr
+ * \return 0 on success, otherwise < 0
+ */
+int gprs_ns2_sns_del_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+ endpoint = ns2_get_sns_endpoint(gss, saddr);
+ if (!endpoint)
+ return -ENOENT;
+
+ /* if this is an unused SNS endpoint it's done */
+ if (gss->initial != endpoint) {
+ llist_del(&endpoint->list);
+ talloc_free(endpoint);
+ return 0;
+ }
+
+ /* gprs_ns2_free_nsvcs() will trigger NS2_SNS_EV_REQ_NO_NSVC on the last NS-VC
+ * and restart SNS SIZE procedure which selects a new initial */
+ LOGNSE(nse, LOGL_INFO, "Current in-use SNS endpoint is being removed."
+ "Closing all NS-VC and restart SNS-SIZE procedure"
+ "with a remaining SNS endpoint.\n");
+
+ /* Continue with the next endpoint in the list.
+ * Special case if the endpoint is at the start or end of the list */
+ if (endpoint->list.prev == &gss->sns_endpoints ||
+ endpoint->list.next == &gss->sns_endpoints)
+ gss->initial = NULL;
+ else
+ gss->initial = llist_entry(endpoint->list.next->prev,
+ struct sns_endpoint,
+ list);
+
+ llist_del(&endpoint->list);
+ gprs_ns2_free_nsvcs(nse);
+ talloc_free(endpoint);
+
+ return 0;
+}
+
+/*! gprs_ns2_sns_count
+ * \param[in] nse NS Entity whose IP-SNS endpoints shall be printed
+ * \return the count of endpoints or < 0 if NSE doesn't contain sns.
+ */
+int gprs_ns2_sns_count(struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+ int count = 0;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list)
+ count++;
+
+ 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 && nse->bss_sns_fi->state != GPRS_SNS_ST_LOCAL_PROCEDURE)
+ return;
+
+ if (gss->block_no_nsvc_events)
+ return;
+
+ if (gss->alive && nse->sum_sig_weight == 0) {
+ sns_failed(nse->bss_sns_fi, "No signalling NSVC available");
+ return;
+ }
+
+ /* check if this is the current SNS NS-VC */
+ if (nsvc == gss->sns_nsvc && !alive) {
+ /* 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 */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (nsvc == tmp)
+ continue;
+ if (ns2_vc_is_unblocked(nsvc)) {
+ ns2_sns_replace_nsvc(nsvc);
+ break;
+ }
+ }
+ }
+
+ if (alive == gss->alive)
+ return;
+
+ if (alive) {
+ /* we need at least a signalling NSVC before become alive */
+ if (nse->sum_sig_weight == 0)
+ return;
+ gss->alive = true;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_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, NS2_SNS_EV_REQ_NO_NSVC, NULL);
+ }
+}
+
+int gprs_ns2_sns_add_bind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_state *gss;
+ struct ns2_sns_bind *tmp;
+
+ OSMO_ASSERT(nse->bss_sns_fi);
+ gss = nse->bss_sns_fi->priv;
+
+ if (!gprs_ns2_is_ip_bind(bind)) {
+ return -EINVAL;
+ }
+
+ if (!llist_empty(&gss->binds)) {
+ llist_for_each_entry(tmp, &gss->binds, list) {
+ if (tmp->bind == bind)
+ return -EALREADY;
+ }
+ }
+
+ tmp = talloc_zero(gss, struct ns2_sns_bind);
+ if (!tmp)
+ return -ENOMEM;
+ tmp->bind = bind;
+ llist_add_tail(&tmp->list, &gss->binds);
+
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_ADD_BIND, tmp);
+ return 0;
+}
+
+/* Remove a bind from the SNS. All assosiated NSVC must be removed. */
+int gprs_ns2_sns_del_bind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_state *gss;
+ struct ns2_sns_bind *tmp, *tmp2;
+ bool found = false;
+
+ if (!nse->bss_sns_fi)
+ return -EINVAL;
+
+ gss = nse->bss_sns_fi->priv;
+ if (gss->initial_bind && gss->initial_bind->bind == bind) {
+ if (gss->initial_bind->list.prev == &gss->binds)
+ gss->initial_bind = NULL;
+ else
+ gss->initial_bind = llist_entry(gss->initial_bind->list.prev, struct ns2_sns_bind, list);
+ }
+
+ llist_for_each_entry_safe(tmp, tmp2, &gss->binds, list) {
+ if (tmp->bind == bind) {
+ llist_del(&tmp->list);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ return -ENOENT;
+
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_DELETE_BIND, tmp);
+ return 0;
+}
+
+/* Update SNS weights for a bind (local endpoint).
+ * \param[in] bind the bind which has been updated
+ */
+void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_bind *sbind;
+ struct gprs_ns2_nse *nse;
+ struct ns2_sns_state *gss;
+ const struct osmo_sockaddr *addr = gprs_ns2_ip_bind_sockaddr(bind);
+
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ if (!nse->bss_sns_fi)
+ continue;
+
+ gss = nse->bss_sns_fi->priv;
+ if (addr->u.sa.sa_family != gss->family)
+ return;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ if (sbind->bind == bind) {
+ osmo_fsm_inst_dispatch(gss->nse->bss_sns_fi, NS2_SNS_EV_REQ_CHANGE_WEIGHT, sbind);
+ break;
+ }
+ }
+ }
+}
+
+
+
+
+/***********************************************************************
+ * SGSN role
+ ***********************************************************************/
+
+/* cleanup all state. If nsvc is given, don't remove this nsvc. (nsvc is given when a SIZE PDU received) */
+static void ns2_clear_sgsn(struct ns2_sns_state *gss, struct gprs_ns2_vc *size_nsvc)
+{
+ struct gprs_ns2_vc *nsvc, *nsvc2;
+
+ ns2_clear_procedures(gss);
+ ns2_clear_elems(&gss->local);
+ ns2_clear_elems(&gss->remote);
+ gss->block_no_nsvc_events = true;
+ llist_for_each_entry_safe(nsvc, nsvc2, &gss->nse->nsvc, list) {
+ /* Ignore the NSVC over which the SIZE PDU got received */
+ if (size_nsvc && size_nsvc == nsvc)
+ continue;
+
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ gss->block_no_nsvc_events = false;
+}
+
+static void ns2_sns_st_sgsn_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ ns2_clear_sgsn(gss, NULL);
+}
+
+static void ns2_sns_st_sgsn_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+ /* do nothing; Rx SNS-SIZE handled in ns2_sns_st_all_action_sgsn() */
+}
+
+/* We're waiting for inbound SNS-CONFIG from the BSS */
+static void ns2_sns_st_sgsn_wait_config(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ uint8_t cause;
+ int rc;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG:
+ case NS2_SNS_EV_RX_CONFIG_END:
+ rc = ns_sns_append_remote_eps(fi, data);
+ if (rc < 0) {
+ cause = -rc;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ /* only change state if last CONFIG was received */
+ if (event == NS2_SNS_EV_RX_CONFIG_END) {
+ /* ensure sum of data weight / sig weights is > 0 */
+ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0);
+ }
+ break;
+ }
+}
+
+static void ns2_sns_st_sgsn_wait_config_ack_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ /* transmit SGSN-oriented SNS-CONFIG */
+ ns2_tx_sns_config(gss->sns_nsvc, true, gss->local.ip4, gss->local.num_ip4,
+ gss->local.ip6, gss->local.num_ip6);
+}
+
+/* We're waiting for SNS-CONFIG-ACK from the BSS (in response to our outbound SNS-CONFIG) */
+static void ns2_sns_st_sgsn_wait_config_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ /* we currently only send one SNS-CONFIG with END FLAG */
+ if (true) {
+ create_missing_nsvcs(fi);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, ns_sns_configured_timeout(fi), 4);
+ }
+ break;
+ }
+}
+
+/* SGSN-side SNS state machine */
+static const struct osmo_fsm_state ns2_sns_sgsn_states[] = {
+ [GPRS_SNS_ST_UNCONFIGURED] = {
+ .in_event_mask = 0, /* handled by all_state_action */
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG),
+ .name = "UNCONFIGURED",
+ .action = ns2_sns_st_sgsn_unconfigured,
+ .onenter = ns2_sns_st_sgsn_unconfigured_onenter,
+ },
+ [GPRS_SNS_ST_SGSN_WAIT_CONFIG] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) |
+ S(NS2_SNS_EV_RX_CONFIG_END),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK),
+ .name = "SGSN_WAIT_CONFIG",
+ .action = ns2_sns_st_sgsn_wait_config,
+ },
+ [GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK) |
+ S(GPRS_SNS_ST_CONFIGURED),
+ .name = "SGSN_WAIT_CONFIG_ACK",
+ .action = ns2_sns_st_sgsn_wait_config_ack,
+ .onenter = ns2_sns_st_sgsn_wait_config_ack_onenter,
+ },
+ [GPRS_SNS_ST_CONFIGURED] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "CONFIGURED",
+ /* shared with BSS side; once configured there's no difference */
+ .action = ns2_sns_st_configured,
+ .onenter = ns2_sns_st_configured_onenter,
+ },
+ [GPRS_SNS_ST_LOCAL_PROCEDURE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_RX_ACK) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "LOCAL_PROCEDURE",
+ /* shared with BSS side; once configured there's no difference */
+ .action = ns2_sns_st_local_procedure,
+ .onenter = ns2_sns_st_local_procedure_onenter,
+ },
+};
+
+static int ns2_sns_fsm_sgsn_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ gss->N++;
+ switch (fi->T) {
+ case 3:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: SGSN Config retries failed. Giving up.\n", nse->nsei);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ case 4:
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online.\n", nse->nsei);
+ break;
+ case 5:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES]) {
+ sns_failed(fi, "SNS Procedure retries failed.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV],
+ fi->T);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* allstate-action for SGSN role */
+static void ns2_sns_st_all_action_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct tlv_parsed *tp = NULL;
+ size_t num_local_eps, num_remote_eps;
+ uint8_t flag;
+ uint8_t cause;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_SIZE:
+ tp = (struct tlv_parsed *) data;
+ /* check for mandatory / conditional IEs */
+ if (!TLVP_PRES_LEN(tp, NS_IE_RESET_FLAG, 1) ||
+ !TLVP_PRES_LEN(tp, NS_IE_MAX_NR_NSVC, 2)) {
+ cause = NS_CAUSE_MISSING_ESSENT_IE;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Missing essential IE");
+ break;
+ }
+ if (!TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2) &&
+ !TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) {
+ cause = NS_CAUSE_MISSING_ESSENT_IE;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Missing essential IE");
+ break;
+ }
+ if (TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2))
+ gss->num_max_ip4_remote = tlvp_val16be(tp, NS_IE_IPv4_EP_NR);
+ if (TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2))
+ gss->num_max_ip6_remote = tlvp_val16be(tp, NS_IE_IPv6_EP_NR);
+ /* decide if we go for IPv4 or IPv6 */
+ if (gss->num_max_ip6_remote && ns2_sns_count_num_local_ep(fi, AF_INET6)) {
+ gss->family = AF_INET6;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ num_local_eps = gss->local.num_ip6;
+ num_remote_eps = gss->num_max_ip6_remote;
+ } else if (gss->num_max_ip4_remote && ns2_sns_count_num_local_ep(fi, AF_INET)) {
+ gss->family = AF_INET;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ num_local_eps = gss->local.num_ip4;
+ num_remote_eps = gss->num_max_ip4_remote;
+ } else {
+ if (gss->local.num_ip4 && !gss->num_max_ip4_remote)
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ else
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Invalid Nr of IPv4/IPv6 EPs");
+ break;
+ }
+ /* ensure number of NS-VCs is sufficient for full mesh */
+ gss->num_max_nsvcs = tlvp_val16be(tp, NS_IE_MAX_NR_NSVC);
+ if (gss->num_max_nsvcs < num_remote_eps * num_local_eps) {
+ LOGPFSML(fi, LOGL_ERROR, "%zu local and %zu remote EPs, requires %zu NS-VC, "
+ "but BSS supports only %zu maximum NS-VCs\n", num_local_eps,
+ num_remote_eps, num_local_eps * num_remote_eps, gss->num_max_nsvcs);
+ cause = NS_CAUSE_INVAL_NR_NS_VC;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, NULL);
+ break;
+ }
+ /* perform state reset, if requested */
+ flag = *TLVP_VAL(tp, NS_IE_RESET_FLAG);
+ if (flag & 1) {
+ /* clear all state */
+ /* TODO: ensure gss->sns_nsvc is always the NSVC on which we received the SIZE PDU */
+ gss->N = 0;
+ ns2_clear_sgsn(gss, gss->sns_nsvc);
+ /* keep the NSVC we need for SNS, but unconfigure it */
+ gss->sns_nsvc->sig_weight = 0;
+ gss->sns_nsvc->data_weight = 0;
+ gss->block_no_nsvc_events = true;
+ ns2_vc_force_unconfigured(gss->sns_nsvc);
+ gss->block_no_nsvc_events = false;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ }
+
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED && !(flag & 1)) {
+ sns_failed(fi, "Rx Size without Reset flag, but NSE is unknown");
+ break;
+ }
+
+ /* send SIZE_ACK */
+ ns2_tx_sns_size_ack(gss->sns_nsvc, NULL);
+ /* only wait for SNS-CONFIG in case of Reset flag */
+ if (flag & 1)
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG, 0, 0);
+ break;
+ case NS2_SNS_EV_REQ_FREE_NSVCS:
+ sns_failed(fi, "On user request to free all NSVCs");
+ break;
+ default:
+ ns2_sns_st_all_action(fi, event, data);
+ break;
+ }
+}
+
+static struct osmo_fsm gprs_ns2_sns_sgsn_fsm = {
+ .name = "GPRS-NS2-SNS-SGSN",
+ .states = ns2_sns_sgsn_states,
+ .num_states = ARRAY_SIZE(ns2_sns_sgsn_states),
+ .allstate_event_mask = S(NS2_SNS_EV_RX_SIZE) |
+ S(NS2_SNS_EV_REQ_NO_NSVC) |
+ S(NS2_SNS_EV_REQ_FREE_NSVCS) |
+ S(NS2_SNS_EV_REQ_ADD_BIND) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_DELETE_BIND),
+ .allstate_action = ns2_sns_st_all_action_sgsn,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_sgsn_timer_cb,
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
+
+/*! Allocate an IP-SNS FSM for the SGSN side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \returns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_sgsn_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+ gss->role = GPRS_SNS_ROLE_SGSN;
+ INIT_LLIST_HEAD(&gss->sns_endpoints);
+ INIT_LLIST_HEAD(&gss->binds);
+ INIT_LLIST_HEAD(&gss->procedures);
+
+ return fi;
+err:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
+}
+
+
+
+
+/* initialize osmo_ctx on main tread */
+static __attribute__((constructor)) void on_dso_load_ctx(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_bss_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_sgsn_fsm) == 0);
+}
diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c
new file mode 100644
index 00000000..1dcc3030
--- /dev/null
+++ b/src/gb/gprs_ns2_udp.c
@@ -0,0 +1,597 @@
+/*! \file gprs_ns2_udp.c
+ * NS-over-UDP implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+
+
+struct gprs_ns2_vc_driver vc_driver_ip = {
+ .name = "GB UDP IPv4/IPv6",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_io_fd *iofd;
+ struct osmo_sockaddr addr;
+ int dscp;
+ uint8_t priority;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+};
+
+/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */
+static void free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (!bind)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ priv = bind->priv;
+
+ osmo_iofd_free(priv->iofd);
+ priv->iofd = NULL;
+ talloc_free(priv);
+}
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ if (!nsvc->priv)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(nsvc->bind));
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+static void dump_vty(const struct gprs_ns2_vc_bind *bind,
+ struct vty *vty, bool stats)
+{
+ struct priv_bind *priv;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr_str sockstr = {};
+ unsigned long nsvcs = 0;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+ if (osmo_sockaddr_str_from_sockaddr(&sockstr, &priv->addr.u.sas))
+ strcpy(sockstr.ip, "invalid");
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ nsvcs++;
+ }
+
+ vty_out(vty, "UDP bind: %s:%d DSCP: %d Priority: %u%s", sockstr.ip, sockstr.port,
+ priv->dscp, priv->priority, VTY_NEWLINE);
+ vty_out(vty, " IP-SNS signalling weight: %u data weight: %u%s",
+ bind->sns_sig_weight, bind->sns_data_weight, VTY_NEWLINE);
+ vty_out(vty, " %lu NS-VC:%s", nsvcs, VTY_NEWLINE);
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+ }
+}
+
+
+/*! Find a NS-VC by its remote socket address.
+ * \param[in] bind in which to search
+ * \param[in] rem_addr remote peer socket address to search
+ * \returns NS-VC matching sockaddr; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *rem_addr)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->remote.u.sa.sa_family != rem_addr->u.sa.sa_family)
+ continue;
+ if (osmo_sockaddr_cmp(&vcpriv->remote, rem_addr))
+ continue;
+
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+static inline int nsip_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const struct osmo_sockaddr *dest)
+{
+ struct priv_bind *priv = bind->priv;
+
+ return osmo_iofd_sendto_msgb(priv->iofd, msg, 0, dest);
+}
+
+/*! send the msg and free it afterwards.
+ * \param nsvc NS-VC on which the message shall be sent
+ * \param msg message to be sent
+ * \return number of bytes transmitted; negative on error */
+static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct priv_vc *priv = nsvc->priv;
+
+ rc = nsip_sendmsg(bind, msg, &priv->remote);
+
+ return rc;
+}
+
+static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *nsvc, const struct osmo_sockaddr *remote)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ nsvc->priv = priv;
+ priv->remote = *remote;
+
+ return priv;
+}
+
+static void handle_nsip_recvfrom(struct osmo_io_fd *iofd, int error, struct msgb *msg,
+ const struct osmo_sockaddr *saddr)
+{
+ int rc = 0;
+ struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd);
+ struct gprs_ns2_vc *nsvc;
+
+ struct msgb *reject;
+
+ msg->l2h = msgb_data(msg);
+
+ /* check if a vc is available */
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, saddr);
+ if (!nsvc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, saddr, "newconnection", &reject, &nsvc);
+ switch (rc) {
+ case NS2_CS_FOUND:
+ break;
+ case NS2_CS_ERROR:
+ case NS2_CS_SKIPPED:
+ rc = 0;
+ goto out;
+ case NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ rc = nsip_sendmsg(bind, reject, saddr);
+ goto out;
+ case NS2_CS_CREATED:
+ ns2_driver_alloc_vc(bind, nsvc, saddr);
+ /* only start the fsm for non SNS. SNS will take care of its own */
+ if (nsvc->nse->dialect != GPRS_NS2_DIALECT_SNS)
+ ns2_vc_fsm_start(nsvc);
+ break;
+ }
+ }
+
+ ns2_recv_vc(nsvc, msg);
+ return;
+
+out:
+ msgb_free(msg);
+}
+
+static void handle_nsip_sendto(struct osmo_io_fd *iofd, int res,
+ struct msgb *msg,
+ const struct osmo_sockaddr *daddr)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd);
+ struct gprs_ns2_vc *nsvc;
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, daddr);
+ if (!nsvc)
+ return;
+
+ if (OSMO_LIKELY(res >= 0)) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, res);
+ } else {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, msgb_length(msg));
+ }
+}
+
+/*! Find NS bind for a given socket address
+ * \param[in] nsi NS instance
+ * \param[in] sockaddr socket address to search for
+ * \return
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *sockaddr)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *local;
+
+ OSMO_ASSERT(nsi);
+ OSMO_ASSERT(sockaddr);
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!osmo_sockaddr_cmp(sockaddr, local))
+ return bind;
+ }
+
+ return NULL;
+}
+
+/*! Bind to an IPv4/IPv6 address
+ * \param[in] nsi NS Instance in which to create the NSVC
+ * \param[in] local the local address to bind to
+ * \param[in] dscp the DSCP/TOS bits used for transmitted data
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
+int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
+ const struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct priv_bind *priv;
+ int rc;
+
+ struct osmo_io_ops ioops = {
+ .sendto_cb = &handle_nsip_sendto,
+ .recvfrom_cb = &handle_nsip_recvfrom,
+ };
+
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6)
+ return -EINVAL;
+
+ if (dscp < 0 || dscp > 63)
+ return -EINVAL;
+
+ bind = gprs_ns2_ip_bind_by_sockaddr(nsi, local);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EBUSY;
+ }
+
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
+ bind->driver = &vc_driver_ip;
+ bind->ll = GPRS_NS2_LL_UDP;
+ /* expect 100 mbit at least.
+ * TODO: ask the network layer about the speed. But would require
+ * notification on change. */
+ bind->transfer_capability = 100;
+ bind->send_vc = nsip_vc_sendmsg;
+ bind->free_vc = free_vc;
+ bind->dump_vty = dump_vty;
+
+ priv = bind->priv = talloc_zero(bind, struct priv_bind);
+ if (!priv) {
+ gprs_ns2_free_bind(bind);
+ return -ENOMEM;
+ }
+
+ priv->addr = *local;
+ priv->dscp = dscp;
+
+ rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP,
+ local, NULL,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp));
+ if (rc < 0) {
+ gprs_ns2_free_bind(bind);
+ return rc;
+ }
+
+ priv->iofd = osmo_iofd_setup(bind, rc, "NS bind", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops, bind);
+ osmo_iofd_register(priv->iofd, rc);
+ osmo_iofd_set_alloc_info(priv->iofd, 4096, 128);
+ osmo_iofd_set_txqueue_max_length(priv->iofd, nsi->txqueue_max_length);
+
+ /* 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;
+
+ return 0;
+}
+
+/*! Create new NS-VC to a given remote address
+ * \param[in] bind the bind we want to connect
+ * \param[in] nse NS entity to be used for the new NS-VC
+ * \param[in] remote remote address to connect to
+ * \return pointer to newly-allocated and connected NS-VC; NULL on error */
+struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc *nsvc;
+ const struct osmo_sockaddr *local;
+ struct priv_vc *priv;
+ enum gprs_ns2_vc_mode vc_mode;
+ char idbuf[256], tmp[INET6_ADDRSTRLEN + 8];
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ vc_mode = ns2_dialect_to_vc_mode(nse->dialect);
+ if ((int) vc_mode == -1) {
+ LOGNSE(nse, LOGL_ERROR, "Can not derive vc mode from dialect %d. Maybe libosmocore is too old.\n",
+ nse->dialect);
+ return NULL;
+ }
+
+ /* duplicate */
+ if (gprs_ns2_nsvc_by_sockaddr_bind(bind, remote))
+ return NULL;
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC-%s-%s-%s", nse->nsei, gprs_ns2_lltype_str(nse->ll),
+ tmp, osmo_sockaddr_to_str(remote));
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+
+ nsvc = ns2_vc_alloc(bind, nse, true, vc_mode, idbuf);
+ if (!nsvc)
+ return NULL;
+
+ nsvc->priv = talloc_zero(bind, struct priv_vc);
+ if (!nsvc->priv) {
+ gprs_ns2_free_nsvc(nsvc);
+ return NULL;
+ }
+
+ priv = nsvc->priv;
+ priv->remote = *remote;
+
+ return nsvc;
+}
+
+/*! Return the socket address of the local peer of a NS-VC.
+ * \param[in] nsvc NS-VC whose local peer we want to know
+ * \return address of the local peer; NULL in case of error */
+const struct osmo_sockaddr *gprs_ns2_ip_vc_local(const struct gprs_ns2_vc *nsvc)
+{
+ struct priv_bind *priv;
+
+ if (nsvc->bind->driver != &vc_driver_ip)
+ return NULL;
+
+ priv = nsvc->bind->priv;
+ return &priv->addr;
+}
+
+/*! Return the socket address of the remote peer of a NS-VC.
+ * \param[in] nsvc NS-VC whose remote peer we want to know
+ * \return address of the remote peer; NULL in case of error */
+const struct osmo_sockaddr *gprs_ns2_ip_vc_remote(const struct gprs_ns2_vc *nsvc)
+{
+ struct priv_vc *priv;
+
+ if (nsvc->bind->driver != &vc_driver_ip)
+ return NULL;
+
+ priv = nsvc->priv;
+ return &priv->remote;
+}
+
+/*! Compare the NS-VC with the given parameter
+ * \param[in] nsvc NS-VC to compare with
+ * \param[in] local The local address
+ * \param[in] remote The remote address
+ * \param[in] nsvci NS-VCI will only be used if the NS-VC in BLOCKRESET mode otherwise NS-VCI isn't applicable.
+ * \return true if the NS-VC has the same properties as given
+ */
+bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsvci)
+{
+ struct priv_vc *vpriv;
+ struct priv_bind *bpriv;
+
+ if (nsvc->bind->driver != &vc_driver_ip)
+ return false;
+
+ vpriv = nsvc->priv;
+ bpriv = nsvc->bind->priv;
+
+ if (osmo_sockaddr_cmp(local, &bpriv->addr))
+ return false;
+
+ if (osmo_sockaddr_cmp(remote, &vpriv->remote))
+ return false;
+
+ if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET)
+ if (nsvc->nsvci != nsvci)
+ return false;
+
+ return true;
+}
+
+/*! Return the locally bound socket address of the bind.
+ * \param[in] bind The bind whose local address we want to know
+ * \return address of the local bind */
+const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ priv = bind->priv;
+ return &priv->addr;
+}
+
+/*! Is the given bind an IP bind? */
+int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_ip);
+}
+
+/*! Set the DSCP (TOS) bit value of the given bind. */
+int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp)
+{
+ struct priv_bind *priv;
+ int rc = 0;
+
+ if (dscp < 0 || dscp > 63)
+ return -EINVAL;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ priv = bind->priv;
+
+ if (dscp != priv->dscp) {
+ priv->dscp = dscp;
+
+ rc = osmo_sock_set_dscp(osmo_iofd_get_fd(priv->iofd), dscp);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to set the DSCP to %u with ret(%d) errno(%d)\n",
+ dscp, rc, errno);
+ }
+ }
+
+ return rc;
+}
+
+/*! Set the socket priority of the given bind. */
+int gprs_ns2_ip_bind_set_priority(struct gprs_ns2_vc_bind *bind, uint8_t priority)
+{
+ struct priv_bind *priv;
+ int rc = 0;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ priv = bind->priv;
+
+ if (priority != priv->priority) {
+ priv->priority = priority;
+
+ rc = osmo_sock_set_priority(osmo_iofd_get_fd(priv->iofd), priority);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to set the priority to %u with ret(%d) errno(%d)\n",
+ priority, rc, errno);
+ }
+ }
+
+ return rc;
+}
+
+
+/*! Count UDP binds compatible with remote */
+int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *sa;
+ int count = 0;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family)
+ count++;
+ }
+
+ return count;
+}
+
+/* return the matching bind by index */
+struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *remote,
+ int index)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *sa;
+ int i = 0;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family) {
+ if (index == i)
+ return bind;
+ i++;
+ }
+ }
+
+ return NULL;
+}
+
+void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length)
+{
+ struct priv_bind *priv = bind->priv;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ osmo_iofd_set_txqueue_max_length(priv->iofd, max_length);
+}
+
+/*! set the signalling and data weight for this bind
+ * \param[in] bind
+ * \param[in] signalling the signalling weight
+ * \param[in] data the data weight
+ */
+void gprs_ns2_ip_bind_set_sns_weight(struct gprs_ns2_vc_bind *bind, uint8_t signalling, uint8_t data)
+{
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ bind->sns_sig_weight = signalling;
+ bind->sns_data_weight = data;
+ ns2_sns_update_weights(bind);
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c
new file mode 100644
index 00000000..9cd83c4f
--- /dev/null
+++ b/src/gb/gprs_ns2_vc_fsm.c
@@ -0,0 +1,992 @@
+/*! \file gprs_ns2_vc_fsm.c
+ * NS virtual circuit FSM implementation
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define S(x) (1 << (x))
+
+struct gprs_ns2_vc_priv {
+ struct gprs_ns2_vc *nsvc;
+ /* how often the timer was triggered */
+ int N;
+ /* The initiator is responsible to UNBLOCK the VC. The BSS is usually the initiator.
+ * It can change during runtime. The side which blocks an unblocked side.*/
+ bool initiator;
+ bool initiate_block;
+ bool initiate_reset;
+ /* if unitdata is forwarded to the user */
+ bool accept_unitdata;
+
+ /* the alive counter is present in all states */
+ struct {
+ struct osmo_timer_list timer;
+ enum ns2_timeout mode;
+ int N;
+ struct timespec timer_started;
+ } alive;
+};
+
+
+/* The FSM covers both the VC with RESET/BLOCK and without RESET/BLOCK procedure..
+ *
+ * With RESET/BLOCK, the state should follow:
+ * - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED
+ *
+ * Without RESET/BLOCK, the state should follow:
+ * - UNCONFIGURED -> RECOVERY -> UNBLOCKED
+ *
+ * The UNBLOCKED and TEST states are used to send ALIVE PDU using the timeout Tns-test and Tns-alive.
+ * UNBLOCKED -> TEST: on expire of Tns-Test, send Alive PDU.
+ * TEST -> UNBLOCKED: on receive of Alive_Ack PDU, go into UNBLOCKED.
+ *
+ * The RECOVERY state is used as intermediate, because a VC is only valid if it received an Alive ACK when
+ * not using RESET/BLOCK procedure.
+ */
+
+enum gprs_ns2_vc_state {
+ GPRS_NS2_ST_UNCONFIGURED,
+ GPRS_NS2_ST_RESET,
+ GPRS_NS2_ST_BLOCKED,
+ GPRS_NS2_ST_UNBLOCKED, /* allows sending NS_UNITDATA */
+
+ GPRS_NS2_ST_RECOVERING, /* only used when not using RESET/BLOCK procedure */
+};
+
+enum gprs_ns2_vc_event {
+ GPRS_NS2_EV_REQ_START,
+
+ /* received messages */
+ GPRS_NS2_EV_RX_RESET,
+ GPRS_NS2_EV_RX_RESET_ACK,
+ GPRS_NS2_EV_RX_UNBLOCK,
+ GPRS_NS2_EV_RX_UNBLOCK_ACK,
+ GPRS_NS2_EV_RX_BLOCK,
+ GPRS_NS2_EV_RX_BLOCK_ACK,
+ GPRS_NS2_EV_RX_ALIVE,
+ GPRS_NS2_EV_RX_ALIVE_ACK,
+ GPRS_NS2_EV_RX_STATUS,
+
+ GPRS_NS2_EV_RX_UNITDATA,
+
+ GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, /* called via vty for tests */
+ GPRS_NS2_EV_REQ_OM_RESET, /* vty cmd: reset */
+ GPRS_NS2_EV_REQ_OM_BLOCK, /* vty cmd: block */
+ GPRS_NS2_EV_REQ_OM_UNBLOCK, /* vty cmd: unblock*/
+ GPRS_NS2_EV_RX_BLOCK_FOREIGN, /* received a BLOCK over another NSVC */
+};
+
+static const struct value_string ns2_vc_event_names[] = {
+ { GPRS_NS2_EV_REQ_START, "REQ-START" },
+ { GPRS_NS2_EV_RX_RESET, "RX-RESET" },
+ { GPRS_NS2_EV_RX_RESET_ACK, "RX-RESET_ACK" },
+ { GPRS_NS2_EV_RX_UNBLOCK, "RX-UNBLOCK" },
+ { GPRS_NS2_EV_RX_UNBLOCK_ACK, "RX-UNBLOCK_ACK" },
+ { GPRS_NS2_EV_RX_BLOCK, "RX-BLOCK" },
+ { GPRS_NS2_EV_RX_BLOCK_FOREIGN, "RX-BLOCK_FOREIGN" },
+ { GPRS_NS2_EV_RX_BLOCK_ACK, "RX-BLOCK_ACK" },
+ { GPRS_NS2_EV_RX_ALIVE, "RX-ALIVE" },
+ { GPRS_NS2_EV_RX_ALIVE_ACK, "RX-ALIVE_ACK" },
+ { GPRS_NS2_EV_RX_STATUS, "RX-STATUS" },
+ { GPRS_NS2_EV_RX_UNITDATA, "RX-UNITDATA" },
+ { GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, "REQ-FORCE_UNCONFIGURED" },
+ { GPRS_NS2_EV_REQ_OM_RESET, "REQ-O&M-RESET"},
+ { GPRS_NS2_EV_REQ_OM_BLOCK, "REQ-O&M-BLOCK"},
+ { GPRS_NS2_EV_REQ_OM_UNBLOCK, "REQ-O&M-UNBLOCK"},
+ { 0, NULL }
+};
+
+static inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ return priv->nsvc->nse->nsi;
+}
+
+/* Start the NS-TEST procedure, either with transmitting a tx_alive,
+ * (start_tx_alive==true) or with starting tns-test */
+static void start_test_procedure(struct osmo_fsm_inst *fi, bool start_tx_alive)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+ unsigned int tout_idx;
+
+ if (osmo_timer_pending(&priv->alive.timer)) {
+ if (start_tx_alive) {
+ if (priv->alive.mode == NS_TOUT_TNS_ALIVE)
+ return;
+ } else {
+ if (priv->alive.mode == NS_TOUT_TNS_TEST)
+ return;
+ }
+ }
+
+ priv->alive.N = 0;
+
+ if (start_tx_alive) {
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started);
+ ns2_tx_alive(priv->nsvc);
+ tout_idx = NS_TOUT_TNS_ALIVE;
+ } else {
+ priv->alive.mode = NS_TOUT_TNS_TEST;
+ tout_idx = NS_TOUT_TNS_TEST;
+ }
+ LOGPFSML(fi, LOGL_DEBUG, "Starting Tns-%s of %u seconds\n",
+ tout_idx == NS_TOUT_TNS_ALIVE ? "alive" : "test", nsi->timeout[tout_idx]);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[tout_idx], 0);
+}
+
+static void stop_test_procedure(struct gprs_ns2_vc_priv *priv)
+{
+ osmo_stat_item_set(osmo_stat_item_group_get_item(priv->nsvc->statg, NS_STAT_ALIVE_DELAY), 0);
+ osmo_timer_del(&priv->alive.timer);
+}
+
+/* how many milliseconds have expired since the last alive timer start? */
+static int alive_timer_elapsed_ms(struct gprs_ns2_vc_priv *priv)
+{
+ struct timespec now, elapsed;
+
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
+ return 0;
+
+ timespecsub(&now, &priv->alive.timer_started, &elapsed);
+ return elapsed.tv_sec * 1000 + (elapsed.tv_nsec / 1000000);
+}
+
+/* we just received a NS-ALIVE-ACK; re-schedule after Tns-test */
+static void recv_test_procedure(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc = priv->nsvc;
+
+ /* ignoring ACKs without sending an ALIVE */
+ if (priv->alive.mode != NS_TOUT_TNS_ALIVE)
+ return;
+
+ priv->alive.mode = NS_TOUT_TNS_TEST;
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(nsvc->statg, NS_STAT_ALIVE_DELAY),
+ alive_timer_elapsed_ms(priv));
+}
+
+
+static void alive_timeout_handler(void *data)
+{
+ struct osmo_fsm_inst *fi = data;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ switch (priv->alive.mode) {
+ case NS_TOUT_TNS_TEST:
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ priv->alive.N = 0;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started);
+ ns2_tx_alive(priv->nsvc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ break;
+ case NS_TOUT_TNS_ALIVE:
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_ALIVE);
+ priv->alive.N++;
+
+ if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+ /* retransmission */
+ ns2_tx_alive(priv->nsvc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ } else {
+ /* lost connection */
+ if (priv->nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void ns2_st_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ stop_test_procedure(fi->priv);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static void ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+
+ priv->initiate_reset = priv->initiate_block = priv->initiator;
+ priv->nsvc->om_blocked = false;
+
+ switch (event) {
+ case GPRS_NS2_EV_REQ_START:
+ switch (priv->nsvc->mode) {
+ case GPRS_NS2_VC_MODE_ALIVE:
+ if (priv->nsvc->nse->dialect == GPRS_NS2_DIALECT_SNS) {
+ /* In IP-SNS, the NS-VC are assumed initially alive, until the alive
+ * procedure should fail at some future point */
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE);
+ }
+ break;
+ case GPRS_NS2_VC_MODE_BLOCKRESET:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+
+static void ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ 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);
+
+ stop_test_procedure(priv);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static void ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ if (priv->initiate_reset) {
+ switch (event) {
+ case GPRS_NS2_EV_RX_RESET:
+ ns2_tx_reset_ack(priv->nsvc);
+ /* fall-through */
+ case GPRS_NS2_EV_RX_RESET_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK);
+ break;
+ }
+ } else {
+ /* we are on the receiving end */
+ switch (event) {
+ case GPRS_NS2_EV_RX_RESET:
+ ns2_tx_reset_ack(priv->nsvc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 0);
+ break;
+ }
+ }
+}
+
+static void ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ if (old_state != GPRS_NS2_ST_BLOCKED) {
+ priv->N = 0;
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_BLOCKED);
+ }
+
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+ if (priv->nsvc->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, NULL);
+ }
+ } else if (priv->initiate_block) {
+ ns2_tx_unblock(priv->nsvc);
+ }
+
+ start_test_procedure(fi, true);
+}
+
+static void ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ if (priv->nsvc->om_blocked) {
+ switch (event) {
+ case GPRS_NS2_EV_RX_BLOCK_ACK:
+ priv->accept_unitdata = false;
+ osmo_timer_del(&fi->timer);
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ /* fall through */
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent
+ * from the receiving nsvc */
+ priv->accept_unitdata = false;
+ osmo_timer_del(&fi->timer);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ priv->accept_unitdata = false;
+ ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL);
+ osmo_timer_add(&fi->timer);
+ break;
+ }
+ } else if (priv->initiate_block) {
+ switch (event) {
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the block ack will be sent by the rx NSVC */
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ /* TODO: BLOCK is a UNBLOCK_NACK */
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ /* fall through */
+ case GPRS_NS2_EV_RX_UNBLOCK_ACK:
+ priv->accept_unitdata = true;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
+ 0, NS_TOUT_TNS_TEST);
+ break;
+ }
+ } else {
+ /* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */
+ switch (event) {
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the block ack will be sent by the rx NSVC */
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
+ 0, 0);
+ break;
+ }
+ }
+}
+
+static void ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_vc *nsvc = priv->nsvc;
+ struct gprs_ns2_nse *nse = nsvc->nse;
+
+ if (old_state != GPRS_NS2_ST_UNBLOCKED) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_UNBLOCKED);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change);
+ }
+
+ priv->accept_unitdata = true;
+ ns2_nse_notify_unblocked(nsvc, true);
+ ns2_prim_status_ind(nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_RECOVERY);
+
+ /* the closest interpretation of the spec would start Tns-test here first,
+ * and only send a NS-ALIVE after Tns-test has expired (i.e. setting the
+ * second argument to 'false'. However, being quick in detecting unavailability
+ * of a NS-VC seems like a good idea */
+ start_test_procedure(fi, true);
+}
+
+static void ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ switch (event) {
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ /* fall through */
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent
+ * from the receiving nsvc */
+ priv->initiate_block = false;
+ priv->accept_unitdata = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 2);
+ break;
+ }
+}
+
+static void ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case GPRS_NS2_EV_RX_ALIVE_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
+ break;
+ }
+}
+
+static void ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+
+ priv->alive.mode = NS_TOUT_TNS_TEST;
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
+
+ if (old_state != GPRS_NS2_ST_RECOVERING)
+ priv->N = 0;
+
+ start_test_procedure(fi, true);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static const struct osmo_fsm_state ns2_vc_states[] = {
+ [GPRS_NS2_ST_UNCONFIGURED] = {
+ .in_event_mask = S(GPRS_NS2_EV_REQ_START),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_UNBLOCKED),
+ .name = "UNCONFIGURED",
+ .action = ns2_st_unconfigured,
+ .onenter = ns2_st_unconfigured_onenter,
+ },
+ [GPRS_NS2_ST_RESET] = {
+ .in_event_mask = S(GPRS_NS2_EV_RX_RESET_ACK) | S(GPRS_NS2_EV_RX_RESET),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_BLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
+ .name = "RESET",
+ .action = ns2_st_reset,
+ .onenter = ns2_st_reset_onenter,
+ },
+ [GPRS_NS2_ST_BLOCKED] = {
+ .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_BLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_BLOCK_FOREIGN),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_UNBLOCKED) |
+ S(GPRS_NS2_ST_BLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
+ .name = "BLOCKED",
+ .action = ns2_st_blocked,
+ .onenter = ns2_st_blocked_onenter,
+ },
+ [GPRS_NS2_ST_UNBLOCKED] = {
+ .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_BLOCK_FOREIGN),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_BLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
+ .name = "UNBLOCKED",
+ .action = ns2_st_unblocked,
+ .onenter = ns2_st_unblocked_on_enter,
+ },
+
+ /* ST_RECOVERING is only used on VC without RESET/BLOCK */
+ [GPRS_NS2_ST_RECOVERING] = {
+ .in_event_mask = S(GPRS_NS2_EV_RX_ALIVE_ACK),
+ .out_state_mask = S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_UNBLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
+ .name = "RECOVERING",
+ .action = ns2_st_alive,
+ .onenter = ns2_st_alive_onenter,
+ },
+};
+
+static int ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ switch (fi->state) {
+ case GPRS_NS2_ST_RESET:
+ if (priv->initiate_reset) {
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_RESET);
+ priv->N++;
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_RESET_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ } else {
+ priv->N = 0;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
+ }
+ break;
+ case GPRS_NS2_ST_BLOCKED:
+ if (priv->initiate_block) {
+ priv->N++;
+ if (priv->nsvc->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 {
+ 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;
+ case GPRS_NS2_ST_RECOVERING:
+ if (priv->initiate_reset) {
+ priv->N++;
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0);
+ } else {
+ priv->N = 0;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0);
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void ns2_recv_unitdata(struct osmo_fsm_inst *fi,
+ struct msgb *msg)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct osmo_gprs_ns2_prim nsp = {};
+ uint16_t bvci;
+
+ if (msgb_l2len(msg) < sizeof(*nsh) + 3) {
+ msgb_free(msg);
+ return;
+ }
+
+ /* TODO: 7.1: For an IP sub-network, an NS-UNITDATA PDU
+ * for a PTP BVC may indicate a request to change the IP endpoint
+ * and/or a response to a change in the IP endpoint. */
+
+ /* TODO: nsh->data[0] -> C/R only valid in IP SNS */
+ bvci = nsh->data[1] << 8 | nsh->data[2];
+
+ msg->l3h = &nsh->data[3];
+ nsp.bvci = bvci;
+ nsp.nsei = priv->nsvc->nse->nsei;
+
+ /* 10.3.9 NS SDU Control Bits */
+ if (nsh->data[0] & 0x1)
+ nsp.u.unitdata.change = GPRS_NS2_ENDPOINT_REQUEST_CHANGE;
+
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA,
+ PRIM_OP_INDICATION, msg);
+ nsi->cb(&nsp.oph, nsi->cb_data);
+}
+
+static void ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
+ uint32_t event,
+ void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct tlv_parsed *tp;
+ struct msgb *msg = data;
+ uint8_t cause;
+
+ switch (event) {
+ case GPRS_NS2_EV_REQ_OM_RESET:
+ if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET)
+ break;
+ /* move the FSM into reset */
+ if (fi->state != GPRS_NS2_ST_RESET) {
+ priv->initiate_reset = true;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
+ }
+ break;
+ case GPRS_NS2_EV_RX_RESET:
+ if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET)
+ break;
+
+ /* move the FSM into reset */
+ if (fi->state != GPRS_NS2_ST_RESET) {
+ priv->initiate_reset = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
+ }
+ /* pass the event down into FSM action */
+ ns2_st_reset(fi, event, data);
+ break;
+ case GPRS_NS2_EV_RX_ALIVE:
+ switch (fi->state) {
+ case GPRS_NS2_ST_UNCONFIGURED:
+ case GPRS_NS2_ST_RESET:
+ /* ignore ALIVE */
+ break;
+ default:
+ ns2_tx_alive_ack(priv->nsvc);
+ }
+ break;
+ case GPRS_NS2_EV_RX_ALIVE_ACK:
+ /* for VCs without RESET/BLOCK/UNBLOCK, the connections comes after ALIVE_ACK unblocked */
+ if (fi->state == GPRS_NS2_ST_RECOVERING)
+ ns2_st_alive(fi, event, data);
+ else
+ recv_test_procedure(fi);
+ break;
+ case GPRS_NS2_EV_RX_UNITDATA:
+ /* UNITDATA has to handle the release of msg.
+ * If send upwards (gprs_ns2_recv_unitdata) it must NOT free
+ * the msg, the upper layer has to do it.
+ * Otherwise the msg must be freed.
+ */
+
+ LOG_NS_DATA(priv->nsvc, "Rx", NS_PDUT_UNITDATA, LOGL_INFO, "\n");
+ switch (fi->state) {
+ case GPRS_NS2_ST_BLOCKED:
+ /* 7.2.1: the BLOCKED_ACK might be lost */
+ if (priv->accept_unitdata) {
+ ns2_recv_unitdata(fi, msg);
+ return;
+ }
+
+ ns2_tx_status(priv->nsvc,
+ NS_CAUSE_NSVC_BLOCKED,
+ 0, msg, NULL);
+ break;
+ /* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */
+ case GPRS_NS2_ST_RECOVERING:
+ case GPRS_NS2_ST_UNBLOCKED:
+ ns2_recv_unitdata(fi, msg);
+ return;
+ }
+
+ msgb_free(msg);
+ break;
+ case GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED:
+ if (fi->state != GPRS_NS2_ST_UNCONFIGURED) {
+ /* Force the NSVC back to its initial state */
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ break;
+ case GPRS_NS2_EV_REQ_OM_BLOCK:
+ /* vty cmd: block */
+ priv->initiate_block = true;
+ priv->nsvc->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->nsvc->om_blocked)
+ return;
+ priv->nsvc->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;
+ case GPRS_NS2_EV_RX_STATUS:
+ tp = data;
+ cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ switch (cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ if (fi->state != GPRS_NS2_ST_BLOCKED) {
+ LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported blocked state.\n");
+ priv->initiate_block = false;
+ priv->accept_unitdata = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ }
+ break;
+ case NS_CAUSE_NSVC_UNKNOWN:
+ if (fi->state != GPRS_NS2_ST_RESET && fi->state != GPRS_NS2_ST_UNCONFIGURED) {
+ LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported unknown nsvc.\n");
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
+ break;
+ }
+
+ break;
+ }
+}
+
+static void ns2_vc_fsm_clean(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause cause)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ osmo_timer_del(&priv->alive.timer);
+}
+
+static struct osmo_fsm ns2_vc_fsm = {
+ .name = "GPRS-NS2-VC",
+ .states = ns2_vc_states,
+ .num_states = ARRAY_SIZE(ns2_vc_states),
+ .allstate_event_mask = S(GPRS_NS2_EV_RX_UNITDATA) |
+ S(GPRS_NS2_EV_RX_RESET) |
+ S(GPRS_NS2_EV_RX_ALIVE) |
+ S(GPRS_NS2_EV_RX_ALIVE_ACK) |
+ S(GPRS_NS2_EV_RX_STATUS) |
+ S(GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED) |
+ S(GPRS_NS2_EV_REQ_OM_RESET) |
+ S(GPRS_NS2_EV_REQ_OM_BLOCK) |
+ S(GPRS_NS2_EV_REQ_OM_UNBLOCK),
+ .allstate_action = ns2_vc_fsm_allstate_action,
+ .cleanup = ns2_vc_fsm_clean,
+ .timer_cb = ns2_vc_fsm_timer_cb,
+ .event_names = ns2_vc_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
+
+/*!
+ * \brief gprs_ns2_vc_fsm_alloc
+ * \param ctx
+ * \param vc
+ * \param id a char representation of the virtual curcuit
+ * \param initiator initiator is the site which starts the connection. Usually the BSS.
+ * \return NULL on error, otherwise the fsm
+ */
+struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+ const char *id, bool initiator)
+{
+ struct osmo_fsm_inst *fi;
+ struct gprs_ns2_vc_priv *priv;
+
+ fi = osmo_fsm_inst_alloc(&ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ nsvc->fi = fi;
+ priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv);
+ priv->nsvc = nsvc;
+ priv->initiator = initiator;
+
+ osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi);
+
+ return fi;
+}
+
+/*! Start a NS-VC FSM.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc)
+{
+ /* allows to call this function even for started nsvc by gprs_ns2_start_alive_all_nsvcs */
+ if (nsvc->fi->state == GPRS_NS2_ST_UNCONFIGURED)
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_START, NULL);
+ return 0;
+}
+
+/*! Reset a NS-VC FSM.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_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)
+{
+ struct gprs_ns2_vc_priv *priv = nsvc->fi->priv;
+ if (priv->nsvc->om_blocked)
+ return -EALREADY;
+
+ 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)
+{
+ struct gprs_ns2_vc_priv *priv = nsvc->fi->priv;
+ if (!priv->nsvc->om_blocked)
+ return -EALREADY;
+
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_UNBLOCK, NULL);
+}
+
+/*! Reset a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_reset(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_RESET, 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
+ * \param tp parsed TLVs of the received message
+ * \return 0 on success; negative on error */
+int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct gprs_ns2_vc *target_nsvc = nsvc;
+ struct osmo_fsm_inst *fi = nsvc->fi;
+ int rc = 0;
+ uint8_t cause;
+ uint16_t nsei, nsvci;
+
+ /* TODO: 7.2: on UNBLOCK/BLOCK: check if NS-VCI is correct,
+ * if not answer STATUS with "NS-VC unknown" */
+ /* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */
+
+ if (ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) {
+ /* don't answer on a STATUS with a STATUS */
+ if (nsh->pdu_type != NS_PDUT_STATUS) {
+ rc = ns2_tx_status(nsvc, cause, 0, msg, NULL);
+ goto out;
+ }
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ nsei = tlvp_val16be(tp, NS_IE_NSEI);
+ if (nsei != nsvc->nse->nsei) {
+ /* 48.016 § 7.3.1 send, RESET_ACK to wrong NSVCI + ignore */
+ if (nsh->pdu_type == NS_PDUT_RESET)
+ ns2_tx_reset_ack(nsvc);
+
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSEI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nse->nsei, nsei);
+ goto out;
+ }
+ }
+
+ if (nsvc->nsvci_is_valid && TLVP_PRESENT(tp, NS_IE_VCI)) {
+ nsvci = tlvp_val16be(tp, NS_IE_VCI);
+ if (nsvci != nsvc->nsvci) {
+ /* 48.016 § 7.3.1 send RESET_ACK to wrong NSVCI + ignore */
+ if (nsh->pdu_type == NS_PDUT_RESET) {
+ ns2_tx_reset_ack(nsvc);
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nsvci, nsvci);
+ goto out;
+ } else if (nsh->pdu_type == NS_PDUT_BLOCK || nsh->pdu_type == NS_PDUT_STATUS) {
+ /* this is a PDU received over a NSVC and reports a status/block for another NSVC */
+ target_nsvc = gprs_ns2_nsvc_by_nsvci(nsvc->nse->nsi, nsvci);
+ if (!target_nsvc) {
+ LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for unknown NSVC (NSVCI %d)\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci);
+ if (nsh->pdu_type == NS_PDUT_BLOCK)
+ ns2_tx_status(nsvc, NS_CAUSE_NSVC_UNKNOWN, 0, msg, &nsvci);
+ goto out;
+ }
+
+ if (target_nsvc->nse != nsvc->nse) {
+ LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for a NSVC (NSVCI %d) but it belongs to a different NSE!\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci);
+ goto out;
+ }
+
+ /* the status/block will be passed to the nsvc/target nsvc in the switch */
+ } else {
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI=%05u. Ignoring PDU.\n", nsvci);
+ goto out;
+ }
+ }
+ }
+
+ switch (nsh->pdu_type) {
+ case NS_PDUT_RESET:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET, tp);
+ break;
+ case NS_PDUT_RESET_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET_ACK, tp);
+ break;
+ case NS_PDUT_BLOCK:
+ if (target_nsvc != nsvc) {
+ osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_BLOCK_FOREIGN, tp);
+ ns2_tx_block_ack(nsvc, &nsvci);
+ } else {
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK, tp);
+ }
+ break;
+ case NS_PDUT_BLOCK_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK_ACK, tp);
+ break;
+ case NS_PDUT_UNBLOCK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK, tp);
+ break;
+ case NS_PDUT_UNBLOCK_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK_ACK, tp);
+ break;
+ case NS_PDUT_ALIVE:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE, tp);
+ break;
+ case NS_PDUT_ALIVE_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE_ACK, tp);
+ break;
+ case NS_PDUT_UNITDATA:
+ /* UNITDATA have to free msg because it might send the msg layer upwards */
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNITDATA, msg);
+ return 0;
+ case NS_PDUT_STATUS:
+ osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_STATUS, tp);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ break;
+ }
+
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+/*! is the given NS-VC unblocked? */
+int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
+{
+ return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED);
+}
+
+/* initialize osmo_ctx on main tread */
+static __attribute__((constructor)) void on_dso_load_ctx(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&ns2_vc_fsm) == 0);
+}
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
new file mode 100644
index 00000000..32de49d4
--- /dev/null
+++ b/src/gb/gprs_ns2_vty.c
@@ -0,0 +1,2351 @@
+/*! \file gprs_ns2_vty.c
+ * VTY interface for our GPRS Networks Service (NS) implementation. */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ * (C) 2021 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/vty.h>
+
+#include "gprs_ns2_internal.h"
+
+#define SHOW_NS_STR "Display information about the NS protocol\n"
+#define NSVCI_STR "NS Virtual Connection ID (NS-VCI)\n"
+#define DLCI_STR "Data Link connection identifier\n"
+
+static struct gprs_ns2_inst *vty_nsi = NULL;
+static struct osmo_fr_network *vty_fr_network = NULL;
+static struct llist_head binds;
+static struct llist_head nses;
+static struct llist_head ip_sns_default_binds;
+
+struct vty_bind {
+ struct llist_head list;
+ const char *name;
+ enum gprs_ns2_ll ll;
+ int dscp;
+ uint8_t priority;
+ bool accept_ipaccess;
+ bool accept_sns;
+ uint8_t ip_sns_sig_weight;
+ uint8_t ip_sns_data_weight;
+};
+
+struct vty_nse {
+ struct llist_head list;
+ uint16_t nsei;
+ /* list of binds which are valid for this nse. Only IP-SNS uses this
+ * to allow `no listen ..` in the bind context. So "half" created binds are valid for
+ * IP-SNS. This allows changing the bind ip without modifying all NSEs afterwards */
+ struct llist_head binds;
+};
+
+/* used by IP-SNS to connect multiple vty_nse_bind to a vty_nse */
+struct vty_nse_bind {
+ struct llist_head list;
+ struct vty_bind *vbind;
+};
+
+/* TODO: this should into osmo timer */
+const struct value_string gprs_ns_timer_strs[] = {
+ { NS_TOUT_TNS_BLOCK, TNS_BLOCK_STR },
+ { NS_TOUT_TNS_BLOCK_RETRIES, TNS_BLOCK_RETRIES_STR },
+ { NS_TOUT_TNS_RESET, TNS_RESET_STR },
+ { NS_TOUT_TNS_RESET_RETRIES, TNS_RESET_RETRIES_STR },
+ { NS_TOUT_TNS_TEST, TNS_TEST_STR },
+ { NS_TOUT_TNS_ALIVE, TNS_ALIVE_STR },
+ { NS_TOUT_TNS_ALIVE_RETRIES, TNS_ALIVE_RETRIES_STR },
+ { NS_TOUT_TSNS_PROV, TSNS_PROV_STR },
+ { NS_TOUT_TSNS_SIZE_RETRIES, TSNS_SIZE_RETRIES_STR },
+ { NS_TOUT_TSNS_CONFIG_RETRIES, TSNS_CONFIG_RETRIES_STR },
+ { NS_TOUT_TSNS_PROCEDURES_RETRIES, TSNS_PROCEDURES_RETRIES_STR },
+ { 0, NULL }
+};
+
+const struct value_string vty_fr_role_names[] = {
+ { FR_ROLE_USER_EQUIPMENT, "fr" },
+ { FR_ROLE_NETWORK_EQUIPMENT, "frnet" },
+ { 0, NULL }
+};
+
+const struct value_string vty_ll_names[] = {
+ { GPRS_NS2_LL_FR, "fr" },
+ { GPRS_NS2_LL_FR_GRE, "frgre" },
+ { GPRS_NS2_LL_UDP, "udp" },
+ { 0, NULL }
+};
+
+static struct vty_bind *vty_bind_by_name(const char *name)
+{
+ struct vty_bind *vbind;
+ llist_for_each_entry(vbind, &binds, list) {
+ if (!strcmp(vbind->name, name))
+ return vbind;
+ }
+ return NULL;
+}
+
+static struct vty_bind *vty_bind_alloc(const char *name)
+{
+ struct vty_bind *vbind = talloc_zero(vty_nsi, struct vty_bind);
+ if (!vbind)
+ return NULL;
+
+ vbind->name = talloc_strdup(vty_nsi, name);
+ if (!vbind->name) {
+ talloc_free(vbind);
+ return NULL;
+ }
+
+ vbind->ip_sns_sig_weight = 1;
+ vbind->ip_sns_data_weight = 1;
+ llist_add_tail(&vbind->list, &binds);
+ return vbind;
+}
+
+static void vty_bind_free(struct vty_bind *vbind)
+{
+ if (!vbind)
+ return;
+
+ llist_del(&vbind->list);
+ talloc_free(vbind);
+}
+
+static struct vty_nse *vty_nse_by_nsei(uint16_t nsei)
+{
+ struct vty_nse *vnse;
+ llist_for_each_entry(vnse, &nses, list) {
+ if (vnse->nsei == nsei)
+ return vnse;
+ }
+ return NULL;
+}
+
+static struct vty_nse *vty_nse_alloc(uint16_t nsei)
+{
+ struct vty_nse *vnse = talloc_zero(vty_nsi, struct vty_nse);
+ if (!vnse)
+ return NULL;
+
+ vnse->nsei = nsei;
+ INIT_LLIST_HEAD(&vnse->binds);
+ llist_add_tail(&vnse->list, &nses);
+ return vnse;
+}
+
+static void vty_nse_free(struct vty_nse *vnse)
+{
+ if (!vnse)
+ return;
+
+ llist_del(&vnse->list);
+ /* all vbind of the nse will be freed by talloc */
+ talloc_free(vnse);
+}
+
+static int vty_nse_add_vbind(struct vty_nse *vnse, struct vty_bind *vbind)
+{
+ struct vty_nse_bind *vnse_bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP)
+ return -EINVAL;
+
+ llist_for_each_entry(vnse_bind, &vnse->binds, list) {
+ if (vnse_bind->vbind == vbind)
+ return -EALREADY;
+ }
+
+ vnse_bind = talloc(vnse, struct vty_nse_bind);
+ if (!vnse_bind)
+ return -ENOMEM;
+ vnse_bind->vbind = vbind;
+
+ llist_add_tail(&vnse_bind->list, &vnse->binds);
+ return 0;
+}
+
+static int vty_nse_remove_vbind(struct vty_nse *vnse, struct vty_bind *vbind)
+{
+ struct vty_nse_bind *vnse_bind, *tmp;
+ if (vbind->ll != GPRS_NS2_LL_UDP)
+ return -EINVAL;
+
+ llist_for_each_entry_safe(vnse_bind, tmp, &vnse->binds, list) {
+ if (vnse_bind->vbind == vbind) {
+ llist_del(&vnse_bind->list);
+ talloc_free(vnse_bind);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+/* check if the NSE still has SNS configuration */
+static bool vty_nse_check_sns(struct gprs_ns2_nse *nse) {
+ struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei);
+
+ int count = gprs_ns2_sns_count(nse);
+ if (count > 0) {
+ /* there are other sns endpoints */
+ return true;
+ }
+
+ if (!vnse)
+ return false;
+
+ if (llist_empty(&vnse->binds))
+ return false;
+
+ return true;
+}
+
+static struct cmd_node ns_node = {
+ L_NS_NODE,
+ "%s(config-ns)# ",
+ 1,
+};
+
+DEFUN(cfg_ns, cfg_ns_cmd,
+ "ns",
+ "Configure the GPRS Network Service")
+{
+ vty->node = L_NS_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
+ "timer " NS_TIMERS " <0-65535>",
+ "Network Service Timer\n"
+ NS_TIMERS_HELP "Timer Value\n")
+{
+ int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+ int val = atoi(argv[1]);
+
+ if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+ return CMD_WARNING;
+
+ vty_nsi->timeout[idx] = val;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_nsei, cfg_ns_nsei_cmd,
+ "nse <0-65535> [ip-sns-role-sgsn]",
+ "Persistent NS Entity\n"
+ "NS Entity ID (NSEI)\n"
+ "Create NSE in SGSN role (default: BSS)\n"
+ )
+{
+ struct gprs_ns2_nse *nse;
+ struct vty_nse *vnse;
+ uint16_t nsei = atoi(argv[0]);
+ bool sgsn_role = false;
+ bool free_vnse = false;
+ if (argc > 1 && !strcmp(argv[1], "ip-sns-role-sgsn"))
+ sgsn_role = true;
+
+ vnse = vty_nse_by_nsei(nsei);
+ if (!vnse) {
+ vnse = vty_nse_alloc(nsei);
+ if (!vnse) {
+ vty_out(vty, "Failed to create vty NSE!%s", VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ free_vnse = true;
+ }
+
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse2(vty_nsi, nsei, GPRS_NS2_LL_UNDEF, GPRS_NS2_DIALECT_UNDEF,
+ sgsn_role);
+ if (!nse) {
+ vty_out(vty, "Failed to create NSE!%s", VTY_NEWLINE);
+ goto err;
+ }
+ nse->persistent = true;
+ }
+
+ if (!nse->persistent) {
+ /* TODO: should the dynamic NSE removed? */
+ vty_out(vty, "A dynamic NSE with the specified NSEI already exists%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ vty->node = L_NS_NSE_NODE;
+ vty->index = nse;
+
+ return CMD_SUCCESS;
+
+err:
+ if (free_vnse)
+ talloc_free(vnse);
+
+ return CMD_ERR_INCOMPLETE;
+}
+
+DEFUN(cfg_no_ns_nsei, cfg_no_ns_nsei_cmd,
+ "no nse <0-65535>",
+ NO_STR
+ "Delete a Persistent NS Entity\n"
+ "NS Entity ID (NSEI)\n"
+ )
+{
+ struct gprs_ns2_nse *nse;
+ struct vty_nse *vnse;
+ uint16_t nsei = atoi(argv[0]);
+
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei);
+ if (!nse) {
+ vty_out(vty, "Can not find NS Entity %s%s", argv[0], VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ }
+
+ if (!nse->persistent) {
+ vty_out(vty, "Ignoring non-persistent NS Entity%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "Deleting NS Entity %u%s", nse->nsei, VTY_NEWLINE);
+ gprs_ns2_free_nse(nse);
+
+ vnse = vty_nse_by_nsei(nsei);
+ vty_nse_free(vnse);
+
+ return CMD_SUCCESS;
+}
+
+/* TODO: add fr/gre */
+DEFUN(cfg_ns_bind, cfg_ns_bind_cmd,
+ "bind (fr|udp) ID",
+ "Configure local Bind\n"
+ "Frame Relay\n" "UDP/IP\n"
+ "Unique identifier for this bind (to reference from NS-VCs, NSEs, ...)\n"
+ )
+{
+ const char *nstype = argv[0];
+ const char *name = argv[1];
+ struct vty_bind *vbind;
+ enum gprs_ns2_ll ll;
+ int rc;
+
+ rc = get_string_value(vty_ll_names, nstype);
+ if (rc < 0)
+ return CMD_WARNING;
+ ll = (enum gprs_ns2_ll) rc;
+
+ if (!osmo_identifier_valid(name)) {
+ vty_out(vty, "Invalid ID. The ID should be only alphanumeric.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (vbind) {
+ if (vbind->ll != ll) {
+ vty_out(vty, "A bind with the specified ID already exists with a different type (fr|frgre|udp)!%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ vbind = vty_bind_alloc(name);
+ if (!vbind) {
+ vty_out(vty, "Can not create bind - out of memory%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vbind->ll = ll;
+ }
+
+ vty->index = vbind;
+ vty->node = L_NS_BIND_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind, cfg_no_ns_bind_cmd,
+ "no bind ID",
+ NO_STR
+ "Delete a bind\n"
+ "Unique identifier for this bind\n"
+ )
+{
+ struct vty_bind *vbind;
+ struct gprs_ns2_vc_bind *bind;
+ const char *name = argv[0];
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "bind %s does not exist!%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty_bind_free(vbind);
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (bind)
+ gprs_ns2_free_bind(bind);
+ return CMD_SUCCESS;
+}
+
+
+static void config_write_vbind(struct vty *vty, struct vty_bind *vbind)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *addr;
+ struct osmo_sockaddr_str addr_str;
+ const char *netif, *frrole_str, *llstr;
+ enum osmo_fr_role frrole;
+
+ llstr = get_value_string_or_null(vty_ll_names, vbind->ll);
+ if (!llstr)
+ return;
+ vty_out(vty, " bind %s %s%s", llstr, vbind->name, VTY_NEWLINE);
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ switch (vbind->ll) {
+ case GPRS_NS2_LL_FR:
+ if (bind) {
+ netif = gprs_ns2_fr_bind_netif(bind);
+ if (!netif)
+ return;
+ frrole = gprs_ns2_fr_bind_role(bind);
+ if ((int) frrole == -1)
+ return;
+ frrole_str = get_value_string_or_null(vty_fr_role_names, frrole);
+ if (netif && frrole_str)
+ vty_out(vty, " fr %s %s%s", netif, frrole_str, VTY_NEWLINE);
+ }
+ break;
+ case GPRS_NS2_LL_UDP:
+ if (bind) {
+ addr = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas)) {
+ vty_out(vty, " listen %s %u%s", addr_str.ip, addr_str.port,
+ VTY_NEWLINE);
+ }
+ }
+ if (vbind->accept_ipaccess)
+ vty_out(vty, " accept-ipaccess%s", VTY_NEWLINE);
+ if (vbind->accept_sns)
+ vty_out(vty, " accept-dynamic-ip-sns%s", VTY_NEWLINE);
+ if (vbind->dscp)
+ vty_out(vty, " dscp %u%s", vbind->dscp, VTY_NEWLINE);
+ if (vbind->priority)
+ vty_out(vty, " socket-priority %u%s", vbind->priority, VTY_NEWLINE);
+ vty_out(vty, " ip-sns signalling-weight %u data-weight %u%s",
+ vbind->ip_sns_sig_weight, vbind->ip_sns_data_weight, VTY_NEWLINE);
+ break;
+ default:
+ return;
+ }
+}
+
+static void config_write_nsvc(struct vty *vty, const struct gprs_ns2_vc *nsvc)
+{
+ const char *netif;
+ uint16_t dlci;
+ const struct osmo_sockaddr *addr;
+ struct osmo_sockaddr_str addr_str;
+
+ switch (nsvc->nse->ll) {
+ case GPRS_NS2_LL_UNDEF:
+ break;
+ case GPRS_NS2_LL_UDP:
+ switch (nsvc->nse->dialect) {
+ case GPRS_NS2_DIALECT_IPACCESS:
+ addr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!addr)
+ break;
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas))
+ break;
+ vty_out(vty, " nsvc ipa %s %s %u nsvci %u%s",
+ nsvc->bind->name, addr_str.ip, addr_str.port,
+ nsvc->nsvci, VTY_NEWLINE);
+ break;
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ addr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!addr)
+ break;
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas))
+ break;
+ vty_out(vty, " nsvc udp %s %s %u%s",
+ nsvc->bind->name, addr_str.ip, addr_str.port, VTY_NEWLINE);
+ break;
+ default:
+ break;
+ }
+ break;
+ case GPRS_NS2_LL_FR:
+ netif = gprs_ns2_fr_bind_netif(nsvc->bind);
+ if (!netif)
+ break;
+ dlci = gprs_ns2_fr_nsvc_dlci(nsvc);
+ if (!dlci)
+ break;
+ OSMO_ASSERT(nsvc->nsvci_is_valid);
+ vty_out(vty, " nsvc fr %s dlci %u nsvci %u%s",
+ netif, dlci, nsvc->nsvci, VTY_NEWLINE);
+ break;
+ case GPRS_NS2_LL_FR_GRE:
+ break;
+ }
+}
+
+static void _config_write_ns_nse(struct vty *vty, struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei);
+ struct vty_nse_bind *vbind;
+
+ OSMO_ASSERT(vnse);
+
+ vty_out(vty, " nse %u%s%s", nse->nsei,
+ nse->ip_sns_role_sgsn ? " ip-sns-role-sgsn" : "", VTY_NEWLINE);
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ ns2_sns_write_vty(vty, nse);
+ llist_for_each_entry(vbind, &vnse->binds, list) {
+ vty_out(vty, " ip-sns-bind %s%s", vbind->vbind->name, VTY_NEWLINE);
+ }
+ break;
+ default:
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ config_write_nsvc(vty, nsvc);
+ }
+ break;
+ }
+}
+
+static int config_write_ns_nse(struct vty *vty)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &vty_nsi->nse, list) {
+ if (!nse->persistent)
+ continue;
+
+ _config_write_ns_nse(vty, nse);
+ }
+
+ return 0;
+}
+
+static int config_write_ns_bind(struct vty *vty)
+{
+ struct vty_bind *vbind;
+
+ llist_for_each_entry(vbind, &binds, list) {
+ config_write_vbind(vty, vbind);
+ }
+
+ return 0;
+}
+
+static int config_write_ns(struct vty *vty)
+{
+ struct vty_nse_bind *vbind;
+ unsigned int i;
+ int ret;
+
+ vty_out(vty, "ns%s", VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++)
+ vty_out(vty, " timer %s %u%s",
+ get_value_string(gprs_ns_timer_strs, i),
+ vty_nsi->timeout[i], VTY_NEWLINE);
+
+ if (vty_nsi->txqueue_max_length != NS_DEFAULT_TXQUEUE_MAX_LENGTH)
+ vty_out(vty, " txqueue-max-length %u%s", vty_nsi->txqueue_max_length, VTY_NEWLINE);
+
+ ret = config_write_ns_bind(vty);
+ if (ret)
+ return ret;
+
+ llist_for_each_entry(vbind, &ip_sns_default_binds, list) {
+ vty_out(vty, " ip-sns-default bind %s%s", vbind->vbind->name, VTY_NEWLINE);
+ }
+
+ ret = config_write_ns_nse(vty);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+
+static struct cmd_node ns_bind_node = {
+ L_NS_BIND_NODE,
+ "%s(config-ns-bind)# ",
+ 1,
+};
+
+DEFUN(cfg_ns_bind_listen, cfg_ns_bind_listen_cmd,
+ "listen " VTY_IPV46_CMD " <1-65535>",
+ "Configure local IP + Port of this bind\n"
+ "Local IPv4 Address\n" "Local IPv6 Address\n"
+ "Local UDP Port\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ int rc;
+ const char *addr_str = argv[0];
+ unsigned int port = atoi(argv[1]);
+ struct osmo_sockaddr_str sockaddr_str;
+ struct osmo_sockaddr sockaddr;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "listen can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&sockaddr_str, addr_str, port)) {
+ vty_out(vty, "Can not parse the Address %s %s%s", argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ osmo_sockaddr_str_to_sockaddr(&sockaddr_str, &sockaddr.u.sas);
+ if (gprs_ns2_ip_bind_by_sockaddr(vty_nsi, &sockaddr)) {
+ vty_out(vty, "A bind with the specified address already exists!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gprs_ns2_ip_bind(vty_nsi, vbind->name, &sockaddr, vbind->dscp, &bind);
+ if (rc != 0) {
+ vty_out(vty, "Failed to create the bind (rc %d)!%s", rc, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind->accept_ipaccess = vbind->accept_ipaccess;
+ bind->accept_sns = vbind->accept_sns;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind_listen, cfg_no_ns_bind_listen_cmd,
+ "no listen",
+ NO_STR
+ "Delete a IP/Port assignment\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no listen can be only used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (!bind)
+ return CMD_ERR_NOTHING_TODO;
+
+ OSMO_ASSERT(bind->ll == GPRS_NS2_LL_UDP);
+ gprs_ns2_free_bind(bind);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_dscp, cfg_ns_bind_dscp_cmd,
+ "dscp <0-63>",
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint16_t dscp = atoi(argv[0]);
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->dscp = dscp;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind_dscp, cfg_no_ns_bind_dscp_cmd,
+ "no dscp",
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint16_t dscp = 0;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->dscp = dscp;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_priority, cfg_ns_bind_priority_cmd,
+ "socket-priority <0-255>",
+ "Set socket priority on the UDP socket\n" "Priority Value (>6 requires CAP_NET_ADMIN)\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint8_t prio = atoi(argv[0]);
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->priority = prio;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_priority(bind, prio);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_ipaccess, cfg_ns_bind_ipaccess_cmd,
+ "accept-ipaccess",
+ "Allow to create dynamic NS Entity by NS Reset PDU on UDP (ip.access style)\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "accept-ipaccess can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_ipaccess = true;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_ipaccess = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind_ipaccess, cfg_no_ns_bind_ipaccess_cmd,
+ "no accept-ipaccess",
+ NO_STR
+ "Reject NS Reset PDU on UDP (ip.access style)\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no accept-ipaccess can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_ipaccess = false;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_ipaccess = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_accept_sns, cfg_ns_bind_accept_sns_cmd,
+ "accept-dynamic-ip-sns",
+ "Allow to create dynamic NS Entities by IP-SNS PDUs\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "accept-dynamic-ip-sns can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_sns = true;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_sns = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind_accept_sns, cfg_no_ns_bind_accept_sns_cmd,
+ "no accept-dynamic-ip-sns",
+ NO_STR
+ "Disable dynamic creation of NS Entities by IP-SNS PDUs\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no accept-dynamic-ip-sns can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_sns = false;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_sns = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_ip_sns_weight, cfg_ns_bind_ip_sns_weight_cmd,
+ "ip-sns signalling-weight <0-254> data-weight <0-254>",
+ "IP SNS\n"
+ "signalling weight used by IP-SNS dynamic configuration\n"
+ "signalling weight used by IP-SNS dynamic configuration\n"
+ "data weight used by IP-SNS dynamic configuration\n"
+ "data weight used by IP-SNS dynamic configuration\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ int signalling = atoi(argv[0]);
+ int data = atoi(argv[1]);
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns signalling-weight <0-254> data-weight <0-254> can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->ip_sns_data_weight = data;
+ vbind->ip_sns_sig_weight = signalling;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_sns_weight(bind, signalling, data);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_fr, cfg_ns_bind_fr_cmd,
+ "fr NETIF (fr|frnet)",
+ "frame relay\n"
+ IFNAME_STR
+ "fr (user) is used by BSS or SGSN attached to UNI of a FR network\n"
+ "frnet (network) is used by SGSN if BSS is directly attached\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ const char *netif = argv[0];
+ const char *role = argv[1];
+
+ int rc = 0;
+ enum osmo_fr_role frrole;
+
+ if (vbind->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "fr can be only used with frame relay bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(role, "fr"))
+ frrole = FR_ROLE_USER_EQUIPMENT;
+ else if (!strcmp(role, "frnet"))
+ frrole = FR_ROLE_NETWORK_EQUIPMENT;
+ else
+ return CMD_WARNING;
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (bind) {
+ vty_out(vty, "Interface %s already used.%s", netif, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gprs_ns2_fr_bind(vty_nsi, vbind->name, netif, vty_fr_network, frrole, &bind);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to bind interface %s on fr. Err: %d\n", netif, rc);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind_fr, cfg_no_ns_bind_fr_cmd,
+ "no fr NETIF",
+ NO_STR
+ "Delete a frame relay link\n"
+ "Delete a frame relay link\n"
+ IFNAME_STR
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ const char *netif = argv[0];
+
+ if (vbind->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "fr can be only used with frame relay bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Interface not found.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(bind->name, vbind->name)) {
+ vty_out(vty, "The specified interface is not bound to this bind.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_bind(bind);
+ return CMD_SUCCESS;
+}
+
+
+static struct cmd_node ns_nse_node = {
+ L_NS_NSE_NODE,
+ "%s(config-ns-nse)# ",
+ 1,
+};
+
+DEFUN(cfg_ns_nse_nsvc_fr, cfg_ns_nse_nsvc_fr_cmd,
+ "nsvc fr NETIF dlci <16-1007> nsvci <0-65535>",
+ "NS Virtual Connection\n"
+ "frame relay\n"
+ "frame relay interface. Must be registered via fr vty\n"
+ NSVCI_STR
+ NSVCI_STR
+ DLCI_STR
+ DLCI_STR
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *netif = argv[0];
+ uint16_t dlci = atoi(argv[1]);
+ uint16_t nsvci = atoi(argv[2]);
+ bool dialect_modified = false;
+ bool ll_modified = false;
+
+ if (nse->ll != GPRS_NS2_LL_FR && nse->ll != GPRS_NS2_LL_UNDEF) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_RESETBLOCK && nse->dialect != GPRS_NS2_DIALECT_UNDEF) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_FR;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ dialect_modified = true;
+ }
+
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Can not find fr interface \"%s\". Please configure it via fr vty.%s",
+ netif, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (gprs_ns2_fr_nsvc_by_dlci(bind, dlci)) {
+ vty_out(vty, "A NS-VC with the specified DLCI already exist!%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci)) {
+ vty_out(vty, "A NS-VC with the specified NS-VCI already exist!%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci);
+ if (!nsvc) {
+ /* Could not create NS-VC, connect failed */
+ vty_out(vty, "Failed to create the NS-VC%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->persistent = true;
+ return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_nsvc_fr_dlci, cfg_no_ns_nse_nsvc_fr_dlci_cmd,
+ "no nsvc fr NETIF dlci <16-1007>",
+ NO_STR
+ "Delete frame relay NS-VC\n"
+ "frame relay\n"
+ "frame relay interface. Must be registered via fr vty\n"
+ DLCI_STR
+ DLCI_STR
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *netif = argv[0];
+ uint16_t dlci = atoi(argv[1]);
+
+ if (nse->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "This NSE doesn't support frame relay.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Can not find fr interface \"%s\"%s",
+ netif, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ }
+
+ nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci);
+ if (!nsvc) {
+ vty_out(vty, "Can not find a NS-VC on fr interface %s with dlci %u%s",
+ netif, dlci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse != nsvc->nse) {
+ vty_out(vty, "The specified NS-VC is not a part of the NSE %u!%s"
+ "To remove this NS-VC go to the vty node 'nse %u'%s",
+ nse->nsei, VTY_NEWLINE,
+ nsvc->nse->nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_nse_nsvci, cfg_no_ns_nse_nsvci_cmd,
+ "no nsvc nsvci <0-65535>",
+ NO_STR
+ "Delete NSVC\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ uint16_t nsvci = atoi(argv[0]);
+
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ vty_out(vty, "NSE doesn't support NSVCI.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ case GPRS_NS2_DIALECT_UNDEF:
+ vty_out(vty, "No NSVCs configured%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ case GPRS_NS2_DIALECT_IPACCESS:
+ case GPRS_NS2_DIALECT_STATIC_RESETBLOCK:
+ break;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with NS-VCI %u%s", nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse != nsvc->nse) {
+ vty_out(vty, "NS-VC with NS-VCI %u is not part of this NSE!%s",
+ nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int ns_nse_nsvc_udp_cmds(struct vty *vty, const char *bind_name, const char *remote_char, uint16_t port,
+ uint16_t sig_weight, uint16_t data_weight)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_ALIVE);
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, remote_char, port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (nsvc) {
+ if (nsvc->nse == nse)
+ vty_out(vty, "Specified NSVC is already present in this NSE.%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "Specified NSVC is already present in another NSE%05u.%s", nsvc->nse->nsei, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_ip_connect(bind, &remote, nse, 0);
+ if (!nsvc) {
+ vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
+ nsvc->persistent = true;
+
+ return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_ns_nse_nsvc_udp, cfg_ns_nse_nsvc_udp_cmd,
+ "nsvc udp BIND " VTY_IPV46_CMD " <1-65535>",
+ "NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n")
+{
+ const char *bind_name = argv[0];
+ const char *remote = argv[1];
+ uint16_t port = atoi(argv[2]);
+ uint16_t sig_weight = 1;
+ uint16_t data_weight = 1;
+
+ return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight);
+}
+
+DEFUN(cfg_ns_nse_nsvc_udp_weights, cfg_ns_nse_nsvc_udp_weights_cmd,
+ "nsvc udp BIND " VTY_IPV46_CMD " <1-65535> signalling-weight <0-254> data-weight <0-254>",
+ "NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ "Signalling weight of the NSVC (default = 1)\n"
+ "Signalling weight of the NSVC (default = 1)\n"
+ "Data weight of the NSVC (default = 1)\n"
+ "Data weight of the NSVC (default = 1)\n"
+ )
+{
+ const char *bind_name = argv[0];
+ const char *remote = argv[1];
+ uint16_t port = atoi(argv[2]);
+ uint16_t sig_weight = atoi(argv[3]);
+ uint16_t data_weight = atoi(argv[4]);
+
+ return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight);
+}
+
+DEFUN(cfg_no_ns_nse_nsvc_udp, cfg_no_ns_nse_nsvc_udp_cmd,
+ "no nsvc udp BIND " VTY_IPV46_CMD " <1-65535>",
+ NO_STR
+ "Delete a NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect static alive.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with remote %s:%u%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->persistent) {
+ vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nse != nse) {
+ vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_nse_nsvc_ipa, cfg_ns_nse_nsvc_ipa_cmd,
+ "nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>" ,
+ "NS Virtual Connection\n"
+ "NS over UDP ip.access style (uses RESET/BLOCK)\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+ uint16_t nsvci = atoi(argv[3]);
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_IPACCESS);
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_ip_connect(bind, &remote, nse, nsvci);
+ if (!nsvc) {
+ vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->persistent = true;
+
+ return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_nsvc_ipa, cfg_no_ns_nse_nsvc_ipa_cmd,
+ "no nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>",
+ NO_STR
+ "Delete a NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+ uint16_t nsvci = atoi(argv[3]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ipaccess.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with remote %s:%u%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->persistent) {
+ vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nse != nse) {
+ vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->nsvci_is_valid) {
+ vty_out(vty, "NS-VC doesn't have a nsvci!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nsvci != nsvci) {
+ vty_out(vty, "NS-VC has a different nsvci (%u)!%s",
+ nsvc->nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_nse_ip_sns_remote, cfg_ns_nse_ip_sns_remote_cmd,
+ "ip-sns-remote " VTY_IPV46_CMD " <1-65535>",
+ "SNS Initial Endpoint\n"
+ "SGSN IPv4 Address\n" "SGSN IPv6 Address\n"
+ "SGSN UDP Port\n"
+ )
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+ int rc;
+
+ /* argv[0] */
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[1]);
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0)
+ goto err;
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ rc = gprs_ns2_sns_add_endpoint(nse, &remote);
+ switch (rc) {
+ case 0:
+ return CMD_SUCCESS;
+ case -EADDRINUSE:
+ vty_out(vty, "Specified SNS endpoint already part of the NSE.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ vty_out(vty, "Can not add specified SNS endpoint.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_ip_sns_remote, cfg_no_ns_nse_ip_sns_remote_cmd,
+ "no ip-sns-remote " VTY_IPV46_CMD " <1-65535>",
+ NO_STR
+ "Delete a SNS Initial Endpoint\n"
+ "SGSN IPv4 Address\n" "SGSN IPv6 Address\n"
+ "SGSN UDP Port\n"
+ )
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ struct osmo_sockaddr_str remote_str; /* argv[0] */
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[1]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gprs_ns2_sns_del_endpoint(nse, &remote)) {
+ vty_out(vty, "Can not remove specified SNS endpoint.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vty_nse_check_sns(nse)) {
+ /* there is still sns configuration valid */
+ return CMD_SUCCESS;
+ } else {
+ /* clean up nse to allow other nsvc commands */
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* add all IP-SNS default binds to the given NSE */
+int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse)
+{
+ struct vty_nse_bind *vnse_bind;
+ int count = 0;
+
+ OSMO_ASSERT(nse->ll == GPRS_NS2_LL_UDP);
+ OSMO_ASSERT(nse->dialect == GPRS_NS2_DIALECT_SNS);
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ struct gprs_ns2_vc_bind *bind = gprs_ns2_bind_by_name(vty_nsi, vnse_bind->vbind->name);
+ /* the bind might not yet created because "listen" is missing. */
+ if (!bind)
+ continue;
+ gprs_ns2_sns_add_bind(nse, bind);
+ count++;
+ }
+ return count;
+}
+
+DEFUN(cfg_ns_ip_sns_default_bind, cfg_ns_ip_sns_default_bind_cmd,
+ "ip-sns-default bind ID",
+ "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n"
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n")
+{
+ struct vty_bind *vbind;
+ struct vty_nse_bind *vnse_bind;
+ const char *name = argv[0];
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ if (vnse_bind->vbind == vbind)
+ return CMD_SUCCESS;
+ }
+
+ vnse_bind = talloc(vty_nsi, struct vty_nse_bind);
+ if (!vnse_bind)
+ return CMD_WARNING;
+ vnse_bind->vbind = vbind;
+
+ llist_add_tail(&vnse_bind->list, &ip_sns_default_binds);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_ip_sns_default_bind, cfg_no_ns_ip_sns_default_bind_cmd,
+ "no ip-sns-default bind ID",
+ NO_STR "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n"
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be removed as IP-SNS local endpoint.\n")
+{
+ struct vty_bind *vbind;
+ struct vty_nse_bind *vnse_bind;
+ const char *name = argv[0];
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ if (vnse_bind->vbind == vbind) {
+ llist_del(&vnse_bind->list);
+ talloc_free(vnse_bind);
+ return CMD_SUCCESS;
+ }
+ }
+
+ vty_out(vty, "Bind '%s' was not an ip-sns-default bind%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_ns_txqueue_max_length, cfg_ns_txqueue_max_length_cmd,
+ "txqueue-max-length <1-4096>",
+ "Set the maximum length of the txqueue (for UDP)\n"
+ "Maximum length of the txqueue\n")
+{
+ struct gprs_ns2_vc_bind *bind;
+ uint32_t max_length = atoi(argv[0]);
+ vty_nsi->txqueue_max_length = max_length;
+
+
+ llist_for_each_entry(bind, &vty_nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ ns2_ip_set_txqueue_max_length(bind, max_length);
+ }
+
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_nse_ip_sns_bind, cfg_ns_nse_ip_sns_bind_cmd,
+ "ip-sns-bind BINDID",
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n")
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ struct vty_bind *vbind;
+ struct vty_nse *vnse;
+ const char *name = argv[0];
+ bool ll_modified = false;
+ bool dialect_modified = false;
+ int rc;
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0)
+ goto err;
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ goto err;
+ }
+
+ /* the vnse has been created together when creating the nse node. The parent node should check this already! */
+ vnse = vty_nse_by_nsei(nse->nsei);
+ OSMO_ASSERT(vnse);
+
+ rc = vty_nse_add_vbind(vnse, vbind);
+ switch (rc) {
+ case 0:
+ break;
+ case -EALREADY:
+ vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE);
+ goto err;
+ case -ENOMEM:
+ vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE);
+ goto err;
+ default:
+ vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE);
+ goto err;
+ }
+
+ /* the bind might not yet created because "listen" is missing. */
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (!bind)
+ return CMD_SUCCESS;
+
+ rc = gprs_ns2_sns_add_bind(nse, bind);
+ switch (rc) {
+ case 0:
+ break;
+ case -EALREADY:
+ vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE);
+ goto err;
+ case -ENOMEM:
+ vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE);
+ goto err;
+ default:
+ vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE);
+ goto err;
+ }
+
+ return CMD_SUCCESS;
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_ip_sns_bind, cfg_no_ns_nse_ip_sns_bind_cmd,
+ "no ip-sns-bind BINDID",
+ NO_STR
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will not be used as IP-SNS local endpoint\n")
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ struct vty_bind *vbind;
+ struct vty_nse *vnse;
+ const char *name = argv[0];
+ int rc;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* the vnse has been created together when creating the nse node. The parent node should check this already! */
+ vnse = vty_nse_by_nsei(nse->nsei);
+ OSMO_ASSERT(vnse);
+
+ rc = vty_nse_remove_vbind(vnse, vbind);
+ switch(rc) {
+ case 0:
+ break;
+ case -ENOENT:
+ vty_out(vty, "Bind %s is not part of this NSE%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ case -EINVAL:
+ vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ return CMD_WARNING;
+ }
+
+ /* the bind might not exists yet */
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (bind)
+ gprs_ns2_sns_del_bind(nse, bind);
+
+ if (!vty_nse_check_sns(nse)) {
+ /* clean up nse to allow other nsvc commands */
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* non-config commands */
+void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+{
+ if (nsvc->nsvci_is_valid)
+ vty_out(vty, " NSVCI %05u: %s %s %s %s %ssince ", nsvc->nsvci,
+ osmo_fsm_inst_state_name(nsvc->fi),
+ nsvc->persistent ? "PERSIST" : "DYNAMIC",
+ gprs_ns2_ll_str(nsvc),
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ nsvc->om_blocked ? "(blocked by O&M/vty) " :
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
+ else
+ vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s %s %ssince ",
+ osmo_fsm_inst_state_name(nsvc->fi),
+ nsvc->persistent ? "PERSIST" : "DYNAMIC",
+ nsvc->sig_weight, nsvc->data_weight,
+ gprs_ns2_ll_str(nsvc),
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
+
+ vty_out_uptime(vty, &nsvc->ts_alive_change);
+ vty_out_newline(vty);
+
+ if (stats) {
+ vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+ vty_out_stat_item_group(vty, " ", nsvc->statg);
+ }
+}
+
+static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_vc *nsvc;
+ unsigned int nsvcs = 0;
+
+ if (persistent_only && !nse->persistent)
+ return;
+
+ vty_out(vty, "NSEI %05u: %s, %s since ", nse->nsei, gprs_ns2_lltype_str(nse->ll),
+ nse->alive ? "ALIVE" : "DEAD");
+ vty_out_uptime(vty, &nse->ts_alive_change);
+ vty_out_newline(vty);
+
+ ns2_sns_dump_vty(vty, " ", nse, stats);
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ nsvcs++;
+ }
+ vty_out(vty, " %u NS-VC:%s", nsvcs, VTY_NEWLINE);
+ llist_for_each_entry(nsvc, &nse->nsvc, list)
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+}
+
+static void dump_bind(struct vty *vty, const struct gprs_ns2_vc_bind *bind, bool stats)
+{
+ if (bind->dump_vty)
+ bind->dump_vty(bind, vty, stats);
+
+ if (stats) {
+ vty_out_stat_item_group(vty, " ", bind->statg);
+ }
+}
+
+static void dump_ns_bind(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ dump_bind(vty, bind, stats);
+ }
+}
+
+
+static void dump_ns_entities(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ dump_nse(vty, nse, stats, persistent_only);
+ }
+}
+
+/* Backwards compatibility, among other things for the TestVTYGbproxy which expects
+ * 'show ns' to output something about binds */
+DEFUN_HIDDEN(show_ns, show_ns_cmd, "show ns",
+ SHOW_STR SHOW_NS_STR)
+{
+ dump_ns_entities(vty, vty_nsi, false, false);
+ dump_ns_bind(vty, vty_nsi, false);
+ if (vty_fr_network && llist_count(&vty_fr_network->links))
+ osmo_fr_network_dump_vty(vty, vty_fr_network);
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(show_ns_binds, show_ns_binds_cmd, "show ns binds [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Display information about the NS protocol binds\n"
+ "Include statistic\n")
+{
+ bool stats = false;
+ if (argc > 0)
+ stats = true;
+
+ dump_ns_bind(vty, vty_nsi, stats);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_entities, show_ns_entities_cmd, "show ns entities [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Display information about the NS protocol entities (NSEs)\n"
+ "Include statistics\n")
+{
+ bool stats = false;
+ if (argc > 0)
+ stats = true;
+
+ dump_ns_entities(vty, vty_nsi, stats, false);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent",
+ SHOW_STR SHOW_NS_STR
+ "Show only persistent NS\n")
+{
+ dump_ns_entities(vty, vty_nsi, true, true);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Select one NSE by its NSE Identifier\n"
+ "Select one NSE by its NS-VC Identifier\n"
+ "The Identifier of selected type\n"
+ "Include Statistics\n")
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+ uint16_t id = atoi(argv[1]);
+ bool show_stats = false;
+
+ if (argc >= 3)
+ show_stats = true;
+
+ if (!strcmp(argv[0], "nsei")) {
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ return CMD_WARNING;
+ }
+
+ dump_nse(vty, nse, show_stats, false);
+ } else {
+ nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
+
+ if (!nsvc) {
+ vty_out(vty, "No such NS Entity%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ns2_vty_dump_nsvc(vty, nsvc, show_stats);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int nsvc_force_unconf_cb(struct gprs_ns2_vc *nsvc, void *ctx)
+{
+ ns2_vc_force_unconfigured(nsvc);
+ ns2_vc_fsm_start(nsvc);
+ return 0;
+}
+
+DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd,
+ "nsvc nsei <0-65535> force-unconfigured",
+ "NS Virtual Connection\n"
+ "The NSEI\n"
+ "Reset the NSVCs back to initial state\n"
+ )
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+
+ uint16_t id = atoi(argv[0]);
+
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nse->persistent) {
+ gprs_ns2_free_nse(nse);
+ } else if (nse->dialect == GPRS_NS2_DIALECT_SNS) {
+ gprs_ns2_free_nsvcs(nse);
+ } else {
+ /* Perform the operation for all nsvc */
+ gprs_ns2_nse_foreach_nsvc(nse, nsvc_force_unconf_cb, NULL);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(nse_restart_sns, nse_restart_sns_cmd,
+ "nse <0-65535> restart-sns",
+ "NSE specific commands\n"
+ "NS Entity ID (NSEI)\n"
+ "Restart SNS procedure\n")
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+
+ uint16_t id = atoi(argv[0]);
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Given NSEI %u doesn't use IP-SNS%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvcs(nse);
+ return CMD_SUCCESS;
+}
+
+DEFUN(nsvc_block, nsvc_block_cmd,
+ "nsvc <0-65535> (block|unblock|reset)",
+ "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"
+ "Reset a NSVC. As cause code O&M intervention will be used.\n")
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_vc *nsvc;
+ int rc;
+
+ 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")) {
+ rc = ns2_vc_block(nsvc);
+ switch (rc) {
+ case 0:
+ vty_out(vty, "The NS-VC %05u will be blocked.%s", id, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ case -EALREADY:
+ vty_out(vty, "The NS-VC %05u is already blocked.%s", id, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ default:
+ vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else if (!strcmp(argv[1], "unblock")) {
+ rc = ns2_vc_unblock(nsvc);
+ switch (rc) {
+ case 0:
+ vty_out(vty, "The NS-VC %05u will be unblocked.%s", id, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ case -EALREADY:
+ vty_out(vty, "The NS-VC %05u is already unblocked.%s", id, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ default:
+ vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ ns2_vc_reset(nsvc);
+ vty_out(vty, "The NS-VC %05u has been resetted.%s", id, VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void log_set_nse_filter(struct log_target *target,
+ struct gprs_ns2_nse *nse)
+{
+ if (nse) {
+ target->filter_map |= (1 << LOG_FLT_GB_NSE);
+ target->filter_data[LOG_FLT_GB_NSE] = nse;
+ } else if (target->filter_data[LOG_FLT_GB_NSE]) {
+ target->filter_map = ~(1 << LOG_FLT_GB_NSE);
+ target->filter_data[LOG_FLT_GB_NSE] = NULL;
+ }
+}
+
+static void log_set_nsvc_filter(struct log_target *target,
+ struct gprs_ns2_vc *nsvc)
+{
+ if (nsvc) {
+ target->filter_map |= (1 << LOG_FLT_GB_NSVC);
+ target->filter_data[LOG_FLT_GB_NSVC] = nsvc;
+ } else if (target->filter_data[LOG_FLT_GB_NSVC]) {
+ target->filter_map = ~(1 << LOG_FLT_GB_NSVC);
+ target->filter_data[LOG_FLT_GB_NSVC] = NULL;
+ }
+}
+
+DEFUN(logging_fltr_nse,
+ logging_fltr_nse_cmd,
+ "logging filter nse nsei <0-65535>",
+ LOGGING_STR FILTER_STR
+ "Filter based on NS Entity\n"
+ "Identify NSE by NSEI\n"
+ "Numeric identifier\n")
+{
+ struct log_target *tgt;
+ struct gprs_ns2_nse *nse;
+ uint16_t id = atoi(argv[0]);
+
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, id);
+ if (!nse) {
+ vty_out(vty, "No NSE by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ log_set_nse_filter(tgt, nse);
+ log_tgt_mutex_unlock();
+ return CMD_SUCCESS;
+}
+
+/* TODO: add filter for single connection by description */
+DEFUN(logging_fltr_nsvc,
+ logging_fltr_nsvc_cmd,
+ "logging filter nsvc nsvci <0-65535>",
+ LOGGING_STR FILTER_STR
+ "Filter based on NS Virtual Connection\n"
+ "Identify NS-VC by NSVCI\n"
+ "Numeric identifier\n")
+{
+ struct log_target *tgt;
+ struct gprs_ns2_vc *nsvc;
+ uint16_t id = atoi(argv[0]);
+
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, id);
+ if (!nsvc) {
+ vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ log_set_nsvc_filter(tgt, nsvc);
+ log_tgt_mutex_unlock();
+ return CMD_SUCCESS;
+}
+
+/*! initialized a reduced vty interface which excludes the configuration nodes besides timeouts.
+ * This can be used by the PCU which can be only configured by the BTS/BSC and not by the vty.
+ * \param[in] nsi NS instance on which we operate
+ * \return 0 on success.
+ */
+int gprs_ns2_vty_init_reduced(struct gprs_ns2_inst *nsi)
+{
+ vty_nsi = nsi;
+ INIT_LLIST_HEAD(&binds);
+ INIT_LLIST_HEAD(&nses);
+ INIT_LLIST_HEAD(&ip_sns_default_binds);
+
+ vty_fr_network = osmo_fr_network_alloc(nsi);
+ if (!vty_fr_network)
+ return -ENOMEM;
+
+ install_lib_element_ve(&show_ns_cmd);
+ install_lib_element_ve(&show_ns_binds_cmd);
+ install_lib_element_ve(&show_ns_entities_cmd);
+ install_lib_element_ve(&show_ns_pers_cmd);
+ install_lib_element_ve(&show_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nse_cmd);
+ 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(ENABLE_NODE, &nse_restart_sns_cmd);
+
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nse_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
+
+ install_lib_element(CONFIG_NODE, &cfg_ns_cmd);
+
+ install_node(&ns_node, config_write_ns);
+ /* TODO: convert into osmo timer */
+ install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd);
+
+ return 0;
+}
+
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi)
+{
+ int rc = gprs_ns2_vty_init_reduced(nsi);
+ if (rc)
+ return rc;
+
+ install_lib_element(L_NS_NODE, &cfg_ns_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_ns_bind_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_bind_cmd);
+
+ install_lib_element(L_NS_NODE, &cfg_ns_ip_sns_default_bind_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_ip_sns_default_bind_cmd);
+
+ install_lib_element(L_NS_NODE, &cfg_ns_txqueue_max_length_cmd);
+
+ install_node(&ns_bind_node, NULL);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_listen_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_listen_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_dscp_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_dscp_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_priority_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ip_sns_weight_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ipaccess_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_ipaccess_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_fr_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_fr_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_accept_sns_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_accept_sns_cmd);
+
+ install_node(&ns_nse_node, NULL);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_fr_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvci_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_fr_dlci_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_weights_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_udp_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_ipa_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_ipa_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_remote_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_remote_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_bind_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_bind_cmd);
+
+ return 0;
+}
diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c
index a39e1395..f27bf6a8 100644
--- a/src/gb/gprs_ns_vty.c
+++ b/src/gb/gprs_ns_vty.c
@@ -644,32 +644,32 @@ int gprs_ns_vty_init(struct gprs_ns_inst *nsi)
return 0;
vty_elements_installed = true;
- install_element_ve(&show_ns_cmd);
- install_element_ve(&show_ns_stats_cmd);
- install_element_ve(&show_ns_pers_cmd);
- install_element_ve(&show_nse_cmd);
- install_element_ve(&logging_fltr_nsvc_cmd);
+ install_lib_element_ve(&show_ns_cmd);
+ install_lib_element_ve(&show_ns_stats_cmd);
+ install_lib_element_ve(&show_ns_pers_cmd);
+ install_lib_element_ve(&show_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nsvc_cmd);
- install_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
- install_element(CONFIG_NODE, &cfg_ns_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_ns_cmd);
install_node(&ns_node, config_write_ns);
- install_element(L_NS_NODE, &cfg_nse_nsvci_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoteip_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoteport_cmd);
- install_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd);
- install_element(L_NS_NODE, &cfg_nse_encaps_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoterole_cmd);
- install_element(L_NS_NODE, &cfg_no_nse_cmd);
- install_element(L_NS_NODE, &cfg_ns_timer_cmd);
- install_element(L_NS_NODE, &cfg_nsip_local_ip_cmd);
- install_element(L_NS_NODE, &cfg_nsip_local_port_cmd);
- install_element(L_NS_NODE, &cfg_nsip_dscp_cmd);
- install_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd);
- install_element(L_NS_NODE, &cfg_frgre_enable_cmd);
- install_element(L_NS_NODE, &cfg_frgre_local_ip_cmd);
-
- install_element(ENABLE_NODE, &nsvc_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_nsvci_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_remoteip_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_remoteport_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_encaps_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_remoterole_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_nse_cmd);
+ install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_local_ip_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_local_port_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_dscp_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd);
+ install_lib_element(L_NS_NODE, &cfg_frgre_enable_cmd);
+ install_lib_element(L_NS_NODE, &cfg_frgre_local_ip_cmd);
+
+ install_lib_element(ENABLE_NODE, &nsvc_nsei_cmd);
return 0;
}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index 7a231db5..f1a6bd69 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -2,7 +2,26 @@ LIBOSMOGB_1.0 {
global:
bssgp_cause_str;
bssgp_create_cell_id;
+bssgp_create_cell_id2;
+bssgp_create_rim_ri;
+bssgp_dec_app_err_cont_nacc;
+bssgp_dec_ran_inf_ack_rim_cont;
+bssgp_dec_ran_inf_err_rim_cont;
+bssgp_dec_ran_inf_req_app_cont_nacc;
+bssgp_dec_ran_inf_req_rim_cont;
+bssgp_dec_ran_inf_app_cont_nacc;
+bssgp_dec_ran_inf_app_err_rim_cont;
+bssgp_dec_ran_inf_rim_cont;
bssgp_pdu_str;
+bssgp_enc_app_err_cont_nacc;
+bssgp_enc_ran_inf_ack_rim_cont;
+bssgp_enc_ran_inf_err_rim_cont;
+bssgp_enc_ran_inf_req_app_cont_nacc;
+bssgp_enc_ran_inf_req_rim_cont;
+bssgp_enc_ran_inf_app_cont_nacc;
+bssgp_enc_ran_inf_app_err_rim_cont;
+bssgp_enc_ran_inf_rim_cont;
+bssgp_encode_rim_pdu;
bssgp_fc_in;
bssgp_fc_init;
bssgp_fc_ms_init;
@@ -12,10 +31,21 @@ bssgp_msgb_alloc;
bssgp_msgb_copy;
bssgp_msgb_tlli_put;
bssgp_msgb_ra_put;
+bssgp_nacc_cause_strs;
bssgp_parse_cell_id;
+bssgp_parse_cell_id2;
+bssgp_parse_rim_pdu;
+bssgp_parse_rim_ri;
+bssgp_parse_rim_ra;
+bssgp_ran_inf_app_id_strs;
+bssgp_rim_routing_info_discr_strs;
+bssgp_rim_ri_name_buf;
+bssgp_rim_ri_name;
+bssgp_set_bssgp_callback;
bssgp_tx_bvc_block;
bssgp_tx_bvc_reset;
bssgp_tx_bvc_reset2;
+bssgp_tx_bvc_reset_nsei_bvci;
bssgp_tx_bvc_unblock;
bssgp_tx_fc_bvc;
bssgp_tx_fc_ms;
@@ -28,6 +58,8 @@ bssgp_tx_radio_status_tmsi;
bssgp_tx_resume;
bssgp_tx_resume_ack;
bssgp_tx_resume_nack;
+bssgp_tx_rim;
+bssgp_tx_rim_encoded;
bssgp_tx_simple_bvci;
bssgp_tx_status;
bssgp_tx_suspend;
@@ -43,6 +75,45 @@ bssgp_tx_paging;
bssgp_vty_init;
bssgp_nsi;
+bssgp2_nsi_tx_ptp;
+bssgp2_nsi_tx_sig;
+bssgp2_dec_fc_bvc;
+bssgp2_dec_fc_ms;
+bssgp2_enc_bvc_block;
+bssgp2_enc_bvc_block_ack;
+bssgp2_enc_bvc_unblock;
+bssgp2_enc_bvc_unblock_ack;
+bssgp2_enc_bvc_reset;
+bssgp2_enc_bvc_reset_ack;
+bssgp2_enc_fc_bvc;
+bssgp2_enc_fc_bvc_ack;
+bssgp2_enc_fc_ms;
+bssgp2_enc_fc_ms_ack;
+bssgp2_enc_flush_ll;
+bssgp2_enc_status;
+
+bssgp_bvc_fsm_alloc_sig_bss;
+bssgp_bvc_fsm_alloc_ptp_bss;
+bssgp_bvc_fsm_alloc_sig_sgsn;
+bssgp_bvc_fsm_alloc_ptp_sgsn;
+bssgp_bvc_fsm_set_ops;
+bssgp_bvc_fsm_is_unblocked;
+bssgp_bvc_fsm_get_block_cause;
+bssgp_bvc_fsm_get_features_advertised;
+bssgp_bvc_fsm_get_features_received;
+bssgp_bvc_fsm_get_features_negotiated;
+bssgp_bvc_fsm_set_max_pdu_len;
+bssgp_bvc_fsm_get_max_pdu_len;
+
+osmo_fr_network_alloc;
+osmo_fr_network_free;
+osmo_fr_link_alloc;
+osmo_fr_link_free;
+osmo_fr_dlc_alloc;
+osmo_fr_rx;
+osmo_fr_tx_dlc;
+osmo_fr_role_names;
+
gprs_ns_signal_ns_names;
gprs_ns_pdu_strings;
gprs_ns_cause_str;
@@ -71,6 +142,65 @@ gprs_ns_ll_copy;
gprs_ns_ll_clear;
gprs_ns_msgb_alloc;
+gprs_ns2_aff_cause_prim_strs;
+gprs_ns2_bind_by_name;
+gprs_ns2_cause_strs;
+gprs_ns2_create_nse;
+gprs_ns2_create_nse2;
+gprs_ns2_find_vc_by_sockaddr;
+gprs_ns2_free;
+gprs_ns2_free_bind;
+gprs_ns2_free_binds;
+gprs_ns2_free_nse;
+gprs_ns2_free_nses;
+gprs_ns2_free_nsvc;
+gprs_ns2_free_nsvcs;
+gprs_ns2_frgre_bind;
+gprs_ns2_fr_bind;
+gprs_ns2_fr_bind_netif;
+gprs_ns2_fr_bind_by_netif;
+gprs_ns2_fr_connect;
+gprs_ns2_fr_nsvc_by_dlci;
+gprs_ns2_fr_nsvc_dlci;
+gprs_ns2_is_fr_bind;
+gprs_ns2_find_vc_by_dlci;
+gprs_ns2_instantiate;
+gprs_ns2_ip_bind;
+gprs_ns2_ip_bind_by_sockaddr;
+gprs_ns2_ip_bind_set_dscp;
+gprs_ns2_ip_bind_set_priority;
+gprs_ns2_ip_bind_set_sns_weight;
+gprs_ns2_ip_bind_sockaddr;
+gprs_ns2_ip_connect;
+gprs_ns2_ip_connect2;
+gprs_ns2_ip_connect_inactive;
+gprs_ns2_ip_vc_local;
+gprs_ns2_ip_vc_remote;
+gprs_ns2_ip_vc_equal;
+gprs_ns2_is_frgre_bind;
+gprs_ns2_is_ip_bind;
+gprs_ns2_ll_str;
+gprs_ns2_ll_str_buf;
+gprs_ns2_ll_str_c;
+gprs_ns2_lltype_strs;
+gprs_ns2_nse_by_nsei;
+gprs_ns2_nse_foreach_nsvc;
+gprs_ns2_nse_nsei;
+gprs_ns2_nse_sns_remote;
+gprs_ns2_nsvc_by_nsvci;
+gprs_ns2_nsvc_by_sockaddr;
+gprs_ns2_nsvc_state_name;
+gprs_ns2_prim_strs;
+gprs_ns2_recv_prim;
+gprs_ns2_reset_persistent_nsvcs;
+gprs_ns2_start_alive_all_nsvcs;
+gprs_ns2_sns_add_bind;
+gprs_ns2_sns_add_endpoint;
+gprs_ns2_sns_del_bind;
+gprs_ns2_sns_del_endpoint;
+gprs_ns2_vty_init;
+gprs_ns2_vty_init_reduced;
+
gprs_nsvc_create2;
gprs_nsvc_delete;
gprs_nsvc_reset;
@@ -85,5 +215,7 @@ bssgp_bvc_ctx_free;
btsctx_by_bvci_nsei;
btsctx_by_raid_cid;
+osmo_pdef_bssgp;
+
local: *;
};