2017-11-12 16:21:03 +00:00
|
|
|
/* (C) 2011 by Pablo Neira Ayuso <pablo@gnumonks.org>
|
|
|
|
* (C) 2015-2016 by Harald Welte <laforge@gnumonks.org>
|
2023-08-03 16:27:38 +00:00
|
|
|
* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
2017-11-12 16:21:03 +00:00
|
|
|
* 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/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2011-10-03 20:09:45 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <sys/fcntl.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <arpa/inet.h>
|
2015-02-25 10:17:51 +00:00
|
|
|
#include <netinet/in.h>
|
2017-04-11 16:31:38 +00:00
|
|
|
#include <netinet/tcp.h>
|
2011-10-03 20:09:45 +00:00
|
|
|
|
2011-10-05 10:31:43 +00:00
|
|
|
#include <osmocom/core/timer.h>
|
2011-10-03 20:09:45 +00:00
|
|
|
#include <osmocom/core/select.h>
|
2017-04-08 20:06:37 +00:00
|
|
|
#include <osmocom/core/utils.h>
|
2011-10-03 20:09:45 +00:00
|
|
|
#include <osmocom/gsm/tlv.h>
|
|
|
|
#include <osmocom/core/msgb.h>
|
2023-03-16 15:04:43 +00:00
|
|
|
#include <osmocom/core/osmo_io.h>
|
|
|
|
#include <osmocom/core/panic.h>
|
2011-10-03 20:09:45 +00:00
|
|
|
#include <osmocom/core/logging.h>
|
|
|
|
#include <osmocom/core/talloc.h>
|
|
|
|
#include <osmocom/core/socket.h>
|
|
|
|
|
|
|
|
#include <osmocom/netif/stream.h>
|
2023-08-03 16:27:38 +00:00
|
|
|
#include <osmocom/netif/stream_private.h>
|
2011-10-03 20:09:45 +00:00
|
|
|
|
2015-12-21 19:54:19 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
2022-10-17 10:22:27 +00:00
|
|
|
#include <osmocom/netif/sctp.h>
|
|
|
|
|
2019-02-05 11:12:22 +00:00
|
|
|
|
2017-04-08 17:48:05 +00:00
|
|
|
/*! \addtogroup stream Osmocom Stream Socket
|
|
|
|
* @{
|
2017-10-27 17:57:37 +00:00
|
|
|
*
|
|
|
|
* This code is intended to abstract any use of stream-type sockets,
|
|
|
|
* such as TCP and SCTP. It offers both server and client side
|
|
|
|
* implementations, fully integrated with the libosmocore select loop
|
|
|
|
* abstraction.
|
2017-04-08 17:48:05 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*! \file stream.c
|
|
|
|
* \brief Osmocom stream socket helpers
|
|
|
|
*/
|
|
|
|
|
2020-07-06 10:36:11 +00:00
|
|
|
#ifdef HAVE_LIBSCTP
|
2023-06-16 11:09:54 +00:00
|
|
|
|
2020-06-01 22:43:31 +00:00
|
|
|
/* is any of the bytes from offset .. u8_size in 'u8' non-zero? return offset or -1 if all zero */
|
|
|
|
static int byte_nonzero(const uint8_t *u8, unsigned int offset, unsigned int u8_size)
|
|
|
|
{
|
|
|
|
int j;
|
|
|
|
|
|
|
|
for (j = offset; j < u8_size; j++) {
|
|
|
|
if (u8[j] != 0)
|
|
|
|
return j;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-07-18 16:16:33 +00:00
|
|
|
static unsigned int sctp_sockopt_event_subscribe_size = 0;
|
2020-06-01 22:43:31 +00:00
|
|
|
|
|
|
|
static int determine_sctp_sockopt_event_subscribe_size(void)
|
|
|
|
{
|
|
|
|
uint8_t buf[256];
|
|
|
|
socklen_t buf_len = sizeof(buf);
|
|
|
|
int sd, rc;
|
|
|
|
|
|
|
|
/* only do this once */
|
2022-07-18 16:16:33 +00:00
|
|
|
if (sctp_sockopt_event_subscribe_size > 0)
|
2020-06-01 22:43:31 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
|
|
|
|
if (sd < 0)
|
|
|
|
return sd;
|
|
|
|
|
|
|
|
rc = getsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, buf, &buf_len);
|
2020-06-03 08:31:30 +00:00
|
|
|
close(sd);
|
2020-06-01 22:43:31 +00:00
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
2022-07-18 16:16:33 +00:00
|
|
|
sctp_sockopt_event_subscribe_size = (unsigned int)buf_len;
|
2020-06-01 22:43:31 +00:00
|
|
|
|
|
|
|
LOGP(DLINP, LOGL_INFO, "sizes of 'struct sctp_event_subscribe': compile-time %zu, kernel: %u\n",
|
|
|
|
sizeof(struct sctp_event_subscribe), sctp_sockopt_event_subscribe_size);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Attempt to work around Linux kernel ABI breakage
|
|
|
|
*
|
|
|
|
* The Linux kernel ABI for the SCTP_EVENTS socket option has been broken repeatedly.
|
|
|
|
* - until commit 35ea82d611da59f8bea44a37996b3b11bb1d3fd7 ( kernel < 4.11), the size is 10 bytes
|
|
|
|
* - in 4.11 it is 11 bytes
|
|
|
|
* - in 4.12 .. 5.4 it is 13 bytes
|
|
|
|
* - in kernels >= 5.5 it is 14 bytes
|
|
|
|
*
|
|
|
|
* This wouldn't be a problem if the kernel didn't have a "stupid" assumption that the structure
|
|
|
|
* size passed by userspace will match 1:1 the length of the structure at kernel compile time. In
|
|
|
|
* an ideal world, it would just use the known first bytes and assume the remainder is all zero.
|
|
|
|
* But as it doesn't do that, let's try to work around this */
|
|
|
|
static int sctp_setsockopt_events_linux_workaround(int fd, const struct sctp_event_subscribe *event)
|
|
|
|
{
|
|
|
|
|
|
|
|
const unsigned int compiletime_size = sizeof(*event);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (determine_sctp_sockopt_event_subscribe_size() < 0) {
|
|
|
|
LOGP(DLINP, LOGL_ERROR, "Cannot determine SCTP_EVENTS socket option size\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (compiletime_size == sctp_sockopt_event_subscribe_size) {
|
|
|
|
/* no kernel workaround needed */
|
|
|
|
return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, event, compiletime_size);
|
|
|
|
} else if (compiletime_size < sctp_sockopt_event_subscribe_size) {
|
|
|
|
/* we are using an older userspace with a more modern kernel and hence need
|
|
|
|
* to pad the data */
|
|
|
|
uint8_t buf[sctp_sockopt_event_subscribe_size];
|
|
|
|
|
|
|
|
memcpy(buf, event, compiletime_size);
|
|
|
|
memset(buf + sizeof(*event), 0, sctp_sockopt_event_subscribe_size - compiletime_size);
|
|
|
|
return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, buf, sctp_sockopt_event_subscribe_size);
|
|
|
|
} else /* if (compiletime_size > sctp_sockopt_event_subscribe_size) */ {
|
|
|
|
/* we are using a newer userspace with an older kernel and hence need to truncate
|
|
|
|
* the data - but only if the caller didn't try to enable any of the events of the
|
|
|
|
* truncated portion */
|
|
|
|
rc = byte_nonzero((const uint8_t *)event, sctp_sockopt_event_subscribe_size,
|
|
|
|
compiletime_size);
|
|
|
|
if (rc >= 0) {
|
|
|
|
LOGP(DLINP, LOGL_ERROR, "Kernel only supports sctp_event_subscribe of %u bytes, "
|
|
|
|
"but caller tried to enable more modern event at offset %u\n",
|
|
|
|
sctp_sockopt_event_subscribe_size, rc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, event, sctp_sockopt_event_subscribe_size);
|
|
|
|
}
|
|
|
|
}
|
2020-07-06 10:36:11 +00:00
|
|
|
#endif // HAVE_LIBSCTP
|
2020-06-01 22:43:31 +00:00
|
|
|
|
2023-08-03 16:27:38 +00:00
|
|
|
int stream_sctp_sock_activate_events(int fd)
|
2015-12-21 19:54:19 +00:00
|
|
|
{
|
|
|
|
#ifdef HAVE_LIBSCTP
|
|
|
|
struct sctp_event_subscribe event;
|
|
|
|
int rc;
|
|
|
|
|
2017-04-17 14:05:22 +00:00
|
|
|
/* subscribe for all relevant events */
|
|
|
|
memset((uint8_t *)&event, 0, sizeof(event));
|
|
|
|
event.sctp_data_io_event = 1;
|
|
|
|
event.sctp_association_event = 1;
|
|
|
|
event.sctp_address_event = 1;
|
|
|
|
event.sctp_send_failure_event = 1;
|
|
|
|
event.sctp_peer_error_event = 1;
|
|
|
|
event.sctp_shutdown_event = 1;
|
|
|
|
/* IMPORTANT: Do NOT enable sender_dry_event here, see
|
|
|
|
* https://bugzilla.redhat.com/show_bug.cgi?id=1442784 */
|
2020-05-07 11:36:01 +00:00
|
|
|
|
2020-06-01 22:43:31 +00:00
|
|
|
rc = sctp_setsockopt_events_linux_workaround(fd, &event);
|
2016-11-26 14:42:16 +00:00
|
|
|
if (rc < 0)
|
2020-06-01 22:43:31 +00:00
|
|
|
LOGP(DLINP, LOGL_ERROR, "couldn't activate SCTP events on FD %u\n", fd);
|
2015-12-21 19:54:19 +00:00
|
|
|
return rc;
|
|
|
|
#else
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2023-08-03 16:27:38 +00:00
|
|
|
int stream_setsockopt_nodelay(int fd, int proto, int on)
|
2017-04-11 16:31:38 +00:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
switch (proto) {
|
2020-01-09 12:27:01 +00:00
|
|
|
#ifdef HAVE_LIBSCTP
|
2017-04-11 16:31:38 +00:00
|
|
|
case IPPROTO_SCTP:
|
|
|
|
rc = setsockopt(fd, IPPROTO_SCTP, SCTP_NODELAY, &on, sizeof(on));
|
|
|
|
break;
|
2020-01-09 12:27:01 +00:00
|
|
|
#endif
|
2017-04-11 16:31:38 +00:00
|
|
|
case IPPROTO_TCP:
|
|
|
|
rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = -1;
|
|
|
|
LOGP(DLINP, LOGL_ERROR, "Unknown protocol %u, cannot set NODELAY\n",
|
|
|
|
proto);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-08-04 16:02:20 +00:00
|
|
|
#ifdef HAVE_LIBSCTP
|
|
|
|
#define LOGPFX(pfx, level, fmt, args...) \
|
|
|
|
LOGP(DLINP, level, "%s " fmt, pfx, ## args)
|
|
|
|
int stream_sctp_recvmsg_wrapper(int fd, struct msgb *msg, const char *log_pfx)
|
|
|
|
{
|
|
|
|
struct sctp_sndrcvinfo sinfo;
|
|
|
|
int flags = 0;
|
|
|
|
int ret;
|
|
|
|
uint8_t *data = msg->tail;
|
|
|
|
|
|
|
|
ret = sctp_recvmsg(fd, data, msgb_tailroom(msg), NULL, NULL, &sinfo, &flags);
|
|
|
|
msgb_sctp_msg_flags(msg) = 0;
|
|
|
|
msgb_sctp_ppid(msg) = ntohl(sinfo.sinfo_ppid);
|
|
|
|
msgb_sctp_stream(msg) = sinfo.sinfo_stream;
|
|
|
|
|
|
|
|
if (flags & MSG_NOTIFICATION) {
|
|
|
|
char buf[512];
|
|
|
|
struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
|
|
|
|
int logl = LOGL_INFO;
|
|
|
|
union sctp_notification *notif = (union sctp_notification *)data;
|
|
|
|
|
|
|
|
OSMO_STRBUF_PRINTF(sb, "%s NOTIFICATION %s flags=0x%x", log_pfx,
|
|
|
|
osmo_sctp_sn_type_str(notif->sn_header.sn_type), notif->sn_header.sn_flags);
|
|
|
|
msgb_put(msg, sizeof(union sctp_notification));
|
|
|
|
msgb_sctp_msg_flags(msg) = OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION;
|
|
|
|
ret = -EAGAIN;
|
|
|
|
|
|
|
|
switch (notif->sn_header.sn_type) {
|
|
|
|
case SCTP_ASSOC_CHANGE:
|
|
|
|
OSMO_STRBUF_PRINTF(sb, " %s", osmo_sctp_assoc_chg_str(notif->sn_assoc_change.sac_state));
|
|
|
|
switch (notif->sn_assoc_change.sac_state) {
|
|
|
|
case SCTP_COMM_UP:
|
|
|
|
break;
|
|
|
|
case SCTP_COMM_LOST:
|
|
|
|
OSMO_STRBUF_PRINTF(sb, " (err: %s)",
|
|
|
|
osmo_sctp_sn_error_str(notif->sn_assoc_change.sac_error));
|
|
|
|
/* Handle this like a regular disconnect */
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
case SCTP_RESTART:
|
|
|
|
case SCTP_SHUTDOWN_COMP:
|
|
|
|
logl = LOGL_NOTICE;
|
|
|
|
break;
|
|
|
|
case SCTP_CANT_STR_ASSOC:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SCTP_SEND_FAILED:
|
|
|
|
logl = LOGL_ERROR;
|
|
|
|
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);
|
|
|
|
OSMO_STRBUF_PRINTF(sb, " %s %s err=%s",
|
|
|
|
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;
|
|
|
|
case SCTP_SHUTDOWN_EVENT:
|
|
|
|
logl = LOGL_NOTICE;
|
|
|
|
/* RFC6458 3.1.4: Any attempt to send more data will cause sendmsg()
|
|
|
|
* to return with an ESHUTDOWN error. */
|
|
|
|
break;
|
|
|
|
case SCTP_REMOTE_ERROR:
|
|
|
|
logl = LOGL_NOTICE;
|
|
|
|
OSMO_STRBUF_PRINTF(sb, " %s", osmo_sctp_op_error_str(ntohs(notif->sn_remote_error.sre_error)));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
LOGP(DLINP, logl, "%s\n", buf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-04-11 16:31:38 +00:00
|
|
|
|
2017-04-08 17:48:05 +00:00
|
|
|
/*! @} */
|