/*! \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 * (C) 2020 by sysmocom - s.f.m.c. GmbH * Author: Alexander Couzens * * 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 . * */ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include "gprs_ns2_internal.h" #define S(x) (1 << (x)) enum ns2_sns_role { GPRS_SNS_ROLE_BSS, GPRS_SNS_ROLE_SGSN, }; /* BSS-side-only states _ST_BSS_; SGSN-side only states _ST_SGSN_; others shared */ enum gprs_sns_bss_state { GPRS_SNS_ST_UNCONFIGURED, GPRS_SNS_ST_BSS_SIZE, /*!< SNS-SIZE procedure ongoing */ GPRS_SNS_ST_BSS_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */ GPRS_SNS_ST_BSS_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */ GPRS_SNS_ST_CONFIGURED, GPRS_SNS_ST_SGSN_WAIT_CONFIG, /* !< SGSN role: Wait for CONFIG from BSS */ GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, /* !< SGSN role: Wait for CONFIG-ACK from BSS */ GPRS_SNS_ST_LOCAL_PROCEDURE, /*!< in process of a ADD/DEL/CHANGE procedure towards SGSN (BSS->SGSN) */ }; static const struct value_string gprs_sns_event_names[] = { { NS2_SNS_EV_REQ_SELECT_ENDPOINT, "REQ_SELECT_ENDPOINT" }, { NS2_SNS_EV_RX_SIZE, "RX_SIZE" }, { NS2_SNS_EV_RX_SIZE_ACK, "RX_SIZE_ACK" }, { NS2_SNS_EV_RX_CONFIG, "RX_CONFIG" }, { NS2_SNS_EV_RX_CONFIG_END, "RX_CONFIG_END" }, { NS2_SNS_EV_RX_CONFIG_ACK, "RX_CONFIG_ACK" }, { NS2_SNS_EV_RX_ADD, "RX_ADD" }, { NS2_SNS_EV_RX_DELETE, "RX_DELETE" }, { NS2_SNS_EV_RX_ACK, "RX_ACK" }, { NS2_SNS_EV_RX_CHANGE_WEIGHT, "RX_CHANGE_WEIGHT" }, { NS2_SNS_EV_REQ_NO_NSVC, "REQ_NO_NSVC" }, { NS2_SNS_EV_REQ_FREE_NSVCS, "REQ_FREE_NSVCS" }, { NS2_SNS_EV_REQ_NSVC_ALIVE, "REQ_NSVC_ALIVE"}, { NS2_SNS_EV_REQ_ADD_BIND, "REQ_ADD_BIND"}, { NS2_SNS_EV_REQ_DELETE_BIND, "REQ_DELETE_BIND"}, { NS2_SNS_EV_REQ_CHANGE_WEIGHT, "REQ_CHANGE_WEIGHT"}, { 0, NULL } }; enum sns_procedure { SNS_PROC_NONE, /*!< used as invalid/idle value */ SNS_PROC_ADD, SNS_PROC_DEL, SNS_PROC_CHANGE_WEIGHT, }; struct sns_endpoint { struct llist_head list; struct osmo_sockaddr saddr; }; struct ns2_sns_bind { struct llist_head list; struct gprs_ns2_vc_bind *bind; uint8_t change_weight_state; }; struct ns2_sns_procedure { struct llist_head list; struct ns2_sns_bind *sbind; uint16_t sig_weight; uint16_t data_weight; /* copy entry to protect against changes of gss->local */ struct gprs_ns_ie_ip4_elem ip4; struct gprs_ns_ie_ip6_elem ip6; enum sns_procedure procedure; uint8_t trans_id; /* is the procedure in process */ bool running; }; struct ns2_sns_elems { struct gprs_ns_ie_ip4_elem *ip4; unsigned int num_ip4; struct gprs_ns_ie_ip6_elem *ip6; unsigned int num_ip6; }; struct ns2_sns_state { struct gprs_ns2_nse *nse; /* containing the address family AF_* */ int family; enum ns2_sns_role role; /* local role: BSS or SGSN */ /* holds the list of initial SNS endpoints */ struct llist_head sns_endpoints; /* list of used struct ns2_sns_bind */ struct llist_head binds; /* pointer to the bind which was used to initiate the SNS connection */ struct ns2_sns_bind *initial_bind; /* prevent recursive reselection */ bool reselection_running; /* The current initial SNS endpoints. * The initial connection will be moved into the NSE * if configured via SNS. Otherwise it will be removed * in configured state. */ struct sns_endpoint *initial; /* all SNS PDU will be sent over this nsvc */ struct gprs_ns2_vc *sns_nsvc; /* timer N */ int N; /* true if at least one nsvc is alive */ bool alive; /* local configuration to send to the remote end */ struct ns2_sns_elems local; /* local configuration after all local procedures applied */ struct ns2_sns_elems local_procedure; /* remote configuration as received */ struct ns2_sns_elems remote; /* local configuration about our capabilities in terms of connections to * remote (SGSN) side */ size_t num_max_nsvcs; size_t num_max_ip4_remote; size_t num_max_ip6_remote; struct llist_head procedures; struct ns2_sns_procedure *current_procedure; uint8_t trans_id; }; static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; return gss->nse; } /* The SNS has failed. Etither restart the SNS (BSS) or remove the SNS (SGSN) */ #define sns_failed(fi, reason) \ _sns_failed(fi, reason, __FILE__, __LINE__) static void _sns_failed(struct osmo_fsm_inst *fi, const char *reason, const char *file, int line) { struct ns2_sns_state *gss = fi->priv; if (reason) LOGPFSML(fi, LOGL_ERROR, "NSE %d: SNS failed: %s\n", gss->nse->nsei, reason); if (gss->role == GPRS_SNS_ROLE_SGSN) { if (!gss->nse->persistent) gprs_ns2_free_nse(gss->nse); else _osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0, file, line); } else { _osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL, file, line); } } /* helper function to compute the sum of all (data or signaling) weights */ static int ip4_weight_sum(const struct ns2_sns_elems *elems, bool data_weight) { unsigned int i; int weight_sum = 0; for (i = 0; i < elems->num_ip4; i++) { if (data_weight) weight_sum += elems->ip4[i].data_weight; else weight_sum += elems->ip4[i].sig_weight; } return weight_sum; } #define ip4_weight_sum_data(elems) ip4_weight_sum(elems, true) #define ip4_weight_sum_sig(elems) ip4_weight_sum(elems, false) /* helper function to compute the sum of all (data or signaling) weights */ static int ip6_weight_sum(const struct ns2_sns_elems *elems, bool data_weight) { unsigned int i; int weight_sum = 0; for (i = 0; i < elems->num_ip6; i++) { if (data_weight) weight_sum += elems->ip6[i].data_weight; else weight_sum += elems->ip6[i].sig_weight; } return weight_sum; } #define ip6_weight_sum_data(elems) ip6_weight_sum(elems, true) #define ip6_weight_sum_sig(elems) ip6_weight_sum(elems, false) static int ip46_weight_sum(const struct ns2_sns_elems *elems, bool data_weight) { return ip4_weight_sum(elems, data_weight) + ip6_weight_sum(elems, data_weight); } #define ip46_weight_sum_data(elems) ip46_weight_sum(elems, true) #define ip46_weight_sum_sig(elems) ip46_weight_sum(elems, false) static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse, const struct gprs_ns_ie_ip4_elem *ip4) { struct osmo_sockaddr sa; /* copy over. Both data structures use network byte order */ sa.u.sin.sin_addr.s_addr = ip4->ip_addr; sa.u.sin.sin_port = ip4->udp_port; sa.u.sin.sin_family = AF_INET; return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa); } static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse, const struct gprs_ns_ie_ip6_elem *ip6) { struct osmo_sockaddr sa; /* copy over. Both data structures use network byte order */ sa.u.sin6.sin6_addr = ip6->ip_addr; sa.u.sin6.sin6_port = ip6->udp_port; sa.u.sin6.sin6_family = AF_INET; return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa); } /*! Return the initial SNS remote socket address * \param nse NS Entity * \return address of the initial SNS connection; NULL in case of error */ const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse) { struct ns2_sns_state *gss; if (!nse->bss_sns_fi) return NULL; gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv; return &gss->initial->saddr; } /*! called when a nsvc is beeing freed or the nsvc became dead */ void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc) { struct gprs_ns2_nse *nse = nsvc->nse; struct gprs_ns2_vc *tmp; struct osmo_fsm_inst *fi = nse->bss_sns_fi; struct ns2_sns_state *gss; if (!fi) return; gss = (struct ns2_sns_state *) fi->priv; if (nsvc != gss->sns_nsvc) return; gss->sns_nsvc = NULL; if (gss->alive) { llist_for_each_entry(tmp, &nse->nsvc, list) { if (ns2_vc_is_unblocked(tmp)) { gss->sns_nsvc = tmp; return; } } } else { /* the SNS is waiting for its first NS-VC to come up * choose any other nsvc */ llist_for_each_entry(tmp, &nse->nsvc, list) { if (nsvc != tmp) { gss->sns_nsvc = tmp; return; } } } osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_NO_NSVC, NULL); } static void ns2_clear_elems(struct ns2_sns_elems *elems) { TALLOC_FREE(elems->ip4); TALLOC_FREE(elems->ip6); elems->num_ip4 = 0; elems->num_ip6 = 0; } static void ns2_clear_procedures(struct ns2_sns_state *gss) { struct ns2_sns_procedure *procedure, *tmp; gss->current_procedure = NULL; llist_for_each_entry_safe(procedure, tmp, &gss->procedures, list) { llist_del(&procedure->list); talloc_free(procedure); } } static void ns2_vc_create_ip(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct osmo_sockaddr *remote, uint8_t sig_weight, uint8_t data_weight) { struct gprs_ns2_inst *nsi = nse->nsi; struct gprs_ns2_vc *nsvc; struct gprs_ns2_vc_bind *bind; /* for every bind, create a connection if bind type == IP */ llist_for_each_entry(bind, &nsi->binding, list) { if (bind->ll != GPRS_NS2_LL_UDP) continue; /* ignore failed connection */ nsvc = gprs_ns2_ip_connect_inactive(bind, remote, nse, 0); if (!nsvc) { LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n"); continue; } nsvc->sig_weight = sig_weight; nsvc->data_weight = data_weight; } } static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct gprs_ns_ie_ip4_elem *ip4) { struct osmo_sockaddr remote = { }; /* copy over. Both data structures use network byte order */ remote.u.sin.sin_family = AF_INET; remote.u.sin.sin_addr.s_addr = ip4->ip_addr; remote.u.sin.sin_port = ip4->udp_port; ns2_vc_create_ip(fi, nse, &remote, ip4->sig_weight, ip4->data_weight); } static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct gprs_ns_ie_ip6_elem *ip6) { struct osmo_sockaddr remote = {}; /* copy over. Both data structures use network byte order */ remote.u.sin6.sin6_family = AF_INET6; remote.u.sin6.sin6_addr = ip6->ip_addr; remote.u.sin6.sin6_port = ip6->udp_port; ns2_vc_create_ip(fi, nse, &remote, ip6->sig_weight, ip6->data_weight); } static struct gprs_ns2_vc *nsvc_for_bind_and_remote(struct gprs_ns2_nse *nse, struct gprs_ns2_vc_bind *bind, const struct osmo_sockaddr *remote) { struct gprs_ns2_vc *nsvc; llist_for_each_entry(nsvc, &nse->nsvc, list) { if (nsvc->bind != bind) continue; if (!osmo_sockaddr_cmp(remote, gprs_ns2_ip_vc_remote(nsvc))) return nsvc; } return NULL; } static int create_missing_nsvcs(struct osmo_fsm_inst *fi) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_vc *nsvc; struct ns2_sns_bind *sbind; struct osmo_sockaddr remote = { }; unsigned int i; /* iterate over all remote IPv4 endpoints */ for (i = 0; i < gss->remote.num_ip4; i++) { const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i]; remote.u.sin.sin_family = AF_INET; remote.u.sin.sin_addr.s_addr = ip4->ip_addr; remote.u.sin.sin_port = ip4->udp_port; /* iterate over all local binds within this SNS */ llist_for_each_entry(sbind, &gss->binds, list) { struct gprs_ns2_vc_bind *bind = sbind->bind; /* we only care about UDP binds */ if (bind->ll != GPRS_NS2_LL_UDP) continue; nsvc = nsvc_for_bind_and_remote(nse, bind, &remote); if (!nsvc) { nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0); if (!nsvc) { /* TODO: add to a list to send back a NS-STATUS */ continue; } } /* update data / signalling weight */ nsvc->data_weight = ip4->data_weight; nsvc->sig_weight = ip4->sig_weight; nsvc->sns_only = false; } } /* iterate over all remote IPv4 endpoints */ for (i = 0; i < gss->remote.num_ip6; i++) { const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i]; remote.u.sin6.sin6_family = AF_INET6; remote.u.sin6.sin6_addr = ip6->ip_addr; remote.u.sin6.sin6_port = ip6->udp_port; /* iterate over all local binds within this SNS */ llist_for_each_entry(sbind, &gss->binds, list) { struct gprs_ns2_vc_bind *bind = sbind->bind; if (bind->ll != GPRS_NS2_LL_UDP) continue; /* we only care about UDP binds */ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote); if (!nsvc) { nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0); if (!nsvc) { /* TODO: add to a list to send back a NS-STATUS */ continue; } } /* update data / signalling weight */ nsvc->data_weight = ip6->data_weight; nsvc->sig_weight = ip6->sig_weight; nsvc->sns_only = false; } } return 0; } /* Add a given remote IPv4 element to gprs_sns_state */ static int add_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, const struct gprs_ns_ie_ip4_elem *ip4) { /* check for duplicates */ for (unsigned int i = 0; i < elems->num_ip4; i++) { if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4))) continue; return -1; } elems->ip4 = talloc_realloc(gss, elems->ip4, struct gprs_ns_ie_ip4_elem, elems->num_ip4+1); elems->ip4[elems->num_ip4] = *ip4; elems->num_ip4 += 1; return 0; } /* Remove a given remote IPv4 element from gprs_sns_state */ static int remove_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, const struct gprs_ns_ie_ip4_elem *ip4) { unsigned int i; for (i = 0; i < elems->num_ip4; i++) { if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4))) continue; /* all array elements < i remain as they are; all > i are shifted left by one */ memmove(&elems->ip4[i], &elems->ip4[i+1], elems->num_ip4-i-1); elems->num_ip4 -= 1; return 0; } return -1; } /* update the weights for specified remote IPv4 */ static int update_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, const struct gprs_ns_ie_ip4_elem *ip4) { unsigned int i; for (i = 0; i < elems->num_ip4; i++) { if (elems->ip4[i].ip_addr != ip4->ip_addr || elems->ip4[i].udp_port != ip4->udp_port) continue; elems->ip4[i].sig_weight = ip4->sig_weight; elems->ip4[i].data_weight = ip4->data_weight; return 0; } return -1; } /* Add a given remote IPv6 element to gprs_sns_state */ static int add_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, const struct gprs_ns_ie_ip6_elem *ip6) { /* check for duplicates */ for (unsigned int i = 0; i < elems->num_ip6; i++) { if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) || elems->ip6[i].udp_port != ip6->udp_port) continue; return -1; } elems->ip6 = talloc_realloc(gss, elems->ip6, struct gprs_ns_ie_ip6_elem, elems->num_ip6+1); elems->ip6[elems->num_ip6] = *ip6; elems->num_ip6 += 1; return 0; } /* Remove a given remote IPv6 element from gprs_sns_state */ static int remove_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, const struct gprs_ns_ie_ip6_elem *ip6) { unsigned int i; for (i = 0; i < elems->num_ip6; i++) { if (memcmp(&elems->ip6[i], ip6, sizeof(*ip6))) continue; /* all array elements < i remain as they are; all > i are shifted left by one */ memmove(&elems->ip6[i], &elems->ip6[i+1], elems->num_ip6-i-1); elems->num_ip6 -= 1; return 0; } return -1; } /* update the weights for specified remote IPv6 */ static int update_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, const struct gprs_ns_ie_ip6_elem *ip6) { unsigned int i; for (i = 0; i < elems->num_ip6; i++) { if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) || elems->ip6[i].udp_port != ip6->udp_port) continue; elems->ip6[i].sig_weight = ip6->sig_weight; elems->ip6[i].data_weight = ip6->data_weight; return 0; } return -1; } static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_vc *nsvc; struct osmo_sockaddr sa = {}; const struct osmo_sockaddr *remote; uint8_t new_signal; uint8_t new_data; /* TODO: Upon receiving an SNS-CHANGEWEIGHT PDU, if the resulting sum of the * signalling weights of all the peer IP endpoints configured for this NSE is * equal to zero or if the resulting sum of the data weights of all the peer IP * endpoints configured for this NSE is equal to zero, the BSS/SGSN shall send an * SNS-ACK PDU with a cause code of "Invalid weights". */ if (ip4) { if (update_ip4_elem(gss, &gss->remote, ip4)) return -NS_CAUSE_UNKN_IP_EP; /* copy over. Both data structures use network byte order */ sa.u.sin.sin_addr.s_addr = ip4->ip_addr; sa.u.sin.sin_port = ip4->udp_port; sa.u.sin.sin_family = AF_INET; new_signal = ip4->sig_weight; new_data = ip4->data_weight; } else if (ip6) { if (update_ip6_elem(gss, &gss->remote, ip6)) return -NS_CAUSE_UNKN_IP_EP; /* copy over. Both data structures use network byte order */ sa.u.sin6.sin6_addr = ip6->ip_addr; sa.u.sin6.sin6_port = ip6->udp_port; sa.u.sin6.sin6_family = AF_INET6; new_signal = ip6->sig_weight; new_data = ip6->data_weight; } else { OSMO_ASSERT(false); } llist_for_each_entry(nsvc, &nse->nsvc, list) { remote = gprs_ns2_ip_vc_remote(nsvc); /* all nsvc in NSE should be IP/UDP nsvc */ OSMO_ASSERT(remote); if (osmo_sockaddr_cmp(&sa, remote)) continue; LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight %u->%u, sig_weight %u->%u\n", gprs_ns2_ll_str(nsvc), nsvc->data_weight, new_data, nsvc->sig_weight, new_signal); nsvc->data_weight = new_data; nsvc->sig_weight = new_signal; } return 0; } static int do_sns_delete(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_vc *nsvc, *tmp; const struct osmo_sockaddr *remote; struct osmo_sockaddr sa = {}; if (ip4) { if (remove_ip4_elem(gss, &gss->remote, ip4) < 0) return -NS_CAUSE_UNKN_IP_EP; /* copy over. Both data structures use network byte order */ sa.u.sin.sin_addr.s_addr = ip4->ip_addr; sa.u.sin.sin_port = ip4->udp_port; sa.u.sin.sin_family = AF_INET; } else if (ip6) { if (remove_ip6_elem(gss, &gss->remote, ip6)) return -NS_CAUSE_UNKN_IP_EP; /* copy over. Both data structures use network byte order */ sa.u.sin6.sin6_addr = ip6->ip_addr; sa.u.sin6.sin6_port = ip6->udp_port; sa.u.sin6.sin6_family = AF_INET6; } else { OSMO_ASSERT(false); } llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) { remote = gprs_ns2_ip_vc_remote(nsvc); /* all nsvc in NSE should be IP/UDP nsvc */ OSMO_ASSERT(remote); if (osmo_sockaddr_cmp(&sa, remote)) continue; LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns2_ll_str(nsvc)); gprs_ns2_free_nsvc(nsvc); } return 0; } static int do_sns_add(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_vc *nsvc; int rc = 0; /* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints * exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send * an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */ switch (gss->family) { case AF_INET: if (gss->remote.num_ip4 >= gss->num_max_ip4_remote) return -NS_CAUSE_INVAL_NR_NS_VC; /* TODO: log message duplicate */ rc = add_ip4_elem(gss, &gss->remote, ip4); break; case AF_INET6: if (gss->remote.num_ip6 >= gss->num_max_ip6_remote) return -NS_CAUSE_INVAL_NR_NS_VC; /* TODO: log message duplicate */ rc = add_ip6_elem(gss, &gss->remote, ip6); break; default: /* the gss->ip is initialized with the bss */ OSMO_ASSERT(false); } if (rc) return -NS_CAUSE_PROTO_ERR_UNSPEC; /* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the * NSE shall send an SNS-ACK PDU with the cause code "Protocol error - * unspecified" */ switch (gss->family) { case AF_INET: nsvc = nsvc_by_ip4_elem(nse, ip4); if (nsvc) { /* the nsvc should be already in sync with the ip4 / ip6 elements */ return -NS_CAUSE_PROTO_ERR_UNSPEC; } /* TODO: failure case */ ns2_nsvc_create_ip4(fi, nse, ip4); break; case AF_INET6: nsvc = nsvc_by_ip6_elem(nse, ip6); if (nsvc) { /* the nsvc should be already in sync with the ip4 / ip6 elements */ return -NS_CAUSE_PROTO_ERR_UNSPEC; } /* TODO: failure case */ ns2_nsvc_create_ip6(fi, nse, ip6); break; } gprs_ns2_start_alive_all_nsvcs(nse); return 0; } static void ns2_sns_st_bss_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); /* empty state - SNS Select will start by ns2_sns_st_all_action() */ } static void ns2_sns_st_bss_size(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_inst *nsi = nse->nsi; struct tlv_parsed *tp = NULL; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); switch (event) { case NS2_SNS_EV_RX_SIZE_ACK: tp = data; if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) { LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n", gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE))); /* TODO: What to do? */ } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2); } break; default: OSMO_ASSERT(0); } } static int ns2_sns_count_num_local_ep(struct osmo_fsm_inst *fi, int ip_proto) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct ns2_sns_bind *sbind; int count = 0; llist_for_each_entry(sbind, &gss->binds, list) { const struct osmo_sockaddr *sa = gprs_ns2_ip_bind_sockaddr(sbind->bind); if (!sa) continue; switch (ip_proto) { case AF_INET: if (sa->u.sas.ss_family == AF_INET) count++; break; case AF_INET6: if (sa->u.sas.ss_family == AF_INET6) count++; break; } } return count; } static int ns2_sns_copy_local_endpoints(struct ns2_sns_state *gss) { switch (gss->family) { case AF_INET: gss->local_procedure.ip4 = talloc_realloc(gss, gss->local_procedure.ip4, struct gprs_ns_ie_ip4_elem, gss->local.num_ip4); if (!gss->local_procedure.ip4) return -ENOMEM; gss->local_procedure.num_ip4 = gss->local.num_ip4; memcpy(gss->local_procedure.ip4, gss->local.ip4, sizeof(struct gprs_ns_ie_ip4_elem) * gss->local.num_ip4); break; case AF_INET6: gss->local_procedure.ip6 = talloc_realloc(gss, gss->local_procedure.ip6, struct gprs_ns_ie_ip6_elem, gss->local.num_ip6); if (!gss->local_procedure.ip6) return -ENOMEM; gss->local_procedure.num_ip6 = gss->local.num_ip6; memcpy(gss->local_procedure.ip6, gss->local.ip6, sizeof(struct gprs_ns_ie_ip6_elem) * gss->local.num_ip6); break; default: OSMO_ASSERT(0); } return 0; } static void ns2_sns_compute_local_ep_from_binds(struct osmo_fsm_inst *fi) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns_ie_ip4_elem *ip4_elems; struct gprs_ns_ie_ip6_elem *ip6_elems; struct gprs_ns2_vc_bind *bind; struct ns2_sns_bind *sbind; const struct osmo_sockaddr *remote; const struct osmo_sockaddr *sa; struct osmo_sockaddr local; int count; ns2_clear_elems(&gss->local); /* no initial available */ if (gss->role == GPRS_SNS_ROLE_BSS) { if (!gss->initial) return; remote = &gss->initial->saddr; } else remote = gprs_ns2_ip_vc_remote(gss->sns_nsvc); /* count how many bindings are available (only UDP binds) */ count = llist_count(&gss->binds); if (count == 0) { LOGPFSML(fi, LOGL_ERROR, "No local binds for this NSE -> cannot determine IP endpoints\n"); return; } switch (gss->family) { case AF_INET: ip4_elems = talloc_realloc(fi, gss->local.ip4, struct gprs_ns_ie_ip4_elem, count); if (!ip4_elems) return; gss->local.ip4 = ip4_elems; llist_for_each_entry(sbind, &gss->binds, list) { bind = sbind->bind; sa = gprs_ns2_ip_bind_sockaddr(bind); if (!sa) continue; if (sa->u.sas.ss_family != AF_INET) continue; /* check if this is an specific bind */ if (sa->u.sin.sin_addr.s_addr == 0) { if (osmo_sockaddr_local_ip(&local, remote)) continue; ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr; } else { ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr; } ip4_elems->udp_port = sa->u.sin.sin_port; ip4_elems->sig_weight = bind->sns_sig_weight; ip4_elems->data_weight = bind->sns_data_weight; ip4_elems++; } gss->local.num_ip4 = count; gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip4_remote * gss->local.num_ip4, 8); break; case AF_INET6: /* IPv6 */ ip6_elems = talloc_realloc(fi, gss->local.ip6, struct gprs_ns_ie_ip6_elem, count); if (!ip6_elems) return; gss->local.ip6 = ip6_elems; llist_for_each_entry(sbind, &gss->binds, list) { bind = sbind->bind; sa = gprs_ns2_ip_bind_sockaddr(bind); if (!sa) continue; if (sa->u.sas.ss_family != AF_INET6) continue; /* check if this is an specific bind */ if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) { if (osmo_sockaddr_local_ip(&local, remote)) continue; ip6_elems->ip_addr = local.u.sin6.sin6_addr; } else { ip6_elems->ip_addr = sa->u.sin6.sin6_addr; } ip6_elems->udp_port = sa->u.sin.sin_port; ip6_elems->sig_weight = bind->sns_sig_weight; ip6_elems->data_weight = bind->sns_data_weight; ip6_elems++; } gss->local.num_ip6 = count; gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->local.num_ip6, 8); break; } ns2_sns_copy_local_endpoints(gss); } static void ns2_sns_choose_next_bind(struct ns2_sns_state *gss) { /* take the first bind or take the next bind */ if (!gss->initial_bind || gss->initial_bind->list.next == &gss->binds) gss->initial_bind = llist_first_entry_or_null(&gss->binds, struct ns2_sns_bind, list); else gss->initial_bind = llist_entry(gss->initial_bind->list.next, struct ns2_sns_bind, list); } /* setup all dynamic SNS settings, create a new nsvc and send the SIZE */ static void ns2_sns_st_bss_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); /* on a generic failure, the timer callback will recover */ if (old_state != GPRS_SNS_ST_UNCONFIGURED) ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_FAILURE); if (old_state != GPRS_SNS_ST_BSS_SIZE) gss->N = 0; ns2_clear_procedures(gss); gss->alive = false; ns2_sns_compute_local_ep_from_binds(fi); ns2_sns_choose_next_bind(gss); /* setup the NSVC */ if (!gss->sns_nsvc) { struct gprs_ns2_vc_bind *bind = gss->initial_bind->bind; struct osmo_sockaddr *remote = &gss->initial->saddr; gss->sns_nsvc = ns2_ip_bind_connect(bind, gss->nse, remote); if (!gss->sns_nsvc) return; /* A pre-configured endpoint shall not be used for NSE data or signalling traffic * (with the exception of Size and Configuration procedures) unless it is configured * by the SGSN using the auto-configuration procedures */ gss->sns_nsvc->sns_only = true; } if (gss->num_max_ip4_remote > 0) ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->local.num_ip4, -1); else ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->local.num_ip6); } static void ns2_sns_st_bss_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct tlv_parsed *tp = NULL; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); switch (event) { case NS2_SNS_EV_RX_CONFIG_ACK: tp = (struct tlv_parsed *) data; if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) { LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n", gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE))); /* TODO: What to do? */ } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3); } break; default: OSMO_ASSERT(0); } } static void ns2_sns_st_bss_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); if (old_state != GPRS_SNS_ST_BSS_CONFIG_BSS) gss->N = 0; /* Transmit SNS-CONFIG */ switch (gss->family) { case AF_INET: ns2_tx_sns_config(gss->sns_nsvc, true, gss->local.ip4, gss->local.num_ip4, NULL, 0); break; case AF_INET6: ns2_tx_sns_config(gss->sns_nsvc, true, NULL, 0, gss->local.ip6, gss->local.num_ip6); break; } } /* calculate the timeout of the configured state. the configured * state will fail if not at least one NS-VC is alive within X second. */ static inline int ns_sns_configured_timeout(struct osmo_fsm_inst *fi) { int secs; struct gprs_ns2_inst *nsi = nse_inst_from_fi(fi)->nsi; secs = nsi->timeout[NS_TOUT_TNS_ALIVE] * nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]; secs += nsi->timeout[NS_TOUT_TNS_TEST]; return secs; } /* append the remote endpoints from the parsed TLV array to the ns2_sns_state */ static int ns_sns_append_remote_eps(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { const struct gprs_ns_ie_ip4_elem *v4_list; unsigned int num_v4; v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); if (num_v4 && gss->remote.ip6) return -NS_CAUSE_INVAL_NR_IPv4_EP; /* realloc to the new size */ gss->remote.ip4 = talloc_realloc(gss, gss->remote.ip4, struct gprs_ns_ie_ip4_elem, gss->remote.num_ip4 + num_v4); /* append the new entries to the end of the list */ memcpy(&gss->remote.ip4[gss->remote.num_ip4], v4_list, num_v4*sizeof(*v4_list)); gss->remote.num_ip4 += num_v4; LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n", gss->remote.num_ip4); } if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { const struct gprs_ns_ie_ip6_elem *v6_list; unsigned int num_v6; v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); if (num_v6 && gss->remote.ip4) return -NS_CAUSE_INVAL_NR_IPv6_EP; /* realloc to the new size */ gss->remote.ip6 = talloc_realloc(gss, gss->remote.ip6, struct gprs_ns_ie_ip6_elem, gss->remote.num_ip6 + num_v6); /* append the new entries to the end of the list */ memcpy(&gss->remote.ip6[gss->remote.num_ip6], v6_list, num_v6*sizeof(*v6_list)); gss->remote.num_ip6 += num_v6; LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %d entries\n", gss->remote.num_ip6); } return 0; } static void ns2_sns_st_bss_config_sgsn_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); if (old_state != GPRS_SNS_ST_BSS_CONFIG_SGSN) gss->N = 0; } static void ns2_sns_st_bss_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); uint8_t cause; int rc; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS); switch (event) { case NS2_SNS_EV_RX_CONFIG_END: case NS2_SNS_EV_RX_CONFIG: rc = ns_sns_append_remote_eps(fi, data); if (rc < 0) { cause = -rc; ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); return; } if (event == NS2_SNS_EV_RX_CONFIG_END) { /* check if sum of data / sig weights == 0 */ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) { cause = NS_CAUSE_INVAL_WEIGH; ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); return; } create_missing_nsvcs(fi); ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); /* start the test procedure on ALL NSVCs! */ gprs_ns2_start_alive_all_nsvcs(nse); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0); } else { /* just send CONFIG-ACK */ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0); } break; default: OSMO_ASSERT(0); } } /* called when receiving NS2_SNS_EV_RX_ADD in state configure */ static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi, struct ns2_sns_state *gss, struct tlv_parsed *tp) { const struct gprs_ns_ie_ip4_elem *v4_list = NULL; const struct gprs_ns_ie_ip6_elem *v6_list = NULL; int num_v4 = 0, num_v6 = 0; uint8_t trans_id, cause = 0xff; unsigned int i; int rc = 0; /* TODO: refactor EV_ADD/CHANGE/REMOVE by * check uniqueness within the lists (no doublicate entries) * check not-known-by-us and sent back a list of unknown/known values * (abnormal behaviour according to 48.016) */ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID); if (gss->family == AF_INET) { if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { cause = NS_CAUSE_INVAL_NR_IPv4_EP; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); for (i = 0; i < num_v4; i++) { unsigned int j; rc = do_sns_add(fi, &v4_list[i], NULL); if (rc < 0) { /* rollback/undo to restore previous state */ for (j = 0; j < i; j++) do_sns_delete(fi, &v4_list[j], NULL); cause = -rc; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); break; } } } else { /* IPv6 */ if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { cause = NS_CAUSE_INVAL_NR_IPv6_EP; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); for (i = 0; i < num_v6; i++) { unsigned int j; rc = do_sns_add(fi, NULL, &v6_list[i]); if (rc < 0) { /* rollback/undo to restore previous state */ for (j = 0; j < i; j++) do_sns_delete(fi, NULL, &v6_list[j]); cause = -rc; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); break; } } } /* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6); } static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi, struct ns2_sns_state *gss, struct tlv_parsed *tp) { const struct gprs_ns_ie_ip4_elem *v4_list = NULL; const struct gprs_ns_ie_ip6_elem *v6_list = NULL; int num_v4 = 0, num_v6 = 0; uint8_t trans_id, cause = 0xff; unsigned int i; int rc = 0; /* TODO: split up delete into v4 + v6 * TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa * TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time */ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID); if (gss->family == AF_INET) { if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); for ( i = 0; i < num_v4; i++) { rc = do_sns_delete(fi, &v4_list[i], NULL); if (rc < 0) { cause = -rc; /* continue to delete others */ } } if (cause != 0xff) { /* TODO: create list of not-deleted and return it */ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) { /* delete all NS-VCs for given IPv4 address */ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR); struct gprs_ns_ie_ip4_elem *ip4_remote; uint32_t ip_addr = *(uint32_t *)(ie+1); if (ie[0] != 0x01) { /* Address Type != IPv4 */ cause = NS_CAUSE_UNKN_IP_ADDR; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } /* make a copy as do_sns_delete() will change the array underneath us */ ip4_remote = talloc_memdup(fi, gss->remote.ip4, gss->remote.num_ip4 * sizeof(*v4_list)); for (i = 0; i < gss->remote.num_ip4; i++) { if (ip4_remote[i].ip_addr == ip_addr) { rc = do_sns_delete(fi, &ip4_remote[i], NULL); if (rc < 0) { cause = -rc; /* continue to delete others */ } } } talloc_free(ip4_remote); if (cause != 0xff) { /* TODO: create list of not-deleted and return it */ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else { cause = NS_CAUSE_INVAL_NR_IPv4_EP; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else { /* IPv6 */ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); for (i = 0; i < num_v6; i++) { rc = do_sns_delete(fi, NULL, &v6_list[i]); if (rc < 0) { cause = -rc; /* continue to delete others */ } } if (cause != 0xff) { /* TODO: create list of not-deleted and return it */ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) { /* delete all NS-VCs for given IPv4 address */ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR); struct gprs_ns_ie_ip6_elem *ip6_remote; struct in6_addr ip6_addr; unsigned int i; if (ie[0] != 0x02) { /* Address Type != IPv6 */ cause = NS_CAUSE_UNKN_IP_ADDR; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr)); /* make a copy as do_sns_delete() will change the array underneath us */ ip6_remote = talloc_memdup(fi, gss->remote.ip6, gss->remote.num_ip6 * sizeof(*v4_list)); for (i = 0; i < gss->remote.num_ip6; i++) { if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) { rc = do_sns_delete(fi, NULL, &ip6_remote[i]); if (rc < 0) { cause = -rc; /* continue to delete others */ } } } talloc_free(ip6_remote); if (cause != 0xff) { /* TODO: create list of not-deleted and return it */ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else { cause = NS_CAUSE_INVAL_NR_IPv6_EP; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6); } static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi, struct ns2_sns_state *gss, struct tlv_parsed *tp) { const struct gprs_ns_ie_ip4_elem *v4_list = NULL; const struct gprs_ns_ie_ip6_elem *v6_list = NULL; int num_v4 = 0, num_v6 = 0; uint8_t trans_id, cause = 0xff; int rc = 0; unsigned int i; trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID); if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) { v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST); num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list); for (i = 0; i < num_v4; i++) { rc = do_sns_change_weight(fi, &v4_list[i], NULL); if (rc < 0) { cause = -rc; /* continue to others */ } } if (cause != 0xff) { ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) { v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST); num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list); for (i = 0; i < num_v6; i++) { rc = do_sns_change_weight(fi, NULL, &v6_list[i]); if (rc < 0) { cause = -rc; /* continue to others */ } } if (cause != 0xff) { ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } } else { cause = NS_CAUSE_INVAL_NR_IPv4_EP; ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0); return; } ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6); } static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct tlv_parsed *tp = data; switch (event) { case NS2_SNS_EV_RX_ADD: ns2_sns_st_configured_add(fi, gss, tp); break; case NS2_SNS_EV_RX_DELETE: ns2_sns_st_configured_delete(fi, gss, tp); break; case NS2_SNS_EV_RX_CHANGE_WEIGHT: ns2_sns_st_configured_change(fi, gss, tp); break; case NS2_SNS_EV_REQ_NSVC_ALIVE: osmo_timer_del(&fi->timer); break; } } static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct gprs_ns2_vc *nsvc; struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); /* NS-VC status updates are only parsed in ST_CONFIGURED. * Do an initial check if there are any nsvc alive atm */ llist_for_each_entry(nsvc, &nse->nsvc, list) { if (ns2_vc_is_unblocked(nsvc)) { gss->alive = true; osmo_timer_del(&fi->timer); break; } } /* remove the initial NSVC if the NSVC isn't part of the configuration */ if (gss->sns_nsvc->sns_only) gprs_ns2_free_nsvc(gss->sns_nsvc); if (old_state != GPRS_SNS_ST_LOCAL_PROCEDURE) ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED); if (!llist_empty(&gss->procedures)) { osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE, gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5); } } static void ns2_sns_st_local_procedure_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; /* check if resend or not */ if (!gss->current_procedure) { /* take next procedure */ gss->current_procedure = llist_first_entry_or_null(&gss->procedures, struct ns2_sns_procedure, list); if (!gss->current_procedure) { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0); return; } gss->N = 0; gss->current_procedure->running = true; gss->current_procedure->trans_id = ++gss->trans_id; if (gss->trans_id == 0) gss->trans_id = gss->current_procedure->trans_id = 1; } /* also takes care of retransmitting */ switch (gss->current_procedure->procedure) { case SNS_PROC_CHANGE_WEIGHT: if (gss->family == AF_INET) ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0); else ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1); break; default: break; } } static void ns2_sns_st_local_procedure(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns_ie_ip4_elem *ip4, *proc4; struct gprs_ns_ie_ip6_elem *ip6, *proc6; struct tlv_parsed *tp = data; uint8_t trans_id; uint8_t cause; switch (event) { case NS2_SNS_EV_RX_ADD: ns2_sns_st_configured_add(fi, gss, tp); break; case NS2_SNS_EV_RX_DELETE: ns2_sns_st_configured_delete(fi, gss, tp); break; case NS2_SNS_EV_RX_CHANGE_WEIGHT: ns2_sns_st_configured_change(fi, gss, tp); break; case NS2_SNS_EV_RX_ACK: /* presence of trans_id is already checked here */ trans_id = tlvp_val8(tp, NS_IE_TRANS_ID, 0); if (trans_id != gss->current_procedure->trans_id) { LOGPFSML(fi, LOGL_INFO, "NSEI=%u Rx SNS ACK with invalid transaction id %d. Valid %d\n", nse->nsei, trans_id, gss->current_procedure->trans_id); break; } if (TLVP_PRESENT(tp, NS_IE_CAUSE)) { /* what happend on error cause? return to size? */ cause = tlvp_val8(tp, NS_IE_CAUSE, 0); LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx SNS ACK trans %d with cause code %d.\n", nse->nsei, trans_id, cause); sns_failed(fi, NULL); break; } switch (gss->current_procedure->procedure) { case SNS_PROC_CHANGE_WEIGHT: switch (gss->family) { case AF_INET: proc4 = &gss->current_procedure->ip4; for (unsigned int i=0; ilocal.num_ip4; i++) { ip4 = &gss->local.ip4[i]; if (ip4->ip_addr != proc4->ip_addr || ip4->udp_port != proc4->udp_port) continue; ip4->sig_weight = proc4->sig_weight; ip4->data_weight = proc4->data_weight; break; } break; case AF_INET6: proc6 = &gss->current_procedure->ip6; for (unsigned int i=0; ilocal.num_ip6; i++) { ip6 = &gss->local.ip6[i]; if (memcmp(&ip6->ip_addr, &proc6->ip_addr, sizeof(proc6->ip_addr)) || ip6->udp_port != proc6->udp_port) { continue; } ip6->sig_weight = proc6->sig_weight; ip6->data_weight = proc6->data_weight; break; } break; default: OSMO_ASSERT(0); } break; default: break; } llist_del(&gss->current_procedure->list); talloc_free(gss->current_procedure); gss->current_procedure = NULL; if (llist_empty(&gss->procedures)) osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_CONFIGURED, 0, 0); else osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE, gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5); break; } } static const struct osmo_fsm_state ns2_sns_bss_states[] = { [GPRS_SNS_ST_UNCONFIGURED] = { .in_event_mask = 0, /* handled by all_state_action */ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_BSS_SIZE), .name = "UNCONFIGURED", .action = ns2_sns_st_bss_unconfigured, }, [GPRS_SNS_ST_BSS_SIZE] = { .in_event_mask = S(NS2_SNS_EV_RX_SIZE_ACK), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_BSS_SIZE) | S(GPRS_SNS_ST_BSS_CONFIG_BSS), .name = "BSS_SIZE", .action = ns2_sns_st_bss_size, .onenter = ns2_sns_st_bss_size_onenter, }, [GPRS_SNS_ST_BSS_CONFIG_BSS] = { .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_BSS_CONFIG_BSS) | S(GPRS_SNS_ST_BSS_CONFIG_SGSN) | S(GPRS_SNS_ST_BSS_SIZE), .name = "BSS_CONFIG_BSS", .action = ns2_sns_st_bss_config_bss, .onenter = ns2_sns_st_bss_config_bss_onenter, }, [GPRS_SNS_ST_BSS_CONFIG_SGSN] = { .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) | S(NS2_SNS_EV_RX_CONFIG_END), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_BSS_CONFIG_SGSN) | S(GPRS_SNS_ST_CONFIGURED) | S(GPRS_SNS_ST_BSS_SIZE), .name = "BSS_CONFIG_SGSN", .action = ns2_sns_st_bss_config_sgsn, .onenter = ns2_sns_st_bss_config_sgsn_onenter, }, [GPRS_SNS_ST_CONFIGURED] = { .in_event_mask = S(NS2_SNS_EV_RX_ADD) | S(NS2_SNS_EV_RX_DELETE) | S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | S(NS2_SNS_EV_REQ_NSVC_ALIVE), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_BSS_SIZE) | S(GPRS_SNS_ST_LOCAL_PROCEDURE), .name = "CONFIGURED", .action = ns2_sns_st_configured, .onenter = ns2_sns_st_configured_onenter, }, [GPRS_SNS_ST_LOCAL_PROCEDURE] = { .in_event_mask = S(NS2_SNS_EV_RX_ADD) | S(NS2_SNS_EV_RX_DELETE) | S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | S(NS2_SNS_EV_RX_ACK) | S(NS2_SNS_EV_REQ_NSVC_ALIVE), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_BSS_SIZE) | S(GPRS_SNS_ST_CONFIGURED) | S(GPRS_SNS_ST_LOCAL_PROCEDURE), .name = "LOCAL_PROCEDURE", .action = ns2_sns_st_local_procedure, .onenter = ns2_sns_st_local_procedure_onenter, }, }; static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_inst *nsi = nse->nsi; gss->N++; switch (fi->T) { case 1: if (gss->N >= nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES]) { sns_failed(fi, "Size retries failed. Selecting next IP-SNS endpoint."); } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1); } break; case 2: if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { sns_failed(fi, "BSS Config retries failed. Selecting next IP-SNS endpoint"); } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2); } break; case 3: if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { sns_failed(fi, "SGSN Config retries failed. Selecting next IP-SNS endpoint."); } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nsi->timeout[NS_TOUT_TSNS_PROV], 3); } break; case 4: sns_failed(fi, "Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint."); break; case 5: if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { sns_failed(fi, "SNS Procedure retries failed."); } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], 5); } break; } return 0; } static struct gprs_ns_ie_ip4_elem *ns2_get_sbind_ip4_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, struct ns2_sns_elems *endpoints) { const struct osmo_sockaddr *addr; struct gprs_ns_ie_ip4_elem *ip4; if (gss->family != AF_INET) return NULL; addr = gprs_ns2_ip_bind_sockaddr(sbind->bind); if (addr->u.sa.sa_family != AF_INET) return NULL; for (unsigned int i=0; inum_ip4; i++) { ip4 = &endpoints->ip4[i]; if (ip4->ip_addr == addr->u.sin.sin_addr.s_addr && ip4->udp_port == addr->u.sin.sin_port) return ip4; } return NULL; } static struct gprs_ns_ie_ip6_elem *ns2_get_sbind_ip6_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, struct ns2_sns_elems *endpoints) { const struct osmo_sockaddr *addr; struct gprs_ns_ie_ip6_elem *ip6; if (gss->family != AF_INET6) return NULL; addr = gprs_ns2_ip_bind_sockaddr(sbind->bind); if (addr->u.sa.sa_family != AF_INET6) return NULL; for (unsigned int i=0; inum_ip6; i++) { ip6 = &endpoints->ip6[i]; if (memcmp(&ip6->ip_addr, &addr->u.sin6.sin6_addr, sizeof(ip6->ip_addr)) || ip6->udp_port != addr->u.sin6.sin6_port) return ip6; } return NULL; } /* return != 0 if the resulting weight is invalid. return 1 if sbind doesn't have an entry */ static int ns2_update_weight_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, struct ns2_sns_elems *endpoints) { struct gprs_ns_ie_ip4_elem *ip4; struct gprs_ns_ie_ip6_elem *ip6; switch (gss->family) { case AF_INET: ip4 = ns2_get_sbind_ip4_entry(gss, sbind, endpoints); if (!ip4) return 1; ip4->sig_weight = sbind->bind->sns_sig_weight; ip4->data_weight = sbind->bind->sns_data_weight; return (ip4_weight_sum_sig(endpoints) != 0 && ip4_weight_sum_data(endpoints) != 0); break; case AF_INET6: ip6 = ns2_get_sbind_ip6_entry(gss, sbind, endpoints); if (!ip6) return 1; ip6->sig_weight = sbind->bind->sns_sig_weight; ip6->data_weight = sbind->bind->sns_data_weight; return (ip6_weight_sum_sig(endpoints) != 0 && ip6_weight_sum_data(endpoints) != 0); break; default: OSMO_ASSERT(0); } } static void ns2_add_procedure(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind, enum sns_procedure procedure_type) { struct ns2_sns_procedure *procedure = NULL; const struct osmo_sockaddr *saddr; saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind); if (saddr->u.sa.sa_family != gss->family) return; switch (procedure_type) { case SNS_PROC_CHANGE_WEIGHT: llist_for_each_entry(procedure, &gss->procedures, list) { if (procedure->sbind == sbind && procedure->procedure == procedure_type && !procedure->running) { switch(gss->family) { case AF_INET: /* merge it with a previous procedure */ procedure->ip4.ip_addr = sbind->bind->sns_sig_weight; procedure->ip4.data_weight = sbind->bind->sns_data_weight; break; case AF_INET6: /* merge it with a previous procedure */ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight; procedure->ip6.data_weight = sbind->bind->sns_data_weight; break; default: OSMO_ASSERT(0); } return; } } procedure = talloc_zero(gss, struct ns2_sns_procedure); if (!procedure) return; procedure->sbind = sbind; procedure->procedure = procedure_type; procedure->sig_weight = sbind->bind->sns_sig_weight; procedure->data_weight = sbind->bind->sns_data_weight; switch(gss->family) { case AF_INET: procedure->ip4.ip_addr = saddr->u.sin.sin_addr.s_addr; procedure->ip4.udp_port = saddr->u.sin.sin_port; procedure->ip4.sig_weight = sbind->bind->sns_sig_weight; procedure->ip4.data_weight = sbind->bind->sns_data_weight; break; case AF_INET6: memcpy(&procedure->ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr)); procedure->ip6.udp_port = saddr->u.sin.sin_port; procedure->ip6.sig_weight = sbind->bind->sns_sig_weight; procedure->ip6.data_weight = sbind->bind->sns_data_weight; break; default: OSMO_ASSERT(0); } llist_add_tail(&procedure->list, &gss->procedures); break; default: return; } if (gss->nse->bss_sns_fi->state == GPRS_SNS_ST_CONFIGURED) { osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE, gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5); } } /* common allstate-action for both roles */ static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct ns2_sns_bind *sbind; struct gprs_ns2_vc *nsvc, *nsvc2; switch (event) { case NS2_SNS_EV_REQ_ADD_BIND: sbind = data; switch (fi->state) { case GPRS_SNS_ST_UNCONFIGURED: osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); break; case GPRS_SNS_ST_BSS_SIZE: /* TODO: add the ip4 element to the list */ break; case GPRS_SNS_ST_BSS_CONFIG_BSS: case GPRS_SNS_ST_BSS_CONFIG_SGSN: case GPRS_SNS_ST_CONFIGURED: /* TODO: add to SNS-IP procedure queue & add nsvc() */ break; } break; case NS2_SNS_EV_REQ_DELETE_BIND: sbind = data; switch (fi->state) { case GPRS_SNS_ST_UNCONFIGURED: break; case GPRS_SNS_ST_BSS_SIZE: /* 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_BSS_CONFIG_BSS: case GPRS_SNS_ST_BSS_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; case NS2_SNS_EV_REQ_CHANGE_WEIGHT: sbind = data; switch (fi->state) { case GPRS_SNS_ST_UNCONFIGURED: /* select_endpoint will check if this is a valid configuration */ if (gss->role == GPRS_SNS_ROLE_BSS) osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); break; case GPRS_SNS_ST_BSS_SIZE: /* invalid weight? */ if (!ns2_update_weight_entry(gss, sbind, &gss->local)) sns_failed(fi, "updating weights results in an invalid configuration."); break; default: if (!ns2_update_weight_entry(gss, sbind, &gss->local_procedure)) { sns_failed(fi, "updating weights results in an invalid configuration."); break; } ns2_add_procedure(gss, sbind, SNS_PROC_CHANGE_WEIGHT); break; } } } /* validate the bss configuration (sns endpoint and binds) * - no endpoints -> invalid * - no binds -> invalid * - only v4 sns endpoints, only v6 binds -> invalid * - only v4 sns endpoints, but v4 sig weights == 0 -> invalid ... */ static int ns2_sns_bss_valid_configuration(struct ns2_sns_state *gss) { struct ns2_sns_bind *sbind; struct sns_endpoint *endpoint; const struct osmo_sockaddr *addr; int v4_sig = 0, v4_data = 0, v6_sig = 0, v6_data = 0; bool v4_endpoints = false; bool v6_endpoints = false; if (llist_empty(&gss->sns_endpoints) || llist_empty(&gss->binds)) return 0; llist_for_each_entry(sbind, &gss->binds, list) { addr = gprs_ns2_ip_bind_sockaddr(sbind->bind); if (!addr) continue; switch (addr->u.sa.sa_family) { case AF_INET: v4_sig += sbind->bind->sns_sig_weight; v4_data += sbind->bind->sns_data_weight; break; case AF_INET6: v6_sig += sbind->bind->sns_sig_weight; v6_data += sbind->bind->sns_data_weight; break; } } llist_for_each_entry(endpoint, &gss->sns_endpoints, list) { switch (endpoint->saddr.u.sa.sa_family) { case AF_INET: v4_endpoints = true; break; case AF_INET6: v6_endpoints = true; break; } } return (v4_endpoints && v4_sig && v4_data) || (v6_endpoints && v6_sig && v6_data); } /* allstate-action for BSS role */ static void ns2_sns_st_all_action_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); /* reset when receiving NS2_SNS_EV_REQ_NO_NSVC */ switch (event) { case NS2_SNS_EV_REQ_NO_NSVC: /* ignore reselection running */ if (gss->reselection_running) break; sns_failed(fi, "no remaining NSVC, resetting SNS FSM"); break; case NS2_SNS_EV_REQ_FREE_NSVCS: case NS2_SNS_EV_REQ_SELECT_ENDPOINT: /* tear down previous state * gprs_ns2_free_nsvcs() will trigger NO_NSVC, prevent this from triggering a reselection */ gss->reselection_running = true; ns2_free_nsvcs(nse); ns2_clear_elems(&gss->local); ns2_clear_elems(&gss->remote); /* Choose the next sns endpoint. */ if (!ns2_sns_bss_valid_configuration(gss)) { gss->initial = NULL; ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 3); return; } else if (!gss->initial) { gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list); } else if (gss->initial->list.next == &gss->sns_endpoints) { /* last entry, continue with first */ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list); } else { /* next element is an entry */ gss->initial = llist_entry(gss->initial->list.next, struct sns_endpoint, list); } gss->family = gss->initial->saddr.u.sa.sa_family; gss->reselection_running = false; osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 1); break; default: ns2_sns_st_all_action(fi, event, data); break; } } static struct osmo_fsm gprs_ns2_sns_bss_fsm = { .name = "GPRS-NS2-SNS-BSS", .states = ns2_sns_bss_states, .num_states = ARRAY_SIZE(ns2_sns_bss_states), .allstate_event_mask = S(NS2_SNS_EV_REQ_NO_NSVC) | S(NS2_SNS_EV_REQ_FREE_NSVCS) | S(NS2_SNS_EV_REQ_SELECT_ENDPOINT) | S(NS2_SNS_EV_REQ_ADD_BIND) | S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) | S(NS2_SNS_EV_REQ_DELETE_BIND), .allstate_action = ns2_sns_st_all_action_bss, .cleanup = NULL, .timer_cb = ns2_sns_fsm_bss_timer_cb, .event_names = gprs_sns_event_names, .pre_term = NULL, .log_subsys = DLNS, }; /*! Allocate an IP-SNS FSM for the BSS side. * \param[in] nse NS Entity in which the FSM runs * \param[in] id string identifier * \returns FSM instance on success; NULL on error */ struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse, const char *id) { struct osmo_fsm_inst *fi; struct ns2_sns_state *gss; fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id); if (!fi) return fi; gss = talloc_zero(fi, struct ns2_sns_state); if (!gss) goto err; fi->priv = gss; gss->nse = nse; gss->role = GPRS_SNS_ROLE_BSS; /* The SGSN doesn't tell the BSS, so we assume there's always sufficient */ gss->num_max_ip4_remote = 8192; gss->num_max_ip6_remote = 8192; INIT_LLIST_HEAD(&gss->sns_endpoints); INIT_LLIST_HEAD(&gss->binds); INIT_LLIST_HEAD(&gss->procedures); return fi; err: osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return NULL; } /*! main entry point for receiving SNS messages from the network. * \param[in] nsvc NS-VC on which the message was received * \param[in] msg message buffer of the IP-SNS message * \param[in] tp parsed TLV structure of message * \returns 0 on success; negative on error */ int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) { struct gprs_ns2_nse *nse = nsvc->nse; struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; uint16_t nsei = nsvc->nse->nsei; struct ns2_sns_state *gss; struct osmo_fsm_inst *fi; int rc = 0; if (!nse->bss_sns_fi) { LOGNSVC(nsvc, LOGL_NOTICE, "Rx %s for NS Instance that has no SNS!\n", get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); rc = -EINVAL; goto out; } /* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */ fi = nse->bss_sns_fi; gss = (struct ns2_sns_state *) fi->priv; if (!gss->sns_nsvc) gss->sns_nsvc = nsvc; LOGPFSML(fi, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); switch (nsh->pdu_type) { case SNS_PDUT_SIZE: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE, tp); break; case SNS_PDUT_SIZE_ACK: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE_ACK, tp); break; case SNS_PDUT_CONFIG: if (nsh->data[0] & 0x01) osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_END, tp); else osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG, tp); break; case SNS_PDUT_CONFIG_ACK: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_ACK, tp); break; case SNS_PDUT_ADD: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ADD, tp); break; case SNS_PDUT_DELETE: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_DELETE, tp); break; case SNS_PDUT_CHANGE_WEIGHT: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CHANGE_WEIGHT, tp); break; case SNS_PDUT_ACK: osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ACK, tp); break; default: LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); rc = -EINVAL; } out: msgb_free(msg); return rc; } #include #include static void vty_dump_sns_ip4(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip4_elem *ip4) { struct in_addr in = { .s_addr = ip4->ip_addr }; vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix, inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE); } static void vty_dump_sns_ip6(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip6_elem *ip6) { char ip_addr[INET6_ADDRSTRLEN] = {}; if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN))) strcpy(ip_addr, "Invalid IPv6"); vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix, ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE); } /*! Dump the IP-SNS state to a vty. * \param[in] vty VTY to which the state shall be printed * \param[in] prefix prefix to print at start of each line (typically indenting) * \param[in] nse NS Entity whose IP-SNS state shall be printed * \param[in] stats Whether or not statistics shall also be printed */ void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats) { struct ns2_sns_state *gss; unsigned int i; if (!nse->bss_sns_fi) return; vty_out_fsm_inst2(vty, prefix, nse->bss_sns_fi); gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv; vty_out(vty, "%sMaximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s", prefix, gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE); if (gss->local.num_ip4 && gss->remote.num_ip4) { vty_out(vty, "%sLocal IPv4 Endpoints:%s", prefix, VTY_NEWLINE); for (i = 0; i < gss->local.num_ip4; i++) vty_dump_sns_ip4(vty, prefix, &gss->local.ip4[i]); vty_out(vty, "%sRemote IPv4 Endpoints:%s", prefix, VTY_NEWLINE); for (i = 0; i < gss->remote.num_ip4; i++) vty_dump_sns_ip4(vty, prefix, &gss->remote.ip4[i]); } if (gss->local.num_ip6 && gss->remote.num_ip6) { vty_out(vty, "%sLocal IPv6 Endpoints:%s", prefix, VTY_NEWLINE); for (i = 0; i < gss->local.num_ip6; i++) vty_dump_sns_ip6(vty, prefix, &gss->local.ip6[i]); vty_out(vty, "%sRemote IPv6 Endpoints:%s", prefix, VTY_NEWLINE); for (i = 0; i < gss->remote.num_ip6; i++) vty_dump_sns_ip6(vty, prefix, &gss->remote.ip6[i]); } } /*! write IP-SNS to a vty * \param[in] vty VTY to which the state shall be printed * \param[in] nse NS Entity whose IP-SNS state shall be printed */ void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse) { struct ns2_sns_state *gss; struct osmo_sockaddr_str addr_str; struct sns_endpoint *endpoint; if (!nse->bss_sns_fi) return; gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv; llist_for_each_entry(endpoint, &gss->sns_endpoints, list) { /* It's unlikely that an error happens, but let's better be safe. */ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &endpoint->saddr.u.sas) != 0) addr_str = (struct osmo_sockaddr_str) { .ip = "" }; vty_out(vty, " ip-sns-remote %s %u%s", addr_str.ip, addr_str.port, VTY_NEWLINE); } } static struct sns_endpoint *ns2_get_sns_endpoint(struct ns2_sns_state *state, const struct osmo_sockaddr *saddr) { struct sns_endpoint *endpoint; llist_for_each_entry(endpoint, &state->sns_endpoints, list) { if (!osmo_sockaddr_cmp(saddr, &endpoint->saddr)) return endpoint; } return NULL; } /*! gprs_ns2_sns_add_endpoint * \param[in] nse * \param[in] sockaddr * \return */ int gprs_ns2_sns_add_endpoint(struct gprs_ns2_nse *nse, const struct osmo_sockaddr *saddr) { struct ns2_sns_state *gss; struct sns_endpoint *endpoint; bool do_selection = false; if (nse->ll != GPRS_NS2_LL_UDP) { return -EINVAL; } if (nse->dialect != GPRS_NS2_DIALECT_SNS) { return -EINVAL; } gss = nse->bss_sns_fi->priv; if (ns2_get_sns_endpoint(gss, saddr)) return -EADDRINUSE; endpoint = talloc_zero(nse->bss_sns_fi->priv, struct sns_endpoint); if (!endpoint) return -ENOMEM; endpoint->saddr = *saddr; if (llist_empty(&gss->sns_endpoints)) do_selection = true; llist_add_tail(&endpoint->list, &gss->sns_endpoints); if (do_selection) osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL); return 0; } /*! gprs_ns2_sns_del_endpoint * \param[in] nse * \param[in] sockaddr * \return 0 on success, otherwise < 0 */ int gprs_ns2_sns_del_endpoint(struct gprs_ns2_nse *nse, const struct osmo_sockaddr *saddr) { struct ns2_sns_state *gss; struct sns_endpoint *endpoint; if (nse->ll != GPRS_NS2_LL_UDP) { return -EINVAL; } if (nse->dialect != GPRS_NS2_DIALECT_SNS) { return -EINVAL; } gss = nse->bss_sns_fi->priv; endpoint = ns2_get_sns_endpoint(gss, saddr); if (!endpoint) return -ENOENT; /* if this is an unused SNS endpoint it's done */ if (gss->initial != endpoint) { llist_del(&endpoint->list); talloc_free(endpoint); return 0; } /* gprs_ns2_free_nsvcs() will trigger NS2_SNS_EV_REQ_NO_NSVC on the last NS-VC * and restart SNS SIZE procedure which selects a new initial */ LOGNSE(nse, LOGL_INFO, "Current in-use SNS endpoint is being removed." "Closing all NS-VC and restart SNS-SIZE procedure" "with a remaining SNS endpoint.\n"); /* Continue with the next endpoint in the list. * Special case if the endpoint is at the start or end of the list */ if (endpoint->list.prev == &gss->sns_endpoints || endpoint->list.next == &gss->sns_endpoints) gss->initial = NULL; else gss->initial = llist_entry(endpoint->list.next->prev, struct sns_endpoint, list); llist_del(&endpoint->list); gprs_ns2_free_nsvcs(nse); talloc_free(endpoint); return 0; } /*! gprs_ns2_sns_count * \param[in] nse NS Entity whose IP-SNS endpoints shall be printed * \return the count of endpoints or < 0 if NSE doesn't contain sns. */ int gprs_ns2_sns_count(struct gprs_ns2_nse *nse) { struct ns2_sns_state *gss; struct sns_endpoint *endpoint; int count = 0; if (nse->ll != GPRS_NS2_LL_UDP) { return -EINVAL; } if (nse->dialect != GPRS_NS2_DIALECT_SNS) { return -EINVAL; } gss = nse->bss_sns_fi->priv; llist_for_each_entry(endpoint, &gss->sns_endpoints, list) count++; return count; } void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive) { struct ns2_sns_state *gss; struct gprs_ns2_vc *tmp; if (!nse->bss_sns_fi) return; gss = nse->bss_sns_fi->priv; if (nse->bss_sns_fi->state != GPRS_SNS_ST_CONFIGURED && nse->bss_sns_fi->state != GPRS_SNS_ST_LOCAL_PROCEDURE) return; if (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, NS2_SNS_EV_REQ_NSVC_ALIVE, NULL); } else { /* is there at least another alive nsvc? */ llist_for_each_entry(tmp, &nse->nsvc, list) { if (ns2_vc_is_unblocked(tmp)) return; } /* all NS-VC have failed */ gss->alive = false; osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NO_NSVC, NULL); } } int gprs_ns2_sns_add_bind(struct gprs_ns2_nse *nse, struct gprs_ns2_vc_bind *bind) { struct ns2_sns_state *gss; struct ns2_sns_bind *tmp; OSMO_ASSERT(nse->bss_sns_fi); gss = nse->bss_sns_fi->priv; if (!gprs_ns2_is_ip_bind(bind)) { return -EINVAL; } if (!llist_empty(&gss->binds)) { llist_for_each_entry(tmp, &gss->binds, list) { if (tmp->bind == bind) return -EALREADY; } } tmp = talloc_zero(gss, struct ns2_sns_bind); if (!tmp) return -ENOMEM; tmp->bind = bind; llist_add_tail(&tmp->list, &gss->binds); osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_ADD_BIND, tmp); return 0; } /* Remove a bind from the SNS. All assosiated NSVC must be removed. */ int gprs_ns2_sns_del_bind(struct gprs_ns2_nse *nse, struct gprs_ns2_vc_bind *bind) { struct ns2_sns_state *gss; struct ns2_sns_bind *tmp, *tmp2; bool found = false; if (!nse->bss_sns_fi) return -EINVAL; gss = nse->bss_sns_fi->priv; if (gss->initial_bind && gss->initial_bind->bind == bind) { if (gss->initial_bind->list.prev == &gss->binds) gss->initial_bind = NULL; else gss->initial_bind = llist_entry(gss->initial_bind->list.prev, struct ns2_sns_bind, list); } llist_for_each_entry_safe(tmp, tmp2, &gss->binds, list) { if (tmp->bind == bind) { llist_del(&tmp->list); found = true; break; } } if (!found) return -ENOENT; osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_DELETE_BIND, tmp); return 0; } /* Update SNS weights for a bind (local endpoint). * \param[in] bind the bind which has been updated */ void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind) { struct ns2_sns_bind *sbind; struct gprs_ns2_nse *nse; struct ns2_sns_state *gss; const struct osmo_sockaddr *addr = gprs_ns2_ip_bind_sockaddr(bind); llist_for_each_entry(nse, &bind->nsi->nse, list) { if (!nse->bss_sns_fi) continue; gss = nse->bss_sns_fi->priv; if (addr->u.sa.sa_family != gss->family) return; llist_for_each_entry(sbind, &gss->binds, list) { if (sbind->bind == bind) { osmo_fsm_inst_dispatch(gss->nse->bss_sns_fi, NS2_SNS_EV_REQ_CHANGE_WEIGHT, sbind); break; } } } } /*********************************************************************** * SGSN role ***********************************************************************/ /* cleanup all state. If nsvc is given, don't remove this nsvc. (nsvc is given when a SIZE PDU received) */ static void ns2_clear_sgsn(struct ns2_sns_state *gss, struct gprs_ns2_vc *size_nsvc) { struct gprs_ns2_vc *nsvc, *nsvc2; ns2_clear_procedures(gss); ns2_clear_elems(&gss->local); ns2_clear_elems(&gss->remote); llist_for_each_entry_safe(nsvc, nsvc2, &gss->nse->nsvc, list) { /* Ignore the NSVC over which the SIZE PDU got received */ if (size_nsvc && size_nsvc == nsvc) continue; gprs_ns2_free_nsvc(nsvc); } } static void ns2_sns_st_sgsn_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; ns2_clear_sgsn(gss, NULL); } static void ns2_sns_st_sgsn_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); /* do nothing; Rx SNS-SIZE handled in ns2_sns_st_all_action_sgsn() */ } /* We're waiting for inbound SNS-CONFIG from the BSS */ static void ns2_sns_st_sgsn_wait_config(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_inst *nsi = nse->nsi; uint8_t cause; int rc; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); switch (event) { case NS2_SNS_EV_RX_CONFIG: case NS2_SNS_EV_RX_CONFIG_END: rc = ns_sns_append_remote_eps(fi, data); if (rc < 0) { cause = -rc; ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); return; } /* only change state if last CONFIG was received */ if (event == NS2_SNS_EV_RX_CONFIG_END) { /* ensure sum of data weight / sig weights is > 0 */ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) { cause = NS_CAUSE_INVAL_WEIGH; ns2_tx_sns_config_ack(gss->sns_nsvc, &cause); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); break; } ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3); } else { /* just send CONFIG-ACK */ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL); osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0); } break; } } static void ns2_sns_st_sgsn_wait_config_ack_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); /* transmit SGSN-oriented SNS-CONFIG */ ns2_tx_sns_config(gss->sns_nsvc, true, gss->local.ip4, gss->local.num_ip4, gss->local.ip6, gss->local.num_ip6); } /* We're waiting for SNS-CONFIG-ACK from the BSS (in response to our outbound SNS-CONFIG) */ static void ns2_sns_st_sgsn_wait_config_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct tlv_parsed *tp = NULL; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); switch (event) { case NS2_SNS_EV_RX_CONFIG_ACK: tp = data; if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) { LOGPFSML(fi, LOGL_ERROR, "Rx SNS-CONFIG-ACK with cause %s\n", gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE))); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0); break; } /* we currently only send one SNS-CONFIG with END FLAG */ if (true) { create_missing_nsvcs(fi); /* start the test procedure on ALL NSVCs! */ gprs_ns2_start_alive_all_nsvcs(nse); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, ns_sns_configured_timeout(fi), 4); } break; } } /* SGSN-side SNS state machine */ static const struct osmo_fsm_state ns2_sns_sgsn_states[] = { [GPRS_SNS_ST_UNCONFIGURED] = { .in_event_mask = 0, /* handled by all_state_action */ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_SGSN_WAIT_CONFIG), .name = "UNCONFIGURED", .action = ns2_sns_st_sgsn_unconfigured, .onenter = ns2_sns_st_sgsn_unconfigured_onenter, }, [GPRS_SNS_ST_SGSN_WAIT_CONFIG] = { .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) | S(NS2_SNS_EV_RX_CONFIG_END), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) | S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK), .name = "SGSN_WAIT_CONFIG", .action = ns2_sns_st_sgsn_wait_config, }, [GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK] = { .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) | S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK) | S(GPRS_SNS_ST_CONFIGURED), .name = "SGSN_WAIT_CONFIG_ACK", .action = ns2_sns_st_sgsn_wait_config_ack, .onenter = ns2_sns_st_sgsn_wait_config_ack_onenter, }, [GPRS_SNS_ST_CONFIGURED] = { .in_event_mask = S(NS2_SNS_EV_RX_ADD) | S(NS2_SNS_EV_RX_DELETE) | S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | S(NS2_SNS_EV_REQ_NSVC_ALIVE), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) | S(GPRS_SNS_ST_LOCAL_PROCEDURE), .name = "CONFIGURED", /* shared with BSS side; once configured there's no difference */ .action = ns2_sns_st_configured, .onenter = ns2_sns_st_configured_onenter, }, [GPRS_SNS_ST_LOCAL_PROCEDURE] = { .in_event_mask = S(NS2_SNS_EV_RX_ADD) | S(NS2_SNS_EV_RX_DELETE) | S(NS2_SNS_EV_RX_CHANGE_WEIGHT) | S(NS2_SNS_EV_RX_ACK) | S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) | S(NS2_SNS_EV_REQ_NSVC_ALIVE), .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) | S(GPRS_SNS_ST_CONFIGURED) | S(GPRS_SNS_ST_LOCAL_PROCEDURE), .name = "LOCAL_PROCEDURE", /* shared with BSS side; once configured there's no difference */ .action = ns2_sns_st_local_procedure, .onenter = ns2_sns_st_local_procedure_onenter, }, }; static int ns2_sns_fsm_sgsn_timer_cb(struct osmo_fsm_inst *fi) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct gprs_ns2_nse *nse = nse_inst_from_fi(fi); struct gprs_ns2_inst *nsi = nse->nsi; gss->N++; switch (fi->T) { case 3: if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) { LOGPFSML(fi, LOGL_ERROR, "NSE %d: SGSN Config retries failed. Giving up.\n", nse->nsei); osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, nsi->timeout[NS_TOUT_TSNS_PROV], 3); } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3); } break; case 4: LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online.\n", nse->nsei); break; case 5: if (gss->N >= nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES]) { sns_failed(fi, "SNS Procedure retries failed."); } else { osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], fi->T); } break; } return 0; } /* allstate-action for SGSN role */ static void ns2_sns_st_all_action_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv; struct tlv_parsed *tp = NULL; size_t num_local_eps, num_remote_eps; uint8_t flag; uint8_t cause; OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN); switch (event) { case NS2_SNS_EV_RX_SIZE: tp = (struct tlv_parsed *) data; /* check for mandatory / conditional IEs */ if (!TLVP_PRES_LEN(tp, NS_IE_RESET_FLAG, 1) || !TLVP_PRES_LEN(tp, NS_IE_MAX_NR_NSVC, 2)) { cause = NS_CAUSE_MISSING_ESSENT_IE; ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); if (fi->state == GPRS_SNS_ST_UNCONFIGURED) sns_failed(fi, "Rx Size: Missing essential IE"); break; } if (!TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2) && !TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) { cause = NS_CAUSE_MISSING_ESSENT_IE; ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); if (fi->state == GPRS_SNS_ST_UNCONFIGURED) sns_failed(fi, "Rx Size: Missing essential IE"); break; } if (TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2)) gss->num_max_ip4_remote = tlvp_val16be(tp, NS_IE_IPv4_EP_NR); if (TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) gss->num_max_ip6_remote = tlvp_val16be(tp, NS_IE_IPv6_EP_NR); /* decide if we go for IPv4 or IPv6 */ if (gss->num_max_ip6_remote && ns2_sns_count_num_local_ep(fi, AF_INET6)) { gss->family = AF_INET6; ns2_sns_compute_local_ep_from_binds(fi); num_local_eps = gss->local.num_ip6; num_remote_eps = gss->num_max_ip6_remote; } else if (gss->num_max_ip4_remote && ns2_sns_count_num_local_ep(fi, AF_INET)) { gss->family = AF_INET; ns2_sns_compute_local_ep_from_binds(fi); num_local_eps = gss->local.num_ip4; num_remote_eps = gss->num_max_ip4_remote; } else { if (gss->local.num_ip4 && !gss->num_max_ip4_remote) cause = NS_CAUSE_INVAL_NR_IPv4_EP; else cause = NS_CAUSE_INVAL_NR_IPv6_EP; ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); if (fi->state == GPRS_SNS_ST_UNCONFIGURED) sns_failed(fi, "Rx Size: Invalid Nr of IPv4/IPv6 EPs"); break; } /* ensure number of NS-VCs is sufficient for full mesh */ gss->num_max_nsvcs = tlvp_val16be(tp, NS_IE_MAX_NR_NSVC); if (gss->num_max_nsvcs < num_remote_eps * num_local_eps) { LOGPFSML(fi, LOGL_ERROR, "%zu local and %zu remote EPs, requires %zu NS-VC, " "but BSS supports only %zu maximum NS-VCs\n", num_local_eps, num_remote_eps, num_local_eps * num_remote_eps, gss->num_max_nsvcs); cause = NS_CAUSE_INVAL_NR_NS_VC; ns2_tx_sns_size_ack(gss->sns_nsvc, &cause); if (fi->state == GPRS_SNS_ST_UNCONFIGURED) sns_failed(fi, NULL); break; } /* perform state reset, if requested */ flag = *TLVP_VAL(tp, NS_IE_RESET_FLAG); if (flag & 1) { /* clear all state */ /* TODO: ensure gss->sns_nsvc is always the NSVC on which we received the SIZE PDU */ gss->N = 0; ns2_clear_sgsn(gss, gss->sns_nsvc); /* keep the NSVC we need for SNS, but unconfigure it */ gss->sns_nsvc->sig_weight = 0; gss->sns_nsvc->data_weight = 0; ns2_vc_force_unconfigured(gss->sns_nsvc); ns2_sns_compute_local_ep_from_binds(fi); } if (fi->state == GPRS_SNS_ST_UNCONFIGURED && !(flag & 1)) { sns_failed(fi, "Rx Size without Reset flag, but NSE is unknown"); break; } /* send SIZE_ACK */ ns2_tx_sns_size_ack(gss->sns_nsvc, NULL); /* only wait for SNS-CONFIG in case of Reset flag */ if (flag & 1) osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG, 0, 0); break; case NS2_SNS_EV_REQ_FREE_NSVCS: sns_failed(fi, "On user request to free all NSVCs"); break; default: ns2_sns_st_all_action(fi, event, data); break; } } static struct osmo_fsm gprs_ns2_sns_sgsn_fsm = { .name = "GPRS-NS2-SNS-SGSN", .states = ns2_sns_sgsn_states, .num_states = ARRAY_SIZE(ns2_sns_sgsn_states), .allstate_event_mask = S(NS2_SNS_EV_RX_SIZE) | S(NS2_SNS_EV_REQ_NO_NSVC) | S(NS2_SNS_EV_REQ_FREE_NSVCS) | S(NS2_SNS_EV_REQ_ADD_BIND) | S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) | S(NS2_SNS_EV_REQ_DELETE_BIND), .allstate_action = ns2_sns_st_all_action_sgsn, .cleanup = NULL, .timer_cb = ns2_sns_fsm_sgsn_timer_cb, .event_names = gprs_sns_event_names, .pre_term = NULL, .log_subsys = DLNS, }; /*! Allocate an IP-SNS FSM for the SGSN side. * \param[in] nse NS Entity in which the FSM runs * \param[in] id string identifier * \returns FSM instance on success; NULL on error */ struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id) { struct osmo_fsm_inst *fi; struct ns2_sns_state *gss; fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_sgsn_fsm, nse, NULL, LOGL_DEBUG, id); if (!fi) return fi; gss = talloc_zero(fi, struct ns2_sns_state); if (!gss) goto err; fi->priv = gss; gss->nse = nse; gss->role = GPRS_SNS_ROLE_SGSN; INIT_LLIST_HEAD(&gss->sns_endpoints); INIT_LLIST_HEAD(&gss->binds); INIT_LLIST_HEAD(&gss->procedures); return fi; err: osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return NULL; } /* initialize osmo_ctx on main tread */ static __attribute__((constructor)) void on_dso_load_ctx(void) { OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_bss_fsm) == 0); OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_sgsn_fsm) == 0); }