mirror of https://gerrit.osmocom.org/libosmocore
1928 lines
56 KiB
C
1928 lines
56 KiB
C
/*! \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_type {
|
|
IPv4,
|
|
IPv6,
|
|
};
|
|
|
|
enum gprs_sns_bss_state {
|
|
GPRS_SNS_ST_UNCONFIGURED,
|
|
GPRS_SNS_ST_SIZE, /*!< SNS-SIZE procedure ongoing */
|
|
GPRS_SNS_ST_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
|
|
GPRS_SNS_ST_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
|
|
GPRS_SNS_ST_CONFIGURED,
|
|
};
|
|
|
|
enum gprs_sns_event {
|
|
GPRS_SNS_EV_REQ_SELECT_ENDPOINT, /*!< Select a SNS endpoint from the list */
|
|
GPRS_SNS_EV_RX_SIZE,
|
|
GPRS_SNS_EV_RX_SIZE_ACK,
|
|
GPRS_SNS_EV_RX_CONFIG,
|
|
GPRS_SNS_EV_RX_CONFIG_END, /*!< SNS-CONFIG with end flag received */
|
|
GPRS_SNS_EV_RX_CONFIG_ACK,
|
|
GPRS_SNS_EV_RX_ADD,
|
|
GPRS_SNS_EV_RX_DELETE,
|
|
GPRS_SNS_EV_RX_CHANGE_WEIGHT,
|
|
GPRS_SNS_EV_RX_ACK, /*!< Rx of SNS-ACK (response to ADD/DELETE/CHG_WEIGHT */
|
|
GPRS_SNS_EV_REQ_NO_NSVC, /*!< no more NS-VC remaining (all dead) */
|
|
GPRS_SNS_EV_REQ_NSVC_ALIVE, /*!< a NS-VC became alive */
|
|
GPRS_SNS_EV_REQ_ADD_BIND, /*!< add a new local bind to this NSE */
|
|
GPRS_SNS_EV_REQ_DELETE_BIND, /*!< remove a local bind from this NSE */
|
|
};
|
|
|
|
static const struct value_string gprs_sns_event_names[] = {
|
|
{ GPRS_SNS_EV_REQ_SELECT_ENDPOINT, "REQ_SELECT_ENDPOINT" },
|
|
{ GPRS_SNS_EV_RX_SIZE, "RX_SIZE" },
|
|
{ GPRS_SNS_EV_RX_SIZE_ACK, "RX_SIZE_ACK" },
|
|
{ GPRS_SNS_EV_RX_CONFIG, "RX_CONFIG" },
|
|
{ GPRS_SNS_EV_RX_CONFIG_END, "RX_CONFIG_END" },
|
|
{ GPRS_SNS_EV_RX_CONFIG_ACK, "RX_CONFIG_ACK" },
|
|
{ GPRS_SNS_EV_RX_ADD, "RX_ADD" },
|
|
{ GPRS_SNS_EV_RX_DELETE, "RX_DELETE" },
|
|
{ GPRS_SNS_EV_RX_ACK, "RX_ACK" },
|
|
{ GPRS_SNS_EV_RX_CHANGE_WEIGHT, "RX_CHANGE_WEIGHT" },
|
|
{ GPRS_SNS_EV_REQ_NO_NSVC, "REQ_NO_NSVC" },
|
|
{ GPRS_SNS_EV_REQ_NSVC_ALIVE, "REQ_NSVC_ALIVE"},
|
|
{ GPRS_SNS_EV_REQ_ADD_BIND, "REQ_ADD_BIND"},
|
|
{ GPRS_SNS_EV_REQ_DELETE_BIND, "REQ_DELETE_BIND"},
|
|
{ 0, NULL }
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
struct ns2_sns_state {
|
|
struct gprs_ns2_nse *nse;
|
|
|
|
enum ns2_sns_type ip;
|
|
|
|
/* 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;
|
|
|
|
/* 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 gprs_ns_ie_ip4_elem *ip4_local;
|
|
size_t num_ip4_local;
|
|
|
|
/* local configuration to send to the remote end */
|
|
struct gprs_ns_ie_ip6_elem *ip6_local;
|
|
size_t num_ip6_local;
|
|
|
|
/* local configuration about our capabilities in terms of connections to
|
|
* remote (SGSN) side */
|
|
size_t num_max_nsvcs;
|
|
size_t num_max_ip4_remote;
|
|
size_t num_max_ip6_remote;
|
|
|
|
/* remote configuration as received */
|
|
struct gprs_ns_ie_ip4_elem *ip4_remote;
|
|
unsigned int num_ip4_remote;
|
|
|
|
/* remote configuration as received */
|
|
struct gprs_ns_ie_ip6_elem *ip6_remote;
|
|
unsigned int num_ip6_remote;
|
|
};
|
|
|
|
static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
return gss->nse;
|
|
}
|
|
|
|
/* helper function to compute the sum of all (data or signaling) weights */
|
|
static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
|
|
bool data_weight)
|
|
{
|
|
unsigned int i;
|
|
int weight_sum = 0;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
if (data_weight)
|
|
weight_sum += ip4[i].data_weight;
|
|
else
|
|
weight_sum += ip4[i].sig_weight;
|
|
}
|
|
return weight_sum;
|
|
}
|
|
#define ip4_weight_sum_data(x,y) ip4_weight_sum(x, y, true)
|
|
#define ip4_weight_sum_sig(x,y) ip4_weight_sum(x, y, false)
|
|
|
|
/* helper function to compute the sum of all (data or signaling) weights */
|
|
static int ip6_weight_sum(const struct gprs_ns_ie_ip6_elem *ip6, unsigned int num,
|
|
bool data_weight)
|
|
{
|
|
unsigned int i;
|
|
int weight_sum = 0;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
if (data_weight)
|
|
weight_sum += ip6[i].data_weight;
|
|
else
|
|
weight_sum += ip6[i].sig_weight;
|
|
}
|
|
return weight_sum;
|
|
}
|
|
#define ip6_weight_sum_data(x,y) ip6_weight_sum(x, y, true)
|
|
#define ip6_weight_sum_sig(x,y) ip6_weight_sum(x, y, false)
|
|
|
|
static int nss_weight_sum(const struct ns2_sns_state *nss, bool data_weight)
|
|
{
|
|
return ip4_weight_sum(nss->ip4_remote, nss->num_ip4_remote, data_weight) +
|
|
ip6_weight_sum(nss->ip6_remote, nss->num_ip6_remote, data_weight);
|
|
}
|
|
#define nss_weight_sum_data(nss) nss_weight_sum(nss, true)
|
|
#define nss_weight_sum_sig(nss) nss_weight_sum(nss, 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_REQ_NO_NSVC, NULL);
|
|
}
|
|
|
|
static void ns2_clear_ipv46_entries(struct ns2_sns_state *gss)
|
|
{
|
|
TALLOC_FREE(gss->ip4_local);
|
|
TALLOC_FREE(gss->ip4_remote);
|
|
TALLOC_FREE(gss->ip6_local);
|
|
TALLOC_FREE(gss->ip6_remote);
|
|
|
|
gss->num_ip4_local = 0;
|
|
gss->num_ip4_remote = 0;
|
|
gss->num_ip6_local = 0;
|
|
gss->num_ip6_remote = 0;
|
|
}
|
|
|
|
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 int create_missing_nsvcs(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
|
|
struct gprs_ns2_vc *nsvc;
|
|
struct gprs_ns2_vc_bind *bind;
|
|
struct osmo_sockaddr remote = { };
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gss->num_ip4_remote; i++) {
|
|
const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[i];
|
|
|
|
remote.u.sin.sin_family = AF_INET;
|
|
remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
|
|
remote.u.sin.sin_port = ip4->udp_port;
|
|
|
|
llist_for_each_entry(bind, &nse->nsi->binding, list) {
|
|
bool found = false;
|
|
if (bind->ll != GPRS_NS2_LL_UDP)
|
|
continue;
|
|
|
|
llist_for_each_entry(nsvc, &nse->nsvc, list) {
|
|
if (nsvc->bind != bind)
|
|
continue;
|
|
|
|
if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_remote(nsvc))) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
|
|
if (!nsvc) {
|
|
/* TODO: add to a list to send back a NS-STATUS */
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* update data / signalling weight */
|
|
nsvc->data_weight = ip4->data_weight;
|
|
nsvc->sig_weight = ip4->sig_weight;
|
|
nsvc->sns_only = false;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < gss->num_ip6_remote; i++) {
|
|
const struct gprs_ns_ie_ip6_elem *ip6 = &gss->ip6_remote[i];
|
|
|
|
remote.u.sin6.sin6_family = AF_INET6;
|
|
remote.u.sin6.sin6_addr = ip6->ip_addr;
|
|
remote.u.sin6.sin6_port = ip6->udp_port;
|
|
|
|
llist_for_each_entry(bind, &nse->nsi->binding, list) {
|
|
bool found = false;
|
|
if (bind->ll != GPRS_NS2_LL_UDP)
|
|
continue;
|
|
|
|
llist_for_each_entry(nsvc, &nse->nsvc, list) {
|
|
if (nsvc->bind != bind)
|
|
continue;
|
|
|
|
if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_remote(nsvc))) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
|
|
if (!nsvc) {
|
|
/* TODO: add to a list to send back a NS-STATUS */
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* update data / signalling weight */
|
|
nsvc->data_weight = ip6->data_weight;
|
|
nsvc->sig_weight = ip6->sig_weight;
|
|
nsvc->sns_only = false;
|
|
}
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Add a given remote IPv4 element to gprs_sns_state */
|
|
static int add_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (gss->num_ip4_remote >= gss->num_max_ip4_remote)
|
|
return -NS_CAUSE_INVAL_NR_NS_VC;
|
|
|
|
/* check for duplicates */
|
|
for (i = 0; i < gss->num_ip4_remote; i++) {
|
|
if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
|
|
continue;
|
|
/* TODO: log message duplicate */
|
|
return -NS_CAUSE_PROTO_ERR_UNSPEC;
|
|
}
|
|
|
|
gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote, struct gprs_ns_ie_ip4_elem,
|
|
gss->num_ip4_remote+1);
|
|
gss->ip4_remote[gss->num_ip4_remote] = *ip4;
|
|
gss->num_ip4_remote += 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Remove a given remote IPv4 element from gprs_sns_state */
|
|
static int remove_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gss->num_ip4_remote; i++) {
|
|
if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
|
|
continue;
|
|
/* all array elements < i remain as they are; all > i are shifted left by one */
|
|
memmove(&gss->ip4_remote[i], &gss->ip4_remote[i+1], gss->num_ip4_remote-i-1);
|
|
gss->num_ip4_remote -= 1;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* update the weights for specified remote IPv4 */
|
|
static int update_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gss->num_ip4_remote; i++) {
|
|
if (gss->ip4_remote[i].ip_addr != ip4->ip_addr ||
|
|
gss->ip4_remote[i].udp_port != ip4->udp_port)
|
|
continue;
|
|
|
|
gss->ip4_remote[i].sig_weight = ip4->sig_weight;
|
|
gss->ip4_remote[i].data_weight = ip4->data_weight;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Add a given remote IPv6 element to gprs_sns_state */
|
|
static int add_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
|
|
{
|
|
if (gss->num_ip6_remote >= gss->num_max_ip6_remote)
|
|
return -NS_CAUSE_INVAL_NR_NS_VC;
|
|
|
|
gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote, struct gprs_ns_ie_ip6_elem,
|
|
gss->num_ip6_remote+1);
|
|
gss->ip6_remote[gss->num_ip6_remote] = *ip6;
|
|
gss->num_ip6_remote += 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Remove a given remote IPv6 element from gprs_sns_state */
|
|
static int remove_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gss->num_ip6_remote; i++) {
|
|
if (memcmp(&gss->ip6_remote[i], ip6, sizeof(*ip6)))
|
|
continue;
|
|
/* all array elements < i remain as they are; all > i are shifted left by one */
|
|
memmove(&gss->ip6_remote[i], &gss->ip6_remote[i+1], gss->num_ip6_remote-i-1);
|
|
gss->num_ip6_remote -= 1;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* update the weights for specified remote IPv6 */
|
|
static int update_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gss->num_ip6_remote; i++) {
|
|
if (memcmp(&gss->ip6_remote[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
|
|
gss->ip6_remote[i].udp_port != ip6->udp_port)
|
|
continue;
|
|
gss->ip6_remote[i].sig_weight = ip6->sig_weight;
|
|
gss->ip6_remote[i].data_weight = ip6->data_weight;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
|
|
struct gprs_ns2_vc *nsvc;
|
|
struct osmo_sockaddr sa = {};
|
|
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_remote_ip4_elem(gss, ip4))
|
|
return -NS_CAUSE_UNKN_IP_EP;
|
|
|
|
/* copy over. Both data structures use network byte order */
|
|
sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
|
|
sa.u.sin.sin_port = ip4->udp_port;
|
|
sa.u.sin.sin_family = AF_INET;
|
|
new_signal = ip4->sig_weight;
|
|
new_data = ip4->data_weight;
|
|
} else if (ip6) {
|
|
if (update_remote_ip6_elem(gss, ip6))
|
|
return -NS_CAUSE_UNKN_IP_EP;
|
|
|
|
/* copy over. Both data structures use network byte order */
|
|
sa.u.sin6.sin6_addr = ip6->ip_addr;
|
|
sa.u.sin6.sin6_port = ip6->udp_port;
|
|
sa.u.sin6.sin6_family = AF_INET6;
|
|
new_signal = ip6->sig_weight;
|
|
new_data = ip6->data_weight;
|
|
} else {
|
|
OSMO_ASSERT(false);
|
|
}
|
|
|
|
llist_for_each_entry(nsvc, &nse->nsvc, list) {
|
|
remote = gprs_ns2_ip_vc_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_remote_ip4_elem(gss, ip4) < 0)
|
|
return -NS_CAUSE_UNKN_IP_EP;
|
|
/* copy over. Both data structures use network byte order */
|
|
sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
|
|
sa.u.sin.sin_port = ip4->udp_port;
|
|
sa.u.sin.sin_family = AF_INET;
|
|
} else if (ip6) {
|
|
if (remove_remote_ip6_elem(gss, ip6))
|
|
return -NS_CAUSE_UNKN_IP_EP;
|
|
|
|
/* copy over. Both data structures use network byte order */
|
|
sa.u.sin6.sin6_addr = ip6->ip_addr;
|
|
sa.u.sin6.sin6_port = ip6->udp_port;
|
|
sa.u.sin6.sin6_family = AF_INET6;
|
|
} else {
|
|
OSMO_ASSERT(false);
|
|
}
|
|
|
|
llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
|
|
remote = gprs_ns2_ip_vc_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->ip) {
|
|
case IPv4:
|
|
rc = add_remote_ip4_elem(gss, ip4);
|
|
break;
|
|
case IPv6:
|
|
rc = add_remote_ip6_elem(gss, ip6);
|
|
break;
|
|
default:
|
|
/* the gss->ip is initialized with the bss */
|
|
OSMO_ASSERT(false);
|
|
}
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the
|
|
* NSE shall send an SNS-ACK PDU with the cause code "Protocol error -
|
|
* unspecified" */
|
|
switch (gss->ip) {
|
|
case IPv4:
|
|
nsvc = nsvc_by_ip4_elem(nse, ip4);
|
|
if (nsvc) {
|
|
/* the nsvc should be already in sync with the ip4 / ip6 elements */
|
|
return -NS_CAUSE_PROTO_ERR_UNSPEC;
|
|
}
|
|
|
|
/* TODO: failure case */
|
|
ns2_nsvc_create_ip4(fi, nse, ip4);
|
|
break;
|
|
case IPv6:
|
|
nsvc = nsvc_by_ip6_elem(nse, ip6);
|
|
if (nsvc) {
|
|
/* the nsvc should be already in sync with the ip4 / ip6 elements */
|
|
return -NS_CAUSE_PROTO_ERR_UNSPEC;
|
|
}
|
|
|
|
/* TODO: failure case */
|
|
ns2_nsvc_create_ip6(fi, nse, ip6);
|
|
break;
|
|
}
|
|
|
|
gprs_ns2_start_alive_all_nsvcs(nse);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void ns2_sns_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
/* empty state - SNS Select will start by ns2_sns_st_all_action() */
|
|
}
|
|
|
|
static void ns2_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
|
|
struct gprs_ns2_inst *nsi = nse->nsi;
|
|
struct tlv_parsed *tp = NULL;
|
|
|
|
switch (event) {
|
|
case GPRS_SNS_EV_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_CONFIG_BSS,
|
|
nsi->timeout[NS_TOUT_TSNS_PROV], 2);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(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;
|
|
struct osmo_sockaddr *remote;
|
|
const struct osmo_sockaddr *sa;
|
|
struct osmo_sockaddr local;
|
|
int count;
|
|
|
|
ns2_clear_ipv46_entries(gss);
|
|
|
|
/* no initial available */
|
|
if (!gss->initial)
|
|
return;
|
|
|
|
remote = &gss->initial->saddr;
|
|
|
|
/* 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->ip) {
|
|
case IPv4:
|
|
ip4_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip4_elem) * count);
|
|
if (!ip4_elems)
|
|
return;
|
|
|
|
gss->ip4_local = 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->num_ip4_local = count;
|
|
gss->num_max_ip4_remote = 4;
|
|
gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip4_remote * gss->num_ip4_local, 8);
|
|
break;
|
|
case IPv6:
|
|
/* IPv6 */
|
|
ip6_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip6_elem) * count);
|
|
if (!ip6_elems)
|
|
return;
|
|
|
|
gss->ip6_local = 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->num_ip6_local = count;
|
|
gss->num_max_ip6_remote = 4;
|
|
gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->num_ip6_local, 8);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* setup all dynamic SNS settings, create a new nsvc and send the SIZE */
|
|
static void ns2_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
|
|
/* 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_SIZE)
|
|
gss->N = 0;
|
|
|
|
gss->alive = false;
|
|
|
|
ns2_sns_compute_local_ep_from_binds(fi);
|
|
|
|
/* take the first bind or take the next bind */
|
|
if (!gss->initial_bind) {
|
|
gss->initial_bind = llist_first_entry(&gss->binds, struct ns2_sns_bind, list);
|
|
} else {
|
|
if (gss->initial_bind->list.next != &gss->binds) {
|
|
gss->initial_bind = llist_entry(gss->initial_bind->list.next, struct ns2_sns_bind, list);
|
|
} else {
|
|
gss->initial_bind = llist_first_entry(&gss->binds, struct ns2_sns_bind, list);
|
|
}
|
|
}
|
|
|
|
|
|
/* 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;
|
|
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->num_max_ip4_remote, -1);
|
|
else
|
|
ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->num_max_ip6_remote);
|
|
}
|
|
|
|
static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct tlv_parsed *tp = NULL;
|
|
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
|
|
|
|
switch (event) {
|
|
case GPRS_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_CONFIG_SGSN, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void ns2_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
|
|
if (old_state != GPRS_SNS_ST_CONFIG_BSS)
|
|
gss->N = 0;
|
|
|
|
/* Transmit SNS-CONFIG */
|
|
switch (gss->ip) {
|
|
case IPv4:
|
|
ns2_tx_sns_config(gss->sns_nsvc, true,
|
|
gss->ip4_local, gss->num_ip4_local,
|
|
NULL, 0);
|
|
break;
|
|
case IPv6:
|
|
ns2_tx_sns_config(gss->sns_nsvc, true,
|
|
NULL, 0,
|
|
gss->ip6_local, gss->num_ip6_local);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 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->ip6_remote)
|
|
return -NS_CAUSE_INVAL_NR_IPv4_EP;
|
|
|
|
/* 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 (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->ip4_remote)
|
|
return -NS_CAUSE_INVAL_NR_IPv6_EP;
|
|
|
|
/* realloc to the new size */
|
|
gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote,
|
|
struct gprs_ns_ie_ip6_elem,
|
|
gss->num_ip6_remote + num_v6);
|
|
/* append the new entries to the end of the list */
|
|
memcpy(&gss->ip6_remote[gss->num_ip6_remote], v6_list, num_v6*sizeof(*v6_list));
|
|
gss->num_ip6_remote += num_v6;
|
|
|
|
LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %u entries\n",
|
|
gss->num_ip6_remote);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ns2_sns_st_config_sgsn_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
|
|
if (old_state != GPRS_SNS_ST_CONFIG_SGSN)
|
|
gss->N = 0;
|
|
}
|
|
|
|
static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
|
|
uint8_t cause;
|
|
int rc;
|
|
|
|
switch (event) {
|
|
case GPRS_SNS_EV_RX_CONFIG_END:
|
|
case GPRS_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 == GPRS_SNS_EV_RX_CONFIG_END) {
|
|
/* check if sum of data / sig weights == 0 */
|
|
if (nss_weight_sum_data(gss) == 0 || nss_weight_sum_sig(gss) == 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 GPRS_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->ip == IPv4) {
|
|
if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
|
|
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
|
|
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
|
|
for (i = 0; i < num_v4; i++) {
|
|
unsigned int j;
|
|
rc = do_sns_add(fi, &v4_list[i], NULL);
|
|
if (rc < 0) {
|
|
/* rollback/undo to restore previous state */
|
|
for (j = 0; j < i; j++)
|
|
do_sns_delete(fi, &v4_list[j], NULL);
|
|
cause = -rc;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
} else { /* IPv6 */
|
|
if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
|
|
cause = NS_CAUSE_INVAL_NR_IPv6_EP;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
|
|
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
|
|
for (i = 0; i < num_v6; i++) {
|
|
unsigned int j;
|
|
rc = do_sns_add(fi, NULL, &v6_list[i]);
|
|
if (rc < 0) {
|
|
/* rollback/undo to restore previous state */
|
|
for (j = 0; j < i; j++)
|
|
do_sns_delete(fi, NULL, &v6_list[j]);
|
|
cause = -rc;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
|
|
}
|
|
|
|
static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
|
|
struct ns2_sns_state *gss,
|
|
struct tlv_parsed *tp)
|
|
{
|
|
const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
|
|
const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
|
|
int num_v4 = 0, num_v6 = 0;
|
|
uint8_t trans_id, cause = 0xff;
|
|
unsigned int i;
|
|
int rc = 0;
|
|
|
|
/* TODO: split up delete into v4 + v6
|
|
* TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa
|
|
* TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time
|
|
*/
|
|
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
|
|
if (gss->ip == IPv4) {
|
|
if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
|
|
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
|
|
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
|
|
for ( i = 0; i < num_v4; i++) {
|
|
rc = do_sns_delete(fi, &v4_list[i], NULL);
|
|
if (rc < 0) {
|
|
cause = -rc;
|
|
/* continue to delete others */
|
|
}
|
|
}
|
|
if (cause != 0xff) {
|
|
/* TODO: create list of not-deleted and return it */
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
} else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) {
|
|
/* delete all NS-VCs for given IPv4 address */
|
|
const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
|
|
struct gprs_ns_ie_ip4_elem *ip4_remote;
|
|
uint32_t ip_addr = *(uint32_t *)(ie+1);
|
|
if (ie[0] != 0x01) { /* Address Type != IPv4 */
|
|
cause = NS_CAUSE_UNKN_IP_ADDR;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
/* make a copy as do_sns_delete() will change the array underneath us */
|
|
ip4_remote = talloc_memdup(fi, gss->ip4_remote,
|
|
gss->num_ip4_remote * sizeof(*v4_list));
|
|
for (i = 0; i < gss->num_ip4_remote; i++) {
|
|
if (ip4_remote[i].ip_addr == ip_addr) {
|
|
rc = do_sns_delete(fi, &ip4_remote[i], NULL);
|
|
if (rc < 0) {
|
|
cause = -rc;
|
|
/* continue to delete others */
|
|
}
|
|
}
|
|
}
|
|
talloc_free(ip4_remote);
|
|
if (cause != 0xff) {
|
|
/* TODO: create list of not-deleted and return it */
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
} else {
|
|
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
} else { /* IPv6 */
|
|
if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
|
|
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
|
|
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
|
|
for (i = 0; i < num_v6; i++) {
|
|
rc = do_sns_delete(fi, NULL, &v6_list[i]);
|
|
if (rc < 0) {
|
|
cause = -rc;
|
|
/* continue to delete others */
|
|
}
|
|
}
|
|
if (cause != 0xff) {
|
|
/* TODO: create list of not-deleted and return it */
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
} else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) {
|
|
/* delete all NS-VCs for given IPv4 address */
|
|
const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
|
|
struct gprs_ns_ie_ip6_elem *ip6_remote;
|
|
struct in6_addr ip6_addr;
|
|
unsigned int i;
|
|
if (ie[0] != 0x02) { /* Address Type != IPv6 */
|
|
cause = NS_CAUSE_UNKN_IP_ADDR;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr));
|
|
/* make a copy as do_sns_delete() will change the array underneath us */
|
|
ip6_remote = talloc_memdup(fi, gss->ip6_remote,
|
|
gss->num_ip6_remote * sizeof(*v4_list));
|
|
for (i = 0; i < gss->num_ip6_remote; i++) {
|
|
if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) {
|
|
rc = do_sns_delete(fi, NULL, &ip6_remote[i]);
|
|
if (rc < 0) {
|
|
cause = -rc;
|
|
/* continue to delete others */
|
|
}
|
|
}
|
|
}
|
|
|
|
talloc_free(ip6_remote);
|
|
if (cause != 0xff) {
|
|
/* TODO: create list of not-deleted and return it */
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
} else {
|
|
cause = NS_CAUSE_INVAL_NR_IPv6_EP;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
}
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
|
|
}
|
|
|
|
static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi,
|
|
struct ns2_sns_state *gss,
|
|
struct tlv_parsed *tp)
|
|
{
|
|
const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
|
|
const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
|
|
int num_v4 = 0, num_v6 = 0;
|
|
uint8_t trans_id, cause = 0xff;
|
|
int rc = 0;
|
|
unsigned int i;
|
|
|
|
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
|
|
if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
|
|
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
|
|
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
|
|
for (i = 0; i < num_v4; i++) {
|
|
rc = do_sns_change_weight(fi, &v4_list[i], NULL);
|
|
if (rc < 0) {
|
|
cause = -rc;
|
|
/* continue to others */
|
|
}
|
|
}
|
|
if (cause != 0xff) {
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
} else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
|
|
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
|
|
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
|
|
for (i = 0; i < num_v6; i++) {
|
|
rc = do_sns_change_weight(fi, NULL, &v6_list[i]);
|
|
if (rc < 0) {
|
|
cause = -rc;
|
|
/* continue to others */
|
|
}
|
|
}
|
|
if (cause != 0xff) {
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
} else {
|
|
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
|
|
return;
|
|
}
|
|
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
|
|
}
|
|
|
|
static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
|
|
struct tlv_parsed *tp = data;
|
|
|
|
switch (event) {
|
|
case GPRS_SNS_EV_RX_ADD:
|
|
ns2_sns_st_configured_add(fi, gss, tp);
|
|
break;
|
|
case GPRS_SNS_EV_RX_DELETE:
|
|
ns2_sns_st_configured_delete(fi, gss, tp);
|
|
break;
|
|
case GPRS_SNS_EV_RX_CHANGE_WEIGHT:
|
|
ns2_sns_st_configured_change(fi, gss, tp);
|
|
break;
|
|
case GPRS_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_nse *nse = nse_inst_from_fi(fi);
|
|
ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED);
|
|
}
|
|
|
|
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_SIZE),
|
|
.name = "UNCONFIGURED",
|
|
.action = ns2_sns_st_unconfigured,
|
|
},
|
|
[GPRS_SNS_ST_SIZE] = {
|
|
.in_event_mask = S(GPRS_SNS_EV_RX_SIZE_ACK),
|
|
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
|
|
S(GPRS_SNS_ST_SIZE) |
|
|
S(GPRS_SNS_ST_CONFIG_BSS),
|
|
.name = "SIZE",
|
|
.action = ns2_sns_st_size,
|
|
.onenter = ns2_sns_st_size_onenter,
|
|
},
|
|
[GPRS_SNS_ST_CONFIG_BSS] = {
|
|
.in_event_mask = S(GPRS_SNS_EV_RX_CONFIG_ACK),
|
|
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
|
|
S(GPRS_SNS_ST_CONFIG_BSS) |
|
|
S(GPRS_SNS_ST_CONFIG_SGSN) |
|
|
S(GPRS_SNS_ST_SIZE),
|
|
.name = "CONFIG_BSS",
|
|
.action = ns2_sns_st_config_bss,
|
|
.onenter = ns2_sns_st_config_bss_onenter,
|
|
},
|
|
[GPRS_SNS_ST_CONFIG_SGSN] = {
|
|
.in_event_mask = S(GPRS_SNS_EV_RX_CONFIG) |
|
|
S(GPRS_SNS_EV_RX_CONFIG_END),
|
|
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
|
|
S(GPRS_SNS_ST_CONFIG_SGSN) |
|
|
S(GPRS_SNS_ST_CONFIGURED) |
|
|
S(GPRS_SNS_ST_SIZE),
|
|
.name = "CONFIG_SGSN",
|
|
.action = ns2_sns_st_config_sgsn,
|
|
.onenter = ns2_sns_st_config_sgsn_onenter,
|
|
},
|
|
[GPRS_SNS_ST_CONFIGURED] = {
|
|
.in_event_mask = S(GPRS_SNS_EV_RX_ADD) |
|
|
S(GPRS_SNS_EV_RX_DELETE) |
|
|
S(GPRS_SNS_EV_RX_CHANGE_WEIGHT) |
|
|
S(GPRS_SNS_EV_REQ_NSVC_ALIVE),
|
|
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
|
|
S(GPRS_SNS_ST_SIZE),
|
|
.name = "CONFIGURED",
|
|
.action = ns2_sns_st_configured,
|
|
.onenter = ns2_sns_st_configured_onenter,
|
|
},
|
|
};
|
|
|
|
static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct 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]) {
|
|
LOGPFSML(fi, LOGL_ERROR, "NSE %d: Size retries failed. Selecting next IP-SNS endpoint.\n", nse->nsei);
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
|
|
} else {
|
|
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
|
|
}
|
|
break;
|
|
case 2:
|
|
if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
|
|
LOGPFSML(fi, LOGL_ERROR, "NSE %d: BSS Config retries failed. Selecting next IP-SNS endpoint.\n", nse->nsei);
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
|
|
} else {
|
|
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
|
|
}
|
|
break;
|
|
case 3:
|
|
if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
|
|
LOGPFSML(fi, LOGL_ERROR, "NSE %d: SGSN Config retries failed. Selecting next IP-SNS endpoint.\n", nse->nsei);
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
|
|
} else {
|
|
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
|
|
}
|
|
break;
|
|
case 4:
|
|
LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint.\n", nse->nsei);
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 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_bind *sbind;
|
|
struct gprs_ns2_vc *nsvc, *nsvc2;
|
|
|
|
switch (event) {
|
|
case GPRS_SNS_EV_REQ_ADD_BIND:
|
|
sbind = data;
|
|
switch (fi->state) {
|
|
case GPRS_SNS_ST_UNCONFIGURED:
|
|
osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
|
|
break;
|
|
case GPRS_SNS_ST_SIZE:
|
|
/* TODO: add the ip4 element to the list */
|
|
break;
|
|
case GPRS_SNS_ST_CONFIG_BSS:
|
|
case GPRS_SNS_ST_CONFIG_SGSN:
|
|
case GPRS_SNS_ST_CONFIGURED:
|
|
/* TODO: add to SNS-IP procedure queue & add nsvc() */
|
|
break;
|
|
}
|
|
break;
|
|
case GPRS_SNS_EV_REQ_DELETE_BIND:
|
|
sbind = data;
|
|
switch (fi->state) {
|
|
case GPRS_SNS_ST_UNCONFIGURED:
|
|
break;
|
|
case GPRS_SNS_ST_SIZE:
|
|
/* TODO: remove the ip4 element from the list */
|
|
llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
|
|
if (nsvc->bind == sbind->bind) {
|
|
gprs_ns2_free_nsvc(nsvc);
|
|
}
|
|
}
|
|
break;
|
|
case GPRS_SNS_ST_CONFIG_BSS:
|
|
case GPRS_SNS_ST_CONFIG_SGSN:
|
|
case GPRS_SNS_ST_CONFIGURED:
|
|
/* TODO: do an delete SNS-IP procedure */
|
|
/* TODO: remove the ip4 element to the list */
|
|
llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
|
|
if (nsvc->bind == sbind->bind) {
|
|
gprs_ns2_free_nsvc(nsvc);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
/* if this is the last bind, the free_nsvc() will trigger a reselection */
|
|
talloc_free(sbind);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 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 GPRS_SNS_EV_REQ_NO_NSVC */
|
|
switch (event) {
|
|
case GPRS_SNS_EV_REQ_NO_NSVC:
|
|
/* ignore reselection running */
|
|
if (gss->reselection_running)
|
|
break;
|
|
|
|
LOGPFSML(fi, LOGL_ERROR, "NSE %d: no remaining NSVC, resetting SNS FSM\n", nse->nsei);
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
|
|
break;
|
|
case GPRS_SNS_EV_REQ_SELECT_ENDPOINT:
|
|
/* tear down previous state
|
|
* gprs_ns2_free_nsvcs() will trigger NO_NSVC, prevent this from triggering a reselection */
|
|
gss->reselection_running = true;
|
|
gprs_ns2_free_nsvcs(nse);
|
|
ns2_clear_ipv46_entries(gss);
|
|
|
|
/* Choose the next sns endpoint. */
|
|
if (llist_empty(&gss->sns_endpoints) || llist_empty(&gss->binds)) {
|
|
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);
|
|
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->reselection_running = false;
|
|
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_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(GPRS_SNS_EV_REQ_NO_NSVC) |
|
|
S(GPRS_SNS_EV_REQ_SELECT_ENDPOINT) |
|
|
S(GPRS_SNS_EV_REQ_ADD_BIND) |
|
|
S(GPRS_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;
|
|
INIT_LLIST_HEAD(&gss->sns_endpoints);
|
|
INIT_LLIST_HEAD(&gss->binds);
|
|
|
|
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 osmo_fsm_inst *fi;
|
|
|
|
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));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
|
|
fi = nse->bss_sns_fi;
|
|
|
|
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, GPRS_SNS_EV_RX_SIZE, tp);
|
|
break;
|
|
case SNS_PDUT_SIZE_ACK:
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_SIZE_ACK, tp);
|
|
break;
|
|
case SNS_PDUT_CONFIG:
|
|
if (nsh->data[0] & 0x01)
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_CONFIG_END, tp);
|
|
else
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_CONFIG, tp);
|
|
break;
|
|
case SNS_PDUT_CONFIG_ACK:
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_CONFIG_ACK, tp);
|
|
break;
|
|
case SNS_PDUT_ADD:
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_ADD, tp);
|
|
break;
|
|
case SNS_PDUT_DELETE:
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_DELETE, tp);
|
|
break;
|
|
case SNS_PDUT_CHANGE_WEIGHT:
|
|
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_RX_CHANGE_WEIGHT, tp);
|
|
break;
|
|
case SNS_PDUT_ACK:
|
|
osmo_fsm_inst_dispatch(fi, GPRS_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));
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#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->num_ip4_local && gss->num_ip4_remote) {
|
|
vty_out(vty, "%sLocal IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
|
|
for (i = 0; i < gss->num_ip4_local; i++)
|
|
vty_dump_sns_ip4(vty, prefix, &gss->ip4_local[i]);
|
|
|
|
vty_out(vty, "%sRemote IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
|
|
for (i = 0; i < gss->num_ip4_remote; i++)
|
|
vty_dump_sns_ip4(vty, prefix, &gss->ip4_remote[i]);
|
|
}
|
|
|
|
if (gss->num_ip6_local && gss->num_ip6_remote) {
|
|
vty_out(vty, "%sLocal IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
|
|
for (i = 0; i < gss->num_ip6_local; i++)
|
|
vty_dump_sns_ip6(vty, prefix, &gss->ip6_local[i]);
|
|
|
|
vty_out(vty, "%sRemote IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
|
|
for (i = 0; i < gss->num_ip6_remote; i++)
|
|
vty_dump_sns_ip6(vty, prefix, &gss->ip6_remote[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, GPRS_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 GPRS_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)
|
|
return;
|
|
|
|
if (alive == gss->alive)
|
|
return;
|
|
|
|
/* check if this is the current SNS NS-VC */
|
|
if (nsvc == gss->sns_nsvc) {
|
|
/* 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 */
|
|
if (gss->alive && !alive)
|
|
ns2_sns_replace_nsvc(nsvc);
|
|
}
|
|
|
|
if (alive) {
|
|
gss->alive = true;
|
|
osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_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, GPRS_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, GPRS_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;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return -ENOENT;
|
|
|
|
osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_REQ_DELETE_BIND, tmp);
|
|
return 0;
|
|
}
|
|
|
|
/* Update SNS weights
|
|
* \param[in] nsvc the NSVC which should be updated
|
|
*/
|
|
void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind)
|
|
{
|
|
/* TODO: implement weights after binds per sns implemented */
|
|
}
|
|
|
|
/* 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);
|
|
}
|