libosmocore/src/gb/gprs_ns2_sns.c

1653 lines
48 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 by Harald Welte <laforge@gnumonks.org>
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <lynxis@fe80.eu>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
* to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
* associated weights. The BSS then uses this to establish a full mesh
* of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports.
*
* Known limitation/expectation/bugs:
* - No concurrent dual stack. It supports either IPv4 or IPv6, but not both at the same time.
* - SNS Add/Change/Delete: Doesn't answer on the same NSVC as received SNS ADD/CHANGE/DELETE PDUs.
* - SNS Add/Change/Delete: Doesn't communicated the failed IPv4/IPv6 entries on the SNS_ACK.
*/
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gprs/gprs_msgb.h>
#include <osmocom/gprs/gprs_ns2.h>
#include <osmocom/gprs/protocol/gsm_08_16.h>
#include "gprs_ns2_internal.h"
#define S(x) (1 << (x))
enum ns2_sns_type {
IPv4,
IPv6,
};
enum gprs_sns_bss_state {
GPRS_SNS_ST_UNCONFIGURED,
GPRS_SNS_ST_SIZE, /*!< SNS-SIZE procedure ongoing */
GPRS_SNS_ST_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
GPRS_SNS_ST_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
GPRS_SNS_ST_CONFIGURED,
};
enum gprs_sns_event {
GPRS_SNS_EV_SELECT_ENDPOINT, /*!< Select a SNS endpoint from the list */
GPRS_SNS_EV_SIZE,
GPRS_SNS_EV_SIZE_ACK,
GPRS_SNS_EV_CONFIG,
GPRS_SNS_EV_CONFIG_END, /*!< SNS-CONFIG with end flag received */
GPRS_SNS_EV_CONFIG_ACK,
GPRS_SNS_EV_ADD,
GPRS_SNS_EV_DELETE,
GPRS_SNS_EV_CHANGE_WEIGHT,
GPRS_SNS_EV_NO_NSVC,
};
static const struct value_string gprs_sns_event_names[] = {
{ GPRS_SNS_EV_SELECT_ENDPOINT, "SELECT_ENDPOINT" },
{ 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" },
{ GPRS_SNS_EV_NO_NSVC, "NO_NSVC" },
{ 0, NULL }
};
struct sns_endpoint {
struct llist_head list;
struct osmo_sockaddr saddr;
};
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;
/* 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;
/* local configuration to send to the remote end */
struct gprs_ns_ie_ip4_elem *ip4_local;
size_t num_ip4_local;
/* local configuration to send to the remote end */
struct gprs_ns_ie_ip6_elem *ip6_local;
size_t num_ip6_local;
/* local configuration about our capabilities in terms of connections to
* remote (SGSN) side */
size_t num_max_nsvcs;
size_t num_max_ip4_remote;
size_t num_max_ip6_remote;
/* remote configuration as received */
struct gprs_ns_ie_ip4_elem *ip4_remote;
unsigned int num_ip4_remote;
/* remote configuration as received */
struct gprs_ns_ie_ip6_elem *ip6_remote;
unsigned int num_ip6_remote;
};
static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
return gss->nse;
}
/* helper function to compute the sum of all (data or signaling) weights */
static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
bool data_weight)
{
unsigned int i;
int weight_sum = 0;
for (i = 0; i < num; i++) {
if (data_weight)
weight_sum += ip4[i].data_weight;
else
weight_sum += ip4[i].sig_weight;
}
return weight_sum;
}
#define ip4_weight_sum_data(x,y) ip4_weight_sum(x, y, true)
#define ip4_weight_sum_sig(x,y) ip4_weight_sum(x, y, false)
/* helper function to compute the sum of all (data or signaling) weights */
static int ip6_weight_sum(const struct gprs_ns_ie_ip6_elem *ip6, unsigned int num,
bool data_weight)
{
unsigned int i;
int weight_sum = 0;
for (i = 0; i < num; i++) {
if (data_weight)
weight_sum += ip6[i].data_weight;
else
weight_sum += ip6[i].sig_weight;
}
return weight_sum;
}
#define ip6_weight_sum_data(x,y) ip6_weight_sum(x, y, true)
#define ip6_weight_sum_sig(x,y) ip6_weight_sum(x, y, false)
static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip4_elem *ip4)
{
struct osmo_sockaddr sa;
/* copy over. Both data structures use network byte order */
sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
sa.u.sin.sin_port = ip4->udp_port;
sa.u.sin.sin_family = AF_INET;
return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
}
static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip6_elem *ip6)
{
struct osmo_sockaddr sa;
/* copy over. Both data structures use network byte order */
sa.u.sin6.sin6_addr = ip6->ip_addr;
sa.u.sin6.sin6_port = ip6->udp_port;
sa.u.sin6.sin6_family = AF_INET;
return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
}
/*! 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 */
void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
{
struct gprs_ns2_nse *nse;
struct gprs_ns2_vc *tmp;
struct ns2_sns_state *gss;
struct osmo_fsm_inst *fi = nsvc->nse->bss_sns_fi;
if (!fi)
return;
gss = (struct ns2_sns_state *) fi->priv;
if (nsvc != gss->sns_nsvc)
return;
nse = nsvc->nse;
if (nse->alive) {
/* choose a different sns nsvc */
llist_for_each_entry(tmp, &nse->nsvc, list) {
if (gprs_ns2_vc_is_unblocked(tmp))
gss->sns_nsvc = tmp;
}
} else {
LOGPFSML(fi, LOGL_ERROR, "NSE %d: no remaining NSVC, resetting SNS FSM\n", nse->nsei);
gss->sns_nsvc = NULL;
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_NO_NSVC, NULL);
}
}
static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi,
struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip4_elem *ip4)
{
struct gprs_ns2_inst *nsi = nse->nsi;
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_vc_bind *bind;
struct osmo_sockaddr remote = { };
/* copy over. Both data structures use network byte order */
remote.u.sin.sin_family = AF_INET;
remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
remote.u.sin.sin_port = ip4->udp_port;
/* for every bind, create a connection if bind type == IP */
llist_for_each_entry(bind, &nsi->binding, list) {
/* ignore failed connection */
nsvc = gprs_ns2_ip_connect_inactive(bind,
&remote,
nse, 0);
if (!nsvc) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
continue;
}
nsvc->sig_weight = ip4->sig_weight;
nsvc->data_weight = ip4->data_weight;
}
}
static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi,
struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip6_elem *ip6)
{
struct gprs_ns2_inst *nsi = nse->nsi;
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_vc_bind *bind;
struct osmo_sockaddr remote = {};
/* copy over. Both data structures use network byte order */
remote.u.sin6.sin6_family = AF_INET6;
remote.u.sin6.sin6_addr = ip6->ip_addr;
remote.u.sin6.sin6_port = ip6->udp_port;
/* for every bind, create a connection if bind type == IP */
llist_for_each_entry(bind, &nsi->binding, list) {
/* ignore failed connection */
nsvc = gprs_ns2_ip_connect_inactive(bind,
&remote,
nse, 0);
if (!nsvc) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
continue;
}
nsvc->sig_weight = ip6->sig_weight;
nsvc->data_weight = ip6->data_weight;
}
}
static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_vc_bind *bind;
struct osmo_sockaddr remote = { };
unsigned int i;
for (i = 0; i < gss->num_ip4_remote; i++) {
const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[i];
remote.u.sin.sin_family = AF_INET;
remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
remote.u.sin.sin_port = ip4->udp_port;
llist_for_each_entry(bind, &nse->nsi->binding, list) {
bool found = false;
llist_for_each_entry(nsvc, &nse->nsvc, list) {
if (nsvc->bind != bind)
continue;
if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_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;
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 */
/* TODO: check if this is the correct cause code */
return -NS_CAUSE_PROTO_ERR_UNSPEC;
}
gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote, struct gprs_ns_ie_ip4_elem,
gss->num_ip4_remote+1);
gss->ip4_remote[gss->num_ip4_remote] = *ip4;
gss->num_ip4_remote += 1;
return 0;
}
/* Remove a given remote IPv4 element from gprs_sns_state */
static int remove_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
{
unsigned int i;
for (i = 0; i < gss->num_ip4_remote; i++) {
if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
continue;
/* all array elements < i remain as they are; all > i are shifted left by one */
memmove(&gss->ip4_remote[i], &gss->ip4_remote[i+1], gss->num_ip4_remote-i-1);
gss->num_ip4_remote -= 1;
return 0;
}
return -1;
}
/* update the weights for specified remote IPv4 */
static int update_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
{
unsigned int i;
for (i = 0; i < gss->num_ip4_remote; i++) {
if (gss->ip4_remote[i].ip_addr != ip4->ip_addr ||
gss->ip4_remote[i].udp_port != ip4->udp_port)
continue;
gss->ip4_remote[i].sig_weight = ip4->sig_weight;
gss->ip4_remote[i].data_weight = ip4->data_weight;
return 0;
}
return -1;
}
/* Add a given remote IPv6 element to gprs_sns_state */
static int add_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
{
if (gss->num_ip6_remote >= gss->num_max_ip6_remote)
return -NS_CAUSE_INVAL_NR_NS_VC;
gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote, struct gprs_ns_ie_ip6_elem,
gss->num_ip6_remote+1);
gss->ip6_remote[gss->num_ip6_remote] = *ip6;
gss->num_ip6_remote += 1;
return 0;
}
/* Remove a given remote IPv6 element from gprs_sns_state */
static int remove_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
{
unsigned int i;
for (i = 0; i < gss->num_ip6_remote; i++) {
if (memcmp(&gss->ip6_remote[i], ip6, sizeof(*ip6)))
continue;
/* all array elements < i remain as they are; all > i are shifted left by one */
memmove(&gss->ip6_remote[i], &gss->ip6_remote[i+1], gss->num_ip6_remote-i-1);
gss->num_ip6_remote -= 1;
return 0;
}
return -1;
}
/* update the weights for specified remote IPv6 */
static int update_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
{
unsigned int i;
for (i = 0; i < gss->num_ip6_remote; i++) {
if (memcmp(&gss->ip6_remote[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
gss->ip6_remote[i].udp_port != ip6->udp_port)
continue;
gss->ip6_remote[i].sig_weight = ip6->sig_weight;
gss->ip6_remote[i].data_weight = ip6->data_weight;
return 0;
}
return -1;
}
static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_vc *nsvc;
struct osmo_sockaddr sa = {};
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_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);
}
}
/* 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;
struct gprs_ns_ie_ip4_elem *ip4_elems;
struct gprs_ns_ie_ip6_elem *ip6_elems;
struct gprs_ns2_vc_bind *bind;
struct gprs_ns2_inst *nsi = gss->nse->nsi;
struct osmo_sockaddr *remote;
const struct osmo_sockaddr *sa;
struct osmo_sockaddr local;
int count;
/* on a generic failure, the timer callback will recover */
if (old_state != GPRS_SNS_ST_UNCONFIGURED)
ns2_prim_status_ind(gss->nse, NULL, 0, NS_AFF_CAUSE_SNS_FAILURE);
/* no initial available */
if (!gss->initial)
return;
remote = &gss->initial->saddr;
/* count how many bindings are available (only UDP binds) */
count = ns2_ip_count_bind(nsi, remote);
if (count == 0) {
/* TODO: logging */
return;
}
bind = ns2_ip_get_bind_by_index(nsi, remote, 0);
if (!bind) {
return;
}
/* setup the NSVC */
if (!gss->sns_nsvc) {
gss->sns_nsvc = gprs_ns2_ip_bind_connect(bind, gss->nse, remote);
if (!gss->sns_nsvc)
return;
gss->sns_nsvc->sns_only = true;
}
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(bind, &nsi->binding, list) {
if (!gprs_ns2_is_ip_bind(bind))
continue;
sa = gprs_ns2_ip_bind_sockaddr(bind);
if (!sa)
continue;
if (sa->u.sas.ss_family != AF_INET)
continue;
/* check if this is an specific bind */
if (sa->u.sin.sin_addr.s_addr == 0) {
if (osmo_sockaddr_local_ip(&local, remote))
continue;
ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
} else {
ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
}
ip4_elems->udp_port = sa->u.sin.sin_port;
ip4_elems->sig_weight = 2;
ip4_elems->data_weight = 1;
ip4_elems++;
}
gss->num_ip4_local = count;
gss->num_max_ip4_remote = 4;
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(bind, &nsi->binding, list) {
if (!gprs_ns2_is_ip_bind(bind))
continue;
sa = gprs_ns2_ip_bind_sockaddr(bind);
if (!sa)
continue;
if (sa->u.sas.ss_family != AF_INET6)
continue;
/* check if this is an specific bind */
if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
if (osmo_sockaddr_local_ip(&local, remote))
continue;
ip6_elems->ip_addr = local.u.sin6.sin6_addr;
} else {
ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
}
ip6_elems->udp_port = sa->u.sin.sin_port;
ip6_elems->sig_weight = 2;
ip6_elems->data_weight = 1;
ip6_elems++;
}
gss->num_ip6_local = count;
gss->num_max_ip6_remote = 4;
gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->num_ip6_local, 8);
break;
}
if (gss->num_max_ip4_remote > 0)
ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->num_max_ip4_remote, -1);
else
ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->num_max_ip6_remote);
}
static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct tlv_parsed *tp = NULL;
switch (event) {
case GPRS_SNS_EV_CONFIG_ACK:
tp = (struct tlv_parsed *) data;
if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
/* TODO: What to do? */
} else {
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, 0, 0);
}
break;
default:
OSMO_ASSERT(0);
}
}
static void ns2_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
/* Transmit SNS-CONFIG */
switch (gss->ip) {
case IPv4:
ns2_tx_sns_config(gss->sns_nsvc, true,
gss->ip4_local, gss->num_ip4_local,
NULL, 0);
break;
case IPv6:
ns2_tx_sns_config(gss->sns_nsvc, true,
NULL, 0,
gss->ip6_local, gss->num_ip6_local);
break;
}
}
static void ns_sns_st_config_sgsn_ip4(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
const struct gprs_ns_ie_ip4_elem *v4_list;
unsigned int num_v4;
struct tlv_parsed *tp = NULL;
uint8_t cause;
tp = (struct tlv_parsed *) data;
if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
return;
}
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
/* realloc to the new size */
gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote,
struct gprs_ns_ie_ip4_elem,
gss->num_ip4_remote+num_v4);
/* append the new entries to the end of the list */
memcpy(&gss->ip4_remote[gss->num_ip4_remote], v4_list, num_v4*sizeof(*v4_list));
gss->num_ip4_remote += num_v4;
LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
gss->num_ip4_remote);
if (event == GPRS_SNS_EV_CONFIG_END) {
/* check if sum of data / sig weights == 0 */
if (ip4_weight_sum_data(gss->ip4_remote, gss->num_ip4_remote) == 0 ||
ip4_weight_sum_sig(gss->ip4_remote, gss->num_ip4_remote) == 0) {
cause = NS_CAUSE_INVAL_WEIGH;
ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
return;
}
create_missing_nsvcs(fi);
ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
/* start the test procedure on ALL NSVCs! */
gprs_ns2_start_alive_all_nsvcs(nse);
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
} else {
/* just send CONFIG-ACK */
ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
}
}
static void ns_sns_st_config_sgsn_ip6(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
const struct gprs_ns_ie_ip6_elem *v6_list;
unsigned int num_v6;
struct tlv_parsed *tp = NULL;
uint8_t cause;
tp = (struct tlv_parsed *) data;
if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
cause = NS_CAUSE_INVAL_NR_IPv6_EP;
ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
return;
}
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
/* realloc to the new size */
gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote,
struct gprs_ns_ie_ip6_elem,
gss->num_ip6_remote+num_v6);
/* append the new entries to the end of the list */
memcpy(&gss->ip6_remote[gss->num_ip6_remote], v6_list, num_v6*sizeof(*v6_list));
gss->num_ip6_remote += num_v6;
LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %u entries\n",
gss->num_ip6_remote);
if (event == GPRS_SNS_EV_CONFIG_END) {
/* check if sum of data / sig weights == 0 */
if (ip6_weight_sum_data(gss->ip6_remote, gss->num_ip6_remote) == 0 ||
ip6_weight_sum_sig(gss->ip6_remote, gss->num_ip6_remote) == 0) {
cause = NS_CAUSE_INVAL_WEIGH;
ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
return;
}
create_missing_nsvcs(fi);
ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
/* start the test procedure on ALL NSVCs! */
gprs_ns2_start_alive_all_nsvcs(nse);
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
} else {
/* just send CONFIG-ACK */
ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
}
}
static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
switch (event) {
case GPRS_SNS_EV_CONFIG_END:
case GPRS_SNS_EV_CONFIG:
#if 0 /* part of incoming SNS-SIZE (doesn't happen on BSS side */
if (TLVP_PRESENT(tp, NS_IE_RESET_FLAG)) {
/* reset all existing config */
if (gss->ip4_remote)
talloc_free(gss->ip4_remote);
gss->num_ip4_remote = 0;
}
#endif
/* TODO: reject IPv6 elements on IPv4 mode and vice versa */
switch (gss->ip) {
case IPv4:
ns_sns_st_config_sgsn_ip4(fi, event, data);
break;
case IPv6:
ns_sns_st_config_sgsn_ip6(fi, event, data);
break;
default:
OSMO_ASSERT(0);
}
break;
default:
OSMO_ASSERT(0);
}
}
/* called when receiving GPRS_SNS_EV_ADD in state configure */
static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
struct ns2_sns_state *gss,
struct tlv_parsed *tp)
{
const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
int num_v4 = 0, num_v6 = 0;
uint8_t trans_id, cause = 0xff;
unsigned int i;
int rc = 0;
/* TODO: refactor EV_ADD/CHANGE/REMOVE by
* check uniqueness within the lists (no doublicate entries)
* check not-known-by-us and sent back a list of unknown/known values
* (abnormal behaviour according to 48.016)
*/
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
if (gss->ip == IPv4) {
if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
for (i = 0; i < num_v4; i++) {
unsigned int j;
rc = do_sns_add(fi, &v4_list[i], NULL);
if (rc < 0) {
/* rollback/undo to restore previous state */
for (j = 0; j < i; j++)
do_sns_delete(fi, &v4_list[j], NULL);
cause = -rc;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
break;
}
}
} else { /* IPv6 */
if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
cause = NS_CAUSE_INVAL_NR_IPv6_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
for (i = 0; i < num_v6; i++) {
unsigned int j;
rc = do_sns_add(fi, NULL, &v6_list[i]);
if (rc < 0) {
/* rollback/undo to restore previous state */
for (j = 0; j < i; j++)
do_sns_delete(fi, NULL, &v6_list[j]);
cause = -rc;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
break;
}
}
}
/* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
}
static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
struct ns2_sns_state *gss,
struct tlv_parsed *tp)
{
const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
int num_v4 = 0, num_v6 = 0;
uint8_t trans_id, cause = 0xff;
unsigned int i;
int rc = 0;
/* TODO: split up delete into v4 + v6
* TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa
* TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time
*/
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
if (gss->ip == IPv4) {
if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
for ( i = 0; i < num_v4; i++) {
rc = do_sns_delete(fi, &v4_list[i], NULL);
if (rc < 0) {
cause = -rc;
/* continue to delete others */
}
}
if (cause != 0xff) {
/* TODO: create list of not-deleted and return it */
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) {
/* delete all NS-VCs for given IPv4 address */
const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
struct gprs_ns_ie_ip4_elem *ip4_remote;
uint32_t ip_addr = *(uint32_t *)(ie+1);
if (ie[0] != 0x01) { /* Address Type != IPv4 */
cause = NS_CAUSE_UNKN_IP_ADDR;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
/* make a copy as do_sns_delete() will change the array underneath us */
ip4_remote = talloc_memdup(fi, gss->ip4_remote,
gss->num_ip4_remote * sizeof(*v4_list));
for (i = 0; i < gss->num_ip4_remote; i++) {
if (ip4_remote[i].ip_addr == ip_addr) {
rc = do_sns_delete(fi, &ip4_remote[i], NULL);
if (rc < 0) {
cause = -rc;
/* continue to delete others */
}
}
}
talloc_free(ip4_remote);
if (cause != 0xff) {
/* TODO: create list of not-deleted and return it */
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else {
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else { /* IPv6 */
if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
for (i = 0; i < num_v6; i++) {
rc = do_sns_delete(fi, NULL, &v6_list[i]);
if (rc < 0) {
cause = -rc;
/* continue to delete others */
}
}
if (cause != 0xff) {
/* TODO: create list of not-deleted and return it */
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) {
/* delete all NS-VCs for given IPv4 address */
const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
struct gprs_ns_ie_ip6_elem *ip6_remote;
struct in6_addr ip6_addr;
unsigned int i;
if (ie[0] != 0x02) { /* Address Type != IPv6 */
cause = NS_CAUSE_UNKN_IP_ADDR;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr));
/* make a copy as do_sns_delete() will change the array underneath us */
ip6_remote = talloc_memdup(fi, gss->ip6_remote,
gss->num_ip6_remote * sizeof(*v4_list));
for (i = 0; i < gss->num_ip6_remote; i++) {
if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) {
rc = do_sns_delete(fi, NULL, &ip6_remote[i]);
if (rc < 0) {
cause = -rc;
/* continue to delete others */
}
}
}
talloc_free(ip6_remote);
if (cause != 0xff) {
/* TODO: create list of not-deleted and return it */
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else {
cause = NS_CAUSE_INVAL_NR_IPv6_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
}
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
}
static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi,
struct ns2_sns_state *gss,
struct tlv_parsed *tp)
{
const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
int num_v4 = 0, num_v6 = 0;
uint8_t trans_id, cause = 0xff;
int rc = 0;
unsigned int i;
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
for (i = 0; i < num_v4; i++) {
rc = do_sns_change_weight(fi, &v4_list[i], NULL);
if (rc < 0) {
cause = -rc;
/* continue to others */
}
}
if (cause != 0xff) {
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
for (i = 0; i < num_v6; i++) {
rc = do_sns_change_weight(fi, NULL, &v6_list[i]);
if (rc < 0) {
cause = -rc;
/* continue to others */
}
}
if (cause != 0xff) {
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
} else {
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
return;
}
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
}
static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct tlv_parsed *tp = data;
switch (event) {
case GPRS_SNS_EV_ADD:
ns2_sns_st_configured_add(fi, gss, tp);
break;
case GPRS_SNS_EV_DELETE:
ns2_sns_st_configured_delete(fi, gss, tp);
break;
case GPRS_SNS_EV_CHANGE_WEIGHT:
ns2_sns_st_configured_change(fi, gss, tp);
break;
}
}
static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
ns2_prim_status_ind(nse, NULL, 0, NS_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_SIZE_ACK),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
S(GPRS_SNS_ST_SIZE) |
S(GPRS_SNS_ST_CONFIG_BSS),
.name = "SIZE",
.action = ns2_sns_st_size,
.onenter = ns2_sns_st_size_onenter,
},
[GPRS_SNS_ST_CONFIG_BSS] = {
.in_event_mask = S(GPRS_SNS_EV_CONFIG_ACK),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
S(GPRS_SNS_ST_CONFIG_BSS) |
S(GPRS_SNS_ST_CONFIG_SGSN) |
S(GPRS_SNS_ST_SIZE),
.name = "CONFIG_BSS",
.action = ns2_sns_st_config_bss,
.onenter = ns2_sns_st_config_bss_onenter,
},
[GPRS_SNS_ST_CONFIG_SGSN] = {
.in_event_mask = S(GPRS_SNS_EV_CONFIG) |
S(GPRS_SNS_EV_CONFIG_END),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
S(GPRS_SNS_ST_CONFIG_SGSN) |
S(GPRS_SNS_ST_CONFIGURED) |
S(GPRS_SNS_ST_SIZE),
.name = "CONFIG_SGSN",
.action = ns2_sns_st_config_sgsn,
},
[GPRS_SNS_ST_CONFIGURED] = {
.in_event_mask = S(GPRS_SNS_EV_ADD) |
S(GPRS_SNS_EV_DELETE) |
S(GPRS_SNS_EV_CHANGE_WEIGHT),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
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 gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_inst *nsi = nse->nsi;
switch (fi->T) {
case 1:
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
break;
case 2:
osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
break;
}
return 0;
}
static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct 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_NO_NSVC */
switch (event) {
case GPRS_SNS_EV_NO_NSVC:
if (!gss->reselection_running)
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SELECT_ENDPOINT, NULL);
break;
case GPRS_SNS_EV_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);
/* Choose the next sns endpoint. */
if (llist_empty(&gss->sns_endpoints)) {
gss->initial = NULL;
ns2_prim_status_ind(gss->nse, NULL, 0, NS_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;
}
}
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_NO_NSVC) |
S(GPRS_SNS_EV_SELECT_ENDPOINT),
.allstate_action = ns2_sns_st_all_action,
.cleanup = NULL,
.timer_cb = ns2_sns_fsm_bss_timer_cb,
/* .log_subsys = DNS, "is not constant" */
.event_names = gprs_sns_event_names,
.pre_term = NULL,
.log_subsys = DLNS,
};
/*! Allocate an IP-SNS FSM for the BSS side.
* \param[in] nse NS Entity in which the FSM runs
* \param[in] id string identifier
* \retruns FSM instance on success; NULL on error */
struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
const char *id)
{
struct osmo_fsm_inst *fi;
struct ns2_sns_state *gss;
fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
if (!fi)
return fi;
gss = talloc_zero(fi, struct ns2_sns_state);
if (!gss)
goto err;
fi->priv = gss;
gss->nse = nse;
INIT_LLIST_HEAD(&gss->sns_endpoints);
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
* \retruns 0 on success; negative on error */
int gprs_ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
{
struct gprs_ns2_nse *nse = nsvc->nse;
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
uint16_t nsei = nsvc->nse->nsei;
struct osmo_fsm_inst *fi;
if (!nse->bss_sns_fi) {
LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx %s for NS Instance that has no SNS!\n",
nsvc->nse->nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
return -EINVAL;
}
LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
/* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
fi = nse->bss_sns_fi;
switch (nsh->pdu_type) {
case SNS_PDUT_SIZE:
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE, tp);
break;
case SNS_PDUT_SIZE_ACK:
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE_ACK, tp);
break;
case SNS_PDUT_CONFIG:
if (nsh->data[0] & 0x01)
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_END, tp);
else
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG, tp);
break;
case SNS_PDUT_CONFIG_ACK:
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_ACK, tp);
break;
case SNS_PDUT_ADD:
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_ADD, tp);
break;
case SNS_PDUT_DELETE:
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_DELETE, tp);
break;
case SNS_PDUT_CHANGE_WEIGHT:
osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CHANGE_WEIGHT, tp);
break;
case SNS_PDUT_ACK:
LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx unsupported SNS PDU type %s\n", nsei,
get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
break;
default:
LOGP(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
return -EINVAL;
}
return 0;
}
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
static void vty_dump_sns_ip4(struct vty *vty, const struct gprs_ns_ie_ip4_elem *ip4)
{
struct in_addr in = { .s_addr = ip4->ip_addr };
vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE);
}
static void vty_dump_sns_ip6(struct vty *vty, const struct gprs_ns_ie_ip6_elem *ip6)
{
char ip_addr[INET6_ADDRSTRLEN] = {};
if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN)))
strcpy(ip_addr, "Invalid IPv6");
vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE);
}
/*! Dump the IP-SNS state to a vty.
* \param[in] vty VTY to which the state shall be printed
* \param[in] nse NS Entity whose IP-SNS state shall be printed
* \param[in] stats Whether or not statistics shall also be printed */
void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats)
{
struct ns2_sns_state *gss;
unsigned int i;
if (!nse->bss_sns_fi)
return;
vty_out_fsm_inst(vty, nse->bss_sns_fi);
gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
vty_out(vty, "Maximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s",
gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
if (gss->num_ip4_local && gss->num_ip4_remote) {
vty_out(vty, "Local IPv4 Endpoints:%s", VTY_NEWLINE);
for (i = 0; i < gss->num_ip4_local; i++)
vty_dump_sns_ip4(vty, &gss->ip4_local[i]);
vty_out(vty, "Remote IPv4 Endpoints:%s", VTY_NEWLINE);
for (i = 0; i < gss->num_ip4_remote; i++)
vty_dump_sns_ip4(vty, &gss->ip4_remote[i]);
}
if (gss->num_ip6_local && gss->num_ip6_remote) {
vty_out(vty, "Local IPv6 Endpoints:%s", VTY_NEWLINE);
for (i = 0; i < gss->num_ip6_local; i++)
vty_dump_sns_ip6(vty, &gss->ip6_local[i]);
vty_out(vty, "Remote IPv6 Endpoints:%s", VTY_NEWLINE);
for (i = 0; i < gss->num_ip6_remote; i++)
vty_dump_sns_ip6(vty, &gss->ip6_remote[i]);
}
}
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 != 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_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 != 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_NO_NSVC on the last NS-VC
* and restart SNS SIZE procedure which selects a new initial */
LOGP(DLNS, 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 != NS2_DIALECT_SNS) {
return -EINVAL;
}
gss = nse->bss_sns_fi->priv;
llist_for_each_entry(endpoint, &gss->sns_endpoints, list)
count++;
return count;
}
/* 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);
}