aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/gb/Makefile.am9
-rw-r--r--src/gb/common_vty.c4
-rw-r--r--src/gb/gprs_bssgp.c20
-rw-r--r--src/gb/gprs_ns2.c1059
-rw-r--r--src/gb/gprs_ns2_frgre.c600
-rw-r--r--src/gb/gprs_ns2_internal.h289
-rw-r--r--src/gb/gprs_ns2_message.c713
-rw-r--r--src/gb/gprs_ns2_sns.c1477
-rw-r--r--src/gb/gprs_ns2_udp.c385
-rw-r--r--src/gb/gprs_ns2_vc_fsm.c668
-rw-r--r--src/gb/gprs_ns2_vty.c837
-rw-r--r--src/gb/libosmogb.map34
-rw-r--r--src/gsm/Makefile.am2
-rw-r--r--src/gsm/cbsp.c1
-rw-r--r--src/gsm/gsm0808.c31
-rw-r--r--src/gsm/ipa.c14
-rw-r--r--src/gsm/lapdm.c27
-rw-r--r--src/gsm/libosmogsm.map1
-rw-r--r--src/logging.c41
-rw-r--r--src/macaddr.c7
-rw-r--r--src/msgb.c2
-rw-r--r--src/sim/Makefile.am2
-rw-r--r--src/sockaddr_str.c33
-rw-r--r--src/socket.c580
-rw-r--r--src/use_count.c26
-rw-r--r--src/utils.c6
-rw-r--r--src/vty/Makefile.am2
-rw-r--r--src/vty/command.c108
-rw-r--r--src/vty/cpu_sched_vty.c677
-rw-r--r--src/vty/vty.c28
31 files changed, 7443 insertions, 242 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index c2847ec1..891b4a6f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,7 +1,7 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=15:0:3
+LIBVERSION=16:0:0
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS)
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index 125afbaf..65c35529 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -1,13 +1,13 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=10:0:1
+LIBVERSION=11:0:0
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing $(TALLOC_CFLAGS)
# FIXME: this should eventually go into a milenage/Makefile.am
-noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h
+noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_internal.h
if ENABLE_GB
lib_LTLIBRARIES = libosmogb.la
@@ -20,7 +20,10 @@ libosmogb_la_LIBADD = $(TALLOC_LIBS) \
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_udp.c gprs_ns2_frgre.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/common_vty.c b/src/gb/common_vty.c
index a47294b5..eb665d52 100644
--- a/src/gb/common_vty.c
+++ b/src/gb/common_vty.c
@@ -40,8 +40,8 @@
int gprs_log_filter_fn(const struct log_context *ctx,
struct log_target *tar)
{
- const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
- const struct gprs_bvc *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+ const void *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
+ const void *bvc = ctx->ctx[LOG_CTX_GB_BVC];
/* Filter on the NS Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index fa4e187a..8b8d534e 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -73,7 +73,7 @@ static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv);
-/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */
+/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */
static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg)
{
return gprs_ns_sendmsg(bssgp_nsi, msg);
@@ -146,23 +146,33 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
ctx->nsei = nsei;
/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
- if (!ctx->ctrg) {
- talloc_free(ctx);
- return NULL;
- }
+ if (!ctx->ctrg)
+ goto err_ctrg;
+
ctx->fc = talloc_zero(ctx, struct bssgp_flow_control);
+ if (!ctx->fc)
+ goto err_fc;
+
/* cofigure for 2Mbit, 30 packets in queue */
bssgp_fc_init(ctx->fc, 100000, 2*1024*1024/8, 30, &_bssgp_tx_dl_ud);
llist_add(&ctx->list, &bssgp_bvc_ctxts);
return ctx;
+
+err_fc:
+ rate_ctr_group_free(ctx->ctrg);
+err_ctrg:
+ talloc_free(ctx);
+ return NULL;
}
void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx)
{
if (!ctx)
return;
+
+ osmo_timer_del(&ctx->fc->timer);
rate_ctr_group_free(ctx->ctrg);
llist_del(&ctx->list);
talloc_free(ctx);
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
new file mode 100644
index 00000000..05d49723
--- /dev/null
+++ b/src/gb/gprs_ns2.c
@@ -0,0 +1,1059 @@
+/*! \file gprs_ns2.c
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2016-2017,2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*! \addtogroup libgb
+ * @{
+ *
+ * GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ *
+ * Some introduction into NS: NS is used typically on top of frame relay,
+ * but in the ip.access world it is encapsulated in UDP packets. It serves
+ * as an intermediate shim betwen BSSGP and the underlying medium. It doesn't
+ * do much, apart from providing congestion notification and status indication.
+ *
+ * Terms:
+ *
+ * NS Network Service
+ * NSVC NS Virtual Connection
+ * NSEI NS Entity Identifier
+ * NSVL NS Virtual Link
+ * NSVLI NS Virtual Link Identifier
+ * BVC BSSGP Virtual Connection
+ * BVCI BSSGP Virtual Connection Identifier
+ * NSVCG NS Virtual Connection Goup
+ * Blocked NS-VC cannot be used for user traffic
+ * Alive Ability of a NS-VC to provide communication
+ *
+ * There can be multiple BSSGP virtual connections over one (group of) NSVC's. BSSGP will
+ * therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
+ * NS then has to figure out which NSVC's are responsible for this BVCI.
+ * Those mappings are administratively configured.
+ *
+ * This implementation has the following limitations:
+ * - 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_ns2.c */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "gprs_ns2_internal.h"
+
+#define ns_set_state(ns_, st_) ns_set_state_with_log(ns_, st_, false, __FILE__, __LINE__)
+#define ns_set_remote_state(ns_, st_) ns_set_state_with_log(ns_, st_, true, __FILE__, __LINE__)
+#define ns_mark_blocked(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_BLOCKED)
+#define ns_mark_unblocked(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_BLOCKED));
+#define ns_mark_alive(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_ALIVE)
+#define ns_mark_dead(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_ALIVE));
+
+/* HACK: The NS_IE_IP_ADDR does not follow any known TLV rules.
+ * Since it's a hard ABI break to implement 16 bit tag with fixed length entries to workaround it,
+ * the parser will be called with ns_att_tlvdef1 and if it's failed with ns_att_tlvdef2.
+ * The TLV parser depends on 8bit tag in many places.
+ * The NS_IE_IP_ADDR is only valid for SNS_ACK SNS_ADD and SNS_DELETE.
+ */
+static const struct tlv_definition ns_att_tlvdef1 = {
+ .def = {
+ [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 },
+ /* NS_IE_IP_ADDR in the IPv4 version */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 },
+ },
+};
+
+static const struct tlv_definition ns_att_tlvdef2 = {
+ .def = {
+ [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 },
+ /* NS_IE_IP_ADDR in the IPv6 version */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 17 },
+ },
+};
+
+
+/* Section 10.3.2, Table 13 */
+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,
+};
+
+
+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,
+};
+
+/*! string-format a given NS-VC into a user-supplied buffer.
+ * \param[in] buf user-allocated output buffer
+ * \param[in] buf_len size of user-allocated output buffer in bytes
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to buf on success; NULL on error */
+char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
+{
+ struct osmo_sockaddr *local;
+ struct osmo_sockaddr *remote;
+ struct osmo_sockaddr_str local_str;
+ struct osmo_sockaddr_str remote_str;
+
+ if (!buf_len)
+ return NULL;
+
+ switch (nsvc->ll) {
+ case GPRS_NS_LL_UDP:
+ if (!gprs_ns2_is_ip_bind(nsvc->bind)) {
+ buf[0] = '\0';
+ return buf;
+ }
+
+ local = gprs_ns2_ip_bind_sockaddr(nsvc->bind);
+ remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ if (osmo_sockaddr_str_from_sockaddr(&local_str, &local->u.sas))
+ strcpy(local_str.ip, "invalid");
+ if (osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas))
+ strcpy(remote_str.ip, "invalid");
+
+ if (nsvc->nsvci_is_valid)
+ snprintf(buf, buf_len, "udp)[%s]:%u<%u>[%s]:%u",
+ local_str.ip, local_str.port,
+ nsvc->nsvci,
+ remote_str.ip, remote_str.port);
+ else
+ snprintf(buf, buf_len, "udp)[%s]:%u<>[%s]:%u",
+ local_str.ip, local_str.port,
+ remote_str.ip, remote_str.port);
+ break;
+ case GPRS_NS_LL_FR_GRE:
+ snprintf(buf, buf_len, "frgre)");
+ break;
+ case GPRS_NS_LL_E1:
+ snprintf(buf, buf_len, "e1)");
+ break;
+ default:
+ buf[0] = '\0';
+ break;
+ }
+
+ buf[buf_len - 1] = '\0';
+
+ return buf;
+}
+
+/* udp is the longest: udp)[IP6]:65536<65536>[IP6]:65536 */
+#define NS2_LL_MAX_STR 4+2*(INET6_ADDRSTRLEN+9)+8
+
+/*! string-format a given NS-VC to a thread-local static buffer.
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to the string on success; NULL on error */
+const char *gprs_ns2_ll_str(struct gprs_ns2_vc *nsvc)
+{
+ static __thread char buf[NS2_LL_MAX_STR];
+ return gprs_ns2_ll_str_buf(buf, sizeof(buf), nsvc);
+}
+
+/*! string-format a given NS-VC to a dynamically allocated string.
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to the string on success; NULL on error */
+char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc)
+{
+ char *buf = talloc_size(ctx, NS2_LL_MAX_STR);
+ if (!buf)
+ return buf;
+ return gprs_ns2_ll_str_buf(buf, NS2_LL_MAX_STR, nsvc);
+}
+
+/*! Receive a primitive from the NS User (Gb).
+ * \param[in] nsi NS instance to which the primitive is issued
+ * \param[in] oph The primitive
+ * \return 0 on success; negative on error */
+int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph)
+{
+ /* TODO: implement load distribution function */
+ /* TODO: implement resource distribution */
+ /* TODO: check for empty PDUs which can be sent to Request/Confirm
+ * the IP endpoint */
+ struct osmo_gprs_ns2_prim *nsp;
+ struct gprs_ns2_nse *nse = NULL;
+ struct gprs_ns2_vc *nsvc = NULL, *tmp;
+ uint16_t bvci, nsei;
+ uint8_t sducontrol = 0;
+
+ if (oph->sap != SAP_NS)
+ return -EINVAL;
+
+ nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
+
+ if (oph->operation != PRIM_OP_REQUEST || oph->primitive != PRIM_NS_UNIT_DATA)
+ return -EINVAL;
+
+ if (!oph->msg)
+ return -EINVAL;
+
+ bvci = nsp->bvci;
+ nsei = nsp->nsei;
+
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+ if (!nse)
+ return -EINVAL;
+
+ llist_for_each_entry(tmp, &nse->nsvc, 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;
+ }
+
+ /* TODO: send a status primitive back */
+ if (!nsvc)
+ return 0;
+
+ if (nsp->u.unitdata.change == NS_ENDPOINT_REQUEST_CHANGE)
+ sducontrol = 1;
+ else if (nsp->u.unitdata.change == NS_ENDPOINT_CONFIRM_CHANGE)
+ sducontrol = 2;
+
+ return ns2_tx_unit_data(nsvc, bvci, sducontrol, oph->msg);
+}
+
+/*! Send a STATUS.ind primitive to the specified NS instance user.
+ * \param[in] nsi NS instance on which we operate
+ * \param[in] nsei NSEI to which the statue relates
+ * \param[in] bvci BVCI to which the status relates
+ * \param[in] cause The cause of the status */
+void ns2_prim_status_ind(struct gprs_ns2_inst *nsi,
+ uint16_t nsei, uint16_t bvci,
+ enum gprs_ns2_affecting_cause cause)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ nsp.nsei = nsei;
+ nsp.bvci = bvci;
+ nsp.u.status.cause = cause;
+ nsp.u.status.transfer = -1;
+ osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_STATUS,
+ PRIM_OP_INDICATION, NULL);
+ nsi->cb(&nsp.oph, nsi->cb_data);
+}
+
+/*! Allocate a NS-VC within the given bind + NSE.
+ * \param[in] bind The 'bind' on which we operate
+ * \param[in] nse The NS Entity on which we operate
+ * \param[in] initiater - if this is an incoming remote (!initiater) or a local outgoing connection (initater)
+ * \return newly allocated NS-VC on success; NULL on error */
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater)
+{
+ struct gprs_ns2_vc *nsvc = talloc_zero(bind, struct gprs_ns2_vc);
+
+ if (!nsvc)
+ return NULL;
+
+ nsvc->bind = bind;
+ nsvc->nse = nse;
+ nsvc->mode = bind->vc_mode;
+ nsvc->sig_weight = 1;
+ nsvc->data_weight = 1;
+
+ nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->rate_ctr_idx);
+ if (!nsvc->ctrg) {
+ goto err;
+ }
+ nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->rate_ctr_idx);
+ if (!nsvc->statg)
+ goto err_group;
+ if (!gprs_ns2_vc_fsm_alloc(nsvc, NULL, initiater))
+ goto err_statg;
+
+ bind->nsi->rate_ctr_idx++;
+
+ llist_add(&nsvc->list, &nse->nsvc);
+ llist_add(&nsvc->blist, &bind->nsvc);
+
+ return nsvc;
+
+err_statg:
+ osmo_stat_item_group_free(nsvc->statg);
+err_group:
+ rate_ctr_group_free(nsvc->ctrg);
+err:
+ talloc_free(nsvc);
+
+ return NULL;
+}
+
+/*! Destroy/release given NS-VC.
+ * \param[in] nsvc NS-VC to destroy */
+void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ ns2_prim_status_ind(nsvc->nse->nsi, nsvc->nse->nsei,
+ 0, NS_AFF_CAUSE_VC_FAILURE);
+
+ llist_del(&nsvc->list);
+ llist_del(&nsvc->blist);
+
+ /* notify nse this nsvc is unavailable */
+ ns2_nse_notify_unblocked(nsvc, false);
+
+ /* check if sns is using this VC */
+ ns2_sns_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);
+
+ osmo_stat_item_group_free(nsvc->statg);
+ rate_ctr_group_free(nsvc->ctrg);
+
+ talloc_free(nsvc);
+}
+
+/*! Allocate a message buffer for use with the NS2 stack. */
+struct msgb *gprs_ns2_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM,
+ "GPRS/NS");
+ if (!msg) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to allocate NS message of size %d\n",
+ NS_ALLOC_SIZE);
+ }
+ return msg;
+}
+
+/*! Create a status message to be sent over a new connection.
+ * \param[in] orig_msg the original message
+ * \param[in] tp TLVP parsed of the original message
+ * \param[out] reject callee-allocated message buffer of the generated NS-STATUS
+ * \param[in] cause Cause for the rejection
+ * \return 0 on success */
+static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struct msgb **reject, enum ns_cause cause)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ bool have_vci = false;
+ uint8_t _cause = cause;
+ uint16_t nsei = 0;
+
+ if (!msg)
+ return -ENOMEM;
+
+ if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ nsei = tlvp_val16be(tp, NS_IE_NSEI);
+
+ LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rejecting message without NSVCI. Tx NS STATUS (cause=%s)\n",
+ nsei, gprs_ns2_cause_str(cause));
+ }
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_STATUS;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &_cause);
+ have_vci = TLVP_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;
+}
+
+/*! Resolve a NS Entity based on its NSEI.
+ * \param[in] nsi NS Instance in which we do the look-up
+ * \param[in] nsei NSEI to look up
+ * \return NS Entity in successful case; NULL if none found */
+struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ if (nse->nsei == nsei)
+ return nse;
+ }
+
+ return NULL;
+}
+
+/*! Resolve a NS-VC Entity based on its NS-VCI.
+ * \param[in] nsi NS Instance in which we do the look-up
+ * \param[in] nsvci NS-VCI to look up
+ * \return NS-VC Entity in successful case; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci)
+{
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->nsvci_is_valid && nsvc->nsvci == nsvci)
+ return nsvc;
+ }
+ }
+
+ return NULL;
+}
+
+/*! Create a NS Entity within given NS instance.
+ * \param[in] nsi NS instance in which to create NS Entity
+ * \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \returns newly-allocated NS-E in successful case; NULL on error */
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei)
+{
+ struct gprs_ns2_nse *nse;
+
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+ if (nse) {
+ LOGP(DLNS, LOGL_ERROR, "NSEI:%u Can not create a NSE with already taken NSEI\n", nsei);
+ return nse;
+ }
+
+ nse = talloc_zero(nsi, struct gprs_ns2_nse);
+ if (!nse)
+ return NULL;
+
+ nse->nsei = nsei;
+ nse->nsi = nsi;
+ llist_add(&nse->list, &nsi->nse);
+ INIT_LLIST_HEAD(&nse->nsvc);
+
+ return nse;
+}
+
+/*! Destroy given NS Entity.
+ * \param[in] nse NS Entity to destroy */
+void gprs_ns2_free_nse(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc, *tmp;
+
+ if (!nse)
+ return;
+
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ ns2_prim_status_ind(nse->nsi, nse->nsei,
+ 0, NS_AFF_CAUSE_FAILURE);
+
+ llist_del(&nse->list);
+ if (nse->bss_sns_fi)
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ talloc_free(nse);
+}
+
+static inline int ns2_tlv_parse(struct tlv_parsed *dec,
+ const uint8_t *buf, int buf_len, uint8_t lv_tag,
+ uint8_t lv_tag2)
+{
+ /* workaround for NS_IE_IP_ADDR not following any known TLV rules.
+ * See comment of ns_att_tlvdef1. */
+ int rc = tlv_parse(dec, &ns_att_tlvdef1, buf, buf_len, lv_tag, lv_tag2);
+ if (rc < 0)
+ return tlv_parse(dec, &ns_att_tlvdef2, buf, buf_len, lv_tag, lv_tag2);
+ return rc;
+}
+
+
+/*! Create a new NS-VC based on a [received] message. Depending on the bind it might create a NSE.
+ * \param[in] bind the bind through which msg was received
+ * \param[in] msg the actual received message
+ * \param[in] logname A name to describe the VC. E.g. ip address pair
+ * \param[out] reject A message filled to be sent back. Only used in failure cases.
+ * \param[out] success A pointer which will be set to the new VC on success
+ * \return enum value indicating the status, e.g. GPRS_NS2_CS_CREATED */
+enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const char *logname,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
+ struct tlv_parsed tp;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+ uint16_t nsvci;
+ uint16_t nsei;
+
+ int rc;
+
+ if (msg->len < sizeof(struct gprs_ns_hdr))
+ return GPRS_NS2_CS_ERROR;
+
+ if (nsh->pdu_type == NS_PDUT_STATUS) {
+ /* Do not respond, see 3GPP TS 08.16, 7.5.1 */
+ LOGP(DLNS, LOGL_INFO, "Ignoring NS STATUS from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ if (nsh->pdu_type == NS_PDUT_ALIVE_ACK) {
+ /* Ignore this, see 3GPP TS 08.16, 7.4.1 */
+ LOGP(DLNS, LOGL_INFO, "Ignoring NS ALIVE ACK from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ if (nsh->pdu_type == NS_PDUT_RESET_ACK) {
+ /* Ignore this, see 3GPP TS 08.16, 7.3.1 */
+ LOGP(DLNS, LOGL_INFO, "Ignoring NS RESET ACK from %s "
+ "for non-existing NS-VC\n",
+ logname);
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ if (bind->vc_mode == NS2_VC_MODE_BLOCKRESET) {
+ /* 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(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return rc;
+ }
+ return GPRS_NS2_CS_REJECTED;
+ }
+ } else { /* NS2_VC_MODE_ALIVE */
+ /* Only the ALIVE procedure creates a new NSVC */
+ if (nsh->pdu_type != NS_PDUT_ALIVE) {
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return rc;
+ }
+ return GPRS_NS2_CS_REJECTED;
+ }
+ }
+
+ rc = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_ERROR, "Rx NS RESET Error %d during "
+ "TLV Parse\n", rc);
+ /* TODO: send invalid message back */
+ return GPRS_NS2_CS_REJECTED;
+ }
+
+ if (bind->vc_mode == NS2_VC_MODE_BLOCKRESET) {
+ if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+ !TLVP_PRESENT(&tp, NS_IE_VCI) || !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+ LOGP(DLNS, 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_NS2_CS_ERROR;
+ }
+ }
+
+ nsvc = ns2_vc_alloc(bind, nse, false);
+ if (!nsvc)
+ return GPRS_NS2_CS_SKIPPED;
+
+ nsvc->ll = GPRS_NS_LL_UDP;
+
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+
+ *success = nsvc;
+
+ return GPRS_NS2_CS_CREATED;
+}
+
+/*! Create, and connect an inactive, new IP-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nse NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and inactive NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
+ 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;
+}
+
+/*! Create, connect and activate a new IP-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nse NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
+ 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;
+}
+
+/*! Create, connect and activate a new IP-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
+ 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);
+}
+
+/*! Create, connect and activate a new IP-SNS NSE.
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] remote remote address to which to connect
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \return 0 on success; negative on error */
+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;
+
+ if (!nse->bss_sns_fi)
+ nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, NULL);
+
+ if (!nse->bss_sns_fi)
+ return -1;
+
+ return ns2_sns_bss_fsm_start(nse, nsvc, remote);
+}
+
+/*! Find NS-VC for given socket address.
+ * \param[in] nse NS Entity in which to search
+ * \param[in] sockaddr socket address to search for
+ * \return NS-VC matching sockaddr; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(struct gprs_ns2_nse *nse,
+ 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->nsvc, list) {
+ remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ if (!osmo_sockaddr_cmp(sockaddr, remote))
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+
+/*! Bottom-side entry-point for received NS PDU from the driver/bind
+ * \param[in] nsvc NS-VC for which the message was received
+ * \param msg the received message. Ownership is trasnferred, caller must not free it!
+ * \return 0 on success; negative on error */
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
+ struct msgb *msg)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct tlv_parsed tp;
+ int rc = 0;
+
+ if (msg->len < sizeof(struct gprs_ns_hdr))
+ return -EINVAL;
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_CONFIG:
+ /* one additional byte ('end flag') before the TLV part starts */
+ rc = ns2_tlv_parse(&tp, nsh->data+1,
+ msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+ if (rc < 0) {
+ LOGPC(DLNS, 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(nsvc, msg, &tp);
+ break;
+ case SNS_PDUT_ACK:
+ case SNS_PDUT_ADD:
+ case SNS_PDUT_CHANGE_WEIGHT:
+ case SNS_PDUT_DELETE:
+ /* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
+ rc = ns2_tlv_parse(&tp, nsh->data+1,
+ msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+ if (rc < 0) {
+ LOGPC(DLNS, 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(nsvc, msg, &tp);
+ break;
+ case SNS_PDUT_CONFIG_ACK:
+ case SNS_PDUT_SIZE:
+ case SNS_PDUT_SIZE_ACK:
+ rc = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGPC(DLNS, 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(nsvc, msg, &tp);
+ break;
+
+ case NS_PDUT_UNITDATA:
+ rc = gprs_ns2_vc_rx(nsvc, msg, NULL);
+ break;
+ default:
+ rc = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGPC(DLNS, LOGL_NOTICE, "Error during TLV Parse\n");
+ if (nsh->pdu_type != NS_PDUT_STATUS)
+ ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg);
+ return rc;
+ }
+ rc = gprs_ns2_vc_rx(nsvc, msg, &tp);
+ break;
+ }
+
+ return rc;
+}
+
+/*! Notify a nse about the change of a NS-VC.
+ * \param[in] nsvc NS-VC which has detected the change (and shall not be notified).
+ * \param[in] unblocked whether the NSE should be marked as unblocked (true) or blocked (false) */
+void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked)
+{
+ struct gprs_ns2_nse *nse = nsvc->nse;
+ struct gprs_ns2_vc *tmp;
+
+ if (unblocked == nse->alive)
+ return;
+
+ if (unblocked) {
+ /* this is the first unblocked NSVC on an unavailable NSE */
+ nse->alive = true;
+ ns2_prim_status_ind(nse->nsi, nse->nsei,
+ 0, NS_AFF_CAUSE_RECOVERY);
+ return;
+ }
+
+ /* check if there are any remaining alive vcs */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp == nsvc)
+ continue;
+
+ if (gprs_ns2_vc_is_unblocked(tmp)) {
+ /* there is at least one remaining alive NSVC */
+ return;
+ }
+ }
+
+ /* nse became unavailable */
+ nse->alive = false;
+ ns2_prim_status_ind(nse->nsi, nse->nsei,
+ 0, NS_AFF_CAUSE_FAILURE);
+}
+
+/*! Create a new GPRS NS instance
+ * \param[in] ctx a talloc context to allocate NS instance from
+ * \param[in] cb Call-back function for dispatching primitives to the user
+ * \param[in] cb_data transparent user data passed to Call-back
+ * \returns dynamically allocated gprs_ns_inst; NULL on error */
+struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data)
+{
+ struct gprs_ns2_inst *nsi;
+
+ nsi = talloc_zero(ctx, struct gprs_ns2_inst);
+ if (!nsi)
+ return NULL;
+
+ nsi->cb = cb;
+ nsi->cb_data = cb_data;
+ INIT_LLIST_HEAD(&nsi->binding);
+ INIT_LLIST_HEAD(&nsi->nse);
+
+ nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
+ nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TNS_RESET] = 3;
+ nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TNS_TEST] = 30;
+ nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
+ nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
+ nsi->timeout[NS_TOUT_TSNS_PROV] = 3; /* 1..10 */
+
+ return nsi;
+}
+
+/*! Destroy a NS Instance (including all its NSEs, binds, ...).
+ * \param[in] nsi NS instance to destroy */
+void gprs_ns2_free(struct gprs_ns2_inst *nsi)
+{
+ 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);
+ }
+}
+
+/*! Configure whether a NS Instance should dynamically create NSEs based on incoming traffic.
+ * \param nsi the instance to modify
+ * \param create_nse if NSE can be created on receiving package. SGSN set this.
+ * \return 0 on success; negative on error
+ */
+int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse)
+{
+ nsi->create_nse = create_nse;
+
+ return 0;
+}
+
+/*! Start the NS-ALIVE FSM in all NS-VCs of given NSE.
+ * \param[in] nse NS Entity in whihc to start NS-ALIVE FSMs */
+void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ OSMO_ASSERT(nse);
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->sns_only)
+ continue;
+
+ gprs_ns2_vc_fsm_start(nsvc);
+ }
+}
+
+/*! Set the mode of given bind.
+ * \param[in] bind the bind we want to set the mode of
+ * \param[in] modde mode to set bind to */
+void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode)
+{
+ bind->vc_mode = mode;
+}
+
+/*! Destroy a given bind.
+ * \param[in] bind the bind we want to destroy */
+void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct gprs_ns2_vc *nsvc, *tmp;
+ if (!bind)
+ return;
+
+ llist_for_each_entry_safe(nsvc, tmp, &bind->nsvc, 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_frgre.c b/src/gb/gprs_ns2_frgre.c
new file mode 100644
index 00000000..e0797b68
--- /dev/null
+++ b/src/gb/gprs_ns2_frgre.c
@@ -0,0 +1,600 @@
+/*! \file gprs_ns2_frgre.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2010,2014,2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include "gprs_ns2_internal.h"
+
+#define GRE_PTYPE_FR 0x6559
+#define GRE_PTYPE_IPv4 0x0800
+#define GRE_PTYPE_IPv6 0x86dd
+#define GRE_PTYPE_KAR 0x0000 /* keepalive response */
+
+#ifndef IPPROTO_GRE
+# define IPPROTO_GRE 47
+#endif
+
+struct gre_hdr {
+ uint16_t flags;
+ uint16_t ptype;
+} __attribute__ ((packed));
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__CYGWIN__)
+/**
+ * On BSD the IPv4 struct is called struct ip and instead of iXX
+ * the members are called ip_XX. One could change this code to use
+ * struct ip but that would require to define _BSD_SOURCE and that
+ * might have other complications. Instead make sure struct iphdr
+ * is present on FreeBSD. The below is taken from GLIBC.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+struct iphdr
+ {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned int ihl:4;
+ unsigned int version:4;
+#elif BYTE_ORDER == BIG_ENDIAN
+ unsigned int version:4;
+ unsigned int ihl:4;
+#endif
+ u_int8_t tos;
+ u_int16_t tot_len;
+ u_int16_t id;
+ u_int16_t frag_off;
+ u_int8_t ttl;
+ u_int8_t protocol;
+ u_int16_t check;
+ u_int32_t saddr;
+ u_int32_t daddr;
+ /*The options start here. */
+ };
+#endif
+
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ struct osmo_sockaddr *dest);
+
+struct gprs_ns2_vc_driver vc_driver_frgre = {
+ .name = "GB frame relay over GRE",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_fd fd;
+ struct osmo_sockaddr addr;
+ uint16_t dlci;
+ int dscp;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+ uint16_t dlci;
+};
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ OSMO_ASSERT(nsvc);
+
+ if (!nsvc->priv)
+ return;
+
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+
+/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */
+static void free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->nsvc));
+
+ osmo_fd_close(&priv->fd);
+ talloc_free(priv);
+}
+
+static struct priv_vc *frgre_alloc_vc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_vc *nsvc,
+ struct osmo_sockaddr *remote,
+ uint16_t dlci)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ nsvc->priv = priv;
+ priv->remote = *remote;
+ priv->dlci = dlci;
+
+ return priv;
+}
+
+static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
+ struct ip6_hdr *ip6hdr, struct gre_hdr *greh)
+{
+ /* RFC 7676 IPv6 Support for Generic Routing Encapsulation (GRE) */
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ int gre_payload_len;
+ struct ip6_hdr *inner_ip6h;
+ struct gre_hdr *inner_greh;
+ struct sockaddr_in6 daddr;
+ struct in6_addr ia6;
+
+ gre_payload_len = msg->len - (sizeof(*ip6hdr) + sizeof(*greh));
+
+ inner_ip6h = (struct ip6_hdr *) ((uint8_t *)greh + sizeof(*greh));
+
+ if (gre_payload_len < sizeof(*ip6hdr) + sizeof(*inner_greh)) {
+ LOGP(DLNS, LOGL_ERROR, "GRE keepalive too short\n");
+ return -EIO;
+ }
+
+ if (!memcmp(&inner_ip6h->ip6_src, &ip6hdr->ip6_src, sizeof(struct in6_addr)) ||
+ !memcmp(&inner_ip6h->ip6_dst, &ip6hdr->ip6_dst, sizeof(struct in6_addr))) {
+ LOGP(DLNS, LOGL_ERROR,
+ "GRE keepalive with wrong tunnel addresses\n");
+ return -EIO;
+ }
+
+ /* Are IPv6 extensions header are allowed in the *inner*? In the outer they are */
+ if (inner_ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_GRE) {
+ LOGP(DLNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ return -EIO;
+ }
+
+ inner_greh = (struct gre_hdr *) ((uint8_t *)inner_ip6h + sizeof(struct ip6_hdr));
+ if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
+ LOGP(DLNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ return -EIO;
+ }
+
+ /* Actually send the response back */
+
+ daddr.sin6_family = AF_INET6;
+ daddr.sin6_addr = inner_ip6h->ip6_dst;
+ daddr.sin6_port = IPPROTO_GRE;
+
+ ia6 = ip6hdr->ip6_src;
+ char ip6str[INET6_ADDRSTRLEN] = {};
+ inet_ntop(AF_INET6, &ia6, ip6str, INET6_ADDRSTRLEN);
+ LOGP(DLNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str);
+
+ /* why does it reduce the gre_payload_len by the ipv6 header?
+ * make it similiar to ipv4 even this seems to be wrong */
+ return sendto(priv->fd.fd, inner_greh,
+ gre_payload_len - sizeof(*inner_ip6h), 0,
+ (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+/* IPv4 messages inside the GRE tunnel might be GRE keepalives */
+static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
+ struct iphdr *iph, struct gre_hdr *greh)
+{
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ int gre_payload_len;
+ struct iphdr *inner_iph;
+ struct gre_hdr *inner_greh;
+ struct sockaddr_in daddr;
+ struct in_addr ia;
+
+ gre_payload_len = msg->len - (iph->ihl*4 + sizeof(*greh));
+
+ inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh));
+
+ if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) {
+ LOGP(DLNS, LOGL_ERROR, "GRE keepalive too short\n");
+ return -EIO;
+ }
+
+ if (inner_iph->saddr != iph->daddr ||
+ inner_iph->daddr != iph->saddr) {
+ LOGP(DLNS, LOGL_ERROR,
+ "GRE keepalive with wrong tunnel addresses\n");
+ return -EIO;
+ }
+
+ if (inner_iph->protocol != IPPROTO_GRE) {
+ LOGP(DLNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ return -EIO;
+ }
+
+ inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4);
+ if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
+ LOGP(DLNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ return -EIO;
+ }
+
+ /* Actually send the response back */
+
+ daddr.sin_family = AF_INET;
+ daddr.sin_addr.s_addr = inner_iph->daddr;
+ daddr.sin_port = IPPROTO_GRE;
+
+ ia.s_addr = iph->saddr;
+ LOGP(DLNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n",
+ inet_ntoa(ia));
+
+ /* why does it reduce the gre_payload_len by the ipv4 header? */
+ return sendto(priv->fd.fd, inner_greh,
+ gre_payload_len - inner_iph->ihl*4, 0,
+ (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
+ struct osmo_sockaddr *saddr, uint16_t *dlci)
+{
+ struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
+ int ret = 0;
+ socklen_t saddr_len = sizeof(*saddr);
+ struct iphdr *iph = NULL;
+ struct ip6_hdr *ip6h = NULL;
+ size_t ip46hdr;
+ struct gre_hdr *greh;
+ uint8_t *frh;
+
+ if (!msg) {
+ *error = -ENOMEM;
+ return NULL;
+ }
+
+ ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
+ &saddr->u.sa, &saddr_len);
+ if (ret < 0) {
+ LOGP(DLNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n",
+ strerror(errno));
+ *error = ret;
+ goto out_err;
+ } else if (ret == 0) {
+ *error = ret;
+ goto out_err;
+ }
+
+ msgb_put(msg, ret);
+
+ /* we've received a raw packet including the IPv4 or IPv6 header */
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ ip46hdr = sizeof(struct iphdr);
+ break;
+ case AF_INET6:
+ ip46hdr = sizeof(struct ip6_hdr);
+ default:
+ *error = -EIO;
+ goto out_err;
+ break;
+ }
+
+ /* TODO: add support for the extension headers */
+ if (msg->len < ip46hdr + sizeof(*greh) + 2) {
+ LOGP(DLNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ *error = -EIO;
+ goto out_err;
+ }
+
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ iph = (struct iphdr *) msg->data;
+ if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) {
+ LOGP(DLNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ *error = -EIO;
+ goto out_err;
+ }
+ break;
+ case AF_INET6:
+ ip6h = (struct ip6_hdr *) msg->data;
+ break;
+ }
+
+ greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ if (greh->flags) {
+ LOGP(DLNS, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n",
+ osmo_ntohs(greh->flags));
+ }
+
+ switch (osmo_ntohs(greh->ptype)) {
+ case GRE_PTYPE_IPv4:
+ /* IPv4 messages might be GRE keepalives */
+ *error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+ goto out_err;
+ break;
+ case GRE_PTYPE_IPv6:
+ *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ goto out_err;
+ break;
+ case GRE_PTYPE_FR:
+ /* continue as usual */
+ break;
+ default:
+ LOGP(DLNS, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n",
+ osmo_ntohs(greh->ptype));
+ *error = -EIO;
+ goto out_err;
+ break;
+ }
+
+ if (msg->len < sizeof(*greh) + 2) {
+ LOGP(DLNS, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
+ *error = -EIO;
+ goto out_err;
+ }
+
+ frh = (uint8_t *)greh + sizeof(*greh);
+ if (frh[0] & 0x01) {
+ LOGP(DLNS, LOGL_NOTICE, "Unsupported single-byte FR address\n");
+ *error = -EIO;
+ goto out_err;
+ }
+ *dlci = ((frh[0] & 0xfc) << 2);
+ if ((frh[1] & 0x0f) != 0x01) {
+ LOGP(DLNS, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n",
+ frh[1]);
+ *error = -EIO;
+ goto out_err;
+ }
+ *dlci |= (frh[1] >> 4);
+
+ msg->l2h = frh+2;
+
+ return msg;
+
+out_err:
+ msgb_free(msg);
+ return NULL;
+}
+
+static int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ if (!result)
+ return -EINVAL;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->dlci != dlci) {
+ *result = nsvc;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int handle_nsfrgre_read(struct osmo_fd *bfd)
+{
+ int rc;
+ struct osmo_sockaddr saddr;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct msgb *msg;
+ struct msgb *reject;
+ uint16_t dlci;
+
+ msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci);
+ if (!msg)
+ return rc;
+
+ if (dlci == 0 || dlci == 1023) {
+ LOGP(DLNS, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n",
+ dlci);
+ rc = 0;
+ goto out;
+ }
+
+ rc = gprs_ns2_find_vc_by_dlci(bind, dlci, &nsvc);
+ if (rc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ switch (rc) {
+ case GPRS_NS2_CS_FOUND:
+ break;
+ case GPRS_NS2_CS_ERROR:
+ case GPRS_NS2_CS_SKIPPED:
+ rc = 0;
+ goto out;
+ case GPRS_NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ rc = frgre_sendmsg(bind, reject, &saddr);
+ goto out;
+ case GPRS_NS2_CS_CREATED:
+ frgre_alloc_vc(bind, nsvc, &saddr, dlci);
+ gprs_ns2_vc_fsm_start(nsvc);
+ break;
+ }
+ }
+
+ rc = ns2_recv_vc(nsvc, msg);
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_nsfrgre_write(struct osmo_fd *bfd)
+{
+ /* FIXME: actually send the data here instead of nsip_sendmsg() */
+ return -EIO;
+}
+
+static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ struct osmo_sockaddr *dest)
+{
+ int rc;
+ struct priv_bind *priv = bind->priv;
+
+ rc = sendto(priv->fd.fd, msg->data, msg->len, 0,
+ &dest->u.sa, sizeof(*dest));
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct priv_vc *vcpriv = nsvc->priv;
+ struct priv_bind *bindpriv = bind->priv;
+
+ uint16_t dlci = osmo_htons(bindpriv->dlci);
+ uint8_t *frh;
+ struct gre_hdr *greh;
+
+ /* Prepend the FR header */
+ frh = msgb_push(msg, 2);
+ frh[0] = (dlci >> 2) & 0xfc;
+ frh[1] = ((dlci & 0xf)<<4) | 0x01;
+
+ /* Prepend the GRE header */
+ greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh));
+ greh->flags = 0;
+ greh->ptype = osmo_htons(GRE_PTYPE_FR);
+
+ return frgre_sendmsg(bind, msg, &vcpriv->remote);
+}
+
+static int frgre_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ int rc = 0;
+
+ if (what & OSMO_FD_READ)
+ rc = handle_nsfrgre_read(bfd);
+ if (what & OSMO_FD_WRITE)
+ rc = handle_nsfrgre_write(bfd);
+
+ return rc;
+}
+
+/*! determine if given bind is for FR-GRE encapsulation. */
+int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_frgre);
+}
+
+/*! Create a new bind for NS over FR-GRE.
+ * \param[in] nsi NS instance in which to create the bind
+ * \param[in] local local address on which to bind
+ * \param[in] dscp DSCP/TOS bits to use for transmitted data on this bind
+ * \param[out] result pointer to created bind
+ * \return 0 on success; negative on error */
+int gprs_ns2_frgre_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_frgre;
+ bind->send_vc = frgre_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 = frgre_fd_cb;
+ priv->fd.data = bind;
+ priv->addr = *local;
+ INIT_LLIST_HEAD(&bind->nsvc);
+
+ llist_add(&bind->list, &nsi->binding);
+
+ rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_RAW, IPPROTO_GRE,
+ 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(DLNS, 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 rc;
+}
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
new file mode 100644
index 00000000..15b0bc50
--- /dev/null
+++ b/src/gb/gprs_ns2_internal.h
@@ -0,0 +1,289 @@
+/*! \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_ns2.h>
+
+struct osmo_fsm_inst;
+struct tlv_parsed;
+struct vty;
+
+struct gprs_ns2_vc_driver;
+struct gprs_ns2_vc_bind;
+
+
+
+#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,
+};
+
+enum nsvc_timer_mode {
+ /* standard timers */
+ NSVC_TIMER_TNS_TEST,
+ NSVC_TIMER_TNS_ALIVE,
+ NSVC_TIMER_TNS_RESET,
+ _NSVC_TIMER_NR,
+};
+
+enum ns_stat {
+ NS_STAT_ALIVE_DELAY,
+};
+
+/*! 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 */
+};
+
+/*! Osmocom NS2 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 */
+};
+
+
+#define NSE_S_BLOCKED 0x0001
+#define NSE_S_ALIVE 0x0002
+#define NSE_S_RESET 0x0004
+
+#define NS_DESC_B(st) ((st) & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED")
+#define NS_DESC_A(st) ((st) & NSE_S_ALIVE ? "ALIVE" : "DEAD")
+#define NS_DESC_R(st) ((st) & NSE_S_RESET ? "RESET" : "UNRESET")
+
+/*! An instance of the NS protocol stack */
+struct gprs_ns2_inst {
+ /*! callback to the user for incoming UNIT DATA IND */
+ osmo_prim_cb cb;
+
+ /*! callback data */
+ void *cb_data;
+
+ /*! linked lists of all NSVC binds (e.g. IPv4 bind, but could be also E1 */
+ struct llist_head binding;
+
+ /*! linked lists of all NSVC in this instance */
+ struct llist_head nse;
+
+ /*! create dynamic NSE on receiving packages */
+ bool create_nse;
+
+ uint16_t timeout[NS_TIMERS_COUNT];
+
+ /*! workaround for rate counter until rate counter accepts char str as index */
+ uint32_t rate_ctr_idx;
+};
+
+/*! Structure repesenting a NSE. The BSS/PCU will only have a single NSE, while SGSN has one for each BSS/PCU */
+struct gprs_ns2_nse {
+ uint16_t nsei;
+
+ /*! entry back to ns2_inst */
+ struct gprs_ns2_inst *nsi;
+
+ /*! llist entry for gprs_ns2_inst */
+ struct llist_head list;
+
+ /*! llist head to hold all nsvc */
+ struct llist_head nsvc;
+
+ /*! true if this NSE was created by VTY or pcu socket) */
+ bool persistent;
+
+ /*! true if this NSE has at least one alive VC */
+ bool alive;
+
+ 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 persistent;
+
+ /*! uniquely identifies NS-VC if VC contains nsvci */
+ uint16_t nsvci;
+
+ /*! signalling weight. 0 = don't use for signalling (BVCI == 0)*/
+ uint8_t sig_weight;
+
+ /*! 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;
+
+ bool nsvci_is_valid;
+ bool sns_only;
+
+ 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 nsvc;
+ /*! 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 */
+ enum gprs_ns2_vc_mode vc_mode;
+
+ /*! send a msg over a VC */
+ int (*send_vc)(struct gprs_ns2_vc *nsvc, struct msgb *msg);
+
+ /*! free the vc priv data */
+ void (*free_vc)(struct gprs_ns2_vc *nsvc);
+};
+
+
+struct gprs_ns2_vc_driver {
+ const char *name;
+ void *priv;
+ void (*free_bind)(struct gprs_ns2_vc_bind *driver);
+};
+
+enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const char *logname,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success);
+
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
+ struct msgb *msg);
+
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ bool initiater);
+
+struct msgb *gprs_ns2_msgb_alloc(void);
+
+void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats);
+void ns2_prim_status_ind(struct gprs_ns2_inst *nsi,
+ uint16_t nsei, uint16_t bvci,
+ enum gprs_ns2_affecting_cause cause);
+void ns2_nse_notify_alive(struct gprs_ns2_vc *nsvc, bool alive);
+
+/* message */
+int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause);
+
+/* SNS messages */
+int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
+ int ip4_ep_nr, int ip6_ep_nr);
+int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+
+/* transmit message over a VC */
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause);
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause);
+int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_unblock(struct gprs_ns2_vc *nsvc);
+int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_alive(struct gprs_ns2_vc *nsvc);
+int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
+ uint16_t bvci, uint8_t sducontrol,
+ struct msgb *msg);
+
+int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
+ uint16_t bvci, struct msgb *orig_msg);
+
+/* 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);
+
+/* sns */
+int gprs_ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
+ const char *id);
+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);
+
+/* vc */
+struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+ const char *id, bool initiate);
+int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc);
+int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+int gprs_ns2_vc_is_alive(struct gprs_ns2_vc *nsvc);
+int gprs_ns2_vc_is_unblocked(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_message.c b/src/gb/gprs_ns2_message.c
new file mode 100644
index 00000000..fac6108c
--- /dev/null
+++ b/src/gb/gprs_ns2_message.c
@@ -0,0 +1,713 @@
+/*! \file gprs_ns2_message.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define ERR_IF_NSVC_USES_SNS(nsvc, reason) \
+ do { \
+ if (!nsvc->nse->bss_sns_fi) \
+ break; \
+ LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Rx invalid packet %s with SNS\n", \
+ nsvc->nse->nsei, reason); \
+ } while (0)
+
+enum ns_ctr {
+ NS_CTR_PKTS_IN,
+ NS_CTR_PKTS_OUT,
+ NS_CTR_BYTES_IN,
+ NS_CTR_BYTES_OUT,
+ NS_CTR_BLOCKED,
+ NS_CTR_DEAD,
+ NS_CTR_REPLACED,
+ NS_CTR_NSEI_CHG,
+ NS_CTR_INV_VCI,
+ NS_CTR_INV_NSEI,
+ NS_CTR_LOST_ALIVE,
+ NS_CTR_LOST_RESET,
+};
+
+
+
+static int gprs_ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRESENT(tp, NS_IE_CAUSE) || !TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+ if (!TLVP_PRESENT(tp, NS_IE_VCI)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+{
+
+ if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ uint8_t _cause = tlvp_val8(tp, NS_IE_VCI, 0);
+
+ switch (_cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ case NS_CAUSE_NSVC_UNKNOWN:
+ if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_SEM_INCORR_PDU:
+ case NS_CAUSE_PDU_INCOMP_PSTATE:
+ case NS_CAUSE_PROTO_ERR_UNSPEC:
+ case NS_CAUSE_INVAL_ESSENT_IE:
+ case NS_CAUSE_MISSING_ESSENT_IE:
+ if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_BVCI_UNKNOWN:
+ if (!TLVP_PRESENT(tp, NS_IE_BVCI)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_UNKN_IP_TEST_FAILED:
+ if (!TLVP_PRESENT (tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause)
+{
+ switch (pdu_type) {
+ case NS_PDUT_RESET:
+ return gprs_ns2_validate_reset(nsvc, msg, tp, cause);
+ case NS_PDUT_RESET_ACK:
+ return gprs_ns2_validate_reset_ack(nsvc, msg, tp, cause);
+ case NS_PDUT_BLOCK:
+ return gprs_ns2_validate_block(nsvc, msg, tp, cause);
+ case NS_PDUT_BLOCK_ACK:
+ return gprs_ns2_validate_block_ack(nsvc, msg, tp, cause);
+ case NS_PDUT_STATUS:
+ return gprs_ns2_validate_status(nsvc, msg, tp, cause);
+
+ /* following PDUs doesn't have any payloads */
+ case NS_PDUT_ALIVE:
+ case NS_PDUT_ALIVE_ACK:
+ case NS_PDUT_UNBLOCK:
+ case NS_PDUT_UNBLOCK_ACK:
+ if (msgb_l2len(msg) != sizeof(struct gprs_ns_hdr)) {
+ *cause = NS_CAUSE_PROTO_ERR_UNSPEC;
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+/* transmit functions */
+static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = pdu_type;
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-BLOCK on a given NS-VC.
+ * \param[in] vc NS-VC on which the NS-BLOCK is to be transmitted
+ * \param[in] cause Numeric NS Cause value
+ * \returns 0 in case of success */
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
+
+ msg = gprs_ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK (NSVCI=%u, cause=%s)\n",
+ nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
+
+ rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_BLOCK;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-BLOCK-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
+
+ msg = gprs_ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK ACK (NSVCI=%u)\n", nsvc->nse->nsei, nsvc->nsvci);
+
+ /* be conservative and mark it as blocked even now! */
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_BLOCK_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-RESET on a given NS-VC.
+ * \param[in] nsvc NS-VC used for transmission
+ * \paam[in] cause Numeric NS cause value
+ * \returns 0 in case of success */
+int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t nsei = osmo_htons(nsvc->nse->nsei);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
+
+ msg = gprs_ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n",
+ nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_RESET;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-RESET-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC used for transmission
+ * \returns 0 in case of success */
+int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci, nsei;
+
+ /* Section 9.2.6 */
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
+
+ msg = gprs_ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ nsvci = osmo_htons(nsvc->nsvci);
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = NS_PDUT_RESET_ACK;
+
+ LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n",
+ nsvc->nse->nsei, nsvc->nsvci);
+
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-UNBLOCK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNBLOCK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK");
+
+ LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
+ nsvc->nse->nsei, nsvc->nsvci);
+
+ return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK);
+}
+
+
+/*! Transmit a NS-UNBLOCK-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNBLOCK-ACK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK ACK");
+
+ LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
+ nsvc->nse->nsei, nsvc->nsvci);
+
+ return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
+}
+
+/*! Transmit a NS-ALIVE on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-ALIVE is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE (NSVCI=%u)\n",
+ nsvc->nse->nsei, nsvc->nsvci);
+
+ return ns2_tx_simple(nsvc, NS_PDUT_ALIVE);
+}
+
+/*! Transmit a NS-ALIVE-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-ALIVE-ACK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc)
+{
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE_ACK (NSVCI=%u)\n",
+ nsvc->nse->nsei, nsvc->nsvci);
+
+ return ns2_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
+}
+
+/*! Transmit NS-UNITDATA on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNITDATA is to be transmitted
+ * \param[in] bvci BVCI to encode in NS-UNITDATA header
+ * \param[in] sducontrol SDU control octet of NS header
+ * \param[in] msg message buffer containing payload
+ * \returns 0 in case of success */
+int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
+ uint16_t bvci, uint8_t sducontrol,
+ struct msgb *msg)
+{
+ struct gprs_ns_hdr *nsh;
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ msg->l2h = msgb_push(msg, sizeof(*nsh) + 3);
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ if (!nsh) {
+ LOGP(DLNS, LOGL_ERROR, "Not enough headroom for NS header\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsh->pdu_type = NS_PDUT_UNITDATA;
+ nsh->data[0] = sducontrol;
+ nsh->data[1] = bvci >> 8;
+ nsh->data[2] = bvci & 0xff;
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-STATUS on a given NS-VC.
+ * \param[in] nsvc NS-VC to be used for transmission
+ * \param[in] cause Numeric NS cause value
+ * \param[in] bvci BVCI to be reset within NSVC
+ * \param[in] orig_msg message causing the STATUS
+ * \returns 0 in case of success */
+int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
+ uint16_t bvci, struct msgb *orig_msg)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n",
+ nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_STATUS;
+
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+
+ /* Section 9.2.7.1: Static conditions for NS-VCI */
+ if (cause == NS_CAUSE_NSVC_BLOCKED ||
+ cause == NS_CAUSE_NSVC_UNKNOWN)
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+
+ /* 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;
+ }
+
+ /* Section 9.2.7.3: Static conditions for BVCI */
+ if (cause == NS_CAUSE_BVCI_UNKNOWN)
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+
+/*! Encode + Transmit a SNS-ACK as per Section 9.3.1.
+ * \param[in] nsvc NS-VC through which to transmit the ACK
+ * \param[in] trans_id Transaction ID which to acknowledge
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nse->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_ACK;
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_v_put(msg, trans_id);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+ if (ip4_elems) {
+ /* List of IP4 Elements 10.3.2c */
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST,
+ num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ }
+ if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST,
+ num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-CONFIG as per Section 9.3.4.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] end_flag Whether or not this is the last SNS-CONFIG
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nse->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_CONFIG;
+
+ msgb_v_put(msg, end_flag ? 0x01 : 0x00);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+ /* List of IP4 Elements 10.3.2c */
+ if (ip4_elems) {
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ } else if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-CONFIG-ACK as per Section 9.3.5.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG-ACK
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nse->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_CONFIG_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+
+/*! Encode + transmit a SNS-SIZE as per Section 9.3.7.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE
+ * \param[in] reset_flag Whether or not to add a RESET flag
+ * \param[in] max_nr_nsvc Maximum number of NS-VCs
+ * \param[in] ip4_ep_nr Number of IPv4 endpoints (< 0 will omit the TLV)
+ * \param[in] ip6_ep_nr Number of IPv6 endpoints (< 0 will omit the TLV)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
+ int ip4_ep_nr, int ip6_ep_nr)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nse->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_SIZE;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_tv_put(msg, NS_IE_RESET_FLAG, reset_flag ? 0x01 : 0x00);
+ msgb_tv16_put(msg, NS_IE_MAX_NR_NSVC, max_nr_nsvc);
+ if (ip4_ep_nr >= 0)
+ msgb_tv16_put(msg, NS_IE_IPv4_EP_NR, ip4_ep_nr);
+ if (ip6_ep_nr >= 0)
+ msgb_tv16_put(msg, NS_IE_IPv6_EP_NR, ip6_ep_nr);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-SIZE-ACK as per Section 9.3.8.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE-ACK
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nse->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ nsh->pdu_type = SNS_PDUT_SIZE_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
new file mode 100644
index 00000000..753ac4d8
--- /dev/null
+++ b/src/gb/gprs_ns2_sns.c
@@ -0,0 +1,1477 @@
+/*! \file gprs_ns2_sns.c
+ * NS Sub-Network Service Protocol implementation
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. The BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports.
+ *
+ * Known limitation/expectation/bugs:
+ * - No concurrent dual stack. It supports either IPv4 or IPv6, but not both at the same time.
+ * - SNS Add/Change/Delete: Doesn't answer on the same NSVC as received SNS ADD/CHANGE/DELETE PDUs.
+ * - SNS Add/Change/Delete: Doesn't communicated the failed IPv4/IPv6 entries on the SNS_ACK.
+ */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define S(x) (1 << (x))
+
+enum ns2_sns_type {
+ IPv4,
+ IPv6,
+};
+
+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,
+ GPRS_SNS_EV_NO_NSVC,
+};
+
+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 }
+};
+
+struct ns2_sns_state {
+ struct gprs_ns2_nse *nse;
+
+ enum ns2_sns_type ip;
+
+ /* 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)
+
+/* helper function to compute the sum of all (data or signaling) weights */
+static int ip6_weight_sum(const struct gprs_ns_ie_ip6_elem *ip6, unsigned int num,
+ bool data_weight)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < num; i++) {
+ if (data_weight)
+ weight_sum += ip6[i].data_weight;
+ else
+ weight_sum += ip6[i].sig_weight;
+ }
+ return weight_sum;
+}
+#define ip6_weight_sum_data(x,y) ip6_weight_sum(x, y, true)
+#define ip6_weight_sum_sig(x,y) ip6_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(nse, &sa);
+}
+
+static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct osmo_sockaddr sa;
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET;
+
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
+}
+
+/*! 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 osmo_fsm_inst *fi = nsvc->nse->bss_sns_fi;
+
+ if (!fi)
+ return;
+
+ gss = (struct ns2_sns_state *) fi->priv;
+ if (nsvc != gss->sns_nsvc)
+ return;
+
+ nse = nsvc->nse;
+ if (nse->alive) {
+ /* choose a different sns nsvc */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (gprs_ns2_vc_is_unblocked(tmp))
+ gss->sns_nsvc = tmp;
+ }
+ } else {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: no remaining NSVC. Reseting SNS FSM.", nse->nsei);
+ gss->sns_nsvc = NULL;
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_NO_NSVC, NULL);
+ }
+}
+
+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 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;
+ }
+}
+
+static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi,
+ struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct 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.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->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 = ip6->sig_weight;
+ nsvc->data_weight = ip6->data_weight;
+ }
+}
+
+
+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 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->nsvc, 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 = false;
+ }
+ }
+
+ for (i = 0; i < gss->num_ip6_remote; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->ip6_remote[i];
+
+ remote.u.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->udp_port;
+
+ llist_for_each_entry(bind, &nse->nsi->binding, list) {
+ bool found = false;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, 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 = ip6->data_weight;
+ nsvc->sig_weight = ip6->sig_weight;
+ nsvc->sns_only = false;
+ }
+ }
+
+
+ return 0;
+}
+
+/* Add a given remote IPv4 element to gprs_sns_state */
+static int add_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ if (gss->num_ip4_remote >= gss->num_max_ip4_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+
+ /* check for duplicates */
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ continue;
+ /* TODO: log message duplicate */
+ /* TODO: check if this is the correct cause code */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ 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;
+}
+
+/* Add a given remote IPv6 element to gprs_sns_state */
+static int add_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ if (gss->num_ip6_remote >= gss->num_max_ip6_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+
+ gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote, struct gprs_ns_ie_ip6_elem,
+ gss->num_ip6_remote+1);
+ gss->ip6_remote[gss->num_ip6_remote] = *ip6;
+ gss->num_ip6_remote += 1;
+ return 0;
+}
+
+/* Remove a given remote IPv6 element from gprs_sns_state */
+static int remove_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip6_remote; i++) {
+ if (memcmp(&gss->ip6_remote[i], ip6, sizeof(*ip6)))
+ continue;
+ /* all array elements < i remain as they are; all > i are shifted left by one */
+ memmove(&gss->ip6_remote[i], &gss->ip6_remote[i+1], gss->num_ip6_remote-i-1);
+ gss->num_ip6_remote -= 1;
+ return 0;
+ }
+ return -1;
+}
+
+/* update the weights for specified remote IPv6 */
+static int update_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip6_remote; i++) {
+ if (memcmp(&gss->ip6_remote[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ gss->ip6_remote[i].udp_port != ip6->udp_port)
+ continue;
+ gss->ip6_remote[i].sig_weight = ip6->sig_weight;
+ gss->ip6_remote[i].data_weight = ip6->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, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr sa = {};
+ struct osmo_sockaddr *remote;
+ uint8_t new_signal;
+ uint8_t new_data;
+
+ /* TODO: Upon receiving an SNS-CHANGEWEIGHT PDU, if the resulting sum of the
+ * signalling weights of all the peer IP endpoints configured for this NSE is
+ * equal to zero or if the resulting sum of the data weights of all the peer IP
+ * endpoints configured for this NSE is equal to zero, the BSS/SGSN shall send an
+ * SNS-ACK PDU with a cause code of "Invalid weights". */
+
+ if (ip4) {
+ if (update_remote_ip4_elem(gss, ip4))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ sa.u.sin.sin_port = ip4->udp_port;
+ sa.u.sin.sin_family = AF_INET;
+ new_signal = ip4->sig_weight;
+ new_data = ip4->data_weight;
+ } else if (ip6) {
+ if (update_remote_ip6_elem(gss, ip6))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET6;
+ new_signal = ip6->sig_weight;
+ new_data = ip6->data_weight;
+ } else {
+ OSMO_ASSERT(false);
+ }
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ /* all nsvc in NSE should be IP/UDP nsvc */
+ OSMO_ASSERT(remote);
+
+ if (osmo_sockaddr_cmp(&sa, remote))
+ continue;
+
+ LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight %u->%u, sig_weight %u->%u\n",
+ gprs_ns2_ll_str(nsvc), nsvc->data_weight, new_data,
+ nsvc->sig_weight, new_signal);
+
+ nsvc->data_weight = new_data;
+ nsvc->sig_weight = new_signal;
+ }
+
+ return 0;
+}
+
+static int do_sns_delete(struct osmo_fsm_inst *fi,
+ const struct gprs_ns_ie_ip4_elem *ip4,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc, *tmp;
+ struct osmo_sockaddr *remote;
+ struct osmo_sockaddr sa = {};
+
+ if (ip4) {
+ if (remove_remote_ip4_elem(gss, ip4) < 0)
+ return -NS_CAUSE_UNKN_IP_EP;
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ sa.u.sin.sin_port = ip4->udp_port;
+ sa.u.sin.sin_family = AF_INET;
+ } else if (ip6) {
+ if (remove_remote_ip6_elem(gss, ip6))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET6;
+ } else {
+ OSMO_ASSERT(false);
+ }
+
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ /* all nsvc in NSE should be IP/UDP nsvc */
+ OSMO_ASSERT(remote);
+ if (osmo_sockaddr_cmp(&sa, remote))
+ continue;
+
+ LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns2_ll_str(nsvc));
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ return 0;
+}
+
+static int do_sns_add(struct osmo_fsm_inst *fi,
+ const struct gprs_ns_ie_ip4_elem *ip4,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ int rc = 0;
+
+ /* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints
+ * exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send
+ * an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */
+ switch (gss->ip) {
+ case IPv4:
+ rc = add_remote_ip4_elem(gss, ip4);
+ break;
+ case IPv6:
+ rc = add_remote_ip6_elem(gss, ip6);
+ break;
+ default:
+ /* the gss->ip is initialized with the bss */
+ OSMO_ASSERT(false);
+ }
+
+ if (rc)
+ return rc;
+
+ /* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the
+ * NSE shall send an SNS-ACK PDU with the cause code "Protocol error -
+ * unspecified" */
+ switch (gss->ip) {
+ case IPv4:
+ nsvc = nsvc_by_ip4_elem(nse, ip4);
+ if (nsvc) {
+ /* the nsvc should be already in sync with the ip4 / ip6 elements */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip4(fi, nse, ip4);
+ break;
+ case IPv6:
+ nsvc = nsvc_by_ip6_elem(nse, ip6);
+ if (nsvc) {
+ /* the nsvc should be already in sync with the ip4 / ip6 elements */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip6(fi, nse, ip6);
+ break;
+ }
+
+ gprs_ns2_start_alive_all_nsvcs(nse);
+
+ return 0;
+}
+
+
+static void ns2_sns_st_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)));
+ /* TODO: 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;
+
+ if (old_state != GPRS_SNS_ST_UNCONFIGURED)
+ ns2_prim_status_ind(gss->nse->nsi, gss->nse->nsei, 0, NS_AFF_CAUSE_SNS_FAILURE);
+
+ if (gss->num_max_ip4_remote > 0)
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->num_max_ip4_remote, -1);
+ else
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->num_max_ip6_remote);
+
+}
+
+static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_CONFIG_ACK:
+ tp = (struct tlv_parsed *) data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* TODO: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_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 */
+ /* TODO: ipv6 */
+ switch (gss->ip) {
+ case IPv4:
+ ns2_tx_sns_config(gss->sns_nsvc, true,
+ gss->ip4_local, gss->num_ip4_local,
+ NULL, 0);
+ break;
+ case IPv6:
+ ns2_tx_sns_config(gss->sns_nsvc, true,
+ NULL, 0,
+ gss->ip6_local, gss->num_ip6_local);
+ break;
+ }
+}
+
+
+static void ns_sns_st_config_sgsn_ip4(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ struct tlv_parsed *tp = NULL;
+
+ uint8_t cause;
+
+ tp = (struct tlv_parsed *) data;
+
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ /* 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;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ create_missing_nsvcs(fi);
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ }
+}
+
+static void ns_sns_st_config_sgsn_ip6(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ const struct gprs_ns_ie_ip6_elem *v6_list;
+ unsigned int num_v6;
+ struct tlv_parsed *tp = NULL;
+
+ uint8_t cause;
+
+ tp = (struct tlv_parsed *) data;
+
+ if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ /* realloc to the new size */
+ gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote,
+ struct gprs_ns_ie_ip6_elem,
+ gss->num_ip6_remote+num_v6);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->ip6_remote[gss->num_ip6_remote], v6_list, num_v6*sizeof(*v6_list));
+ gss->num_ip6_remote += num_v6;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %u entries\n",
+ gss->num_ip6_remote);
+ if (event == GPRS_SNS_EV_CONFIG_END) {
+ /* check if sum of data / sig weights == 0 */
+ if (ip6_weight_sum_data(gss->ip6_remote, gss->num_ip6_remote) == 0 ||
+ ip6_weight_sum_sig(gss->ip6_remote, gss->num_ip6_remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ create_missing_nsvcs(fi);
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ }
+}
+
+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;
+
+ switch (event) {
+ case GPRS_SNS_EV_CONFIG_END:
+ case GPRS_SNS_EV_CONFIG:
+
+#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
+ /* TODO: reject IPv6 elements on IPv4 mode and vice versa */
+ switch (gss->ip) {
+ case IPv4:
+ ns_sns_st_config_sgsn_ip4(fi, event, data);
+ break;
+ case IPv6:
+ ns_sns_st_config_sgsn_ip6(fi, event, data);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* called when receiving GPRS_SNS_EV_ADD in state configure */
+static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ unsigned int i;
+ int rc = 0;
+
+ /* TODO: refactor EV_ADD/CHANGE/REMOVE by
+ * check uniqueness within the lists (no doublicate entries)
+ * check not-known-by-us and sent back a list of unknown/known values
+ * (abnormal behaviour according to 48.016)
+ */
+
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (gss->ip == IPv4) {
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ for (i = 0; i < num_v4; i++) {
+ unsigned int j;
+ rc = do_sns_add(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, &v4_list[j], NULL);
+ cause = -rc;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ break;
+ }
+ }
+ } else { /* IPv6 */
+ if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ for (i = 0; i < num_v6; i++) {
+ unsigned int j;
+ rc = do_sns_add(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, NULL, &v6_list[j]);
+ cause = -rc;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ break;
+ }
+ }
+ }
+
+ /* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ unsigned int i;
+ int rc = 0;
+
+ /* TODO: split up delete into v4 + v6
+ * TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa
+ * TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time
+ */
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (gss->ip == IPv4) {
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ for ( i = 0; i < num_v4; i++) {
+ rc = do_sns_delete(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ } else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) {
+ /* delete all NS-VCs for given IPv4 address */
+ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
+ struct gprs_ns_ie_ip4_elem *ip4_remote;
+ uint32_t ip_addr = *(uint32_t *)(ie+1);
+ if (ie[0] != 0x01) { /* Address Type != IPv4 */
+ cause = NS_CAUSE_UNKN_IP_ADDR;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ /* make a copy as do_sns_delete() will change the array underneath us */
+ ip4_remote = talloc_memdup(fi, gss->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], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ }
+ talloc_free(ip4_remote);
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else { /* IPv6 */
+ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ for (i = 0; i < num_v6; i++) {
+ rc = do_sns_delete(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) {
+ /* delete all NS-VCs for given IPv4 address */
+ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
+ struct gprs_ns_ie_ip6_elem *ip6_remote;
+ struct in6_addr ip6_addr;
+ unsigned int i;
+ if (ie[0] != 0x02) { /* Address Type != IPv6 */
+ cause = NS_CAUSE_UNKN_IP_ADDR;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr));
+ /* make a copy as do_sns_delete() will change the array underneath us */
+ ip6_remote = talloc_memdup(fi, gss->ip6_remote,
+ gss->num_ip6_remote * sizeof(*v4_list));
+ for (i = 0; i < gss->num_ip6_remote; i++) {
+ if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) {
+ rc = do_sns_delete(fi, NULL, &ip6_remote[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ }
+
+ talloc_free(ip6_remote);
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ }
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ int rc = 0;
+ unsigned int i;
+
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ for (i = 0; i < num_v4; i++) {
+ rc = do_sns_change_weight(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+ for (i = 0; i < num_v6; i++) {
+ rc = do_sns_change_weight(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct tlv_parsed *tp = data;
+
+ switch (event) {
+ case GPRS_SNS_EV_ADD:
+ ns2_sns_st_configured_add(fi, gss, tp);
+ break;
+ case GPRS_SNS_EV_DELETE:
+ ns2_sns_st_configured_delete(fi, gss, tp);
+ break;
+ case GPRS_SNS_EV_CHANGE_WEIGHT:
+ ns2_sns_st_configured_change(fi, gss, tp);
+ break;
+ }
+}
+
+static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ ns2_prim_status_ind(nse->nsi, nse->nsei, 0, NS_AFF_CAUSE_SNS_CONFIGURED);
+}
+
+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) |
+ S(GPRS_SNS_ST_SIZE),
+ .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) |
+ S(GPRS_SNS_ST_SIZE),
+ .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 void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+
+ /* reset when receiving GPRS_SNS_EV_NO_NSVC */
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+}
+
+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 = GPRS_SNS_EV_NO_NSVC,
+ .allstate_action = ns2_sns_st_all_action,
+ .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,
+ .log_subsys = DLNS,
+};
+
+/*! Allocate an IP-SNS FSM for the BSS side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \retruns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
+ const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+
+ return fi;
+err:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
+}
+
+/*! Start an IP-SNS FSM.
+ * \param[in] nse NS Entity whose IP-SNS FSM shall be started
+ * \param[in] nsvc Initial NS-VC
+ * \param[in] remote remote (SGSN) address
+ * \returns 0 on success; negative on error */
+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_elems;
+ struct gprs_ns_ie_ip6_elem *ip6_elems;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ struct osmo_sockaddr *sa, local;
+ gss->ip = remote->u.sa.sa_family == AF_INET ? IPv4 : IPv6;
+
+ gss->initial = *remote;
+ gss->sns_nsvc = nsvc;
+ nsvc->sns_only = true;
+
+ int count = 0;
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family)
+ count++;
+ }
+
+ if (count == 0) {
+ /* TODO: logging */
+ goto err;
+ }
+
+ switch (gss->ip) {
+ case IPv4:
+ ip4_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip4_elem) * count);
+ if (!ip4_elems)
+ goto err;
+
+ gss->ip4_local = ip4_elems;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET)
+ continue;
+
+ /* check if this is an specific bind */
+ if (sa->u.sin.sin_addr.s_addr == 0) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
+ } else {
+ ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
+ }
+
+ ip4_elems->udp_port = sa->u.sin.sin_port;
+ ip4_elems->sig_weight = 2;
+ ip4_elems->data_weight = 1;
+ ip4_elems++;
+ }
+
+ gss->num_ip4_local = count;
+ gss->num_max_ip4_remote = 4;
+ break;
+ case IPv6:
+ /* IPv6 */
+ ip6_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip6_elem) * count);
+ if (!ip6_elems)
+ goto err;
+
+ gss->ip6_local = ip6_elems;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET6)
+ continue;
+
+ /* check if this is an specific bind */
+ if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip6_elems->ip_addr = local.u.sin6.sin6_addr;
+ } else {
+ ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
+ }
+
+ ip6_elems->udp_port = sa->u.sin.sin_port;
+ ip6_elems->sig_weight = 2;
+ ip6_elems->data_weight = 1;
+
+ ip6_elems++;
+ }
+ gss->num_ip6_local = count;
+ gss->num_max_ip6_remote = 4;
+ break;
+ }
+
+ gss->num_max_nsvcs = 8;
+
+ 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.
+ * \param[in] nsvc NS-VC on which the message was received
+ * \param[in] msg message buffer of the IP-SNS message
+ * \param[in] tp parsed TLV structure of message
+ * \retruns 0 on success; negative on error */
+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 = nsvc->nse->nsei;
+ struct osmo_fsm_inst *fi;
+
+ if (!nse->bss_sns_fi) {
+ LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx %s for NS Instance that has no SNS!\n",
+ nsvc->nse->nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ return -EINVAL;
+ }
+
+ LOGP(DLNS, 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(DLNS, 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(DLNS, 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;
+}
+
+#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);
+}
+
+static void vty_dump_sns_ip6(struct vty *vty, const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ char ip_addr[INET6_ADDRSTRLEN] = {};
+ if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN)))
+ strcpy(ip_addr, "Invalid IPv6");
+
+ vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE);
+}
+
+/*! Dump the IP-SNS state to a vty.
+ * \param[in] vty VTY to which the state shall be printed
+ * \param[in] nse NS Entity whose IP-SNS state shall be printed
+ * \param[in] stats Whether or not statistics shall also be printed */
+void gprs_ns2_sns_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, IPv6 Endpoints: %zu%s",
+ gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
+
+ if (gss->num_ip4_local && gss->num_ip4_remote) {
+ 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]);
+ }
+
+ if (gss->num_ip6_local && gss->num_ip6_remote) {
+ vty_out(vty, "Local IPv6 Endpoints:%s", VTY_NEWLINE);
+ for (i = 0; i < gss->num_ip6_local; i++)
+ vty_dump_sns_ip6(vty, &gss->ip6_local[i]);
+
+ vty_out(vty, "Remote IPv6 Endpoints:%s", VTY_NEWLINE);
+ for (i = 0; i < gss->num_ip6_remote; i++)
+ vty_dump_sns_ip6(vty, &gss->ip6_remote[i]);
+ }
+}
+
+/* initialize osmo_ctx on main tread */
+static __attribute__((constructor)) void on_dso_load_ctx(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_bss_fsm) == 0);
+}
diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c
new file mode 100644
index 00000000..251f869a
--- /dev/null
+++ b/src/gb/gprs_ns2_udp.c
@@ -0,0 +1,385 @@
+/*! \file gprs_ns2_udp.c
+ * NS-over-UDP implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+
+
+struct gprs_ns2_vc_driver vc_driver_ip = {
+ .name = "GB UDP IPv4/IPv6",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_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_fd_close(&priv->fd);
+ talloc_free(priv);
+}
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc->priv)
+ return;
+
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+/*! Find a NS-VC by its remote socket address.
+ * \param[in] bind in which to search
+ * \param[in] saddr remote peer socket adddress to search
+ * \returns NS-VC matching sockaddr; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind, struct osmo_sockaddr *saddr)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->remote.u.sa.sa_family != saddr->u.sa.sa_family)
+ continue;
+ if (osmo_sockaddr_cmp(&vcpriv->remote, saddr))
+ continue;
+
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+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;
+}
+
+/*! send the msg and free it afterwards.
+ * \param nsvc NS-VC on which the message shall be sent
+ * \param msg message to be sent
+ * \return number of bytes transmitted; negative on error */
+static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct priv_vc *priv = nsvc->priv;
+
+ rc = nsip_sendmsg(bind, msg, &priv->remote);
+
+ return rc;
+}
+
+/* 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(DLNS, 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 *nsvc, struct osmo_sockaddr *remote)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ nsvc->priv = priv;
+ priv->remote = *remote;
+
+ return priv;
+}
+
+static 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 *nsvc;
+ struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+ struct msgb *reject;
+
+ if (!msg)
+ return -EINVAL;
+
+ /* check if a vc is available */
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &saddr);
+ if (!nsvc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ switch (rc) {
+ case GPRS_NS2_CS_FOUND:
+ break;
+ case GPRS_NS2_CS_ERROR:
+ case GPRS_NS2_CS_SKIPPED:
+ rc = 0;
+ goto out;
+ case GPRS_NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ rc = nsip_sendmsg(bind, reject, &saddr);
+ goto out;
+ case GPRS_NS2_CS_CREATED:
+ ns2_driver_alloc_vc(bind, nsvc, &saddr);
+ gprs_ns2_vc_fsm_start(nsvc);
+ break;
+ }
+ }
+
+ rc = ns2_recv_vc(nsvc, msg);
+out:
+ 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;
+}
+
+/*! Bind to an IPv4/IPv6 address
+ * \param[in] nsi NS Instance in which to create the NSVC
+ * \param[in] local the local address to bind to
+ * \param[in] dscp the DSCP/TOS bits used for transmitted data
+ * \param[out] result if set, returns the bind object
+ * \return 0 on success; negative in case of error */
+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->nsvc);
+
+ llist_add(&bind->list, &nsi->binding);
+
+ rc = osmo_sock_init_osa_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(DLNS, 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;
+}
+
+/*! Create new NS-VC to a given remote address
+ * \param[in] bind the bind we want to connect
+ * \param[in] nse NS entity to be used for the new NS-VC
+ * \param[in] remote remote address to connect to
+ * \return pointer to newly-allocated and connected NS-VC; NULL on error */
+struct gprs_ns2_vc *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;
+
+ 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;
+}
+
+/*! Return the socket address of the remote peer of a NS-VC.
+ * \param[in] nsvc NS-VC whose remote peer we want to know
+ * \return address of the remote peer; NULL in case of error */
+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;
+}
+
+/*! Return the locally bound socket address of the bind.
+ * \param[in] bind The bind whose local address we want to know
+ * \return address of the local bind */
+struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ priv = bind->priv;
+ return &priv->addr;
+}
+
+/*! Is the given bind an IP bind? */
+int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_ip);
+}
+
+/*! Set the DSCP (TOS) bit value of the given bind. */
+int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp)
+{
+ struct priv_bind *priv;
+ int rc = 0;
+
+ 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(DLNS, LOGL_ERROR,
+ "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
+ dscp, rc, errno);
+ }
+
+ return rc;
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c
new file mode 100644
index 00000000..d13f1ce6
--- /dev/null
+++ b/src/gb/gprs_ns2_vc_fsm.c
@@ -0,0 +1,668 @@
+/*! \file gprs_ns2_vc_fsm.c
+ * NS virtual circuit FSM implementation
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. In theory, the BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define S(x) (1 << (x))
+
+#define DNS 10
+
+struct gprs_ns2_vc_priv {
+ struct gprs_ns2_vc *nsvc;
+ /* 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;
+ struct timeval timer_started;
+ } alive;
+};
+
+
+/* The FSM covers both the VC with RESET/BLOCK and without RESET/BLOCK procedure..
+ *
+ * With RESET/BLOCK, the state should follow:
+ * - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED
+ *
+ * Without RESET/BLOCK, the state should follow:
+ * - UNCONFIGURED -> 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 inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ return priv->nsvc->nse->nsi;
+}
+
+static void start_test_procedure(struct gprs_ns2_vc_priv *priv)
+{
+ struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+
+ if (osmo_timer_pending(&priv->alive.timer))
+ return;
+
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ priv->alive.N = 0;
+
+ osmo_gettimeofday(&priv->alive.timer_started, NULL);
+ ns2_tx_alive(priv->nsvc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+}
+
+static void stop_test_procedure(struct gprs_ns2_vc_priv *priv)
+{
+ osmo_timer_del(&priv->alive.timer);
+}
+
+static int alive_timer_elapsed_ms(struct gprs_ns2_vc_priv *priv)
+{
+ struct timeval now, elapsed;
+ osmo_gettimeofday(&now, NULL);
+ timersub(&now, &priv->alive.timer_started, &elapsed);
+
+ return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000;
+}
+
+static void recv_test_procedure(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc = priv->nsvc;
+
+ /* ignoring ACKs without sending an ALIVE */
+ if (priv->alive.mode != NS_TOUT_TNS_ALIVE)
+ return;
+
+ priv->alive.mode = NS_TOUT_TNS_TEST;
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
+ osmo_stat_item_set(nsvc->statg->items[NS_STAT_ALIVE_DELAY],
+ alive_timer_elapsed_ms(priv));
+}
+
+
+static void alive_timeout_handler(void *data)
+{
+ struct osmo_fsm_inst *fi = data;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ switch (priv->alive.mode) {
+ case NS_TOUT_TNS_TEST:
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ ns2_tx_alive(priv->nsvc);
+ 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]) {
+ /* retransmission */
+ ns2_tx_alive(priv->nsvc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ } else {
+ /* lost connection */
+ if (priv->nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], 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->nsvc->nse->nsi;
+
+ switch (event) {
+ case GPRS_NS2_EV_START:
+ switch (priv->nsvc->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)
+ ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION);
+
+ stop_test_procedure(priv);
+ ns2_nse_notify_unblocked(priv->nsvc, 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;
+ }
+ } else {
+ /* we are on the receiving end */
+ switch (event) {
+ case GPRS_NS2_EV_RESET:
+ ns2_tx_reset_ack(priv->nsvc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 0);
+ break;
+ }
+ }
+}
+
+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)
+ ns2_tx_unblock(priv->nsvc);
+
+ start_test_procedure(priv);
+}
+
+static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ if (priv->initiater) {
+ switch (event) {
+ case GPRS_NS2_EV_BLOCK:
+ /* TODO: BLOCK is a UNBLOCK_NACK */
+ ns2_tx_block_ack(priv->nsvc);
+ break;
+ case GPRS_NS2_EV_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ /* 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:
+ ns2_tx_unblock_ack(priv->nsvc);
+ 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;
+
+ ns2_nse_notify_unblocked(priv->nsvc, 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;
+ ns2_tx_block_ack(priv->nsvc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 2);
+ break;
+ }
+}
+
+static void gprs_ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case GPRS_NS2_EV_ALIVE_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
+ break;
+ }
+}
+
+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;
+
+ ns2_tx_alive(priv->nsvc);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static void gprs_ns2_st_alive_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
+{
+ start_test_procedure(fi->priv);
+}
+
+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_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct osmo_gprs_ns2_prim nsp = {};
+ uint16_t bvci;
+
+ if (msgb_l2len(msg) < sizeof(*nsh) + 3)
+ return;
+
+ /* TODO: 7.1: For an IP sub-network, an NS-UNITDATA PDU
+ * for a PTP BVC may indicate a request to change the IP endpoint
+ * and/or a response to a change in the IP endpoint. */
+
+ /* TODO: nsh->data[0] -> C/R only valid in IP SNS */
+ bvci = nsh->data[1] << 8 | nsh->data[2];
+
+ msg->l3h = &nsh->data[3];
+ nsp.bvci = bvci;
+ nsp.nsei = priv->nsvc->nse->nsei;
+
+ /* 10.3.9 NS SDU Control Bits */
+ if (nsh->data[0] & 0x1)
+ nsp.u.unitdata.change = NS_ENDPOINT_REQUEST_CHANGE;
+
+ osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_UNIT_DATA,
+ PRIM_OP_INDICATION, msg);
+ nsi->cb(&nsp.oph, nsi->cb_data);
+}
+
+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->nsvc->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:
+ ns2_tx_alive_ack(priv->nsvc);
+ }
+ 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
+ ns2_tx_status(priv->nsvc,
+ 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,
+ .log_subsys = DLNS,
+};
+
+/*!
+ * \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 *nsvc,
+ 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, nsvc, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ nsvc->fi = fi;
+ priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv);
+ priv->nsvc = nsvc;
+ priv->initiater = initiater;
+
+ osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi);
+
+ return fi;
+}
+
+/*! Start a NS-VC FSM.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int 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;
+}
+
+/*! entry point for messages from the driver/VL
+ * \param nsvc virtual circuit on which the message was received
+ * \param msg message that was received
+ * \param tp parsed TLVs of the received message
+ * \return 0 on success; negative on error */
+int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct osmo_fsm_inst *fi = nsvc->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 */
+
+ if (gprs_ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) {
+ if (nsh->pdu_type != NS_PDUT_STATUS) {
+ return ns2_tx_status(nsvc, 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(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*! is the given NS-VC unblocked? */
+int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
+{
+ return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED);
+}
+
+/* initialize osmo_ctx on main tread */
+static __attribute__((constructor)) void on_dso_load_ctx(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_vc_fsm) == 0);
+}
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
new file mode 100644
index 00000000..9deb3a10
--- /dev/null
+++ b/src/gb/gprs_ns2_vty.c
@@ -0,0 +1,837 @@
+/*! \file gprs_ns2_vty.c
+ * VTY interface for our GPRS Networks Service (NS) implementation. */
+
+/* (C) 2009-2014 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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/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/core/socket.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gsm/tlv.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"
+
+struct ns2_vty_priv {
+ /* global listen */
+ struct osmo_sockaddr_str udp;
+ struct osmo_sockaddr_str frgreaddr;
+ int dscp;
+ enum gprs_ns2_vc_mode vc_mode;
+ /* force vc mode if another configuration forces
+ * the vc mode. E.g. SNS configuration */
+ bool force_vc_mode;
+ const char *force_vc_mode_reason;
+ 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 vtyvc;
+}
+
+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_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)
+ return NULL;
+
+ vtyvc = vtyvc_alloc(nsei);
+ if (!vtyvc)
+ return vtyvc;
+
+ vtyvc->nsei = nsei;
+ return vtyvc;
+}
+
+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.vc_mode == NS2_VC_MODE_BLOCKRESET ? "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",
+ 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 fr-dlci %u%s",
+ vtyvc->nsei, vtyvc->frdlci,
+ 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_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+{
+ struct osmo_sockaddr_str remote;
+ struct osmo_sockaddr_str local;
+ struct osmo_sockaddr *sockaddr;
+
+ 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:
+ /* TODO: implement dump_nse for FR GRE */
+ case GPRS_NS_LL_E1:
+ /* TODO: implement dump_nse for E1 */
+ break;
+ }
+
+ vty_out(vty, "Remote: %s ",
+ 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_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ vty_out(vty, "NSEI %5u%s",
+ nse->nsei, VTY_NEWLINE);
+
+ gprs_ns2_sns_dump_vty(vty, nse, stats);
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (persistent_only) {
+ if (nsvc->persistent)
+ dump_nsvc(vty, nsvc, stats);
+ } else {
+ dump_nsvc(vty, nsvc, stats);
+ }
+ }
+}
+
+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) {
+ dump_nse(vty, nse, stats, persistent_only);
+ 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;
+ }
+
+ dump_nsvc(vty, nsvc, show_stats);
+ }
+
+ 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 IPv4 Address\n"
+ "Remote IPv6 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"
+ "IPv4 Address\n"
+ "IPv6 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")
+{
+ enum gprs_ns2_vc_mode vc_mode;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (!strcmp(argv[0], "enabled"))
+ vc_mode = NS2_VC_MODE_BLOCKRESET;
+ else
+ vc_mode = NS2_VC_MODE_ALIVE;
+
+ if (priv.force_vc_mode) {
+ if (priv.vc_mode != vc_mode)
+ {
+ vty_out(vty, "Ignoring use-reset-block because it's already set by %s.%s",
+ priv.force_vc_mode_reason, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+ }
+
+ priv.vc_mode = vc_mode;
+
+ llist_for_each_entry(bind, &vty_nsi->binding, list) {
+ gprs_ns2_bind_set_mode(bind, priv.vc_mode);
+ }
+
+ 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"
+ "IPv4 Address\n"
+ "IPv6 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;
+}
+
+/* TODO: allow vty to reset/block/unblock nsvc/nsei */
+
+/* TODO: add filter for NSEI as ns1 code does */
+/* TODO: add filter for single connection by description */
+DEFUN(logging_fltr_nsvc,
+ logging_fltr_nsvc_cmd,
+ "logging filter nsvc nsvci <0-65535>",
+ LOGGING_STR FILTER_STR
+ "Filter based on NS Virtual Connection\n"
+ "Identify NS-VC by NSVCI\n"
+ "Numeric identifier\n")
+{
+ struct log_target *tgt;
+ struct gprs_ns2_vc *nsvc;
+ uint16_t id = atoi(argv[1]);
+
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, id);
+ if (!nsvc) {
+ vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ log_set_nsvc_filter(tgt, nsvc);
+ log_tgt_mutex_unlock();
+ return CMD_SUCCESS;
+}
+
+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.vc_mode = NS2_VC_MODE_BLOCKRESET;
+
+ /* 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);
+
+ /* TODO: nsvc/nsei command to reset states or reset/block/unblock nsei/nsvcs */
+
+ 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_bind_set_mode(bind, priv.vc_mode);
+ }
+
+ /* 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;
+ }
+ }
+ nse->persistent = true;
+
+ if (bind) {
+ nsvc = gprs_ns2_ip_connect(bind,
+ &sockaddr,
+ nse,
+ vtyvc->nsvci);
+ if (!nsvc) {
+ /* Could not create NSVC, connect failed */
+ continue;
+ }
+ nsvc->persistent = true;
+ }
+ }
+
+
+ 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_bind_set_mode(bind, priv.vc_mode);
+}
+
+/*!
+ * \brief ns2_vty_force_vc_mode force a mode and prevents the vty from overwriting it.
+ * \param force if true mode and reason will be set. false to allow modification via vty.
+ * \param mode
+ * \param reason A description shown to the user when a vty command wants to change the mode.
+ */
+void gprs_ns2_vty_force_vc_mode(bool force, enum gprs_ns2_vc_mode mode, const char *reason)
+{
+ priv.force_vc_mode = force;
+
+ if (force) {
+ priv.vc_mode = mode;
+ priv.force_vc_mode_reason = reason;
+ }
+}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index 0c0c5c43..d08e85e9 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -72,6 +72,40 @@ gprs_ns_ll_copy;
gprs_ns_ll_clear;
gprs_ns_msgb_alloc;
+gprs_ns2_bind_set_mode;
+gprs_ns2_cause_str;
+gprs_ns2_create_nse;
+gprs_ns2_dynamic_create_nse;
+gprs_ns2_find_vc_by_sockaddr;
+gprs_ns2_free;
+gprs_ns2_free_bind;
+gprs_ns2_free_nse;
+gprs_ns2_free_nsvc;
+gprs_ns2_frgre_bind;
+gprs_ns2_instantiate;
+gprs_ns2_ip_bind;
+gprs_ns2_ip_bind_set_dscp;
+gprs_ns2_ip_bind_sockaddr;
+gprs_ns2_ip_connect;
+gprs_ns2_ip_connect2;
+gprs_ns2_ip_connect_inactive;
+gprs_ns2_ip_connect_sns;
+gprs_ns2_ip_vc_sockaddr;
+gprs_ns2_is_frgre_bind;
+gprs_ns2_is_ip_bind;
+gprs_ns2_ll_str;
+gprs_ns2_ll_str_buf;
+gprs_ns2_ll_str_c;
+gprs_ns2_nse_by_nsei;
+gprs_ns2_nsvc_by_nsvci;
+gprs_ns2_nsvc_by_sockaddr;
+gprs_ns2_recv_prim;
+gprs_ns2_reset_persistent_nsvcs;
+gprs_ns2_start_alive_all_nsvcs;
+gprs_ns2_vty_create;
+gprs_ns2_vty_force_vc_mode;
+gprs_ns2_vty_init;
+
gprs_nsvc_create2;
gprs_nsvc_delete;
gprs_nsvc_reset;
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am
index 0e879e98..c5232abb 100644
--- a/src/gsm/Makefile.am
+++ b/src/gsm/Makefile.am
@@ -1,7 +1,7 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=14:0:1
+LIBVERSION=15:0:0
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN}
diff --git a/src/gsm/cbsp.c b/src/gsm/cbsp.c
index ccc2df53..b89358d4 100644
--- a/src/gsm/cbsp.c
+++ b/src/gsm/cbsp.c
@@ -1,3 +1,4 @@
+/* Cell Broadcast Service Protocol (CBSP, 3GPP TS 48.049): Message encoding, decoding and reception */
/*
* Copyright (C) 2019 Harald Welte <laforge@gnumonks.org>
*
diff --git a/src/gsm/gsm0808.c b/src/gsm/gsm0808.c
index a1d567df..53220617 100644
--- a/src/gsm/gsm0808.c
+++ b/src/gsm/gsm0808.c
@@ -283,8 +283,9 @@ struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id)
msgb_l3len(layer3), layer3->l3h);
}
- /* and the optional BSS message */
- msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, alg_id);
+ /* Optional Chosen Encryption Algorithm IE */
+ if (alg_id > 0)
+ msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, alg_id);
/* pre-pend the header */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
@@ -444,8 +445,9 @@ struct msgb *gsm0808_create_classmark_update(const uint8_t *cm2, uint8_t cm2_len
/*! Create BSSMAP SAPI N Reject message
* \param[in] link_id Link Identifier
+ * \param[in] cause BSSAP Cause value (see 3GPP TS 48.008, section 3.2.2.5)
* \returns callee-allocated msgb with BSSMAP SAPI N Reject message */
-struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
+struct msgb *gsm0808_create_sapi_reject_cause(uint8_t link_id, uint16_t cause)
{
struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
"bssmap: sapi 'n' reject");
@@ -454,13 +456,23 @@ struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
msgb_v_put(msg, BSS_MAP_MSG_SAPI_N_REJECT);
msgb_tv_put(msg, GSM0808_IE_DLCI, link_id);
- gsm0808_enc_cause(msg, GSM0808_CAUSE_BSS_NOT_EQUIPPED);
+ gsm0808_enc_cause(msg, cause);
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
}
+/*! Create BSSMAP SAPI N Reject message (with hard-coded cause "BSS not equipped").
+ * DEPRECATED: use gsm0808_create_sapi_reject_cause() instead.
+ * \param[in] link_id Link Identifier
+ * \param[in] cause BSSAP Cause value (see 3GPP TS 48.008, section 3.2.2.5)
+ * \returns callee-allocated msgb with BSSMAP SAPI N Reject message */
+struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
+{
+ return gsm0808_create_sapi_reject_cause(link_id, GSM0808_CAUSE_BSS_NOT_EQUIPPED);
+}
+
/*! Create BSSMAP Assignment Request message, 3GPP TS 48.008 ยง3.2.1.1.
* This is identical to gsm0808_create_ass(), but adds KC and LCLS IEs.
* \param[in] ct Channel Type
@@ -592,7 +604,8 @@ struct msgb *gsm0808_create_ass_compl2(uint8_t rr_cause, uint8_t chosen_channel,
msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, chosen_channel);
/* write chosen encryption algorithm 3.2.2.44 */
- msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, encr_alg_id);
+ if (encr_alg_id > 0)
+ msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, encr_alg_id);
/* write circuit pool 3.2.2.45 */
/* write speech version chosen: 3.2.2.51 when BTS picked it */
@@ -953,7 +966,7 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque
}
/* Chosen Encryption Algorithm (Serving) 3.2.2.44 */
- if (params->chosen_encryption_algorithm_serving)
+ if (params->chosen_encryption_algorithm_serving > 0)
msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, params->chosen_encryption_algorithm_serving);
/* Old BSS to New BSS Information 3.2.2.58 */
@@ -1016,7 +1029,7 @@ struct msgb *gsm0808_create_handover_request_ack2(const struct gsm0808_handover_
if (params->chosen_channel_present)
msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, params->chosen_channel);
- if (params->chosen_encr_alg)
+ if (params->chosen_encr_alg > 0)
msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, params->chosen_encr_alg);
if (params->chosen_speech_version != 0)
@@ -1146,7 +1159,7 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp
gsm0808_enc_speech_codec_list(msg, &params->codec_list_bss_supported);
/* Chosen Encryption Algorithm 3.2.2.44 */
- if (params->chosen_encr_alg_present)
+ if (params->chosen_encr_alg_present && params->chosen_encr_alg > 0)
msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, params->chosen_encr_alg);
/* LCLS-BSS-Status 3.2.2.119 */
@@ -1214,7 +1227,7 @@ struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_per
msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, params->chosen_channel);
/* Chosen Encryption Algorithm 3.2.2.44 */
- if (params->chosen_encr_alg_present)
+ if (params->chosen_encr_alg_present && params->chosen_encr_alg > 0)
msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, params->chosen_encr_alg);
/* Speech Version (chosen) 3.2.2.51 */
diff --git a/src/gsm/ipa.c b/src/gsm/ipa.c
index 1563d0a3..7a26ba45 100644
--- a/src/gsm/ipa.c
+++ b/src/gsm/ipa.c
@@ -121,22 +121,25 @@ int ipa_ccm_idtag_parse_off(struct tlv_parsed *dec, unsigned char *buf, int len,
memset(dec, 0, sizeof(*dec));
+ LOGP(DLMI, LOGL_DEBUG, "Rx IPA CCM ID_GET: ");
while (len >= 2) {
len -= 2;
t_len = *cur++;
t_tag = *cur++;
if (t_len < len_offset) {
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
LOGP(DLMI, LOGL_ERROR, "minimal offset not included: %d < %d\n", t_len, len_offset);
return -EINVAL;
}
if (t_len > len + 1) {
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1);
return -EINVAL;
}
- DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
+ LOGPC(DLMI, LOGL_DEBUG, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
dec->lv[t_tag].len = t_len - len_offset;
dec->lv[t_tag].val = cur;
@@ -144,6 +147,7 @@ int ipa_ccm_idtag_parse_off(struct tlv_parsed *dec, unsigned char *buf, int len,
cur += t_len - len_offset;
len -= t_len - len_offset;
}
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
return 0;
}
@@ -164,17 +168,19 @@ int ipa_ccm_id_get_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned in
memset(dec, 0, sizeof(*dec));
+ LOGP(DLMI, LOGL_DEBUG, "Rx IPA CCM ID_GET: ");
while (len >= 2) {
len -= 2;
t_len = *cur++;
t_tag = *cur++;
if (t_len > len + 1) {
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1);
return -EINVAL;
}
- DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
+ LOGPC(DLMI, LOGL_DEBUG, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
dec->lv[t_tag].len = t_len-1;
dec->lv[t_tag].val = cur;
@@ -182,6 +188,7 @@ int ipa_ccm_id_get_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned in
cur += t_len-1;
len -= t_len-1;
}
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
return 0;
}
@@ -202,6 +209,7 @@ int ipa_ccm_id_resp_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned i
memset(dec, 0, sizeof(*dec));
+ LOGP(DLMI, LOGL_DEBUG, "Rx IPA CCM ID_RESP: ");
while (len >= 3) {
len -= 3;
t_len = osmo_load16be(cur);
@@ -209,6 +217,7 @@ int ipa_ccm_id_resp_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned i
t_tag = *cur++;
if (t_len > len + 1) {
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1);
return -EINVAL;
}
@@ -221,6 +230,7 @@ int ipa_ccm_id_resp_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned i
cur += t_len-1;
len -= t_len-1;
}
+ LOGPC(DLMI, LOGL_DEBUG, "\n");
return 0;
}
diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c
index e5fbaf0d..bfd6d263 100644
--- a/src/gsm/lapdm.c
+++ b/src/gsm/lapdm.c
@@ -336,8 +336,8 @@ static void lapdm_pad_msgb(struct msgb *msg, uint8_t n201)
return;
}
- data = msgb_put(msg, pad_len);
- memset(data, 0x2B, pad_len);
+ data = msgb_put(msg, pad_len); /* TODO: random padding */
+ memset(data, GSM_MACBLOCK_PADDING, pad_len);
}
/* input function that L2 calls when sending messages up to L3 */
@@ -380,7 +380,21 @@ static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg,
return le->l1_prim_cb(&pp.oph, le->l1_ctx);
}
-static struct msgb *tx_dequeue_msgb(struct lapdm_entity *le)
+/* Dequeue a Downlink message for DCCH (dedicated channel) */
+static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le)
+{
+ struct msgb *msg;
+
+ /* SAPI=0 always has higher priority than SAPI=3 */
+ msg = msgb_dequeue(&le->datalink[DL_SAPI0].dl.tx_queue);
+ if (msg == NULL) /* no SAPI=0 messages, dequeue SAPI=3 (if any) */
+ msg = msgb_dequeue(&le->datalink[DL_SAPI3].dl.tx_queue);
+
+ return msg;
+}
+
+/* Dequeue a Downlink message for ACCH (associated channel) */
+static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le)
{
struct lapdm_datalink *dl;
int last = le->last_tx_dequeue;
@@ -411,7 +425,12 @@ int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp
struct msgb *msg;
uint8_t pad;
- msg = tx_dequeue_msgb(le);
+ /* Dequeue depending on channel type: DCCH or ACCH.
+ * See 3GPP TS 44.005, section 4.2.2 "Priority". */
+ if (le == &le->lapdm_ch->lapdm_dcch)
+ msg = tx_dequeue_dcch_msgb(le);
+ else
+ msg = tx_dequeue_acch_msgb(le);
if (!msg)
return -ENODEV;
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 713ffe3e..4ece1079 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -190,6 +190,7 @@ gsm0808_create_lcls_conn_ctrl_ack;
gsm0808_create_lcls_notification;
gsm0808_create_reset;
gsm0808_create_reset_ack;
+gsm0808_create_sapi_reject_cause;
gsm0808_create_sapi_reject;
gsm0808_create_handover_required;
gsm0808_create_handover_required_reject;
diff --git a/src/logging.c b/src/logging.c
index c14e6961..212b0b99 100644
--- a/src/logging.c
+++ b/src/logging.c
@@ -39,6 +39,11 @@
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
#include <time.h>
#include <sys/time.h>
#include <errno.h>
@@ -225,6 +230,11 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
.description = "Remote SIM protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
+ [INT2IDX(DLNS)] = {
+ .name = "DLNS",
+ .description = "GPRS NS layer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
};
void assert_loginfo(const char *src)
@@ -500,7 +510,7 @@ static void _output(struct log_target *target, unsigned int subsys,
}
}
- if (target->use_color) {
+ if (target->use_color && c_subsys) {
ret = snprintf(buf + offset, rem, OSMO_LOGCOLOR_END);
if (ret < 0)
goto err;
@@ -910,8 +920,10 @@ struct log_target *log_target_create_file(const char *fname)
target->type = LOG_TGT_TYPE_FILE;
target->tgt_file.out = fopen(fname, "a");
- if (!target->tgt_file.out)
+ if (!target->tgt_file.out) {
+ log_target_destroy(target);
return NULL;
+ }
target->output = _file_output;
@@ -959,16 +971,21 @@ void log_target_destroy(struct log_target *target)
log_del_target(target);
#if (!EMBEDDED)
- if (target->output == &_file_output) {
-/* since C89/C99 says stderr is a macro, we can safely do this! */
-#ifdef stderr
- /* don't close stderr */
- if (target->tgt_file.out != stderr)
-#endif
- {
- fclose(target->tgt_file.out);
- target->tgt_file.out = NULL;
- }
+ switch (target->type) {
+ case LOG_TGT_TYPE_FILE:
+ if (target->tgt_file.out == NULL)
+ break;
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = NULL;
+ break;
+#ifdef HAVE_SYSLOG_H
+ case LOG_TGT_TYPE_SYSLOG:
+ closelog();
+ break;
+#endif /* HAVE_SYSLOG_H */
+ default:
+ /* make GCC happy */
+ break;
}
#endif
diff --git a/src/macaddr.c b/src/macaddr.c
index de9d07af..56fdf86e 100644
--- a/src/macaddr.c
+++ b/src/macaddr.c
@@ -34,6 +34,7 @@
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
+#include <errno.h>
/*! Parse a MAC address from human-readable notation
* This function parses an ethernet MAC address in the commonly-used
@@ -77,11 +78,11 @@ int osmo_macaddr_parse(uint8_t *out, const char *in)
*/
int osmo_get_macaddr(uint8_t *mac_out, const char *dev_name)
{
- int rc = -1;
struct ifaddrs *ifa, *ifaddr;
+ int rc = -ENODEV;
if (getifaddrs(&ifaddr) != 0)
- return -1;
+ return -errno;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
struct sockaddr_dl *sdl;
@@ -102,7 +103,7 @@ int osmo_get_macaddr(uint8_t *mac_out, const char *dev_name)
}
freeifaddrs(ifaddr);
- return 0;
+ return rc;
}
#else
diff --git a/src/msgb.c b/src/msgb.c
index 4edbdf34..fbb157ae 100644
--- a/src/msgb.c
+++ b/src/msgb.c
@@ -64,7 +64,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
-/*! Allocate a new message buffer from given talloc cotext
+/*! Allocate a new message buffer from given talloc context
* \param[in] ctx talloc context from which to allocate
* \param[in] size Length in octets, including headroom
* \param[in] name Human-readable name to be associated with msgb
diff --git a/src/sim/Makefile.am b/src/sim/Makefile.am
index 0539dd98..4e2348bd 100644
--- a/src/sim/Makefile.am
+++ b/src/sim/Makefile.am
@@ -1,7 +1,7 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=1:2:1
+LIBVERSION=2:0:0
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
AM_CFLAGS = -fPIC -Wall $(TALLOC_CFLAGS)
diff --git a/src/sockaddr_str.c b/src/sockaddr_str.c
index c38a05cb..f5508a03 100644
--- a/src/sockaddr_str.c
+++ b/src/sockaddr_str.c
@@ -188,25 +188,24 @@ int osmo_ip_str_type(const char *ip)
return AF_UNSPEC;
}
-/*! Safely copy the given ip string to sockaddr_str, classify to AF_INET or AF_INET6, and set the port.
+/*! Safely copy the given ip string to sockaddr_str, classify to AF_INET or AF_INET6.
* Data will be written to sockaddr_str even if an error is returned.
* \param[out] sockaddr_str The instance to copy to.
* \param[in] ip Valid IP address string.
- * \param[in] port Port number.
* \return 0 on success, negative if copying the address string failed (e.g. too long), if the address family could
* not be detected (i.e. if osmo_ip_str_type() returned AF_UNSPEC), or if sockaddr_str is NULL.
*/
-int osmo_sockaddr_str_from_str(struct osmo_sockaddr_str *sockaddr_str, const char *ip, uint16_t port)
+int osmo_sockaddr_str_from_str2(struct osmo_sockaddr_str *sockaddr_str, const char *ip)
{
int rc;
if (!sockaddr_str)
return -ENOSPC;
if (!ip)
ip = "";
- *sockaddr_str = (struct osmo_sockaddr_str){
- .af = osmo_ip_str_type(ip),
- .port = port,
- };
+ sockaddr_str->af = osmo_ip_str_type(ip);
+ /* to be compatible with previous behaviour, zero the full IP field.
+ * Allow the usage of memcmp(&sockaddr_str, ...) */
+ memset(sockaddr_str->ip, 0x0, sizeof(sockaddr_str->ip));
rc = osmo_strlcpy(sockaddr_str->ip, ip, sizeof(sockaddr_str->ip));
if (rc <= 0)
return -EIO;
@@ -217,6 +216,26 @@ int osmo_sockaddr_str_from_str(struct osmo_sockaddr_str *sockaddr_str, const cha
return 0;
}
+/*! Safely copy the given ip string to sockaddr_str, classify to AF_INET or AF_INET6, and set the port.
+ * Data will be written to sockaddr_str even if an error is returned.
+ * \param[out] sockaddr_str The instance to copy to.
+ * \param[in] ip Valid IP address string.
+ * \param[in] port Port number.
+ * \return 0 on success, negative if copying the address string failed (e.g. too long), if the address family could
+ * not be detected (i.e. if osmo_ip_str_type() returned AF_UNSPEC), or if sockaddr_str is NULL.
+ */
+int osmo_sockaddr_str_from_str(struct osmo_sockaddr_str *sockaddr_str, const char *ip, uint16_t port)
+{
+ int rc;
+ if (!sockaddr_str)
+ return -ENOSPC;
+
+ rc = osmo_sockaddr_str_from_str2(sockaddr_str, ip);
+ sockaddr_str->port = port;
+
+ return rc;
+}
+
/*! Convert IPv4 address to osmo_sockaddr_str, and set port.
* \param[out] sockaddr_str The instance to copy to.
* \param[in] addr IPv4 address data.
diff --git a/src/socket.c b/src/socket.c
index 9c608218..f078242c 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -34,6 +34,7 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
@@ -85,8 +86,8 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t
rc = getaddrinfo(host, portbuf, &hints, &result);
if (rc != 0) {
- LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo returned NULL: %s:%u: %s\n",
- host, port, strerror(errno));
+ LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo(%s, %u) failed: %s\n",
+ host, port, gai_strerror(rc));
return NULL;
}
@@ -153,6 +154,28 @@ static int socket_helper(const struct addrinfo *rp, unsigned int flags)
return sfd;
}
+static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, uint8_t proto, unsigned int flags)
+{
+ int sfd, on = 1;
+
+ sfd = socket(addr->u.sa.sa_family, type, proto);
+ if (sfd == -1) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "unable to create socket: %s\n", strerror(errno));
+ return sfd;
+ }
+ if (flags & OSMO_SOCK_F_NONBLOCK) {
+ if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "cannot set this socket unblocking: %s\n",
+ strerror(errno));
+ close(sfd);
+ sfd = -EINVAL;
+ }
+ }
+ return sfd;
+}
+
#ifdef HAVE_LIBSCTP
/* Fill buf with a string representation of the address set, in the form:
* buf_len == 0: "()"
@@ -316,19 +339,27 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
}
}
- /* priotize ipv6 as per RFC */
- if (local_ipv6 && remote_ipv6)
- family = AF_INET6;
- else if (local_ipv4 && remote_ipv4)
- family = AF_INET;
- else {
- if (local)
- freeaddrinfo(local);
- if (remote)
- freeaddrinfo(remote);
- LOGP(DLGLOBAL, LOGL_ERROR, "Unable to find a common protocol (IPv4 or IPv6) for local host: %s and remote host: %s.\n",
- local_host, remote_host);
- return -ENODEV;
+ if ((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) {
+ /* prioritize ipv6 as per RFC */
+ if (local_ipv6 && remote_ipv6)
+ family = AF_INET6;
+ else if (local_ipv4 && remote_ipv4)
+ family = AF_INET;
+ else {
+ if (local)
+ freeaddrinfo(local);
+ if (remote)
+ freeaddrinfo(remote);
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "Unable to find a common protocol (IPv4 or IPv6) "
+ "for local host: %s and remote host: %s.\n",
+ local_host, remote_host);
+ return -ENODEV;
+ }
+ } else if ((flags & OSMO_SOCK_F_BIND)) {
+ family = local_ipv6 ? AF_INET6 : AF_INET;
+ } else if ((flags & OSMO_SOCK_F_CONNECT)) {
+ family = remote_ipv6 ? AF_INET6 : AF_INET;
}
}
@@ -426,26 +457,210 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
return sfd;
}
+#define _SOCKADDR_TO_STR(dest, sockaddr) do { \
+ if (osmo_sockaddr_str_from_sockaddr(&dest, &sockaddr->u.sas)) \
+ osmo_strlcpy(dest.ip, "Invalid IP", 11); \
+ } while (0)
+
+/*! Initialize a socket (including bind and/or connect)
+ * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ * \param[in] local local address
+ * \param[in] remote remote address
+ * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ * \returns socket file descriptor on success; negative on error
+ *
+ * This function creates a new socket of the
+ * \a type and \a proto and optionally binds it to the \a local
+ * as well as optionally connects it to the \a remote
+ * depending on the value * of \a flags parameter.
+ *
+ * As opposed to \ref osmo_sock_init(), this function allows to combine
+ * the \ref OSMO_SOCK_F_BIND and \ref OSMO_SOCK_F_CONNECT flags. This
+ * is useful if you want to connect to a remote host/port, but still
+ * want to bind that socket to either a specific local alias IP and/or a
+ * specific local source port.
+ *
+ * You must specify either \ref OSMO_SOCK_F_BIND, or \ref
+ * OSMO_SOCK_F_CONNECT, or both.
+ *
+ * If \ref OSMO_SOCK_F_NONBLOCK is specified, the socket will be set to
+ * non-blocking mode.
+ */
+int osmo_sock_init_osa(uint16_t type, uint8_t proto,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ unsigned int flags)
+{
+ int sfd = -1, rc, on = 1;
+ struct osmo_sockaddr_str sastr = {};
+
+ if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
+ "BIND or CONNECT flags\n");
+ return -EINVAL;
+ }
+
+ if ((flags & OSMO_SOCK_F_BIND) && !local) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "invalid argument. Cannot BIND when local is NULL\n");
+ return -EINVAL;
+ }
+
+ if ((flags & OSMO_SOCK_F_CONNECT) && !remote) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "invalid argument. Cannot CONNECT when remote is NULL\n");
+ return -EINVAL;
+ }
+
+ if ((flags & OSMO_SOCK_F_BIND) &&
+ (flags & OSMO_SOCK_F_CONNECT) &&
+ local->u.sa.sa_family != remote->u.sa.sa_family) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "invalid: the family for "
+ "local and remote endpoint must be same.\n");
+ return -EINVAL;
+ }
+
+ /* figure out local side of socket */
+ if (flags & OSMO_SOCK_F_BIND) {
+ sfd = socket_helper_osa(local, type, proto, flags);
+ if (sfd < 0) {
+ _SOCKADDR_TO_STR(sastr, local);
+ LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: %s:%u\n",
+ sastr.ip, sastr.port);
+ return -ENODEV;
+ }
+
+ if (proto != IPPROTO_UDP || (flags & OSMO_SOCK_F_UDP_REUSEADDR)) {
+ rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
+ &on, sizeof(on));
+ if (rc < 0) {
+ _SOCKADDR_TO_STR(sastr, local);
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "cannot setsockopt socket:"
+ " %s:%u: %s\n",
+ sastr.ip, sastr.port,
+ strerror(errno));
+ close(sfd);
+ return rc;
+ }
+ }
+
+ if (bind(sfd, &local->u.sa, sizeof(struct osmo_sockaddr)) == -1) {
+ _SOCKADDR_TO_STR(sastr, local);
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n",
+ sastr.ip, sastr.port, strerror(errno));
+ close(sfd);
+ return -1;
+ }
+ }
+
+ /* Reached this point, if OSMO_SOCK_F_BIND then sfd is valid (>=0) or it
+ was already closed and func returned. If OSMO_SOCK_F_BIND is not
+ set, then sfd = -1 */
+
+ /* figure out remote side of socket */
+ if (flags & OSMO_SOCK_F_CONNECT) {
+ if (sfd < 0) {
+ sfd = socket_helper_osa(remote, type, proto, flags);
+ if (sfd < 0) {
+ return sfd;
+ }
+ }
+
+ rc = connect(sfd, &remote->u.sa, sizeof(struct osmo_sockaddr));
+ if (rc != 0 && errno != EINPROGRESS) {
+ _SOCKADDR_TO_STR(sastr, remote);
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
+ sastr.ip, sastr.port, strerror(errno));
+ close(sfd);
+ return rc;
+ }
+ }
+
+ rc = osmo_sock_init_tail(sfd, type, flags);
+ if (rc < 0) {
+ close(sfd);
+ sfd = -1;
+ }
+
+ return sfd;
+}
+
#ifdef HAVE_LIBSCTP
+/* Check whether there's an IPv6 Addr as first option of any addrinfo item in the addrinfo set */
+static void addrinfo_has_v4v6addr(const struct addrinfo **result, size_t result_count, bool *has_v4, bool *has_v6)
+{
+ size_t host_idx;
+ *has_v4 = false;
+ *has_v6 = false;
+
+ for (host_idx = 0; host_idx < result_count; host_idx++) {
+ if (result[host_idx]->ai_family == AF_INET)
+ *has_v4 = true;
+ else if (result[host_idx]->ai_family == AF_INET6)
+ *has_v6 = true;
+ }
+}
+
+/* Check whether there's an IPv6 with IN6ADDR_ANY_INIT ("::") */
+static bool addrinfo_has_in6addr_any(const struct addrinfo **result, size_t result_count)
+{
+ size_t host_idx;
+ struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+
+ for (host_idx = 0; host_idx < result_count; host_idx++) {
+ if (result[host_idx]->ai_family != AF_INET6)
+ continue;
+ if (memcmp(&((struct sockaddr_in6 *)result[host_idx]->ai_addr)->sin6_addr,
+ &in6addr_any, sizeof(in6addr_any)) == 0)
+ return true;
+ }
+ return false;
+}
+
+static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto, unsigned int flags)
+{
+ int sfd, on = 1;
+
+ sfd = socket(family, type, proto);
+ if (sfd == -1) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "Unable to create socket: %s\n", strerror(errno));
+ return sfd;
+ }
+ if (flags & OSMO_SOCK_F_NONBLOCK) {
+ if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "Cannot set this socket unblocking: %s\n",
+ strerror(errno));
+ close(sfd);
+ sfd = -EINVAL;
+ }
+ }
+ return sfd;
+}
/* Build array of addresses taking first addrinfo result of the requested family
- * for each host in hosts. addrs4 or addrs6 are filled based on family type. */
+ * for each host in addrs_buf. */
static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
const char **hosts, int host_cont,
- struct sockaddr_in *addrs4, struct sockaddr_in6 *addrs6) {
- size_t host_idx;
+ uint8_t *addrs_buf, size_t addrs_buf_len) {
+ size_t host_idx, offset = 0;
const struct addrinfo *rp;
- OSMO_ASSERT(family == AF_INET || family == AF_INET6);
for (host_idx = 0; host_idx < host_cont; host_idx++) {
+ /* Addresses are ordered based on RFC 3484, see man getaddrinfo */
for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
- if (rp->ai_family != family)
+ if (family != AF_UNSPEC && rp->ai_family != family)
continue;
- if (family == AF_INET)
- memcpy(&addrs4[host_idx], rp->ai_addr, sizeof(addrs4[host_idx]));
- else
- memcpy(&addrs6[host_idx], rp->ai_addr, sizeof(addrs6[host_idx]));
+ if (offset + rp->ai_addrlen > addrs_buf_len) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: %zu\n",
+ addrs_buf_len);
+ return -ENOSPC;
+ }
+ memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen);
+ offset += rp->ai_addrlen;
break;
}
if (!rp) { /* No addr could be bound for this host! */
@@ -483,28 +698,23 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
unsigned int flags)
{
- struct addrinfo *result[OSMO_SOCK_MAX_ADDRS];
+ struct addrinfo *res_loc[OSMO_SOCK_MAX_ADDRS], *res_rem[OSMO_SOCK_MAX_ADDRS];
int sfd = -1, rc, on = 1;
int i;
- struct sockaddr_in addrs4[OSMO_SOCK_MAX_ADDRS];
- struct sockaddr_in6 addrs6[OSMO_SOCK_MAX_ADDRS];
- struct sockaddr *addrs;
+ bool loc_has_v4addr, rem_has_v4addr;
+ bool loc_has_v6addr, rem_has_v6addr;
+ struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
char strbuf[512];
+ /* updated later in case of AF_UNSPEC */
+ loc_has_v4addr = rem_has_v4addr = (family == AF_INET);
+ loc_has_v6addr = rem_has_v6addr = (family == AF_INET6);
+
/* TODO: So far this function is only aimed for SCTP, but could be
reused in the future for other protocols with multi-addr support */
if (proto != IPPROTO_SCTP)
return -ENOTSUP;
- /* TODO: Let's not support AF_UNSPEC for now. sctp_bindx() actually
- supports binding both types of addresses on a AF_INET6 soscket, but
- that would mean we could get both AF_INET and AF_INET6 addresses for
- each host, and makes complexity of this function increase a lot since
- we'd need to find out which subsets to use, use v4v6 mapped socket,
- etc. */
- if (family == AF_UNSPEC)
- return -ENOTSUP;
-
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
"BIND or CONNECT flags\n");
@@ -519,23 +729,45 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
/* figure out local side of socket */
if (flags & OSMO_SOCK_F_BIND) {
- rc = addrinfo_helper_multi(result, family, type, proto, local_hosts,
- local_hosts_cnt, local_port, true);
+ rc = addrinfo_helper_multi(res_loc, family, type, proto, local_hosts,
+ local_hosts_cnt, local_port, true);
if (rc < 0)
return -EINVAL;
-
- /* Since addrinfo_helper sets ai_family, socktype and
- ai_protocol in hints, we know all results will use same
- values, so simply pick the first one and pass it to create
- the socket:
- */
- sfd = socket_helper(result[0], flags);
- if (sfd < 0) {
- for (i = 0; i < local_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- return sfd;
+ /* Figure out if there's any IPV4 or IPv6 addr in the set */
+ if (family == AF_UNSPEC)
+ addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, local_hosts_cnt,
+ &loc_has_v4addr, &loc_has_v6addr);
+ }
+ /* figure out remote side of socket */
+ if (flags & OSMO_SOCK_F_CONNECT) {
+ rc = addrinfo_helper_multi(res_rem, family, type, proto, remote_hosts,
+ remote_hosts_cnt, remote_port, false);
+ if (rc < 0) {
+ rc = -EINVAL;
+ goto ret_freeaddrinfo_loc;
}
+ /* Figure out if there's any IPv4 or IPv6 addr in the set */
+ if (family == AF_UNSPEC)
+ addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, remote_hosts_cnt,
+ &rem_has_v4addr, &rem_has_v6addr);
+ }
+
+ if (((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) &&
+ !addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt) &&
+ (loc_has_v4addr != rem_has_v4addr || loc_has_v6addr != rem_has_v6addr)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote addresses\n");
+ rc = -EINVAL;
+ goto ret_freeaddrinfo;
+ }
+
+ sfd = socket_helper_multiaddr(loc_has_v6addr ? AF_INET6 : AF_INET,
+ type, proto, flags);
+ if (sfd < 0) {
+ rc = sfd;
+ goto ret_freeaddrinfo;
+ }
+ if (flags & OSMO_SOCK_F_BIND) {
/* Since so far we only allow IPPROTO_SCTP in this function,
no need to check below for "proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR" */
rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
@@ -547,97 +779,51 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
" %s:%u: %s\n",
strbuf, local_port,
strerror(errno));
- for (i = 0; i < local_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- close(sfd);
- return rc;
+ goto ret_close;
}
- /* Build array of addresses taking first of same family for each host.
+ /* Build array of addresses taking first entry for each host.
TODO: Ideally we should use backtracking storing last used
indexes and trying next combination if connect() fails .*/
- rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)result,
- local_hosts, local_hosts_cnt, addrs4, addrs6);
+ /* We could alternatively use v4v6 mapped addresses and call sctp_bindx once with an array od sockaddr_in6 */
+ rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)res_loc,
+ local_hosts, local_hosts_cnt,
+ (uint8_t*)addrs_buf, sizeof(addrs_buf));
if (rc < 0) {
- for (i = 0; i < local_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- close(sfd);
- return -ENODEV;
+ rc = -ENODEV;
+ goto ret_close;
}
- if (family == AF_INET)
- addrs = (struct sockaddr *)addrs4;
- else
- addrs = (struct sockaddr *)addrs6;
- if (sctp_bindx(sfd, addrs, local_hosts_cnt, SCTP_BINDX_ADD_ADDR) == -1) {
+ rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, local_hosts_cnt, SCTP_BINDX_ADD_ADDR);
+ if (rc == -1) {
multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
LOGP(DLGLOBAL, LOGL_NOTICE, "unable to bind socket: %s:%u: %s\n",
strbuf, local_port, strerror(errno));
- for (i = 0; i < local_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- close(sfd);
- return -ENODEV;
+ rc = -ENODEV;
+ goto ret_close;
}
- for (i = 0; i < local_hosts_cnt; i++)
- freeaddrinfo(result[i]);
}
- /* Reached this point, if OSMO_SOCK_F_BIND then sfd is valid (>=0) or it
- was already closed and func returned. If OSMO_SOCK_F_BIND is not
- set, then sfd = -1 */
-
- /* figure out remote side of socket */
if (flags & OSMO_SOCK_F_CONNECT) {
- rc = addrinfo_helper_multi(result, family, type, proto, remote_hosts,
- remote_hosts_cnt, remote_port, false);
- if (rc < 0) {
- if (sfd >= 0)
- close(sfd);
- return -EINVAL;
- }
-
- if (sfd < 0) {
- /* Since addrinfo_helper sets ai_family, socktype and
- ai_protocol in hints, we know all results will use same
- values, so simply pick the first one and pass it to create
- the socket:
- */
- sfd = socket_helper(result[0], flags);
- if (sfd < 0) {
- for (i = 0; i < remote_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- return sfd;
- }
- }
-
/* Build array of addresses taking first of same family for each host.
TODO: Ideally we should use backtracking storing last used
indexes and trying next combination if connect() fails .*/
- rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)result,
- remote_hosts, remote_hosts_cnt, addrs4, addrs6);
+ rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)res_rem,
+ remote_hosts, remote_hosts_cnt,
+ (uint8_t*)addrs_buf, sizeof(addrs_buf));
if (rc < 0) {
- for (i = 0; i < remote_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- close(sfd);
- return -ENODEV;
+ rc = -ENODEV;
+ goto ret_close;
}
- if (family == AF_INET)
- addrs = (struct sockaddr *)addrs4;
- else
- addrs = (struct sockaddr *)addrs6;
- rc = sctp_connectx(sfd, addrs, remote_hosts_cnt, NULL);
+ rc = sctp_connectx(sfd, (struct sockaddr *)addrs_buf, remote_hosts_cnt, NULL);
if (rc != 0 && errno != EINPROGRESS) {
multiaddr_snprintf(strbuf, sizeof(strbuf), remote_hosts, remote_hosts_cnt);
LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
strbuf, remote_port, strerror(errno));
- for (i = 0; i < remote_hosts_cnt; i++)
- freeaddrinfo(result[i]);
- close(sfd);
- return -ENODEV;
+ rc = -ENODEV;
+ goto ret_close;
}
- for (i = 0; i < remote_hosts_cnt; i++)
- freeaddrinfo(result[i]);
}
rc = osmo_sock_init_tail(sfd, type, flags);
@@ -646,7 +832,23 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
sfd = -1;
}
- return sfd;
+ rc = sfd;
+ goto ret_freeaddrinfo;
+
+ret_close:
+ if (sfd >= 0)
+ close(sfd);
+ret_freeaddrinfo:
+ if (flags & OSMO_SOCK_F_CONNECT) {
+ for (i = 0; i < remote_hosts_cnt; i++)
+ freeaddrinfo(res_rem[i]);
+ }
+ret_freeaddrinfo_loc:
+ if (flags & OSMO_SOCK_F_BIND) {
+ for (i = 0; i < local_hosts_cnt; i++)
+ freeaddrinfo(res_loc[i]);
+ }
+ return rc;
}
#endif /* HAVE_LIBSCTP */
@@ -677,11 +879,8 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
}
result = addrinfo_helper(family, type, proto, host, port, flags & OSMO_SOCK_F_BIND);
- if (!result) {
- LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo returned NULL: %s:%u: %s\n",
- host, port, strerror(errno));
+ if (!result)
return -EINVAL;
- }
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket_helper(rp, flags);
@@ -813,6 +1012,13 @@ int osmo_sock_init2_ofd(struct osmo_fd *ofd, int family, int type, int proto,
local_port, remote_host, remote_port, flags));
}
+int osmo_sock_init_osa_ofd(struct osmo_fd *ofd, int type, int proto,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote, unsigned int flags)
+{
+ return osmo_fd_init_ofd(ofd, osmo_sock_init_osa(type, proto, local, remote, flags));
+}
+
/*! Initialize a socket and fill \ref sockaddr
* \param[out] ss socket address (will be filled in)
* \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
@@ -944,9 +1150,54 @@ size_t osmo_sockaddr_in_to_str_and_uint(char *addr, unsigned int addr_len, uint1
unsigned int osmo_sockaddr_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
const struct sockaddr *sa)
{
- const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
- return osmo_sockaddr_in_to_str_and_uint(addr, addr_len, port, sin);
+ const struct sockaddr_in6 *sin6;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ return osmo_sockaddr_in_to_str_and_uint(addr, addr_len, port,
+ (const struct sockaddr_in *)sa);
+ case AF_INET6:
+ sin6 = (const struct sockaddr_in6 *)sa;
+ if (port)
+ *port = ntohs(sin6->sin6_port);
+ if (addr && inet_ntop(sa->sa_family, &sin6->sin6_addr, addr, addr_len))
+ return strlen(addr);
+ break;
+ }
+ return 0;
+}
+
+/*! inet_ntop() wrapper for a struct sockaddr.
+ * \param[in] sa source sockaddr to get the address from.
+ * \param[out] dst string buffer of at least INET6_ADDRSTRLEN size.
+ * \returns returns a non-null pointer to dst. NULL is returned if there was an
+ * error, with errno set to indicate the error.
+ */
+const char *osmo_sockaddr_ntop(const struct sockaddr *sa, char *dst)
+{
+ const struct osmo_sockaddr *osa = (const struct osmo_sockaddr *)sa;
+ return inet_ntop(osa->u.sa.sa_family,
+ osa->u.sa.sa_family == AF_INET6 ?
+ (const void *)&osa->u.sin6.sin6_addr :
+ (const void *)&osa->u.sin.sin_addr,
+ dst, INET6_ADDRSTRLEN);
+}
+
+/*! Get sockaddr port content (in host byte order)
+ * \param[in] sa source sockaddr to get the port from.
+ * \returns returns the sockaddr port in host byte order
+ */
+uint16_t osmo_sockaddr_port(const struct sockaddr *sa)
+{
+ const struct osmo_sockaddr *osa = (const struct osmo_sockaddr *)sa;
+ switch (osa->u.sa.sa_family) {
+ case AF_INET6:
+ return ntohs(osa->u.sin6.sin6_port);
+ case AF_INET:
+ return ntohs(osa->u.sin.sin_port);
+ }
+ return 0;
}
/*! Initialize a unix domain socket (including bind/connect)
@@ -1057,16 +1308,16 @@ int osmo_sock_unix_init_ofd(struct osmo_fd *ofd, uint16_t type, uint8_t proto,
*/
int osmo_sock_get_ip_and_port(int fd, char *ip, size_t ip_len, char *port, size_t port_len, bool local)
{
- struct sockaddr sa;
+ struct sockaddr_storage sa;
socklen_t len = sizeof(sa);
char ipbuf[INET6_ADDRSTRLEN], portbuf[6];
int rc;
- rc = local ? getsockname(fd, &sa, &len) : getpeername(fd, &sa, &len);
+ rc = local ? getsockname(fd, (struct sockaddr*)&sa, &len) : getpeername(fd, (struct sockaddr*)&sa, &len);
if (rc < 0)
return rc;
- rc = getnameinfo(&sa, len, ipbuf, sizeof(ipbuf),
+ rc = getnameinfo((const struct sockaddr*)&sa, len, ipbuf, sizeof(ipbuf),
portbuf, sizeof(portbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (rc < 0)
@@ -1361,13 +1612,15 @@ int osmo_sock_local_ip(char *local_ip, const char *remote_ip)
int rc;
struct addrinfo addrinfo_hint;
struct addrinfo *addrinfo = NULL;
- struct sockaddr_in local_addr;
+ struct sockaddr_storage local_addr;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
socklen_t local_addr_len;
uint16_t family;
/* Find out the address family (AF_INET or AF_INET6?) */
memset(&addrinfo_hint, '\0', sizeof(addrinfo_hint));
- addrinfo_hint.ai_family = PF_UNSPEC;
+ addrinfo_hint.ai_family = AF_UNSPEC;
addrinfo_hint.ai_flags = AI_NUMERICHOST;
rc = getaddrinfo(remote_ip, NULL, &addrinfo_hint, &addrinfo);
if (rc)
@@ -1390,16 +1643,77 @@ int osmo_sock_local_ip(char *local_ip, const char *remote_ip)
close(sfd);
if (rc < 0)
return -EINVAL;
- if (local_addr.sin_family == AF_INET)
- inet_ntop(AF_INET, &local_addr.sin_addr, local_ip, INET_ADDRSTRLEN);
- else if (local_addr.sin_family == AF_INET6)
- inet_ntop(AF_INET6, &local_addr.sin_addr, local_ip, INET6_ADDRSTRLEN);
- else
+
+ switch (local_addr.ss_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in*)&local_addr;
+ if (!inet_ntop(AF_INET, &sin->sin_addr, local_ip, INET_ADDRSTRLEN))
+ return -EINVAL;
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6*)&local_addr;
+ if (!inet_ntop(AF_INET6, &sin6->sin6_addr, local_ip, INET6_ADDRSTRLEN))
+ return -EINVAL;
+ break;
+ default:
return -EINVAL;
+ }
return 0;
}
+/*! Determine the matching local address for a given remote address.
+ * \param[out] local_ip caller provided memory for resulting local address
+ * \param[in] remote_ip remote address
+ * \returns 0 on success; negative otherwise
+ */
+int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip, const struct osmo_sockaddr *remote_ip)
+{
+ int sfd;
+ int rc;
+ socklen_t local_ip_len;
+
+ sfd = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, NULL, remote_ip, OSMO_SOCK_F_CONNECT);
+ if (sfd < 0)
+ return -EINVAL;
+
+ memset(local_ip, 0, sizeof(*local_ip));
+ local_ip_len = sizeof(*local_ip);
+ rc = getsockname(sfd, (struct sockaddr *)local_ip, &local_ip_len);
+ close(sfd);
+
+ return rc;
+}
+
+/*! Compare two osmo_sockaddr.
+ * \param[in] a
+ * \param[in] b
+ * \return 0 if a and b are equal. Otherwise it follows memcmp()
+ */
+int osmo_sockaddr_cmp(struct osmo_sockaddr *a, struct osmo_sockaddr *b)
+{
+ if (a == b)
+ return 0;
+ if (!a)
+ return 1;
+ if (!b)
+ return -1;
+
+ if (a->u.sa.sa_family != b->u.sa.sa_family) {
+ return OSMO_CMP(a->u.sa.sa_family, b->u.sa.sa_family);
+ }
+
+ switch (a->u.sa.sa_family) {
+ case AF_INET:
+ return memcmp(&a->u.sin, &b->u.sin, sizeof(struct sockaddr_in));
+ case AF_INET6:
+ return memcmp(&a->u.sin6, &b->u.sin6, sizeof(struct sockaddr_in6));
+ default:
+ /* fallback to memcmp for remaining AF over the full osmo_sockaddr length */
+ return memcmp(a, b, sizeof(struct osmo_sockaddr));
+ }
+}
+
#endif /* HAVE_SYS_SOCKET_H */
/*! @} */
diff --git a/src/use_count.c b/src/use_count.c
index 453d2ae8..738cc5da 100644
--- a/src/use_count.c
+++ b/src/use_count.c
@@ -107,6 +107,19 @@ int32_t osmo_use_count_by(const struct osmo_use_count *uc, const char *use)
*/
const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc)
{
+ osmo_use_count_to_str_buf(buf, buf_len, uc);
+ return buf;
+}
+
+/*! Write a comprehensive listing of use counts to a string buffer.
+ * Reads like "12 (3*barring,fighting,8*kungfoo)".
+ * \param[inout] buf Destination buffer.
+ * \param[in] buf_len sizeof(buf).
+ * \param[in] uc Use counts to print.
+ * \return number of bytes that would be written, like snprintf().
+ */
+int osmo_use_count_to_str_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc)
+{
int32_t count = osmo_use_count_total(uc);
struct osmo_strbuf sb = { .buf = buf, .len = buf_len };
struct osmo_use_count_entry *e;
@@ -128,7 +141,18 @@ const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo
if (first)
OSMO_STRBUF_PRINTF(sb, "-");
OSMO_STRBUF_PRINTF(sb, ")");
- return buf;
+ return sb.chars_needed;
+}
+
+/*! Write a comprehensive listing of use counts to a talloc allocated string buffer.
+ * Reads like "12 (3*barring,fighting,8*kungfoo)".
+ * \param[in] ctx talloc pool to allocate from.
+ * \param[in] uc Use counts to print.
+ * \return buf, always nul-terminated.
+ */
+char *osmo_use_count_to_str_c(void *ctx, const struct osmo_use_count *uc)
+{
+ OSMO_NAME_C_IMPL(ctx, 32, "ERROR", osmo_use_count_to_str_buf, uc)
}
/* Return a use token's use count entry -- probably you want osmo_use_count_by() instead.
diff --git a/src/utils.c b/src/utils.c
index 3c4a8c9f..772b6714 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -545,7 +545,7 @@ uint64_t osmo_decode_big_endian(const uint8_t *data, size_t data_len)
/*! Generic big-endian encoding of big endian number up to 64bit
* \param[in] value unsigned integer value to be stored
- * \param[in] data_len number of octets
+ * \param[in] data_len number of octets
* \returns static buffer containing big-endian stored value
*
* This is like osmo_store64be_ext, except that this returns a static buffer of
@@ -576,8 +576,8 @@ size_t osmo_strlcpy(char *dst, const char *src, size_t siz)
size_t ret = src ? strlen(src) : 0;
if (siz) {
- size_t len = (ret >= siz) ? siz - 1 : ret;
- if (src)
+ size_t len = OSMO_MIN(siz - 1, ret);
+ if (len)
memcpy(dst, src, len);
dst[len] = '\0';
}
diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
index 35350cc3..81ff1045 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/Makefile.am
@@ -12,7 +12,7 @@ lib_LTLIBRARIES = libosmovty.la
libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
telnet_interface.c logging_vty.c stats_vty.c \
fsm_vty.c talloc_ctx_vty.c \
- tdef_vty.c
+ cpu_sched_vty.c tdef_vty.c
libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
endif
diff --git a/src/vty/command.c b/src/vty/command.c
index 278ceb5d..bad26888 100644
--- a/src/vty/command.c
+++ b/src/vty/command.c
@@ -628,17 +628,42 @@ typedef int (*print_func_t)(void *data, const char *fmt, ...);
static int vty_dump_element(struct cmd_element *cmd, print_func_t print_func, void *data, const char *newline)
{
char *xml_string = xml_escape(cmd->string);
+ unsigned int i;
print_func(data, " <command id='%s'>%s", xml_string, newline);
+
+ /* Print application specific attributes and their description */
+ if (cmd->usrattr != 0x00) { /* ... if at least one flag is set */
+ print_func(data, " <attributes scope='application'>%s", newline);
+
+ for (i = 0; i < ARRAY_SIZE(host.app_info->usr_attr_desc); i++) {
+ char *xml_att_desc;
+ char flag;
+
+ /* Skip attribute if *not* set */
+ if (~cmd->usrattr & (1 << i))
+ continue;
+
+ xml_att_desc = xml_escape(host.app_info->usr_attr_desc[i]);
+ print_func(data, " <attribute doc='%s'", xml_att_desc);
+ talloc_free(xml_att_desc);
+
+ if ((flag = host.app_info->usr_attr_letters[i]) != '\0')
+ print_func(data, " flag='%c'", flag);
+ print_func(data, " />%s", newline);
+ }
+
+ print_func(data, " </attributes>%s", newline);
+ }
+
print_func(data, " <params>%s", newline);
- int j;
- for (j = 0; j < vector_count(cmd->strvec); ++j) {
- vector descvec = vector_slot(cmd->strvec, j);
- int i;
- for (i = 0; i < vector_active(descvec); ++i) {
+ for (i = 0; i < vector_count(cmd->strvec); ++i) {
+ vector descvec = vector_slot(cmd->strvec, i);
+ int j;
+ for (j = 0; j < vector_active(descvec); ++j) {
char *xml_param, *xml_doc;
- struct desc *desc = vector_slot(descvec, i);
+ struct desc *desc = vector_slot(descvec, j);
if (desc == NULL)
continue;
@@ -2217,23 +2242,23 @@ static void vty_clear_parents(struct vty *vty)
int vty_go_parent(struct vty *vty)
{
switch (vty->node) {
- case AUTH_NODE:
- case VIEW_NODE:
- case ENABLE_NODE:
- case CONFIG_NODE:
- vty_clear_parents(vty);
- break;
+ case AUTH_NODE:
+ case VIEW_NODE:
+ case ENABLE_NODE:
+ case CONFIG_NODE:
+ vty_clear_parents(vty);
+ break;
- case AUTH_ENABLE_NODE:
- vty->node = VIEW_NODE;
- vty_clear_parents(vty);
- break;
+ case AUTH_ENABLE_NODE:
+ vty->node = VIEW_NODE;
+ vty_clear_parents(vty);
+ break;
- default:
- if (host.app_info->go_parent_cb)
- host.app_info->go_parent_cb(vty);
- vty_pop_parent(vty);
- break;
+ default:
+ if (host.app_info->go_parent_cb)
+ host.app_info->go_parent_cb(vty);
+ vty_pop_parent(vty);
+ break;
}
return vty->node;
@@ -2834,20 +2859,21 @@ DEFUN(show_online_help,
gDEFUN(config_help,
config_help_cmd, "help", "Description of the interactive help system\n")
{
- vty_out(vty,
- "This VTY provides advanced help features. When you need help,%s\
-anytime at the command line please press '?'.%s\
-%s\
-If nothing matches, the help list will be empty and you must backup%s\
- until entering a '?' shows the available options.%s\
-Two styles of help are provided:%s\
-1. Full help is available when you are ready to enter a%s\
-command argument (e.g. 'show ?') and describes each possible%s\
-argument.%s\
-2. Partial help is provided when an abbreviated argument is entered%s\
- and you want to know what arguments match the input%s\
- (e.g. 'show me?'.)%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
- VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ vty_out(vty, "This VTY provides advanced help features. When you need help,%s"
+ "anytime at the command line please press '?'.%s%s"
+ "If nothing matches, the help list will be empty and you must backup%s"
+ " until entering a '?' shows the available options.%s"
+ "Two styles of help are provided:%s"
+ "1. Full help is available when you are ready to enter a%s"
+ "command argument (e.g. 'show ?') and describes each possible%s"
+ "argument.%s"
+ "2. Partial help is provided when an abbreviated argument is entered%s"
+ " and you want to know what arguments match the input%s"
+ " (e.g. 'show me?'.)%s%s",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
@@ -2858,10 +2884,14 @@ gDEFUN(config_list, config_list_cmd, "list", "Print command list\n")
struct cmd_node *cnode = vector_slot(cmdvec, vty->node);
struct cmd_element *cmd;
- for (i = 0; i < vector_active(cnode->cmd_vector); i++)
- if ((cmd = vector_slot(cnode->cmd_vector, i)) != NULL
- && !(cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)))
- vty_out(vty, " %s%s", cmd->string, VTY_NEWLINE);
+ for (i = 0; i < vector_active(cnode->cmd_vector); i++) {
+ if ((cmd = vector_slot(cnode->cmd_vector, i)) == NULL)
+ continue;
+ if (cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN))
+ continue;
+ vty_out(vty, " %s%s", cmd->string, VTY_NEWLINE);
+ }
+
return CMD_SUCCESS;
}
diff --git a/src/vty/cpu_sched_vty.c b/src/vty/cpu_sched_vty.c
new file mode 100644
index 00000000..35671a91
--- /dev/null
+++ b/src/vty/cpu_sched_vty.c
@@ -0,0 +1,677 @@
+/*! \file cpu_sched_vty.c
+ * Implementation to CPU / Threading / Scheduler properties from VTY configuration.
+ */
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPLv2+
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sched.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <inttypes.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/linuxlist.h>
+
+/*! \addtogroup Tdef_VTY
+ *
+ * CPU Scheduling related VTY API.
+ *
+ * @{
+ * \file cpu_sched_vty.c
+ */
+
+enum sched_vty_thread_id {
+ SCHED_VTY_THREAD_SELF,
+ SCHED_VTY_THREAD_ALL,
+ SCHED_VTY_THREAD_ID,
+ SCHED_VTY_THREAD_NAME,
+ SCHED_VTY_THREAD_UNKNOWN,
+};
+
+struct cpu_affinity_it {
+ struct llist_head entry;
+ enum sched_vty_thread_id tid_type;
+ char bufname[64];
+ cpu_set_t *cpuset;
+ size_t cpuset_size;
+ bool delay;
+};
+
+struct sched_vty_opts {
+ void *tall_ctx;
+ int sched_rr_prio;
+ struct llist_head cpu_affinity_li;
+ pthread_mutex_t cpu_affinity_li_mutex;
+};
+
+static struct sched_vty_opts *sched_vty_opts;
+
+static struct cmd_node sched_node = {
+ L_CPU_SCHED_NODE,
+ "%s(config-cpu-sched)# ",
+ 1,
+};
+
+/* returns number of configured CPUs in the system, or negative otherwise */
+static int get_num_cpus() {
+ static unsigned int num_cpus = 0;
+ long ln;
+
+ if (num_cpus)
+ return num_cpus;
+
+ /* This is expensive (goes across /sys, so let's do it only once. It is
+ * guaranteed it won't change during process span anyway). */
+ ln = sysconf(_SC_NPROCESSORS_CONF);
+ if (ln < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "sysconf(_SC_NPROCESSORS_CONF) failed: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ num_cpus = (unsigned int) ln;
+ return num_cpus;
+}
+
+/* Parses string with CPU hex Affinity Mask, with right-most bit being CPU0, and
+ * fills a cpuset of size cpuset_size.
+ */
+static int parse_cpu_hex_mask(const char *str, cpu_set_t *cpuset, size_t cpuset_size)
+{
+ int len = strlen(str);
+ const char *ptr = str + len - 1;
+ int cpu = 0;
+
+ /* skip optional '0x' prefix format */
+ if (len >= 2 && str[0] == '0' && str[1] == 'x')
+ str += 2;
+ CPU_ZERO_S(cpuset_size, cpuset);
+
+ while (ptr >= str) {
+ char c = *ptr;
+ uint8_t val;
+
+ if (c >= '0' && c <= '9') {
+ val = c - '0';
+ } else {
+ c = (char)tolower((int)c);
+ if (c >= 'a' && c <= 'f')
+ val = c + (10 - 'a');
+ else
+ return -1;
+ }
+ if (val & 0x01)
+ CPU_SET_S(cpu, cpuset_size, cpuset);
+ if (val & 0x02)
+ CPU_SET_S(cpu + 1, cpuset_size, cpuset);
+ if (val & 0x04)
+ CPU_SET_S(cpu + 2, cpuset_size, cpuset);
+ if (val & 0x08)
+ CPU_SET_S(cpu + 3, cpuset_size, cpuset);
+ ptr--;
+ cpu += 4;
+ }
+
+ return 0;
+}
+
+/* Generates a hexstring in str from cpuset of size cpuset_size */
+static int generate_cpu_hex_mask(char *str, size_t str_buf_size,
+ cpu_set_t *cpuset, size_t cpuset_size)
+{
+ char *ptr = str;
+ int cpu;
+ bool first_nonzero_found = false;
+
+ /* 2 char per byte, + '0x' prefix + '\0' */
+ if (cpuset_size * 2 + 2 + 1 > str_buf_size)
+ return -1;
+
+ *ptr++ = '0';
+ *ptr++ = 'x';
+
+ for (cpu = cpuset_size*8 - 4; cpu >= 0; cpu -= 4) {
+ uint8_t val = 0;
+
+ if (CPU_ISSET_S(cpu, cpuset_size, cpuset))
+ val |= 0x01;
+ if (CPU_ISSET_S(cpu + 1, cpuset_size, cpuset))
+ val |= 0x02;
+ if (CPU_ISSET_S(cpu + 2, cpuset_size, cpuset))
+ val |= 0x04;
+ if (CPU_ISSET_S(cpu + 3, cpuset_size, cpuset))
+ val |= 0x08;
+
+ if (val < 10)
+ *ptr = '0' + val;
+ else
+ *ptr = ('a' - 10) + val;
+ if (val)
+ first_nonzero_found = true;
+ if (first_nonzero_found)
+ ptr++;
+
+ }
+ if (!first_nonzero_found)
+ *ptr++ = '0';
+ *ptr = '\0';
+ return 0;
+}
+
+/* Checks whther a thread identified by tid exists and belongs to the running process */
+static bool proc_tid_exists(pid_t tid)
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char dirname[100];
+ int tid_it;
+ bool found = false;
+
+ snprintf(dirname, sizeof(dirname), "/proc/%ld/task", (long int)getpid());
+ proc_dir = opendir(dirname);
+ if (!proc_dir)
+ return false; /*FIXME; print error */
+
+ while ((entry = readdir(proc_dir))) {
+ if (entry->d_name[0] == '.')
+ continue;
+ tid_it = atoi(entry->d_name);
+ if (tid_it == tid) {
+ found = true;
+ break;
+ }
+ }
+
+ closedir(proc_dir);
+ return found;
+}
+
+/* Checks whther a thread identified by name exists and belongs to the running
+ * process, and returns its disocevered TID in res_pid.
+ */
+static bool proc_name_exists(const char *name, pid_t *res_pid)
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char path[100];
+ char buf[17]; /* 15 + \n + \0 */
+ int tid_it;
+ int fd;
+ pid_t mypid = getpid();
+ bool found = false;
+ int rc;
+
+ *res_pid = 0;
+
+ snprintf(path, sizeof(path), "/proc/%ld/task", (long int)mypid);
+ proc_dir = opendir(path);
+ if (!proc_dir)
+ return false;
+
+ while ((entry = readdir(proc_dir)))
+ {
+ if (entry->d_name[0] == '.')
+ continue;
+
+ tid_it = atoi(entry->d_name);
+ snprintf(path, sizeof(path), "/proc/%ld/task/%ld/comm", (long int)mypid, (long int) tid_it);
+ if ((fd = open(path, O_RDONLY)) == -1)
+ continue;
+ rc = read(fd, buf, sizeof(buf) - 1);
+ if (rc >= 0) {
+ /* Last may char contain a '\n', get rid of it */
+ if (rc > 0 && buf[rc - 1] == '\n')
+ buf[rc - 1] = '\0';
+ else
+ buf[rc] = '\0';
+ if (strcmp(name, buf) == 0) {
+ *res_pid = tid_it;
+ found = true;
+ }
+ }
+ close(fd);
+
+ if (found)
+ break;
+ }
+
+ closedir(proc_dir);
+ return found;
+}
+
+/* Parse VTY THREADNAME variable, return its type and fill discovered res_pid if required */
+static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bool applynow)
+{
+ size_t i, len;
+ char *end;
+ bool is_pid = true;
+
+ if (strcmp(str, "all") == 0) {
+ *res_pid = 0;
+ return SCHED_VTY_THREAD_ALL;
+ }
+
+ if (strcmp(str, "self") == 0) {
+ *res_pid = 0;
+ return SCHED_VTY_THREAD_SELF;
+ }
+
+ len = strlen(str);
+ for (i = 0; i < len; i++) {
+ if (!isdigit(str[i])) {
+ is_pid = false;
+ break;
+ }
+ }
+ if (is_pid) {
+ errno = 0;
+ *res_pid = strtoul(str, &end, 0);
+ if ((errno == ERANGE && *res_pid == ULONG_MAX) || (errno && !*res_pid) ||
+ str == end) {
+ return SCHED_VTY_THREAD_UNKNOWN;
+ }
+ if (!applynow || proc_tid_exists(*res_pid))
+ return SCHED_VTY_THREAD_ID;
+ else
+ return SCHED_VTY_THREAD_UNKNOWN;
+ }
+
+ if (len > 15) {
+ /* Thread names only allow up to 15+1 null chars, see man pthread_setname_np */
+ return SCHED_VTY_THREAD_UNKNOWN;
+ }
+
+ if (applynow) {
+ if (proc_name_exists(str, res_pid))
+ return SCHED_VTY_THREAD_NAME;
+ else
+ return SCHED_VTY_THREAD_UNKNOWN;
+ } else {
+ /* assume a thread will be named after it */
+ *res_pid = 0;
+ return SCHED_VTY_THREAD_NAME;
+ }
+}
+
+/* Wrapper for sched_setaffinity applying to single thread or all threads in process based on tid_type. */
+static int my_sched_setaffinity(enum sched_vty_thread_id tid_type, pid_t pid,
+ cpu_set_t *cpuset, size_t cpuset_size)
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char dirname[100];
+ char str_mask[1024];
+ int tid_it;
+ int rc = 0;
+
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask), cpuset, cpuset_size) < 0)
+ str_mask[0] = '\0';
+
+ if (tid_type != SCHED_VTY_THREAD_ALL) {
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Setting CPU affinity mask for tid %lu to: %s\n",
+ (unsigned long) pid, str_mask);
+
+ rc = sched_setaffinity(pid, sizeof(cpu_set_t), cpuset);
+ return rc;
+ }
+
+ snprintf(dirname, sizeof(dirname), "/proc/%ld/task", (long int)getpid());
+ proc_dir = opendir(dirname);
+ if (!proc_dir)
+ return -EINVAL;
+
+ while ((entry = readdir(proc_dir)))
+ {
+ if (entry->d_name[0] == '.')
+ continue;
+ tid_it = atoi(entry->d_name);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Setting CPU affinity mask for tid %lu to: %s\n",
+ (unsigned long) tid_it, str_mask);
+
+ rc = sched_setaffinity(tid_it, sizeof(cpu_set_t), cpuset);
+ if (rc == -1)
+ break;
+ }
+
+ closedir(proc_dir);
+ return rc;
+
+}
+
+DEFUN(cfg_sched_cpu_affinity, cfg_sched_cpu_affinity_cmd,
+ "cpu-affinity (self|all|<0-4294967295>|THREADNAME) CPUHEXMASK [delay]",
+ "Set CPU affinity mask on a (group of) thread(s)\n"
+ "Set CPU affinity mask on thread running the VTY\n"
+ "Set CPU affinity mask on all process' threads\n"
+ "Set CPU affinity mask on a thread with specified PID\n"
+ "Set CPU affinity mask on a thread with specified thread name\n"
+ "CPU affinity mask\n"
+ "If set, delay applying the affinity mask now and let the app handle it at a later point\n")
+{
+ const char* str_who = argv[0];
+ const char *str_mask = argv[1];
+ bool applynow = (argc != 3);
+ int rc;
+ pid_t pid;
+ enum sched_vty_thread_id tid_type;
+ struct cpu_affinity_it *it, *it_next;
+ cpu_set_t *cpuset;
+ size_t cpuset_size;
+
+ tid_type = procname2pid(&pid, str_who, applynow);
+ if (tid_type == SCHED_VTY_THREAD_UNKNOWN) {
+ vty_out(vty, "%% Failed parsing target thread %s%s",
+ str_who, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (tid_type == SCHED_VTY_THREAD_ID && !applynow) {
+ vty_out(vty, "%% It makes no sense to delay applying cpu-affinity on tid %lu%s",
+ (unsigned long)pid, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (tid_type == SCHED_VTY_THREAD_ALL && !applynow) {
+ vty_out(vty, "%% It makes no sense to delay applying cpu-affinity on all threads%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ cpuset = CPU_ALLOC(get_num_cpus());
+ cpuset_size = CPU_ALLOC_SIZE(get_num_cpus());
+ if (parse_cpu_hex_mask(str_mask, cpuset, cpuset_size) < 0) {
+ vty_out(vty, "%% Failed parsing CPU Affinity Mask %s%s",
+ str_mask, VTY_NEWLINE);
+ CPU_FREE(cpuset);
+ return CMD_WARNING;
+ }
+
+ if (applynow) {
+ rc = my_sched_setaffinity(tid_type, pid, cpuset, cpuset_size);
+ if (rc == -1) {
+ vty_out(vty, "%% Failed setting sched CPU Affinity Mask %s: %s%s",
+ str_mask, strerror(errno), VTY_NEWLINE);
+ CPU_FREE(cpuset);
+ return CMD_WARNING;
+ }
+ }
+
+ /* Keep history of cmds applied to be able to rewrite config. If PID was passed
+ directly it makes no sense to store it since PIDs are temporary */
+ if (tid_type == SCHED_VTY_THREAD_SELF ||
+ tid_type == SCHED_VTY_THREAD_ALL ||
+ tid_type == SCHED_VTY_THREAD_NAME) {
+ pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex);
+
+ /* Drop previous entries matching, since they will be overwritten */
+ llist_for_each_entry_safe(it, it_next, &sched_vty_opts->cpu_affinity_li, entry) {
+ if (strcmp(it->bufname, str_who) == 0) {
+ llist_del(&it->entry);
+ CPU_FREE(it->cpuset);
+ talloc_free(it);
+ break;
+ }
+ }
+ it = talloc_zero(sched_vty_opts->tall_ctx, struct cpu_affinity_it);
+ OSMO_STRLCPY_ARRAY(it->bufname, str_who);
+ it->tid_type = tid_type;
+ it->cpuset = cpuset;
+ it->cpuset_size = cpuset_size;
+ it->delay = !applynow;
+ llist_add_tail(&it->entry, &sched_vty_opts->cpu_affinity_li);
+
+ pthread_mutex_unlock(&sched_vty_opts->cpu_affinity_li_mutex);
+ } else {
+ /* We don't need cpuset for later, free it: */
+ CPU_FREE(cpuset);
+ }
+ return CMD_SUCCESS;
+}
+
+static int set_sched_rr(unsigned int prio)
+{
+ struct sched_param param;
+ int rc;
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = prio;
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Setting SCHED_RR priority %d\n", param.sched_priority);
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc == -1) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Setting SCHED_RR priority %d failed: %s\n",
+ param.sched_priority, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+DEFUN(cfg_sched_policy, cfg_sched_policy_cmd,
+ "policy rr <1-32>",
+ "Set the scheduling policy to use for the process\n"
+ "Use the SCHED_RR real-time scheduling algorithm\n"
+ "Set the SCHED_RR real-time priority\n")
+{
+ sched_vty_opts->sched_rr_prio = atoi(argv[0]);
+
+ if (set_sched_rr(sched_vty_opts->sched_rr_prio) < 0) {
+ vty_out(vty, "%% Failed setting SCHED_RR priority %d%s",
+ sched_vty_opts->sched_rr_prio, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sched,
+ cfg_sched_cmd,
+ "cpu-sched", "Configure CPU Scheduler related settings")
+{
+ vty->index = NULL;
+ vty->node = L_CPU_SCHED_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sched_threads, show_sched_threads_cmd,
+ "show cpu-sched threads",
+ SHOW_STR
+ "Show Sched section information\n"
+ "Show information about running threads)\n")
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char path[100];
+ char name[17];
+ char str_mask[1024];
+ int tid_it;
+ int fd;
+ pid_t mypid = getpid();
+ int rc;
+ cpu_set_t *cpuset;
+ size_t cpuset_size;
+
+ vty_out(vty, "Thread list for PID %lu:%s", (unsigned long) mypid, VTY_NEWLINE);
+
+ snprintf(path, sizeof(path), "/proc/%ld/task", (long int)mypid);
+ proc_dir = opendir(path);
+ if (!proc_dir) {
+ vty_out(vty, "%% Failed opening dir%s%s", path, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ while ((entry = readdir(proc_dir)))
+ {
+ if (entry->d_name[0] == '.')
+ continue;
+
+ tid_it = atoi(entry->d_name);
+ snprintf(path, sizeof(path), "/proc/%ld/task/%ld/comm", (long int)mypid, (long int)tid_it);
+ if ((fd = open(path, O_RDONLY)) != -1) {
+ rc = read(fd, name, sizeof(name) - 1);
+ if (rc >= 0) {
+ /* Last may char contain a '\n', get rid of it */
+ if (rc > 0 && name[rc - 1] == '\n')
+ name[rc - 1] = '\0';
+ else
+ name[rc] = '\0';
+ }
+ close(fd);
+ } else {
+ name[0] = '\0';
+ }
+
+ str_mask[0] = '\0';
+ cpuset = CPU_ALLOC(get_num_cpus());
+ cpuset_size = CPU_ALLOC_SIZE(get_num_cpus());
+ CPU_ZERO_S(cpuset_size, cpuset);
+ if (sched_getaffinity(tid_it, cpuset_size, cpuset) == 0) {
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask), cpuset, cpuset_size) < 0)
+ str_mask[0] = '\0';
+ }
+ CPU_FREE(cpuset);
+
+ vty_out(vty, " TID: %lu, NAME: '%s', cpu-affinity: %s%s",
+ (unsigned long) tid_it, name, str_mask, VTY_NEWLINE);
+ }
+
+ closedir(proc_dir);
+ return CMD_SUCCESS;
+}
+
+static int config_write_sched(struct vty *vty)
+{
+ struct cpu_affinity_it *it;
+ char str_mask[1024];
+
+ /* Only add the node if there's something to write under it */
+ if (sched_vty_opts->sched_rr_prio || !llist_empty(&sched_vty_opts->cpu_affinity_li))
+ vty_out(vty, "cpu-sched%s", VTY_NEWLINE);
+
+ if (sched_vty_opts->sched_rr_prio)
+ vty_out(vty, " policy rr %d%s", sched_vty_opts->sched_rr_prio, VTY_NEWLINE);
+
+ llist_for_each_entry(it, &sched_vty_opts->cpu_affinity_li, entry) {
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask), it->cpuset, it->cpuset_size) < 0)
+ OSMO_STRLCPY_ARRAY(str_mask, "ERROR");
+ vty_out(vty, " cpu-affinity %s %s%s%s", it->bufname, str_mask,
+ it->delay ? " delay" : "", VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+/*! Initialize sched VTY nodes
+ * \param[in] tall_ctx Talloc context to use internally by vty_sched subsystem.
+ * \return 0 on success, non-zero on error.
+ */
+int osmo_cpu_sched_vty_init(void *tall_ctx)
+{
+ OSMO_ASSERT(!sched_vty_opts); /* assert only called once */
+
+ sched_vty_opts = talloc_zero(tall_ctx, struct sched_vty_opts);
+ sched_vty_opts->tall_ctx = tall_ctx;
+ INIT_LLIST_HEAD(&sched_vty_opts->cpu_affinity_li);
+ pthread_mutex_init(&sched_vty_opts->cpu_affinity_li_mutex, NULL);
+
+ install_element(CONFIG_NODE, &cfg_sched_cmd);
+ install_node(&sched_node, config_write_sched);
+
+ install_element(L_CPU_SCHED_NODE, &cfg_sched_policy_cmd);
+ install_element(L_CPU_SCHED_NODE, &cfg_sched_cpu_affinity_cmd);
+
+ install_element_ve(&show_sched_threads_cmd);
+
+ /* Initialize amount of cpus now */
+ if (get_num_cpus() < 0)
+ return -1;
+
+ return 0;
+}
+
+/*! Apply cpu-affinity on calling thread based on VTY configuration
+ * \return 0 on success, non-zero on error.
+ */
+int osmo_cpu_sched_vty_apply_localthread(void)
+{
+ struct cpu_affinity_it *it, *it_match = NULL;
+ char name[16]; /* 15 + \0 */
+ char str_mask[1024];
+ bool has_name = false;
+ int rc = 0;
+
+ /* Assert subsystem was inited and structs are preset */
+ if (!sched_vty_opts) {
+ LOGP(DLGLOBAL, LOGL_FATAL, "Setting cpu-affinity mask impossible: no opts!\n");
+ return 0;
+ }
+
+ if (pthread_getname_np(pthread_self(), name, sizeof(name)) == 0)
+ has_name = true;
+
+ /* Get latest matching mask for the thread */
+ pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex);
+ llist_for_each_entry(it, &sched_vty_opts->cpu_affinity_li, entry) {
+ switch (it->tid_type) {
+ case SCHED_VTY_THREAD_SELF:
+ continue; /* self to the VTY thread, not us */
+ case SCHED_VTY_THREAD_ALL:
+ it_match = it;
+ break;
+ case SCHED_VTY_THREAD_NAME:
+ if (!has_name)
+ continue;
+ if (strcmp(name, it->bufname) != 0)
+ continue;
+ it_match = it;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ }
+
+ if (it_match) {
+ rc = my_sched_setaffinity(SCHED_VTY_THREAD_SELF, 0, it_match->cpuset, it_match->cpuset_size);
+ if (rc == -1) {
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask),
+ it_match->cpuset, it_match->cpuset_size) < 0)
+ str_mask[0] = '\0';
+ LOGP(DLGLOBAL, LOGL_FATAL, "Setting cpu-affinity mask %s failed: %s\n",
+ str_mask, strerror(errno));
+ }
+ }
+ pthread_mutex_unlock(&sched_vty_opts->cpu_affinity_li_mutex);
+ return rc;
+}
+
+/*! @} */
diff --git a/src/vty/vty.c b/src/vty/vty.c
index ebdf9fc9..6e7bdcb3 100644
--- a/src/vty/vty.c
+++ b/src/vty/vty.c
@@ -1119,7 +1119,7 @@ static void vty_describe_command(struct vty *vty)
int ret;
vector vline;
vector describe;
- unsigned int i, width, desc_width;
+ unsigned int i, cmd_width, desc_width;
struct desc *desc, *desc_cr = NULL;
vline = cmd_make_strvec(vty->buf);
@@ -1143,19 +1143,17 @@ static void vty_describe_command(struct vty *vty)
vty_prompt(vty);
vty_redraw_line(vty);
return;
- break;
case CMD_ERR_NO_MATCH:
cmd_free_strvec(vline);
vty_out(vty, "%% There is no matched command.%s", VTY_NEWLINE);
vty_prompt(vty);
vty_redraw_line(vty);
return;
- break;
}
/* Get width of command string. */
- width = 0;
- for (i = 0; i < vector_active(describe); i++)
+ cmd_width = 0;
+ for (i = 0; i < vector_active(describe); i++) {
if ((desc = vector_slot(describe, i)) != NULL) {
unsigned int len;
@@ -1166,15 +1164,16 @@ static void vty_describe_command(struct vty *vty)
if (desc->cmd[0] == '.')
len--;
- if (width < len)
- width = len;
+ if (cmd_width < len)
+ cmd_width = len;
}
+ }
/* Get width of description string. */
- desc_width = vty->width - (width + 6);
+ desc_width = vty->width - (cmd_width + 6);
/* Print out description. */
- for (i = 0; i < vector_active(describe); i++)
+ for (i = 0; i < vector_active(describe); i++) {
if ((desc = vector_slot(describe, i)) != NULL) {
if (desc->cmd[0] == '\0')
continue;
@@ -1190,19 +1189,20 @@ static void vty_describe_command(struct vty *vty)
'.' ? desc->cmd + 1 : desc->cmd,
VTY_NEWLINE);
else if (desc_width >= strlen(desc->str))
- vty_out(vty, " %-*s %s%s", width,
+ vty_out(vty, " %-*s %s%s", cmd_width,
desc->cmd[0] ==
'.' ? desc->cmd + 1 : desc->cmd,
desc->str, VTY_NEWLINE);
else
- vty_describe_fold(vty, width, desc_width, desc);
+ vty_describe_fold(vty, cmd_width, desc_width, desc);
#if 0
- vty_out(vty, " %-*s %s%s", width
+ vty_out(vty, " %-*s %s%s", cmd_width
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
desc->str ? desc->str : "", VTY_NEWLINE);
#endif /* 0 */
}
+ }
if ((desc = desc_cr)) {
if (!desc->str)
@@ -1210,11 +1210,11 @@ static void vty_describe_command(struct vty *vty)
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
VTY_NEWLINE);
else if (desc_width >= strlen(desc->str))
- vty_out(vty, " %-*s %s%s", width,
+ vty_out(vty, " %-*s %s%s", cmd_width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
desc->str, VTY_NEWLINE);
else
- vty_describe_fold(vty, width, desc_width, desc);
+ vty_describe_fold(vty, cmd_width, desc_width, desc);
}
cmd_free_strvec(vline);