From 7af7598086e5a3f9a75507cd6f9907a98faf3d9d Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Sun, 25 Oct 2026 02:41:48 +0200 Subject: Add sub system to abstract low level multi platform socket handling ourselves --- ot_socket.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 ot_socket.c (limited to 'ot_socket.c') diff --git a/ot_socket.c b/ot_socket.c new file mode 100644 index 0000000..e26ad52 --- /dev/null +++ b/ot_socket.c @@ -0,0 +1,256 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include +#include +#include +#include +#include + +/* Opentracker */ +#include "ot_ip.h" +#include "ot_socket.h" + +int g_socket_reuseport; + +static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { + memset(sa, 0, sizeof(*sa)); + sa->sin6_family = AF_INET6; + sa->sin6_port = htons(port); + memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6)); +} + +/* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped + sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so + both forms have to be read. */ +static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) { + if (ss->ss_family == AF_INET) { + const struct sockaddr_in *sa = (const struct sockaddr_in *)ss; + + memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); + memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4); + *port = ntohs(sa->sin_port); + } else { + const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss; + + memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6)); + *port = ntohs(sa->sin6_port); + } +} + +static int set_nonblocking(int s) { + int flags = fcntl(s, F_GETFL, 0); + + if (flags == -1) + return -1; + return fcntl(s, F_SETFL, flags | O_NONBLOCK); +} + +/* Close without letting the failure we are about to report be overwritten */ +static int close_and_fail(int s) { + int saved = errno; + close(s); + errno = saved; + return -1; +} + +static int socket_v6(int type) { + int s = socket(PF_INET6, type, 0); + int zero = 0; + + if (s == -1) + return -1; + + /* A tracker that answered only one family would be half a tracker */ + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1) + return close_and_fail(s); + + if (set_nonblocking(s) == -1) + return close_and_fail(s); + + return s; +} + +static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port) { + struct sockaddr_in6 sa; + int one = 1; + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#ifdef SO_REUSEPORT + if (g_socket_reuseport) + setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); +#endif + + sockaddr_from_ip(&sa, ip, port); + return bind(s, (struct sockaddr *)&sa, sizeof(sa)); +} + +int socket_tcp_listen(const ot_ip6 ip, uint16_t port) { + int s = socket_v6(SOCK_STREAM); + + if (s == -1) + return -1; + if (socket_bind_v6(s, ip, port) == -1 || listen(s, SOMAXCONN) == -1) + return close_and_fail(s); + + return s; +} + +int socket_udp_bind(const ot_ip6 ip, uint16_t port) { + int s = socket_v6(SOCK_DGRAM); + + if (s == -1) + return -1; + if (socket_bind_v6(s, ip, port) == -1) + return close_and_fail(s); + + return s; +} + +int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) { + struct sockaddr_in6 sa; + int s = socket_v6(SOCK_STREAM); + + if (s == -1) + return -1; + if (socket_bind_v6(s, srcip, srcport) == -1) + return close_and_fail(s); + + sockaddr_from_ip(&sa, dstip, dstport); + if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK) + return close_and_fail(s); + + return s; +} + +int socket_is_connected(int s) { + int error = 0; + socklen_t len = sizeof(error); + + if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) + return 0; + if (error) { + errno = error; + return 0; + } + return 1; +} + +int socket_accept(int s, ot_ip6 ip, uint16_t *port) { + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + int fd; + + memset(&ss, 0, sizeof(ss)); + if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1) + return -1; + + /* Linux hands over a blocking socket whatever the listening socket was, the + BSDs and macOS inherit the listener's flags, so say which one we want. */ + if (set_nonblocking(fd) == -1) + return close_and_fail(fd); + + ip_from_sockaddr(ip, port, &ss); + return fd; +} + +ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) { + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + ssize_t received; + + memset(&ss, 0, sizeof(ss)); + received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen); + if (received == -1) + return -1; + + ip_from_sockaddr(ip, port, &ss); + return received; +} + +ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) { + struct sockaddr_in6 sa; + + sockaddr_from_ip(&sa, ip, port); + return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa)); +} + +#ifdef WANT_SYNC_LIVE +static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) { + memset(sa, 0, sizeof(*sa)); + sa->sin_family = AF_INET; + sa->sin_port = htons(port); + memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); +} + +static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { + struct sockaddr_in sa; + int s, one = 1; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#ifdef SO_REUSEPORT + if (g_socket_reuseport) + setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); +#endif + + sockaddr4_from_ip(&sa, ip, port); + if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) + return close_and_fail(s); + + return s; +} + +int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) { + struct ip_mreq mreq; + ot_ip6 any; + int s; + + /* The group is what is listened to, so the socket takes the wildcard and + the join names the interface to receive it on */ + memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); + memset(any + sizeof(ip_v4mapped_prefix), 0, 4); + + if ((s = socket_v4_bind(any, port)) == -1) + return -1; + + memset(&mreq, 0, sizeof(mreq)); + memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4); + memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4); + if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) + return close_and_fail(s); + + return s; +} + +int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) { + /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux + accepts either that or an int. */ + unsigned char ttl = 1, loop = (unsigned char)(loopback != 0); + int s = socket_v4_bind(interface, port); + + if (s == -1) + return -1; + if (set_nonblocking(s) == -1) + return close_and_fail(s); + + if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 || + setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1) + return close_and_fail(s); + + return s; +} + +ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) { + struct sockaddr_in sa; + + sockaddr4_from_ip(&sa, group, port); + return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa)); +} +#endif -- cgit v1.3