1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
|
/* (C) 2011 by Pablo Neira Ayuso <pablo@gnumonks.org>
* (C) 2015-2016 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 <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>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/select.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/osmo_io.h>
#include <osmocom/core/panic.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/socket.h>
#include <osmocom/netif/stream.h>
#include <osmocom/netif/stream_private.h>
#include "config.h"
#include <osmocom/netif/sctp.h>
/*! \cond private */
#ifdef HAVE_LIBSCTP
/* 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;
}
static unsigned 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;
rc = getsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, buf, &buf_len);
close(sd);
if (rc < 0)
return rc;
sctp_sockopt_event_subscribe_size = (unsigned int)buf_len;
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);
}
}
#endif // HAVE_LIBSCTP
int stream_sctp_sock_activate_events(int fd)
{
#ifdef HAVE_LIBSCTP
struct sctp_event_subscribe event;
int rc;
/* 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 */
rc = sctp_setsockopt_events_linux_workaround(fd, &event);
if (rc < 0)
LOGP(DLINP, LOGL_ERROR, "couldn't activate SCTP events on FD %u\n", fd);
return rc;
#else
return -1;
#endif
}
int stream_setsockopt_nodelay(int fd, int proto, int on)
{
int rc;
switch (proto) {
#ifdef HAVE_LIBSCTP
case IPPROTO_SCTP:
rc = setsockopt(fd, IPPROTO_SCTP, SCTP_NODELAY, &on, sizeof(on));
break;
#endif
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;
}
#ifdef HAVE_LIBSCTP
static int stream_sctp_recvmsg_trailer(const char *log_pfx, struct msgb *msg, int ret, const struct sctp_sndrcvinfo *sinfo, int flags)
{
msgb_sctp_msg_flags(msg) = 0;
if (OSMO_LIKELY(sinfo)) {
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 *) msg->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;
}
if (OSMO_UNLIKELY(ret > 0 && !sinfo))
LOGP(DLINP, LOGL_ERROR, "%s sctp_recvmsg without SNDRCV cmsg?!?\n", log_pfx);
return ret;
}
/*! wrapper for regular synchronous sctp_recvmsg(3) */
int stream_sctp_recvmsg_wrapper(int fd, struct msgb *msg, const char *log_pfx)
{
struct sctp_sndrcvinfo sinfo;
int flags = 0;
int ret;
ret = sctp_recvmsg(fd, msg->tail, msgb_tailroom(msg), NULL, NULL, &sinfo, &flags);
return stream_sctp_recvmsg_trailer(log_pfx, msg, ret, &sinfo, flags);
}
/*! wrapper for osmo_io asynchronous recvmsg response */
int stream_iofd_sctp_recvmsg_trailer(struct osmo_io_fd *iofd, struct msgb *msg, int ret, const struct msghdr *msgh)
{
const struct sctp_sndrcvinfo *sinfo = NULL;
struct cmsghdr *cmsg = NULL;
for (cmsg = CMSG_FIRSTHDR((struct msghdr *) msgh); cmsg != NULL;
cmsg = CMSG_NXTHDR((struct msghdr *) msgh, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_SCTP && cmsg->cmsg_type == SCTP_SNDRCV) {
sinfo = (const struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
break;
}
}
return stream_sctp_recvmsg_trailer(osmo_iofd_get_name(iofd), msg, ret, sinfo, msgh->msg_flags);
}
/*! Send a message through a connected SCTP socket, similar to sctp_sendmsg().
*
* Appends the message to the internal transmit queue.
* If the function returns success (0), it will take ownership of the msgb and
* internally call msgb_free() after the write request completes.
* In case of an error the msgb needs to be freed by the caller.
*
* \param[in] iofd file descriptor to write to
* \param[in] msg message buffer to send; uses msgb_sctp_ppid/msg_sctp_stream
* \param[in] sendmsg_flags Flags to pass to the send call
* \returns 0 in case of success; a negative value in case of error
*/
int stream_iofd_sctp_send_msgb(struct osmo_io_fd *iofd, struct msgb *msg, int sendmsg_flags)
{
struct msghdr outmsg = {};
char outcmsg[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))];
struct sctp_sndrcvinfo *sinfo;
struct cmsghdr *cmsg;
outmsg.msg_control = outcmsg;
outmsg.msg_controllen = sizeof(outcmsg);
cmsg = CMSG_FIRSTHDR(&outmsg);
cmsg->cmsg_level = IPPROTO_SCTP;
cmsg->cmsg_type = SCTP_SNDRCV;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo));
outmsg.msg_controllen = cmsg->cmsg_len;
sinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
memset(sinfo, 0, sizeof(struct sctp_sndrcvinfo));
sinfo->sinfo_ppid = htonl(msgb_sctp_ppid(msg));
sinfo->sinfo_stream = msgb_sctp_stream(msg);
return osmo_iofd_sendmsg_msgb(iofd, msg, sendmsg_flags, &outmsg);
}
#endif
/*! \endccond */
|