diff options
Diffstat (limited to 'src/gb')
-rw-r--r-- | src/gb/Makefile.am | 33 | ||||
-rw-r--r-- | src/gb/bssgp_bvc_fsm.c | 857 | ||||
-rw-r--r-- | src/gb/common_vty.c | 14 | ||||
-rw-r--r-- | src/gb/common_vty.h | 2 | ||||
-rw-r--r-- | src/gb/frame_relay.c | 1051 | ||||
-rw-r--r-- | src/gb/gprs_bssgp.c | 353 | ||||
-rw-r--r-- | src/gb/gprs_bssgp2.c | 486 | ||||
-rw-r--r-- | src/gb/gprs_bssgp_bss.c | 100 | ||||
-rw-r--r-- | src/gb/gprs_bssgp_internal.h | 9 | ||||
-rw-r--r-- | src/gb/gprs_bssgp_rim.c | 1261 | ||||
-rw-r--r-- | src/gb/gprs_bssgp_util.c | 350 | ||||
-rw-r--r-- | src/gb/gprs_bssgp_vty.c | 16 | ||||
-rw-r--r-- | src/gb/gprs_ns.c | 69 | ||||
-rw-r--r-- | src/gb/gprs_ns2.c | 1695 | ||||
-rw-r--r-- | src/gb/gprs_ns2_fr.c | 988 | ||||
-rw-r--r-- | src/gb/gprs_ns2_frgre.c | 616 | ||||
-rw-r--r-- | src/gb/gprs_ns2_internal.h | 503 | ||||
-rw-r--r-- | src/gb/gprs_ns2_message.c | 848 | ||||
-rw-r--r-- | src/gb/gprs_ns2_sns.c | 3106 | ||||
-rw-r--r-- | src/gb/gprs_ns2_udp.c | 597 | ||||
-rw-r--r-- | src/gb/gprs_ns2_vc_fsm.c | 992 | ||||
-rw-r--r-- | src/gb/gprs_ns2_vty.c | 2351 | ||||
-rw-r--r-- | src/gb/gprs_ns_vty.c | 46 | ||||
-rw-r--r-- | src/gb/libosmogb.map | 132 |
24 files changed, 16230 insertions, 245 deletions
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am index 7248413a..0aa5b131 100644 --- a/src/gb/Makefile.am +++ b/src/gb/Makefile.am @@ -1,26 +1,43 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=10:0:1 +LIBVERSION=16:1:2 -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing $(TALLOC_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall -fno-strict-aliasing \ + $(TALLOC_CFLAGS) \ + $(NULL) # FIXME: this should eventually go into a milenage/Makefile.am -noinst_HEADERS = common_vty.h gb_internal.h +noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_internal.h if ENABLE_GB lib_LTLIBRARIES = libosmogb.la -libosmogb_la_LDFLAGS = $(LTLDFLAGS_OSMOGB) -version-info $(LIBVERSION) +libosmogb_la_LDFLAGS = \ + $(LTLDFLAGS_OSMOGB) \ + -version-info $(LIBVERSION) \ + -no-undefined \ + $(NULL) libosmogb_la_LIBADD = $(TALLOC_LIBS) \ - $(top_builddir)/src/libosmocore.la \ + $(top_builddir)/src/core/libosmocore.la \ $(top_builddir)/src/vty/libosmovty.la \ $(top_builddir)/src/gsm/libosmogsm.la libosmogb_la_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c gprs_ns_sns.c \ - gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c \ - gprs_bssgp_bss.c common_vty.c + gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c gprs_bssgp_rim.c \ + gprs_bssgp_bss.c \ + gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_fr.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \ + gprs_ns2_message.c gprs_ns2_vty.c \ + gprs_bssgp2.c bssgp_bvc_fsm.c \ + common_vty.c frame_relay.c + +# convenience library for testing with access to all non-static symbols +noinst_LTLIBRARIES = libosmogb-test.la +libosmogb_test_la_LIBADD = $(libosmogb_la_LIBADD) +libosmogb_test_la_SOURCES= $(libosmogb_la_SOURCES) + endif EXTRA_DIST = libosmogb.map +EXTRA_libosmogb_la_DEPENDENCIES = libosmogb.map diff --git a/src/gb/bssgp_bvc_fsm.c b/src/gb/bssgp_bvc_fsm.c new file mode 100644 index 00000000..3a36c7dc --- /dev/null +++ b/src/gb/bssgp_bvc_fsm.c @@ -0,0 +1,857 @@ +/* BSSGP per-BVC Finite State Machine */ + +/* (C) 2020 Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <stdio.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/tdef.h> + +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/tlv.h> + +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gprs/gprs_bssgp.h> +#include <osmocom/gprs/gprs_bssgp2.h> +#include <osmocom/gprs/bssgp_bvc_fsm.h> + +#include "common_vty.h" + +#define S(x) (1 << (x)) + +/* TODO: Those are not made cofnigurable via a VTY yet */ +struct osmo_tdef bssgp_bvc_fsm_tdefs[] = { + { + .T = 1, + .default_val = 5, + .min_val = 1, + .max_val = 30, + .unit = OSMO_TDEF_S, + .desc = "Guards the BSSGP BVC (un)blocking procedure", + }, { + .T = 2, + .default_val = 10, + .min_val = 1, + .max_val = 120, + .unit = OSMO_TDEF_S, + .desc = "Guards the BSSGP BVC reset procedure", + }, { + .T = 3, + .default_val = 500, + .min_val = 100, + .max_val = 10000, + .unit = OSMO_TDEF_MS, + .desc = "Guards the BSSGP SUSPEND procedure", + }, { + .T = 4, + .default_val = 500, + .min_val = 100, + .max_val = 10000, + .unit = OSMO_TDEF_MS, + .desc = "Guards the BSSGP RESUME procedure", + }, { + .T = 5, + .default_val = 15, + .min_val = 1, + .max_val = 30, + .unit = OSMO_TDEF_S, + .desc = "Guards the BSSGP Radio Access Capability Update procedure", + }, + {} +}; + +#define T1 1 +#define T2 2 + +/* We cannot use osmo_tdef_fsm_* as it makes hard-coded assumptions that + * each new/target state will always use the same timer and timeout - or + * a timeout at all */ +#define T1_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 1, OSMO_TDEF_S, 5) +#define T2_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 2, OSMO_TDEF_S, 10) + +/* forward declaration */ +static struct osmo_fsm bssgp_bvc_fsm; + +static const struct value_string ptp_bvc_event_names[] = { + { BSSGP_BVCFSM_E_RX_BLOCK, "RX-BVC-BLOCK" }, + { BSSGP_BVCFSM_E_RX_BLOCK_ACK, "RX-BVC-BLOCK-ACK" }, + { BSSGP_BVCFSM_E_RX_UNBLOCK, "RX-BVC-UNBLOCK" }, + { BSSGP_BVCFSM_E_RX_UNBLOCK_ACK, "RX-BVC-UNBLOCK-ACK" }, + { BSSGP_BVCFSM_E_RX_RESET, "RX-BVC-RESET" }, + { BSSGP_BVCFSM_E_RX_RESET_ACK, "RX-BVC-RESET-ACK" }, + { BSSGP_BVCFSM_E_RX_FC_BVC, "RX-FLOW-CONTROL-BVC" }, + { BSSGP_BVCFSM_E_RX_FC_BVC_ACK, "RX-FLOW-CONTROL-BVC-ACK" }, + { BSSGP_BVCFSM_E_REQ_BLOCK, "REQ-BLOCK" }, + { BSSGP_BVCFSM_E_REQ_UNBLOCK, "REQ-UNBLOCK" }, + { BSSGP_BVCFSM_E_REQ_RESET, "REQ-RESET" }, + { BSSGP_BVCFSM_E_REQ_FC_BVC, "REQ-FLOW-CONTROL-BVC" }, + { 0, NULL } +}; + +struct bvc_fsm_priv { + /* NS-instance; defining the scope for NSEI below */ + struct gprs_ns2_inst *nsi; + + /* NSEI of the underlying NS Entity */ + uint16_t nsei; + /* Maximum size of the BSSGP PDU */ + uint16_t max_pdu_len; + + /* BVCI of this BVC */ + uint16_t bvci; + + /* are we the SGSN (true) or the BSS (false) */ + bool role_sgsn; + + /* BSS side: are we locally marked blocked? */ + bool locally_blocked; + uint8_t block_cause; + + /* cause value of the last outbound BVC-RESET (for re-transmissions) */ + uint8_t last_reset_cause; + + struct { + /* Bit 0..7: Features; Bit 8..15: Extended Features */ + uint32_t advertised; + uint32_t received; + uint32_t negotiated; + /* only used if BSSGP_XFEAT_GBIT is negotiated */ + enum bssgp_fc_granularity fc_granularity; + } features; + + /* Cell Identification used by BSS when + * transmitting BVC-RESET / BVC-RESET-ACK, or those received + * from BSS in SGSN role */ + struct gprs_ra_id ra_id; + uint16_t cell_id; + + /* call-backs provided by the user */ + const struct bssgp_bvc_fsm_ops *ops; + /* private data pointer passed to each call-back invocation */ + void *ops_priv; +}; + +static int fi_tx_ptp(struct osmo_fsm_inst *fi, struct msgb *msg) +{ + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + + LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type)); + + return bssgp2_nsi_tx_ptp(bfp->nsi, bfp->nsei, bfp->bvci, msg, 0); +} + +static int fi_tx_sig(struct osmo_fsm_inst *fi, struct msgb *msg) +{ + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + + LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type)); + + return bssgp2_nsi_tx_sig(bfp->nsi, bfp->nsei, msg, 0); +} + +/* helper function to transmit BVC-RESET with right combination of conditional/optional IEs */ +static void _tx_bvc_reset(struct osmo_fsm_inst *fi, uint8_t cause) +{ + struct bvc_fsm_priv *bfp = fi->priv; + const uint8_t *features = NULL; + const uint8_t *features_ext = NULL; + uint8_t _features[2] = { + (bfp->features.advertised >> 0) & 0xff, + (bfp->features.advertised >> 8) & 0xff, + }; + struct msgb *tx; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + + /* transmit BVC-RESET to peer; RA-ID only present for PTP from BSS */ + if (bfp->bvci == 0) { + features = &_features[0]; + features_ext = &_features[1]; + } + tx = bssgp2_enc_bvc_reset(bfp->bvci, cause, + bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL, + bfp->cell_id, features, features_ext); + fi_tx_sig(fi, tx); +} + +/* helper function to transmit BVC-RESET-ACK with right combination of conditional/optional IEs */ +static void _tx_bvc_reset_ack(struct osmo_fsm_inst *fi) +{ + struct bvc_fsm_priv *bfp = fi->priv; + const uint8_t *features = NULL; + const uint8_t *features_ext = NULL; + uint8_t _features[2] = { + (bfp->features.advertised >> 0) & 0xff, + (bfp->features.advertised >> 8) & 0xff, + }; + struct msgb *tx; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + + /* transmit BVC-RESET-ACK to peer; RA-ID only present for PTP from BSS -> SGSN */ + if (bfp->bvci == 0) { + features = &_features[0]; + features_ext = &_features[1]; + } + tx = bssgp2_enc_bvc_reset_ack(bfp->bvci, bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL, + bfp->cell_id, features, features_ext); + fi_tx_sig(fi, tx); +} + +/* helper function to transmit BVC-STATUS with right combination of conditional/optional IEs */ +static void _tx_status(struct osmo_fsm_inst *fi, enum gprs_bssgp_cause cause, const struct msgb *rx) +{ + struct bvc_fsm_priv *bfp = fi->priv; + struct msgb *tx; + uint16_t *bvci = NULL; + + /* GSM 08.18, 10.4.14.1: The BVCI must be included if (and only if) the + * cause is either "BVCI blocked" or "BVCI unknown" */ + if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED) + bvci = &bfp->bvci; + + tx = bssgp2_enc_status(cause, bvci, rx, bfp->max_pdu_len); + + if (msgb_bvci(rx) == 0) + fi_tx_sig(fi, tx); + else + fi_tx_ptp(fi, tx); +} + +/* Update the features by bit-wise AND of advertised + received features */ +static void update_negotiated_features(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + + bfp->features.received = 0; + + if (TLVP_PRES_LEN(tp, BSSGP_IE_FEATURE_BITMAP, 1)) + bfp->features.received |= *TLVP_VAL(tp, BSSGP_IE_FEATURE_BITMAP); + + if (TLVP_PRES_LEN(tp, BSSGP_IE_EXT_FEATURE_BITMAP, 1)) + bfp->features.received |= (*TLVP_VAL(tp, BSSGP_IE_EXT_FEATURE_BITMAP) << 8); + + bfp->features.negotiated = bfp->features.advertised & bfp->features.received; + + LOGPFSML(fi, LOGL_NOTICE, "Updating features: Advertised 0x%04x, Received 0x%04x, Negotiated 0x%04x\n", + bfp->features.advertised, bfp->features.received, bfp->features.negotiated); +} + +/* "tail" of each onenter() handler: Calling the state change notification call-back */ +static void _onenter_tail(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + if (prev_state == fi->state) + return; + + if (bfp->ops && bfp->ops->state_chg_notification) + bfp->ops->state_chg_notification(bfp->nsei, bfp->bvci, prev_state, fi->state, bfp->ops_priv); +} + +static void bssgp_bvc_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + /* we don't really expect anything in this state; all handled via allstate */ + OSMO_ASSERT(0); +} + +static void bssgp_bvc_fsm_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct bvc_fsm_priv *bfp = fi->priv; + /* signaling BVC can never be blocked */ + OSMO_ASSERT(bfp->bvci != 0); + _onenter_tail(fi, prev_state); +} + +static void bssgp_bvc_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct bvc_fsm_priv *bfp = fi->priv; + struct msgb *rx = NULL, *tx; + const struct tlv_parsed *tp = NULL; + uint8_t cause; + + switch (event) { + case BSSGP_BVCFSM_E_RX_BLOCK_ACK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + /* If a BVC-BLOCK-ACK PDU is received by a BSS for the signalling BVC, the PDU is ignored. */ + if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK-ACK on BVCI=0 is illegal\n"); + if (!bfp->role_sgsn) + break; + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + /* stop T1 timer */ + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0); + break; + case BSSGP_BVCFSM_E_RX_BLOCK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE); + LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n", bssgp_cause_str(cause)); + /* If a BVC-BLOCK PDU is received by an SGSN for a blocked BVC, a BVC-BLOCK-ACK + * PDU shall be returned. */ + if (bfp->role_sgsn) { + /* If a BVC-BLOCK PDU is received by an SGSN for + * the signalling BVC, the PDU is ignored */ + if (bfp->bvci == 0) + break; + tx = bssgp2_enc_bvc_block_ack(bfp->bvci); + fi_tx_sig(fi, tx); + } + break; + case BSSGP_BVCFSM_E_RX_UNBLOCK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-UNBLOCK\n"); + if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BVCI=0 is illegal\n"); + /* If BVC-UNBLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored.*/ + if (bfp->role_sgsn) + break; + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + if (!bfp->role_sgsn) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BSS is illegal\n"); + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + tx = bssgp2_enc_bvc_unblock_ack(bfp->bvci); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1); + break; + case BSSGP_BVCFSM_E_REQ_UNBLOCK: + if (bfp->role_sgsn) { + LOGPFSML(fi, LOGL_ERROR, "SGSN side cannot initiate BVC unblock\n"); + break; + } + if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be unblocked\n"); + break; + } + bfp->locally_blocked = false; + tx = bssgp2_enc_bvc_unblock(bfp->bvci); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0); + break; + } +} + +/* Waiting for RESET-ACK: Receive PDUs but don't transmit */ +static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct bvc_fsm_priv *bfp = fi->priv; + const struct tlv_parsed *tp = NULL; + struct msgb *rx = NULL, *tx; + uint8_t cause; + + switch (event) { + case BSSGP_BVCFSM_E_RX_RESET: + /* 48.018 Section 8.4.3: If the BSS (or SGSN) has sent a BVC-RESET PDU for a BVCI to + * the SGSN (or BSS) and is awaiting a BVC-RESET-ACK PDU in response, but instead + * receives a BVC-RESET PDU indicating the same BVCI, then this shall be interpreted + * as a BVC-RESET ACK PDU and the T2 timer shall be stopped. */ + /* fall-through */ + case BSSGP_BVCFSM_E_RX_RESET_ACK: + rx = data; + cause = bfp->last_reset_cause; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + if (bfp->bvci == 0) + update_negotiated_features(fi, tp); + if (bfp->role_sgsn && bfp->bvci != 0) + bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID)); + if (!bfp->role_sgsn && bfp->bvci != 0 && bfp->locally_blocked) { + /* initiate the blocking procedure */ + /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */ + tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1); + } else + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0); + if (bfp->ops && bfp->ops->reset_ack_notification) + bfp->ops->reset_ack_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id, cause, bfp->ops_priv); + break; + } +} + +static void bssgp_bvc_fsm_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct bssgp2_flow_ctrl rx_fc, *tx_fc; + struct bvc_fsm_priv *bfp = fi->priv; + const struct tlv_parsed *tp = NULL; + struct msgb *rx = NULL, *tx; + int rc; + + switch (event) { + case BSSGP_BVCFSM_E_RX_UNBLOCK_ACK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + /* If BVC-UNBLOCK-ACK PDU is received by an BSS for the signalling BVC, the PDU is ignored. */ + if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK-ACK on BVCI=0 is illegal\n"); + if (!bfp->role_sgsn) + break; + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + /* stop T1 timer */ + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0); + break; + case BSSGP_BVCFSM_E_RX_UNBLOCK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + /* If a BVC-UNBLOCK PDU is received by an SGSN for a blocked BVC, a BVC-UNBLOCK-ACK + * PDU shall be returned. */ + if (bfp->role_sgsn) { + /* If a BVC-UNBLOCK PDU is received by an SGSN for + * the signalling BVC, the PDU is ignored */ + if (bfp->bvci == 0) + break; + bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, bfp->nsei, bfp->bvci, 0); + } + break; + case BSSGP_BVCFSM_E_RX_BLOCK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n", + bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE))); + /* If a BVC-BLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored */ + if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BVCI=0 is illegal\n"); + if (bfp->role_sgsn) + break; + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + if (!bfp->role_sgsn) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BSS is illegal\n"); + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + /* transmit BVC-BLOCK-ACK, transition to BLOCKED state */ + tx = bssgp2_enc_bvc_block_ack(bfp->bvci); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0); + break; + case BSSGP_BVCFSM_E_REQ_BLOCK: + if (bfp->role_sgsn) { + LOGPFSML(fi, LOGL_ERROR, "SGSN may not initiate BVC-BLOCK\n"); + break; + } + if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be blocked\n"); + break; + } + bfp->locally_blocked = true; + bfp->block_cause = *(uint8_t *)data; + /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */ + tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1); + break; + case BSSGP_BVCFSM_E_RX_FC_BVC: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */ + OSMO_ASSERT(bfp->role_sgsn); + rc = bssgp2_dec_fc_bvc(&rx_fc, tp); + if (rc < 0) { + _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); + break; + } + if (bfp->ops->rx_fc_bvc) + bfp->ops->rx_fc_bvc(bfp->nsei, bfp->bvci, &rx_fc, bfp->ops_priv); + tx = bssgp2_enc_fc_bvc_ack(rx_fc.tag); + fi_tx_ptp(fi, tx); + break; + case BSSGP_BVCFSM_E_RX_FC_BVC_ACK: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */ + OSMO_ASSERT(!bfp->role_sgsn); + break; + case BSSGP_BVCFSM_E_REQ_FC_BVC: + tx_fc = data; + tx = bssgp2_enc_fc_bvc(tx_fc, bfp->features.negotiated & (BSSGP_XFEAT_GBIT << 8) ? + &bfp->features.fc_granularity : NULL); + fi_tx_ptp(fi, tx); + break; + } +} + +static void bssgp_bvc_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct bvc_fsm_priv *bfp = fi->priv; + uint8_t cause; + const struct tlv_parsed *tp = NULL; + struct msgb *rx = NULL; + + switch (event) { + case BSSGP_BVCFSM_E_REQ_RESET: + bfp->locally_blocked = false; + cause = bfp->last_reset_cause = *(uint8_t *) data; + _tx_bvc_reset(fi, cause); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2); +#if 0 /* not sure if we really should notify the application if itself has requested the reset? */ + if (bfp->ops && bfp->ops->reset_notification) + bfp->ops->reset_notification(bfp->nsei, bfp->bvci, NULL, 0, cause, bfp->ops_priv); +#endif + break; + case BSSGP_BVCFSM_E_RX_RESET: + rx = data; + tp = (const struct tlv_parsed *) msgb_bcid(rx); + cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE); + if (bfp->role_sgsn && bfp->bvci != 0) + bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID)); + LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-RESET (cause=%s)\n", bssgp_cause_str(cause)); + if (bfp->bvci == 0) + update_negotiated_features(fi, tp); + _tx_bvc_reset_ack(fi); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0); + if (bfp->ops && bfp->ops->reset_notification) { + bfp->ops->reset_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id, + cause, bfp->ops_priv); + } + break; + } +} + +static int bssgp_bvc_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct bvc_fsm_priv *bfp = fi->priv; + struct msgb *tx; + + switch (fi->T) { + case T1: + switch (fi->state) { + case BSSGP_BVCFSM_S_BLOCKED: + /* re-transmit BVC-BLOCK */ + tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1); + break; + case BSSGP_BVCFSM_S_UNBLOCKED: + /* re-transmit BVC-UNBLOCK */ + tx = bssgp2_enc_bvc_unblock(bfp->bvci); + fi_tx_sig(fi, tx); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1); + break; + } + break; + case T2: + switch (fi->state) { + case BSSGP_BVCFSM_S_WAIT_RESET_ACK: + /* re-transmit BVC-RESET */ + _tx_bvc_reset(fi, bfp->last_reset_cause); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2); + break; + case BSSGP_BVCFSM_S_UNBLOCKED: + /* re-transmit BVC-RESET-ACK */ + _tx_bvc_reset_ack(fi); + osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T2_SECS, T2); + break; + } + break; + default: + OSMO_ASSERT(0); + break; + } + return 0; +} + + + +static const struct osmo_fsm_state bssgp_bvc_fsm_states[] = { + [BSSGP_BVCFSM_S_NULL] = { + /* initial state from which we must do a RESET */ + .name = "NULL", + .in_event_mask = 0, + .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) | + S(BSSGP_BVCFSM_S_UNBLOCKED), + .action = bssgp_bvc_fsm_null, + }, + [BSSGP_BVCFSM_S_BLOCKED] = { + .name = "BLOCKED", + .in_event_mask = S(BSSGP_BVCFSM_E_RX_UNBLOCK) | + S(BSSGP_BVCFSM_E_RX_BLOCK) | + S(BSSGP_BVCFSM_E_RX_BLOCK_ACK) | + S(BSSGP_BVCFSM_E_REQ_UNBLOCK), + .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) | + S(BSSGP_BVCFSM_S_UNBLOCKED) | + S(BSSGP_BVCFSM_S_BLOCKED), + .action = bssgp_bvc_fsm_blocked, + .onenter = bssgp_bvc_fsm_blocked_onenter, + }, + [BSSGP_BVCFSM_S_WAIT_RESET_ACK]= { + .name = "WAIT_RESET_ACK", + .in_event_mask = S(BSSGP_BVCFSM_E_RX_RESET_ACK) | + S(BSSGP_BVCFSM_E_RX_RESET), + .out_state_mask = S(BSSGP_BVCFSM_S_UNBLOCKED) | + S(BSSGP_BVCFSM_S_BLOCKED) | + S(BSSGP_BVCFSM_S_WAIT_RESET_ACK), + .action = bssgp_bvc_fsm_wait_reset_ack, + .onenter = _onenter_tail, + }, + + [BSSGP_BVCFSM_S_UNBLOCKED] = { + .name = "UNBLOCKED", + .in_event_mask = S(BSSGP_BVCFSM_E_RX_BLOCK) | + S(BSSGP_BVCFSM_E_RX_UNBLOCK) | + S(BSSGP_BVCFSM_E_RX_UNBLOCK_ACK) | + S(BSSGP_BVCFSM_E_REQ_BLOCK) | + S(BSSGP_BVCFSM_E_RX_FC_BVC) | + S(BSSGP_BVCFSM_E_RX_FC_BVC_ACK) | + S(BSSGP_BVCFSM_E_REQ_FC_BVC), + .out_state_mask = S(BSSGP_BVCFSM_S_BLOCKED) | + S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) | + S(BSSGP_BVCFSM_S_UNBLOCKED), + .action = bssgp_bvc_fsm_unblocked, + .onenter = _onenter_tail, + }, +}; + +static struct osmo_fsm bssgp_bvc_fsm = { + .name = "BSSGP-BVC", + .states = bssgp_bvc_fsm_states, + .num_states = ARRAY_SIZE(bssgp_bvc_fsm_states), + .allstate_event_mask = S(BSSGP_BVCFSM_E_REQ_RESET) | + S(BSSGP_BVCFSM_E_RX_RESET), + .allstate_action = bssgp_bvc_fsm_allstate, + .timer_cb = bssgp_bvc_fsm_timer_cb, + .log_subsys = DLBSSGP, + .event_names = ptp_bvc_event_names, +}; + +static struct osmo_fsm_inst * +_bvc_fsm_alloc(void *ctx, struct gprs_ns2_inst *nsi, bool role_sgsn, uint16_t nsei, uint16_t bvci) +{ + struct osmo_fsm_inst *fi; + struct bvc_fsm_priv *bfp; + char idbuf[64]; + + /* TODO: encode our role in the id string? */ + snprintf(idbuf, sizeof(idbuf), "NSE%05u-BVC%05u", nsei, bvci); + + fi = osmo_fsm_inst_alloc(&bssgp_bvc_fsm, ctx, NULL, LOGL_INFO, idbuf); + if (!fi) + return NULL; + + bfp = talloc_zero(fi, struct bvc_fsm_priv); + if (!bfp) { + osmo_fsm_inst_free(fi); + return NULL; + } + fi->priv = bfp; + + bfp->nsi = nsi; + bfp->role_sgsn = role_sgsn; + bfp->nsei = nsei; + bfp->bvci = bvci; + bfp->max_pdu_len = UINT16_MAX; + + return fi; +} + +/*! Allocate a SIGNALING-BVC FSM for the BSS role (facing a remote SGSN). + * \param[in] ctx talloc context from which to allocate + * \param[in] nsi NS Instance on which this BVC operates + * \param[in] nsei NS Entity Identifier on which this BVC operates + * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap + * \returns newly-allocated FSM Instance; NULL in case of error */ +struct osmo_fsm_inst * +bssgp_bvc_fsm_alloc_sig_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features) +{ + struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, 0); + struct bvc_fsm_priv *bfp; + + if (!fi) + return NULL; + + bfp = fi->priv; + bfp->features.advertised = features; + + return fi; +} + +/*! Allocate a PTP-BVC FSM for the BSS role (facing a remote SGSN). + * \param[in] ctx talloc context from which to allocate + * \param[in] nsi NS Instance on which this BVC operates + * \param[in] nsei NS Entity Identifier on which this BVC operates + * \param[in] bvci BVCI of this FSM + * \param[in] ra_id Routing Area Identity of the cell (reported to SGSN) + * \param[in] cell_id Cell Identifier of the cell (reported to SGSN) + * \returns newly-allocated FSM Instance; NULL in case of error */ +struct osmo_fsm_inst * +bssgp_bvc_fsm_alloc_ptp_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, + uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id) +{ + struct osmo_fsm_inst *fi; + struct bvc_fsm_priv *bfp; + + OSMO_ASSERT(bvci >= 2); + OSMO_ASSERT(ra_id); + + fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, bvci); + if (!fi) + return NULL; + + bfp = fi->priv; + bfp->ra_id = *ra_id; + bfp->cell_id = cell_id; + + return fi; +} + +/*! Allocate a SIGNALING-BVC FSM for the SGSN role (facing a remote BSS). + * \param[in] ctx talloc context from which to allocate + * \param[in] nsi NS Instance on which this BVC operates + * \param[in] nsei NS Entity Identifier on which this BVC operates + * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap + * \returns newly-allocated FSM Instance; NULL in case of error */ +struct osmo_fsm_inst * +bssgp_bvc_fsm_alloc_sig_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features) +{ + struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, 0); + struct bvc_fsm_priv *bfp; + + if (!fi) + return NULL; + + bfp = fi->priv; + bfp->features.advertised = features; + + return fi; +} + +/*! Allocate a PTP-BVC FSM for the SGSN role (facing a remote BSS). + * \param[in] ctx talloc context from which to allocate + * \param[in] nsi NS Instance on which this BVC operates + * \param[in] nsei NS Entity Identifier on which this BVC operates + * \param[in] bvci BVCI of this FSM + * \returns newly-allocated FSM Instance; NULL in case of error */ +struct osmo_fsm_inst * +bssgp_bvc_fsm_alloc_ptp_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci) +{ + struct osmo_fsm_inst *fi; + + OSMO_ASSERT(bvci >= 2); + + fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, bvci); + if (!fi) + return NULL; + + return fi; +} + +/*! Set the 'operations' callbacks + private data. + * \param[in] fi FSM instance for which the data shall be set + * \param[in] ops BSSGP BVC FSM operations (call-back functions) to register + * \param[in] ops_priv opaque/private data pointer passed through to call-backs */ +void bssgp_bvc_fsm_set_ops(struct osmo_fsm_inst *fi, const struct bssgp_bvc_fsm_ops *ops, void *ops_priv) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + + bfp->ops = ops; + bfp->ops_priv = ops_priv; +} + +/*! Return if the given BVC FSM is in UNBLOCKED state. */ +bool bssgp_bvc_fsm_is_unblocked(struct osmo_fsm_inst *fi) +{ + return fi->state == BSSGP_BVCFSM_S_UNBLOCKED; +} + +/*! Determine the cause value why given BVC FSM is blocked. */ +uint8_t bssgp_bvc_fsm_get_block_cause(struct osmo_fsm_inst *fi) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + return bfp->block_cause; +} + +/*! Return the advertised features / extended features. */ +uint32_t bssgp_bvc_fsm_get_features_advertised(struct osmo_fsm_inst *fi) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + return bfp->features.advertised; +} + +/*! Return the received features / extended features. */ +uint32_t bssgp_bvc_fsm_get_features_received(struct osmo_fsm_inst *fi) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + return bfp->features.received; +} + +/*! Return the negotiated features / extended features. */ +uint32_t bssgp_bvc_fsm_get_features_negotiated(struct osmo_fsm_inst *fi) +{ + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + return bfp->features.negotiated; +} + +/*! Set the maximum size of a BSSGP PDU. + *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */ +void bssgp_bvc_fsm_set_max_pdu_len(struct osmo_fsm_inst *fi, uint16_t max_pdu_len) { + struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + bfp->max_pdu_len = max_pdu_len; +} + +/*! Return the maximum size of a BSSGP PDU + *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */ +uint16_t bssgp_bvc_fsm_get_max_pdu_len(const struct osmo_fsm_inst *fi) +{ + const struct bvc_fsm_priv *bfp = fi->priv; + + OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm); + return bfp->max_pdu_len; +} + + +static __attribute__((constructor)) void on_dso_load_bvc_fsm(void) +{ + OSMO_ASSERT(osmo_fsm_register(&bssgp_bvc_fsm) == 0); +} diff --git a/src/gb/common_vty.c b/src/gb/common_vty.c index a47294b5..ad3dea23 100644 --- a/src/gb/common_vty.c +++ b/src/gb/common_vty.c @@ -40,15 +40,21 @@ int gprs_log_filter_fn(const struct log_context *ctx, struct log_target *tar) { - const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC]; - const struct gprs_bvc *bvc = ctx->ctx[LOG_CTX_GB_BVC]; + const void *nse = ctx->ctx[LOG_CTX_GB_NSE]; + const void *nsvc = ctx->ctx[LOG_CTX_GB_NSVC]; + const void *bvc = ctx->ctx[LOG_CTX_GB_BVC]; + + /* Filter on the NS Entity */ + if ((tar->filter_map & (1 << LOG_FLT_GB_NSE)) != 0 + && nse && (nse == tar->filter_data[LOG_FLT_GB_NSE])) + return 1; /* Filter on the NS Virtual Connection */ if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0 && nsvc && (nsvc == tar->filter_data[LOG_FLT_GB_NSVC])) return 1; - /* Filter on the NS Virtual Connection */ + /* Filter on the BSSGP Virtual Connection */ if ((tar->filter_map & (1 << LOG_FLT_GB_BVC)) != 0 && bvc && (bvc == tar->filter_data[LOG_FLT_GB_BVC])) return 1; @@ -57,4 +63,4 @@ int gprs_log_filter_fn(const struct log_context *ctx, } -int DNS, DBSSGP; +int DNS; diff --git a/src/gb/common_vty.h b/src/gb/common_vty.h index 801d2dab..8e883319 100644 --- a/src/gb/common_vty.h +++ b/src/gb/common_vty.h @@ -3,5 +3,5 @@ #include <osmocom/vty/command.h> #include <osmocom/core/logging.h> -extern int DNS, DBSSGP; +extern int DNS; diff --git a/src/gb/frame_relay.c b/src/gb/frame_relay.c new file mode 100644 index 00000000..e973b915 --- /dev/null +++ b/src/gb/frame_relay.c @@ -0,0 +1,1051 @@ +/*! \file frame_relay.c + * Implement frame relay/PVC by Q.933 + */ +/* (C) 2020 Harald Welte <laforge@gnumonks.org> + * (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> + +#include <osmocom/gprs/frame_relay.h> +#include <osmocom/core/endian.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/tlv.h> + +#define LOGPFRL(frl, lvl, fmt, args ...) \ + LOGP(DFR, lvl, "%s: " fmt, (frl)->name, ## args) + +#define DFR DLNS + +/* Table 4-2/Q.931 */ +enum q931_msgtype { + /* Call establishment message */ + Q931_MSGT_ALERTING = 0x01, + Q931_MSGT_CALL_PROCEEDING = 0x02, + Q931_MSGT_CONNECT = 0x07, + Q931_MSGT_CONNECT_ACK = 0x0f, + Q931_MSGT_PROGRESS = 0x03, + Q931_MSGT_SETUP = 0x05, + Q931_MSGT_SETUP_ACK = 0x0d, + /* Call information phase message */ + Q931_MSGT_RESUME = 0x26, + Q931_MSGT_RESUME_ACK = 0x2e, + Q931_MSGT_RESUME_REJ = 0x22, + Q931_MSGT_SUSPEND = 0x25, + Q931_MSGT_SUSPEND_ACK = 0x2d, + Q931_MSGT_USER_INFO = 0x20, + /* Call clearing message */ + Q931_MSGT_DISCONNECT = 0x45, + Q931_MSGT_RELEASE = 0x4d, + Q931_MSGT_RELEASE_COMPLETE = 0x5a, + Q931_MSGT_RESTART = 0x46, + Q931_MSGT_RESTART_ACK = 0x4e, + /* Miscellaneous messages */ + Q931_MSGT_SEGMENT = 0x60, + Q931_MSGT_CONGESTION_CONTROL = 0x79, + Q931_MSGT_IFORMATION = 0x7b, + Q931_MSGT_NOTIFY = 0x6e, + Q931_MSGT_STATUS = 0x7d, + Q931_MSGT_STATUS_ENQUIRY = 0x75, +}; + + +/* Figure A.1/Q.933 Report type information element */ +enum q933_type_of_report { + Q933_REPT_FULL_STATUS = 0x00, + Q933_REPT_LINK_INTEGRITY_VERIF = 0x01, + Q933_REPT_SINGLE_PVC_ASYNC_STS = 0x02, +}; + +/* Q.933 Section A.3 */ +enum q933_iei { + Q933_IEI_REPORT_TYPE = 0x51, + Q933_IEI_LINK_INT_VERIF = 0x53, + Q933_IEI_PVC_STATUS = 0x57, +}; + +/* Q.933 Section A.3.3 */ +enum q933_pvc_status { + Q933_PVC_STATUS_DLC_ACTIVE = 0x02, + Q933_PVC_STATUS_DLC_DELETE = 0x04, + Q933_PVC_STATUS_DLC_NEW = 0x08, +}; + + + +#define LAPF_UI 0x03 /* UI control word */ +#define Q931_PDISC_CC 0x08 /* protocol discriminator */ +#define LMI_Q933A_CALLREF 0x00 /* NULL call-ref */ + +/* LMI DLCI values */ +#define LMI_Q933A_DLCI 0 /* Q.933A DLCI */ +#define LMI_CISCO_DLCI 1023 /* Cisco DLCI */ + +/* maximum of supported */ +#define MAX_SUPPORTED_PVC 10 + +/* TODO: add counters since good connection */ + +/* Message header of the L3 payload of a Q.933 Annex A message */ +struct q933_a_hdr { + uint8_t prot_disc; + uint8_t call_ref; + uint8_t msg_type; +} __attribute__((packed)); + +/* Value part of the Q.933 Annex A.3.3 IE */ +struct q933_a_pvc_sts { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t dlci_msb:6, + spare:1, + ext0:1; + uint8_t space1:3, + dlci_lsb:4, + ext1:1; + uint8_t reserved:1, + active:1, + delete:1, + new:1, + spare2:3, + ext2:1; + +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t ext0:1, spare:1, dlci_msb:6; + uint8_t ext1:1, dlci_lsb:4, space1:3; + uint8_t ext2:1, spare2:3, new:1, delete:1, active:1, reserved:1; +#endif +} __attribute__((packed)); + +/* RX Message: 14 [ 00 01 03 08 00 75 95 01 01 00 03 02 01 00 ] */ +/* RX Message: 13 [ 00 01 03 08 00 75 51 01 00 53 02 01 00 ] */ + +const struct value_string osmo_fr_role_names[] = { + { FR_ROLE_USER_EQUIPMENT, "USER" }, + { FR_ROLE_NETWORK_EQUIPMENT, "NETWORK" }, + { 0, NULL } +}; + +/* Table A.4/Q.933 */ +struct osmo_tdef fr_tdefs[] = { + { + .T=391, + .default_val = 10, + .min_val = 5, + .max_val = 30, + .desc = "Link integrity verification polling timer", + .unit = OSMO_TDEF_S, + }, { + .T=392, + .default_val = 15, + .min_val = 5, + .max_val = 30, + .desc = "Polling verification timer", + .unit = OSMO_TDEF_S, + }, + {} +}; + +static const struct tlv_definition q933_att_tlvdef = { + .def = { + [Q933_IEI_REPORT_TYPE] = { TLV_TYPE_TLV }, + [Q933_IEI_LINK_INT_VERIF] = { TLV_TYPE_TLV }, + [Q933_IEI_PVC_STATUS] = { TLV_TYPE_TLV }, + }, +}; + +static void check_link_state(struct osmo_fr_link *link, bool valid); + +static inline uint16_t q922_to_dlci(const uint8_t *hdr) +{ + return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4); +} + + +static inline void dlci_to_q922(uint8_t *hdr, uint16_t dlci) +{ + hdr[0] = (dlci >> 2) & 0xFC; + hdr[1] = ((dlci << 4) & 0xF0) | 0x01; +} + +static void dlc_set_active(struct osmo_fr_dlc *dlc, bool active) +{ + if (active == dlc->active) + return; + + dlc->active = active; + + LOGPFRL(dlc->link, LOGL_NOTICE, "DLCI %u became %s\n", dlc->dlci, active ? "active" : "inactive"); + if (dlc->status_cb) + dlc->status_cb(dlc, dlc->cb_data, active); +} + +/* allocate a message buffer and put Q.933 Annex A headers (L2 + L3) */ +static struct msgb *q933_msgb_alloc(uint16_t dlci, uint8_t prot_disc, uint8_t msg_type) +{ + struct msgb *msg = msgb_alloc_headroom(1600+64, 64, "FR Q.933 Tx"); + struct q933_a_hdr *qh; + + if (!msg) + return NULL; + + msg->l1h = msgb_put(msg, 2); + dlci_to_q922(msg->l1h, dlci); + + /* LAPF UI control */ + msg->l2h = msgb_put(msg, 1); + *msg->l2h = LAPF_UI; + + msg->l3h = msgb_put(msg, sizeof(*qh)); + qh = (struct q933_a_hdr *) msg->l3h; + qh->prot_disc = prot_disc; + qh->call_ref = LMI_Q933A_CALLREF; + qh->msg_type = msg_type; + + return msg; +} + +/* obtain the [next] transmit sequence number */ +static uint8_t link_get_tx_seq(struct osmo_fr_link *link) +{ + /* The {user equipment, network} increments the send sequence + * counter using modulo 256. The value zero is skipped. */ + link->last_tx_seq++; + if (link->last_tx_seq == 0) + link->last_tx_seq++; + + return link->last_tx_seq; +} + +/* Append PVC Status IE according to Q.933 A.3.2 */ +static void msgb_put_link_int_verif(struct msgb *msg, struct osmo_fr_link *link) +{ + uint8_t link_int_tx[2]; + link_int_tx[0] = link_get_tx_seq(link); + link_int_tx[1] = link->last_rx_seq; + msgb_tlv_put(msg, Q933_IEI_LINK_INT_VERIF, 2, link_int_tx); +} + +static void dlc_destroy(struct osmo_fr_dlc *dlc) +{ + llist_del(&dlc->list); + talloc_free(dlc); +} + +/* Append PVC Status IE according to Q.933 A.3.3 */ +static void msgb_put_pvc_status(struct msgb *msg, struct osmo_fr_dlc *dlc) +{ + uint8_t ie[3]; + + ie[0] = (dlc->dlci >> 4) & 0x3f; + /* extension bits */ + ie[1] = 0x80 | ((dlc->dlci & 0xf) << 3); + /* extension bits */ + ie[2] = 0x80; + + /* FIXME: validate: this status should be added as long it's not yet acked by the remote */ + if (dlc->active) + ie[2] |= Q933_PVC_STATUS_DLC_ACTIVE; + + if (dlc->add) { + ie[2] |= Q933_PVC_STATUS_DLC_NEW; + /* we've reported it as new once, reset the status */ + } + + if (dlc->del) { + ie[2] |= Q933_PVC_STATUS_DLC_DELETE; + /* we've reported it as deleted once, destroy it */ + dlc_destroy(dlc); + } + + msgb_tlv_put(msg, Q933_IEI_PVC_STATUS, 3, ie); +} + +/* Send a Q.933 STATUS ENQUIRY given type over given link */ +static int tx_lmi_q933_status_enq(struct osmo_fr_link *link, uint8_t rep_type) +{ + struct msgb *resp; + + resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS_ENQUIRY); + if (!resp) + return -1; + resp->dst = link; + link->expected_rep = rep_type; + + /* Table A.2/Q.933 */ + msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type); + msgb_put_link_int_verif(resp, link); + + return link->tx_cb(link->cb_data, resp); +} + +/* Send a Q.933 STATUS of given type over given link */ +static int tx_lmi_q933_status(struct osmo_fr_link *link, uint8_t rep_type) +{ + struct osmo_fr_dlc *dlc; + struct msgb *resp; + + resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS); + if (!resp) + return -1; + + resp->dst = link; + + /* Table A.1/Q.933 */ + msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type); + switch (rep_type) { + case Q933_REPT_FULL_STATUS: + msgb_put_link_int_verif(resp, link); + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->add || dlc->del) + dlc->state_send = true; + + msgb_put_pvc_status(resp, dlc); + } + break; + case Q933_REPT_LINK_INTEGRITY_VERIF: + msgb_put_link_int_verif(resp, link); + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->add || dlc->del) { + msgb_put_pvc_status(resp, dlc); + dlc->state_send = true; + } + } + break; + case Q933_REPT_SINGLE_PVC_ASYNC_STS: + llist_for_each_entry(dlc, &link->dlc_list, list) + msgb_put_pvc_status(resp, dlc); + break; + } + + return link->tx_cb(link->cb_data, resp); +} + + +static void link_set_failed(struct osmo_fr_link *link) +{ + struct osmo_fr_dlc *dlc; + + LOGPFRL(link, LOGL_NOTICE, "Link failed\n"); + link->state = false; + if (link->status_cb) + link->status_cb(link, link->cb_data, link->state); + + llist_for_each_entry(dlc, &link->dlc_list, list) { + dlc_set_active(dlc, false); + } +} + +/* Q.933 */ +static int rx_lmi_q933_status_enq(struct msgb *msg, struct tlv_parsed *tp) +{ + struct osmo_fr_link *link = msg->dst; + struct osmo_fr_dlc *dlc; + const uint8_t *link_int_rx; + uint8_t rep_type; + + OSMO_ASSERT(link); + + if (link->role == FR_ROLE_USER_EQUIPMENT) { + LOGPFRL(link, LOGL_ERROR, "STATUS-ENQ aren't supported in role user\n"); + return -1; + } + + /* check for mandatory IEs */ + if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1) || + !TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) + return -1; + + rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE); + + link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF); + link->last_rx_seq = link_int_rx[0]; + + /* this is a bit of a hack. Q.933 explicitly forbids either side from ever + * sending a sequence number of '0'. Values start from '1' and are modulo 256, + * but '0' is always skipped. So if the peer is sending us a "last received + * sequence number of '0' it means it has not yet received any packets from us, + * which in turn can only mean that it has just been restarted. Let's treat + * this as "service affecting condition" and notify upper layers. This helps + * particularly in recovering from rapidly re-starting peers, where the Q.933 + * nor NS have time to actually detect the connection was lost. Se OS#4974 */ + if (link_int_rx[1] == 0) { + link_set_failed(link); + /* the network checks the receive sequence number received from + * the user equipment against its send sequence counter */ + } else if (link_int_rx[1] != link->last_tx_seq) { + check_link_state(link, false); + link->err_count++; + } else { + check_link_state(link, true); + /* confirm DLC state changes */ + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (!dlc->state_send) + continue; + + if (dlc->add) { + dlc_set_active(dlc, link->state); + dlc->add = false; + } + + if (dlc->del) { + dlc->del = false; + } + + dlc->state_send = false; + } + } + + + /* The network responds to each STATUS ENQUIRY message with a + * STATUS message and resets the T392 timer */ + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); + + return tx_lmi_q933_status(link, rep_type); +} + +/* check if the link become active. + * The link becomes active when enough times a STATUS/STATUS ENQUIRY arrives without any loss. + * Look at the last N393 STATUS/STATUS ENQUIRY PDUs. The link is valid if at least N392 + * got received. + * param[in] valid contains the status of the last packet */ +static void check_link_state(struct osmo_fr_link *link, bool valid) +{ + unsigned int last, i; + unsigned int carry = 0; + struct osmo_fr_dlc *dlc; + + link->succeed <<= 1; + if (valid) + link->succeed |= 1; + + /* count the bits */ + last = link->succeed & ((1 << link->net->n393) - 1); + for (i = 0; i < link->net->n393; i++) + if (last & (1 << i)) + carry++; + + if (link->net->n393 - carry >= link->net->n392) { + /* failing link */ + if (!link->state) + return; + + link_set_failed(link); + } else { + /* good link */ + if (link->state) + return; + + LOGPFRL(link, LOGL_NOTICE, "Link recovered\n"); + link->state = true; + if (link->status_cb) + link->status_cb(link, link->cb_data, link->state); + + if (link->role == FR_ROLE_USER_EQUIPMENT) { + /* make sure the next STATUS ENQUIRY is for a full + * status report to get the configred DLCs ASAP */ + link->polling_count = 0; + /* we must not proceed further below if we're in user role, + * as otherwise link recovery would set all DLCs as active */ + return; + } + + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (!dlc->add && !dlc->del) + dlc_set_active(dlc, true); + } + } +} + +static int validate_pvc_status(struct tlv_parsed *tp, size_t tp_len) +{ + size_t i; + uint16_t len = 0; + + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + + /* PVC status can be 2 or 3 bytes. If the PVC is bigger + * ignore this to be compatible to future extensions. */ + len = TLVP_LEN(&tp[i], Q933_IEI_PVC_STATUS); + if (len <= 1) { + return -EINVAL; + } + /* FIXME: validate correct flags: are some flags invalid at the same time? */ + } + + return 0; +} + +static int parse_full_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len) +{ + size_t i; + int err = 0; + struct osmo_fr_dlc *dlc, *tmp; + struct q933_a_pvc_sts *pvc; + uint16_t dlci = 0; + uint16_t *dlcis = talloc_zero_array(link, uint16_t, tp_len); + if (!dlcis) + return -ENOMEM; + + /* first run validate all PVCs */ + err = validate_pvc_status(tp, tp_len); + if (err < 0) + goto out; + + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + + /* parse only 3 byte PVCs */ + pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN( + &tp[i], + Q933_IEI_PVC_STATUS, + sizeof(struct q933_a_pvc_sts)); + if (!pvc) + continue; + + dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf); + dlcis[i] = dlci; + dlc = osmo_fr_dlc_by_dlci(link, dlci); + if (!dlc) { + dlc = osmo_fr_dlc_alloc(link, dlci); + if (!dlc) { + LOGPFRL(link, LOGL_ERROR, "Could not create DLC %d\n", dlci); + continue; + } + } + + /* Figure A.3/Q.933: The delete bit is only applicable for timely notification + * using the optional single PVC asynchronous status report. + * Ignoring the delete. */ + dlc->add = pvc->new; + dlc_set_active(dlc, pvc->active); + dlc->del = 0; + } + + /* check if all dlc are present in PVC Status */ + llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) { + bool found = false; + for (i = 0; i < tp_len; i++) { + if (dlcis[i] == dlc->dlci) { + found = true; + break; + } + } + + if (!found) { + dlc_set_active(dlc, false); + dlc->del = true; + } + } + + return 0; +out: + talloc_free(dlcis); + return err; +} + +static int parse_link_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len) +{ + int err; + size_t i; + struct q933_a_pvc_sts *pvc; + struct osmo_fr_dlc *dlc; + uint16_t dlci = 0; + + err = validate_pvc_status(tp, tp_len); + if (err < 0) + return err; + + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + + /* parse only 3 byte PVCs */ + pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN( + &tp[i], + Q933_IEI_PVC_STATUS, + sizeof(struct q933_a_pvc_sts)); + if (!pvc) + continue; + + dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf); + dlc = osmo_fr_dlc_by_dlci(link, dlci); + if (!dlc) { + /* don't create dlc's for the ones which are about to be deleted. */ + if (pvc->delete) + continue; + + dlc = osmo_fr_dlc_alloc(link, dlci); + if (!dlc) { + LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Could not create DLC %d\n", dlci); + continue; + } + } + + if (pvc->delete) { + dlc->del = 1; + } else { + dlc->add = pvc->new; + dlc_set_active(dlc, pvc->active); + dlc->del = 0; + } + } + + return 0; +} + +static size_t count_pvc_status(struct tlv_parsed *tp, size_t tp_len) +{ + size_t i, count = 0; + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + count++; + } + + return count; +} + +static int rx_lmi_q933_status(struct msgb *msg, struct tlv_parsed *tp) +{ + struct osmo_fr_link *link = msg->dst; + const uint8_t *link_int_rx; + uint8_t rep_type; + + OSMO_ASSERT(link); + + if (link->role == FR_ROLE_NETWORK_EQUIPMENT) { + LOGPFRL(link, LOGL_ERROR, "Rx STATUS: STATUS aren't supported in role network\n"); + return -1; + } + + /* check for mandatory IEs */ + if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1)) { + LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Report Type\n"); + return -1; + } + + rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE); + + switch (rep_type) { + case Q933_REPT_FULL_STATUS: + case Q933_REPT_LINK_INTEGRITY_VERIF: + if (rep_type != link->expected_rep) { + LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Unexpected Q933 report type (got 0x%x != exp 0x%x)\n", + rep_type, link->expected_rep); + return -1; + } + + if (!TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) { + LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Link Integrety Verification\n"); + return -1; + } + link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF); + link->last_rx_seq = link_int_rx[0]; + /* The received receive sequence number is not valid if + * it is not equal to the last transmitted send sequence + * number. Ignore messages containing this error. As a + * result, timer T391 expires and the user then + * increments the error count. */ + if (link_int_rx[1] != link->last_tx_seq) + return 0; + break; + case Q933_REPT_SINGLE_PVC_ASYNC_STS: + default: + return -1; + } + + check_link_state(link, true); + if (count_pvc_status(tp, MAX_SUPPORTED_PVC + 1) > MAX_SUPPORTED_PVC) { + LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Too many PVC! Only %d are supported!\n", MAX_SUPPORTED_PVC); + } + + switch (rep_type) { + case Q933_REPT_FULL_STATUS: + parse_full_pvc_status(link, tp, MAX_SUPPORTED_PVC); + break; + case Q933_REPT_LINK_INTEGRITY_VERIF: + parse_link_pvc_status(link, tp, MAX_SUPPORTED_PVC); + break; + default: + break; + } + + /* The network responds to each STATUS ENQUIRY message with a + * STATUS message and resets the T392 timer */ + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); + + return 0; +} + +static int rx_lmi_q922(struct msgb *msg) +{ + struct osmo_fr_link *link = msg->dst; + struct q933_a_hdr *qh; + /* the + 1 is used to detect more than MAX_SUPPORTED_PVC */ + struct tlv_parsed tp[MAX_SUPPORTED_PVC + 1]; + uint8_t *lapf; + int rc; + + OSMO_ASSERT(link); + + if (msgb_l2len(msg) < 1) + return -1; + lapf = msgb_l2(msg); + + /* we only support LAPF UI frames */ + if (lapf[0] != LAPF_UI) + return -1; + + msg->l3h = msg->l2h + 1; + if (msgb_l3len(msg) < 3) + return -1; + + qh = (struct q933_a_hdr *) msgb_l3(msg); + if (qh->prot_disc != Q931_PDISC_CC) { + LOGPFRL(link, LOGL_NOTICE, + "Rx unsupported LMI protocol discriminator %u\n", qh->prot_disc); + return -1; + } + + rc = tlv_parse2(tp, MAX_SUPPORTED_PVC + 1, &q933_att_tlvdef, + msgb_l3(msg) + sizeof(*qh), + msgb_l3len(msg) - sizeof(*qh), 0, 0); + if (rc < 0) { + LOGPFRL(link, LOGL_NOTICE, + "Failed to parse TLVs in LMI message type %u\n", qh->msg_type); + return rc; + } + + switch (qh->msg_type) { + case Q931_MSGT_STATUS_ENQUIRY: + rc = rx_lmi_q933_status_enq(msg, tp); + break; + case Q931_MSGT_STATUS: + rc = rx_lmi_q933_status(msg, tp); + break; + default: + LOGPFRL(link, LOGL_NOTICE, + "Rx unsupported LMI message type %u\n", qh->msg_type); + rc = -1; + break; + } + msgb_free(msg); + + return rc; +} + +int osmo_fr_rx(struct msgb *msg) +{ + int rc = 0; + uint8_t *frh; + uint16_t dlci; + struct osmo_fr_dlc *dlc; + struct osmo_fr_link *link = msg->dst; + + OSMO_ASSERT(link); + + if (msgb_length(msg) < 2) { + LOGPFRL(link, LOGL_ERROR, "Rx short FR header: %u bytes\n", msgb_length(msg)); + rc = -1; + goto out; + } + + frh = msg->l1h = msgb_data(msg); + if (frh[0] & 0x01) { + LOGPFRL(link, LOGL_NOTICE, "Rx Unsupported single-byte FR address\n"); + rc = -1; + goto out; + } + if ((frh[1] & 0x0f) != 0x01) { + LOGPFRL(link, LOGL_NOTICE, "Rx Unknown second FR octet 0x%02x\n", frh[1]); + rc = -1; + goto out; + } + dlci = q922_to_dlci(frh); + msg->l2h = frh + 2; + + switch (dlci) { + case LMI_Q933A_DLCI: + return rx_lmi_q922(msg); + case LMI_CISCO_DLCI: + LOGPFRL(link, LOGL_ERROR, "Rx Unsupported FR DLCI %u\n", dlci); + goto out; + } + + if (!link->state) { + LOGPFRL(link, LOGL_NOTICE, "Link is not reliable. Discarding Rx PDU on DLCI %d\n", dlci); + goto out; + } + + dlc = osmo_fr_dlc_by_dlci(link, dlci); + if (dlc) { + if (dlc->active) { + /* dispatch to handler of respective DLC */ + msg->dst = dlc; + return dlc->rx_cb(dlc->cb_data, msg); + } else { + LOGPFRL(link, LOGL_NOTICE, "DLCI %u not yet active. Discarding Rx PDU\n", dlci); + } + } else { + if (link->unknown_dlc_rx_cb) + return link->unknown_dlc_rx_cb(link->unknown_dlc_rx_cb_data, msg); + else + LOGPFRL(link, LOGL_NOTICE, "DLCI %u doesn't exist. Discarding Rx PDU\n", dlci); + } + +out: + msgb_free(msg); + + return rc; +} + +int osmo_fr_tx_dlc(struct msgb *msg) +{ + uint8_t *frh; + struct osmo_fr_dlc *dlc = msg->dst; + struct osmo_fr_link *link = dlc->link; + + OSMO_ASSERT(dlc); + OSMO_ASSERT(link); + + if (!link->state) { + LOGPFRL(link, LOGL_NOTICE, "Link is not reliable (yet), discarding Tx\n"); + msgb_free(msg); + return -1; + } + if (!dlc->active) { + LOGPFRL(link, LOGL_NOTICE, "DLCI %u is not active (yet), discarding Tx\n", dlc->dlci); + msgb_free(msg); + return -1; + } + LOGPFRL(link, LOGL_DEBUG, "DLCI %u is active, sending message\n", dlc->dlci); + + if (msgb_headroom(msg) < 2) { + msgb_free(msg); + return -ENOSPC; + } + + frh = msgb_push(msg, 2); + dlci_to_q922(frh, dlc->dlci); + + msg->dst = link; + return link->tx_cb(link->cb_data, msg); +} + +/* Every T391 seconds, the user equipment sends a STATUS ENQUIRY + * message to the network and resets its polling timer (T391). */ +static void fr_t391_cb(void *data) +{ + struct osmo_fr_link *link = data; + + OSMO_ASSERT(link); + + if (link->polling_count % link->net->n391 == 0) + tx_lmi_q933_status_enq(link, Q933_REPT_FULL_STATUS); + else + tx_lmi_q933_status_enq(link, Q933_REPT_LINK_INTEGRITY_VERIF); + link->polling_count++; + osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 10), 0); +} + +static void fr_t392_cb(void *data) +{ + struct osmo_fr_link *link = data; + + OSMO_ASSERT(link); + + /* A.5 The network increments the error count .. Non-receipt of + * a STATUS ENQUIRY within T392, which results in restarting + * T392 */ + link->err_count++; + check_link_state(link, false); + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); +} + +/* allocate a frame relay network */ +struct osmo_fr_network *osmo_fr_network_alloc(void *ctx) +{ + struct osmo_fr_network *net = talloc_zero(ctx, struct osmo_fr_network); + if (!net) + return NULL; + + INIT_LLIST_HEAD(&net->links); + net->T_defs = fr_tdefs; + osmo_tdefs_reset(net->T_defs); + net->n391 = 6; + net->n392 = 3; + net->n393 = 4; + + return net; +} + +void osmo_fr_network_free(struct osmo_fr_network *net) +{ + struct osmo_fr_link *link, *tmp; + + if (!net) + return; + + llist_for_each_entry_safe(link, tmp, &net->links, list) { + osmo_fr_link_free(link); + } +} + +/* allocate a frame relay link in a given network */ +struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name) +{ + struct osmo_fr_link *link = talloc_zero(net, struct osmo_fr_link); + if (!link) + return NULL; + link->role = role; + link->net = net; + link->name = talloc_strdup(link, name); + INIT_LLIST_HEAD(&link->dlc_list); + llist_add_tail(&link->list, &net->links); + + osmo_timer_setup(&link->t391, fr_t391_cb, link); + osmo_timer_setup(&link->t392, fr_t392_cb, link); + + switch (role) { + case FR_ROLE_USER_EQUIPMENT: + osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 15), 0); + break; + case FR_ROLE_NETWORK_EQUIPMENT: + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); + break; + } + + LOGPFRL(link, LOGL_INFO, "Creating frame relay link with role %s\n", osmo_fr_role_str(role)); + + return link; +} + +void osmo_fr_link_free(struct osmo_fr_link *link) +{ + struct osmo_fr_dlc *dlc, *tmp; + + if (!link) + return; + + osmo_timer_del(&link->t391); + osmo_timer_del(&link->t392); + + llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) { + osmo_fr_dlc_free(dlc); + } + + llist_del(&link->list); + talloc_free(link); +} + +/* allocate a data link connectoin on a given framerelay link */ +struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci) +{ + struct osmo_fr_dlc *dlc = talloc_zero(link, struct osmo_fr_dlc); + if (!dlc) + return NULL; + + dlc->link = link; + dlc->dlci = dlci; + dlc->active = false; + + llist_add_tail(&dlc->list, &link->dlc_list); + + dlc->add = true; + tx_lmi_q933_status(link, Q933_REPT_SINGLE_PVC_ASYNC_STS); + + return dlc; +} + +void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc) +{ + llist_del(&dlc->list); + talloc_free(dlc); +} + +/* TODO: rework osmo_fr_dlc_alloc/free with handling it's own memory. + * For network role: The dlc have to created by the application (e.g. vty). + * The dlc shouldn't free'd directly. It should be communicated to the + * other side and wait until it's confirmed OR the link go off and free it afterwards. + * For user equpment role: The dlc can be created by the application or the dlc will be created + * by the frame relay because the network is configuring the dlc. + * The dlc shouldn't be free'd. Only the handler should be set to NULL. + */ + +struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci) +{ + struct osmo_fr_dlc *dlc; + + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->dlci == dlci) + return dlc; + } + return NULL; +} + + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/tdef_vty.h> + +static void fr_dlc_dump_vty(struct vty *vty, const struct osmo_fr_dlc *dlc) +{ + vty_out(vty, " FR DLC %05u: %s%s%s%s", dlc->dlci, + dlc->active ? "ACTIVE" : "INACTIVE", + dlc->add ? " ADDED" : "", dlc->del ? " DELETED" : "", VTY_NEWLINE); +} + +static void fr_link_dump_vty(struct vty *vty, const struct osmo_fr_link *link) +{ + const struct osmo_fr_dlc *dlc; + + vty_out(vty, "FR Link '%s': Role %s, LastRxSeq %u, LastTxSeq %u%s", + link->name, link->role == FR_ROLE_USER_EQUIPMENT ? "USER" : "NETWORK", + link->last_rx_seq, link->last_tx_seq, VTY_NEWLINE); + llist_for_each_entry(dlc, &link->dlc_list, list) { + fr_dlc_dump_vty(vty, dlc); + } +} + +void osmo_fr_network_dump_vty(struct vty *vty, const struct osmo_fr_network *net) +{ + struct osmo_fr_link *link; + + vty_out(vty, "FR Network: N391 %u, N392 %u, N393 %u%s", + net->n391, net->n392, net->n393, VTY_NEWLINE); + osmo_tdef_vty_out_all(vty, net->T_defs, " "); + llist_for_each_entry(link, &net->links, list) { + fr_link_dump_vty(vty, link); + } +} diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c index 38794c28..d436f0aa 100644 --- a/src/gb/gprs_bssgp.c +++ b/src/gb/gprs_bssgp.c @@ -40,10 +40,16 @@ #include <osmocom/gprs/gprs_bssgp_bss.h> #include <osmocom/gprs/gprs_ns.h> -#include "common_vty.h" +#include "osmocom/gsm/gsm48.h" +#include "gprs_bssgp_internal.h" void *bssgp_tall_ctx = NULL; +static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg); + +bssgp_bvc_send bssgp_ns_send = _gprs_ns_sendmsg; +void *bssgp_ns_send_data = NULL; + static const struct rate_ctr_desc bssgp_ctr_description[] = { { "packets:in", "Packets at BSSGP Level ( In)" }, { "packets:out","Packets at BSSGP Level (Out)" }, @@ -67,6 +73,14 @@ LLIST_HEAD(bssgp_bvc_ctxts); static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg, uint32_t llc_pdu_len, void *priv); + +/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */ +static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg) +{ + OSMO_ASSERT(bssgp_nsi); + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + /* Find a BTS Context based on parsed RA ID and Cell ID */ struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid) { @@ -80,6 +94,75 @@ struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t return NULL; } +/* Transmit a BVC-RESET or BVC-RESET-ACK with a given nsei and bvci (Chapter 10.4.12) + * \param[in] pdu Either BSSGP_PDUT_BVC_RESET or BSSGP_PDUT_BVC_RESET_ACK + * \param[in] nsei The NSEI to transmit over + * \param[in] bvci BVCI of the BVC to reset + * \param[in] cause The cause of the reset only valid for BSSGP_PDUT_BVC_RESET. + * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included + * \param[in] cell_id The cell_id to include (if ra_id is not NULL) + * returns >= 0 on success, on error < 0. + */ +static int tx_bvc_reset_nsei_bvci(enum bssgp_pdu_type pdu, uint16_t nsei, uint16_t bvci, + enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint16_t _bvci = osmo_htons(bvci); + + OSMO_ASSERT(pdu == BSSGP_PDUT_BVC_RESET || pdu == BSSGP_PDUT_BVC_RESET_ACK); + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = BVCI_SIGNALLING; + bgph->pdu_type = pdu; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + + if (pdu == BSSGP_PDUT_BVC_RESET) { + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause); + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET " + "CAUSE=%s\n", bvci, bssgp_cause_str(cause)); + } else { + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET-ACK\n", bvci); + } + + if (ra_id) { + uint8_t bssgp_cid[8]; + bssgp_create_cell_id(bssgp_cid, ra_id, cell_id); + msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid); + } + + /* Optional: Feature Bitmap */ + + return bssgp_ns_send(bssgp_ns_send_data, msg); +} + +/*! Transmit a BVC-RESET message with a given nsei and bvci (Chapter 10.4.12) + * \param[in] nsei The NSEI to transmit over + * \param[in] bvci BVCI of the BVC to reset + * \param[in] cause The cause of the reset + * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included + * \param[in] cell_id The cell_id to include (if ra_id is not NULL) + * returns >= 0 on success, on error < 0. + */ +int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id) +{ + return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET, nsei, bvci, cause, ra_id, cell_id); +} + +/*! Transmit a BVC-RESET-ACK message with a given nsei and bvci (Chapter 10.4.12) + * \param[in] nsei The NSEI to transmit over + * \param[in] bvci BVCI of the BVC to reset + * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included + * \param[in] cell_id The cell_id to include (if ra_id is not NULL) + * returns >= 0 on success, on error < 0. + */ +int bssgp_tx_bvc_reset_ack_nsei_bvci(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id) +{ + return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET_ACK, nsei, bvci, 0, ra_id, cell_id); +} + /*! Initiate reset procedure for all PTP BVC on a given NSEI. * * This function initiates reset procedure for all PTP BVC with a given cause. @@ -94,7 +177,7 @@ int bssgp_tx_bvc_ptp_reset(uint16_t nsei, enum gprs_bssgp_cause cause) llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) { if (bctx->nsei == nsei && bctx->bvci != BVCI_SIGNALLING) { - LOGP(DBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n", + LOGP(DLBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n", nsei, bctx->bvci, bssgp_cause_str(cause)); rc = bssgp_tx_bvc_reset(bctx, bctx->bvci, cause); if (rc < 0) @@ -117,6 +200,12 @@ struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei) return NULL; } +void bssgp_set_bssgp_callback(bssgp_bvc_send ns_send, void *data) +{ + bssgp_ns_send = ns_send; + bssgp_ns_send_data = data; +} + struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei) { struct bssgp_bvc_ctx *ctx; @@ -126,25 +215,36 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei) return NULL; ctx->bvci = bvci; ctx->nsei = nsei; + ctx->is_sgsn = true; /* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */ ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci); - if (!ctx->ctrg) { - talloc_free(ctx); - return NULL; - } + if (!ctx->ctrg) + goto err_ctrg; + ctx->fc = talloc_zero(ctx, struct bssgp_flow_control); + if (!ctx->fc) + goto err_fc; + /* cofigure for 2Mbit, 30 packets in queue */ bssgp_fc_init(ctx->fc, 100000, 2*1024*1024/8, 30, &_bssgp_tx_dl_ud); llist_add(&ctx->list, &bssgp_bvc_ctxts); return ctx; + +err_fc: + rate_ctr_group_free(ctx->ctrg); +err_ctrg: + talloc_free(ctx); + return NULL; } void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx) { if (!ctx) return; + + osmo_timer_del(&ctx->fc->timer); rate_ctr_group_free(ctx->ctrg); llist_del(&ctx->list); talloc_free(ctx); @@ -163,7 +263,7 @@ static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci) bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK; msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* 10.3.7 SUSPEND-ACK PDU */ @@ -182,7 +282,7 @@ int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli, bssgp_msgb_ra_put(msg, ra_id); msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* 10.3.8 SUSPEND-NACK PDU */ @@ -204,7 +304,7 @@ int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli, if (cause) msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* 10.3.10 RESUME-ACK PDU */ @@ -222,7 +322,7 @@ int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli, bssgp_msgb_tlli_put(msg, tlli); bssgp_msgb_ra_put(msg, ra_id); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* 10.3.11 RESUME-NACK PDU */ @@ -243,7 +343,7 @@ int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli, if (cause) msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf) @@ -254,6 +354,24 @@ uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf) return osmo_load16be(buf+6); } +/*! Parse the value of a BSSGP Cell identity (04.08 RAI + Cell Id) */ +int bssgp_parse_cell_id2(struct osmo_routing_area_id *raid, uint16_t *cid, + const uint8_t *buf, size_t buf_len) +{ + if (buf_len < 8) + return -EINVAL; + + /* 6 octets RAC */ + if (raid) + osmo_routing_area_id_decode(raid, buf, buf_len); + + /* 2 octets CID */ + if (cid) + *cid = osmo_load16be(buf + sizeof(struct gsm48_ra_id)); + + return 0; +} + int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid, uint16_t cid) { @@ -265,8 +383,25 @@ int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid, return 8; } +/*! Encode the 04.08 RAI, Cell Id into BSSGP Cell identity */ +int bssgp_create_cell_id2(uint8_t *buf, size_t buf_len, + const struct osmo_routing_area_id *raid, + const uint16_t cid) +{ + if (buf_len < 8) + return -ENOMEM; + + /* 6 octets RAC */ + osmo_routing_area_id_encode_buf(buf, buf_len, raid); + + /* 2 octets CID */ + osmo_store16be(cid, buf + sizeof(struct gsm48_ra_id)); + + return 8; +} + /* Chapter 8.4 BVC-Reset Procedure */ -static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp, +static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp, uint16_t ns_bvci) { struct osmo_bssgp_prim nmp; @@ -275,7 +410,7 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci; bvci = tlvp_val16be(tp, BSSGP_IE_BVCI); - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci, + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci, bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE))); /* look-up or create the BTS context for this BVC */ @@ -288,19 +423,26 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp, /* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS * informs us about its RAC + Cell ID, so we can create a mapping */ - if (bvci != 0 && bvci != 1) { - if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET " + if (bctx->is_sgsn && bvci != BVCI_SIGNALLING && bvci != BVCI_PTM) { + if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET " "missing mandatory IE\n", bvci); return -EINVAL; } /* actually extract RAC / CID */ bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID)); - LOGP(DBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n", + LOGP(DLBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n", osmo_rai_name(&bctx->ra_id), bctx->cell_id, bvci); } + /* Acknowledge the RESET to the BTS */ + if (bvci == BVCI_SIGNALLING || bvci == BVCI_PTM || bctx->is_sgsn) + bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, + nsei, bvci, ns_bvci); + else + bssgp_tx_bvc_reset_ack_nsei_bvci(nsei, bvci, &bctx->ra_id, bctx->cell_id); + /* Send NM_BVC_RESET.ind to NM */ memset(&nmp, 0, sizeof(nmp)); nmp.nsei = nsei; @@ -310,10 +452,6 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp, osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_RESET, PRIM_OP_INDICATION, msg); bssgp_prim_cb(&nmp.oph, NULL); - - /* Acknowledge the RESET to the BTS */ - bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, - nsei, bvci, ns_bvci); return 0; } @@ -326,20 +464,20 @@ static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp) bvci = tlvp_val16be(tp, BSSGP_IE_BVCI); if (bvci == BVCI_SIGNALLING) { /* 8.3.2: Signalling BVC shall never be blocked */ - LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " + LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " "received block for signalling BVC!?!\n", nsei, msgb_bvci(msg)); return 0; } - LOGP(DBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci); + LOGP(DLBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci); ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei); if (!ptp_ctx) return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg); ptp_ctx->state |= BVC_S_BLOCKED; - rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]); + rate_ctr_inc(rate_ctr_group_get_ctr(ptp_ctx->ctrg, BSSGP_CTR_BLOCKED)); /* Send NM_BVC_BLOCK.ind to NM */ memset(&nmp, 0, sizeof(nmp)); @@ -364,13 +502,13 @@ static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp) bvci = tlvp_val16be(tp, BSSGP_IE_BVCI); if (bvci == BVCI_SIGNALLING) { /* 8.3.2: Signalling BVC shall never be blocked */ - LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " + LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " "received unblock for signalling BVC!?!\n", nsei, msgb_bvci(msg)); return 0; } - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci); + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci); ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei); if (!ptp_ctx) @@ -402,12 +540,12 @@ static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp, /* extract TLLI and parse TLV IEs */ msgb_tlli(msg) = osmo_ntohl(budh->tlli); - DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg)); + DEBUGP(DLBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg)); /* Cell ID and LLC_PDU are the only mandatory IE */ - if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) || + if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8) || !TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD " + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD " "missing mandatory IE\n", msgb_tlli(msg)); return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } @@ -435,16 +573,16 @@ static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp) uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg); int rc; - if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || - !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) || + !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND " "missing mandatory IE\n", ns_bvci); return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } tlli = tlvp_val32be(tp, BSSGP_IE_TLLI); - DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n", ns_bvci, tlli); gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); @@ -476,10 +614,10 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp) uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg); int rc; - if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || - !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) || - !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4 ) || + !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6) || + !TLVP_PRES_LEN(tp, BSSGP_IE_SUSPEND_REF_NR, 1)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME " "missing mandatory IE\n", ns_bvci); return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } @@ -487,7 +625,7 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp) tlli = tlvp_val32be(tp, BSSGP_IE_TLLI); suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR); - DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli); + DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli); gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); @@ -518,21 +656,21 @@ static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp, uint32_t tlli = 0; uint16_t nsei = msgb_nsei(msg); - if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || - !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) || - !TLVP_PRESENT(tp, BSSGP_IE_BVCI) || - !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) || + !TLVP_PRES_LEN(tp, BSSGP_IE_LLC_FRAMES_DISCARDED, 1) || + !TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) || + !TLVP_PRES_LEN(tp, BSSGP_IE_NUM_OCT_AFF, 3)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED " "missing mandatory IE\n", ctx->bvci); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } - if (TLVP_PRESENT(tp, BSSGP_IE_TLLI)) - tlli = tlvp_val32be(tp, BSSGP_IE_TLLI); + tlli = tlvp_val32be(tp, BSSGP_IE_TLLI); - DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n", ctx->bvci, tlli); - rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctx->ctrg, BSSGP_CTR_DISCARDED)); /* send NM_LLC_DISCARDED to NM */ memset(&nmp, 0, sizeof(nmp)); @@ -553,27 +691,27 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp, struct osmo_bssgp_prim nmp; enum gprs_bssgp_cause cause; - if (!TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS " "missing mandatory IE\n", bvci); cause = BSSGP_CAUSE_PROTO_ERR_UNSPEC; } else { cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE); } - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n", + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n", bvci, bssgp_cause_str(cause)); if (cause == BSSGP_CAUSE_BVCI_BLOCKED || cause == BSSGP_CAUSE_UNKNOWN_BVCI) { - if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) - LOGP(DBSSGP, LOGL_ERROR, + if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS cause=%s " "missing conditional BVCI IE\n", bvci, bssgp_cause_str(cause)); } if (bctx) - rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_STATUS]); + rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_STATUS)); /* send NM_STATUS to NM */ memset(&nmp, 0, sizeof(nmp)); @@ -586,7 +724,6 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp, return bssgp_prim_cb(&nmp.oph, NULL); } - /* One element (msgb) in a BSSGP Flow Control queue */ struct bssgp_fc_queue_element { /* linked list of queue elements */ @@ -618,7 +755,7 @@ static void fc_timer_cb(void *data) list); if (bssgp_fc_needs_queueing(fc, fcqe->llc_pdu_len)) { - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still " + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still " "not able to send PDU of %u bytes\n", fcqe->llc_pdu_len); /* make sure we re-start the timer */ fc_queue_timer_cfg(fc); @@ -744,7 +881,7 @@ static int bssgp_fc_needs_queueing(struct bssgp_flow_control *fc, uint32_t pdu_l static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg, uint32_t llc_pdu_len, void *priv) { - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* input function of the flow control implementation, called first @@ -756,7 +893,7 @@ int bssgp_fc_in(struct bssgp_flow_control *fc, struct msgb *msg, struct timeval time_now; if (llc_pdu_len > fc->bucket_size_max) { - LOGP(DBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger " + LOGP(DLBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger " "than maximum bucket size (%u)!\n", llc_pdu_len, fc->bucket_size_max); msgb_free(msg); @@ -817,15 +954,15 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp, uint32_t old_leak_rate = bctx->fc->bucket_leak_rate; uint32_t old_r_def_ms = bctx->r_default_ms; - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n", bctx->bvci); - if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) || - !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) || - !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) || - !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) || - !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_TAG, 1) || + !TLVP_PRES_LEN(tp, BSSGP_IE_BVC_BUCKET_SIZE, 2) || + !TLVP_PRES_LEN(tp, BSSGP_IE_BUCKET_LEAK_RATE, 2) || + !TLVP_PRES_LEN(tp, BSSGP_IE_BMAX_DEFAULT_MS, 2) || + !TLVP_PRES_LEN(tp, BSSGP_IE_R_DEFAULT_MS,2)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC " "missing mandatory IE\n", bctx->bvci); return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } @@ -840,17 +977,17 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp, bctx->r_default_ms = 100 * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS) / 8; if (old_leak_rate != 0 && bctx->fc->bucket_leak_rate == 0) - LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak " + LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak " "rate of 0, stopping all DL GPRS!\n"); else if (old_leak_rate == 0 && bctx->fc->bucket_leak_rate != 0) - LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak " + LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak " "rate of != 0, restarting all DL GPRS!\n"); if (old_r_def_ms != 0 && bctx->r_default_ms == 0) - LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default " + LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default " "bucket leak rate of 0, stopping DL GPRS!\n"); else if (old_r_def_ms == 0 && bctx->r_default_ms != 0) - LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default " + LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default " "bucket leak rate != 0, restarting DL GPRS!\n"); /* reconfigure the timer for flow control based on new values */ @@ -887,13 +1024,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, break; case BSSGP_PDUT_RA_CAPABILITY: /* BSS requests RA capability or IMSI */ - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n", bctx->bvci); /* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */ /* FIXME: send RA_CAPA_UPDATE_ACK */ break; case BSSGP_PDUT_RADIO_STATUS: - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci); + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci); /* BSS informs us of some exception */ /* FIXME: send GMM_RADIO_STATUS.ind to GMM */ break; @@ -903,7 +1040,7 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, break; case BSSGP_PDUT_FLOW_CONTROL_MS: /* BSS informs us of available bandwidth to one MS */ - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n", bctx->bvci); /* FIXME: actually implement flow control */ /* FIXME: Send FLOW_CONTROL_MS_ACK */ @@ -911,12 +1048,15 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, case BSSGP_PDUT_STATUS: /* This is already handled in bssgp_rcvmsg() */ break; + case BSSGP_PDUT_BVC_RESET: + rc = bssgp_rx_bvc_reset(msg, tp, bctx->bvci); + break; case BSSGP_PDUT_DOWNLOAD_BSS_PFC: case BSSGP_PDUT_CREATE_BSS_PFC_ACK: case BSSGP_PDUT_CREATE_BSS_PFC_NACK: case BSSGP_PDUT_MODIFY_BSS_PFC: case BSSGP_PDUT_DELETE_BSS_PFC_ACK: - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] " + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] " "implemented\n", bctx->bvci, bssgp_pdu_str(pdu_type)); rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); break; @@ -927,13 +1067,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, case BSSGP_PDUT_RA_CAPA_UPDATE_ACK: case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK: case BSSGP_PDUT_FLOW_CONTROL_MS_ACK: - DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n", bctx->bvci, bssgp_pdu_str(pdu_type)); bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); rc = -EINVAL; break; default: - DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n", bctx->bvci, bssgp_pdu_str(pdu_type)); rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); break; @@ -964,13 +1104,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, break; case BSSGP_PDUT_FLUSH_LL_ACK: /* BSS informs us it has performed LL FLUSH */ - DEBUGP(DBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci); + DEBUGP(DLBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci); /* FIXME: send NM_FLUSH_LL.res to NM */ break; case BSSGP_PDUT_LLC_DISCARD: /* BSS informs that some LLC PDU's have been discarded */ if (!bctx) { - LOGP(DBSSGP, LOGL_ERROR, + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx LLC-DISCARD missing mandatory BVCI\n"); goto err_mand_ie; } @@ -978,9 +1118,9 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, break; case BSSGP_PDUT_BVC_BLOCK: /* BSS tells us that BVC shall be blocked */ - if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) || - !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) || + !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK " "missing mandatory IE\n"); goto err_mand_ie; } @@ -988,21 +1128,21 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, break; case BSSGP_PDUT_BVC_UNBLOCK: /* BSS tells us that BVC shall be unblocked */ - if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK " + if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK " "missing mandatory IE\n"); goto err_mand_ie; } rc = bssgp_rx_bvc_unblock(msg, tp); break; case BSSGP_PDUT_BVC_RESET_ACK: - LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci); + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci); break; case BSSGP_PDUT_BVC_RESET: - /* BSS tells us that BVC init is required */ - if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) || - !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) { - LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET " + /* SGSN or BSS tells us that BVC init is required */ + if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) || + !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET " "missing mandatory IE\n"); goto err_mand_ie; } @@ -1011,6 +1151,15 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, case BSSGP_PDUT_STATUS: /* This is already handled in bssgp_rcvmsg() */ break; + + case BSSGP_PDUT_RAN_INFO: + case BSSGP_PDUT_RAN_INFO_REQ: + case BSSGP_PDUT_RAN_INFO_ACK: + case BSSGP_PDUT_RAN_INFO_ERROR: + case BSSGP_PDUT_RAN_INFO_APP_ERROR: + rc = bssgp_rx_rim(msg, tp, bvci); + break; + /* those only exist in the SGSN -> BSS direction */ case BSSGP_PDUT_PAGING_PS: case BSSGP_PDUT_PAGING_CS: @@ -1022,13 +1171,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, case BSSGP_PDUT_BVC_BLOCK_ACK: case BSSGP_PDUT_BVC_UNBLOCK_ACK: case BSSGP_PDUT_SGSN_INVOKE_TRACE: - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n", bvci, bssgp_pdu_str(pdu_type)); bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); rc = -EINVAL; break; default: - DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n", + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n", bvci, bssgp_pdu_str(pdu_type)); rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); break; @@ -1066,14 +1215,14 @@ int bssgp_rcvmsg(struct msgb *msg) rc = bssgp_tlv_parse(&tp, budh->data, data_len); } if (rc < 0) { - LOGP(DBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n", + LOGP(DLBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n", bssgp_pdu_str(pdu_type), msgb_hexdump(msg)); if (pdu_type != BSSGP_PDUT_STATUS) return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); return rc; } - if (bvci == BVCI_SIGNALLING && TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) + if (bvci == BVCI_SIGNALLING && TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI); /* look-up or create the BTS context for this BVC */ @@ -1081,8 +1230,8 @@ int bssgp_rcvmsg(struct msgb *msg) if (bctx) { log_set_context(LOG_CTX_GB_BVC, bctx); - rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]); - rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN], + rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_IN)); + rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_IN), msgb_bssgp_len(msg)); } @@ -1096,7 +1245,7 @@ int bssgp_rcvmsg(struct msgb *msg) * registered if a BVCI is given. */ if (!bctx && bvci != BVCI_SIGNALLING && pdu_type != BSSGP_PDUT_BVC_RESET) { - LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci, + LOGP(DLBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci, bssgp_pdu_str(pdu_type)); return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg); } @@ -1108,7 +1257,7 @@ int bssgp_rcvmsg(struct msgb *msg) else if (bctx) rc = bssgp_rx_ptp(msg, &tp, bctx); else - LOGP(DBSSGP, LOGL_NOTICE, + LOGP(DLBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Cannot handle PDU type %s for unknown BVCI, NS BVCI %u\n", nsei, bvci, bssgp_pdu_str(pdu_type), ns_bvci); @@ -1132,7 +1281,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime, /* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */ if (bvci <= BVCI_PTM ) { - LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n", + LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n", bvci); msgb_free(msg); return -EINVAL; @@ -1140,7 +1289,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime, bctx = btsctx_by_bvci_nsei(bvci, nsei); if (!bctx) { - LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n", + LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n", bvci); msgb_free(msg); return -ENODEV; @@ -1206,8 +1355,8 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime, budh->tlli = osmo_htonl(msgb_tlli(msg)); budh->pdu_type = BSSGP_PDUT_DL_UNITDATA; - rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]); - rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len); + rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT)); + rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len); /* Identifiers down: BVCI, NSEI (in msgb->cb) */ @@ -1286,12 +1435,14 @@ int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci, msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *) &ptmsi); } - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } void bssgp_set_log_ss(int ss) { - DBSSGP = ss; + /* BSSGP has moved from DGPRS to DLGPRS, please update your code if it's + * still calling this function + */ } /*! @@ -1312,7 +1463,7 @@ void bssgp_fc_flush_queue(struct bssgp_flow_control *fc) /*! * \brief Flush the queues of all BSSGP contexts. */ -void bssgp_flush_all_queues() +void bssgp_flush_all_queues(void) { struct bssgp_bvc_ctx *bctx; diff --git a/src/gb/gprs_bssgp2.c b/src/gb/gprs_bssgp2.c new file mode 100644 index 00000000..104fe08e --- /dev/null +++ b/src/gb/gprs_bssgp2.c @@ -0,0 +1,486 @@ +/* BSSGP2 - second generation of BSSGP library */ + +/* (C) 2020 Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/utils.h> +#include <osmocom/core/byteswap.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/tlv.h> + +#include <osmocom/gprs/gprs_ns2.h> +#include <osmocom/gprs/gprs_bssgp.h> +#include <osmocom/gprs/gprs_bssgp2.h> + + +/*! transmit BSSGP PDU over NS (PTP BVC) + * \param[in] nsi NS Instance through which to transmit + * \param[in] nsei NSEI of NSE through which to transmit + * \param[in] bvci BVCI through which to transmit + * \param[in] msg BSSGP PDU to transmit + * \returns 0 on success; negative on error */ +int bssgp2_nsi_tx_ptp(struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci, + struct msgb *msg, uint32_t lsp) +{ + struct osmo_gprs_ns2_prim nsp = {}; + int rc; + + if (!msg) + return 0; + + nsp.bvci = bvci; + nsp.nsei = nsei; + nsp.u.unitdata.link_selector = lsp; + + osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg); + rc = gprs_ns2_recv_prim(nsi, &nsp.oph); + + return rc; +} + +/*! transmit BSSGP PDU over NS (SIGNALING BVC) + * \param[in] nsi NS Instance through which to transmit + * \param[in] nsei NSEI of NSE through which to transmit + * \param[in] msg BSSGP PDU to transmit + * \returns 0 on success; negative on error */ +int bssgp2_nsi_tx_sig(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg, uint32_t lsp) +{ + return bssgp2_nsi_tx_ptp(nsi, nsei, 0, msg, lsp); +} + +/*! Encode BSSGP BVC-BLOCK PDU as per TS 48.018 Section 10.4.8. */ +struct msgb *bssgp2_enc_bvc_block(uint16_t bvci, enum gprs_bssgp_cause cause) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint16_t _bvci = osmo_htons(bvci); + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause); + + return msg; +} + +/*! Encode BSSGP BVC-BLOCK-ACK PDU as per TS 48.018 Section 10.4.9. */ +struct msgb *bssgp2_enc_bvc_block_ack(uint16_t bvci) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint16_t _bvci = osmo_htons(bvci); + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK_ACK; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + + return msg; +} + +/*! Encode BSSGP BVC-UNBLOCK PDU as per TS 48.018 Section 10.4.10. */ +struct msgb *bssgp2_enc_bvc_unblock(uint16_t bvci) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint16_t _bvci = osmo_htons(bvci); + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + + return msg; +} + +/*! Encode BSSGP BVC-UNBLOCK-ACK PDU as per TS 48.018 Section 10.4.11. */ +struct msgb *bssgp2_enc_bvc_unblock_ack(uint16_t bvci) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint16_t _bvci = osmo_htons(bvci); + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK_ACK; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + + return msg; +} + +/*! Encode BSSGP BVC-RESET PDU as per TS 48.018 Section 10.4.12. + * \param[in] bvci PTP BVCI to encode into the BVCI IE + * \param[in] cause BSSGP Cause value (reason for reset) + * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional) + * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL) + * \param[in] feat_bm Feature Bitmap (optional) + * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */ +struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause, + const struct gprs_ra_id *ra_id, uint16_t cell_id, + const uint8_t *feat_bm, const uint8_t *ext_feat_bm) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint16_t _bvci = osmo_htons(bvci); + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_BVC_RESET; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause); + if (ra_id) { + uint8_t bssgp_cid[8]; + bssgp_create_cell_id(bssgp_cid, ra_id, cell_id); + msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid); + } + + if (feat_bm) + msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm); + + if (ext_feat_bm) + msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm); + + return msg; +} + +/*! Encode BSSGP BVC-RESET-ACK PDU as per TS 48.018 Section 10.4.13. + * \param[in] bvci PTP BVCI to encode into the BVCI IE + * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional) + * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL) + * \param[in] feat_bm Feature Bitmap (optional) + * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */ +struct msgb *bssgp2_enc_bvc_reset_ack(uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id, + const uint8_t *feat_bm, const uint8_t *ext_feat_bm) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint16_t _bvci = osmo_htons(bvci); + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_BVC_RESET_ACK; + + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + if (ra_id) { + uint8_t bssgp_cid[8]; + bssgp_create_cell_id(bssgp_cid, ra_id, cell_id); + msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid); + } + + if (feat_bm) + msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm); + + if (ext_feat_bm) + msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm); + + return msg; +} + +/*! Encode BSSGP STATUS PDU as per TS 48.018 Section 10.4.14. + * \param[in] cause BSSGP Cause value + * \param[in] bvci optional BVCI - only encoded if non-NULL + * \param[in] msg optional message buffer containing PDU in error - only encoded if non-NULL + * \param[in] max_pdu_len Maximum BSSGP PDU size the NS layer accepts */ +struct msgb *bssgp2_enc_status(uint8_t cause, const uint16_t *bvci, const struct msgb *orig_msg, uint16_t max_pdu_len) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_STATUS; + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause); + /* FIXME: Require/encode BVCI only if cause is BVCI unknown/blocked + * See 3GPP TS 48.018 Ch. 10.4.14 */ + if (bvci) { + uint16_t _bvci = osmo_htons(*bvci); + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + } + if (orig_msg) { + uint32_t orig_len, max_orig_len; + /* Calculate how big the reply would be: the BSSGP msg so far + size of the PDU IN ERROR including tvl */ + orig_len = msgb_bssgp_len(orig_msg); + max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len); + /* Truncate the difference between max_orig_len and mtu */ + if (max_orig_len > max_pdu_len) + orig_len -= max_orig_len - max_pdu_len; + msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, orig_len, msgb_bssgph(orig_msg)); + } + + return msg; +} + +static const unsigned int bssgp_fc_gran_tbl[] = { + [BSSGP_FC_GRAN_100] = 100, + [BSSGP_FC_GRAN_1000] = 1000, + [BSSGP_FC_GRAN_10000] = 10000, + [BSSGP_FC_GRAN_100000] = 100000, +}; + +/*! Decode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4. + * \param[out] fc caller-allocated memory for parsed output + * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length + * \returns 0 on success; negative in case of error */ +int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp) +{ + unsigned int granularity = 100; + + /* optional "Flow Control Granularity IE" (11.3.102); applies to + * bucket_size_max, bucket_leak_rate and PFC FC params IE */ + if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) { + uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY); + granularity = bssgp_fc_gran_tbl[gran & 3]; + } + + /* mandatory IEs */ + fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG); + fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE); + fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8; + fc->u.bvc.bmax_default_ms = granularity * tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS); + fc->u.bvc.r_default_ms = (granularity * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS)) / 8; + + /* optional / conditional */ + if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) { + fc->bucket_full_ratio_present = true; + fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO); + } else { + fc->bucket_full_ratio_present = false; + } + + if (TLVP_PRESENT(tp, BSSGP_IE_BVC_MEASUREMENT)) { + uint16_t val = tlvp_val16be(tp, BSSGP_IE_BVC_MEASUREMENT); + fc->u.bvc.measurement_present = true; + /* convert from centi-seconds to milli-seconds */ + if (val == 0xffff) + fc->u.bvc.measurement = 0xffffffff; + else + fc->u.bvc.measurement = val * 10; + } else { + fc->u.bvc.measurement_present = false; + } + + return 0; + +} + +/*! Encode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4. + * \param[in] fc structure describing to-be-encoded FC parameters + * \param[in] gran if non-NULL: Encode using specified unit granularity + * \returns encoded PDU or NULL in case of error */ +struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + unsigned int granularity = 100; + + if (gran) + granularity = bssgp_fc_gran_tbl[*gran & 3]; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC; + + msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag); + msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_BUCKET_SIZE, fc->bucket_size_max / granularity); + msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity); + msgb_tvlv_put_16be(msg, BSSGP_IE_BMAX_DEFAULT_MS, fc->u.bvc.bmax_default_ms / granularity); + msgb_tvlv_put_16be(msg, BSSGP_IE_R_DEFAULT_MS, fc->u.bvc.r_default_ms * 8 / granularity); + + if (fc->bucket_full_ratio_present) + msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio); + + if (fc->u.bvc.measurement_present) { + uint16_t val; + /* convert from ms to cs */ + if (fc->u.bvc.measurement == 0xffffffff) + val = 0xffff; + else + val = fc->u.bvc.measurement / 10; + msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_MEASUREMENT, val); + } + + if (gran) { + uint8_t val = *gran & 3; + msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val); + } + + return msg; +} + +/*! Encode BSSGP FLUSH-LL PDU as per TS 48.018 Section 10.4.1. + * \param[in] tlli - the TLLI of the MS + * \param[in] old_bvci BVCI + * \param[in] new_bvci2 optional BVCI - only encoded if non-NULL + * \param[in] nsei optional - only encoded if non-NULL + * \returns encoded PDU or NULL in case of error */ +struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci, + const uint16_t *new_bvci, const uint16_t *nsei) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_FLUSH_LL; + + msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli); + msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, old_bvci); + if (new_bvci) + msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *new_bvci); + + if (nsei) + msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *nsei); + + return msg; +} + +/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4. + * \param[in] tag the tag IE value to encode + * \returns encoded PDU or NULL in case of error */ +struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK; + + msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); + + return msg; +} + +/*! Decode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6. + * \param[out] fc caller-allocated memory for parsed output + * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length + * \returns 0 on success; negative in case of error */ +int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp) +{ + unsigned int granularity = 100; + + /* optional "Flow Control Granularity IE" (11.3.102); applies to + * bucket_size_max, bucket_leak_rate and PFC FC params IE */ + if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) { + uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY); + granularity = bssgp_fc_gran_tbl[gran & 3]; + } + + /* mandatory IEs */ + fc->u.ms.tlli = tlvp_val32be(tp, BSSGP_IE_TLLI); + fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG); + fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_MS_BUCKET_SIZE); + fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8; + + /* optional / conditional */ + if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) { + fc->bucket_full_ratio_present = true; + fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO); + } else { + fc->bucket_full_ratio_present = false; + } + + return 0; +} + +/*! Encode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6. + * \param[in] fc structure describing to-be-encoded FC parameters + * \param[in] gran if non-NULL: Encode using specified unit granularity + * \returns encoded PDU or NULL in case of error */ +struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + unsigned int granularity = 100; + + if (gran) + granularity = bssgp_fc_gran_tbl[*gran & 3]; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS; + + msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, fc->u.ms.tlli); + msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag); + msgb_tvlv_put_16be(msg, BSSGP_IE_MS_BUCKET_SIZE, fc->bucket_size_max / granularity); + msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity); + + if (fc->bucket_full_ratio_present) + msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio); + + if (gran) { + uint8_t val = *gran & 3; + msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val); + } + + return msg; +} + +/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.7. + * \param[in] tlli the TLLI IE value to encode + * \param[in] tag the tag IE value to encode + * \returns encoded PDU or NULL in case of error */ +struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS_ACK; + + msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli); + msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); + + return msg; +} diff --git a/src/gb/gprs_bssgp_bss.c b/src/gb/gprs_bssgp_bss.c index 5a1ffb92..8230d871 100644 --- a/src/gb/gprs_bssgp_bss.c +++ b/src/gb/gprs_bssgp_bss.c @@ -34,7 +34,7 @@ #include <osmocom/gprs/gprs_bssgp_bss.h> #include <osmocom/gprs/gprs_ns.h> -#include "common_vty.h" +#include "gprs_bssgp_internal.h" #define GSM_IMSI_LENGTH 17 @@ -60,7 +60,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli, struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n", + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n", tlli); msgb_nsei(msg) = nsei; msgb_bvci(msg) = 0; /* Signalling */ @@ -69,7 +69,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli, bssgp_msgb_tlli_put(msg, tlli); bssgp_msgb_ra_put(msg, ra_id); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! GMM-RESUME.req (Chapter 10.3.9) */ @@ -80,7 +80,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli, struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n", + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n", tlli); msgb_nsei(msg) = nsei; msgb_bvci(msg) = 0; /* Signalling */ @@ -91,7 +91,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli, msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit RA-CAPABILITY-UPDATE (10.3.3) */ @@ -101,7 +101,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag) struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n", + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n", bctx->bvci, tlli); /* set NSEI and BVCI in msgb cb */ @@ -113,7 +113,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag) msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* first common part of RADIO-STATUS */ @@ -123,7 +123,7 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx) struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ", + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ", bctx->bvci); /* set NSEI and BVCI in msgb cb */ @@ -139,9 +139,9 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx) static int common_tx_radio_status2(struct msgb *msg, uint8_t cause) { msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause); - LOGPC(DBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause)); + LOGPC(DLBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause)); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit RADIO-STATUS for TLLI (10.3.5) */ @@ -153,7 +153,7 @@ int bssgp_tx_radio_status_tlli(struct bssgp_bvc_ctx *bctx, uint8_t cause, if (!msg) return -ENOMEM; bssgp_msgb_tlli_put(msg, tlli); - LOGPC(DBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli); + LOGPC(DLBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli); return common_tx_radio_status2(msg, cause); } @@ -168,7 +168,7 @@ int bssgp_tx_radio_status_tmsi(struct bssgp_bvc_ctx *bctx, uint8_t cause, if (!msg) return -ENOMEM; msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *)&_tmsi); - LOGPC(DBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi); + LOGPC(DLBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi); return common_tx_radio_status2(msg, cause); } @@ -194,7 +194,7 @@ int bssgp_tx_radio_status_imsi(struct bssgp_bvc_ctx *bctx, uint8_t cause, if (imsi_len > 2) msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2); #pragma GCC diagnostic pop - LOGPC(DBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi); + LOGPC(DLBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi); return common_tx_radio_status2(msg, cause); } @@ -220,7 +220,7 @@ int bssgp_tx_flush_ll_ack(struct bssgp_bvc_ctx *bctx, uint32_t tlli, msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci_new); msgb_tvlv_put(msg, BSSGP_IE_NUM_OCT_AFF, 3, (uint8_t *) &_oct_aff); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit LLC-DISCARDED (Chapter 10.4.3) */ @@ -233,7 +233,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint16_t _bvci = osmo_htons(bctx->bvci); uint32_t _oct_aff = osmo_htonl(num_octets & 0xFFFFFF); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED " + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED " "TLLI=0x%04x, FRAMES=%u, OCTETS=%u\n", bctx->bvci, tlli, num_frames, num_octets); msgb_nsei(msg) = bctx->nsei; @@ -246,7 +246,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli, msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); msgb_tvlv_put(msg, BSSGP_IE_NUM_OCT_AFF, 3, ((uint8_t *) &_oct_aff) + 1); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit a BVC-BLOCK message (Chapter 10.4.8) */ @@ -257,7 +257,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause) (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); uint16_t _bvci = osmo_htons(bctx->bvci); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK " + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK " "CAUSE=%s\n", bctx->bvci, bssgp_cause_str(cause)); msgb_nsei(msg) = bctx->nsei; @@ -267,7 +267,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause) msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit a BVC-UNBLOCK message (Chapter 10.4.10) */ @@ -278,7 +278,7 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx) (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); uint16_t _bvci = osmo_htons(bctx->bvci); - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci); + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci); msgb_nsei(msg) = bctx->nsei; msgb_bvci(msg) = 0; /* Signalling */ @@ -286,38 +286,29 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx) msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit a BVC-RESET message (Chapter 10.4.12) */ int bssgp_tx_bvc_reset2(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause, bool add_cell_id) { - struct msgb *msg = bssgp_msgb_alloc(); - struct bssgp_normal_hdr *bgph = - (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); - uint16_t _bvci = osmo_htons(bvci); - - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET " - "CAUSE=%s\n", bvci, bssgp_cause_str(cause)); - - msgb_nsei(msg) = bctx->nsei; - msgb_bvci(msg) = 0; /* Signalling */ - bgph->pdu_type = BSSGP_PDUT_BVC_RESET; - - msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); - msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause); - if (add_cell_id) { - uint8_t bssgp_cid[8]; - bssgp_create_cell_id(bssgp_cid, &bctx->ra_id, bctx->cell_id); - msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid); - } - /* Optional: Feature Bitmap */ - - return gprs_ns_sendmsg(bssgp_nsi, msg); + if (add_cell_id) + return bssgp_tx_bvc_reset_nsei_bvci(bctx->nsei, bvci, cause, &bctx->ra_id, bctx->cell_id); + else + return bssgp_tx_bvc_reset_nsei_bvci(bctx->nsei, bvci, cause, NULL, 0); } int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause) { - return bssgp_tx_bvc_reset2(bctx, bvci, cause, bvci != BVCI_PTM); + /* The Cell Identifier IE is mandatory in the BVC-RESET PDU sent from BSS to SGSN in order to reset a + * BVC corresponding to a PTP functional entity. The Cell Identifier IE shall not be used in any other + * BVC-RESET PDU. */ + switch (bvci) { + case BVCI_SIGNALLING: + case BVCI_PTM: + return bssgp_tx_bvc_reset2(bctx, bvci, cause, false); + default: + return bssgp_tx_bvc_reset2(bctx, bvci, cause, true); + } } /*! Transmit a FLOW_CONTROL-BVC (Chapter 10.4.4) @@ -389,7 +380,7 @@ int bssgp_tx_fc_bvc(struct bssgp_bvc_ctx *bctx, uint8_t tag, sizeof(e_queue_delay), (uint8_t *) &e_queue_delay); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! Transmit a FLOW_CONTROL-MS (Chapter 10.4.6) @@ -432,7 +423,7 @@ int bssgp_tx_fc_ms(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag, msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, bucket_full_ratio); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /*! RL-UL-UNITDATA.req (Chapter 10.2.2) @@ -475,10 +466,10 @@ int bssgp_tx_ul_ud(struct bssgp_bvc_ctx *bctx, uint32_t tlli, msgb_nsei(msg) = bctx->nsei; msgb_bvci(msg) = bctx->bvci; - rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]); - rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len); + rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT)); + rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* Parse a single GMM-PAGING.req to a given NSEI/NS-BVCI */ @@ -519,24 +510,24 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo, TLVP_LEN(&tp, BSSGP_IE_IMSI)); /* DRX Parameters */ - if (!TLVP_PRESENT(&tp, BSSGP_IE_DRX_PARAMS)) + if (!TLVP_PRES_LEN(&tp, BSSGP_IE_DRX_PARAMS, 2)) goto err_mand_ie; pinfo->drx_params = tlvp_val16be(&tp, BSSGP_IE_DRX_PARAMS); /* Scope */ - if (TLVP_PRESENT(&tp, BSSGP_IE_BSS_AREA_ID)) { + if (TLVP_PRES_LEN(&tp, BSSGP_IE_BSS_AREA_ID, 1)) { pinfo->scope = BSSGP_PAGING_BSS_AREA; - } else if (TLVP_PRESENT(&tp, BSSGP_IE_LOCATION_AREA)) { + } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_LOCATION_AREA, 5)) { pinfo->scope = BSSGP_PAGING_LOCATION_AREA; memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_LOCATION_AREA), TLVP_LEN(&tp, BSSGP_IE_LOCATION_AREA)); gsm48_parse_ra(&pinfo->raid, ra); - } else if (TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) { + } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_ROUTEING_AREA, 6)) { pinfo->scope = BSSGP_PAGING_ROUTEING_AREA; memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA), TLVP_LEN(&tp, BSSGP_IE_ROUTEING_AREA)); gsm48_parse_ra(&pinfo->raid, ra); - } else if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) { + } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) { pinfo->scope = BSSGP_PAGING_BVCI; pinfo->bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI); } else @@ -554,8 +545,7 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo, } /* Optional (P-)TMSI */ - if (TLVP_PRESENT(&tp, BSSGP_IE_TMSI) && - TLVP_LEN(&tp, BSSGP_IE_TMSI) >= 4) { + if (TLVP_PRES_LEN(&tp, BSSGP_IE_TMSI, 4)) { if (!pinfo->ptmsi) pinfo->ptmsi = talloc_zero_size(pinfo, sizeof(uint32_t)); *(pinfo->ptmsi) = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TMSI)); diff --git a/src/gb/gprs_bssgp_internal.h b/src/gb/gprs_bssgp_internal.h new file mode 100644 index 00000000..5022d32d --- /dev/null +++ b/src/gb/gprs_bssgp_internal.h @@ -0,0 +1,9 @@ + +#pragma once + +#include <osmocom/gprs/gprs_bssgp.h> + +extern bssgp_bvc_send bssgp_ns_send; +extern void *bssgp_ns_send_data; + +int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci); diff --git a/src/gb/gprs_bssgp_rim.c b/src/gb/gprs_bssgp_rim.c new file mode 100644 index 00000000..9c09e1ef --- /dev/null +++ b/src/gb/gprs_bssgp_rim.c @@ -0,0 +1,1261 @@ +/*! \file gprs_bssgp.c + * GPRS BSSGP RIM protocol implementation as per 3GPP TS 48.018. */ +/* + * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH + * Author: Philipp Maier <pmaier@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <osmocom/gprs/gprs_bssgp.h> +#include <osmocom/gprs/gprs_bssgp_rim.h> +#include <osmocom/gsm/gsm0808_utils.h> +#include "gprs_bssgp_internal.h" + +/* TVLV IEs use a variable length field. To be sure we will do all buffer + * length checks with the maximum possible header length, which is + * 1 octet tag + 2 octets length = 3 */ +#define TVLV_HDR_MAXLEN 3 + +/* Usually RIM application containers and their surrounding RIM containers + * are not likely to exceed 128 octets, so the usual header length will be 2 */ +#define TVLV_HDR_LEN 2 + +/* The reporting cell identifier is encoded as a cell identifier IE + * (3GPP TS 48.018, sub-clause 11.3.9) but without IE and length octets. */ +#define REP_CELL_ID_LEN 8 + +const struct value_string bssgp_rim_routing_info_discr_strs[] = { + { BSSGP_RIM_ROUTING_INFO_GERAN, "GERAN-cell" }, + { BSSGP_RIM_ROUTING_INFO_UTRAN, "UTRAN-RNC" }, + { BSSGP_RIM_ROUTING_INFO_EUTRAN, "E-UTRAN-eNodeB/HeNB" }, + { 0, NULL } +}; + +/*! Parse a RIM Routing address IE (3GPP TS 29.060, chapter 7.7.57 and 7.7.77). + * \param[out] ri user provided memory to store the parsed results. + * \param[in] buf input buffer of the value part of the RIM Routing address IE. + * \discr[in] discr value part (one byte) of the RIM Routing Address Discriminator IE. + * \returns length of parsed octets, -EINVAL on error. */ +int bssgp_parse_rim_ra(struct bssgp_rim_routing_info *ri, const uint8_t *buf, + unsigned int len, uint8_t discr) +{ + struct gprs_ra_id raid_temp; + + memset(ri, 0, sizeof(*ri)); + if (len < 2) + return -EINVAL; + + ri->discr = discr; + + switch (ri->discr) { + case BSSGP_RIM_ROUTING_INFO_GERAN: + if (len < 8) + return -EINVAL; + ri->geran.cid = bssgp_parse_cell_id(&ri->geran.raid, buf); + return 8; + case BSSGP_RIM_ROUTING_INFO_UTRAN: + if (len < 8) + return -EINVAL; + gsm48_parse_ra(&ri->utran.raid, buf); + ri->utran.rncid = osmo_load16be(buf + 6); + return 8; + case BSSGP_RIM_ROUTING_INFO_EUTRAN: + if (len < 6 || len > 13) + return -EINVAL; + /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008 + * Figure 10.5.130 specify MCC/MNC encoding in the same way, + * so we can re-use gsm48_parse_ra() for that. */ + gsm48_parse_ra(&raid_temp, buf); + ri->eutran.tai.mcc = raid_temp.mcc; + ri->eutran.tai.mnc = raid_temp.mnc; + ri->eutran.tai.mnc_3_digits = raid_temp.mnc_3_digits; + ri->eutran.tai.tac = osmo_load16be(buf + 3); + memcpy(ri->eutran.global_enb_id, buf + 5, len - 5); + ri->eutran.global_enb_id_len = len - 5; + return len; + default: + return -EINVAL; + } +} + +/*! Parse a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70). + * \param[out] ri user provided memory to store the parsed results. + * \param[in] buf input buffer of the value part of the IE. + * \returns length of parsed octets, -EINVAL on error. */ +int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf, + unsigned int len) +{ + uint8_t discr; + int rc; + + if (len < 1) + return -EINVAL; + + discr = buf[0] & 0x0f; + + rc = bssgp_parse_rim_ra(ri, buf + 1, len - 1, discr); + if (rc < 0) + return rc; + return rc + 1; +} + +/*! Encode a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70). + * \param[out] buf user provided memory (at least 14 byte) for the generated value part of the IE. + * \param[in] ri user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_create_rim_ri(uint8_t *buf, const struct bssgp_rim_routing_info *ri) +{ + int rc; + struct gprs_ra_id raid_temp; + int len; + + buf[0] = ri->discr & 0x0f; + buf++; + + switch (ri->discr) { + case BSSGP_RIM_ROUTING_INFO_GERAN: + rc = bssgp_create_cell_id(buf, &ri->geran.raid, ri->geran.cid); + if (rc < 0) + return -EINVAL; + len = rc + 1; + break; + case BSSGP_RIM_ROUTING_INFO_UTRAN: + gsm48_encode_ra((struct gsm48_ra_id *)buf, &ri->utran.raid); + osmo_store16be(ri->utran.rncid, buf + 6); + len = 9; + break; + case BSSGP_RIM_ROUTING_INFO_EUTRAN: + /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008 + * Figure 10.5.130 specify MCC/MNC encoding in the same way, + * so we can re-use gsm48_encode_ra() for that. */ + raid_temp = (struct gprs_ra_id) { + .mcc = ri->eutran.tai.mcc, + .mnc = ri->eutran.tai.mnc, + .mnc_3_digits = ri->eutran.tai.mnc_3_digits, + }; + + gsm48_encode_ra((struct gsm48_ra_id *)buf, &raid_temp); + osmo_store16be(ri->eutran.tai.tac, buf + 3); + OSMO_ASSERT(ri->eutran.global_enb_id_len <= + sizeof(ri->eutran.global_enb_id)); + memcpy(buf + 5, ri->eutran.global_enb_id, + ri->eutran.global_enb_id_len); + len = ri->eutran.global_enb_id_len + 6; + break; + default: + return -EINVAL; + } + + OSMO_ASSERT(len <= BSSGP_RIM_ROUTING_INFO_MAXLEN); + return len; +} + +/*! Encode a RIM Routing information into a human readable string. + * \param[buf] user provided string buffer to store the resulting string. + * \param[buf_len] maximum length of string buffer. + * \param[in] ri user provided input data struct. + * \returns pointer to the beginning of the resulting string stored in string buffer. */ +char *bssgp_rim_ri_name_buf(char *buf, size_t buf_len, const struct bssgp_rim_routing_info *ri) +{ + char plmn_str[16]; + char enb_id_str[16]; + char g_id_ps_str[32]; + struct osmo_plmn_id plmn; + struct osmo_cell_global_id_ps g_id_ps; + + if (!ri) + return NULL; + + switch (ri->discr) { + case BSSGP_RIM_ROUTING_INFO_GERAN: + g_id_ps.rai.rac = ri->geran.raid.rac; + g_id_ps.rai.lac.lac = ri->geran.raid.lac; + g_id_ps.rai.lac.plmn.mcc = ri->geran.raid.mcc; + g_id_ps.rai.lac.plmn.mnc_3_digits = ri->geran.raid.mnc_3_digits; + g_id_ps.rai.lac.plmn.mnc = ri->geran.raid.mnc; + g_id_ps.cell_identity = ri->geran.cid; + snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr), + osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps)); + break; + case BSSGP_RIM_ROUTING_INFO_UTRAN: + g_id_ps.rai.rac = ri->utran.raid.rac; + g_id_ps.rai.lac.lac = ri->utran.raid.lac; + g_id_ps.rai.lac.plmn.mcc = ri->utran.raid.mcc; + g_id_ps.rai.lac.plmn.mnc_3_digits = ri->utran.raid.mnc_3_digits; + g_id_ps.rai.lac.plmn.mnc = ri->utran.raid.mnc; + g_id_ps.cell_identity = ri->utran.rncid; + snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr), + osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps)); + break; + case BSSGP_RIM_ROUTING_INFO_EUTRAN: + plmn.mcc = ri->eutran.tai.mcc; + plmn.mnc = ri->eutran.tai.mnc; + plmn.mnc_3_digits = ri->eutran.tai.mnc_3_digits; + snprintf(buf, buf_len, "%s-%s-%u-%s", bssgp_rim_routing_info_discr_str(ri->discr), + osmo_plmn_name_buf(plmn_str, sizeof(plmn_str), &plmn), ri->eutran.tai.tac, + osmo_hexdump_buf(enb_id_str, sizeof(enb_id_str), ri->eutran.global_enb_id, + ri->eutran.global_enb_id_len, "", false)); + break; + default: + snprintf(buf, buf_len, "invalid"); + } + + return buf; +} + +/*! Encode a RIM Routing information into a human readable string. + * \param[in] ri user provided input data struct. + * \returns pointer to the resulting string. */ +const char *bssgp_rim_ri_name(const struct bssgp_rim_routing_info *ri) +{ + static __thread char rim_ri_buf[64]; + return bssgp_rim_ri_name_buf(rim_ri_buf, sizeof(rim_ri_buf), ri); +} + +/*! Decode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_req_app_cont_nacc(struct bssgp_ran_inf_req_app_cont_nacc *cont, const uint8_t *buf, size_t len) +{ + int rc; + + if (len < REP_CELL_ID_LEN) + return -EINVAL; + + rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell, + CELL_IDENT_WHOLE_GLOBAL_PS, buf, len); + if (rc < 0) + return -EINVAL; + + return 0; +} + +/*! Encode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_req_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_app_cont_nacc *cont) +{ + int rc; + struct gprs_ra_id *raid; + + if (len < REP_CELL_ID_LEN) + return -EINVAL; + + raid = (struct gprs_ra_id *)&cont->reprt_cell.rai; + rc = bssgp_create_cell_id(buf, raid, cont->reprt_cell.cell_identity); + if (rc < 0) + return -EINVAL; + return rc; +} + +/*! Decode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_app_cont_nacc(struct bssgp_ran_inf_app_cont_nacc *cont, const uint8_t *buf, size_t len) +{ + unsigned int i; + int remaining_buf_len; + int rc; + + /* The given buffer must at least contain a reporting cell identifer + * plus one octet that defines number/type of attached sysinfo messages. */ + if (len < REP_CELL_ID_LEN + 1) + return -EINVAL; + + rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell, + CELL_IDENT_WHOLE_GLOBAL_PS, buf, len); + if (rc < 0) + return -EINVAL; + + buf += REP_CELL_ID_LEN; + + cont->type_psi = buf[0] & 1; + cont->num_si = buf[0] >> 1; + buf++; + + /* The number of sysinfo messages may be zero */ + if (cont->num_si == 0) + return 0; + + /* Check if the prospected system information messages fit in the + * remaining buffer space */ + remaining_buf_len = len - REP_CELL_ID_LEN - 1; + if (remaining_buf_len <= 0) + return -EINVAL; + if (cont->type_psi && remaining_buf_len / BSSGP_RIM_PSI_LEN < cont->num_si) + return -EINVAL; + else if (remaining_buf_len / BSSGP_RIM_SI_LEN < cont->num_si) + return -EINVAL; + + for (i = 0; i < cont->num_si; i++) { + cont->si[i] = buf; + if (cont->type_psi) + buf += BSSGP_RIM_PSI_LEN; + else + buf += BSSGP_RIM_SI_LEN; + } + + return 0; +} + +/*! Encode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_cont_nacc *cont) +{ + uint8_t *buf_ptr = buf; + int rc; + unsigned int silen; + unsigned int i; + struct gprs_ra_id *raid; + + if (cont->type_psi) + silen = BSSGP_RIM_PSI_LEN; + else + silen = BSSGP_RIM_SI_LEN; + + /* The buffer must accept the reporting cell id, plus 1 byte to define + * the type and number of sysinfo messages. */ + if (len < REP_CELL_ID_LEN + 1 + silen * cont->num_si) + return -EINVAL; + + raid = (struct gprs_ra_id *)&cont->reprt_cell.rai; + rc = bssgp_create_cell_id(buf_ptr, raid, cont->reprt_cell.cell_identity); + if (rc < 0) + return -EINVAL; + buf_ptr += rc; + + buf_ptr[0] = 0x00; + if (cont->type_psi) + buf_ptr[0] |= 0x01; + buf_ptr[0] |= (cont->num_si << 1); + buf_ptr++; + + for (i = 0; i < cont->num_si; i++) { + memcpy(buf_ptr, cont->si[i], silen); + buf_ptr += silen; + } + + return (int)(buf_ptr - buf); +} + +/* 3GPP TS 48.018, table 11.3.64.1.b, NACC Cause coding */ +const struct value_string bssgp_nacc_cause_strs[] = { + { BSSGP_NACC_CAUSE_UNSPEC, "unspecified error" }, + { BSSGP_NACC_CAUSE_SYNTAX_ERR, "syntax error in app container" }, + { BSSGP_NACC_CAUSE_RPRT_CELL_MISSMTCH, "reporting cell id mismatch" }, + { BSSGP_NACC_CAUSE_SIPSI_TYPE_ERR, "SI/PSI type error" }, + { BSSGP_NACC_CAUSE_SIPSI_LEN_ERR, "SI/PSI inconsistent length" }, + { BSSGP_NACC_CAUSE_SIPSI_SET_ERR, "inconsistent set of msg" }, + { 0, NULL } +}; + +/*! Decode a Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_app_err_cont_nacc(struct bssgp_app_err_cont_nacc *cont, const uint8_t *buf, size_t len) +{ + /* The buffer must at least contain the NACC cause code, it should also + * contain the application container, but we won't error if it is missing. */ + if (len < 1) + return -EINVAL; + + cont->nacc_cause = buf[0]; + + if (len > 1) { + cont->err_app_cont = buf + 1; + cont->err_app_cont_len = len - 1; + } else { + cont->err_app_cont = NULL; + cont->err_app_cont_len = 0; + } + + return 0; +} + +/*! Encode Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_app_err_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_app_err_cont_nacc *cont) +{ + uint8_t *buf_ptr = buf; + + /* The buffer must accept the length of the application container and the NACC + * cause code, which is one octet in length. */ + if (len < cont->err_app_cont_len + 1) + return -EINVAL; + + buf_ptr[0] = cont->nacc_cause; + buf_ptr++; + + memcpy(buf_ptr, cont->err_app_cont, cont->err_app_cont_len); + buf_ptr += cont->err_app_cont_len; + + return (int)(buf_ptr - buf); +} + +/* The structs bssgp_ran_inf_req_rim_cont, bssgp_ran_inf_rim_cont and bssgp_ran_inf_app_err_rim_cont *cont + * share four common fields at the beginning, we use the following struct as parameter type for the common + * encoder/decoder functions. (See also 3GPP TS 48.018 table 11.3.62a.1.b, table 11.3.62a.2.b, and + * table 11.3.62a.5.b) */ +struct bssgp_ran_inf_x_cont { + enum bssgp_ran_inf_app_id app_id; + uint32_t seq_num; + struct bssgp_rim_pdu_ind pdu_ind; + uint8_t prot_ver; +}; + +static int dec_rim_cont_common(struct bssgp_ran_inf_x_cont *cont, struct tlv_parsed *tp) +{ + if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t))) + cont->app_id = TLVP_VAL(tp, BSSGP_IE_RIM_APP_IDENTITY)[0]; + else + return -EINVAL; + + if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num))) + cont->seq_num = tlvp_val32be(tp, BSSGP_IE_RIM_SEQ_NR); + else + return -EINVAL; + + if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind))) + memcpy(&cont->pdu_ind, TLVP_VAL(tp, BSSGP_IE_RIM_PDU_INDICATIONS), sizeof(cont->pdu_ind)); + else + return -EINVAL; + + if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver))) + cont->prot_ver = TLVP_VAL(tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0]; + else + cont->prot_ver = 1; + + return 0; +} + +static uint8_t *enc_rim_cont_common(uint8_t *buf, size_t len, const struct bssgp_ran_inf_x_cont *cont) +{ + + uint32_t seq_num = osmo_htonl(cont->seq_num); + uint8_t app_id_temp; + uint8_t *buf_ptr = buf; + + if (len < + TVLV_HDR_MAXLEN * 4 + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->pdu_ind) + + sizeof(cont->prot_ver)) + return NULL; + + app_id_temp = cont->app_id; + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp); + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num); + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind), (uint8_t *) & cont->pdu_ind); + if (cont->prot_ver > 0) + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver); + + return buf_ptr; +} + +/* 3GPP TS 48.018, table 11.3.61.b: RIM Application Identity coding */ +const struct value_string bssgp_ran_inf_app_id_strs[] = { + { BSSGP_RAN_INF_APP_ID_NACC, "Network Assisted Cell Change (NACC)" }, + { BSSGP_RAN_INF_APP_ID_SI3, "System Information 3 (SI3)" }, + { BSSGP_RAN_INF_APP_ID_MBMS, "MBMS data channel" }, + { BSSGP_RAN_INF_APP_ID_SON, "SON Transfer" }, + { BSSGP_RAN_INF_APP_ID_UTRA_SI, "UTRA System Information (UTRA SI)" }, + { 0, NULL } +}; + +/*! Decode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *cont, const uint8_t *buf, size_t len) +{ + int rc; + struct tlv_parsed tp; + + memset(cont, 0, sizeof(*cont)); + + rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0); + if (rc < 0) + return -EINVAL; + + rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp); + if (rc < 0) + return -EINVAL; + + if (TLVP_PRESENT(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER)) { + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + rc = bssgp_dec_ran_inf_req_app_cont_nacc(&cont->u.app_cont_nacc, + TLVP_VAL(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER), + TLVP_LEN(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER)); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + + if (rc < 0) + return rc; + } + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) { + cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + } + + return 0; +} + +/* Dub a TLVP header into a given buffer. The value part of the IE must start + * at the 2nd octet. Should the length field make a 3 octet TLVP header + * necessary (unlikely, but possible) the value part is moved ahead by one + * octet. The function returns a pointer to the end of value part. */ +static uint8_t *dub_tlvp_header(uint8_t *buf, uint8_t iei, uint16_t len) +{ + uint8_t *buf_ptr = buf; + + buf_ptr[0] = iei; + if (len <= TVLV_MAX_ONEBYTE) { + buf_ptr[1] = (uint8_t) len; + buf_ptr[1] |= 0x80; + buf_ptr += TVLV_HDR_LEN; + } else { + memmove(buf_ptr + 1, buf_ptr, len); + buf_ptr[1] = len >> 8; + buf_ptr[2] = len & 0xff; + buf_ptr += TVLV_HDR_MAXLEN; + } + buf_ptr += len; + + return buf_ptr; +} + +/*! Encode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_req_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_rim_cont *cont) +{ + uint8_t *buf_ptr = buf; + int app_cont_len = 0; + int remaining_buf_len; + + buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont); + if (!buf_ptr) + return -EINVAL; + + remaining_buf_len = len - (int)(buf_ptr - buf); + if (remaining_buf_len <= 0) + return -EINVAL; + + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + app_cont_len = + bssgp_enc_ran_inf_req_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN, + &cont->u.app_cont_nacc); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + + if (app_cont_len < 0) + return -EINVAL; + buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RIM_REQ_APP_CONTAINER, app_cont_len); + + remaining_buf_len = len - (int)(buf_ptr - buf); + if (remaining_buf_len < 0) + return -EINVAL; + + if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) { + if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN) + return -EINVAL; + buf_ptr = + tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id); + } + return (int)(buf_ptr - buf); +} + +/*! Decode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *cont, const uint8_t *buf, size_t len) +{ + int rc; + struct tlv_parsed tp; + + memset(cont, 0, sizeof(*cont)); + + rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0); + if (rc < 0) + return -EINVAL; + + rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp); + if (rc < 0) + return -EINVAL; + + if (TLVP_PRESENT(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER)) { + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + rc = bssgp_dec_ran_inf_app_cont_nacc(&cont->u.app_cont_nacc, + TLVP_VAL(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER), + TLVP_LEN(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER)); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + + if (rc < 0) + return rc; + } else if (TLVP_PRESENT(&tp, BSSGP_IE_APP_ERROR_CONTAINER)) { + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc, + TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp, + BSSGP_IE_APP_ERROR_CONTAINER)); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + if (rc < 0) + return rc; + cont->app_err = true; + } + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) { + cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + } + + return 0; +} + +/*! Encode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_rim_cont *cont) +{ + uint8_t *buf_ptr = buf; + int app_cont_len = 0; + int remaining_buf_len; + + buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont); + if (!buf_ptr) + return -EINVAL; + + remaining_buf_len = len - (int)(buf_ptr - buf); + if (remaining_buf_len <= 0) + return -EINVAL; + + if (cont->app_err) { + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + app_cont_len = + bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN, + &cont->u.app_err_cont_nacc); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + if (app_cont_len < 0) + return -EINVAL; + buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len); + } else { + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + app_cont_len = + bssgp_enc_ran_inf_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN, + &cont->u.app_cont_nacc); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + if (app_cont_len < 0) + return -EINVAL; + buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RAN_INFO_APP_CONTAINER, app_cont_len); + } + + remaining_buf_len = len - (int)(buf_ptr - buf); + if (remaining_buf_len < 0) + return -EINVAL; + + if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) { + if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN) + return -EINVAL; + buf_ptr = + tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id); + } + return (int)(buf_ptr - buf); +} + +/*! Decode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *cont, const uint8_t *buf, size_t len) +{ + int rc; + struct tlv_parsed tp; + + memset(cont, 0, sizeof(*cont)); + + rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0); + if (rc < 0) + return -EINVAL; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t))) + cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0]; + else + return -EINVAL; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num))) + cont->seq_num = tlvp_val32be(&tp, BSSGP_IE_RIM_SEQ_NR); + else + return -EINVAL; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver))) + cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0]; + else + cont->prot_ver = 1; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) { + cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + } + + return 0; +} + +/*! Encode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_ack_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_ack_rim_cont *cont) +{ + uint8_t *buf_ptr = buf; + uint32_t seq_num = osmo_htonl(cont->seq_num); + uint8_t app_id_temp; + + if (len < + 4 * TVLV_HDR_MAXLEN + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->prot_ver) + + cont->son_trans_app_id_len) + return -EINVAL; + + app_id_temp = cont->app_id; + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp); + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num); + + if (cont->prot_ver > 0) + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver); + + if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) + buf_ptr = + tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id); + + return (int)(buf_ptr - buf); +} + +/*! Decode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *cont, const uint8_t *buf, size_t len) +{ + int rc; + struct tlv_parsed tp; + + memset(cont, 0, sizeof(*cont)); + + rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0); + if (rc < 0) + return -EINVAL; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t))) + cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0]; + else + return -EINVAL; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_CAUSE, sizeof(cont->cause))) + cont->cause = TLVP_VAL(&tp, BSSGP_IE_CAUSE)[0]; + else + return -EINVAL; + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver))) + cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0]; + else + cont->prot_ver = 1; + + if (TLVP_PRESENT(&tp, BSSGP_IE_PDU_IN_ERROR)) { + cont->err_pdu = TLVP_VAL(&tp, BSSGP_IE_PDU_IN_ERROR); + cont->err_pdu_len = TLVP_LEN(&tp, BSSGP_IE_PDU_IN_ERROR); + } else { + return -EINVAL; + } + + if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) { + cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID); + } + + return 0; +} + +/*! Encode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_err_rim_cont *cont) +{ + uint8_t *buf_ptr = buf; + uint8_t app_id_temp; + + if (len < + TVLV_HDR_MAXLEN * 5 + sizeof(app_id_temp) + sizeof(cont->cause) + sizeof(cont->prot_ver) + + cont->err_pdu_len + cont->son_trans_app_id_len) + return -EINVAL; + + app_id_temp = cont->app_id; + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp); + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_CAUSE, sizeof(cont->cause), &cont->cause); + + if (cont->prot_ver > 0) + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver); + + if (cont->err_pdu && cont->err_pdu_len > 0) + buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_PDU_IN_ERROR, cont->err_pdu_len, cont->err_pdu); + else + return -EINVAL; + + if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) + buf_ptr = + tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id); + + return (int)(buf_ptr - buf); +} + +/*! Decode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b). + * \param[out] user provided memory for decoded data struct. + * \param[in] buf user provided memory with the encoded value data of the IE. + * \returns 0 on success, -EINVAL on error. */ +int bssgp_dec_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *cont, const uint8_t *buf, size_t len) +{ + int rc; + struct tlv_parsed tp; + + memset(cont, 0, sizeof(*cont)); + + rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0); + if (rc < 0) + return -EINVAL; + + rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp); + if (rc < 0) + return -EINVAL; + + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc, + TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp, + BSSGP_IE_APP_ERROR_CONTAINER)); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + if (rc < 0) + return rc; + + return 0; +} + +/*! Encode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b). + * \param[out] buf user provided memory for the generated value part of the IE. + * \param[in] cont user provided input data struct. + * \returns length of encoded octets, -EINVAL on error. */ +int bssgp_enc_ran_inf_app_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_err_rim_cont *cont) +{ + uint8_t *buf_ptr = buf; + int app_cont_len = 0; + int remaining_buf_len; + + buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont); + if (!buf_ptr) + return -EINVAL; + + remaining_buf_len = len - (int)(buf_ptr - buf); + if (remaining_buf_len <= 0) + return -EINVAL; + + switch (cont->app_id) { + case BSSGP_RAN_INF_APP_ID_NACC: + app_cont_len = + bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN, + &cont->u.app_err_cont_nacc); + break; + case BSSGP_RAN_INF_APP_ID_SI3: + case BSSGP_RAN_INF_APP_ID_MBMS: + case BSSGP_RAN_INF_APP_ID_SON: + case BSSGP_RAN_INF_APP_ID_UTRA_SI: + /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + if (app_cont_len < 0) + return -EINVAL; + buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len); + + return (int)(buf_ptr - buf); +} + +/*! Parse a given message buffer into a rim-pdu struct. + * \param[out] pdu user provided memory for the resulting RAN INFORMATION PDU. + * \param[in] msg BSSGP message buffer that contains the encoded RAN INFORMATION PDU. + * \returns 0 on sccess, -EINVAL on error. */ +int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb *msg) +{ + struct tlv_parsed tp[2]; + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg); + int data_len; + int rc; + uint16_t nsei = msgb_nsei(msg); + + memset(pdu, 0, sizeof(*pdu)); + + data_len = msgb_bssgp_len(msg) - sizeof(*bgph); + if (data_len < 0) + return -EINVAL; + + rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, tp, ARRAY_SIZE(tp), bgph->pdu_type, bgph->data, data_len, 0, 0, + DLBSSGP, __func__); + if (rc < 0) + return -EINVAL; + + if (TLVP_PRESENT(&tp[0], BSSGP_IE_RIM_ROUTING_INFO)) { + rc = bssgp_parse_rim_ri(&pdu->routing_info_dest, TLVP_VAL(&tp[0], BSSGP_IE_RIM_ROUTING_INFO), + TLVP_LEN(&tp[0], BSSGP_IE_RIM_ROUTING_INFO)); + if (rc < 0) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei); + return -EINVAL; + } + } else { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Destination Cell Identifier IE\n", nsei); + return -EINVAL; + } + + if (TLVP_PRESENT(&tp[1], BSSGP_IE_RIM_ROUTING_INFO)) { + rc = bssgp_parse_rim_ri(&pdu->routing_info_src, TLVP_VAL(&tp[1], BSSGP_IE_RIM_ROUTING_INFO), + TLVP_LEN(&tp[1], BSSGP_IE_RIM_ROUTING_INFO)); + if (rc < 0) { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei); + return -EINVAL; + } + } else { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Source Cell Identifier IE\n", nsei); + return -EINVAL; + } + + if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_REQ_RIM_CONTAINER)) + pdu->rim_cont_iei = BSSGP_IE_RI_REQ_RIM_CONTAINER; + else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_RIM_CONTAINER)) + pdu->rim_cont_iei = BSSGP_IE_RI_RIM_CONTAINER; + else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_APP_ERROR_RIM_CONT)) + pdu->rim_cont_iei = BSSGP_IE_RI_APP_ERROR_RIM_CONT; + else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ACK_RIM_CONTAINER)) + pdu->rim_cont_iei = BSSGP_IE_RI_ACK_RIM_CONTAINER; + else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ERROR_RIM_COINTAINER)) + pdu->rim_cont_iei = BSSGP_IE_RI_ERROR_RIM_COINTAINER; + else { + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing or wrong RIM Container IE\n", nsei); + return -EINVAL; + } + + pdu->rim_cont = TLVP_VAL(&tp[0], pdu->rim_cont_iei); + pdu->rim_cont_len = TLVP_LEN(&tp[0], pdu->rim_cont_iei); + + /* Make sure the rim container field is not empty */ + if (pdu->rim_cont_len < 1) + return -EINVAL; + if (!pdu->rim_cont) + return -EINVAL; + + /* Note: It is not an error if we fail to parse the RIM container, + * since there are applications where parsing the RIM container + * is not necessary (routing). It is up to the API user to check + * the results. */ + switch (pdu->rim_cont_iei) { + case BSSGP_IE_RI_REQ_RIM_CONTAINER: + rc = bssgp_dec_ran_inf_req_rim_cont(&pdu->decoded.req_rim_cont, pdu->rim_cont, pdu->rim_cont_len); + break; + case BSSGP_IE_RI_RIM_CONTAINER: + rc = bssgp_dec_ran_inf_rim_cont(&pdu->decoded.rim_cont, pdu->rim_cont, pdu->rim_cont_len); + break; + case BSSGP_IE_RI_APP_ERROR_RIM_CONT: + rc = bssgp_dec_ran_inf_app_err_rim_cont(&pdu->decoded.app_err_rim_cont, pdu->rim_cont, + pdu->rim_cont_len); + break; + case BSSGP_IE_RI_ACK_RIM_CONTAINER: + rc = bssgp_dec_ran_inf_ack_rim_cont(&pdu->decoded.ack_rim_cont, pdu->rim_cont, pdu->rim_cont_len); + break; + case BSSGP_IE_RI_ERROR_RIM_COINTAINER: + rc = bssgp_dec_ran_inf_err_rim_cont(&pdu->decoded.err_rim_cont, pdu->rim_cont, pdu->rim_cont_len); + break; + default: + LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) cannot parse unknown RIM container.\n", nsei); + return 0; + } + if (rc < 0) { + LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) unable to parse RIM container.\n", nsei); + return 0; + } + pdu->decoded_present = true; + + return 0; +} + +/*! Encode a given rim-pdu struct into a message buffer. + * \param[out] pdu user provided memory that contains the RAN INFORMATION PDU to encode. + * \returns BSSGP message buffer on sccess, NULL on error. */ +struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + uint8_t rim_ri_buf[BSSGP_RIM_ROUTING_INFO_MAXLEN]; + int rc; + + if (!msg) + return NULL; + bgph = (struct bssgp_normal_hdr *)msgb_put(msg, sizeof(*bgph)); + + /* Set PDU type based on RIM container type */ + switch (pdu->rim_cont_iei) { + case BSSGP_IE_RI_REQ_RIM_CONTAINER: + bgph->pdu_type = BSSGP_PDUT_RAN_INFO_REQ; + break; + case BSSGP_IE_RI_RIM_CONTAINER: + bgph->pdu_type = BSSGP_PDUT_RAN_INFO; + break; + case BSSGP_IE_RI_APP_ERROR_RIM_CONT: + bgph->pdu_type = BSSGP_PDUT_RAN_INFO_APP_ERROR; + break; + case BSSGP_IE_RI_ACK_RIM_CONTAINER: + bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ACK; + break; + case BSSGP_IE_RI_ERROR_RIM_COINTAINER: + bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ERROR; + break; + default: + /* The caller must correctly specify the container type! */ + OSMO_ASSERT(false); + } + + /* Put RIM routing information */ + rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_dest); + if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN) + goto error; + msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf); + rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_src); + if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN) + goto error; + msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf); + + /* Put RIM container */ + if (pdu->decoded_present) { + uint8_t *rim_cont_buf = talloc_zero_size(msg, msg->data_len); + if (!rim_cont_buf) + goto error; + + switch (pdu->rim_cont_iei) { + case BSSGP_IE_RI_REQ_RIM_CONTAINER: + rc = bssgp_enc_ran_inf_req_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.req_rim_cont); + break; + case BSSGP_IE_RI_RIM_CONTAINER: + rc = bssgp_enc_ran_inf_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.rim_cont); + break; + case BSSGP_IE_RI_APP_ERROR_RIM_CONT: + rc = bssgp_enc_ran_inf_app_err_rim_cont(rim_cont_buf, msg->data_len, + &pdu->decoded.app_err_rim_cont); + break; + case BSSGP_IE_RI_ACK_RIM_CONTAINER: + rc = bssgp_enc_ran_inf_ack_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.ack_rim_cont); + break; + case BSSGP_IE_RI_ERROR_RIM_COINTAINER: + rc = bssgp_enc_ran_inf_err_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.err_rim_cont); + break; + default: + /* The API user must set the iei properly! */ + OSMO_ASSERT(false); + } + if (rc < 0) { + talloc_free(rim_cont_buf); + goto error; + } + + msgb_tvlv_put(msg, pdu->rim_cont_iei, rc, rim_cont_buf); + talloc_free(rim_cont_buf); + } else { + /* Make sure the RIM container is actually present. */ + OSMO_ASSERT(pdu->rim_cont_iei != 0 && pdu->rim_cont_len > 0 && pdu->rim_cont); + msgb_tvlv_put(msg, pdu->rim_cont_iei, pdu->rim_cont_len, pdu->rim_cont); + } + + return msg; +error: + msgb_free(msg); + return 0; +} + +/*! Send RIM RAN INFORMATION REQUEST via BSSGP (3GPP TS 48.018, section 10.6.1). + * \param[in] pdu user provided memory for the RAN INFORMATION PDU to be sent. + * \param[in] nsei BSSGP network service entity identifier (NSEI). + * \returns 0 on sccess, -EINVAL on error. */ +int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei) +{ + struct msgb *msg; + struct bssgp_normal_hdr *bgph; + char ri_src_str[64]; + char ri_dest_str[64]; + + /* Encode RIM PDU into mesage buffer */ + msg = bssgp_encode_rim_pdu(pdu); + if (!msg) { + LOGP(DLBSSGP, LOGL_ERROR, + "BSSGP RIM (NSEI=%u) unable to encode BSSGP RIM PDU\n", nsei); + return -EINVAL; + } + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + + bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg); + DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s, src=%s, dest=%s\n", + nsei, bssgp_pdu_str(bgph->pdu_type), + bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src), + bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &pdu->routing_info_dest)); + + return bssgp_ns_send(bssgp_ns_send_data, msg); +} + +/*! Send encoded RAN TRANSPARENT CONTAINER via BSSGP (3GPP TS 29.060, section 7.7.43). + * \param[in] msg user provided memory for the encoded RAN TRANSPARENT CONTAINER to be sent. + * (this function will take ownership of msg). + * \param[in] nsei BSSGP network service entity identifier (NSEI). + * \returns 0 on sccess, -EINVAL on error. */ +int bssgp_tx_rim_encoded(struct msgb *msg, uint16_t nsei) +{ + struct bssgp_normal_hdr *bgph; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + + bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg); + DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s\n", + nsei, bssgp_pdu_str(bgph->pdu_type)); + + return bssgp_ns_send(bssgp_ns_send_data, msg); +} + +/* For internal use only (called from gprs_bssgp.c) */ +int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci) +{ + struct osmo_bssgp_prim nmp; + uint16_t nsei = msgb_nsei(msg); + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg); + enum bssgp_prim prim; + char ri_src_str[64]; + char ri_dest_str[64]; + + /* Specify PRIM type based on the RIM PDU */ + switch (bgph->pdu_type) { + case BSSGP_PDUT_RAN_INFO: + case BSSGP_PDUT_RAN_INFO_REQ: + case BSSGP_PDUT_RAN_INFO_ACK: + case BSSGP_PDUT_RAN_INFO_ERROR: + case BSSGP_PDUT_RAN_INFO_APP_ERROR: + prim = PRIM_BSSGP_RIM_PDU_TRANSFER; + break; + default: + /* Caller already makes sure that this can't happen. */ + OSMO_ASSERT(false); + } + + /* Send BSSGP RIM indication to NM */ + memset(&nmp, 0, sizeof(nmp)); + nmp.nsei = nsei; + nmp.bvci = bvci; + nmp.tp = tp; + if (bssgp_parse_rim_pdu(&nmp.u.rim_pdu, msg) < 0) + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); + DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RIM-PDU:%s, src=%s, dest=%s\n", + bvci, bssgp_pdu_str(bgph->pdu_type), + bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &nmp.u.rim_pdu.routing_info_src), + bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &nmp.u.rim_pdu.routing_info_dest)); + osmo_prim_init(&nmp.oph, SAP_BSSGP_RIM, prim, PRIM_OP_INDICATION, msg); + bssgp_prim_cb(&nmp.oph, NULL); + + return 0; +} diff --git a/src/gb/gprs_bssgp_util.c b/src/gb/gprs_bssgp_util.c index 77089491..92896c1f 100644 --- a/src/gb/gprs_bssgp_util.c +++ b/src/gb/gprs_bssgp_util.c @@ -32,7 +32,7 @@ #include <osmocom/gprs/gprs_bssgp.h> #include <osmocom/gprs/gprs_ns.h> -#include "common_vty.h" +#include "gprs_bssgp_internal.h" struct gprs_ns_inst *bssgp_nsi; @@ -106,6 +106,8 @@ static const struct value_string bssgp_pdu_strings[] = { { BSSGP_PDUT_UL_UNITDATA, "UL-UNITDATA" }, { BSSGP_PDUT_RA_CAPABILITY, "RA-CAPABILITY" }, { BSSGP_PDUT_PTM_UNITDATA, "PTM-UNITDATA" }, + { BSSGP_PDUT_DL_MMBS_UNITDATA, "DL-MBMS-UNITDATA" }, + { BSSGP_PDUT_UL_MMBS_UNITDATA, "UL-MBMS-UNITDATA" }, { BSSGP_PDUT_PAGING_PS, "PAGING-PS" }, { BSSGP_PDUT_PAGING_CS, "PAGING-CS" }, { BSSGP_PDUT_RA_CAPA_UDPATE, "RA-CAPABILITY-UPDATE" }, @@ -117,6 +119,10 @@ static const struct value_string bssgp_pdu_strings[] = { { BSSGP_PDUT_RESUME, "RESUME" }, { BSSGP_PDUT_RESUME_ACK, "RESUME-ACK" }, { BSSGP_PDUT_RESUME_NACK, "RESUME-NACK" }, + { BSSGP_PDUT_DUMMY_PAGING_PS, "DUMMY-PAGING-PS" }, + { BSSGP_PDUT_DUMMY_PAGING_PS_RESP, "DUMMY-PAGING-PS-RESP" }, + { BSSGP_PDUT_MS_REGISTR_ENQ, "MS-REGISTRATION-ENQ" }, + { BSSGP_PDUT_MS_REGISTR_ENQ_RESP, "MS-REGISTRATION-ENQ-RESP" }, { BSSGP_PDUT_BVC_BLOCK, "BVC-BLOCK" }, { BSSGP_PDUT_BVC_BLOCK_ACK, "BVC-BLOCK-ACK" }, { BSSGP_PDUT_BVC_RESET, "BVC-RESET" }, @@ -130,8 +136,11 @@ static const struct value_string bssgp_pdu_strings[] = { { BSSGP_PDUT_FLUSH_LL, "FLUSH-LL" }, { BSSGP_PDUT_FLUSH_LL_ACK, "FLUSH-LL-ACK" }, { BSSGP_PDUT_LLC_DISCARD, "LLC DISCARDED" }, + { BSSGP_PDUT_FLOW_CONTROL_PFC, "FLOW-CONTROL-PFC" }, + { BSSGP_PDUT_FLOW_CONTROL_PFC_ACK, "FLOW-CONTROL-PFC-ACK" }, { BSSGP_PDUT_SGSN_INVOKE_TRACE, "SGSN-INVOKE-TRACE" }, { BSSGP_PDUT_STATUS, "STATUS" }, + { BSSGP_PDUT_OVERLOAD, "OVERLOAD" }, { BSSGP_PDUT_DOWNLOAD_BSS_PFC, "DOWNLOAD-BSS-PFC" }, { BSSGP_PDUT_CREATE_BSS_PFC, "CREATE-BSS-PFC" }, { BSSGP_PDUT_CREATE_BSS_PFC_ACK, "CREATE-BSS-PFC-ACK" }, @@ -140,9 +149,338 @@ static const struct value_string bssgp_pdu_strings[] = { { BSSGP_PDUT_MODIFY_BSS_PFC_ACK, "MODIFY-BSS-PFC-ACK" }, { BSSGP_PDUT_DELETE_BSS_PFC, "DELETE-BSS-PFC" }, { BSSGP_PDUT_DELETE_BSS_PFC_ACK, "DELETE-BSS-PFC-ACK" }, + { BSSGP_PDUT_DELETE_BSS_PFC_REQ, "DELETE-BSS-PFC-REQ" }, + { BSSGP_PDUT_PS_HO_REQUIRED, "PS-HO-REQUIRED" }, + { BSSGP_PDUT_PS_HO_REQUIRED_ACK, "PS-HO-REQUIRED-ACK" }, + { BSSGP_PDUT_PS_HO_REQUIRED_NACK, "PS-HO-REQUIRED-NACK" }, + { BSSGP_PDUT_PS_HO_REQUEST, "PS-HO-REQUEST" }, + { BSSGP_PDUT_PS_HO_REQUEST_ACK, "PS-HO-REQUEST-ACK" }, + { BSSGP_PDUT_PS_HO_REQUEST_NACK, "PS-HO-REQUEST-NACK" }, + { BSSGP_PDUT_PS_HO_COMPLETE, "PS-HO-COMPLETE" }, + { BSSGP_PDUT_PS_HO_CANCEL, "PS-HO-CANCEL" }, + { BSSGP_PDUT_PS_HO_COMPLETE_ACK, "PS-HO-COMPLETE-ACK" }, + { BSSGP_PDUT_PERFORM_LOC_REQ, "PERFORM-LOC-REQ" }, + { BSSGP_PDUT_PERFORM_LOC_RESP, "PERFORM-LOC-RESP" }, + { BSSGP_PDUT_PERFORM_LOC_ABORT, "PERFORM-LOC-ABORT" }, + { BSSGP_PDUT_POSITION_COMMAND, "POSITION-COMMAND" }, + { BSSGP_PDUT_POSITION_RESPONSE, "POSITION-RESPONSE" }, + { BSSGP_PDUT_RAN_INFO, "RAN-INFO" }, + { BSSGP_PDUT_RAN_INFO_REQ, "RAN-INFO-REQ" }, + { BSSGP_PDUT_RAN_INFO_ACK, "RAN-INFO-ACK" }, + { BSSGP_PDUT_RAN_INFO_ERROR, "RAN-INFO-ERROR" }, + { BSSGP_PDUT_RAN_INFO_APP_ERROR, "RAN-INFO-APP-ERROR" }, + { BSSGP_PDUT_MBMS_START_REQ, "MBMS-START-REQ" }, + { BSSGP_PDUT_MBMS_START_RESP, "MBMS-START-RESP" }, + { BSSGP_PDUT_MBMS_STOP_REQ, "MBMS-STOP-REQ" }, + { BSSGP_PDUT_MBMS_STOP_RESP, "MBMS-STOP-RESP" }, + { BSSGP_PDUT_MBMS_UPDATE_REQ, "MBMS-UPDATE-REQ" }, + { BSSGP_PDUT_MBMS_UPDATE_RESP, "MBMS-UPDATE-RESP" }, { 0, NULL }, }; +static const uint8_t dl_ud_ies[] = { BSSGP_IE_PDU_LIFETIME }; +static const uint8_t ul_ud_ies[] = { BSSGP_IE_CELL_ID }; +static const uint8_t ra_cap_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_MS_RADIO_ACCESS_CAP }; +static const uint8_t dl_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU }; +static const uint8_t ul_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU }; +static const uint8_t pag_ps_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_QOS_PROFILE }; +static const uint8_t pag_cs_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_DRX_PARAMS }; +static const uint8_t ra_cap_upd_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG }; +static const uint8_t ra_cap_upd_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_RA_CAP_UPD_CAUSE }; +static const uint8_t rad_sts_ies[] = { BSSGP_IE_RADIO_CAUSE }; +static const uint8_t suspend_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA }; +static const uint8_t suspend_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR }; +static const uint8_t suspend_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA }; +static const uint8_t resume_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR }; +static const uint8_t resume_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA }; +static const uint8_t resume_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA }; +static const uint8_t d_pag_ps_ies[] = { BSSGP_IE_IMSI }; +static const uint8_t d_pag_ps_resp_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING }; +static const uint8_t d_pag_ps_rej_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING }; +static const uint8_t ms_reg_enq_ies[] = { BSSGP_IE_IMSI }; +static const uint8_t ms_reg_enq_res_ies[] = { BSSGP_IE_IMSI }; +static const uint8_t flush_ll_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_BVCI }; +static const uint8_t flush_ll_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_FLUSH_ACTION }; +static const uint8_t llc_disc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LLC_FRAMES_DISCARDED, BSSGP_IE_BVCI, + BSSGP_IE_NUM_OCT_AFF }; +static const uint8_t fc_bvc_ies[] = { BSSGP_IE_TAG, BSSGP_IE_BVC_BUCKET_SIZE, BSSGP_IE_BUCKET_LEAK_RATE, + BSSGP_IE_BMAX_DEFAULT_MS, BSSGP_IE_R_DEFAULT_MS }; +static const uint8_t fc_bvc_ack_ies[] = { BSSGP_IE_TAG }; +static const uint8_t fc_ms_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_MS_BUCKET_SIZE, + BSSGP_IE_BUCKET_LEAK_RATE }; +static const uint8_t fc_ms_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG }; +static const uint8_t block_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE }; +static const uint8_t block_ack_ies[] = { BSSGP_IE_BVCI }; +static const uint8_t unblock_ies[] = { BSSGP_IE_BVCI }; +static const uint8_t unblock_ack_ies[] = { BSSGP_IE_BVCI }; +static const uint8_t reset_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE }; +static const uint8_t reset_ack_ies[] = { BSSGP_IE_BVCI }; +static const uint8_t status_ies[] = { BSSGP_IE_CAUSE }; +static const uint8_t inv_trc_ies[] = { BSSGP_IE_TRACE_TYPE, BSSGP_IE_TRACE_REFERENC }; +static const uint8_t dl_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID }; +static const uint8_t crt_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, + BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE }; +static const uint8_t crt_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, + BSSGP_IE_AGG_BSS_QOS_PROFILE }; +static const uint8_t crt_bss_pfc_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE }; +static const uint8_t mod_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, + BSSGP_IE_AGG_BSS_QOS_PROFILE }; +static const uint8_t mod_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, + BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE }; +static const uint8_t del_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID }; +static const uint8_t del_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID }; +static const uint8_t fc_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_PFC_FLOW_CTRL_PARAMS }; +static const uint8_t fc_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG }; +static const uint8_t del_bss_pfc_req_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE }; +static const uint8_t ps_ho_required_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID, + BSSGP_IE_ACTIVE_PFC_LIST }; +static const uint8_t ps_ho_required_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC }; +static const uint8_t ps_ho_required_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE }; +static const uint8_t ps_ho_request_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI, BSSGP_IE_CAUSE, + BSSGP_IE_CELL_ID, BSSGP_IE_SBSS_TO_TBSS_TR_CONT, + BSSGP_IE_PFC_TO_BE_SETUP_LIST }; +static const uint8_t ps_ho_request_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC, + BSSGP_IE_TBSS_TO_SBSS_TR_CONT }; +static const uint8_t ps_ho_request_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE }; +static const uint8_t ps_ho_compl_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI }; +static const uint8_t ps_ho_cancel_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID }; +static const uint8_t ps_ho_compl_ack_ies[] = { BSSGP_IE_TLLI }; +static const uint8_t overload_ies[] = { BSSGP_IE_PRIO_CLASS_IND }; +static const uint8_t rinfo_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_RIM_CONTAINER }; +static const uint8_t rinfo_req_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_REQ_RIM_CONTAINER }; +static const uint8_t rinfo_ack_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ACK_RIM_CONTAINER }; +static const uint8_t rinfo_err_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ERROR_RIM_COINTAINER }; +static const uint8_t rinfo_aerr_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_APP_ERROR_RIM_CONT }; + +#define DL BSSGP_PDUF_DL +#define UL BSSGP_PDUF_UL +#define SIG BSSGP_PDUF_SIG +#define PTP BSSGP_PDUF_PTP +#define PTM BSSGP_PDUF_PTM + +const struct osmo_tlv_prot_def osmo_pdef_bssgp = { + .name = "BSSGP", + .tlv_def = &tvlv_att_def, + .msg_def = { + [BSSGP_PDUT_DL_UNITDATA] = MSG_DEF("DL-UNITDATA", dl_ud_ies, DL|PTP), + [BSSGP_PDUT_UL_UNITDATA] = MSG_DEF("UL-UNITDATA", ul_ud_ies, UL|PTP), + [BSSGP_PDUT_RA_CAPABILITY] = MSG_DEF("RA-CAPABILITY", ra_cap_ies, DL|PTP), + [BSSGP_PDUT_DL_MMBS_UNITDATA] = MSG_DEF("DL-MBMS-UNITDATA", dl_mb_ud_ies, DL|PTM), + [BSSGP_PDUT_UL_MMBS_UNITDATA] = MSG_DEF("UL-MBMS-UNITDATA", ul_mb_ud_ies, UL|PTM), + [BSSGP_PDUT_PAGING_PS] = MSG_DEF("PAGING-PS", pag_ps_ies, DL|PTP|SIG), + [BSSGP_PDUT_PAGING_CS] = MSG_DEF("PAGING-CS", pag_cs_ies, DL|PTP|SIG), + [BSSGP_PDUT_RA_CAPA_UDPATE] = MSG_DEF("RA-CAPABILITY-UPDATE", ra_cap_upd_ies, UL|PTP), + [BSSGP_PDUT_RA_CAPA_UPDATE_ACK] = MSG_DEF("RA-CAPABILITY-UPDATE-ACK", ra_cap_upd_ack_ies, DL|PTP), + [BSSGP_PDUT_RADIO_STATUS] = MSG_DEF("RADIO-STATUS", rad_sts_ies, UL|PTP), + [BSSGP_PDUT_SUSPEND] = MSG_DEF("SUSPEND", suspend_ies, UL|SIG), + [BSSGP_PDUT_SUSPEND_ACK] = MSG_DEF("SUSPEND-ACK", suspend_ack_ies, DL|SIG), + [BSSGP_PDUT_SUSPEND_NACK] = MSG_DEF("SUSPEND-NACK", suspend_nack_ies, DL|SIG), + [BSSGP_PDUT_RESUME] = MSG_DEF("RESUME", resume_ies, UL|SIG), + [BSSGP_PDUT_RESUME_ACK] = MSG_DEF("RESUME-ACK", resume_ack_ies, DL|SIG), + [BSSGP_PDUT_RESUME_NACK] = MSG_DEF("RESUME-NACK", resume_nack_ies, DL|SIG), + [BSSGP_PDUT_DUMMY_PAGING_PS] = MSG_DEF("DUMMY-PAGING-PS", d_pag_ps_ies, DL|SIG|PTP), + [BSSGP_PDUT_DUMMY_PAGING_PS_RESP] = MSG_DEF("DUMMY-PAGING-PS-RESP", d_pag_ps_resp_ies, UL|SIG|PTP), + [BSSGP_PDUT_PAGING_PS_REJECT] = MSG_DEF("PAGING-PS-REJ", d_pag_ps_rej_ies, UL|SIG|PTP), + [BSSGP_PDUT_MS_REGISTR_ENQ] = MSG_DEF("MS-REGISRATION-ENQ", ms_reg_enq_ies, UL|SIG), + [BSSGP_PDUT_MS_REGISTR_ENQ_RESP] = MSG_DEF("MS-REGISRATION-ENQ-RESP", ms_reg_enq_res_ies, DL|SIG), + [BSSGP_PDUT_FLUSH_LL] = MSG_DEF("FLUSH-LL", flush_ll_ies, DL|SIG), + [BSSGP_PDUT_FLUSH_LL_ACK] = MSG_DEF("FLUSH-LL-ACK", flush_ll_ack_ies, UL|SIG), + [BSSGP_PDUT_LLC_DISCARD] = MSG_DEF("LLC-DISCARDED", llc_disc_ies, UL|SIG), + [BSSGP_PDUT_FLOW_CONTROL_BVC] = MSG_DEF("FC-BVC", fc_bvc_ies, UL|PTP), + [BSSGP_PDUT_FLOW_CONTROL_BVC_ACK] = MSG_DEF("FC-BVC-ACK", fc_bvc_ack_ies, DL|PTP), + [BSSGP_PDUT_FLOW_CONTROL_MS] = MSG_DEF("FC-MS", fc_ms_ies, UL|PTP), + [BSSGP_PDUT_FLOW_CONTROL_MS_ACK] = MSG_DEF("FC-MS-ACK", fc_ms_ack_ies, DL|PTP), + [BSSGP_PDUT_BVC_BLOCK] = MSG_DEF("BVC-BLOCK", block_ies, UL|SIG), + [BSSGP_PDUT_BVC_BLOCK_ACK] = MSG_DEF("BVC-BLOCK-ACK", block_ack_ies, DL|SIG), + [BSSGP_PDUT_BVC_UNBLOCK] = MSG_DEF("BVC-UNBLOCK", unblock_ies, UL|SIG), + [BSSGP_PDUT_BVC_UNBLOCK_ACK] = MSG_DEF("BVC-UNBLOCK-ACK", unblock_ack_ies, DL|SIG), + [BSSGP_PDUT_BVC_RESET] = MSG_DEF("BVC-RESET", reset_ies, UL|DL|SIG|PTP), + [BSSGP_PDUT_BVC_RESET_ACK] = MSG_DEF("BVC-RESET-ACK", reset_ack_ies, UL|DL|SIG|PTP), + [BSSGP_PDUT_STATUS] = MSG_DEF("STATUS", status_ies, UL|DL|PTP|SIG|PTM), + [BSSGP_PDUT_SGSN_INVOKE_TRACE] = MSG_DEF("SGSN-INVOKE-TRACE", inv_trc_ies, DL|SIG), + [BSSGP_PDUT_DOWNLOAD_BSS_PFC] = MSG_DEF("DOWNLOAD-BSS-PFC", dl_bss_pfc_ies, UL|PTP), + [BSSGP_PDUT_CREATE_BSS_PFC] = MSG_DEF("CREATE-BSS-PFC", crt_bss_pfc_ies, DL|PTP), + [BSSGP_PDUT_CREATE_BSS_PFC_ACK] = MSG_DEF("CREATE-BSS-PFC-ACK", crt_bss_pfc_ack_ies, UL|PTP), + [BSSGP_PDUT_CREATE_BSS_PFC_NACK] = MSG_DEF("CREATE-BSS-PFC-NACK", crt_bss_pfc_nack_ies, UL|PTP), + [BSSGP_PDUT_MODIFY_BSS_PFC] = MSG_DEF("MODIFY-BSS-PFC", mod_bss_pfc_ies, DL|PTP), + [BSSGP_PDUT_MODIFY_BSS_PFC_ACK] = MSG_DEF("MODIFY-BSS-PFC-ACK", mod_bss_pfc_ack_ies, UL|PTP), + [BSSGP_PDUT_DELETE_BSS_PFC] = MSG_DEF("DELETE-BSS-PFC", del_bss_pfc_ies, DL|PTP), + [BSSGP_PDUT_DELETE_BSS_PFC_ACK] = MSG_DEF("DELETE-BSS-PFC-ACK", del_bss_pfc_ack_ies, UL|PTP), + [BSSGP_PDUT_FLOW_CONTROL_PFC] = MSG_DEF("FC-PFC", fc_pfc_ies, UL|PTP), + [BSSGP_PDUT_FLOW_CONTROL_PFC_ACK] = MSG_DEF("FC-PFC-ACK", fc_pfc_ack_ies, DL|PTP), + [BSSGP_PDUT_DELETE_BSS_PFC_REQ] = MSG_DEF("DELETE-BSS-PFC-REQ", del_bss_pfc_req_ies, UL|PTP), + [BSSGP_PDUT_PS_HO_REQUIRED] = MSG_DEF("PS-HO-REQUIRED", ps_ho_required_ies, UL|PTP), + [BSSGP_PDUT_PS_HO_REQUIRED_ACK] = MSG_DEF("PS-HO-REQUIRED-ACK", ps_ho_required_ack_ies, DL|PTP), + [BSSGP_PDUT_PS_HO_REQUIRED_NACK] = MSG_DEF("PS-HO-REQUIRED-NACK", ps_ho_required_nack_ies, DL|PTP), + [BSSGP_PDUT_PS_HO_REQUEST] = MSG_DEF("PS-HO-REQUEST", ps_ho_request_ies, DL|PTP), + [BSSGP_PDUT_PS_HO_REQUEST_ACK] = MSG_DEF("PS-HO-REQUEST-ACK", ps_ho_request_ack_ies, UL|PTP), + [BSSGP_PDUT_PS_HO_REQUEST_NACK] = MSG_DEF("PS-HO-REQUEST-NACK", ps_ho_request_nack_ies, UL|PTP), + [BSSGP_PDUT_PS_HO_COMPLETE] = MSG_DEF("PS-HO-COMPLETE", ps_ho_compl_ies, UL|PTP), + [BSSGP_PDUT_PS_HO_CANCEL] = MSG_DEF("PS-HO-CANCEL", ps_ho_cancel_ies, UL|PTP), + [BSSGP_PDUT_PS_HO_COMPLETE_ACK] = MSG_DEF("PS-HO-COMPLETE-ACK", ps_ho_compl_ack_ies, DL|PTP), + [BSSGP_PDUT_OVERLOAD] = MSG_DEF("OVERLOAD", overload_ies, DL|SIG), + /* TODO: Messages on LCS SAP */ + /* Messages on RIM SAP */ + [BSSGP_PDUT_RAN_INFO] = MSG_DEF("RAN-INFORMATION", rinfo_ies, DL|UL|SIG), + [BSSGP_PDUT_RAN_INFO_REQ] = MSG_DEF("RAN-INFORMATION-REQUEST", rinfo_req_ies, DL|UL|SIG), + [BSSGP_PDUT_RAN_INFO_ACK] = MSG_DEF("RAN-INFORMATION-ACK", rinfo_ack_ies, DL|UL|SIG), + [BSSGP_PDUT_RAN_INFO_ERROR] = MSG_DEF("RAN-INFORMATION-ERROR", rinfo_err_ies, DL|UL|SIG), + [BSSGP_PDUT_RAN_INFO_APP_ERROR] = MSG_DEF("RAN-INFORMATION-APP-ERROR", rinfo_aerr_ies, DL|UL|SIG), + /* TODO: Messages on MBMS SAP */ + }, + .ie_def = { + [BSSGP_IE_ALIGNMENT] = { 0, "Alignment Octets" }, + [BSSGP_IE_BMAX_DEFAULT_MS] = { 2, "Bmax default MS" }, + [BSSGP_IE_BSS_AREA_ID] = { 1, "BSS Area Indication" }, + [BSSGP_IE_BUCKET_LEAK_RATE] = { 2, "Bucket Leak Rate (R)" }, + [BSSGP_IE_BVC_BUCKET_SIZE] = { 2, "BVC Bucket Size" }, + [BSSGP_IE_BVCI] = { 2, "BVCI" }, + [BSSGP_IE_BVC_MEASUREMENT] = {2, "BVC Measurement" }, + [BSSGP_IE_CAUSE] = { 1, "Cause" }, + [BSSGP_IE_CELL_ID] = { 8, "Cell Identifier" }, + [BSSGP_IE_CHAN_NEEDED] = { 1, "Channel Needed" }, + [BSSGP_IE_DRX_PARAMS] = { 2, "DRX Parameters" }, + [BSSGP_IE_EMLPP_PRIO] = { 3, "eMLPP Priority" }, + [BSSGP_IE_FLUSH_ACTION] = { 1, "Flush Action" }, + [BSSGP_IE_IMSI] = { 1, "Mobile Identity" }, + [BSSGP_IE_LLC_PDU] = { 0, "LLC-PDU" }, + [BSSGP_IE_LLC_FRAMES_DISCARDED] = { 1, "LLC Frames Discarded" }, + [BSSGP_IE_LOCATION_AREA] = { 5, "Location Area" }, + [BSSGP_IE_LSA_ID_LIST] = { 3, "LSA Identifier List" }, + [BSSGP_IE_LSA_INFORMATION] = { 5, "LSA Information" }, + [BSSGP_IE_MOBILE_ID] = { 1, "Mobile Identity" }, + [BSSGP_IE_MS_BUCKET_SIZE] = { 2, "MS Bucket Size" }, + [BSSGP_IE_MS_RADIO_ACCESS_CAP] = { 1, "MS Radio Access Capability" }, + [BSSGP_IE_OMC_ID] = { 1, "OMC Id" }, + [BSSGP_IE_PDU_IN_ERROR] = { 0, "PDU In Error" }, + [BSSGP_IE_PDU_LIFETIME] = { 2, "PDU Lifetime" }, + [BSSGP_IE_PRIORITY] = { 1, "Priority" }, + [BSSGP_IE_QOS_PROFILE] = { 3, "QoS Profile" }, + [BSSGP_IE_RADIO_CAUSE] = { 1, "Radio Cause" }, + [BSSGP_IE_RA_CAP_UPD_CAUSE] = { 1, "RA-Cap-UPD-Cause" }, + [BSSGP_IE_ROUTEING_AREA] = { 6, "Routeing Area" }, + [BSSGP_IE_R_DEFAULT_MS] = { 2, "R_default_MS" }, + [BSSGP_IE_SUSPEND_REF_NR] = { 1, "Suspend Reference Number" }, + [BSSGP_IE_TAG] = { 1, "Tag" }, + [BSSGP_IE_TLLI] = { 4, "TLLI" }, + [BSSGP_IE_TMSI] = { 4, "TMSI" }, + [BSSGP_IE_TRACE_REFERENC] = { 2, "Trace Reference" }, + [BSSGP_IE_TRACE_TYPE] = { 1, "Trace Type" }, + [BSSGP_IE_TRANSACTION_ID] = { 2, "Transaction Id" }, + [BSSGP_IE_TRIGGER_ID] = { 1, "Trigger Id" }, + [BSSGP_IE_NUM_OCT_AFF] = { 3, "Number of octets affected" }, + [BSSGP_IE_PACKET_FLOW_ID] = { 1, "Packet Flow Identifier (PFI)" }, + [BSSGP_IE_AGG_BSS_QOS_PROFILE] = { 14, "Aggregate BSS QoS Profile" }, + [BSSGP_IE_PACKET_FLOW_TIMER] = { 1, "GPRS Timer" }, + [BSSGP_IE_FEATURE_BITMAP] = { 1, "Feature Bitmap" }, + [BSSGP_IE_BUCKET_FULL_RATIO] = { 1, "Bucket Full Ratio" }, + [BSSGP_IE_SERVICE_UTRAN_CCO] = { 1, "Service UTRAN COO" }, + [BSSGP_IE_NSEI] = { 2, "NSEI" }, + [BSSGP_IE_RRLP_APDU] = { 1, "RLLP APDU" }, + [BSSGP_IE_LCS_QOS] = { 4, "LCS QoS" }, + [BSSGP_IE_LCS_CLIENT_TYPE] = { 1, "LCS Client Type" }, + [BSSGP_IE_REQUESTED_GPS_AST_DATA] = { 4, "Requested GPS Assistance Data" }, + [BSSGP_IE_LOCATION_TYPE] = { 2, "Location Type" }, + [BSSGP_IE_LOCATION_ESTIMATE] = { 1, "Location Estimate" }, + [BSSGP_IE_POSITIONING_DATA] = { 1, "Positioning Data" }, + [BSSGP_IE_DECIPHERING_KEYS] = { 15, "Deciphering Keys" }, + [BSSGP_IE_LCS_PRIORITY] = { 1, "LCS Priority" }, + [BSSGP_IE_LCS_CAUSE] = { 1, "LCS Cause" }, + [BSSGP_IE_LCS_CAPABILITY] = { 1, "LCS Capability" }, + [BSSGP_IE_RRLP_FLAGS] = { 1, "RRLP Flags" }, + [BSSGP_IE_RIM_APP_IDENTITY] = { 1, "RIM Application Identity" }, + [BSSGP_IE_RIM_SEQ_NR] = { 4, "RIM Sequence Number" }, + [BSSGP_IE_RIM_REQ_APP_CONTAINER] = { 12, "RIM-REQUEST RIM Container" }, + [BSSGP_IE_RAN_INFO_APP_CONTAINER] = { 12, "RAN-INFORMATION RIM Container" }, + [BSSGP_IE_RI_ACK_RIM_CONTAINER] = { 9, "RAN-INFORMATION-ACK RIM Container" }, + [BSSGP_IE_RI_ERROR_RIM_COINTAINER] = { 9, "RAN-INFOIRMATION-ERROR RIM Container" }, + [BSSGP_IE_RI_APP_ERROR_RIM_CONT] = { 14, "RAN-INFORMATION-APP-ERROR RIM Container" }, + [BSSGP_IE_RIM_PDU_INDICATIONS] = { 1, "RIM PDU Indications" }, + [BSSGP_IE_RIM_PROTOCOL_VERSION] = { 1, "RIM Protocol Version Number" }, + [BSSGP_IE_PFC_FLOW_CTRL_PARAMS] = { 7, "PFC FLow Control Parameters" }, + [BSSGP_IE_GLOBAL_CN_ID] = { 5, "Global CN-Id" }, + [BSSGP_IE_RIM_ROUTING_INFO] = { 1, "RIM Routing Information" }, + [BSSGP_IE_MBMS_SESSION_ID] = { 0, "MBMS Session Identity" }, + [BSSGP_IE_MBMS_SESSION_DURATION] = { 0, "MBMS Session Duration" }, + [BSSGP_IE_MBMS_SA_ID_LIST] = { 3, "MBMS Service Area Identity List" }, + [BSSGP_IE_MBMS_RESPONSE] = { 1, "MBMS Response" }, + [BSSGP_IE_MBMS_RA_LIST] = { 9, "MBMS Routing Area List" }, + [BSSGP_IE_MBMS_SESSION_INFO] = { 1, "MBMS Session Information" }, + [BSSGP_IE_TMGI] = { 6, "TMGI" }, + [BSSGP_IE_MBMS_STOP_CAUSE] = { 1, "MBM Stop Cause" }, + [BSSGP_IE_SBSS_TO_TBSS_TR_CONT] = { 7, "Source BSS to Target BSS Transparent Container" }, + [BSSGP_IE_TBSS_TO_SBSS_TR_CONT] = { 0, "Target BSS to Source BSS Transparent Container" }, + [BSSGP_IE_NAS_CONT_FOR_PS_HO] = { 0, "NAS container for PS Handover" }, + [BSSGP_IE_PFC_TO_BE_SETUP_LIST] = { 9, "PFCs to be set-up list" }, + [BSSGP_IE_LIST_OF_SETUP_PFC] = { 1, "List of set-up PFCs" }, + [BSSGP_IE_EXT_FEATURE_BITMAP] = { 1, "Extended Feature Bitmap" }, + [BSSGP_IE_SRC_TO_TGT_TR_CONT] = { 0, "Source to Target Transparent Container" }, + [BSSGP_IE_TGT_TO_SRC_TR_CONT] = { 0, "Target to Source Transparent Container" }, + [BSSGP_IE_NC_ID] = { 8, "RNC Identifier" }, + [BSSGP_IE_PAGE_MODE] = { 1, "Page Mode" }, + [BSSGP_IE_CONTAINER_ID] = { 1, "Container ID" }, + [BSSGP_IE_GLOBAL_TFI] = { 1, "Global TFI" }, + [BSSGP_IE_IMEI] = { 1, "IMEI" }, + [BSSGP_IE_TIME_TO_MBMS_DATA_XFR] = { 1, "Time to MBMS Data Transfer" }, + [BSSGP_IE_MBMS_SESSION_REP_NR] = { 1, "MBMS Session Repetition Number" }, + [BSSGP_IE_INTER_RAT_HO_INFO] = { 0, "Inter RAT Handover Info" }, + [BSSGP_IE_PS_HO_COMMAND] = { 0, "PS Handover Command" }, + [BSSGP_IE_PS_HO_INDICATIONS] = { 1, "PS Handover Indications" }, + [BSSGP_IE_SI_PSI_CONTAINER] = { 1, "SI/PSI Container" }, + [BSSGP_IE_ACTIVE_PFC_LIST] = { 2, "Active PFCs List" }, + [BSSGP_IE_VELOCITY_DATA] = { 0, "Velocity Data" }, + [BSSGP_IE_DTM_HO_COMMAND] = { 0, "DTM Handover Command" }, + [BSSGP_IE_CS_INDICATION] = { 1, "CS Indication" }, + [BSSGP_IE_RQD_GANNS_AST_DATA] = { 0, "Requested GANSS Assistance Data" }, + [BSSGP_IE_GANSS_LOCATION_TYPE] = { 1, "GANSS Location Type" }, + [BSSGP_IE_GANSS_POSITIONING_DATA] = { 0, "GANSS Positioning Data" }, + [BSSGP_IE_FLOW_CTRL_GRANULARITY] = { 1, "Flow Control Granularity" }, + [BSSGP_IE_ENB_ID] = { 6, "eNB Identifier" }, + [BSSGP_IE_EUTRAN_IRAT_HO_INFO] = { 0, "E-UTRAN Inter RAT Handover Info" }, + [BSSGP_IE_SUB_PID4RAT_FREQ_PRIO] = { 1, "Subscriber Profile ID for RAT/Frequency priority" }, + [BSSGP_IE_REQ4IRAT_HO_INFO] = { 1, "Request for Inter-RAT Handover Info" }, + [BSSGP_IE_RELIABLE_IRAT_HO_INFO] = { 1, "Reliable Inter-RAT Handover Info" }, + [BSSGP_IE_SON_TRANSFER_APP_ID] = { 0, "SON Transfer Application Identity" }, + [BSSGP_IE_CSG_ID] = { 5, "CSG Identifier" }, + [BSSGP_IE_TAC] = { 3, "Tracking Area Code" }, + [BSSGP_IE_REDIRECT_ATTEMPT_FLAG] = { 1, "Redirect Attempt Flag" }, + [BSSGP_IE_REDIRECTION_INDICATION] = { 1, "Redirection Indication" }, + [BSSGP_IE_REDIRECTION_COMPLETED] = { 1, "Redirection Completed" }, + [BSSGP_IE_UNCONF_SEND_STATE_VAR] = { 2, "Unconfirmed send state variable" }, + [BSSGP_IE_IRAT_MEASUREMENT_CONF] = { 10, "IRAT Measurement Configuration" }, + [BSSGP_IE_SCI] = { 1, "SCI" }, + [BSSGP_IE_GGSN_PGW_LOCATION] = { 1, "GGSN/P-GW Location" }, + [BSSGP_IE_SELECTED_PLMN_ID] = { 3, "Selected PLMN ID" }, + [BSSGP_IE_PRIO_CLASS_IND] = { 1, "Priority Class Indication" }, + [BSSGP_IE_SOURCE_CELL_ID] = { 6, "Source Cell ID" }, + [BSSGP_IE_IRAT_MEAS_CFG_E_EARFCN] = { 10, "IRAT Measurement Configuration (extended E-ARFCNs)" }, + [BSSGP_IE_EDRX_PARAMETERS] = { 1, "eDRX Parameters" }, + [BSSGP_IE_T_UNTIL_NEXT_PAGING] = { 2, "Time Until Next Paging Occasion" }, + [BSSGP_IE_COVERAGE_CLASS] = { 1, "Coverage Class" }, + [BSSGP_IE_PAGING_ATTEMPT_INFO] = { 1, "Paging Attempt Information" }, + [BSSGP_IE_EXCEPTION_REPORT_FLAG] = { 1, "Exception Report Flag" }, + [BSSGP_IE_OLD_RA_ID] = { 6, "Old Routing Area Identification" }, + [BSSGP_IE_ATTACH_IND] = { 1, "Attach Indicator" }, + [BSSGP_IE_PLMN_ID] = { 3, "PLMN Identity" }, + [BSSGP_IE_MME_QUERY] = { 1, "MME Query" }, + [BSSGP_IE_SGSN_GROUP_ID] = { 3, "SGSN Group Identity" }, + [BSSGP_IE_ADDITIONAL_PTMSI] = { 4, "Additional P-TMSI" }, + [BSSGP_IE_UE_USAGE_TYPE] = { 1, "UE Usage Type" }, + [BSSGP_IE_MLAT_TIMER] = { 1, "Multilateration Timer" }, + [BSSGP_IE_MLAT_TA] = { 2, "Multilateration Timing Advance" }, + [BSSGP_IE_MS_SYNC_ACCURACY] = { 1, "MS Sync Accuracy" }, + [BSSGP_IE_BTS_RX_ACCURACY_LVL] = { 1, "BTS Reception Accuracy Level" }, + [BSSGP_IE_TA_REQ] = { 1, "Timing Advance Request (TAR)" }, + }, +}; + +#undef DL +#undef UL +#undef SIG +#undef PTP +#undef PTM + + const char *bssgp_cause_str(enum gprs_bssgp_cause cause) { return get_value_string(bssgp_cause_strings, cause); @@ -210,7 +548,7 @@ int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei, _bvci = osmo_htons(bvci); msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } /* Chapter 10.4.14: Status */ @@ -224,17 +562,17 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg) cause is either "BVCI blocked" or "BVCI unknown" */ if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED) { if (bvci == NULL) - LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: " + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: " "missing conditional BVCI\n", bssgp_cause_str(cause)); } else { if (bvci != NULL) - LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: " + LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: " "unexpected conditional BVCI\n", bssgp_cause_str(cause)); } - LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n", + LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n", bvci ? *bvci : 0, bssgp_cause_str(cause)); msgb_nsei(msg) = msgb_nsei(orig_msg); msgb_bvci(msg) = 0; @@ -248,5 +586,5 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg) msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg)); - return gprs_ns_sendmsg(bssgp_nsi, msg); + return bssgp_ns_send(bssgp_ns_send_data, msg); } diff --git a/src/gb/gprs_bssgp_vty.c b/src/gb/gprs_bssgp_vty.c index 5dab94e7..d04641d9 100644 --- a/src/gb/gprs_bssgp_vty.c +++ b/src/gb/gprs_bssgp_vty.c @@ -44,8 +44,6 @@ #include <osmocom/vty/telnet_interface.h> #include <osmocom/vty/misc.h> -#include "common_vty.h" - static void log_set_bvc_filter(struct log_target *target, struct bssgp_bvc_ctx *bctx) { @@ -207,15 +205,15 @@ DEFUN(logging_fltr_bvc, int bssgp_vty_init(void) { - install_element_ve(&show_bssgp_cmd); - install_element_ve(&show_bssgp_stats_cmd); - install_element_ve(&show_bvc_cmd); - install_element_ve(&logging_fltr_bvc_cmd); - install_element_ve(&bvc_reset_cmd); + install_lib_element_ve(&show_bssgp_cmd); + install_lib_element_ve(&show_bssgp_stats_cmd); + install_lib_element_ve(&show_bvc_cmd); + install_lib_element_ve(&logging_fltr_bvc_cmd); + install_lib_element_ve(&bvc_reset_cmd); - install_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd); + install_lib_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd); - install_element(CONFIG_NODE, &cfg_bssgp_cmd); + install_lib_element(CONFIG_NODE, &cfg_bssgp_cmd); install_node(&bssgp_node, config_write_bssgp); return 0; diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c index 14fe6614..304fe7c1 100644 --- a/src/gb/gprs_ns.c +++ b/src/gb/gprs_ns.c @@ -29,7 +29,7 @@ * @{ * * GPRS Networks Service (NS) messages on the Gb interface - * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) * * Some introduction into NS: NS is used typically on top of frame relay, * but in the ip.access world it is encapsulated in UDP packets. It serves @@ -352,8 +352,7 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci, */ void gprs_nsvc_delete(struct gprs_nsvc *nsvc) { - if (osmo_timer_pending(&nsvc->timer)) - osmo_timer_del(&nsvc->timer); + osmo_timer_del(&nsvc->timer); llist_del(&nsvc->list); rate_ctr_group_free(nsvc->ctrg); osmo_stat_item_group_free(nsvc->statg); @@ -486,8 +485,8 @@ static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg) } /* Increment number of Uplink bytes */ - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]); - rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg)); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_PKTS_OUT)); + rate_ctr_add(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BYTES_OUT), msgb_l2len(msg)); switch (nsvc->ll) { case GPRS_NS_LL_UDP: @@ -533,7 +532,7 @@ static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type) /*! Transmit a NS-RESET on a given NSVC * \param[in] nsvc NS-VC used for transmission - * \paam[in] cause Numeric NS cause value + * \param[in] cause Numeric NS cause value */ int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause) { @@ -619,7 +618,7 @@ int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause, return gprs_ns_tx(nsvc, msg); } -/*! Transmit a NS-BLOCK on a tiven NS-VC +/*! Transmit a NS-BLOCK on a given NS-VC * \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted * \param[in] cause Numeric NS Cause value * \returns 0 in case of success @@ -643,7 +642,7 @@ int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause) /* be conservative and mark it as blocked even now! */ ns_mark_blocked(nsvc); - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED)); msg->l2h = msgb_put(msg, sizeof(*nsh)); nsh = (struct gprs_ns_hdr *) msg->l2h; @@ -750,8 +749,7 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode) nsvc->nsei, get_value_string(timer_mode_strs, mode), seconds); - if (osmo_timer_pending(&nsvc->timer)) - osmo_timer_del(&nsvc->timer); + osmo_timer_del(&nsvc->timer); osmo_gettimeofday(&nsvc->timer_started, NULL); nsvc->timer_mode = mode; @@ -781,15 +779,15 @@ static void gprs_ns_timer_cb(void *data) switch (nsvc->timer_mode) { case NSVC_TIMER_TNS_ALIVE: /* Tns-alive case: we expired without response ! */ - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_ALIVE]); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_ALIVE)); nsvc->alive_retries++; if (nsvc->alive_retries > nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { /* mark as dead (and blocked unless IP-SNS) */ - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_DEAD)); if (!nsvc->nsi->bss_sns_fi && nsvc->nsi->nsip.use_reset_block_unblock) { ns_set_state(nsvc, NSE_S_BLOCKED); - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED)); } else ns_set_state(nsvc, 0); LOGP(DNS, LOGL_NOTICE, @@ -816,7 +814,7 @@ static void gprs_ns_timer_cb(void *data) nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE); break; case NSVC_TIMER_TNS_RESET: - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_RESET]); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_RESET)); if (!(nsvc->state & NSE_S_RESET)) LOGP(DNS, LOGL_NOTICE, "NSEI=%u Reset timed out but RESET flag is not set\n", @@ -1076,7 +1074,7 @@ int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause) * if the NS-VC is ALIVE and not BLOCKED. After that, it adds a NS * header for the NS-UNITDATA message type and sends it off. * - * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive + * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive */ int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg) { @@ -1251,7 +1249,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) ns_osmo_signal_dispatch_mismatch(*nsvc, msg, NS_PDUT_RESET, NS_IE_VCI); - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI)); gprs_ns_tx_reset_ack(*nsvc); return 0; } @@ -1277,14 +1275,14 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) ns_osmo_signal_dispatch_mismatch(*nsvc, msg, NS_PDUT_RESET, NS_IE_NSEI); - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI)); rc = gprs_ns_tx_reset_ack(*nsvc); CHECK_TX_RC(rc, *nsvc); return 0; } /* NSEI has changed */ - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG)); (*nsvc)->nsei = nsei; } @@ -1292,7 +1290,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE); if (orig_nsvc) { - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED)); ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc); /* Update the ll info fields */ @@ -1390,7 +1388,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg) ns_osmo_signal_dispatch_mismatch(*nsvc, msg, NS_PDUT_RESET_ACK, NS_IE_VCI); - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI)); LOGP(DNS, LOGL_ERROR, "NS RESET ACK Unknown NS-VCI %d (%s NSEI=%d) " "from %s\n", @@ -1401,7 +1399,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg) } /* Notify others */ - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED)); ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc); /* Update the ll info fields */ @@ -1415,7 +1413,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg) ns_osmo_signal_dispatch_mismatch(*nsvc, msg, NS_PDUT_RESET_ACK, NS_IE_NSEI); - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI)); LOGP(DNS, LOGL_ERROR, "NS RESET ACK Unknown NSEI %d (NS-VCI=%u) from %s\n", nsei, nsvci, gprs_ns_ll_str(*nsvc)); @@ -1423,14 +1421,14 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg) } /* NSEI has changed */ - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG)); (*nsvc)->nsei = nsei; } /* Mark NS-VC as blocked and alive */ ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE); ns_set_remote_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE); - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_BLOCKED]); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BLOCKED)); if ((*nsvc)->persistent || (*nsvc)->remote_end_is_sgsn) { /* stop RESET timer */ osmo_timer_del(&(*nsvc)->timer); @@ -1471,7 +1469,7 @@ static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg) //nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI); ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, *cause); - rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED)); return gprs_ns_tx_block_ack(nsvc); } @@ -1705,7 +1703,7 @@ int gprs_ns_vc_create(struct gprs_ns_inst *nsi, struct msgb *msg, existing_nsvc->nsei = nsei; /* Do statistics */ - rate_ctr_inc(&existing_nsvc->ctrg->ctr[NS_CTR_NSEI_CHG]); + rate_ctr_inc(rate_ctr_group_get_ctr(existing_nsvc->ctrg, NS_CTR_NSEI_CHG)); } *new_nsvc = existing_nsvc; @@ -1734,8 +1732,8 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg, log_set_context(LOG_CTX_GB_NSVC, *nsvc); /* Increment number of Incoming bytes */ - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_PKTS_IN]); - rate_ctr_add(&(*nsvc)->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg)); + rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_PKTS_IN)); + rate_ctr_add(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BYTES_IN), msgb_l2len(msg)); if (nsvc_is_not_used(*nsvc) && !ns_is_sns(nsh->pdu_type) && nsh->pdu_type != NS_PDUT_STATUS) { LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx %s on unused/pre-configured endpoint, discarding\n", @@ -1762,7 +1760,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg, case NS_PDUT_ALIVE_ACK: ns_mark_alive(*nsvc); if ((*nsvc)->timer_mode == NSVC_TIMER_TNS_ALIVE) - osmo_stat_item_set((*nsvc)->statg->items[NS_STAT_ALIVE_DELAY], + osmo_stat_item_set(osmo_stat_item_group_get_item((*nsvc)->statg, NS_STAT_ALIVE_DELAY), nsvc_timer_elapsed_ms(*nsvc)); /* stop Tns-alive and start Tns-test */ nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST); @@ -2068,7 +2066,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi) osmo_sock_init2_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port, remote_str, - nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); + nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT | + OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp)); LOGP(DNS, LOGL_NOTICE, "Listening for nsip packets from %s:%u on %s:%u\n", @@ -2076,7 +2075,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi) } else { /* Accept UDP packets from any source IP/Port */ ret = osmo_sock_init_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM, - IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port, OSMO_SOCK_F_BIND); + IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp)); LOGP(DNS, LOGL_NOTICE, "Listening for nsip packets on %s:%u\n", inet_ntoa(in), nsi->nsip.local_port); } @@ -2087,13 +2087,6 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi) return ret; } - ret = setsockopt(nsi->nsip.fd.fd, IPPROTO_IP, IP_TOS, - &nsi->nsip.dscp, sizeof(nsi->nsip.dscp)); - if (ret < 0) - LOGP(DNS, LOGL_ERROR, - "Failed to set the DSCP to %d with ret(%d) errno(%d)\n", - nsi->nsip.dscp, ret, errno); - LOGP(DNS, LOGL_NOTICE, "NS UDP socket at %s:%d\n", inet_ntoa(in), nsi->nsip.local_port); return ret; diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c new file mode 100644 index 00000000..4e496c1f --- /dev/null +++ b/src/gb/gprs_ns2.c @@ -0,0 +1,1695 @@ +/*! \file gprs_ns2.c + * GPRS Networks Service (NS) messages on the Gb interface. + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org> + * (C) 2016-2017,2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/*! \addtogroup libgb + * @{ + * + * GPRS Networks Service (NS) messages on the Gb interface + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * + * Some introduction into NS: NS is used typically on top of frame relay, + * but in the ip.access world it is encapsulated in UDP packets. It serves + * as an intermediate shim betwen BSSGP and the underlying medium. It doesn't + * do much, apart from providing congestion notification and status indication. + * + * Terms: + * + * NS Network Service + * NSVC NS Virtual Connection + * NSEI NS Entity Identifier + * NSVL NS Virtual Link + * NSVLI NS Virtual Link Identifier + * BVC BSSGP Virtual Connection + * BVCI BSSGP Virtual Connection Identifier + * NSVCG NS Virtual Connection Goup + * Blocked NS-VC cannot be used for user traffic + * Alive Ability of a NS-VC to provide communication + * + * There can be multiple BSSGP virtual connections over one (group of) NSVC's. BSSGP will + * therefore identify the BSSGP virtual connection by a BVCI passed down to NS. + * NS then has to figure out which NSVC's are responsible for this BVCI. + * Those mappings are administratively configured. + * + * This implementation has the following limitations: + * - NSVCI 65535 and 65534 are reserved for internal use + * - There are no BLOCK and UNBLOCK timers (yet?) + * + * \file gprs_ns2.c */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/tlv.h> + +#include "gprs_ns2_internal.h" + +#define ns_set_state(ns_, st_) ns_set_state_with_log(ns_, st_, false, __FILE__, __LINE__) +#define ns_set_remote_state(ns_, st_) ns_set_state_with_log(ns_, st_, true, __FILE__, __LINE__) +#define ns_mark_blocked(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_BLOCKED) +#define ns_mark_unblocked(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_BLOCKED)); +#define ns_mark_alive(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_ALIVE) +#define ns_mark_dead(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_ALIVE)); + +/* HACK: The NS_IE_IP_ADDR does not follow any known TLV rules. + * Since it's a hard ABI break to implement 16 bit tag with fixed length entries to workaround it, + * the parser will be called with ns_att_tlvdef1 and if it's failed with ns_att_tlvdef2. + * The TLV parser depends on 8bit tag in many places. + * The NS_IE_IP_ADDR is only valid for SNS_ACK SNS_ADD and SNS_DELETE. + */ +static const struct tlv_definition ns_att_tlvdef1 = { + .def = { + [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 }, + [NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 }, + [NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 }, + [NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 }, + /* NS_IE_IP_ADDR in the IPv4 version */ + [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 }, + }, +}; + +static const struct tlv_definition ns_att_tlvdef2 = { + .def = { + [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 }, + [NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 }, + [NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 }, + [NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 }, + /* NS_IE_IP_ADDR in the IPv6 version */ + [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 17 }, + }, +}; + + +/* Section 10.3.2, Table 13 */ +const struct value_string gprs_ns2_cause_strs[] = { + { NS_CAUSE_TRANSIT_FAIL, "Transit network failure" }, + { NS_CAUSE_OM_INTERVENTION, "O&M intervention" }, + { NS_CAUSE_EQUIP_FAIL, "Equipment failure" }, + { NS_CAUSE_NSVC_BLOCKED, "NS-VC blocked" }, + { NS_CAUSE_NSVC_UNKNOWN, "NS-VC unknown" }, + { NS_CAUSE_BVCI_UNKNOWN, "BVCI unknown" }, + { NS_CAUSE_SEM_INCORR_PDU, "Semantically incorrect PDU" }, + { NS_CAUSE_PDU_INCOMP_PSTATE, "PDU not compatible with protocol state" }, + { NS_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" }, + { NS_CAUSE_INVAL_ESSENT_IE, "Invalid essential IE" }, + { NS_CAUSE_MISSING_ESSENT_IE, "Missing essential IE" }, + { NS_CAUSE_INVAL_NR_IPv4_EP, "Invalid Number of IPv4 Endpoints" }, + { NS_CAUSE_INVAL_NR_IPv6_EP, "Invalid Number of IPv6 Endpoints" }, + { NS_CAUSE_INVAL_NR_NS_VC, "Invalid Number of NS-VCs" }, + { NS_CAUSE_INVAL_WEIGH, "Invalid Weights" }, + { NS_CAUSE_UNKN_IP_EP, "Unknown IP Endpoint" }, + { NS_CAUSE_UNKN_IP_ADDR, "Unknown IP Address" }, + { NS_CAUSE_UNKN_IP_TEST_FAILED, "IP Test Failed" }, + { 0, NULL } +}; + +static const struct rate_ctr_desc ns_ctr_description[] = { + [NS_CTR_PKTS_IN] = { "packets:in", "Packets at NS Level ( In)" }, + [NS_CTR_PKTS_OUT] = { "packets:out", "Packets at NS Level (Out)" }, + [NS_CTR_PKTS_OUT_DROP] = { "packets:out:drop", "Dropped Packets (Out)" }, + [NS_CTR_BYTES_IN] = { "bytes:in", "Bytes at NS Level ( In)" }, + [NS_CTR_BYTES_OUT] = { "bytes:out", "Bytes at NS Level (Out)" }, + [NS_CTR_BYTES_OUT_DROP] = { "bytes:out:drop", "Dropped Bytes (Out)" }, + [NS_CTR_BLOCKED] = { "blocked", "NS-VC Block count " }, + [NS_CTR_UNBLOCKED] = { "unblocked", "NS-VC Unblock count " }, + [NS_CTR_DEAD] = { "dead", "NS-VC gone dead count " }, + [NS_CTR_REPLACED] = { "replaced", "NS-VC replaced other count" }, + [NS_CTR_NSEI_CHG] = { "nsei-chg", "NS-VC changed NSEI count " }, + [NS_CTR_INV_VCI] = { "inv-nsvci", "NS-VCI was invalid count " }, + [NS_CTR_INV_NSEI] = { "inv-nsei", "NSEI was invalid count " }, + [NS_CTR_LOST_ALIVE] = { "lost:alive", "ALIVE ACK missing count " }, + [NS_CTR_LOST_RESET] = { "lost:reset", "RESET ACK missing count " }, +}; + +static const struct rate_ctr_group_desc nse_ctrg_desc = { + .group_name_prefix = "ns:nse", + .group_description = "NSE Peer Statistics", + .num_ctr = ARRAY_SIZE(ns_ctr_description), + .ctr_desc = ns_ctr_description, + .class_id = OSMO_STATS_CLASS_PEER, +}; + +static const struct rate_ctr_group_desc nsvc_ctrg_desc = { + .group_name_prefix = "ns:nsvc", + .group_description = "NSVC Peer Statistics", + .num_ctr = ARRAY_SIZE(ns_ctr_description), + .ctr_desc = ns_ctr_description, + .class_id = OSMO_STATS_CLASS_PEER, +}; + + +static const struct osmo_stat_item_desc nsvc_stat_description[] = { + [NS_STAT_ALIVE_DELAY] = { "alive.delay", "ALIVE response time ", "ms", 16, 0 }, +}; + +static const struct osmo_stat_item_group_desc nsvc_statg_desc = { + .group_name_prefix = "ns.nsvc", + .group_description = "NSVC Peer Statistics", + .num_items = ARRAY_SIZE(nsvc_stat_description), + .item_desc = nsvc_stat_description, + .class_id = OSMO_STATS_CLASS_PEER, +}; + +const struct osmo_stat_item_desc nsbind_stat_description[] = { + [NS2_BIND_STAT_BACKLOG_LEN] = { "tx_backlog_length", "Transmit backlog length", "packets", 16, 0 }, +}; + +static const struct osmo_stat_item_group_desc nsbind_statg_desc = { + .group_name_prefix = "ns.bind", + .group_description = "NS Bind Statistics", + .num_items = ARRAY_SIZE(nsbind_stat_description), + .item_desc = nsbind_stat_description, + .class_id = OSMO_STATS_CLASS_PEER, +}; + +const struct value_string gprs_ns2_aff_cause_prim_strs[] = { + { GPRS_NS2_AFF_CAUSE_VC_FAILURE, "NSVC failure" }, + { GPRS_NS2_AFF_CAUSE_VC_RECOVERY, "NSVC recovery" }, + { GPRS_NS2_AFF_CAUSE_FAILURE, "NSE failure" }, + { GPRS_NS2_AFF_CAUSE_RECOVERY, "NSE recovery" }, + { GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED, "NSE SNS configured" }, + { GPRS_NS2_AFF_CAUSE_SNS_FAILURE, "NSE SNS failure" }, + { GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS, "NSE SNS no endpoints"}, + { GPRS_NS2_AFF_CAUSE_MTU_CHANGE, "NSE MTU changed" }, + { 0, NULL } +}; + +const struct value_string gprs_ns2_prim_strs[] = { + { GPRS_NS2_PRIM_UNIT_DATA, "UNIT DATA" }, + { GPRS_NS2_PRIM_CONGESTION, "CONGESTION" }, + { GPRS_NS2_PRIM_STATUS, "STATUS" }, + { 0, NULL } +}; + +const struct value_string gprs_ns2_lltype_strs[] = { + { GPRS_NS2_LL_UDP, "UDP" }, + { GPRS_NS2_LL_FR_GRE, "FR_GRE" }, + { GPRS_NS2_LL_FR, "FR" }, + { 0, NULL } +}; + +/*! string-format a given NS-VC into a user-supplied buffer. + * \param[in] buf user-allocated output buffer + * \param[in] buf_len size of user-allocated output buffer in bytes + * \param[in] nsvc NS-VC to be string-formatted + * \return pointer to buf on success; NULL on error */ +char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc) +{ + const struct osmo_sockaddr *local; + const struct osmo_sockaddr *remote; + struct osmo_sockaddr_str local_str; + struct osmo_sockaddr_str remote_str; + + if (!buf_len) + return NULL; + + switch (nsvc->nse->ll) { + case GPRS_NS2_LL_UDP: + if (!gprs_ns2_is_ip_bind(nsvc->bind)) { + buf[0] = '\0'; + return buf; + } + + local = gprs_ns2_ip_bind_sockaddr(nsvc->bind); + remote = gprs_ns2_ip_vc_remote(nsvc); + if (osmo_sockaddr_str_from_sockaddr(&local_str, &local->u.sas)) + strcpy(local_str.ip, "invalid"); + if (osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas)) + strcpy(remote_str.ip, "invalid"); + + if (nsvc->nsvci_is_valid) + snprintf(buf, buf_len, "udp)[%s]:%u<%u>[%s]:%u", + local_str.ip, local_str.port, + nsvc->nsvci, + remote_str.ip, remote_str.port); + else + snprintf(buf, buf_len, "udp)[%s]:%u<>[%s]:%u", + local_str.ip, local_str.port, + remote_str.ip, remote_str.port); + break; + case GPRS_NS2_LL_FR_GRE: + snprintf(buf, buf_len, "frgre)"); + break; + case GPRS_NS2_LL_FR: + snprintf(buf, buf_len, "fr)netif: %s dlci: %u", gprs_ns2_fr_bind_netif(nsvc->bind), + gprs_ns2_fr_nsvc_dlci(nsvc)); + break; + default: + snprintf(buf, buf_len, "unknown)"); + break; + } + + buf[buf_len - 1] = '\0'; + + return buf; +} + +/* udp is the longest: udp)[IP6]:65536<65536>[IP6]:65536 */ +#define NS2_LL_MAX_STR 4+2*(INET6_ADDRSTRLEN+9)+8 + +/*! string-format a given NS-VC to a thread-local static buffer. + * \param[in] nsvc NS-VC to be string-formatted + * \return pointer to the string on success; NULL on error */ +const char *gprs_ns2_ll_str(struct gprs_ns2_vc *nsvc) +{ + static __thread char buf[NS2_LL_MAX_STR]; + return gprs_ns2_ll_str_buf(buf, sizeof(buf), nsvc); +} + +/*! string-format a given NS-VC to a dynamically allocated string. + * \param[in] ctx talloc context from which to allocate + * \param[in] nsvc NS-VC to be string-formatted + * \return pointer to the string on success; NULL on error */ +char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc) +{ + char *buf = talloc_size(ctx, NS2_LL_MAX_STR); + if (!buf) + return buf; + return gprs_ns2_ll_str_buf(buf, NS2_LL_MAX_STR, nsvc); +} + +/*! Return the current state name of a given NS-VC to a thread-local static buffer. + * \param[in] nsvc NS-VC to return the state of + * \return pointer to the string on success; NULL on error */ +const char *gprs_ns2_nsvc_state_name(struct gprs_ns2_vc *nsvc) +{ + return osmo_fsm_inst_state_name(nsvc->fi); +} + +/* select a signalling NSVC and respect sig_counter + * param[out] reset_counter - all counter has to be resetted to their signal weight + * return the chosen nsvc or NULL + */ +static struct gprs_ns2_vc *ns2_load_sharing_signal(struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc = NULL, *last = NULL, *tmp; + + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (tmp->sig_weight == 0) + continue; + if (!ns2_vc_is_unblocked(tmp)) + continue; + if (tmp->sig_counter == 0) { + last = tmp; + continue; + } + + tmp->sig_counter--; + nsvc = tmp; + break; + } + + /* all counter were zero, but there are valid nsvc */ + if (!nsvc && last) { + llist_for_each_entry(tmp, &nse->nsvc, list) { + tmp->sig_counter = tmp->sig_weight; + } + + last->sig_counter--; + return last; + } else { + return nsvc; + } +} + +/* 4.4.1 Load Sharing function for the Frame Relay Sub-Network */ +static struct gprs_ns2_vc *ns2_load_sharing_modulo( + struct gprs_ns2_nse *nse, + uint16_t bvci, + uint32_t load_selector) +{ + struct gprs_ns2_vc *tmp; + uint32_t mod; + uint32_t i = 0; + + if (nse->nsvc_count == 0) + return NULL; + + mod = (bvci + load_selector) % nse->nsvc_count; + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (!ns2_vc_is_unblocked(tmp)) + continue; + if (i == mod) + return tmp; + i++; + } + + return NULL; +} + +/* 4.4.2 Load Sharing function for the IP Sub-Network + * + * Implement a simple approach for UDP load sharing of data weight based on the modulo of the lsp. + * + * E.g. 3 NSVC: 1st weight 5, 2nd weight 3, 3rd weight 1, lsp = 3. + * sum all weights = 9 + * target_weight = lsp % sum = 3 + * + * 1st NSVC will be the target for 0-4 + * 2nd NSVC will be the target for 5-7 + * 3rd NSVC will be the target for 8 + * + * The 1st NSVC will be used. + * E.g. lsp = 7. The 2nd NSVC will used. + */ +static struct gprs_ns2_vc *ns2_load_sharing_weight_modulo( + struct gprs_ns2_nse *nse, + uint16_t bvci, + uint32_t load_selector) +{ + struct gprs_ns2_vc *tmp; + uint32_t mod; + uint32_t i = 0; + + if (nse->nsvc_count == 0) + return NULL; + + mod = (bvci + load_selector) % nse->sum_data_weight; + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (tmp->data_weight == 0) + continue; + if (!ns2_vc_is_unblocked(tmp)) + continue; + if (i == mod || mod < i + tmp->data_weight) + return tmp; + i += tmp->data_weight; + } + + return NULL; +} + +/* pick the first available data NSVC - no load sharing */ +struct gprs_ns2_vc *ns2_load_sharing_first(struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc = NULL, *tmp; + + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (!ns2_vc_is_unblocked(tmp)) + continue; + if (tmp->data_weight == 0) + continue; + + nsvc = tmp; + break; + } + + return nsvc; +} + + +static struct gprs_ns2_vc *ns2_load_sharing( + struct gprs_ns2_nse *nse, + uint16_t bvci, + uint32_t link_selector) +{ + struct gprs_ns2_vc *nsvc = NULL; + + switch (nse->ll) { + case GPRS_NS2_LL_FR: + nsvc = ns2_load_sharing_modulo(nse, bvci, link_selector); + break; + case GPRS_NS2_LL_UDP: + default: + if (bvci == 0) { + /* signalling */ + nsvc = ns2_load_sharing_signal(nse); + } else { + /* data with load sharing parameter */ + nsvc = ns2_load_sharing_weight_modulo(nse, bvci, link_selector); + } + break; + } + + return nsvc; +} + +/*! Receive a primitive from the NS User (Gb). + * \param[in] nsi NS instance to which the primitive is issued + * \param[in] oph The primitive + * \return 0 on success; negative on error */ +int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph) +{ + /* TODO: implement resource distribution */ + /* TODO: check for empty PDUs which can be sent to Request/Confirm + * the IP endpoint */ + struct osmo_gprs_ns2_prim *nsp; + struct gprs_ns2_nse *nse = NULL; + struct gprs_ns2_vc *nsvc = NULL; + uint16_t bvci, nsei; + uint8_t sducontrol = 0; + int rc = 0; + + if (oph->sap != SAP_NS) { + rc = -EINVAL; + goto out; + } + + nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph); + + if (oph->operation != PRIM_OP_REQUEST || oph->primitive != GPRS_NS2_PRIM_UNIT_DATA) { + rc = -EINVAL; + goto out; + } + + if (!oph->msg) { + rc = -EINVAL; + goto out; + } + + bvci = nsp->bvci; + nsei = nsp->nsei; + + nse = gprs_ns2_nse_by_nsei(nsi, nsei); + if (!nse) { + rc = -EINVAL; + goto out; + } + + if (!nse->alive) { + goto out; + } + + nsvc = ns2_load_sharing(nse, bvci, nsp->u.unitdata.link_selector); + + /* TODO: send a status primitive back */ + if (!nsvc) + goto out; + + if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_REQUEST_CHANGE) + sducontrol = 1; + else if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_CONFIRM_CHANGE) + sducontrol = 2; + + return ns2_tx_unit_data(nsvc, bvci, sducontrol, oph->msg); + +out: + msgb_free(oph->msg); + return rc; +} + +/*! Send a STATUS.ind primitive to the specified NS instance user. + * \param[in] nsi NS instance on which we operate + * \param[in] nsei NSEI to which the statue relates + * \param[in] bvci BVCI to which the status relates + * \param[in] cause The cause of the status */ +void ns2_prim_status_ind(struct gprs_ns2_nse *nse, + struct gprs_ns2_vc *nsvc, + uint16_t bvci, + enum gprs_ns2_affecting_cause cause) +{ + char nsvc_str[NS2_LL_MAX_STR]; + struct osmo_gprs_ns2_prim nsp = {}; + nsp.nsei = nse->nsei; + nsp.bvci = bvci; + nsp.u.status.cause = cause; + nsp.u.status.transfer = ns2_count_transfer_cap(nse, bvci); + nsp.u.status.first = nse->first; + nsp.u.status.persistent = nse->persistent; + if (nse->mtu < 4) + nsp.u.status.mtu = 0; + else + nsp.u.status.mtu = nse->mtu - 4; /* 1 Byte NS PDU type, 1 Byte NS SDU control, 2 Byte BVCI */ + + if (nsvc) { + nsp.u.status.nsvc = gprs_ns2_ll_str_buf(nsvc_str, sizeof(nsvc_str), nsvc); + LOGNSVC(nsvc, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n", + nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause), + nsp.u.status.transfer, nsp.u.status.first, nsp.u.status.mtu); + } else { + LOGNSE(nse, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n", + nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause), + nsp.u.status.transfer, nsp.u.status.first, nsp.u.status.mtu); + } + + osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_STATUS, PRIM_OP_INDICATION, NULL); + nse->nsi->cb(&nsp.oph, nse->nsi->cb_data); +} + +/*! Allocate a NS-VC within the given bind + NSE. + * \param[in] bind The 'bind' on which we operate + * \param[in] nse The NS Entity on which we operate + * \param[in] initiater - if this is an incoming remote (!initiater) or a local outgoing connection (initater) + * \param[in] id - human-readable identifier + * \return newly allocated NS-VC on success; NULL on error */ +struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater, + enum gprs_ns2_vc_mode vc_mode, const char *id) +{ + /* Sanity check */ + OSMO_ASSERT(bind->ll == nse->ll); + + struct gprs_ns2_vc *nsvc = talloc_zero(bind, struct gprs_ns2_vc); + + if (!nsvc) + return NULL; + + nsvc->bind = bind; + nsvc->nse = nse; + nsvc->mode = vc_mode; + nsvc->sig_weight = 1; + nsvc->data_weight = 1; + + nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->nsvc_rate_ctr_idx); + if (!nsvc->ctrg) { + goto err; + } + nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->nsvc_rate_ctr_idx); + if (!nsvc->statg) + goto err_group; + if (!ns2_vc_fsm_alloc(nsvc, id, initiater)) + goto err_statg; + + bind->nsi->nsvc_rate_ctr_idx++; + + rate_ctr_group_set_name(nsvc->ctrg, id); + osmo_stat_item_group_set_name(nsvc->statg, id); + + llist_add_tail(&nsvc->list, &nse->nsvc); + llist_add_tail(&nsvc->blist, &bind->nsvc); + osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change); + ns2_nse_update_mtu(nse); + + return nsvc; + +err_statg: + osmo_stat_item_group_free(nsvc->statg); +err_group: + rate_ctr_group_free(nsvc->ctrg); +err: + talloc_free(nsvc); + + return NULL; +} + +/*! Destroy/release given NS-VC. + * \param[in] nsvc NS-VC to destroy */ +void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc) +{ + if (!nsvc || nsvc->freed) + return; + nsvc->freed = true; + ns2_prim_status_ind(nsvc->nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_FAILURE); + + llist_del(&nsvc->list); + llist_del(&nsvc->blist); + + /* notify nse this nsvc is unavailable */ + ns2_nse_notify_unblocked(nsvc, false); + + /* check if sns is using this VC */ + ns2_sns_replace_nsvc(nsvc); + osmo_fsm_inst_term(nsvc->fi, OSMO_FSM_TERM_REQUEST, NULL); + + /* let the driver/bind clean up it's internal state */ + if (nsvc->priv && nsvc->bind->free_vc) + nsvc->bind->free_vc(nsvc); + + osmo_stat_item_group_free(nsvc->statg); + rate_ctr_group_free(nsvc->ctrg); + + talloc_free(nsvc); +} + +void ns2_free_nsvcs(struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc; + + /* prevent recursive free() when the user reacts on a down event and free() a second time */ + while (!llist_empty(&nse->nsvc)) { + nsvc = llist_first_entry(&nse->nsvc, struct gprs_ns2_vc, list); + gprs_ns2_free_nsvc(nsvc); + } +} + +/*! Destroy/release all NS-VC of given NSE + * \param[in] nse NSE + */ +void gprs_ns2_free_nsvcs(struct gprs_ns2_nse *nse) +{ + if (!nse || nse->freed) + return; + + if (nse->bss_sns_fi) { + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_FREE_NSVCS, NULL); + } else { + ns2_free_nsvcs(nse); + } +} + +/*! Allocate a message buffer for use with the NS2 stack. */ +struct msgb *ns2_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, + "GPRS/NS"); + if (!msg) { + LOGP(DLNS, LOGL_ERROR, "Failed to allocate NS message of size %d\n", + NS_ALLOC_SIZE); + } + return msg; +} + +/*! Create a status message to be sent over a new connection. + * \param[in] orig_msg the original message + * \param[in] tp TLVP parsed of the original message + * \param[out] reject callee-allocated message buffer of the generated NS-STATUS + * \param[in] cause Cause for the rejection + * \return 0 on success */ +static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struct msgb **reject, enum ns_cause cause) +{ + struct msgb *msg = ns2_msgb_alloc(); + struct gprs_ns_hdr *nsh; + bool have_vci = false; + uint8_t _cause = cause; + uint16_t nsei = 0; + + if (!msg) + return -ENOMEM; + + if (TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) { + nsei = tlvp_val16be(tp, NS_IE_NSEI); + + LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rejecting message without NSVCI. Tx NS STATUS (cause=%s)\n", + nsei, gprs_ns2_cause_str(cause)); + } + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_STATUS; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &_cause); + have_vci = TLVP_PRES_LEN(tp, NS_IE_VCI, 2); + + /* Section 9.2.7.1: Static conditions for NS-VCI */ + if (cause == NS_CAUSE_NSVC_BLOCKED || + cause == NS_CAUSE_NSVC_UNKNOWN) { + if (!have_vci) { + msgb_free(msg); + return -EINVAL; + } + + msgb_tvlv_put(msg, NS_IE_VCI, 2, TLVP_VAL(tp, NS_IE_VCI)); + } + + /* Section 9.2.7.2: Static conditions for NS PDU */ + switch (cause) { + case NS_CAUSE_SEM_INCORR_PDU: + case NS_CAUSE_PDU_INCOMP_PSTATE: + case NS_CAUSE_PROTO_ERR_UNSPEC: + case NS_CAUSE_INVAL_ESSENT_IE: + case NS_CAUSE_MISSING_ESSENT_IE: + msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg), + orig_msg->l2h); + break; + default: + break; + } + + *reject = msg; + return 0; +} + +/*! Resolve a NS Entity based on its NSEI. + * \param[in] nsi NS Instance in which we do the look-up + * \param[in] nsei NSEI to look up + * \return NS Entity in successful case; NULL if none found */ +struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei) +{ + struct gprs_ns2_nse *nse; + + llist_for_each_entry(nse, &nsi->nse, list) { + if (nse->nsei == nsei) + return nse; + } + + return NULL; +} + +/*! Resolve a NS-VC Entity based on its NS-VCI. + * \param[in] nsi NS Instance in which we do the look-up + * \param[in] nsvci NS-VCI to look up + * \return NS-VC Entity in successful case; NULL if none found */ +struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci) +{ + struct gprs_ns2_nse *nse; + struct gprs_ns2_vc *nsvc; + + llist_for_each_entry(nse, &nsi->nse, list) { + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (nsvc->nsvci_is_valid && nsvc->nsvci == nsvci) + return nsvc; + } + } + + return NULL; +} + +/*! Create a NS Entity within given NS instance. + * \param[in] nsi NS instance in which to create NS Entity + * \param[in] nsei NS Entity Identifier of to-be-created NSE + * \param[in] ip_sns_role_sgsn Does local side implement SGSN role? + * \returns newly-allocated NS-E in successful case; NULL on error */ +struct gprs_ns2_nse *gprs_ns2_create_nse2(struct gprs_ns2_inst *nsi, uint16_t nsei, + enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect, + bool ip_sns_role_sgsn) +{ + struct gprs_ns2_nse *nse; + + nse = gprs_ns2_nse_by_nsei(nsi, nsei); + if (nse) { + LOGNSE(nse, LOGL_ERROR, "Can not create a NSE with already taken NSEI\n"); + return nse; + } + + nse = talloc_zero(nsi, struct gprs_ns2_nse); + if (!nse) + return NULL; + nse->dialect = GPRS_NS2_DIALECT_UNDEF; + nse->ip_sns_role_sgsn = ip_sns_role_sgsn; + + if (ns2_nse_set_dialect(nse, dialect) < 0) { + talloc_free(nse); + return NULL; + } + + nse->ctrg = rate_ctr_group_alloc(nse, &nse_ctrg_desc, nsei); + if (!nse->ctrg) { + talloc_free(nse); + return NULL; + } + + nse->ll = linklayer; + nse->nsei = nsei; + nse->nsi = nsi; + nse->first = true; + nse->mtu = 0; + llist_add_tail(&nse->list, &nsi->nse); + INIT_LLIST_HEAD(&nse->nsvc); + osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change); + + return nse; +} + +int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect) +{ + char sns[16]; + + if (nse->dialect == dialect) + return 0; + + switch (nse->dialect) { + case GPRS_NS2_DIALECT_UNDEF: + if (dialect == GPRS_NS2_DIALECT_SNS) { + snprintf(sns, sizeof(sns), "NSE%05u-SNS", nse->nsei); + if (nse->ip_sns_role_sgsn) + nse->bss_sns_fi = ns2_sns_sgsn_fsm_alloc(nse, sns); + else + nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, sns); + if (!nse->bss_sns_fi) + return -1; + } + nse->dialect = dialect; + break; + default: + if (dialect == GPRS_NS2_DIALECT_UNDEF) { + if (nse->bss_sns_fi) + osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL); + nse->bss_sns_fi = NULL; + nse->dialect = GPRS_NS2_DIALECT_UNDEF; + } else { + /* we don't support arbitrary changes without going through UNDEF first */ + return -EPERM; + } + } + + return 0; +} + +/*! Create a NS Entity within given NS instance. + * \param[in] nsi NS instance in which to create NS Entity + * \param[in] nsei NS Entity Identifier of to-be-created NSE + * \returns newly-allocated NS-E in successful case; NULL on error */ +struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei, + enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect) +{ + return gprs_ns2_create_nse2(nsi, nsei, linklayer, dialect, false); +} + +/*! Return the NSEI + * \param[in] nse NS Entity + * \return the nsei. + */ +uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse) +{ + return nse->nsei; +} + +/*! Destroy given NS Entity. + * \param[in] nse NS Entity to destroy */ +void gprs_ns2_free_nse(struct gprs_ns2_nse *nse) +{ + if (!nse || nse->freed) + return; + + nse->freed = true; + nse->alive = false; + if (nse->bss_sns_fi) { + osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL); + nse->bss_sns_fi = NULL; + } + + gprs_ns2_free_nsvcs(nse); + ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE); + rate_ctr_group_free(nse->ctrg); + ns2_free_nsvcs(nse); + + llist_del(&nse->list); + talloc_free(nse); +} + +void gprs_ns2_free_nses(struct gprs_ns2_inst *nsi) +{ + struct gprs_ns2_nse *nse; + + /* prevent recursive free() when the user reacts on a down event and free() a second time */ + while (!llist_empty(&nsi->nse)) { + nse = llist_first_entry(&nsi->nse, struct gprs_ns2_nse, list); + gprs_ns2_free_nse(nse); + } +} + +static inline int ns2_tlv_parse(struct tlv_parsed *dec, + const uint8_t *buf, int buf_len, uint8_t lv_tag, + uint8_t lv_tag2) +{ + /* workaround for NS_IE_IP_ADDR not following any known TLV rules. + * See comment of ns_att_tlvdef1. */ + int rc = tlv_parse(dec, &ns_att_tlvdef1, buf, buf_len, lv_tag, lv_tag2); + if (rc < 0) + return tlv_parse(dec, &ns_att_tlvdef2, buf, buf_len, lv_tag, lv_tag2); + return rc; +} + +static enum ns2_cs ns2_create_vc_sns(struct gprs_ns2_vc_bind *bind, + const struct osmo_sockaddr *remote, + struct gprs_ns2_vc **success, uint16_t nsei) +{ + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse; + + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, remote); + /* ns2_create_vc() is only called if no NS-VC could be found */ + OSMO_ASSERT(!nsvc); + + nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei); + if (!nse) { + if (!bind->accept_sns) { + struct osmo_sockaddr_str remote_str; + osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas); + /* no dynamic creation of IP-SNS NSE permitted */ + LOGP(DLNS, LOGL_ERROR, "[%s]:%u: Dynamic creation of NSE(%05u) via IP-SNS not " + "permitted. Check your config.\n", remote_str.ip, remote_str.port, nsei); + return NS2_CS_ERROR; + } + nse = gprs_ns2_create_nse2(bind->nsi, nsei, bind->ll, GPRS_NS2_DIALECT_SNS, true); + if (!nse) { + LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei); + return NS2_CS_ERROR; + } + /* add configured list of default binds; if that fails, use only current bind */ + if (!ns2_sns_add_sns_default_binds(nse)) + gprs_ns2_sns_add_bind(nse, bind); + } else { + /* nsei already known */ + if (nse->ll != bind->ll) { + LOGNSE(nse, LOGL_ERROR, "Received NS-RESET with wrong linklayer(%s)" + " for already known NSE(%s)\n", gprs_ns2_lltype_str(bind->ll), + gprs_ns2_lltype_str(nse->ll)); + return NS2_CS_SKIPPED; + } + } + + nsvc = ns2_ip_bind_connect(bind, nse, remote); + if (!nsvc) + return NS2_CS_SKIPPED; + + nsvc->nsvci_is_valid = false; + + *success = nsvc; + + return NS2_CS_CREATED; +} + +/*! Create a new NS-VC based on a [received] message. Depending on the bind it might create a NSE. + * \param[in] bind the bind through which msg was received + * \param[in] msg the actual received message + * \param[in] remote address of remote peer sending message + * \param[in] logname A name to describe the VC. E.g. ip address pair + * \param[out] reject A message filled to be sent back. Only used in failure cases. + * \param[out] success A pointer which will be set to the new VC on success + * \return enum value indicating the status, e.g. GPRS_NS2_CS_CREATED */ +enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind, + struct msgb *msg, + const struct osmo_sockaddr *remote, + const char *logname, + struct msgb **reject, + struct gprs_ns2_vc **success) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h; + struct tlv_parsed tp; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse; + enum gprs_ns2_dialect dialect; + enum gprs_ns2_vc_mode vc_mode; + uint16_t nsvci; + uint16_t nsei; + const struct osmo_sockaddr *local; + char idbuf[256], tmp[INET6_ADDRSTRLEN + 8]; + + int rc, tlv; + + if (msg->len < sizeof(struct gprs_ns_hdr)) + return NS2_CS_ERROR; + + /* parse the tlv early to allow reject status msg to + * work with valid tp. + * Ignore the return code until the pdu type is parsed because + * an unknown pdu type should be ignored */ + tlv = ns2_tlv_parse(&tp, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + + if (bind->ll == GPRS_NS2_LL_UDP && nsh->pdu_type == SNS_PDUT_SIZE && tlv >= 0) { + uint16_t nsei; + + if (!TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) { + rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE); + if (rc < 0) + LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc); + return NS2_CS_REJECTED; + } + nsei = tlvp_val16be(&tp, NS_IE_NSEI); + /* Create NS-VC, and if required, even NSE dynamically */ + return ns2_create_vc_sns(bind, remote, success, nsei); + } + + switch (nsh->pdu_type) { + case NS_PDUT_STATUS: + /* Do not respond, see 3GPP TS 08.16, 7.5.1 */ + LOGP(DLNS, LOGL_INFO, "Ignoring NS STATUS from %s " + "for non-existing NS-VC\n", + logname); + return NS2_CS_SKIPPED; + case NS_PDUT_ALIVE_ACK: + /* Ignore this, see 3GPP TS 08.16, 7.4.1 */ + LOGP(DLNS, LOGL_INFO, "Ignoring NS ALIVE ACK from %s " + "for non-existing NS-VC\n", + logname); + return NS2_CS_SKIPPED; + case NS_PDUT_RESET_ACK: + /* Ignore this, see 3GPP TS 08.16, 7.3.1 */ + LOGP(DLNS, LOGL_INFO, "Ignoring NS RESET ACK from %s " + "for non-existing NS-VC\n", + logname); + return NS2_CS_SKIPPED; + case NS_PDUT_RESET: + /* accept PDU RESET when vc_mode matches */ + if (bind->accept_ipaccess) { + dialect = GPRS_NS2_DIALECT_IPACCESS; + break; + } + + rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE); + if (rc < 0) + LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc); + return NS2_CS_REJECTED; + default: + rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE); + if (rc < 0) + LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc); + return NS2_CS_REJECTED; + } + + if (tlv < 0) { + /* TODO: correct behaviour would checking what's wrong. + * If it's an essential TLV for the PDU return NS_CAUSE_INVAL_ESSENT_IE. + * Otherwise ignore the non-essential TLV. */ + LOGP(DLNS, LOGL_ERROR, "Rx NS RESET Error %d during " + "TLV Parse\n", tlv); + rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PROTO_ERR_UNSPEC); + if (rc < 0) + LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc); + return NS2_CS_REJECTED; + } + + if (!TLVP_PRES_LEN(&tp, NS_IE_CAUSE, 1) || + !TLVP_PRES_LEN(&tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) { + LOGP(DLNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n"); + rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE); + if (rc < 0) + LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc); + return NS2_CS_REJECTED; + } + + nsei = tlvp_val16be(&tp, NS_IE_NSEI); + nsvci = tlvp_val16be(&tp, NS_IE_VCI); + + /* find or create NSE */ + nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei); + if (!nse) { + /* only create nse for udp & ipaccess */ + if (bind->ll != GPRS_NS2_LL_UDP || dialect != GPRS_NS2_DIALECT_IPACCESS) + return NS2_CS_SKIPPED; + + if (!bind->accept_ipaccess) + return NS2_CS_SKIPPED; + + nse = gprs_ns2_create_nse(bind->nsi, nsei, bind->ll, dialect); + if (!nse) { + LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei); + return NS2_CS_ERROR; + } + } else { + /* nsei already known */ + if (nse->ll != bind->ll) { + LOGNSE(nse, LOGL_ERROR, "Received NS-RESET NS-VCI(%05u) with wrong linklayer(%s)" + " for already known NSE(%s)\n", nsvci, gprs_ns2_lltype_str(bind->ll), + gprs_ns2_lltype_str(nse->ll)); + return NS2_CS_SKIPPED; + } + } + + nsvc = gprs_ns2_nsvc_by_nsvci(bind->nsi, nsvci); + if (nsvc) { + if (nsvc->persistent) { + LOGNSVC(nsvc, LOGL_ERROR, "Received NS-RESET for a persistent NSE over wrong connection.\n"); + return NS2_CS_SKIPPED; + } + /* destroy old dynamic nsvc */ + gprs_ns2_free_nsvc(nsvc); + } + + /* do nse persistent check late to be more precise on the error message */ + if (nse->persistent) { + LOGNSE(nse, LOGL_ERROR, "Received NS-RESET for a persistent NSE but the unknown " + "NS-VCI(%05u)\n", nsvci); + return NS2_CS_SKIPPED; + } + + nsvci = tlvp_val16be(&tp, NS_IE_VCI); + vc_mode = ns2_dialect_to_vc_mode(dialect); + + local = gprs_ns2_ip_bind_sockaddr(bind); + osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local); + snprintf(idbuf, sizeof(idbuf), "%s-NSE%05u-NSVC%05u-%s-%s", gprs_ns2_lltype_str(nse->ll), + nse->nsei, nsvci, tmp, osmo_sockaddr_to_str(remote)); + osmo_identifier_sanitize_buf(idbuf, NULL, '_'); + nsvc = ns2_vc_alloc(bind, nse, false, vc_mode, idbuf); + if (!nsvc) + return NS2_CS_SKIPPED; + + nsvc->nsvci = nsvci; + nsvc->nsvci_is_valid = true; + + *success = nsvc; + + return NS2_CS_CREATED; +} + +/*! Create, and connect an inactive, new IP-based NS-VC + * \param[in] bind bind in which the new NS-VC is to be created + * \param[in] remote remote address to which to connect + * \param[in] nse NS Entity in which the NS-VC is to be created + * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET + * \return pointer to newly-allocated, connected and inactive NS-VC; NULL on error */ +struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind, + const struct osmo_sockaddr *remote, + struct gprs_ns2_nse *nse, + uint16_t nsvci) +{ + struct gprs_ns2_vc *nsvc; + + nsvc = ns2_ip_bind_connect(bind, nse, remote); + if (!nsvc) + return NULL; + + if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) { + nsvc->nsvci = nsvci; + nsvc->nsvci_is_valid = true; + } + + return nsvc; +} + +/*! Create, connect and activate a new IP-based NS-VC + * \param[in] bind bind in which the new NS-VC is to be created + * \param[in] remote remote address to which to connect + * \param[in] nse NS Entity in which the NS-VC is to be created + * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET + * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */ +struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind, + const struct osmo_sockaddr *remote, + struct gprs_ns2_nse *nse, + uint16_t nsvci) +{ + struct gprs_ns2_vc *nsvc; + nsvc = gprs_ns2_ip_connect_inactive(bind, remote, nse, nsvci); + if (!nsvc) + return NULL; + + ns2_vc_fsm_start(nsvc); + + return nsvc; +} + +/*! Create, connect and activate a new IP-based NS-VC + * \param[in] bind bind in which the new NS-VC is to be created + * \param[in] remote remote address to which to connect + * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created + * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET + * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */ +struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind, + const struct osmo_sockaddr *remote, + uint16_t nsei, + uint16_t nsvci, + enum gprs_ns2_dialect dialect) +{ + struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei); + + if (!nse) { + nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_UDP, dialect); + if (!nse) + return NULL; + } + + return gprs_ns2_ip_connect(bind, remote, nse, nsvci); +} + +/*! Find NS-VC for given socket address. + * \param[in] nse NS Entity in which to search + * \param[in] sockaddr socket address to search for + * \return NS-VC matching sockaddr; NULL if none found */ +struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(struct gprs_ns2_nse *nse, + const struct osmo_sockaddr *sockaddr) +{ + struct gprs_ns2_vc *nsvc; + const struct osmo_sockaddr *remote; + + OSMO_ASSERT(nse); + OSMO_ASSERT(sockaddr); + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + remote = gprs_ns2_ip_vc_remote(nsvc); + if (!osmo_sockaddr_cmp(sockaddr, remote)) + return nsvc; + } + + return NULL; +} + +/*! + * Iterate over all nsvc of a NS Entity and call the callback. + * If the callback returns < 0 it aborts the loop and returns the callback return code. + * \param[in] nse NS Entity to iterate over all nsvcs + * \param[in] cb the callback to call + * \param[inout] cb_data the private data of the callback + * \return 0 if the loop completes. If a callback returns < 0 it will returns this value. + */ +int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse, gprs_ns2_foreach_nsvc_cb cb, void *cb_data) +{ + struct gprs_ns2_vc *nsvc, *tmp; + int rc = 0; + llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) { + rc = cb(nsvc, cb_data); + if (rc < 0) + return rc; + } + + return 0; +} + + + +/*! Bottom-side entry-point for received NS PDU from the driver/bind + * \param[in] nsvc NS-VC for which the message was received + * \param msg the received message. Ownership is transferred, caller must not free it! + * \return 0 on success; negative on error */ +int ns2_recv_vc(struct gprs_ns2_vc *nsvc, + struct msgb *msg) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct tlv_parsed tp = { }; + int rc = 0; + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_IN); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_IN, msg->len); + + if (msg->len < sizeof(struct gprs_ns_hdr)) { + rc = -EINVAL; + goto freemsg; + } + + if (nsh->pdu_type != NS_PDUT_UNITDATA) + LOG_NS_RX_SIGNAL(nsvc, nsh->pdu_type); + else + LOG_NS_DATA(nsvc, "Rx", nsh->pdu_type, LOGL_INFO, "\n"); + + switch (nsh->pdu_type) { + case SNS_PDUT_CONFIG: + /* one additional byte ('end flag') before the TLV part starts */ + rc = ns2_tlv_parse(&tp, nsh->data+1, + msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0); + if (rc < 0) { + LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg)); + goto freemsg; + } + /* All sub-network service related message types */ + return ns2_sns_rx(nsvc, msg, &tp); + case SNS_PDUT_ACK: + case SNS_PDUT_ADD: + case SNS_PDUT_CHANGE_WEIGHT: + case SNS_PDUT_DELETE: + /* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */ + rc = ns2_tlv_parse(&tp, nsh->data+5, + msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0); + if (rc < 0) { + LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg)); + goto freemsg; + } + tp.lv[NS_IE_NSEI].val = nsh->data+2; + tp.lv[NS_IE_NSEI].len = 2; + tp.lv[NS_IE_TRANS_ID].val = nsh->data+4; + tp.lv[NS_IE_TRANS_ID].len = 1; + return ns2_sns_rx(nsvc, msg, &tp); + case SNS_PDUT_CONFIG_ACK: + case SNS_PDUT_SIZE: + case SNS_PDUT_SIZE_ACK: + rc = ns2_tlv_parse(&tp, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg)); + goto freemsg; + } + /* All sub-network service related message types */ + return ns2_sns_rx(nsvc, msg, &tp); + case NS_PDUT_UNITDATA: + return ns2_vc_rx(nsvc, msg, &tp); + default: + rc = ns2_tlv_parse(&tp, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse\n"); + if (nsh->pdu_type != NS_PDUT_STATUS) + ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg, NULL); + return rc; + } + return ns2_vc_rx(nsvc, msg, &tp); + } +freemsg: + msgb_free(msg); + + return rc; +} + +/* summarize all active data nsvcs */ +void ns2_nse_data_sum(struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc; + + nse->nsvc_count = 0; + nse->sum_data_weight = 0; + nse->sum_sig_weight = 0; + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (!ns2_vc_is_unblocked(nsvc)) + continue; + + nse->nsvc_count++; + nse->sum_data_weight += nsvc->data_weight; + nse->sum_sig_weight += nsvc->sig_weight; + } +} + +/*! Notify a nse about the change of a NS-VC. + * \param[in] nsvc NS-VC which has detected the change (and shall not be notified). + * \param[in] unblocked whether the NSE should be marked as unblocked (true) or blocked (false) */ +void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked) +{ + struct gprs_ns2_nse *nse = nsvc->nse; + struct gprs_ns2_inst *nsi = nse->nsi; + uint16_t nsei = nse->nsei; + + ns2_nse_data_sum(nse); + ns2_sns_notify_alive(nse, nsvc, unblocked); + + /* NSE could have been freed, try to get it again */ + nse = gprs_ns2_nse_by_nsei(nsi, nsei); + + if (!nse || unblocked == nse->alive) + return; + + /* wait until both data_weight and sig_weight are != 0 before declaring NSE as alive */ + if (unblocked && nse->sum_data_weight && nse->sum_sig_weight) { + nse->alive = true; + osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change); + ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_RECOVERY); + nse->first = false; + return; + } + + if (nse->alive && (nse->sum_data_weight == 0 || nse->sum_sig_weight == 0)) { + /* nse became unavailable */ + nse->alive = false; + osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change); + ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE); + } +} + +/*! Create a new GPRS NS instance + * \param[in] ctx a talloc context to allocate NS instance from + * \param[in] cb Call-back function for dispatching primitives to the user. The Call-back must free all msgb* given in the primitive. + * \param[in] cb_data transparent user data passed to Call-back + * \returns dynamically allocated gprs_ns_inst; NULL on error */ +struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data) +{ + struct gprs_ns2_inst *nsi; + + nsi = talloc_zero(ctx, struct gprs_ns2_inst); + if (!nsi) + return NULL; + + nsi->cb = cb; + nsi->cb_data = cb_data; + INIT_LLIST_HEAD(&nsi->binding); + INIT_LLIST_HEAD(&nsi->nse); + + nsi->timeout[NS_TOUT_TNS_BLOCK] = 3; + nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3; + nsi->timeout[NS_TOUT_TNS_RESET] = 3; + nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3; + nsi->timeout[NS_TOUT_TNS_TEST] = 30; + nsi->timeout[NS_TOUT_TNS_ALIVE] = 3; + nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10; + nsi->timeout[NS_TOUT_TSNS_PROV] = 3; /* 1..10 */ + nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES] = 3; + nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES] = 3; + nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES] = 3; + + nsi->txqueue_max_length = NS_DEFAULT_TXQUEUE_MAX_LENGTH; + + return nsi; +} + +/*! Destroy a NS Instance (including all its NSEs, binds, ...). + * \param[in] nsi NS instance to destroy */ +void gprs_ns2_free(struct gprs_ns2_inst *nsi) +{ + if (!nsi) + return; + + gprs_ns2_free_nses(nsi); + gprs_ns2_free_binds(nsi); + + talloc_free(nsi); +} + +/*! Start the NS-ALIVE FSM in all NS-VCs of given NSE. + * \param[in] nse NS Entity in whihc to start NS-ALIVE FSMs */ +void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc; + OSMO_ASSERT(nse); + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + /* A pre-configured endpoint shall not be used for NSE data or signalling traffic + * (with the exception of Size and Configuration procedures) unless it is + * configured by the SGSN using the auto-configuration procedures */ + if (nsvc->sns_only) + continue; + + ns2_vc_fsm_start(nsvc); + } +} + +/*! Destroy a given bind. + * \param[in] bind the bind we want to destroy */ +void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind) +{ + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse; + if (!bind || bind->freed) + return; + bind->freed = true; + + if (gprs_ns2_is_ip_bind(bind)) { + llist_for_each_entry(nse, &bind->nsi->nse, list) { + gprs_ns2_sns_del_bind(nse, bind); + } + } + + /* prevent recursive free() when the user reacts on a down event and free() a second time */ + while (!llist_empty(&bind->nsvc)) { + nsvc = llist_first_entry(&bind->nsvc, struct gprs_ns2_vc, blist); + gprs_ns2_free_nsvc(nsvc); + } + + if (bind->driver->free_bind) + bind->driver->free_bind(bind); + + llist_del(&bind->list); + osmo_stat_item_group_free(bind->statg); + talloc_free((char *)bind->name); + talloc_free(bind); +} + +void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi) +{ + struct gprs_ns2_vc_bind *bind; + + /* prevent recursive free() when the user reacts on a down event and free() a second time */ + while (!llist_empty(&nsi->binding)) { + bind = llist_first_entry(&nsi->binding, struct gprs_ns2_vc_bind, list); + gprs_ns2_free_bind(bind); + } +} + +/*! Search for a bind with a unique name + * \param[in] nsi NS instance on which we operate + * \param[in] name The unique bind name to search for + * \return the bind or NULL if not found + */ +struct gprs_ns2_vc_bind *gprs_ns2_bind_by_name(struct gprs_ns2_inst *nsi, const char *name) +{ + struct gprs_ns2_vc_bind *bind; + + llist_for_each_entry(bind, &nsi->binding, list) { + if (!strcmp(bind->name, name)) + return bind; + } + + return NULL; +} + +enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect) +{ + switch (dialect) { + case GPRS_NS2_DIALECT_SNS: + case GPRS_NS2_DIALECT_STATIC_ALIVE: + return GPRS_NS2_VC_MODE_ALIVE; + case GPRS_NS2_DIALECT_STATIC_RESETBLOCK: + case GPRS_NS2_DIALECT_IPACCESS: + return GPRS_NS2_VC_MODE_BLOCKRESET; + default: + return -1; + } +} + +static void add_bind_array(struct gprs_ns2_vc_bind **array, + struct gprs_ns2_vc_bind *bind, int size) +{ + int i; + for (i=0; i < size; i++) { + if (array[i] == bind) + return; + if (!array[i]) + break; + } + + if (i == size) + return; + + array[i] = bind; +} + +void ns2_nse_update_mtu(struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc; + int mtu = 0; + + if (llist_empty(&nse->nsvc)) { + nse->mtu = 0; + return; + } + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (mtu == 0) + mtu = nsvc->bind->mtu; + else if (mtu > nsvc->bind->mtu) + mtu = nsvc->bind->mtu; + } + + if (nse->mtu == mtu) + return; + + nse->mtu = mtu; + if (nse->alive) + ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_MTU_CHANGE); +} + +/*! calculate the transfer capabilities for a nse + * \param nse the nse to count the transfer capability + * \param bvci a bvci - unused + * \return the transfer capability in mbit. On error < 0. + */ +int ns2_count_transfer_cap(struct gprs_ns2_nse *nse, + uint16_t bvci) +{ + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_vc_bind **active_binds; + int i, active_nsvcs = 0, transfer_cap = 0; + + /* calculate the transfer capabilities based on the binds. + * A bind has a transfer capability which is shared across all NSVCs. + * Take care the bind cap is not counted twice within a NSE. + * This should be accurate for FR and UDP but not for FR/GRE. */ + + if (!nse->alive) + return 0; + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (ns2_vc_is_unblocked(nsvc)) + active_nsvcs++; + } + + if (!active_nsvcs) + return 0; + + active_binds = talloc_zero_array(nse, struct gprs_ns2_vc_bind*, active_nsvcs); + if (!active_binds) + return -ENOMEM; + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (!ns2_vc_is_unblocked(nsvc)) + continue; + add_bind_array(active_binds, nsvc->bind, active_nsvcs); + } + + /* TODO: change calcuation for FR/GRE */ + for (i = 0; i < active_nsvcs; i++) { + if (active_binds[i]) + transfer_cap += active_binds[i]->transfer_capability; + } + + talloc_free(active_binds); + return transfer_cap; +} + +/*! common allocation + low-level initialization of a bind. Called by vc-drivers */ +int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name, + struct gprs_ns2_vc_bind **result) +{ + struct gprs_ns2_vc_bind *bind; + + if (!name) + return -EINVAL; + + if (gprs_ns2_bind_by_name(nsi, name)) + return -EALREADY; + + bind = talloc_zero(nsi, struct gprs_ns2_vc_bind); + if (!bind) + return -ENOMEM; + + bind->name = talloc_strdup(bind, name); + if (!bind->name) { + talloc_free(bind); + return -ENOMEM; + } + + bind->statg = osmo_stat_item_group_alloc(bind, &nsbind_statg_desc, nsi->bind_rate_ctr_idx); + if (!bind->statg) { + talloc_free(bind); + return -ENOMEM; + } + + bind->sns_sig_weight = 1; + bind->sns_data_weight = 1; + bind->nsi = nsi; + INIT_LLIST_HEAD(&bind->nsvc); + llist_add_tail(&bind->list, &nsi->binding); + + nsi->bind_rate_ctr_idx++; + + if (result) + *result = bind; + + return 0; +} + +/*! @} */ diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c new file mode 100644 index 00000000..f6bee39c --- /dev/null +++ b/src/gb/gprs_ns2_fr.c @@ -0,0 +1,988 @@ +/*! \file gprs_ns2_fr.c + * NS-over-FR-over-GRE implementation. + * GPRS Networks Service (NS) messages on the Gb interface. + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2009-2021 by Harald Welte <laforge@gnumonks.org> + * (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <arpa/inet.h> +#include <linux/if.h> + +#include <sys/ioctl.h> +#include <netpacket/packet.h> +#include <linux/if_ether.h> +#include <linux/hdlc.h> +#include <linux/hdlc/ioctl.h> +#include <linux/sockios.h> + +#include <osmocom/gprs/frame_relay.h> +#include <osmocom/core/byteswap.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gprs/gprs_ns2.h> +#include <osmocom/core/netdev.h> +#include <osmocom/gprs/protocol/gsm_08_16.h> +#include <osmocom/gprs/protocol/gsm_08_18.h> + +#include "config.h" +#include "common_vty.h" +#include "gprs_ns2_internal.h" + +#define GRE_PTYPE_FR 0x6559 +#define GRE_PTYPE_IPv4 0x0800 +#define GRE_PTYPE_IPv6 0x86dd +#define GRE_PTYPE_KAR 0x0000 /* keepalive response */ + +#ifndef IPPROTO_GRE +# define IPPROTO_GRE 47 +#endif + +#define E1_LINERATE 2048000 +#define E1_SLOTS_TOTAL 32 +#define E1_SLOTS_USED 31 +/* usable bitrate of the E1 superchannel with 31 of 32 timeslots */ +#define SUPERCHANNEL_LINERATE (E1_LINERATE*E1_SLOTS_USED)/E1_SLOTS_TOTAL +/* nanoseconds per bit (504) */ +#define BIT_DURATION_NS (1000000000 / SUPERCHANNEL_LINERATE) + +static void free_bind(struct gprs_ns2_vc_bind *bind); +static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg); + +struct gprs_ns2_vc_driver vc_driver_fr = { + .name = "GB frame relay", + .free_bind = free_bind, +}; + +struct priv_bind { + struct osmo_netdev *netdev; + char netif[IFNAMSIZ]; + struct osmo_fr_link *link; + int ifindex; + bool if_running; + /* backlog queue for AF_PACKET / ENOBUFS handling (see OS#4995) */ + struct { + /* file-descriptor for AF_PACKET socket */ + struct osmo_fd ofd; + /* LMI bucket (we only store the last LMI message, no need to queue */ + struct msgb *lmi_msg; + /* list of NS msgb (backlog) */ + struct llist_head list; + /* timer to trigger next attempt of AF_PACKET write */ + struct osmo_timer_list timer; + /* re-try after that many micro-seconds */ + uint32_t retry_us; + } backlog; +}; + +struct priv_vc { + struct osmo_sockaddr remote; + uint16_t dlci; + struct osmo_fr_dlc *dlc; +}; + +static void free_vc(struct gprs_ns2_vc *nsvc) +{ + if (!nsvc) + return; + + if (!nsvc->priv) + return; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(nsvc->bind)); + talloc_free(nsvc->priv); + nsvc->priv = NULL; +} + +static void dump_vty(const struct gprs_ns2_vc_bind *bind, struct vty *vty, bool stats) +{ + struct priv_bind *priv; + struct gprs_ns2_vc *nsvc; + struct osmo_fr_link *fr_link; + + if (!bind) + return; + + priv = bind->priv; + fr_link = priv->link; + + vty_out(vty, "FR bind: %s, role: %s, link: %s%s", priv->netif, + osmo_fr_role_str(fr_link->role), priv->if_running ? "UP" : "DOWN", VTY_NEWLINE); + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + ns2_vty_dump_nsvc(vty, nsvc, stats); + } + + priv = bind->priv; +} + +/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */ +static void free_bind(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + struct msgb *msg, *msg2; + + if (!bind) + return; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(bind)); + priv = bind->priv; + + OSMO_ASSERT(llist_empty(&bind->nsvc)); + + osmo_timer_del(&priv->backlog.timer); + llist_for_each_entry_safe(msg, msg2, &priv->backlog.list, list) { + msgb_free(msg); + } + msgb_free(priv->backlog.lmi_msg); + + osmo_netdev_free(priv->netdev); + osmo_fr_link_free(priv->link); + osmo_fd_close(&priv->backlog.ofd); + talloc_free(priv); +} + +static void fr_dlci_status_cb(struct osmo_fr_dlc *dlc, void *cb_data, bool active) +{ + struct gprs_ns2_vc *nsvc = cb_data; + + if (active) { + ns2_vc_fsm_start(nsvc); + } else { + ns2_vc_force_unconfigured(nsvc); + } +} + +static struct priv_vc *fr_alloc_vc(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_vc *nsvc, + uint16_t dlci) +{ + struct priv_bind *privb = bind->priv; + struct priv_vc *priv = talloc_zero(bind, struct priv_vc); + if (!priv) + return NULL; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(bind)); + nsvc->priv = priv; + priv->dlci = dlci; + priv->dlc = osmo_fr_dlc_alloc(privb->link, dlci); + if (!priv->dlc) { + nsvc->priv = NULL; + talloc_free(priv); + return NULL; + } + + priv->dlc->cb_data = nsvc; + priv->dlc->rx_cb = fr_dlci_rx_cb; + priv->dlc->status_cb = fr_dlci_status_cb; + + return priv; +} + +int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind, + uint16_t dlci, + struct gprs_ns2_vc **result) +{ + struct gprs_ns2_vc *nsvc; + struct priv_vc *vcpriv; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(bind)); + if (!result) + return -EINVAL; + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vcpriv = nsvc->priv; + if (vcpriv->dlci != dlci) { + *result = nsvc; + return 0; + } + } + + return 1; +} + +/* PDU from the network interface towards the fr layer (upwards) */ +static int fr_netif_ofd_cb(struct osmo_fd *bfd, uint32_t what) +{ + struct gprs_ns2_vc_bind *bind = bfd->data; + struct priv_bind *priv = bind->priv; + struct msgb *msg; + struct sockaddr_ll sll; + socklen_t sll_len = sizeof(sll); + int rc = 0; + + /* we only handle read here. write to AF_PACKET sockets cannot be triggered + * by select or poll, see OS#4995 */ + if (!(what & OSMO_FD_READ)) + return 0; + + msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR Rx"); + if (!msg) + return -ENOMEM; + + rc = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, (struct sockaddr *)&sll, &sll_len); + if (rc < 0) { + LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR recv\n", strerror(errno)); + goto out_err; + } else if (rc == 0) { + goto out_err; + } + + /* ignore any packets that we might have received for a different interface, between + * the socket() and the bind() call */ + if (sll.sll_ifindex != priv->ifindex) + goto out_err; + + msgb_put(msg, rc); + msg->dst = priv->link; + return osmo_fr_rx(msg); + +out_err: + msgb_free(msg); + return rc; +} + +/* PDU from the frame relay towards the NS-VC (upwards) */ +static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg) +{ + int rc; + struct gprs_ns2_vc *nsvc = cb_data; + + rc = ns2_recv_vc(nsvc, msg); + + return rc; +} + +static int fr_netif_write_one(struct gprs_ns2_vc_bind *bind, struct msgb *msg) +{ + struct priv_bind *priv = bind->priv; + unsigned int len = msgb_length(msg); + int rc; + + /* estimate the retry time based on the data rate it takes to transmit */ + priv->backlog.retry_us = (BIT_DURATION_NS * 8 * len) / 1000; + + rc = write(priv->backlog.ofd.fd, msgb_data(msg), len); + if (rc == len) { + msgb_free(msg); + return 0; + } else if (rc < 0) { + /* don't free, the caller might want to re-transmit */ + switch (errno) { + case EAGAIN: + case ENOBUFS: + /* not a real error, but more a normal event on AF_PACKET */ + /* don't free the message and let the caller re-enqueue */ + return -errno; + default: + /* an actual error, like -ENETDOWN, -EMSGSIZE */ + LOGBIND(bind, LOGL_ERROR, "error during write to AF_PACKET: %s\n", strerror(errno)); + msgb_free(msg); + return 0; + } + } else { + /* short write */ + LOGBIND(bind, LOGL_ERROR, "short write on AF_PACKET: %d < %d\n", rc, len); + msgb_free(msg); + return 0; + } +} + +/*! determine if given bind is for FR-GRE encapsulation. */ +int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind) +{ + return (bind->driver == &vc_driver_fr); +} + +/* PDU from the NS-VC towards the frame relay layer (downwards) */ +static int fr_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) +{ + struct priv_vc *vcpriv = nsvc->priv; + unsigned int vc_len = msgb_length(msg); + int rc; + + msg->dst = vcpriv->dlc; + rc = osmo_fr_tx_dlc(msg); + if (OSMO_LIKELY(rc >= 0)) { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, vc_len); + } else { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len); + } + return rc; +} + +static void enqueue_at_head(struct gprs_ns2_vc_bind *bind, struct msgb *msg) +{ + struct priv_bind *priv = bind->priv; + llist_add(&msg->list, &priv->backlog.list); + osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1); + osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us); +} + +static void enqueue_at_tail(struct gprs_ns2_vc_bind *bind, struct msgb *msg) +{ + struct priv_bind *priv = bind->priv; + llist_add_tail(&msg->list, &priv->backlog.list); + osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1); + osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us); +} + +#define LMI_Q933A_DLCI 0 + +/* enqueue to backlog (LMI, signaling) or drop (userdata msg) */ +static int backlog_enqueue_or_free(struct gprs_ns2_vc_bind *bind, struct msgb *msg) +{ + struct priv_bind *priv = bind->priv; + uint8_t dlci = msg->data[0]; + uint8_t ns_pdu_type; + uint16_t bvci; + + if (msgb_length(msg) < 1) + goto out_free; + + /* we want to enqueue only Q.933 LMI traffic or NS signaling; NOT user traffic */ + switch (dlci) { + case LMI_Q933A_DLCI: + /* always store only the last LMI message in the lmi_msg bucket */ + msgb_free(priv->backlog.lmi_msg); + priv->backlog.lmi_msg = msg; + return 0; + default: + /* there's no point in trying to enqueue messages if the interface is down */ + if (!priv->if_running) + break; + + if (msgb_length(msg) < 3) + break; + ns_pdu_type = msg->data[2]; + switch (ns_pdu_type) { + case NS_PDUT_UNITDATA: + if (msgb_length(msg) < 6) + break; + bvci = osmo_load16be(msg->data + 4); + /* enqueue BVCI=0 traffic at tail of queue */ + if (bvci == BVCI_SIGNALLING) { + enqueue_at_tail(bind, msg); + return 0; + } + break; + default: + /* enqueue NS signaling traffic at head of queue */ + enqueue_at_head(bind, msg); + return 0; + } + break; + } + +out_free: + /* drop everything that is not LMI, NS-signaling or BVCI-0 */ + msgb_free(msg); + return -1; +} + +static void fr_backlog_timer_cb(void *data) +{ + struct gprs_ns2_vc_bind *bind = data; + struct priv_bind *priv = bind->priv; + int i, rc; + + /* first try to get rid of the LMI message, if any */ + if (priv->backlog.lmi_msg) { + rc = fr_netif_write_one(bind, priv->backlog.lmi_msg); + if (rc < 0) + goto restart_timer; + /* fr_netif_write_one() has just free'd it */ + priv->backlog.lmi_msg = NULL; + } + + /* attempt to send up to 10 messages in every timer */ + for (i = 0; i < 10; i++) { + struct msgb *msg = msgb_dequeue(&priv->backlog.list); + if (!msg) + break; + + rc = fr_netif_write_one(bind, msg); + if (rc < 0) { + /* re-add at head of list */ + llist_add(&msg->list, &priv->backlog.list); + break; + } + osmo_stat_item_dec(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1); + } + +restart_timer: + /* re-start timer if we still have data in the queue */ + if (!llist_empty(&priv->backlog.list)) + osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us); +} + +/* PDU from the frame relay layer towards the network interface (downwards) */ +int fr_tx_cb(void *data, struct msgb *msg) +{ + struct gprs_ns2_vc_bind *bind = data; + struct priv_bind *priv = bind->priv; + int rc; + + if (llist_empty(&priv->backlog.list)) { + /* attempt to transmit right now */ + rc = fr_netif_write_one(bind, msg); + if (rc < 0) { + /* enqueue to backlog in case it fails */ + return backlog_enqueue_or_free(bind, msg); + } + } else { + /* enqueue to backlog */ + return backlog_enqueue_or_free(bind, msg); + } + + return 0; +} + +static int devname2ifindex(const char *ifname) +{ + struct ifreq ifr; + int sk, rc; + + sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sk < 0) + return sk; + + + memset(&ifr, 0, sizeof(ifr)); + OSMO_STRLCPY_ARRAY(ifr.ifr_name, ifname); + + rc = ioctl(sk, SIOCGIFINDEX, &ifr); + close(sk); + if (rc < 0) + return rc; + + return ifr.ifr_ifindex; +} + +static int open_socket(int ifindex, const struct gprs_ns2_vc_bind *nsbind) +{ + struct sockaddr_ll addr; + int fd, rc; + + memset(&addr, 0, sizeof(addr)); + addr.sll_family = AF_PACKET; + addr.sll_protocol = htons(ETH_P_ALL); + addr.sll_ifindex = ifindex; + + fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_HDLC)); + if (fd < 0) { + LOGBIND(nsbind, LOGL_ERROR, "Can not create AF_PACKET socket. Are you root or have CAP_NET_RAW?\n"); + return fd; + } + + /* there's a race condition between the above syscall and the bind() call below, + * causing other packets to be received in between */ + + rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc < 0) { + LOGBIND(nsbind, LOGL_ERROR, "Can not bind AF_PACKET socket to ifindex %d\n", ifindex); + close(fd); + return rc; + } + + return fd; +} + +static int gprs_n2_fr_ifupdown_ind_cb(struct osmo_netdev *netdev, bool if_running) +{ + struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev); + struct priv_bind *bpriv = bind->priv; + struct msgb *msg, *msg2; + + if (bpriv->if_running == if_running) + return 0; + + LOGBIND(bind, LOGL_NOTICE, "FR net-device '%s': Physical link state changed: %s\n", + bpriv->netif, if_running ? "UP" : "DOWN"); + + /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes + * sense to get one out of the door ASAP. */ + llist_for_each_entry_safe(msg, msg2, &bpriv->backlog.list, list) { + msgb_free(msg); + } + + if (if_running) { + /* interface just came up */ + if (bpriv->backlog.lmi_msg) + osmo_timer_schedule(&bpriv->backlog.timer, 0, bpriv->backlog.retry_us); + } else { + /* interface just went down; no need to retransmit */ + osmo_timer_del(&bpriv->backlog.timer); + } + + bpriv->if_running = if_running; + return 0; +} + +static int gprs_n2_fr_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu) +{ + struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev); + struct priv_bind *bpriv = bind->priv; + struct gprs_ns2_nse *nse; + + /* 2 byte DLCI header */ + if (new_mtu <= 2) + return 0; + new_mtu -= 2; + + if (new_mtu == bind->mtu) + return 0; + + LOGBIND(bind, LOGL_INFO, "MTU changed from %d to %d.\n", + bind->mtu + 2, new_mtu + 2); + + bind->mtu = new_mtu; + if (!bpriv->if_running) + return 0; + + llist_for_each_entry(nse, &bind->nsi->nse, list) { + ns2_nse_update_mtu(nse); + } + return 0; +} + +static int set_ifupdown(const char *netif, bool up) +{ + int sock, rc; + struct ifreq req; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + return sock; + + memset(&req, 0, sizeof req); + OSMO_STRLCPY_ARRAY(req.ifr_name, netif); + + rc = ioctl(sock, SIOCGIFFLAGS, &req); + if (rc < 0) { + close(sock); + return rc; + } + + if ((req.ifr_flags & IFF_UP) == up) { + close(sock); + return 0; + } + + if (up) + req.ifr_flags |= IFF_UP; + + rc = ioctl(sock, SIOCSIFFLAGS, &req); + close(sock); + return rc; +} + +static int setup_device(const char *netif, const struct gprs_ns2_vc_bind *bind) +{ + int sock, rc; + char buffer[128]; + fr_proto *fr = (void*)buffer; + struct ifreq req; + + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock < 0) { + LOGBIND(bind, LOGL_ERROR, "%s: Unable to create socket: %s\n", + netif, strerror(errno)); + return sock; + } + + memset(&req, 0, sizeof(struct ifreq)); + memset(&buffer, 0, sizeof(buffer)); + OSMO_STRLCPY_ARRAY(req.ifr_name, netif); + req.ifr_settings.ifs_ifsu.sync = (void*)buffer; + req.ifr_settings.size = sizeof(buffer); + req.ifr_settings.type = IF_GET_PROTO; + + /* EINVAL is returned when no protocol has been set */ + rc = ioctl(sock, SIOCWANDEV, &req); + if (rc < 0 && errno != EINVAL) { + LOGBIND(bind, LOGL_ERROR, "%s: Unable to get FR protocol information: %s\n", + netif, strerror(errno)); + goto err; + } + + /* check if the device is good */ + if (rc == 0 && req.ifr_settings.type == IF_PROTO_FR && fr->lmi == LMI_NONE) { + LOGBIND(bind, LOGL_NOTICE, "%s: has correct frame relay mode and lmi\n", netif); + goto ifup; + } + + /* modify the device to match */ + rc = set_ifupdown(netif, false); + if (rc) { + LOGBIND(bind, LOGL_ERROR, "Unable to bring down the device %s: %s\n", + netif, strerror(errno)); + goto err; + } + + memset(&req, 0, sizeof(struct ifreq)); + memset(fr, 0, sizeof(fr_proto)); + OSMO_STRLCPY_ARRAY(req.ifr_name, netif); + req.ifr_settings.type = IF_PROTO_FR; + req.ifr_settings.size = sizeof(fr_proto); + req.ifr_settings.ifs_ifsu.fr = fr; + fr->lmi = LMI_NONE; + /* even those settings aren't used, they must be in the range */ + /* polling verification timer*/ + fr->t391 = 10; + /* link integrity verification polling timer */ + fr->t392 = 15; + /* full status polling counter*/ + fr->n391 = 6; + /* error threshold */ + fr->n392 = 3; + /* monitored events count */ + fr->n393 = 4; + + LOGBIND(bind, LOGL_INFO, "%s: Setting frame relay related parameters\n", netif); + rc = ioctl(sock, SIOCWANDEV, &req); + if (rc) { + LOGBIND(bind, LOGL_ERROR, "%s: Unable to set FR protocol on information: %s\n", + netif, strerror(errno)); + goto err; + } + +ifup: + rc = set_ifupdown(netif, true); + if (rc) + LOGBIND(bind, LOGL_ERROR, "Unable to bring up the device %s: %s\n", + netif, strerror(errno)); +err: + close(sock); + return rc; +} + +/*! Create a new bind for NS over FR. + * \param[in] nsi NS instance in which to create the bind + * \param[in] netif Network interface to bind to + * \param[in] fr_network + * \param[in] fr_role + * \param[out] result pointer to the created bind or if a bind with the name exists return the bind. + * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */ +int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi, + const char *name, + const char *netif, + struct osmo_fr_network *fr_network, + enum osmo_fr_role fr_role, + struct gprs_ns2_vc_bind **result) +{ + struct gprs_ns2_vc_bind *bind; + struct priv_bind *priv; + struct osmo_fr_link *fr_link; + int rc = 0; + + if (strlen(netif) > IFNAMSIZ) + return -EINVAL; + + bind = gprs_ns2_bind_by_name(nsi, name); + if (bind) { + if (result) + *result = bind; + return -EALREADY; + } + + rc = ns2_bind_alloc(nsi, name, &bind); + if (rc < 0) + return rc; + + bind->driver = &vc_driver_fr; + bind->ll = GPRS_NS2_LL_FR; + /* 2 mbit */ + bind->transfer_capability = 2; + bind->send_vc = fr_vc_sendmsg; + bind->free_vc = free_vc; + bind->dump_vty = dump_vty; + bind->mtu = FRAME_RELAY_SDU; + priv = bind->priv = talloc_zero(bind, struct priv_bind); + if (!priv) { + rc = -ENOMEM; + goto err_bind; + } + + INIT_LLIST_HEAD(&priv->backlog.list); + OSMO_STRLCPY_ARRAY(priv->netif, netif); + + /* FIXME: move fd handling into socket.c */ + fr_link = osmo_fr_link_alloc(fr_network, fr_role, netif); + if (!fr_link) { + rc = -EINVAL; + goto err_bind; + } + + fr_link->tx_cb = fr_tx_cb; + fr_link->cb_data = bind; + priv->link = fr_link; + + priv->ifindex = rc = devname2ifindex(netif); + if (rc < 0) { + LOGBIND(bind, LOGL_ERROR, "Can not get interface index for interface %s\n", netif); + goto err_fr; + } + + priv->netdev = osmo_netdev_alloc(bind, name); + if (!priv->netdev) { + rc = -ENOENT; + goto err_fr; + } + osmo_netdev_set_priv_data(priv->netdev, bind); + osmo_netdev_set_ifupdown_ind_cb(priv->netdev, gprs_n2_fr_ifupdown_ind_cb); + osmo_netdev_set_mtu_chg_cb(priv->netdev, gprs_n2_fr_mtu_chg_cb); + rc = osmo_netdev_set_ifindex(priv->netdev, priv->ifindex); + if (rc < 0) + goto err_free_netdev; + rc = osmo_netdev_register(priv->netdev); + if (rc < 0) + goto err_free_netdev; + + /* set protocol frame relay and lmi */ + rc = setup_device(priv->netif, bind); + if(rc < 0) { + LOGBIND(bind, LOGL_ERROR, "Failed to setup the interface %s for frame relay and lmi\n", netif); + goto err_free_netdev; + } + + rc = open_socket(priv->ifindex, bind); + if (rc < 0) + goto err_free_netdev; + priv->backlog.retry_us = 2500; /* start with some non-zero value; this corrsponds to 496 bytes */ + osmo_timer_setup(&priv->backlog.timer, fr_backlog_timer_cb, bind); + osmo_fd_setup(&priv->backlog.ofd, rc, OSMO_FD_READ, fr_netif_ofd_cb, bind, 0); + rc = osmo_fd_register(&priv->backlog.ofd); + if (rc < 0) + goto err_fd; + + if (result) + *result = bind; + + return rc; + +err_fd: + close(priv->backlog.ofd.fd); +err_free_netdev: + osmo_netdev_free(priv->netdev); + priv->netdev = NULL; +err_fr: + osmo_fr_link_free(fr_link); + priv->link = NULL; +err_bind: + gprs_ns2_free_bind(bind); + + return rc; +} + +/*! Return the frame relay role of a bind + * \param[in] bind The bind + * \return the frame relay role or -EINVAL if bind is not frame relay + */ +enum osmo_fr_role gprs_ns2_fr_bind_role(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + + if (bind->driver != &vc_driver_fr) + return -EINVAL; + + priv = bind->priv; + return priv->link->role; +} + +/*! Return the network interface of the bind + * \param[in] bind The bind + * \return the network interface + */ +const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + + if (bind->driver != &vc_driver_fr) + return NULL; + + priv = bind->priv; + return priv->netif; +} + +/*! Find NS bind for a given network interface + * \param[in] nsi NS instance + * \param[in] netif the network interface to search for + * \return the bind or NULL if not found + */ +struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif( + struct gprs_ns2_inst *nsi, + const char *netif) +{ + struct gprs_ns2_vc_bind *bind; + const char *_netif; + + OSMO_ASSERT(nsi); + OSMO_ASSERT(netif); + + llist_for_each_entry(bind, &nsi->binding, list) { + if (!gprs_ns2_is_fr_bind(bind)) + continue; + + _netif = gprs_ns2_fr_bind_netif(bind); + if (!strncmp(_netif, netif, IFNAMSIZ)) + return bind; + } + + return NULL; +} + +/*! Create, connect and activate a new FR-based NS-VC + * \param[in] bind bind in which the new NS-VC is to be created + * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created + * \param[in] dlci Data Link connection identifier + * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */ +struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_nse *nse, + uint16_t nsvci, + uint16_t dlci) +{ + struct gprs_ns2_vc *nsvc = NULL; + struct priv_vc *priv = NULL; + struct priv_bind *bpriv = bind->priv; + char idbuf[64]; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(bind)); + nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci); + if (nsvc) { + goto err; + } + + snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC%05u-%s-%s-DLCI%u", nse->nsei, nsvci, + gprs_ns2_lltype_str(nse->ll), bpriv->netif, dlci); + osmo_identifier_sanitize_buf(idbuf, NULL, '_'); + nsvc = ns2_vc_alloc(bind, nse, true, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf); + if (!nsvc) + goto err; + + nsvc->priv = priv = fr_alloc_vc(bind, nsvc, dlci); + if (!priv) + goto err; + + nsvc->nsvci = nsvci; + nsvc->nsvci_is_valid = true; + + return nsvc; + +err: + gprs_ns2_free_nsvc(nsvc); + return NULL; +} + + +/*! Create, connect and activate a new FR-based NS-VC + * \param[in] bind bind in which the new NS-VC is to be created + * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created + * \param[in] dlci Data Link connection identifier + * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */ +struct gprs_ns2_vc *gprs_ns2_fr_connect2(struct gprs_ns2_vc_bind *bind, + uint16_t nsei, + uint16_t nsvci, + uint16_t dlci) +{ + bool created_nse = false; + struct gprs_ns2_vc *nsvc = NULL; + struct gprs_ns2_nse *nse; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(bind)); + nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei); + if (!nse) { + nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_FR, GPRS_NS2_DIALECT_STATIC_RESETBLOCK); + if (!nse) + return NULL; + created_nse = true; + } + + nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci); + if (!nsvc) + goto err_nse; + + return nsvc; + +err_nse: + if (created_nse) + gprs_ns2_free_nse(nse); + + return NULL; +} + +/*! Return the nsvc by dlci. + * \param[in] bind + * \param[in] dlci Data Link connection identifier + * \return the nsvc or NULL if not found + */ +struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind, + uint16_t dlci) +{ + struct gprs_ns2_vc *nsvc; + struct priv_vc *vcpriv; + + OSMO_ASSERT(gprs_ns2_is_fr_bind(bind)); + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vcpriv = nsvc->priv; + + if (dlci == vcpriv->dlci) + return nsvc; + } + + return NULL; +} + +/*! Return the dlci of the nsvc + * \param[in] nsvc + * \return the dlci or 0 on error. 0 is not a valid dlci. + */ +uint16_t gprs_ns2_fr_nsvc_dlci(const struct gprs_ns2_vc *nsvc) +{ + struct priv_vc *vcpriv; + + if (!nsvc->bind) + return 0; + + if (nsvc->bind->driver != &vc_driver_fr) + return 0; + + vcpriv = nsvc->priv; + return vcpriv->dlci; +} diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c new file mode 100644 index 00000000..b99761eb --- /dev/null +++ b/src/gb/gprs_ns2_frgre.c @@ -0,0 +1,616 @@ +/*! \file gprs_ns2_frgre.c + * NS-over-FR-over-GRE implementation. + * GPRS Networks Service (NS) messages on the Gb interface. + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2009-2010,2014,2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <arpa/inet.h> + +#include <osmocom/core/byteswap.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gprs/gprs_ns2.h> + +#include "gprs_ns2_internal.h" + +#define GRE_PTYPE_FR 0x6559 +#define GRE_PTYPE_IPv4 0x0800 +#define GRE_PTYPE_IPv6 0x86dd +#define GRE_PTYPE_KAR 0x0000 /* keepalive response */ + +#ifndef IPPROTO_GRE +# define IPPROTO_GRE 47 +#endif + +struct gre_hdr { + uint16_t flags; + uint16_t ptype; +} __attribute__ ((packed)); + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__CYGWIN__) +/** + * On BSD the IPv4 struct is called struct ip and instead of iXX + * the members are called ip_XX. One could change this code to use + * struct ip but that would require to define _BSD_SOURCE and that + * might have other complications. Instead make sure struct iphdr + * is present on FreeBSD. The below is taken from GLIBC. + * + * The GNU C Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ +struct iphdr + { +#if BYTE_ORDER == LITTLE_ENDIAN + unsigned int ihl:4; + unsigned int version:4; +#elif BYTE_ORDER == BIG_ENDIAN + unsigned int version:4; + unsigned int ihl:4; +#endif + u_int8_t tos; + u_int16_t tot_len; + u_int16_t id; + u_int16_t frag_off; + u_int8_t ttl; + u_int8_t protocol; + u_int16_t check; + u_int32_t saddr; + u_int32_t daddr; + /*The options start here. */ + }; +#endif + + +static void free_bind(struct gprs_ns2_vc_bind *bind); +static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind, + struct msgb *msg, + struct osmo_sockaddr *dest); + +struct gprs_ns2_vc_driver vc_driver_frgre = { + .name = "GB frame relay over GRE", + .free_bind = free_bind, +}; + +struct priv_bind { + struct osmo_fd fd; + struct osmo_sockaddr addr; + uint16_t dlci; + int dscp; +}; + +struct priv_vc { + struct osmo_sockaddr remote; + uint16_t dlci; +}; + +static void free_vc(struct gprs_ns2_vc *nsvc) +{ + OSMO_ASSERT(nsvc); + + if (!nsvc->priv) + return; + + talloc_free(nsvc->priv); + nsvc->priv = NULL; +} + + +/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */ +static void free_bind(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + + if (!bind) + return; + + priv = bind->priv; + + OSMO_ASSERT(llist_empty(&bind->nsvc)); + + osmo_fd_close(&priv->fd); + talloc_free(priv); +} + +static struct priv_vc *frgre_alloc_vc(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_vc *nsvc, + struct osmo_sockaddr *remote, + uint16_t dlci) +{ + struct priv_vc *priv = talloc_zero(bind, struct priv_vc); + if (!priv) + return NULL; + + nsvc->priv = priv; + priv->remote = *remote; + priv->dlci = dlci; + + return priv; +} + +static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg, + struct ip6_hdr *ip6hdr, struct gre_hdr *greh) +{ + /* RFC 7676 IPv6 Support for Generic Routing Encapsulation (GRE) */ + struct gprs_ns2_vc_bind *bind = bfd->data; + struct priv_bind *priv = bind->priv; + int gre_payload_len; + struct ip6_hdr *inner_ip6h; + struct gre_hdr *inner_greh; + struct sockaddr_in6 daddr; + struct in6_addr ia6; + + gre_payload_len = msg->len - (sizeof(*ip6hdr) + sizeof(*greh)); + + inner_ip6h = (struct ip6_hdr *) ((uint8_t *)greh + sizeof(*greh)); + + if (gre_payload_len < sizeof(*ip6hdr) + sizeof(*inner_greh)) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n"); + return -EIO; + } + + if (!memcmp(&inner_ip6h->ip6_src, &ip6hdr->ip6_src, sizeof(struct in6_addr)) || + !memcmp(&inner_ip6h->ip6_dst, &ip6hdr->ip6_dst, sizeof(struct in6_addr))) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n"); + return -EIO; + } + + /* Are IPv6 extensions header are allowed in the *inner*? In the outer they are */ + if (inner_ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_GRE) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n"); + return -EIO; + } + + inner_greh = (struct gre_hdr *) ((uint8_t *)inner_ip6h + sizeof(struct ip6_hdr)); + if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n"); + return -EIO; + } + + /* Actually send the response back */ + + daddr.sin6_family = AF_INET6; + daddr.sin6_addr = inner_ip6h->ip6_dst; + daddr.sin6_port = IPPROTO_GRE; + + ia6 = ip6hdr->ip6_src; + char ip6str[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &ia6, ip6str, INET6_ADDRSTRLEN); + LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str); + + /* why does it reduce the gre_payload_len by the ipv6 header? + * make it similiar to ipv4 even this seems to be wrong */ + return sendto(priv->fd.fd, inner_greh, + gre_payload_len - sizeof(*inner_ip6h), 0, + (struct sockaddr *)&daddr, sizeof(daddr)); +} + +/* IPv4 messages inside the GRE tunnel might be GRE keepalives */ +static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg, + struct iphdr *iph, struct gre_hdr *greh) +{ + struct gprs_ns2_vc_bind *bind = bfd->data; + struct priv_bind *priv = bind->priv; + int gre_payload_len; + struct iphdr *inner_iph; + struct gre_hdr *inner_greh; + struct sockaddr_in daddr; + struct in_addr ia; + + gre_payload_len = msg->len - (iph->ihl*4 + sizeof(*greh)); + + inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh)); + + if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n"); + return -EIO; + } + + if (inner_iph->saddr != iph->daddr || + inner_iph->daddr != iph->saddr) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n"); + return -EIO; + } + + if (inner_iph->protocol != IPPROTO_GRE) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n"); + return -EIO; + } + + inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4); + if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) { + LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n"); + return -EIO; + } + + /* Actually send the response back */ + + daddr.sin_family = AF_INET; + daddr.sin_addr.s_addr = inner_iph->daddr; + daddr.sin_port = IPPROTO_GRE; + + ia.s_addr = iph->saddr; + LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", inet_ntoa(ia)); + + /* why does it reduce the gre_payload_len by the ipv4 header? */ + return sendto(priv->fd.fd, inner_greh, + gre_payload_len - inner_iph->ihl*4, 0, + (struct sockaddr *)&daddr, sizeof(daddr)); +} + +static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error, + struct osmo_sockaddr *saddr, uint16_t *dlci, + const struct gprs_ns2_vc_bind *bind) +{ + struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx"); + int ret = 0; + socklen_t saddr_len = sizeof(*saddr); + struct iphdr *iph = NULL; + struct ip6_hdr *ip6h = NULL; + size_t ip46hdr; + struct gre_hdr *greh; + uint8_t *frh; + + if (!msg) { + *error = -ENOMEM; + return NULL; + } + + ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, + &saddr->u.sa, &saddr_len); + if (ret < 0) { + LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", strerror(errno)); + *error = ret; + goto out_err; + } else if (ret == 0) { + *error = ret; + goto out_err; + } + + msgb_put(msg, ret); + + /* we've received a raw packet including the IPv4 or IPv6 header */ + switch (saddr->u.sa.sa_family) { + case AF_INET: + ip46hdr = sizeof(struct iphdr); + break; + case AF_INET6: + ip46hdr = sizeof(struct ip6_hdr); + break; + default: + *error = -EIO; + goto out_err; + break; + } + + /* TODO: add support for the extension headers */ + if (msg->len < ip46hdr + sizeof(*greh) + 2) { + LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len); + *error = -EIO; + goto out_err; + } + + switch (saddr->u.sa.sa_family) { + case AF_INET: + iph = (struct iphdr *) msg->data; + if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) { + LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len); + *error = -EIO; + goto out_err; + } + break; + case AF_INET6: + ip6h = (struct ip6_hdr *) msg->data; + break; + } + + if (iph) + greh = (struct gre_hdr *) (msg->data + iph->ihl*4); + else + greh = (struct gre_hdr *) (msg->data + sizeof(struct ip6_hdr)); + + if (greh->flags) { + LOGBIND(bind, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n", osmo_ntohs(greh->flags)); + } + + switch (osmo_ntohs(greh->ptype)) { + case GRE_PTYPE_IPv4: + /* IPv4 messages might be GRE keepalives */ + if (iph) + *error = handle_rx_gre_ipv4(bfd, msg, iph, greh); + else + *error = -EIO; + goto out_err; + break; + case GRE_PTYPE_IPv6: + if (ip6h) + *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh); + else + *error = -EIO; + goto out_err; + break; + case GRE_PTYPE_FR: + /* continue as usual */ + break; + default: + LOGBIND(bind, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n", osmo_ntohs(greh->ptype)); + *error = -EIO; + goto out_err; + break; + } + + if (msg->len < sizeof(*greh) + 2) { + LOGBIND(bind, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len); + *error = -EIO; + goto out_err; + } + + frh = (uint8_t *)greh + sizeof(*greh); + if (frh[0] & 0x01) { + LOGBIND(bind, LOGL_NOTICE, "Unsupported single-byte FR address\n"); + *error = -EIO; + goto out_err; + } + *dlci = ((frh[0] & 0xfc) << 2); + if ((frh[1] & 0x0f) != 0x01) { + LOGBIND(bind, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n", frh[1]); + *error = -EIO; + goto out_err; + } + *dlci |= (frh[1] >> 4); + + msg->l2h = frh+2; + + return msg; + +out_err: + msgb_free(msg); + return NULL; +} + +static int ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind, + uint16_t dlci, + struct gprs_ns2_vc **result) +{ + struct gprs_ns2_vc *nsvc; + struct priv_vc *vcpriv; + + if (!result) + return -EINVAL; + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vcpriv = nsvc->priv; + if (vcpriv->dlci != dlci) { + *result = nsvc; + return 0; + } + } + + return 1; +} + +static int handle_nsfrgre_read(struct osmo_fd *bfd) +{ + int rc; + struct osmo_sockaddr saddr; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_vc_bind *bind = bfd->data; + struct msgb *msg; + struct msgb *reject; + uint16_t dlci; + + msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci, bind); + if (!msg) + return rc; + + if (dlci == 0 || dlci == 1023) { + LOGBIND(bind, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n", dlci); + rc = 0; + goto out; + } + + rc = ns2_find_vc_by_dlci(bind, dlci, &nsvc); + if (rc) { + /* VC not found */ + rc = ns2_create_vc(bind, msg, &saddr, "newconnection", &reject, &nsvc); + switch (rc) { + case NS2_CS_FOUND: + break; + case NS2_CS_ERROR: + case NS2_CS_SKIPPED: + rc = 0; + goto out; + case NS2_CS_REJECTED: + /* nsip_sendmsg will free reject */ + rc = frgre_sendmsg(bind, reject, &saddr); + goto out; + case NS2_CS_CREATED: + frgre_alloc_vc(bind, nsvc, &saddr, dlci); + ns2_vc_fsm_start(nsvc); + break; + } + } + + rc = ns2_recv_vc(nsvc, msg); +out: + msgb_free(msg); + + return rc; +} + +static int handle_nsfrgre_write(struct osmo_fd *bfd) +{ + /* FIXME: actually send the data here instead of nsip_sendmsg() */ + return -EIO; +} + +static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind, + struct msgb *msg, + struct osmo_sockaddr *dest) +{ + int rc; + struct priv_bind *priv = bind->priv; + + rc = sendto(priv->fd.fd, msg->data, msg->len, 0, + &dest->u.sa, sizeof(*dest)); + + msgb_free(msg); + + return rc; +} + +static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) +{ + struct gprs_ns2_vc_bind *bind = nsvc->bind; + struct priv_vc *vcpriv = nsvc->priv; + struct priv_bind *bindpriv = bind->priv; + + uint16_t dlci = osmo_htons(bindpriv->dlci); + uint8_t *frh; + struct gre_hdr *greh; + unsigned int vc_len = msgb_length(msg); + int rc; + + /* Prepend the FR header */ + frh = msgb_push(msg, 2); + frh[0] = (dlci >> 2) & 0xfc; + frh[1] = ((dlci & 0xf)<<4) | 0x01; + + /* Prepend the GRE header */ + greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh)); + greh->flags = 0; + greh->ptype = osmo_htons(GRE_PTYPE_FR); + + rc = frgre_sendmsg(bind, msg, &vcpriv->remote); + if (OSMO_LIKELY(rc >= 0)) { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, rc); + } else { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len); + } + return rc; +} + +static int frgre_fd_cb(struct osmo_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & OSMO_FD_READ) + rc = handle_nsfrgre_read(bfd); + if (what & OSMO_FD_WRITE) + rc = handle_nsfrgre_write(bfd); + + return rc; +} + +/*! determine if given bind is for FR-GRE encapsulation. */ +int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind) +{ + return (bind->driver == &vc_driver_frgre); +} + +/*! Create a new bind for NS over FR-GRE. + * \param[in] nsi NS instance in which to create the bind + * \param[in] local local address on which to bind + * \param[in] dscp DSCP/TOS bits to use for transmitted data on this bind + * \param[out] result pointer to the created bind or if a bind with the name exists return the bind. + * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */ +int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi, + const char *name, + const struct osmo_sockaddr *local, + int dscp, + struct gprs_ns2_vc_bind **result) +{ + struct gprs_ns2_vc_bind *bind; + struct priv_bind *priv; + int rc; + + if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) + return -EINVAL; + + if (dscp < 0 || dscp > 63) + return -EINVAL; + + bind = gprs_ns2_bind_by_name(nsi, name); + if (bind) { + if (result) + *result = bind; + return -EALREADY; + } + + rc = ns2_bind_alloc(nsi, name, &bind); + if (rc < 0) + return rc; + + bind->driver = &vc_driver_frgre; + bind->ll = GPRS_NS2_LL_FR_GRE; + /* 2 mbit transfer capability. Counting should be done different for this. */ + bind->transfer_capability = 2; + bind->send_vc = frgre_vc_sendmsg; + bind->free_vc = free_vc; + bind->nsi = nsi; + /* TODO: allow to set the MTU via vty. It can not be automatic detected because it goes over an + * ethernet device and the MTU here must match the FR side of the FR-to-GRE gateway. + */ + bind->mtu = FRAME_RELAY_SDU; + + priv = bind->priv = talloc_zero(bind, struct priv_bind); + if (!priv) { + gprs_ns2_free_bind(bind); + return -ENOMEM; + } + priv->fd.cb = frgre_fd_cb; + priv->fd.data = bind; + priv->addr = *local; + INIT_LLIST_HEAD(&bind->nsvc); + priv->dscp = dscp; + + rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_RAW, IPPROTO_GRE, + local, NULL, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp)); + if (rc < 0) { + gprs_ns2_free_bind(bind); + return rc; + } + + if (result) + *result = bind; + + return rc; +} diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h new file mode 100644 index 00000000..2e7dac3f --- /dev/null +++ b/src/gb/gprs_ns2_internal.h @@ -0,0 +1,503 @@ +/*! \file gprs_ns2_internal.h */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/gprs/protocol/gsm_08_16.h> +#include <osmocom/gprs/gprs_ns2.h> + +#define LOGNSE(nse, lvl, fmt, args ...) \ + LOGP(DLNS, lvl, "NSE(%05u) " fmt, (nse)->nsei, ## args) + +#define LOGBIND(bind, lvl, fmt, args ...) \ + LOGP(DLNS, lvl, "BIND(%s) " fmt, (bind)->name, ## args) + +#define LOGNSVC_SS(ss, nsvc, lvl, fmt, args ...) \ + do { \ + if ((nsvc)->nsvci_is_valid) { \ + LOGP(ss, lvl, "NSE(%05u)-NSVC(%05u) " fmt, \ + (nsvc)->nse->nsei, (nsvc)->nsvci, ## args); \ + } else { \ + LOGP(ss, lvl, "NSE(%05u)-NSVC(none) " fmt, \ + (nsvc)->nse->nsei, ## args); \ + } \ + } while (0) + +#define LOGNSVC(nsvc, lvl, fmt, args ...) \ + LOGNSVC_SS(DLNS, nsvc, lvl, fmt, ## args) + +#define LOG_NS_SIGNAL(nsvc, direction, pdu_type, lvl, fmt, args ...) \ + LOGNSVC_SS(DLNSSIGNAL, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args) + +#define LOG_NS_DATA(nsvc, direction, pdu_type, lvl, fmt, args ...) \ + LOGNSVC_SS(DLNSDATA, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args) + +#define LOG_NS_RX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Rx", pdu_type, LOGL_INFO, "\n") +#define LOG_NS_TX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Tx", pdu_type, LOGL_INFO, "\n") + +#define RATE_CTR_INC_NS(nsvc, ctr) \ + do { \ + struct gprs_ns2_vc *_nsvc = (nsvc); \ + rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr)); \ + rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr)); \ + } while (0) + +#define RATE_CTR_ADD_NS(nsvc, ctr, val) \ + do { \ + struct gprs_ns2_vc *_nsvc = (nsvc); \ + rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr), val); \ + rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr), val); \ + } while (0) + + +struct osmo_fsm_inst; +struct tlv_parsed; +struct vty; + +struct gprs_ns2_vc_driver; +struct gprs_ns2_vc_bind; + +#define NS_TIMERS_COUNT 11 + +#define TNS_BLOCK_STR "tns-block" +#define TNS_BLOCK_RETRIES_STR "tns-block-retries" +#define TNS_RESET_STR "tns-reset" +#define TNS_RESET_RETRIES_STR "tns-reset-retries" +#define TNS_TEST_STR "tns-test" +#define TNS_ALIVE_STR "tns-alive" +#define TNS_ALIVE_RETRIES_STR "tns-alive-retries" +#define TSNS_PROV_STR "tsns-prov" +#define TSNS_SIZE_RETRIES_STR "tsns-size-retries" +#define TSNS_CONFIG_RETRIES_STR "tsns-config-retries" +#define TSNS_PROCEDURES_RETRIES_STR "tsns-procedures-retries" +#define NS_TIMERS "(" TNS_BLOCK_STR "|" TNS_BLOCK_RETRIES_STR "|" TNS_RESET_STR "|" TNS_RESET_RETRIES_STR "|" TNS_TEST_STR "|"\ + TNS_ALIVE_STR "|" TNS_ALIVE_RETRIES_STR "|" TSNS_PROV_STR "|" TSNS_SIZE_RETRIES_STR "|" TSNS_CONFIG_RETRIES_STR "|"\ + TSNS_PROCEDURES_RETRIES_STR ")" + +#define NS_TIMERS_HELP \ + "(un)blocking Timer (Tns-block) timeout\n" \ + "(un)blocking Timer (Tns-block) number of retries\n" \ + "Reset Timer (Tns-reset) timeout\n" \ + "Reset Timer (Tns-reset) number of retries\n" \ + "Test Timer (Tns-test) timeout\n" \ + "Alive Timer (Tns-alive) timeout\n" \ + "Alive Timer (Tns-alive) number of retries\n" \ + "SNS Provision Timer (Tsns-prov) timeout\n" \ + "SNS Size number of retries\n" \ + "SNS Config number of retries\n" \ + "SNS Procedures number of retries\n" \ + +/* Educated guess - LLC user payload is 1500 bytes plus possible headers */ +#define NS_ALLOC_SIZE 3072 +#define NS_ALLOC_HEADROOM 20 + +#define NS_DEFAULT_TXQUEUE_MAX_LENGTH 128 + +enum ns2_timeout { + NS_TOUT_TNS_BLOCK, + NS_TOUT_TNS_BLOCK_RETRIES, + NS_TOUT_TNS_RESET, + NS_TOUT_TNS_RESET_RETRIES, + NS_TOUT_TNS_TEST, + NS_TOUT_TNS_ALIVE, + NS_TOUT_TNS_ALIVE_RETRIES, + NS_TOUT_TSNS_PROV, + NS_TOUT_TSNS_SIZE_RETRIES, + NS_TOUT_TSNS_CONFIG_RETRIES, + NS_TOUT_TSNS_PROCEDURES_RETRIES, +}; + +enum nsvc_timer_mode { + /* standard timers */ + NSVC_TIMER_TNS_TEST, + NSVC_TIMER_TNS_ALIVE, + NSVC_TIMER_TNS_RESET, + _NSVC_TIMER_NR, +}; + +enum ns2_vc_stat { + NS_STAT_ALIVE_DELAY, +}; + +enum ns2_bind_stat { + NS2_BIND_STAT_BACKLOG_LEN, +}; + +/*! Osmocom NS2 VC create status */ +enum ns2_cs { + NS2_CS_CREATED, /*!< A NSVC object has been created */ + NS2_CS_FOUND, /*!< A NSVC object has been found */ + NS2_CS_REJECTED, /*!< Rejected and answered message */ + NS2_CS_SKIPPED, /*!< Skipped message */ + NS2_CS_ERROR, /*!< Failed to process message */ +}; + +enum ns_ctr { + NS_CTR_PKTS_IN, + NS_CTR_PKTS_OUT, + NS_CTR_PKTS_OUT_DROP, + NS_CTR_BYTES_IN, + NS_CTR_BYTES_OUT, + NS_CTR_BYTES_OUT_DROP, + NS_CTR_BLOCKED, + NS_CTR_UNBLOCKED, + NS_CTR_DEAD, + NS_CTR_REPLACED, + NS_CTR_NSEI_CHG, + NS_CTR_INV_VCI, + NS_CTR_INV_NSEI, + NS_CTR_LOST_ALIVE, + NS_CTR_LOST_RESET, +}; + +#define NSE_S_BLOCKED 0x0001 +#define NSE_S_ALIVE 0x0002 +#define NSE_S_RESET 0x0004 + +#define NS_DESC_B(st) ((st) & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED") +#define NS_DESC_A(st) ((st) & NSE_S_ALIVE ? "ALIVE" : "DEAD") +#define NS_DESC_R(st) ((st) & NSE_S_RESET ? "RESET" : "UNRESET") + +/*! An instance of the NS protocol stack */ +struct gprs_ns2_inst { + /*! callback to the user for incoming UNIT DATA IND */ + osmo_prim_cb cb; + + /*! callback data */ + void *cb_data; + + /*! linked lists of all NSVC binds (e.g. IPv4 bind, but could be also E1 */ + struct llist_head binding; + + /*! linked lists of all NSVC in this instance */ + struct llist_head nse; + + uint16_t timeout[NS_TIMERS_COUNT]; + + /*! workaround for rate counter until rate counter accepts char str as index */ + uint32_t nsvc_rate_ctr_idx; + uint32_t bind_rate_ctr_idx; + + uint32_t txqueue_max_length; +}; + + +/*! Structure repesenting a NSE. The BSS/PCU will only have a single NSE, while SGSN has one for each BSS/PCU */ +struct gprs_ns2_nse { + uint16_t nsei; + + /*! entry back to ns2_inst */ + struct gprs_ns2_inst *nsi; + + /*! llist entry for gprs_ns2_inst */ + struct llist_head list; + + /*! llist head to hold all nsvc */ + struct llist_head nsvc; + + /*! count all active NSVCs */ + int nsvc_count; + + /*! true if this NSE was created by VTY or pcu socket) */ + bool persistent; + + /*! true if this NSE wasn't yet alive at all. + * Will be true after the first status ind with NS_AFF_CAUSE_RECOVERY */ + bool first; + + /*! true if this NSE has at least one alive VC */ + bool alive; + + /*! which link-layer are we based on? */ + enum gprs_ns2_ll ll; + + /*! which dialect does this NSE speaks? */ + enum gprs_ns2_dialect dialect; + + struct osmo_fsm_inst *bss_sns_fi; + + /*! sum of all the data weight of _alive_ NS-VCs */ + uint32_t sum_data_weight; + + /*! sum of all the signalling weight of _alive_ NS-VCs */ + uint32_t sum_sig_weight; + + /*! MTU of a NS PDU. This is the lowest MTU of all NSVCs */ + uint16_t mtu; + + /*! are we implementing the SGSN role? */ + bool ip_sns_role_sgsn; + + /*! NSE-wide statistics */ + struct rate_ctr_group *ctrg; + + /*! recursive anchor */ + bool freed; + + /*! when the NSE became alive or dead */ + struct timespec ts_alive_change; +}; + +/*! Structure representing a single NS-VC */ +struct gprs_ns2_vc { + /*! list of NS-VCs within NSE */ + struct llist_head list; + + /*! list of NS-VCs within bind, bind is the owner! */ + struct llist_head blist; + + /*! pointer to NS Instance */ + struct gprs_ns2_nse *nse; + + /*! pointer to NS VL bind. bind own the memory of this instance */ + struct gprs_ns2_vc_bind *bind; + + /*! true if this NS was created by VTY or pcu socket) */ + bool persistent; + + /*! uniquely identifies NS-VC if VC contains nsvci */ + uint16_t nsvci; + + /*! signalling weight. 0 = don't use for signalling (BVCI == 0)*/ + uint8_t sig_weight; + + /*! signalling packet counter for the load sharing function */ + uint8_t sig_counter; + + /*! data weight. 0 = don't use for user data (BVCI != 0) */ + uint8_t data_weight; + + /*! can be used by the bind/driver of the virtual circuit. e.g. ipv4/ipv6/frgre/e1 */ + void *priv; + + bool nsvci_is_valid; + /*! should this NS-VC only be used for SNS-SIZE and SNS-CONFIG? */ + bool sns_only; + + struct rate_ctr_group *ctrg; + struct osmo_stat_item_group *statg; + + enum gprs_ns2_vc_mode mode; + + struct osmo_fsm_inst *fi; + + /*! recursive anchor */ + bool freed; + + /*! if blocked by O&M/vty */ + bool om_blocked; + + /*! when the NSVC became alive or dead */ + struct timespec ts_alive_change; +}; + +/*! Structure repesenting a bind instance. E.g. IPv4 listen port. */ +struct gprs_ns2_vc_bind { + /*! unique name */ + const char *name; + /*! list entry in nsi */ + struct llist_head list; + /*! list of all VC */ + struct llist_head nsvc; + /*! driver private structure */ + void *priv; + /*! a pointer back to the nsi */ + struct gprs_ns2_inst *nsi; + struct gprs_ns2_vc_driver *driver; + + bool accept_ipaccess; + bool accept_sns; + + /*! transfer capability in mbit */ + int transfer_capability; + + /*! MTU of a NS PDU on this bind. */ + uint16_t mtu; + + /*! which link-layer are we based on? */ + enum gprs_ns2_ll ll; + + /*! send a msg over a VC */ + int (*send_vc)(struct gprs_ns2_vc *nsvc, struct msgb *msg); + + /*! free the vc priv data */ + void (*free_vc)(struct gprs_ns2_vc *nsvc); + + /*! allow to show information for the vty */ + void (*dump_vty)(const struct gprs_ns2_vc_bind *bind, + struct vty *vty, bool stats); + + /*! the IP-SNS signalling weight when doing dynamic configuration */ + uint8_t sns_sig_weight; + /*! the IP-SNS data weight when doing dynamic configuration */ + uint8_t sns_data_weight; + + struct osmo_stat_item_group *statg; + + /*! recursive anchor */ + bool freed; +}; + +struct gprs_ns2_vc_driver { + const char *name; + void *priv; + void (*free_bind)(struct gprs_ns2_vc_bind *driver); +}; + +enum ns2_sns_event { + NS2_SNS_EV_REQ_SELECT_ENDPOINT, /*!< Select a SNS endpoint from the list */ + NS2_SNS_EV_RX_SIZE, + NS2_SNS_EV_RX_SIZE_ACK, + NS2_SNS_EV_RX_CONFIG, + NS2_SNS_EV_RX_CONFIG_END, /*!< SNS-CONFIG with end flag received */ + NS2_SNS_EV_RX_CONFIG_ACK, + NS2_SNS_EV_RX_ADD, + NS2_SNS_EV_RX_DELETE, + NS2_SNS_EV_RX_CHANGE_WEIGHT, + NS2_SNS_EV_RX_ACK, /*!< Rx of SNS-ACK (response to ADD/DELETE/CHG_WEIGHT */ + NS2_SNS_EV_REQ_NO_NSVC, /*!< no more NS-VC remaining (all dead) */ + NS2_SNS_EV_REQ_FREE_NSVCS, /*!< free all NS-VCs */ + NS2_SNS_EV_REQ_NSVC_ALIVE, /*!< a NS-VC became alive */ + NS2_SNS_EV_REQ_ADD_BIND, /*!< add a new local bind to this NSE */ + NS2_SNS_EV_REQ_DELETE_BIND, /*!< remove a local bind from this NSE */ + NS2_SNS_EV_REQ_CHANGE_WEIGHT, /*!< a bind changed its weight */ +}; + +enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind, + struct msgb *msg, + const struct osmo_sockaddr *remote, + const char *logname, + struct msgb **reject, + struct gprs_ns2_vc **success); + +int ns2_recv_vc(struct gprs_ns2_vc *nsvc, + struct msgb *msg); + +struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_nse *nse, + bool initiater, + enum gprs_ns2_vc_mode vc_mode, + const char *id); + +void ns2_free_nsvcs(struct gprs_ns2_nse *nse); +int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name, + struct gprs_ns2_vc_bind **result); + +struct msgb *ns2_msgb_alloc(void); + +void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse); +void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats); +void ns2_prim_status_ind(struct gprs_ns2_nse *nse, + struct gprs_ns2_vc *nsvc, + uint16_t bvci, + enum gprs_ns2_affecting_cause cause); +void ns2_nse_notify_alive(struct gprs_ns2_vc *nsvc, bool alive); +void ns2_nse_update_mtu(struct gprs_ns2_nse *nse); +int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect); + +/* message */ +int ns2_validate(struct gprs_ns2_vc *nsvc, + uint8_t pdu_type, + struct msgb *msg, + struct tlv_parsed *tp, + uint8_t *cause); + +/* SNS messages */ +int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems); +int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems); +int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause); +int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc, + int ip4_ep_nr, int ip6_ep_nr); +int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause); + +int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems); +int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems); +int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems); + +/* transmit message over a VC */ +int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci); +int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci); + +int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause); +int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc); + +int ns2_tx_unblock(struct gprs_ns2_vc *nsvc); +int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc); + +int ns2_tx_alive(struct gprs_ns2_vc *nsvc); +int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc); + +int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc, + uint16_t bvci, uint8_t sducontrol, + struct msgb *msg); + +int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause, + uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci); + +/* driver */ +struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_nse *nse, + const struct osmo_sockaddr *remote); +int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote); +struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi, + struct osmo_sockaddr *remote, + int index); +void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length); + +/* sns */ +int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp); +struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse, + const char *id); +struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id); +void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc); +void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive); +void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind); + +/* vc */ +struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc, + const char *id, bool initiate); +int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc); +int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc); +int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp); +int ns2_vc_is_alive(struct gprs_ns2_vc *nsvc); +int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc); +int ns2_vc_block(struct gprs_ns2_vc *nsvc); +int ns2_vc_reset(struct gprs_ns2_vc *nsvc); +int ns2_vc_unblock(struct gprs_ns2_vc *nsvc); +void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats); + +/* nse */ +void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked); +enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect); +int ns2_count_transfer_cap(struct gprs_ns2_nse *nse, + uint16_t bvci); + +/* vty */ +int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse); diff --git a/src/gb/gprs_ns2_message.c b/src/gb/gprs_ns2_message.c new file mode 100644 index 00000000..de63b7aa --- /dev/null +++ b/src/gb/gprs_ns2_message.c @@ -0,0 +1,848 @@ +/*! \file gprs_ns2_message.c + * NS-over-FR-over-GRE implementation. + * GPRS Networks Service (NS) messages on the Gb interface. + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> + +#include <osmocom/core/byteswap.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/stats.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gprs/gprs_ns2.h> +#include <osmocom/gprs/protocol/gsm_08_16.h> + +#include "gprs_ns2_internal.h" + +#define ERR_IF_NSVC_USES_SNS(nsvc, reason) \ + do { \ + if (!nsvc->nse->bss_sns_fi) \ + break; \ + LOGNSVC(nsvc, LOGL_DEBUG, "invalid packet %s with SNS\n", reason); \ + } while (0) + +static int ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause) +{ + if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1) || + !TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + + return 0; +} + +static int ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause) +{ + if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + + return 0; +} + +static int ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause) +{ + if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + + return 0; +} + +static int ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause) +{ + if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + + return 0; +} + +static int ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause) +{ + + if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + + uint8_t _cause = tlvp_val8(tp, NS_IE_CAUSE, 0); + switch (_cause) { + case NS_CAUSE_NSVC_BLOCKED: + case NS_CAUSE_NSVC_UNKNOWN: + if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 1)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + + if (nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) { + *cause = NS_CAUSE_PDU_INCOMP_PSTATE; + return -1; + } + break; + case NS_CAUSE_SEM_INCORR_PDU: + case NS_CAUSE_PDU_INCOMP_PSTATE: + case NS_CAUSE_PROTO_ERR_UNSPEC: + case NS_CAUSE_INVAL_ESSENT_IE: + case NS_CAUSE_MISSING_ESSENT_IE: + if (!TLVP_PRES_LEN(tp, NS_IE_PDU, 1)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + break; + case NS_CAUSE_BVCI_UNKNOWN: + if (!TLVP_PRES_LEN(tp, NS_IE_BVCI, 2)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + break; + case NS_CAUSE_UNKN_IP_TEST_FAILED: + if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { + *cause = NS_CAUSE_MISSING_ESSENT_IE; + return -1; + } + break; + } + + return 0; +} + +int ns2_validate(struct gprs_ns2_vc *nsvc, + uint8_t pdu_type, + struct msgb *msg, + struct tlv_parsed *tp, + uint8_t *cause) +{ + switch (pdu_type) { + case NS_PDUT_RESET: + return ns2_validate_reset(nsvc, msg, tp, cause); + case NS_PDUT_RESET_ACK: + return ns2_validate_reset_ack(nsvc, msg, tp, cause); + case NS_PDUT_BLOCK: + return ns2_validate_block(nsvc, msg, tp, cause); + case NS_PDUT_BLOCK_ACK: + return ns2_validate_block_ack(nsvc, msg, tp, cause); + case NS_PDUT_STATUS: + return ns2_validate_status(nsvc, msg, tp, cause); + + /* following PDUs doesn't have any payloads */ + case NS_PDUT_ALIVE: + case NS_PDUT_ALIVE_ACK: + case NS_PDUT_UNBLOCK: + case NS_PDUT_UNBLOCK_ACK: + if (msgb_l2len(msg) != sizeof(struct gprs_ns_hdr)) { + *cause = NS_CAUSE_PROTO_ERR_UNSPEC; + return -1; + } + break; + } + + return 0; +} + +static int ns_vc_tx(struct gprs_ns2_vc *nsvc, struct msgb *msg) +{ + return nsvc->bind->send_vc(nsvc, msg); +} + +/* transmit functions */ +static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type) +{ + struct msgb *msg = ns2_msgb_alloc(); + struct gprs_ns_hdr *nsh; + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + if (!msg) + return -ENOMEM; + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = pdu_type; + + LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type); + return ns_vc_tx(nsvc, msg); +} + +/*! Transmit a NS-BLOCK on a given NS-VC. + * \param[in] vc NS-VC on which the NS-BLOCK is to be transmitted + * \param[in] cause Numeric NS Cause value + * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used. + * \returns 0 in case of success */ +int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t encoded_nsvci; + + if (nsvci) + encoded_nsvci = osmo_htons(*nsvci); + else + encoded_nsvci = osmo_htons(nsvc->nsvci); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK"); + + msg = ns2_msgb_alloc(); + if (!msg) + return -ENOMEM; + + rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED)); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_BLOCK; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci); + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause)); + return ns_vc_tx(nsvc, msg); +} + +/*! Transmit a NS-BLOCK-ACK on a given NS-VC. + * \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted + * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used. + * \returns 0 in case of success */ +int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t encoded_nsvci; + + if (nsvci) + encoded_nsvci = osmo_htons(*nsvci); + else + encoded_nsvci = osmo_htons(nsvc->nsvci); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK"); + + msg = ns2_msgb_alloc(); + if (!msg) + return -ENOMEM; + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_BLOCK_ACK; + + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci); + + LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type); + return ns_vc_tx(nsvc, msg); +} + +/*! Transmit a NS-RESET on a given NS-VC. + * \param[in] nsvc NS-VC used for transmission + * \param[in] cause Numeric NS cause value + * \returns 0 in case of success */ +int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsvci = osmo_htons(nsvc->nsvci); + uint16_t nsei = osmo_htons(nsvc->nse->nsei); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET"); + + msg = ns2_msgb_alloc(); + if (!msg) + return -ENOMEM; + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_RESET; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci); + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei); + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause)); + return ns_vc_tx(nsvc, msg); +} + +/*! Transmit a NS-RESET-ACK on a given NS-VC. + * \param[in] nsvc NS-VC used for transmission + * \returns 0 in case of success */ +int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsvci, nsei; + + /* Section 9.2.6 */ + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK"); + + msg = ns2_msgb_alloc(); + if (!msg) + return -ENOMEM; + + nsvci = osmo_htons(nsvc->nsvci); + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = NS_PDUT_RESET_ACK; + + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci); + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + + LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type); + return ns_vc_tx(nsvc, msg); +} + +/*! Transmit a NS-UNBLOCK on a given NS-VC. + * \param[in] nsvc NS-VC on which the NS-UNBLOCK is to be transmitted + * \returns 0 in case of success */ +int ns2_tx_unblock(struct gprs_ns2_vc *nsvc) +{ + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK"); + + return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK); +} + + +/*! Transmit a NS-UNBLOCK-ACK on a given NS-VC. + * \param[in] nsvc NS-VC on which the NS-UNBLOCK-ACK is to be transmitted + * \returns 0 in case of success */ +int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc) +{ + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK ACK"); + + return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK); +} + +/*! Transmit a NS-ALIVE on a given NS-VC. + * \param[in] nsvc NS-VC on which the NS-ALIVE is to be transmitted + * \returns 0 in case of success */ +int ns2_tx_alive(struct gprs_ns2_vc *nsvc) +{ + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + return ns2_tx_simple(nsvc, NS_PDUT_ALIVE); +} + +/*! Transmit a NS-ALIVE-ACK on a given NS-VC. + * \param[in] nsvc NS-VC on which the NS-ALIVE-ACK is to be transmitted + * \returns 0 in case of success */ +int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc) +{ + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + return ns2_tx_simple(nsvc, NS_PDUT_ALIVE_ACK); +} + +/*! Transmit NS-UNITDATA on a given NS-VC. + * \param[in] nsvc NS-VC on which the NS-UNITDATA is to be transmitted + * \param[in] bvci BVCI to encode in NS-UNITDATA header + * \param[in] sducontrol SDU control octet of NS header + * \param[in] msg message buffer containing payload + * \returns 0 in case of success */ +int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc, + uint16_t bvci, uint8_t sducontrol, + struct msgb *msg) +{ + struct gprs_ns_hdr *nsh; + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + msg->l2h = msgb_push(msg, sizeof(*nsh) + 3); + nsh = (struct gprs_ns_hdr *) msg->l2h; + if (!nsh) { + LOGNSVC(nsvc, LOGL_ERROR, "Not enough headroom for NS header\n"); + msgb_free(msg); + return -EIO; + } + + nsh->pdu_type = NS_PDUT_UNITDATA; + nsh->data[0] = sducontrol; + nsh->data[1] = bvci >> 8; + nsh->data[2] = bvci & 0xff; + + LOG_NS_DATA(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, "\n"); + return ns_vc_tx(nsvc, msg); +} + +/*! Transmit a NS-STATUS on a given NS-VC. + * \param[in] nsvc NS-VC to be used for transmission + * \param[in] cause Numeric NS cause value + * \param[in] bvci BVCI to be reset within NSVC + * \param[in] orig_msg message causing the STATUS + * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used. + * \returns 0 in case of success */ +int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause, + uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci) +{ + struct msgb *msg = ns2_msgb_alloc(); + struct gprs_ns_hdr *nsh; + uint16_t encoded_nsvci; + unsigned int orig_len, max_orig_len; + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + + bvci = osmo_htons(bvci); + + if (!msg) + return -ENOMEM; + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_STATUS; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); + + switch (cause) { + case NS_CAUSE_NSVC_BLOCKED: + case NS_CAUSE_NSVC_UNKNOWN: + /* Section 9.2.7.1: Static conditions for NS-VCI */ + if (nsvci) + encoded_nsvci = osmo_htons(*nsvci); + else + encoded_nsvci = osmo_htons(nsvc->nsvci); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&encoded_nsvci); + break; + case NS_CAUSE_SEM_INCORR_PDU: + case NS_CAUSE_PDU_INCOMP_PSTATE: + case NS_CAUSE_PROTO_ERR_UNSPEC: + case NS_CAUSE_INVAL_ESSENT_IE: + case NS_CAUSE_MISSING_ESSENT_IE: + /* Section 9.2.7.2: Static conditions for NS PDU */ + /* ensure the PDU doesn't exceed the MTU */ + orig_len = msgb_l2len(orig_msg); + max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len); + if (max_orig_len > nsvc->bind->mtu) + orig_len -= max_orig_len - nsvc->bind->mtu; + msgb_tvlv_put(msg, NS_IE_PDU, orig_len, orig_msg->l2h); + break; + case NS_CAUSE_BVCI_UNKNOWN: + /* Section 9.2.7.3: Static conditions for BVCI */ + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci); + break; + + default: + break; + } + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause)); + return ns_vc_tx(nsvc, msg); +} + +/*! Encode + Transmit a SNS-ADD/SNS-CHANGE-WEIGHT as per Section 9.3.2/9.3.3. + * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG + * \param[in] pdu The PDU type to send out + * \param[in] trans_id The transaction id + * \param[in] ip4_elems Array of IPv4 Elements + * \param[in] num_ip4_elems number of ip4_elems + * \param[in] ip6_elems Array of IPv6 Elements + * \param[in] num_ip6_elems number of ip6_elems + * \returns 0 on success; negative in case of error */ +static int ns2_tx_sns_procedure(struct gprs_ns2_vc *nsvc, + enum ns_pdu_type pdu, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsei; + + if (!nsvc) + return -EINVAL; + + if (!ip4_elems && !ip6_elems) + return -EINVAL; + + msg = ns2_msgb_alloc(); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + if (!nsvc->nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n"); + msgb_free(msg); + return -EIO; + } + + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = pdu; + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + msgb_v_put(msg, trans_id); + + /* List of IP4 Elements 10.3.2c */ + if (ip4_elems) { + msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem), + (const uint8_t *)ip4_elems); + } else if (ip6_elems) { + /* List of IP6 elements 10.3.2d */ + msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem), + (const uint8_t *)ip6_elems); + } + + return ns_vc_tx(nsvc, msg); +} + +/*! Encode + Transmit a SNS-ADD as per Section 9.3.2. + * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG + * \param[in] trans_id The transaction id + * \param[in] ip4_elems Array of IPv4 Elements + * \param[in] num_ip4_elems number of ip4_elems + * \param[in] ip6_elems Array of IPv6 Elements + * \param[in] num_ip6_elems number of ip6_elems + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems) +{ + return ns2_tx_sns_procedure(nsvc, SNS_PDUT_ADD, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems); +} + +/*! Encode + Transmit a SNS-CHANGE-WEIGHT as per Section 9.3.3. + * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG + * \param[in] trans_id The transaction id + * \param[in] ip4_elems Array of IPv4 Elements + * \param[in] num_ip4_elems number of ip4_elems + * \param[in] ip6_elems Array of IPv6 Elements + * \param[in] num_ip6_elems number of ip6_elems + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems) +{ + return ns2_tx_sns_procedure(nsvc, SNS_PDUT_CHANGE_WEIGHT, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems); +} + +/*! Encode + Transmit a SNS-DEL as per Section 9.3.6. + * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG + * \param[in] trans_id The transaction id + * \param[in] ip4_elems Array of IPv4 Elements + * \param[in] num_ip4_elems number of ip4_elems + * \param[in] ip6_elems Array of IPv6 Elements + * \param[in] num_ip6_elems number of ip6_elems + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc, + uint8_t trans_id, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems) +{ + /* TODO: IP Address field */ + return ns2_tx_sns_procedure(nsvc, SNS_PDUT_DELETE, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems); +} + + +/*! Encode + Transmit a SNS-ACK as per Section 9.3.1. + * \param[in] nsvc NS-VC through which to transmit the ACK + * \param[in] trans_id Transaction ID which to acknowledge + * \param[in] cause Pointer to cause value (NULL if no cause to be sent) + * \param[in] ip4_elems Array of IPv4 Elements + * \param[in] num_ip4_elems number of ip4_elems + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsei; + + if (!nsvc) + return -1; + + msg = ns2_msgb_alloc(); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + if (!nsvc->nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n"); + msgb_free(msg); + return -EIO; + } + + + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = SNS_PDUT_ACK; + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + msgb_v_put(msg, trans_id); + if (cause) + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause); + if (ip4_elems) { + /* List of IP4 Elements 10.3.2c */ + msgb_tvlv_put(msg, NS_IE_IPv4_LIST, + num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem), + (const uint8_t *)ip4_elems); + } + if (ip6_elems) { + /* List of IP6 elements 10.3.2d */ + msgb_tvlv_put(msg, NS_IE_IPv6_LIST, + num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem), + (const uint8_t *)ip6_elems); + } + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, + " (trans_id=%u, cause=%s, num_ip4=%u, num_ip6=%u)\n", + trans_id, cause ? gprs_ns2_cause_str(*cause) : "NULL", num_ip4_elems, num_ip6_elems); + return ns_vc_tx(nsvc, msg); +} + +/*! Encode + Transmit a SNS-CONFIG as per Section 9.3.4. + * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG + * \param[in] end_flag Whether or not this is the last SNS-CONFIG + * \param[in] ip4_elems Array of IPv4 Elements + * \param[in] num_ip4_elems number of ip4_elems + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag, + const struct gprs_ns_ie_ip4_elem *ip4_elems, + unsigned int num_ip4_elems, + const struct gprs_ns_ie_ip6_elem *ip6_elems, + unsigned int num_ip6_elems) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsei; + + if (!nsvc) + return -1; + + msg = ns2_msgb_alloc(); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + if (!nsvc->nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n"); + msgb_free(msg); + return -EIO; + } + + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = SNS_PDUT_CONFIG; + + msgb_v_put(msg, end_flag ? 0x01 : 0x00); + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + + /* List of IP4 Elements 10.3.2c */ + if (ip4_elems) { + msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem), + (const uint8_t *)ip4_elems); + } else if (ip6_elems) { + /* List of IP6 elements 10.3.2d */ + msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem), + (const uint8_t *)ip6_elems); + } + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, + " (end_flag=%u, num_ip4=%u, num_ip6=%u)\n", + end_flag, num_ip4_elems, num_ip6_elems); + return ns_vc_tx(nsvc, msg); +} + +/*! Encode + Transmit a SNS-CONFIG-ACK as per Section 9.3.5. + * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG-ACK + * \param[in] cause Pointer to cause value (NULL if no cause to be sent) + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsei; + + if (!nsvc) + return -1; + + msg = ns2_msgb_alloc(); + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + if (!nsvc->nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n"); + msgb_free(msg); + return -EIO; + } + + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = SNS_PDUT_CONFIG_ACK; + + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + if (cause) + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause); + + LOGNSVC(nsvc, LOGL_INFO, "Tx SNS-CONFIG-ACK (cause=%s)\n", + cause ? gprs_ns2_cause_str(*cause) : "NULL"); + LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type); + return ns_vc_tx(nsvc, msg); +} + + +/*! Encode + transmit a SNS-SIZE as per Section 9.3.7. + * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE + * \param[in] reset_flag Whether or not to add a RESET flag + * \param[in] max_nr_nsvc Maximum number of NS-VCs + * \param[in] ip4_ep_nr Number of IPv4 endpoints (< 0 will omit the TLV) + * \param[in] ip6_ep_nr Number of IPv6 endpoints (< 0 will omit the TLV) + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc, + int ip4_ep_nr, int ip6_ep_nr) +{ + struct msgb *msg; + struct gprs_ns_hdr *nsh; + uint16_t nsei; + + if (!nsvc) + return -1; + + msg = ns2_msgb_alloc(); + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + if (!nsvc->nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n"); + msgb_free(msg); + return -EIO; + } + + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = SNS_PDUT_SIZE; + + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + msgb_tv_put(msg, NS_IE_RESET_FLAG, reset_flag ? 0x01 : 0x00); + msgb_tv16_put(msg, NS_IE_MAX_NR_NSVC, max_nr_nsvc); + if (ip4_ep_nr >= 0) + msgb_tv16_put(msg, NS_IE_IPv4_EP_NR, ip4_ep_nr); + if (ip6_ep_nr >= 0) + msgb_tv16_put(msg, NS_IE_IPv6_EP_NR, ip6_ep_nr); + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, + " (reset=%u, max_nr_nsvc=%u, num_ip4=%d, num_ip6=%d)\n", + reset_flag, max_nr_nsvc, ip4_ep_nr, ip6_ep_nr); + return ns_vc_tx(nsvc, msg); +} + +/*! Encode + Transmit a SNS-SIZE-ACK as per Section 9.3.8. + * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE-ACK + * \param[in] cause Pointer to cause value (NULL if no cause to be sent) + * \returns 0 on success; negative in case of error */ +int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause) +{ + struct msgb *msg = ns2_msgb_alloc(); + struct gprs_ns_hdr *nsh; + uint16_t nsei; + + log_set_context(LOG_CTX_GB_NSE, nsvc->nse); + log_set_context(LOG_CTX_GB_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + if (!nsvc->nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n"); + msgb_free(msg); + return -EIO; + } + + nsei = osmo_htons(nsvc->nse->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = SNS_PDUT_SIZE_ACK; + + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + if (cause) + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause); + + LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", + cause ? gprs_ns2_cause_str(*cause) : "NULL"); + return ns_vc_tx(nsvc, msg); +} diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c new file mode 100644 index 00000000..0afc06ed --- /dev/null +++ b/src/gb/gprs_ns2_sns.c @@ -0,0 +1,3106 @@ +/*! \file gprs_ns2_sns.c + * NS Sub-Network Service Protocol implementation + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2018-2021 by Harald Welte <laforge@gnumonks.org> + * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures + * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and + * associated weights. The BSS then uses this to establish a full mesh + * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports. + * + * Known limitation/expectation/bugs: + * - No concurrent dual stack. It supports either IPv4 or IPv6, but not both at the same time. + * - SNS Add/Change/Delete: Doesn't answer on the same NSVC as received SNS ADD/CHANGE/DELETE PDUs. + * - SNS Add/Change/Delete: Doesn't communicated the failed IPv4/IPv6 entries on the SNS_ACK. + */ + +#include <errno.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdint.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gprs/gprs_ns2.h> +#include <osmocom/gprs/protocol/gsm_08_16.h> + +#include "gprs_ns2_internal.h" + +#define S(x) (1 << (x)) + +enum ns2_sns_role { + GPRS_SNS_ROLE_BSS, + GPRS_SNS_ROLE_SGSN, +}; + +/* BSS-side-only states _ST_BSS_; SGSN-side only states _ST_SGSN_; others shared */ +enum gprs_sns_bss_state { + GPRS_SNS_ST_UNCONFIGURED, + GPRS_SNS_ST_BSS_SIZE, /*!< SNS-SIZE procedure ongoing */ + GPRS_SNS_ST_BSS_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */ + GPRS_SNS_ST_BSS_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */ + GPRS_SNS_ST_CONFIGURED, + GPRS_SNS_ST_SGSN_WAIT_CONFIG, /* !< SGSN role: Wait for CONFIG from BSS */ + GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, /* !< SGSN role: Wait for CONFIG-ACK from BSS */ + GPRS_SNS_ST_LOCAL_PROCEDURE, /*!< in process of a ADD/DEL/CHANGE procedure towards SGSN (BSS->SGSN) */ +}; + +static const struct value_string gprs_sns_event_names[] = { + { NS2_SNS_EV_REQ_SELECT_ENDPOINT, "REQ_SELECT_ENDPOINT" }, + { NS2_SNS_EV_RX_SIZE, "RX_SIZE" }, + { NS2_SNS_EV_RX_SIZE_ACK, "RX_SIZE_ACK" }, + { NS2_SNS_EV_RX_CONFIG, "RX_CONFIG" }, + { NS2_SNS_EV_RX_CONFIG_END, "RX_CONFIG_END" }, + { NS2_SNS_EV_RX_CONFIG_ACK, "RX_CONFIG_ACK" }, + { NS2_SNS_EV_RX_ADD, "RX_ADD" }, + { NS2_SNS_EV_RX_DELETE, "RX_DELETE" }, + { NS2_SNS_EV_RX_ACK, "RX_ACK" }, + { NS2_SNS_EV_RX_CHANGE_WEIGHT, "RX_CHANGE_WEIGHT" }, + { NS2_SNS_EV_REQ_NO_NSVC, "REQ_NO_NSVC" }, + { NS2_SNS_EV_REQ_FREE_NSVCS, "REQ_FREE_NSVCS" }, + { NS2_SNS_EV_REQ_NSVC_ALIVE, "REQ_NSVC_ALIVE"}, + { NS2_SNS_EV_REQ_ADD_BIND, "REQ_ADD_BIND"}, + { NS2_SNS_EV_REQ_DELETE_BIND, "REQ_DELETE_BIND"}, + { NS2_SNS_EV_REQ_CHANGE_WEIGHT, "REQ_CHANGE_WEIGHT"}, + { 0, NULL } +}; + +#define GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER (void *) 1 + +enum sns_procedure { + SNS_PROC_NONE, /*!< used as invalid/idle value */ + SNS_PROC_ADD, + SNS_PROC_DEL, + SNS_PROC_CHANGE_WEIGHT, +}; + +struct sns_endpoint { + struct llist_head list; + struct osmo_sockaddr saddr; +}; + +struct ns2_sns_bind { + struct llist_head list; + struct gprs_ns2_vc_bind *bind; + uint8_t change_weight_state; +}; + +struct ns2_sns_procedure { + struct llist_head list; + struct ns2_sns_bind *sbind; + uint16_t sig_weight; + uint16_t data_weight; + /* copy entry to protect against changes of gss->local */ + struct gprs_ns_ie_ip4_elem ip4; + struct gprs_ns_ie_ip6_elem ip6; + enum sns_procedure procedure; + uint8_t trans_id; + /* is the procedure in process */ + bool running; +}; + +struct ns2_sns_elems { + struct gprs_ns_ie_ip4_elem *ip4; + unsigned int num_ip4; + struct gprs_ns_ie_ip6_elem *ip6; + unsigned int num_ip6; +}; + +struct ns2_sns_state { + struct gprs_ns2_nse *nse; + + /* containing the address family AF_* */ + int family; + enum ns2_sns_role role; /* local role: BSS or SGSN */ + + /* holds the list of initial SNS endpoints */ + struct llist_head sns_endpoints; + /* list of used struct ns2_sns_bind */ + struct llist_head binds; + /* pointer to the bind which was used to initiate the SNS connection */ + struct ns2_sns_bind *initial_bind; + /* prevent recursive reselection */ + bool reselection_running; + + /* protection against recursive free() */ + bool block_no_nsvc_events; + + /* The current initial SNS endpoints. + * The initial connection will be moved into the NSE + * if configured via SNS. Otherwise it will be removed + * in configured state. */ + struct sns_endpoint *initial; + /* all SNS PDU will be sent over this nsvc */ + struct gprs_ns2_vc *sns_nsvc; + /* timer N */ + int N; + /* true if at least one nsvc is alive */ + bool alive; + + /* local configuration to send to the remote end */ + struct ns2_sns_elems local; + + /* local configuration after all local procedures applied */ + struct ns2_sns_elems local_procedure; + + /* remote configuration as received */ + struct ns2_sns_elems remote; + + /* local configuration about our capabilities in terms of connections to + * remote (SGSN) side */ + size_t num_max_nsvcs; + size_t num_max_ip4_remote; + size_t num_max_ip6_remote; + + struct llist_head procedures; + struct ns2_sns_procedure *current_procedure; + uint8_t trans_id; +}; + +static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + return gss->nse; +} + +/* The SNS has failed. Etither restart the SNS (BSS) or remove the SNS (SGSN) */ +#define sns_failed(fi, reason) \ + _sns_failed(fi, reason, __FILE__, __LINE__) +static void _sns_failed(struct osmo_fsm_inst *fi, const char *reason, const char *file, int line) +{ + struct ns2_sns_state *gss = fi->priv; + + if (reason) + LOGPFSMLSRC(fi, LOGL_ERROR, file, line, "NSE %d: SNS failed: %s\n", gss->nse->nsei, reason); + + gss->alive = false; + if (gss->role == GPRS_SNS_ROLE_SGSN) { + if (!gss->nse->persistent) + gprs_ns2_free_nse(gss->nse); + else + _osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0, file, line); + } else { + _osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL, file, line); + } +} + +/* helper function to compute the sum of all (data or signaling) weights */ +static int ip4_weight_sum(const struct ns2_sns_elems *elems, bool data_weight) +{ + unsigned int i; + int weight_sum = 0; + + for (i = 0; i < elems->num_ip4; i++) { + if (data_weight) + weight_sum += elems->ip4[i].data_weight; + else + weight_sum += elems->ip4[i].sig_weight; + } + return weight_sum; +} +#define ip4_weight_sum_data(elems) ip4_weight_sum(elems, true) +#define ip4_weight_sum_sig(elems) ip4_weight_sum(elems, false) + +/* helper function to compute the sum of all (data or signaling) weights */ +static int ip6_weight_sum(const struct ns2_sns_elems *elems, bool data_weight) +{ + unsigned int i; + int weight_sum = 0; + + for (i = 0; i < elems->num_ip6; i++) { + if (data_weight) + weight_sum += elems->ip6[i].data_weight; + else + weight_sum += elems->ip6[i].sig_weight; + } + return weight_sum; +} +#define ip6_weight_sum_data(elems) ip6_weight_sum(elems, true) +#define ip6_weight_sum_sig(elems) ip6_weight_sum(elems, false) + +static int ip46_weight_sum(const struct ns2_sns_elems *elems, bool data_weight) +{ + return ip4_weight_sum(elems, data_weight) + + ip6_weight_sum(elems, data_weight); +} +#define ip46_weight_sum_data(elems) ip46_weight_sum(elems, true) +#define ip46_weight_sum_sig(elems) ip46_weight_sum(elems, false) + +static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse, + const struct gprs_ns_ie_ip4_elem *ip4) +{ + struct osmo_sockaddr sa; + /* copy over. Both data structures use network byte order */ + sa.u.sin.sin_addr.s_addr = ip4->ip_addr; + sa.u.sin.sin_port = ip4->udp_port; + sa.u.sin.sin_family = AF_INET; + + return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa); +} + +static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + struct osmo_sockaddr sa; + /* copy over. Both data structures use network byte order */ + sa.u.sin6.sin6_addr = ip6->ip_addr; + sa.u.sin6.sin6_port = ip6->udp_port; + sa.u.sin6.sin6_family = AF_INET; + + return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa); +} + +/*! Return the initial SNS remote socket address + * \param nse NS Entity + * \return address of the initial SNS connection; NULL in case of error + */ +const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse) +{ + struct ns2_sns_state *gss; + + if (!nse->bss_sns_fi) + return NULL; + + gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv; + return &gss->initial->saddr; +} + +/*! called when a nsvc is beeing freed or the nsvc became dead */ +void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc) +{ + struct gprs_ns2_nse *nse = nsvc->nse; + struct gprs_ns2_vc *tmp; + struct osmo_fsm_inst *fi = nse->bss_sns_fi; + struct ns2_sns_state *gss; + + if (!fi) + return; + + gss = (struct ns2_sns_state *) fi->priv; + if (nsvc != gss->sns_nsvc) + return; + + gss->sns_nsvc = NULL; + if (gss->alive) { + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (ns2_vc_is_unblocked(tmp)) { + gss->sns_nsvc = tmp; + return; + } + } + } else { + /* the SNS is waiting for its first NS-VC to come up + * choose any other nsvc */ + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (nsvc != tmp) { + gss->sns_nsvc = tmp; + return; + } + } + } + + if (gss->block_no_nsvc_events) + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_NO_NSVC, NULL); +} + +static void ns2_clear_elems(struct ns2_sns_elems *elems) +{ + TALLOC_FREE(elems->ip4); + TALLOC_FREE(elems->ip6); + + elems->num_ip4 = 0; + elems->num_ip6 = 0; +} + +static void ns2_clear_procedures(struct ns2_sns_state *gss) +{ + struct ns2_sns_procedure *procedure, *tmp; + gss->current_procedure = NULL; + llist_for_each_entry_safe(procedure, tmp, &gss->procedures, list) { + llist_del(&procedure->list); + talloc_free(procedure); + } +} + +static void ns2_vc_create_ip(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct osmo_sockaddr *remote, + uint8_t sig_weight, uint8_t data_weight) +{ + struct gprs_ns2_inst *nsi = nse->nsi; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_vc_bind *bind; + + /* for every bind, create a connection if bind type == IP */ + llist_for_each_entry(bind, &nsi->binding, list) { + if (bind->ll != GPRS_NS2_LL_UDP) + continue; + /* ignore failed connection */ + nsvc = gprs_ns2_ip_connect_inactive(bind, + remote, + nse, 0); + if (!nsvc) { + LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n"); + continue; + } + + nsvc->sig_weight = sig_weight; + nsvc->data_weight = data_weight; + } +} + +static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi, + struct gprs_ns2_nse *nse, + const struct gprs_ns_ie_ip4_elem *ip4) +{ + struct osmo_sockaddr remote = { }; + /* copy over. Both data structures use network byte order */ + remote.u.sin.sin_family = AF_INET; + remote.u.sin.sin_addr.s_addr = ip4->ip_addr; + remote.u.sin.sin_port = ip4->udp_port; + + ns2_vc_create_ip(fi, nse, &remote, ip4->sig_weight, ip4->data_weight); +} + +static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi, + struct gprs_ns2_nse *nse, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + struct osmo_sockaddr remote = {}; + /* copy over. Both data structures use network byte order */ + remote.u.sin6.sin6_family = AF_INET6; + remote.u.sin6.sin6_addr = ip6->ip_addr; + remote.u.sin6.sin6_port = ip6->udp_port; + + ns2_vc_create_ip(fi, nse, &remote, ip6->sig_weight, ip6->data_weight); +} + +static struct gprs_ns2_vc *nsvc_for_bind_and_remote(struct gprs_ns2_nse *nse, + struct gprs_ns2_vc_bind *bind, + const struct osmo_sockaddr *remote) +{ + struct gprs_ns2_vc *nsvc; + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (nsvc->bind != bind) + continue; + + if (!osmo_sockaddr_cmp(remote, gprs_ns2_ip_vc_remote(nsvc))) + return nsvc; + } + return NULL; +} + +static int create_missing_nsvcs(struct osmo_fsm_inst *fi) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_vc *nsvc; + struct ns2_sns_bind *sbind; + struct osmo_sockaddr remote = { }; + unsigned int i; + + /* iterate over all remote IPv4 endpoints */ + for (i = 0; i < gss->remote.num_ip4; i++) { + const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i]; + + remote.u.sin.sin_family = AF_INET; + remote.u.sin.sin_addr.s_addr = ip4->ip_addr; + remote.u.sin.sin_port = ip4->udp_port; + + /* iterate over all local binds within this SNS */ + llist_for_each_entry(sbind, &gss->binds, list) { + struct gprs_ns2_vc_bind *bind = sbind->bind; + + /* we only care about UDP binds */ + if (bind->ll != GPRS_NS2_LL_UDP) + continue; + + nsvc = nsvc_for_bind_and_remote(nse, bind, &remote); + if (!nsvc) { + nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0); + if (!nsvc) { + /* TODO: add to a list to send back a NS-STATUS */ + continue; + } + } + + /* update data / signalling weight */ + nsvc->data_weight = ip4->data_weight; + nsvc->sig_weight = ip4->sig_weight; + nsvc->sns_only = false; + } + } + + /* iterate over all remote IPv4 endpoints */ + for (i = 0; i < gss->remote.num_ip6; i++) { + const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i]; + + remote.u.sin6.sin6_family = AF_INET6; + remote.u.sin6.sin6_addr = ip6->ip_addr; + remote.u.sin6.sin6_port = ip6->udp_port; + + /* iterate over all local binds within this SNS */ + llist_for_each_entry(sbind, &gss->binds, list) { + struct gprs_ns2_vc_bind *bind = sbind->bind; + + if (bind->ll != GPRS_NS2_LL_UDP) + continue; + + /* we only care about UDP binds */ + nsvc = nsvc_for_bind_and_remote(nse, bind, &remote); + if (!nsvc) { + nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0); + if (!nsvc) { + /* TODO: add to a list to send back a NS-STATUS */ + continue; + } + } + + /* update data / signalling weight */ + nsvc->data_weight = ip6->data_weight; + nsvc->sig_weight = ip6->sig_weight; + nsvc->sns_only = false; + } + } + + + return 0; +} + +/* Add a given remote IPv4 element to gprs_sns_state */ +static int add_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, + const struct gprs_ns_ie_ip4_elem *ip4) +{ + /* check for duplicates */ + for (unsigned int i = 0; i < elems->num_ip4; i++) { + if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4))) + continue; + return -1; + } + + elems->ip4 = talloc_realloc(gss, elems->ip4, struct gprs_ns_ie_ip4_elem, + elems->num_ip4+1); + elems->ip4[elems->num_ip4] = *ip4; + elems->num_ip4 += 1; + return 0; +} + +/* Remove a given remote IPv4 element from gprs_sns_state */ +static int remove_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, + const struct gprs_ns_ie_ip4_elem *ip4) +{ + unsigned int i; + + for (i = 0; i < elems->num_ip4; i++) { + if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4))) + continue; + /* all array elements < i remain as they are; all > i are shifted left by one */ + memmove(&elems->ip4[i], &elems->ip4[i+1], elems->num_ip4-i-1); + elems->num_ip4 -= 1; + return 0; + } + return -1; +} + +/* update the weights for specified remote IPv4 */ +static int update_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, + const struct gprs_ns_ie_ip4_elem *ip4) +{ + unsigned int i; + + for (i = 0; i < elems->num_ip4; i++) { + if (elems->ip4[i].ip_addr != ip4->ip_addr || + elems->ip4[i].udp_port != ip4->udp_port) + continue; + + elems->ip4[i].sig_weight = ip4->sig_weight; + elems->ip4[i].data_weight = ip4->data_weight; + return 0; + } + return -1; +} + +/* Add a given remote IPv6 element to gprs_sns_state */ +static int add_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + /* check for duplicates */ + for (unsigned int i = 0; i < elems->num_ip6; i++) { + if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) || + elems->ip6[i].udp_port != ip6->udp_port) + continue; + return -1; + } + + elems->ip6 = talloc_realloc(gss, elems->ip6, struct gprs_ns_ie_ip6_elem, + elems->num_ip6+1); + elems->ip6[elems->num_ip6] = *ip6; + elems->num_ip6 += 1; + return 0; +} + +/* Remove a given remote IPv6 element from gprs_sns_state */ +static int remove_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + unsigned int i; + + for (i = 0; i < elems->num_ip6; i++) { + if (memcmp(&elems->ip6[i], ip6, sizeof(*ip6))) + continue; + /* all array elements < i remain as they are; all > i are shifted left by one */ + memmove(&elems->ip6[i], &elems->ip6[i+1], elems->num_ip6-i-1); + elems->num_ip6 -= 1; + return 0; + } + return -1; +} + +/* update the weights for specified remote IPv6 */ +static int update_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + unsigned int i; + + for (i = 0; i < elems->num_ip6; i++) { + if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) || + elems->ip6[i].udp_port != ip6->udp_port) + continue; + elems->ip6[i].sig_weight = ip6->sig_weight; + elems->ip6[i].data_weight = ip6->data_weight; + return 0; + } + return -1; +} + +static int remove_bind_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, struct ns2_sns_bind *sbind) +{ + struct gprs_ns_ie_ip4_elem ip4; + struct gprs_ns_ie_ip6_elem ip6; + const struct osmo_sockaddr *saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind); + + switch (saddr->u.sa.sa_family) { + case AF_INET: + ip4.ip_addr = saddr->u.sin.sin_addr.s_addr; + ip4.udp_port = saddr->u.sin.sin_port; + ip4.sig_weight = sbind->bind->sns_sig_weight; + ip4.data_weight = sbind->bind->sns_data_weight; + return remove_ip4_elem(gss, elems, &ip4); + case AF_INET6: + memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr)); + ip6.udp_port = saddr->u.sin.sin_port; + ip6.sig_weight = sbind->bind->sns_sig_weight; + ip6.data_weight = sbind->bind->sns_data_weight; + return remove_ip6_elem(gss, elems, &ip6); + default: + return -1; + } + + return -1; +} + +static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_vc *nsvc; + struct osmo_sockaddr sa = {}; + const struct osmo_sockaddr *remote; + uint8_t new_signal; + uint8_t new_data; + + /* TODO: Upon receiving an SNS-CHANGEWEIGHT PDU, if the resulting sum of the + * signalling weights of all the peer IP endpoints configured for this NSE is + * equal to zero or if the resulting sum of the data weights of all the peer IP + * endpoints configured for this NSE is equal to zero, the BSS/SGSN shall send an + * SNS-ACK PDU with a cause code of "Invalid weights". */ + + if (ip4) { + if (update_ip4_elem(gss, &gss->remote, ip4)) + return -NS_CAUSE_UNKN_IP_EP; + + /* copy over. Both data structures use network byte order */ + sa.u.sin.sin_addr.s_addr = ip4->ip_addr; + sa.u.sin.sin_port = ip4->udp_port; + sa.u.sin.sin_family = AF_INET; + new_signal = ip4->sig_weight; + new_data = ip4->data_weight; + } else if (ip6) { + if (update_ip6_elem(gss, &gss->remote, ip6)) + return -NS_CAUSE_UNKN_IP_EP; + + /* copy over. Both data structures use network byte order */ + sa.u.sin6.sin6_addr = ip6->ip_addr; + sa.u.sin6.sin6_port = ip6->udp_port; + sa.u.sin6.sin6_family = AF_INET6; + new_signal = ip6->sig_weight; + new_data = ip6->data_weight; + } else { + OSMO_ASSERT(false); + } + + llist_for_each_entry(nsvc, &nse->nsvc, list) { + remote = gprs_ns2_ip_vc_remote(nsvc); + /* all nsvc in NSE should be IP/UDP nsvc */ + OSMO_ASSERT(remote); + + if (osmo_sockaddr_cmp(&sa, remote)) + continue; + + LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight %u->%u, sig_weight %u->%u\n", + gprs_ns2_ll_str(nsvc), nsvc->data_weight, new_data, + nsvc->sig_weight, new_signal); + + nsvc->data_weight = new_data; + nsvc->sig_weight = new_signal; + } + + return 0; +} + +static int do_sns_delete(struct osmo_fsm_inst *fi, + const struct gprs_ns_ie_ip4_elem *ip4, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_vc *nsvc, *tmp; + const struct osmo_sockaddr *remote; + struct osmo_sockaddr sa = {}; + + if (ip4) { + if (remove_ip4_elem(gss, &gss->remote, ip4) < 0) + return -NS_CAUSE_UNKN_IP_EP; + /* copy over. Both data structures use network byte order */ + sa.u.sin.sin_addr.s_addr = ip4->ip_addr; + sa.u.sin.sin_port = ip4->udp_port; + sa.u.sin.sin_family = AF_INET; + } else if (ip6) { + if (remove_ip6_elem(gss, &gss->remote, ip6)) + return -NS_CAUSE_UNKN_IP_EP; + + /* copy over. Both data structures use network byte order */ + sa.u.sin6.sin6_addr = ip6->ip_addr; + sa.u.sin6.sin6_port = ip6->udp_port; + sa.u.sin6.sin6_family = AF_INET6; + } else { + OSMO_ASSERT(false); + } + + llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) { + remote = gprs_ns2_ip_vc_remote(nsvc); + /* all nsvc in NSE should be IP/UDP nsvc */ + OSMO_ASSERT(remote); + if (osmo_sockaddr_cmp(&sa, remote)) + continue; + + LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns2_ll_str(nsvc)); + gprs_ns2_free_nsvc(nsvc); + } + + return 0; +} + +static int do_sns_add(struct osmo_fsm_inst *fi, + const struct gprs_ns_ie_ip4_elem *ip4, + const struct gprs_ns_ie_ip6_elem *ip6) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_vc *nsvc; + int rc = 0; + + /* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints + * exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send + * an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */ + switch (gss->family) { + case AF_INET: + if (gss->remote.num_ip4 >= gss->num_max_ip4_remote) + return -NS_CAUSE_INVAL_NR_NS_VC; + /* TODO: log message duplicate */ + rc = add_ip4_elem(gss, &gss->remote, ip4); + break; + case AF_INET6: + if (gss->remote.num_ip6 >= gss->num_max_ip6_remote) + return -NS_CAUSE_INVAL_NR_NS_VC; + /* TODO: log message duplicate */ + rc = add_ip6_elem(gss, &gss->remote, ip6); + break; + default: + /* the gss->ip is initialized with the bss */ + OSMO_ASSERT(false); + } + + if (rc) + return -NS_CAUSE_PROTO_ERR_UNSPEC; + + /* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the + * NSE shall send an SNS-ACK PDU with the cause code "Protocol error - + * unspecified" */ + switch (gss->family) { + case AF_INET: + nsvc = nsvc_by_ip4_elem(nse, ip4); + if (nsvc) { + /* the nsvc should be already in sync with the ip4 / ip6 elements */ + return -NS_CAUSE_PROTO_ERR_UNSPEC; + } + + /* TODO: failure case */ + ns2_nsvc_create_ip4(fi, nse, ip4); + break; + case AF_INET6: + nsvc = nsvc_by_ip6_elem(nse, ip6); + if (nsvc) { + /* the nsvc should be already in sync with the ip4 / ip6 elements */ + return -NS_CAUSE_PROTO_ERR_UNSPEC; + } + + /* TODO: failure case */ + ns2_nsvc_create_ip6(fi, nse, ip6); + break; + } + + gprs_ns2_start_alive_all_nsvcs(nse); + + return 0; +} + + +static void ns2_sns_st_bss_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + /* empty state - SNS Select will start by ns2_sns_st_all_action() */ +} + +static void ns2_sns_st_bss_size(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_inst *nsi = nse->nsi; + struct tlv_parsed *tp = NULL; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + + switch (event) { + case NS2_SNS_EV_RX_SIZE_ACK: + tp = data; + if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) { + LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n", + gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE))); + /* TODO: What to do? */ + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, + nsi->timeout[NS_TOUT_TSNS_PROV], 2); + } + break; + default: + OSMO_ASSERT(0); + } +} + +static int ns2_sns_count_num_local_ep(struct osmo_fsm_inst *fi, int ip_proto) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct ns2_sns_bind *sbind; + int count = 0; + + llist_for_each_entry(sbind, &gss->binds, list) { + const struct osmo_sockaddr *sa = gprs_ns2_ip_bind_sockaddr(sbind->bind); + if (!sa) + continue; + + switch (ip_proto) { + case AF_INET: + if (sa->u.sas.ss_family == AF_INET) + count++; + break; + case AF_INET6: + if (sa->u.sas.ss_family == AF_INET6) + count++; + break; + } + } + return count; +} + +static int ns2_sns_copy_local_endpoints(struct ns2_sns_state *gss) +{ + switch (gss->family) { + case AF_INET: + gss->local_procedure.ip4 = talloc_realloc(gss, gss->local_procedure.ip4, struct gprs_ns_ie_ip4_elem, + gss->local.num_ip4); + if (!gss->local_procedure.ip4) + return -ENOMEM; + + gss->local_procedure.num_ip4 = gss->local.num_ip4; + memcpy(gss->local_procedure.ip4, gss->local.ip4, + sizeof(struct gprs_ns_ie_ip4_elem) * gss->local.num_ip4); + break; + case AF_INET6: + gss->local_procedure.ip6 = talloc_realloc(gss, gss->local_procedure.ip6, struct gprs_ns_ie_ip6_elem, + gss->local.num_ip6); + if (!gss->local_procedure.ip6) + return -ENOMEM; + + gss->local_procedure.num_ip6 = gss->local.num_ip6; + memcpy(gss->local_procedure.ip6, gss->local.ip6, + sizeof(struct gprs_ns_ie_ip6_elem) * gss->local.num_ip6); + break; + default: + OSMO_ASSERT(0); + } + + return 0; +} + +static void ns2_sns_compute_local_ep_from_binds(struct osmo_fsm_inst *fi) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns_ie_ip4_elem *ip4_elems; + struct gprs_ns_ie_ip6_elem *ip6_elems; + struct gprs_ns2_vc_bind *bind; + struct ns2_sns_bind *sbind; + const struct osmo_sockaddr *remote; + const struct osmo_sockaddr *sa; + struct osmo_sockaddr local; + int count; + + ns2_clear_elems(&gss->local); + + /* no initial available */ + if (gss->role == GPRS_SNS_ROLE_BSS) { + if (!gss->initial) + return; + remote = &gss->initial->saddr; + } else + remote = gprs_ns2_ip_vc_remote(gss->sns_nsvc); + + /* count how many bindings are available (only UDP binds) */ + count = llist_count(&gss->binds); + if (count == 0) { + LOGPFSML(fi, LOGL_ERROR, "No local binds for this NSE -> cannot determine IP endpoints\n"); + return; + } + + switch (gss->family) { + case AF_INET: + ip4_elems = talloc_realloc(fi, gss->local.ip4, struct gprs_ns_ie_ip4_elem, count); + if (!ip4_elems) + return; + + gss->local.ip4 = ip4_elems; + llist_for_each_entry(sbind, &gss->binds, list) { + bind = sbind->bind; + sa = gprs_ns2_ip_bind_sockaddr(bind); + if (!sa) + continue; + + if (sa->u.sas.ss_family != AF_INET) + continue; + + /* check if this is an specific bind */ + if (sa->u.sin.sin_addr.s_addr == 0) { + if (osmo_sockaddr_local_ip(&local, remote)) + continue; + + ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr; + } else { + ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr; + } + + ip4_elems->udp_port = sa->u.sin.sin_port; + ip4_elems->sig_weight = bind->sns_sig_weight; + ip4_elems->data_weight = bind->sns_data_weight; + ip4_elems++; + } + + gss->local.num_ip4 = count; + gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip4_remote * gss->local.num_ip4, 8); + break; + case AF_INET6: + /* IPv6 */ + ip6_elems = talloc_realloc(fi, gss->local.ip6, struct gprs_ns_ie_ip6_elem, count); + if (!ip6_elems) + return; + + gss->local.ip6 = ip6_elems; + + llist_for_each_entry(sbind, &gss->binds, list) { + bind = sbind->bind; + sa = gprs_ns2_ip_bind_sockaddr(bind); + if (!sa) + continue; + + if (sa->u.sas.ss_family != AF_INET6) + continue; + + /* check if this is an specific bind */ + if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) { + if (osmo_sockaddr_local_ip(&local, remote)) + continue; + + ip6_elems->ip_addr = local.u.sin6.sin6_addr; + } else { + ip6_elems->ip_addr = sa->u.sin6.sin6_addr; + } + + ip6_elems->udp_port = sa->u.sin.sin_port; + ip6_elems->sig_weight = bind->sns_sig_weight; + ip6_elems->data_weight = bind->sns_data_weight; + + ip6_elems++; + } + gss->local.num_ip6 = count; + gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->local.num_ip6, 8); + break; + } + + ns2_sns_copy_local_endpoints(gss); +} + +static void ns2_sns_choose_next_bind(struct ns2_sns_state *gss) +{ + /* take the first bind or take the next bind */ + if (!gss->initial_bind || gss->initial_bind->list.next == &gss->binds) + gss->initial_bind = llist_first_entry_or_null(&gss->binds, struct ns2_sns_bind, list); + else + gss->initial_bind = llist_entry(gss->initial_bind->list.next, struct ns2_sns_bind, list); +} + +/* setup all dynamic SNS settings, create a new nsvc and send the SIZE */ +static void ns2_sns_st_bss_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + + /* on a generic failure, the timer callback will recover */ + if (old_state != GPRS_SNS_ST_UNCONFIGURED) + ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_FAILURE); + if (old_state != GPRS_SNS_ST_BSS_SIZE) + gss->N = 0; + + ns2_clear_procedures(gss); + gss->alive = false; + + ns2_sns_compute_local_ep_from_binds(fi); + ns2_sns_choose_next_bind(gss); + + /* setup the NSVC */ + if (!gss->sns_nsvc) { + struct gprs_ns2_vc_bind *bind = gss->initial_bind->bind; + struct osmo_sockaddr *remote = &gss->initial->saddr; + gss->sns_nsvc = ns2_ip_bind_connect(bind, gss->nse, remote); + if (!gss->sns_nsvc) + return; + /* A pre-configured endpoint shall not be used for NSE data or signalling traffic + * (with the exception of Size and Configuration procedures) unless it is configured + * by the SGSN using the auto-configuration procedures */ + gss->sns_nsvc->sns_only = true; + } + + if (gss->num_max_ip4_remote > 0) + ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->local.num_ip4, -1); + else + ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->local.num_ip6); +} + +static void ns2_sns_st_bss_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct tlv_parsed *tp = NULL; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + + switch (event) { + case NS2_SNS_EV_RX_CONFIG_ACK: + tp = (struct tlv_parsed *) data; + if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) { + LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n", + gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE))); + /* TODO: What to do? */ + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3); + } + break; + default: + OSMO_ASSERT(0); + } +} + +static void ns2_sns_st_bss_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + + if (old_state != GPRS_SNS_ST_BSS_CONFIG_BSS) + gss->N = 0; + + /* Transmit SNS-CONFIG */ + switch (gss->family) { + case AF_INET: + ns2_tx_sns_config(gss->sns_nsvc, true, + gss->local.ip4, gss->local.num_ip4, + NULL, 0); + break; + case AF_INET6: + ns2_tx_sns_config(gss->sns_nsvc, true, + NULL, 0, + gss->local.ip6, gss->local.num_ip6); + break; + } +} + +/* calculate the timeout of the configured state. the configured + * state will fail if not at least one NS-VC is alive within X second. + */ +static inline int ns_sns_configured_timeout(struct osmo_fsm_inst *fi) +{ + int secs; + struct gprs_ns2_inst *nsi = nse_inst_from_fi(fi)->nsi; + secs = nsi->timeout[NS_TOUT_TNS_ALIVE] * nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]; + secs += nsi->timeout[NS_TOUT_TNS_TEST]; + + return secs; +} + +/* append the remote endpoints from the parsed TLV array to the ns2_sns_state */ +static int ns_sns_append_remote_eps(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + + if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { + const struct gprs_ns_ie_ip4_elem *v4_list; + unsigned int num_v4; + v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); + num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); + + if (num_v4 && gss->remote.ip6) + return -NS_CAUSE_INVAL_NR_IPv4_EP; + + /* realloc to the new size */ + gss->remote.ip4 = talloc_realloc(gss, gss->remote.ip4, + struct gprs_ns_ie_ip4_elem, + gss->remote.num_ip4 + num_v4); + /* append the new entries to the end of the list */ + memcpy(&gss->remote.ip4[gss->remote.num_ip4], v4_list, num_v4*sizeof(*v4_list)); + gss->remote.num_ip4 += num_v4; + + LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n", + gss->remote.num_ip4); + } + + if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { + const struct gprs_ns_ie_ip6_elem *v6_list; + unsigned int num_v6; + v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); + num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); + + if (num_v6 && gss->remote.ip4) + return -NS_CAUSE_INVAL_NR_IPv6_EP; + + /* realloc to the new size */ + gss->remote.ip6 = talloc_realloc(gss, gss->remote.ip6, + struct gprs_ns_ie_ip6_elem, + gss->remote.num_ip6 + num_v6); + /* append the new entries to the end of the list */ + memcpy(&gss->remote.ip6[gss->remote.num_ip6], v6_list, num_v6*sizeof(*v6_list)); + gss->remote.num_ip6 += num_v6; + + LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %d entries\n", + gss->remote.num_ip6); + } + + return 0; +} + +static void ns2_sns_st_bss_config_sgsn_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + + if (old_state != GPRS_SNS_ST_BSS_CONFIG_SGSN) + gss->N = 0; +} + +static void ns2_sns_st_bss_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + uint8_t cause; + int rc; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); + + switch (event) { + case NS2_SNS_EV_RX_CONFIG_END: + case NS2_SNS_EV_RX_CONFIG: + rc = ns_sns_append_remote_eps(fi, data); + if (rc < 0) { + cause = -rc; + ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); + return; + } + if (event == NS2_SNS_EV_RX_CONFIG_END) { + /* check if sum of data / sig weights == 0 */ + if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) { + cause = NS_CAUSE_INVAL_WEIGH; + ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); + return; + } + create_missing_nsvcs(fi); + ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); + /* start the test procedure on ALL NSVCs! */ + gprs_ns2_start_alive_all_nsvcs(nse); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0); + } else { + /* just send CONFIG-ACK */ + ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); + osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0); + } + break; + default: + OSMO_ASSERT(0); + } +} + +/* called when receiving NS2_SNS_EV_RX_ADD in state configure */ +static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi, + struct ns2_sns_state *gss, + struct tlv_parsed *tp) +{ + const struct gprs_ns_ie_ip4_elem *v4_list = NULL; + const struct gprs_ns_ie_ip6_elem *v6_list = NULL; + int num_v4 = 0, num_v6 = 0; + uint8_t trans_id, cause = 0xff; + unsigned int i; + int rc = 0; + + /* TODO: refactor EV_ADD/CHANGE/REMOVE by + * check uniqueness within the lists (no doublicate entries) + * check not-known-by-us and sent back a list of unknown/known values + * (abnormal behaviour according to 48.016) + */ + + trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID); + if (gss->family == AF_INET) { + if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { + cause = NS_CAUSE_INVAL_NR_IPv4_EP; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + + v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); + num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); + for (i = 0; i < num_v4; i++) { + unsigned int j; + rc = do_sns_add(fi, &v4_list[i], NULL); + if (rc < 0) { + /* rollback/undo to restore previous state */ + for (j = 0; j < i; j++) + do_sns_delete(fi, &v4_list[j], NULL); + cause = -rc; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + break; + } + } + } else { /* IPv6 */ + if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { + cause = NS_CAUSE_INVAL_NR_IPv6_EP; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + + v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); + num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); + for (i = 0; i < num_v6; i++) { + unsigned int j; + rc = do_sns_add(fi, NULL, &v6_list[i]); + if (rc < 0) { + /* rollback/undo to restore previous state */ + for (j = 0; j < i; j++) + do_sns_delete(fi, NULL, &v6_list[j]); + cause = -rc; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + break; + } + } + } + + /* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */ + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6); +} + +static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi, + struct ns2_sns_state *gss, + struct tlv_parsed *tp) +{ + const struct gprs_ns_ie_ip4_elem *v4_list = NULL; + const struct gprs_ns_ie_ip6_elem *v6_list = NULL; + int num_v4 = 0, num_v6 = 0; + uint8_t trans_id, cause = 0xff; + unsigned int i; + int rc = 0; + + /* TODO: split up delete into v4 + v6 + * TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa + * TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time + */ + trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID); + if (gss->family == AF_INET) { + if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { + v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); + num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); + for ( i = 0; i < num_v4; i++) { + rc = do_sns_delete(fi, &v4_list[i], NULL); + if (rc < 0) { + cause = -rc; + /* continue to delete others */ + } + } + if (cause != 0xff) { + /* TODO: create list of not-deleted and return it */ + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + + } else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) { + /* delete all NS-VCs for given IPv4 address */ + const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR); + struct gprs_ns_ie_ip4_elem *ip4_remote; + uint32_t ip_addr = *(uint32_t *)(ie+1); + if (ie[0] != 0x01) { /* Address Type != IPv4 */ + cause = NS_CAUSE_UNKN_IP_ADDR; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + /* make a copy as do_sns_delete() will change the array underneath us */ + ip4_remote = talloc_memdup(fi, gss->remote.ip4, + gss->remote.num_ip4 * sizeof(*v4_list)); + for (i = 0; i < gss->remote.num_ip4; i++) { + if (ip4_remote[i].ip_addr == ip_addr) { + rc = do_sns_delete(fi, &ip4_remote[i], NULL); + if (rc < 0) { + cause = -rc; + /* continue to delete others */ + } + } + } + talloc_free(ip4_remote); + if (cause != 0xff) { + /* TODO: create list of not-deleted and return it */ + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } else { + cause = NS_CAUSE_INVAL_NR_IPv4_EP; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } else { /* IPv6 */ + if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { + v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); + num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); + for (i = 0; i < num_v6; i++) { + rc = do_sns_delete(fi, NULL, &v6_list[i]); + if (rc < 0) { + cause = -rc; + /* continue to delete others */ + } + } + if (cause != 0xff) { + /* TODO: create list of not-deleted and return it */ + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) { + /* delete all NS-VCs for given IPv4 address */ + const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR); + struct gprs_ns_ie_ip6_elem *ip6_remote; + struct in6_addr ip6_addr; + unsigned int i; + if (ie[0] != 0x02) { /* Address Type != IPv6 */ + cause = NS_CAUSE_UNKN_IP_ADDR; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr)); + /* make a copy as do_sns_delete() will change the array underneath us */ + ip6_remote = talloc_memdup(fi, gss->remote.ip6, + gss->remote.num_ip6 * sizeof(*v4_list)); + for (i = 0; i < gss->remote.num_ip6; i++) { + if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) { + rc = do_sns_delete(fi, NULL, &ip6_remote[i]); + if (rc < 0) { + cause = -rc; + /* continue to delete others */ + } + } + } + + talloc_free(ip6_remote); + if (cause != 0xff) { + /* TODO: create list of not-deleted and return it */ + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } else { + cause = NS_CAUSE_INVAL_NR_IPv6_EP; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6); +} + +static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi, + struct ns2_sns_state *gss, + struct tlv_parsed *tp) +{ + const struct gprs_ns_ie_ip4_elem *v4_list = NULL; + const struct gprs_ns_ie_ip6_elem *v6_list = NULL; + int num_v4 = 0, num_v6 = 0; + uint8_t trans_id, cause = 0xff; + int rc = 0; + unsigned int i; + + trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID); + if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { + v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); + num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); + for (i = 0; i < num_v4; i++) { + rc = do_sns_change_weight(fi, &v4_list[i], NULL); + if (rc < 0) { + cause = -rc; + /* continue to others */ + } + } + if (cause != 0xff) { + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { + v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); + num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); + for (i = 0; i < num_v6; i++) { + rc = do_sns_change_weight(fi, NULL, &v6_list[i]); + if (rc < 0) { + cause = -rc; + /* continue to others */ + } + } + if (cause != 0xff) { + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + } else { + cause = NS_CAUSE_INVAL_NR_IPv4_EP; + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); + return; + } + ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6); +} + +static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct tlv_parsed *tp = data; + + switch (event) { + case NS2_SNS_EV_RX_ADD: + ns2_sns_st_configured_add(fi, gss, tp); + break; + case NS2_SNS_EV_RX_DELETE: + ns2_sns_st_configured_delete(fi, gss, tp); + break; + case NS2_SNS_EV_RX_CHANGE_WEIGHT: + ns2_sns_st_configured_change(fi, gss, tp); + break; + case NS2_SNS_EV_REQ_NSVC_ALIVE: + osmo_timer_del(&fi->timer); + break; + } +} + +static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc *nsvc; + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + /* NS-VC status updates are only parsed in ST_CONFIGURED. + * Do an initial check if there are any nsvc alive atm */ + llist_for_each_entry(nsvc, &nse->nsvc, list) { + if (ns2_vc_is_unblocked(nsvc)) { + gss->alive = true; + osmo_timer_del(&fi->timer); + break; + } + } + + /* remove the initial NSVC if the NSVC isn't part of the configuration */ + if (gss->sns_nsvc->sns_only) + gprs_ns2_free_nsvc(gss->sns_nsvc); + + if (old_state != GPRS_SNS_ST_LOCAL_PROCEDURE) + ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED); + + if (!llist_empty(&gss->procedures)) { + osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE, + gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5); + } +} + +static void ns2_sns_st_local_procedure_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + + /* check if resend or not */ + if (!gss->current_procedure) { + /* take next procedure */ + gss->current_procedure = llist_first_entry_or_null(&gss->procedures, + struct ns2_sns_procedure, list); + if (!gss->current_procedure) { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0); + return; + } + gss->N = 0; + gss->current_procedure->running = true; + gss->current_procedure->trans_id = ++gss->trans_id; + if (gss->trans_id == 0) + gss->trans_id = gss->current_procedure->trans_id = 1; + + } + + /* also takes care of retransmitting */ + switch (gss->current_procedure->procedure) { + case SNS_PROC_ADD: + if (gss->family == AF_INET) + ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0); + else + ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1); + break; + case SNS_PROC_CHANGE_WEIGHT: + if (gss->family == AF_INET) + ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0); + else + ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1); + break; + case SNS_PROC_DEL: + if (gss->family == AF_INET) + ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0); + else + ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1); + break; + default: + break; + } +} + +static void create_nsvc_for_new_sbind(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind) +{ + struct gprs_ns2_nse *nse = gss->nse; + struct gprs_ns2_vc_bind *bind = sbind->bind; + struct gprs_ns2_vc *nsvc; + struct osmo_sockaddr remote = { }; + unsigned int i; + + /* iterate over all remote IPv4 endpoints */ + for (i = 0; i < gss->remote.num_ip4; i++) { + const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i]; + + remote.u.sin.sin_family = AF_INET; + remote.u.sin.sin_addr.s_addr = ip4->ip_addr; + remote.u.sin.sin_port = ip4->udp_port; + /* we only care about UDP binds */ + if (bind->ll != GPRS_NS2_LL_UDP) + continue; + + nsvc = nsvc_for_bind_and_remote(nse, bind, &remote); + if (!nsvc) { + nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0); + if (!nsvc) { + /* TODO: add to a list to send back a NS-STATUS */ + continue; + } + } + + /* update data / signalling weight */ + nsvc->data_weight = ip4->data_weight; + nsvc->sig_weight = ip4->sig_weight; + nsvc->sns_only = false; + } + + /* iterate over all remote IPv4 endpoints */ + for (i = 0; i < gss->remote.num_ip6; i++) { + const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i]; + + remote.u.sin6.sin6_family = AF_INET6; + remote.u.sin6.sin6_addr = ip6->ip_addr; + remote.u.sin6.sin6_port = ip6->udp_port; + + /* we only care about UDP binds */ + nsvc = nsvc_for_bind_and_remote(nse, bind, &remote); + if (!nsvc) { + nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0); + if (!nsvc) { + /* TODO: add to a list to send back a NS-STATUS */ + continue; + } + } + + /* update data / signalling weight */ + nsvc->data_weight = ip6->data_weight; + nsvc->sig_weight = ip6->sig_weight; + nsvc->sns_only = false; + } +} + +static void ns2_sns_st_local_procedure(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns_ie_ip4_elem *ip4, *proc4; + struct gprs_ns_ie_ip6_elem *ip6, *proc6; + struct tlv_parsed *tp = data; + uint8_t trans_id; + uint8_t cause; + + switch (event) { + case NS2_SNS_EV_RX_ADD: + ns2_sns_st_configured_add(fi, gss, tp); + break; + case NS2_SNS_EV_RX_DELETE: + ns2_sns_st_configured_delete(fi, gss, tp); + break; + case NS2_SNS_EV_RX_CHANGE_WEIGHT: + ns2_sns_st_configured_change(fi, gss, tp); + break; + case NS2_SNS_EV_RX_ACK: + /* presence of trans_id is already checked here */ + trans_id = tlvp_val8(tp, NS_IE_TRANS_ID, 0); + if (trans_id != gss->current_procedure->trans_id) { + LOGPFSML(fi, LOGL_INFO, "NSEI=%u Rx SNS ACK with invalid transaction id %d. Valid %d\n", + nse->nsei, trans_id, gss->current_procedure->trans_id); + break; + } + + if (TLVP_PRESENT(tp, NS_IE_CAUSE)) { + /* what happend on error cause? return to size? */ + cause = tlvp_val8(tp, NS_IE_CAUSE, 0); + LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx SNS ACK trans %d with cause code %d.\n", + nse->nsei, trans_id, cause); + sns_failed(fi, NULL); + break; + } + + switch (gss->current_procedure->procedure) { + case SNS_PROC_ADD: + switch (gss->family) { + case AF_INET: + add_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4); + break; + case AF_INET6: + add_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6); + break; + } + /* the sbind can be NULL if the bind has been released by del_bind */ + if (gss->current_procedure->sbind) { + create_nsvc_for_new_sbind(gss, gss->current_procedure->sbind); + gprs_ns2_start_alive_all_nsvcs(nse); + } + break; + case SNS_PROC_CHANGE_WEIGHT: + switch (gss->family) { + case AF_INET: + proc4 = &gss->current_procedure->ip4; + for (unsigned int i=0; i<gss->local.num_ip4; i++) { + ip4 = &gss->local.ip4[i]; + if (ip4->ip_addr != proc4->ip_addr || + ip4->udp_port != proc4->udp_port) + continue; + ip4->sig_weight = proc4->sig_weight; + ip4->data_weight = proc4->data_weight; + break; + } + break; + case AF_INET6: + proc6 = &gss->current_procedure->ip6; + for (unsigned int i=0; i<gss->local.num_ip6; i++) { + ip6 = &gss->local.ip6[i]; + if (memcmp(&ip6->ip_addr, &proc6->ip_addr, sizeof(proc6->ip_addr)) || + ip6->udp_port != proc6->udp_port) { + continue; + } + ip6->sig_weight = proc6->sig_weight; + ip6->data_weight = proc6->data_weight; + break; + } + break; + default: + OSMO_ASSERT(0); + } + break; + case SNS_PROC_DEL: + switch (gss->family) { + case AF_INET: + remove_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4); + break; + case AF_INET6: + remove_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6); + break; + } + break; + default: + break; + } + + llist_del(&gss->current_procedure->list); + talloc_free(gss->current_procedure); + gss->current_procedure = NULL; + + if (llist_empty(&gss->procedures)) + osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_CONFIGURED, + 0, 0); + else + osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE, + gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5); + break; + } +} + +static const struct osmo_fsm_state ns2_sns_bss_states[] = { + [GPRS_SNS_ST_UNCONFIGURED] = { + .in_event_mask = 0, /* handled by all_state_action */ + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_BSS_SIZE), + .name = "UNCONFIGURED", + .action = ns2_sns_st_bss_unconfigured, + }, + [GPRS_SNS_ST_BSS_SIZE] = { + .in_event_mask = S(NS2_SNS_EV_RX_SIZE_ACK), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_BSS_SIZE) | + S(GPRS_SNS_ST_BSS_CONFIG_BSS), + .name = "BSS_SIZE", + .action = ns2_sns_st_bss_size, + .onenter = ns2_sns_st_bss_size_onenter, + }, + [GPRS_SNS_ST_BSS_CONFIG_BSS] = { + .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_BSS_CONFIG_BSS) | + S(GPRS_SNS_ST_BSS_CONFIG_SGSN) | + S(GPRS_SNS_ST_BSS_SIZE), + .name = "BSS_CONFIG_BSS", + .action = ns2_sns_st_bss_config_bss, + .onenter = ns2_sns_st_bss_config_bss_onenter, + }, + [GPRS_SNS_ST_BSS_CONFIG_SGSN] = { + .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) | + S(NS2_SNS_EV_RX_CONFIG_END), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_BSS_CONFIG_SGSN) | + S(GPRS_SNS_ST_CONFIGURED) | + S(GPRS_SNS_ST_BSS_SIZE), + .name = "BSS_CONFIG_SGSN", + .action = ns2_sns_st_bss_config_sgsn, + .onenter = ns2_sns_st_bss_config_sgsn_onenter, + }, + [GPRS_SNS_ST_CONFIGURED] = { + .in_event_mask = S(NS2_SNS_EV_RX_ADD) | + S(NS2_SNS_EV_RX_DELETE) | + S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | + S(NS2_SNS_EV_REQ_NSVC_ALIVE), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_BSS_SIZE) | + S(GPRS_SNS_ST_LOCAL_PROCEDURE), + .name = "CONFIGURED", + .action = ns2_sns_st_configured, + .onenter = ns2_sns_st_configured_onenter, + }, + [GPRS_SNS_ST_LOCAL_PROCEDURE] = { + .in_event_mask = S(NS2_SNS_EV_RX_ADD) | + S(NS2_SNS_EV_RX_DELETE) | + S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | + S(NS2_SNS_EV_RX_ACK) | + S(NS2_SNS_EV_REQ_NSVC_ALIVE), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_BSS_SIZE) | + S(GPRS_SNS_ST_CONFIGURED) | + S(GPRS_SNS_ST_LOCAL_PROCEDURE), + .name = "LOCAL_PROCEDURE", + .action = ns2_sns_st_local_procedure, + .onenter = ns2_sns_st_local_procedure_onenter, + }, + +}; + +static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_inst *nsi = nse->nsi; + + gss->N++; + switch (fi->T) { + case 1: + if (gss->N >= nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES]) { + sns_failed(fi, "Size retries failed. Selecting next IP-SNS endpoint."); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1); + } + break; + case 2: + if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { + sns_failed(fi, "BSS Config retries failed. Selecting next IP-SNS endpoint"); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2); + } + break; + case 3: + if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { + sns_failed(fi, "SGSN Config retries failed. Selecting next IP-SNS endpoint."); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nsi->timeout[NS_TOUT_TSNS_PROV], 3); + } + break; + case 4: + sns_failed(fi, "Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint."); + break; + case 5: + if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { + sns_failed(fi, "SNS Procedure retries failed."); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], 5); + } + break; + } + return 0; +} + +static struct gprs_ns_ie_ip4_elem *ns2_get_sbind_ip4_entry(struct ns2_sns_state *gss, + struct ns2_sns_bind *sbind, + struct ns2_sns_elems *endpoints) +{ + const struct osmo_sockaddr *addr; + struct gprs_ns_ie_ip4_elem *ip4; + + if (gss->family != AF_INET) + return NULL; + + addr = gprs_ns2_ip_bind_sockaddr(sbind->bind); + if (addr->u.sa.sa_family != AF_INET) + return NULL; + + for (unsigned int i=0; i<endpoints->num_ip4; i++) { + ip4 = &endpoints->ip4[i]; + if (ip4->ip_addr == addr->u.sin.sin_addr.s_addr && + ip4->udp_port == addr->u.sin.sin_port) + return ip4; + } + + return NULL; +} + +static struct gprs_ns_ie_ip6_elem *ns2_get_sbind_ip6_entry(struct ns2_sns_state *gss, + struct ns2_sns_bind *sbind, + struct ns2_sns_elems *endpoints) +{ + const struct osmo_sockaddr *addr; + struct gprs_ns_ie_ip6_elem *ip6; + + if (gss->family != AF_INET6) + return NULL; + + addr = gprs_ns2_ip_bind_sockaddr(sbind->bind); + if (addr->u.sa.sa_family != AF_INET6) + return NULL; + + for (unsigned int i=0; i<endpoints->num_ip6; i++) { + ip6 = &endpoints->ip6[i]; + if (memcmp(&ip6->ip_addr, &addr->u.sin6.sin6_addr, sizeof(ip6->ip_addr)) || + ip6->udp_port != addr->u.sin6.sin6_port) + return ip6; + } + + return NULL; +} + +/* return != 0 if the resulting weight is invalid. return 1 if sbind doesn't have an entry */ +static int ns2_update_weight_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, + struct ns2_sns_elems *endpoints) +{ + struct gprs_ns_ie_ip4_elem *ip4; + struct gprs_ns_ie_ip6_elem *ip6; + + switch (gss->family) { + case AF_INET: + ip4 = ns2_get_sbind_ip4_entry(gss, sbind, endpoints); + if (!ip4) + return 1; + ip4->sig_weight = sbind->bind->sns_sig_weight; + ip4->data_weight = sbind->bind->sns_data_weight; + return (ip4_weight_sum_sig(endpoints) != 0 && ip4_weight_sum_data(endpoints) != 0); + break; + case AF_INET6: + ip6 = ns2_get_sbind_ip6_entry(gss, sbind, endpoints); + if (!ip6) + return 1; + ip6->sig_weight = sbind->bind->sns_sig_weight; + ip6->data_weight = sbind->bind->sns_data_weight; + return (ip6_weight_sum_sig(endpoints) != 0 && ip6_weight_sum_data(endpoints) != 0); + break; + default: + OSMO_ASSERT(0); + } +} +static void ns2_add_procedure(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, + enum sns_procedure procedure_type) +{ + struct ns2_sns_procedure *procedure = NULL; + const struct osmo_sockaddr *saddr; + saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind); + + OSMO_ASSERT(saddr->u.sa.sa_family == gss->family); + + switch (procedure_type) { + case SNS_PROC_ADD: + break; + case SNS_PROC_DEL: + break; + case SNS_PROC_CHANGE_WEIGHT: + llist_for_each_entry(procedure, &gss->procedures, list) { + if (procedure->sbind == sbind && procedure->procedure == procedure_type && + !procedure->running) { + switch(gss->family) { + case AF_INET: + /* merge it with a previous procedure */ + procedure->ip4.ip_addr = sbind->bind->sns_sig_weight; + procedure->ip4.data_weight = sbind->bind->sns_data_weight; + break; + case AF_INET6: + /* merge it with a previous procedure */ + procedure->ip6.sig_weight = sbind->bind->sns_sig_weight; + procedure->ip6.data_weight = sbind->bind->sns_data_weight; + break; + default: + OSMO_ASSERT(0); + } + return; + } + } + break; + default: + return; + } + + procedure = talloc_zero(gss, struct ns2_sns_procedure); + if (!procedure) + return; + + switch (procedure_type) { + case SNS_PROC_ADD: + case SNS_PROC_CHANGE_WEIGHT: + procedure->sbind = sbind; + break; + default: + break; + } + + llist_add_tail(&procedure->list, &gss->procedures); + procedure->procedure = procedure_type; + procedure->sig_weight = sbind->bind->sns_sig_weight; + procedure->data_weight = sbind->bind->sns_data_weight; + + switch(gss->family) { + case AF_INET: + procedure->ip4.ip_addr = saddr->u.sin.sin_addr.s_addr; + procedure->ip4.udp_port = saddr->u.sin.sin_port; + procedure->ip4.sig_weight = sbind->bind->sns_sig_weight; + procedure->ip4.data_weight = sbind->bind->sns_data_weight; + break; + case AF_INET6: + memcpy(&procedure->ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr)); + procedure->ip6.udp_port = saddr->u.sin.sin_port; + procedure->ip6.sig_weight = sbind->bind->sns_sig_weight; + procedure->ip6.data_weight = sbind->bind->sns_data_weight; + break; + default: + OSMO_ASSERT(0); + } + + if (gss->nse->bss_sns_fi->state == GPRS_SNS_ST_CONFIGURED) { + osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE, + gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5); + } +} + +/* add an entrypoint to sns_endpoints */ +static int ns2_sns_add_elements(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, + struct ns2_sns_elems *elems) +{ + const struct osmo_sockaddr *saddr; + struct gprs_ns_ie_ip4_elem ip4; + struct gprs_ns_ie_ip6_elem ip6; + int rc = -1; + + saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind); + OSMO_ASSERT(saddr->u.sa.sa_family == gss->family); + + switch (gss->family) { + case AF_INET: + ip4.ip_addr = saddr->u.sin.sin_addr.s_addr; + ip4.udp_port= saddr->u.sin.sin_port; + ip4.sig_weight = sbind->bind->sns_sig_weight; + ip4.data_weight = sbind->bind->sns_data_weight; + rc = add_ip4_elem(gss, elems, &ip4); + break; + case AF_INET6: + memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr)); + ip6.udp_port= saddr->u.sin.sin_port; + ip6.sig_weight = sbind->bind->sns_sig_weight; + ip6.data_weight = sbind->bind->sns_data_weight; + rc = add_ip6_elem(gss, elems, &ip6); + break; + } + + return rc; +} + +/* common allstate-action for both roles */ +static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct ns2_sns_bind *sbind; + struct gprs_ns2_vc *nsvc, *nsvc2; + struct ns2_sns_procedure *procedure; + + switch (event) { + case NS2_SNS_EV_REQ_ADD_BIND: + sbind = data; + switch (fi->state) { + case GPRS_SNS_ST_UNCONFIGURED: + if (gss->role == GPRS_SNS_ROLE_BSS) + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); + break; + case GPRS_SNS_ST_BSS_SIZE: + switch (gss->family) { + case AF_INET: + if (gss->num_max_ip4_remote <= gss->local.num_ip4 || + gss->num_max_ip4_remote * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) { + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER); + return; + } + break; + case AF_INET6: + if (gss->num_max_ip6_remote <= gss->local.num_ip6 || + gss->num_max_ip6_remote * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) { + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER); + return; + } + break; + } + ns2_sns_add_elements(gss, sbind, &gss->local); + break; + case GPRS_SNS_ST_BSS_CONFIG_BSS: + case GPRS_SNS_ST_BSS_CONFIG_SGSN: + case GPRS_SNS_ST_CONFIGURED: + switch (gss->family) { + case AF_INET: + if (gss->num_max_ip4_remote <= gss->local.num_ip4) { + LOGPFSML(fi, LOGL_ERROR, + "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n", + nse->nsei, sbind->bind->name); + return; + } + if (gss->remote.num_ip4 * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) { + LOGPFSML(fi, LOGL_ERROR, + "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n", + nse->nsei, sbind->bind->name); + return; + } + break; + case AF_INET6: + if (gss->num_max_ip6_remote <= gss->local.num_ip6) { + LOGPFSML(fi, LOGL_ERROR, + "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n", + nse->nsei, sbind->bind->name); + return; + } + if (gss->remote.num_ip6 * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) { + LOGPFSML(fi, LOGL_ERROR, + "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n", + nse->nsei, sbind->bind->name); + return; + } + break; + } + ns2_sns_add_elements(gss, sbind, &gss->local_procedure); + ns2_add_procedure(gss, sbind, SNS_PROC_ADD); + break; + } + break; + case NS2_SNS_EV_REQ_DELETE_BIND: + sbind = data; + switch (fi->state) { + case GPRS_SNS_ST_UNCONFIGURED: + break; + case GPRS_SNS_ST_BSS_SIZE: + llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) { + if (nsvc->bind == sbind->bind) { + gprs_ns2_free_nsvc(nsvc); + } + } + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); + break; + case GPRS_SNS_ST_BSS_CONFIG_BSS: + case GPRS_SNS_ST_BSS_CONFIG_SGSN: + case GPRS_SNS_ST_CONFIGURED: + case GPRS_SNS_ST_LOCAL_PROCEDURE: + remove_bind_elem(gss, &gss->local_procedure, sbind); + if (ip46_weight_sum(&gss->local_procedure, true) == 0 || + ip46_weight_sum(&gss->local_procedure, false) == 0) { + LOGPFSML(fi, LOGL_ERROR, "NSE %d: weight has become invalid because of removing bind %s. Resetting the configuration\n", + nse->nsei, sbind->bind->name); + sns_failed(fi, NULL); + break; + } + gss->block_no_nsvc_events = true; + llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) { + if (nsvc->bind == sbind->bind) { + gprs_ns2_free_nsvc(nsvc); + } + } + gss->block_no_nsvc_events = false; + if (nse->sum_sig_weight == 0 || !nse->alive || !gss->alive) { + sns_failed(fi, "While deleting a bind the current state became invalid (no signalling weight)"); + break; + } + + /* ensure other procedures doesn't use the sbind */ + llist_for_each_entry(procedure, &gss->procedures, list) { + if (procedure->sbind == sbind) + procedure->sbind = NULL; + } + ns2_add_procedure(gss, sbind, SNS_PROC_DEL); + break; + } + + /* if this is the last bind, the free_nsvc() will trigger a reselection */ + talloc_free(sbind); + break; + case NS2_SNS_EV_REQ_CHANGE_WEIGHT: + sbind = data; + switch (fi->state) { + case GPRS_SNS_ST_UNCONFIGURED: + /* select_endpoint will check if this is a valid configuration */ + if (gss->role == GPRS_SNS_ROLE_BSS) + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); + break; + case GPRS_SNS_ST_BSS_SIZE: + /* invalid weight? */ + if (!ns2_update_weight_entry(gss, sbind, &gss->local)) + sns_failed(fi, "updating weights results in an invalid configuration."); + break; + default: + if (!ns2_update_weight_entry(gss, sbind, &gss->local_procedure)) { + sns_failed(fi, "updating weights results in an invalid configuration."); + break; + } + ns2_add_procedure(gss, sbind, SNS_PROC_CHANGE_WEIGHT); + break; + } + } +} + +/* validate the bss configuration (sns endpoint and binds) + * - no endpoints -> invalid + * - no binds -> invalid + * - only v4 sns endpoints, only v6 binds -> invalid + * - only v4 sns endpoints, but v4 sig weights == 0 -> invalid ... + */ +static int ns2_sns_bss_valid_configuration(struct ns2_sns_state *gss) +{ + struct ns2_sns_bind *sbind; + struct sns_endpoint *endpoint; + const struct osmo_sockaddr *addr; + int v4_sig = 0, v4_data = 0, v6_sig = 0, v6_data = 0; + bool v4_endpoints = false; + bool v6_endpoints = false; + + if (llist_empty(&gss->sns_endpoints) || llist_empty(&gss->binds)) + return 0; + + llist_for_each_entry(sbind, &gss->binds, list) { + addr = gprs_ns2_ip_bind_sockaddr(sbind->bind); + if (!addr) + continue; + switch (addr->u.sa.sa_family) { + case AF_INET: + v4_sig += sbind->bind->sns_sig_weight; + v4_data += sbind->bind->sns_data_weight; + break; + case AF_INET6: + v6_sig += sbind->bind->sns_sig_weight; + v6_data += sbind->bind->sns_data_weight; + break; + } + } + + llist_for_each_entry(endpoint, &gss->sns_endpoints, list) { + switch (endpoint->saddr.u.sa.sa_family) { + case AF_INET: + v4_endpoints = true; + break; + case AF_INET6: + v6_endpoints = true; + break; + } + } + + return (v4_endpoints && v4_sig && v4_data) || (v6_endpoints && v6_sig && v6_data); +} + +/* allstate-action for BSS role */ +static void ns2_sns_st_all_action_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + + /* reset when receiving NS2_SNS_EV_REQ_NO_NSVC */ + switch (event) { + case NS2_SNS_EV_REQ_NO_NSVC: + /* ignore reselection running */ + if (gss->reselection_running || gss->block_no_nsvc_events) + break; + + sns_failed(fi, "no remaining NSVC, resetting SNS FSM"); + break; + case NS2_SNS_EV_REQ_FREE_NSVCS: + case NS2_SNS_EV_REQ_SELECT_ENDPOINT: + /* TODO: keep the order of binds when data == GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER */ + /* tear down previous state + * gprs_ns2_free_nsvcs() will trigger NO_NSVC, prevent this from triggering a reselection */ + if (gss->reselection_running || gss->block_no_nsvc_events) + break; + + gss->reselection_running = true; + ns2_free_nsvcs(nse); + ns2_clear_elems(&gss->local); + ns2_clear_elems(&gss->remote); + + /* Choose the next sns endpoint. */ + if (!ns2_sns_bss_valid_configuration(gss)) { + gss->initial = NULL; + ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 3); + gss->reselection_running = false; + return; + } else if (!gss->initial) { + gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list); + } else if (gss->initial->list.next == &gss->sns_endpoints) { + /* last entry, continue with first */ + gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list); + } else { + /* next element is an entry */ + gss->initial = llist_entry(gss->initial->list.next, struct sns_endpoint, list); + } + + gss->family = gss->initial->saddr.u.sa.sa_family; + gss->reselection_running = false; + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 1); + break; + default: + ns2_sns_st_all_action(fi, event, data); + break; + } +} + +static struct osmo_fsm gprs_ns2_sns_bss_fsm = { + .name = "GPRS-NS2-SNS-BSS", + .states = ns2_sns_bss_states, + .num_states = ARRAY_SIZE(ns2_sns_bss_states), + .allstate_event_mask = S(NS2_SNS_EV_REQ_NO_NSVC) | + S(NS2_SNS_EV_REQ_FREE_NSVCS) | + S(NS2_SNS_EV_REQ_SELECT_ENDPOINT) | + S(NS2_SNS_EV_REQ_ADD_BIND) | + S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) | + S(NS2_SNS_EV_REQ_DELETE_BIND), + .allstate_action = ns2_sns_st_all_action_bss, + .cleanup = NULL, + .timer_cb = ns2_sns_fsm_bss_timer_cb, + .event_names = gprs_sns_event_names, + .pre_term = NULL, + .log_subsys = DLNS, +}; + +/*! Allocate an IP-SNS FSM for the BSS side. + * \param[in] nse NS Entity in which the FSM runs + * \param[in] id string identifier + * \returns FSM instance on success; NULL on error */ +struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse, + const char *id) +{ + struct osmo_fsm_inst *fi; + struct ns2_sns_state *gss; + + fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id); + if (!fi) + return fi; + + gss = talloc_zero(fi, struct ns2_sns_state); + if (!gss) + goto err; + + fi->priv = gss; + gss->nse = nse; + gss->role = GPRS_SNS_ROLE_BSS; + /* The SGSN doesn't tell the BSS, so we assume there's always sufficient */ + gss->num_max_ip4_remote = 8192; + gss->num_max_ip6_remote = 8192; + INIT_LLIST_HEAD(&gss->sns_endpoints); + INIT_LLIST_HEAD(&gss->binds); + INIT_LLIST_HEAD(&gss->procedures); + + return fi; +err: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; +} + +/*! main entry point for receiving SNS messages from the network. + * \param[in] nsvc NS-VC on which the message was received + * \param[in] msg message buffer of the IP-SNS message + * \param[in] tp parsed TLV structure of message + * \returns 0 on success; negative on error */ +int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) +{ + struct gprs_ns2_nse *nse = nsvc->nse; + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + uint16_t nsei = nsvc->nse->nsei; + struct ns2_sns_state *gss; + struct osmo_fsm_inst *fi; + int rc = 0; + + if (!nse->bss_sns_fi) { + LOGNSVC(nsvc, LOGL_NOTICE, "Rx %s for NS Instance that has no SNS!\n", + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); + rc = -EINVAL; + goto out; + } + + /* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */ + fi = nse->bss_sns_fi; + gss = (struct ns2_sns_state *) fi->priv; + gss->sns_nsvc = nsvc; + + LOGPFSML(fi, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei, + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); + + switch (nsh->pdu_type) { + case SNS_PDUT_SIZE: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE, tp); + break; + case SNS_PDUT_SIZE_ACK: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE_ACK, tp); + break; + case SNS_PDUT_CONFIG: + if (nsh->data[0] & 0x01) + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_END, tp); + else + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG, tp); + break; + case SNS_PDUT_CONFIG_ACK: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_ACK, tp); + break; + case SNS_PDUT_ADD: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ADD, tp); + break; + case SNS_PDUT_DELETE: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_DELETE, tp); + break; + case SNS_PDUT_CHANGE_WEIGHT: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CHANGE_WEIGHT, tp); + break; + case SNS_PDUT_ACK: + osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ACK, tp); + break; + default: + LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei, + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); + rc = -EINVAL; + } + +out: + msgb_free(msg); + + return rc; +} + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/misc.h> + +static void vty_dump_sns_ip4(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip4_elem *ip4) +{ + struct in_addr in = { .s_addr = ip4->ip_addr }; + vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix, + inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE); +} + +static void vty_dump_sns_ip6(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip6_elem *ip6) +{ + char ip_addr[INET6_ADDRSTRLEN] = {}; + if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN))) + strcpy(ip_addr, "Invalid IPv6"); + + vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix, + ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE); +} + +/*! Dump the IP-SNS state to a vty. + * \param[in] vty VTY to which the state shall be printed + * \param[in] prefix prefix to print at start of each line (typically indenting) + * \param[in] nse NS Entity whose IP-SNS state shall be printed + * \param[in] stats Whether or not statistics shall also be printed */ +void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats) +{ + struct ns2_sns_state *gss; + unsigned int i; + + if (!nse->bss_sns_fi) + return; + + vty_out_fsm_inst2(vty, prefix, nse->bss_sns_fi); + gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv; + + vty_out(vty, "%sMaximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s", + prefix, gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE); + + if (gss->local.num_ip4 && gss->remote.num_ip4) { + vty_out(vty, "%sLocal IPv4 Endpoints:%s", prefix, VTY_NEWLINE); + for (i = 0; i < gss->local.num_ip4; i++) + vty_dump_sns_ip4(vty, prefix, &gss->local.ip4[i]); + + vty_out(vty, "%sRemote IPv4 Endpoints:%s", prefix, VTY_NEWLINE); + for (i = 0; i < gss->remote.num_ip4; i++) + vty_dump_sns_ip4(vty, prefix, &gss->remote.ip4[i]); + } + + if (gss->local.num_ip6 && gss->remote.num_ip6) { + vty_out(vty, "%sLocal IPv6 Endpoints:%s", prefix, VTY_NEWLINE); + for (i = 0; i < gss->local.num_ip6; i++) + vty_dump_sns_ip6(vty, prefix, &gss->local.ip6[i]); + + vty_out(vty, "%sRemote IPv6 Endpoints:%s", prefix, VTY_NEWLINE); + for (i = 0; i < gss->remote.num_ip6; i++) + vty_dump_sns_ip6(vty, prefix, &gss->remote.ip6[i]); + } +} + +/*! write IP-SNS to a vty + * \param[in] vty VTY to which the state shall be printed + * \param[in] nse NS Entity whose IP-SNS state shall be printed */ +void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse) +{ + struct ns2_sns_state *gss; + struct osmo_sockaddr_str addr_str; + struct sns_endpoint *endpoint; + + if (!nse->bss_sns_fi) + return; + + gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv; + llist_for_each_entry(endpoint, &gss->sns_endpoints, list) { + /* It's unlikely that an error happens, but let's better be safe. */ + if (osmo_sockaddr_str_from_sockaddr(&addr_str, &endpoint->saddr.u.sas) != 0) + addr_str = (struct osmo_sockaddr_str) { .ip = "<INVALID>" }; + vty_out(vty, " ip-sns-remote %s %u%s", addr_str.ip, addr_str.port, VTY_NEWLINE); + } +} + +static struct sns_endpoint *ns2_get_sns_endpoint(struct ns2_sns_state *state, + const struct osmo_sockaddr *saddr) +{ + struct sns_endpoint *endpoint; + + llist_for_each_entry(endpoint, &state->sns_endpoints, list) { + if (!osmo_sockaddr_cmp(saddr, &endpoint->saddr)) + return endpoint; + } + + return NULL; +} + +/*! gprs_ns2_sns_add_endpoint + * \param[in] nse + * \param[in] sockaddr + * \return + */ +int gprs_ns2_sns_add_endpoint(struct gprs_ns2_nse *nse, + const struct osmo_sockaddr *saddr) +{ + struct ns2_sns_state *gss; + struct sns_endpoint *endpoint; + bool do_selection = false; + + if (nse->ll != GPRS_NS2_LL_UDP) { + return -EINVAL; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + return -EINVAL; + } + + gss = nse->bss_sns_fi->priv; + + if (ns2_get_sns_endpoint(gss, saddr)) + return -EADDRINUSE; + + endpoint = talloc_zero(nse->bss_sns_fi->priv, struct sns_endpoint); + if (!endpoint) + return -ENOMEM; + + endpoint->saddr = *saddr; + if (llist_empty(&gss->sns_endpoints)) + do_selection = true; + + llist_add_tail(&endpoint->list, &gss->sns_endpoints); + if (do_selection) + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); + + return 0; +} + +/*! gprs_ns2_sns_del_endpoint + * \param[in] nse + * \param[in] sockaddr + * \return 0 on success, otherwise < 0 + */ +int gprs_ns2_sns_del_endpoint(struct gprs_ns2_nse *nse, + const struct osmo_sockaddr *saddr) +{ + struct ns2_sns_state *gss; + struct sns_endpoint *endpoint; + + if (nse->ll != GPRS_NS2_LL_UDP) { + return -EINVAL; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + return -EINVAL; + } + + gss = nse->bss_sns_fi->priv; + endpoint = ns2_get_sns_endpoint(gss, saddr); + if (!endpoint) + return -ENOENT; + + /* if this is an unused SNS endpoint it's done */ + if (gss->initial != endpoint) { + llist_del(&endpoint->list); + talloc_free(endpoint); + return 0; + } + + /* gprs_ns2_free_nsvcs() will trigger NS2_SNS_EV_REQ_NO_NSVC on the last NS-VC + * and restart SNS SIZE procedure which selects a new initial */ + LOGNSE(nse, LOGL_INFO, "Current in-use SNS endpoint is being removed." + "Closing all NS-VC and restart SNS-SIZE procedure" + "with a remaining SNS endpoint.\n"); + + /* Continue with the next endpoint in the list. + * Special case if the endpoint is at the start or end of the list */ + if (endpoint->list.prev == &gss->sns_endpoints || + endpoint->list.next == &gss->sns_endpoints) + gss->initial = NULL; + else + gss->initial = llist_entry(endpoint->list.next->prev, + struct sns_endpoint, + list); + + llist_del(&endpoint->list); + gprs_ns2_free_nsvcs(nse); + talloc_free(endpoint); + + return 0; +} + +/*! gprs_ns2_sns_count + * \param[in] nse NS Entity whose IP-SNS endpoints shall be printed + * \return the count of endpoints or < 0 if NSE doesn't contain sns. + */ +int gprs_ns2_sns_count(struct gprs_ns2_nse *nse) +{ + struct ns2_sns_state *gss; + struct sns_endpoint *endpoint; + int count = 0; + + if (nse->ll != GPRS_NS2_LL_UDP) { + return -EINVAL; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + return -EINVAL; + } + + gss = nse->bss_sns_fi->priv; + llist_for_each_entry(endpoint, &gss->sns_endpoints, list) + count++; + + return count; +} + +void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive) +{ + struct ns2_sns_state *gss; + struct gprs_ns2_vc *tmp; + + if (!nse->bss_sns_fi) + return; + + gss = nse->bss_sns_fi->priv; + if (nse->bss_sns_fi->state != GPRS_SNS_ST_CONFIGURED && nse->bss_sns_fi->state != GPRS_SNS_ST_LOCAL_PROCEDURE) + return; + + if (gss->block_no_nsvc_events) + return; + + if (gss->alive && nse->sum_sig_weight == 0) { + sns_failed(nse->bss_sns_fi, "No signalling NSVC available"); + return; + } + + /* check if this is the current SNS NS-VC */ + if (nsvc == gss->sns_nsvc && !alive) { + /* only replace the SNS NS-VC if there are other alive NS-VC. + * There aren't any other alive NS-VC when the SNS fsm just reached CONFIGURED + * and couldn't confirm yet if the NS-VC comes up */ + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (nsvc == tmp) + continue; + if (ns2_vc_is_unblocked(nsvc)) { + ns2_sns_replace_nsvc(nsvc); + break; + } + } + } + + if (alive == gss->alive) + return; + + if (alive) { + /* we need at least a signalling NSVC before become alive */ + if (nse->sum_sig_weight == 0) + return; + gss->alive = true; + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NSVC_ALIVE, NULL); + } else { + /* is there at least another alive nsvc? */ + llist_for_each_entry(tmp, &nse->nsvc, list) { + if (ns2_vc_is_unblocked(tmp)) + return; + } + + /* all NS-VC have failed */ + gss->alive = false; + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NO_NSVC, NULL); + } +} + +int gprs_ns2_sns_add_bind(struct gprs_ns2_nse *nse, + struct gprs_ns2_vc_bind *bind) +{ + struct ns2_sns_state *gss; + struct ns2_sns_bind *tmp; + + OSMO_ASSERT(nse->bss_sns_fi); + gss = nse->bss_sns_fi->priv; + + if (!gprs_ns2_is_ip_bind(bind)) { + return -EINVAL; + } + + if (!llist_empty(&gss->binds)) { + llist_for_each_entry(tmp, &gss->binds, list) { + if (tmp->bind == bind) + return -EALREADY; + } + } + + tmp = talloc_zero(gss, struct ns2_sns_bind); + if (!tmp) + return -ENOMEM; + tmp->bind = bind; + llist_add_tail(&tmp->list, &gss->binds); + + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_ADD_BIND, tmp); + return 0; +} + +/* Remove a bind from the SNS. All assosiated NSVC must be removed. */ +int gprs_ns2_sns_del_bind(struct gprs_ns2_nse *nse, + struct gprs_ns2_vc_bind *bind) +{ + struct ns2_sns_state *gss; + struct ns2_sns_bind *tmp, *tmp2; + bool found = false; + + if (!nse->bss_sns_fi) + return -EINVAL; + + gss = nse->bss_sns_fi->priv; + if (gss->initial_bind && gss->initial_bind->bind == bind) { + if (gss->initial_bind->list.prev == &gss->binds) + gss->initial_bind = NULL; + else + gss->initial_bind = llist_entry(gss->initial_bind->list.prev, struct ns2_sns_bind, list); + } + + llist_for_each_entry_safe(tmp, tmp2, &gss->binds, list) { + if (tmp->bind == bind) { + llist_del(&tmp->list); + found = true; + break; + } + } + + if (!found) + return -ENOENT; + + osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_DELETE_BIND, tmp); + return 0; +} + +/* Update SNS weights for a bind (local endpoint). + * \param[in] bind the bind which has been updated + */ +void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind) +{ + struct ns2_sns_bind *sbind; + struct gprs_ns2_nse *nse; + struct ns2_sns_state *gss; + const struct osmo_sockaddr *addr = gprs_ns2_ip_bind_sockaddr(bind); + + llist_for_each_entry(nse, &bind->nsi->nse, list) { + if (!nse->bss_sns_fi) + continue; + + gss = nse->bss_sns_fi->priv; + if (addr->u.sa.sa_family != gss->family) + return; + + llist_for_each_entry(sbind, &gss->binds, list) { + if (sbind->bind == bind) { + osmo_fsm_inst_dispatch(gss->nse->bss_sns_fi, NS2_SNS_EV_REQ_CHANGE_WEIGHT, sbind); + break; + } + } + } +} + + + + +/*********************************************************************** + * SGSN role + ***********************************************************************/ + +/* cleanup all state. If nsvc is given, don't remove this nsvc. (nsvc is given when a SIZE PDU received) */ +static void ns2_clear_sgsn(struct ns2_sns_state *gss, struct gprs_ns2_vc *size_nsvc) +{ + struct gprs_ns2_vc *nsvc, *nsvc2; + + ns2_clear_procedures(gss); + ns2_clear_elems(&gss->local); + ns2_clear_elems(&gss->remote); + gss->block_no_nsvc_events = true; + llist_for_each_entry_safe(nsvc, nsvc2, &gss->nse->nsvc, list) { + /* Ignore the NSVC over which the SIZE PDU got received */ + if (size_nsvc && size_nsvc == nsvc) + continue; + + gprs_ns2_free_nsvc(nsvc); + } + gss->block_no_nsvc_events = false; +} + +static void ns2_sns_st_sgsn_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + + ns2_clear_sgsn(gss, NULL); +} + +static void ns2_sns_st_sgsn_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); + /* do nothing; Rx SNS-SIZE handled in ns2_sns_st_all_action_sgsn() */ +} + +/* We're waiting for inbound SNS-CONFIG from the BSS */ +static void ns2_sns_st_sgsn_wait_config(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_inst *nsi = nse->nsi; + uint8_t cause; + int rc; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); + + switch (event) { + case NS2_SNS_EV_RX_CONFIG: + case NS2_SNS_EV_RX_CONFIG_END: + rc = ns_sns_append_remote_eps(fi, data); + if (rc < 0) { + cause = -rc; + ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); + return; + } + /* only change state if last CONFIG was received */ + if (event == NS2_SNS_EV_RX_CONFIG_END) { + /* ensure sum of data weight / sig weights is > 0 */ + if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) { + cause = NS_CAUSE_INVAL_WEIGH; + ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); + break; + } + ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3); + } else { + /* just send CONFIG-ACK */ + ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); + osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0); + } + break; + } +} + +static void ns2_sns_st_sgsn_wait_config_ack_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); + + /* transmit SGSN-oriented SNS-CONFIG */ + ns2_tx_sns_config(gss->sns_nsvc, true, gss->local.ip4, gss->local.num_ip4, + gss->local.ip6, gss->local.num_ip6); +} + +/* We're waiting for SNS-CONFIG-ACK from the BSS (in response to our outbound SNS-CONFIG) */ +static void ns2_sns_st_sgsn_wait_config_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct tlv_parsed *tp = NULL; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); + + switch (event) { + case NS2_SNS_EV_RX_CONFIG_ACK: + tp = data; + if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) { + LOGPFSML(fi, LOGL_ERROR, "Rx SNS-CONFIG-ACK with cause %s\n", + gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE))); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); + break; + } + /* we currently only send one SNS-CONFIG with END FLAG */ + if (true) { + create_missing_nsvcs(fi); + /* start the test procedure on ALL NSVCs! */ + gprs_ns2_start_alive_all_nsvcs(nse); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, ns_sns_configured_timeout(fi), 4); + } + break; + } +} + +/* SGSN-side SNS state machine */ +static const struct osmo_fsm_state ns2_sns_sgsn_states[] = { + [GPRS_SNS_ST_UNCONFIGURED] = { + .in_event_mask = 0, /* handled by all_state_action */ + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_SGSN_WAIT_CONFIG), + .name = "UNCONFIGURED", + .action = ns2_sns_st_sgsn_unconfigured, + .onenter = ns2_sns_st_sgsn_unconfigured_onenter, + }, + [GPRS_SNS_ST_SGSN_WAIT_CONFIG] = { + .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) | + S(NS2_SNS_EV_RX_CONFIG_END), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) | + S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK), + .name = "SGSN_WAIT_CONFIG", + .action = ns2_sns_st_sgsn_wait_config, + }, + [GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK] = { + .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) | + S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK) | + S(GPRS_SNS_ST_CONFIGURED), + .name = "SGSN_WAIT_CONFIG_ACK", + .action = ns2_sns_st_sgsn_wait_config_ack, + .onenter = ns2_sns_st_sgsn_wait_config_ack_onenter, + }, + [GPRS_SNS_ST_CONFIGURED] = { + .in_event_mask = S(NS2_SNS_EV_RX_ADD) | + S(NS2_SNS_EV_RX_DELETE) | + S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | + S(NS2_SNS_EV_REQ_NSVC_ALIVE), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) | + S(GPRS_SNS_ST_LOCAL_PROCEDURE), + .name = "CONFIGURED", + /* shared with BSS side; once configured there's no difference */ + .action = ns2_sns_st_configured, + .onenter = ns2_sns_st_configured_onenter, + }, + [GPRS_SNS_ST_LOCAL_PROCEDURE] = { + .in_event_mask = S(NS2_SNS_EV_RX_ADD) | + S(NS2_SNS_EV_RX_DELETE) | + S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | + S(NS2_SNS_EV_RX_ACK) | + S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) | + S(NS2_SNS_EV_REQ_NSVC_ALIVE), + .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | + S(GPRS_SNS_ST_CONFIGURED) | + S(GPRS_SNS_ST_LOCAL_PROCEDURE), + .name = "LOCAL_PROCEDURE", + /* shared with BSS side; once configured there's no difference */ + .action = ns2_sns_st_local_procedure, + .onenter = ns2_sns_st_local_procedure_onenter, + }, +}; + +static int ns2_sns_fsm_sgsn_timer_cb(struct osmo_fsm_inst *fi) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); + struct gprs_ns2_inst *nsi = nse->nsi; + + gss->N++; + switch (fi->T) { + case 3: + if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { + LOGPFSML(fi, LOGL_ERROR, "NSE %d: SGSN Config retries failed. Giving up.\n", nse->nsei); + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, nsi->timeout[NS_TOUT_TSNS_PROV], 3); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3); + } + break; + case 4: + LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online.\n", nse->nsei); + break; + case 5: + if (gss->N >= nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES]) { + sns_failed(fi, "SNS Procedure retries failed."); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], + fi->T); + } + break; + } + + return 0; +} + +/* allstate-action for SGSN role */ +static void ns2_sns_st_all_action_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; + struct tlv_parsed *tp = NULL; + size_t num_local_eps, num_remote_eps; + uint8_t flag; + uint8_t cause; + + OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); + + switch (event) { + case NS2_SNS_EV_RX_SIZE: + tp = (struct tlv_parsed *) data; + /* check for mandatory / conditional IEs */ + if (!TLVP_PRES_LEN(tp, NS_IE_RESET_FLAG, 1) || + !TLVP_PRES_LEN(tp, NS_IE_MAX_NR_NSVC, 2)) { + cause = NS_CAUSE_MISSING_ESSENT_IE; + ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); + if (fi->state == GPRS_SNS_ST_UNCONFIGURED) + sns_failed(fi, "Rx Size: Missing essential IE"); + break; + } + if (!TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2) && + !TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) { + cause = NS_CAUSE_MISSING_ESSENT_IE; + ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); + if (fi->state == GPRS_SNS_ST_UNCONFIGURED) + sns_failed(fi, "Rx Size: Missing essential IE"); + break; + } + if (TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2)) + gss->num_max_ip4_remote = tlvp_val16be(tp, NS_IE_IPv4_EP_NR); + if (TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) + gss->num_max_ip6_remote = tlvp_val16be(tp, NS_IE_IPv6_EP_NR); + /* decide if we go for IPv4 or IPv6 */ + if (gss->num_max_ip6_remote && ns2_sns_count_num_local_ep(fi, AF_INET6)) { + gss->family = AF_INET6; + ns2_sns_compute_local_ep_from_binds(fi); + num_local_eps = gss->local.num_ip6; + num_remote_eps = gss->num_max_ip6_remote; + } else if (gss->num_max_ip4_remote && ns2_sns_count_num_local_ep(fi, AF_INET)) { + gss->family = AF_INET; + ns2_sns_compute_local_ep_from_binds(fi); + num_local_eps = gss->local.num_ip4; + num_remote_eps = gss->num_max_ip4_remote; + } else { + if (gss->local.num_ip4 && !gss->num_max_ip4_remote) + cause = NS_CAUSE_INVAL_NR_IPv4_EP; + else + cause = NS_CAUSE_INVAL_NR_IPv6_EP; + ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); + if (fi->state == GPRS_SNS_ST_UNCONFIGURED) + sns_failed(fi, "Rx Size: Invalid Nr of IPv4/IPv6 EPs"); + break; + } + /* ensure number of NS-VCs is sufficient for full mesh */ + gss->num_max_nsvcs = tlvp_val16be(tp, NS_IE_MAX_NR_NSVC); + if (gss->num_max_nsvcs < num_remote_eps * num_local_eps) { + LOGPFSML(fi, LOGL_ERROR, "%zu local and %zu remote EPs, requires %zu NS-VC, " + "but BSS supports only %zu maximum NS-VCs\n", num_local_eps, + num_remote_eps, num_local_eps * num_remote_eps, gss->num_max_nsvcs); + cause = NS_CAUSE_INVAL_NR_NS_VC; + ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); + if (fi->state == GPRS_SNS_ST_UNCONFIGURED) + sns_failed(fi, NULL); + break; + } + /* perform state reset, if requested */ + flag = *TLVP_VAL(tp, NS_IE_RESET_FLAG); + if (flag & 1) { + /* clear all state */ + /* TODO: ensure gss->sns_nsvc is always the NSVC on which we received the SIZE PDU */ + gss->N = 0; + ns2_clear_sgsn(gss, gss->sns_nsvc); + /* keep the NSVC we need for SNS, but unconfigure it */ + gss->sns_nsvc->sig_weight = 0; + gss->sns_nsvc->data_weight = 0; + gss->block_no_nsvc_events = true; + ns2_vc_force_unconfigured(gss->sns_nsvc); + gss->block_no_nsvc_events = false; + ns2_sns_compute_local_ep_from_binds(fi); + } + + if (fi->state == GPRS_SNS_ST_UNCONFIGURED && !(flag & 1)) { + sns_failed(fi, "Rx Size without Reset flag, but NSE is unknown"); + break; + } + + /* send SIZE_ACK */ + ns2_tx_sns_size_ack(gss->sns_nsvc, NULL); + /* only wait for SNS-CONFIG in case of Reset flag */ + if (flag & 1) + osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG, 0, 0); + break; + case NS2_SNS_EV_REQ_FREE_NSVCS: + sns_failed(fi, "On user request to free all NSVCs"); + break; + default: + ns2_sns_st_all_action(fi, event, data); + break; + } +} + +static struct osmo_fsm gprs_ns2_sns_sgsn_fsm = { + .name = "GPRS-NS2-SNS-SGSN", + .states = ns2_sns_sgsn_states, + .num_states = ARRAY_SIZE(ns2_sns_sgsn_states), + .allstate_event_mask = S(NS2_SNS_EV_RX_SIZE) | + S(NS2_SNS_EV_REQ_NO_NSVC) | + S(NS2_SNS_EV_REQ_FREE_NSVCS) | + S(NS2_SNS_EV_REQ_ADD_BIND) | + S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) | + S(NS2_SNS_EV_REQ_DELETE_BIND), + .allstate_action = ns2_sns_st_all_action_sgsn, + .cleanup = NULL, + .timer_cb = ns2_sns_fsm_sgsn_timer_cb, + .event_names = gprs_sns_event_names, + .pre_term = NULL, + .log_subsys = DLNS, +}; + +/*! Allocate an IP-SNS FSM for the SGSN side. + * \param[in] nse NS Entity in which the FSM runs + * \param[in] id string identifier + * \returns FSM instance on success; NULL on error */ +struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id) +{ + struct osmo_fsm_inst *fi; + struct ns2_sns_state *gss; + + fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_sgsn_fsm, nse, NULL, LOGL_DEBUG, id); + if (!fi) + return fi; + + gss = talloc_zero(fi, struct ns2_sns_state); + if (!gss) + goto err; + + fi->priv = gss; + gss->nse = nse; + gss->role = GPRS_SNS_ROLE_SGSN; + INIT_LLIST_HEAD(&gss->sns_endpoints); + INIT_LLIST_HEAD(&gss->binds); + INIT_LLIST_HEAD(&gss->procedures); + + return fi; +err: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; +} + + + + +/* initialize osmo_ctx on main tread */ +static __attribute__((constructor)) void on_dso_load_ctx(void) +{ + OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_bss_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_sgsn_fsm) == 0); +} diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c new file mode 100644 index 00000000..1dcc3030 --- /dev/null +++ b/src/gb/gprs_ns2_udp.c @@ -0,0 +1,597 @@ +/*! \file gprs_ns2_udp.c + * NS-over-UDP implementation. + * GPRS Networks Service (NS) messages on the Gb interface. + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> + +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/select.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/core/socket.h> +#include <osmocom/gprs/gprs_ns2.h> + +#include "common_vty.h" +#include "gprs_ns2_internal.h" + + +static void free_bind(struct gprs_ns2_vc_bind *bind); + + +struct gprs_ns2_vc_driver vc_driver_ip = { + .name = "GB UDP IPv4/IPv6", + .free_bind = free_bind, +}; + +struct priv_bind { + struct osmo_io_fd *iofd; + struct osmo_sockaddr addr; + int dscp; + uint8_t priority; +}; + +struct priv_vc { + struct osmo_sockaddr remote; +}; + +/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */ +static void free_bind(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + + if (!bind) + return; + + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + + priv = bind->priv; + + osmo_iofd_free(priv->iofd); + priv->iofd = NULL; + talloc_free(priv); +} + +static void free_vc(struct gprs_ns2_vc *nsvc) +{ + if (!nsvc) + return; + + if (!nsvc->priv) + return; + + OSMO_ASSERT(gprs_ns2_is_ip_bind(nsvc->bind)); + talloc_free(nsvc->priv); + nsvc->priv = NULL; +} + +static void dump_vty(const struct gprs_ns2_vc_bind *bind, + struct vty *vty, bool stats) +{ + struct priv_bind *priv; + struct gprs_ns2_vc *nsvc; + struct osmo_sockaddr_str sockstr = {}; + unsigned long nsvcs = 0; + + if (!bind) + return; + + priv = bind->priv; + if (osmo_sockaddr_str_from_sockaddr(&sockstr, &priv->addr.u.sas)) + strcpy(sockstr.ip, "invalid"); + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + nsvcs++; + } + + vty_out(vty, "UDP bind: %s:%d DSCP: %d Priority: %u%s", sockstr.ip, sockstr.port, + priv->dscp, priv->priority, VTY_NEWLINE); + vty_out(vty, " IP-SNS signalling weight: %u data weight: %u%s", + bind->sns_sig_weight, bind->sns_data_weight, VTY_NEWLINE); + vty_out(vty, " %lu NS-VC:%s", nsvcs, VTY_NEWLINE); + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + ns2_vty_dump_nsvc(vty, nsvc, stats); + } +} + + +/*! Find a NS-VC by its remote socket address. + * \param[in] bind in which to search + * \param[in] rem_addr remote peer socket address to search + * \returns NS-VC matching sockaddr; NULL if none found */ +struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind, + const struct osmo_sockaddr *rem_addr) +{ + struct gprs_ns2_vc *nsvc; + struct priv_vc *vcpriv; + + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vcpriv = nsvc->priv; + if (vcpriv->remote.u.sa.sa_family != rem_addr->u.sa.sa_family) + continue; + if (osmo_sockaddr_cmp(&vcpriv->remote, rem_addr)) + continue; + + return nsvc; + } + + return NULL; +} + +static inline int nsip_sendmsg(struct gprs_ns2_vc_bind *bind, + struct msgb *msg, + const struct osmo_sockaddr *dest) +{ + struct priv_bind *priv = bind->priv; + + return osmo_iofd_sendto_msgb(priv->iofd, msg, 0, dest); +} + +/*! send the msg and free it afterwards. + * \param nsvc NS-VC on which the message shall be sent + * \param msg message to be sent + * \return number of bytes transmitted; negative on error */ +static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) +{ + int rc; + struct gprs_ns2_vc_bind *bind = nsvc->bind; + struct priv_vc *priv = nsvc->priv; + + rc = nsip_sendmsg(bind, msg, &priv->remote); + + return rc; +} + +static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *nsvc, const struct osmo_sockaddr *remote) +{ + struct priv_vc *priv = talloc_zero(bind, struct priv_vc); + if (!priv) + return NULL; + + nsvc->priv = priv; + priv->remote = *remote; + + return priv; +} + +static void handle_nsip_recvfrom(struct osmo_io_fd *iofd, int error, struct msgb *msg, + const struct osmo_sockaddr *saddr) +{ + int rc = 0; + struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd); + struct gprs_ns2_vc *nsvc; + + struct msgb *reject; + + msg->l2h = msgb_data(msg); + + /* check if a vc is available */ + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, saddr); + if (!nsvc) { + /* VC not found */ + rc = ns2_create_vc(bind, msg, saddr, "newconnection", &reject, &nsvc); + switch (rc) { + case NS2_CS_FOUND: + break; + case NS2_CS_ERROR: + case NS2_CS_SKIPPED: + rc = 0; + goto out; + case NS2_CS_REJECTED: + /* nsip_sendmsg will free reject */ + rc = nsip_sendmsg(bind, reject, saddr); + goto out; + case NS2_CS_CREATED: + ns2_driver_alloc_vc(bind, nsvc, saddr); + /* only start the fsm for non SNS. SNS will take care of its own */ + if (nsvc->nse->dialect != GPRS_NS2_DIALECT_SNS) + ns2_vc_fsm_start(nsvc); + break; + } + } + + ns2_recv_vc(nsvc, msg); + return; + +out: + msgb_free(msg); +} + +static void handle_nsip_sendto(struct osmo_io_fd *iofd, int res, + struct msgb *msg, + const struct osmo_sockaddr *daddr) +{ + struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd); + struct gprs_ns2_vc *nsvc; + + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, daddr); + if (!nsvc) + return; + + if (OSMO_LIKELY(res >= 0)) { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, res); + } else { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, msgb_length(msg)); + } +} + +/*! Find NS bind for a given socket address + * \param[in] nsi NS instance + * \param[in] sockaddr socket address to search for + * \return + */ +struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi, + const struct osmo_sockaddr *sockaddr) +{ + struct gprs_ns2_vc_bind *bind; + const struct osmo_sockaddr *local; + + OSMO_ASSERT(nsi); + OSMO_ASSERT(sockaddr); + + llist_for_each_entry(bind, &nsi->binding, list) { + if (!gprs_ns2_is_ip_bind(bind)) + continue; + + local = gprs_ns2_ip_bind_sockaddr(bind); + if (!osmo_sockaddr_cmp(sockaddr, local)) + return bind; + } + + return NULL; +} + +/*! Bind to an IPv4/IPv6 address + * \param[in] nsi NS Instance in which to create the NSVC + * \param[in] local the local address to bind to + * \param[in] dscp the DSCP/TOS bits used for transmitted data + * \param[out] result pointer to the created bind or if a bind with the name exists return the bind. + * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */ +int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi, + const char *name, + const struct osmo_sockaddr *local, + int dscp, + struct gprs_ns2_vc_bind **result) +{ + struct gprs_ns2_vc_bind *bind; + struct priv_bind *priv; + int rc; + + struct osmo_io_ops ioops = { + .sendto_cb = &handle_nsip_sendto, + .recvfrom_cb = &handle_nsip_recvfrom, + }; + + if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) + return -EINVAL; + + if (dscp < 0 || dscp > 63) + return -EINVAL; + + bind = gprs_ns2_ip_bind_by_sockaddr(nsi, local); + if (bind) { + if (result) + *result = bind; + return -EBUSY; + } + + rc = ns2_bind_alloc(nsi, name, &bind); + if (rc < 0) + return rc; + + bind->driver = &vc_driver_ip; + bind->ll = GPRS_NS2_LL_UDP; + /* expect 100 mbit at least. + * TODO: ask the network layer about the speed. But would require + * notification on change. */ + bind->transfer_capability = 100; + bind->send_vc = nsip_vc_sendmsg; + bind->free_vc = free_vc; + bind->dump_vty = dump_vty; + + priv = bind->priv = talloc_zero(bind, struct priv_bind); + if (!priv) { + gprs_ns2_free_bind(bind); + return -ENOMEM; + } + + priv->addr = *local; + priv->dscp = dscp; + + rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, + local, NULL, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp)); + if (rc < 0) { + gprs_ns2_free_bind(bind); + return rc; + } + + priv->iofd = osmo_iofd_setup(bind, rc, "NS bind", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops, bind); + osmo_iofd_register(priv->iofd, rc); + osmo_iofd_set_alloc_info(priv->iofd, 4096, 128); + osmo_iofd_set_txqueue_max_length(priv->iofd, nsi->txqueue_max_length); + + /* IPv4: max fragmented payload can be (13 bit) * 8 byte => 65535. + * IPv6: max payload can be 65535 (RFC 2460). + * UDP header = 8 byte */ + bind->mtu = 65535 - 8; + if (result) + *result = bind; + + return 0; +} + +/*! Create new NS-VC to a given remote address + * \param[in] bind the bind we want to connect + * \param[in] nse NS entity to be used for the new NS-VC + * \param[in] remote remote address to connect to + * \return pointer to newly-allocated and connected NS-VC; NULL on error */ +struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_nse *nse, + const struct osmo_sockaddr *remote) +{ + struct gprs_ns2_vc *nsvc; + const struct osmo_sockaddr *local; + struct priv_vc *priv; + enum gprs_ns2_vc_mode vc_mode; + char idbuf[256], tmp[INET6_ADDRSTRLEN + 8]; + + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + + vc_mode = ns2_dialect_to_vc_mode(nse->dialect); + if ((int) vc_mode == -1) { + LOGNSE(nse, LOGL_ERROR, "Can not derive vc mode from dialect %d. Maybe libosmocore is too old.\n", + nse->dialect); + return NULL; + } + + /* duplicate */ + if (gprs_ns2_nsvc_by_sockaddr_bind(bind, remote)) + return NULL; + + local = gprs_ns2_ip_bind_sockaddr(bind); + osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local); + snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC-%s-%s-%s", nse->nsei, gprs_ns2_lltype_str(nse->ll), + tmp, osmo_sockaddr_to_str(remote)); + osmo_identifier_sanitize_buf(idbuf, NULL, '_'); + + nsvc = ns2_vc_alloc(bind, nse, true, vc_mode, idbuf); + if (!nsvc) + return NULL; + + nsvc->priv = talloc_zero(bind, struct priv_vc); + if (!nsvc->priv) { + gprs_ns2_free_nsvc(nsvc); + return NULL; + } + + priv = nsvc->priv; + priv->remote = *remote; + + return nsvc; +} + +/*! Return the socket address of the local peer of a NS-VC. + * \param[in] nsvc NS-VC whose local peer we want to know + * \return address of the local peer; NULL in case of error */ +const struct osmo_sockaddr *gprs_ns2_ip_vc_local(const struct gprs_ns2_vc *nsvc) +{ + struct priv_bind *priv; + + if (nsvc->bind->driver != &vc_driver_ip) + return NULL; + + priv = nsvc->bind->priv; + return &priv->addr; +} + +/*! Return the socket address of the remote peer of a NS-VC. + * \param[in] nsvc NS-VC whose remote peer we want to know + * \return address of the remote peer; NULL in case of error */ +const struct osmo_sockaddr *gprs_ns2_ip_vc_remote(const struct gprs_ns2_vc *nsvc) +{ + struct priv_vc *priv; + + if (nsvc->bind->driver != &vc_driver_ip) + return NULL; + + priv = nsvc->priv; + return &priv->remote; +} + +/*! Compare the NS-VC with the given parameter + * \param[in] nsvc NS-VC to compare with + * \param[in] local The local address + * \param[in] remote The remote address + * \param[in] nsvci NS-VCI will only be used if the NS-VC in BLOCKRESET mode otherwise NS-VCI isn't applicable. + * \return true if the NS-VC has the same properties as given + */ +bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc, + const struct osmo_sockaddr *local, + const struct osmo_sockaddr *remote, + uint16_t nsvci) +{ + struct priv_vc *vpriv; + struct priv_bind *bpriv; + + if (nsvc->bind->driver != &vc_driver_ip) + return false; + + vpriv = nsvc->priv; + bpriv = nsvc->bind->priv; + + if (osmo_sockaddr_cmp(local, &bpriv->addr)) + return false; + + if (osmo_sockaddr_cmp(remote, &vpriv->remote)) + return false; + + if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) + if (nsvc->nsvci != nsvci) + return false; + + return true; +} + +/*! Return the locally bound socket address of the bind. + * \param[in] bind The bind whose local address we want to know + * \return address of the local bind */ +const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + + priv = bind->priv; + return &priv->addr; +} + +/*! Is the given bind an IP bind? */ +int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind) +{ + return (bind->driver == &vc_driver_ip); +} + +/*! Set the DSCP (TOS) bit value of the given bind. */ +int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp) +{ + struct priv_bind *priv; + int rc = 0; + + if (dscp < 0 || dscp > 63) + return -EINVAL; + + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + priv = bind->priv; + + if (dscp != priv->dscp) { + priv->dscp = dscp; + + rc = osmo_sock_set_dscp(osmo_iofd_get_fd(priv->iofd), dscp); + if (rc < 0) { + LOGBIND(bind, LOGL_ERROR, "Failed to set the DSCP to %u with ret(%d) errno(%d)\n", + dscp, rc, errno); + } + } + + return rc; +} + +/*! Set the socket priority of the given bind. */ +int gprs_ns2_ip_bind_set_priority(struct gprs_ns2_vc_bind *bind, uint8_t priority) +{ + struct priv_bind *priv; + int rc = 0; + + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + priv = bind->priv; + + if (priority != priv->priority) { + priv->priority = priority; + + rc = osmo_sock_set_priority(osmo_iofd_get_fd(priv->iofd), priority); + if (rc < 0) { + LOGBIND(bind, LOGL_ERROR, "Failed to set the priority to %u with ret(%d) errno(%d)\n", + priority, rc, errno); + } + } + + return rc; +} + + +/*! Count UDP binds compatible with remote */ +int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote) +{ + struct gprs_ns2_vc_bind *bind; + const struct osmo_sockaddr *sa; + int count = 0; + + llist_for_each_entry(bind, &nsi->binding, list) { + if (!gprs_ns2_is_ip_bind(bind)) + continue; + + sa = gprs_ns2_ip_bind_sockaddr(bind); + if (!sa) + continue; + + if (sa->u.sa.sa_family == remote->u.sa.sa_family) + count++; + } + + return count; +} + +/* return the matching bind by index */ +struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi, + struct osmo_sockaddr *remote, + int index) +{ + struct gprs_ns2_vc_bind *bind; + const struct osmo_sockaddr *sa; + int i = 0; + + llist_for_each_entry(bind, &nsi->binding, list) { + if (!gprs_ns2_is_ip_bind(bind)) + continue; + + sa = gprs_ns2_ip_bind_sockaddr(bind); + if (!sa) + continue; + + if (sa->u.sa.sa_family == remote->u.sa.sa_family) { + if (index == i) + return bind; + i++; + } + } + + return NULL; +} + +void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length) +{ + struct priv_bind *priv = bind->priv; + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + + osmo_iofd_set_txqueue_max_length(priv->iofd, max_length); +} + +/*! set the signalling and data weight for this bind + * \param[in] bind + * \param[in] signalling the signalling weight + * \param[in] data the data weight + */ +void gprs_ns2_ip_bind_set_sns_weight(struct gprs_ns2_vc_bind *bind, uint8_t signalling, uint8_t data) +{ + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + bind->sns_sig_weight = signalling; + bind->sns_data_weight = data; + ns2_sns_update_weights(bind); +} diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c new file mode 100644 index 00000000..9cd83c4f --- /dev/null +++ b/src/gb/gprs_ns2_vc_fsm.c @@ -0,0 +1,992 @@ +/*! \file gprs_ns2_vc_fsm.c + * NS virtual circuit FSM implementation + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures + * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and + * associated weights. In theory, the BSS then uses this to establish a full mesh + * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */ + +#include <errno.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gprs/protocol/gsm_08_16.h> + +#include "gprs_ns2_internal.h" + +#define S(x) (1 << (x)) + +struct gprs_ns2_vc_priv { + struct gprs_ns2_vc *nsvc; + /* how often the timer was triggered */ + int N; + /* The initiator is responsible to UNBLOCK the VC. The BSS is usually the initiator. + * It can change during runtime. The side which blocks an unblocked side.*/ + bool initiator; + bool initiate_block; + bool initiate_reset; + /* if unitdata is forwarded to the user */ + bool accept_unitdata; + + /* the alive counter is present in all states */ + struct { + struct osmo_timer_list timer; + enum ns2_timeout mode; + int N; + struct timespec timer_started; + } alive; +}; + + +/* The FSM covers both the VC with RESET/BLOCK and without RESET/BLOCK procedure.. + * + * With RESET/BLOCK, the state should follow: + * - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED + * + * Without RESET/BLOCK, the state should follow: + * - UNCONFIGURED -> RECOVERY -> UNBLOCKED + * + * The UNBLOCKED and TEST states are used to send ALIVE PDU using the timeout Tns-test and Tns-alive. + * UNBLOCKED -> TEST: on expire of Tns-Test, send Alive PDU. + * TEST -> UNBLOCKED: on receive of Alive_Ack PDU, go into UNBLOCKED. + * + * The RECOVERY state is used as intermediate, because a VC is only valid if it received an Alive ACK when + * not using RESET/BLOCK procedure. + */ + +enum gprs_ns2_vc_state { + GPRS_NS2_ST_UNCONFIGURED, + GPRS_NS2_ST_RESET, + GPRS_NS2_ST_BLOCKED, + GPRS_NS2_ST_UNBLOCKED, /* allows sending NS_UNITDATA */ + + GPRS_NS2_ST_RECOVERING, /* only used when not using RESET/BLOCK procedure */ +}; + +enum gprs_ns2_vc_event { + GPRS_NS2_EV_REQ_START, + + /* received messages */ + GPRS_NS2_EV_RX_RESET, + GPRS_NS2_EV_RX_RESET_ACK, + GPRS_NS2_EV_RX_UNBLOCK, + GPRS_NS2_EV_RX_UNBLOCK_ACK, + GPRS_NS2_EV_RX_BLOCK, + GPRS_NS2_EV_RX_BLOCK_ACK, + GPRS_NS2_EV_RX_ALIVE, + GPRS_NS2_EV_RX_ALIVE_ACK, + GPRS_NS2_EV_RX_STATUS, + + GPRS_NS2_EV_RX_UNITDATA, + + GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, /* called via vty for tests */ + GPRS_NS2_EV_REQ_OM_RESET, /* vty cmd: reset */ + GPRS_NS2_EV_REQ_OM_BLOCK, /* vty cmd: block */ + GPRS_NS2_EV_REQ_OM_UNBLOCK, /* vty cmd: unblock*/ + GPRS_NS2_EV_RX_BLOCK_FOREIGN, /* received a BLOCK over another NSVC */ +}; + +static const struct value_string ns2_vc_event_names[] = { + { GPRS_NS2_EV_REQ_START, "REQ-START" }, + { GPRS_NS2_EV_RX_RESET, "RX-RESET" }, + { GPRS_NS2_EV_RX_RESET_ACK, "RX-RESET_ACK" }, + { GPRS_NS2_EV_RX_UNBLOCK, "RX-UNBLOCK" }, + { GPRS_NS2_EV_RX_UNBLOCK_ACK, "RX-UNBLOCK_ACK" }, + { GPRS_NS2_EV_RX_BLOCK, "RX-BLOCK" }, + { GPRS_NS2_EV_RX_BLOCK_FOREIGN, "RX-BLOCK_FOREIGN" }, + { GPRS_NS2_EV_RX_BLOCK_ACK, "RX-BLOCK_ACK" }, + { GPRS_NS2_EV_RX_ALIVE, "RX-ALIVE" }, + { GPRS_NS2_EV_RX_ALIVE_ACK, "RX-ALIVE_ACK" }, + { GPRS_NS2_EV_RX_STATUS, "RX-STATUS" }, + { GPRS_NS2_EV_RX_UNITDATA, "RX-UNITDATA" }, + { GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, "REQ-FORCE_UNCONFIGURED" }, + { GPRS_NS2_EV_REQ_OM_RESET, "REQ-O&M-RESET"}, + { GPRS_NS2_EV_REQ_OM_BLOCK, "REQ-O&M-BLOCK"}, + { GPRS_NS2_EV_REQ_OM_UNBLOCK, "REQ-O&M-UNBLOCK"}, + { 0, NULL } +}; + +static inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + return priv->nsvc->nse->nsi; +} + +/* Start the NS-TEST procedure, either with transmitting a tx_alive, + * (start_tx_alive==true) or with starting tns-test */ +static void start_test_procedure(struct osmo_fsm_inst *fi, bool start_tx_alive) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi; + unsigned int tout_idx; + + if (osmo_timer_pending(&priv->alive.timer)) { + if (start_tx_alive) { + if (priv->alive.mode == NS_TOUT_TNS_ALIVE) + return; + } else { + if (priv->alive.mode == NS_TOUT_TNS_TEST) + return; + } + } + + priv->alive.N = 0; + + if (start_tx_alive) { + priv->alive.mode = NS_TOUT_TNS_ALIVE; + osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started); + ns2_tx_alive(priv->nsvc); + tout_idx = NS_TOUT_TNS_ALIVE; + } else { + priv->alive.mode = NS_TOUT_TNS_TEST; + tout_idx = NS_TOUT_TNS_TEST; + } + LOGPFSML(fi, LOGL_DEBUG, "Starting Tns-%s of %u seconds\n", + tout_idx == NS_TOUT_TNS_ALIVE ? "alive" : "test", nsi->timeout[tout_idx]); + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[tout_idx], 0); +} + +static void stop_test_procedure(struct gprs_ns2_vc_priv *priv) +{ + osmo_stat_item_set(osmo_stat_item_group_get_item(priv->nsvc->statg, NS_STAT_ALIVE_DELAY), 0); + osmo_timer_del(&priv->alive.timer); +} + +/* how many milliseconds have expired since the last alive timer start? */ +static int alive_timer_elapsed_ms(struct gprs_ns2_vc_priv *priv) +{ + struct timespec now, elapsed; + + if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0) + return 0; + + timespecsub(&now, &priv->alive.timer_started, &elapsed); + return elapsed.tv_sec * 1000 + (elapsed.tv_nsec / 1000000); +} + +/* we just received a NS-ALIVE-ACK; re-schedule after Tns-test */ +static void recv_test_procedure(struct osmo_fsm_inst *fi) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc *nsvc = priv->nsvc; + + /* ignoring ACKs without sending an ALIVE */ + if (priv->alive.mode != NS_TOUT_TNS_ALIVE) + return; + + priv->alive.mode = NS_TOUT_TNS_TEST; + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(nsvc->statg, NS_STAT_ALIVE_DELAY), + alive_timer_elapsed_ms(priv)); +} + + +static void alive_timeout_handler(void *data) +{ + struct osmo_fsm_inst *fi = data; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc_priv *priv = fi->priv; + + switch (priv->alive.mode) { + case NS_TOUT_TNS_TEST: + priv->alive.mode = NS_TOUT_TNS_ALIVE; + priv->alive.N = 0; + osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started); + ns2_tx_alive(priv->nsvc); + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); + break; + case NS_TOUT_TNS_ALIVE: + RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_ALIVE); + priv->alive.N++; + + if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { + /* retransmission */ + ns2_tx_alive(priv->nsvc); + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); + } else { + /* lost connection */ + if (priv->nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); + } + } + break; + default: + break; + } +} + + +static void ns2_st_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + stop_test_procedure(fi->priv); + ns2_nse_notify_unblocked(priv->nsvc, false); +} + +static void ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi; + + priv->initiate_reset = priv->initiate_block = priv->initiator; + priv->nsvc->om_blocked = false; + + switch (event) { + case GPRS_NS2_EV_REQ_START: + switch (priv->nsvc->mode) { + case GPRS_NS2_VC_MODE_ALIVE: + if (priv->nsvc->nse->dialect == GPRS_NS2_DIALECT_SNS) { + /* In IP-SNS, the NS-VC are assumed initially alive, until the alive + * procedure should fail at some future point */ + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE); + } + break; + case GPRS_NS2_VC_MODE_BLOCKRESET: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET); + break; + } + break; + default: + OSMO_ASSERT(0); + } +} + + +static void ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (old_state != GPRS_NS2_ST_RESET) + priv->N = 0; + + priv->accept_unitdata = false; + if (priv->initiate_reset) + ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION); + + stop_test_procedure(priv); + ns2_nse_notify_unblocked(priv->nsvc, false); +} + +static void ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (priv->initiate_reset) { + switch (event) { + case GPRS_NS2_EV_RX_RESET: + ns2_tx_reset_ack(priv->nsvc); + /* fall-through */ + case GPRS_NS2_EV_RX_RESET_ACK: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, + nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK); + break; + } + } else { + /* we are on the receiving end */ + switch (event) { + case GPRS_NS2_EV_RX_RESET: + ns2_tx_reset_ack(priv->nsvc); + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, + 0, 0); + break; + } + } +} + +static void ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (old_state != GPRS_NS2_ST_BLOCKED) { + priv->N = 0; + RATE_CTR_INC_NS(priv->nsvc, NS_CTR_BLOCKED); + } + + ns2_nse_notify_unblocked(priv->nsvc, false); + if (priv->nsvc->om_blocked) { + /* we are already blocked after a RESET */ + if (old_state == GPRS_NS2_ST_RESET) { + osmo_timer_del(&fi->timer); + } else { + ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL); + } + } else if (priv->initiate_block) { + ns2_tx_unblock(priv->nsvc); + } + + start_test_procedure(fi, true); +} + +static void ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (priv->nsvc->om_blocked) { + switch (event) { + case GPRS_NS2_EV_RX_BLOCK_ACK: + priv->accept_unitdata = false; + osmo_timer_del(&fi->timer); + break; + case GPRS_NS2_EV_RX_BLOCK: + ns2_tx_block_ack(priv->nsvc, NULL); + /* fall through */ + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent + * from the receiving nsvc */ + priv->accept_unitdata = false; + osmo_timer_del(&fi->timer); + break; + case GPRS_NS2_EV_RX_UNBLOCK: + priv->accept_unitdata = false; + ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL); + osmo_timer_add(&fi->timer); + break; + } + } else if (priv->initiate_block) { + switch (event) { + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the block ack will be sent by the rx NSVC */ + break; + case GPRS_NS2_EV_RX_BLOCK: + /* TODO: BLOCK is a UNBLOCK_NACK */ + ns2_tx_block_ack(priv->nsvc, NULL); + break; + case GPRS_NS2_EV_RX_UNBLOCK: + ns2_tx_unblock_ack(priv->nsvc); + /* fall through */ + case GPRS_NS2_EV_RX_UNBLOCK_ACK: + priv->accept_unitdata = true; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, + 0, NS_TOUT_TNS_TEST); + break; + } + } else { + /* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */ + switch (event) { + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the block ack will be sent by the rx NSVC */ + break; + case GPRS_NS2_EV_RX_BLOCK: + ns2_tx_block_ack(priv->nsvc, NULL); + break; + case GPRS_NS2_EV_RX_UNBLOCK: + ns2_tx_unblock_ack(priv->nsvc); + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, + 0, 0); + break; + } + } +} + +static void ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_vc *nsvc = priv->nsvc; + struct gprs_ns2_nse *nse = nsvc->nse; + + if (old_state != GPRS_NS2_ST_UNBLOCKED) { + RATE_CTR_INC_NS(nsvc, NS_CTR_UNBLOCKED); + osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change); + } + + priv->accept_unitdata = true; + ns2_nse_notify_unblocked(nsvc, true); + ns2_prim_status_ind(nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_RECOVERY); + + /* the closest interpretation of the spec would start Tns-test here first, + * and only send a NS-ALIVE after Tns-test has expired (i.e. setting the + * second argument to 'false'. However, being quick in detecting unavailability + * of a NS-VC seems like a good idea */ + start_test_procedure(fi, true); +} + +static void ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + switch (event) { + case GPRS_NS2_EV_RX_UNBLOCK: + ns2_tx_unblock_ack(priv->nsvc); + break; + case GPRS_NS2_EV_RX_BLOCK: + ns2_tx_block_ack(priv->nsvc, NULL); + /* fall through */ + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent + * from the receiving nsvc */ + priv->initiate_block = false; + priv->accept_unitdata = false; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, + 0, 2); + break; + } +} + +static void ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case GPRS_NS2_EV_RX_ALIVE_ACK: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0); + break; + } +} + +static void ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + + priv->alive.mode = NS_TOUT_TNS_TEST; + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0); + + if (old_state != GPRS_NS2_ST_RECOVERING) + priv->N = 0; + + start_test_procedure(fi, true); + ns2_nse_notify_unblocked(priv->nsvc, false); +} + +static const struct osmo_fsm_state ns2_vc_states[] = { + [GPRS_NS2_ST_UNCONFIGURED] = { + .in_event_mask = S(GPRS_NS2_EV_REQ_START), + .out_state_mask = S(GPRS_NS2_ST_RESET) | + S(GPRS_NS2_ST_RECOVERING) | + S(GPRS_NS2_ST_UNBLOCKED), + .name = "UNCONFIGURED", + .action = ns2_st_unconfigured, + .onenter = ns2_st_unconfigured_onenter, + }, + [GPRS_NS2_ST_RESET] = { + .in_event_mask = S(GPRS_NS2_EV_RX_RESET_ACK) | S(GPRS_NS2_EV_RX_RESET), + .out_state_mask = S(GPRS_NS2_ST_RESET) | + S(GPRS_NS2_ST_BLOCKED) | + S(GPRS_NS2_ST_UNCONFIGURED), + .name = "RESET", + .action = ns2_st_reset, + .onenter = ns2_st_reset_onenter, + }, + [GPRS_NS2_ST_BLOCKED] = { + .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_BLOCK_ACK) | + S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) | + S(GPRS_NS2_EV_RX_BLOCK_FOREIGN), + .out_state_mask = S(GPRS_NS2_ST_RESET) | + S(GPRS_NS2_ST_UNBLOCKED) | + S(GPRS_NS2_ST_BLOCKED) | + S(GPRS_NS2_ST_UNCONFIGURED), + .name = "BLOCKED", + .action = ns2_st_blocked, + .onenter = ns2_st_blocked_onenter, + }, + [GPRS_NS2_ST_UNBLOCKED] = { + .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) | + S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_BLOCK_FOREIGN), + .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_RECOVERING) | + S(GPRS_NS2_ST_BLOCKED) | + S(GPRS_NS2_ST_UNCONFIGURED), + .name = "UNBLOCKED", + .action = ns2_st_unblocked, + .onenter = ns2_st_unblocked_on_enter, + }, + + /* ST_RECOVERING is only used on VC without RESET/BLOCK */ + [GPRS_NS2_ST_RECOVERING] = { + .in_event_mask = S(GPRS_NS2_EV_RX_ALIVE_ACK), + .out_state_mask = S(GPRS_NS2_ST_RECOVERING) | + S(GPRS_NS2_ST_UNBLOCKED) | + S(GPRS_NS2_ST_UNCONFIGURED), + .name = "RECOVERING", + .action = ns2_st_alive, + .onenter = ns2_st_alive_onenter, + }, +}; + +static int ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc_priv *priv = fi->priv; + + switch (fi->state) { + case GPRS_NS2_ST_RESET: + if (priv->initiate_reset) { + RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_RESET); + priv->N++; + if (priv->N <= nsi->timeout[NS_TOUT_TNS_RESET_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } else { + priv->N = 0; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } + } + break; + case GPRS_NS2_ST_BLOCKED: + if (priv->initiate_block) { + priv->N++; + if (priv->nsvc->om_blocked) { + if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + } else { + /* 7.2 stop accepting data when BLOCK PDU not responded */ + priv->accept_unitdata = false; + } + } else { + if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } + } + } + break; + case GPRS_NS2_ST_RECOVERING: + if (priv->initiate_reset) { + priv->N++; + if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0); + } else { + priv->N = 0; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0); + } + break; + } + break; + } + return 0; +} + +static void ns2_recv_unitdata(struct osmo_fsm_inst *fi, + struct msgb *msg) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct osmo_gprs_ns2_prim nsp = {}; + uint16_t bvci; + + if (msgb_l2len(msg) < sizeof(*nsh) + 3) { + msgb_free(msg); + return; + } + + /* TODO: 7.1: For an IP sub-network, an NS-UNITDATA PDU + * for a PTP BVC may indicate a request to change the IP endpoint + * and/or a response to a change in the IP endpoint. */ + + /* TODO: nsh->data[0] -> C/R only valid in IP SNS */ + bvci = nsh->data[1] << 8 | nsh->data[2]; + + msg->l3h = &nsh->data[3]; + nsp.bvci = bvci; + nsp.nsei = priv->nsvc->nse->nsei; + + /* 10.3.9 NS SDU Control Bits */ + if (nsh->data[0] & 0x1) + nsp.u.unitdata.change = GPRS_NS2_ENDPOINT_REQUEST_CHANGE; + + osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, + PRIM_OP_INDICATION, msg); + nsi->cb(&nsp.oph, nsi->cb_data); +} + +static void ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi, + uint32_t event, + void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct tlv_parsed *tp; + struct msgb *msg = data; + uint8_t cause; + + switch (event) { + case GPRS_NS2_EV_REQ_OM_RESET: + if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) + break; + /* move the FSM into reset */ + if (fi->state != GPRS_NS2_ST_RESET) { + priv->initiate_reset = true; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET); + } + break; + case GPRS_NS2_EV_RX_RESET: + if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) + break; + + /* move the FSM into reset */ + if (fi->state != GPRS_NS2_ST_RESET) { + priv->initiate_reset = false; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET); + } + /* pass the event down into FSM action */ + ns2_st_reset(fi, event, data); + break; + case GPRS_NS2_EV_RX_ALIVE: + switch (fi->state) { + case GPRS_NS2_ST_UNCONFIGURED: + case GPRS_NS2_ST_RESET: + /* ignore ALIVE */ + break; + default: + ns2_tx_alive_ack(priv->nsvc); + } + break; + case GPRS_NS2_EV_RX_ALIVE_ACK: + /* for VCs without RESET/BLOCK/UNBLOCK, the connections comes after ALIVE_ACK unblocked */ + if (fi->state == GPRS_NS2_ST_RECOVERING) + ns2_st_alive(fi, event, data); + else + recv_test_procedure(fi); + break; + case GPRS_NS2_EV_RX_UNITDATA: + /* UNITDATA has to handle the release of msg. + * If send upwards (gprs_ns2_recv_unitdata) it must NOT free + * the msg, the upper layer has to do it. + * Otherwise the msg must be freed. + */ + + LOG_NS_DATA(priv->nsvc, "Rx", NS_PDUT_UNITDATA, LOGL_INFO, "\n"); + switch (fi->state) { + case GPRS_NS2_ST_BLOCKED: + /* 7.2.1: the BLOCKED_ACK might be lost */ + if (priv->accept_unitdata) { + ns2_recv_unitdata(fi, msg); + return; + } + + ns2_tx_status(priv->nsvc, + NS_CAUSE_NSVC_BLOCKED, + 0, msg, NULL); + break; + /* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */ + case GPRS_NS2_ST_RECOVERING: + case GPRS_NS2_ST_UNBLOCKED: + ns2_recv_unitdata(fi, msg); + return; + } + + msgb_free(msg); + break; + case GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED: + if (fi->state != GPRS_NS2_ST_UNCONFIGURED) { + /* Force the NSVC back to its initial state */ + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNCONFIGURED, 0, 0); + return; + } + break; + case GPRS_NS2_EV_REQ_OM_BLOCK: + /* vty cmd: block */ + priv->initiate_block = true; + priv->nsvc->om_blocked = true; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + break; + case GPRS_NS2_EV_REQ_OM_UNBLOCK: + /* vty cmd: unblock*/ + if (!priv->nsvc->om_blocked) + return; + priv->nsvc->om_blocked = false; + if (fi->state == GPRS_NS2_ST_BLOCKED) + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + break; + case GPRS_NS2_EV_RX_STATUS: + tp = data; + cause = tlvp_val8(tp, NS_IE_CAUSE, 0); + switch (cause) { + case NS_CAUSE_NSVC_BLOCKED: + if (fi->state != GPRS_NS2_ST_BLOCKED) { + LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported blocked state.\n"); + priv->initiate_block = false; + priv->accept_unitdata = false; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + } + break; + case NS_CAUSE_NSVC_UNKNOWN: + if (fi->state != GPRS_NS2_ST_RESET && fi->state != GPRS_NS2_ST_UNCONFIGURED) { + LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported unknown nsvc.\n"); + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } + break; + } + + break; + } +} + +static void ns2_vc_fsm_clean(struct osmo_fsm_inst *fi, + enum osmo_fsm_term_cause cause) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + osmo_timer_del(&priv->alive.timer); +} + +static struct osmo_fsm ns2_vc_fsm = { + .name = "GPRS-NS2-VC", + .states = ns2_vc_states, + .num_states = ARRAY_SIZE(ns2_vc_states), + .allstate_event_mask = S(GPRS_NS2_EV_RX_UNITDATA) | + S(GPRS_NS2_EV_RX_RESET) | + S(GPRS_NS2_EV_RX_ALIVE) | + S(GPRS_NS2_EV_RX_ALIVE_ACK) | + S(GPRS_NS2_EV_RX_STATUS) | + S(GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED) | + S(GPRS_NS2_EV_REQ_OM_RESET) | + S(GPRS_NS2_EV_REQ_OM_BLOCK) | + S(GPRS_NS2_EV_REQ_OM_UNBLOCK), + .allstate_action = ns2_vc_fsm_allstate_action, + .cleanup = ns2_vc_fsm_clean, + .timer_cb = ns2_vc_fsm_timer_cb, + .event_names = ns2_vc_event_names, + .pre_term = NULL, + .log_subsys = DLNS, +}; + +/*! + * \brief gprs_ns2_vc_fsm_alloc + * \param ctx + * \param vc + * \param id a char representation of the virtual curcuit + * \param initiator initiator is the site which starts the connection. Usually the BSS. + * \return NULL on error, otherwise the fsm + */ +struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc, + const char *id, bool initiator) +{ + struct osmo_fsm_inst *fi; + struct gprs_ns2_vc_priv *priv; + + fi = osmo_fsm_inst_alloc(&ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id); + if (!fi) + return fi; + + nsvc->fi = fi; + priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv); + priv->nsvc = nsvc; + priv->initiator = initiator; + + osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi); + + return fi; +} + +/*! Start a NS-VC FSM. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc) +{ + /* allows to call this function even for started nsvc by gprs_ns2_start_alive_all_nsvcs */ + if (nsvc->fi->state == GPRS_NS2_ST_UNCONFIGURED) + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_START, NULL); + return 0; +} + +/*! Reset a NS-VC FSM. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc) +{ + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, NULL); +} + +/*! Block a NS-VC. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_block(struct gprs_ns2_vc *nsvc) +{ + struct gprs_ns2_vc_priv *priv = nsvc->fi->priv; + if (priv->nsvc->om_blocked) + return -EALREADY; + + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_BLOCK, NULL); +} + +/*! Unblock a NS-VC. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_unblock(struct gprs_ns2_vc *nsvc) +{ + struct gprs_ns2_vc_priv *priv = nsvc->fi->priv; + if (!priv->nsvc->om_blocked) + return -EALREADY; + + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_UNBLOCK, NULL); +} + +/*! Reset a NS-VC. + * \param nsvc the virtual circuit + * \return 0 on success; negative on error */ +int ns2_vc_reset(struct gprs_ns2_vc *nsvc) +{ + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_RESET, NULL); +} + +/*! entry point for messages from the driver/VL + * \param nsvc virtual circuit on which the message was received + * \param msg message that was received + * \param tp parsed TLVs of the received message + * \return 0 on success; negative on error */ +int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct gprs_ns2_vc *target_nsvc = nsvc; + struct osmo_fsm_inst *fi = nsvc->fi; + int rc = 0; + uint8_t cause; + uint16_t nsei, nsvci; + + /* TODO: 7.2: on UNBLOCK/BLOCK: check if NS-VCI is correct, + * if not answer STATUS with "NS-VC unknown" */ + /* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */ + + if (ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) { + /* don't answer on a STATUS with a STATUS */ + if (nsh->pdu_type != NS_PDUT_STATUS) { + rc = ns2_tx_status(nsvc, cause, 0, msg, NULL); + goto out; + } + } + + if (TLVP_PRESENT(tp, NS_IE_NSEI)) { + nsei = tlvp_val16be(tp, NS_IE_NSEI); + if (nsei != nsvc->nse->nsei) { + /* 48.016 § 7.3.1 send, RESET_ACK to wrong NSVCI + ignore */ + if (nsh->pdu_type == NS_PDUT_RESET) + ns2_tx_reset_ack(nsvc); + + LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSEI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nse->nsei, nsei); + goto out; + } + } + + if (nsvc->nsvci_is_valid && TLVP_PRESENT(tp, NS_IE_VCI)) { + nsvci = tlvp_val16be(tp, NS_IE_VCI); + if (nsvci != nsvc->nsvci) { + /* 48.016 § 7.3.1 send RESET_ACK to wrong NSVCI + ignore */ + if (nsh->pdu_type == NS_PDUT_RESET) { + ns2_tx_reset_ack(nsvc); + LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nsvci, nsvci); + goto out; + } else if (nsh->pdu_type == NS_PDUT_BLOCK || nsh->pdu_type == NS_PDUT_STATUS) { + /* this is a PDU received over a NSVC and reports a status/block for another NSVC */ + target_nsvc = gprs_ns2_nsvc_by_nsvci(nsvc->nse->nsi, nsvci); + if (!target_nsvc) { + LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for unknown NSVC (NSVCI %d)\n", + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci); + if (nsh->pdu_type == NS_PDUT_BLOCK) + ns2_tx_status(nsvc, NS_CAUSE_NSVC_UNKNOWN, 0, msg, &nsvci); + goto out; + } + + if (target_nsvc->nse != nsvc->nse) { + LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for a NSVC (NSVCI %d) but it belongs to a different NSE!\n", + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci); + goto out; + } + + /* the status/block will be passed to the nsvc/target nsvc in the switch */ + } else { + LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI=%05u. Ignoring PDU.\n", nsvci); + goto out; + } + } + } + + switch (nsh->pdu_type) { + case NS_PDUT_RESET: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET, tp); + break; + case NS_PDUT_RESET_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET_ACK, tp); + break; + case NS_PDUT_BLOCK: + if (target_nsvc != nsvc) { + osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_BLOCK_FOREIGN, tp); + ns2_tx_block_ack(nsvc, &nsvci); + } else { + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK, tp); + } + break; + case NS_PDUT_BLOCK_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK_ACK, tp); + break; + case NS_PDUT_UNBLOCK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK, tp); + break; + case NS_PDUT_UNBLOCK_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK_ACK, tp); + break; + case NS_PDUT_ALIVE: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE, tp); + break; + case NS_PDUT_ALIVE_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE_ACK, tp); + break; + case NS_PDUT_UNITDATA: + /* UNITDATA have to free msg because it might send the msg layer upwards */ + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNITDATA, msg); + return 0; + case NS_PDUT_STATUS: + osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_STATUS, tp); + break; + default: + LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei, + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); + rc = -EINVAL; + break; + } + +out: + msgb_free(msg); + + return rc; +} + +/*! is the given NS-VC unblocked? */ +int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc) +{ + return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED); +} + +/* initialize osmo_ctx on main tread */ +static __attribute__((constructor)) void on_dso_load_ctx(void) +{ + OSMO_ASSERT(osmo_fsm_register(&ns2_vc_fsm) == 0); +} diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c new file mode 100644 index 00000000..32de49d4 --- /dev/null +++ b/src/gb/gprs_ns2_vty.c @@ -0,0 +1,2351 @@ +/*! \file gprs_ns2_vty.c + * VTY interface for our GPRS Networks Service (NS) implementation. */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Alexander Couzens <lynxis@fe80.eu> + * (C) 2021 by Harald Welte <laforge@osmocom.org> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> + +#include <arpa/inet.h> +#include <net/if.h> + +#include <osmocom/core/byteswap.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/select.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/core/socket.h> +#include <osmocom/gprs/frame_relay.h> +#include <osmocom/gprs/gprs_ns2.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/vty.h> + +#include "gprs_ns2_internal.h" + +#define SHOW_NS_STR "Display information about the NS protocol\n" +#define NSVCI_STR "NS Virtual Connection ID (NS-VCI)\n" +#define DLCI_STR "Data Link connection identifier\n" + +static struct gprs_ns2_inst *vty_nsi = NULL; +static struct osmo_fr_network *vty_fr_network = NULL; +static struct llist_head binds; +static struct llist_head nses; +static struct llist_head ip_sns_default_binds; + +struct vty_bind { + struct llist_head list; + const char *name; + enum gprs_ns2_ll ll; + int dscp; + uint8_t priority; + bool accept_ipaccess; + bool accept_sns; + uint8_t ip_sns_sig_weight; + uint8_t ip_sns_data_weight; +}; + +struct vty_nse { + struct llist_head list; + uint16_t nsei; + /* list of binds which are valid for this nse. Only IP-SNS uses this + * to allow `no listen ..` in the bind context. So "half" created binds are valid for + * IP-SNS. This allows changing the bind ip without modifying all NSEs afterwards */ + struct llist_head binds; +}; + +/* used by IP-SNS to connect multiple vty_nse_bind to a vty_nse */ +struct vty_nse_bind { + struct llist_head list; + struct vty_bind *vbind; +}; + +/* TODO: this should into osmo timer */ +const struct value_string gprs_ns_timer_strs[] = { + { NS_TOUT_TNS_BLOCK, TNS_BLOCK_STR }, + { NS_TOUT_TNS_BLOCK_RETRIES, TNS_BLOCK_RETRIES_STR }, + { NS_TOUT_TNS_RESET, TNS_RESET_STR }, + { NS_TOUT_TNS_RESET_RETRIES, TNS_RESET_RETRIES_STR }, + { NS_TOUT_TNS_TEST, TNS_TEST_STR }, + { NS_TOUT_TNS_ALIVE, TNS_ALIVE_STR }, + { NS_TOUT_TNS_ALIVE_RETRIES, TNS_ALIVE_RETRIES_STR }, + { NS_TOUT_TSNS_PROV, TSNS_PROV_STR }, + { NS_TOUT_TSNS_SIZE_RETRIES, TSNS_SIZE_RETRIES_STR }, + { NS_TOUT_TSNS_CONFIG_RETRIES, TSNS_CONFIG_RETRIES_STR }, + { NS_TOUT_TSNS_PROCEDURES_RETRIES, TSNS_PROCEDURES_RETRIES_STR }, + { 0, NULL } +}; + +const struct value_string vty_fr_role_names[] = { + { FR_ROLE_USER_EQUIPMENT, "fr" }, + { FR_ROLE_NETWORK_EQUIPMENT, "frnet" }, + { 0, NULL } +}; + +const struct value_string vty_ll_names[] = { + { GPRS_NS2_LL_FR, "fr" }, + { GPRS_NS2_LL_FR_GRE, "frgre" }, + { GPRS_NS2_LL_UDP, "udp" }, + { 0, NULL } +}; + +static struct vty_bind *vty_bind_by_name(const char *name) +{ + struct vty_bind *vbind; + llist_for_each_entry(vbind, &binds, list) { + if (!strcmp(vbind->name, name)) + return vbind; + } + return NULL; +} + +static struct vty_bind *vty_bind_alloc(const char *name) +{ + struct vty_bind *vbind = talloc_zero(vty_nsi, struct vty_bind); + if (!vbind) + return NULL; + + vbind->name = talloc_strdup(vty_nsi, name); + if (!vbind->name) { + talloc_free(vbind); + return NULL; + } + + vbind->ip_sns_sig_weight = 1; + vbind->ip_sns_data_weight = 1; + llist_add_tail(&vbind->list, &binds); + return vbind; +} + +static void vty_bind_free(struct vty_bind *vbind) +{ + if (!vbind) + return; + + llist_del(&vbind->list); + talloc_free(vbind); +} + +static struct vty_nse *vty_nse_by_nsei(uint16_t nsei) +{ + struct vty_nse *vnse; + llist_for_each_entry(vnse, &nses, list) { + if (vnse->nsei == nsei) + return vnse; + } + return NULL; +} + +static struct vty_nse *vty_nse_alloc(uint16_t nsei) +{ + struct vty_nse *vnse = talloc_zero(vty_nsi, struct vty_nse); + if (!vnse) + return NULL; + + vnse->nsei = nsei; + INIT_LLIST_HEAD(&vnse->binds); + llist_add_tail(&vnse->list, &nses); + return vnse; +} + +static void vty_nse_free(struct vty_nse *vnse) +{ + if (!vnse) + return; + + llist_del(&vnse->list); + /* all vbind of the nse will be freed by talloc */ + talloc_free(vnse); +} + +static int vty_nse_add_vbind(struct vty_nse *vnse, struct vty_bind *vbind) +{ + struct vty_nse_bind *vnse_bind; + + if (vbind->ll != GPRS_NS2_LL_UDP) + return -EINVAL; + + llist_for_each_entry(vnse_bind, &vnse->binds, list) { + if (vnse_bind->vbind == vbind) + return -EALREADY; + } + + vnse_bind = talloc(vnse, struct vty_nse_bind); + if (!vnse_bind) + return -ENOMEM; + vnse_bind->vbind = vbind; + + llist_add_tail(&vnse_bind->list, &vnse->binds); + return 0; +} + +static int vty_nse_remove_vbind(struct vty_nse *vnse, struct vty_bind *vbind) +{ + struct vty_nse_bind *vnse_bind, *tmp; + if (vbind->ll != GPRS_NS2_LL_UDP) + return -EINVAL; + + llist_for_each_entry_safe(vnse_bind, tmp, &vnse->binds, list) { + if (vnse_bind->vbind == vbind) { + llist_del(&vnse_bind->list); + talloc_free(vnse_bind); + return 0; + } + } + + return -ENOENT; +} + +/* check if the NSE still has SNS configuration */ +static bool vty_nse_check_sns(struct gprs_ns2_nse *nse) { + struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei); + + int count = gprs_ns2_sns_count(nse); + if (count > 0) { + /* there are other sns endpoints */ + return true; + } + + if (!vnse) + return false; + + if (llist_empty(&vnse->binds)) + return false; + + return true; +} + +static struct cmd_node ns_node = { + L_NS_NODE, + "%s(config-ns)# ", + 1, +}; + +DEFUN(cfg_ns, cfg_ns_cmd, + "ns", + "Configure the GPRS Network Service") +{ + vty->node = L_NS_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_timer, cfg_ns_timer_cmd, + "timer " NS_TIMERS " <0-65535>", + "Network Service Timer\n" + NS_TIMERS_HELP "Timer Value\n") +{ + int idx = get_string_value(gprs_ns_timer_strs, argv[0]); + int val = atoi(argv[1]); + + if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout)) + return CMD_WARNING; + + vty_nsi->timeout[idx] = val; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_nsei, cfg_ns_nsei_cmd, + "nse <0-65535> [ip-sns-role-sgsn]", + "Persistent NS Entity\n" + "NS Entity ID (NSEI)\n" + "Create NSE in SGSN role (default: BSS)\n" + ) +{ + struct gprs_ns2_nse *nse; + struct vty_nse *vnse; + uint16_t nsei = atoi(argv[0]); + bool sgsn_role = false; + bool free_vnse = false; + if (argc > 1 && !strcmp(argv[1], "ip-sns-role-sgsn")) + sgsn_role = true; + + vnse = vty_nse_by_nsei(nsei); + if (!vnse) { + vnse = vty_nse_alloc(nsei); + if (!vnse) { + vty_out(vty, "Failed to create vty NSE!%s", VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + free_vnse = true; + } + + nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei); + if (!nse) { + nse = gprs_ns2_create_nse2(vty_nsi, nsei, GPRS_NS2_LL_UNDEF, GPRS_NS2_DIALECT_UNDEF, + sgsn_role); + if (!nse) { + vty_out(vty, "Failed to create NSE!%s", VTY_NEWLINE); + goto err; + } + nse->persistent = true; + } + + if (!nse->persistent) { + /* TODO: should the dynamic NSE removed? */ + vty_out(vty, "A dynamic NSE with the specified NSEI already exists%s", VTY_NEWLINE); + goto err; + } + + vty->node = L_NS_NSE_NODE; + vty->index = nse; + + return CMD_SUCCESS; + +err: + if (free_vnse) + talloc_free(vnse); + + return CMD_ERR_INCOMPLETE; +} + +DEFUN(cfg_no_ns_nsei, cfg_no_ns_nsei_cmd, + "no nse <0-65535>", + NO_STR + "Delete a Persistent NS Entity\n" + "NS Entity ID (NSEI)\n" + ) +{ + struct gprs_ns2_nse *nse; + struct vty_nse *vnse; + uint16_t nsei = atoi(argv[0]); + + nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei); + if (!nse) { + vty_out(vty, "Can not find NS Entity %s%s", argv[0], VTY_NEWLINE); + return CMD_ERR_NOTHING_TODO; + } + + if (!nse->persistent) { + vty_out(vty, "Ignoring non-persistent NS Entity%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "Deleting NS Entity %u%s", nse->nsei, VTY_NEWLINE); + gprs_ns2_free_nse(nse); + + vnse = vty_nse_by_nsei(nsei); + vty_nse_free(vnse); + + return CMD_SUCCESS; +} + +/* TODO: add fr/gre */ +DEFUN(cfg_ns_bind, cfg_ns_bind_cmd, + "bind (fr|udp) ID", + "Configure local Bind\n" + "Frame Relay\n" "UDP/IP\n" + "Unique identifier for this bind (to reference from NS-VCs, NSEs, ...)\n" + ) +{ + const char *nstype = argv[0]; + const char *name = argv[1]; + struct vty_bind *vbind; + enum gprs_ns2_ll ll; + int rc; + + rc = get_string_value(vty_ll_names, nstype); + if (rc < 0) + return CMD_WARNING; + ll = (enum gprs_ns2_ll) rc; + + if (!osmo_identifier_valid(name)) { + vty_out(vty, "Invalid ID. The ID should be only alphanumeric.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vbind = vty_bind_by_name(name); + if (vbind) { + if (vbind->ll != ll) { + vty_out(vty, "A bind with the specified ID already exists with a different type (fr|frgre|udp)!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } else { + vbind = vty_bind_alloc(name); + if (!vbind) { + vty_out(vty, "Can not create bind - out of memory%s", VTY_NEWLINE); + return CMD_WARNING; + } + vbind->ll = ll; + } + + vty->index = vbind; + vty->node = L_NS_BIND_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_bind, cfg_no_ns_bind_cmd, + "no bind ID", + NO_STR + "Delete a bind\n" + "Unique identifier for this bind\n" + ) +{ + struct vty_bind *vbind; + struct gprs_ns2_vc_bind *bind; + const char *name = argv[0]; + + vbind = vty_bind_by_name(name); + if (!vbind) { + vty_out(vty, "bind %s does not exist!%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + vty_bind_free(vbind); + bind = gprs_ns2_bind_by_name(vty_nsi, name); + if (bind) + gprs_ns2_free_bind(bind); + return CMD_SUCCESS; +} + + +static void config_write_vbind(struct vty *vty, struct vty_bind *vbind) +{ + struct gprs_ns2_vc_bind *bind; + const struct osmo_sockaddr *addr; + struct osmo_sockaddr_str addr_str; + const char *netif, *frrole_str, *llstr; + enum osmo_fr_role frrole; + + llstr = get_value_string_or_null(vty_ll_names, vbind->ll); + if (!llstr) + return; + vty_out(vty, " bind %s %s%s", llstr, vbind->name, VTY_NEWLINE); + + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + switch (vbind->ll) { + case GPRS_NS2_LL_FR: + if (bind) { + netif = gprs_ns2_fr_bind_netif(bind); + if (!netif) + return; + frrole = gprs_ns2_fr_bind_role(bind); + if ((int) frrole == -1) + return; + frrole_str = get_value_string_or_null(vty_fr_role_names, frrole); + if (netif && frrole_str) + vty_out(vty, " fr %s %s%s", netif, frrole_str, VTY_NEWLINE); + } + break; + case GPRS_NS2_LL_UDP: + if (bind) { + addr = gprs_ns2_ip_bind_sockaddr(bind); + if (!osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas)) { + vty_out(vty, " listen %s %u%s", addr_str.ip, addr_str.port, + VTY_NEWLINE); + } + } + if (vbind->accept_ipaccess) + vty_out(vty, " accept-ipaccess%s", VTY_NEWLINE); + if (vbind->accept_sns) + vty_out(vty, " accept-dynamic-ip-sns%s", VTY_NEWLINE); + if (vbind->dscp) + vty_out(vty, " dscp %u%s", vbind->dscp, VTY_NEWLINE); + if (vbind->priority) + vty_out(vty, " socket-priority %u%s", vbind->priority, VTY_NEWLINE); + vty_out(vty, " ip-sns signalling-weight %u data-weight %u%s", + vbind->ip_sns_sig_weight, vbind->ip_sns_data_weight, VTY_NEWLINE); + break; + default: + return; + } +} + +static void config_write_nsvc(struct vty *vty, const struct gprs_ns2_vc *nsvc) +{ + const char *netif; + uint16_t dlci; + const struct osmo_sockaddr *addr; + struct osmo_sockaddr_str addr_str; + + switch (nsvc->nse->ll) { + case GPRS_NS2_LL_UNDEF: + break; + case GPRS_NS2_LL_UDP: + switch (nsvc->nse->dialect) { + case GPRS_NS2_DIALECT_IPACCESS: + addr = gprs_ns2_ip_vc_remote(nsvc); + if (!addr) + break; + if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas)) + break; + vty_out(vty, " nsvc ipa %s %s %u nsvci %u%s", + nsvc->bind->name, addr_str.ip, addr_str.port, + nsvc->nsvci, VTY_NEWLINE); + break; + case GPRS_NS2_DIALECT_STATIC_ALIVE: + addr = gprs_ns2_ip_vc_remote(nsvc); + if (!addr) + break; + if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas)) + break; + vty_out(vty, " nsvc udp %s %s %u%s", + nsvc->bind->name, addr_str.ip, addr_str.port, VTY_NEWLINE); + break; + default: + break; + } + break; + case GPRS_NS2_LL_FR: + netif = gprs_ns2_fr_bind_netif(nsvc->bind); + if (!netif) + break; + dlci = gprs_ns2_fr_nsvc_dlci(nsvc); + if (!dlci) + break; + OSMO_ASSERT(nsvc->nsvci_is_valid); + vty_out(vty, " nsvc fr %s dlci %u nsvci %u%s", + netif, dlci, nsvc->nsvci, VTY_NEWLINE); + break; + case GPRS_NS2_LL_FR_GRE: + break; + } +} + +static void _config_write_ns_nse(struct vty *vty, struct gprs_ns2_nse *nse) +{ + struct gprs_ns2_vc *nsvc; + struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei); + struct vty_nse_bind *vbind; + + OSMO_ASSERT(vnse); + + vty_out(vty, " nse %u%s%s", nse->nsei, + nse->ip_sns_role_sgsn ? " ip-sns-role-sgsn" : "", VTY_NEWLINE); + switch (nse->dialect) { + case GPRS_NS2_DIALECT_SNS: + ns2_sns_write_vty(vty, nse); + llist_for_each_entry(vbind, &vnse->binds, list) { + vty_out(vty, " ip-sns-bind %s%s", vbind->vbind->name, VTY_NEWLINE); + } + break; + default: + llist_for_each_entry(nsvc, &nse->nsvc, list) { + config_write_nsvc(vty, nsvc); + } + break; + } +} + +static int config_write_ns_nse(struct vty *vty) +{ + struct gprs_ns2_nse *nse; + + llist_for_each_entry(nse, &vty_nsi->nse, list) { + if (!nse->persistent) + continue; + + _config_write_ns_nse(vty, nse); + } + + return 0; +} + +static int config_write_ns_bind(struct vty *vty) +{ + struct vty_bind *vbind; + + llist_for_each_entry(vbind, &binds, list) { + config_write_vbind(vty, vbind); + } + + return 0; +} + +static int config_write_ns(struct vty *vty) +{ + struct vty_nse_bind *vbind; + unsigned int i; + int ret; + + vty_out(vty, "ns%s", VTY_NEWLINE); + + for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++) + vty_out(vty, " timer %s %u%s", + get_value_string(gprs_ns_timer_strs, i), + vty_nsi->timeout[i], VTY_NEWLINE); + + if (vty_nsi->txqueue_max_length != NS_DEFAULT_TXQUEUE_MAX_LENGTH) + vty_out(vty, " txqueue-max-length %u%s", vty_nsi->txqueue_max_length, VTY_NEWLINE); + + ret = config_write_ns_bind(vty); + if (ret) + return ret; + + llist_for_each_entry(vbind, &ip_sns_default_binds, list) { + vty_out(vty, " ip-sns-default bind %s%s", vbind->vbind->name, VTY_NEWLINE); + } + + ret = config_write_ns_nse(vty); + if (ret) + return ret; + + return 0; +} + + +static struct cmd_node ns_bind_node = { + L_NS_BIND_NODE, + "%s(config-ns-bind)# ", + 1, +}; + +DEFUN(cfg_ns_bind_listen, cfg_ns_bind_listen_cmd, + "listen " VTY_IPV46_CMD " <1-65535>", + "Configure local IP + Port of this bind\n" + "Local IPv4 Address\n" "Local IPv6 Address\n" + "Local UDP Port\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + int rc; + const char *addr_str = argv[0]; + unsigned int port = atoi(argv[1]); + struct osmo_sockaddr_str sockaddr_str; + struct osmo_sockaddr sockaddr; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "listen can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_from_str(&sockaddr_str, addr_str, port)) { + vty_out(vty, "Can not parse the Address %s %s%s", argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + osmo_sockaddr_str_to_sockaddr(&sockaddr_str, &sockaddr.u.sas); + if (gprs_ns2_ip_bind_by_sockaddr(vty_nsi, &sockaddr)) { + vty_out(vty, "A bind with the specified address already exists!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + rc = gprs_ns2_ip_bind(vty_nsi, vbind->name, &sockaddr, vbind->dscp, &bind); + if (rc != 0) { + vty_out(vty, "Failed to create the bind (rc %d)!%s", rc, VTY_NEWLINE); + return CMD_WARNING; + } + + bind->accept_ipaccess = vbind->accept_ipaccess; + bind->accept_sns = vbind->accept_sns; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_bind_listen, cfg_no_ns_bind_listen_cmd, + "no listen", + NO_STR + "Delete a IP/Port assignment\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "no listen can be only used with UDP bind%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (!bind) + return CMD_ERR_NOTHING_TODO; + + OSMO_ASSERT(bind->ll == GPRS_NS2_LL_UDP); + gprs_ns2_free_bind(bind); + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_bind_dscp, cfg_ns_bind_dscp_cmd, + "dscp <0-63>", + "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n") +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + uint16_t dscp = atoi(argv[0]); + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "dscp can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->dscp = dscp; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + gprs_ns2_ip_bind_set_dscp(bind, dscp); + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_bind_dscp, cfg_no_ns_bind_dscp_cmd, + "no dscp", + "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n") +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + uint16_t dscp = 0; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "dscp can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->dscp = dscp; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + gprs_ns2_ip_bind_set_dscp(bind, dscp); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_bind_priority, cfg_ns_bind_priority_cmd, + "socket-priority <0-255>", + "Set socket priority on the UDP socket\n" "Priority Value (>6 requires CAP_NET_ADMIN)\n") +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + uint8_t prio = atoi(argv[0]); + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "dscp can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->priority = prio; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + gprs_ns2_ip_bind_set_priority(bind, prio); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_bind_ipaccess, cfg_ns_bind_ipaccess_cmd, + "accept-ipaccess", + "Allow to create dynamic NS Entity by NS Reset PDU on UDP (ip.access style)\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "accept-ipaccess can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->accept_ipaccess = true; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + bind->accept_ipaccess = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_bind_ipaccess, cfg_no_ns_bind_ipaccess_cmd, + "no accept-ipaccess", + NO_STR + "Reject NS Reset PDU on UDP (ip.access style)\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "no accept-ipaccess can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->accept_ipaccess = false; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + bind->accept_ipaccess = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_bind_accept_sns, cfg_ns_bind_accept_sns_cmd, + "accept-dynamic-ip-sns", + "Allow to create dynamic NS Entities by IP-SNS PDUs\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "accept-dynamic-ip-sns can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->accept_sns = true; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + bind->accept_sns = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_bind_accept_sns, cfg_no_ns_bind_accept_sns_cmd, + "no accept-dynamic-ip-sns", + NO_STR + "Disable dynamic creation of NS Entities by IP-SNS PDUs\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "no accept-dynamic-ip-sns can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->accept_sns = false; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + bind->accept_sns = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_bind_ip_sns_weight, cfg_ns_bind_ip_sns_weight_cmd, + "ip-sns signalling-weight <0-254> data-weight <0-254>", + "IP SNS\n" + "signalling weight used by IP-SNS dynamic configuration\n" + "signalling weight used by IP-SNS dynamic configuration\n" + "data weight used by IP-SNS dynamic configuration\n" + "data weight used by IP-SNS dynamic configuration\n") +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + + int signalling = atoi(argv[0]); + int data = atoi(argv[1]); + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "ip-sns signalling-weight <0-254> data-weight <0-254> can be only used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vbind->ip_sns_data_weight = data; + vbind->ip_sns_sig_weight = signalling; + bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name); + if (bind) + gprs_ns2_ip_bind_set_sns_weight(bind, signalling, data); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_bind_fr, cfg_ns_bind_fr_cmd, + "fr NETIF (fr|frnet)", + "frame relay\n" + IFNAME_STR + "fr (user) is used by BSS or SGSN attached to UNI of a FR network\n" + "frnet (network) is used by SGSN if BSS is directly attached\n" + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + const char *netif = argv[0]; + const char *role = argv[1]; + + int rc = 0; + enum osmo_fr_role frrole; + + if (vbind->ll != GPRS_NS2_LL_FR) { + vty_out(vty, "fr can be only used with frame relay bind%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(role, "fr")) + frrole = FR_ROLE_USER_EQUIPMENT; + else if (!strcmp(role, "frnet")) + frrole = FR_ROLE_NETWORK_EQUIPMENT; + else + return CMD_WARNING; + + bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif); + if (bind) { + vty_out(vty, "Interface %s already used.%s", netif, VTY_NEWLINE); + return CMD_WARNING; + } + + rc = gprs_ns2_fr_bind(vty_nsi, vbind->name, netif, vty_fr_network, frrole, &bind); + if (rc < 0) { + LOGP(DLNS, LOGL_ERROR, "Failed to bind interface %s on fr. Err: %d\n", netif, rc); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_bind_fr, cfg_no_ns_bind_fr_cmd, + "no fr NETIF", + NO_STR + "Delete a frame relay link\n" + "Delete a frame relay link\n" + IFNAME_STR + ) +{ + struct vty_bind *vbind = vty->index; + struct gprs_ns2_vc_bind *bind; + const char *netif = argv[0]; + + if (vbind->ll != GPRS_NS2_LL_FR) { + vty_out(vty, "fr can be only used with frame relay bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif); + if (!bind) { + vty_out(vty, "Interface not found.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(bind->name, vbind->name)) { + vty_out(vty, "The specified interface is not bound to this bind.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_bind(bind); + return CMD_SUCCESS; +} + + +static struct cmd_node ns_nse_node = { + L_NS_NSE_NODE, + "%s(config-ns-nse)# ", + 1, +}; + +DEFUN(cfg_ns_nse_nsvc_fr, cfg_ns_nse_nsvc_fr_cmd, + "nsvc fr NETIF dlci <16-1007> nsvci <0-65535>", + "NS Virtual Connection\n" + "frame relay\n" + "frame relay interface. Must be registered via fr vty\n" + NSVCI_STR + NSVCI_STR + DLCI_STR + DLCI_STR + ) +{ + struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + const char *netif = argv[0]; + uint16_t dlci = atoi(argv[1]); + uint16_t nsvci = atoi(argv[2]); + bool dialect_modified = false; + bool ll_modified = false; + + if (nse->ll != GPRS_NS2_LL_FR && nse->ll != GPRS_NS2_LL_UNDEF) { + vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE); + goto err; + } + + if (nse->dialect != GPRS_NS2_DIALECT_STATIC_RESETBLOCK && nse->dialect != GPRS_NS2_DIALECT_UNDEF) { + vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE); + goto err; + } + + if (nse->ll == GPRS_NS2_LL_UNDEF) { + nse->ll = GPRS_NS2_LL_FR; + ll_modified = true; + } + + if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) { + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_RESETBLOCK); + dialect_modified = true; + } + + + bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif); + if (!bind) { + vty_out(vty, "Can not find fr interface \"%s\". Please configure it via fr vty.%s", + netif, VTY_NEWLINE); + goto err; + } + + if (gprs_ns2_fr_nsvc_by_dlci(bind, dlci)) { + vty_out(vty, "A NS-VC with the specified DLCI already exist!%s", VTY_NEWLINE); + goto err; + } + + if (gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci)) { + vty_out(vty, "A NS-VC with the specified NS-VCI already exist!%s", VTY_NEWLINE); + goto err; + } + + nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci); + if (!nsvc) { + /* Could not create NS-VC, connect failed */ + vty_out(vty, "Failed to create the NS-VC%s", VTY_NEWLINE); + goto err; + } + nsvc->persistent = true; + return CMD_SUCCESS; + +err: + if (ll_modified) + nse->ll = GPRS_NS2_LL_UNDEF; + if (dialect_modified) + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + + return CMD_WARNING; +} + +DEFUN(cfg_no_ns_nse_nsvc_fr_dlci, cfg_no_ns_nse_nsvc_fr_dlci_cmd, + "no nsvc fr NETIF dlci <16-1007>", + NO_STR + "Delete frame relay NS-VC\n" + "frame relay\n" + "frame relay interface. Must be registered via fr vty\n" + DLCI_STR + DLCI_STR + ) +{ + struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + const char *netif = argv[0]; + uint16_t dlci = atoi(argv[1]); + + if (nse->ll != GPRS_NS2_LL_FR) { + vty_out(vty, "This NSE doesn't support frame relay.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif); + if (!bind) { + vty_out(vty, "Can not find fr interface \"%s\"%s", + netif, VTY_NEWLINE); + return CMD_ERR_NOTHING_TODO; + } + + nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci); + if (!nsvc) { + vty_out(vty, "Can not find a NS-VC on fr interface %s with dlci %u%s", + netif, dlci, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse != nsvc->nse) { + vty_out(vty, "The specified NS-VC is not a part of the NSE %u!%s" + "To remove this NS-VC go to the vty node 'nse %u'%s", + nse->nsei, VTY_NEWLINE, + nsvc->nse->nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_nsvc(nsvc); + if (llist_empty(&nse->nsvc)) { + nse->ll = GPRS_NS2_LL_UNDEF; + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_nse_nsvci, cfg_no_ns_nse_nsvci_cmd, + "no nsvc nsvci <0-65535>", + NO_STR + "Delete NSVC\n" + NSVCI_STR + NSVCI_STR + ) +{ + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + uint16_t nsvci = atoi(argv[0]); + + switch (nse->dialect) { + case GPRS_NS2_DIALECT_SNS: + case GPRS_NS2_DIALECT_STATIC_ALIVE: + vty_out(vty, "NSE doesn't support NSVCI.%s", VTY_NEWLINE); + return CMD_WARNING; + case GPRS_NS2_DIALECT_UNDEF: + vty_out(vty, "No NSVCs configured%s", VTY_NEWLINE); + return CMD_WARNING; + case GPRS_NS2_DIALECT_IPACCESS: + case GPRS_NS2_DIALECT_STATIC_RESETBLOCK: + break; + } + + nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci); + if (!nsvc) { + vty_out(vty, "Can not find NS-VC with NS-VCI %u%s", nsvci, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse != nsvc->nse) { + vty_out(vty, "NS-VC with NS-VCI %u is not part of this NSE!%s", + nsvci, VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_nsvc(nsvc); + if (llist_empty(&nse->nsvc)) { + nse->ll = GPRS_NS2_LL_UNDEF; + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + } + + return CMD_SUCCESS; +} + +static int ns_nse_nsvc_udp_cmds(struct vty *vty, const char *bind_name, const char *remote_char, uint16_t port, + uint16_t sig_weight, uint16_t data_weight) +{ + struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + bool dialect_modified = false; + bool ll_modified = false; + + struct osmo_sockaddr_str remote_str; + struct osmo_sockaddr remote; + + if (nse->ll == GPRS_NS2_LL_UNDEF) { + nse->ll = GPRS_NS2_LL_UDP; + ll_modified = true; + } + + if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) { + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_ALIVE); + dialect_modified = true; + } + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE); + goto err; + } + + if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) { + vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE); + goto err; + } + + if (osmo_sockaddr_str_from_str(&remote_str, remote_char, port)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + goto err; + } + + if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + goto err; + } + + bind = gprs_ns2_bind_by_name(vty_nsi, bind_name); + if (!bind) { + vty_out(vty, "Can not find bind with name %s%s", + bind_name, VTY_NEWLINE); + goto err; + } + + if (bind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Bind %s is not an UDP bind.%s", + bind_name, VTY_NEWLINE); + goto err; + } + + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote); + if (nsvc) { + if (nsvc->nse == nse) + vty_out(vty, "Specified NSVC is already present in this NSE.%s", VTY_NEWLINE); + else + vty_out(vty, "Specified NSVC is already present in another NSE%05u.%s", nsvc->nse->nsei, VTY_NEWLINE); + goto err; + } + + nsvc = gprs_ns2_ip_connect(bind, &remote, nse, 0); + if (!nsvc) { + vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE); + goto err; + } + nsvc->sig_weight = sig_weight; + nsvc->data_weight = data_weight; + nsvc->persistent = true; + + return CMD_SUCCESS; + +err: + if (ll_modified) + nse->ll = GPRS_NS2_LL_UNDEF; + if (dialect_modified) + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + return CMD_WARNING; +} + +DEFUN(cfg_ns_nse_nsvc_udp, cfg_ns_nse_nsvc_udp_cmd, + "nsvc udp BIND " VTY_IPV46_CMD " <1-65535>", + "NS Virtual Connection\n" + "NS over UDP\n" + "A unique bind identifier created by ns bind\n" + "Remote IPv4 Address\n" "Remote IPv6 Address\n" + "Remote UDP Port\n") +{ + const char *bind_name = argv[0]; + const char *remote = argv[1]; + uint16_t port = atoi(argv[2]); + uint16_t sig_weight = 1; + uint16_t data_weight = 1; + + return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight); +} + +DEFUN(cfg_ns_nse_nsvc_udp_weights, cfg_ns_nse_nsvc_udp_weights_cmd, + "nsvc udp BIND " VTY_IPV46_CMD " <1-65535> signalling-weight <0-254> data-weight <0-254>", + "NS Virtual Connection\n" + "NS over UDP\n" + "A unique bind identifier created by ns bind\n" + "Remote IPv4 Address\n" "Remote IPv6 Address\n" + "Remote UDP Port\n" + "Signalling weight of the NSVC (default = 1)\n" + "Signalling weight of the NSVC (default = 1)\n" + "Data weight of the NSVC (default = 1)\n" + "Data weight of the NSVC (default = 1)\n" + ) +{ + const char *bind_name = argv[0]; + const char *remote = argv[1]; + uint16_t port = atoi(argv[2]); + uint16_t sig_weight = atoi(argv[3]); + uint16_t data_weight = atoi(argv[4]); + + return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight); +} + +DEFUN(cfg_no_ns_nse_nsvc_udp, cfg_no_ns_nse_nsvc_udp_cmd, + "no nsvc udp BIND " VTY_IPV46_CMD " <1-65535>", + NO_STR + "Delete a NS Virtual Connection\n" + "NS over UDP\n" + "A unique bind identifier created by ns bind\n" + "Remote IPv4 Address\n" "Remote IPv6 Address\n" + "Remote UDP Port\n" + ) +{ + struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + const char *bind_name = argv[0]; + struct osmo_sockaddr_str remote_str; + struct osmo_sockaddr remote; + uint16_t port = atoi(argv[2]); + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) { + vty_out(vty, "This NSE doesn't support UDP with dialect static alive.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bind = gprs_ns2_bind_by_name(vty_nsi, bind_name); + if (!bind) { + vty_out(vty, "Can not find bind with name %s%s", + bind_name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Bind %s is not an UDP bind.%s", + bind_name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote); + if (!nsvc) { + vty_out(vty, "Can not find NS-VC with remote %s:%u%s", + remote_str.ip, remote_str.port, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!nsvc->persistent) { + vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s", + remote_str.ip, remote_str.port, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nsvc->nse != nse) { + vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_nsvc(nsvc); + if (llist_empty(&nse->nsvc)) { + nse->ll = GPRS_NS2_LL_UNDEF; + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_nse_nsvc_ipa, cfg_ns_nse_nsvc_ipa_cmd, + "nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>" , + "NS Virtual Connection\n" + "NS over UDP ip.access style (uses RESET/BLOCK)\n" + "A unique bind identifier created by ns bind\n" + "Remote IPv4 Address\n" "Remote IPv6 Address\n" + "Remote UDP Port\n" + NSVCI_STR + NSVCI_STR + ) +{ + struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + bool dialect_modified = false; + bool ll_modified = false; + + const char *bind_name = argv[0]; + struct osmo_sockaddr_str remote_str; + struct osmo_sockaddr remote; + uint16_t port = atoi(argv[2]); + uint16_t nsvci = atoi(argv[3]); + + if (nse->ll == GPRS_NS2_LL_UNDEF) { + nse->ll = GPRS_NS2_LL_UDP; + ll_modified = true; + } + + if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) { + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_IPACCESS); + dialect_modified = true; + } + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE); + goto err; + } + + if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) { + vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE); + goto err; + } + + if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + goto err; + } + + if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + goto err; + } + + bind = gprs_ns2_bind_by_name(vty_nsi, bind_name); + if (!bind) { + vty_out(vty, "Can not find bind with name %s%s", + bind_name, VTY_NEWLINE); + goto err; + } + + if (bind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Bind %s is not an UDP bind.%s", + bind_name, VTY_NEWLINE); + goto err; + } + + nsvc = gprs_ns2_ip_connect(bind, &remote, nse, nsvci); + if (!nsvc) { + vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE); + goto err; + } + nsvc->persistent = true; + + return CMD_SUCCESS; + +err: + if (ll_modified) + nse->ll = GPRS_NS2_LL_UNDEF; + if (dialect_modified) + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + return CMD_WARNING; +} + +DEFUN(cfg_no_ns_nse_nsvc_ipa, cfg_no_ns_nse_nsvc_ipa_cmd, + "no nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>", + NO_STR + "Delete a NS Virtual Connection\n" + "NS over UDP\n" + "A unique bind identifier created by ns bind\n" + "Remote IPv4 Address\n" "Remote IPv6 Address\n" + "Remote UDP Port\n" + NSVCI_STR + NSVCI_STR + ) +{ + struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc *nsvc; + struct gprs_ns2_nse *nse = vty->index; + const char *bind_name = argv[0]; + struct osmo_sockaddr_str remote_str; + struct osmo_sockaddr remote; + uint16_t port = atoi(argv[2]); + uint16_t nsvci = atoi(argv[3]); + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) { + vty_out(vty, "This NSE doesn't support UDP with dialect ipaccess.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bind = gprs_ns2_bind_by_name(vty_nsi, bind_name); + if (!bind) { + vty_out(vty, "Can not find bind with name %s%s", + bind_name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Bind %s is not an UDP bind.%s", + bind_name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote); + if (!nsvc) { + vty_out(vty, "Can not find NS-VC with remote %s:%u%s", + remote_str.ip, remote_str.port, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!nsvc->persistent) { + vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s", + remote_str.ip, remote_str.port, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nsvc->nse != nse) { + vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!nsvc->nsvci_is_valid) { + vty_out(vty, "NS-VC doesn't have a nsvci!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (nsvc->nsvci != nsvci) { + vty_out(vty, "NS-VC has a different nsvci (%u)!%s", + nsvc->nsvci, VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_nsvc(nsvc); + if (llist_empty(&nse->nsvc)) { + nse->ll = GPRS_NS2_LL_UNDEF; + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_nse_ip_sns_remote, cfg_ns_nse_ip_sns_remote_cmd, + "ip-sns-remote " VTY_IPV46_CMD " <1-65535>", + "SNS Initial Endpoint\n" + "SGSN IPv4 Address\n" "SGSN IPv6 Address\n" + "SGSN UDP Port\n" + ) +{ + struct gprs_ns2_nse *nse = vty->index; + bool dialect_modified = false; + bool ll_modified = false; + int rc; + + /* argv[0] */ + struct osmo_sockaddr_str remote_str; + struct osmo_sockaddr remote; + uint16_t port = atoi(argv[1]); + + if (nse->ll == GPRS_NS2_LL_UNDEF) { + nse->ll = GPRS_NS2_LL_UDP; + ll_modified = true; + } + + if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) { + if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0) + goto err; + dialect_modified = true; + } + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE); + goto err; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE); + goto err; + } + + if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + goto err; + } + + if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + goto err; + } + + rc = gprs_ns2_sns_add_endpoint(nse, &remote); + switch (rc) { + case 0: + return CMD_SUCCESS; + case -EADDRINUSE: + vty_out(vty, "Specified SNS endpoint already part of the NSE.%s", VTY_NEWLINE); + return CMD_WARNING; + default: + vty_out(vty, "Can not add specified SNS endpoint.%s", VTY_NEWLINE); + return CMD_WARNING; + } + +err: + if (ll_modified) + nse->ll = GPRS_NS2_LL_UNDEF; + if (dialect_modified) + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + return CMD_WARNING; +} + +DEFUN(cfg_no_ns_nse_ip_sns_remote, cfg_no_ns_nse_ip_sns_remote_cmd, + "no ip-sns-remote " VTY_IPV46_CMD " <1-65535>", + NO_STR + "Delete a SNS Initial Endpoint\n" + "SGSN IPv4 Address\n" "SGSN IPv6 Address\n" + "SGSN UDP Port\n" + ) +{ + struct gprs_ns2_nse *nse = vty->index; + struct osmo_sockaddr_str remote_str; /* argv[0] */ + struct osmo_sockaddr remote; + uint16_t port = atoi(argv[1]); + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) { + vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (gprs_ns2_sns_del_endpoint(nse, &remote)) { + vty_out(vty, "Can not remove specified SNS endpoint.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (vty_nse_check_sns(nse)) { + /* there is still sns configuration valid */ + return CMD_SUCCESS; + } else { + /* clean up nse to allow other nsvc commands */ + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + nse->ll = GPRS_NS2_LL_UNDEF; + } + + return CMD_SUCCESS; +} + +/* add all IP-SNS default binds to the given NSE */ +int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse) +{ + struct vty_nse_bind *vnse_bind; + int count = 0; + + OSMO_ASSERT(nse->ll == GPRS_NS2_LL_UDP); + OSMO_ASSERT(nse->dialect == GPRS_NS2_DIALECT_SNS); + + llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) { + struct gprs_ns2_vc_bind *bind = gprs_ns2_bind_by_name(vty_nsi, vnse_bind->vbind->name); + /* the bind might not yet created because "listen" is missing. */ + if (!bind) + continue; + gprs_ns2_sns_add_bind(nse, bind); + count++; + } + return count; +} + +DEFUN(cfg_ns_ip_sns_default_bind, cfg_ns_ip_sns_default_bind_cmd, + "ip-sns-default bind ID", + "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n" + "IP SNS binds\n" + "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n") +{ + struct vty_bind *vbind; + struct vty_nse_bind *vnse_bind; + const char *name = argv[0]; + + vbind = vty_bind_by_name(name); + if (!vbind) { + vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE); + return CMD_WARNING; + } + + llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) { + if (vnse_bind->vbind == vbind) + return CMD_SUCCESS; + } + + vnse_bind = talloc(vty_nsi, struct vty_nse_bind); + if (!vnse_bind) + return CMD_WARNING; + vnse_bind->vbind = vbind; + + llist_add_tail(&vnse_bind->list, &ip_sns_default_binds); + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ns_ip_sns_default_bind, cfg_no_ns_ip_sns_default_bind_cmd, + "no ip-sns-default bind ID", + NO_STR "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n" + "IP SNS binds\n" + "Name of NS udp bind whose IP endpoint will be removed as IP-SNS local endpoint.\n") +{ + struct vty_bind *vbind; + struct vty_nse_bind *vnse_bind; + const char *name = argv[0]; + + vbind = vty_bind_by_name(name); + if (!vbind) { + vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE); + return CMD_WARNING; + } + + llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) { + if (vnse_bind->vbind == vbind) { + llist_del(&vnse_bind->list); + talloc_free(vnse_bind); + return CMD_SUCCESS; + } + } + + vty_out(vty, "Bind '%s' was not an ip-sns-default bind%s", name, VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(cfg_ns_txqueue_max_length, cfg_ns_txqueue_max_length_cmd, + "txqueue-max-length <1-4096>", + "Set the maximum length of the txqueue (for UDP)\n" + "Maximum length of the txqueue\n") +{ + struct gprs_ns2_vc_bind *bind; + uint32_t max_length = atoi(argv[0]); + vty_nsi->txqueue_max_length = max_length; + + + llist_for_each_entry(bind, &vty_nsi->binding, list) { + if (!gprs_ns2_is_ip_bind(bind)) + continue; + + ns2_ip_set_txqueue_max_length(bind, max_length); + } + + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_nse_ip_sns_bind, cfg_ns_nse_ip_sns_bind_cmd, + "ip-sns-bind BINDID", + "IP SNS binds\n" + "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n") +{ + struct gprs_ns2_nse *nse = vty->index; + struct gprs_ns2_vc_bind *bind; + struct vty_bind *vbind; + struct vty_nse *vnse; + const char *name = argv[0]; + bool ll_modified = false; + bool dialect_modified = false; + int rc; + + if (nse->ll == GPRS_NS2_LL_UNDEF) { + nse->ll = GPRS_NS2_LL_UDP; + ll_modified = true; + } + + if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) { + if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0) + goto err; + dialect_modified = true; + } + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE); + goto err; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE); + goto err; + } + + vbind = vty_bind_by_name(name); + if (!vbind) { + vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE); + goto err; + } + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "ip-sns-bind can only be used with UDP bind%s", + VTY_NEWLINE); + goto err; + } + + /* the vnse has been created together when creating the nse node. The parent node should check this already! */ + vnse = vty_nse_by_nsei(nse->nsei); + OSMO_ASSERT(vnse); + + rc = vty_nse_add_vbind(vnse, vbind); + switch (rc) { + case 0: + break; + case -EALREADY: + vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE); + goto err; + case -ENOMEM: + vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE); + goto err; + default: + vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE); + goto err; + } + + /* the bind might not yet created because "listen" is missing. */ + bind = gprs_ns2_bind_by_name(vty_nsi, name); + if (!bind) + return CMD_SUCCESS; + + rc = gprs_ns2_sns_add_bind(nse, bind); + switch (rc) { + case 0: + break; + case -EALREADY: + vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE); + goto err; + case -ENOMEM: + vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE); + goto err; + default: + vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE); + goto err; + } + + return CMD_SUCCESS; +err: + if (ll_modified) + nse->ll = GPRS_NS2_LL_UNDEF; + if (dialect_modified) + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + + return CMD_WARNING; +} + +DEFUN(cfg_no_ns_nse_ip_sns_bind, cfg_no_ns_nse_ip_sns_bind_cmd, + "no ip-sns-bind BINDID", + NO_STR + "IP SNS binds\n" + "Name of NS udp bind whose IP endpoint will not be used as IP-SNS local endpoint\n") +{ + struct gprs_ns2_nse *nse = vty->index; + struct gprs_ns2_vc_bind *bind; + struct vty_bind *vbind; + struct vty_nse *vnse; + const char *name = argv[0]; + int rc; + + if (nse->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vbind = vty_bind_by_name(name); + if (!vbind) { + vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + + if (vbind->ll != GPRS_NS2_LL_UDP) { + vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + /* the vnse has been created together when creating the nse node. The parent node should check this already! */ + vnse = vty_nse_by_nsei(nse->nsei); + OSMO_ASSERT(vnse); + + rc = vty_nse_remove_vbind(vnse, vbind); + switch(rc) { + case 0: + break; + case -ENOENT: + vty_out(vty, "Bind %s is not part of this NSE%s", name, VTY_NEWLINE); + return CMD_WARNING; + case -EINVAL: + vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s", + VTY_NEWLINE); + return CMD_WARNING; + default: + return CMD_WARNING; + } + + /* the bind might not exists yet */ + bind = gprs_ns2_bind_by_name(vty_nsi, name); + if (bind) + gprs_ns2_sns_del_bind(nse, bind); + + if (!vty_nse_check_sns(nse)) { + /* clean up nse to allow other nsvc commands */ + ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF); + nse->ll = GPRS_NS2_LL_UNDEF; + } + + return CMD_SUCCESS; +} + +/* non-config commands */ +void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats) +{ + if (nsvc->nsvci_is_valid) + vty_out(vty, " NSVCI %05u: %s %s %s %s %ssince ", nsvc->nsvci, + osmo_fsm_inst_state_name(nsvc->fi), + nsvc->persistent ? "PERSIST" : "DYNAMIC", + gprs_ns2_ll_str(nsvc), + ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD", + nsvc->om_blocked ? "(blocked by O&M/vty) " : + !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : ""); + else + vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s %s %ssince ", + osmo_fsm_inst_state_name(nsvc->fi), + nsvc->persistent ? "PERSIST" : "DYNAMIC", + nsvc->sig_weight, nsvc->data_weight, + gprs_ns2_ll_str(nsvc), + ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD", + !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : ""); + + vty_out_uptime(vty, &nsvc->ts_alive_change); + vty_out_newline(vty); + + if (stats) { + vty_out_rate_ctr_group(vty, " ", nsvc->ctrg); + vty_out_stat_item_group(vty, " ", nsvc->statg); + } +} + +static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only) +{ + struct gprs_ns2_vc *nsvc; + unsigned int nsvcs = 0; + + if (persistent_only && !nse->persistent) + return; + + vty_out(vty, "NSEI %05u: %s, %s since ", nse->nsei, gprs_ns2_lltype_str(nse->ll), + nse->alive ? "ALIVE" : "DEAD"); + vty_out_uptime(vty, &nse->ts_alive_change); + vty_out_newline(vty); + + ns2_sns_dump_vty(vty, " ", nse, stats); + llist_for_each_entry(nsvc, &nse->nsvc, list) { + nsvcs++; + } + vty_out(vty, " %u NS-VC:%s", nsvcs, VTY_NEWLINE); + llist_for_each_entry(nsvc, &nse->nsvc, list) + ns2_vty_dump_nsvc(vty, nsvc, stats); +} + +static void dump_bind(struct vty *vty, const struct gprs_ns2_vc_bind *bind, bool stats) +{ + if (bind->dump_vty) + bind->dump_vty(bind, vty, stats); + + if (stats) { + vty_out_stat_item_group(vty, " ", bind->statg); + } +} + +static void dump_ns_bind(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats) +{ + struct gprs_ns2_vc_bind *bind; + + llist_for_each_entry(bind, &nsi->binding, list) { + dump_bind(vty, bind, stats); + } +} + + +static void dump_ns_entities(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only) +{ + struct gprs_ns2_nse *nse; + + llist_for_each_entry(nse, &nsi->nse, list) { + dump_nse(vty, nse, stats, persistent_only); + } +} + +/* Backwards compatibility, among other things for the TestVTYGbproxy which expects + * 'show ns' to output something about binds */ +DEFUN_HIDDEN(show_ns, show_ns_cmd, "show ns", + SHOW_STR SHOW_NS_STR) +{ + dump_ns_entities(vty, vty_nsi, false, false); + dump_ns_bind(vty, vty_nsi, false); + if (vty_fr_network && llist_count(&vty_fr_network->links)) + osmo_fr_network_dump_vty(vty, vty_fr_network); + return CMD_SUCCESS; +} + + +DEFUN(show_ns_binds, show_ns_binds_cmd, "show ns binds [stats]", + SHOW_STR SHOW_NS_STR + "Display information about the NS protocol binds\n" + "Include statistic\n") +{ + bool stats = false; + if (argc > 0) + stats = true; + + dump_ns_bind(vty, vty_nsi, stats); + return CMD_SUCCESS; +} + +DEFUN(show_ns_entities, show_ns_entities_cmd, "show ns entities [stats]", + SHOW_STR SHOW_NS_STR + "Display information about the NS protocol entities (NSEs)\n" + "Include statistics\n") +{ + bool stats = false; + if (argc > 0) + stats = true; + + dump_ns_entities(vty, vty_nsi, stats, false); + return CMD_SUCCESS; +} + +DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent", + SHOW_STR SHOW_NS_STR + "Show only persistent NS\n") +{ + dump_ns_entities(vty, vty_nsi, true, true); + return CMD_SUCCESS; +} + +DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]", + SHOW_STR SHOW_NS_STR + "Select one NSE by its NSE Identifier\n" + "Select one NSE by its NS-VC Identifier\n" + "The Identifier of selected type\n" + "Include Statistics\n") +{ + struct gprs_ns2_inst *nsi = vty_nsi; + struct gprs_ns2_nse *nse; + struct gprs_ns2_vc *nsvc; + uint16_t id = atoi(argv[1]); + bool show_stats = false; + + if (argc >= 3) + show_stats = true; + + if (!strcmp(argv[0], "nsei")) { + nse = gprs_ns2_nse_by_nsei(nsi, id); + if (!nse) { + return CMD_WARNING; + } + + dump_nse(vty, nse, show_stats, false); + } else { + nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id); + + if (!nsvc) { + vty_out(vty, "No such NS Entity%s", VTY_NEWLINE); + return CMD_WARNING; + } + + ns2_vty_dump_nsvc(vty, nsvc, show_stats); + } + + return CMD_SUCCESS; +} + +static int nsvc_force_unconf_cb(struct gprs_ns2_vc *nsvc, void *ctx) +{ + ns2_vc_force_unconfigured(nsvc); + ns2_vc_fsm_start(nsvc); + return 0; +} + +DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd, + "nsvc nsei <0-65535> force-unconfigured", + "NS Virtual Connection\n" + "The NSEI\n" + "Reset the NSVCs back to initial state\n" + ) +{ + struct gprs_ns2_inst *nsi = vty_nsi; + struct gprs_ns2_nse *nse; + + uint16_t id = atoi(argv[0]); + + nse = gprs_ns2_nse_by_nsei(nsi, id); + if (!nse) { + vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!nse->persistent) { + gprs_ns2_free_nse(nse); + } else if (nse->dialect == GPRS_NS2_DIALECT_SNS) { + gprs_ns2_free_nsvcs(nse); + } else { + /* Perform the operation for all nsvc */ + gprs_ns2_nse_foreach_nsvc(nse, nsvc_force_unconf_cb, NULL); + } + + return CMD_SUCCESS; +} + +DEFUN(nse_restart_sns, nse_restart_sns_cmd, + "nse <0-65535> restart-sns", + "NSE specific commands\n" + "NS Entity ID (NSEI)\n" + "Restart SNS procedure\n") +{ + struct gprs_ns2_inst *nsi = vty_nsi; + struct gprs_ns2_nse *nse; + + uint16_t id = atoi(argv[0]); + nse = gprs_ns2_nse_by_nsei(nsi, id); + if (!nse) { + vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + vty_out(vty, "Given NSEI %u doesn't use IP-SNS%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_nsvcs(nse); + return CMD_SUCCESS; +} + +DEFUN(nsvc_block, nsvc_block_cmd, + "nsvc <0-65535> (block|unblock|reset)", + "NS Virtual Connection\n" + NSVCI_STR + "Block a NSVC. As cause code O&M intervention will be used.\n" + "Unblock a NSVC. As cause code O&M intervention will be used.\n" + "Reset a NSVC. As cause code O&M intervention will be used.\n") +{ + struct gprs_ns2_inst *nsi = vty_nsi; + struct gprs_ns2_vc *nsvc; + int rc; + + uint16_t id = atoi(argv[0]); + + nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id); + if (!nsvc) { + vty_out(vty, "Could not find NSVCI %05u%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "block")) { + rc = ns2_vc_block(nsvc); + switch (rc) { + case 0: + vty_out(vty, "The NS-VC %05u will be blocked.%s", id, VTY_NEWLINE); + return CMD_SUCCESS; + case -EALREADY: + vty_out(vty, "The NS-VC %05u is already blocked.%s", id, VTY_NEWLINE); + return CMD_ERR_NOTHING_TODO; + default: + vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE); + return CMD_WARNING; + } + } else if (!strcmp(argv[1], "unblock")) { + rc = ns2_vc_unblock(nsvc); + switch (rc) { + case 0: + vty_out(vty, "The NS-VC %05u will be unblocked.%s", id, VTY_NEWLINE); + return CMD_SUCCESS; + case -EALREADY: + vty_out(vty, "The NS-VC %05u is already unblocked.%s", id, VTY_NEWLINE); + return CMD_ERR_NOTHING_TODO; + default: + vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE); + return CMD_WARNING; + } + } else { + ns2_vc_reset(nsvc); + vty_out(vty, "The NS-VC %05u has been resetted.%s", id, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +static void log_set_nse_filter(struct log_target *target, + struct gprs_ns2_nse *nse) +{ + if (nse) { + target->filter_map |= (1 << LOG_FLT_GB_NSE); + target->filter_data[LOG_FLT_GB_NSE] = nse; + } else if (target->filter_data[LOG_FLT_GB_NSE]) { + target->filter_map = ~(1 << LOG_FLT_GB_NSE); + target->filter_data[LOG_FLT_GB_NSE] = NULL; + } +} + +static void log_set_nsvc_filter(struct log_target *target, + struct gprs_ns2_vc *nsvc) +{ + if (nsvc) { + target->filter_map |= (1 << LOG_FLT_GB_NSVC); + target->filter_data[LOG_FLT_GB_NSVC] = nsvc; + } else if (target->filter_data[LOG_FLT_GB_NSVC]) { + target->filter_map = ~(1 << LOG_FLT_GB_NSVC); + target->filter_data[LOG_FLT_GB_NSVC] = NULL; + } +} + +DEFUN(logging_fltr_nse, + logging_fltr_nse_cmd, + "logging filter nse nsei <0-65535>", + LOGGING_STR FILTER_STR + "Filter based on NS Entity\n" + "Identify NSE by NSEI\n" + "Numeric identifier\n") +{ + struct log_target *tgt; + struct gprs_ns2_nse *nse; + uint16_t id = atoi(argv[0]); + + log_tgt_mutex_lock(); + tgt = osmo_log_vty2tgt(vty); + if (!tgt) { + log_tgt_mutex_unlock(); + return CMD_WARNING; + } + + nse = gprs_ns2_nse_by_nsei(vty_nsi, id); + if (!nse) { + vty_out(vty, "No NSE by that identifier%s", VTY_NEWLINE); + log_tgt_mutex_unlock(); + return CMD_WARNING; + } + + log_set_nse_filter(tgt, nse); + log_tgt_mutex_unlock(); + return CMD_SUCCESS; +} + +/* TODO: add filter for single connection by description */ +DEFUN(logging_fltr_nsvc, + logging_fltr_nsvc_cmd, + "logging filter nsvc nsvci <0-65535>", + LOGGING_STR FILTER_STR + "Filter based on NS Virtual Connection\n" + "Identify NS-VC by NSVCI\n" + "Numeric identifier\n") +{ + struct log_target *tgt; + struct gprs_ns2_vc *nsvc; + uint16_t id = atoi(argv[0]); + + log_tgt_mutex_lock(); + tgt = osmo_log_vty2tgt(vty); + if (!tgt) { + log_tgt_mutex_unlock(); + return CMD_WARNING; + } + + nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, id); + if (!nsvc) { + vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE); + log_tgt_mutex_unlock(); + return CMD_WARNING; + } + + log_set_nsvc_filter(tgt, nsvc); + log_tgt_mutex_unlock(); + return CMD_SUCCESS; +} + +/*! initialized a reduced vty interface which excludes the configuration nodes besides timeouts. + * This can be used by the PCU which can be only configured by the BTS/BSC and not by the vty. + * \param[in] nsi NS instance on which we operate + * \return 0 on success. + */ +int gprs_ns2_vty_init_reduced(struct gprs_ns2_inst *nsi) +{ + vty_nsi = nsi; + INIT_LLIST_HEAD(&binds); + INIT_LLIST_HEAD(&nses); + INIT_LLIST_HEAD(&ip_sns_default_binds); + + vty_fr_network = osmo_fr_network_alloc(nsi); + if (!vty_fr_network) + return -ENOMEM; + + install_lib_element_ve(&show_ns_cmd); + install_lib_element_ve(&show_ns_binds_cmd); + install_lib_element_ve(&show_ns_entities_cmd); + install_lib_element_ve(&show_ns_pers_cmd); + install_lib_element_ve(&show_nse_cmd); + install_lib_element_ve(&logging_fltr_nse_cmd); + install_lib_element_ve(&logging_fltr_nsvc_cmd); + + install_lib_element(ENABLE_NODE, &nsvc_force_unconf_cmd); + install_lib_element(ENABLE_NODE, &nsvc_block_cmd); + install_lib_element(ENABLE_NODE, &nse_restart_sns_cmd); + + install_lib_element(CFG_LOG_NODE, &logging_fltr_nse_cmd); + install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd); + + install_lib_element(CONFIG_NODE, &cfg_ns_cmd); + + install_node(&ns_node, config_write_ns); + /* TODO: convert into osmo timer */ + install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd); + + return 0; +} + +int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi) +{ + int rc = gprs_ns2_vty_init_reduced(nsi); + if (rc) + return rc; + + install_lib_element(L_NS_NODE, &cfg_ns_nsei_cmd); + install_lib_element(L_NS_NODE, &cfg_no_ns_nsei_cmd); + install_lib_element(L_NS_NODE, &cfg_ns_bind_cmd); + install_lib_element(L_NS_NODE, &cfg_no_ns_bind_cmd); + + install_lib_element(L_NS_NODE, &cfg_ns_ip_sns_default_bind_cmd); + install_lib_element(L_NS_NODE, &cfg_no_ns_ip_sns_default_bind_cmd); + + install_lib_element(L_NS_NODE, &cfg_ns_txqueue_max_length_cmd); + + install_node(&ns_bind_node, NULL); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_listen_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_listen_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_dscp_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_dscp_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_priority_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ip_sns_weight_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ipaccess_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_ipaccess_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_fr_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_fr_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_accept_sns_cmd); + install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_accept_sns_cmd); + + install_node(&ns_nse_node, NULL); + install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_fr_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvci_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_fr_dlci_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_weights_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_udp_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_ipa_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_ipa_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_remote_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_remote_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_bind_cmd); + install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_bind_cmd); + + return 0; +} diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c index a39e1395..f27bf6a8 100644 --- a/src/gb/gprs_ns_vty.c +++ b/src/gb/gprs_ns_vty.c @@ -644,32 +644,32 @@ int gprs_ns_vty_init(struct gprs_ns_inst *nsi) return 0; vty_elements_installed = true; - install_element_ve(&show_ns_cmd); - install_element_ve(&show_ns_stats_cmd); - install_element_ve(&show_ns_pers_cmd); - install_element_ve(&show_nse_cmd); - install_element_ve(&logging_fltr_nsvc_cmd); + install_lib_element_ve(&show_ns_cmd); + install_lib_element_ve(&show_ns_stats_cmd); + install_lib_element_ve(&show_ns_pers_cmd); + install_lib_element_ve(&show_nse_cmd); + install_lib_element_ve(&logging_fltr_nsvc_cmd); - install_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd); + install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd); - install_element(CONFIG_NODE, &cfg_ns_cmd); + install_lib_element(CONFIG_NODE, &cfg_ns_cmd); install_node(&ns_node, config_write_ns); - install_element(L_NS_NODE, &cfg_nse_nsvci_cmd); - install_element(L_NS_NODE, &cfg_nse_remoteip_cmd); - install_element(L_NS_NODE, &cfg_nse_remoteport_cmd); - install_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd); - install_element(L_NS_NODE, &cfg_nse_encaps_cmd); - install_element(L_NS_NODE, &cfg_nse_remoterole_cmd); - install_element(L_NS_NODE, &cfg_no_nse_cmd); - install_element(L_NS_NODE, &cfg_ns_timer_cmd); - install_element(L_NS_NODE, &cfg_nsip_local_ip_cmd); - install_element(L_NS_NODE, &cfg_nsip_local_port_cmd); - install_element(L_NS_NODE, &cfg_nsip_dscp_cmd); - install_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd); - install_element(L_NS_NODE, &cfg_frgre_enable_cmd); - install_element(L_NS_NODE, &cfg_frgre_local_ip_cmd); - - install_element(ENABLE_NODE, &nsvc_nsei_cmd); + install_lib_element(L_NS_NODE, &cfg_nse_nsvci_cmd); + install_lib_element(L_NS_NODE, &cfg_nse_remoteip_cmd); + install_lib_element(L_NS_NODE, &cfg_nse_remoteport_cmd); + install_lib_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd); + install_lib_element(L_NS_NODE, &cfg_nse_encaps_cmd); + install_lib_element(L_NS_NODE, &cfg_nse_remoterole_cmd); + install_lib_element(L_NS_NODE, &cfg_no_nse_cmd); + install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd); + install_lib_element(L_NS_NODE, &cfg_nsip_local_ip_cmd); + install_lib_element(L_NS_NODE, &cfg_nsip_local_port_cmd); + install_lib_element(L_NS_NODE, &cfg_nsip_dscp_cmd); + install_lib_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd); + install_lib_element(L_NS_NODE, &cfg_frgre_enable_cmd); + install_lib_element(L_NS_NODE, &cfg_frgre_local_ip_cmd); + + install_lib_element(ENABLE_NODE, &nsvc_nsei_cmd); return 0; } diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map index 7a231db5..f1a6bd69 100644 --- a/src/gb/libosmogb.map +++ b/src/gb/libosmogb.map @@ -2,7 +2,26 @@ LIBOSMOGB_1.0 { global: bssgp_cause_str; bssgp_create_cell_id; +bssgp_create_cell_id2; +bssgp_create_rim_ri; +bssgp_dec_app_err_cont_nacc; +bssgp_dec_ran_inf_ack_rim_cont; +bssgp_dec_ran_inf_err_rim_cont; +bssgp_dec_ran_inf_req_app_cont_nacc; +bssgp_dec_ran_inf_req_rim_cont; +bssgp_dec_ran_inf_app_cont_nacc; +bssgp_dec_ran_inf_app_err_rim_cont; +bssgp_dec_ran_inf_rim_cont; bssgp_pdu_str; +bssgp_enc_app_err_cont_nacc; +bssgp_enc_ran_inf_ack_rim_cont; +bssgp_enc_ran_inf_err_rim_cont; +bssgp_enc_ran_inf_req_app_cont_nacc; +bssgp_enc_ran_inf_req_rim_cont; +bssgp_enc_ran_inf_app_cont_nacc; +bssgp_enc_ran_inf_app_err_rim_cont; +bssgp_enc_ran_inf_rim_cont; +bssgp_encode_rim_pdu; bssgp_fc_in; bssgp_fc_init; bssgp_fc_ms_init; @@ -12,10 +31,21 @@ bssgp_msgb_alloc; bssgp_msgb_copy; bssgp_msgb_tlli_put; bssgp_msgb_ra_put; +bssgp_nacc_cause_strs; bssgp_parse_cell_id; +bssgp_parse_cell_id2; +bssgp_parse_rim_pdu; +bssgp_parse_rim_ri; +bssgp_parse_rim_ra; +bssgp_ran_inf_app_id_strs; +bssgp_rim_routing_info_discr_strs; +bssgp_rim_ri_name_buf; +bssgp_rim_ri_name; +bssgp_set_bssgp_callback; bssgp_tx_bvc_block; bssgp_tx_bvc_reset; bssgp_tx_bvc_reset2; +bssgp_tx_bvc_reset_nsei_bvci; bssgp_tx_bvc_unblock; bssgp_tx_fc_bvc; bssgp_tx_fc_ms; @@ -28,6 +58,8 @@ bssgp_tx_radio_status_tmsi; bssgp_tx_resume; bssgp_tx_resume_ack; bssgp_tx_resume_nack; +bssgp_tx_rim; +bssgp_tx_rim_encoded; bssgp_tx_simple_bvci; bssgp_tx_status; bssgp_tx_suspend; @@ -43,6 +75,45 @@ bssgp_tx_paging; bssgp_vty_init; bssgp_nsi; +bssgp2_nsi_tx_ptp; +bssgp2_nsi_tx_sig; +bssgp2_dec_fc_bvc; +bssgp2_dec_fc_ms; +bssgp2_enc_bvc_block; +bssgp2_enc_bvc_block_ack; +bssgp2_enc_bvc_unblock; +bssgp2_enc_bvc_unblock_ack; +bssgp2_enc_bvc_reset; +bssgp2_enc_bvc_reset_ack; +bssgp2_enc_fc_bvc; +bssgp2_enc_fc_bvc_ack; +bssgp2_enc_fc_ms; +bssgp2_enc_fc_ms_ack; +bssgp2_enc_flush_ll; +bssgp2_enc_status; + +bssgp_bvc_fsm_alloc_sig_bss; +bssgp_bvc_fsm_alloc_ptp_bss; +bssgp_bvc_fsm_alloc_sig_sgsn; +bssgp_bvc_fsm_alloc_ptp_sgsn; +bssgp_bvc_fsm_set_ops; +bssgp_bvc_fsm_is_unblocked; +bssgp_bvc_fsm_get_block_cause; +bssgp_bvc_fsm_get_features_advertised; +bssgp_bvc_fsm_get_features_received; +bssgp_bvc_fsm_get_features_negotiated; +bssgp_bvc_fsm_set_max_pdu_len; +bssgp_bvc_fsm_get_max_pdu_len; + +osmo_fr_network_alloc; +osmo_fr_network_free; +osmo_fr_link_alloc; +osmo_fr_link_free; +osmo_fr_dlc_alloc; +osmo_fr_rx; +osmo_fr_tx_dlc; +osmo_fr_role_names; + gprs_ns_signal_ns_names; gprs_ns_pdu_strings; gprs_ns_cause_str; @@ -71,6 +142,65 @@ gprs_ns_ll_copy; gprs_ns_ll_clear; gprs_ns_msgb_alloc; +gprs_ns2_aff_cause_prim_strs; +gprs_ns2_bind_by_name; +gprs_ns2_cause_strs; +gprs_ns2_create_nse; +gprs_ns2_create_nse2; +gprs_ns2_find_vc_by_sockaddr; +gprs_ns2_free; +gprs_ns2_free_bind; +gprs_ns2_free_binds; +gprs_ns2_free_nse; +gprs_ns2_free_nses; +gprs_ns2_free_nsvc; +gprs_ns2_free_nsvcs; +gprs_ns2_frgre_bind; +gprs_ns2_fr_bind; +gprs_ns2_fr_bind_netif; +gprs_ns2_fr_bind_by_netif; +gprs_ns2_fr_connect; +gprs_ns2_fr_nsvc_by_dlci; +gprs_ns2_fr_nsvc_dlci; +gprs_ns2_is_fr_bind; +gprs_ns2_find_vc_by_dlci; +gprs_ns2_instantiate; +gprs_ns2_ip_bind; +gprs_ns2_ip_bind_by_sockaddr; +gprs_ns2_ip_bind_set_dscp; +gprs_ns2_ip_bind_set_priority; +gprs_ns2_ip_bind_set_sns_weight; +gprs_ns2_ip_bind_sockaddr; +gprs_ns2_ip_connect; +gprs_ns2_ip_connect2; +gprs_ns2_ip_connect_inactive; +gprs_ns2_ip_vc_local; +gprs_ns2_ip_vc_remote; +gprs_ns2_ip_vc_equal; +gprs_ns2_is_frgre_bind; +gprs_ns2_is_ip_bind; +gprs_ns2_ll_str; +gprs_ns2_ll_str_buf; +gprs_ns2_ll_str_c; +gprs_ns2_lltype_strs; +gprs_ns2_nse_by_nsei; +gprs_ns2_nse_foreach_nsvc; +gprs_ns2_nse_nsei; +gprs_ns2_nse_sns_remote; +gprs_ns2_nsvc_by_nsvci; +gprs_ns2_nsvc_by_sockaddr; +gprs_ns2_nsvc_state_name; +gprs_ns2_prim_strs; +gprs_ns2_recv_prim; +gprs_ns2_reset_persistent_nsvcs; +gprs_ns2_start_alive_all_nsvcs; +gprs_ns2_sns_add_bind; +gprs_ns2_sns_add_endpoint; +gprs_ns2_sns_del_bind; +gprs_ns2_sns_del_endpoint; +gprs_ns2_vty_init; +gprs_ns2_vty_init_reduced; + gprs_nsvc_create2; gprs_nsvc_delete; gprs_nsvc_reset; @@ -85,5 +215,7 @@ bssgp_bvc_ctx_free; btsctx_by_bvci_nsei; btsctx_by_raid_cid; +osmo_pdef_bssgp; + local: *; }; |