diff options
Diffstat (limited to 'libcody/netserver.cc')
-rw-r--r-- | libcody/netserver.cc | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/libcody/netserver.cc b/libcody/netserver.cc new file mode 100644 index 00000000000..7e43eb033f4 --- /dev/null +++ b/libcody/netserver.cc @@ -0,0 +1,153 @@ +// CODYlib -*- mode:c++ -*- +// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org +// License: Apache v2.0 + +// Cody +#include "internal.hh" +#if CODY_NETWORKING +// C +#include <cerrno> +#include <cstring> +// OS +#include <netdb.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <sys/un.h> + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0 +#endif + +// Server-side networking helpers + +namespace Cody { + +int ListenSocket (char const **e, sockaddr const *addr, socklen_t len, + unsigned backlog) +{ + char const *errstr = nullptr; + + int fd = socket (addr->sa_family, SOCK_STREAM, 0); + if (fd < 0) + { + errstr = "creating socket"; + + fail:; + int err = errno; + if (e) + *e = errstr; + if (fd >= 0) + close (fd); + errno = err; + return -1; + } + + if (bind (fd, addr, len) < 0) + { + errstr = "binding socket"; + goto fail; + } + + if (listen (fd, backlog ? backlog : 17) < 0) + { + errstr = "listening socket"; + goto fail; + } + + return fd; +} + +int ListenLocal (char const **e, char const *name, unsigned backlog) +{ + sockaddr_un addr; + size_t len = strlen (name); + + if (len >= sizeof (addr.sun_path)) + { + errno = ENAMETOOLONG; + return -1; + } + + memset (&addr, 0, offsetof (sockaddr_un, sun_path)); + addr.sun_family = AF_UNIX; + memcpy (addr.sun_path, name, len + 1); + + return ListenSocket (e, (sockaddr *)&addr, sizeof (addr), backlog); +} + +int ListenInet6 (char const **e, char const *name, int port, unsigned backlog) +{ + addrinfo *addrs = nullptr; + int fd = -1; + char const *errstr = nullptr; + + fd = socket (AF_INET6, SOCK_STREAM, 0); + if (fd < 0) + { + errstr = "creating socket"; + + fail:; + int err = errno; + if (e) + *e = errstr; + if (fd >= 0) + close (fd); + if (addrs) + freeaddrinfo (addrs); + errno = err; + return -1; + } + + addrinfo hints; + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_addr = nullptr; + hints.ai_canonname = nullptr; + hints.ai_next = nullptr; + + /* getaddrinfo requires a port number, but is quite happy to accept + invalid ones. So don't rely on it. */ + if (int err = getaddrinfo (name, "0", &hints, &addrs)) + { + errstr = gai_strerror (err); + // What's the best errno to set? + errno = 0; + goto fail; + } + + sockaddr_in6 addr; + memset (&addr, 0, sizeof (addr)); + addr.sin6_family = AF_INET6; + + for (struct addrinfo *next = addrs; next; next = next->ai_next) + if (next->ai_family == AF_INET6 + && next->ai_socktype == SOCK_STREAM) + { + sockaddr_in6 *in6 = (sockaddr_in6 *)next->ai_addr; + in6->sin6_port = htons (port); + if (ntohs (in6->sin6_port) != port) + errno = EINVAL; + else if (!bind (fd, next->ai_addr, next->ai_addrlen)) + goto listen; + } + + errstr = "binding socket"; + goto fail; + + listen:; + freeaddrinfo (addrs); + + if (listen (fd, backlog ? backlog : 17) < 0) + { + errstr = "listening socket"; + goto fail; + } + + return fd; +} + +} +#endif |