summaryrefslogtreecommitdiff
path: root/libhw/host_net.c
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-27 23:22:01 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-27 23:49:37 -0600
commit88adb90f5e805bea27e619fd5209ef58dbff6fd1 (patch)
treec3e24877b40ce183f1d72f6e064b0478ecf92207 /libhw/host_net.c
parent89761191a98f7dce4d1049b9a84c3d645378222a (diff)
Factor out a libhw
Diffstat (limited to 'libhw/host_net.c')
-rw-r--r--libhw/host_net.c455
1 files changed, 455 insertions, 0 deletions
diff --git a/libhw/host_net.c b/libhw/host_net.c
new file mode 100644
index 0000000..d71afee
--- /dev/null
+++ b/libhw/host_net.c
@@ -0,0 +1,455 @@
+/* libhw/host_net.c - <libhw/generic/net.h> implementation for hosted glibc
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */
+/* misc */
+#include <assert.h> /* for assert() */
+#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(), SIGRTMIN, SIGRTMAX, SA_SIGINFO */
+
+#include <libcr/coroutine.h>
+#include <libmisc/vcall.h>
+
+#include <libhw/host_net.h>
+
+/* common *********************************************************************/
+
+#define UNUSED(name) /* name __attribute__ ((unused)) */
+
+static int hostnet_sig_io = 0;
+
+static void hostnet_handle_sig_io(int UNUSED(sig), siginfo_t *info, void *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 = SIGRTMIN;
+ if (hostnet_sig_io > SIGRTMAX)
+ error(1, 0, "SIGRTMAX exceeded");
+
+ 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;
+ if (pthread_create(&thread, NULL, fn, args))
+ return true;
+ cr_pause_and_yield();
+ if (pthread_join(thread, NULL))
+ return true;
+ return false;
+}
+
+static inline ssize_t hostnet_map_errno(ssize_t v) {
+ if (v >= 0)
+ return v;
+ switch (v) {
+ case ETIMEDOUT:
+ return NET_ETIMEDOUT;
+ default:
+ return NET_EOTHER;
+ }
+}
+
+/* TCP init() ( AKA socket(3) + listen(3) )************************************/
+
+static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener);
+static ssize_t hostnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count);
+static ssize_t hostnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count);
+static int hostnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr);
+
+static struct net_stream_listener_vtable hostnet_tcp_listener_vtable = {
+ .accept = hostnet_tcp_accept,
+};
+
+static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = {
+ .read = hostnet_tcp_read,
+ .write = hostnet_tcp_write,
+ .close = hostnet_tcp_close,
+};
+
+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->vtable = &hostnet_tcp_listener_vtable;
+ self->fd = listenerfd;
+}
+
+/* TCP 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 implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener) {
+ struct hostnet_tcp_listener *listener =
+ VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_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 NULL;
+
+ listener->active_conn.vtable = &hostnet_tcp_conn_vtable;
+ listener->active_conn.fd = ret_connfd;
+ return &listener->active_conn;
+}
+
+/* TCP read() *****************************************************************/
+
+struct hostnet_pthread_read_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ void *buf;
+ size_t count;
+
+ ssize_t *ret;
+};
+
+static void *hostnet_pthread_read(void *_args) {
+ struct hostnet_pthread_read_args *args = _args;
+ *(args->ret) = read(args->connfd, args->buf, args->count);
+ if (*(args->ret) < 0)
+ *(args->ret) = hostnet_map_errno(-errno);
+ WAKE_COROUTINE(args);
+ return NULL;
+};
+
+static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, size_t count) {
+ struct _hostnet_tcp_conn *conn =
+ VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+ assert(conn);
+
+ ssize_t ret;
+ struct hostnet_pthread_read_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+
+ .connfd = conn->fd,
+ .buf = buf,
+ .count = count,
+
+ .ret = &ret,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_read, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* TCP write() ****************************************************************/
+
+struct hostnet_pthread_write_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ void *buf;
+ size_t count;
+
+ ssize_t *ret;
+};
+
+static void *hostnet_pthread_write(void *_args) {
+ struct hostnet_pthread_read_args *args = _args;
+ size_t done = 0;
+ while (done < args->count) {
+ ssize_t r = write(args->connfd, args->buf, args->count);
+ if (r < 0) {
+ hostnet_map_errno(-errno);
+ break;
+ }
+ done += r;
+ }
+ if (done == args->count)
+ *(args->ret) = done;
+ WAKE_COROUTINE(args);
+ return NULL;
+};
+
+static ssize_t hostnet_tcp_write(implements_net_stream_conn *_conn, void *buf, size_t count) {
+ struct _hostnet_tcp_conn *conn =
+ VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+ assert(conn);
+
+ ssize_t ret;
+ struct hostnet_pthread_write_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+
+ .connfd = conn->fd,
+ .buf = buf,
+ .count = count,
+
+ .ret = &ret,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_write, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* TCP close() ****************************************************************/
+
+static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr) {
+ struct _hostnet_tcp_conn *conn =
+ VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+ assert(conn);
+
+ int how;
+ if (rd && wr)
+ how = SHUT_RDWR;
+ else if (rd && !wr)
+ how = SHUT_RD;
+ else if (!rd && wr)
+ how = SHUT_WR;
+ else
+ assert(false);
+ return hostnet_map_errno(shutdown(conn->fd, how) ? -errno : 0);
+}
+
+/* UDP init() *****************************************************************/
+
+static ssize_t hostnet_udp_sendto(implements_net_packet_conn *self, void *buf, size_t len,
+ struct net_ip4_addr addr, uint16_t port);
+static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *self, void *buf, size_t len,
+ struct net_ip4_addr *ret_addr, uint16_t *ret_port);
+static int hostnet_udp_close(implements_net_packet_conn *self);
+
+static struct net_packet_conn_vtable hostnet_udp_conn_vtable = {
+ .sendto = hostnet_udp_sendto,
+ .recvfrom = hostnet_udp_recvfrom,
+ .close = hostnet_udp_close,
+};
+
+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->vtable = &hostnet_udp_conn_vtable;
+ self->fd = fd;
+}
+
+/* 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_errno(-errno);
+ WAKE_COROUTINE(args);
+ return NULL;
+}
+
+static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, size_t count,
+ struct net_ip4_addr node, uint16_t port) {
+ struct hostnet_udp_conn *conn =
+ VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+ 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() *************************************************************/
+
+struct hostnet_pthread_recvfrom_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ 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) = recvfrom(args->connfd, args->buf, args->count, 0, &addr.gen, &addr_size);
+ if (*(args->ret_size) < 0)
+ *(args->ret_size) = hostnet_map_errno(-errno);
+ else {
+ 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);
+ }
+ WAKE_COROUTINE(args);
+ return NULL;
+}
+
+static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf, size_t count,
+ struct net_ip4_addr *ret_node, uint16_t *ret_port) {
+ struct hostnet_udp_conn *conn =
+ VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+ 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 (RUN_PTHREAD(hostnet_pthread_recvfrom, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* UDP close() ****************************************************************/
+
+static int hostnet_udp_close(implements_net_packet_conn *_conn) {
+ struct hostnet_udp_conn *conn =
+ VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+ assert(conn);
+
+ return hostnet_map_errno(close(conn->fd) ? -errno : 0);
+}