pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmo-sccp/+/34329?usp=email )
Change subject: ss7: Split asp and xua_server into their own files ......................................................................
ss7: Split asp and xua_server into their own files
This allows having a clearer picture of the several entities involved, as well as simplifying the files.
In the future, we can do the same for AS, route, etc. and leave osmo_ss7 for the "instance" object.
Change-Id: I3d43268459d61b0b9f9bec34bf31dc0851fa5e48 --- M src/Makefile.am M src/osmo_ss7.c A src/osmo_ss7_asp.c A src/osmo_ss7_xua_srv.c M src/ss7_internal.h 5 files changed, 1,588 insertions(+), 1,449 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmo-sccp refs/changes/29/34329/1
diff --git a/src/Makefile.am b/src/Makefile.am index 78336d1..d8f0bb6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,7 +31,8 @@ sccp2sua.c sccp_scrc.c sccp_sclc.c sccp_scoc.c \ sccp_user.c sccp_types.c sccp_lbcs.c sccp_scmg.c \ xua_rkm.c xua_shared.c xua_default_lm_fsm.c \ - osmo_ss7.c osmo_ss7_hmrt.c xua_asp_fsm.c xua_as_fsm.c \ + osmo_ss7.c osmo_ss7_asp.c osmo_ss7_hmrt.c osmo_ss7_xua_srv.c \ + xua_asp_fsm.c xua_as_fsm.c \ xua_snm.c osmo_ss7_vty.c sccp_vty.c ipa.c libosmo_sigtran_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -export-symbols-regex '^osmo_' libosmo_sigtran_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \ diff --git a/src/osmo_ss7.c b/src/osmo_ss7.c index 2ba9766..9265898 100644 --- a/src/osmo_ss7.c +++ b/src/osmo_ss7.c @@ -1,6 +1,7 @@ /* Core SS7 Instance/Linkset/Link/AS/ASP Handling */
/* (C) 2015-2017 by Harald Welte laforge@gnumonks.org + * (C) 2023 by sysmocom s.f.m.c. GmbH info@sysmocom.de * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ @@ -55,7 +56,7 @@
#define MAX_PC_STR_LEN 32
-static bool ss7_initialized = false; +bool ss7_initialized = false;
LLIST_HEAD(osmo_ss7_instances); static int32_t next_rctx = 1; @@ -76,33 +77,6 @@ { 0, NULL } };
-struct value_string osmo_ss7_asp_protocol_vals[] = { - { OSMO_SS7_ASP_PROT_NONE, "none" }, - { OSMO_SS7_ASP_PROT_SUA, "sua" }, - { OSMO_SS7_ASP_PROT_M3UA, "m3ua" }, - { OSMO_SS7_ASP_PROT_IPA, "ipa" }, - { 0, NULL } -}; - -const struct value_string osmo_ss7_asp_role_names[] = { - { OSMO_SS7_ASP_ROLE_ASP, "ASP" }, - { OSMO_SS7_ASP_ROLE_SG, "SG" }, - { OSMO_SS7_ASP_ROLE_IPSP, "IPSP" }, - { 0, NULL } -}; - -static int asp_proto_to_ip_proto(enum osmo_ss7_asp_protocol proto) -{ - switch (proto) { - case OSMO_SS7_ASP_PROT_IPA: - return IPPROTO_TCP; - case OSMO_SS7_ASP_PROT_SUA: - case OSMO_SS7_ASP_PROT_M3UA: - default: - return IPPROTO_SCTP; - } -} - int osmo_ss7_find_free_rctx(struct osmo_ss7_instance *inst) { int32_t rctx; @@ -125,60 +99,6 @@ return -1; }
-static int _setsockopt_peer_primary_addr(int fd, const struct osmo_sockaddr *saddr) -{ - int rc; - - struct sctp_setpeerprim so_sctp_setpeerprim = {0}; - - /* rfc6458 sec 8: "For the one-to-one style sockets and branched-off one-to-many - * style sockets (see Section 9.2), this association ID parameter is ignored" - */ - - /* NOTE: Requires setting: - * - sysctl net.sctp.addip_enable = 1, otherwise EPERM is returned - * - sysctl net.sctp.auth_enable = 1, RFC 5061 4.2.7 "An implementation supporting this - * extension MUST list the ASCONF,the ASCONF-ACK, and the AUTH chunks in - * its INIT and INIT-ACK parameters." - */ - - so_sctp_setpeerprim.sspp_addr = saddr->u.sas; - rc = setsockopt(fd, IPPROTO_SCTP, SCTP_SET_PEER_PRIMARY_ADDR, - &so_sctp_setpeerprim, sizeof(so_sctp_setpeerprim)); - if (rc < 0) { - char buf[128]; - int err = errno; - strerror_r(err, (char *)buf, sizeof(buf)); - LOGP(DLSS7, LOGL_ERROR, "setsockopt(SCTP_SET_PEER_PRIMARY_ADDR, %s) failed: %s%s\n", - osmo_sockaddr_to_str(saddr), buf, - err == EPERM ? " (EPERM: Make sure you have sysctl 'net.sctp.auth_enable' " - "and 'net.sctp.addip_enable' set to 1)" : ""); - } - return rc; -} - -static int _setsockopt_primary_addr(int fd, const struct osmo_sockaddr *saddr) -{ - int rc; - - struct sctp_prim so_sctp_prim = {0}; - - /* rfc6458 sec 8: "For the one-to-one style sockets and branched-off one-to-many - * style sockets (see Section 9.2), this association ID parameter is ignored" - */ - - so_sctp_prim.ssp_addr = saddr->u.sas; - rc = setsockopt(fd, IPPROTO_SCTP, SCTP_PRIMARY_ADDR, - &so_sctp_prim, sizeof(so_sctp_prim)); - if (rc < 0) { - char buf[128]; - strerror_r(errno, (char *)buf, sizeof(buf)); - LOGP(DLSS7, LOGL_ERROR, "setsockopt(SCTP_PRIMARY_ADDR, %s) failed: %s\n", - osmo_sockaddr_to_str(saddr), buf); - } - return rc; -} - /*********************************************************************** * SS7 Point Code Parsing / Printing ***********************************************************************/ @@ -380,21 +300,6 @@ } }
-static const uint16_t prot2port[] = { - [OSMO_SS7_ASP_PROT_NONE] = 0, - [OSMO_SS7_ASP_PROT_SUA] = SUA_PORT, - [OSMO_SS7_ASP_PROT_M3UA] = M3UA_PORT, - [OSMO_SS7_ASP_PROT_IPA] = 5000, -}; - -int osmo_ss7_asp_protocol_port(enum osmo_ss7_asp_protocol prot) -{ - if (prot >= ARRAY_SIZE(prot2port)) - return -EINVAL; - else - return prot2port[prot]; -} - /*********************************************************************** * SS7 Instance ***********************************************************************/ @@ -1208,347 +1113,7 @@ return as->fi->state == XUA_AS_S_DOWN; }
-/*********************************************************************** - * SS7 Application Server Process - ***********************************************************************/ - -enum ss7_asp_ctr { - SS7_ASP_CTR_PKT_RX_TOTAL, - SS7_ASP_CTR_PKT_RX_UNKNOWN, - SS7_ASP_CTR_PKT_TX_TOTAL, -}; - -static const struct rate_ctr_desc ss7_asp_rcd[] = { - [SS7_ASP_CTR_PKT_RX_TOTAL] = { "rx:packets:total", "Total number of packets received" }, - [SS7_ASP_CTR_PKT_RX_UNKNOWN] = { "rx:packets:unknown", "Number of packets received for unknown PPID" }, - [SS7_ASP_CTR_PKT_TX_TOTAL] = { "tx:packets:total", "Total number of packets transmitted" }, -}; - -static const struct rate_ctr_group_desc ss7_asp_rcgd = { - .group_name_prefix = "sigtran_asp", - .group_description = "SIGTRAN Application Server Process", - .num_ctr = ARRAY_SIZE(ss7_asp_rcd), - .ctr_desc = ss7_asp_rcd, -}; -static unsigned int g_ss7_asp_rcg_idx; - -void osmo_ss7_asp_peer_init(struct osmo_ss7_asp_peer *peer) -{ - memset(peer, 0, sizeof(*peer)); - peer->idx_primary = -1; -} - -int osmo_ss7_asp_peer_snprintf(char* buf, size_t buf_len, struct osmo_ss7_asp_peer *peer) -{ - int len = 0, offset = 0, rem = buf_len; - int ret, i; - char *after; - char *primary; - - if (buf_len < 3) - return -EINVAL; - - if (peer->host_cnt > 1) { - ret = snprintf(buf, rem, "("); - if (ret < 0) - return ret; - OSMO_SNPRINTF_RET(ret, rem, offset, len); - } - for (i = 0; i < peer->host_cnt; i++) { - primary = (peer->idx_primary == i) ? "*" : ""; - if (peer->host_cnt == 1) - after = ""; - else - after = (i == (peer->host_cnt - 1)) ? ")" : "|"; - ret = snprintf(buf + offset, rem, "%s%s%s", peer->host[i] ? : "0.0.0.0", primary, after); - OSMO_SNPRINTF_RET(ret, rem, offset, len); - } - ret = snprintf(buf + offset, rem, ":%u", peer->port); - if (ret < 0) - return ret; - OSMO_SNPRINTF_RET(ret, rem, offset, len); - - return len; -} - -/*! \brief Set (copy) addresses for a given ASP peer. Previous addresses are freed. - * \param[in] peer Application Server Process peer whose addresses are to be set. - * \param[in] talloc_ctx talloc context used to allocate new addresses. - * \param[in] hosts Array of strings containing IP addresses. - * \param[in] host_cnt Number of strings in hosts - * \param[in] idx_primary Index in "hosts" array marking the SCTP Primary Address, -1 if no explicit Primary Address set - * \returns 0 on success; negative otherwise */ -int osmo_ss7_asp_peer_set_hosts2(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, const char *const*hosts, size_t host_cnt, int idx_primary) -{ - int i = 0; - - if (idx_primary >= (int)host_cnt || idx_primary < -1) - return -EINVAL; - - if (host_cnt > ARRAY_SIZE(peer->host)) - return -EINVAL; - - for (; i < host_cnt; i++) - osmo_talloc_replace_string(talloc_ctx, &peer->host[i], hosts[i]); - for (; i < peer->host_cnt; i++) { - talloc_free(peer->host[i]); - peer->host[i] = NULL; - } - - peer->host_cnt = host_cnt; - peer->idx_primary = idx_primary; - return 0; -} - -/*! \brief Set (copy) addresses for a given ASP peer. Previous addresses are freed. - * \param[in] peer Application Server Process peer whose addresses are to be set. - * \param[in] talloc_ctx talloc context used to allocate new addresses. - * \param[in] hosts Array of strings containing IP addresses. - * \param[in] host_cnt Number of strings in hosts - * \returns 0 on success; negative otherwise */ -int osmo_ss7_asp_peer_set_hosts(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, const char *const*hosts, size_t host_cnt) -{ - return osmo_ss7_asp_peer_set_hosts2(peer, talloc_ctx, hosts, host_cnt, -1); -} - -/* Is string formatted IPv4/v6 addr considered IN(6)ADDR_ANY? */ -static inline bool host_is_ip_anyaddr(const char *host, bool is_v6) -{ - /* NULL addr is resolved as 0.0.0.0 (IPv4) by getaddrinfo(), most - * probably for backward-compatibility reasons. - */ - return is_v6 ? (host && !strcmp(host, "::")) - : (!host || !strcmp(host, "0.0.0.0")); -} - -/*! \brief Append (copy) address to a given ASP peer. Previous addresses are kept. - * \param[in] peer Application Server Process peer the address is appended to. - * \param[in] talloc_ctx talloc context used to allocate new address. - * \param[in] host string containing an IP address. - * \param[in] is_primary_addr whether this IP address is to be added as SCTP Primary Address - * \returns 0 on success; negative otherwise */ -int osmo_ss7_asp_peer_add_host2(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, - const char *host, bool is_primary_addr) -{ - int i; - bool new_is_v6 = osmo_ip_str_type(host) == AF_INET6; - bool new_is_any = host_is_ip_anyaddr(host, new_is_v6); - struct osmo_sockaddr_str addr_str; - - if (osmo_sockaddr_str_from_str(&addr_str, host, 0) < 0) - return -EINVAL; - - if (new_is_any) { - /* Makes no sense to have INET(6)_ANY many times, or INET(6)_ANY - * together with specific addresses, be it of same or different - * IP version: */ - if (peer->host_cnt != 0) - return -EINVAL; - - /* Makes no sense to have INET(6)_ANY as primary: */ - if (is_primary_addr) - return -EINVAL; - - if (peer->host_cnt >= ARRAY_SIZE(peer->host)) - return -EINVAL; - osmo_talloc_replace_string(talloc_ctx, &peer->host[peer->host_cnt], host); - peer->host_cnt++; - return 0; - } - - /* Makes no sense to add specific address to set if INET(6)_ANY - * is already set, be it from same or different IP version: */ - for (i = 0; i < peer->host_cnt; i++) { - bool iter_is_v6 = osmo_ip_str_type(peer->host[i]) == AF_INET6; - if (host_is_ip_anyaddr(peer->host[i], iter_is_v6)) - return -EINVAL; - } - /* Reached this point, no INET(6)_ANY address is set nor we are adding an INET(6)_ANY address. */ - - /* Check if address already exists, and then if primary flags need to be changed: */ - for (i = 0; i < peer->host_cnt; i++) { - struct osmo_sockaddr_str it_addr_str; - bool it_is_primary; - osmo_sockaddr_str_from_str(&it_addr_str, peer->host[i], 0); - - if (osmo_sockaddr_str_cmp(&addr_str, &it_addr_str) != 0) - continue; - it_is_primary = (peer->idx_primary == i); - if (is_primary_addr == it_is_primary) { - /* Nothing to do, return below */ - } else if (is_primary_addr && !it_is_primary) { - /* Mark it as primary: */ - peer->idx_primary = i; - } else { /* if (!is_primary_addr && it_is_primary) { */ - /* mark it as non-primary: */ - peer->idx_primary = -1; - } - return 0; - } - - if (peer->host_cnt >= ARRAY_SIZE(peer->host)) - return -EINVAL; - - osmo_talloc_replace_string(talloc_ctx, &peer->host[peer->host_cnt], host); - if (is_primary_addr) - peer->idx_primary = peer->host_cnt; - peer->host_cnt++; - return 0; -} - -/*! \brief Append (copy) address to a given ASP peer. Previous addresses are kept. - * \param[in] peer Application Server Process peer the address is appended to. - * \param[in] talloc_ctx talloc context used to allocate new address. - * \param[in] host string containing an IP address. - * \returns 0 on success; negative otherwise */ -int osmo_ss7_asp_peer_add_host(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, - const char *host) -{ - return osmo_ss7_asp_peer_add_host2(peer, talloc_ctx, host, false); -} - -static int asp_apply_peer_primary_address(const struct osmo_ss7_asp *asp) -{ - struct osmo_fd *ofd; - struct osmo_sockaddr_str addr_str; - struct osmo_sockaddr addr; - uint16_t local_port; - int rc; - - /* No SCTP Peer Primary Address explicitly configured, do nothing. */ - if (asp->cfg.local.idx_primary == -1) - return 0; - OSMO_ASSERT(asp->cfg.local.idx_primary < asp->cfg.local.host_cnt); - - if (asp->cfg.is_server) - ofd = osmo_stream_srv_get_ofd(asp->server); - else - ofd = osmo_stream_cli_get_ofd(asp->client); - - if (asp->cfg.local.port == 0) { - char port_buf[16]; - osmo_sock_get_local_ip_port(ofd->fd, port_buf, sizeof(port_buf)); - local_port = atoi(port_buf); - } else { - local_port = asp->cfg.local.port; - } - rc = osmo_sockaddr_str_from_str(&addr_str, - asp->cfg.local.host[asp->cfg.local.idx_primary], - local_port); - if (rc < 0) - return rc; - rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas); - if (rc < 0) - return rc; - LOGPASP(asp, DLSS7, LOGL_INFO, "Set Peer's Primary Address %s\n", - osmo_sockaddr_to_str(&addr)); - rc = _setsockopt_peer_primary_addr(ofd->fd, &addr); - - return rc; -} - -static int asp_apply_primary_address(const struct osmo_ss7_asp *asp) -{ - struct osmo_fd *ofd; - struct osmo_sockaddr_str addr_str; - struct osmo_sockaddr addr; - int rc; - - /* No SCTP Primary Address explicitly configured, do nothing. */ - if (asp->cfg.remote.idx_primary == -1) - return 0; - OSMO_ASSERT(asp->cfg.remote.idx_primary < asp->cfg.remote.host_cnt); - - if (asp->cfg.is_server) - ofd = osmo_stream_srv_get_ofd(asp->server); - else - ofd = osmo_stream_cli_get_ofd(asp->client); - - rc = osmo_sockaddr_str_from_str(&addr_str, - asp->cfg.remote.host[asp->cfg.remote.idx_primary], - asp->cfg.remote.port); - if (rc < 0) - return rc; - rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas); - if (rc < 0) - return rc; - LOGPASP(asp, DLSS7, LOGL_INFO, "Set Primary Address %s\n", - osmo_sockaddr_to_str(&addr)); - rc = _setsockopt_primary_addr(ofd->fd, &addr); - return rc; -} - -/* Returns whether the address signalled in the SCTP_PEER_ADDR_CHANGE matches - * the user-configured Primary Address. */ -static bool sctp_peer_addr_change_ev_addr_matches_our_primary(const struct osmo_ss7_asp *asp, - const union sctp_notification *notif) -{ - const char *primary_str; - int primary_str_type; - struct osmo_sockaddr ev_addr, primary; - - OSMO_ASSERT(asp->cfg.remote.idx_primary >= 0); - - primary_str = asp->cfg.remote.host[asp->cfg.remote.idx_primary]; - primary_str_type = osmo_ip_str_type(primary_str); - memcpy(&ev_addr.u.sas, ¬if->sn_paddr_change.spc_aaddr, sizeof(ev_addr.u.sas)); - - /* This whole switch is to properly compare addresses (take into account v6mapped IPv4 addresses): */ - switch (ev_addr.u.sa.sa_family) { - case AF_INET: - switch (primary_str_type) { - case AF_INET: - primary = ev_addr; /* Copy AF + port */ - inet_pton(AF_INET, primary_str, &primary.u.sin.sin_addr); - return (osmo_sockaddr_cmp(&primary, &ev_addr) == 0); - case AF_INET6: - /* for sure not the same */ - return false; - } - return false; - case AF_INET6: - /* "ev_addr" can either be a IPv6 addr or a v6-mapped IPv4 - * address. Compare both as IPv6 (or v6-mapped IPv4) addresses. */ - primary = ev_addr; /* Copy AF + port */ - inet_pton(AF_INET6, primary_str, &primary.u.sin6.sin6_addr); - return (osmo_sockaddr_cmp(&primary, &ev_addr) == 0); - default: - return false; - } -} - -/* Simple SCTP Path-manager tracking/driving the VTY-user-configured primary - * address against the kernel when assoc state changes: */ -static void asp_handle_sctp_notif_monitor_primary_address(const struct osmo_ss7_asp *asp, - const union sctp_notification *notif) -{ - bool match; - - if (asp->cfg.remote.idx_primary == -1) - return; - if (notif->sn_header.sn_type != SCTP_PEER_ADDR_CHANGE) - return; - - switch (notif->sn_paddr_change.spc_state) { - case SCTP_ADDR_AVAILABLE: - case SCTP_ADDR_ADDED: - case SCTP_ADDR_CONFIRMED: - /* If our primary addr became available/added/confirmed, set it */ - match = sctp_peer_addr_change_ev_addr_matches_our_primary(asp, notif); - if (match) - asp_apply_primary_address(asp); - break; - case SCTP_ADDR_MADE_PRIM: - /* If another primary addr was made primary, overwrite it by setting it again */ - match = sctp_peer_addr_change_ev_addr_matches_our_primary(asp, notif); - if (!match) - asp_apply_primary_address(asp); - default: - break; - } -} - -static bool ipv6_sctp_supported(const char *host, bool bind) +bool ss7_ipv6_sctp_supported(const char *host, bool bind) { int rc; struct addrinfo hints; @@ -1574,46 +1139,6 @@ return true; }
-/* Set default values for local and remote peer hosts if they are not yet set. - * \param[in] asp ASP for which to set default hosts. - * \returns true if values where changed, false otherwise. - * - * If the ASP is already started, osmo_ss7_asp_restart() must be called - * afterwards in order to apply the new settings. - * This API is internal, hence doesn't appear in osmo_ss7.h - */ -bool ss7_asp_set_default_peer_hosts(struct osmo_ss7_asp *asp) -{ - bool changed = false; - /* If no local addr was set */ - if (!asp->cfg.local.host_cnt) { - bool rem_has_v4 = false, rem_has_v6 = false; - int i; - for (i = 0; i < asp->cfg.remote.host_cnt; i++) { - if (osmo_ip_str_type(asp->cfg.remote.host[i]) == AF_INET6) - rem_has_v6 = true; - else - rem_has_v4 = true; - } - /* "::" Covers both IPv4 and IPv6, but if only IPv4 - * address are set on the remote side, IPv4 on the local - * side must be set too */ - if (ipv6_sctp_supported("::", true) && !(rem_has_v4 && !rem_has_v6)) - osmo_ss7_asp_peer_add_host(&asp->cfg.local, asp, "::"); - else - osmo_ss7_asp_peer_add_host(&asp->cfg.local, asp, "0.0.0.0"); - changed = true; - } - /* If no remote addr was set */ - if (!asp->cfg.remote.host_cnt) { - osmo_ss7_asp_peer_add_host(&asp->cfg.remote, asp, "127.0.0.1"); - if (ipv6_sctp_supported("::1", false)) - osmo_ss7_asp_peer_add_host(&asp->cfg.remote, asp, "::1"); - changed = true; - } - return changed; -} - struct osmo_ss7_asp * osmo_ss7_asp_find_by_name(struct osmo_ss7_instance *inst, const char *name) { @@ -1627,133 +1152,6 @@ return NULL; }
-static uint16_t get_in_port(struct sockaddr *sa) -{ - switch (sa->sa_family) { - case AF_INET: - return (((struct sockaddr_in*)sa)->sin_port); - case AF_INET6: - return (((struct sockaddr_in6*)sa)->sin6_port); - default: - return 0; - } -} - -/* Converts string representation of v4-mappend-on-v6 IP addr to a pure IPv4 address. - * Example: ::ffff:172.18.19.200 => 172.18.19.200 - */ -static void chop_v4_mapped_on_v6_prefix(char* buf) -{ - char *last_colon; - size_t len; - char *first_dot = strchr(buf, '.'); - - if (!first_dot) - return; /* Not an IPv4-mappend-on-v6 string representation, nothing to do */ - last_colon = strrchr(buf, ':'); - if (!last_colon) - return; /* pure IPv4 address, nothing to do */ - - len = strlen(last_colon + 1); - memmove(buf, last_colon + 1, len); - buf[len] = '\0'; -} - -/*! \brief Find an ASP definition matching the local+remote IP/PORT of given fd - * \param[in] fd socket descriptor of given socket - * \returns SS7 ASP in case a matching one is found; NULL otherwise */ -static struct osmo_ss7_asp * -osmo_ss7_asp_find_by_socket_addr(int fd) -{ - struct osmo_ss7_instance *inst; - struct sockaddr_storage sa_l, sa_r; - socklen_t sa_len_l = sizeof(sa_l); - socklen_t sa_len_r = sizeof(sa_r); - char hostbuf_l[64], hostbuf_r[64]; - uint16_t local_port, remote_port; - bool loc_is_v6, rem_is_v6; - int rc; - int i; - - OSMO_ASSERT(ss7_initialized); - /* convert local and remote IP to string */ - rc = getsockname(fd, (struct sockaddr*)&sa_l, &sa_len_l); - if (rc < 0) - return NULL; - rc = getnameinfo((struct sockaddr*)&sa_l, sa_len_l, - hostbuf_l, sizeof(hostbuf_l), - NULL, 0, NI_NUMERICHOST); - if (rc < 0) - return NULL; - local_port = ntohs(get_in_port((struct sockaddr*)&sa_l)); - - rc = getpeername(fd, (struct sockaddr*)&sa_r, &sa_len_r); - if (rc < 0) - return NULL; - rc = getnameinfo((struct sockaddr*)&sa_r, sa_len_r, - hostbuf_r, sizeof(hostbuf_r), - NULL, 0, NI_NUMERICHOST); - if (rc < 0) - return NULL; - remote_port = ntohs(get_in_port((struct sockaddr*)&sa_r)); - - /* If multi-home is used with both IPv4 and IPv6, then the socket is - * AF_INET6, and then returned IPv4 addresses are actually v6mapped ones. - * We need to convert them to IPv4 before matching. - */ - chop_v4_mapped_on_v6_prefix(hostbuf_l); - chop_v4_mapped_on_v6_prefix(hostbuf_r); - loc_is_v6 = osmo_ip_str_type(hostbuf_l) == AF_INET6; - rem_is_v6 = osmo_ip_str_type(hostbuf_r) == AF_INET6; - - /* check all instances for any ASP definition matching the - * address combination of local/remote ip/port */ - llist_for_each_entry(inst, &osmo_ss7_instances, list) { - struct osmo_ss7_asp *asp; - llist_for_each_entry(asp, &inst->asp_list, list) { - if (asp->cfg.local.port != local_port) - continue; - if (asp->cfg.remote.port && asp->cfg.remote.port != remote_port) - continue; - - for (i = 0; i < asp->cfg.local.host_cnt; i++) { - bool iter_is_v6 = osmo_ip_str_type(asp->cfg.local.host[i]) == AF_INET6; - bool iter_is_anyaddr = host_is_ip_anyaddr(asp->cfg.local.host[i], iter_is_v6); - /* "::" (v6) covers "0.0.0.0" (v4), but not otherwise */ - if (iter_is_v6 != loc_is_v6 && - !(iter_is_v6 && iter_is_anyaddr)) - continue; - if (iter_is_anyaddr || - !strcmp(asp->cfg.local.host[i], hostbuf_l)) - break; - } - if (i == asp->cfg.local.host_cnt) - continue; /* didn't match any local.host */ - - /* If no remote host was set, it's probably a server and hence we match any cli src */ - if (asp->cfg.remote.host_cnt) { - for (i = 0; i < asp->cfg.remote.host_cnt; i++) { - bool iter_is_v6 = osmo_ip_str_type(asp->cfg.remote.host[i]) == AF_INET6; - bool iter_is_anyaddr = host_is_ip_anyaddr(asp->cfg.remote.host[i], iter_is_v6); - /* "::" (v6) covers "0.0.0.0" (v4), but not otherwise */ - if (iter_is_v6 != rem_is_v6 && - !(iter_is_v6 && iter_is_anyaddr)) - continue; - if (iter_is_anyaddr || - !strcmp(asp->cfg.remote.host[i], hostbuf_r)) - break; - } - if (i == asp->cfg.remote.host_cnt) - continue; /* didn't match any remote.host */ - } - - return asp; - } - } - - return NULL; -} - /*! \brief Find an ASP that matches the given protocol. * \param[in] as Application Server in which to look for \ref asp * \returns SS7 ASP in case a matching one is found; NULL otherwise */ @@ -1789,38 +1187,6 @@ return asp; }
-static struct osmo_ss7_asp * -osmo_ss7_asp_alloc(struct osmo_ss7_instance *inst, const char *name, - uint16_t remote_port, uint16_t local_port, - enum osmo_ss7_asp_protocol proto) -{ - struct osmo_ss7_asp *asp = talloc_zero(inst, struct osmo_ss7_asp); - asp->ctrg = rate_ctr_group_alloc(asp, &ss7_asp_rcgd, g_ss7_asp_rcg_idx++); - if (!asp->ctrg) { - talloc_free(asp); - return NULL; - } - rate_ctr_group_set_name(asp->ctrg, name); - asp->inst = inst; - osmo_ss7_asp_peer_init(&asp->cfg.remote); - asp->cfg.remote.port = remote_port; - osmo_ss7_asp_peer_init(&asp->cfg.local); - asp->cfg.local.port = local_port; - asp->cfg.proto = proto; - asp->cfg.name = talloc_strdup(asp, name); - - asp->cfg.T_defs_lm = talloc_memdup(asp, ss7_asp_lm_timer_defaults, - sizeof(ss7_asp_lm_timer_defaults)); - osmo_tdefs_reset(asp->cfg.T_defs_lm); - - llist_add_tail(&asp->list, &inst->asp_list); - - /* The SUA code internally needs SCCP to work */ - if (proto == OSMO_SS7_ASP_PROT_SUA) - osmo_ss7_ensure_sccp(inst); - return asp; -} - struct osmo_ss7_asp * osmo_ss7_asp_find_or_create(struct osmo_ss7_instance *inst, const char *name, uint16_t remote_port, uint16_t local_port, @@ -1838,630 +1204,9 @@ return asp; }
- return osmo_ss7_asp_alloc(inst, name, remote_port, local_port, proto); + return ss7_asp_alloc(inst, name, remote_port, local_port, proto); }
-void osmo_ss7_asp_destroy(struct osmo_ss7_asp *asp) -{ - struct osmo_ss7_as *as; - - OSMO_ASSERT(ss7_initialized); - LOGPASP(asp, DLSS7, LOGL_INFO, "Destroying ASP\n"); - - if (asp->server) - osmo_stream_srv_destroy(asp->server); - if (asp->client) - osmo_stream_cli_destroy(asp->client); - if (asp->fi) - osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); - if (asp->xua_server) - llist_del(&asp->siblings); - - /* unlink from all ASs we are part of */ - llist_for_each_entry(as, &asp->inst->as_list, list) { - unsigned int i; - for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { - if (as->cfg.asps[i] == asp) { - as->cfg.asps[i] = NULL; - } - } - } - /* unlink from ss7_instance */ - asp->inst = NULL; - llist_del(&asp->list); - rate_ctr_group_free(asp->ctrg); - /* release memory */ - talloc_free(asp); -} - -static int xua_cli_read_cb(struct osmo_stream_cli *conn); -static int ipa_cli_read_cb(struct osmo_stream_cli *conn); -static int xua_cli_connect_cb(struct osmo_stream_cli *cli); - -int osmo_ss7_asp_restart(struct osmo_ss7_asp *asp) -{ - int rc; - char bufloc[512], bufrem[512]; - - OSMO_ASSERT(ss7_initialized); - osmo_ss7_asp_peer_snprintf(bufloc, sizeof(bufloc), &asp->cfg.local); - osmo_ss7_asp_peer_snprintf(bufrem, sizeof(bufrem), &asp->cfg.remote); - LOGPASP(asp, DLSS7, LOGL_INFO, "Restarting ASP %s, r=%s<->l=%s\n", - asp->cfg.name, bufrem, bufloc); - - if (!asp->cfg.is_server) { - /* We are in client mode now */ - if (asp->server) { - /* if we previously were in server mode, - * destroy it */ - osmo_stream_srv_destroy(asp->server); - asp->server = NULL; - } - if (!asp->client) - asp->client = osmo_stream_cli_create(asp); - if (!asp->client) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Unable to create stream" - " client for ASP %s\n", asp->cfg.name); - return -1; - } - osmo_stream_cli_set_name(asp->client, asp->cfg.name); - osmo_stream_cli_set_nodelay(asp->client, true); - osmo_stream_cli_set_addrs(asp->client, (const char**)asp->cfg.remote.host, asp->cfg.remote.host_cnt); - osmo_stream_cli_set_port(asp->client, asp->cfg.remote.port); - osmo_stream_cli_set_local_addrs(asp->client, (const char**)asp->cfg.local.host, asp->cfg.local.host_cnt); - osmo_stream_cli_set_local_port(asp->client, asp->cfg.local.port); - osmo_stream_cli_set_proto(asp->client, asp_proto_to_ip_proto(asp->cfg.proto)); - osmo_stream_cli_set_reconnect_timeout(asp->client, 5); - osmo_stream_cli_set_connect_cb(asp->client, xua_cli_connect_cb); - if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA) - osmo_stream_cli_set_read_cb(asp->client, ipa_cli_read_cb); - else - osmo_stream_cli_set_read_cb(asp->client, xua_cli_read_cb); - osmo_stream_cli_set_data(asp->client, asp); - rc = osmo_stream_cli_open(asp->client); - if (rc < 0) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Unable to open stream" - " client for ASP %s, %s ==> %s\n", asp->cfg.name, bufloc, bufrem); - /* we don't return error in here because osmo_stream_cli_open() - will continue to retry (due to timeout being explicitly set with - osmo_stream_cli_set_reconnect_timeout() above) to connect so the error is transient */ - } - } else { - /* We are in server mode now */ - if (asp->client) { - /* if we previously were in client mode, - * destroy it */ - osmo_stream_cli_destroy(asp->client); - asp->client = NULL; - } - /* FIXME: ensure we have a SCTP server */ - LOGPASP(asp, DLSS7, LOGL_NOTICE, "ASP Restart for server " - "not implemented yet!\n"); - } - - /* (re)start the ASP FSM */ - if (asp->fi) - osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); - asp->fi = xua_asp_fsm_start(asp, asp->cfg.role, LOGL_DEBUG); - - return 0; -} - -bool osmo_ss7_asp_active(const struct osmo_ss7_asp *asp) -{ - if (!asp->fi) - return false; - return asp->fi->state == XUA_ASP_S_ACTIVE; -} - -/*********************************************************************** - * libosmo-netif integration for SCTP stream server/client - ***********************************************************************/ - -static int get_logevel_by_sn_type(int sn_type) -{ - switch (sn_type) { - case SCTP_ADAPTATION_INDICATION: - case SCTP_PEER_ADDR_CHANGE: -#ifdef SCTP_AUTHENTICATION_INDICATION - case SCTP_AUTHENTICATION_INDICATION: -#endif -#ifdef SCTP_SENDER_DRY_EVENT - case SCTP_SENDER_DRY_EVENT: -#endif - return LOGL_INFO; - case SCTP_ASSOC_CHANGE: - return LOGL_NOTICE; - case SCTP_SHUTDOWN_EVENT: - case SCTP_PARTIAL_DELIVERY_EVENT: - return LOGL_NOTICE; - case SCTP_SEND_FAILED: - case SCTP_REMOTE_ERROR: - return LOGL_ERROR; - default: - return LOGL_NOTICE; - } -} - -static void log_sctp_notification(struct osmo_ss7_asp *asp, const char *pfx, - union sctp_notification *notif) -{ - int log_level; - - LOGPASP(asp, DLSS7, LOGL_INFO, "%s SCTP NOTIFICATION %u flags=0x%0x\n", - pfx, notif->sn_header.sn_type, - notif->sn_header.sn_flags); - - log_level = get_logevel_by_sn_type(notif->sn_header.sn_type); - - switch (notif->sn_header.sn_type) { - case SCTP_ASSOC_CHANGE: - LOGPASP(asp, DLSS7, log_level, "%s SCTP_ASSOC_CHANGE: %s\n", - pfx, osmo_sctp_assoc_chg_str(notif->sn_assoc_change.sac_state)); - break; - case SCTP_PEER_ADDR_CHANGE: - { - char addr_str[INET6_ADDRSTRLEN + 10]; - struct sockaddr_storage sa = notif->sn_paddr_change.spc_aaddr; - osmo_sockaddr_to_str_buf(addr_str, sizeof(addr_str), (const struct osmo_sockaddr *)&sa); - LOGPASP(asp, DLSS7, log_level, "%s SCTP_PEER_ADDR_CHANGE: %s %s err=%s\n", - pfx, osmo_sctp_paddr_chg_str(notif->sn_paddr_change.spc_state), addr_str, - (notif->sn_paddr_change.spc_state == SCTP_ADDR_UNREACHABLE) ? - osmo_sctp_sn_error_str(notif->sn_paddr_change.spc_error) : "None"); - } - break; - default: - LOGPASP(asp, DLSS7, log_level, "%s %s\n", - pfx, osmo_sctp_sn_type_str(notif->sn_header.sn_type)); - break; - } -} - -/* netif code tells us we can read something from the socket */ -static int ipa_srv_conn_cb(struct osmo_stream_srv *conn) -{ - struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); - struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); - struct msgb *msg = NULL; - int rc; - - /* read IPA message from socket and process it */ - rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); - LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", - __func__, rc); - if (rc <= 0) { - if (rc == -EAGAIN) { - /* more data needed */ - return 0; - } - osmo_stream_srv_destroy(conn); - return rc; - } - if (osmo_ipa_process_msg(msg) < 0) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); - osmo_stream_srv_destroy(conn); - msgb_free(msg); - return -1; - } - msg->dst = asp; - - return ipa_rx_msg(asp, msg, ofd->fd & 0xf); -} - -/* netif code tells us we can read something from the socket */ -static int xua_srv_conn_cb(struct osmo_stream_srv *conn) -{ - struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); - struct msgb *msg = m3ua_msgb_alloc("xUA Server Rx"); - unsigned int ppid; - int flags; - int rc; - - if (!msg) - return -ENOMEM; - - /* read xUA message from socket and process it */ - rc = osmo_stream_srv_recv(conn, msg); - flags = msgb_sctp_msg_flags(msg); - - LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", - __func__, rc, flags); - - if (flags & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) { - union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); - log_sctp_notification(asp, "xUA CLNT", notif); - asp_handle_sctp_notif_monitor_primary_address(asp, notif); - - switch (notif->sn_header.sn_type) { - case SCTP_ASSOC_CHANGE: - if (notif->sn_assoc_change.sac_state == SCTP_RESTART) - xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, - PRIM_OP_INDICATION); - default: - break; - } - - if (rc == 0) { - osmo_stream_srv_destroy(conn); - rc = -EBADF; - } else { - rc = 0; - } - goto out; - } - if (rc < 0) { - osmo_stream_srv_destroy(conn); - rc = -EBADF; - goto out; - } else if (rc == 0) { - osmo_stream_srv_destroy(conn); - rc = -EBADF; - goto out; - } - - ppid = msgb_sctp_ppid(msg); - msg->dst = asp; - rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); - - if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) - rc = sua_rx_msg(asp, msg); - else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) - rc = m3ua_rx_msg(asp, msg); - else - rc = ss7_asp_rx_unknown(asp, ppid, msg); - -out: - msgb_free(msg); - return rc; -} - -/* client has established SCTP connection to server */ -static int xua_cli_connect_cb(struct osmo_stream_cli *cli) -{ - struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); - struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); - int rc = 0; - - /* update the socket name */ - talloc_free(asp->sock_name); - asp->sock_name = osmo_sock_get_name(asp, ofd->fd); - - LOGPASP(asp, DLSS7, LOGL_INFO, "Client connected %s\n", asp->sock_name); - - /* Now that we have the conn in place, the local/remote addresses are - * fed and the local port is known for sure. Apply SCTP Primary addresses - * if needed: - */ - if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA) { - rc = asp_apply_peer_primary_address(asp); - rc = asp_apply_primary_address(asp); - } - - if (asp->lm && asp->lm->prim_cb) { - /* Notify layer manager that a connection has been - * established */ - xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); - } else { - /* directly as the ASP FSM to start by sending an ASP-UP ... */ - osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); - } - - return rc; -} - -static void xua_cli_close(struct osmo_stream_cli *cli) -{ - struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); - - osmo_stream_cli_close(cli); - osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, asp); - /* send M-SCTP_RELEASE.ind to XUA Layer Manager */ - xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); -} - -static void xua_cli_close_and_reconnect(struct osmo_stream_cli *cli) -{ - xua_cli_close(cli); - osmo_stream_cli_reconnect(cli); -} - -/* read call-back for IPA/SCCPlite socket */ -static int ipa_cli_read_cb(struct osmo_stream_cli *conn) -{ - struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn); - struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); - struct msgb *msg = NULL; - int rc; - - /* read IPA message from socket and process it */ - rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); - LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", - __func__, rc); - if (rc <= 0) { - if (rc == -EAGAIN) { - /* more data needed */ - return 0; - } - xua_cli_close_and_reconnect(conn); - return rc; - } - if (osmo_ipa_process_msg(msg) < 0) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); - xua_cli_close_and_reconnect(conn); - msgb_free(msg); - return -1; - } - msg->dst = asp; - rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); - return ipa_rx_msg(asp, msg, ofd->fd & 0xf); -} - -static int xua_cli_read_cb(struct osmo_stream_cli *conn) -{ - struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); - struct msgb *msg = m3ua_msgb_alloc("xUA Client Rx"); - unsigned int ppid; - int flags; - int rc; - - if (!msg) - return -ENOMEM; - - /* read xUA message from socket and process it */ - rc = osmo_stream_cli_recv(conn, msg); - flags = msgb_sctp_msg_flags(msg); - - LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", - __func__, rc, flags); - - if (flags & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) { - union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); - log_sctp_notification(asp, "xUA CLNT", notif); - asp_handle_sctp_notif_monitor_primary_address(asp, notif); - - switch (notif->sn_header.sn_type) { - case SCTP_ASSOC_CHANGE: - if (notif->sn_assoc_change.sac_state == SCTP_RESTART) - xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, - PRIM_OP_INDICATION); - default: - break; - } - - if (rc == 0) - xua_cli_close_and_reconnect(conn); - rc = 0; - goto out; - } - if (rc < 0) { - xua_cli_close_and_reconnect(conn); - goto out; - } else if (rc == 0) { - xua_cli_close_and_reconnect(conn); - goto out; - } - - ppid = msgb_sctp_ppid(msg); - msg->dst = asp; - rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); - - if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) - rc = sua_rx_msg(asp, msg); - else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) - rc = m3ua_rx_msg(asp, msg); - else - rc = ss7_asp_rx_unknown(asp, ppid, msg); - -out: - msgb_free(msg); - return rc; -} - -static int xua_srv_conn_closed_cb(struct osmo_stream_srv *srv) -{ - struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(srv); - - LOGP(DLSS7, LOGL_INFO, "%s: connection closed\n", asp ? asp->cfg.name : "?"); - - if (!asp) - return 0; - - /* notify ASP FSM and everyone else */ - osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, NULL); - - /* delete any RKM-dynamically allocated ASs for this ASP */ - xua_rkm_cleanup_dyn_as_for_asp(asp); - - /* send M-SCTP_RELEASE.ind to Layer Manager */ - xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); - - asp->server = NULL; - - /* if we were dynamically allocated at accept_cb() time, let's - * self-destruct now. A new connection will re-create the ASP. */ - if (asp->dyn_allocated) { - /* avoid re-entrance via osmo_stream_srv_destroy() which - * called us */ - osmo_ss7_asp_destroy(asp); - } - - return 0; -} - - -/* server has accept()ed a new SCTP association, let's find the ASP for - * it (if any) */ -static int xua_accept_cb(struct osmo_stream_srv_link *link, int fd) -{ - struct osmo_xua_server *oxs = osmo_stream_srv_link_get_data(link); - struct osmo_stream_srv *srv; - struct osmo_ss7_asp *asp; - char *sock_name = osmo_sock_get_name(link, fd); - const char *proto_name = get_value_string(osmo_ss7_asp_protocol_vals, oxs->cfg.proto); - int rc = 0; - - LOGP(DLSS7, LOGL_INFO, "%s: New %s connection accepted\n", sock_name, proto_name); - - if (oxs->cfg.proto == OSMO_SS7_ASP_PROT_IPA) { - srv = osmo_stream_srv_create(oxs, link, fd, - ipa_srv_conn_cb, - xua_srv_conn_closed_cb, NULL); - } else { - srv = osmo_stream_srv_create(oxs, link, fd, - xua_srv_conn_cb, - xua_srv_conn_closed_cb, NULL); - } - if (!srv) { - LOGP(DLSS7, LOGL_ERROR, "%s: Unable to create stream server " - "for connection\n", sock_name); - close(fd); - talloc_free(sock_name); - return -1; - } - - asp = osmo_ss7_asp_find_by_socket_addr(fd); - if (asp) { - LOGP(DLSS7, LOGL_INFO, "%s: matched connection to ASP %s\n", - sock_name, asp->cfg.name); - /* we need to check if we already have a socket associated, and close it. Otherwise it might - * happen that both the listen-fd for this accept() and the old socket are marked 'readable' - * during the same scheduling interval, and we're processing them in the "wrong" order, i.e. - * we first see the accept of the new fd before we see the close on the old fd */ - if (asp->server) { - LOGPASP(asp, DLSS7, LOGL_FATAL, "accept of new connection from %s before old was closed " - "-> close old one\n", sock_name); - osmo_stream_srv_set_data(asp->server, NULL); - osmo_stream_srv_destroy(asp->server); - asp->server = NULL; - } - } else { - if (!oxs->cfg.accept_dyn_reg) { - LOGP(DLSS7, LOGL_NOTICE, "%s: %s connection without matching " - "ASP definition and no dynamic registration enabled, terminating\n", - sock_name, proto_name); - } else { - char namebuf[32]; - static uint32_t dyn_asp_num = 0; - snprintf(namebuf, sizeof(namebuf), "asp-dyn-%u", dyn_asp_num++); - asp = osmo_ss7_asp_find_or_create(oxs->inst, namebuf, 0, 0, - oxs->cfg.proto); - if (asp) { - char hostbuf[INET6_ADDRSTRLEN]; - const char *hostbuf_ptr = &hostbuf[0]; - char portbuf[16]; - - osmo_sock_get_ip_and_port(fd, hostbuf, sizeof(hostbuf), portbuf, sizeof(portbuf), false); - LOGP(DLSS7, LOGL_INFO, "%s: created dynamic ASP %s\n", - sock_name, asp->cfg.name); - asp->cfg.is_server = true; - asp->cfg.role = OSMO_SS7_ASP_ROLE_SG; - asp->cfg.local.port = oxs->cfg.local.port; - asp->cfg.remote.port = atoi(portbuf); - asp->dyn_allocated = true; - asp->server = srv; - osmo_ss7_asp_peer_set_hosts(&asp->cfg.local, asp, - (const char* const*)oxs->cfg.local.host, - oxs->cfg.local.host_cnt); - osmo_ss7_asp_peer_set_hosts(&asp->cfg.remote, asp, - &hostbuf_ptr, 1); - osmo_ss7_asp_restart(asp); - } - } - if (!asp) { - osmo_stream_srv_destroy(srv); - talloc_free(sock_name); - return -1; - } - llist_add_tail(&asp->siblings, &oxs->asp_list); - } - - /* update the ASP reference back to the server over which the - * connection came in */ - asp->server = srv; - asp->xua_server = oxs; - - /* update the ASP socket name */ - talloc_free(asp->sock_name); - asp->sock_name = talloc_reparent(link, asp, sock_name); - osmo_stream_srv_set_name(asp->server, asp->cfg.name); - /* make sure the conn_cb() is called with the asp as private - * data */ - osmo_stream_srv_set_data(srv, asp); - - if (oxs->cfg.proto != OSMO_SS7_ASP_PROT_IPA) { - rc = asp_apply_peer_primary_address(asp); - rc = asp_apply_primary_address(asp); - } - - /* send M-SCTP_ESTABLISH.ind to Layer Manager */ - osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_EST_IND, 0); - xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); - - return rc; -} - -/*! \brief send a fully encoded msgb via a given ASP - * \param[in] asp Application Server Process through which to send - * \param[in] msg message buffer to transmit. Ownership transferred. - * \returns 0 on success; negative in case of error */ -int osmo_ss7_asp_send(struct osmo_ss7_asp *asp, struct msgb *msg) -{ - OSMO_ASSERT(ss7_initialized); - - switch (asp->cfg.proto) { - case OSMO_SS7_ASP_PROT_SUA: - msgb_sctp_ppid(msg) = SUA_PPID; - break; - case OSMO_SS7_ASP_PROT_M3UA: - msgb_sctp_ppid(msg) = M3UA_PPID; - break; - case OSMO_SS7_ASP_PROT_IPA: - break; - default: - OSMO_ASSERT(0); - } - - rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_TX_TOTAL); - - if (asp->cfg.is_server) { - if (!asp->server) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->server\n"); - /* FIXME: what to do here? delete the route? send DUNA? */ - msgb_free(msg); - return -EIO; - } - osmo_stream_srv_send(asp->server, msg); - } else { - if (!asp->client) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->client\n"); - /* FIXME: what to do here? delete the route? send DUNA? */ - msgb_free(msg); - return -EIO; - } - if (!osmo_stream_cli_is_connected(asp->client)) { - LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, asp->client not connected\n"); - msgb_free(msg); - return -EIO; - } - osmo_stream_cli_send(asp->client, msg); - } - - return 0; -} - -void osmo_ss7_asp_disconnect(struct osmo_ss7_asp *asp) -{ - if (asp->server) - osmo_stream_srv_destroy(asp->server); - /* the close_cb() will handle the remaining cleanup here */ - else if (asp->client) - xua_cli_close_and_reconnect(asp->client); -} - -/*********************************************************************** - * SS7 xUA Server - ***********************************************************************/ - struct osmo_xua_server * osmo_ss7_xua_server_find(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, uint16_t local_port) @@ -2477,136 +1222,6 @@ return NULL; }
-/*! \brief create a new xUA server configured with given ip/port - * \param[in] ctx talloc allocation context - * \param[in] proto protocol (xUA variant) to use - * \param[in] local_port local SCTP port to bind/listen to - * \param[in] local_host local IP address to bind/listen to (optional) - * \returns callee-allocated \ref osmo_xua_server in case of success - */ -struct osmo_xua_server * -osmo_ss7_xua_server_create(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, - uint16_t local_port, const char *local_host) -{ - struct osmo_xua_server *oxs = talloc_zero(inst, struct osmo_xua_server); - - OSMO_ASSERT(ss7_initialized); - if (!oxs) - return NULL; - - LOGP(DLSS7, LOGL_INFO, "Creating %s Server %s:%u\n", - get_value_string(osmo_ss7_asp_protocol_vals, proto), local_host, local_port); - - INIT_LLIST_HEAD(&oxs->asp_list); - - oxs->cfg.proto = proto; - oxs->cfg.local.port = local_port; - - oxs->server = osmo_stream_srv_link_create(oxs); - osmo_stream_srv_link_set_name(oxs->server, osmo_ss7_asp_protocol_name(proto)); - osmo_stream_srv_link_set_data(oxs->server, oxs); - osmo_stream_srv_link_set_accept_cb(oxs->server, xua_accept_cb); - - osmo_stream_srv_link_set_nodelay(oxs->server, true); - osmo_stream_srv_link_set_port(oxs->server, oxs->cfg.local.port); - osmo_stream_srv_link_set_proto(oxs->server, asp_proto_to_ip_proto(proto)); - - osmo_ss7_xua_server_set_local_host(oxs, local_host); - - LOGP(DLSS7, LOGL_INFO, "Created %s server on %s:%" PRIu16 "\n", - get_value_string(osmo_ss7_asp_protocol_vals, proto), local_host, local_port); - - oxs->inst = inst; - llist_add_tail(&oxs->list, &inst->xua_servers); - - /* The SUA code internally needs SCCP to work */ - if (proto == OSMO_SS7_ASP_PROT_SUA) - osmo_ss7_ensure_sccp(inst); - - return oxs; -} - -/*! \brief Set the xUA server to bind/listen to the currently configured ip/port - * \param[in] xs xUA server to operate - * \returns 0 on success, negative value on error. - */ -int -osmo_ss7_xua_server_bind(struct osmo_xua_server *xs) -{ - char buf[512]; - int rc; - const char *proto = get_value_string(osmo_ss7_asp_protocol_vals, xs->cfg.proto); - - rc = osmo_ss7_asp_peer_snprintf(buf, sizeof(buf), &xs->cfg.local); - if (rc < 0) { - LOGP(DLSS7, LOGL_INFO, "Failed parsing %s Server osmo_ss7_asp_peer\n", proto); - } else { - LOGP(DLSS7, LOGL_INFO, "(Re)binding %s Server to %s\n", - proto, buf); - } - return osmo_stream_srv_link_open(xs->server); -} - -int -osmo_ss7_xua_server_set_local_host(struct osmo_xua_server *xs, const char *local_host) -{ - return osmo_ss7_xua_server_set_local_hosts(xs, &local_host, 1); -} - -int -osmo_ss7_xua_server_set_local_hosts(struct osmo_xua_server *xs, const char **local_hosts, size_t local_host_cnt) -{ - int rc; - OSMO_ASSERT(ss7_initialized); - - rc = osmo_ss7_asp_peer_set_hosts(&xs->cfg.local, xs, local_hosts, local_host_cnt); - if (rc < 0) - return rc; - return osmo_stream_srv_link_set_addrs(xs->server, (const char **)xs->cfg.local.host, xs->cfg.local.host_cnt); -} - -int -osmo_ss7_xua_server_add_local_host(struct osmo_xua_server *xs, const char *local_host) -{ - int rc; - - rc = osmo_ss7_asp_peer_add_host(&xs->cfg.local, xs, local_host); - if (rc < 0) - return rc; - return osmo_stream_srv_link_set_addrs(xs->server, (const char **)xs->cfg.local.host, xs->cfg.local.host_cnt); -} - -bool ss7_xua_server_set_default_local_hosts(struct osmo_xua_server *oxs) -{ - /* If no local addr was set, or erased after _create(): */ - if (!oxs->cfg.local.host_cnt) { - /* "::" Covers both IPv4 and IPv6 */ - if (ipv6_sctp_supported("::", true)) - osmo_ss7_xua_server_set_local_host(oxs, "::"); - else - osmo_ss7_xua_server_set_local_host(oxs, "0.0.0.0"); - return true; - } - return false; -} - -void osmo_ss7_xua_server_destroy(struct osmo_xua_server *xs) -{ - struct osmo_ss7_asp *asp, *asp2; - - if (xs->server) { - osmo_stream_srv_link_close(xs->server); - osmo_stream_srv_link_destroy(xs->server); - } - /* iterate and close all connections established in relation - * with this server */ - llist_for_each_entry_safe(asp, asp2, &xs->asp_list, siblings) - osmo_ss7_asp_destroy(asp); - - llist_del(&xs->list); - talloc_free(xs); -} - bool osmo_ss7_pc_is_local(struct osmo_ss7_instance *inst, uint32_t pc) { OSMO_ASSERT(ss7_initialized); @@ -2694,62 +1309,3 @@ } return false; } - -static osmo_ss7_asp_rx_unknown_cb *g_osmo_ss7_asp_rx_unknown_cb; - -int ss7_asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg) -{ - rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_UNKNOWN); - - if (g_osmo_ss7_asp_rx_unknown_cb) - return (*g_osmo_ss7_asp_rx_unknown_cb)(asp, ppid_mux, msg); - - switch(asp->cfg.proto) { - case OSMO_SS7_ASP_PROT_IPA: - LOGPASP(asp, DLSS7, LOGL_NOTICE, "Rx IPA for unknown Stream ID 0x%02x: %s\n", - ppid_mux, msgb_hexdump(msg)); - break; - default: - LOGPASP(asp, DLSS7, LOGL_NOTICE, "Rx SCTP chunk for unknown PPID %u: %s\n", - ppid_mux, msgb_hexdump(msg)); - break; - } - return 0; -} - -/*! Get the logging subsystem for a given ASP. Used by generic code. */ -int osmo_ss7_asp_get_log_subsys(const struct osmo_ss7_asp *asp) -{ - switch (asp->cfg.proto) { - case OSMO_SS7_ASP_PROT_M3UA: - return DLM3UA; - case OSMO_SS7_ASP_PROT_SUA: - return DLSUA; - default: - return DLSS7; - } -} - -/*! \brief Get the name of a given ASP - * \param[in] asp The ASP for which the name is requested - * \returns The name of the ASP, or NULL if not set - */ -const char *osmo_ss7_asp_get_name(const struct osmo_ss7_asp *asp) -{ - return asp->cfg.name; -} - -/*! \brief Get the proto of a given ASP - * \param[in] asp The ASP for which the proto is requested - * \returns The proto of the ASP - */ -enum osmo_ss7_asp_protocol osmo_ss7_asp_get_proto(const struct osmo_ss7_asp *asp) -{ - return asp->cfg.proto; -} - -/*! Register a call-back function for unknown SCTP PPID / IPA Stream ID */ -void osmo_ss7_register_rx_unknown_cb(osmo_ss7_asp_rx_unknown_cb *cb) -{ - g_osmo_ss7_asp_rx_unknown_cb = cb; -} diff --git a/src/osmo_ss7_asp.c b/src/osmo_ss7_asp.c new file mode 100644 index 0000000..c282730 --- /dev/null +++ b/src/osmo_ss7_asp.c @@ -0,0 +1,1246 @@ +/* Core SS7 ASP Handling */ + +/* (C) 2015-2017 by Harald Welte laforge@gnumonks.org + * (C) 2023 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <inttypes.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/sctp.h> + +#include <osmocom/sigtran/osmo_ss7.h> +#include <osmocom/sigtran/mtp_sap.h> +#include <osmocom/sigtran/protocol/mtp.h> +#include <osmocom/sigtran/protocol/sua.h> +#include <osmocom/sigtran/protocol/m3ua.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> + +#include <osmocom/netif/stream.h> +#include <osmocom/netif/ipa.h> +#include <osmocom/netif/sctp.h> + +#include "sccp_internal.h" +#include "xua_internal.h" +#include "ss7_internal.h" +#include "xua_asp_fsm.h" +#include "xua_as_fsm.h" + +static int _setsockopt_peer_primary_addr(int fd, const struct osmo_sockaddr *saddr) +{ + int rc; + + struct sctp_setpeerprim so_sctp_setpeerprim = {0}; + + /* rfc6458 sec 8: "For the one-to-one style sockets and branched-off one-to-many + * style sockets (see Section 9.2), this association ID parameter is ignored" + */ + + /* NOTE: Requires setting: + * - sysctl net.sctp.addip_enable = 1, otherwise EPERM is returned + * - sysctl net.sctp.auth_enable = 1, RFC 5061 4.2.7 "An implementation supporting this + * extension MUST list the ASCONF,the ASCONF-ACK, and the AUTH chunks in + * its INIT and INIT-ACK parameters." + */ + + so_sctp_setpeerprim.sspp_addr = saddr->u.sas; + rc = setsockopt(fd, IPPROTO_SCTP, SCTP_SET_PEER_PRIMARY_ADDR, + &so_sctp_setpeerprim, sizeof(so_sctp_setpeerprim)); + if (rc < 0) { + char buf[128]; + int err = errno; + strerror_r(err, (char *)buf, sizeof(buf)); + LOGP(DLSS7, LOGL_ERROR, "setsockopt(SCTP_SET_PEER_PRIMARY_ADDR, %s) failed: %s%s\n", + osmo_sockaddr_to_str(saddr), buf, + err == EPERM ? " (EPERM: Make sure you have sysctl 'net.sctp.auth_enable' " + "and 'net.sctp.addip_enable' set to 1)" : ""); + } + return rc; +} + +static int _setsockopt_primary_addr(int fd, const struct osmo_sockaddr *saddr) +{ + int rc; + + struct sctp_prim so_sctp_prim = {0}; + + /* rfc6458 sec 8: "For the one-to-one style sockets and branched-off one-to-many + * style sockets (see Section 9.2), this association ID parameter is ignored" + */ + + so_sctp_prim.ssp_addr = saddr->u.sas; + rc = setsockopt(fd, IPPROTO_SCTP, SCTP_PRIMARY_ADDR, + &so_sctp_prim, sizeof(so_sctp_prim)); + if (rc < 0) { + char buf[128]; + strerror_r(errno, (char *)buf, sizeof(buf)); + LOGP(DLSS7, LOGL_ERROR, "setsockopt(SCTP_PRIMARY_ADDR, %s) failed: %s\n", + osmo_sockaddr_to_str(saddr), buf); + } + return rc; +} + +/*********************************************************************** + * SS7 Application Server Process + ***********************************************************************/ + +struct value_string osmo_ss7_asp_protocol_vals[] = { + { OSMO_SS7_ASP_PROT_NONE, "none" }, + { OSMO_SS7_ASP_PROT_SUA, "sua" }, + { OSMO_SS7_ASP_PROT_M3UA, "m3ua" }, + { OSMO_SS7_ASP_PROT_IPA, "ipa" }, + { 0, NULL } +}; + +const struct value_string osmo_ss7_asp_role_names[] = { + { OSMO_SS7_ASP_ROLE_ASP, "ASP" }, + { OSMO_SS7_ASP_ROLE_SG, "SG" }, + { OSMO_SS7_ASP_ROLE_IPSP, "IPSP" }, + { 0, NULL } +}; + +int ss7_asp_proto_to_ip_proto(enum osmo_ss7_asp_protocol proto) +{ + switch (proto) { + case OSMO_SS7_ASP_PROT_IPA: + return IPPROTO_TCP; + case OSMO_SS7_ASP_PROT_SUA: + case OSMO_SS7_ASP_PROT_M3UA: + default: + return IPPROTO_SCTP; + } +} + +static const uint16_t prot2port[] = { + [OSMO_SS7_ASP_PROT_NONE] = 0, + [OSMO_SS7_ASP_PROT_SUA] = SUA_PORT, + [OSMO_SS7_ASP_PROT_M3UA] = M3UA_PORT, + [OSMO_SS7_ASP_PROT_IPA] = 5000, +}; + +int osmo_ss7_asp_protocol_port(enum osmo_ss7_asp_protocol prot) +{ + if (prot >= ARRAY_SIZE(prot2port)) + return -EINVAL; + else + return prot2port[prot]; +} + +static const struct rate_ctr_desc ss7_asp_rcd[] = { + [SS7_ASP_CTR_PKT_RX_TOTAL] = { "rx:packets:total", "Total number of packets received" }, + [SS7_ASP_CTR_PKT_RX_UNKNOWN] = { "rx:packets:unknown", "Number of packets received for unknown PPID" }, + [SS7_ASP_CTR_PKT_TX_TOTAL] = { "tx:packets:total", "Total number of packets transmitted" }, +}; + +static const struct rate_ctr_group_desc ss7_asp_rcgd = { + .group_name_prefix = "sigtran_asp", + .group_description = "SIGTRAN Application Server Process", + .num_ctr = ARRAY_SIZE(ss7_asp_rcd), + .ctr_desc = ss7_asp_rcd, +}; +static unsigned int g_ss7_asp_rcg_idx; + +void osmo_ss7_asp_peer_init(struct osmo_ss7_asp_peer *peer) +{ + memset(peer, 0, sizeof(*peer)); + peer->idx_primary = -1; +} + +int osmo_ss7_asp_peer_snprintf(char *buf, size_t buf_len, struct osmo_ss7_asp_peer *peer) +{ + int len = 0, offset = 0, rem = buf_len; + int ret, i; + char *after; + char *primary; + + if (buf_len < 3) + return -EINVAL; + + if (peer->host_cnt > 1) { + ret = snprintf(buf, rem, "("); + if (ret < 0) + return ret; + OSMO_SNPRINTF_RET(ret, rem, offset, len); + } + for (i = 0; i < peer->host_cnt; i++) { + primary = (peer->idx_primary == i) ? "*" : ""; + if (peer->host_cnt == 1) + after = ""; + else + after = (i == (peer->host_cnt - 1)) ? ")" : "|"; + ret = snprintf(buf + offset, rem, "%s%s%s", peer->host[i] ? : "0.0.0.0", primary, after); + OSMO_SNPRINTF_RET(ret, rem, offset, len); + } + ret = snprintf(buf + offset, rem, ":%u", peer->port); + if (ret < 0) + return ret; + OSMO_SNPRINTF_RET(ret, rem, offset, len); + + return len; +} + +/*! \brief Set (copy) addresses for a given ASP peer. Previous addresses are freed. + * \param[in] peer Application Server Process peer whose addresses are to be set. + * \param[in] talloc_ctx talloc context used to allocate new addresses. + * \param[in] hosts Array of strings containing IP addresses. + * \param[in] host_cnt Number of strings in hosts + * \param[in] idx_primary Index in "hosts" array marking the SCTP Primary Address, -1 if no explicit Primary Address set + * \returns 0 on success; negative otherwise */ +int osmo_ss7_asp_peer_set_hosts2(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, const char *const*hosts, size_t host_cnt, int idx_primary) +{ + int i = 0; + + if (idx_primary >= (int)host_cnt || idx_primary < -1) + return -EINVAL; + + if (host_cnt > ARRAY_SIZE(peer->host)) + return -EINVAL; + + for (; i < host_cnt; i++) + osmo_talloc_replace_string(talloc_ctx, &peer->host[i], hosts[i]); + for (; i < peer->host_cnt; i++) { + talloc_free(peer->host[i]); + peer->host[i] = NULL; + } + + peer->host_cnt = host_cnt; + peer->idx_primary = idx_primary; + return 0; +} + +/*! \brief Set (copy) addresses for a given ASP peer. Previous addresses are freed. + * \param[in] peer Application Server Process peer whose addresses are to be set. + * \param[in] talloc_ctx talloc context used to allocate new addresses. + * \param[in] hosts Array of strings containing IP addresses. + * \param[in] host_cnt Number of strings in hosts + * \returns 0 on success; negative otherwise */ +int osmo_ss7_asp_peer_set_hosts(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, const char *const*hosts, size_t host_cnt) +{ + return osmo_ss7_asp_peer_set_hosts2(peer, talloc_ctx, hosts, host_cnt, -1); +} + +/* Is string formatted IPv4/v6 addr considered IN(6)ADDR_ANY? */ +static inline bool host_is_ip_anyaddr(const char *host, bool is_v6) +{ + /* NULL addr is resolved as 0.0.0.0 (IPv4) by getaddrinfo(), most + * probably for backward-compatibility reasons. + */ + return is_v6 ? (host && !strcmp(host, "::")) + : (!host || !strcmp(host, "0.0.0.0")); +} + +/*! \brief Append (copy) address to a given ASP peer. Previous addresses are kept. + * \param[in] peer Application Server Process peer the address is appended to. + * \param[in] talloc_ctx talloc context used to allocate new address. + * \param[in] host string containing an IP address. + * \param[in] is_primary_addr whether this IP address is to be added as SCTP Primary Address + * \returns 0 on success; negative otherwise */ +int osmo_ss7_asp_peer_add_host2(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, + const char *host, bool is_primary_addr) +{ + int i; + bool new_is_v6 = osmo_ip_str_type(host) == AF_INET6; + bool new_is_any = host_is_ip_anyaddr(host, new_is_v6); + struct osmo_sockaddr_str addr_str; + + if (osmo_sockaddr_str_from_str(&addr_str, host, 0) < 0) + return -EINVAL; + + if (new_is_any) { + /* Makes no sense to have INET(6)_ANY many times, or INET(6)_ANY + * together with specific addresses, be it of same or different + * IP version: */ + if (peer->host_cnt != 0) + return -EINVAL; + + /* Makes no sense to have INET(6)_ANY as primary: */ + if (is_primary_addr) + return -EINVAL; + + if (peer->host_cnt >= ARRAY_SIZE(peer->host)) + return -EINVAL; + osmo_talloc_replace_string(talloc_ctx, &peer->host[peer->host_cnt], host); + peer->host_cnt++; + return 0; + } + + /* Makes no sense to add specific address to set if INET(6)_ANY + * is already set, be it from same or different IP version: */ + for (i = 0; i < peer->host_cnt; i++) { + bool iter_is_v6 = osmo_ip_str_type(peer->host[i]) == AF_INET6; + if (host_is_ip_anyaddr(peer->host[i], iter_is_v6)) + return -EINVAL; + } + /* Reached this point, no INET(6)_ANY address is set nor we are adding an INET(6)_ANY address. */ + + /* Check if address already exists, and then if primary flags need to be changed: */ + for (i = 0; i < peer->host_cnt; i++) { + struct osmo_sockaddr_str it_addr_str; + bool it_is_primary; + osmo_sockaddr_str_from_str(&it_addr_str, peer->host[i], 0); + + if (osmo_sockaddr_str_cmp(&addr_str, &it_addr_str) != 0) + continue; + it_is_primary = (peer->idx_primary == i); + if (is_primary_addr == it_is_primary) { + /* Nothing to do, return below */ + } else if (is_primary_addr && !it_is_primary) { + /* Mark it as primary: */ + peer->idx_primary = i; + } else { /* if (!is_primary_addr && it_is_primary) { */ + /* mark it as non-primary: */ + peer->idx_primary = -1; + } + return 0; + } + + if (peer->host_cnt >= ARRAY_SIZE(peer->host)) + return -EINVAL; + + osmo_talloc_replace_string(talloc_ctx, &peer->host[peer->host_cnt], host); + if (is_primary_addr) + peer->idx_primary = peer->host_cnt; + peer->host_cnt++; + return 0; +} + +/*! \brief Append (copy) address to a given ASP peer. Previous addresses are kept. + * \param[in] peer Application Server Process peer the address is appended to. + * \param[in] talloc_ctx talloc context used to allocate new address. + * \param[in] host string containing an IP address. + * \returns 0 on success; negative otherwise */ +int osmo_ss7_asp_peer_add_host(struct osmo_ss7_asp_peer *peer, void *talloc_ctx, + const char *host) +{ + return osmo_ss7_asp_peer_add_host2(peer, talloc_ctx, host, false); +} + +int ss7_asp_apply_peer_primary_address(const struct osmo_ss7_asp *asp) +{ + struct osmo_fd *ofd; + struct osmo_sockaddr_str addr_str; + struct osmo_sockaddr addr; + uint16_t local_port; + int rc; + + /* No SCTP Peer Primary Address explicitly configured, do nothing. */ + if (asp->cfg.local.idx_primary == -1) + return 0; + OSMO_ASSERT(asp->cfg.local.idx_primary < asp->cfg.local.host_cnt); + + if (asp->cfg.is_server) + ofd = osmo_stream_srv_get_ofd(asp->server); + else + ofd = osmo_stream_cli_get_ofd(asp->client); + + if (asp->cfg.local.port == 0) { + char port_buf[16]; + osmo_sock_get_local_ip_port(ofd->fd, port_buf, sizeof(port_buf)); + local_port = atoi(port_buf); + } else { + local_port = asp->cfg.local.port; + } + rc = osmo_sockaddr_str_from_str(&addr_str, + asp->cfg.local.host[asp->cfg.local.idx_primary], + local_port); + if (rc < 0) + return rc; + rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas); + if (rc < 0) + return rc; + LOGPASP(asp, DLSS7, LOGL_INFO, "Set Peer's Primary Address %s\n", + osmo_sockaddr_to_str(&addr)); + rc = _setsockopt_peer_primary_addr(ofd->fd, &addr); + + return rc; +} + +int ss7_asp_apply_primary_address(const struct osmo_ss7_asp *asp) +{ + struct osmo_fd *ofd; + struct osmo_sockaddr_str addr_str; + struct osmo_sockaddr addr; + int rc; + + /* No SCTP Primary Address explicitly configured, do nothing. */ + if (asp->cfg.remote.idx_primary == -1) + return 0; + OSMO_ASSERT(asp->cfg.remote.idx_primary < asp->cfg.remote.host_cnt); + + if (asp->cfg.is_server) + ofd = osmo_stream_srv_get_ofd(asp->server); + else + ofd = osmo_stream_cli_get_ofd(asp->client); + + rc = osmo_sockaddr_str_from_str(&addr_str, + asp->cfg.remote.host[asp->cfg.remote.idx_primary], + asp->cfg.remote.port); + if (rc < 0) + return rc; + rc = osmo_sockaddr_str_to_sockaddr(&addr_str, &addr.u.sas); + if (rc < 0) + return rc; + LOGPASP(asp, DLSS7, LOGL_INFO, "Set Primary Address %s\n", + osmo_sockaddr_to_str(&addr)); + rc = _setsockopt_primary_addr(ofd->fd, &addr); + return rc; +} + +/* Returns whether the address signalled in the SCTP_PEER_ADDR_CHANGE matches + * the user-configured Primary Address. */ +static bool sctp_peer_addr_change_ev_addr_matches_our_primary(const struct osmo_ss7_asp *asp, + const union sctp_notification *notif) +{ + const char *primary_str; + int primary_str_type; + struct osmo_sockaddr ev_addr, primary; + + OSMO_ASSERT(asp->cfg.remote.idx_primary >= 0); + + primary_str = asp->cfg.remote.host[asp->cfg.remote.idx_primary]; + primary_str_type = osmo_ip_str_type(primary_str); + memcpy(&ev_addr.u.sas, ¬if->sn_paddr_change.spc_aaddr, sizeof(ev_addr.u.sas)); + + /* This whole switch is to properly compare addresses (take into account v6mapped IPv4 addresses): */ + switch (ev_addr.u.sa.sa_family) { + case AF_INET: + switch (primary_str_type) { + case AF_INET: + primary = ev_addr; /* Copy AF + port */ + inet_pton(AF_INET, primary_str, &primary.u.sin.sin_addr); + return (osmo_sockaddr_cmp(&primary, &ev_addr) == 0); + case AF_INET6: + /* for sure not the same */ + return false; + } + return false; + case AF_INET6: + /* "ev_addr" can either be a IPv6 addr or a v6-mapped IPv4 + * address. Compare both as IPv6 (or v6-mapped IPv4) addresses. */ + primary = ev_addr; /* Copy AF + port */ + inet_pton(AF_INET6, primary_str, &primary.u.sin6.sin6_addr); + return (osmo_sockaddr_cmp(&primary, &ev_addr) == 0); + default: + return false; + } +} + +/* Simple SCTP Path-manager tracking/driving the VTY-user-configured primary + * address against the kernel when assoc state changes: */ +static void asp_handle_sctp_notif_monitor_primary_address(const struct osmo_ss7_asp *asp, + const union sctp_notification *notif) +{ + bool match; + + if (asp->cfg.remote.idx_primary == -1) + return; + if (notif->sn_header.sn_type != SCTP_PEER_ADDR_CHANGE) + return; + + switch (notif->sn_paddr_change.spc_state) { + case SCTP_ADDR_AVAILABLE: + case SCTP_ADDR_ADDED: + case SCTP_ADDR_CONFIRMED: + /* If our primary addr became available/added/confirmed, set it */ + match = sctp_peer_addr_change_ev_addr_matches_our_primary(asp, notif); + if (match) + ss7_asp_apply_primary_address(asp); + break; + case SCTP_ADDR_MADE_PRIM: + /* If another primary addr was made primary, overwrite it by setting it again */ + match = sctp_peer_addr_change_ev_addr_matches_our_primary(asp, notif); + if (!match) + ss7_asp_apply_primary_address(asp); + default: + break; + } +} + +/* Set default values for local and remote peer hosts if they are not yet set. + * \param[in] asp ASP for which to set default hosts. + * \returns true if values where changed, false otherwise. + * + * If the ASP is already started, osmo_ss7_asp_restart() must be called + * afterwards in order to apply the new settings. + * This API is internal, hence doesn't appear in osmo_ss7.h + */ +bool ss7_asp_set_default_peer_hosts(struct osmo_ss7_asp *asp) +{ + bool changed = false; + /* If no local addr was set */ + if (!asp->cfg.local.host_cnt) { + bool rem_has_v4 = false, rem_has_v6 = false; + int i; + for (i = 0; i < asp->cfg.remote.host_cnt; i++) { + if (osmo_ip_str_type(asp->cfg.remote.host[i]) == AF_INET6) + rem_has_v6 = true; + else + rem_has_v4 = true; + } + /* "::" Covers both IPv4 and IPv6, but if only IPv4 + * address are set on the remote side, IPv4 on the local + * side must be set too */ + if (ss7_ipv6_sctp_supported("::", true) && !(rem_has_v4 && !rem_has_v6)) + osmo_ss7_asp_peer_add_host(&asp->cfg.local, asp, "::"); + else + osmo_ss7_asp_peer_add_host(&asp->cfg.local, asp, "0.0.0.0"); + changed = true; + } + /* If no remote addr was set */ + if (!asp->cfg.remote.host_cnt) { + osmo_ss7_asp_peer_add_host(&asp->cfg.remote, asp, "127.0.0.1"); + if (ss7_ipv6_sctp_supported("::1", false)) + osmo_ss7_asp_peer_add_host(&asp->cfg.remote, asp, "::1"); + changed = true; + } + return changed; +} + +static uint16_t get_in_port(struct sockaddr *sa) +{ + switch (sa->sa_family) { + case AF_INET: + return (((struct sockaddr_in *)sa)->sin_port); + case AF_INET6: + return (((struct sockaddr_in6 *)sa)->sin6_port); + default: + return 0; + } +} + +/* Converts string representation of v4-mappend-on-v6 IP addr to a pure IPv4 address. + * Example: ::ffff:172.18.19.200 => 172.18.19.200 + */ +static void chop_v4_mapped_on_v6_prefix(char *buf) +{ + char *last_colon; + size_t len; + char *first_dot = strchr(buf, '.'); + + if (!first_dot) + return; /* Not an IPv4-mappend-on-v6 string representation, nothing to do */ + last_colon = strrchr(buf, ':'); + if (!last_colon) + return; /* pure IPv4 address, nothing to do */ + + len = strlen(last_colon + 1); + memmove(buf, last_colon + 1, len); + buf[len] = '\0'; +} + +/*! \brief Find an ASP definition matching the local+remote IP/PORT of given fd + * \param[in] fd socket descriptor of given socket + * \returns SS7 ASP in case a matching one is found; NULL otherwise */ +struct osmo_ss7_asp * +ss7_asp_find_by_socket_addr(int fd) +{ + struct osmo_ss7_instance *inst; + struct sockaddr_storage sa_l, sa_r; + socklen_t sa_len_l = sizeof(sa_l); + socklen_t sa_len_r = sizeof(sa_r); + char hostbuf_l[64], hostbuf_r[64]; + uint16_t local_port, remote_port; + bool loc_is_v6, rem_is_v6; + int rc; + int i; + + OSMO_ASSERT(ss7_initialized); + /* convert local and remote IP to string */ + rc = getsockname(fd, (struct sockaddr *)&sa_l, &sa_len_l); + if (rc < 0) + return NULL; + rc = getnameinfo((struct sockaddr *)&sa_l, sa_len_l, + hostbuf_l, sizeof(hostbuf_l), + NULL, 0, NI_NUMERICHOST); + if (rc < 0) + return NULL; + local_port = ntohs(get_in_port((struct sockaddr *)&sa_l)); + + rc = getpeername(fd, (struct sockaddr *)&sa_r, &sa_len_r); + if (rc < 0) + return NULL; + rc = getnameinfo((struct sockaddr *)&sa_r, sa_len_r, + hostbuf_r, sizeof(hostbuf_r), + NULL, 0, NI_NUMERICHOST); + if (rc < 0) + return NULL; + remote_port = ntohs(get_in_port((struct sockaddr *)&sa_r)); + + /* If multi-home is used with both IPv4 and IPv6, then the socket is + * AF_INET6, and then returned IPv4 addresses are actually v6mapped ones. + * We need to convert them to IPv4 before matching. + */ + chop_v4_mapped_on_v6_prefix(hostbuf_l); + chop_v4_mapped_on_v6_prefix(hostbuf_r); + loc_is_v6 = osmo_ip_str_type(hostbuf_l) == AF_INET6; + rem_is_v6 = osmo_ip_str_type(hostbuf_r) == AF_INET6; + + /* check all instances for any ASP definition matching the + * address combination of local/remote ip/port */ + llist_for_each_entry(inst, &osmo_ss7_instances, list) { + struct osmo_ss7_asp *asp; + llist_for_each_entry(asp, &inst->asp_list, list) { + if (asp->cfg.local.port != local_port) + continue; + if (asp->cfg.remote.port && asp->cfg.remote.port != remote_port) + continue; + + for (i = 0; i < asp->cfg.local.host_cnt; i++) { + bool iter_is_v6 = osmo_ip_str_type(asp->cfg.local.host[i]) == AF_INET6; + bool iter_is_anyaddr = host_is_ip_anyaddr(asp->cfg.local.host[i], iter_is_v6); + /* "::" (v6) covers "0.0.0.0" (v4), but not otherwise */ + if (iter_is_v6 != loc_is_v6 && + !(iter_is_v6 && iter_is_anyaddr)) + continue; + if (iter_is_anyaddr || + !strcmp(asp->cfg.local.host[i], hostbuf_l)) + break; + } + if (i == asp->cfg.local.host_cnt) + continue; /* didn't match any local.host */ + + /* If no remote host was set, it's probably a server and hence we match any cli src */ + if (asp->cfg.remote.host_cnt) { + for (i = 0; i < asp->cfg.remote.host_cnt; i++) { + bool iter_is_v6 = osmo_ip_str_type(asp->cfg.remote.host[i]) == AF_INET6; + bool iter_is_anyaddr = host_is_ip_anyaddr(asp->cfg.remote.host[i], iter_is_v6); + /* "::" (v6) covers "0.0.0.0" (v4), but not otherwise */ + if (iter_is_v6 != rem_is_v6 && + !(iter_is_v6 && iter_is_anyaddr)) + continue; + if (iter_is_anyaddr || + !strcmp(asp->cfg.remote.host[i], hostbuf_r)) + break; + } + if (i == asp->cfg.remote.host_cnt) + continue; /* didn't match any remote.host */ + } + + return asp; + } + } + + return NULL; +} + +struct osmo_ss7_asp *ss7_asp_alloc(struct osmo_ss7_instance *inst, const char *name, + uint16_t remote_port, uint16_t local_port, + enum osmo_ss7_asp_protocol proto) +{ + struct osmo_ss7_asp *asp = talloc_zero(inst, struct osmo_ss7_asp); + asp->ctrg = rate_ctr_group_alloc(asp, &ss7_asp_rcgd, g_ss7_asp_rcg_idx++); + if (!asp->ctrg) { + talloc_free(asp); + return NULL; + } + rate_ctr_group_set_name(asp->ctrg, name); + asp->inst = inst; + osmo_ss7_asp_peer_init(&asp->cfg.remote); + asp->cfg.remote.port = remote_port; + osmo_ss7_asp_peer_init(&asp->cfg.local); + asp->cfg.local.port = local_port; + asp->cfg.proto = proto; + asp->cfg.name = talloc_strdup(asp, name); + + asp->cfg.T_defs_lm = talloc_memdup(asp, ss7_asp_lm_timer_defaults, + sizeof(ss7_asp_lm_timer_defaults)); + osmo_tdefs_reset(asp->cfg.T_defs_lm); + + llist_add_tail(&asp->list, &inst->asp_list); + + /* The SUA code internally needs SCCP to work */ + if (proto == OSMO_SS7_ASP_PROT_SUA) + osmo_ss7_ensure_sccp(inst); + return asp; +} + +void osmo_ss7_asp_destroy(struct osmo_ss7_asp *asp) +{ + struct osmo_ss7_as *as; + + OSMO_ASSERT(ss7_initialized); + LOGPASP(asp, DLSS7, LOGL_INFO, "Destroying ASP\n"); + + if (asp->server) + osmo_stream_srv_destroy(asp->server); + if (asp->client) + osmo_stream_cli_destroy(asp->client); + if (asp->fi) + osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); + if (asp->xua_server) + llist_del(&asp->siblings); + + /* unlink from all ASs we are part of */ + llist_for_each_entry(as, &asp->inst->as_list, list) { + unsigned int i; + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (as->cfg.asps[i] == asp) + as->cfg.asps[i] = NULL; + } + } + /* unlink from ss7_instance */ + asp->inst = NULL; + llist_del(&asp->list); + rate_ctr_group_free(asp->ctrg); + /* release memory */ + talloc_free(asp); +} + +static int xua_cli_read_cb(struct osmo_stream_cli *conn); +static int ipa_cli_read_cb(struct osmo_stream_cli *conn); +static int xua_cli_connect_cb(struct osmo_stream_cli *cli); + +int osmo_ss7_asp_restart(struct osmo_ss7_asp *asp) +{ + int rc; + char bufloc[512], bufrem[512]; + + OSMO_ASSERT(ss7_initialized); + osmo_ss7_asp_peer_snprintf(bufloc, sizeof(bufloc), &asp->cfg.local); + osmo_ss7_asp_peer_snprintf(bufrem, sizeof(bufrem), &asp->cfg.remote); + LOGPASP(asp, DLSS7, LOGL_INFO, "Restarting ASP %s, r=%s<->l=%s\n", + asp->cfg.name, bufrem, bufloc); + + if (!asp->cfg.is_server) { + /* We are in client mode now */ + if (asp->server) { + /* if we previously were in server mode, + * destroy it */ + osmo_stream_srv_destroy(asp->server); + asp->server = NULL; + } + if (!asp->client) + asp->client = osmo_stream_cli_create(asp); + if (!asp->client) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Unable to create stream" + " client for ASP %s\n", asp->cfg.name); + return -1; + } + osmo_stream_cli_set_name(asp->client, asp->cfg.name); + osmo_stream_cli_set_nodelay(asp->client, true); + osmo_stream_cli_set_addrs(asp->client, (const char **)asp->cfg.remote.host, asp->cfg.remote.host_cnt); + osmo_stream_cli_set_port(asp->client, asp->cfg.remote.port); + osmo_stream_cli_set_local_addrs(asp->client, (const char **)asp->cfg.local.host, asp->cfg.local.host_cnt); + osmo_stream_cli_set_local_port(asp->client, asp->cfg.local.port); + osmo_stream_cli_set_proto(asp->client, ss7_asp_proto_to_ip_proto(asp->cfg.proto)); + osmo_stream_cli_set_reconnect_timeout(asp->client, 5); + osmo_stream_cli_set_connect_cb(asp->client, xua_cli_connect_cb); + if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA) + osmo_stream_cli_set_read_cb(asp->client, ipa_cli_read_cb); + else + osmo_stream_cli_set_read_cb(asp->client, xua_cli_read_cb); + osmo_stream_cli_set_data(asp->client, asp); + rc = osmo_stream_cli_open(asp->client); + if (rc < 0) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Unable to open stream" + " client for ASP %s, %s ==> %s\n", asp->cfg.name, bufloc, bufrem); + /* we don't return error in here because osmo_stream_cli_open() + will continue to retry (due to timeout being explicitly set with + osmo_stream_cli_set_reconnect_timeout() above) to connect so the error is transient */ + } + } else { + /* We are in server mode now */ + if (asp->client) { + /* if we previously were in client mode, + * destroy it */ + osmo_stream_cli_destroy(asp->client); + asp->client = NULL; + } + /* FIXME: ensure we have a SCTP server */ + LOGPASP(asp, DLSS7, LOGL_NOTICE, "ASP Restart for server " + "not implemented yet!\n"); + } + + /* (re)start the ASP FSM */ + if (asp->fi) + osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); + asp->fi = xua_asp_fsm_start(asp, asp->cfg.role, LOGL_DEBUG); + + return 0; +} + +bool osmo_ss7_asp_active(const struct osmo_ss7_asp *asp) +{ + if (!asp->fi) + return false; + return asp->fi->state == XUA_ASP_S_ACTIVE; +} + +/*********************************************************************** + * libosmo-netif integration for SCTP stream server/client + ***********************************************************************/ + +static int get_logevel_by_sn_type(int sn_type) +{ + switch (sn_type) { + case SCTP_ADAPTATION_INDICATION: + case SCTP_PEER_ADDR_CHANGE: +#ifdef SCTP_AUTHENTICATION_INDICATION + case SCTP_AUTHENTICATION_INDICATION: +#endif +#ifdef SCTP_SENDER_DRY_EVENT + case SCTP_SENDER_DRY_EVENT: +#endif + return LOGL_INFO; + case SCTP_ASSOC_CHANGE: + return LOGL_NOTICE; + case SCTP_SHUTDOWN_EVENT: + case SCTP_PARTIAL_DELIVERY_EVENT: + return LOGL_NOTICE; + case SCTP_SEND_FAILED: + case SCTP_REMOTE_ERROR: + return LOGL_ERROR; + default: + return LOGL_NOTICE; + } +} + +static void log_sctp_notification(struct osmo_ss7_asp *asp, const char *pfx, + union sctp_notification *notif) +{ + int log_level; + + LOGPASP(asp, DLSS7, LOGL_INFO, "%s SCTP NOTIFICATION %u flags=0x%0x\n", + pfx, notif->sn_header.sn_type, + notif->sn_header.sn_flags); + + log_level = get_logevel_by_sn_type(notif->sn_header.sn_type); + + switch (notif->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + LOGPASP(asp, DLSS7, log_level, "%s SCTP_ASSOC_CHANGE: %s\n", + pfx, osmo_sctp_assoc_chg_str(notif->sn_assoc_change.sac_state)); + break; + case SCTP_PEER_ADDR_CHANGE: + { + char addr_str[INET6_ADDRSTRLEN + 10]; + struct sockaddr_storage sa = notif->sn_paddr_change.spc_aaddr; + osmo_sockaddr_to_str_buf(addr_str, sizeof(addr_str), (const struct osmo_sockaddr *)&sa); + LOGPASP(asp, DLSS7, log_level, "%s SCTP_PEER_ADDR_CHANGE: %s %s err=%s\n", + pfx, osmo_sctp_paddr_chg_str(notif->sn_paddr_change.spc_state), addr_str, + (notif->sn_paddr_change.spc_state == SCTP_ADDR_UNREACHABLE) ? + osmo_sctp_sn_error_str(notif->sn_paddr_change.spc_error) : "None"); + } + break; + default: + LOGPASP(asp, DLSS7, log_level, "%s %s\n", + pfx, osmo_sctp_sn_type_str(notif->sn_header.sn_type)); + break; + } +} + +/* netif code tells us we can read something from the socket */ +int ss7_asp_ipa_srv_conn_cb(struct osmo_stream_srv *conn) +{ + struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); + struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); + struct msgb *msg = NULL; + int rc; + + /* read IPA message from socket and process it */ + rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", + __func__, rc); + if (rc <= 0) { + if (rc == -EAGAIN) { + /* more data needed */ + return 0; + } + osmo_stream_srv_destroy(conn); + return rc; + } + if (osmo_ipa_process_msg(msg) < 0) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); + osmo_stream_srv_destroy(conn); + msgb_free(msg); + return -1; + } + msg->dst = asp; + + return ipa_rx_msg(asp, msg, ofd->fd & 0xf); +} + +/* netif code tells us we can read something from the socket */ +int ss7_asp_xua_srv_conn_cb(struct osmo_stream_srv *conn) +{ + struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); + struct msgb *msg = m3ua_msgb_alloc("xUA Server Rx"); + unsigned int ppid; + int flags; + int rc; + + if (!msg) + return -ENOMEM; + + /* read xUA message from socket and process it */ + rc = osmo_stream_srv_recv(conn, msg); + flags = msgb_sctp_msg_flags(msg); + + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", + __func__, rc, flags); + + if (flags & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) { + union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); + log_sctp_notification(asp, "xUA CLNT", notif); + asp_handle_sctp_notif_monitor_primary_address(asp, notif); + + switch (notif->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + if (notif->sn_assoc_change.sac_state == SCTP_RESTART) + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, + PRIM_OP_INDICATION); + default: + break; + } + + if (rc == 0) { + osmo_stream_srv_destroy(conn); + rc = -EBADF; + } else { + rc = 0; + } + goto out; + } + if (rc < 0) { + osmo_stream_srv_destroy(conn); + rc = -EBADF; + goto out; + } else if (rc == 0) { + osmo_stream_srv_destroy(conn); + rc = -EBADF; + goto out; + } + + ppid = msgb_sctp_ppid(msg); + msg->dst = asp; + rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); + + if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) + rc = sua_rx_msg(asp, msg); + else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) + rc = m3ua_rx_msg(asp, msg); + else + rc = ss7_asp_rx_unknown(asp, ppid, msg); + +out: + msgb_free(msg); + return rc; +} + +/* client has established SCTP connection to server */ +static int xua_cli_connect_cb(struct osmo_stream_cli *cli) +{ + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); + int rc = 0; + + /* update the socket name */ + talloc_free(asp->sock_name); + asp->sock_name = osmo_sock_get_name(asp, ofd->fd); + + LOGPASP(asp, DLSS7, LOGL_INFO, "Client connected %s\n", asp->sock_name); + + /* Now that we have the conn in place, the local/remote addresses are + * fed and the local port is known for sure. Apply SCTP Primary addresses + * if needed: + */ + if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA) { + rc = ss7_asp_apply_peer_primary_address(asp); + rc = ss7_asp_apply_primary_address(asp); + } + + if (asp->lm && asp->lm->prim_cb) { + /* Notify layer manager that a connection has been + * established */ + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); + } else { + /* directly as the ASP FSM to start by sending an ASP-UP ... */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); + } + + return rc; +} + +static void xua_cli_close(struct osmo_stream_cli *cli) +{ + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); + + osmo_stream_cli_close(cli); + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, asp); + /* send M-SCTP_RELEASE.ind to XUA Layer Manager */ + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); +} + +static void xua_cli_close_and_reconnect(struct osmo_stream_cli *cli) +{ + xua_cli_close(cli); + osmo_stream_cli_reconnect(cli); +} + +/* read call-back for IPA/SCCPlite socket */ +static int ipa_cli_read_cb(struct osmo_stream_cli *conn) +{ + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn); + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); + struct msgb *msg = NULL; + int rc; + + /* read IPA message from socket and process it */ + rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", + __func__, rc); + if (rc <= 0) { + if (rc == -EAGAIN) { + /* more data needed */ + return 0; + } + xua_cli_close_and_reconnect(conn); + return rc; + } + if (osmo_ipa_process_msg(msg) < 0) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); + xua_cli_close_and_reconnect(conn); + msgb_free(msg); + return -1; + } + msg->dst = asp; + rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); + return ipa_rx_msg(asp, msg, ofd->fd & 0xf); +} + +static int xua_cli_read_cb(struct osmo_stream_cli *conn) +{ + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); + struct msgb *msg = m3ua_msgb_alloc("xUA Client Rx"); + unsigned int ppid; + int flags; + int rc; + + if (!msg) + return -ENOMEM; + + /* read xUA message from socket and process it */ + rc = osmo_stream_cli_recv(conn, msg); + flags = msgb_sctp_msg_flags(msg); + + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", + __func__, rc, flags); + + if (flags & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) { + union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); + log_sctp_notification(asp, "xUA CLNT", notif); + asp_handle_sctp_notif_monitor_primary_address(asp, notif); + + switch (notif->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + if (notif->sn_assoc_change.sac_state == SCTP_RESTART) + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, + PRIM_OP_INDICATION); + default: + break; + } + + if (rc == 0) + xua_cli_close_and_reconnect(conn); + rc = 0; + goto out; + } + if (rc < 0) { + xua_cli_close_and_reconnect(conn); + goto out; + } else if (rc == 0) { + xua_cli_close_and_reconnect(conn); + goto out; + } + + ppid = msgb_sctp_ppid(msg); + msg->dst = asp; + rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); + + if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) + rc = sua_rx_msg(asp, msg); + else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) + rc = m3ua_rx_msg(asp, msg); + else + rc = ss7_asp_rx_unknown(asp, ppid, msg); + +out: + msgb_free(msg); + return rc; +} + +int ss7_asp_xua_srv_conn_closed_cb(struct osmo_stream_srv *srv) +{ + struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(srv); + + LOGP(DLSS7, LOGL_INFO, "%s: connection closed\n", asp ? asp->cfg.name : "?"); + + if (!asp) + return 0; + + /* notify ASP FSM and everyone else */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, NULL); + + /* delete any RKM-dynamically allocated ASs for this ASP */ + xua_rkm_cleanup_dyn_as_for_asp(asp); + + /* send M-SCTP_RELEASE.ind to Layer Manager */ + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); + + asp->server = NULL; + + /* if we were dynamically allocated at accept_cb() time, let's + * self-destruct now. A new connection will re-create the ASP. */ + if (asp->dyn_allocated) { + /* avoid re-entrance via osmo_stream_srv_destroy() which + * called us */ + osmo_ss7_asp_destroy(asp); + } + + return 0; +} + +/*! \brief send a fully encoded msgb via a given ASP + * \param[in] asp Application Server Process through which to send + * \param[in] msg message buffer to transmit. Ownership transferred. + * \returns 0 on success; negative in case of error */ +int osmo_ss7_asp_send(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + OSMO_ASSERT(ss7_initialized); + + switch (asp->cfg.proto) { + case OSMO_SS7_ASP_PROT_SUA: + msgb_sctp_ppid(msg) = SUA_PPID; + break; + case OSMO_SS7_ASP_PROT_M3UA: + msgb_sctp_ppid(msg) = M3UA_PPID; + break; + case OSMO_SS7_ASP_PROT_IPA: + break; + default: + OSMO_ASSERT(0); + } + + rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_TX_TOTAL); + + if (asp->cfg.is_server) { + if (!asp->server) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->server\n"); + /* FIXME: what to do here? delete the route? send DUNA? */ + msgb_free(msg); + return -EIO; + } + osmo_stream_srv_send(asp->server, msg); + } else { + if (!asp->client) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->client\n"); + /* FIXME: what to do here? delete the route? send DUNA? */ + msgb_free(msg); + return -EIO; + } + if (!osmo_stream_cli_is_connected(asp->client)) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, asp->client not connected\n"); + msgb_free(msg); + return -EIO; + } + osmo_stream_cli_send(asp->client, msg); + } + + return 0; +} + +void osmo_ss7_asp_disconnect(struct osmo_ss7_asp *asp) +{ + if (asp->server) + osmo_stream_srv_destroy(asp->server); + /* the close_cb() will handle the remaining cleanup here */ + else if (asp->client) + xua_cli_close_and_reconnect(asp->client); +} + +static osmo_ss7_asp_rx_unknown_cb *g_osmo_ss7_asp_rx_unknown_cb; + +/*! Register a call-back function for unknown SCTP PPID / IPA Stream ID */ +void osmo_ss7_register_rx_unknown_cb(osmo_ss7_asp_rx_unknown_cb *cb) +{ + g_osmo_ss7_asp_rx_unknown_cb = cb; +} + +int ss7_asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg) +{ + rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_UNKNOWN); + + if (g_osmo_ss7_asp_rx_unknown_cb) + return (*g_osmo_ss7_asp_rx_unknown_cb)(asp, ppid_mux, msg); + + switch (asp->cfg.proto) { + case OSMO_SS7_ASP_PROT_IPA: + LOGPASP(asp, DLSS7, LOGL_NOTICE, "Rx IPA for unknown Stream ID 0x%02x: %s\n", + ppid_mux, msgb_hexdump(msg)); + break; + default: + LOGPASP(asp, DLSS7, LOGL_NOTICE, "Rx SCTP chunk for unknown PPID %u: %s\n", + ppid_mux, msgb_hexdump(msg)); + break; + } + return 0; +} + +/*! Get the logging subsystem for a given ASP. Used by generic code. */ +int osmo_ss7_asp_get_log_subsys(const struct osmo_ss7_asp *asp) +{ + switch (asp->cfg.proto) { + case OSMO_SS7_ASP_PROT_M3UA: + return DLM3UA; + case OSMO_SS7_ASP_PROT_SUA: + return DLSUA; + default: + return DLSS7; + } +} + +/*! \brief Get the name of a given ASP + * \param[in] asp The ASP for which the name is requested + * \returns The name of the ASP, or NULL if not set + */ +const char *osmo_ss7_asp_get_name(const struct osmo_ss7_asp *asp) +{ + return asp->cfg.name; +} + +/*! \brief Get the proto of a given ASP + * \param[in] asp The ASP for which the proto is requested + * \returns The proto of the ASP + */ +enum osmo_ss7_asp_protocol osmo_ss7_asp_get_proto(const struct osmo_ss7_asp *asp) +{ + return asp->cfg.proto; +} diff --git a/src/osmo_ss7_xua_srv.c b/src/osmo_ss7_xua_srv.c new file mode 100644 index 0000000..04ae893 --- /dev/null +++ b/src/osmo_ss7_xua_srv.c @@ -0,0 +1,300 @@ +/* Core SS7 xUA Server */ + +/* (C) 2015-2017 by Harald Welte laforge@gnumonks.org + * (C) 2023 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <inttypes.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/sctp.h> + +#include <osmocom/sigtran/osmo_ss7.h> +#include <osmocom/sigtran/mtp_sap.h> +#include <osmocom/sigtran/protocol/mtp.h> +#include <osmocom/sigtran/protocol/sua.h> +#include <osmocom/sigtran/protocol/m3ua.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> + +#include <osmocom/netif/stream.h> +#include <osmocom/netif/ipa.h> +#include <osmocom/netif/sctp.h> + +#include "sccp_internal.h" +#include "xua_internal.h" +#include "ss7_internal.h" +#include "xua_asp_fsm.h" +#include "xua_as_fsm.h" + +/*********************************************************************** + * SS7 xUA Server + ***********************************************************************/ + +/* server has accept()ed a new SCTP association, let's find the ASP for + * it (if any) */ +static int xua_accept_cb(struct osmo_stream_srv_link *link, int fd) +{ + struct osmo_xua_server *oxs = osmo_stream_srv_link_get_data(link); + struct osmo_stream_srv *srv; + struct osmo_ss7_asp *asp; + char *sock_name = osmo_sock_get_name(link, fd); + const char *proto_name = get_value_string(osmo_ss7_asp_protocol_vals, oxs->cfg.proto); + int rc = 0; + + LOGP(DLSS7, LOGL_INFO, "%s: New %s connection accepted\n", sock_name, proto_name); + + if (oxs->cfg.proto == OSMO_SS7_ASP_PROT_IPA) { + srv = osmo_stream_srv_create(oxs, link, fd, + ss7_asp_ipa_srv_conn_cb, + ss7_asp_xua_srv_conn_closed_cb, NULL); + } else { + srv = osmo_stream_srv_create(oxs, link, fd, + ss7_asp_xua_srv_conn_cb, + ss7_asp_xua_srv_conn_closed_cb, NULL); + } + if (!srv) { + LOGP(DLSS7, LOGL_ERROR, "%s: Unable to create stream server " + "for connection\n", sock_name); + close(fd); + talloc_free(sock_name); + return -1; + } + + asp = ss7_asp_find_by_socket_addr(fd); + if (asp) { + LOGP(DLSS7, LOGL_INFO, "%s: matched connection to ASP %s\n", + sock_name, asp->cfg.name); + /* we need to check if we already have a socket associated, and close it. Otherwise it might + * happen that both the listen-fd for this accept() and the old socket are marked 'readable' + * during the same scheduling interval, and we're processing them in the "wrong" order, i.e. + * we first see the accept of the new fd before we see the close on the old fd */ + if (asp->server) { + LOGPASP(asp, DLSS7, LOGL_FATAL, "accept of new connection from %s before old was closed " + "-> close old one\n", sock_name); + osmo_stream_srv_set_data(asp->server, NULL); + osmo_stream_srv_destroy(asp->server); + asp->server = NULL; + } + } else { + if (!oxs->cfg.accept_dyn_reg) { + LOGP(DLSS7, LOGL_NOTICE, "%s: %s connection without matching " + "ASP definition and no dynamic registration enabled, terminating\n", + sock_name, proto_name); + } else { + char namebuf[32]; + static uint32_t dyn_asp_num = 0; + snprintf(namebuf, sizeof(namebuf), "asp-dyn-%u", dyn_asp_num++); + asp = osmo_ss7_asp_find_or_create(oxs->inst, namebuf, 0, 0, + oxs->cfg.proto); + if (asp) { + char hostbuf[INET6_ADDRSTRLEN]; + const char *hostbuf_ptr = &hostbuf[0]; + char portbuf[16]; + + osmo_sock_get_ip_and_port(fd, hostbuf, sizeof(hostbuf), portbuf, sizeof(portbuf), false); + LOGP(DLSS7, LOGL_INFO, "%s: created dynamic ASP %s\n", + sock_name, asp->cfg.name); + asp->cfg.is_server = true; + asp->cfg.role = OSMO_SS7_ASP_ROLE_SG; + asp->cfg.local.port = oxs->cfg.local.port; + asp->cfg.remote.port = atoi(portbuf); + asp->dyn_allocated = true; + asp->server = srv; + osmo_ss7_asp_peer_set_hosts(&asp->cfg.local, asp, + (const char * const*)oxs->cfg.local.host, + oxs->cfg.local.host_cnt); + osmo_ss7_asp_peer_set_hosts(&asp->cfg.remote, asp, + &hostbuf_ptr, 1); + osmo_ss7_asp_restart(asp); + } + } + if (!asp) { + osmo_stream_srv_destroy(srv); + talloc_free(sock_name); + return -1; + } + llist_add_tail(&asp->siblings, &oxs->asp_list); + } + + /* update the ASP reference back to the server over which the + * connection came in */ + asp->server = srv; + asp->xua_server = oxs; + + /* update the ASP socket name */ + talloc_free(asp->sock_name); + asp->sock_name = talloc_reparent(link, asp, sock_name); + osmo_stream_srv_set_name(asp->server, asp->cfg.name); + /* make sure the conn_cb() is called with the asp as private + * data */ + osmo_stream_srv_set_data(srv, asp); + + if (oxs->cfg.proto != OSMO_SS7_ASP_PROT_IPA) { + rc = ss7_asp_apply_peer_primary_address(asp); + rc = ss7_asp_apply_primary_address(asp); + } + + /* send M-SCTP_ESTABLISH.ind to Layer Manager */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_EST_IND, 0); + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); + + return rc; +} + +/*! \brief create a new xUA server configured with given ip/port + * \param[in] ctx talloc allocation context + * \param[in] proto protocol (xUA variant) to use + * \param[in] local_port local SCTP port to bind/listen to + * \param[in] local_host local IP address to bind/listen to (optional) + * \returns callee-allocated \ref osmo_xua_server in case of success + */ +struct osmo_xua_server * +osmo_ss7_xua_server_create(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, + uint16_t local_port, const char *local_host) +{ + struct osmo_xua_server *oxs = talloc_zero(inst, struct osmo_xua_server); + + OSMO_ASSERT(ss7_initialized); + if (!oxs) + return NULL; + + LOGP(DLSS7, LOGL_INFO, "Creating %s Server %s:%u\n", + get_value_string(osmo_ss7_asp_protocol_vals, proto), local_host, local_port); + + INIT_LLIST_HEAD(&oxs->asp_list); + + oxs->cfg.proto = proto; + oxs->cfg.local.port = local_port; + + oxs->server = osmo_stream_srv_link_create(oxs); + osmo_stream_srv_link_set_name(oxs->server, osmo_ss7_asp_protocol_name(proto)); + osmo_stream_srv_link_set_data(oxs->server, oxs); + osmo_stream_srv_link_set_accept_cb(oxs->server, xua_accept_cb); + + osmo_stream_srv_link_set_nodelay(oxs->server, true); + osmo_stream_srv_link_set_port(oxs->server, oxs->cfg.local.port); + osmo_stream_srv_link_set_proto(oxs->server, ss7_asp_proto_to_ip_proto(proto)); + + osmo_ss7_xua_server_set_local_host(oxs, local_host); + + LOGP(DLSS7, LOGL_INFO, "Created %s server on %s:%" PRIu16 "\n", + get_value_string(osmo_ss7_asp_protocol_vals, proto), local_host, local_port); + + oxs->inst = inst; + llist_add_tail(&oxs->list, &inst->xua_servers); + + /* The SUA code internally needs SCCP to work */ + if (proto == OSMO_SS7_ASP_PROT_SUA) + osmo_ss7_ensure_sccp(inst); + + return oxs; +} + +/*! \brief Set the xUA server to bind/listen to the currently configured ip/port + * \param[in] xs xUA server to operate + * \returns 0 on success, negative value on error. + */ +int +osmo_ss7_xua_server_bind(struct osmo_xua_server *xs) +{ + char buf[512]; + int rc; + const char *proto = get_value_string(osmo_ss7_asp_protocol_vals, xs->cfg.proto); + + rc = osmo_ss7_asp_peer_snprintf(buf, sizeof(buf), &xs->cfg.local); + if (rc < 0) { + LOGP(DLSS7, LOGL_INFO, "Failed parsing %s Server osmo_ss7_asp_peer\n", proto); + } else { + LOGP(DLSS7, LOGL_INFO, "(Re)binding %s Server to %s\n", + proto, buf); + } + return osmo_stream_srv_link_open(xs->server); +} + +int +osmo_ss7_xua_server_set_local_host(struct osmo_xua_server *xs, const char *local_host) +{ + return osmo_ss7_xua_server_set_local_hosts(xs, &local_host, 1); +} + +int +osmo_ss7_xua_server_set_local_hosts(struct osmo_xua_server *xs, const char **local_hosts, size_t local_host_cnt) +{ + int rc; + OSMO_ASSERT(ss7_initialized); + + rc = osmo_ss7_asp_peer_set_hosts(&xs->cfg.local, xs, local_hosts, local_host_cnt); + if (rc < 0) + return rc; + return osmo_stream_srv_link_set_addrs(xs->server, (const char **)xs->cfg.local.host, xs->cfg.local.host_cnt); +} + +int +osmo_ss7_xua_server_add_local_host(struct osmo_xua_server *xs, const char *local_host) +{ + int rc; + + rc = osmo_ss7_asp_peer_add_host(&xs->cfg.local, xs, local_host); + if (rc < 0) + return rc; + return osmo_stream_srv_link_set_addrs(xs->server, (const char **)xs->cfg.local.host, xs->cfg.local.host_cnt); +} + +bool ss7_xua_server_set_default_local_hosts(struct osmo_xua_server *oxs) +{ + /* If no local addr was set, or erased after _create(): */ + if (!oxs->cfg.local.host_cnt) { + /* "::" Covers both IPv4 and IPv6 */ + if (ss7_ipv6_sctp_supported("::", true)) + osmo_ss7_xua_server_set_local_host(oxs, "::"); + else + osmo_ss7_xua_server_set_local_host(oxs, "0.0.0.0"); + return true; + } + return false; +} + +void osmo_ss7_xua_server_destroy(struct osmo_xua_server *xs) +{ + struct osmo_ss7_asp *asp, *asp2; + + if (xs->server) { + osmo_stream_srv_link_close(xs->server); + osmo_stream_srv_link_destroy(xs->server); + } + /* iterate and close all connections established in relation + * with this server */ + llist_for_each_entry_safe(asp, asp2, &xs->asp_list, siblings) + osmo_ss7_asp_destroy(asp); + + llist_del(&xs->list); + talloc_free(xs); +} diff --git a/src/ss7_internal.h b/src/ss7_internal.h index f6669e2..34378e8 100644 --- a/src/ss7_internal.h +++ b/src/ss7_internal.h @@ -5,10 +5,31 @@ #include <stdbool.h> #include <osmocom/sigtran/osmo_ss7.h>
+extern bool ss7_initialized; + +bool ss7_ipv6_sctp_supported(const char *host, bool bind); + +struct osmo_ss7_asp *ss7_asp_alloc(struct osmo_ss7_instance *inst, const char *name, + uint16_t remote_port, uint16_t local_port, + enum osmo_ss7_asp_protocol proto); bool ss7_asp_set_default_peer_hosts(struct osmo_ss7_asp *asp); +struct osmo_ss7_asp *ss7_asp_find_by_socket_addr(int fd); +int ss7_asp_proto_to_ip_proto(enum osmo_ss7_asp_protocol proto); +int ss7_asp_ipa_srv_conn_cb(struct osmo_stream_srv *conn); +int ss7_asp_xua_srv_conn_cb(struct osmo_stream_srv *conn); +int ss7_asp_xua_srv_conn_closed_cb(struct osmo_stream_srv *srv); +int ss7_asp_apply_peer_primary_address(const struct osmo_ss7_asp *asp); +int ss7_asp_apply_primary_address(const struct osmo_ss7_asp *asp); + bool ss7_xua_server_set_default_local_hosts(struct osmo_xua_server *oxs);
enum ss7_as_ctr { SS7_AS_CTR_RX_MSU_TOTAL, SS7_AS_CTR_TX_MSU_TOTAL, }; + +enum ss7_asp_ctr { + SS7_ASP_CTR_PKT_RX_TOTAL, + SS7_ASP_CTR_PKT_RX_UNKNOWN, + SS7_ASP_CTR_PKT_TX_TOTAL, +};