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