From 817e9120259fa601922295b791bebc2f77392152 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 10 Aug 2017 12:17:35 +0200 Subject: [PATCH] net/SocketDescriptor: specialization of FileDescriptor for network sockets --- Makefile.am | 2 + src/net/SocketDescriptor.cxx | 405 +++++++++++++++++++++++++++++ src/net/SocketDescriptor.hxx | 205 +++++++++++++++ src/net/StaticSocketAddress.hxx | 2 + src/net/UniqueSocketDescriptor.hxx | 103 ++++++++ 5 files changed, 717 insertions(+) create mode 100644 src/net/SocketDescriptor.cxx create mode 100644 src/net/SocketDescriptor.hxx create mode 100644 src/net/UniqueSocketDescriptor.hxx diff --git a/Makefile.am b/Makefile.am index bd7852830..494302210 100644 --- a/Makefile.am +++ b/Makefile.am @@ -484,6 +484,8 @@ libnet_a_SOURCES = \ src/net/AllocatedSocketAddress.cxx src/net/AllocatedSocketAddress.hxx \ src/net/SocketAddress.cxx src/net/SocketAddress.hxx \ src/net/SocketUtil.cxx src/net/SocketUtil.hxx \ + src/net/SocketDescriptor.cxx src/net/SocketDescriptor.hxx \ + src/net/UniqueSocketDescriptor.hxx \ src/net/SocketError.cxx src/net/SocketError.hxx # System library diff --git a/src/net/SocketDescriptor.cxx b/src/net/SocketDescriptor.cxx new file mode 100644 index 000000000..f8326030a --- /dev/null +++ b/src/net/SocketDescriptor.cxx @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2012-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SocketDescriptor.hxx" +#include "SocketAddress.hxx" +#include "StaticSocketAddress.hxx" + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#include +#include +#include +#include + +#ifdef _WIN32 + +void +SocketDescriptor::Close() +{ + if (IsDefined()) + ::closesocket(Steal()); +} + +#endif + +SocketDescriptor +SocketDescriptor::Accept() +{ +#if defined(__linux__) && !defined(__BIONIC__) && !defined(KOBO) + int connection_fd = ::accept4(Get(), nullptr, nullptr, SOCK_CLOEXEC); +#else + int connection_fd = ::accept(Get(), nullptr, nullptr); +#endif + return connection_fd >= 0 + ? SocketDescriptor(connection_fd) + : Undefined(); +} + +SocketDescriptor +SocketDescriptor::AcceptNonBlock(StaticSocketAddress &address) const +{ + address.SetMaxSize(); +#if defined(__linux__) && !defined(__BIONIC__) && !defined(KOBO) + int connection_fd = ::accept4(Get(), address, &address.size, + SOCK_CLOEXEC|SOCK_NONBLOCK); +#else + int connection_fd = ::accept(Get(), address, &address.size); +#endif + return SocketDescriptor(connection_fd); +} + +bool +SocketDescriptor::Connect(SocketAddress address) +{ + assert(address.IsDefined()); + + return ::connect(Get(), address.GetAddress(), address.GetSize()) >= 0; +} + +bool +SocketDescriptor::Create(int domain, int type, int protocol) +{ +#ifdef WIN32 + static bool initialised = false; + if (!initialised) { + WSADATA data; + WSAStartup(MAKEWORD(2,2), &data); + initialised = true; + } +#endif + +#ifdef SOCK_CLOEXEC + /* implemented since Linux 2.6.27 */ + type |= SOCK_CLOEXEC; +#endif + + int new_fd = socket(domain, type, protocol); + if (new_fd < 0) + return false; + + Set(new_fd); + return true; +} + +bool +SocketDescriptor::CreateNonBlock(int domain, int type, int protocol) +{ +#ifdef SOCK_NONBLOCK + type |= SOCK_NONBLOCK; +#endif + + if (!Create(domain, type, protocol)) + return false; + +#ifndef __linux__ + SetNonBlocking(); +#endif + + return true; +} + +bool +SocketDescriptor::CreateSocketPair(int domain, int type, int protocol, + SocketDescriptor &a, SocketDescriptor &b) +{ +#ifdef SOCK_CLOEXEC + /* implemented since Linux 2.6.27 */ + type |= SOCK_CLOEXEC; +#endif + + int fds[2]; + if (socketpair(domain, type, protocol, fds) < 0) + return false; + + a = SocketDescriptor(fds[0]); + b = SocketDescriptor(fds[1]); + return true; +} + +bool +SocketDescriptor::CreateSocketPairNonBlock(int domain, int type, int protocol, + SocketDescriptor &a, SocketDescriptor &b) +{ +#ifdef SOCK_CLOEXEC + /* implemented since Linux 2.6.27 */ + type |= SOCK_CLOEXEC; +#endif + if (!CreateSocketPair(domain, type, protocol, a, b)) + return false; + +#ifndef __linux__ + a.SetNonBlocking(); + b.SetNonBlocking(); +#endif + + return true; +} + +int +SocketDescriptor::GetError() +{ + assert(IsDefined()); + + int s_err = 0; + socklen_t s_err_size = sizeof(s_err); + return getsockopt(fd, SOL_SOCKET, SO_ERROR, + (char *)&s_err, &s_err_size) == 0 + ? s_err + : errno; +} + +bool +SocketDescriptor::SetOption(int level, int name, + const void *value, size_t size) +{ + assert(IsDefined()); + + return setsockopt(fd, level, name, value, size) == 0; +} + +#ifdef __linux__ + +bool +SocketDescriptor::SetReuseAddress(bool value) +{ + return SetBoolOption(SOL_SOCKET, SO_REUSEADDR, value); +} + +#ifdef SO_REUSEPORT + +bool +SocketDescriptor::SetReusePort(bool value) +{ + return SetBoolOption(SOL_SOCKET, SO_REUSEPORT, value); +} + +#endif + +bool +SocketDescriptor::SetFreeBind(bool value) +{ + return SetBoolOption(IPPROTO_IP, IP_FREEBIND, value); +} + +bool +SocketDescriptor::SetNoDelay(bool value) +{ + return SetBoolOption(IPPROTO_TCP, TCP_NODELAY, value); +} + +bool +SocketDescriptor::SetCork(bool value) +{ + return SetBoolOption(IPPROTO_TCP, TCP_CORK, value); +} + +bool +SocketDescriptor::SetTcpDeferAccept(const int &seconds) +{ + return SetOption(IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds)); +} + +bool +SocketDescriptor::SetV6Only(bool value) +{ + return SetBoolOption(IPPROTO_IPV6, IPV6_V6ONLY, value); +} + +bool +SocketDescriptor::SetBindToDevice(const char *name) +{ + return SetOption(SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name)); +} + +#ifdef TCP_FASTOPEN + +bool +SocketDescriptor::SetTcpFastOpen(int qlen) +{ + return SetOption(SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)); +} + +#endif + +#endif + +bool +SocketDescriptor::Bind(SocketAddress address) +{ + return bind(Get(), address.GetAddress(), address.GetSize()) == 0; +} + +#ifdef __linux__ + +bool +SocketDescriptor::AutoBind() +{ + static constexpr sa_family_t family = AF_LOCAL; + return Bind(SocketAddress((const struct sockaddr *)&family, + sizeof(family))); +} + +#endif + +bool +SocketDescriptor::Listen(int backlog) +{ + return listen(Get(), backlog) == 0; +} + +StaticSocketAddress +SocketDescriptor::GetLocalAddress() const +{ + assert(IsDefined()); + + StaticSocketAddress result; + result.size = result.GetCapacity(); + if (getsockname(fd, result, &result.size) < 0) + result.Clear(); + + return result; +} + +StaticSocketAddress +SocketDescriptor::GetPeerAddress() const +{ + assert(IsDefined()); + + StaticSocketAddress result; + result.size = result.GetCapacity(); + if (getpeername(fd, result, &result.size) < 0) + result.Clear(); + + return result; +} + +ssize_t +SocketDescriptor::Read(void *buffer, size_t length) +{ + int flags = 0; +#ifndef _WIN32 + flags |= MSG_DONTWAIT; +#endif + + return ::recv(Get(), (char *)buffer, length, flags); +} + +ssize_t +SocketDescriptor::Write(const void *buffer, size_t length) +{ + int flags = 0; +#ifdef __linux__ + flags |= MSG_NOSIGNAL; +#endif + + return ::send(Get(), (const char *)buffer, length, flags); +} + +#ifdef _WIN32 + +int +SocketDescriptor::WaitReadable(int timeout_ms) const +{ + assert(IsDefined()); + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(Get(), &rfds); + + struct timeval timeout, *timeout_p = nullptr; + if (timeout_ms >= 0) { + timeout.tv_sec = unsigned(timeout_ms) / 1000; + timeout.tv_usec = (unsigned(timeout_ms) % 1000) * 1000; + timeout_p = &timeout; + } + + return select(Get() + 1, &rfds, nullptr, nullptr, timeout_p); +} + +int +SocketDescriptor::WaitWritable(int timeout_ms) const +{ + assert(IsDefined()); + + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(Get(), &wfds); + + struct timeval timeout, *timeout_p = nullptr; + if (timeout_ms >= 0) { + timeout.tv_sec = unsigned(timeout_ms) / 1000; + timeout.tv_usec = (unsigned(timeout_ms) % 1000) * 1000; + timeout_p = &timeout; + } + + return select(Get() + 1, nullptr, &wfds, nullptr, timeout_p); +} + +#endif + +ssize_t +SocketDescriptor::Read(void *buffer, size_t length, + StaticSocketAddress &address) +{ + int flags = 0; +#ifndef _WIN32 + flags |= MSG_DONTWAIT; +#endif + + socklen_t addrlen = address.GetCapacity(); + ssize_t nbytes = ::recvfrom(Get(), (char *)buffer, length, flags, + address, &addrlen); + if (nbytes > 0) + address.SetSize(addrlen); + + return nbytes; +} + +ssize_t +SocketDescriptor::Write(const void *buffer, size_t length, + SocketAddress address) +{ + int flags = 0; +#ifndef _WIN32 + flags |= MSG_DONTWAIT; +#endif +#ifdef __linux__ + flags |= MSG_NOSIGNAL; +#endif + + return ::sendto(Get(), (const char *)buffer, length, flags, + address.GetAddress(), address.GetSize()); +} diff --git a/src/net/SocketDescriptor.hxx b/src/net/SocketDescriptor.hxx new file mode 100644 index 000000000..96d7cdc35 --- /dev/null +++ b/src/net/SocketDescriptor.hxx @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2012-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SOCKET_DESCRIPTOR_HXX +#define SOCKET_DESCRIPTOR_HXX + +#include "system/FileDescriptor.hxx" + +#include + +class SocketAddress; +class StaticSocketAddress; + +/** + * An OO wrapper for a UNIX socket descriptor. + */ +class SocketDescriptor : protected FileDescriptor { +protected: + explicit constexpr SocketDescriptor(int _fd) + :FileDescriptor(_fd) {} + + explicit constexpr SocketDescriptor(FileDescriptor _fd) + :FileDescriptor(_fd) {} + +public: + SocketDescriptor() = default; + + constexpr bool operator==(SocketDescriptor other) const { + return fd == other.fd; + } + +#ifndef _WIN32 + /** + * Convert a #FileDescriptor to a #SocketDescriptor. This is only + * possible on operating systems where socket descriptors are the + * same as file descriptors (i.e. not on Windows). Use this only + * when you know what you're doing. + */ + static constexpr SocketDescriptor FromFileDescriptor(FileDescriptor fd) { + return SocketDescriptor(fd); + } + + /** + * Convert this object to a #FileDescriptor instance. This is only + * possible on operating systems where socket descriptors are the + * same as file descriptors (i.e. not on Windows). Use this only + * when you know what you're doing. + */ + constexpr const FileDescriptor &ToFileDescriptor() const { + return *this; + } +#endif + + using FileDescriptor::IsDefined; + using FileDescriptor::IsValid; + using FileDescriptor::Get; + using FileDescriptor::Set; + using FileDescriptor::Steal; + using FileDescriptor::SetUndefined; + + static constexpr SocketDescriptor Undefined() { + return SocketDescriptor(FileDescriptor::Undefined()); + } + +#ifndef _WIN32 + using FileDescriptor::SetNonBlocking; + using FileDescriptor::SetBlocking; + using FileDescriptor::EnableCloseOnExec; + using FileDescriptor::DisableCloseOnExec; + using FileDescriptor::Duplicate; + using FileDescriptor::Close; +#else + /** + * This method replaces FileDescriptor::Close(), using closesocket() + * on Windows. FileDescriptor::Close() is not virtual, so be + * careful when dealing with a FileDescriptor reference that is + * really a SocketDescriptor. + */ + void Close(); +#endif + + /** + * Create a socket. + * + * @param domain is the address domain + * @param type is the sochet type + * @param protocol is the protocol + * @return True on success, False on failure + * See man 2 socket for detailed information + */ + bool Create(int domain, int type, int protocol); + + /** + * Like Create(), but enable non-blocking mode. + */ + bool CreateNonBlock(int domain, int type, int protocol); + + static bool CreateSocketPair(int domain, int type, int protocol, + SocketDescriptor &a, SocketDescriptor &b); + static bool CreateSocketPairNonBlock(int domain, int type, int protocol, + SocketDescriptor &a, SocketDescriptor &b); + + int GetError(); + + bool SetOption(int level, int name, const void *value, size_t size); + + bool SetBoolOption(int level, int name, bool _value) { + const int value = _value; + return SetOption(level, name, &value, sizeof(value)); + } + +#ifdef __linux__ + bool SetReuseAddress(bool value=true); + bool SetReusePort(bool value=true); + bool SetFreeBind(bool value=true); + bool SetNoDelay(bool value=true); + bool SetCork(bool value=true); + + bool SetTcpDeferAccept(const int &seconds); + bool SetV6Only(bool value); + + /** + * Setter for SO_BINDTODEVICE. + */ + bool SetBindToDevice(const char *name); + + bool SetTcpFastOpen(int qlen=16); +#endif + + bool Bind(SocketAddress address); + +#ifdef __linux__ + /** + * Binds the socket to a unique abstract address. + */ + bool AutoBind(); +#endif + + bool Listen(int backlog); + + SocketDescriptor Accept(); + SocketDescriptor AcceptNonBlock(StaticSocketAddress &address) const; + + bool Connect(SocketAddress address); + + gcc_pure + StaticSocketAddress GetLocalAddress() const; + + gcc_pure + StaticSocketAddress GetPeerAddress() const; + + ssize_t Read(void *buffer, size_t length); + ssize_t Write(const void *buffer, size_t length); + +#ifdef _WIN32 + int WaitReadable(int timeout_ms) const; + int WaitWritable(int timeout_ms) const; +#else + using FileDescriptor::WaitReadable; + using FileDescriptor::WaitWritable; + using FileDescriptor::IsReadyForWriting; +#endif + + /** + * Receive a datagram and return the source address. + */ + ssize_t Read(void *buffer, size_t length, + StaticSocketAddress &address); + + /** + * Send a datagram to the specified address. + */ + ssize_t Write(const void *buffer, size_t length, + SocketAddress address); +}; + +static_assert(std::is_trivial::value, "type is not trivial"); + +#endif diff --git a/src/net/StaticSocketAddress.hxx b/src/net/StaticSocketAddress.hxx index 74427c61e..5da2cb3d7 100644 --- a/src/net/StaticSocketAddress.hxx +++ b/src/net/StaticSocketAddress.hxx @@ -39,6 +39,8 @@ * An OO wrapper for struct sockaddr_storage. */ class StaticSocketAddress { + friend class SocketDescriptor; + public: typedef SocketAddress::size_type size_type; diff --git a/src/net/UniqueSocketDescriptor.hxx b/src/net/UniqueSocketDescriptor.hxx new file mode 100644 index 000000000..32f23f556 --- /dev/null +++ b/src/net/UniqueSocketDescriptor.hxx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UNIQUE_SOCKET_DESCRIPTOR_SOCKET_HXX +#define UNIQUE_SOCKET_DESCRIPTOR_SOCKET_HXX + +#include "SocketDescriptor.hxx" + +#include +#include + +class StaticSocketAddress; + +/** + * Wrapper for a socket file descriptor. + */ +class UniqueSocketDescriptor : public SocketDescriptor { +public: + UniqueSocketDescriptor() + :SocketDescriptor(SocketDescriptor::Undefined()) {} + + explicit UniqueSocketDescriptor(SocketDescriptor _fd) + :SocketDescriptor(_fd) {} + explicit UniqueSocketDescriptor(FileDescriptor _fd) + :SocketDescriptor(_fd) {} + explicit UniqueSocketDescriptor(int _fd):SocketDescriptor(_fd) {} + + UniqueSocketDescriptor(UniqueSocketDescriptor &&other) + :SocketDescriptor(std::exchange(other.fd, -1)) {} + + ~UniqueSocketDescriptor() { + if (IsDefined()) + Close(); + } + + /** + * Release ownership and return the descriptor as an unmanaged + * #SocketDescriptor instance. + */ + SocketDescriptor Release() { + return std::exchange(*(SocketDescriptor *)this, Undefined()); + } + + UniqueSocketDescriptor &operator=(UniqueSocketDescriptor &&src) { + std::swap(fd, src.fd); + return *this; + } + + bool operator==(const UniqueSocketDescriptor &other) const { + return fd == other.fd; + } + + /** + * @return an "undefined" instance on error + */ + UniqueSocketDescriptor AcceptNonBlock(StaticSocketAddress &address) const { + return UniqueSocketDescriptor(SocketDescriptor::AcceptNonBlock(address)); + } + + static bool CreateSocketPair(int domain, int type, int protocol, + UniqueSocketDescriptor &a, + UniqueSocketDescriptor &b) { + return SocketDescriptor::CreateSocketPair(domain, type, + protocol, + a, b); + } + + static bool CreateSocketPairNonBlock(int domain, int type, int protocol, + UniqueSocketDescriptor &a, + UniqueSocketDescriptor &b) { + return SocketDescriptor::CreateSocketPairNonBlock(domain, type, + protocol, + a, b); + } +}; + +#endif