From d06dcb0bba521c4992ca07e538bf7048dbc41d41 Mon Sep 17 00:00:00 2001 From: Wiebe Cazemier Date: Thu, 11 Mar 2021 22:34:23 +0100 Subject: [PATCH] Add IPv6 support, with related listener options --- CMakeLists.txt | 2 ++ bindaddr.cpp | 3 +++ bindaddr.h | 17 +++++++++++++++++ configfileparser.cpp | 24 +++++++++++++++++++++++- listener.cpp | 17 +++++++++++++++++ listener.h | 12 ++++++++++++ mainapp.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------- mainapp.h | 4 +++- scopedsocket.cpp | 18 ++++++++++++++++++ scopedsocket.h | 18 ++++++++++++++++++ utils.cpp | 38 ++++++++++++++++++++++++++++++++++++++ utils.h | 5 +++++ 12 files changed, 209 insertions(+), 40 deletions(-) create mode 100644 bindaddr.cpp create mode 100644 bindaddr.h create mode 100644 scopedsocket.cpp create mode 100644 scopedsocket.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f13c2bf..d40960a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,8 @@ add_executable(FlashMQ settings.cpp listener.cpp unscopedlock.cpp + scopedsocket.cpp + bindaddr.cpp ) target_link_libraries(FlashMQ pthread dl ssl crypto) diff --git a/bindaddr.cpp b/bindaddr.cpp new file mode 100644 index 0000000..f3887d0 --- /dev/null +++ b/bindaddr.cpp @@ -0,0 +1,3 @@ +#include "bindaddr.h" + + diff --git a/bindaddr.h b/bindaddr.h new file mode 100644 index 0000000..a5b6b2c --- /dev/null +++ b/bindaddr.h @@ -0,0 +1,17 @@ +#ifndef BINDADDR_H +#define BINDADDR_H + +#include +#include + +/** + * @brief The BindAddr struct helps creating the resource for bind(). It uses an intermediate struct sockaddr to avoid compiler warnings, and + * this class helps a bit with resource management of it. + */ +struct BindAddr +{ + std::unique_ptr p; + socklen_t len = 0; +}; + +#endif // BINDADDR_H diff --git a/configfileparser.cpp b/configfileparser.cpp index dbb9c7d..b170d43 100644 --- a/configfileparser.cpp +++ b/configfileparser.cpp @@ -71,6 +71,9 @@ ConfigFileParser::ConfigFileParser(const std::string &path) : validListenKeys.insert("protocol"); validListenKeys.insert("fullchain"); validListenKeys.insert("privkey"); + validListenKeys.insert("inet_protocol"); + validListenKeys.insert("inet4_bind_address"); + validListenKeys.insert("inet6_bind_address"); settings.reset(new Settings()); } @@ -93,7 +96,7 @@ void ConfigFileParser::loadFile(bool test) std::list lines; - const std::regex key_value_regex("^([a-zA-Z0-9_\\-]+) +([a-zA-Z0-9_\\-/\\.]+)$"); + const std::regex key_value_regex("^([a-zA-Z0-9_\\-]+) +([a-zA-Z0-9_\\-/\\.:]+)$"); const std::regex block_regex_start("^([a-zA-Z0-9_\\-]+) *\\{$"); const std::regex block_regex_end("^\\}$"); @@ -211,6 +214,25 @@ void ConfigFileParser::loadFile(bool test) { curListener->sslPrivkey = value; } + if (key == "inet_protocol") + { + if (value == "ip4") + curListener->protocol = ListenerProtocol::IPv4; + else if (value == "ip6") + curListener->protocol = ListenerProtocol::IPv6; + else if (value == "ip4_ip6") + curListener->protocol = ListenerProtocol::IPv46; + else + throw ConfigFileException(formatString("Invalid inet protocol: %s", value.c_str())); + } + if (key == "inet4_bind_address") + { + curListener->inet4BindAddress = value; + } + if (key == "inet6_bind_address") + { + curListener->inet6BindAddress = value; + } continue; } diff --git a/listener.cpp b/listener.cpp index 33873bf..605b38f 100644 --- a/listener.cpp +++ b/listener.cpp @@ -76,3 +76,20 @@ void Listener::loadCertAndKeyFromConfig() if (SSL_CTX_use_PrivateKey_file(sslctx->get(), sslPrivkey.c_str(), SSL_FILETYPE_PEM) != 1) throw std::runtime_error("Loading key failed. This was after test loading the certificate, so is very unexpected."); } + +std::string Listener::getBindAddress(ListenerProtocol p) +{ + if (p == ListenerProtocol::IPv4) + { + if (inet4BindAddress.empty()) + return "0.0.0.0"; + return inet4BindAddress; + } + if (p == ListenerProtocol::IPv6) + { + if (inet6BindAddress.empty()) + return "::"; + return inet6BindAddress; + } + return ""; +} diff --git a/listener.h b/listener.h index 7d507bb..2c902db 100644 --- a/listener.h +++ b/listener.h @@ -6,8 +6,18 @@ #include "sslctxmanager.h" +enum class ListenerProtocol +{ + IPv46, + IPv4, + IPv6 +}; + struct Listener { + ListenerProtocol protocol = ListenerProtocol::IPv46; + std::string inet4BindAddress; + std::string inet6BindAddress; int port = 0; bool websocket = false; std::string sslFullchain; @@ -18,5 +28,7 @@ struct Listener bool isSsl() const; std::string getProtocolName() const; void loadCertAndKeyFromConfig(); + + std::string getBindAddress(ListenerProtocol p); }; #endif // LISTENER_H diff --git a/mainapp.cpp b/mainapp.cpp index eb5d214..0216b74 100644 --- a/mainapp.cpp +++ b/mainapp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -205,47 +206,59 @@ void MainApp::showLicense() puts("Author: Wiebe Cazemier "); } -int MainApp::createListenSocket(const std::shared_ptr &listener) +std::list MainApp::createListenSocket(const std::shared_ptr &listener) { - if (listener->port <= 0) - return -2; + std::list result; - logger->logf(LOG_NOTICE, "Creating %s listener on port %d", listener->getProtocolName().c_str(), listener->port); + if (listener->port <= 0) + return result; - try + for (ListenerProtocol p : std::list({ ListenerProtocol::IPv4, ListenerProtocol::IPv6})) { - int listen_fd = check(socket(AF_INET, SOCK_STREAM, 0)); + std::string pname = p == ListenerProtocol::IPv4 ? "IPv4" : "IPv6"; + int family = p == ListenerProtocol::IPv4 ? AF_INET : AF_INET6; - // Not needed for now. Maybe I will make multiple accept threads later, with SO_REUSEPORT. - int optval = 1; - check(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(optval))); + if (!(listener->protocol == ListenerProtocol::IPv46 || listener->protocol == p)) + continue; - int flags = fcntl(listen_fd, F_GETFL); - check(fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK )); + try + { + logger->logf(LOG_NOTICE, "Creating %s %s listener on [%s]:%d", pname.c_str(), listener->getProtocolName().c_str(), + listener->getBindAddress(p).c_str(), listener->port); - struct sockaddr_in in_addr_plain; - in_addr_plain.sin_family = AF_INET; - in_addr_plain.sin_addr.s_addr = INADDR_ANY; - in_addr_plain.sin_port = htons(listener->port); + BindAddr bindAddr = getBindAddr(family, listener->getBindAddress(p), listener->port); - check(bind(listen_fd, (struct sockaddr *)(&in_addr_plain), sizeof(struct sockaddr_in))); - check(listen(listen_fd, 1024)); + int listen_fd = check(socket(family, SOCK_STREAM, 0)); - struct epoll_event ev; - memset(&ev, 0, sizeof (struct epoll_event)); + // Not needed for now. Maybe I will make multiple accept threads later, with SO_REUSEPORT. + int optval = 1; + check(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(optval))); - ev.data.fd = listen_fd; - ev.events = EPOLLIN; - check(epoll_ctl(this->epollFdAccept, EPOLL_CTL_ADD, listen_fd, &ev)); + int flags = fcntl(listen_fd, F_GETFL); + check(fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK )); - return listen_fd; - } - catch (std::exception &ex) - { - logger->logf(LOG_NOTICE, "Creating %s listener on port %d failed: %s", listener->getProtocolName().c_str(), listener->port, ex.what()); - return -1; + check(bind(listen_fd, bindAddr.p.get(), bindAddr.len)); + check(listen(listen_fd, 1024)); + + struct epoll_event ev; + memset(&ev, 0, sizeof (struct epoll_event)); + + ev.data.fd = listen_fd; + ev.events = EPOLLIN; + check(epoll_ctl(this->epollFdAccept, EPOLL_CTL_ADD, listen_fd, &ev)); + + result.push_back(ScopedSocket(listen_fd)); + + } + catch (std::exception &ex) + { + logger->logf(LOG_ERR, "Creating %s %s listener on [%s]:%d failed: %s", pname.c_str(), listener->getProtocolName().c_str(), + listener->getBindAddress(p).c_str(), listener->port, ex.what()); + return std::list(); + } } - return -1; + + return result; } void MainApp::wakeUpThread() @@ -367,13 +380,19 @@ void MainApp::start() { timer.start(); - std::map> listenerMap; + std::map> listenerMap; // For finding listeners by fd. + std::list activeListenSockets; // For RAII/ownership for(std::shared_ptr &listener : this->listeners) { - int fd = createListenSocket(listener); - if (fd > 0) - listenerMap[fd] = listener; + std::list scopedSockets = createListenSocket(listener); + + for (ScopedSocket &scopedSocket : scopedSockets) + { + if (scopedSocket.socket > 0) + listenerMap[scopedSocket.socket] = listener; + activeListenSockets.push_back(std::move(scopedSocket)); + } } #ifdef NDEBUG @@ -506,11 +525,6 @@ void MainApp::start() { thread->waitForQuit(); } - - for(auto pair : listenerMap) - { - close(pair.first); - } } void MainApp::quit() @@ -596,3 +610,4 @@ void MainApp::queueCleanup() wakeUpThread(); } + diff --git a/mainapp.h b/mainapp.h index 24586fb..ac139f0 100644 --- a/mainapp.h +++ b/mainapp.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "forward_declarations.h" @@ -20,6 +21,7 @@ #include "subscriptionstore.h" #include "configfileparser.h" #include "timer.h" +#include "scopedsocket.h" class MainApp { @@ -48,7 +50,7 @@ class MainApp void reloadConfig(); static void doHelp(const char *arg); static void showLicense(); - int createListenSocket(const std::shared_ptr &listener); + std::list createListenSocket(const std::shared_ptr &listener); void wakeUpThread(); void queueKeepAliveCheckAtAllThreads(); void setFuzzFile(const std::string &fuzzFilePath); diff --git a/scopedsocket.cpp b/scopedsocket.cpp new file mode 100644 index 0000000..01189a1 --- /dev/null +++ b/scopedsocket.cpp @@ -0,0 +1,18 @@ +#include "scopedsocket.h" + +ScopedSocket::ScopedSocket(int socket) : socket(socket) +{ + +} + +ScopedSocket::ScopedSocket(ScopedSocket &&other) +{ + this->socket = other.socket; + other.socket = 0; +} + +ScopedSocket::~ScopedSocket() +{ + if (socket > 0) + close(socket); +} diff --git a/scopedsocket.h b/scopedsocket.h new file mode 100644 index 0000000..1ba87fc --- /dev/null +++ b/scopedsocket.h @@ -0,0 +1,18 @@ +#ifndef SCOPEDSOCKET_H +#define SCOPEDSOCKET_H + +#include +#include + +/** + * @brief The ScopedSocket struct allows for a bit of RAII and move semantics on a socket fd. + */ +struct ScopedSocket +{ + int socket = 0; + ScopedSocket(int socket); + ScopedSocket(ScopedSocket &&other); + ~ScopedSocket(); +}; + +#endif // SCOPEDSOCKET_H diff --git a/utils.cpp b/utils.cpp index b41e58e..04d65e8 100644 --- a/utils.cpp +++ b/utils.cpp @@ -427,3 +427,41 @@ std::string dirnameOf(const std::string& path) return (std::string::npos == pos) ? "" : path.substr(0, pos); } + +BindAddr getBindAddr(int family, const std::string &bindAddress, int port) +{ + BindAddr result; + + if (family == AF_INET) + { + struct sockaddr_in *in_addr_v4 = new sockaddr_in(); + result.len = sizeof(struct sockaddr_in); + memset(in_addr_v4, 0, result.len); + + if (bindAddress.empty()) + in_addr_v4->sin_addr.s_addr = INADDR_ANY; + else + inet_pton(AF_INET, bindAddress.c_str(), &in_addr_v4->sin_addr); + + in_addr_v4->sin_family = AF_INET; + in_addr_v4->sin_port = htons(port); + result.p.reset(reinterpret_cast(in_addr_v4)); + } + if (family == AF_INET6) + { + struct sockaddr_in6 *in_addr_v6 = new sockaddr_in6(); + result.len = sizeof(struct sockaddr_in6); + memset(in_addr_v6, 0, result.len); + + if (bindAddress.empty()) + in_addr_v6->sin6_addr = IN6ADDR_ANY_INIT; + else + inet_pton(AF_INET6, bindAddress.c_str(), &in_addr_v6->sin6_addr); + + in_addr_v6->sin6_family = AF_INET6; + in_addr_v6->sin6_port = htons(port); + result.p.reset(reinterpret_cast(in_addr_v6)); + } + + return result; +} diff --git a/utils.h b/utils.h index a1ffb4f..d54ea4d 100644 --- a/utils.h +++ b/utils.h @@ -9,8 +9,11 @@ #include #include #include +#include +#include #include "cirbuf.h" +#include "bindaddr.h" template int check(int rc) { @@ -62,5 +65,7 @@ std::string formatString(const std::string str, ...); std::string dirnameOf(const std::string& fname); +BindAddr getBindAddr(int family, const std::string &bindAddress, int port); + #endif // UTILS_H -- libgit2 0.21.4