open5gs/lib/sctp/ogs-lksctp.c

666 lines
20 KiB
C

/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 <https://www.gnu.org/licenses/>.
*/
#include "ogs-sctp.h"
#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __ogs_sock_domain
static int subscribe_to_events(ogs_sock_t *sock);
void ogs_sctp_init(uint16_t port)
{
}
void ogs_sctp_final(void)
{
}
ogs_sock_t *ogs_sctp_socket(int family, int type)
{
ogs_sock_t *new = NULL;
int rv;
new = ogs_sock_socket(family, type, IPPROTO_SCTP);
if (!new) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"ogs_sock_socket(faimly:%d type:%d) failed", family, type);
return NULL;
}
rv = subscribe_to_events(new);
if (rv != OGS_OK) {
ogs_sock_destroy(new);
return NULL;
}
return new;
}
ogs_sock_t *ogs_sctp_server(
int type, ogs_sockaddr_t *sa_list, ogs_sockopt_t *socket_option)
{
int rv;
char buf[OGS_ADDRSTRLEN];
ogs_sock_t *new = NULL;
ogs_sockaddr_t *addr;
ogs_sockopt_t option;
ogs_assert(sa_list);
ogs_sockopt_init(&option);
if (socket_option)
memcpy(&option, socket_option, sizeof option);
addr = sa_list;
while (addr) {
new = ogs_sctp_socket(addr->ogs_sa_family, type);
if (new) {
rv = ogs_sctp_peer_addr_params(new, &option);
ogs_assert(rv == OGS_OK);
rv = ogs_sctp_rto_info(new, &option);
ogs_assert(rv == OGS_OK);
rv = ogs_sctp_initmsg(new, &option);
ogs_assert(rv == OGS_OK);
if (option.sctp_nodelay == true) {
rv = ogs_sctp_nodelay(new, true);
ogs_assert(rv == OGS_OK);
} else
ogs_warn("SCTP NO_DELAY Disabled");
if (option.so_linger.l_onoff == true) {
rv = ogs_sctp_so_linger(new, option.so_linger.l_linger);
ogs_assert(rv == OGS_OK);
}
rv = ogs_listen_reusable(new->fd, true);
ogs_assert(rv == OGS_OK);
if (ogs_sock_bind(new, addr) == OGS_OK) {
ogs_debug("sctp_server() [%s]:%d",
OGS_ADDR(addr, buf), OGS_PORT(addr));
break;
}
ogs_sock_destroy(new);
}
addr = addr->next;
}
if (addr == NULL) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"sctp_server() [%s]:%d failed",
OGS_ADDR(sa_list, buf), OGS_PORT(sa_list));
return NULL;
}
ogs_assert(new);
rv = ogs_sock_listen(new);
ogs_assert(rv == OGS_OK);
return new;
}
ogs_sock_t *ogs_sctp_client(
int type, ogs_sockaddr_t *sa_list, ogs_sockopt_t *socket_option)
{
int rv;
char buf[OGS_ADDRSTRLEN];
ogs_sock_t *new = NULL;
ogs_sockaddr_t *addr;
ogs_sockopt_t option;
ogs_assert(sa_list);
ogs_sockopt_init(&option);
if (socket_option)
memcpy(&option, socket_option, sizeof option);
addr = sa_list;
while (addr) {
new = ogs_sctp_socket(addr->ogs_sa_family, type);
if (new) {
rv = ogs_sctp_peer_addr_params(new, &option);
ogs_assert(rv == OGS_OK);
rv = ogs_sctp_rto_info(new, &option);
ogs_assert(rv == OGS_OK);
rv = ogs_sctp_initmsg(new, &option);
ogs_assert(rv == OGS_OK);
if (option.sctp_nodelay == true) {
rv = ogs_sctp_nodelay(new, true);
ogs_assert(rv == OGS_OK);
} else
ogs_warn("SCTP NO_DELAY Disabled");
if (option.so_linger.l_onoff == true) {
rv = ogs_sctp_so_linger(new, option.so_linger.l_linger);
ogs_assert(rv == OGS_OK);
}
if (ogs_sock_connect(new, addr) == OGS_OK) {
ogs_debug("sctp_client() [%s]:%d",
OGS_ADDR(addr, buf), OGS_PORT(addr));
break;
}
ogs_sock_destroy(new);
}
addr = addr->next;
}
if (addr == NULL) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"sctp_client() [%s]:%d failed",
OGS_ADDR(sa_list, buf), OGS_PORT(sa_list));
return NULL;
}
ogs_assert(new);
return new;
}
int ogs_sctp_connect(ogs_sock_t *sock, ogs_sockaddr_t *sa_list)
{
ogs_sockaddr_t *addr;
char buf[OGS_ADDRSTRLEN];
ogs_assert(sock);
addr = sa_list;
while (addr) {
if (ogs_sock_connect(sock, addr) == OGS_OK) {
ogs_debug("sctp_connect() [%s]:%d",
OGS_ADDR(addr, buf), OGS_PORT(addr));
break;
}
addr = addr->next;
}
if (addr == NULL) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"sctp_connect() [%s]:%d failed",
OGS_ADDR(sa_list, buf), OGS_PORT(sa_list));
return OGS_ERROR;
}
return OGS_OK;
}
int ogs_sctp_sendmsg(ogs_sock_t *sock, const void *msg, size_t len,
ogs_sockaddr_t *to, uint32_t ppid, uint16_t stream_no)
{
socklen_t addrlen = 0;
ogs_assert(sock);
if (to)
addrlen = ogs_sockaddr_len(to);
return sctp_sendmsg(sock->fd, msg, len,
to ? &to->sa : NULL, addrlen,
htobe32(ppid),
0, /* flags */
stream_no,
0, /* timetolive */
0); /* context */
}
int ogs_sctp_recvmsg(ogs_sock_t *sock, void *msg, size_t len,
ogs_sockaddr_t *from, ogs_sctp_info_t *sinfo, int *msg_flags)
{
int size;
socklen_t addrlen = sizeof(struct sockaddr_storage);
ogs_sockaddr_t addr;
int flags = 0;
struct sctp_sndrcvinfo sndrcvinfo;
ogs_assert(sock);
memset(&sndrcvinfo, 0, sizeof sndrcvinfo);
memset(&addr, 0, sizeof addr);
size = sctp_recvmsg(sock->fd, msg, len, &addr.sa, &addrlen,
&sndrcvinfo, &flags);
if (size < 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"sctp_recvmsg(%d) failed", size);
return size;
}
if (from) {
memcpy(from, &addr, sizeof(ogs_sockaddr_t));
}
if (msg_flags) {
*msg_flags = flags;
}
if (sinfo) {
sinfo->ppid = be32toh(sndrcvinfo.sinfo_ppid);
sinfo->stream_no = sndrcvinfo.sinfo_stream;
}
return size;
}
/* 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 OGS_ERROR;
}
static int sctp_sockopt_event_subscribe_size = 0;
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 */
if (sctp_sockopt_event_subscribe_size != 0)
return 0;
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (sd < 0)
return sd;
memset(buf, 0, sizeof(buf));
rc = getsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, buf, &buf_len);
ogs_closesocket(sd);
if (rc < 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"getsockopt(SCTP_PEER_ADDR_PARAMS) failed [%d]", rc);
return rc;
}
sctp_sockopt_event_subscribe_size = buf_len;
ogs_debug("sizes of 'struct sctp_event_subscribe': "
"compile-time %zu, kernel: %u",
sizeof(struct sctp_event_subscribe),
sctp_sockopt_event_subscribe_size);
return 0;
}
/*
* The workaround is stolen from libosmo-netif.
* - http://osmocom.org/projects/libosmo-netif/repository/revisions/master/entry/src/stream.c
*
* 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_event_subscribe_workaround(
int fd, const struct sctp_event_subscribe *event_subscribe)
{
const unsigned int compiletime_size = sizeof(*event_subscribe);
int rc;
if (determine_sctp_sockopt_event_subscribe_size() < 0) {
ogs_error("Cannot determine SCTP_EVENTS socket option size");
return OGS_ERROR;
}
if (compiletime_size == sctp_sockopt_event_subscribe_size) {
/* no kernel workaround needed */
return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS,
event_subscribe, 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[256];
ogs_assert(sctp_sockopt_event_subscribe_size <= sizeof(buf));
memcpy(buf, event_subscribe, compiletime_size);
memset(buf + sizeof(*event_subscribe),
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_subscribe,
sctp_sockopt_event_subscribe_size, compiletime_size);
if (rc >= 0) {
ogs_error("Kernel only supports sctp_event_subscribe of %u bytes, "
"but caller tried to enable more modern event at offset %u",
sctp_sockopt_event_subscribe_size, rc);
return OGS_ERROR;
}
return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, event_subscribe,
sctp_sockopt_event_subscribe_size);
}
}
static int subscribe_to_events(ogs_sock_t *sock)
{
struct sctp_event_subscribe event_subscribe;
ogs_assert(sock);
memset(&event_subscribe, 0, sizeof(event_subscribe));
event_subscribe.sctp_data_io_event = 1;
event_subscribe.sctp_association_event = 1;
event_subscribe.sctp_send_failure_event = 1;
event_subscribe.sctp_shutdown_event = 1;
#ifdef DISABLE_SCTP_EVENT_WORKAROUND
if (setsockopt(sock->fd, IPPROTO_SCTP, SCTP_EVENTS,
&event_subscribe, sizeof(event_subscribe)) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"setsockopt(SCTP_EVENTS) failed");
return OGS_ERROR;
}
#else
if (sctp_setsockopt_event_subscribe_workaround(
sock->fd, &event_subscribe) < 0) {
ogs_error("sctp_setsockopt_events_linux_workaround() failed");
return OGS_ERROR;
}
#endif
return OGS_OK;
}
static int sctp_sockopt_paddrparams_size = 0;
static int determine_sctp_sockopt_paddrparams_size(void)
{
uint8_t buf[256];
socklen_t buf_len = sizeof(buf);
int sd, rc;
/* only do this once */
if (sctp_sockopt_paddrparams_size != 0)
return 0;
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (sd < 0)
return sd;
memset(buf, 0, sizeof(buf));
rc = getsockopt(sd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, buf, &buf_len);
ogs_closesocket(sd);
if (rc < 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"getsockopt(SCTP_PEER_ADDR_PARAMS) failed [%d]", rc);
return rc;
}
sctp_sockopt_paddrparams_size = buf_len;
ogs_debug("sizes of 'struct sctp_paddrparams': "
"compile-time %zu, kernel: %u",
sizeof(struct sctp_paddrparams),
sctp_sockopt_paddrparams_size);
return 0;
}
static int sctp_setsockopt_paddrparams_workaround(
int fd, const struct sctp_paddrparams *paddrparams)
{
const unsigned int compiletime_size = sizeof(*paddrparams);
int rc;
if (determine_sctp_sockopt_paddrparams_size() < 0) {
ogs_error("Cannot determine SCTP_PEER_ADDR_PARAMS socket option size");
return OGS_ERROR;
}
if (compiletime_size == sctp_sockopt_paddrparams_size) {
/* no kernel workaround needed */
return setsockopt(fd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS,
paddrparams, compiletime_size);
} else if (compiletime_size < sctp_sockopt_paddrparams_size) {
/* we are using an older userspace with a more modern kernel
* and hence need to pad the data */
uint8_t buf[256];
ogs_assert(sctp_sockopt_paddrparams_size <= sizeof(buf));
memcpy(buf, paddrparams, compiletime_size);
memset(buf + sizeof(*paddrparams),
0, sctp_sockopt_paddrparams_size - compiletime_size);
return setsockopt(fd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS,
buf, sctp_sockopt_paddrparams_size);
} else /* if (compiletime_size > sctp_sockopt_paddrparams_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 *)paddrparams,
sctp_sockopt_paddrparams_size, compiletime_size);
if (rc >= 0) {
ogs_error("Kernel only supports sctp_paddrparams of %u bytes, "
"but caller tried to enable more modern event at offset %u",
sctp_sockopt_paddrparams_size, rc);
return OGS_ERROR;
}
return setsockopt(fd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, paddrparams,
sctp_sockopt_paddrparams_size);
}
}
int ogs_sctp_peer_addr_params(ogs_sock_t *sock, ogs_sockopt_t *option)
{
struct sctp_paddrparams paddrparams;
socklen_t socklen;
ogs_assert(sock);
ogs_assert(option);
memset(&paddrparams, 0, sizeof(paddrparams));
socklen = sizeof(paddrparams);
if (getsockopt(sock->fd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS,
&paddrparams, &socklen) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"getsockopt(SCTP_PEER_ADDR) failed");
return OGS_ERROR;
}
#if !defined(__FreeBSD__)
ogs_debug("OLD spp_flags = 0x%x hbinter = %d pathmax = %d, sackdelay = %d",
paddrparams.spp_flags,
paddrparams.spp_hbinterval,
paddrparams.spp_pathmaxrxt,
paddrparams.spp_sackdelay);
#else
ogs_debug("OLD spp_flags = 0x%x hbinter = %d pathmax = %d",
paddrparams.spp_flags,
paddrparams.spp_hbinterval,
paddrparams.spp_pathmaxrxt);
#endif
paddrparams.spp_hbinterval = option->sctp.spp_hbinterval;
#if !defined(__FreeBSD__)
paddrparams.spp_sackdelay = option->sctp.spp_sackdelay;
#endif
#ifdef DISABLE_SCTP_EVENT_WORKAROUND
if (setsockopt(sock->fd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS,
&paddrparams, sizeof(paddrparams)) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"setsockopt(SCTP_PEER_ADDR_PARAMS) failed");
return OGS_ERROR;
}
#else
if (sctp_setsockopt_paddrparams_workaround(sock->fd, &paddrparams) < 0) {
ogs_error("sctp_setsockopt_paddrparams_workaround() failed");
return OGS_ERROR;
}
#endif
#if !defined(__FreeBSD__)
ogs_debug("NEW spp_flags = 0x%x hbinter = %d pathmax = %d, sackdelay = %d",
paddrparams.spp_flags,
paddrparams.spp_hbinterval,
paddrparams.spp_pathmaxrxt,
paddrparams.spp_sackdelay);
#else
ogs_debug("NEW spp_flags = 0x%x hbinter = %d pathmax = %d",
paddrparams.spp_flags,
paddrparams.spp_hbinterval,
paddrparams.spp_pathmaxrxt);
#endif
return OGS_OK;
}
int ogs_sctp_rto_info(ogs_sock_t *sock, ogs_sockopt_t *option)
{
struct sctp_rtoinfo rtoinfo;
socklen_t socklen;
ogs_assert(sock);
ogs_assert(option);
memset(&rtoinfo, 0, sizeof(rtoinfo));
socklen = sizeof(rtoinfo);
if (getsockopt(sock->fd, IPPROTO_SCTP, SCTP_RTOINFO,
&rtoinfo, &socklen) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"getsockopt for SCTP_RTOINFO failed");
return OGS_ERROR;
}
ogs_debug("OLD RTO (initial:%d max:%d min:%d)",
rtoinfo.srto_initial,
rtoinfo.srto_max,
rtoinfo.srto_min);
rtoinfo.srto_initial = option->sctp.srto_initial;
rtoinfo.srto_min = option->sctp.srto_min;
rtoinfo.srto_max = option->sctp.srto_max;
if (setsockopt(sock->fd, IPPROTO_SCTP, SCTP_RTOINFO,
&rtoinfo, sizeof(rtoinfo)) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"setsockopt for SCTP_RTOINFO failed");
return OGS_ERROR;
}
ogs_debug("New RTO (initial:%d max:%d min:%d)",
rtoinfo.srto_initial,
rtoinfo.srto_max,
rtoinfo.srto_min);
return OGS_OK;
}
int ogs_sctp_initmsg(ogs_sock_t *sock, ogs_sockopt_t *option)
{
struct sctp_initmsg initmsg;
socklen_t socklen;
ogs_assert(sock);
ogs_assert(option);
ogs_assert(option->sctp.sinit_num_ostreams > 1);
memset(&initmsg, 0, sizeof(initmsg));
socklen = sizeof(initmsg);
if (getsockopt(sock->fd, IPPROTO_SCTP, SCTP_INITMSG,
&initmsg, &socklen) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"getsockopt for SCTP_INITMSG failed");
return OGS_ERROR;
}
ogs_debug("Old INITMSG (numout:%d maxin:%d maxattempt:%d maxinit_to:%d)",
initmsg.sinit_num_ostreams,
initmsg.sinit_max_instreams,
initmsg.sinit_max_attempts,
initmsg.sinit_max_init_timeo);
initmsg.sinit_num_ostreams = option->sctp.sinit_num_ostreams;
initmsg.sinit_max_instreams = option->sctp.sinit_max_instreams;
initmsg.sinit_max_attempts = option->sctp.sinit_max_attempts;
initmsg.sinit_max_init_timeo = option->sctp.sinit_max_init_timeo;
if (setsockopt(sock->fd, IPPROTO_SCTP, SCTP_INITMSG,
&initmsg, sizeof(initmsg)) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"setsockopt for SCTP_INITMSG failed");
return OGS_ERROR;
}
ogs_debug("New INITMSG (numout:%d maxin:%d maxattempt:%d maxinit_to:%d)",
initmsg.sinit_num_ostreams,
initmsg.sinit_max_instreams,
initmsg.sinit_max_attempts,
initmsg.sinit_max_init_timeo);
return OGS_OK;
}
int ogs_sctp_nodelay(ogs_sock_t *sock, int on)
{
ogs_assert(sock);
ogs_debug("Turn on SCTP_NODELAY");
if (setsockopt(sock->fd, IPPROTO_SCTP, SCTP_NODELAY,
&on, sizeof(on)) != 0) {
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
"setsockopt(IPPROTO_SCTP, SCTP_NODELAY) failed");
return OGS_ERROR;
}
return OGS_OK;
}
int ogs_sctp_so_linger(ogs_sock_t *sock, int l_linger)
{
ogs_assert(sock);
return ogs_so_linger(sock->fd, l_linger);
}