cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
sctp.c
Go to the documentation of this file.
1
26#include "webrtc_internal.h"
27
28#include <errno.h>
29#include <stdlib.h>
30#include <string.h>
31#include <time.h>
32#include <usrsctp.h>
33
35
38#define DCEP_ACK 0x02
39#define DCEP_OPEN 0x03
44#define PPID_DCEP 50
45#define PPID_STRING 51
46#define PPID_BINARY 53
47#define PPID_STRING_EMPTY 56
48#define PPID_BINARY_EMPTY 57
52#define CWIST_SCTP_PORT 5000
53
55#define CWIST_SCTP_STREAMS 1024
56
60#define CWIST_SCTP_MTU 1160
61
63#define CWIST_SCTP_HEARTBEAT_MS 10000
64
66#define CWIST_SCTP_SOCKBUF (1 << 20)
67
72static uint64_t g_sctp_clock_ms;
74static pthread_once_t g_sctp_once = PTHREAD_ONCE_INIT;
75
77static uint64_t sctp_now_ms(void) {
78 struct timespec ts;
80 return (uint64_t)ts.tv_sec * 1000ull + (uint64_t)ts.tv_nsec / 1000000ull;
81}
82
88static int sctp_global_output(void *addr, void *buffer, size_t length, uint8_t tos,
90 (void)tos;
91 (void)set_df;
92 struct cwist_webrtc_conn *conn = addr;
93 if (!conn) return -1;
94 cwist_webrtc_conn_sctp_out(conn, buffer, length);
95 return 0;
96}
97
99static void sctp_init_once(void) {
100 usrsctp_init_nothreads(0, &sctp_global_output, NULL);
101 /* RFC 8831: SCTP runs directly over DTLS, never over UDP tunneling. */
103 g_sctp_clock_ms = sctp_now_ms();
104}
105
107 pthread_once(&g_sctp_once, sctp_init_once);
108 return 0;
109}
110
111void cwist_sctp_tick(void) {
112 pthread_mutex_lock(&g_sctp_lock);
113 uint64_t now = sctp_now_ms();
114 if (now > g_sctp_clock_ms) {
115 uint64_t elapsed = now - g_sctp_clock_ms;
116 g_sctp_clock_ms = now;
118 }
119 pthread_mutex_unlock(&g_sctp_lock);
120}
121
122/* ---- per-stream state: 2 bits per stream id ---- */
123
125static bool ch_get(const struct cwist_webrtc_conn *conn, uint16_t ch, int bit) {
126 size_t off = (size_t)ch * 2 + (size_t)bit;
127 if (off / 8 >= conn->ch_bits_len) return false;
128 return (conn->ch_bits[off / 8] >> (off % 8)) & 1u;
129}
130
136static int ch_set(struct cwist_webrtc_conn *conn, uint16_t ch, int bit) {
137 size_t off = (size_t)ch * 2 + (size_t)bit;
138 if (off / 8 >= conn->ch_bits_len) {
139 uint32_t len = conn->ch_bits_len ? conn->ch_bits_len : 16;
140 while (off / 8 >= len) len *= 2;
141 uint8_t *bits = cwist_alloc(len);
142 if (!bits) return -1;
143 if (conn->ch_bits_len) memcpy(bits, conn->ch_bits, conn->ch_bits_len);
144 cwist_free(conn->ch_bits);
145 conn->ch_bits = bits;
146 conn->ch_bits_len = len;
147 }
148 conn->ch_bits[off / 8] |= (uint8_t)(1u << (off % 8));
149 return 0;
150}
151
153static struct socket *sctp_data_sock(const struct cwist_webrtc_conn *conn) {
154 return conn->sctp_acc ? conn->sctp_acc : conn->is_server_role ? NULL : conn->sctp_sock;
155}
156
161static int dcep_send(struct cwist_webrtc_conn *conn, uint16_t channel, uint8_t type,
162 const char *label) {
163 uint8_t msg[256];
164 size_t len = 0;
165 msg[len++] = type;
166 if (type == DCEP_OPEN) {
167 size_t label_len = label ? strlen(label) : 0;
168 if (label_len > 200) label_len = 200;
169 msg[len++] = 0x00; /* DATA_CHANNEL_RELIABLE */
170 msg[len++] = 0x00;
171 msg[len++] = 0x00; /* priority */
172 msg[len++] = 0x00;
173 msg[len++] = 0x00;
174 msg[len++] = 0x00; /* reliability parameter */
175 msg[len++] = 0x00;
176 msg[len++] = (uint8_t)(label_len >> 8);
177 msg[len++] = (uint8_t)label_len;
178 msg[len++] = 0x00;
179 msg[len++] = 0x00; /* protocol length 0 */
180 memcpy(msg + len, label, label_len);
181 len += label_len;
182 }
183
184 struct sctp_sndinfo info;
185 memset(&info, 0, sizeof(info));
186 info.snd_sid = channel;
187 info.snd_ppid = htonl(PPID_DCEP);
188 ssize_t rc = usrsctp_sendv(sctp_data_sock(conn), msg, len, NULL, 0, &info, sizeof(info),
190 if (rc >= 0) return 0;
191 return (errno == EWOULDBLOCK || errno == EAGAIN) ? 1 : -1;
192}
193
195static void dispatch_message(struct cwist_webrtc_conn *conn, uint16_t sid, uint32_t ppid,
196 const uint8_t *msg, size_t datalen) {
197 if (ppid == PPID_DCEP && datalen >= 1) {
198 if (msg[0] == DCEP_OPEN && datalen >= 12) {
199 uint16_t label_len = (uint16_t)((uint16_t)msg[8] << 8 | msg[9]);
200 char label[201];
201 size_t n = label_len;
202 if (n > sizeof(label) - 1) n = sizeof(label) - 1;
203 if (12 + n > datalen) n = datalen - 12;
204 memcpy(label, msg + 12, n);
205 label[n] = '\0';
206 ch_set(conn, sid, 0);
207 dcep_send(conn, sid, DCEP_ACK, NULL);
209 }
210 /* ACK (0x02): the channel we opened is confirmed; nothing to do. */
211 } else if (ppid == PPID_BINARY) {
212 cwist_webrtc_conn_on_message(conn, sid, msg, datalen, 0);
213 } else if (ppid == PPID_STRING) {
214 cwist_webrtc_conn_on_message(conn, sid, msg, datalen, 1);
215 } else if (ppid == PPID_BINARY_EMPTY || ppid == PPID_STRING_EMPTY) {
216 /* SCTP cannot carry a zero-length message: the 0x00 is padding. */
218 }
219}
220
222static void sctp_apply_sockopts(struct socket *sock) {
223 int on = 1;
227
228 /* close() aborts instead of lingering in SHUTDOWN: no timer can fire for
229 * the association after its conn is gone. */
230 struct linger lg = {.l_onoff = 1, .l_linger = 0};
232
233 int buf = CWIST_SCTP_SOCKBUF;
234 usrsctp_setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf, sizeof(buf));
235 usrsctp_setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf));
236
237 struct sctp_initmsg init;
238 memset(&init, 0, sizeof(init));
239 init.sinit_num_ostreams = CWIST_SCTP_STREAMS;
240 init.sinit_max_instreams = CWIST_SCTP_STREAMS;
241 usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_INITMSG, &init, sizeof(init));
242
243 /* Fixed path MTU: the DTLS link MTU is known and PMTU probes would only
244 * get lost inside the tunnel. */
245 struct sctp_paddrparams pp;
246 memset(&pp, 0, sizeof(pp));
247 pp.spp_assoc_id = SCTP_FUTURE_ASSOC;
248 pp.spp_flags = SPP_PMTUD_DISABLE | SPP_HB_ENABLE;
249 pp.spp_pathmtu = CWIST_SCTP_MTU;
250 /* Heartbeats keep both directions talking well inside the 30 s liveness
251 * window even when the peer sends no ICE consent checks. */
252 pp.spp_hbinterval = CWIST_SCTP_HEARTBEAT_MS;
254
255 /* Association loss (abort, shutdown) arrives as a notification so the
256 * conn is closed right away instead of waiting for the liveness timer. */
257 struct sctp_event ev;
258 memset(&ev, 0, sizeof(ev));
259 ev.se_assoc_id = SCTP_ALL_ASSOC;
260 ev.se_on = 1;
261 ev.se_type = SCTP_ASSOC_CHANGE;
263}
264
268 if (!conn->sctp_sock) return -1;
269 sctp_apply_sockopts(conn->sctp_sock);
271
272 struct sockaddr_conn local;
273 memset(&local, 0, sizeof(local));
274 local.sconn_family = AF_CONN;
275 local.sconn_port = htons(CWIST_SCTP_PORT);
276 local.sconn_addr = conn;
277 if (usrsctp_bind(conn->sctp_sock, (struct sockaddr *)&local, sizeof(local)) < 0) goto fail;
278
279 if (conn->is_server_role) {
280 /* Passive side must accept the incoming SCTP association. */
281 if (usrsctp_listen(conn->sctp_sock, 1) < 0) goto fail;
282 } else {
283 /* Active side initiates the SCTP association (sends the INIT). */
284 struct sockaddr_conn remote = local;
285 if (usrsctp_connect(conn->sctp_sock, (struct sockaddr *)&remote, sizeof(remote)) < 0 &&
287 goto fail;
288 }
289 return 0;
290
291fail:
293 return -1;
294}
295
297 if (!conn->sctp_sock && !conn->sctp_acc) return;
298 pthread_mutex_lock(&g_sctp_lock);
299 if (conn->sctp_acc) {
300 usrsctp_close(conn->sctp_acc);
301 conn->sctp_acc = NULL;
302 }
303 if (conn->sctp_sock) {
305 conn->sctp_sock = NULL;
306 }
308 pthread_mutex_unlock(&g_sctp_lock);
309 cwist_free(conn->ch_bits);
310 conn->ch_bits = NULL;
311 conn->ch_bits_len = 0;
312 cwist_free(conn->rx_partial);
313 conn->rx_partial = NULL;
314 conn->rx_partial_len = conn->rx_partial_cap = 0;
315}
316
317void cwist_sctp_conn_input(struct cwist_webrtc_conn *conn, const uint8_t *data, size_t len) {
318 if (conn->sctp_sock || conn->sctp_acc) usrsctp_conninput(conn, data, len, 0);
319}
320
325static int rx_partial_append(struct cwist_webrtc_conn *conn, const uint8_t *data, size_t len) {
326 size_t need = conn->rx_partial_len + len;
327 if (need > CWIST_WEBRTC_MAX_MESSAGE) return -1;
328 if (need > conn->rx_partial_cap) {
329 size_t cap = conn->rx_partial_cap ? conn->rx_partial_cap : 65536;
330 while (cap < need) cap *= 2;
331 uint8_t *buf = cwist_alloc(cap);
332 if (!buf) return -1;
333 if (conn->rx_partial_len) memcpy(buf, conn->rx_partial, conn->rx_partial_len);
334 cwist_free(conn->rx_partial);
335 conn->rx_partial = buf;
336 conn->rx_partial_cap = cap;
337 }
338 memcpy(conn->rx_partial + conn->rx_partial_len, data, len);
339 conn->rx_partial_len = need;
340 return 0;
341}
342
344 if (!conn->sctp_sock && !conn->sctp_acc) return 0;
345 if (conn->is_server_role && !conn->sctp_acc) {
346 conn->sctp_acc = usrsctp_accept(conn->sctp_sock, NULL, NULL);
347 if (!conn->sctp_acc) return 0;
348 sctp_apply_sockopts(conn->sctp_acc);
349 /* One association per conn: the listener has done its job. */
350 pthread_mutex_lock(&g_sctp_lock);
352 conn->sctp_sock = NULL;
353 pthread_mutex_unlock(&g_sctp_lock);
354 }
355 struct socket *sock = sctp_data_sock(conn);
356 if (!sock) return 0;
357 uint8_t *buf = conn->ctx->sctp_buf;
358 for (;;) {
359 struct sctp_rcvinfo rcv;
360 socklen_t infolen = sizeof(rcv);
361 unsigned int infotype = 0;
362 int msg_flags = 0;
363 struct sockaddr_conn from;
364 socklen_t fromlen = sizeof(from);
367 if (n < 0) return (errno == EWOULDBLOCK || errno == EAGAIN) ? 0 : -1;
368 if (n == 0) return -1; /* peer shut the association down */
370 const union sctp_notification *note = (const union sctp_notification *)buf;
371 if ((size_t)n >= sizeof(note->sn_header) &&
372 note->sn_header.sn_type == SCTP_ASSOC_CHANGE &&
373 (size_t)n >= sizeof(note->sn_assoc_change)) {
374 uint16_t state = note->sn_assoc_change.sac_state;
375 if (state == SCTP_COMM_LOST || state == SCTP_SHUTDOWN_COMP ||
376 state == SCTP_CANT_STR_ASSOC)
377 return -1;
378 }
379 continue;
380 }
381 if (infotype != SCTP_RECVV_RCVINFO || infolen < (socklen_t)sizeof(rcv)) continue;
382 if (!(msg_flags & MSG_EOR)) {
383 /* Partial delivery: keep the piece until the end of the message. */
384 if (rx_partial_append(conn, buf, (size_t)n) < 0) return -1;
385 continue;
386 }
387 if (conn->rx_partial_len) {
388 if (rx_partial_append(conn, buf, (size_t)n) < 0) return -1;
389 size_t total = conn->rx_partial_len;
390 conn->rx_partial_len = 0;
391 dispatch_message(conn, rcv.rcv_sid, ntohl(rcv.rcv_ppid), conn->rx_partial, total);
392 } else {
393 dispatch_message(conn, rcv.rcv_sid, ntohl(rcv.rcv_ppid), buf, (size_t)n);
394 }
395 if (conn->close_requested || atomic_load(&conn->detached)) return 0;
396 }
397}
398
400 if (conn->is_server_role) return conn->sctp_acc != NULL;
401 if (!conn->sctp_sock) return false;
402 struct sctp_status status;
403 socklen_t slen = sizeof(status);
404 memset(&status, 0, sizeof(status));
405 if (usrsctp_getsockopt(conn->sctp_sock, IPPROTO_SCTP, SCTP_STATUS, &status, &slen) < 0)
406 return false;
407 return status.sstat_state == SCTP_ESTABLISHED;
408}
409
410int cwist_sctp_send(struct cwist_webrtc_conn *conn, uint16_t channel, const uint8_t *data,
411 size_t len, int is_string) {
412 struct socket *sock = sctp_data_sock(conn);
413 if (!sock || !conn->sctp_ready || channel >= CWIST_SCTP_STREAMS) return -1;
414 if (!ch_get(conn, channel, 0) && !ch_get(conn, channel, 1)) {
415 /* Brand-new channel opened by us: announce it with DCEP OPEN. */
416 char label[32];
417 snprintf(label, sizeof(label), "dc%u", channel);
418 int rc = dcep_send(conn, channel, DCEP_OPEN, label);
419 if (rc != 0) return rc;
420 if (ch_set(conn, channel, 1) < 0) return -1;
421 }
422 struct sctp_sndinfo info;
423 memset(&info, 0, sizeof(info));
424 info.snd_sid = channel;
425 static const uint8_t empty_pad = 0;
426 if (len == 0) {
427 /* RFC 8831 section 6.6: an empty message is one byte with an
428 * "empty" PPID. */
429 info.snd_ppid = htonl(is_string ? PPID_STRING_EMPTY : PPID_BINARY_EMPTY);
430 data = &empty_pad;
431 len = 1;
432 } else {
433 info.snd_ppid = htonl(is_string ? PPID_STRING : PPID_BINARY);
434 }
435 ssize_t rc =
436 usrsctp_sendv(sock, data, len, NULL, 0, &info, sizeof(info), SCTP_SENDV_SNDINFO, 0);
437 if (rc >= 0) return 0;
438 return (errno == EWOULDBLOCK || errno == EAGAIN) ? 1 : -1;
439}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
size_t cap
Definition otel.c:56
int cwist_sctp_conn_open(struct cwist_webrtc_conn *conn)
Create, bind and listen/connect the conn's SCTP socket.
Definition sctp.c:265
#define CWIST_SCTP_SOCKBUF
Definition sctp.c:66
#define CWIST_SCTP_HEARTBEAT_MS
Definition sctp.c:63
int cwist_sctp_global_init(void)
One-time usrsctp initialisation (no threads, AF_CONN only).
Definition sctp.c:106
int cwist_sctp_conn_drain(struct cwist_webrtc_conn *conn)
Accept (server role), then deliver every complete inbound message.
Definition sctp.c:343
#define PPID_STRING_EMPTY
Definition sctp.c:47
void cwist_sctp_tick(void)
Advance usrsctp's process-wide timer clock to now. Any thread.
Definition sctp.c:111
#define DCEP_OPEN
Definition sctp.c:39
#define CWIST_SCTP_MTU
Definition sctp.c:60
#define DCEP_ACK
Definition sctp.c:38
bool cwist_sctp_assoc_established(struct cwist_webrtc_conn *conn)
true once the SCTP association is up.
Definition sctp.c:399
int cwist_sctp_send(struct cwist_webrtc_conn *conn, uint16_t channel, const uint8_t *data, size_t len, int is_string)
Send one message, announcing the channel with DCEP OPEN first if we are opening it.
Definition sctp.c:410
#define CWIST_SCTP_PORT
Definition sctp.c:52
#define PPID_BINARY_EMPTY
Definition sctp.c:48
#define PPID_BINARY
Definition sctp.c:46
void cwist_sctp_conn_close(struct cwist_webrtc_conn *conn)
Abort the association and release SCTP state. Idempotent.
Definition sctp.c:296
#define PPID_STRING
Definition sctp.c:45
#define CWIST_SCTP_STREAMS
Definition sctp.c:55
#define PPID_DCEP
Definition sctp.c:44
void cwist_sctp_conn_input(struct cwist_webrtc_conn *conn, const uint8_t *data, size_t len)
Feed one decrypted SCTP packet to usrsctp.
Definition sctp.c:317
One peer: ICE state, DTLS session, SCTP association.
cwist_webrtc_ctx * ctx
struct socket * sctp_sock
struct socket * sctp_acc
void cwist_webrtc_conn_on_channel_open(struct cwist_webrtc_conn *conn, uint16_t channel, const char *label)
Report a channel the peer opened to the channel handler.
Definition webrtc.c:548
void cwist_webrtc_conn_sctp_out(struct cwist_webrtc_conn *conn, const void *buffer, size_t len)
usrsctp output for conn; marshalled to the owner thread when needed.
Definition webrtc.c:577
void cwist_webrtc_conn_on_message(struct cwist_webrtc_conn *conn, uint16_t channel, const uint8_t *data, size_t len, int is_string)
Deliver a complete inbound message to the message handler.
Definition webrtc.c:540
Internal types and cross-file helpers for the WebRTC DataChannel module (ICE-lite + DTLS + SCTP over ...
#define CWIST_WEBRTC_MAX_MESSAGE