diff options
| author | 2023-01-17 04:22:00 +0000 | |
|---|---|---|
| committer | 2023-01-17 04:45:26 +0000 | |
| commit | ca8a576c74290a582bcb7f72f8e6846cdc983b15 (patch) | |
| tree | c3d7f5ae05256a1d475b905737fa7b1870a9434f /src | |
| parent | Replace SocketEngine::SetReuse with SocketEngine::SetOption. (diff) | |
Refactor the ListenSocket constructor to be less of a mess.
Also clear up the error handling.
Diffstat (limited to 'src')
| -rw-r--r-- | src/listensocket.cpp | 153 |
1 files changed, 93 insertions, 60 deletions
diff --git a/src/listensocket.cpp b/src/listensocket.cpp index 3556041ba..de32561da 100644 --- a/src/listensocket.cpp +++ b/src/listensocket.cpp @@ -35,88 +35,121 @@ # include <unistd.h> #endif -ListenSocket::ListenSocket(const std::shared_ptr<ConfigTag>& tag, const irc::sockets::sockaddrs& bind_to, int protocol) - : bind_tag(tag) - , bind_sa(bind_to) - , bind_protocol(protocol) +namespace { - // Are we creating a UNIX socket? - if (bind_to.family() == AF_UNIX) + // Removes a dead UNIX socket so we can bind over it. + bool RemoveSocket(ListenSocket* ls) { - // Should we replace the UNIX socket if it exists? - const bool replace = tag->getBool("replace", true); - if (replace && irc::sockets::isunix(bind_to.str())) - unlink(bind_to.str().c_str()); - } + const bool replace = ls->bind_tag->getBool("replace", true); + if (!replace || !irc::sockets::isunix(ls->bind_sa.str())) + return true; - SetFd(socket(bind_to.family(), SOCK_STREAM, protocol)); - if (!HasFd()) - return; + return unlink(ls->bind_sa.str().c_str()) != -1; + } -#ifdef IPV6_V6ONLY - /* This OS supports IPv6 sockets that can also listen for IPv4 - * connections. If our address is "*" or empty, enable both v4 and v6 to - * allow for simpler configuration on dual-stack hosts. Otherwise, if it - * is "::" or an IPv6 address, disable support so that an IPv4 bind will - * work on the port (by us or another application). - */ - if (bind_to.family() == AF_INET6) + int SetDeferAccept(ListenSocket* ls) { - std::string addr = tag->getString("address"); - /* This must be >= sizeof(DWORD) on Windows */ - const int enable = (addr.empty() || addr == "*") ? 0 : 1; - /* This must be before bind() */ - setsockopt(GetFd(), IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char* >(&enable), sizeof(enable)); - // errors ignored intentionally - } + // Default defer to on for TLS listeners because in TLS the client always speaks first. + unsigned int timeoutdef = ls->bind_tag->getString("sslprofile").empty() ? 0 : 5; + int timeout = static_cast<int>(ls->bind_tag->getDuration("defer", timeoutdef, 0, 60)); + if (!timeout) + return 0; + +#if defined TCP_DEFER_ACCEPT + return SocketEngine::SetOption(ls, IPPROTO_TCP, TCP_DEFER_ACCEPT, timeout); +#elif defined SO_ACCEPTFILTER + struct accept_filter_arg afa = { 0 }; + strcpy(afa.af_name, "dataready"); + return SocketEngine::SetOption(ls, SOL_SOCKET, SO_ACCEPTFILTER, afa); +#else + return 0; #endif + } - if (tag->getBool("free")) + // Allows binding to an IP address which is not available yet. + int SetFreeBind(ListenSocket* ls) { - socklen_t enable = 1; #if defined IP_FREEBIND // Linux 2.4+ - setsockopt(GetFd(), SOL_IP, IP_FREEBIND, &enable, sizeof(enable)); + return SocketEngine::SetOption<int>(ls, SOL_IP, IP_FREEBIND, 1); #elif defined IP_BINDANY // FreeBSD - setsockopt(GetFd(), IPPROTO_IP, IP_BINDANY, &enable, sizeof(enable)); -#elif defined SO_BINDANY // NetBSD/OpenBSD - setsockopt(GetFd(), SOL_SOCKET, SO_BINDANY, &enable, sizeof(enable)); + return SocketEngine::SetOption<int>(ls, IPPROTO_IP, IP_BINDANY, 1); +#elif defined SO_BINDANY // NetBSD, OpenBSD + return SocketEngine::SetOption<int>(ls, SOL_SOCKET, SO_BINDANY, 1); #else - (void)enable; + return 0; #endif } - SocketEngine::SetOption<int>(GetFd(), SOL_SOCKET, SO_REUSEADDR, 1); - int rv = SocketEngine::Bind(this, bind_to); - if (rv >= 0) - rv = SocketEngine::Listen(this, ServerInstance->Config->MaxConn); - - if (bind_to.family() == AF_UNIX) + // Sets the filesystem permissions for a UNIX socket. + int SetPermissions(ListenSocket* ls) { - const std::string permissionstr = tag->getString("permissions"); + const std::string permissionstr = ls->bind_tag->getString("permissions"); unsigned long permissions = strtoul(permissionstr.c_str(), nullptr, 8); - if (permissions && permissions <= 07777) - { - // This cast is safe thanks to the above check. - chmod(bind_to.str().c_str(), static_cast<int>(permissions)); - } + if (!permissions || permissions > 07777) + return 0; + + // This cast is safe thanks to the above check. + return chmod(ls->bind_sa.str().c_str(), static_cast<int>(permissions)); } - // Default defer to on for TLS listeners because in TLS the client always speaks first - unsigned int timeoutdef = tag->getString("sslprofile").empty() ? 0 : 3; - int timeout = static_cast<int>(tag->getDuration("defer", timeoutdef, 0, 60)); - if (timeout && !rv) + // Allow binding on IPv4 with an IPv6 socket. + void SetIPv6Only(ListenSocket* ls) { -#if defined TCP_DEFER_ACCEPT - setsockopt(GetFd(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, sizeof(timeout)); -#elif defined SO_ACCEPTFILTER - struct accept_filter_arg afa; - memset(&afa, 0, sizeof(afa)); - strcpy(afa.af_name, "dataready"); - setsockopt(GetFd(), SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)); +#ifdef IPV6_V6ONLY + /* This OS supports IPv6 sockets that can also listen for IPv4 + * connections. If listening on all interfaces we enable both v4 and v6 + * to allow for simpler configuration on dual-stack hosts. Otherwise, + * if it is "::" or an IPv6 address we disable support so that an IPv4 + * bind will work on the same port (by us or another application). + */ + const std::string address = ls->bind_tag->getString("address"); + + // IMPORTANT: This must be >= sizeof(DWORD) on Windows. + const int enable = (address.empty() || address == "*") ? 0 : 1; + + // Intentionally ignore the result of this so we can fall back to default behaviour. + SocketEngine::SetOption(ls, IPPROTO_IPV6, IPV6_V6ONLY, enable); #endif } +} + +ListenSocket::ListenSocket(const std::shared_ptr<ConfigTag>& tag, const irc::sockets::sockaddrs& bind_to, int protocol) + : bind_tag(tag) + , bind_sa(bind_to) + , bind_protocol(protocol) +{ + if (bind_to.family() == AF_UNIX && !RemoveSocket(this)) + return; + + SetFd(socket(bind_to.family(), SOCK_STREAM, protocol)); + if (!HasFd()) + return; + + // Its okay if these fails. + if (bind_to.family() == AF_INET6) + SetIPv6Only(this); + SocketEngine::SetOption<int>(this, SOL_SOCKET, SO_REUSEADDR, 1); + + int rv = 0; + if (bind_to.is_ip() && tag->getBool("free")) + rv = SetFreeBind(this); + + if (rv != -1) + rv = SocketEngine::Bind(this, bind_to); + + if (rv != -1) + rv = SocketEngine::Listen(this, ServerInstance->Config->MaxConn); + + if (rv != -1) + { + if (bind_to.family() == AF_UNIX) + rv = SetPermissions(this); + + else if (bind_to.is_ip() && protocol == IPPROTO_TCP) + rv = SetDeferAccept(this); + } - if (rv < 0) + if (rv == -1) { int errstore = errno; SocketEngine::Shutdown(this, 2); |
