diff options
Diffstat (limited to 'libhw_cr/host_net.c')
-rw-r--r-- | libhw_cr/host_net.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/libhw_cr/host_net.c b/libhw_cr/host_net.c new file mode 100644 index 0000000..f1c988c --- /dev/null +++ b/libhw_cr/host_net.c @@ -0,0 +1,534 @@ +/* libhw_cr/host_net.c - <libhw/generic/net.h> implementation for hosted glibc + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */ +/* misc */ +#include <errno.h> /* for errno, EAGAIN, EINVAL */ +#include <error.h> /* for error(3gnu) */ +#include <stdlib.h> /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */ +#include <unistd.h> /* for read(), write() */ +/* net */ +#include <arpa/inet.h> /* for htons(3p) */ +#include <netinet/in.h> /* for struct sockaddr_in */ +#include <sys/socket.h> /* for struct sockaddr{,_storage}, SOCK_*, SOL_*, SO_*, socket(), setsockopt(), bind(), listen(), accept() */ +/* async */ +#include <pthread.h> /* for pthread_* */ +#include <signal.h> /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SA_SIGINFO */ + +#include <libcr/coroutine.h> +#include <libmisc/assert.h> +#include <libmisc/macro.h> +#include <libobj/obj.h> + +#include <libhw/generic/alarmclock.h> + +#define IMPLEMENTATION_FOR_LIBHW_HOST_NET_H YES +#include <libhw/host_net.h> + +#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_us_time() */ + +LO_IMPLEMENTATION_C(io_closer, struct hostnet_tcp_listener, hostnet_tcplist, static) +LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist, static) + +LO_IMPLEMENTATION_C(io_reader, struct _hostnet_tcp_conn, hostnet_tcp, static) +LO_IMPLEMENTATION_C(io_writer, struct _hostnet_tcp_conn, hostnet_tcp, static) +LO_IMPLEMENTATION_C(io_readwriter, struct _hostnet_tcp_conn, hostnet_tcp, static) +LO_IMPLEMENTATION_C(io_closer, struct _hostnet_tcp_conn, hostnet_tcp, static) +LO_IMPLEMENTATION_C(io_bidi_closer, struct _hostnet_tcp_conn, hostnet_tcp, static) +LO_IMPLEMENTATION_C(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp, static) + +LO_IMPLEMENTATION_C(io_closer, struct hostnet_udp_conn, hostnet_udp, static) +LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp, static) + +/* common *********************************************************************/ + +static int hostnet_sig_io = 0; + +static void hostnet_handle_sig_io(int LM_UNUSED(sig), siginfo_t *info, void *LM_UNUSED(ucontext)) { + cr_unpause_from_intrhandler((cid_t)info->si_value.sival_int); +} + +static void hostnet_init(void) { + struct sigaction action = {0}; + + if (hostnet_sig_io) + return; + + hostnet_sig_io = host_sigrt_alloc(); + + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = hostnet_handle_sig_io; + if (sigaction(hostnet_sig_io, &action, NULL) < 0) + error(1, errno, "sigaction"); +} + +#define WAKE_COROUTINE(args) do { \ + int r; \ + union sigval val = {0}; \ + val.sival_int = (int)((args)->cr_coroutine); \ + do { \ + r = pthread_sigqueue((args)->cr_thread, \ + hostnet_sig_io, \ + val); \ + assert(r == 0 || r == EAGAIN); \ + } while (r == EAGAIN); \ + } while (0) + +static inline bool RUN_PTHREAD(void *(*fn)(void *), void *args) { + pthread_t thread; + bool saved = cr_save_and_disable_interrupts(); + if (pthread_create(&thread, NULL, fn, args)) + return true; + cr_pause_and_yield(); + cr_restore_interrupts(saved); + if (pthread_join(thread, NULL)) + return true; + return false; +} + +enum hostnet_timeout_op { + OP_NONE, + OP_SEND, + OP_RECV, +}; + +static inline ssize_t hostnet_map_negerrno(ssize_t v, enum hostnet_timeout_op op) { + if (v >= 0) + return v; + switch (v) { + case -EHOSTUNREACH: + return -NET_EARP_TIMEOUT; + case -ETIMEDOUT: + switch (op) { + case OP_NONE: + assert_notreached("impossible ETIMEDOUT"); + case OP_SEND: + return -NET_EACK_TIMEOUT; + case OP_RECV: + return -NET_ERECV_TIMEOUT; + } + assert_notreached("invalid timeout op"); + case -EBADF: + return -NET_ECLOSED; + case -EMSGSIZE: + return -NET_EMSGSIZE; + default: + return -NET_EOTHER; + } +} + +/* TCP init() ( AKA socket(3) + listen(3) )************************************/ + +void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) { + int listenerfd; + union { + struct sockaddr_in in; + struct sockaddr gen; + } addr = { 0 }; + + hostnet_init(); + + addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(port); + listenerfd = socket(AF_INET, SOCK_STREAM, 0); + if (listenerfd < 0) + error(1, errno, "socket"); + if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) + error(1, errno, "setsockopt(fd=%d, SO_REUSEADDR=1)", listenerfd); + if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)) < 0) + error(1, errno, "setsockopt(fd=%d, SO_REUSEPORT=1)", listenerfd); + if (bind(listenerfd, &addr.gen, sizeof addr) < 0) + error(1, errno, "bind(fd=%d)", listenerfd); + if (listen(listenerfd, 0) < 0) + error(1, errno, "listen(fd=%d)", listenerfd); + + self->fd = listenerfd; +} + +/* TCP listener accept() ******************************************************/ + +struct hostnet_pthread_accept_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int listenerfd; + + int *ret_connfd; +}; + +static void *hostnet_pthread_accept(void *_args) { + struct hostnet_pthread_accept_args *args = _args; + *(args->ret_connfd) = accept(args->listenerfd, NULL, NULL); + if (*(args->ret_connfd) < 0) + *(args->ret_connfd) = -errno; + WAKE_COROUTINE(args); + return NULL; +} + +static lo_interface net_stream_conn hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) { + assert(listener); + + int ret_connfd; + struct hostnet_pthread_accept_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + .listenerfd = listener->fd, + .ret_connfd = &ret_connfd, + }; + if (RUN_PTHREAD(hostnet_pthread_accept, &args)) + return LO_NULL(net_stream_conn); + + if (ret_connfd < 0) + return LO_NULL(net_stream_conn); + + listener->active_conn.fd = ret_connfd; + listener->active_conn.read_deadline_ns = 0; + return lo_box_hostnet_tcp_as_net_stream_conn(&listener->active_conn); +} + +/* TCP listener close() *******************************************************/ + +static int hostnet_tcplist_close(struct hostnet_tcp_listener *listener) { + assert(listener); + + return hostnet_map_negerrno(shutdown(listener->fd, SHUT_RDWR) ? -errno : 0, OP_NONE); +} + +/* TCP read() *****************************************************************/ + +static void hostnet_tcp_set_read_deadline(struct _hostnet_tcp_conn *conn, uint64_t ts_ns) { + assert(conn); + + conn->read_deadline_ns = ts_ns; +} + +struct hostnet_pthread_readv_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + struct timeval timeout; + const struct iovec *iov; + int iovcnt; + + ssize_t *ret; +}; + +static void *hostnet_pthread_readv(void *_args) { + struct hostnet_pthread_readv_args *args = _args; + + *(args->ret) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, + &args->timeout, sizeof(args->timeout)); + if (*(args->ret) < 0) + goto end; + + *(args->ret) = readv(args->connfd, args->iov, args->iovcnt); + if (*(args->ret) < 0) + goto end; + + end: + if (*(args->ret) < 0) + *(args->ret) = hostnet_map_negerrno(-errno, OP_SEND); + WAKE_COROUTINE(args); + return NULL; +} + +static ssize_t hostnet_tcp_readv(struct _hostnet_tcp_conn *conn, const struct iovec *iov, int iovcnt) { + assert(conn); + assert(iov); + assert(iovcnt > 0); + + ssize_t ret; + struct hostnet_pthread_readv_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .iov = iov, + .iovcnt = iovcnt, + + .ret = &ret, + }; + if (conn->read_deadline_ns) { + uint64_t now_ns = LO_CALL(bootclock, get_time_ns); + if (conn->read_deadline_ns < now_ns) + return -NET_ERECV_TIMEOUT; + args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns); + } else { + args.timeout = (host_us_time_t){0}; + } + + if (RUN_PTHREAD(hostnet_pthread_readv, &args)) + return -NET_ETHREAD; + return ret; +} + +/* TCP write() ****************************************************************/ + +struct hostnet_pthread_writev_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + const struct iovec *iov; + int iovcnt; + + ssize_t *ret; +}; + +static void *hostnet_pthread_writev(void *_args) { + struct hostnet_pthread_writev_args *args = _args; + + size_t count = 0; + struct iovec *iov = alloca(sizeof(struct iovec)*args->iovcnt); + for (int i = 0; i < args->iovcnt; i++) { + iov[i] = args->iov[i]; + count += args->iov[i].iov_len; + } + int iovcnt = args->iovcnt; + + size_t done = 0; + while (done < count) { + ssize_t r = writev(args->connfd, iov, iovcnt); + if (r < 0) { + hostnet_map_negerrno(-errno, OP_RECV); + break; + } + done += r; + while (iovcnt && (size_t)r >= iov[0].iov_len) { + r -= iov[0].iov_len; + iov++; + iovcnt--; + } + if (r > 0) { + iov[0].iov_base += r; + iov[0].iov_len -= r; + } + } + if (done == count) + *(args->ret) = done; + WAKE_COROUTINE(args); + return NULL; +} + +static ssize_t hostnet_tcp_writev(struct _hostnet_tcp_conn *conn, const struct iovec *iov, int iovcnt) { + assert(conn); + assert(iov); + assert(iovcnt > 0); + + ssize_t ret; + struct hostnet_pthread_writev_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .iov = iov, + .iovcnt = iovcnt, + + .ret = &ret, + }; + if (RUN_PTHREAD(hostnet_pthread_writev, &args)) + return -NET_ETHREAD; + return ret; +} + +/* TCP close() ****************************************************************/ + +static int hostnet_tcp_close(struct _hostnet_tcp_conn *conn) { + assert(conn); + return hostnet_map_negerrno(shutdown(conn->fd, SHUT_RDWR) ? -errno : 0, OP_NONE); +} +static int hostnet_tcp_close_read(struct _hostnet_tcp_conn *conn) { + assert(conn); + return hostnet_map_negerrno(shutdown(conn->fd, SHUT_RD) ? -errno : 0, OP_NONE); +} +static int hostnet_tcp_close_write(struct _hostnet_tcp_conn *conn) { + assert(conn); + return hostnet_map_negerrno(shutdown(conn->fd, SHUT_WR) ? -errno : 0, OP_NONE); +} + +/* UDP init() *****************************************************************/ + +void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { + int fd; + union { + struct sockaddr_in in; + struct sockaddr gen; + struct sockaddr_storage stor; + } addr = { 0 }; + + hostnet_init(); + + addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(port); + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + error(1, errno, "socket"); + if (bind(fd, &addr.gen, sizeof addr) < 0) + error(1, errno, "bind"); + + self->fd = fd; + self->read_deadline_ns = 0; +} + +/* UDP sendto() ***************************************************************/ + +struct hostnet_pthread_sendto_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + void *buf; + size_t count; + struct net_ip4_addr node; + uint16_t port; + + ssize_t *ret; +}; + +static void *hostnet_pthread_sendto(void *_args) { + struct hostnet_pthread_sendto_args *args = _args; + union { + struct sockaddr_in in; + struct sockaddr gen; + struct sockaddr_storage stor; + } addr = { 0 }; + + addr.in.sin_family = AF_INET; + addr.in.sin_addr.s_addr = + (((uint32_t)args->node.octets[0])<<24) | + (((uint32_t)args->node.octets[1])<<16) | + (((uint32_t)args->node.octets[2])<< 8) | + (((uint32_t)args->node.octets[3])<< 0) ; + addr.in.sin_port = htons(args->port); + *(args->ret) = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr)); + if (*(args->ret) < 0) + *(args->ret) = hostnet_map_negerrno(-errno, OP_SEND); + WAKE_COROUTINE(args); + return NULL; +} + +static ssize_t hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count, + struct net_ip4_addr node, uint16_t port) { + assert(conn); + + ssize_t ret; + struct hostnet_pthread_sendto_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .buf = buf, + .count = count, + .node = node, + .port = port, + + .ret = &ret, + }; + if (RUN_PTHREAD(hostnet_pthread_sendto, &args)) + return -NET_ETHREAD; + return ret; +} + +/* UDP recvfrom() *************************************************************/ + +static void hostnet_udp_set_recv_deadline(struct hostnet_udp_conn *conn, + uint64_t ts_ns) { + assert(conn); + + conn->read_deadline_ns = ts_ns; +} + +struct hostnet_pthread_recvfrom_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + struct timeval timeout; + void *buf; + size_t count; + + ssize_t *ret_size; + struct net_ip4_addr *ret_node; + uint16_t *ret_port; +}; + +static void *hostnet_pthread_recvfrom(void *_args) { + struct hostnet_pthread_recvfrom_args *args = _args; + + union { + struct sockaddr_in in; + struct sockaddr gen; + struct sockaddr_storage stor; + } addr = { 0 }; + socklen_t addr_size; + + *(args->ret_size) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, + &args->timeout, sizeof(args->timeout)); + if (*(args->ret_size) < 0) + goto end; + + *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, + MSG_TRUNC, &addr.gen, &addr_size); + if (*(args->ret_size) < 0) + goto end; + + assert(addr.in.sin_family == AF_INET); + if (args->ret_node) { + args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; + args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; + args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; + args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; + } + if (args->ret_port) { + (*args->ret_port) = ntohs(addr.in.sin_port); + } + + end: + if (*(args->ret_size) < 0) + *(args->ret_size) = hostnet_map_negerrno(-errno, OP_RECV); + WAKE_COROUTINE(args); + return NULL; +} + +static ssize_t hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count, + struct net_ip4_addr *ret_node, uint16_t *ret_port) { + assert(conn); + + ssize_t ret; + struct hostnet_pthread_recvfrom_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .buf = buf, + .count = count, + + .ret_size = &ret, + .ret_node = ret_node, + .ret_port = ret_port, + }; + if (conn->read_deadline_ns) { + uint64_t now_ns = LO_CALL(bootclock, get_time_ns); + if (conn->read_deadline_ns < now_ns) + return -NET_ERECV_TIMEOUT; + args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns); + } else { + args.timeout = (host_us_time_t){0}; + } + + if (RUN_PTHREAD(hostnet_pthread_recvfrom, &args)) + return -NET_ETHREAD; + return ret; +} + +/* UDP close() ****************************************************************/ + +static int hostnet_udp_close(struct hostnet_udp_conn *conn) { + assert(conn); + + return hostnet_map_negerrno(close(conn->fd) ? -errno : 0, OP_NONE); +} |