summaryrefslogtreecommitdiffstats
path: root/src/gb
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2018-07-01 21:04:45 +0200
committerHarald Welte <laforge@gnumonks.org>2019-02-26 12:18:30 +0100
commit047f3872f511353e894659719a6c5346249bca40 (patch)
treee719164186b93e96cfca6daa86b95f7679bbe669 /src/gb
parent35042a29197bd086a545976e5fa38d01c434f8ac (diff)
NS: Add support for GPRS NS IP Sub-Network-Service (SNS)
The NS implementation part of the Gb implementation libosmogb so far implemented a rather classic dialect of Gb, with lots of heritage to FR (Frame Relay) transports. At least since Release 6 of the NS specification, there's an IP Sub-Network Service (SNS), which * permits for dynamic configuration of IP endpoints and their NS-VCs * abandons the concept of a NSVCI on IP transport * forbids the use of RESET/BLOCK/UNBLOCK procedures on IP transport This commit introduces BSS-side IP-SNS support to libosmogb in a minimally invasive way. It adds a corresponding SNS FSM to each NS instance, and implements the new SIZE/CONFIG/ADD/DELETE/CHANGE_WEIGHT procedures very closely aligned with the spec. In order to use the SNS flavor (rather than the classic one), a BSS implementation should use gprs_ns_nsip_connect_sns() instead of the existing gprs_ns_nsip_connect(). This implementation comes with a set of TTCN-3 tests in PCU_Tests_RAW_SNS.ttcn, see Change-ID I0fe3d4579960bab0494c294ec7ab8032feed4fb2 of osmo-ttcn3-hacks.git Closes: OS#3372 Closes: OS#3617 Change-Id: I84786c3b43a8ae34ef3b3ba84b33c90042d234ea
Diffstat (limited to 'src/gb')
-rw-r--r--src/gb/Makefile.am2
-rw-r--r--src/gb/gb_internal.h13
-rw-r--r--src/gb/gprs_ns.c253
-rw-r--r--src/gb/gprs_ns_sns.c772
-rw-r--r--src/gb/gprs_ns_vty.c10
-rw-r--r--src/gb/libosmogb.map1
6 files changed, 1034 insertions, 17 deletions
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index d074092e..3180f9c1 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -17,7 +17,7 @@ libosmogb_la_LIBADD = $(TALLOC_LIBS) \
$(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 \
+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
endif
diff --git a/src/gb/gb_internal.h b/src/gb/gb_internal.h
index 72d940f5..cc7222e8 100644
--- a/src/gb/gb_internal.h
+++ b/src/gb/gb_internal.h
@@ -4,7 +4,17 @@
#include <osmocom/gsm/tlv.h>
#include <osmocom/gprs/gprs_ns.h>
+/* gprs_ns_sns.c */
+int gprs_ns_rx_sns(struct gprs_ns_inst *nsi, struct msgb *msg, struct tlv_parsed *tp);
+
+struct osmo_fsm_inst *gprs_sns_bss_fsm_alloc(void *ctx, struct gprs_nsvc *nsvc, const char *id);
+int gprs_sns_bss_fsm_start(struct gprs_ns_inst *nsi);
+
+int gprs_sns_init(void);
+
+/* gprs_ns.c */
void gprs_nsvc_start_test(struct gprs_nsvc *nsvc);
+void gprs_start_alive_all_nsvcs(struct gprs_ns_inst *nsi);
int gprs_ns_tx_sns_ack(struct gprs_nsvc *nsvc, uint8_t trans_id, uint8_t *cause,
const struct gprs_ns_ie_ip4_elem *ip4_elems,unsigned int num_ip4_elems);
@@ -18,3 +28,6 @@ int gprs_ns_tx_sns_size(struct gprs_nsvc *nsvc, bool reset_flag, uint16_t max_nr
uint16_t *ip4_ep_nr, uint16_t *ip6_ep_nr);
int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause);
+
+struct vty;
+void gprs_sns_dump_vty(struct vty *vty, const struct gprs_ns_inst *nsi, bool stats);
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index ad68bc99..33de2c1a 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -72,6 +72,7 @@
#include <sys/socket.h>
#include <arpa/inet.h>
+#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/byteswap.h>
#include <osmocom/gsm/tlv.h>
@@ -93,6 +94,18 @@
#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));
+
+#define ERR_IF_NSVC_USES_SNS(nsvc, reason) \
+ do { \
+ if ((nsvc)->nsi->bss_sns_fi) { \
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Asked to %s. Rejected on IP-SNS\n", \
+ nsvc->nsei, reason); \
+ osmo_log_backtrace(DNS, LOGL_ERROR); \
+ return -EIO; \
+ } \
+ } while (0)
static const struct tlv_definition ns_att_tlvdef = {
.def = {
@@ -107,6 +120,9 @@ static const struct tlv_definition ns_att_tlvdef = {
[NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 },
[NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 },
[NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 },
+ /* TODO: IP_ADDR can be 5 or 17 bytes long, depending on first byte. This
+ * cannot be expressed in our TLV parser. However, we don't do IPv6 anyway */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 },
},
};
@@ -171,6 +187,7 @@ const struct value_string gprs_ns_signal_ns_names[] = {
{ S_NS_ALIVE_EXP, "NS-ALIVE expired" },
{ S_NS_REPLACED, "NSVC replaced" },
{ S_NS_MISMATCH, "Unexpected IE" },
+ { S_SNS_CONFIGURED, "SNS Configured" },
{ 0, NULL }
};
@@ -237,11 +254,20 @@ struct gprs_nsvc *gprs_nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei)
return NULL;
}
+/*! Determine active NS-VC for given NSEI + BVCI.
+ * Use this function to determine which of the NS-VCs inside the NS Instance
+ * shall be used to transmit data for given NSEI + BVCI */
static struct gprs_nsvc *gprs_active_nsvc_by_nsei(struct gprs_ns_inst *nsi,
- uint16_t nsei)
+ uint16_t nsei, uint16_t bvci)
{
struct gprs_nsvc *nsvc;
llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+ /* if signalling BVCI, skip any NSVC with signalling weight == 0 */
+ if (bvci == 0 && nsvc->sig_weight == 0)
+ continue;
+ /* if point-to-point BVCI, skip any NSVC with data weight == 0 */
+ if (bvci != 0 && nsvc->data_weight == 0)
+ continue;
if (nsvc->nsei == nsei) {
if (!(nsvc->state & NSE_S_BLOCKED) &&
nsvc->state & NSE_S_ALIVE)
@@ -251,9 +277,11 @@ static struct gprs_nsvc *gprs_active_nsvc_by_nsei(struct gprs_ns_inst *nsi,
return NULL;
}
-/* Lookup struct gprs_nsvc based on remote peer socket addr */
-static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi,
- struct sockaddr_in *sin)
+/*! Lookup NS-VC based on specified remote peer socket addr.
+ * \param[in] nsi NS Instance within which we shall look up the NS-VC
+ * \param[in] sin Remote peer Socket Address (IP + UDP Port)
+ * \returns NS-VC matching the given peer; NULL in case of none */
+struct gprs_nsvc *gprs_nsvc_by_rem_addr(struct gprs_ns_inst *nsi, const struct sockaddr_in *sin)
{
struct gprs_nsvc *nsvc;
llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
@@ -267,27 +295,52 @@ static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi,
static void gprs_ns_timer_cb(void *data);
-struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+/*! Create a new NS-VC (Virtual Circuit) within given instance
+ * \param[in] nsi NS Instance in which to create the NSVC
+ * \param[in] nsvci] NS Virtual Connection Identifier for this NSVC
+ * \param[in] sig_weight Signalling Weight of this NS-VC. Use "0" for no signalling
+ * \param[in] data_weight Data WEight of this NS-VC. Use "0" for no data
+ * \returns newly-created gprs_nsvc within nsi. NULL on error. */
+struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
+ uint8_t sig_weight, uint8_t data_weight)
{
struct gprs_nsvc *nsvc;
+ if (gprs_nsvc_by_nsvci(nsi, nsvci)) {
+ LOGP(DNS, LOGL_ERROR, "Cannot create NS-VC for already-existing NSVCI=%u\n", nsvci);
+ return NULL;
+ }
+
LOGP(DNS, LOGL_INFO, "NSVCI=%u Creating NS-VC\n", nsvci);
nsvc = talloc_zero(nsi, struct gprs_nsvc);
+ if (!nsvc)
+ return NULL;
nsvc->nsvci = nsvci;
nsvc->nsvci_is_valid = 1;
/* before RESET procedure: BLOCKED and DEAD */
- ns_set_state(nsvc, NSE_S_BLOCKED);
+ if (nsi->bss_sns_fi)
+ ns_set_state(nsvc, 0);
+ else
+ ns_set_state(nsvc, NSE_S_BLOCKED);
nsvc->nsi = nsi;
osmo_timer_setup(&nsvc->timer, gprs_ns_timer_cb, nsvc);
nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci);
nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, nsvci);
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
llist_add(&nsvc->list, &nsi->gprs_nsvcs);
return nsvc;
}
+/*! Old API for creating a NS-VC. Uses gprs_nsvc_create2 with fixed weights. */
+struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+{
+ return gprs_nsvc_create2(nsi, nsvci, 1, 1);
+}
+
/*! Delete given NS-VC
* \param[in] nsvc gprs_nsvc to be deleted
*/
@@ -450,13 +503,16 @@ static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
*/
int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
{
- struct msgb *msg = gprs_ns_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsvci = osmo_htons(nsvc->nsvci);
uint16_t nsei = osmo_htons(nsvc->nsei);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
+
+ msg = gprs_ns_msgb_alloc();
if (!msg)
return -ENOMEM;
@@ -536,12 +592,15 @@ int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause,
*/
int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
{
- struct msgb *msg = gprs_ns_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsvci = osmo_htons(nsvc->nsvci);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
+
+ msg = gprs_ns_msgb_alloc();
if (!msg)
return -ENOMEM;
@@ -574,6 +633,8 @@ static int gprs_ns_tx_block_ack(struct gprs_nsvc *nsvc)
log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
+
msg = gprs_ns_msgb_alloc();
if (!msg)
return -ENOMEM;
@@ -597,6 +658,9 @@ static int gprs_ns_tx_block_ack(struct gprs_nsvc *nsvc)
int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc)
{
log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK");
+
LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
nsvc->nsei, nsvc->nsvci);
@@ -687,16 +751,21 @@ static void gprs_ns_timer_cb(void *data)
nsvc->alive_retries++;
if (nsvc->alive_retries >
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
- /* mark as dead and blocked */
- ns_set_state(nsvc, NSE_S_BLOCKED);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ /* mark as dead (and blocked unless IP-SNS) */
rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+ if (!nsvc->nsi->bss_sns_fi) {
+ ns_set_state(nsvc, NSE_S_BLOCKED);
+ rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ } else
+ ns_set_state(nsvc, 0);
LOGP(DNS, LOGL_NOTICE,
"NSEI=%u Tns-alive expired more then "
"%u times, blocking NS-VC\n", nsvc->nsei,
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]);
ns_osmo_signal_dispatch(nsvc, S_NS_ALIVE_EXP, 0);
- ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
+ /* FIXME: should we send this signal in case of SNS? */
+ if (!nsvc->nsi->bss_sns_fi)
+ ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
return;
}
/* Tns-test case: send NS-ALIVE PDU */
@@ -733,11 +802,15 @@ static void gprs_ns_timer_cb(void *data)
/* Section 9.2.6 */
static int gprs_ns_tx_reset_ack(struct gprs_nsvc *nsvc)
{
- struct msgb *msg = gprs_ns_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsvci, nsei;
log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
+
+ msg = gprs_ns_msgb_alloc();
if (!msg)
return -ENOMEM;
@@ -777,6 +850,13 @@ int gprs_ns_tx_sns_ack(struct gprs_nsvc *nsvc, uint8_t trans_id, uint8_t *cause,
if (!msg)
return -ENOMEM;
+ if (!nsvc->nsi->bss_sns_fi) {
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
nsei = osmo_htons(nsvc->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -815,6 +895,13 @@ int gprs_ns_tx_sns_config(struct gprs_nsvc *nsvc, bool end_flag,
if (!msg)
return -ENOMEM;
+ if (!nsvc->nsi->bss_sns_fi) {
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
nsei = osmo_htons(nsvc->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -847,6 +934,13 @@ int gprs_ns_tx_sns_config_ack(struct gprs_nsvc *nsvc, uint8_t *cause)
if (!msg)
return -ENOMEM;
+ if (!nsvc->nsi->bss_sns_fi) {
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
nsei = osmo_htons(nsvc->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -880,6 +974,13 @@ int gprs_ns_tx_sns_size(struct gprs_nsvc *nsvc, bool reset_flag, uint16_t max_nr
if (!msg)
return -ENOMEM;
+ if (!nsvc->nsi->bss_sns_fi) {
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
nsei = osmo_htons(nsvc->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -912,6 +1013,13 @@ int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause)
if (!msg)
return -ENOMEM;
+ if (!nsvc->nsi->bss_sns_fi) {
+ LOGP(DNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
+ nsvc->nsei);
+ msgb_free(msg);
+ return -EIO;
+ }
+
nsei = osmo_htons(nsvc->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -942,7 +1050,7 @@ int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
struct gprs_ns_hdr *nsh;
uint16_t bvci = msgb_bvci(msg);
- nsvc = gprs_active_nsvc_by_nsei(nsi, msgb_nsei(msg));
+ nsvc = gprs_active_nsvc_by_nsei(nsi, msgb_nsei(msg), msgb_bvci(msg));
if (!nsvc) {
int rc;
if (gprs_nsvc_by_nsei(nsi, msgb_nsei(msg))) {
@@ -1358,7 +1466,7 @@ int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
int rc = 0;
/* look up the NSVC based on source address */
- nsvc = nsvc_by_rem_addr(nsi, saddr);
+ nsvc = gprs_nsvc_by_rem_addr(nsi, saddr);
if (!nsvc) {
struct gprs_nsvc *fallback_nsvc;
@@ -1572,6 +1680,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
struct gprs_nsvc **nsvc)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct tlv_parsed tp;
int rc = 0;
msgb_nsei(msg) = (*nsvc)->nsei;
@@ -1594,6 +1703,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
rc = gprs_ns_tx_alive_ack(*nsvc);
break;
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],
nsvc_timer_elapsed_ms(*nsvc));
@@ -1648,15 +1758,68 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
/* mark remote NS-VC as blocked + active */
ns_set_remote_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
break;
+ case SNS_PDUT_CONFIG:
+ if (!nsi->bss_sns_fi)
+ goto unexpected_sns;
+ /* one additional byte ('end flag') before the TLV part starts */
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data+1,
+ msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ /* All sub-network service related message types */
+ rc = gprs_ns_rx_sns(nsi, msg, &tp);
+ break;
+ case SNS_PDUT_ACK:
+ case SNS_PDUT_ADD:
+ case SNS_PDUT_CHANGE_WEIGHT:
+ case SNS_PDUT_DELETE:
+ if (!nsi->bss_sns_fi)
+ goto unexpected_sns;
+ /* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data+5,
+ msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ tp.lv[NS_IE_NSEI].val = nsh->data+2;
+ tp.lv[NS_IE_NSEI].len = 2;
+ tp.lv[NS_IE_TRANS_ID].val = nsh->data+4;
+ tp.lv[NS_IE_TRANS_ID].len = 1;
+ rc = gprs_ns_rx_sns(nsi, msg, &tp);
+ break;
+ case SNS_PDUT_CONFIG_ACK:
+ case SNS_PDUT_SIZE:
+ case SNS_PDUT_SIZE_ACK:
+ if (!nsi->bss_sns_fi)
+ goto unexpected_sns;
+ rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ /* All sub-network service related message types */
+ rc = gprs_ns_rx_sns(nsi, msg, &tp);
+ break;
default:
LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx Unknown NS PDU type 0x%02x\n",
(*nsvc)->nsei, nsh->pdu_type);
rc = -EINVAL;
break;
+unexpected_sns:
+ LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx %s for NS Instance that has no SNS!\n",
+ (*nsvc)->nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ break;
}
return rc;
}
+static bool gprs_sns_fsm_registered = false;
+
/*! Create a new GPRS NS instance
* \param[in] cb Call-back function for incoming BSSGP data
* \returns dynamically allocated gprs_ns_inst
@@ -1665,6 +1828,11 @@ struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb, void *ctx)
{
struct gprs_ns_inst *nsi = talloc_zero(ctx, struct gprs_ns_inst);
+ if (!gprs_sns_fsm_registered) {
+ gprs_sns_init();
+ gprs_sns_fsm_registered = true;
+ }
+
nsi->cb = cb;
INIT_LLIST_HEAD(&nsi->gprs_nsvcs);
nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
@@ -1674,6 +1842,7 @@ struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb, void *ctx)
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 */
/* Create the dummy NSVC that we use for sending
* messages to non-existant/unknown NS-VC's */
@@ -1875,6 +2044,8 @@ int gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause)
{
int rc;
+ ERR_IF_NSVC_USES_SNS(nsvc, "RESET procedure based on API request");
+
LOGP(DNS, LOGL_INFO, "NSEI=%u RESET procedure based on API request\n",
nsvc->nsei);
@@ -1909,7 +2080,7 @@ struct gprs_nsvc *gprs_ns_nsip_connect(struct gprs_ns_inst *nsi,
{
struct gprs_nsvc *nsvc;
- nsvc = nsvc_by_rem_addr(nsi, dest);
+ nsvc = gprs_nsvc_by_rem_addr(nsi, dest);
if (!nsvc)
nsvc = gprs_nsvc_create(nsi, nsvci);
nsvc->ip.bts_addr = *dest;
@@ -1920,6 +2091,47 @@ struct gprs_nsvc *gprs_ns_nsip_connect(struct gprs_ns_inst *nsi,
return nsvc;
}
+/*! Establish a NS connection (from the BSS) to the SGSN using SNS auto-configuration
+ * \param nsi NS-instance
+ * \param[in] dest Destination IP/Port
+ * \param[in] nsei NSEI of the to-be-established NS-VC
+ * \param[in] nsvci NSVCI of the to-be-established NS-VC
+ * \returns struct gprs_nsvc representing the new NS-VC
+ *
+ * This function will establish a single NS/UDP/IP connection in uplink
+ * (BSS to SGSN) direction. It will start with the SNS-SIZE procedure,
+ * followed by BSS-originated SNS-CONFIG, then SGSN-originated SNS-CONFIG.
+ *
+ * Once configuration completes, the user will be notified by the S_SNS_CONFIGURED signal,
+ * at which point he typically would want to initiate NS-RESET by means of gprs_nsvc_reset().
+ */
+struct gprs_nsvc *gprs_ns_nsip_connect_sns(struct gprs_ns_inst *nsi,
+ struct sockaddr_in *dest, uint16_t nsei,
+ uint16_t nsvci)
+{
+ struct gprs_nsvc *nsvc;
+
+ /* FIXME: We are getting the order wrong here. Normally, one would want
+ * to start the SNS FSM *before* creating any NS-VC and then create the NS-VC
+ * after the SNS layer has established the IP/port/etc. However, this would
+ * require some massive code and API changes compared to existing libosmogb,
+ * so let's keep the old logic. */
+ nsvc = gprs_nsvc_by_rem_addr(nsi, dest);
+ if (!nsvc)
+ nsvc = gprs_nsvc_create(nsi, nsvci);
+ nsvc->ip.bts_addr = *dest;
+ nsvc->nsei = nsei;
+ nsvc->remote_end_is_sgsn = 1;
+ /* NSVCs are always UNBLOCKED in IP-SNS */
+ ns_set_state(nsvc, 0);
+
+ if (nsi->bss_sns_fi)
+ osmo_fsm_inst_term(nsi->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nsi->bss_sns_fi = gprs_sns_bss_fsm_alloc(nsi, nsvc, "NSIP");
+ gprs_sns_bss_fsm_start(nsi);
+ return nsvc;
+}
+
void gprs_ns_set_log_ss(int ss)
{
DNS = ss;
@@ -1954,4 +2166,13 @@ void gprs_nsvc_start_test(struct gprs_nsvc *nsvc)
nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
}
+void gprs_start_alive_all_nsvcs(struct gprs_ns_inst *nsi)
+{
+ struct gprs_nsvc *nsvc;
+ llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+ /* start the test procedure */
+ gprs_nsvc_start_test(nsvc);
+ }
+}
+
/*! @} */
diff --git a/src/gb/gprs_ns_sns.c b/src/gb/gprs_ns_sns.c
new file mode 100644
index 00000000..b0ee5d79
--- /dev/null
+++ b/src/gb/gprs_ns_sns.c
@@ -0,0 +1,772 @@
+/* Implementation of 3GPP TS 48.016 NS IP Sub-Network Service */
+/* (C) 2018 by Harald Welte <laforge@gnumonks.org> */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. In theory, the BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns.h>
+
+#include "common_vty.h"
+#include "gb_internal.h"
+
+#define S(x) (1 << (x))
+
+struct gprs_sns_state {
+ struct gprs_ns_inst *nsi;
+ struct gprs_nsvc *nsvc_hack;
+
+ /* local configuration to send to the remote end */
+ struct gprs_ns_ie_ip4_elem *ip4_local;
+ size_t num_ip4_local;
+
+ /* local configuration about our capabilities in terms of connections to
+ * remote (SGSN) side */
+ size_t num_max_nsvcs;
+ size_t num_max_ip4_remote;
+
+ /* remote configuration as received */
+ struct gprs_ns_ie_ip4_elem *ip4_remote;
+ unsigned int num_ip4_remote;
+
+ /* IP-SNS based Gb doesn't have a NSVCI. However, our existing Gb stack
+ * requires a unique NSVCI per NS-VC. Let's simply allocate them dynamically from
+ * the maximum (65533), counting downwards */
+ uint16_t next_nsvci;
+};
+
+static inline struct gprs_ns_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ return gss->nsi;
+}
+
+/* helper function to compute the sum of all (data or signaling) weights */
+static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
+ bool data_weight)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < num; i++) {
+ if (data_weight)
+ weight_sum += ip4[i].data_weight;
+ else
+ weight_sum += ip4[i].sig_weight;
+ }
+ return weight_sum;
+}
+#define ip4_weight_sum_data(x,y) ip4_weight_sum(x, y, true)
+#define ip4_weight_sum_sig(x,y) ip4_weight_sum(x, y, false)
+
+static struct gprs_nsvc *nsvc_by_ip4_elem(struct gprs_ns_inst *nsi,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct sockaddr_in sin;
+ /* copy over. Both data structures use network byte order */
+ sin.sin_addr.s_addr = ip4->ip_addr;
+ sin.sin_port = ip4->udp_port;
+ return gprs_nsvc_by_rem_addr(nsi, &sin);
+}
+
+static struct gprs_nsvc *gprs_nsvc_create_ip4(struct gprs_ns_inst *nsi,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) nsi->bss_sns_fi->priv;
+ struct gprs_nsvc *nsvc;
+ struct sockaddr_in sin;
+ /* copy over. Both data structures use network byte order */
+ sin.sin_addr.s_addr = ip4->ip_addr;
+ sin.sin_port = ip4->udp_port;
+
+ nsvc = gprs_nsvc_create2(nsi, gss->next_nsvci--, ip4->sig_weight, ip4->data_weight);
+ if (!nsvc)
+ return NULL;
+
+ /* NSEI is the same across all NS-VCs */
+ nsvc->nsei = gss->nsvc_hack->nsei;
+ nsvc->nsvci_is_valid = 0;
+ nsvc->ip.bts_addr = sin;
+
+ return nsvc;
+}
+
+static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[i];
+ struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+ if (!nsvc) {
+ /* create, if it doesn't exist */
+ nsvc = gprs_nsvc_create_ip4(nsi, ip4);
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
+ continue;
+ }
+ } else {
+ /* update data / signalling weight */
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+ }
+ LOGPFSML(fi, LOGL_INFO, "NS-VC %s data_weight=%u, sig_weight=%u\n",
+ gprs_ns_ll_str(nsvc), nsvc->data_weight, nsvc->sig_weight);
+ }
+
+ return 0;
+}
+
+/* Add a given remote IPv4 element to gprs_sns_state */
+static int add_remote_ip4_elem(struct gprs_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ if (gss->num_ip4_remote >= gss->num_max_ip4_remote)
+ return -E2BIG;
+
+ gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote, struct gprs_ns_ie_ip4_elem,
+ gss->num_ip4_remote+1);
+ gss->ip4_remote[gss->num_ip4_remote] = *ip4;
+ gss->num_ip4_remote += 1;
+ return 0;
+}
+
+/* Remove a given remote IPv4 element from gprs_sns_state */
+static int remove_remote_ip4_elem(struct gprs_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ continue;
+ /* all array elements < i remain as they are; all > i are shifted left by one */
+ memmove(&gss->ip4_remote[i], &gss->ip4_remote[i+1], gss->num_ip4_remote-i-1);
+ gss->num_ip4_remote -= 1;
+ return 0;
+ }
+ return -1;
+}
+
+/* update the weights for specified remote IPv4 */
+static int update_remote_ip4_elem(struct gprs_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (gss->ip4_remote[i].ip_addr != ip4->ip_addr ||
+ gss->ip4_remote[i].udp_port != ip4->udp_port)
+ continue;
+ gss->ip4_remote[i].sig_weight = ip4->sig_weight;
+ gss->ip4_remote[i].data_weight = ip4->data_weight;
+ return 0;
+ }
+ return -1;
+}
+
+
+static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+
+ /* TODO: Upon receiving an SNS-CHANGEWEIGHT PDU, if the resulting sum of the
+ * signalling weights of all the peer IP endpoints configured for this NSE is
+ * equal to zero or if the resulting sum of the data weights of all the peer IP
+ * endpoints configured for this NSE is equal to zero, the BSS/SGSN shall send an
+ * SNS-ACK PDU with a cause code of "Invalid weights". */
+
+ update_remote_ip4_elem(gss, ip4);
+
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_NOTICE, "Couldn't find NS-VC for SNS-CHANGE_WEIGHT\n");
+ return -NS_CAUSE_NSVC_UNKNOWN;
+ }
+
+ LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight %u->%u, sig_weight %u->%u\n",
+ gprs_ns_ll_str(nsvc), nsvc->data_weight, ip4->data_weight,
+ nsvc->sig_weight, ip4->sig_weight);
+
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+
+ return 0;
+}
+
+static int do_sns_delete(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_nsvc *nsvc = nsvc_by_ip4_elem(nsi, ip4);
+
+ if (remove_remote_ip4_elem(gss, ip4) < 0)
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_NOTICE, "Couldn't find NS-VC for SNS-DELETE\n");
+ return -NS_CAUSE_NSVC_UNKNOWN;
+ }
+ LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns_ll_str(nsvc));
+ gprs_nsvc_delete(nsvc);
+
+ return 0;
+}
+
+static int do_sns_add(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_nsvc *nsvc;
+
+ /* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints
+ * exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send
+ * an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */
+ if (add_remote_ip4_elem(gss, ip4) < 0)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+
+ /* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the
+ * NSE shall send an SNS-ACK PDU with the cause code "Protocol error -
+ * unspecified" */
+ nsvc = nsvc_by_ip4_elem(nsi, ip4);
+ if (nsvc)
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+
+ nsvc = gprs_nsvc_create_ip4(nsi, ip4);
+ if (!nsvc) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-ADD: Failed to create NSVC\n");
+ remove_remote_ip4_elem(gss, ip4);
+ return -NS_CAUSE_EQUIP_FAIL;
+ }
+ LOGPFSML(fi, LOGL_INFO, "ADD NS-VC %s data_weight=%u, sig_weight=%u\n",
+ gprs_ns_ll_str(nsvc), nsvc->data_weight, nsvc->sig_weight);
+ /* Start the test procedure for this new NS-VC */
+ gprs_nsvc_start_test(nsvc);
+ return 0;
+}
+
+
+
+/***********************************************************************
+ * BSS-side FSM for IP Sub-Network Service
+ ***********************************************************************/
+
+enum gprs_sns_bss_state {
+ GPRS_SNS_ST_UNCONFIGURED,
+ GPRS_SNS_ST_SIZE, /*!< SNS-SIZE procedure ongoing */
+ GPRS_SNS_ST_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
+ GPRS_SNS_ST_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
+ GPRS_SNS_ST_CONFIGURED,
+};
+
+enum gprs_sns_event {
+ GPRS_SNS_EV_START,
+ GPRS_SNS_EV_SIZE,
+ GPRS_SNS_EV_SIZE_ACK,
+ GPRS_SNS_EV_CONFIG,
+ GPRS_SNS_EV_CONFIG_END, /*!< SNS-CONFIG with end flag received */
+ GPRS_SNS_EV_CONFIG_ACK,
+ GPRS_SNS_EV_ADD,
+ GPRS_SNS_EV_DELETE,
+ GPRS_SNS_EV_CHANGE_WEIGHT,
+};
+
+static const struct value_string gprs_sns_event_names[] = {
+ { GPRS_SNS_EV_START, "START" },
+ { GPRS_SNS_EV_SIZE, "SIZE" },
+ { GPRS_SNS_EV_SIZE_ACK, "SIZE_ACK" },
+ { GPRS_SNS_EV_CONFIG, "CONFIG" },
+ { GPRS_SNS_EV_CONFIG_END, "CONFIG_END" },
+ { GPRS_SNS_EV_CONFIG_ACK, "CONFIG_ACK" },
+ { GPRS_SNS_EV_ADD, "ADD" },
+ { GPRS_SNS_EV_DELETE, "DELETE" },
+ { GPRS_SNS_EV_CHANGE_WEIGHT, "CHANGE_WEIGHT" },
+ { 0, NULL }
+};
+
+static void gprs_sns_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ switch (event) {
+ case GPRS_SNS_EV_START:
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void gprs_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_SIZE_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n",
+ gprs_ns_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* FIXME: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS,
+ nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void gprs_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ uint16_t num_max_ip4_remote = gss->num_max_ip4_remote;
+
+ gprs_ns_tx_sns_size(gss->nsvc_hack, true, gss->num_max_nsvcs, &num_max_ip4_remote, NULL);
+}
+
+
+static void gprs_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ //struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ //struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_CONFIG_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ /* FIXME: What to do? */
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, 0, 0);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void gprs_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ /* Transmit SNS-CONFIG */
+ gprs_ns_tx_sns_config(gss->nsvc_hack, true, gss->ip4_local, gss->num_ip4_local);
+}
+
+static void gprs_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_sns_state *gss = (struct gprs_sns_state *) fi->priv;
+ struct tlv_parsed *tp = NULL;
+ struct gprs_ns_inst *nsi = ns_inst_from_fi(fi);
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ uint8_t cause;
+
+ switch (event) {
+ case GPRS_SNS_EV_CONFIG_END:
+ case GPRS_SNS_EV_CONFIG:
+ tp = data;
+#if 0 /* part of incoming SNS-SIZE (doesn't happen on BSS side */
+ if (TLVP_PRESENT(tp, NS_IE_RESET_FLAG)) {
+ /* reset all existing config */
+ if (gss->ip4_remote)
+ talloc_free(gss->ip4_remote);
+ gss->num_ip4_remote = 0;
+ }
+#endif
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ gprs_ns_tx_sns_config_ack(gss->nsvc_hack, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
+ /* realloc to the new size */
+ gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote,
+ struct gprs_ns_ie_ip4_elem,
+ gss->num_ip4_remote+num_v4);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->ip4_remote[gss->num_ip4_remote], v4_list, num_v4*sizeof(*v4_list));
+ gss->num_ip4_remote += num_v4;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
+ gss->num_ip4_remote);
+ if (event == GPRS_SNS_EV_CONFIG_END) {
+ /* check if sum of data / sig weights == 0 */
+ if (ip4_weight_sum_data(gss->ip4_remote, gss->num_ip4_remote) == 0 ||
+ ip4_weight_sum_sig(gss->ip4_remote, gss->num_ip4_remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ gprs_ns_tx_sns_config_ack(gss->nsvc_hack, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ create_missing_nsvcs(fi);