/* Core SS7 ASP Handling */ /* (C) 2015-2017 by Harald Welte * (C) 2023 by sysmocom s.f.m.c. GmbH * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 } }; /* check if the given transport and ASP protocols are compatible (and implemented) */ bool ss7_asp_protocol_check_trans_proto(enum osmo_ss7_asp_protocol proto, int trans_proto) { switch (proto) { case OSMO_SS7_ASP_PROT_IPA: if (trans_proto == IPPROTO_TCP) return true; return false; case OSMO_SS7_ASP_PROT_SUA: if (trans_proto == IPPROTO_SCTP) return true; return false; case OSMO_SS7_ASP_PROT_M3UA: if (trans_proto == IPPROTO_SCTP) return true; if (trans_proto == IPPROTO_TCP) return true; return false; case OSMO_SS7_ASP_PROT_NONE: default: return false; } } /* get _default_ transport protocol for the given ASP protocol */ int ss7_default_trans_proto_for_asp_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; int ss7_asp_apply_new_local_address(const struct osmo_ss7_asp *asp, unsigned int loc_idx) { const char *new_loc_addr; int fd; OSMO_ASSERT(loc_idx < asp->cfg.local.host_cnt); new_loc_addr = asp->cfg.local.host[loc_idx]; LOGPASP(asp, DLSS7, LOGL_INFO, "Add local address %s\n", new_loc_addr); if (asp->cfg.is_server) fd = osmo_stream_srv_get_fd(asp->server); else fd = osmo_stream_cli_get_fd(asp->client); if (fd < 0) return fd; return osmo_sock_multiaddr_add_local_addr(fd, &new_loc_addr, 1); } int ss7_asp_apply_drop_local_address(const struct osmo_ss7_asp *asp, unsigned int loc_idx) { const char *new_loc_addr; int fd; OSMO_ASSERT(loc_idx < asp->cfg.local.host_cnt); new_loc_addr = asp->cfg.local.host[loc_idx]; LOGPASP(asp, DLSS7, LOGL_INFO, "Remove local address %s\n", new_loc_addr); if (asp->cfg.is_server) fd = osmo_stream_srv_get_fd(asp->server); else fd = osmo_stream_cli_get_fd(asp->client); if (fd < 0) return fd; return osmo_sock_multiaddr_del_local_addr(fd, &new_loc_addr, 1); } int ss7_asp_apply_peer_primary_address(const struct osmo_ss7_asp *asp) { struct osmo_sockaddr_str addr_str; struct osmo_sockaddr addr; uint16_t local_port; int fd, 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) fd = osmo_stream_srv_get_fd(asp->server); else fd = osmo_stream_cli_get_fd(asp->client); if (fd < 0) return fd; if (asp->cfg.local.port == 0) { char port_buf[16]; osmo_sock_get_local_ip_port(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(fd, &addr); return rc; } int ss7_asp_apply_primary_address(const struct osmo_ss7_asp *asp) { struct osmo_sockaddr_str addr_str; struct osmo_sockaddr addr; int fd, 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) fd = osmo_stream_srv_get_fd(asp->server); else fd = osmo_stream_cli_get_fd(asp->client); if (fd < 0) return fd; 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(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, int trans_proto) { 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; 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.trans_proto != trans_proto) continue; if (asp->cfg.local.port != local_port) continue; if (asp->cfg.remote.port && asp->cfg.remote.port != remote_port) continue; if (!ss7_asp_peer_match_host(&asp->cfg.local, hostbuf_l, loc_is_v6)) 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) { if (!ss7_asp_peer_match_host(&asp->cfg.remote, hostbuf_r, rem_is_v6)) 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, int trans_proto, enum osmo_ss7_asp_protocol proto) { struct osmo_ss7_asp *asp; if (!ss7_asp_protocol_check_trans_proto(proto, trans_proto)) { LOGP(DLSCCP, LOGL_ERROR, "ASP protocol '%s' with transport protocol %d is not supported", osmo_ss7_asp_protocol_name(proto), trans_proto); return NULL; } 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.trans_proto = trans_proto; 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, int res, struct msgb *msg); static int ipa_cli_read_cb(struct osmo_stream_cli *conn, int res, struct msgb *msg); static int m3ua_tcp_cli_read_cb(struct osmo_stream_cli *conn, int res, struct msgb *msg); static int xua_cli_connect_cb(struct osmo_stream_cli *cli); static int xua_cli_close_and_reconnect(struct osmo_stream_cli *cli); int osmo_ss7_asp_restart(struct osmo_ss7_asp *asp) { int rc; char bufloc[512], bufrem[512]; uint8_t byte; 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->cfg.trans_proto); osmo_stream_cli_set_reconnect_timeout(asp->client, 5); osmo_stream_cli_set_connect_cb(asp->client, xua_cli_connect_cb); osmo_stream_cli_set_disconnect_cb(asp->client, xua_cli_close_and_reconnect); switch (asp->cfg.proto) { case OSMO_SS7_ASP_PROT_IPA: OSMO_ASSERT(asp->cfg.trans_proto == IPPROTO_TCP); osmo_stream_cli_set_read_cb2(asp->client, ipa_cli_read_cb); osmo_stream_cli_set_segmentation_cb(asp->client, osmo_ipa_segmentation_cb); break; case OSMO_SS7_ASP_PROT_M3UA: if (asp->cfg.trans_proto == IPPROTO_SCTP) { osmo_stream_cli_set_read_cb2(asp->client, xua_cli_read_cb); osmo_stream_cli_set_segmentation_cb(asp->client, NULL); } else if (asp->cfg.trans_proto == IPPROTO_TCP) { osmo_stream_cli_set_read_cb2(asp->client, m3ua_tcp_cli_read_cb); osmo_stream_cli_set_segmentation_cb(asp->client, xua_tcp_segmentation_cb); } else OSMO_ASSERT(0); break; default: OSMO_ASSERT(asp->cfg.trans_proto == IPPROTO_SCTP); osmo_stream_cli_set_read_cb2(asp->client, xua_cli_read_cb); osmo_stream_cli_set_segmentation_cb(asp->client, NULL); break; } osmo_stream_cli_set_data(asp->client, asp); byte = 1; /*AUTH is needed by ASCONF. enable, don't abort socket creation if AUTH can't be enabled */ osmo_stream_cli_set_param(asp->client, OSMO_STREAM_CLI_PAR_SCTP_SOCKOPT_AUTH_SUPPORTED, &byte, sizeof(byte)); byte = 1; /* enable, don't abort socket creation if ASCONF can't be enabled */ osmo_stream_cli_set_param(asp->client, OSMO_STREAM_CLI_PAR_SCTP_SOCKOPT_ASCONF_SUPPORTED, &byte, sizeof(byte)); if (asp->cfg.sctp_init.num_ostreams_present) osmo_stream_cli_set_param(asp->client, OSMO_STREAM_CLI_PAR_SCTP_INIT_NUM_OSTREAMS, &asp->cfg.sctp_init.num_ostreams_value, sizeof(asp->cfg.sctp_init.num_ostreams_value)); if (asp->cfg.sctp_init.max_instreams_present) osmo_stream_cli_set_param(asp->client, OSMO_STREAM_CLI_PAR_SCTP_INIT_MAX_INSTREAMS, &asp->cfg.sctp_init.max_instreams_value, sizeof(asp->cfg.sctp_init.max_instreams_value)); if (asp->cfg.sctp_init.max_attempts_present) osmo_stream_cli_set_param(asp->client, OSMO_STREAM_CLI_PAR_SCTP_INIT_MAX_ATTEMPTS, &asp->cfg.sctp_init.max_attempts_value, sizeof(asp->cfg.sctp_init.max_attempts_value)); if (asp->cfg.sctp_init.max_init_timeo_present) osmo_stream_cli_set_param(asp->client, OSMO_STREAM_CLI_PAR_SCTP_INIT_TIMEOUT, &asp->cfg.sctp_init.max_init_timeo_value, sizeof(asp->cfg.sctp_init.max_init_timeo_value)); 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; } bool ss7_asp_is_started(const struct osmo_ss7_asp *asp) { if (asp->cfg.is_server) return !!asp->server; else return !!asp->client; } /*********************************************************************** * 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_rx_cb(struct osmo_stream_srv *conn, int res, struct msgb *msg) { struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); if (res <= 0) { if (res == -EAGAIN) { msgb_free(msg); return 0; } msgb_free(msg); osmo_stream_srv_destroy(conn); return res; } msg->dst = asp; rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); /* we simply use the lower 4 bits of the asp_id, which is initialized to a pseudo-random value upon * connect */ return ipa_rx_msg(asp, msg, asp->asp_id & 0xf); } /* netif code tells us we can read something from the socket */ int ss7_asp_xua_srv_conn_rx_cb(struct osmo_stream_srv *conn, int res, struct msgb *msg) { struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); unsigned int ppid; int flags; int rc = 0; /* process the received xUA message */ 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 SRV", 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; } msgb_free(msg); return 0; } if (res <= 0) { msgb_free(msg); osmo_stream_srv_destroy(conn); return rc; } 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); msgb_free(msg); return rc; } int xua_tcp_segmentation_cb(struct msgb *msg) { const struct xua_common_hdr *hdr; size_t msg_length; if (msgb_length(msg) < sizeof(*hdr)) return -EAGAIN; hdr = (const struct xua_common_hdr *) msg->data; msg_length = ntohl(hdr->msg_length); /* includes sizeof(*hdr) */ return msg_length; } /* netif code tells us we can read something from the socket */ int ss7_asp_m3ua_tcp_srv_conn_rx_cb(struct osmo_stream_srv *conn, int res, struct msgb *msg) { struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); const struct xua_common_hdr *hdr; int rc; if (res <= 0) { if (res == -EAGAIN) { msgb_free(msg); return 0; } msgb_free(msg); osmo_stream_srv_destroy(conn); return res; } msg->dst = asp; rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); /* spoof SCTP Stream ID */ hdr = (const struct xua_common_hdr *)msg->data; if (hdr->msg_class == M3UA_MSGC_XFER) msgb_sctp_stream(msg) = 1; else msgb_sctp_stream(msg) = 0; rc = m3ua_rx_msg(asp, msg); msgb_free(msg); return rc; } /* client has established SCTP connection to server */ static int xua_cli_connect_cb(struct osmo_stream_cli *cli) { int fd = osmo_stream_cli_get_fd(cli); struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); int rc = 0; if (fd < 0) return fd; osmo_iofd_set_alloc_info(osmo_stream_cli_get_iofd(cli), M3UA_MSG_SIZE, M3UA_MSG_HEADROOM); /* update the socket name */ talloc_free(asp->sock_name); asp->sock_name = osmo_sock_get_name(asp, 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.trans_proto == IPPROTO_SCTP) { rc = ss7_asp_apply_peer_primary_address(asp); rc = ss7_asp_apply_primary_address(asp); } else { if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA) { /* we use the lower 4 bits of the asp_id feld as SLS; let's initialize it here from a * pseudo-random value */ asp->asp_id = rand() & 0xf; } } 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 ask 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 int xua_cli_close_and_reconnect(struct osmo_stream_cli *cli) { xua_cli_close(cli); osmo_stream_cli_reconnect(cli); return 0; } /* read call-back for IPA/SCCPlite socket */ static int ipa_cli_read_cb(struct osmo_stream_cli *conn, int res, struct msgb *msg) { int fd = osmo_stream_cli_get_fd(conn); struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); if (res <= 0) { if (res == -EAGAIN) { msgb_free(msg); return 0; } msgb_free(msg); xua_cli_close_and_reconnect(conn); return res; } msg->dst = asp; rate_ctr_inc2(asp->ctrg, SS7_ASP_CTR_PKT_RX_TOTAL); /* we can use the 'fd' return value of osmo_stream_srv_get_fd() here unverified as all we do * is 'roll the dice' to obtain a 4-bit SLS value. */ return ipa_rx_msg(asp, msg, fd & 0xf); } /* read call-back for M3UA-over-TCP socket */ static int m3ua_tcp_cli_read_cb(struct osmo_stream_cli *conn, int res, struct msgb *msg) { const struct xua_common_hdr *hdr; /* spoof SCTP PPID */ msgb_sctp_ppid(msg) = M3UA_PPID; /* spoof SCTP Stream ID */ hdr = (const struct xua_common_hdr *) msg->data; if (hdr->msg_class == M3UA_MSGC_XFER) msgb_sctp_stream(msg) = 1; else msgb_sctp_stream(msg) = 0; return xua_cli_read_cb(conn, res, msg); } static int xua_cli_read_cb(struct osmo_stream_cli *conn, int res, struct msgb *msg) { struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); unsigned int ppid; int flags; int rc = 0; flags = msgb_sctp_msg_flags(msg); LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", __func__, msgb_length(msg), 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 (res == 0) xua_cli_close_and_reconnect(conn); goto out; } if (res < 0) { xua_cli_close_and_reconnect(conn); goto out; } else if (res == 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; } /*! \brief Get the transport proto of a given ASP * \param[in] asp The ASP for which the transport proto is requested * \returns The transport proto of the ASP (one of IPPROTO_*) */ int osmo_ss7_asp_get_trans_proto(const struct osmo_ss7_asp *asp) { return asp->cfg.trans_proto; } /*! \brief Get the fd of a given ASP * \param[in] asp The ASP for which the fd is requested * \returns The fd of the ASP if acailable, negative otherwise */ int ss7_asp_get_fd(const struct osmo_ss7_asp *asp) { if (asp->cfg.is_server) { if (asp->server) return osmo_stream_srv_get_fd(asp->server); } else { if (asp->client) return osmo_stream_cli_get_fd(asp->client); } return -1; }