This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
lynxis lazus gerrit-no-reply at lists.osmocom.orglynxis lazus has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmocore/+/19417 )
Change subject: Gb: add a second NS implementation
......................................................................
Gb: add a second NS implementation
Change-Id: I3525beef205588dfab9d3880a34115f1a2676e48
---
M include/Makefile.am
M include/osmocom/core/logging.h
A include/osmocom/gprs/gprs_ns2.h
A include/osmocom/gprs/gprs_ns_common.h
M src/gb/Makefile.am
A src/gb/gprs_ns2.c
A src/gb/gprs_ns2_driver.c
A src/gb/gprs_ns2_driver.h
A src/gb/gprs_ns2_internal.h
A src/gb/gprs_ns2_sns.c
A src/gb/gprs_ns2_vc_fsm.c
A src/gb/gprs_ns2_vc_fsm.h
A src/gb/gprs_ns2_vty.c
M src/gb/libosmogb.map
14 files changed, 4,251 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/17/19417/1
diff --git a/include/Makefile.am b/include/Makefile.am
index 7af7e01..f961bb4 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -65,6 +65,8 @@
osmocom/gprs/gprs_msgb.h \
osmocom/gprs/gprs_ns.h \
osmocom/gprs/gprs_ns_frgre.h \
+ osmocom/gprs/gprs_ns2.h \
+ osmocom/gprs/gprs_ns_common.h \
osmocom/gprs/gprs_rlc.h \
osmocom/gprs/protocol/gsm_04_60.h \
osmocom/gprs/protocol/gsm_08_16.h \
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
index 79eec10..00d4a68 100644
--- a/include/osmocom/core/logging.h
+++ b/include/osmocom/core/logging.h
@@ -187,6 +187,7 @@
LOG_FLT_BSC_SUBSCR,
LOG_FLT_VLR_SUBSCR,
LOG_FLT_L1_SAPI,
+ LOG_FLT_GB_NSE,
_LOG_FLT_COUNT
};
diff --git a/include/osmocom/gprs/gprs_ns2.h b/include/osmocom/gprs/gprs_ns2.h
new file mode 100644
index 0000000..4db34c0
--- /dev/null
+++ b/include/osmocom/gprs/gprs_ns2.h
@@ -0,0 +1,131 @@
+/*! \file gprs_ns2.h */
+
+
+#ifndef OSMO_GPRS_NS2_H
+#define OSMO_GPRS_NS2_H
+
+#include <stdint.h>
+
+/* Our Implementation */
+#include <netinet/in.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns_common.h>
+
+struct gprs_ns2_inst;
+struct gprs_ns2_nse;
+struct gprs_ns2_vc;
+struct gprs_ns2_vc_bind;
+struct gprs_ns2_vc_driver;
+struct gprs_ns_ie_ip4_elem;
+
+/* callbacks for messages transfer */
+/*! Osmocom GPRS callback function type */
+typedef int gprs_ns2_cb_t(enum gprs_ns_evt event, struct msgb *msg,
+ uint16_t nsei, uint16_t bvci);
+
+/* instance */
+struct gprs_ns2_inst *gprs_ns2_instantiate(gprs_ns2_cb_t *cb, void *ctx);
+void gprs_ns2_free(struct gprs_ns2_inst *inst);
+int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse);
+
+/* NSE */
+/*! Send a UNIT DATA over an NSE. Should be used by higher layers */
+int gprs_ns2_send(struct gprs_ns2_inst *inst,struct msgb *msg);
+int gprs_ns2_send_nse(struct gprs_ns2_nse *nse, struct msgb *msg);
+int gprs_ns2_send_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg);
+int gprs_ns2_recv_vc(struct gprs_ns2_inst *nsi,
+ struct gprs_ns2_vc *nsvc,
+ struct msgb *msg);
+struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei);
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei);
+void gprs_ns2_free_nse(struct gprs_ns2_nse *nse);
+
+/* create vc */
+void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc);
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci);
+
+/* transmit message over a VC */
+int gprs_ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause);
+int gprs_ns2_tx_block_ack(struct gprs_ns2_vc *nsvc);
+
+int gprs_ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause);
+int gprs_ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc);
+
+int gprs_ns2_tx_unblock(struct gprs_ns2_vc *nsvc);
+int gprs_ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc);
+
+int gprs_ns2_tx_alive(struct gprs_ns2_vc *nsvc);
+int gprs_ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc);
+
+int gprs_ns2_tx_unit_data(struct gprs_ns2_vc *nsvc, struct msgb *msg);
+
+int gprs_ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
+ uint16_t bvci, struct msgb *orig_msg);
+
+/* SNS messages */
+int gprs_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);
+int gprs_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);
+int gprs_ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+int gprs_ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
+ uint16_t *ip4_ep_nr, uint16_t *ip6_ep_nr);
+int gprs_ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+
+/* IP VL driver */
+int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result);
+void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode);
+
+/* create a VC connection */
+struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci);
+
+struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ uint16_t nsei,
+ uint16_t nsvci);
+/* TODO: should the inactive prviate */
+struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci);
+
+void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind);
+
+/* create a VC SNS connection */
+int gprs_ns2_ip_connect_sns(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ uint16_t nsei);
+
+struct osmo_sockaddr *gprs_ns2_ip_vc_sockaddr(struct gprs_ns2_vc *nsvc);
+struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind);
+int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind);
+int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp);
+
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr(struct gprs_ns2_nse *nsei,
+ struct osmo_sockaddr *sockaddr);
+void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse);
+void gprs_ns2_set_log_ss(int ss);
+const char *gprs_ns2_cause_str(int cause);
+const char *gprs_ns2_ll_str(const struct gprs_ns2_vc *nsvc);
+
+/* vty */
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi);
+int gprs_ns2_vty_create();
+
+
+#endif /* OSMO_GPRS_NS2_H */
+
+/*! @} */
diff --git a/include/osmocom/gprs/gprs_ns_common.h b/include/osmocom/gprs/gprs_ns_common.h
new file mode 100644
index 0000000..7bc7492
--- /dev/null
+++ b/include/osmocom/gprs/gprs_ns_common.h
@@ -0,0 +1,104 @@
+/*! \file gprs_ns_common.h */
+
+#ifndef OSMO_GPRS_NS_COMMON_H
+#define OSMO_GPRS_NS_COMMON_H
+
+#include <stdint.h>
+
+struct gprs_ns2_nse;
+struct gprs_ns2_vc;
+struct msgb;
+
+#define NS_TIMERS_COUNT 8
+#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov)"
+#define NS_TIMERS_HELP \
+ "(un)blocking Timer (Tns-block) timeout\n" \
+ "(un)blocking Timer (Tns-block) number of retries\n" \
+ "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"
+
+/* Educated guess - LLC user payload is 1500 bytes plus possible headers */
+#define NS_ALLOC_SIZE 3072
+#define NS_ALLOC_HEADROOM 20
+
+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,
+};
+
+#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")
+
+/*! Osmocom NS link layer types */
+enum gprs_ns_ll {
+ GPRS_NS_LL_UDP, /*!< NS/UDP/IP */
+ GPRS_NS_LL_E1, /*!< NS/E1 */
+ GPRS_NS_LL_FR_GRE, /*!< NS/FR/GRE/IP */
+};
+
+/*! Osmoco NS events */
+enum gprs_ns_evt {
+ GPRS_NS_EVT_UNIT_DATA,
+};
+
+/*! Osmocom NS VC create status */
+enum gprs_ns_cs {
+ GPRS_NS_CS_CREATED, /*!< A NSVC object has been created */
+ GPRS_NS_CS_FOUND, /*!< A NSVC object has been found */
+ GPRS_NS_CS_REJECTED, /*!< Rejected and answered message */
+ GPRS_NS_CS_SKIPPED, /*!< Skipped message */
+ GPRS_NS_CS_ERROR, /*!< Failed to process message */
+};
+
+enum nsvc_timer_mode {
+ /* standard timers */
+ NSVC_TIMER_TNS_TEST,
+ NSVC_TIMER_TNS_ALIVE,
+ NSVC_TIMER_TNS_RESET,
+ _NSVC_TIMER_NR,
+};
+
+enum gprs_ns2_signal_ns {
+ S_NS_RESET,
+ S_NS_BLOCK,
+ S_NS_UNBLOCK,
+ S_NS_ALIVE_EXP, /* Tns-alive expired more than N times */
+ S_NS_REPLACED, /* nsvc object is replaced (sets old_nsvc) */
+ S_NS_MISMATCH, /* got an unexpected IE (sets msg, pdu_type, ie_type) */
+ S_SNS_CONFIGURED, /* IP-SNS configuration completed */
+ S_NSE_AVAILABLE, /* NS2 NSE came alive */
+ S_NSE_UNAVAILABLE, /* */
+};
+
+struct gprs_ns2_signal_data {
+ struct gprs_ns2_nse *nse;
+ uint16_t nsei;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc *old_nsvc;
+ uint8_t cause;
+ uint8_t pdu_type;
+ uint8_t ie_type;
+ struct msgb *msg;
+};
+
+enum gprs_ns2_vc_mode {
+ NS2_VC_MODE_ALIVE, /* The VC will use RESET/BLOCK/UNBLOCK to start the connection and do ALIVE/ACK */
+ NS2_VC_MODE_BLOCKRESET, /* The will only use ALIVE/ACK no initiation */
+};
+
+#endif /* OSMO_GPRS_NS_COMMON_H */
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index 7248413..40df58e 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -20,7 +20,10 @@
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_bss.c \
+ gprs_ns2.c gprs_ns2_driver.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \
+ gprs_ns2_message.c gprs_ns2_vty.c \
+ common_vty.c
endif
EXTRA_DIST = libosmogb.map
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
new file mode 100644
index 0000000..583ca24
--- /dev/null
+++ b/src/gb/gprs_ns2.c
@@ -0,0 +1,933 @@
+/*! \file gprs_ns.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 at gnumonks.org>
+ * (C) 2016-2017,2020 sysmocom - s.f.m.c. GmbH
+ *
+ * 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:
+ * - Only one NS-VC for each NSE: No load-sharing function
+ * - NSVCI 65535 and 65534 are reserved for internal use
+ * - Only UDP is supported as of now, no frame relay support
+ * - There are no BLOCK and UNBLOCK timers (yet?)
+ *
+ * \file gprs_ns.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/msgb.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_msgb.h>
+
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+#include "gprs_ns2_vc_fsm.h"
+#include "gprs_ns2_driver.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));
+
+static const struct tlv_definition ns_att_tlvdef = {
+ .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 },
+ /* TODO: IP_ADDR can be 5 or 17 bytes long, depending on first byte. This
+ * cannot be expressed in our TLV parser. However, we don't do IPv6 anyway */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 },
+ },
+};
+
+/* TODO: move into common part */
+
+/* Section 10.3.2, Table 13 */
+static const struct value_string ns2_cause_str[] = {
+ { 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 }
+};
+
+/*! Obtain a human-readable string for NS cause value */
+const char *gprs_ns2_cause_str(int cause)
+{
+ enum ns_cause _cause = cause;
+ return get_value_string(ns2_cause_str, _cause);
+}
+
+static const struct rate_ctr_desc nsvc_ctr_description[] = {
+ { "packets:in", "Packets at NS Level ( In)" },
+ { "packets:out","Packets at NS Level (Out)" },
+ { "bytes:in", "Bytes at NS Level ( In)" },
+ { "bytes:out", "Bytes at NS Level (Out)" },
+ { "blocked", "NS-VC Block count " },
+ { "dead", "NS-VC gone dead count " },
+ { "replaced", "NS-VC replaced other count" },
+ { "nsei-chg", "NS-VC changed NSEI count " },
+ { "inv-nsvci", "NS-VCI was invalid count " },
+ { "inv-nsei", "NSEI was invalid count " },
+ { "lost:alive", "ALIVE ACK missing count " },
+ { "lost:reset", "RESET ACK missing count " },
+};
+
+static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
+ .group_name_prefix = "ns:nsvc",
+ .group_description = "NSVC Peer Statistics",
+ .num_ctr = ARRAY_SIZE(nsvc_ctr_description),
+ .ctr_desc = nsvc_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+enum ns_stat {
+ NS_STAT_ALIVE_DELAY,
+};
+
+static const struct osmo_stat_item_desc nsvc_stat_description[] = {
+ { "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 value_string gprs_ns2_signal_ns_names[] = {
+ { S_NS_RESET, "NS-RESET" },
+ { S_NS_BLOCK, "NS-BLOCK" },
+ { S_NS_UNBLOCK, "NS-UNBLOCK" },
+ { S_NS_ALIVE_EXP, "NS-ALIVE expired" },
+ { S_NS_REPLACED, "NSVC replaced" },
+ { S_NS_MISMATCH, "Unexpected IE" },
+ { S_SNS_CONFIGURED, "SNS Configured" },
+ { 0, NULL }
+};
+
+void gprs_ns2_set_log_ss(int ss)
+{
+ DNS = ss;
+}
+
+const char *gprs_ns2_ll_str(const struct gprs_ns2_vc *nsvc)
+{
+ static __thread char buf[80] = "";
+ /* TODO ns2_ll_str */
+// return gprs_ns_ll_str_buf(buf, sizeof(buf), nsvc);
+ return &buf[0];
+}
+
+/*!
+ * \brief gprs_ns2_send_nse send PDU for a NSE
+ * The bvci is encoded in the message. See msg_bvci()
+ * \param nse
+ * \param msg
+ * \return
+ */
+int gprs_ns2_send_nse(struct gprs_ns2_nse *nse, struct msgb *msg)
+{
+ /* TODO: do loadbalancing in here and bvci stickyness */
+ struct gprs_ns2_vc *nsvc = NULL, *tmp;
+ uint16_t bvci = msgb_bvci(msg);
+
+ llist_for_each_entry(tmp, &nse->vc, list) {
+ if (!gprs_ns2_vc_is_unblocked(tmp))
+ continue;
+ if (bvci == 0 && tmp->sig_weight == 0)
+ continue;
+ if (bvci != 0 && tmp->data_weight == 0)
+ continue;
+
+ nsvc = tmp;
+ }
+
+ if (!nsvc)
+ return -1;
+
+ return gprs_ns2_tx_unit_data(nsvc, msg);
+}
+
+int gprs_ns2_send_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg)
+{
+ struct gprs_ns2_nse *nse;
+
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+ if (!nse)
+ return -ENOENT;
+
+ return gprs_ns2_send_nse(nse, msg);
+}
+
+int gprs_ns2_send(struct gprs_ns2_inst *nsi, struct msgb *msg)
+{
+ struct gprs_ns2_nse *nse;
+
+ nse = gprs_ns2_nse_by_nsei(nsi, msgb_nsei(msg));
+ if (!nse)
+ return -1;
+
+ return gprs_ns2_send_nse(nse, msg);
+}
+
+/*!
+ * \brief ns2_vc_alloc
+ * \param bind
+ * \param nse
+ * \param initiater - if this is an incoming remote (!initiater) or a local outgoing connection (initater)
+ * \return
+ */
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater)
+{
+ struct gprs_ns2_vc *vc = talloc_zero(bind, struct gprs_ns2_vc);
+ int nsvci = 0;
+
+ if (!vc)
+ return NULL;
+
+ vc->bind = bind;
+ vc->nse = nse;
+ vc->mode = bind->use_reset_block_unblock ? NS2_VC_MODE_BLOCKRESET : NS2_VC_MODE_ALIVE;
+ vc->sig_weight = 1;
+ vc->data_weight = 1;
+ llist_add(&vc->list, &nse->vc);
+ llist_add(&vc->blist, &bind->vc);
+
+ /* TODO: fix ctrg index. It must be != 0. Maybe use a integer at nsi level? */
+ vc->ctrg = rate_ctr_group_alloc(vc, &nsvc_ctrg_desc, nsvci);
+ if (!vc->ctrg) {
+ goto err;
+ }
+ /* TODO: fix statg index. It must be != 0 */
+ vc->statg = osmo_stat_item_group_alloc(vc, &nsvc_statg_desc, nsvci);
+ if (!vc->statg)
+ goto err_group;
+ if (!gprs_ns2_vc_fsm_alloc(vc, NULL, initiater))
+ goto err_statg;
+
+ return vc;
+
+err_statg:
+ osmo_stat_item_group_free(vc->statg);
+err_group:
+ rate_ctr_group_free(vc->ctrg);
+err:
+ llist_del(&vc->list);
+ llist_del(&vc->blist);
+ talloc_free(vc);
+
+ return NULL;
+}
+
+
+void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ llist_del(&nsvc->list);
+ llist_del(&nsvc->blist);
+
+ /* TODO: signal to the user the removal of this vc */
+ /* TODO: communicate to the NSE the loss, e.g. if all VCs are freed, the user space might want to know */
+
+ /* notify nse this nsvc is unavailable */
+ ns2_nse_notify_unblocked(nsvc, false);
+
+ /* check if sns is using this VC */
+ ns2_sns_free_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);
+ }
+
+ talloc_free(nsvc);
+}
+
+struct msgb *gprs_ns2_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM,
+ "GPRS/NS");
+ if (!msg) {
+ LOGP(DNS, LOGL_ERROR, "Failed to allocate NS message of size %d\n",
+ NS_ALLOC_SIZE);
+ }
+ return msg;
+}
+
+/*!
+ * \brief gprs_ns2_cerate_status_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] result
+ * \param[in] cause
+ * \return
+ */
+static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struct msgb **reject, enum ns_cause cause)
+{
+ /* TODO has to create all messages. It might append NSVCI */
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ bool have_vci = false;
+ uint8_t _cause = cause;
+
+ if (!msg)
+ return -ENOMEM;
+
+// LOGP(DNS, LOGL_NOTICE, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n",
+// nsvc->nsei, nsvc->nsvci, gprs_ns_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_PRESENT(tp, NS_IE_VCI);
+
+ /* 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;
+}
+
+
+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;
+}
+
+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->vc, list) {
+ if (nsvc->nsvci_is_valid && nsvc->nsvci == nsvci)
+ return nsvc;
+ }
+ }
+
+ return NULL;
+}
+
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei)
+{
+ struct gprs_ns2_nse *nse = talloc_zero(nsi, struct gprs_ns2_nse);
+
+ OSMO_ASSERT(nsi);
+
+ if (!nse)
+ return NULL;
+
+ /* TODO: check if nsei already taken! */
+
+ nse->nsei = nsei;
+ nse->nsi = nsi;
+ llist_add(&nse->list, &nsi->nse);
+ INIT_LLIST_HEAD(&nse->vc);
+
+ nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, NULL);
+ if (!nse->bss_sns_fi) {
+ llist_del(&nse->list);
+ return NULL;
+ }
+
+ return nse;
+}
+
+void gprs_ns2_free_nse(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *vc, *tmp;
+ struct gprs_ns2_signal_data nssd = {0};
+
+ if (!nse)
+ return;
+
+ /* inform the user */
+ nssd.nse = nse;
+ nssd.nsei = nse->nsei;
+ nse->unblocked = false;
+ osmo_signal_dispatch(SS_L_NS, S_NSE_UNAVAILABLE, &nssd);
+
+ llist_for_each_entry_safe(vc, tmp, &nse->vc, list) {
+ gprs_ns2_free_nsvc(vc);
+ }
+
+ llist_del(&nse->list);
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ talloc_free(nse);
+}
+
+
+/*!
+ * \brief gprs_ns2_create_vc create a new VC from a message. might also create a new NSEI
+ * \param[in] nsi
+ * \param[in] msg
+ * \param[in] logname A name to describe the VC. E.g. ip address pair
+ * \param[in] do_block_reset If the VC is doing BLOCK/UNBLOCK/RESET.
+ * \param[out] reject A message filled to be sent back. Only used in failure cases.
+ * \return
+ */
+int ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const char *logname,
+ enum gprs_ns2_vc_mode mode,
+ 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 *vc;
+ struct gprs_ns2_nse *nse;
+ uint16_t nsvci;
+ uint16_t nsei;
+
+ int rc;
+
+ /* TODO: length check on msgb */
+ if (nsh->pdu_type == NS_PDUT_STATUS) {
+ /* Do not respond, see 3GPP TS 08.16, 7.5.1 */
+ LOGP(DNS, LOGL_INFO, "Ignoring NS STATUS from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ if (nsh->pdu_type == NS_PDUT_ALIVE_ACK) {
+ /* Ignore this, see 3GPP TS 08.16, 7.4.1 */
+ LOGP(DNS, LOGL_INFO, "Ignoring NS ALIVE ACK from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ if (nsh->pdu_type == NS_PDUT_RESET_ACK) {
+ /* Ignore this, see 3GPP TS 08.16, 7.3.1 */
+ LOGP(DNS, LOGL_INFO, "Ignoring NS RESET ACK from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ /* Only the RESET procedure creates a new NSVC */
+ if (nsh->pdu_type != NS_PDUT_RESET) {
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+
+ if (rc < 0) {
+ LOGP(DNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return rc;
+ }
+ return GPRS_NS2_CS_REJECTED;
+ }
+
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGP(DNS, LOGL_ERROR, "Rx NS RESET Error %d during "
+ "TLV Parse\n", rc);
+ /* TODO: send invalid message back */
+ return GPRS_NS2_CS_REJECTED;
+ }
+
+ if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+ !TLVP_PRESENT(&tp, NS_IE_VCI) || !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+ LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
+ return GPRS_NS2_CS_REJECTED;
+ }
+
+ /* find or create NSE */
+ nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ if (!bind->nsi->create_nse) {
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ if (!nse) {
+ return GPRS_NS_CS_ERROR;
+ }
+ }
+
+ vc = ns2_vc_alloc(bind, nse, false);
+ if (!vc)
+ return GPRS_NS_CS_SKIPPED;
+
+ vc->mode = mode;
+ vc->ll = GPRS_NS_LL_UDP;
+
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+ vc->nsvci = nsvci;
+ vc->nsvci_is_valid = true;
+
+ *success = vc;
+
+ return GPRS_NS2_CS_CREATED;
+}
+
+struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return NULL;
+
+ if (nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+ }
+
+ return nsvc;
+}
+
+/*!
+ * \brief gprs_ns2_ip_connect
+ * \param bind
+ * \param remote
+ * \param nse
+ * \param nsvci only required when mode == NS2_VC_MODE_BLOCKRESET
+ * \param mode
+ * \return
+ */
+struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
+ 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;
+
+ gprs_ns2_vc_fsm_start(nsvc);
+
+ return nsvc;
+}
+
+/*!
+ * \brief gprs_ns2_ip_connect2
+ * \param bind
+ * \param remote
+ * \param nsei
+ * \param nsvci only required when mode == NS2_VC_MODE_BLOCKRESET
+ * \param mode
+ * \return
+ */
+struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ uint16_t nsei,
+ uint16_t nsvci)
+{
+ struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ if (!nse)
+ return NULL;
+ }
+
+ return gprs_ns2_ip_connect(bind, remote, nse, nsvci);
+}
+
+int gprs_ns2_ip_connect_sns(struct gprs_ns2_vc_bind *bind,
+ struct osmo_sockaddr *remote,
+ uint16_t nsei)
+{
+ struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ struct gprs_ns2_vc *nsvc;
+
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ if (!nse)
+ return -1;
+ }
+
+ nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return -1;
+
+ ns2_sns_bss_fsm_start(nse, nsvc, remote);
+
+ return 0;
+}
+
+/* TODO: vc start */
+
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr(struct gprs_ns2_nse *nse,
+ struct osmo_sockaddr *sockaddr)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr *remote;
+
+ OSMO_ASSERT(nse);
+ OSMO_ASSERT(sockaddr);
+
+ llist_for_each_entry(nsvc, &nse->vc, list) {
+ remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ if (!osmo_sockaddr_cmp(sockaddr, remote))
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+
+/*!
+ * \brief gprs_ns2_recv_vc entrypoint of received NS PDU from the driver/bind
+ * \param nsi
+ * \param vc
+ * \param msg the received message. Must not be freeded.
+ * \return
+ */
+int ns2_recv_vc(struct gprs_ns2_inst *nsi,
+ struct gprs_ns2_vc *vc,
+ struct msgb *msg)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct tlv_parsed tp;
+ int rc = 0;
+
+ if (msg->len < sizeof(struct gprs_ns_hdr))
+ return -EINVAL;
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_CONFIG:
+ /* TODO */
+// if (!nsi->bss_sns_fi)
+// goto unexpected_sns;
+ /* one additional byte ('end flag') before the TLV part starts */
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data+1,
+ msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ /* All sub-network service related message types */
+ rc = gprs_ns2_sns_rx(vc, msg, &tp);
+ break;
+ case SNS_PDUT_ACK:
+ case SNS_PDUT_ADD:
+ case SNS_PDUT_CHANGE_WEIGHT:
+ case SNS_PDUT_DELETE:
+ /* TODO */
+// if (!nsi->bss_sns_fi)
+// goto unexpected_sns;
+ /* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data+5,
+ msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ tp.lv[NS_IE_NSEI].val = nsh->data+2;
+ tp.lv[NS_IE_NSEI].len = 2;
+ tp.lv[NS_IE_TRANS_ID].val = nsh->data+4;
+ tp.lv[NS_IE_TRANS_ID].len = 1;
+ rc = gprs_ns2_sns_rx(vc, msg, &tp);
+ break;
+ case SNS_PDUT_CONFIG_ACK:
+ case SNS_PDUT_SIZE:
+ case SNS_PDUT_SIZE_ACK:
+ /* TODO */
+// if (!nsi->bss_sns_fi)
+// goto unexpected_sns;
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ /* All sub-network service related message types */
+ rc = gprs_ns2_sns_rx(vc, msg, &tp);
+ break;
+
+ case NS_PDUT_UNITDATA:
+ rc = gprs_ns2_vc_rx(vc, msg, NULL);
+ break;
+ default:
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse\n");
+ if (nsh->pdu_type != NS_PDUT_STATUS)
+ gprs_ns2_tx_status(vc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg);
+ return rc;
+ }
+ rc = gprs_ns2_vc_rx(vc, msg, &tp);
+ }
+
+ return rc;
+}
+
+void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked)
+{
+ struct gprs_ns2_nse *nse = nsvc->nse;
+ struct gprs_ns2_vc *tmp;
+ struct gprs_ns2_signal_data nssd = {0};
+
+ if (nse->unblocked == unblocked)
+ return;
+
+ nssd.nse = nse;
+ nssd.nsei = nse->nsei;
+
+ /* this is the first unblocked NSVC on a unavaiable NSE */
+ if (!nse->unblocked) {
+ nse->unblocked = true;
+ osmo_signal_dispatch(SS_L_NS, S_NSE_AVAILABLE, &nssd);
+ return;
+ }
+
+ /* check if there are any remaining alive vcs */
+ llist_for_each_entry(tmp, &nse->vc, list) {
+ if (gprs_ns2_vc_is_unblocked(tmp)) {
+ return;
+ }
+ }
+
+ /* nse became unavailable */
+ nse->unblocked = false;
+ osmo_signal_dispatch(SS_L_NS, S_NSE_UNAVAILABLE, &nssd);
+}
+
+/* TODO: remove this global, why the hell do I use it */
+static bool gprs_fsm_vc_registered = false;
+static bool gprs_fsm_sns_registered = false;
+
+
+/*! Create a new GPRS NS instance
+ * \param[in] cb Call-back function for incoming BSSGP data
+ * \returns dynamically allocated gprs_ns_inst
+ */
+struct gprs_ns2_inst *gprs_ns2_instantiate(gprs_ns2_cb_t *cb, void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+
+ if (!gprs_fsm_vc_registered) {
+ if (gprs_ns2_vc_fsm_init() < 0)
+ return NULL;
+ gprs_fsm_vc_registered = true;
+ }
+ if (!gprs_fsm_sns_registered) {
+ if (gprs_ns2_sns_init() < 0)
+ return NULL;
+ gprs_fsm_sns_registered = true;
+ }
+
+ nsi = talloc_zero(ctx, struct gprs_ns2_inst);
+ if (!nsi)
+ return NULL;
+
+ nsi->cb = cb;
+ 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 */
+
+ return nsi;
+}
+
+void gprs_ns2_free(struct gprs_ns2_inst *nsi)
+{
+ struct gprs_ns2_vc_bind *bind, *tbind;
+ struct gprs_ns2_nse *nse, *ntmp;
+
+ if (!nsi)
+ return;
+
+ llist_for_each_entry_safe(nse, ntmp, &nsi->nse, list) {
+ gprs_ns2_free_nse(nse);
+ }
+
+ llist_for_each_entry_safe(bind, tbind, &nsi->binding, list) {
+ gprs_ns2_free_bind(bind);
+ }
+}
+
+/*!
+ * \brief gprs_ns2_dynamic_create_nse
+ * \param nsi the instance to modify
+ * \param create_nse if NSE can be created on receiving package. SGSN set this.
+ * \return
+ */
+int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse)
+{
+ nsi->create_nse = create_nse;
+
+ return 0;
+}
+
+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->vc, list) {
+ if (nsvc->sns_only)
+ continue;
+
+ gprs_ns2_vc_fsm_start(nsvc);
+ }
+}
+
+void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode)
+{
+ bind->use_reset_block_unblock = mode == NS2_VC_MODE_BLOCKRESET ? true : false;
+}
+
+void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct gprs_ns2_vc *nsvc, *tmp;
+ if (!bind)
+ return;
+
+ llist_for_each_entry_safe(nsvc, tmp, &bind->vc, blist) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ if (bind->driver->free_bind)
+ bind->driver->free_bind(bind);
+
+ llist_del(&bind->list);
+ talloc_free(bind);
+}
+/*! @} */
diff --git a/src/gb/gprs_ns2_driver.c b/src/gb/gprs_ns2_driver.c
new file mode 100644
index 0000000..e576550
--- /dev/null
+++ b/src/gb/gprs_ns2_driver.c
@@ -0,0 +1,372 @@
+
+#include <errno.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"
+#include "gprs_ns2_driver.h"
+#include "gprs_ns2_vc_fsm.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_fd fd;
+ struct osmo_sockaddr addr;
+ int dscp;
+};
+
+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;
+
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->vc));
+
+ osmo_fd_close(&priv->fd);
+ talloc_free(priv);
+}
+
+static void free_vc(struct gprs_ns2_vc *vc)
+{
+ OSMO_ASSERT(vc);
+
+ if (!vc->priv)
+ return;
+
+ talloc_free(vc->priv);
+ vc->priv = NULL;
+}
+
+int gprs_ns2_find_vc_by_sockaddr(struct gprs_ns2_vc_bind *bind, struct osmo_sockaddr *saddr, struct gprs_ns2_vc **result)
+{
+ struct gprs_ns2_vc *vc1;
+ struct priv_vc *vcpriv;
+
+ if (!result)
+ return -EINVAL;
+
+ llist_for_each_entry(vc1, &bind->vc, blist) {
+ vcpriv = vc1->priv;
+ if (vcpriv->remote.u.sa.sa_family != saddr->u.sa.sa_family)
+ continue;
+ if (osmo_sockaddr_cmp(&vcpriv->remote, saddr))
+ continue;
+
+ *result = vc1;
+ return 0;
+ }
+
+ return 1;
+}
+
+static inline int nsip_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;
+}
+
+/*!
+ * \brief nsip_vc_sendmsg will send the msg and free msgb afterwards.
+ * \param vc
+ * \param msg
+ * \return < 0 on erros, otherwise the bytes sent.
+ */
+static int nsip_vc_sendmsg(struct gprs_ns2_vc *vc, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc_bind *bind = vc->bind;
+ struct priv_vc *priv = vc->priv;
+
+ rc = nsip_sendmsg(bind, msg, &priv->remote);
+
+ return rc;
+}
+
+/* Read a single NS-over-IP message */
+static struct msgb *read_nsip_msg(struct osmo_fd *bfd, int *error,
+ struct osmo_sockaddr *saddr)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ int ret = 0;
+ socklen_t saddr_len = sizeof(*saddr);
+
+ if (!msg) {
+ *error = -ENOMEM;
+ return NULL;
+ }
+
+ ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE - NS_ALLOC_HEADROOM, 0,
+ &saddr->u.sa, &saddr_len);
+ if (ret < 0) {
+ LOGP(DNS, LOGL_ERROR, "recv error %s during NSIP recvfrom %s\n",
+ strerror(errno), osmo_sock_get_name2(bfd->fd));
+ msgb_free(msg);
+ *error = ret;
+ return NULL;
+ } else if (ret == 0) {
+ msgb_free(msg);
+ *error = ret;
+ return NULL;
+ }
+
+ msg->l2h = msg->data;
+ msgb_put(msg, ret);
+
+ return msg;
+}
+
+static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *vc, struct osmo_sockaddr *remote)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ vc->priv = priv;
+ priv->remote = *remote;
+
+ return priv;
+}
+
+static int handle_nsip_read(struct osmo_fd *bfd)
+{
+ int rc;
+ int error = 0;
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct osmo_sockaddr saddr;
+ struct gprs_ns2_vc *vc;
+ struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+ struct msgb *reject;
+
+ if (!msg)
+ return -EINVAL;
+
+ /* check if a vc is available */
+ rc = gprs_ns2_find_vc_by_sockaddr(bind, &saddr, &vc);
+ if (rc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, "newconnection", true, &reject, &vc);
+ switch (rc) {
+ case GPRS_NS2_CS_FOUND:
+ rc = ns2_recv_vc(bind->nsi, vc, msg);
+ break;
+ case GPRS_NS2_CS_ERROR:
+ case GPRS_NS2_CS_SKIPPED:
+ rc = 0;
+ break;
+ case GPRS_NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ nsip_sendmsg(bind, reject, &saddr);
+ return 0;
+ case GPRS_NS2_CS_CREATED:
+ /* TODO: check if we need to call recv_vc(). It might be already enough to be parsed by create_vc() */
+ ns2_driver_alloc_vc(bind, vc, &saddr);
+ gprs_ns2_vc_fsm_start(vc);
+ rc = ns2_recv_vc(bind->nsi, vc, msg);
+ break;
+ }
+ } else {
+ /* VC found */
+ rc = ns2_recv_vc(bind->nsi, vc, msg);
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_nsip_write(struct osmo_fd *bfd)
+{
+ /* FIXME: actually send the data here instead of nsip_sendmsg() */
+ return -EIO;
+}
+
+static int nsip_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ int rc = 0;
+
+ if (what & OSMO_FD_READ)
+ rc = handle_nsip_read(bfd);
+ if (what & OSMO_FD_WRITE)
+ rc = handle_nsip_write(bfd);
+
+ return rc;
+}
+
+/*!
+ * \brief bind to an IPv4/IPv6 address
+ * \param[in] nsi NS Instance in which to create the NSVC
+ * \param[in] address the address to bind to
+ * \param[out] result if set, returns the bind object
+ * \return
+ */
+int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ struct priv_bind *priv;
+ int rc;
+
+ if (!bind)
+ return -ENOSPC;
+
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) {
+ talloc_free(bind);
+ return -EINVAL;
+ }
+
+ bind->driver = &vc_driver_ip;
+ bind->send_vc = nsip_vc_sendmsg;
+ bind->free_vc = free_vc;
+ bind->nsi = nsi;
+
+ priv = bind->priv = talloc_zero(bind, struct priv_bind);
+ if (!priv) {
+ talloc_free(bind);
+ return -ENOSPC;
+ }
+ priv->fd.cb = nsip_fd_cb;
+ priv->fd.data = bind;
+ priv->addr = *local;
+ INIT_LLIST_HEAD(&bind->vc);
+
+ llist_add(&bind->list, &nsi->binding);
+
+ rc = osmo_sock_init3_ofd(&priv->fd, SOCK_DGRAM, IPPROTO_UDP,
+ local, NULL,
+ OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ talloc_free(priv);
+ talloc_free(bind);
+ return rc;
+ }
+
+ if (dscp > 0) {
+ priv->dscp = dscp;
+
+ rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
+ &dscp, sizeof(dscp));
+ if (rc < 0)
+ LOGP(DNS, LOGL_ERROR,
+ "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
+ dscp, rc, errno);
+ }
+
+ ns2_vty_bind_apply(bind);
+
+ if (result)
+ *result = bind;
+
+ return 0;
+}
+
+struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *priv;
+
+ OSMO_ASSERT(bind);
+ OSMO_ASSERT(nse);
+ OSMO_ASSERT(remote);
+
+ nsvc = ns2_vc_alloc(bind, nse, true);
+ nsvc->priv = talloc_zero(bind, struct priv_vc);
+ if (!nsvc->priv) {
+ gprs_ns2_free_nsvc(nsvc);
+ return NULL;
+ }
+
+ priv = nsvc->priv;
+ priv->remote = *remote;
+
+ nsvc->ll = GPRS_NS_LL_UDP;
+
+ return nsvc;
+}
+
+struct osmo_sockaddr *gprs_ns2_ip_vc_sockaddr(struct gprs_ns2_vc *nsvc)
+{
+ struct priv_vc *priv;
+
+ if (nsvc->ll != GPRS_NS_LL_UDP)
+ return NULL;
+
+ priv = nsvc->priv;
+ return &priv->remote;
+}
+
+struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ OSMO_ASSERT(bind);
+
+ priv = bind->priv;
+ return &priv->addr;
+}
+
+int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_ip);
+}
+
+int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp)
+{
+ struct priv_bind *priv;
+ int rc;
+
+ OSMO_ASSERT(bind);
+
+ priv = bind->priv;
+
+ if (dscp != priv->dscp) {
+ priv->dscp = dscp;
+
+ rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
+ &dscp, sizeof(dscp));
+ if (rc < 0)
+ LOGP(DNS, LOGL_ERROR,
+ "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
+ dscp, rc, errno);
+ }
+
+ return rc;
+}
+
+void gprs_ns2_ip_bind_reset_block_unblock(
+ struct gprs_ns2_vc_bind *bind,
+ bool use_reset_block_unblock)
+{
+ bind->use_reset_block_unblock = use_reset_block_unblock;
+}
diff --git a/src/gb/gprs_ns2_driver.h b/src/gb/gprs_ns2_driver.h
new file mode 100644
index 0000000..339f16b
--- /dev/null
+++ b/src/gb/gprs_ns2_driver.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <stdbool.h>
+
+struct osmo_sockaddr;
+
+struct gprs_ns2_inst;
+struct gprs_ns2_nse;
+struct gprs_ns2_vc_bind;
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
new file mode 100644
index 0000000..f226788
--- /dev/null
+++ b/src/gb/gprs_ns2_internal.h
@@ -0,0 +1,199 @@
+/*! \file gprs_ns2_internal.h */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include <osmocom/gprs/gprs_ns_common.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+struct gprs_ns2_vc_driver;
+struct gprs_ns2_vc_bind;
+
+struct tlv_parsed;
+
+/*! An instance of the NS protocol stack */
+struct gprs_ns2_inst {
+ /*! callback to the user for incoming UNIT DATA IND */
+ gprs_ns2_cb_t *cb;
+
+ /*! 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;
+
+ /*! create dynamic NSE on receiving packages */
+ bool create_nse;
+
+ uint16_t timeout[NS_TIMERS_COUNT];
+};
+
+/*! 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 vc;
+
+ /*! true if this NS was created by VTY or pcu socket) */
+ bool persistant;
+
+ /*! true if this NS has at an alive VC */
+ bool unblocked;
+
+ struct osmo_fsm_inst *bss_sns_fi;
+};
+
+/*! 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 persistant;
+
+ /*! 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;
+
+ /*! signaling 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;
+
+ uint32_t state;
+ uint32_t remote_state;
+
+ struct osmo_timer_list timer;
+ enum nsvc_timer_mode timer_mode;
+ struct timeval timer_started;
+ int alive_retries;
+
+ unsigned int remote_end_is_sgsn:1;
+ unsigned int persistent:1;
+ unsigned int nsvci_is_valid:1;
+ unsigned int sns_only:1;
+
+ struct rate_ctr_group *ctrg;
+ struct osmo_stat_item_group *statg;
+
+ /*! which link-layer are we based on? */
+ enum gprs_ns_ll ll;
+
+ enum gprs_ns2_vc_mode mode;
+
+ struct osmo_fsm_inst *fi;
+};
+
+/*! Structure repesenting a bind instance. E.g. IPv4 listen port. */
+struct gprs_ns2_vc_bind {
+ /*! list entry in nsi */
+ struct llist_head list;
+ /*! list of all VC */
+ struct llist_head vc;
+ /*! driver private structure */
+ void *priv;
+ /*! a pointer back to the nsi */
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_driver *driver;
+
+ /*! if VCs use reset/block/unblock method. IP shall not use this */
+ bool use_reset_block_unblock;
+
+ /*! send a msg over a VC */
+ int (*send_vc)(struct gprs_ns2_vc *vc, struct msgb *msg);
+
+ /*! free the vc priv data */
+ void (*free_vc)(struct gprs_ns2_vc *vc);
+};
+
+/*! Osmocom NS VC create status */
+enum gprs_ns2_cs {
+ GPRS_NS2_CS_CREATED, /*!< A NSVC object has been created */
+ GPRS_NS2_CS_FOUND, /*!< A NSVC object has been found */
+ GPRS_NS2_CS_REJECTED, /*!< Rejected and answered message */
+ GPRS_NS2_CS_SKIPPED, /*!< Skipped message */
+ GPRS_NS2_CS_ERROR, /*!< Failed to process message */
+};
+
+struct gprs_ns2_vc_driver {
+ const char *name;
+ void *priv;
+ void (*free_bind)(struct gprs_ns2_vc_bind *driver);
+};
+
+int ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const char *logname,
+ enum gprs_ns2_vc_mode mode,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success);
+
+int ns2_recv_vc(struct gprs_ns2_inst *nsi,
+ struct gprs_ns2_vc *vc,
+ struct msgb *msg);
+
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ bool initiater);
+
+struct msgb *gprs_ns2_msgb_alloc(void);
+
+static inline int ns2_is_sns(uint8_t pdu_type)
+{
+ return pdu_type >= SNS_PDUT_ACK && pdu_type <= SNS_PDUT_SIZE_ACK;
+}
+
+static inline int ns2_is_ns(uint8_t pdu_type)
+{
+ return pdu_type < SNS_PDUT_ACK || pdu_type > SNS_PDUT_SIZE_ACK;
+}
+
+void gprs_ns2_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats);
+
+void ns2_nse_notify_alive(struct gprs_ns2_vc *vc, bool alive);
+
+/* driver */
+struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ struct osmo_sockaddr *remote);
+void gprs_ns2_ip_bind_reset_block_unblock(
+ struct gprs_ns2_vc_bind *bind,
+ bool use_reset_block_unblock);
+
+/* sns */
+int gprs_ns2_sns_rx(struct gprs_ns2_vc *vc, struct msgb *msg, struct tlv_parsed *tp);
+int gprs_ns2_sns_init(void);
+struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
+ const char *id);
+int ns2_sns_bss_fsm_start(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc,
+ struct osmo_sockaddr *remote);
+void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc);
+
+/* vty.c */
+void ns2_vty_bind_apply(struct gprs_ns2_vc_bind *bind);
+
+/* nse */
+void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked);
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
new file mode 100644
index 0000000..8bae68f
--- /dev/null
+++ b/src/gb/gprs_ns2_sns.c
@@ -0,0 +1,924 @@
+/* Implementation of 3GPP TS 48.016 NS IP Sub-Network Service */
+/* (C) 2018 by Harald Welte <laforge at gnumonks.org> */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. In theory, the BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+#include "gprs_ns2_vc_fsm.h"
+
+#define S(x) (1 << (x))
+
+struct ns2_sns_state {
+ struct gprs_ns2_nse *nse;
+
+ /* initial connection. the initial connection will be terminated
+ * in configured state or moved into NSE if valid */
+ struct osmo_sockaddr initial;
+ /* all SNS PDU will be sent over this nsvc */
+ struct gprs_ns2_vc *sns_nsvc;
+
+ /* local configuration to send to the remote end */
+ struct gprs_ns_ie_ip4_elem *ip4_local;
+ size_t num_ip4_local;
+
+ /* local configuration to send to the remote end */
+ struct gprs_ns_ie_ip6_elem *ip6_local;
+ size_t num_ip6_local;
+
+ /* local configuration about our capabilities in terms of connections to
+ * remote (SGSN) side */
+ size_t num_max_nsvcs;
+ size_t num_max_ip4_remote;
+ size_t num_max_ip6_remote;
+
+ /* remote configuration as received */
+ struct gprs_ns_ie_ip4_elem *ip4_remote;
+ unsigned int num_ip4_remote;
+
+ /* remote configuration as received */
+ struct gprs_ns_ie_ip6_elem *ip6_remote;
+ unsigned int num_ip6_remote;
+};
+
+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;
+}
+
+/* helper function to compute the sum of all (data or signaling) weights */
+static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
+ bool data_weight)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < num; i++) {
+ if (data_weight)
+ weight_sum += ip4[i].data_weight;
+ else
+ weight_sum += ip4[i].sig_weight;
+ }
+ return weight_sum;
+}
+#define ip4_weight_sum_data(x,y) ip4_weight_sum(x, y, true)
+#define ip4_weight_sum_sig(x,y) ip4_weight_sum(x, y, false)
+
+static struct gprs_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, &sa);
+}
+
+/*! called when a nsvc is beeing freed */
+void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *tmp;
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) nsvc->nse->bss_sns_fi->priv;
+
+ if (nsvc != gss->sns_nsvc)
+ return;
+
+ if (nsvc->nse->unblocked) {
+ /* choose a different signal nsvc */
+ llist_for_each_entry(tmp, &nse->vc, list) {
+ if (gprs_ns2_vc_is_unblocked(tmp))
+ gss->sns_nsvc = tmp;
+ }
+ }
+
+ gss->sns_nsvc = NULL;
+ /* TODO: state change */
+}
+
+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 ns2_sns_state *gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind *bind;
+ 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;
+
+ /* for every bind, create a connection if bind type == IP */
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ /* 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 = ip4->sig_weight;
+ nsvc->data_weight = ip4->data_weight;
+
+ /* TODO: check if we have to start the VC FSM here */
+ }
+}
+
+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 gprs_ns2_vc_bind *bind;
+ struct osmo_sockaddr *tmpaddr, remote;
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[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;
+
+ llist_for_each_entry(bind, &nse->nsi->binding, list) {
+ bool found = false;
+
+ llist_for_each_entry(nsvc, &nse->vc, list) {
+ if (nsvc->bind != bind)
+ continue;
+
+ if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_sockaddr(nsvc))) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ 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 = 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Add a given remote IPv4 element to gprs_sns_state */
+static int add_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ if (gss->num_ip4_remote >= gss->num_max_ip4_remote)
+ return -E2BIG;
+
+ gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote, struct gprs_ns_ie_ip4_elem,
+ gss->num_ip4_remote+1);
+ gss->ip4_remote[gss->num_ip4_remote] = *ip4;
+ gss->num_ip4_remote += 1;
+ return 0;
+}
+
+/* Remove a given remote IPv4 element from gprs_sns_state */
+static int remove_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ continue;
+ /* all array elements < i remain as they are; all > i are shifted left by one */
+ memmove(&gss->ip4_remote[i], &gss->ip4_remote[i+1], gss->num_ip4_remote-i-1);
+ gss->num_ip4_remote -= 1;
+ return 0;
+ }
+ return -1;
+}
+
+/* update the weights for specified remote IPv4 */
+static int update_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (gss->ip4_remote[i].ip_addr != ip4->ip_addr ||
+ gss->ip4_remote[i].udp_port != ip4->udp_port)
+ continue;
+ gss->ip4_remote[i].sig_weight = ip4->sig_weight;
+ gss->ip4_remote[i].data_weight = ip4->data_weight;
+ return 0;
+ }
+ return -1;
+}
+
+
+static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ 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 = nsvc_by_ip4_elem(nse, ip4);
+
+ /* 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". */
+
+ update_remote_ip4_elem(gss, ip4);
+
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_NOTICE, "Couldn't find NS-VC for SNS-CHANGE_WEIGHT\n");
+ return -NS_CAUSE_NSVC_UNKNOWN;
+ }
+
+ 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, ip4->data_weight,
+ nsvc->sig_weight, ip4->sig_weight);
+
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+
+ return 0;
+}
+
+static int do_sns_delete(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ 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 = nsvc_by_ip4_elem(nse, ip4);
+
+ if (remove_remote_ip4_elem(gss, ip4) < 0)
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_NOTICE, "Couldn't find NS-VC for SNS-DELETE\n");
+ return -NS_CAUSE_NSVC_UNKNOWN;
+ }
+ 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)
+{
+ 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;
+
+ /* 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". */
+ if (add_remote_ip4_elem(gss, ip4) < 0)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+
+ /* 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" */
+ nsvc = nsvc_by_ip4_elem(nse, ip4);
+ if (nsvc)
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip4(fi, nse, ip4);
+
+ gprs_ns2_start_alive_all_nsvcs(nse);
+
+ return 0;
+}
+
+
+
+/***********************************************************************
+ * BSS-side FSM for IP Sub-Network Service
+ ***********************************************************************/
+
+enum gprs_sns_bss_state {
+ GPRS_SNS_ST_UNCONFIGURED,
+ GPRS_SNS_ST_SIZE, /*!< SNS-SIZE procedure ongoing */
+ GPRS_SNS_ST_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
+ GPRS_SNS_ST_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
+ GPRS_SNS_ST_CONFIGURED,
+};
+
+enum gprs_sns_event {
+ GPRS_SNS_EV_START,
+ GPRS_SNS_EV_SIZE,
+ GPRS_SNS_EV_SIZE_ACK,
+ GPRS_SNS_EV_CONFIG,
+ GPRS_SNS_EV_CONFIG_END, /*!< SNS-CONFIG with end flag received */
+ GPRS_SNS_EV_CONFIG_ACK,
+ GPRS_SNS_EV_ADD,
+ GPRS_SNS_EV_DELETE,
+ GPRS_SNS_EV_CHANGE_WEIGHT,
+};
+
+static const struct value_string gprs_sns_event_names[] = {
+ { GPRS_SNS_EV_START, "START" },
+ { GPRS_SNS_EV_SIZE, "SIZE" },
+ { GPRS_SNS_EV_SIZE_ACK, "SIZE_ACK" },
+ { GPRS_SNS_EV_CONFIG, "CONFIG" },
+ { GPRS_SNS_EV_CONFIG_END, "CONFIG_END" },
+ { GPRS_SNS_EV_CONFIG_ACK, "CONFIG_ACK" },
+ { GPRS_SNS_EV_ADD, "ADD" },
+ { GPRS_SNS_EV_DELETE, "DELETE" },
+ { GPRS_SNS_EV_CHANGE_WEIGHT, "CHANGE_WEIGHT" },
+ { 0, NULL }
+};
+
+static void ns2_sns_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ switch (event) {
+ case GPRS_SNS_EV_START:
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void ns2_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_SIZE_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* FIXME: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS,
+ nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void ns2_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ uint16_t num_max_ip4_remote = gss->num_max_ip4_remote;
+
+ gprs_ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, &num_max_ip4_remote, NULL);
+}
+
+
+static void ns2_sns_st_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 = ns_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_CONFIG_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* FIXME: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, 0, 0);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void ns2_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ /* Transmit SNS-CONFIG */
+ gprs_ns2_tx_sns_config(gss->sns_nsvc, true, gss->ip4_local, gss->num_ip4_local);
+}
+
+static void ns2_sns_st_config_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;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ uint8_t cause;
+
+ switch (event) {
+ case GPRS_SNS_EV_CONFIG_END:
+ case GPRS_SNS_EV_CONFIG:
+ tp = data;
+#if 0 /* part of incoming SNS-SIZE (doesn't happen on BSS side */
+ if (TLVP_PRESENT(tp, NS_IE_RESET_FLAG)) {
+ /* reset all existing config */
+ if (gss->ip4_remote)
+ talloc_free(gss->ip4_remote);
+ gss->num_ip4_remote = 0;
+ }
+#endif
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ gprs_ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ /* realloc to the new size */
+ gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote,
+ struct gprs_ns_ie_ip4_elem,
+ gss->num_ip4_remote+num_v4);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->ip4_remote[gss->num_ip4_remote], v4_list, num_v4*sizeof(*v4_list));
+ gss->num_ip4_remote += num_v4;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
+ gss->num_ip4_remote);
+ if (event == GPRS_SNS_EV_CONFIG_END) {
+ /* check if sum of data / sig weights == 0 */
+ if (ip4_weight_sum_data(gss->ip4_remote, gss->num_ip4_remote) == 0 ||
+ ip4_weight_sum_sig(gss->ip4_remote, gss->num_ip4_remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ gprs_ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ create_missing_nsvcs(fi);
+ gprs_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 */
+ gprs_ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+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 = NULL;
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ unsigned int num_v4 = 0;
+ uint8_t trans_id;
+ uint8_t cause = 0xff;
+ unsigned int i;
+ int rc;
+
+ switch (event) {
+ case GPRS_SNS_EV_ADD:
+ tp = data;
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ for (i = 0; i < num_v4; i++) {
+ rc = do_sns_add(fi, &v4_list[i]);
+ if (rc < 0) {
+ unsigned int j;
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, &v4_list[j]);
+ cause = -rc;
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4);
+ break;
+ case GPRS_SNS_EV_DELETE:
+ tp = data;
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ for (i = 0; i < num_v4; i++) {
+ rc = do_sns_delete(fi, &v4_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ } else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 5)) {
+ /* delete all NS-VCs for given IP 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;
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ /* make a copy as do_sns_delete() will change the array underneath us */
+ ip4_remote = talloc_memdup(fi, gss->ip4_remote,
+ gss->num_ip4_remote*sizeof(*v4_list));
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (ip4_remote[i].ip_addr == ip_addr) {
+ rc = do_sns_delete(fi, &ip4_remote[i]);
+ 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 */
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4);
+ break;
+ case GPRS_SNS_EV_CHANGE_WEIGHT:
+ tp = data;
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ for (i = 0; i < num_v4; i++) {
+ rc = do_sns_change_weight(fi, &v4_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0);
+ break;
+ }
+ gprs_ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4);
+ break;
+ }
+}
+
+static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_signal_data nssd = {0};
+// struct gprs_ns2_nse *nse = gss->nse;
+// struct gprs_ns2_vc *nsvc;
+// struct gprs_ns_ie_ip4_elem *ipv4;
+// struct osmo_sockaddr remote, *tmp;
+
+ nssd.nse = gss->nse;
+ nssd.nsei = gss->nse->nsei;
+
+// remote.u.sin.sin_family = AF_INET;
+
+// /* drop initial connection */
+// bool found = false;
+// for (int i = 0; i < gss->num_ip4_local; i++)
+// {
+// ipv4 = &gss->ip4_remote[i];
+// remote.u.sin.sin_addr.s_addr = ipv4->ip_addr;
+// remote.u.sin.sin_port = ipv4->udp_port;
+
+// if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_sockaddr(gss->sns_nsvc))) {
+// found = true;
+// break;
+// }
+// }
+
+// if (!found) {
+// /* it will update the sns by callback from free_nsvc */
+// gprs_ns2_free_nsvc(gss->sns_nsvc);
+// }
+
+ osmo_signal_dispatch(SS_L_NS, S_SNS_CONFIGURED, &nssd);
+}
+
+static const struct osmo_fsm_state ns2_sns_bss_states[] = {
+ [GPRS_SNS_ST_UNCONFIGURED] = {
+ .in_event_mask = S(GPRS_SNS_EV_START),
+ .out_state_mask = S(GPRS_SNS_ST_SIZE),
+ .name = "UNCONFIGURED",
+ .action = ns2_sns_st_unconfigured,
+ },
+ [GPRS_SNS_ST_SIZE] = {
+ .in_event_mask = S(GPRS_SNS_EV_SIZE_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SIZE) |
+ S(GPRS_SNS_ST_CONFIG_BSS),
+ .name = "SIZE",
+ .action = ns2_sns_st_size,
+ .onenter = ns2_sns_st_size_onenter,
+ },
+ [GPRS_SNS_ST_CONFIG_BSS] = {
+ .in_event_mask = S(GPRS_SNS_EV_CONFIG_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_CONFIG_BSS) |
+ S(GPRS_SNS_ST_CONFIG_SGSN),
+ .name = "CONFIG_BSS",
+ .action = ns2_sns_st_config_bss,
+ .onenter = ns2_sns_st_config_bss_onenter,
+ },
+ [GPRS_SNS_ST_CONFIG_SGSN] = {
+ .in_event_mask = S(GPRS_SNS_EV_CONFIG) |
+ S(GPRS_SNS_EV_CONFIG_END),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_CONFIGURED),
+ .name = "CONFIG_SGSN",
+ .action = ns2_sns_st_config_sgsn,
+ },
+ [GPRS_SNS_ST_CONFIGURED] = {
+ .in_event_mask = S(GPRS_SNS_EV_ADD) |
+ S(GPRS_SNS_EV_DELETE) |
+ S(GPRS_SNS_EV_CHANGE_WEIGHT),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED),
+ .name = "CONFIGURED",
+ .action = ns2_sns_st_configured,
+ .onenter = ns2_sns_st_configured_onenter,
+ },
+};
+
+static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ switch (fi->T) {
+ case 1:
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ break;
+ case 2:
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ break;
+ }
+ return 0;
+}
+
+static struct osmo_fsm gprs_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 = 0,
+ .allstate_action = NULL,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_bss_timer_cb,
+ /* .log_subsys = DNS, "is not constant" */
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+};
+
+
+static struct gprs_ns_ie_ip4_elem *alloc_ip4_elem(void *ctx, struct osmo_sockaddr *sockaddr,
+ uint8_t sig_weight, uint8_t data_weight)
+{
+ struct gprs_ns_ie_ip4_elem *ip4;
+
+ if (sockaddr->u.sa.sa_family != AF_INET)
+ return NULL;
+
+ ip4 = talloc_zero(ctx, struct gprs_ns_ie_ip4_elem);
+ if (!ip4)
+ return NULL;
+
+ ip4->ip_addr = sockaddr->u.sin.sin_addr.s_addr;
+ ip4->udp_port = sockaddr->u.sin.sin_port;
+ ip4->sig_weight = sig_weight;
+ ip4->data_weight = data_weight;
+
+ return ip4;
+}
+
+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;
+
+ return fi;
+err:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
+}
+
+int ns2_sns_bss_fsm_start(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, struct osmo_sockaddr *remote)
+{
+ struct osmo_fsm_inst *fi = nse->bss_sns_fi;
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ struct gprs_ns_ie_ip4_elem *ip4;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ struct osmo_sockaddr *sa, local;
+
+ gss->initial = *remote;
+ gss->sns_nsvc = nsvc;
+ nsvc->sns_only = 1;
+
+ /* TODO: create IPv4 list from the one IP/port the NS instance has */
+ 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;
+ count++;
+ }
+
+ ip4 = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip4_elem) * count);
+ if (!ip4)
+ goto err;
+
+ gss->ip4_local = ip4;
+
+ 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;
+
+ /* check if this is an specific bind */
+ if ((sa->u.sas.ss_family == AF_INET && sa->u.sin.sin_addr.s_addr == 0) ||
+ (sa->u.sas.ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr) ))
+ {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip4->ip_addr = local.u.sin.sin_addr.s_addr;
+ } else {
+ ip4->ip_addr = sa->u.sin.sin_addr.s_addr;
+ }
+
+ ip4->udp_port = sa->u.sin.sin_port;
+ ip4->sig_weight = 2;
+ ip4->data_weight = 1;
+
+ ip4++;
+ }
+
+ gss->num_ip4_local = count;
+ gss->num_max_nsvcs = 8;
+ gss->num_max_ip4_remote = 4;
+
+ return osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_START, NULL);
+
+err:
+ return -1;
+}
+
+/* main entry point for receiving SNS messages from the network */
+int gprs_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 = msgb_nsei(msg);
+ struct osmo_fsm_inst *fi;
+
+ LOGP(DNS, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+
+ /* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
+ fi = nse->bss_sns_fi;
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_SIZE:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE, tp);
+ break;
+ case SNS_PDUT_SIZE_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE_ACK, tp);
+ break;
+ case SNS_PDUT_CONFIG:
+ if (nsh->data[0] & 0x01)
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_END, tp);
+ else
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG, tp);
+ break;
+ case SNS_PDUT_CONFIG_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_ACK, tp);
+ break;
+ case SNS_PDUT_ADD:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_ADD, tp);
+ break;
+ case SNS_PDUT_DELETE:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_DELETE, tp);
+ break;
+ case SNS_PDUT_CHANGE_WEIGHT:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CHANGE_WEIGHT, tp);
+ break;
+ case SNS_PDUT_ACK:
+ LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx unsupported SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ break;
+ default:
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int gprs_ns2_sns_init(void)
+{
+ /* "DNS" is not a constant/#define, but an integer variable set by the client app */
+ gprs_ns2_sns_bss_fsm.log_subsys = DNS;
+ return osmo_fsm_register(&gprs_ns2_sns_bss_fsm);
+}
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+
+static void vty_dump_sns_ip4(struct vty *vty, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct in_addr in = { .s_addr = ip4->ip_addr };
+ vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE);
+}
+
+void gprs_ns2_dump_vty(struct vty *vty, 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_inst(vty, nse->bss_sns_fi);
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+
+ vty_out(vty, "Maximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu%s",
+ gss->num_max_nsvcs, gss->num_max_ip4_remote, VTY_NEWLINE);
+
+ vty_out(vty, "Local IPv4 Endpoints:%s", VTY_NEWLINE);
+ for (i = 0; i < gss->num_ip4_local; i++)
+ vty_dump_sns_ip4(vty, &gss->ip4_local[i]);
+
+ vty_out(vty, "Remote IPv4 Endpoints:%s", VTY_NEWLINE);
+ for (i = 0; i < gss->num_ip4_remote; i++)
+ vty_dump_sns_ip4(vty, &gss->ip4_remote[i]);
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c
new file mode 100644
index 0000000..77806ca
--- /dev/null
+++ b/src/gb/gprs_ns2_vc_fsm.c
@@ -0,0 +1,671 @@
+/* Implementation of 3GPP TS 48.016 NS IP Sub-Network Service
+ * based on gprs_ns_sns.c by Harald Welte <laforge at gnumonks.org>
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+ * Author: Alexander Couzens <lynxis at fe80.eu> */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. In theory, the BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+#include "gprs_ns2_vc_fsm.h"
+#include "gprs_ns2_message.h"
+
+#define S(x) (1 << (x))
+
+#define DNS 10
+
+struct gprs_ns2_vc_priv {
+ struct gprs_ns2_vc *vc;\
+ /* how often the timer was triggered */
+ int N;
+ /* The initiater is responsible to UNBLOCK the VC. The BSS is usually the initiater.
+ * It can change while runtime. The side which blocks an unblocked side.*/
+ bool initiater;
+
+ /* the alive counter is present in all states */
+ struct {
+ struct osmo_timer_list timer;
+ enum ns2_timeout mode;
+ int N;
+ } 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 -> ALIVE -> 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 ALIVE 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_ALIVE, /* only used when not using RESET/BLOCK procedure */
+};
+
+enum gprs_ns2_vc_event {
+ GPRS_NS2_EV_START,
+
+ /* received messages */
+ GPRS_NS2_EV_RESET,
+ GPRS_NS2_EV_RESET_ACK,
+ GPRS_NS2_EV_UNBLOCK,
+ GPRS_NS2_EV_UNBLOCK_ACK,
+ GPRS_NS2_EV_BLOCK,
+ GPRS_NS2_EV_BLOCK_ACK,
+ GPRS_NS2_EV_ALIVE,
+ GPRS_NS2_EV_ALIVE_ACK,
+ GPRS_NS2_EV_STATUS,
+
+ GPRS_NS2_EV_UNITDATA,
+};
+
+static const struct value_string gprs_ns2_vc_event_names[] = {
+ { GPRS_NS2_EV_START, "START" },
+ { GPRS_NS2_EV_RESET, "RESET" },
+ { GPRS_NS2_EV_RESET_ACK, "RESET_ACK" },
+ { GPRS_NS2_EV_UNBLOCK, "UNBLOCK" },
+ { GPRS_NS2_EV_UNBLOCK_ACK, "UNBLOCK_ACK" },
+ { GPRS_NS2_EV_BLOCK, "BLOCK" },
+ { GPRS_NS2_EV_BLOCK_ACK, "BLOCK_ACK" },
+ { GPRS_NS2_EV_ALIVE, "ALIVE" },
+ { GPRS_NS2_EV_ALIVE_ACK, "ALIVE_ACK" },
+ { GPRS_NS2_EV_STATUS, "STATUS" },
+ { GPRS_NS2_EV_UNITDATA, "UNITDATA" },
+ { 0, NULL }
+};
+
+static void signal_dispatch(struct gprs_ns2_vc *nsvc, unsigned int signal,
+ uint8_t cause)
+{
+ struct gprs_ns2_signal_data nssd = {0};
+
+ nssd.nse = nsvc->nse;
+ nssd.nsei = nsvc->nse->nsei;
+ nssd.nsvc = nsvc;
+ nssd.cause = cause;
+
+ osmo_signal_dispatch(SS_L_NS, signal, &nssd);
+}
+
+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->vc->nse->nsi;
+}
+
+static void start_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);
+
+ if (osmo_timer_pending(&priv->alive.timer))
+ return;
+
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ priv->alive.N = 0;
+
+ gprs_ns2_tx_alive(priv->vc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+}
+
+static void stop_test_procedure(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ osmo_timer_del(&priv->alive.timer);
+}
+
+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);
+
+ /* 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);
+}
+
+
+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;
+ gprs_ns2_tx_alive(priv->vc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ break;
+ case NS_TOUT_TNS_ALIVE:
+ priv->alive.N++;
+
+ if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+ gprs_ns2_tx_alive(priv->vc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ } else {
+ if (priv->vc->mode == NS2_VC_MODE_BLOCKRESET) {
+ /* lost connection */
+ 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_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ }
+ signal_dispatch(priv->vc, S_NS_ALIVE_EXP, 0);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void gprs_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->vc->nse->nsi;
+
+ switch (event) {
+ case GPRS_NS2_EV_START:
+ switch (priv->vc->mode) {
+ case NS2_VC_MODE_ALIVE:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE);
+ break;
+ case 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 gprs_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;
+
+ if (priv->initiater)
+ gprs_ns2_tx_reset(priv->vc, NS_CAUSE_OM_INTERVENTION);
+
+ stop_test_procedure(fi);
+ signal_dispatch(priv->vc, S_NS_RESET, 0);
+ ns2_nse_notify_unblocked(priv->vc, false);
+}
+
+static void gprs_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->initiater) {
+ switch (event) {
+ case GPRS_NS2_EV_RESET_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ } else {
+ /* we are on the receiving end */
+ switch (event) {
+ case GPRS_NS2_EV_RESET:
+ gprs_ns2_tx_reset_ack(priv->vc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ }
+}
+
+static void gprs_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;
+
+ if (priv->initiater)
+ gprs_ns2_tx_unblock(priv->vc);
+
+ start_test_procedure(fi);
+ signal_dispatch(priv->vc, S_NS_BLOCK, 0);
+}
+
+static void gprs_ns2_st_blocked(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->initiater) {
+ switch (event) {
+ case GPRS_NS2_EV_BLOCK:
+ /* TODO: BLOCK is a BLOCK_NACK */
+ gprs_ns2_tx_block_ack(priv->vc);
+ break;
+ case GPRS_NS2_EV_UNBLOCK:
+ gprs_ns2_tx_unblock_ack(priv->vc);
+ /* fall through */
+ case GPRS_NS2_EV_UNBLOCK_ACK:
+ 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_UNBLOCK:
+ gprs_ns2_tx_unblock_ack(priv->vc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
+ 0, 0);
+ break;
+ }
+ }
+}
+
+static void gprs_ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ signal_dispatch(priv->vc, S_NS_UNBLOCK, 0);
+ ns2_nse_notify_unblocked(priv->vc, true);
+}
+
+static void gprs_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_BLOCK:
+ priv->initiater = false;
+ gprs_ns2_tx_block_ack(priv->vc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 2);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void gprs_ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+
+ switch (event) {
+ case GPRS_NS2_EV_ALIVE_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
+ 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void gprs_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_ALIVE)
+ priv->N = 0;
+
+ gprs_ns2_tx_alive(priv->vc);
+ ns2_nse_notify_unblocked(priv->vc, false);
+}
+
+static void gprs_ns2_st_alive_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
+{
+ start_test_procedure(fi);
+}
+
+static const struct osmo_fsm_state gprs_ns2_vc_states[] = {
+ [GPRS_NS2_ST_UNCONFIGURED] = {
+ .in_event_mask = S(GPRS_NS2_EV_START),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE),
+ .name = "UNCONFIGURED",
+ .action = gprs_ns2_st_unconfigured,
+ },
+ [GPRS_NS2_ST_RESET] = {
+ .in_event_mask = S(GPRS_NS2_EV_RESET_ACK) | S(GPRS_NS2_EV_RESET),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_BLOCKED),
+ .name = "RESET",
+ .action = gprs_ns2_st_reset,
+ .onenter = gprs_ns2_st_reset_onenter,
+ },
+ [GPRS_NS2_ST_BLOCKED] = {
+ .in_event_mask = S(GPRS_NS2_EV_BLOCK) | S(GPRS_NS2_EV_BLOCK_ACK) |
+ S(GPRS_NS2_EV_UNBLOCK) | S(GPRS_NS2_EV_UNBLOCK_ACK),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_UNBLOCKED) |
+ S(GPRS_NS2_ST_BLOCKED),
+ .name = "BLOCKED",
+ .action = gprs_ns2_st_blocked,
+ .onenter = gprs_ns2_st_blocked_onenter,
+ },
+ [GPRS_NS2_ST_UNBLOCKED] = {
+ .in_event_mask = S(GPRS_NS2_EV_BLOCK),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE) |
+ S(GPRS_NS2_ST_BLOCKED),
+ .name = "UNBLOCKED",
+ .action = gprs_ns2_st_unblocked,
+ .onenter = gprs_ns2_st_unblocked_on_enter,
+ },
+
+ /* ST_ALIVE is only used on VC without RESET/BLOCK */
+ [GPRS_NS2_ST_ALIVE] = {
+ .in_event_mask = S(GPRS_NS2_EV_ALIVE_ACK),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_UNBLOCKED),
+ .name = "ALIVE",
+ .action = gprs_ns2_st_alive,
+ .onenter = gprs_ns2_st_alive_onenter,
+ .onleave = gprs_ns2_st_alive_onleave,
+ },
+};
+
+static int gprs_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;
+
+ if (priv->initiater) {
+ /* PCU timeouts */
+ switch (fi->state) {
+ case GPRS_NS2_ST_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:
+ priv->N++;
+ 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_ALIVE:
+ priv->N++;
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ } else {
+ priv->N = 0;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
+ struct msgb *msg)
+{
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ uint16_t bvci;
+
+ if (msgb_l2len(msg) < sizeof(*nsh) + 3)
+ 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];
+
+ msgb_bssgph(msg) = &nsh->data[3];
+ msgb_bvci(msg) = bvci;
+ msgb_nsei(msg) = priv->vc->nse->nsei;
+
+ nsi->cb(GPRS_NS_EVT_UNIT_DATA, msg, priv->vc->nse->nsei, bvci);
+}
+
+static void gprs_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);
+
+ switch (event) {
+ case GPRS_NS2_EV_RESET:
+ if (priv->vc->mode != NS2_VC_MODE_BLOCKRESET)
+ break;
+
+ /* move the FSM into reset */
+ if (fi->state != GPRS_NS2_ST_RESET) {
+ priv->initiater = 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 */
+ gprs_ns2_st_reset(fi, event, data);
+ break;
+ case GPRS_NS2_EV_ALIVE:
+ switch (fi->state) {
+ case GPRS_NS2_ST_UNCONFIGURED:
+ case GPRS_NS2_ST_RESET:
+ /* ignore ALIVE */
+ break;
+ default:
+ gprs_ns2_tx_alive_ack(priv->vc);
+ }
+ break;
+ case GPRS_NS2_EV_ALIVE_ACK:
+ /* for VCs without RESET/BLOCK/UNBLOCK, the connections comes after ALIVE_ACK unblocked */
+ if (fi->state == GPRS_NS2_ST_ALIVE)
+ gprs_ns2_st_alive(fi, event, data);
+ else
+ recv_test_procedure(fi);
+ break;
+ case GPRS_NS2_EV_UNITDATA:
+ switch (fi->state) {
+ case GPRS_NS2_ST_BLOCKED:
+ /* 7.2.1: the BLOCKED_ACK might be lost */
+ if (priv->initiater)
+ gprs_ns2_recv_unitdata(fi, data);
+ else
+ gprs_ns2_tx_status(priv->vc,
+ NS_CAUSE_NSVC_BLOCKED,
+ 0, data);
+ break;
+ /* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */
+ case GPRS_NS2_ST_ALIVE:
+ case GPRS_NS2_ST_UNBLOCKED:
+ gprs_ns2_recv_unitdata(fi, data);
+ break;
+ }
+ break;
+ }
+}
+
+static struct osmo_fsm gprs_ns2_vc_fsm = {
+ .name = "GPRS-NS2-VC",
+ .states = gprs_ns2_vc_states,
+ .num_states = ARRAY_SIZE(gprs_ns2_vc_states),
+ .allstate_event_mask = S(GPRS_NS2_EV_UNITDATA) |
+ S(GPRS_NS2_EV_RESET) |
+ S(GPRS_NS2_EV_ALIVE) |
+ S(GPRS_NS2_EV_ALIVE_ACK),
+ .allstate_action = gprs_ns2_vc_fsm_allstate_action,
+ .cleanup = NULL,
+ .timer_cb = gprs_ns2_vc_fsm_timer_cb,
+ /* .log_subsys = DNS, "is not constant" */
+ .event_names = gprs_ns2_vc_event_names,
+ .pre_term = NULL,
+};
+
+/*!
+ * \brief gprs_ns2_vc_fsm_alloc
+ * \param ctx
+ * \param vc
+ * \param id a char representation of the virtual curcuit
+ * \param initiater initiater is the site which starts the connection. Usually the BSS.
+ * \return NULL on error, otherwise the fsm
+ */
+struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *vc,
+ const char *id, bool initiater)
+{
+ struct osmo_fsm_inst *fi;
+ struct gprs_ns2_vc_priv *priv;
+
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_vc_fsm, vc, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ vc->fi = fi;
+ priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv);
+ priv->vc = vc;
+ priv->initiater = initiater;
+
+ osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi);
+
+ return fi;
+}
+
+/*!
+ * \brief gprs_ns2_vc_fsm_start start the FSM
+ * \param vc the virtual circuit
+ * \return 0 on success
+ */
+int gprs_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_START, NULL);
+ return 0;
+}
+
+/*!
+ * \brief gprs_ns2_vc_rx entry point for messages from the driver/VL
+ * \param vc the virtual circuit on which is recived
+ * \param msg the message
+ * \param tp the parsed TLVs
+ * \return 0 on success
+ */
+int gprs_ns2_vc_rx(struct gprs_ns2_vc *vc, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct osmo_fsm_inst *fi = vc->fi;
+ uint8_t cause;
+
+ /* TODO: 7.2: on UNBLOCK/BLOCK: check if NS-VCI is correct, if not answer STATUS with "NS-VC unknown" */
+ /* TODO: handle RESET with different VCI */
+ /* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */
+
+ OSMO_ASSERT(vc);
+ OSMO_ASSERT(fi);
+
+ if (gprs_ns2_validate(vc, nsh->pdu_type, msg, tp, &cause)) {
+ if (nsh->pdu_type != NS_PDUT_STATUS) {
+ return gprs_ns2_tx_status(vc, cause, 0, msg);
+ }
+ }
+
+ switch (nsh->pdu_type) {
+ case NS_PDUT_RESET:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET, tp);
+ break;
+ case NS_PDUT_RESET_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET_ACK, tp);
+ break;
+ case NS_PDUT_BLOCK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK, tp);
+ break;
+ case NS_PDUT_BLOCK_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK_ACK, tp);
+ break;
+ case NS_PDUT_UNBLOCK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK, tp);
+ break;
+ case NS_PDUT_UNBLOCK_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK_ACK, tp);
+ break;
+ case NS_PDUT_ALIVE:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE, tp);
+ break;
+ case NS_PDUT_ALIVE_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE_ACK, tp);
+ break;
+ case NS_PDUT_UNITDATA:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNITDATA, msg);
+ break;
+ default:
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", vc->nse->nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief gprs_ns2_vc_fsm_init must be called once in a library/executable
+ * \return 0 on success
+ */
+int gprs_ns2_vc_fsm_init(void)
+{
+ /* "DNS" is not a constant/#define, but an integer variable set by the client app */
+ gprs_ns2_vc_fsm.log_subsys = DNS;
+ return osmo_fsm_register(&gprs_ns2_vc_fsm);
+}
+
+/*!
+ * \brief gprs_ns2_vc_is_alive says if this
+ * \param vc
+ * \return
+ */
+int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *vc)
+{
+ return (vc->fi->state == GPRS_NS2_ST_UNBLOCKED);
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.h b/src/gb/gprs_ns2_vc_fsm.h
new file mode 100644
index 0000000..7a67181
--- /dev/null
+++ b/src/gb/gprs_ns2_vc_fsm.h
@@ -0,0 +1,16 @@
+
+#pragma once
+
+#include <stdbool.h>
+
+struct gprs_ns2_vc;
+struct msgb;
+struct tlv_parsed;
+
+int gprs_ns2_vc_fsm_init(void);
+struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *vc,
+ const char *id, bool initiate);
+int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *vc);
+int gprs_ns2_vc_rx(struct gprs_ns2_vc *vc, struct msgb *msg, struct tlv_parsed *tp);
+int gprs_ns2_vc_is_alive(struct gprs_ns2_vc *vc);
+int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *vc);
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
new file mode 100644
index 0000000..9e6e5b9
--- /dev/null
+++ b/src/gb/gprs_ns2_vty.c
@@ -0,0 +1,851 @@
+/*! \file gprs_ns_vty.c
+ * VTY interface for our GPRS Networks Service (NS) implementation. */
+/*
+ * (C) 2009-2014 by Harald Welte <laforge at gnumonks.org>
+ * (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
+ *
+ *
+ * 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 <osmocom/core/msgb.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/misc.h>
+
+#include "gprs_ns2_internal.h"
+#include "common_vty.h"
+
+struct ns2_vty_priv {
+ /* global listen */
+ struct osmo_sockaddr_str udp;
+ struct osmo_sockaddr_str frgreaddr;
+ int dscp;
+ bool use_reset_block_unblock;
+ bool frgre;
+
+ struct llist_head vtyvc;
+};
+
+struct ns2_vty_vc {
+ struct llist_head list;
+
+ struct osmo_sockaddr_str remote;
+ enum gprs_ns_ll ll;
+
+ /* old vty code doesnt support multiple NSVCI per NSEI */
+ uint16_t nsei;
+ uint16_t nsvci;
+ uint16_t frdlci;
+
+ bool remote_end_is_sgsn;
+ bool configured;
+};
+
+static struct gprs_ns2_inst *vty_nsi = NULL;
+static struct ns2_vty_priv priv;
+
+/* FIXME: this should go to some common file as it is copied
+ * in vty_interface.c of the BSC */
+static const struct value_string gprs_ns_timer_strs[] = {
+ { 0, "tns-block" },
+ { 1, "tns-block-retries" },
+ { 2, "tns-reset" },
+ { 3, "tns-reset-retries" },
+ { 4, "tns-test" },
+ { 5, "tns-alive" },
+ { 6, "tns-alive-retries" },
+ { 7, "tsns-prov" },
+ { 0, 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;
+ }
+}
+
+static struct cmd_node ns_node = {
+ L_NS_NODE,
+ "%s(config-ns)# ",
+ 1,
+};
+
+static struct ns2_vty_vc *vtyvc_alloc(uint16_t nsei) {
+ struct ns2_vty_vc *vtyvc = talloc_zero(vty_nsi, struct ns2_vty_vc);
+ if (!vtyvc)
+ return vtyvc;
+
+ vtyvc->nsei = nsei;
+
+ llist_add(&vtyvc->list, &priv.vtyvc);
+
+ return NULL;
+}
+
+static void ns2_vc_free(struct ns2_vty_vc *vtyvc) {
+ if (!vtyvc)
+ return;
+
+ llist_del(&vtyvc->list);
+ talloc_free(vtyvc);
+}
+
+static struct ns2_vty_vc *vtyvc_by_nsvci(uint16_t nsvci) {
+ struct ns2_vty_vc *vtyvc;
+ llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
+ if (vtyvc->nsvci == nsvci)
+ return vtyvc;
+ }
+
+ return NULL;
+}
+
+static struct ns2_vty_vc *vtyvc_by_nsei(uint16_t nsei, bool alloc_missing) {
+ struct ns2_vty_vc *vtyvc;
+ llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
+ if (vtyvc->nsei == nsei)
+ return vtyvc;
+ }
+
+ if (alloc_missing) {
+ vtyvc = vtyvc_alloc(nsei);
+ if (!vtyvc)
+ return vtyvc;
+
+ vtyvc->nsei = nsei;
+ }
+
+ return NULL;
+}
+
+static int config_write_ns(struct vty *vty)
+{
+ struct ns2_vty_vc *vtyvc;
+ unsigned int i;
+ struct osmo_sockaddr_str sockstr;
+
+ vty_out(vty, "ns%s", VTY_NEWLINE);
+
+ /* global configuration must be written first, as some of it may be
+ * relevant when creating the NSE/NSVC later below */
+
+ vty_out(vty, " encapsulation framerelay-gre enabled %u%s",
+ priv.frgre ? 1 : 0, VTY_NEWLINE);
+
+ if (priv.frgre) {
+ if (strlen(priv.frgreaddr.ip)) {
+ vty_out(vty, " encapsulation framerelay-gre local-ip %s%s",
+ sockstr.ip, VTY_NEWLINE);
+ }
+ } else {
+ if (strlen(priv.udp.ip)) {
+ vty_out(vty, " encapsulation udp local-ip %s%s",
+ priv.udp.ip, VTY_NEWLINE);
+ }
+
+ if (priv.udp.port)
+ vty_out(vty, " encapsulation udp local-port %u%s",
+ priv.udp.port, VTY_NEWLINE);
+ }
+
+ if (priv.dscp)
+ vty_out(vty, " encapsulation udp dscp %d%s",
+ priv.dscp, VTY_NEWLINE);
+
+ vty_out(vty, " encapsulation udp use-reset-block-unblock %s%s",
+ priv.use_reset_block_unblock ? "enabled" : "disabled", VTY_NEWLINE);
+
+ llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
+ vty_out(vty, " nse %u nsvci %u%s",
+ vtyvc->nsei, vtyvc->nsvci, VTY_NEWLINE);
+
+ vty_out(vty, " nse %u remote-role %s%s",
+ vtyvc->nsei, vtyvc->remote_end_is_sgsn ? "sgsn" : "bss",
+ VTY_NEWLINE);
+
+ switch (vtyvc->ll) {
+ case GPRS_NS_LL_UDP:
+ vty_out(vty, " nse %u encapsulation udp%s", vtyvc->nsei, VTY_NEWLINE);
+ vty_out(vty, " nse %u remote-ip %s%s",
+ vtyvc->nsei,
+ vtyvc->remote.ip,
+ VTY_NEWLINE);
+ vty_out(vty, " nse %u remote-port %u%s",
+ vtyvc->nsei, vtyvc->remote.port,
+ VTY_NEWLINE);
+ break;
+ case GPRS_NS_LL_FR_GRE:
+ // vty_out(vty, " nse %u encapsulation framerelay-gre%s",
+ // nse->nsei, VTY_NEWLINE);
+ // vty_out(vty, " nse %u remote-ip %s%s",
+ // nse->nsei,
+ // inet_ntoa(nsvc->frgre.bts_addr.sin_addr),
+ // VTY_NEWLINE);
+ // vty_out(vty, " nse %u fr-dlci %u%s",
+ // nsvc->nsei, osmo_ntohs(nsvc->frgre.bts_addr.sin_port),
+ // VTY_NEWLINE);
+ break;
+ default:
+ break;
+ }
+ }
+
+ 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);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns, cfg_ns_cmd,
+ "ns",
+ "Configure the GPRS Network Service")
+{
+ vty->node = L_NS_NODE;
+ return CMD_SUCCESS;
+}
+
+static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr_str remote;
+ struct osmo_sockaddr_str local;
+ struct osmo_sockaddr *sockaddr;
+
+ vty_out(vty, "NSEI %5u%s",
+ nse->nsei, VTY_NEWLINE);
+
+ llist_for_each_entry(nsvc, &nse->vc, list) {
+ switch (nsvc->ll) {
+ case GPRS_NS_LL_UDP: {
+ sockaddr = gprs_ns2_ip_vc_sockaddr(nsvc);
+ if (!sockaddr) {
+ vty_out(vty, "unknown");
+ break;
+ }
+
+ if (osmo_sockaddr_str_from_sockaddr(
+ &remote,
+ &sockaddr->u.sas)) {
+ vty_out(vty, "unknown");
+ break;
+ }
+
+ vty_out(vty, "%s:%u <> %s:%u", local.ip, local.port, remote.ip, remote.port);
+ break;
+ }
+ case GPRS_NS_LL_FR_GRE:
+ case GPRS_NS_LL_E1:
+ break;
+ }
+
+// vty_out(vty, "Remote: %-4s, %5s %9s, %s ",
+// nsvc->remote_end_is_sgsn ? "SGSN" : "BSS",
+// NS_DESC_A(nsvc->remote_state),
+// NS_DESC_B(nsvc->remote_state), gprs_ns2_ll_str(nsvc));
+
+// vty_out(vty, "%s%s", nsvc->ll == GPRS_NS_LL_UDP ? "UDP" : "FR-GRE", VTY_NEWLINE);
+
+ if (stats) {
+ vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+ vty_out_stat_item_group(vty, " ", nsvc->statg);
+ }
+ }
+}
+
+static void dump_ns(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) {
+ gprs_ns2_dump_vty(vty, nse, stats);
+ break;
+ }
+
+}
+
+DEFUN(show_ns, show_ns_cmd, "show ns",
+ SHOW_STR "Display information about the NS protocol")
+{
+ dump_ns(vty, vty_nsi, false, false);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_stats, show_ns_stats_cmd, "show ns stats",
+ SHOW_STR
+ "Display information about the NS protocol\n"
+ "Include statistics\n")
+{
+ dump_ns(vty, vty_nsi, true, false);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent",
+ SHOW_STR
+ "Display information about the NS protocol\n"
+ "Show only persistent NS\n")
+{
+ dump_ns(vty, vty_nsi, true, true);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
+ SHOW_STR "Display information about the NS protocol\n"
+ "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;
+ }
+
+ vty_out(vty, "TODO: dump nsvc%s", VTY_NEWLINE);
+ /* TODO: dump nsvc */
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define NSE_CMD_STR "Persistent NS Entity\n" "NS Entity ID (NSEI)\n"
+
+DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd,
+ "nse <0-65535> nsvci <0-65535>",
+ NSE_CMD_STR
+ "NS Virtual Connection\n"
+ "NS Virtual Connection ID (NSVCI)\n"
+ )
+{
+ struct ns2_vty_vc *vtyvc;
+
+ uint16_t nsei = atoi(argv[0]);
+ uint16_t nsvci = atoi(argv[1]);
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vtyvc->nsvci = nsvci;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_remoteip, cfg_nse_remoteip_cmd,
+ "nse <0-65535> remote-ip " VTY_IPV46_CMD,
+ NSE_CMD_STR
+ "Remote IP Address\n"
+ "Remote IP Address\n")
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_sockaddr_str_from_str2(&vtyvc->remote, argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_remoteport, cfg_nse_remoteport_cmd,
+ "nse <0-65535> remote-port <0-65535>",
+ NSE_CMD_STR
+ "Remote UDP Port\n"
+ "Remote UDP Port Number\n")
+{
+ uint16_t nsei = atoi(argv[0]);
+ uint16_t port = atoi(argv[1]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vtyvc->remote.port = port;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd,
+ "nse <0-65535> fr-dlci <16-1007>",
+ NSE_CMD_STR
+ "Frame Relay DLCI\n"
+ "Frame Relay DLCI Number\n")
+{
+ uint16_t nsei = atoi(argv[0]);
+ uint16_t dlci = atoi(argv[1]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vtyvc->ll != GPRS_NS_LL_FR_GRE) {
+ vty_out(vty, "Warning: seting FR DLCI on non-FR NSE%s",
+ VTY_NEWLINE);
+ }
+
+ vtyvc->frdlci = dlci;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_encaps, cfg_nse_encaps_cmd,
+ "nse <0-65535> encapsulation (udp|framerelay-gre)",
+ NSE_CMD_STR
+ "Encapsulation for NS\n"
+ "UDP/IP Encapsulation\n" "Frame-Relay/GRE/IP Encapsulation\n")
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "udp"))
+ vtyvc->ll = GPRS_NS_LL_UDP;
+ else
+ vtyvc->ll = GPRS_NS_LL_FR_GRE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd,
+ "nse <0-65535> remote-role (sgsn|bss)",
+ NSE_CMD_STR
+ "Remote NSE Role\n"
+ "Remote Peer is SGSN\n"
+ "Remote Peer is BSS\n")
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "sgsn"))
+ vtyvc->remote_end_is_sgsn = 1;
+ else
+ vtyvc->remote_end_is_sgsn = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_nse, cfg_no_nse_cmd,
+ "no nse <0-65535>",
+ "Delete Persistent NS Entity\n"
+ "Delete " NSE_CMD_STR)
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, false);
+ if (!vtyvc) {
+ vty_out(vty, "The NSE %d does not exists.%s", nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ns2_vc_free(vtyvc);
+
+ 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;
+}
+
+#define ENCAPS_STR "NS encapsulation options\n"
+
+DEFUN(cfg_nsip_local_ip, cfg_nsip_local_ip_cmd,
+ "encapsulation udp local-ip " VTY_IPV46_CMD,
+ ENCAPS_STR "NS over UDP Encapsulation\n"
+ "Set the IP address on which we listen for NS/UDP\n"
+ "IP Address\n")
+{
+ osmo_sockaddr_str_from_str2(&priv.udp, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_local_port, cfg_nsip_local_port_cmd,
+ "encapsulation udp local-port <0-65535>",
+ ENCAPS_STR "NS over UDP Encapsulation\n"
+ "Set the UDP port on which we listen for NS/UDP\n"
+ "UDP port number\n")
+{
+ unsigned int port = atoi(argv[0]);
+
+ priv.udp.port = port;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_dscp, cfg_nsip_dscp_cmd,
+ "encapsulation udp dscp <0-255>",
+ ENCAPS_STR "NS over UDP Encapsulation\n"
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
+{
+ int dscp = atoi(argv[0]);
+ struct gprs_ns2_vc_bind *bind;
+
+ priv.dscp = dscp;
+
+ llist_for_each_entry(bind, &vty_nsi->binding, list) {
+ if (gprs_ns2_is_ip_bind(bind))
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_res_block_unblock, cfg_nsip_res_block_unblock_cmd,
+ "encapsulation udp use-reset-block-unblock (enabled|disabled)",
+ ENCAPS_STR "NS over UDP Encapsulation\n"
+ "Use NS-{RESET,BLOCK,UNBLOCK} procedures in violation of 3GPP TS 48.016\n"
+ "Enable NS-{RESET,BLOCK,UNBLOCK}\n"
+ "Disable NS-{RESET,BLOCK,UNBLOCK}\n")
+{
+ bool use_reset_block_unblock;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (!strcmp(argv[0], "enabled"))
+ use_reset_block_unblock = true;
+ else
+ use_reset_block_unblock = false;
+
+ priv.use_reset_block_unblock = use_reset_block_unblock;
+
+ llist_for_each_entry(bind, &vty_nsi->binding, list) {
+ bind->use_reset_block_unblock = use_reset_block_unblock;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_frgre_local_ip, cfg_frgre_local_ip_cmd,
+ "encapsulation framerelay-gre local-ip " VTY_IPV46_CMD,
+ ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
+ "Set the IP address on which we listen for NS/FR/GRE\n"
+ "IP Address\n")
+{
+ osmo_sockaddr_str_from_str2(&priv.frgreaddr, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_frgre_enable, cfg_frgre_enable_cmd,
+ "encapsulation framerelay-gre enabled (1|0)",
+ ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
+ "Enable or disable Frame Relay over GRE\n"
+ "Enable\n" "Disable\n")
+{
+ int enabled = atoi(argv[0]);
+
+ priv.frgre = enabled;
+
+ return CMD_SUCCESS;
+}
+
+
+//DEFUN(nsvc_nsei, nsvc_nsei_cmd,
+// "nsvc (nsei|nsvci) <0-65535> (block|unblock|reset)",
+// "Perform an operation on a NSVC\n"
+// "NSEI to identify NS-VC Identifier (NS-VCI)\n"
+// "NS-VC Identifier (NS-VCI)\n"
+// "The NSEI\n"
+// "Initiate BLOCK procedure\n"
+// "Initiate UNBLOCK procedure\n"
+// "Initiate RESET procedure\n")
+//{
+// const char *id_type = argv[0];
+// uint16_t id = atoi(argv[1]);
+// const char *operation = argv[2];
+// struct gprs_ns2_vc *nsvc;
+
+// if (!strcmp(id_type, "nsei"))
+// nsvc = gprs_ns2_vc_by_nsei(vty_nsi, id);
+// else if (!strcmp(id_type, "nsvci"))
+// nsvc = gprs_ns2_vc_by_nsvci(vty_nsi, id);
+// else {
+// vty_out(vty, "%%No such id_type '%s'%s", id_type, VTY_NEWLINE);
+// return CMD_WARNING;
+// }
+
+// if (!nsvc) {
+// vty_out(vty, "No such %s (%u)%s", id_type, id, VTY_NEWLINE);
+// return CMD_WARNING;
+// }
+
+// if (nsvc->nsi->bss_sns_fi) {
+// vty_out(vty, "A NS Instance using the IP Sub-Network doesn't use BLOCK/UNBLOCK/RESET%s",
+// VTY_NEWLINE);
+// return CMD_WARNING;
+// }
+
+// if (!strcmp(operation, "block"))
+// gprs_ns_tx_block(nsvc, NS_CAUSE_OM_INTERVENTION);
+// else if (!strcmp(operation, "unblock"))
+// gprs_ns_tx_unblock(nsvc);
+// else if (!strcmp(operation, "reset"))
+// gprs_ns2_vc_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+// else
+// return CMD_WARNING;
+
+// return CMD_SUCCESS;
+//}
+
+//DEFUN(logging_fltr_nsvc,
+// logging_fltr_nsvc_cmd,
+// "logging filter nsvc (nsei|nsvci) <0-65535>",
+// LOGGING_STR FILTER_STR
+// "Filter based on NS Virtual Connection\n"
+// "Identify NS-VC by NSEI\n"
+// "Identify NS-VC by NSVCI\n"
+// "Numeric identifier\n")
+//{
+// struct log_target *tgt;
+// struct gprs_ns2_vc *nsvc;
+// uint16_t id = atoi(argv[1]);
+
+// log_tgt_mutex_lock();
+// tgt = osmo_log_vty2tgt(vty);
+// if (!tgt) {
+// log_tgt_mutex_unlock();
+// return CMD_WARNING;
+// }
+
+// if (!strcmp(argv[0], "nsei"))
+// nsvc = gprs_ns2_vc_by_nsei(vty_nsi, id);
+// else
+// nsvc = gprs_ns2_vc_by_nsvci(vty_nsi, id);
+
+// if (!nsvc) {remote_end_is_sgsn
+// 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;
+//}
+
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi)
+{
+ static bool vty_elements_installed = false;
+
+ vty_nsi = nsi;
+ memset(&priv, 0, sizeof(struct ns2_vty_priv));
+ INIT_LLIST_HEAD(&priv.vtyvc);
+ priv.use_reset_block_unblock = true;
+
+ /* Regression test code may call this function repeatedly, so make sure
+ * that VTY elements are not duplicated, which would assert. */
+ if (vty_elements_installed)
+ 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_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
+
+ install_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);
+
+ return 0;
+}
+
+/*!
+ * \brief gprs_ns2_vty_create parse the vty tree into ns nodes
+ * It has to be in different steps to ensure the bind is created before creating VCs.
+ * \return 0 on success
+ */
+int gprs_ns2_vty_create() {
+ struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr sockaddr;
+
+ if (!vty_nsi)
+ return -1;
+
+ /* create binds, only support a single bind. either FR or UDP */
+ if (priv.frgre) {
+ /* TODO not yet supported !*/
+ return -1;
+ } else {
+ /* UDP */
+ osmo_sockaddr_str_to_sockaddr(&priv.udp, &sockaddr.u.sas);
+ gprs_ns2_ip_bind(vty_nsi, &sockaddr, priv.dscp, &bind);
+ if (!bind) {
+ /* TODO: could not bind on the specific address */
+ return -1;
+ }
+ gprs_ns2_ip_bind_reset_block_unblock(bind,
+ priv.use_reset_block_unblock);
+ }
+
+ /* create vcs */
+ llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
+ if (strlen(vtyvc->remote.ip) == 0) {
+ /* Invalid IP for VC */
+ continue;
+ }
+
+ if (!vtyvc->remote.port) {
+ /* Invalid port for VC */
+ continue;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&vtyvc->remote, &sockaddr.u.sas)) {
+ /* Invalid sockaddr for VC */
+ continue;
+ }
+
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, vtyvc->nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse(vty_nsi, vtyvc->nsei);
+ if (!nse) {
+ /* Could not create NSE for VTY */
+ continue;
+ }
+ }
+
+ if (bind) {
+ nsvc = gprs_ns2_ip_connect(bind,
+ &sockaddr,
+ nse,
+ vtyvc->nsvci);
+ if (!nsvc) {
+ /* Could not create NSVC, connect failed */
+ continue;
+ }
+ }
+ }
+
+
+ return 0;
+}
+
+/*!
+ * \brief ns2_vty_bind_apply will be called when a new bind is created to apply vty settings
+ * \param bind
+ * \return
+ */
+void ns2_vty_bind_apply(struct gprs_ns2_vc_bind *bind)
+{
+ gprs_ns2_ip_bind_reset_block_unblock(bind,
+ priv.use_reset_block_unblock);
+}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index 0c0c5c4..4137a0b 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -72,6 +72,40 @@
gprs_ns_ll_clear;
gprs_ns_msgb_alloc;
+gprs_ns2_bind_set_mode;
+gprs_ns2_close;
+gprs_ns2_instantiate;
+gprs_ns2_create_connect;
+gprs_ns2_dynamic_create_nse;
+gprs_ns2_free;
+gprs_ns2_free_nse;
+gprs_ns2_free_bind;
+gprs_ns2_free_nsvc;
+gprs_ns2_ip_bind;
+gprs_ns2_ip_connect_inactive;
+gprs_ns2_ip_connect;
+gprs_ns2_ip_connect2;
+gprs_ns2_ip_connect_sns;
+gprs_ns2_recv_vc;
+gprs_ns2_send;
+gprs_ns2_send_nse;
+gprs_ns2_send_nsei;
+gprs_ns2_set_log_ss;
+gprs_ns2_nse_by_nsei;
+gprs_ns2_create_nse;
+gprs_ns2_tx_alive;
+gprs_ns2_tx_alive_ack;
+gprs_ns2_tx_block;
+gprs_ns2_tx_block_ack;
+gprs_ns2_tx_reset;
+gprs_ns2_tx_reset_ack;
+gprs_ns2_tx_status;
+gprs_ns2_tx_unblock;
+gprs_ns2_tx_unblock_ack;
+gprs_ns2_tx_unit_data;
+gprs_ns2_vty_create;
+gprs_ns2_vty_init;
+
gprs_nsvc_create2;
gprs_nsvc_delete;
gprs_nsvc_reset;
--
To view, visit https://gerrit.osmocom.org/c/libosmocore/+/19417
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: I3525beef205588dfab9d3880a34115f1a2676e48
Gerrit-Change-Number: 19417
Gerrit-PatchSet: 1
Gerrit-Owner: lynxis lazus <lynxis at fe80.eu>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200728/af4eaaa4/attachment.htm>