diff options
Diffstat (limited to 'src/net.cc')
| -rw-r--r-- | src/net.cc | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/net.cc b/src/net.cc new file mode 100644 index 0000000..bb25e69 --- /dev/null +++ b/src/net.cc @@ -0,0 +1,136 @@ +#include "net.hh" + +#include <charconv> +#include <cerrno> +#include <cstring> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/types.h> + +namespace net { + +std::vector<unique_fd> bind_and_listen( + Logger* logger, std::string const& host, uint16_t port) { + std::vector<unique_fd> fds; + + std::string service; + service.resize(10); + { + auto [ptr, err] = std::to_chars(service.data(), + service.data() + service.size(), port); + if (err != std::errc{}) { + logger->err("Invalid port: %u", static_cast<unsigned int>(port)); + return {}; + } + service.resize(ptr - service.data()); + } + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_ADDRCONFIG; + struct addrinfo* result; + + if (auto err = getaddrinfo(host.empty() ? nullptr : host.c_str(), + service.c_str(), + &hints, &result)) { + logger->err("Unable to resolve %s:%u: %s", + host.c_str(), static_cast<unsigned int>(port), + gai_strerror(err)); + return {}; + } + + for (auto* rp = result; rp; rp = rp->ai_next) { + unique_fd fd(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)); + if (!fd) { + logger->warn("Unable to create socket: %s", strerror(errno)); + continue; + } + + if (bind(fd.get(), rp->ai_addr, rp->ai_addrlen)) { + logger->warn("Unable to bind socket: %s", strerror(errno)); + continue; + } + + fds.push_back(std::move(fd)); + } + + freeaddrinfo(result); + + { + auto it = fds.begin(); + while (it != fds.end()) { + if (listen(it->get(), SOMAXCONN)) { + logger->err("Unable to listen: %s", strerror(errno)); + it = fds.erase(it); + } else { + ++it; + } + } + } + return fds; +} + +unique_fd connect(Logger* logger, std::string const& host, uint16_t port) { + unique_fd fd; + std::string service; + service.resize(10); + { + auto [ptr, err] = std::to_chars(service.data(), + service.data() + service.size(), port); + if (err != std::errc{}) { + logger->err("Invalid port: %u", static_cast<unsigned int>(port)); + return fd; + } + service.resize(ptr - service.data()); + } + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG; + struct addrinfo* result; + + if (auto err = getaddrinfo(host.c_str(), service.c_str(), + &hints, &result)) { + logger->err("Unable to resolve %s:%u: %s", + host.c_str(), static_cast<unsigned int>(port), + gai_strerror(err)); + return fd; + } + + for (auto* rp = result; rp; rp = rp->ai_next) { + // Note: SOCK_NONBLOCK is a linux 2.6.27+ thing. + fd.reset(socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, + rp->ai_protocol)); + if (!fd) { + logger->warn("Unable to create socket: %s", strerror(errno)); + continue; + } + + if (connect(fd.get(), rp->ai_addr, rp->ai_addrlen)) { + logger->warn("Unable to connect socket: %s", strerror(errno)); + fd.reset(); + continue; + } + + break; + } + + freeaddrinfo(result); + return fd; +} + +unique_fd accept(Logger* logger, int fd) { + unique_fd ret(accept4(fd, nullptr, nullptr, SOCK_NONBLOCK)); + if (!ret) { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + logger->warn("Error accepting connection: %s", strerror(errno)); + } + } + return ret; +} + +} // namespace net |
