diff options
Diffstat (limited to 'src/socket.c')
-rw-r--r-- | src/socket.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/socket.c b/src/socket.c index ced884a..9b5d78a 100644 --- a/src/socket.c +++ b/src/socket.c @@ -17,10 +17,12 @@ #ifdef _WIN32 # include <ws2tcpip.h> #else +# include <fcntl.h> # include <netdb.h> # include <unistd.h> # include <netinet/in.h> # include <sys/socket.h> +# include <sys/stat.h> #endif #ifdef HAVE_SYS_UN_H # include <sys/un.h> @@ -46,6 +48,256 @@ # define AI_V4MAPPED 0 #endif +/** + * Return the pointer to the singular colon character, any other input + * yields nullptr. + */ +static inline const char *has_exactly_one_colon(const char *s) +{ + s = strchr(s, ':'); + if (s == nullptr) + return nullptr; + return strchr(s + 1, ':') == nullptr ? s : nullptr; +} + +/** + * @spec: "[" HOST-ANY "]" [ ":" PORT ] + * HOST-NAME [ ":" PORT ] + * HOST-IPV4 [ ":" PORT ] + * HOST-IPV6 + * @host: buffer for placing the extracted hostname + * (can overlap @spec) + * @hsize: buffer size for @host + * @port: storage space for extracted port number + * (can be nullptr) + * + * Returns <0 (error code) if unparsable or if the output buffer is too small. + * Success if on >=0. + */ +int HX_addrport_split(const char *spec, char *host, + size_t hbufsz, uint16_t *pport) +{ + if (*spec == '[') { + /* We also happen to allow IPv4 addrs and hostnames in [] */ + ++spec; + const char *end = strchr(spec, ']'); + if (end == nullptr) + return -EINVAL; + unsigned long hlen = end - spec; + if (hlen >= hbufsz) + return -E2BIG; + if (*++end == '\0') + return 1; + if (*end++ != ':') + return -EINVAL; + char *nend = nullptr; + uint16_t port = strtoul(end, &nend, 10); + if (nend == nullptr || *nend != '\0') + return -EINVAL; + memmove(host, spec, hlen); + host[hlen] = '\0'; + if (pport == nullptr) + return 2; + *pport = port; + return 2; + } + const char *onecolon = has_exactly_one_colon(spec); + if (onecolon != nullptr) { + unsigned long hlen = onecolon - spec; + if (hlen >= hbufsz) + return -E2BIG; + char *nend = nullptr; + uint16_t port = strtoul(onecolon + 1, &nend, 10); + if (nend == nullptr || *nend != '\0') + return -EINVAL; + memmove(host, spec, hlen); + host[hlen] = '\0'; + if (pport == nullptr) + return 2; + *pport = port; + return 2; + } + size_t hlen = strlen(spec); + if (hlen >= SIZE_MAX || ++hlen >= hbufsz) + return -E2BIG; + memmove(host, spec, hlen); + return 1; +} + +static int HX_inet_lookup(const char *host, uint16_t port, unsigned int xflags, + struct addrinfo **res) +{ + struct addrinfo hints = {}; +#if defined(AI_V4MAPPED) + hints.ai_flags = AI_V4MAPPED | xflags; +#else + hints.ai_flags = xflags; +#endif + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + + char portbuf[HXSIZEOF_Z32]; + snprintf(portbuf, sizeof(portbuf), "%hu", port); + return getaddrinfo(host, port == 0 ? nullptr : portbuf, &hints, res); +} + +int HX_inet_connect(const char *host, uint16_t port, unsigned int oflags) +{ + struct addrinfo *aires = nullptr; + int ret = HX_inet_lookup(host, port, AI_ADDRCONFIG, &aires); + int saved_errno = 0; + for (const struct addrinfo *r = aires; r != nullptr; r = r->ai_next) { + int fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (fd < 0) { + if (saved_errno == 0) + saved_errno = errno; + continue; + } +#ifdef O_NONBLOCK + if (oflags & O_NONBLOCK) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + flags = 0; + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) != 0) { + saved_errno = errno; + close(fd); + continue; + } + } +#endif + ret = connect(fd, r->ai_addr, r->ai_addrlen); + if (ret == 0) { + freeaddrinfo(aires); + return fd; + } +#ifdef O_NONBLOCK + if ((errno == EWOULDBLOCK || errno == EINPROGRESS) && + (oflags & O_NONBLOCK)) { + freeaddrinfo(aires); + return fd; + } +#endif + saved_errno = errno; + close(fd); + } + if (aires == nullptr && saved_errno == 0) + saved_errno = EHOSTUNREACH; + if (aires != nullptr) + freeaddrinfo(aires); + return -(errno = saved_errno); +} + +static int HX_gai_listen(const struct addrinfo *r) +{ + int fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (fd < 0) + return -2; + static const int y = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, STUPIDWIN(&y), sizeof(y)) < 0) + /* warn setsockopt: %s, strerror(errno)) */ ; + int ret = bind(fd, r->ai_addr, r->ai_addrlen); + if (ret != 0) { + int se = errno; + close(fd); + errno = se; + return -1; + } + ret = listen(fd, SOMAXCONN); + if (ret != 0) { + int se = errno; + close(fd); + errno = se; + return -2; + } + return fd; +} + +int HX_inet_listen(const char *host, uint16_t port) +{ + struct addrinfo *aires = nullptr; + int ret = HX_inet_lookup(host, port, AI_PASSIVE, &aires); + if (ret != 0) + ; + int saved_errno = EHOSTUNREACH; + bool use_env = getenv("HX_LISTEN_TOP_FD") != nullptr || getenv("LISTEN_FDS") != nullptr; + for (const struct addrinfo *r = aires; r != nullptr; r = r->ai_next) { + if (use_env) { + int fd = HX_socket_from_env(r, nullptr); + if (fd >= 0) { + freeaddrinfo(aires); + return fd; + } + } + int fd = HX_gai_listen(r); + if (fd >= 0) { + freeaddrinfo(aires); + return fd; + } + saved_errno = errno; + if (fd == -2) + continue; + break; + } + if (aires != nullptr) + freeaddrinfo(aires); + return -(errno = saved_errno); +} + +int HX_local_listen(const char *path) +{ +#ifdef HAVE_SYS_UN_H + struct sockaddr_un u; + if (strlen(path) >= sizeof(u.sun_path)) + return -EINVAL; + u.sun_family = AF_LOCAL; + strcpy(u.sun_path, path); + struct addrinfo r = {}; + r.ai_flags = AI_PASSIVE; + r.ai_family = AF_LOCAL; + r.ai_socktype = SOCK_STREAM; + r.ai_addrlen = sizeof(u) - sizeof(u.sun_path) + strlen(u.sun_path) + 1; + r.ai_addr = reinterpret_cast(struct sockaddr *, &u); + bool use_env = getenv("HX_LISTEN_TOP_FD") != nullptr || getenv("LISTEN_FDS") != nullptr; + if (use_env) { + int fd = HX_socket_from_env(&r, nullptr); + if (fd >= 0) + return fd; + } + int ret = HX_gai_listen(&r); + if (ret >= 0) + return ret; /* fd */ + if (ret == -2 || errno != EADDRINUSE) + return -errno; + + struct stat sb; + ret = stat(path, &sb); + if (ret < 0) + return -errno; + if (!S_ISSOCK(sb.st_mode)) + return -ENOTSOCK; + + int testfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (testfd < 0) + return -errno; + ret = connect(testfd, r.ai_addr, r.ai_addrlen); + close(testfd); + if (ret == 0) + return -EADDRINUSE; + + /* There will be a TOCTOU report, but what can you do... */ + ret = unlink(path); + if (ret < 0 && errno != ENOENT) + return -errno; + ret = HX_gai_listen(&r); + if (ret >= 0) + return ret; /* fd */ + return -errno; +#else + return -EPROTONOSUPPORT; +#endif +} + static int try_sk_from_env(int fd, const struct addrinfo *ai, const char *intf) { int value = 0; |