sctp-echo - SCTP-over-UDP echo server/client for creating reliable tunnels over UDP.

git clone https://benconnors.ca/git-repos/sctp-echo

About | Log | Files | Refs

commit e18302e83c243b1166fd1909c9054b1f55fc08f0
parent 6d3b7745145321da1614f9ea8150c1768463f08e
Author: Ben Connors <benconnors@outlook.com>
Date:   Wed,  5 Aug 2026 16:11:22 -0400

Fix throughput problem

Diffstat:
Msrc/sctp_echo.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 61 insertions(+), 14 deletions(-)

diff --git a/src/sctp_echo.c b/src/sctp_echo.c @@ -8,15 +8,29 @@ #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> +#include <math.h> +#include <pthread.h> +#include <errno.h> + +uint32_t min(uint32_t a, uint32_t b) { + return (a > b) ? b : a; +} int done = 0; +int ready = 0; + +const size_t send_buffer_size = 1024; + +pthread_mutex_t lock; int receive_cb(struct socket *sock, union sctp_sockstore addr, void *data, size_t datalen, struct sctp_rcvinfo info, int flags, void *ulp_info) { - // Read data from SCTP and write to stdout if (data == NULL) { // Closed connection done = 1; usrsctp_close(sock); + + // The mainthread might be stuck, unlock it so it can exit + pthread_mutex_unlock(&lock); } else { if (write(fileno(stdout), data, datalen) < 0) { perror("write"); @@ -26,6 +40,26 @@ int receive_cb(struct socket *sock, union sctp_sockstore addr, void *data, size_ return 1; } +int send_cb(struct socket *sock, uint32_t sb_free, void *ulp_info) { + // Callback called when the socket has space in its send cue. When this function is called is + // rather bizarre: + // + // 1. Once called, this function is not called again until something has been added to the send + // queue; + // 2. On the client side, this will be called BEFORE a connection has been established; + // 3. If it blocks, at least on the client we will get stuck in the establishment process until + // it unblocks; and + // 4. On the server this is never called after connection establishment until something has + // been added to the send queue. + // + // The easiest solution here is to do all the sending in the main thread and have this unlock + // a mutex that the main thread will try to lock whenever it gets `EAGAIN` from usrsctp. + + pthread_mutex_unlock(&lock); + + return 1; +} + int main(int argc, char **argv) { if (argc < 2 || !(strncmp(argv[1], "-c", 3) == 0 || strncmp(argv[1], "-s", 3) == 0)) { fprintf(stderr, "Usage: %s [-c|-s] ...\n", argv[0]); @@ -35,6 +69,8 @@ int main(int argc, char **argv) { const bool is_server = strncmp(argv[1], "-s", 3) == 0; const bool is_client = !is_server; + pthread_mutex_init(&lock, NULL); + int local_udp_port; int remote_sctp_port; int remote_ip; @@ -77,18 +113,14 @@ int main(int argc, char **argv) { // This is the local UDP encapsulation port usrsctp_init(local_udp_port, NULL, NULL); - // The null fields at the end are for the callback API; we use the data received - // callback since usrsctp doesn't seem to support using poll/select. Since we - // only deal with two incoming streams (one usrsctp connection and stdin) we can - // get away with polling stdin directly and letting usrsctp run the callback for - // its data + // We use the callback API since usrsctp doesn't seem to support using poll/select sock = usrsctp_socket( PF_INET, SOCK_STREAM, IPPROTO_SCTP, receive_cb, - NULL, - 0, + send_cb, + send_buffer_size, NULL ); @@ -122,7 +154,7 @@ int main(int argc, char **argv) { // This is the remote address struct sockaddr_in addr; - socklen_t addr_len; + socklen_t addr_len = sizeof(addr); if (is_client) { // Set the remote address @@ -153,7 +185,7 @@ int main(int argc, char **argv) { } if (remote_ip != 0 && (addr.sin_addr.s_addr != remote_ip || addr.sin_port != htons(remote_sctp_port))) { - fprintf(stderr, "Bad IP/port tried: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + fprintf(stderr, "Bad IP/port tried: %d %s:%d\n", addr_len, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); usrsctp_close(sock); } else { // Valid connection @@ -167,8 +199,10 @@ int main(int argc, char **argv) { usrsctp_listen(listen_sock, 0); } + ready = 1; + // Mainloop: read from stdin and write to the SCTP socket - char buff[1024]; + char buff[send_buffer_size]; int data_read; while (done == 0) { @@ -177,9 +211,22 @@ int main(int argc, char **argv) { if (data_read == 0) { // EOF done = 1; - } else if (usrsctp_sendv(sock, buff, data_read, NULL, 0, NULL, 0, 0, 0) < 0) { - perror("sendv"); - done = 1; + } else { + while (usrsctp_sendv(sock, buff, data_read, NULL, 0, NULL, 0, 0, 0) < 0) { + // usrsctp has a rather small send buffer, so on e.g. a file transfer we will + // run into EAGAIN. Using usrsctp_set_non_blocking(sock, 0) doesn't seem to + // have any effect on this. + if (errno == EAGAIN) { + pthread_mutex_lock(&lock); + if (done == 1) { + break; + } + } else { + perror("sendv"); + done = 1; + break; + } + } } } }