Mercurial > irccd
changeset 175:c9a95b5ee962
Irccd: update sockets code
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 25 May 2016 12:50:34 +0200 |
parents | fcfa8ac395fd |
children | b8616981cc09 |
files | lib/irccd/CMakeSources.cmake lib/irccd/cmd-watch.cpp lib/irccd/connection.cpp lib/irccd/connection.hpp lib/irccd/irccd.cpp lib/irccd/irccdctl.cpp lib/irccd/net.hpp lib/irccd/server-state.hpp lib/irccd/service-interrupt.cpp lib/irccd/service-interrupt.hpp lib/irccd/service.hpp lib/irccd/sockets.cpp lib/irccd/sockets.hpp lib/irccd/transport-client.hpp lib/irccd/transport-server.cpp lib/irccd/transport-server.hpp |
diffstat | 16 files changed, 5010 insertions(+), 4082 deletions(-) [+] |
line wrap: on
line diff
--- a/lib/irccd/CMakeSources.cmake Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/CMakeSources.cmake Wed May 25 12:50:34 2016 +0200 @@ -46,6 +46,7 @@ ${CMAKE_CURRENT_LIST_DIR}/mod-timer.hpp ${CMAKE_CURRENT_LIST_DIR}/mod-unicode.hpp ${CMAKE_CURRENT_LIST_DIR}/mod-util.hpp + ${CMAKE_CURRENT_LIST_DIR}/net.hpp ${CMAKE_CURRENT_LIST_DIR}/logger.hpp ${CMAKE_CURRENT_LIST_DIR}/module.hpp ${CMAKE_CURRENT_LIST_DIR}/options.hpp @@ -69,7 +70,6 @@ ${CMAKE_CURRENT_LIST_DIR}/service-rule.hpp ${CMAKE_CURRENT_LIST_DIR}/service-server.hpp ${CMAKE_CURRENT_LIST_DIR}/service-transport.hpp - ${CMAKE_CURRENT_LIST_DIR}/sockets.hpp ${CMAKE_CURRENT_LIST_DIR}/system.hpp ${CMAKE_CURRENT_LIST_DIR}/timer.hpp ${CMAKE_CURRENT_LIST_DIR}/transport-client.hpp @@ -143,7 +143,6 @@ ${CMAKE_CURRENT_LIST_DIR}/service-rule.cpp ${CMAKE_CURRENT_LIST_DIR}/service-server.cpp ${CMAKE_CURRENT_LIST_DIR}/service-transport.cpp - ${CMAKE_CURRENT_LIST_DIR}/sockets.cpp ${CMAKE_CURRENT_LIST_DIR}/system.cpp ${CMAKE_CURRENT_LIST_DIR}/timer.cpp ${CMAKE_CURRENT_LIST_DIR}/transport-client.cpp
--- a/lib/irccd/cmd-watch.cpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/cmd-watch.cpp Wed May 25 12:50:34 2016 +0200 @@ -215,7 +215,7 @@ if (format != "native" && format != "json") throw std::invalid_argument("invalid format given: " + format); - while (ctl.connection().isConnected()) { + for (;;) { try { auto object = ctl.connection().next(-1); auto it = events.find(object.valueOr("event", "").toString());
--- a/lib/irccd/connection.cpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/connection.cpp Wed May 25 12:50:34 2016 +0200 @@ -25,7 +25,7 @@ { m_timer.reset(); - while (isConnected()) { + for (;;) { json::Value object = next(clamp(timeout)); if (object.isObject() && object["response"].toString() == name)
--- a/lib/irccd/connection.hpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/connection.hpp Wed May 25 12:50:34 2016 +0200 @@ -29,7 +29,7 @@ #include "elapsed-timer.hpp" #include "json.hpp" -#include "sockets.hpp" +#include "net.hpp" #include "sysconfig.hpp" #include "system.hpp" #include "util.hpp" @@ -85,13 +85,6 @@ IRCCD_EXPORT void verify(const std::string &name, int timeout = 30000); /** - * Check if the socket is still connected. - * - * \return true if connected - */ - virtual bool isConnected() const noexcept = 0; - - /** * Try to connect to the host. * * \param timeout the maximum time in milliseconds @@ -149,14 +142,6 @@ } /** - * \copydoc Connection::isConnected - */ - inline bool isConnected() const noexcept override - { - return m_socket.state() == net::State::Connected; - } - - /** * \copydoc Connection::connect */ void connect(int timeout) override; @@ -175,14 +160,22 @@ template <typename Address> void ConnectionBase<Address>::connect(int timeout) { - m_socket.connect(m_address); + net::Condition cond; + + m_socket.connect(m_address, cond); + m_timer.reset(); - if (m_socket.state() == net::State::Connecting) { - m_listener.set(m_socket.handle(), net::Condition::Writable); - m_listener.wait(timeout); - m_socket.connect(); - m_listener.unset(m_socket.handle(), net::Condition::Writable); + while (cond != net::Condition::None) { + if (timeout >= 0 && m_timer.elapsed() >= static_cast<unsigned>(timeout)) + throw std::runtime_error("timeout while connecting"); + + m_listener.remove(m_socket.handle()); + m_listener.set(m_socket.handle(), cond); + m_listener.wait(timeout - m_timer.elapsed()); + m_socket.resumeConnect(cond); } + + m_listener.set(m_socket.handle(), net::Condition::Readable); } template <typename Address> @@ -221,7 +214,7 @@ m_timer.reset(); // Read if there is nothing. - while (buffer.empty() && isConnected()) { + while (buffer.empty()) { // Wait and read. m_listener.wait(clamp(timeout)); m_input += m_socket.recv(512); @@ -230,9 +223,6 @@ buffer = util::nextNetwork(m_input); } - if (!isConnected()) - throw std::runtime_error("connection lost"); - json::Value value = json::fromString(buffer); if (!value.isObject())
--- a/lib/irccd/irccd.cpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/irccd.cpp Wed May 25 12:50:34 2016 +0200 @@ -18,6 +18,7 @@ #include "irccd.hpp" #include "logger.hpp" +#include "net.hpp" #include "service-command.hpp" #include "service-interrupt.hpp" #include "service-module.hpp" @@ -25,7 +26,6 @@ #include "service-rule.hpp" #include "service-server.hpp" #include "service-transport.hpp" -#include "sockets.hpp" using namespace std; using namespace std::placeholders;
--- a/lib/irccd/irccdctl.cpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/irccdctl.cpp Wed May 25 12:50:34 2016 +0200 @@ -31,10 +31,10 @@ #include "ini.hpp" #include "irccdctl.hpp" #include "json.hpp" +#include "net.hpp" #include "logger.hpp" #include "options.hpp" #include "path.hpp" -#include "sockets.hpp" #include "sysconfig.hpp" #include "system.hpp" #include "util.hpp" @@ -134,7 +134,7 @@ host = it->value(); // Port. - int port; + std::uint16_t port; if ((it = sc.find("port")) == sc.end()) throw std::invalid_argument("missing port parameter"); @@ -146,18 +146,18 @@ } // Domain. - Ip::Type domain{Ip::v4}; + int domain = AF_INET; if ((it = sc.find("domain")) != sc.end()) { if (it->value() == "ipv6") - domain = Ip::v6; + domain = AF_INET6; else if (it->value() == "ipv4") - domain = Ip::v4; + domain = AF_INET; else throw std::invalid_argument("invalid domain: " + it->value()); } - m_connection = std::make_unique<ConnectionBase<Ip>>(Ip{host, port, domain}); + m_connection = std::make_unique<ConnectionBase<Ip>>(Ip::resolve(host, std::to_string(port), domain)); } void Irccdctl::readConnectUnix(const ini::Section &sc) @@ -165,9 +165,8 @@ #if !defined(IRCCD_SYSTEM_WINDOWS) auto it = sc.find("path"); - if (it == sc.end()) { + if (it == sc.end()) throw std::invalid_argument("missing path parameter"); - } m_connection = std::make_unique<ConnectionBase<Local>>(Local{it->value(), false}); #else @@ -271,18 +270,21 @@ host = it->second; // Port (-p or --port). - int port; + std::uint16_t port; if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) throw std::invalid_argument("missing port argument (-p or --port)"); try { - port = std::stoi(it->second); + port = util::toNumber<std::uint16_t>(it->second); } catch (...) { throw std::invalid_argument("invalid port number: " + it->second); } - m_connection = std::make_unique<ConnectionBase<Ip>>(Ip{host, port, (ipv6) ? Ip::v6 : Ip::v4}); + // Domain + int domain = (ipv6) ? AF_INET6 : AF_INET; + + m_connection = std::make_unique<ConnectionBase<Ip>>(Ip::resolve(host, std::to_string(port), domain, SOCK_STREAM)); } void Irccdctl::parseConnectUnix(const parser::Result &options)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/net.hpp Wed May 25 12:50:34 2016 +0200 @@ -0,0 +1,4962 @@ +/* + * net.hpp -- portable C++ socket wrappers + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef NET_HPP +#define NET_HPP + +/** + * \file net.hpp + * \brief Networking + * \author David Demelier <markand@malikania.fr> + */ + +/** + * \defgroup net-module-tcp Network TCP support + * \brief TCP support in the networking module. + */ + +/** + * \defgroup net-module-udp Network UDP support + * \brief UDP support in networking module. + */ + +/** + * \defgroup net-module-tls Network TLS support + * \brief TLS support in networking module. + */ + +/** + * \defgroup net-module-addresses Network predefined addresses + * \brief Predefined addresses for sockets + */ + +/** + * \defgroup net-module-options Network predefined options + * \brief Predefined options for sockets + */ + +/** + * \defgroup net-module-backends Network predefined backends + * \brief Predefined backends for Listener + */ + +/** + * \defgroup net-module-resolv Network resolver + * \brief Resolv functions + */ + +/** + * \page Networking Networking + * + * - \subpage net-configuration + * - \subpage net-options + * - \subpage net-concepts + */ + +/** + * \page net-configuration Configuration + * + * # General compatibility options. + * + * The following options are auto detected but you can override them if you want. + * + * - **NET_HAVE_INET_PTON**: (bool) Set to 1 if you have inet_pton function. True for all platforms and Windows + * if _WIN32_WINNT is greater or equal to 0x0600. + * + * - **NET_HAVE_INET_NTOP**: (bool) Same as above. + * + * **Note:** On Windows, it is highly encouraged to set _WIN32_WINNT to at least 0x0600 on MinGW. + * + * # Options for Listener class + * + * Feature detection, multiple implementations may be avaible, for example, Linux has poll, select and epoll. + * + * We assume that `select(2)` is always available. + * + * Of course, you can set the variables yourself if you test it with your build system. + * + * - **NET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows + * if _WIN32_WINNT is set to 0x0600 or greater. + * + * - **NET_HAVE_KQUEUE**: Defined on all BSD and Apple. + * - **NET_HAVE_EPOLL**: Defined on Linux only. + * - **NET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). + * + * The preference priority is ordered from left to right. + * + * | System | Backend | Class name | + * |---------------|-------------------------|--------------| + * | Linux | epoll(7) | Epoll | + * | *BSD | kqueue(2) | Kqueue | + * | Windows | poll(2), select(2) | Poll, Select | + * | Mac OS X | kqueue(2) | Kqueue | + */ + +/** + * \page net-options User options + * + * The user may set the following variables before compiling these files: + * + * # General options + * + * - **NET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to + * automatically calls net::init function and net::finish functions. + * + * - **NET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. + * + * - **NET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class with Tls to automatically init + * the OpenSSL library. You will need to call net::ssl::init and net::ssl::finish. + */ + +/** + * \page net-concepts Concepts + * + * - \subpage net-concept-address + * - \subpage net-concept-backend + * - \subpage net-concept-option + * - \subpage net-concept-stream + * - \subpage net-concept-datagram + */ + +/** + * \page net-concept-address Address (Concept) + * + * An address is used in many place for creating, binding, connecting, receiving and sending. They are implemented as + * templates to allow any type of address and to make sure the same address is used for a given socket. + * + * This concepts requires the following functions: + * + * # Address (constructor) + * + * The address must have the following constructor overloads. + * + * **Note**: the user can add custom constructors. + * + * ## Synopsis + * + * ```` + * Address(); // (0); + * Address(const sockaddr *sa, socklen_t length); // (1) + * ```` + * + * ## Arguments + * + * - **ss**: the storage to construct from, + * - **length**: the storage length. + * + * # domain + * + * Get the domain (e.g. AF_INET). + * + * ## Synopsis + * + * ```` + * int domain() const noexcept; + * ```` + * + * ## Returns + * + * The domain. + * + * # address + * + * Get the underlying address. + * + * ## Synopsis + * + * ```` + * const sockaddr *address() const noexcept; + * ```` + * + * ## Returns + * + * The address. + * + * # length + * + * Get the underlying length. + * + * ## Synopsis + * + * ```` + * socklen_t length() const noexcept; + * ```` + * + * ## Returns + * + * The length. + */ + +/** + * \page net-concept-backend Backend (Concept) + * + * A backend is an interface for the Listener class. It is primarily designed to be the most suitable for the host + * environment. + * + * The backend must be default constructible, it is highly encouraged to be move constructible. + * + * This concepts requires the following functions: + * + * # name + * + * Get the backend name, informational only. + * + * ## Synopsis + * + * ```` + * std::string name() const; + * ```` + * + * ## Returns + * + * The backend name. + * + * # set + * + * Set one or more condition for the given handle. + * + * ## Synopsis + * + * ```` + * void set(const ListenerTable &table, Handle handle, Condition condition, bool add); + * ```` + * + * ## Arguments + * + * - **table**: the current table of sockets, + * - **handle**: the handle to set, + * - **condition**: the condition to add (may be OR'ed), + * - **add**: hint set to true if the handle is not currently registered at all. + * + * # unset + * + * Unset one or more condition for the given handle. + * + * ## Synopsis + * + * ```` + * void unset(const ListenerTable &table, Handle handle, Condition condition, bool remove); + * ```` + * + * ## Arguments + * + * - **table**: the current table of sockets, + * - **handle**: the handle to update, + * - **condition**: the condition to remove (may be OR'ed), + * - **remove**: hint set to true if the handle will be completely removed. + * + * # wait + * + * Wait for multiple sockets to be ready. + * + * ## Synopsis + * + * ```` + * std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); + * ```` + * + * ## Arguments + * + * - **table**: the current table, + * - **ms**: the number to wait in milliseconds, negative means forever. + * + * ## Returns + * + * The list of sockets ready paired to their condition. + */ + +/** + * \page net-concept-option Option (Concept) + * + * An option can be set or get from a socket. + * + * If an operation is not available, provides the function but throws an exception with ENOSYS errno code. + * + * This concepts requires the following functions: + * + * # Option (constructor) + * + * At least one default constructor must be present. + * + * ## Synopsis + * + * ```` + * Option() noexcept; + * ```` + * + * # set + * + * Set the option. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * inline void set(Socket<Address, Protocol> &sc) const; + * ```` + * + * ## Arguments + * + * - **sc**: the socket. + * + * # get + * + * Get an option, T can be any type. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * inline bool get(Socket<Address, Protocol> &sc) const; + * ```` + * + * ## Arguments + * + * - **sc**: the socket. + * + * ## Returns + * + * The value. + */ + +/** + * \page net-concept-stream Stream (Concept) + * + * This concepts requires the following functions: + * + * # type + * + * Return the type of socket, usually `SOCK_STREAM`. + * + * ## Synopsis + * + * ```` + * int type() const noexcept; + * ```` + * + * ## Returns + * + * The type of socket. + * + * # create + * + * Function called immediately after creation of socket. Interface must provides this function even if the + * interface does not require anything. + * + * ## Synopsis + * + * ```` + * template <typename Address> + * void create(Socket<Address, Tcp> &) const noexcept; + * ```` + * + * # connect + * + * Initial connect function. + * + * In this function, the interface receive the socket, address and a condition (initially set to None). If the + * underlying socket is marked non-blocking and the operation would block, the interface must set the condition + * to the required one. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond); + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **address**: the address, + * - **length**: the address length, + * - **cond**: the condition to update. + * + * # resumeConnect + * + * Continue the connection. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * void resumeConnect(Socket<Address, Protocol> &sc, Condition &cond) + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **cond**: the condition to update. + * + * # accept + * + * Accept a new client. + * + * If the interface has no pending connection, an invalid socket SHOULD be returned, otherwise return the client and + * set the condition if the accept process is not complete yet. + * + * The interface MUST stores the client information into address and length parameters, they are guaranted to never be + * null. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length, Condition &cond); + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **address**: the information address, + * - **length**: the address initial length (sockaddr_storage), + * - **cond**: the condition to update. + * + * # resumeAccept + * + * Continue the accept process on the returned client. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * void accept(Socket<Address, Protocol> &sc, Condition &cond) const noexcept; + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **cond**: the condition to update. + * + * # recv + * + * Receive data. + * + * ## Synopsis + * + * ```` + * template <typename Address> + * std::size_t recv(Socket<Address, Tcp> &sc, void *data, std::size_t length, Condition &cond); + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **data**: the destination buffer, + * - **length**: the destination buffer length, + * - **cond**: the condition to update. + * + * ## Returns + * + * The number of bytes sent. + * + * # send + * + * Send data. + * + * ## Synopsis + * + * ```` + * template <typename Address> + * std::size_t send(Socket<Address, Tcp> &sc, const void *data, std::size_t length, Condition &cond); + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **data**: the data to send, + * - **length**: the data length, + * - **cond**: the condition to update. + * + * ## Returns + * + * The number of bytes sent. + */ + +/** + * \page net-concept-datagram Datagram (Concept) + * + * This concepts requires the following functions: + * + * # type + * + * Return the type of socket, usually `SOCK_DGRAM`. + * + * ## Synopsis + * + * ```` + * int type() const noexcept; + * ```` + * + * ## Returns + * + * The type of socket. + * + * # recvfrom + * + * Receive data. + * + * ## Synopsis + * + * ```` + * template <typename Address, typename Protocol> + * std::size_t recvfrom(Socket<Address, Protocol> &sc, void *data, std::size_t length, sockaddr *address, socklen_t *addrlen, Condition &cond); + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **data**: the data, + * - **length**: the length, + * - **address**: the source address, + * - **addrlen**: the source address in/out length, + * - **cond**: the condition. + * + * ## Returns + * + * The number of bytes received. + * + * # sendto + * + * ```` + * template <typename Address, typename Protocol> + * std::size_t sendto(Socket<Address, Protocol> &sc, const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen, Condition &cond); + * ```` + * + * ## Arguments + * + * - **sc**: the socket, + * - **data**: the data to send, + * - **length**: the data length, + * - **address**: the destination address, + * - **addrlen**: the destination address length, + * - **cond**: the condition. + * + * ## Returns + * + * The number of bytes sent. + */ + +/* + * Headers to include. + * ------------------------------------------------------------------ + */ + +// Include Windows headers before because it brings _WIN32_WINNT if not specified by the user. +#if defined(_WIN32) +# include <WinSock2.h> +# include <WS2tcpip.h> +#else +# include <sys/ioctl.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/un.h> + +# include <arpa/inet.h> + +# include <netinet/in.h> +# include <netinet/tcp.h> + +# include <fcntl.h> +# include <netdb.h> +# include <unistd.h> +#endif + +#include <algorithm> +#include <atomic> +#include <cassert> +#include <cerrno> +#include <chrono> +#include <climits> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <iterator> +#include <memory> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#if !defined(NET_NO_SSL) + +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/ssl.h> + +#endif // !NET_NO_SSL + +/* + * Determine which I/O multiplexing implementations are available. + * ------------------------------------------------------------------ + * + * May define the following: + * + * - NET_HAVE_EPOLL + * - NET_HAVE_KQUEUE + * - NET_HAVE_POLL + */ + +#if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 && !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +# if !defined(NET_HAVE_KQUEUE) +# define NET_HAVE_KQUEUE +# endif +# if !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif +#elif defined(__linux__) +# if !defined(NET_HAVE_EPOLL) +# define NET_HAVE_EPOLL +# endif +# if !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif +#endif + +/* + * Compatibility macros. + * ------------------------------------------------------------------ + */ + +/** + * \brief Tells if inet_pton is available + */ +#if !defined(NET_HAVE_INET_PTON) +# if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 +# define NET_HAVE_INET_PTON +# endif +# else +# define NET_HAVE_INET_PTON +# endif +#endif + +/** + * \brief Tells if inet_ntop is available + */ +#if !defined(NET_HAVE_INET_NTOP) +# if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 +# define NET_HAVE_INET_NTOP +# endif +# else +# define NET_HAVE_INET_NTOP +# endif +#endif + +/* + * Define NET_DEFAULT_BACKEND. + * ------------------------------------------------------------------ + * + * Define the default I/O multiplexing implementation to use if not specified. + */ + +/** + * \brief Defines the default backend + */ +#if defined(_WIN32) +# if !defined(NET_DEFAULT_BACKEND) +# if defined(NET_HAVE_POLL) +# define NET_DEFAULT_BACKEND Poll +# else +# define NET_DEFAULT_BACKEND Select +# endif +# endif +#elif defined(__linux__) +# include <sys/epoll.h> + +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Epoll +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +# include <sys/types.h> +# include <sys/event.h> +# include <sys/time.h> + +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Kqueue +# endif +#else +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Select +# endif +#endif + +#if defined(NET_HAVE_POLL) && !defined(_WIN32) +# include <poll.h> +#endif + +namespace irccd { + +/** + * The network namespace. + */ +namespace net { + +/* + * Portables types. + * ------------------------------------------------------------------ + * + * The following types are defined differently between Unix and Windows. + */ + +#if defined(_WIN32) + +/** + * Socket type, SOCKET. + */ +using Handle = SOCKET; + +/** + * Argument to pass to set. + */ +using ConstArg = const char *; + +/** + * Argument to pass to get. + */ +using Arg = char *; + +#else + +/** + * Socket type, int. + */ +using Handle = int; + +/** + * Argument to pass to set. + */ +using ConstArg = const void *; + +/** + * Argument to pass to get. + */ +using Arg = void *; + +#endif + +/* + * Portable constants. + * ------------------------------------------------------------------ + * + * These constants are needed to check functions return codes, they are rarely needed in end user code. + */ + +#if defined(_WIN32) + +/** + * Socket creation failure or invalidation. + */ +const Handle Invalid{INVALID_SOCKET}; + +/** + * Socket operation failure. + */ +const int Failure{SOCKET_ERROR}; + +#else + +/** + * Socket creation failure or invalidation. + */ +const Handle Invalid{-1}; + +/** + * Socket operation failure. + */ +const int Failure{-1}; + +#endif + +/** + * Close the socket library. + */ +inline void finish() noexcept +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +/** + * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you don't need to call this + * function manually. + */ +inline void init() noexcept +{ +#if defined(_WIN32) + static std::atomic<bool> initialized; + static std::mutex mutex; + + std::lock_guard<std::mutex> lock(mutex); + + if (!initialized) { + initialized = true; + + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + + // If NET_NO_AUTO_INIT is not set then the user must also call finish himself. +#if !defined(NET_NO_AUTO_INIT) + atexit(finish); +#endif + } +#endif +} + +/** + * Get the last system error. + * + * \param errn the error number (errno or WSAGetLastError) + * \return the error + */ +inline std::string error(int errn) +{ +#if defined(_WIN32) + LPSTR str = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errn, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&str, 0, NULL); + + + if (str) { + errmsg = std::string(str); + LocalFree(str); + } + + return errmsg; +#else + return strerror(errn); +#endif +} + +/** + * Get the last socket system error. The error is set from errno or from WSAGetLastError on Windows. + * + * \return a string message + */ +inline std::string error() +{ +#if defined(_WIN32) + return error(WSAGetLastError()); +#else + return error(errno); +#endif +} + +#if !defined(NET_NO_SSL) + +namespace ssl { + +/** + * \enum Method + * \brief Which OpenSSL method to use. + */ +enum Method { + Tlsv1, //!< TLS v1.2 (recommended) + Sslv3 //!< SSLv3 +}; + +/** + * Initialize the OpenSSL library. Except if you defined NET_NO_AUTO_SSL_INIT, you don't need to call this function + * manually. + */ +inline void init() noexcept +{ + static std::atomic<bool> initialized; + static std::mutex mutex; + + std::lock_guard<std::mutex> lock(mutex); + + if (!initialized) { + initialized = true; + + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + +#if !defined(NET_NO_AUTO_SSL_INIT) + atexit(finish); +#endif + } +} + +/** + * Close the OpenSSL library. + */ +inline void finish() noexcept +{ + ERR_free_strings(); +} + +} // !ssl + +#endif // !NET_NO_SSL + +/* + * Error class. + * ------------------------------------------------------------------ + * + * This is the main exception thrown on socket operations. + */ + +/** + * \brief Base class for sockets error + */ +class Error : public std::exception { +public: + /** + * \enum Code + * \brief Which kind of error + */ + enum Code { + Timeout, ///!< The action did timeout + System, ///!< There is a system error + Other ///!< Other custom error + }; + +private: + Code m_code; + std::string m_function; + std::string m_error; + +public: + /** + * Constructor that use the last system error. + * + * \param code which kind of error + * \param function the function name + */ + inline Error(Code code, std::string function) + : m_code(code) + , m_function(std::move(function)) + , m_error(error()) + { + } + + /** + * Constructor that use the system error set by the user. + * + * \param code which kind of error + * \param function the function name + * \param n the error + */ + inline Error(Code code, std::string function, int n) + : m_code(code) + , m_function(std::move(function)) + , m_error(error(n)) + { + } + + /** + * Constructor that set the error specified by the user. + * + * \param code which kind of error + * \param function the function name + * \param error the error + */ + inline Error(Code code, std::string function, std::string error) + : m_code(code) + , m_function(std::move(function)) + , m_error(std::move(error)) + { + } + + /** + * Get which function has triggered the error. + * + * \return the function name (e.g connect) + */ + inline const std::string &function() const noexcept + { + return m_function; + } + + /** + * The error code. + * + * \return the code + */ + inline Code code() const noexcept + { + return m_code; + } + + /** + * Get the error (only the error content). + * + * \return the error + */ + const char *what() const noexcept override + { + return m_error.c_str(); + } +}; + +/* + * Condition enum + * ------------------------------------------------------------------ + * + * Defines if we must wait for reading or writing. + */ + +/** + * \enum Condition + * \brief Define the required condition for the socket. + * + * As explained in Action enumeration, some operations required to be called several times, before calling these + * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. + */ +enum class Condition { + None, //!< No condition is required + Readable = (1 << 0), //!< The socket must be readable + Writable = (1 << 1) //!< The socket must be writable +}; + +/** + * Apply bitwise XOR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition operator^(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); +} + +/** + * Apply bitwise AND. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition operator&(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); +} + +/** + * Apply bitwise OR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition operator|(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); +} + +/** + * Apply bitwise NOT. + * + * \param v the value + * \return the complement + */ +inline Condition operator~(Condition v) noexcept +{ + return static_cast<Condition>(~static_cast<int>(v)); +} + +/** + * Assign bitwise OR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition &operator|=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); + + return v1; +} + +/** + * Assign bitwise AND. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition &operator&=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); + + return v1; +} + +/** + * Assign bitwise XOR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition &operator^=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); + + return v1; +} + +/* + * Base Socket class + * ------------------------------------------------------------------ + * + * This base class has operations that are common to all types of sockets but you usually instanciate + * a SocketTcp or SocketUdp + */ + +/** + * \brief Base socket class for socket operations. + * + * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the + * underlying protocol for more details. + * + * When using non-blocking sockets, it is important to pass the condition to functions which may block, they indicate + * the condition to wait to perform or continue the operation if they would block. + * + * For example, when trying to connect with non-blocking, user should do the following: + * + * 1. Call Socket::connect() with the condition, + * 2. Loop until condition is not set to Condition::None (or an exception is thrown), + * 3. Wait with a listener for the condition to be ready (see Listener::poll), + * 4. Call Socket::resumeConnect() with the condition again. + * + * \see protocol::Tls + * \see protocol::Tcp + * \see protocol::Udp + */ +template <typename Address, typename Protocol> +class Socket { +private: + Protocol m_proto; + +protected: + /** + * The native handle. + */ + Handle m_handle{Invalid}; + +public: + /** + * Create a socket handle. + * + * This is the primary function and the only one that creates the socket handle, all other constructors + * are just overloaded functions. + * + * \param domain the domain AF_* + * \param type the type SOCK_* + * \param protocol the protocol + * \param iface the implementation + * \throw net::Error on errors + */ + Socket(int domain, int type, int protocol, Protocol iface = {}) + : m_proto(std::move(iface)) + { +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + m_handle = ::socket(domain, type, protocol); + + if (m_handle == Invalid) + throw Error(Error::System, "socket"); + + m_proto.create(*this); + } + + /** + * This tries to create a socket. + * + * Domain and type are determined by the Address and Protocol object. + * + * \param address which type of address + * \param protocol the protocol + * \throw net::Error on errors + */ + explicit inline Socket(const Address &address = {}, Protocol protocol = {}) + : Socket(address.domain(), protocol.type(), 0, std::move(protocol)) + { + } + + /** + * Create the socket with an already defined handle and its protocol. + * + * \param handle the handle + * \param protocol the protocol + */ + explicit inline Socket(Handle handle, Protocol protocol = {}) noexcept + : m_proto(std::move(protocol)) + , m_handle(handle) + { + } + + /** + * Create an invalid socket. Can be used when you cannot instanciate the socket immediately. + */ + explicit inline Socket(std::nullptr_t) noexcept + : m_handle(Invalid) + { + } + + /** + * Copy constructor deleted. + */ + Socket(const Socket &) = delete; + + /** + * Transfer ownership from other to this. + * + * \param other the other socket + */ + inline Socket(Socket &&other) noexcept + : m_proto(std::move(other.m_proto)) + , m_handle(other.m_handle) + { + other.m_handle = Invalid; + } + + /** + * Default destructor. + */ + virtual ~Socket() + { + close(); + } + + /** + * Access the implementation. + * + * \return the implementation + * \warning use this function with care + */ + inline const Protocol &protocol() const noexcept + { + return m_proto; + } + + /** + * Overloaded function. + * + * \return the implementation + */ + inline Protocol &protocol() noexcept + { + return m_proto; + } + + /** + * Tells if the socket is not invalid. + * + * \return true if not invalid + */ + inline bool isOpen() const noexcept + { + return m_handle != Invalid; + } + + /** + * Set an option for the socket. Wrapper of setsockopt(2). + * + * \pre isOpen() + * \param level the setting level + * \param name the name + * \param arg the value + * \throw net::Error on errors + */ + template <typename Argument> + inline void set(int level, int name, const Argument &arg) + { + assert(m_handle != Invalid); + + if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) + throw Error(Error::System, "set"); + } + + /** + * Object-oriented option setter. + * + * The object must have `set(Socket<Address, Protocol> &) const`. + * + * \pre isOpen() + * \param option the option + * \throw net::Error on errors + */ + template <typename Option> + inline void set(const Option &option) + { + assert(m_handle != Invalid); + + option.set(*this); + } + + /** + * Get an option for the socket. Wrapper of getsockopt(2). + * + * \pre isOpen() + * \param level the setting level + * \param name the name + * \return the value + * \throw net::Error on errors + */ + template <typename Argument> + Argument get(int level, int name) + { + assert(m_handle != Invalid); + + Argument desired, result{}; + socklen_t size = sizeof (result); + + if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) + throw Error(Error::System, "get"); + + std::memcpy(&result, &desired, size); + + return result; + } + + /** + * Object-oriented option getter. + * + * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value + * returned from this function. + * + * \pre isOpen() + * \return the same value as get() in the option + * \throw net::Error on errors + */ + template <typename Option> + inline auto get() -> decltype(std::declval<Option>().get(*this)) + { + assert(m_handle != Invalid); + + return Option().get(*this); + } + + /** + * Get the native handle. + * + * \return the handle + * \warning Not portable + */ + inline Handle handle() const noexcept + { + return m_handle; + } + + /** + * Bind using a native address. + * + * \pre isOpen() + * \param address the address + * \param length the size + * \throw net::Error on errors + */ + inline void bind(const sockaddr *address, socklen_t length) + { + assert(m_handle != Invalid); + + if (::bind(m_handle, address, length) == Failure) + throw Error(Error::System, "bind"); + } + + /** + * Overload that takes an address. + * + * \pre isOpen() + * \param address the address + * \throw net::Error on errors + */ + inline void bind(const Address &address) + { + assert(m_handle != Invalid); + + if (::bind(m_handle, address.address(), address.length()) == Failure) + throw Error(Error::System, "bind"); + } + + /** + * Listen for pending connection. + * + * \pre isOpen() + * \param max the maximum number + * \throw net::Error on errors + */ + inline void listen(int max = 128) + { + assert(m_handle != Invalid); + + if (::listen(this->m_handle, max) == Failure) + throw Error(Error::System, "listen"); + } + + /** + * Get the local name. This is a wrapper of getsockname(). + * + * \pre isOpen() + * \return the address + * \throw Error on failures + */ + Address getsockname() const + { + assert(m_handle != Invalid); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getsockname(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) + throw Error(Error::System, "getsockname"); + + return Address(reinterpret_cast<sockaddr *>(&ss), length); + } + + /** + * Get connected address. This is a wrapper for getpeername(). + * + * \pre isOpen() + * \return the address + * \throw Error on failures + */ + Address getpeername() const + { + assert(m_handle != Invalid); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getpeername(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) + throw Error(Error::System, "getpeername"); + + return Address(reinterpret_cast<sockaddr *>(&ss), length); + } + + /** + * Initialize connection to the given address. + * + * \pre isOpen() + * \param address the address + * \param length the address length + * \param cond the condition + * \throw net::Error on failures + */ + inline void connect(const sockaddr *address, socklen_t length, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + m_proto.connect(*this, address, length, cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param address the address + * \param length the address length + * \throw net::Error on failures + */ + inline void connect(const sockaddr *address, socklen_t length) + { + Condition dummy; + + connect(address, length, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param address the address + * \param cond the condition + * \throw net::Error on failures + */ + inline void connect(const Address &address, Condition &cond) + { + connect(address.address(), address.length(), cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param address the address + * \throw net::Error on failures + */ + inline void connect(const Address &address) + { + Condition dummy; + + connect(address.address(), address.length(), dummy); + } + + /** + * Continue connect process. + * + * \pre isOpen() + * \param cond the condition require for next selection + * \throw net::Error on failures + */ + inline void resumeConnect(Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + m_proto.resumeConnect(*this, cond); + } + + /** + * Continue connect process. + * + * \pre isOpen() + * \throw net::Error on failures + */ + inline void resumeConnect() + { + Condition dummy; + + resumeConnect(dummy); + } + + /** + * Accept a new client. + * + * If no connection is available immediately, returns an invalid socket. + * + * \pre isOpen() + * \param address the client information + * \param cond the condition to wait to complete accept on the **client** + * \return the new client or an invalid if no client is immediately available + * \throw net::Error on failures + */ + Socket<Address, Protocol> accept(Address &address, Condition &cond) + { + assert(m_handle != Invalid); + + sockaddr_storage storage; + socklen_t length = sizeof (storage); + + cond = Condition::None; + + Socket<Address, Protocol> client = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length, cond); + + address = Address(reinterpret_cast<sockaddr *>(&storage), length); + + return client; + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param address the client information + * \return the new client or an invalid if no client is immediately available + * \throw net::Error on failures + */ + inline Socket<Address, Protocol> accept(Address &address) + { + Condition dummy; + + return accept(address, dummy); + } + + /** + * Overlaoded function. + * + * \pre isOpen() + * \return the new client or an invalid if no client is immediately available + * \throw net::Error on failures + */ + inline Socket<Address, Protocol> accept() + { + Address da; + Condition dc; + + return accept(da, dc); + } + + /** + * Continue accept process. + * + * \pre isOpen() + * \param cond the condition + * \throw net::Error on failures + * \note This should be called on the returned client from accept + */ + inline void resumeAccept(Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + m_proto.resumeAccept(*this, cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \throw net::Error on failures + */ + inline void resumeAccept() + { + Condition dummy; + + resumeAccept(dummy); + } + + /** + * Receive some data. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the data length + * \param cond the condition + * \return the number of bytes received + * \throw net::Error on failures + */ + inline std::size_t recv(void *data, std::size_t length, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.recv(*this, data, length, cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the data length + * \return the number of bytes received + * \throw net::Error on failures + */ + inline std::size_t recv(void *data, std::size_t length) + { + Condition dummy; + + return recv(data, length, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param count number of bytes desired + * \param cond the condition + * \return the result string + * \throw net::Error on failures + */ + std::string recv(std::size_t count, Condition &cond) + { + assert(m_handle != Invalid); + + std::string result; + + result.resize(count); + auto n = recv(const_cast<char *>(result.data()), count, cond); + result.resize(n); + + return result; + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param count number of bytes desired + * \return the result string + * \throw net::Error on failures + */ + inline std::string recv(std::size_t count) + { + Condition dummy; + + return recv(count, dummy); + } + + /** + * Send some data. + * + * \pre isOpen() + * \param data the data to send + * \param length the length + * \param cond the condition + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t send(const void *data, std::size_t length, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.send(*this, data, length, cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data to send + * \param length the length + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t send(const void *data, std::size_t length) + { + Condition dummy; + + return send(data, length, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data to send + * \param cond the condition + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t send(const std::string &data, Condition &cond) + { + return send(data.c_str(), data.length(), cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data to send + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t send(const std::string &data) + { + Condition dummy; + + return send(data.c_str(), data.length(), dummy); + } + + /** + * Send some data to the given client. + * + * \pre isOpen() + * \param data the data + * \param length the length + * \param address the client address + * \param addrlen the client address length + * \param cond the condition + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t sendto(const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.sendto(*this, data, length, address, addrlen, cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data + * \param length the length + * \param address the client address + * \param addrlen the client address length + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t sendto(const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen) + { + Condition dummy; + + return send(data, length, address, addrlen, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data + * \param length the length + * \param address the client address + * \param cond the condition + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t sendto(const void *data, std::size_t length, const Address &address, Condition &cond) + { + return sendto(data, length, address.address(), address.length(), cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data + * \param length the length + * \param address the client address + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t sendto(const void *data, std::size_t length, const Address &address) + { + Condition dummy; + + return sendto(data, length, address.address(), address.length(), dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data + * \param cond the condition + * \param address the client address + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t sendto(const std::string &data, const Address &address, Condition &cond) + { + return sendto(data.c_str(), data.length(), address.address(), address.length(), cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the data + * \param address the client address + * \return the number of bytes sent + * \throw net::Error on failures + */ + inline std::size_t sendto(const std::string &data, const Address &address) + { + Condition dummy; + + return sendto(data.c_str(), data.length(), address.address(), address.length(), dummy); + } + + /** + * Receive some data from a client. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the buffer length + * \param address the client information + * \param addrlen the client address initial length + * \param cond the condition + * \return the number of bytes received + * \throw net::Error on failures + */ + inline std::size_t recvfrom(void *data, std::size_t length, sockaddr *address, socklen_t *addrlen, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.recvfrom(*this, data, length, address, addrlen, cond); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the buffer length + * \param address the client information + * \param addrlen the client address initial length + * \return the number of bytes received + * \throw net::Error on failures + */ + inline std::size_t recvfrom(void *data, std::size_t length, sockaddr *address, socklen_t *addrlen) + { + Condition dummy; + + return recvfrom(data, length, address, addrlen, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the buffer length + * \param address the client information + * \param cond the condition + * \return the number of bytes received + * \throw net::Error on failures + */ + std::size_t recvfrom(void *data, std::size_t length, Address &address, Condition &cond) + { + sockaddr_storage storage; + socklen_t addrlen = sizeof (sockaddr_storage); + + auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen, cond); + + if (n != 0 && cond == Condition::None) + address = Address(reinterpret_cast<sockaddr *>(&storage), addrlen); + + return n; + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the buffer length + * \param address the client information + * \return the number of bytes received + * \throw net::Error on failures + */ + inline std::size_t recvfrom(void *data, std::size_t length, Address &address) + { + Condition dummy; + + return recvfrom(data, length, address, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param data the destination buffer + * \param length the buffer length + * \return the number of bytes received + * \throw net::Error on failures + */ + inline std::size_t recvfrom(void *data, std::size_t length) + { + Address da; + Condition dc; + + return recvfrom(data, length, da, dc); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param count the number of bytes desired + * \param address the client information + * \param cond the condition + * \return the result string + * \throw net::Error on failures + */ + std::string recvfrom(std::size_t count, Address &address, Condition &cond) + { + std::string result; + + result.resize(count); + auto n = recvfrom(const_cast<char *>(result.data()), count, address, cond); + result.resize(n); + + return result; + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param count the number of bytes desired + * \param address the client information + * \return the result string + * \throw net::Error on failures + */ + inline std::string recvfrom(std::size_t count, Address &address) + { + Condition dummy; + + return recvfrom(count, address, dummy); + } + + /** + * Overloaded function. + * + * \pre isOpen() + * \param count the number of bytes desired + * \return the result string + * \throw net::Error on failures + */ + inline std::string recvfrom(std::size_t count) + { + Address da; + Condition dc; + + return recvfrom(count, da, dc); + } + + /** + * Close the socket. + * + * Automatically called from the destructor. + */ + void close() + { + if (m_handle != Invalid) { +#if defined(_WIN32) + ::closesocket(m_handle); +#else + ::close(m_handle); +#endif + m_handle = Invalid; + } + } + + /** + * Assignment operator forbidden. + * + * \return *this + */ + Socket &operator=(const Socket &) = delete; + + /** + * Transfer ownership from other to this. The other socket is left + * invalid and will not be closed. + * + * \param other the other socket + * \return this + */ + Socket &operator=(Socket &&other) noexcept + { + m_handle = other.m_handle; + m_proto = std::move(other.m_proto); + + other.m_handle = Invalid; + + return *this; + } +}; + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if they equals + */ +template <typename Address, typename Protocol> +inline bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() == s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if they are different + */ +template <typename Address, typename Protocol> +inline bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() != s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 < s2 + */ +template <typename Address, typename Protocol> +inline bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() < s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 > s2 + */ +template <typename Address, typename Protocol> +inline bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() > s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 <= s2 + */ +template <typename Address, typename Protocol> +inline bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() <= s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 >= s2 + */ +template <typename Address, typename Protocol> +inline bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() >= s2.handle(); +} + +/** + * \brief Predefined protocols. + */ +namespace protocol { + +/** + * \brief Clear TCP implementation. + * \ingroup net-module-tcp + * + * This is the basic TCP protocol that implements recv, send, connect and accept as wrappers of the usual + * C functions. + */ +class Tcp { +public: + /** + * Socket type. + * + * \return SOCK_STREAM + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Do nothing. + * + * This function is just present for compatibility, it should never be called. + */ + template <typename Address> + inline void create(Socket<Address, Tcp> &) const noexcept + { + } + + /** + * Initiate connection. + * + * \param sc the socket + * \param address the address + * \param length the address length + * \param cond the condition + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond) + { + if (::connect(sc.handle(), address, length) == Failure) { + /* + * Determine if the error comes from a non-blocking connect that cannot be + * accomplished yet. + */ +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + cond = Condition::Writable; + else + throw Error(Error::System, "connect", error); +#else + if (errno == EINPROGRESS) + cond = Condition::Writable; + else + throw Error(Error::System, "connect"); +#endif + } + } + + /** + * Resume the connection. + * + * Just check for SOL_SOCKET/SO_ERROR. + * + * User is responsible to wait before the socket is writable, otherwise behavior is undefined. + * + * \param sc the socket + * \param cond the condition + */ + template <typename Address, typename Protocol> + void resumeConnect(Socket<Address, Protocol> &sc, Condition &cond) + { + int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); + +#if defined(_WIN32) + if (error == WSAEWOULDBLOCK) + cond = Condition::Writable; + else if (error != 0) + throw Error(Error::System, "connect", error); +#else + if (error == EINPROGRESS) + cond = Condition::Writable; + else if (error != 0) + throw Error(Error::System, "connect", error); +#endif + } + + /** + * Accept a new client. + * + * If there are no pending connection, an invalid socket is returned, condition is left to Condition::None. + * + * \param sc the socket + * \param address the address + * \param length the length + * \return the new socket + */ + template <typename Address, typename Protocol> + Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length, Condition &) + { + Handle handle = ::accept(sc.handle(), address, length); + + if (handle == Invalid) + return Socket<Address, Protocol>(); + + return Socket<Address, Protocol>(handle); + } + + /** + * Resume accept process. + * + * No-op for TCP. + */ + template <typename Address, typename Protocol> + inline void resumeAcept(Socket<Address, Protocol> &, Condition &) const noexcept + { + } + + /** + * Receive some data. + * + * \param sc the socket + * \param data the destination buffer + * \param length the buffer length + * \param cond the condition + * \return the number of byte received + */ + template <typename Address> + std::size_t recv(Socket<Address, Tcp> &sc, void *data, std::size_t length, Condition &cond) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = ::recv(sc.handle(), (Arg)data, max, 0); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbread = 0; + cond = Condition::Readable; + } else + throw Error(Error::System, "recv", error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbread = 0; + cond = Condition::Readable; + } else + throw Error(Error::System, "recv"); +#endif + } + + return static_cast<std::size_t>(nbread); + } + + /** + * Send some data. + * + * \param sc the socket + * \param data the data to send + * \param length the length + * \param cond the condition + * \return the number of bytes sent + */ + template <typename Address> + std::size_t send(Socket<Address, Tcp> &sc, const void *data, std::size_t length, Condition &cond) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = ::send(sc.handle(), (ConstArg)data, max, 0); + + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbsent = 0; + cond = Condition::Writable; + } else + throw Error(Error::System, "send", error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbsent = 0; + cond = Condition::Writable; + } else + throw Error(Error::System, "send"); +#endif + } + + return static_cast<unsigned>(nbsent); + } +}; + +/** + * \brief Clear UDP type. + * + * This class is the basic implementation of UDP sockets. + */ +class Udp { +public: + /** + * Socket type. + * + * \return SOCK_DGRAM + */ + inline int type() const noexcept + { + return SOCK_DGRAM; + } + + /** + * Do nothing. + */ + template <typename Address, typename Protocol> + inline void create(Socket<Address, Protocol> &) noexcept + { + } + + /** + * Receive some data. + * + * \param sc the socket + * \param data the data + * \param length the length + * \param address the source address + * \param addrlen the source address in/out length + * \param cond the condition + * \return the number of bytes received + */ + template <typename Address, typename Protocol> + std::size_t recvfrom(Socket<Address, Protocol> &sc, void *data, std::size_t length, sockaddr *address, socklen_t *addrlen, Condition &cond) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread; + + nbread = ::recvfrom(sc.handle(), (Arg)data, max, 0, address, addrlen); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbread = 0; + cond = Condition::Writable; + } else + throw Error(Error::System, "recvfrom"); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbread = 0; + cond = Condition::Writable; + } else + throw Error(Error::System, "recvfrom"); +#endif + } + + return static_cast<unsigned>(nbread); + } + + /** + * Send some data. + * + * \param sc the socket + * \param data the data to send + * \param length the data length + * \param address the destination address + * \param addrlen the destination address length + * \param cond the condition + * \return the number of bytes sent + */ + template <typename Address, typename Protocol> + std::size_t sendto(Socket<Address, Protocol> &sc, const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen, Condition &cond) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent; + + nbsent = ::sendto(sc.handle(), (ConstArg)data, max, 0, address, addrlen); + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbsent = 0; + cond = Condition::Writable; + } else + throw Error(Error::System, "sendto", error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbsent = 0; + cond = Condition::Writable; + } else + throw Error(Error::System, "sendto"); +#endif + } + + return static_cast<unsigned>(nbsent); + } +}; + +#if !defined(NET_NO_SSL) + +/** + * \brief Experimental TLS support. + * \ingroup net-module-tls + * \warning This class is highly experimental. + */ +class Tls : private Tcp { +private: + using Context = std::shared_ptr<SSL_CTX>; + using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; + + // OpenSSL objects. + Context m_context; + Ssl m_ssl{nullptr, nullptr}; + + // Status. + bool m_tcpconnected{false}; + + /* + * User definable parameters. + */ + ssl::Method m_method{ssl::Tlsv1}; + std::string m_key; + std::string m_certificate; + bool m_verify{false}; + + // Construct with a context and ssl, for Tls::accept. + Tls(Context context, Ssl ssl) + : m_context(std::move(context)) + , m_ssl(std::move(ssl)) + { + } + + inline std::string error() + { + BIO *bio = BIO_new(BIO_s_mem()); + char *buf = nullptr; + + ERR_print_errors(bio); + + std::size_t length = BIO_get_mem_data (bio, &buf); + std::string result(buf, length); + + BIO_free(bio); + + return result; + } + + template <typename Function> + void wrap(const std::string &func, Condition &cond, Function &&function) + { + auto ret = function(); + + if (ret <= 0) { + int no = SSL_get_error(m_ssl.get(), ret); + + switch (no) { + case SSL_ERROR_WANT_READ: + cond = Condition::Readable; + break; + case SSL_ERROR_WANT_WRITE: + cond = Condition::Writable; + break; + default: + throw Error(Error::System, func, error()); + } + } + } + + template <typename Address, typename Protocol> + void doConnect(Socket<Address, Protocol> &, Condition &cond) + { + wrap("connect", cond, [&] () -> int { + return SSL_connect(m_ssl.get()); + }); + } + + template <typename Address, typename Protocol> + void doAccept(Socket<Address, Protocol> &, Condition &cond) + { + wrap("accept", cond, [&] () -> int { + return SSL_accept(m_ssl.get()); + }); + } + +public: + /** + * \copydoc Tcp::type + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Empty TLS constructor. + */ + inline Tls() + { +#if !defined(NET_NO_SSL_AUTO_INIT) + ssl::init(); +#endif + } + + /** + * Set the method. + * + * \param method the method + * \pre the socket must not be already created + */ + inline void setMethod(ssl::Method method) noexcept + { + assert(!m_context); + assert(!m_ssl); + + m_method = method; + } + + /** + * Use the specified private key file. + * + * \param file the path to the private key + */ + inline void setPrivateKey(std::string file) noexcept + { + m_key = std::move(file); + } + + /** + * Use the specified certificate file. + * + * \param file the path to the file + */ + inline void setCertificate(std::string file) noexcept + { + m_certificate = std::move(file); + } + + /** + * Set to true if we must verify the certificate and private key. + * + * \param verify the mode + */ + inline void setVerify(bool verify = true) noexcept + { + m_verify = verify; + } + + /** + * Initialize the SSL objects after have created. + * + * \param sc the socket + * \throw net::Error on errors + */ + template <typename Address> + void create(Socket<Address, Tls> &sc) + { + auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv23_method(); + + m_context = Context(SSL_CTX_new(method), SSL_CTX_free); + m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); + + SSL_set_fd(m_ssl.get(), static_cast<int>(sc.handle())); + + /* + * Load certificates, the wrap function requires a condition so just add a dummy value. + */ + Condition dummy; + + if (m_certificate.size() > 0) + wrap("SSL_CTX_use_certificate_file", dummy, [&] () -> int { + return SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); + }); + if (m_key.size() > 0) + wrap("SSL_CTX_use_PrivateKey_file", dummy, [&] () -> int { + return SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); + }); + if (m_verify && !SSL_CTX_check_private_key(m_context.get())) + throw Error(Error::System, "(openssl)", "unable to verify key"); + } + + /** + * Initiate connection. + * + * \param sc the socket + * \param address the address + * \param length the address length + * \param cond the condition + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond) + { + // 1. Connect using raw TCP. + Tcp::connect(sc, address, length, cond); + + // 2. If the connection is complete (e.g. non-blocking), try handshake. + if (cond == Condition::None) { + m_tcpconnected = true; + doConnect(sc, cond); + } + } + + /** + * Resume the connection. + * + * \param sc the socket + * \param cond the condition to wait + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc, Condition &cond) + { + // 1. Be sure to complete standard connect before. + if (!m_tcpconnected) { + Tcp::connect(sc, cond); + m_tcpconnected = (cond == Condition::None); + } + + // 2. Do SSL connect. + if (m_tcpconnected) + doConnect(sc, cond); + } + + /** + * Accept a new client. + * + * If there are no pending connection, an invalid socket is returned, condition is left to Condition::None. + * + * \param sc the socket + * \param address the address + * \param length the length + * \param cond the condition to wait + * \return the new socket + */ + template <typename Address> + Socket<Address, Tls> accept(Socket<Address, Tls> &sc, sockaddr *address, socklen_t *length, Condition &cond) + { + // 1. TCP returns empty client if no pending connection is available. + auto client = Tcp::accept(sc, address, length, cond); + + // 2. If a client is available, try initial accept. + if (client.isOpen()) { + Tls &proto = client.protocol(); + + // 2.1. Share the context. + proto.m_context = m_context; + + // 2.2. Create new SSL instance. + proto.m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); + + SSL_set_fd(proto.m_ssl.get(), static_cast<int>(client.handle())); + + // 2.3. Try accept process on the **new** client. + proto.doAccept(client, cond); + } + + return client; + } + + /** + * Resume accept process. + * + * \param sc the socket + * \param cond the condition to wait + * \throw net::Error on failures + */ + template <typename Address, typename Protocol> + inline void accept(Socket<Address, Protocol> &sc, Condition &cond) + { + doAccept(sc, cond); + } + + /** + * Receive some data. + * + * \param data the destination buffer + * \param length the buffer length + * \param cond the condition + * \return the number of bytes received + */ + template <typename Address> + std::size_t recv(Socket<Address, Tls> &, void *data, std::size_t length, Condition &cond) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = 0; + + wrap("recv", cond, [&] () -> int { + return (nbread = SSL_read(m_ssl.get(), data, max)); + }); + + return static_cast<std::size_t>(nbread < 0 ? 0 : nbread); + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the length + * \param cond the condition + * \return the number of bytes sent + */ + template <typename Address> + std::size_t send(Socket<Address, Tls> &, const void *data, std::size_t length, Condition &cond) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = 0; + + wrap("send", cond, [&] () -> int { + return (nbsent = SSL_write(m_ssl.get(), data, max)); + }); + + return static_cast<std::size_t>(nbsent < 0 ? 0 : nbsent); + } +}; + +#endif // !NET_NO_SSL + +} // !protocol + +/** + * \brief Predefined addresses. + */ +namespace address { + +/** + * \brief Generic address. + * \ingroup net-module-addresses + * + * This address can store anything that fits into a sockaddr_storage. + */ +class GenericAddress { +private: + sockaddr_storage m_address; + socklen_t m_length{0}; + +public: + /** + * Construct a null address. + */ + inline GenericAddress() noexcept + { + std::memset(&m_address, 0, sizeof (sockaddr_storage)); + } + + /** + * Construct an address. + * + * \pre address is not null + * \pre length <= sizeof (sockaddr_storage) + * \param address the address to copy + * \param length the address length + */ + inline GenericAddress(const sockaddr *address, socklen_t length) noexcept + : m_length(length) + { + assert(address); + assert(length <= sizeof (sockaddr_storage)); + + std::memset(&m_address, 0, sizeof (sockaddr_storage)); + std::memcpy(&m_address, address, length); + } + + /** + * Get the address family. + * + * \return the address family + */ + inline int domain() const noexcept + { + return m_address.ss_family; + } + + /** + * Get the underlying address. + * + * \return the address + */ + inline sockaddr *address() noexcept + { + return reinterpret_cast<sockaddr *>(&m_address); + } + + /** + * Overloaded function. + * + * \return the address + */ + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_address); + } + + /** + * Get the underlying address length. + * + * \return the length + */ + inline socklen_t length() const noexcept + { + return m_length; + } +}; + +/** + * Compare two generic addresses. + * + * \param a1 the first address + * \param a2 the second address + * \return true if they equal + */ +inline bool operator==(const GenericAddress &a1, const GenericAddress &a2) noexcept +{ + return a1.length() == a2.length() && std::memcmp(a1.address(), a2.address(), a1.length()) == 0; +} + +/** + * Compare two generic addresses. + * + * \param a1 the first address + * \param a2 the second address + * \return false if they equal + */ +inline bool operator!=(const GenericAddress &a1, const GenericAddress &a2) noexcept +{ + return !(a1 == a2); +} + +/** + * \brief Generic IP address. + * \ingroup net-module-addresses + * + * You can use this address instead of Ipv4 or Ipv6 if you don't know which address to use at runtime. However, + * when creating your socket, you will need to define the correct domain. + */ +class Ip { +private: + union { + sockaddr_in6 m_sin6; + sockaddr_in m_sin; + }; + + int m_domain; + +public: + /** + * Create IP address, defaults to IPv4. + * + * \pre domain must be AF_INET or AF_INET6 + * \param domain the domain + */ + inline Ip(int domain = AF_INET) noexcept + : m_domain(domain) + { + assert(domain == AF_INET || domain == AF_INET6); + + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + } + + /** + * Create an IP address on the specific ip address. + * + * \pre domain must be AF_INET or AF_INET6 + * \param ip the address or "*" for any + * \param port the port + * \param domain the domain + * \warning If NET_HAVE_INET_PTON is undefined, host can not be other than "*" + * \throw net::Error on failures or if inet_pton is unavailable + */ + inline Ip(const std::string &ip, std::uint16_t port, int domain) + : Ip(domain) + { + if (m_domain == AF_INET) + make(ip, port, m_sin); + else + make(ip, port, m_sin6); + } + + /** + * Create the IP address from the storage. + * + * \pre the storage domain must be AF_INET or AF_INET6 + * \param ss the the storage + * \param length the storage length + */ + inline Ip(const sockaddr *ss, socklen_t length) noexcept + : Ip(ss->sa_family) + { + assert(ss->sa_family == AF_INET || ss->sa_family == AF_INET6); + + if (ss->sa_family == AF_INET) + std::memcpy(&m_sin, ss, length); + else + std::memcpy(&m_sin6, ss, length); + } + + /** + * Get the domain. + * + * \return AF_INET or AF_INET6 + */ + inline int domain() const noexcept + { + return m_domain; + } + + /** + * Get the underlying address, may be a sockaddr_in or sockaddr_in6. + * + * \return the address + */ + inline const sockaddr *address() const noexcept + { + return m_domain == AF_INET ? reinterpret_cast<const sockaddr *>(&m_sin) : reinterpret_cast<const sockaddr *>(&m_sin6); + } + + /** + * Get the address length. + * + * \return the address length + */ + inline socklen_t length() const noexcept + { + return m_domain == AF_INET ? sizeof (sockaddr_in) : sizeof (sockaddr_in6); + } + + /** + * Retrieve the port. + * + * \return the port + */ + inline std::uint16_t port() const noexcept + { + return m_domain == AF_INET ? ntohs(m_sin.sin_port) : ntohs(m_sin6.sin6_port); + } + + /** + * Get the ip address. + * + * \return the ip address + * \throw net::Error on errors or if inet_ntop is unavailable + */ + inline std::string ip() const + { + return m_domain == AF_INET ? ip(m_sin) : ip(m_sin6); + } + + /** + * Prepare the sockaddr_in structure with the given ip. + * + * \param ip the ip address + * \param port the port + * \param sin the Ipv4 address + * \throw net::Error if inet_pton is unavailable + */ + static void make(const std::string &ip, std::uint16_t port, sockaddr_in &sin) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + if (ip == "*") + sin.sin_addr.s_addr = INADDR_ANY; +#if defined(NET_HAVE_INET_PTON) + else if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0) + throw Error(Error::System, "inet_pton"); +#else + else + throw Error(Error::System, "inet_pton", std::strerror(ENOSYS)); +#endif + } + + /** + * Prepare the sockaddr_in structure with the given ip. + * + * \param ip the ip address + * \param port the port + * \param sin6 the Ipv6 address + * \throw net::Error if inet_pton is unavailable + */ + static void make(const std::string &ip, std::uint16_t port, sockaddr_in6 &sin6) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + + if (ip == "*") + sin6.sin6_addr = in6addr_any; +#if defined(NET_HAVE_INET_PTON) + else if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0) + throw Error(Error::System, "inet_pton"); +#else + else + throw Error(Error::System, "inet_pton", std::strerror(ENOSYS)); +#endif + } + + /** + * Get the underlying ip from the given address. + * + * \param sin the Ipv4 address + * \return the ip address + * \throw net::Error if inet_ntop is unavailable + */ + static std::string ip(const sockaddr_in &sin) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + +#if !defined(NET_HAVE_INET_NTOP) + (void)sin; + + throw Error(Error::System, "inet_ntop", std::strerror(ENOSYS)); +#else + char result[INET_ADDRSTRLEN + 1]; + + std::memset(result, 0, sizeof (result)); + + if (!inet_ntop(AF_INET, const_cast<in_addr *>(&sin.sin_addr), result, sizeof (result))) + throw Error(Error::System, "inet_ntop"); + + return result; +#endif + } + + /** + * Get the underlying ip from the given address. + * + * \param sin6 the Ipv6 address + * \return the ip address + * \throw net::Error if inet_ntop is unavailable + */ + static std::string ip(const sockaddr_in6 &sin6) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + +#if !defined(NET_HAVE_INET_NTOP) + (void)sin6; + + throw Error(Error::System, "inet_ntop", std::strerror(ENOSYS)); +#else + char result[INET6_ADDRSTRLEN]; + + std::memset(result, 0, sizeof (result)); + + if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&sin6.sin6_addr), result, sizeof (result))) + throw Error(Error::System, "inet_ntop"); + + return result; +#endif + } + + /** + * Resolve an hostname. + * + * This function wraps getaddrinfo and returns the first result. + * + * \param host the hostname + * \param service the service name (port or name) + * \param domain the domain (e.g. AF_INET) + * \param type the socket type (e.g. SOCK_STREAM) + * \return the resolved address + * \throw net::Error on failures + */ + static Ip resolve(const std::string &host, const std::string &service, int domain = AF_INET, int type = SOCK_STREAM) + { + assert(domain == AF_INET || domain == AF_INET6); +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + + struct addrinfo hints, *res; + + std::memset(&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = domain; + hints.ai_socktype = type; + + int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); + + if (e != 0) + throw Error(Error::System, "getaddrinfo", gai_strerror(e)); + + Ip ip(res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return ip; + } +}; + +/** + * \brief Ipv4 only address. + * \ingroup net-module-addresses + */ +class Ipv4 { +private: + sockaddr_in m_sin; + +public: + /** + * Create an Ipv4 address. + */ + inline Ipv4() noexcept + { + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + } + + /** + * Create an Ipv4 address on the specific ip address. + * + * \param ip the address or "*" for any + * \param port the port + * \warning If NET_HAVE_INET_PTON is undefined, host can not be other than "*" + * \throw net::Error on failures or if inet_pton is unavailable + */ + inline Ipv4(const std::string &ip, std::uint16_t port) + : Ipv4() + { + Ip::make(ip, port, m_sin); + } + + /** + * Create the IP address from the storage. + * + * \pre the storage domain must be AF_INET + * \param ss the the storage + * \param length the storage length + */ + inline Ipv4(const sockaddr *ss, socklen_t length) noexcept + { + assert(ss->sa_family == AF_INET); + + std::memcpy(&m_sin, ss, length); + } + + /** + * Get the domain. + * + * \return AF_INET + */ + inline int domain() const noexcept + { + return AF_INET; + } + + /** + * Get the underlying address. + * + * \return the address + */ + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_sin); + } + + /** + * Get the address length. + * + * \return the size of sockaddr_in + */ + inline socklen_t length() const noexcept + { + return sizeof (sockaddr_in); + } + + /** + * Get the port. + * + * \return the port + */ + inline std::uint16_t port() const noexcept + { + return ntohs(m_sin.sin_port); + } + + /** + * Get the ip address. + * + * \return the ip address + * \throw net::Error on errors or if inet_ntop is unavailable + */ + inline std::string ip() const + { + return Ip::ip(m_sin); + } + + /** + * Same as Ip::resolve with AF_INET as domain. + * + * \param host the hostname + * \param service the service name (port or name) + * \param type the socket type (e.g. SOCK_STREAM) + * \return the resolved address + * \throw net::Error on failures + */ + static Ipv4 resolve(const std::string &host, const std::string &service, int type = SOCK_STREAM) + { + Ip result = Ip::resolve(host, service, AF_INET, type); + + return Ipv4(result.address(), result.length()); + } +}; + +/** + * \brief Ipv4 only address. + * \ingroup net-module-addresses + */ +class Ipv6 { +private: + sockaddr_in6 m_sin6; + +public: + /** + * Create an Ipv6 address. + */ + inline Ipv6() noexcept + { + std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); + } + + /** + * Create an Ipv6 address on the specific ip address. + * + * \param ip the address or "*" for any + * \param port the port + * \warning If NET_HAVE_INET_PTON is undefined, host can not be other than "*" + * \throw net::Error on failures or if inet_pton is unavailable + */ + inline Ipv6(const std::string &ip, std::uint16_t port) + : Ipv6() + { + Ip::make(ip, port, m_sin6); + } + + /** + * Create the IP address from the storage. + * + * \pre the storage domain must be AF_INET6 + * \param ss the the storage + * \param length the storage length + */ + inline Ipv6(const sockaddr *ss, socklen_t length) noexcept + { + assert(ss->sa_family == AF_INET6); + + std::memcpy(&m_sin6, ss, length); + } + + /** + * Get the domain. + * + * \return AF_INET6 + */ + inline int domain() const noexcept + { + return AF_INET6; + } + + /** + * Get the underlying address. + * + * \return the address + */ + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_sin6); + } + + /** + * Get the address length. + * + * \return the size of sockaddr_in + */ + inline socklen_t length() const noexcept + { + return sizeof (sockaddr_in6); + } + + /** + * Get the port. + * + * \return the port + */ + inline std::uint16_t port() const noexcept + { + return ntohs(m_sin6.sin6_port); + } + + /** + * Get the ip address. + * + * \return the ip address + * \throw net::Error on errors or if inet_ntop is unavailable + */ + inline std::string ip() const + { + return Ip::ip(m_sin6); + } + + /** + * Same as Ip::resolve with AF_INET6 as domain. + * + * \param host the hostname + * \param service the service name (port or name) + * \param type the socket type (e.g. SOCK_STREAM) + * \return the resolved address + * \throw net::Error on failures + */ + static Ipv6 resolve(const std::string &host, const std::string &service, int type = SOCK_STREAM) + { + Ip result = Ip::resolve(host, service, AF_INET6, type); + + return Ipv6(result.address(), result.length()); + } +}; + +#if !defined(_WIN32) + +/** + * \brief unix family sockets + * \ingroup net-module-addresses + * + * Create an address to a specific path. Only available on Unix. + */ +class Local { +private: + sockaddr_un m_sun; + std::string m_path; + +public: + /** + * Get the domain AF_LOCAL. + * + * \return AF_LOCAL + */ + inline int domain() const noexcept + { + return AF_LOCAL; + } + + /** + * Default constructor. + */ + inline Local() noexcept + { + std::memset(&m_sun, 0, sizeof (sockaddr_un)); + } + + /** + * Construct an address to a path. + * + * \param path the path + * \param rm remove the file before (default: false) + */ + Local(std::string path, bool rm = false) noexcept + : m_path(std::move(path)) + { + // Silently remove the file even if it fails. + if (rm) + ::remove(m_path.c_str()); + + // Copy the path. + std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); + std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); + + // Set the parameters. + m_sun.sun_family = AF_LOCAL; + } + + /** + * Construct an unix address from a storage address. + * + * \pre storage's domain must be AF_LOCAL + * \param ss the storage + * \param length the length + */ + Local(const sockaddr *ss, socklen_t length) noexcept + { + assert(ss->sa_family == AF_LOCAL); + + std::memcpy(&m_sun, ss, length); + m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; + } + + /** + * Get the sockaddr_un. + * + * \return the address + */ + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_sun); + } + + /** + * Get the address length. + * + * \return the length + */ + inline socklen_t length() const noexcept + { +#if defined(NET_HAVE_SUN_LEN) + return SUN_LEN(&m_sun); +#else + return sizeof (m_sun); +#endif + } +}; + +#endif // !_WIN32 + +/** + * \brief Address iterator. + * \ingroup net-module-addresses + * \see resolve + * + * This iterator can be used to try to connect to an host. + * + * When you use net::resolve with unspecified domain or socket type, the function may retrieve several different addresses that you can + * iterate over to try to connect to. + * + * Example: + * + * ````cpp + * net::SocketTcpIp sc; + * net::AddressIterator end, it = net::resolve("hostname.test", "80"); + * + * while (!connected_condition && it != end) + * sc.connect(it->address(), it->length()); + * ```` + * + * When an iterator equals to a default constructed iterator, it is considered not dereferenceable. + */ +class AddressIterator : public std::iterator<std::forward_iterator_tag, GenericAddress> { +private: + std::vector<GenericAddress> m_addresses; + std::size_t m_index{0}; + +public: + /** + * Construct a null iterator. + * + * The default constructed iterator is not dereferenceable. + */ + inline AddressIterator() noexcept = default; + + /** + * Construct an address iterator with a set of addresses. + * + * \pre index < m_addresses.size() + * \param addresses the addresses + * \param index the first index + */ + inline AddressIterator(std::vector<GenericAddress> addresses, std::size_t index = 0) noexcept + : m_addresses(std::move(addresses)) + , m_index(index) + { + assert(index < m_addresses.size()); + } + + /** + * Get the generic address. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline const GenericAddress &operator*() const noexcept + { + assert(m_index <= m_addresses.size()); + + return m_addresses[m_index]; + } + + /** + * Overloaded function. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline GenericAddress &operator*() noexcept + { + assert(m_index <= m_addresses.size()); + + return m_addresses[m_index]; + } + + /** + * Get the generic address. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline const GenericAddress *operator->() const noexcept + { + assert(m_index <= m_addresses.size()); + + return &m_addresses[m_index]; + } + + /** + * Overloaded function. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline GenericAddress *operator->() noexcept + { + assert(m_index <= m_addresses.size()); + + return &m_addresses[m_index]; + } + + /** + * Pre-increment the iterator. + * + * \return this + */ + inline AddressIterator &operator++(int) noexcept + { + if (m_index + 1 >= m_addresses.size()) { + m_addresses.clear(); + m_index = 0; + } else + m_index ++; + + return *this; + } + + /** + * Post-increment the iterator. + * + * \return copy of this + */ + inline AddressIterator operator++() noexcept + { + AddressIterator save = *this; + + if (m_index + 1 >= m_addresses.size()) { + m_addresses.clear(); + m_index = 0; + } else + m_index ++; + + return save; + } + + friend bool operator==(const AddressIterator &, const AddressIterator &) noexcept; + friend bool operator!=(const AddressIterator &, const AddressIterator &) noexcept; +}; + +/** + * Compare two address iterators. + * + * \param i1 the first iterator + * \param i2 the second iterator + * \return true if they equal + */ +inline bool operator==(const AddressIterator &i1, const AddressIterator &i2) noexcept +{ + return i1.m_addresses == i2.m_addresses && i1.m_index == i2.m_index; +} + +/** + * Compare two address iterators. + * + * \param i1 the first iterator + * \param i2 the second iterator + * \return false if they equal + */ +inline bool operator!=(const AddressIterator &i1, const AddressIterator &i2) noexcept +{ + return !(i1 == i2); +} + +} // !address + +/** + * \brief Predefined options. + */ +namespace option { + +/** + * \ingroup net-module-options + * \brief Set or get the blocking-mode for a socket. + * \warning On Windows, it's not possible to check if the socket is blocking or not. + */ +class SockBlockMode { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default the blocking mode is set to true. + * + * \param value set to true to make blocking sockets + */ + inline SockBlockMode(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + template <typename Address, typename Protocol> + void set(Socket<Address, Protocol> &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags; + + if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) + flags = 0; + + if (m_value) + flags &= ~(O_NONBLOCK); + else + flags |= O_NONBLOCK; + + if (fcntl(sc.handle(), F_SETFL, flags) < 0) + throw Error(Error::System, "fcntl"); +#else + unsigned long flags = (m_value) ? 0 : 1; + + if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) + throw Error(Error::System, "fcntl"); +#endif + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + template <typename Address, typename Protocol> + bool get(Socket<Address, Protocol> &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags = fcntl(sc.handle(), F_GETFL, 0); + + if (flags < 0) + throw Error(Error::System, "fcntl"); + + return !(flags & O_NONBLOCK); +#else + (void)sc; + + throw Error(Error::Other, "get", std::strerror(ENOSYS)); +#endif + } +}; + +/** + * \ingroup net-module-options + * \brief Set or get the input buffer. + */ +class SockReceiveBuffer { +private: + int m_value; + +public: + /** + * Create the option. + * + * \param size the buffer size + */ + inline SockReceiveBuffer(int size = 2048) noexcept + : m_value(size) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_RCVBUF, m_value); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline int get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); + } +}; + +/** + * \ingroup net-module-options + * \brief Reuse address, must be used before calling Socket::bind + */ +class SockReuseAddress { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default the option reuses the address. + * + * \param value set to true to reuse the address + */ + inline SockReuseAddress(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_REUSEADDR) != 0; + } +}; + +/** + * \ingroup net-module-options + * \brief Set or get the output buffer. + */ +class SockSendBuffer { +private: + int m_value; + +public: + /** + * Create the option. + * + * \param size the buffer size + */ + inline SockSendBuffer(int size = 2048) noexcept + : m_value(size) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_SNDBUF, m_value); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline int get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); + } +}; + +/** + * \ingroup net-module-options + * \brief Set this option if you want to disable nagle's algorithm. + */ +class TcpNoDelay { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default disable TCP delay. + * + * \param value set to true to disable TCP delay + */ + inline TcpNoDelay(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(IPPROTO_TCP, TCP_NODELAY) != 0; + } +}; + +/** + * \ingroup net-module-options + * \brief Control IPPROTO_IPV6/IPV6_V6ONLY + * + * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either + * false or true if portability is a concern. + */ +class Ipv6Only { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default with want IPv6 only. + * + * \param value set to true to use IPv6 only + */ + inline Ipv6Only(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0; + } +}; + +} // !option + +/** + * \brief Result of polling + * + * Result of a select call, returns the first ready socket found with its + * flags. + */ +class ListenerStatus { +public: + Handle socket; //!< which socket is ready + Condition flags; //!< the flags +}; + +/** + * Table used in the socket listener to store which sockets have been + * set in which directions. + */ +using ListenerTable = std::unordered_map<Handle, Condition>; + +/** + * \brief Predefined backends for Listener. + */ +namespace backend { + +#if defined(NET_HAVE_EPOLL) + +/** + * \ingroup net-module-backends + * \brief Linux's epoll. + */ +class Epoll { +private: + int m_handle{-1}; + std::vector<epoll_event> m_events; + + Epoll(const Epoll &) = delete; + Epoll &operator=(const Epoll &) = delete; + + std::uint32_t toEpoll(Condition condition) const noexcept + { + std::uint32_t events = 0; + + if ((condition & Condition::Readable) == Condition::Readable) + events |= EPOLLIN; + if ((condition & Condition::Writable) == Condition::Writable) + events |= EPOLLOUT; + + return events; + } + + Condition toCondition(std::uint32_t events) const noexcept + { + Condition condition = Condition::None; + + if ((events & EPOLLIN) || (events & EPOLLHUP)) + condition |= Condition::Readable; + if (events & EPOLLOUT) + condition |= Condition::Writable; + + return condition; + } + + void update(Handle h, int op, int eflags) + { + epoll_event ev; + + std::memset(&ev, 0, sizeof (epoll_event)); + + ev.events = eflags; + ev.data.fd = h; + + if (epoll_ctl(m_handle, op, h, &ev) < 0) + throw Error(Error::System, "epoll_ctl"); + } + +public: + /** + * Create epoll. + * + * \throw net::Error on failures + */ + inline Epoll() + : m_handle(epoll_create1(0)) + { + if (m_handle < 0) + throw Error(Error::System, "epoll_create"); + } + + /** + * Move constructor. + * + * \param other the other backend + */ + inline Epoll(Epoll &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = -1; + } + + /** + * Close the kqueue descriptor. + */ + inline ~Epoll() + { + if (m_handle != -1) + close(m_handle); + } + + /** + * Get the backend name. + * + * \return kqueue + */ + inline std::string name() const noexcept + { + return "epoll"; + } + + /** + * For set and unset, we need to apply the whole flags required, so if the socket + * was set to Connection::Readable and user *8adds** Connection::Writable, we must + * place both. + * + * \param table the listener table + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw net::Error on failures + */ + void set(const ListenerTable &table, Handle h, Condition condition, bool add) + { + if (add) { + update(h, EPOLL_CTL_ADD, toEpoll(condition)); + m_events.resize(m_events.size() + 1); + } else + update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) | condition)); + } + + /** + * Unset is a bit complicated case because Listener tells us which + * flag to remove but to update epoll descriptor we need to pass + * the effective flags that we want to be applied. + * + * So we put the same flags that are currently effective and remove the + * requested one. + * + * \param table the listener table + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw net::Error on failures + */ + void unset(const ListenerTable &table, Handle h, Condition condition, bool remove) + { + if (remove) { + update(h, EPOLL_CTL_DEL, 0); + m_events.resize(m_events.size() - 1); + } else + update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) & ~(condition))); + } + + /** + * Wait for sockets to be ready. + * + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw net::Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { + int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); + std::vector<ListenerStatus> result; + + if (ret == 0) + throw Error(Error::Timeout, "epoll_wait", std::strerror(ETIMEDOUT)); + if (ret < 0) + throw Error(Error::System, "epoll_wait"); + + for (int i = 0; i < ret; ++i) + result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); + + return result; + } + + /** + * Move operator. + * + * \param other the other + * \return this + */ + inline Epoll &operator=(Epoll &&other) + { + m_handle = other.m_handle; + other.m_handle = -1; + + return *this; + } +}; + +#endif // !NET_HAVE_EPOLL + +#if defined(NET_HAVE_KQUEUE) + +/** + * \ingroup net-module-backends + * \brief Implements kqueue(2). + * + * This implementation is available on all BSD and Mac OS X. It is better than + * poll(2) because it's O(1), however it's a bit more memory consuming. + */ +class Kqueue { +private: + std::vector<struct kevent> m_result; + int m_handle; + + Kqueue(const Kqueue &) = delete; + Kqueue &operator=(const Kqueue &) = delete; + + void update(Handle h, int filter, int kflags) + { + struct kevent ev; + + EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); + + if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) + throw Error(Error::System, "kevent"); + } + +public: + /** + * Create kqueue. + * + * \throw net::Error on failures + */ + inline Kqueue() + : m_handle(kqueue()) + { + if (m_handle < 0) + throw Error(Error::System, "kqueue"); + } + + /** + * Move constructor. + * + * \param other the other backend + */ + inline Kqueue(Kqueue &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = -1; + } + + /** + * Close the kqueue descriptor. + */ + inline ~Kqueue() + { + if (m_handle != -1) + close(m_handle); + } + + /** + * Get the backend name. + * + * \return kqueue + */ + inline std::string name() const noexcept + { + return "kqueue"; + } + + /** + * Set socket. + * + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw net::Error on failures + */ + void set(const ListenerTable &, Handle h, Condition condition, bool add) + { + if ((condition & Condition::Readable) == Condition::Readable) + update(h, EVFILT_READ, EV_ADD | EV_ENABLE); + if ((condition & Condition::Writable) == Condition::Writable) + update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); + if (add) + m_result.resize(m_result.size() + 1); + } + + /** + * Unset socket. + * + * \param h the handle + * \param condition the condition + * \param remove set to true if the socket is completely removed + * \throw net::Error on failures + */ + void unset(const ListenerTable &, Handle h, Condition condition, bool remove) + { + if ((condition & Condition::Readable) == Condition::Readable) + update(h, EVFILT_READ, EV_DELETE); + if ((condition & Condition::Writable) == Condition::Writable) + update(h, EVFILT_WRITE, EV_DELETE); + if (remove) + m_result.resize(m_result.size() - 1); + } + + /** + * Wait for sockets to be ready. + * + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw net::Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { + std::vector<ListenerStatus> sockets; + timespec ts = { 0, 0 }; + timespec *pts = (ms <= 0) ? nullptr : &ts; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + + int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); + + if (nevents == 0) + throw Error(Error::Timeout, "kevent", std::strerror(ETIMEDOUT)); + if (nevents < 0) + throw Error(Error::System, "kevent"); + + for (int i = 0; i < nevents; ++i) { + sockets.push_back(ListenerStatus{ + static_cast<Handle>(m_result[i].ident), + m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable + }); + } + + return sockets; + } + + /** + * Move operator. + * + * \param other the other + * \return this + */ + inline Kqueue &operator=(Kqueue &&other) noexcept + { + m_handle = other.m_handle; + other.m_handle = -1; + + return *this; + } +}; + +#endif // !NET_HAVE_KQUEUE + +#if defined(NET_HAVE_POLL) + +/** + * \ingroup net-module-backends + * \brief Implements poll(2). + * + * Poll is widely supported and is better than select(2). It is still not the + * best option as selecting the sockets is O(n). + */ +class Poll { +private: + std::vector<pollfd> m_fds; + + short toPoll(Condition condition) const noexcept + { + short result = 0; + + if ((condition & Condition::Readable) == Condition::Readable) + result |= POLLIN; + if ((condition & Condition::Writable) == Condition::Writable) + result |= POLLOUT; + + return result; + } + + Condition toCondition(short &event) const noexcept + { + Condition condition = Condition::None; + + /* + * Poll implementations mark the socket differently regarding the disconnection of a socket. + * + * At least, even if POLLHUP or POLLIN is set, recv() always return 0 so we mark the socket as readable. + */ + if ((event & POLLIN) || (event & POLLHUP)) + condition |= Condition::Readable; + if (event & POLLOUT) + condition |= Condition::Writable; + + // Reset event for safety. + event = 0; + + return condition; + } + +public: + /** + * Get the backend name. + * + * \return kqueue + */ + inline std::string name() const noexcept + { + return "poll"; + } + + /** + * Set socket. + * + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw net::Error on failures + */ + void set(const ListenerTable &, Handle h, Condition condition, bool add) + { + if (add) + m_fds.push_back(pollfd{h, toPoll(condition), 0}); + else { + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { + return pfd.fd == h; + }); + + it->events |= toPoll(condition); + } + } + + /** + * Unset socket. + * + * \param h the handle + * \param condition the condition + * \param remove set to true if the socket is completely removed + * \throw net::Error on failures + */ + void unset(const ListenerTable &, Handle h, Condition condition, bool remove) + { + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { + return pfd.fd == h; + }); + + if (remove) + m_fds.erase(it); + else + it->events &= ~(toPoll(condition)); + } + + /** + * Wait for sockets to be ready. + * + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw net::Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { +#if defined(_WIN32) + auto result = WSAPoll(m_fds.data(), (ULONG)m_fds.size(), ms); +#else + auto result = poll(m_fds.data(), m_fds.size(), ms); +#endif + + if (result == 0) + throw Error(Error::Timeout, "select", std::strerror(ETIMEDOUT)); + if (result < 0) + throw Error(Error::System, "poll"); + + std::vector<ListenerStatus> sockets; + + for (auto &fd : m_fds) + if (fd.revents != 0) + sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); + + return sockets; + } +}; + +#endif // !NET_HAVE_POLL + +/** + * \ingroup net-module-backends + * \brief Implements select(2) + * + * This class is the fallback of any other method, it is not preferred at all for many reasons. + */ +class Select { +public: + /** + * Get the backend name. + * + * \return select + */ + inline std::string name() const + { + return "select"; + } + + /** + * No-op. + */ + inline void set(const ListenerTable &, Handle, Condition, bool) noexcept + { + } + + /** + * No-op. + */ + inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept + { + } + + /** + * Wait for sockets to be ready. + * + * \param table the listener table + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw net::Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &table, int ms) + { + timeval maxwait, *towait; + fd_set readset; + fd_set writeset; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + + Handle max = 0; + + for (const auto &pair : table) { + if ((pair.second & Condition::Readable) == Condition::Readable) + FD_SET(pair.first, &readset); + if ((pair.second & Condition::Writable) == Condition::Writable) + FD_SET(pair.first, &writeset); + if (pair.first > max) + max = pair.first; + } + + maxwait.tv_sec = 0; + maxwait.tv_usec = ms * 1000; + + // Set to nullptr for infinite timeout. + towait = (ms < 0) ? nullptr : &maxwait; + + auto error = ::select(static_cast<int>(max + 1), &readset, &writeset, nullptr, towait); + + if (error == Failure) + throw Error(Error::System, "select"); + if (error == 0) + throw Error(Error::Timeout, "select", std::strerror(ETIMEDOUT)); + + std::vector<ListenerStatus> sockets; + + for (const auto &pair : table) { + if (FD_ISSET(pair.first, &readset)) + sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); + if (FD_ISSET(pair.first, &writeset)) + sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); + } + + return sockets; + } +}; + +} // !backend + +/** + * \brief Synchronous multiplexing + * + * Convenient wrapper around the select() system call. + * + * This class is implemented using a bridge pattern to allow different uses + * of listener implementation. + * + * You should not reinstanciate a new Listener at each iteartion of your + * main loop as it can be extremely costly. Instead use the same listener that + * you can safely modify on the fly. + * + * Currently, poll, epoll, select and kqueue are available. + */ +template <typename Backend = backend :: NET_DEFAULT_BACKEND> +class Listener { +private: + Backend m_backend; + ListenerTable m_table; + +public: + /** + * Construct an empty listener. + */ + Listener() = default; + + /** + * Get the backend. + * + * \return the backend + */ + inline const Backend &backend() const noexcept + { + return m_backend; + } + + /** + * Get the non-modifiable table. + * + * \return the table + */ + inline const ListenerTable &table() const noexcept + { + return m_table; + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator begin() const noexcept + { + return m_table.begin(); + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator cbegin() const noexcept + { + return m_table.cbegin(); + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator end() const noexcept + { + return m_table.end(); + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator cend() const noexcept + { + return m_table.cend(); + } + + /** + * Add or update a socket to the listener. + * + * If the socket is already placed with the appropriate flags, the + * function is a no-op. + * + * If incorrect flags are passed, the function does nothing. + * + * \param sc the socket + * \param condition the condition (may be OR'ed) + * \throw Error if the backend failed to set + */ + void set(Handle sc, Condition condition) + { + // Invalid or useless flags. + if (condition == Condition::None || static_cast<int>(condition) > 0x3) + return; + + auto it = m_table.find(sc); + + // Do not update the table if the backend failed to add or update. + if (it == m_table.end()) { + m_backend.set(m_table, sc, condition, true); + m_table.emplace(sc, condition); + } else { + // Remove flag if already present. + if ((condition & Condition::Readable) == Condition::Readable && + (it->second & Condition::Readable) == Condition::Readable) + condition &= ~(Condition::Readable); + if ((condition & Condition::Writable) == Condition::Writable && + (it->second & Condition::Writable) == Condition::Writable) + condition &= ~(Condition::Writable); + + // Still need a call? + if (condition != Condition::None) { + m_backend.set(m_table, sc, condition, false); + it->second |= condition; + } + } + } + + /** + * Unset a socket from the listener, only the flags is removed + * unless the two flagss are requested. + * + * For example, if you added a socket for both reading and writing, + * unsetting the write flags will keep the socket for reading. + * + * \param sc the socket + * \param condition the condition (may be OR'ed) + * \see remove + */ + void unset(Handle sc, Condition condition) + { + auto it = m_table.find(sc); + + // Invalid or useless flags. + if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) + return; + + // Like set, do not update if the socket is already at the appropriate state. + if ((condition & Condition::Readable) == Condition::Readable && + (it->second & Condition::Readable) != Condition::Readable) + condition &= ~(Condition::Readable); + if ((condition & Condition::Writable) == Condition::Writable && + (it->second & Condition::Writable) != Condition::Writable) + condition &= ~(Condition::Writable); + + if (condition != Condition::None) { + // Determine if it's a complete removal. + bool removal = ((it->second) & ~(condition)) == Condition::None; + + m_backend.unset(m_table, sc, condition, removal); + + if (removal) + m_table.erase(it); + else + it->second &= ~(condition); + } + } + + /** + * Remove completely the socket from the listener. + * + * It is a shorthand for unset(sc, Condition::Readable | Condition::Writable); + * + * \param sc the socket + */ + inline void remove(Handle sc) + { + unset(sc, Condition::Readable | Condition::Writable); + } + + /** + * Remove all sockets. + */ + inline void clear() + { + while (!m_table.empty()) + remove(m_table.begin()->first); + } + + /** + * Get the number of sockets in the listener. + * + * \return the number of sockets + */ + inline ListenerTable::size_type size() const noexcept + { + return m_table.size(); + } + + /** + * Select a socket. Waits for a specific amount of time specified as the duration. + * + * \param duration the duration + * \return the socket ready + */ + template <typename Rep, typename Ratio> + inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) + { + auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); + auto max = cvt.count() > INT_MAX ? INT_MAX : static_cast<int>(cvt.count()); + + return m_backend.wait(m_table, max)[0]; + } + + /** + * Overload with milliseconds. + * + * \param timeout the optional timeout in milliseconds + * \return the socket ready + */ + inline ListenerStatus wait(long long int timeout = -1) + { + return wait(std::chrono::milliseconds(timeout)); + } + + /** + * Select multiple sockets. + * + * \param duration the duration + * \return the socket ready + */ + template <typename Rep, typename Ratio> + inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) + { + auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); + + return m_backend.wait(m_table, cvt.count()); + } + + /** + * Overload with milliseconds. + * + * \param timeout the optional timeout in milliseconds + * \return the socket ready + */ + inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) + { + return waitMultiple(std::chrono::milliseconds(timeout)); + } +}; + +/** + * \ingroup net-module-tcp + * \brief Helper to create TCP sockets. + */ +template <typename Address> +using SocketTcp = Socket<Address, protocol::Tcp>; + +/** + * \ingroup net-module-tcp + * \brief Helper to create TCP/Ipv4 or TCP/Ipv6 sockets. + */ +using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; + +/** + * \ingroup net-module-tcp + * \brief Helper to create TCP/Ipv4 sockets. + */ +using SocketTcpIpv4 = Socket<address::Ipv4, protocol::Tcp>; + +/** + * \ingroup net-module-tcp + * \brief Helper to create TCP/Ipv6 sockets. + */ +using SocketTcpIpv6 = Socket<address::Ipv6, protocol::Tcp>; + +/** + * \ingroup net-module-udp + * \brief Helper to create UDP sockets. + */ +template <typename Address> +using SocketUdp = Socket<Address, protocol::Udp>; + +/** + * \ingroup net-module-udp + * \brief Helper to create UDP/Ipv4 or UDP/Ipv6 sockets. + */ +using SocketUdpIp = Socket<address::Ip, protocol::Udp>; + +/** + * \ingroup net-module-udp + * \brief Helper to create UDP/Ipv4 sockets. + */ +using SocketUdpIpv4 = Socket<address::Ipv4, protocol::Udp>; + +/** + * \ingroup net-module-udp + * \brief Helper to create UDP/Ipv6 sockets. + */ +using SocketUdpIpv6 = Socket<address::Ipv6, protocol::Udp>; + +#if !defined(_WIN32) + +/** + * \ingroup net-module-tcp + * \brief Helper to create TCP/Local sockets. + */ +using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; + +/** + * \ingroup net-module-udp + * \brief Helper to create UDP/Local sockets. + */ +using SocketUdpLocal = Socket<address::Local, protocol::Udp>; + +#endif + +#if !defined(NET_NO_SSL) + +/** + * \ingroup net-module-tls + * \brief Helper to create TLS sockets. + */ +template <typename Address> +using SocketTls = Socket<Address, protocol::Tls>; + +/** + * \ingroup net-module-tls + * \brief Helper to create TLS/Ipv4 or TLS/Ipv6 sockets. + */ +using SocketTlsIp = Socket<address::Ip, protocol::Tls>; + +/** + * \ingroup net-module-tls + * \brief Helper to create TLS/Ipv4 sockets. + */ +using SocketTlsIpv4 = Socket<address::Ip, protocol::Tls>; + +/** + * \ingroup net-module-tls + * \brief Helper to create TLS/Ipv6 sockets. + */ +using SocketTlsIpv6 = Socket<address::Ip, protocol::Tls>; + +#endif + +/** + * \ingroup net-module-resolv + * + * Resolve an hostname immediately. + * + * \param host the hostname + * \param service the service (e.g. http or port number) + * \param domain the domain (e.g. AF_INET) + * \param type the type (e.g. SOCK_STREAM) + * \return the address iterator + * \throw net::Error on failures + */ +inline address::AddressIterator resolve(const std::string &host, const std::string &service, int domain = AF_UNSPEC, int type = 0) +{ +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + + struct addrinfo hints, *res, *p; + + std::memset(&hints, 0, sizeof (hints)); + hints.ai_family = domain; + hints.ai_socktype = type; + + int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); + + if (e != 0) + throw Error(Error::System, "getaddrinfo", gai_strerror(e)); + + std::vector<address::GenericAddress> addresses; + + for (p = res; p != nullptr; p = p->ai_next) + addresses.push_back(address::GenericAddress(p->ai_addr, p->ai_addrlen)); + + return address::AddressIterator(addresses, 0); +} + +/** + * Overloaded function. + * + * \param sc the parent socket + * \param host the hostname + * \param service the service name + * \return the address iterator + * \throw net::Error on failures + */ +template <typename Address, typename Protocol> +address::AddressIterator resolve(const Socket<Address, Protocol> &sc, const std::string &host, const std::string &service) +{ + return resolve(host, service, Address().domain(), sc.protocol().type()); +} + +/** + * Resolve the first address. + * + * \param host the hostname + * \param service the service name + * \param domain the domain (e.g. AF_INET) + * \param type the type (e.g. SOCK_STREAM) + * \return the first generic address available + * \throw net::Error on failures + * \note do not use AF_UNSPEC and 0 as type for this function + */ +inline address::GenericAddress resolveOne(const std::string &host, const std::string &service, int domain, int type) +{ + address::AddressIterator end; + address::AddressIterator it = resolve(host, service, domain, type); + + if (it == end) + throw Error(Error::Other, "resolveOne", "no address available"); + + return *it; +} + +/** + * Overloaded function + * + * \param sc the parent socket + * \param host the hostname + * \param service the service name + * \return the first generic address available + * \throw net::Error on failures + */ +template <typename Address, typename Protocol> +address::GenericAddress resolveOne(const Socket<Address, Protocol> &sc, const std::string &host, const std::string &service) +{ + return resolveOne(host, service, Address().domain(), sc.protocol().type()); +} + +} // !irccd + +} // !net + +#endif // !NET_HPP
--- a/lib/irccd/server-state.hpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/server-state.hpp Wed May 25 12:50:34 2016 +0200 @@ -25,7 +25,7 @@ */ #include "elapsed-timer.hpp" -#include "sockets.hpp" +#include "net.hpp" #include "sysconfig.hpp" namespace irccd {
--- a/lib/irccd/service-interrupt.cpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/service-interrupt.cpp Wed May 25 12:50:34 2016 +0200 @@ -24,14 +24,14 @@ InterruptService::InterruptService() { // Bind a socket to any port. - m_in.set(net::option::SockReuseAddress{true}); - m_in.bind(net::address::Ip{"*", 0}); + m_in.set(net::option::SockReuseAddress(true)); + m_in.bind(net::address::Ipv4("*", 0)); m_in.listen(1); // Do the socket pair. - m_out.connect(net::address::Ip{"127.0.0.1", m_in.address().port()}); - m_in = m_in.accept(nullptr); - m_out.set(net::option::SockBlockMode{false}); + m_out.connect(net::address::Ipv4("127.0.0.1", m_in.getsockname().port())); + m_in = m_in.accept(); + m_out.set(net::option::SockBlockMode(false)); } void InterruptService::prepare(fd_set &in, fd_set &, net::Handle &max)
--- a/lib/irccd/service-interrupt.hpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/service-interrupt.hpp Wed May 25 12:50:34 2016 +0200 @@ -33,8 +33,8 @@ */ class InterruptService : public Service { private: - net::SocketTcpIp m_in; - net::SocketTcpIp m_out; + net::SocketTcpIpv4 m_in; + net::SocketTcpIpv4 m_out; public: /**
--- a/lib/irccd/service.hpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/service.hpp Wed May 25 12:50:34 2016 +0200 @@ -24,7 +24,7 @@ * \brief Selectable service. */ -#include "sockets.hpp" +#include "net.hpp" #include "sysconfig.hpp" #include "util.hpp"
--- a/lib/irccd/sockets.cpp Wed May 25 22:43:59 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,713 +0,0 @@ -/* - * sockets.cpp -- portable C++ socket wrappers - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define TIMEOUT_MSG "operation timeout" - -#include <algorithm> -#include <atomic> -#include <cstring> -#include <mutex> - -#include "sockets.hpp" - -namespace irccd { - -namespace net { - -/* - * Portable constants - * ------------------------------------------------------------------ - */ - -/* {{{ Constants */ - -#if defined(_WIN32) - -const Handle Invalid{INVALID_SOCKET}; -const int Failure{SOCKET_ERROR}; - -#else - -const Handle Invalid{-1}; -const int Failure{-1}; - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - */ - -/* {{{ Functions */ - -#if defined(_WIN32) - -namespace { - -static std::mutex s_mutex; -static std::atomic<bool> s_initialized{false}; - -} // !namespace - -#endif // !_WIN32 - -void init() noexcept -{ -#if defined(_WIN32) - std::lock_guard<std::mutex> lock(s_mutex); - - if (!s_initialized) { - s_initialized = true; - - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); - - /* - * If SOCKET_WSA_NO_INIT is not set then the user - * must also call finish himself. - */ -#if !defined(SOCKET_NO_AUTO_INIT) - atexit(finish); -#endif - } -#endif -} - -void finish() noexcept -{ -#if defined(_WIN32) - WSACleanup(); -#endif -} - -std::string error(int errn) -{ -#if defined(_WIN32) - LPSTR str = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - errn, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&str, 0, NULL); - - - if (str) { - errmsg = std::string(str); - LocalFree(str); - } - - return errmsg; -#else - return strerror(errn); -#endif -} - -std::string error() -{ -#if defined(_WIN32) - return error(WSAGetLastError()); -#else - return error(errno); -#endif -} - -/* }}} */ - -/* - * SSL stuff - * ------------------------------------------------------------------ - */ - -/* {{{ SSL initialization */ - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -namespace { - -std::mutex mutex; -std::atomic<bool> initialized{false}; - -} // !namespace - -void finish() noexcept -{ - ERR_free_strings(); -} - -void init() noexcept -{ - std::lock_guard<std::mutex> lock{mutex}; - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(SOCKET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif // SOCKET_NO_AUTO_SSL_INIT - } -} - -} // !ssl - -#endif // SOCKET_NO_SSL - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - */ - -/* {{{ Error */ - -Error::Error(Code code, std::string function) - : m_code{code} - , m_function{std::move(function)} - , m_error{error()} -{ -} - -Error::Error(Code code, std::string function, int n) - : m_code{code} - , m_function{std::move(function)} - , m_error{error(n)} -{ -} - -Error::Error(Code code, std::string function, std::string error) - : m_code{code} - , m_function{std::move(function)} - , m_error{std::move(error)} -{ -} - -/* }}} */ - -/* - * Predefine addressed to be used - * ------------------------------------------------------------------ - */ - -/* {{{ Addresses */ - -namespace address { - -/* Default domain */ -int Ip::m_default{AF_INET}; - -Ip::Ip(Type domain) noexcept - : m_domain(static_cast<int>(domain)) -{ - assert(m_domain == AF_INET6 || m_domain == AF_INET); - - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - } else { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - } -} - -Ip::Ip(const std::string &host, int port, Type domain) - : m_domain(static_cast<int>(domain)) -{ - assert(m_domain == AF_INET6 || m_domain == AF_INET); - - if (host == "*") { - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - - m_length = sizeof (sockaddr_in6); - m_sin6.sin6_addr = in6addr_any; - m_sin6.sin6_family = AF_INET6; - m_sin6.sin6_port = htons(port); - } else { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - - m_length = sizeof (sockaddr_in); - m_sin.sin_addr.s_addr = INADDR_ANY; - m_sin.sin_family = AF_INET; - m_sin.sin_port = htons(port); - } - } else { - addrinfo hints, *res; - - std::memset(&hints, 0, sizeof (addrinfo)); - hints.ai_family = domain; - - auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); - if (error != 0) { - throw Error{Error::System, "getaddrinfo", gai_strerror(error)}; - } - - if (m_domain == AF_INET6) { - std::memcpy(&m_sin6, res->ai_addr, res->ai_addrlen); - } else { - std::memcpy(&m_sin, res->ai_addr, res->ai_addrlen); - } - - m_length = res->ai_addrlen; - freeaddrinfo(res); - } -} - -Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept - : m_length{length} - , m_domain{ss->ss_family} -{ - assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET); - - if (ss->ss_family == AF_INET6) { - std::memcpy(&m_sin6, ss, length); - } else if (ss->ss_family == AF_INET) { - std::memcpy(&m_sin, ss, length); - } -} - -#if !defined(_WIN32) - -Local::Local() noexcept -{ - std::memset(&m_sun, 0, sizeof (sockaddr_un)); -} - -Local::Local(std::string path, bool rm) noexcept - : m_path{std::move(path)} -{ - /* Silently remove the file even if it fails */ - if (rm) { - ::remove(m_path.c_str()); - } - - /* Copy the path */ - std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); - std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); - - /* Set the parameters */ - m_sun.sun_family = AF_LOCAL; -} - -Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept -{ - assert(ss->ss_family == AF_LOCAL); - - if (ss->ss_family == AF_LOCAL) { - std::memcpy(&m_sun, ss, length); - m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } -} - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Select - * ------------------------------------------------------------------ - */ - -/* {{{ Select */ - -std::vector<ListenerStatus> Select::wait(const ListenerTable &table, int ms) -{ - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - Handle max = 0; - - for (const auto &pair : table) { - if ((pair.second & Condition::Readable) == Condition::Readable) { - FD_SET(pair.first, &readset); - } - if ((pair.second & Condition::Writable) == Condition::Writable) { - FD_SET(pair.first, &writeset); - } - - if (pair.first > max) { - max = pair.first; - } - } - - maxwait.tv_sec = 0; - maxwait.tv_usec = ms * 1000; - - // Set to nullptr for infinite timeout. - towait = (ms < 0) ? nullptr : &maxwait; - - auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); - if (error == Failure) { - throw Error{Error::System, "select"}; - } - if (error == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - - std::vector<ListenerStatus> sockets; - - for (const auto &pair : table) { - if (FD_ISSET(pair.first, &readset)) { - sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); - } - if (FD_ISSET(pair.first, &writeset)) { - sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); - } - } - - return sockets; -} - -/* }}} */ - -/* - * Poll - * ------------------------------------------------------------------ - */ - -/* {{{ Poll */ - -/* - * Poll implementation - * ------------------------------------------------------------------ - */ - -#if defined(SOCKET_HAVE_POLL) - -#if defined(_WIN32) -# define poll WSAPoll -#endif - -short Poll::toPoll(Condition condition) const noexcept -{ - short result(0); - - if ((condition & Condition::Readable) == Condition::Readable) { - result |= POLLIN; - } - if ((condition & Condition::Writable) == Condition::Writable) { - result |= POLLOUT; - } - - return result; -} - -Condition Poll::toCondition(short &event) const noexcept -{ - Condition condition{Condition::None}; - - /* - * Poll implementations mark the socket differently regarding - * the disconnection of a socket. - * - * At least, even if POLLHUP or POLLIN is set, recv() always - * return 0 so we mark the socket as readable. - */ - if ((event & POLLIN) || (event & POLLHUP)) { - condition |= Condition::Readable; - } - if (event & POLLOUT) { - condition |= Condition::Writable; - } - - /* Reset event for safety */ - event = 0; - - return condition; -} - -void Poll::set(const ListenerTable &, Handle h, Condition condition, bool add) -{ - if (add) { - m_fds.push_back(pollfd{h, toPoll(condition), 0}); - } else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - it->events |= toPoll(condition); - } -} - -void Poll::unset(const ListenerTable &, Handle h, Condition condition, bool remove) -{ - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - if (remove) { - m_fds.erase(it); - } else { - it->events &= ~(toPoll(condition)); - } -} - -std::vector<ListenerStatus> Poll::wait(const ListenerTable &, int ms) -{ - auto result = poll(m_fds.data(), m_fds.size(), ms); - if (result == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - if (result < 0) { - throw Error{Error::System, "poll"}; - } - - std::vector<ListenerStatus> sockets; - for (auto &fd : m_fds) { - if (fd.revents != 0) { - sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); - } - } - - return sockets; -} - -#endif // !SOCKET_HAVE_POLL - -/* }}} */ - -/* - * Epoll implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Epoll */ - -#if defined(SOCKET_HAVE_EPOLL) - -uint32_t Epoll::toEpoll(Condition condition) const noexcept -{ - uint32_t events = 0; - - if ((condition & Condition::Readable) == Condition::Readable) { - events |= EPOLLIN; - } - if ((condition & Condition::Writable) == Condition::Writable) { - events |= EPOLLOUT; - } - - return events; -} - -Condition Epoll::toCondition(uint32_t events) const noexcept -{ - Condition condition{Condition::None}; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) { - condition |= Condition::Readable; - } - if (events & EPOLLOUT) { - condition |= Condition::Writable; - } - - return condition; -} - -void Epoll::update(Handle h, int op, int eflags) -{ - epoll_event ev; - - std::memset(&ev, 0, sizeof (epoll_event)); - - ev.events = eflags; - ev.data.fd = h; - - if (epoll_ctl(m_handle, op, h, &ev) < 0) { - throw Error{Error::System, "epoll_ctl"}; - } -} - -Epoll::Epoll() - : m_handle{epoll_create1(0)} -{ - if (m_handle < 0) { - throw Error{Error::System, "epoll_create"}; - } -} - -Epoll::~Epoll() -{ - close(m_handle); -} - -/* - * For set and unset, we need to apply the whole flags required, so if the socket - * was set to Connection::Readable and user add Connection::Writable, we must - * place both. - */ -void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) -{ - if (add) { - update(sc, EPOLL_CTL_ADD, toEpoll(condition)); - m_events.resize(m_events.size() + 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | condition)); - } -} - -/* - * Unset is a bit complicated case because Listener tells us which - * flag to remove but to update epoll descriptor we need to pass - * the effective flags that we want to be applied. - * - * So we put the same flags that are currently effective and remove the - * requested one. - */ -void Epoll::unset(const ListenerTable &table, Handle sc, Condition condition, bool remove) -{ - if (remove) { - update(sc, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); - } -} - -std::vector<ListenerStatus> Epoll::wait(const ListenerTable &, int ms) -{ - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<ListenerStatus> result; - - if (ret == 0) { - throw Error{Error::Timeout, "epoll_wait", TIMEOUT_MSG}; - } - if (ret < 0) { - throw Error{Error::System, "epoll_wait"}; - } - - for (int i = 0; i < ret; ++i) { - result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); - } - - return result; -} - -#endif // !SOCKET_HAVE_EPOLL - -/* }}} */ - -/* - * Kqueue implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Kqueue */ - -#if defined(SOCKET_HAVE_KQUEUE) - -Kqueue::Kqueue() - : m_handle(kqueue()) -{ - if (m_handle < 0) { - throw Error{Error::System, "kqueue"}; - } -} - -Kqueue::~Kqueue() -{ - close(m_handle); -} - -void Kqueue::update(Handle h, int filter, int kflags) -{ - struct kevent ev; - - EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { - throw Error{Error::System, "kevent"}; - } -} - -void Kqueue::set(const ListenerTable &, Handle h, Condition condition, bool add) -{ - if ((condition & Condition::Readable) == Condition::Readable) { - update(h, EVFILT_READ, EV_ADD | EV_ENABLE); - } - if ((condition & Condition::Writable) == Condition::Writable) { - update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); - } - - if (add) { - m_result.resize(m_result.size() + 1); - } -} - -void Kqueue::unset(const ListenerTable &, Handle h, Condition condition, bool remove) -{ - if ((condition & Condition::Readable) == Condition::Readable) { - update(h, EVFILT_READ, EV_DELETE); - } - if ((condition & Condition::Writable) == Condition::Writable) { - update(h, EVFILT_WRITE, EV_DELETE); - } - - if (remove) { - m_result.resize(m_result.size() - 1); - } -} - -std::vector<ListenerStatus> Kqueue::wait(const ListenerTable &, int ms) -{ - std::vector<ListenerStatus> sockets; - timespec ts = { 0, 0 }; - timespec *pts = (ms <= 0) ? nullptr : &ts; - - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000; - - int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); - - if (nevents == 0) { - throw Error{Error::Timeout, "kevent", TIMEOUT_MSG}; - } - if (nevents < 0) { - throw Error{Error::System, "kevent"}; - } - - for (int i = 0; i < nevents; ++i) { - sockets.push_back(ListenerStatus{ - static_cast<Handle>(m_result[i].ident), - m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable - }); - } - - return sockets; -} - -#endif // !SOCKET_HAVE_KQUEUE - -/* }}} */ - -} // !net - -} // !irccd
--- a/lib/irccd/sockets.hpp Wed May 25 22:43:59 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3312 +0,0 @@ -/* - * sockets.hpp -- portable C++ socket wrappers - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_SOCKETS_HPP -#define IRCCD_SOCKETS_HPP - -/** - * \file sockets.hpp - * \brief Portable socket abstraction - * - * # Introduction - * - * This file is a portable networking library. - * - * ## Options - * - * The user may set the following variables before compiling these files: - * - * ### General options - * - * - **SOCKET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to - * automatically calls net::init function and net::finish functions. - * - **SOCKET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. - * - **SOCKET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class with Tls to automatically init - * the OpenSSL library. You will need to call net::ssl::init and net::ssl::finish. - * - * ### Options for Listener class - * - * Feature detection, multiple implementations may be avaible, for example, Linux has poll, select and epoll. - * - * We assume that `select(2)` is always available. - * - * Of course, you can set the variables yourself if you test it with your build system. - * - * - **SOCKET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows - * if _WIN32_WINNT is set to 0x0600 or greater. - * - **SOCKET_HAVE_KQUEUE**: Defined on all BSD and Apple. - * - **SOCKET_HAVE_EPOLL**: Defined on Linux only. - * - **SOCKET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). - * - * The preference priority is ordered from left to right. - * - * | System | Backend | Class name | - * |---------------|-------------------------|--------------| - * | Linux | epoll(7) | Epoll | - * | *BSD | kqueue(2) | Kqueue | - * | Windows | poll(2), select(2) | Poll, Select | - * | Mac OS X | kqueue(2) | Kqueue | - */ - -#include "sysconfig.hpp" - -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# if !defined(SOCKET_HAVE_KQUEUE) -# define SOCKET_HAVE_KQUEUE -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__linux__) -# if !defined(SOCKET_HAVE_EPOLL) -# define SOCKET_HAVE_EPOLL -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#endif - -/* - * Define SOCKET_DEFAULT_BACKEND - * ------------------------------------------------------------------ - */ - -/** - * Defines the default Listener backend to use. - * - * \note Do not rely on the value shown in doxygen. - */ -#if defined(_WIN32) -# if !defined(SOCKET_DEFAULT_BACKEND) -# if defined(SOCKET_HAVE_POLL) -# define SOCKET_DEFAULT_BACKEND Poll -# else -# define SOCKET_DEFAULT_BACKEND Select -# endif -# endif -#elif defined(__linux__) -# include <sys/epoll.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Epoll -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Kqueue -# endif -#else -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Select -# endif -#endif - -#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -/* - * Headers to include - * ------------------------------------------------------------------ - */ - -#if defined(_WIN32) -# include <cstdlib> - -# include <WinSock2.h> -# include <WS2tcpip.h> -#else -# include <cerrno> - -# include <sys/ioctl.h> -# include <sys/types.h> -# include <sys/socket.h> -# include <sys/un.h> - -# include <arpa/inet.h> - -# include <netinet/in.h> -# include <netinet/tcp.h> - -# include <fcntl.h> -# include <netdb.h> -# include <unistd.h> -#endif - -#if !defined(SOCKET_NO_SSL) -# include <openssl/err.h> -# include <openssl/evp.h> -# include <openssl/ssl.h> -#endif - -#include <cassert> -#include <chrono> -#include <cstdlib> -#include <cstring> -#include <exception> -#include <functional> -#include <map> -#include <memory> -#include <string> -#include <vector> - -namespace irccd { - -/** - * General network namespace. - */ -namespace net { - -/* - * Portables types - * ------------------------------------------------------------------ - * - * The following types are defined differently between Unix and Windows. - */ - -/* {{{ Protocols */ - -#if defined(_WIN32) - -/** - * Socket type, SOCKET. - */ -using Handle = SOCKET; - -/** - * Argument to pass to set. - */ -using ConstArg = const char *; - -/** - * Argument to pass to get. - */ -using Arg = char *; - -#else - -/** - * Socket type, int. - */ -using Handle = int; - -/** - * Argument to pass to set. - */ -using ConstArg = const void *; - -/** - * Argument to pass to get. - */ -using Arg = void *; - -#endif - -/* }}} */ - -/* - * Portable constants - * ------------------------------------------------------------------ - * - * These constants are needed to check functions return codes, they are rarely needed in end user code. - */ - -/* {{{ Constants */ - -/* - * The following constants are defined differently from Unix - * to Windows. - */ -#if defined(_WIN32) - -/** - * Socket creation failure or invalidation. - */ -extern const Handle Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#else - -/** - * Socket creation failure or invalidation. - */ -extern const int Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#endif - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -/** - * \enum Method - * \brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -} // !ssl - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - * - * The following free functions can be used to initialize the library or to get the last system error. - */ - -/* {{{ Functions */ - -/** - * Initialize the socket library. Except if you defined SOCKET_NO_AUTO_INIT, you don't need to call this - * function manually. - */ -IRCCD_EXPORT void init() noexcept; - -/** - * Close the socket library. - */ -IRCCD_EXPORT void finish() noexcept; - -#if !defined(SOCKET_NO_SSL) - -/** - * OpenSSL namespace. - */ -namespace ssl { - -/** - * Initialize the OpenSSL library. Except if you defined SOCKET_NO_AUTO_SSL_INIT, you don't need to call this function - * manually. - */ -IRCCD_EXPORT void init() noexcept; - -/** - * Close the OpenSSL library. - */ -IRCCD_EXPORT void finish() noexcept; - -} // !ssl - -#endif // SOCKET_NO_SSL - -/** - * Get the last socket system error. The error is set from errno or from - * WSAGetLastError on Windows. - * - * \return a string message - */ -IRCCD_EXPORT std::string error(); - -/** - * Get the last system error. - * - * \param errn the error number (errno or WSAGetLastError) - * \return the error - */ -IRCCD_EXPORT std::string error(int errn); - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - * - * This is the main exception thrown on socket operations. - */ - -/* {{{ Error */ - -/** - * \class Error - * \brief Base class for sockets error - */ -class Error : public std::exception { -public: - /** - * \enum Code - * \brief Which kind of error - */ - enum Code { - Timeout, ///!< The action did timeout - System, ///!< There is a system error - Other ///!< Other custom error - }; - -private: - Code m_code; - std::string m_function; - std::string m_error; - -public: - /** - * Constructor that use the last system error. - * - * \param code which kind of error - * \param function the function name - */ - IRCCD_EXPORT Error(Code code, std::string function); - - /** - * Constructor that use the system error set by the user. - * - * \param code which kind of error - * \param function the function name - * \param error the error - */ - IRCCD_EXPORT Error(Code code, std::string function, int error); - - /** - * Constructor that set the error specified by the user. - * - * \param code which kind of error - * \param function the function name - * \param error the error - */ - IRCCD_EXPORT Error(Code code, std::string function, std::string error); - - /** - * Get which function has triggered the error. - * - * \return the function name (e.g connect) - */ - inline const std::string &function() const noexcept - { - return m_function; - } - - /** - * The error code. - * - * \return the code - */ - inline Code code() const noexcept - { - return m_code; - } - - /** - * Get the error (only the error content). - * - * \return the error - */ - const char *what() const noexcept - { - return m_error.c_str(); - } -}; - -/* }}} */ - -/* - * State class - * ------------------------------------------------------------------ - * - * To facilitate higher-level stuff, the socket has a state. - */ - -/* {{{ State */ - -/** - * \enum State - * \brief Current socket state. - */ -enum class State { - Open, //!< Socket is open - Bound, //!< Socket is bound to an address - Connecting, //!< The connection is in progress - Connected, //!< Connection is complete - Accepted, //!< Socket has been accepted (client) - Accepting, //!< The client acceptation is in progress - Closed, //!< The socket has been closed - Disconnected, //!< The connection was lost -}; - -/* }}} */ - -/* - * Action enum - * ------------------------------------------------------------------ - * - * Defines the pending operation. - */ - -/* {{{ Action */ - -/** - * \enum Action - * \brief Define the current operation that must complete. - * - * Some operations like accept, connect, recv or send must sometimes do several round-trips to complete and the socket - * action is set with that enumeration. The user is responsible of calling accept, connect send or recv until the - * operation is complete. - * - * Note: the user must wait for the appropriate condition in Socket::condition to check if the required condition is - * met. - * - * It is important to complete the operation in the correct order because protocols like Tls may require to continue - * re-negociating when calling Socket::send or Socket::Recv. - */ -enum class Action { - None, //!< No action is required, socket is ready - Accept, //!< The socket is not yet accepted, caller must call accept() again - Connect, //!< The socket is not yet connected, caller must call connect() again - Receive, //!< The received operation has not succeeded yet, caller must call recv() or recvfrom() again - Send //!< The send operation has not succeded yet, caller must call send() or sendto() again -}; - -/* }}} */ - -/* - * Condition enum - * ------------------------------------------------------------------ - * - * Defines if we must wait for reading or writing. - */ - -/* {{{ Condition */ - -/** - * \enum Condition - * \brief Define the required condition for the socket. - * - * As explained in Action enumeration, some operations required to be called several times, before calling these - * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. - */ -enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1) //!< The socket must be writable -}; - -/** - * Apply bitwise XOR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -constexpr Condition operator^(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); -} - -/** - * Apply bitwise AND. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -constexpr Condition operator&(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); -} - -/** - * Apply bitwise OR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -constexpr Condition operator|(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); -} - -/** - * Apply bitwise NOT. - * - * \param v the value - * \return the complement - */ -constexpr Condition operator~(Condition v) noexcept -{ - return static_cast<Condition>(~static_cast<int>(v)); -} - -/** - * Assign bitwise OR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator|=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise AND. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator&=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise XOR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator^=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); - - return v1; -} - -/* }}} */ - -/* - * Base Socket class - * ------------------------------------------------------------------ - * - * This base class has operations that are common to all types of sockets but you usually instanciate - * a SocketTcp or SocketUdp - */ - -/* {{{ Socket */ - -/** - * \class Socket - * \brief Base socket class for socket operations. - * - * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the - * underlying protocol for more details. - * - * \see protocol::Tls - * \see protocol::Tcp - * \see protocol::Udp - */ -template <typename Address, typename Protocol> -class Socket { -private: - Protocol m_proto; - State m_state{State::Closed}; - Action m_action{Action::None}; - Condition m_condition{Condition::None}; - -protected: - /** - * The native handle. - */ - Handle m_handle{Invalid}; - -public: - /** - * Create a socket handle. - * - * This is the primary function and the only one that creates the socket handle, all other constructors - * are just overloaded functions. - * - * \param domain the domain AF_* - * \param type the type SOCK_* - * \param protocol the protocol - * \param iface the implementation - * \throw net::Error on errors - * \post state is set to Open - * \post handle is not set to Invalid - */ - Socket(int domain, int type, int protocol, Protocol iface = {}) - : m_proto(std::move(iface)) - { -#if !defined(SOCKET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) { - throw Error{Error::System, "socket"}; - } - - m_proto.create(*this); - m_state = State::Open; - - assert(m_handle != Invalid); - } - - /** - * This tries to create a socket. - * - * Domain and type are determined by the Address and Protocol object. - * - * \param protocol the protocol - * \param address which type of address - * \throw net::Error on errors - */ - explicit inline Socket(Protocol protocol = {}, const Address &address = {}) - : Socket{address.domain(), protocol.type(), 0, std::move(protocol)} - { - } - - /** - * Construct a socket with an already created descriptor. - * - * \param handle the native descriptor - * \param state specify the socket state - * \param protocol the type of socket implementation - * \post action is set to None - * \post condition is set to None - */ - explicit inline Socket(Handle handle, State state = State::Closed, Protocol protocol = {}) noexcept - : m_proto(std::move(protocol)) - , m_state{state} - , m_handle{handle} - { - assert(m_action == Action::None); - assert(m_condition == Condition::None); - } - - /** - * Create an invalid socket. Can be used when you cannot instanciate the socket immediately. - */ - explicit inline Socket(std::nullptr_t) noexcept - : m_handle{Invalid} - { - } - - /** - * Copy constructor deleted. - */ - Socket(const Socket &) = delete; - - /** - * Transfer ownership from other to this. - * - * \param other the other socket - */ - inline Socket(Socket &&other) noexcept - : m_proto(std::move(other.m_proto)) - , m_state{other.m_state} - , m_action{other.m_action} - , m_condition{other.m_condition} - , m_handle{other.m_handle} - { - /* Invalidate other */ - other.m_handle = Invalid; - other.m_state = State::Closed; - other.m_action = Action::None; - other.m_condition = Condition::None; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Access the implementation. - * - * \return the implementation - * \warning use this function with care - */ - inline const Protocol &protocol() const noexcept - { - return m_proto; - } - - /** - * Overloaded function. - * - * \return the implementation - */ - inline Protocol &protocol() noexcept - { - return m_proto; - } - - /** - * Get the current socket state. - * - * \return the state - */ - inline State state() const noexcept - { - return m_state; - } - - /** - * Change the current socket state. - * - * \param state the new state - * \warning only implementations should call this function - */ - inline void setState(State state) noexcept - { - m_state = state; - } - - /** - * Get the pending operation. - * - * \return the action to complete before continuing - * \note usually only needed in non-blocking sockets - */ - inline Action action() const noexcept - { - return m_action; - } - - /** - * Change the pending operation. - * - * \param action the action - * \warning you should not call this function yourself - */ - inline void setAction(Action action) noexcept - { - m_action = action; - } - - /** - * Get the condition to wait for. - * - * \return the condition - */ - inline Condition condition() const noexcept - { - return m_condition; - } - - /** - * Change the condition required. - * - * \param condition the condition - * \warning you should not call this function yourself - */ - inline void setCondition(Condition condition) noexcept - { - m_condition = condition; - } - - /** - * Set an option for the socket. Wrapper of setsockopt(2). - * - * \param level the setting level - * \param name the name - * \param arg the value - * \throw net::Error on errors - */ - template <typename Argument> - void set(int level, int name, const Argument &arg) - { - if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) { - throw Error{Error::System, "set"}; - } - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket<Address, Protocol> &) const`. - * - * \param option the option - * \throw net::Error on errors - */ - template <typename Option> - inline void set(const Option &option) - { - option.set(*this); - } - - /** - * Get an option for the socket. Wrapper of getsockopt(2). - * - * \param level the setting level - * \param name the name - * \throw net::Error on errors - */ - template <typename Argument> - Argument get(int level, int name) - { - Argument desired, result{}; - socklen_t size = sizeof (result); - - if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) { - throw Error{Error::System, "get"}; - } - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value - * returned from this function. - * - * \return the same value as get() in the option - * \throw net::Error on errors - */ - template <typename Option> - inline auto get() -> decltype(std::declval<Option>().get(*this)) - { - return Option{}.get(*this); - } - - /** - * Get the native handle. - * - * \return the handle - * \warning Not portable - */ - inline Handle handle() const noexcept - { - return m_handle; - } - - /** - * Bind using a native address. - * - * \param address the address - * \param length the size - * \pre state must not be Bound - * \throw net::Error on errors - */ - void bind(const sockaddr *address, socklen_t length) - { - assert(m_state != State::Bound); - - if (::bind(m_handle, address, length) == Failure) { - throw Error{Error::System, "bind"}; - } - - m_state = State::Bound; - } - - /** - * Overload that takes an address. - * - * \param address the address - * \throw net::Error on errors - */ - inline void bind(const Address &address) - { - bind(address.address(), address.length()); - } - - /** - * Listen for pending connection. - * - * \param max the maximum number - * \pre state must be Bound - * \throw net::Error on errors - */ - inline void listen(int max = 128) - { - assert(m_state == State::Bound); - - if (::listen(this->m_handle, max) == Failure) { - throw Error{Error::System, "listen"}; - } - } - - /** - * Connect to the address. - * - * If connection cannot be established immediately, connect with no argument must be called again. See - * the underlying protocol for more information. - * - * \pre state must be State::Open - * \param address the address - * \param length the the address length - * \throw net::Error on errors - * \post state is set to State::Connecting or State::Connected - * \note For non-blocking sockets, see the underlying protocol function for more details - */ - void connect(const sockaddr *address, socklen_t length) - { - assert(m_state == State::Open); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.connect(*this, address, length); - - assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || - (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); - } - - /** - * Overloaded function. - * - * Effectively call connect(address.address(), address.length()); - * - * \param address the address - */ - inline void connect(const Address &address) - { - connect(address.address(), address.length()); - } - - /** - * Continue the connection, only required with non-blocking sockets. - * - * \pre state must be State::Connecting - * \throw net::Error on errors - */ - void connect() - { - assert(m_state == State::Connecting); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.connect(*this); - - assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || - (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); - } - - /** - * Accept a new client. If there are no pending connection, throws an error. - * - * If the client cannot be accepted immediately, the client is returned and accept with no arguments - * must be called on it. See the underlying protocol for more information. - * - * \pre state must be State::Bound - * \param info the address where to store client's information (optional) - * \return the new socket - * \throw Error on errors - * \post returned client's state is set to State::Accepting or State::Accepted - * \note For non-blocking sockets, see the underlying protocol function for more details - */ - Socket<Address, Protocol> accept(Address *info) - { - assert(m_state == State::Bound); - - m_action = Action::None; - m_condition = Condition::None; - - sockaddr_storage storage; - socklen_t length = sizeof (storage); - - Socket<Address, Protocol> sc = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length); - - if (info) { - *info = Address{&storage, length}; - } - - /* Master do not change */ - assert(m_state == State::Bound); - assert(m_action == Action::None); - assert(m_condition == Condition::None); - - /* Client */ - assert( - (sc.state() == State::Accepting && sc.action() == Action::Accept && sc.condition() != Condition::None) || - (sc.state() == State::Accepted && sc.action() == Action::None && sc.condition() == Condition::None) - ); - - return sc; - } - - /** - * Continue the accept process on this client. This function must be called only when the socket is - * ready to be readable or writable! (see condition). - * - * \pre state must be State::Accepting - * \throw Error on errors - * \post if connection is complete, state is changed to State::Accepted, action and condition are unset - * \post if connection is still in progress, condition is set - */ - void accept() - { - assert(m_state == State::Accepting); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.accept(*this); - - assert( - (m_state == State::Accepting && m_action == Action::Accept && m_condition != Condition::None) || - (m_state == State::Accepted && m_action == Action::None && m_condition == Condition::None) - ); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * \return the address - * \throw Error on failures - * \pre state() must not be State::Closed - */ - Address address() const - { - assert(m_state != State::Closed); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getsockname(m_handle, (sockaddr *)&ss, &length) == Failure) { - throw Error{Error::System, "getsockname"}; - } - - return Address(&ss, length); - } - - /** - * Receive some data. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * If action is set to Action::None and result is set to 0, disconnection occured. - * - * \param data the destination buffer - * \param length the buffer length - * \pre action must not be Action::Send - * \return the number of bytes received or 0 - * \throw Error on error - * \note For non-blocking sockets, see the underlying protocol function for more details - */ - unsigned recv(void *data, unsigned length) - { - assert(m_action != Action::Send); - - m_action = Action::None; - m_condition = Condition::None; - - unsigned nbread = m_proto.recv(*this, data, length); - - return nbread; - } - - /** - * Overloaded function. - * - * \param count the number of bytes to receive - * \return the string - * \throw Error on error - */ - inline std::string recv(unsigned count) - { - std::string result; - - result.resize(count); - auto n = recv(const_cast<char *>(result.data()), count); - result.resize(n); - - return result; - } - - /** - * Send some data. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * \param data the data buffer - * \param length the buffer length - * \return the number of bytes sent or 0 - * \pre action() must not be Flag::Receive - * \throw Error on error - * \note For non-blocking sockets, see the underlying protocol function for more details - */ - unsigned send(const void *data, unsigned length) - { - assert(m_action != Action::Receive); - - m_action = Action::None; - m_condition = Condition::None; - - unsigned nbsent = m_proto.send(*this, data, length); - - assert((m_action == Action::None && m_condition == Condition::None) || - (m_action == Action::Send && m_condition != Condition::None)); - - return nbsent; - } - - /** - * Overloaded function. - * - * \param data the string to send - * \return the number of bytes sent - * \throw Error on error - */ - inline unsigned send(const std::string &data) - { - return send(data.c_str(), data.size()); - } - - /** - * Send data to an end point. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * \param data the buffer - * \param length the buffer length - * \param address the client address - * \param addrlen the address length - * \return the number of bytes sent - * \throw net::Error on errors - * \note For non-blocking sockets, see the underlying protocol function for more details - */ - inline unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - return m_proto.sendto(*this, data, length, address, addrlen); - } - - /** - * Overloaded function. - * - * \param data the buffer - * \param length the buffer length - * \param address the destination - * \return the number of bytes sent - * \throw net::Error on errors - */ - inline unsigned sendto(const void *data, unsigned length, const Address &address) - { - return sendto(data, length, address.address(), address.length()); - } - - /** - * Overloaded function. - * - * \param data the data - * \param address the address - * \return the number of bytes sent - * \throw net:;Error on errors - */ - inline unsigned sendto(const std::string &data, const Address &address) - { - return sendto(data.c_str(), data.length(), address); - } - - /** - * Receive data from an end point. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * \param data the destination buffer - * \param length the buffer length - * \param address the address destination - * \param addrlen the address length (in/out) - * \return the number of bytes received - * \throw net::Error on errors - * \note For non-blocking sockets, see the underlying protocol function for more details - */ - inline unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - return m_proto.recvfrom(*this, data, length, address, addrlen); - } - - /** - * Overloaded function. - * - * \param data the destination buffer - * \param length the buffer length - * \param info the address destination - * \return the number of bytes received - * \throw net::Error on errors - */ - inline unsigned recvfrom(void *data, unsigned length, Address *info = nullptr) - { - sockaddr_storage storage; - socklen_t addrlen = sizeof (sockaddr_storage); - - auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen); - - if (info && n != 0) { - *info = Address{&storage, addrlen}; - } - - return n; - } - - /** - * Overloaded function. - * - * \param count the maximum number of bytes to receive - * \param info the client information - * \return the string - * \throw net::Error on errors - */ - std::string recvfrom(unsigned count, Address *info = nullptr) - { - std::string result; - - result.resize(count); - auto n = recvfrom(const_cast<char *>(result.data()), count, info); - result.resize(n); - - return result; - } - - /** - * Close the socket. - * - * Automatically called from the destructor. - */ - void close() - { - if (m_handle != Invalid) { -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - m_handle = Invalid; - } - - m_state = State::Closed; - m_action = Action::None; - m_condition = Condition::None; - } - - /** - * Assignment operator forbidden. - * - * \return *this - */ - Socket &operator=(const Socket &) = delete; - - /** - * Transfer ownership from other to this. The other socket is left - * invalid and will not be closed. - * - * \param other the other socket - * \return this - */ - Socket &operator=(Socket &&other) noexcept - { - m_handle = other.m_handle; - m_proto = std::move(other.m_proto); - m_state = other.m_state; - m_action = other.m_action; - m_condition = other.m_condition; - - /* Invalidate other */ - other.m_handle = Invalid; - other.m_state = State::Closed; - other.m_action = Action::None; - other.m_condition = Condition::None; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they equals - */ -template <typename Address, typename Protocol> -bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() == s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they are different - */ -template <typename Address, typename Protocol> -bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 < s2 - */ -template <typename Address, typename Protocol> -bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 > s2 - */ -template <typename Address, typename Protocol> -bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 <= s2 - */ -template <typename Address, typename Protocol> -bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 >= s2 - */ -template <typename Address, typename Protocol> -bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() >= s2.handle(); -} - -/* }}} */ - -/* - * Predefined options - * ------------------------------------------------------------------ - */ - -/* {{{ Options */ - -/** - * Namespace of predefined options. - */ -namespace option { - -/* - * Options for socket - * ------------------------------------------------------------------ - */ - -/* {{{ Options for socket */ - -/** - * \class SockBlockMode - * \brief Set or get the blocking-mode for a socket. - * \warning On Windows, it's not possible to check if the socket is blocking or not. - */ -class SockBlockMode { -public: - /** - * Set to false if you want non-blocking socket. - */ - bool value{false}; - - SockBlockMode(bool v) - : value(v) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - template <typename Address, typename Protocol> - void set(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) { - flags = 0; - } - - if (value) { - flags &= ~(O_NONBLOCK); - } else { - flags |= O_NONBLOCK; - } - - if (fcntl(sc.handle(), F_SETFL, flags) < 0) { - throw Error{Error::System, "fcntl"}; - } -#else - unsigned long flags = (value) ? 0 : 1; - - if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) { - throw Error{Error::System, "fcntl"}; - } -#endif - } - - /** - * Get the option. - * - * \return the value - * \throw Error on errors - */ - template <typename Address, typename Protocol> - bool get(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags = fcntl(sc.handle(), F_GETFL, 0); - - if (flags < 0) { - throw Error{Error::System, "fcntl"}; - } - - return !(flags & O_NONBLOCK); -#else - throw Error{Error::Other, "get", "Windows API cannot let you get the blocking status of a socket"}; -#endif - } -}; - -/** - * \class SockReuseAddress - * \brief Reuse address, must be used before calling Socket::bind - */ -class SockReuseAddress { -public: - /** - * Set to true if you want to set the SOL_SOCKET/SO_REUSEADDR option. - */ - bool value{true}; - - SockReuseAddress(bool v) - : value(v) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_REUSEADDR, value ? 1 : 0); - } - - /** - * Get the option. - * - * \return the value - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(SOL_SOCKET, SO_REUSEADDR)); - } -}; - -/** - * \class SockSendBuffer - * \brief Set or get the output buffer. - */ -class SockSendBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_SNDBUF, value); - } - - /** - * Get the option. - * - * \return the value - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); - } -}; - -/** - * \class SockReceiveBuffer - * \brief Set or get the input buffer. - */ -class SockReceiveBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_RCVBUF, value); - } - - /** - * Get the option. - * - * \return the value - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); - } -}; - -/* }}} */ - -/** - * \class TcpNoDelay - * \brief Set this option if you want to disable nagle's algorithm. - */ -class TcpNoDelay { -public: - /** - * Set to true to set TCP_NODELAY option. - */ - bool value{true}; - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0); - } - - /** - * Get the option. - * - * \return the value - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_TCP, TCP_NODELAY)); - } -}; - -/** - * \class Ipv6Only - * \brief Control IPPROTO_IPV6/IPV6_V6ONLY - * - * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either - * false or true if portability is a concern. - */ -class Ipv6Only { -public: - /** - * Set this to use only IPv6. - */ - bool value{true}; - - Ipv6Only(bool v) - : value(v) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_IPV6, IPV6_V6ONLY, value ? 1 : 0); - } - - /** - * Get the option. - * - * \return the value - * \throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); - } -}; - -} // !option - -/* }}} */ - -/* - * Predefined addressed to be used - * ------------------------------------------------------------------ - * - * - Ip, - * - Local. - */ - -/* {{{ Addresses */ - -/** - * Set of predefined addresses. - */ -namespace address { - -/** - * \class Ip - * \brief Base class for IPv6 and IPv4, you can use it if you don't know in advance if you'll use IPv6 or IPv4. - */ -class Ip { -public: - /** - * \enum Type - * \brief Type of ip address. - */ - enum Type { - v4 = AF_INET, //!< AF_INET - v6 = AF_INET6 //!< AF_INET6 - }; - -private: - /* - * Default domain when using default constructors. - * - * Note: AF_INET or AF_INET6, not - */ - static int m_default; - - union { - sockaddr_in m_sin; - sockaddr_in6 m_sin6; - }; - - socklen_t m_length{0}; - int m_domain{AF_INET}; - -public: - /** - * Set the default domain to use when using default Ip constructor. By default, AF_INET is used. - * - * \pre domain must be Type::v4 or Type::v6 - */ - static inline void setDefault(Type domain) noexcept - { - assert(domain == Type::v4 || domain == Type::v6); - - m_default = static_cast<int>(domain); - } - - /** - * Construct using the default domain. - */ - inline Ip() noexcept - : Ip(static_cast<Type>(m_default)) - { - } - - /** - * Default initialize the Ip domain. - * - * \pre domain must be AF_INET or AF_INET6 only - * \param domain the domain (AF_INET or AF_INET6) - */ - Ip(Type domain) noexcept; - - /** - * Construct an address suitable for bind() or connect(). - * - * \pre domain must be Type::v4 or Type::v6 - * \param domain the domain (AF_INET or AF_INET6) - * \param host the host (* for any) - * \param port the port number - * \throw Error on errors - */ - Ip(const std::string &host, int port, Type domain = v4); - - /** - * Construct an address from a storage. - * - * \pre storage's domain must be AF_INET or AF_INET6 only - * \param ss the storage - * \param length the length - */ - Ip(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the domain (AF_INET or AF_INET6). - * - * \return the domain - */ - inline int domain() const noexcept - { - return m_domain; - } - - /** - * Return the underlying address, either sockaddr_in6 or sockaddr_in. - * - * \return the address - */ - inline const sockaddr *address() const noexcept - { - if (m_domain == AF_INET6) { - return reinterpret_cast<const sockaddr *>(&m_sin6); - } - - return reinterpret_cast<const sockaddr *>(&m_sin); - } - - /** - * Return the underlying address length. - * - * \return the length - */ - inline socklen_t length() const noexcept - { - return m_length; - } - - /** - * Get the port. - * - * \return the port - */ - inline int port() const noexcept - { - if (m_domain == AF_INET6) { - return ntohs(m_sin6.sin6_port); - } - - return ntohs(m_sin.sin_port); - } -}; - -#if !defined(_WIN32) - -/** - * \class Local - * \brief unix family sockets - * - * Create an address to a specific path. Only available on Unix. - */ -class Local { -private: - sockaddr_un m_sun; - std::string m_path; - -public: - /** - * Get the domain AF_LOCAL. - * - * \return AF_LOCAL - */ - inline int domain() const noexcept - { - return AF_LOCAL; - } - - /** - * Default constructor. - */ - Local() noexcept; - - /** - * Construct an address to a path. - * - * \param path the path - * \param rm remove the file before (default: false) - */ - Local(std::string path, bool rm = false) noexcept; - - /** - * Construct an unix address from a storage address. - * - * \pre storage's domain must be AF_LOCAL - * \param ss the storage - * \param length the length - */ - Local(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the sockaddr_un. - * - * \return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sun); - } - - /** - * Get the address length. - * - * \return the length - */ - inline socklen_t length() const noexcept - { -#if defined(SOCKET_HAVE_SUN_LEN) - return SUN_LEN(&m_sun); -#else - return sizeof (m_sun); -#endif - } -}; - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Predefined protocols - * ------------------------------------------------------------------ - * - * - Tcp, for standard stream connections, - * - Udp, for standard datagram connections, - * - Tls, for secure stream connections. - */ - -/* {{{ Protocols */ - -/** - * Set of predefined protocols. - */ -namespace protocol { - -/* {{{ Tcp */ - -/** - * \class Tcp - * \brief Clear TCP implementation. - * - * This is the basic TCP protocol that implements recv, send, connect and accept as wrappers of the usual - * C functions. - */ -class Tcp { -public: - /** - * Socket type. - * - * \return SOCK_STREAM - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Do nothing. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address> - inline void create(Socket<Address, Tcp> &) const noexcept - { - /* No-op */ - } - - /** - * Standard connect. - * - * If the socket is marked non-blocking and the connection cannot be established immediately, then the - * following is true: - * - * - state is set to State::Connecting, - * - action is set to Action::Connect, - * - condition is set to Condition::Writable. - * - * Then the user must wait until the socket is writable and call connect() with 0 arguments. - * - * If the socket is blocking, this function blocks until the connection is complete or an error occurs, in - * that case state is either set to State::Connected or State::Disconnected but action and condition are - * not set. - * - * \param sc the socket - * \param address the address - * \param length the length - * \throw net::Error on errors - * \note Wrapper of connect(2) - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) - { - if (::connect(sc.handle(), address, length) == Failure) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - sc.setState(State::Connecting); - sc.setAction(Action::Connect); - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error}; - } -#else - if (errno == EINPROGRESS) { - sc.setState(State::Connecting); - sc.setAction(Action::Connect); - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect"}; - } -#endif - } else { - sc.setState(State::Connected); - } - } - - /** - * Continue the connection. This function must only be called when the socket is ready for writing, - * the user is responsible of waiting for that condition. - * - * This function check for SOL_SOCKET/SO_ERROR status. - * - * If the connection is complete, status is set to State::Connected, otherwise it is set to - * State::Disconnected. In both cases, action and condition are not set. - * - * \param sc the socket - * \throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc) - { - int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); - - if (error == Failure) { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error}; - } - - sc.setState(State::Connected); - } - - /** - * Accept a clear client. - * - * If the socket is marked non-blocking and there are no pending connection, this function throws an - * error. The user must wait that the socket is readable before calling this function. - * - * If the socket is blocking, this function blocks until a new client is connected or throws an error on - * errors. - * - * If the socket is correctly returned, its state is set to State::Accepted and its action and condition - * are not set. - * - * In any case, action and condition of this socket are not set. - * - * \param sc the socket - * \param address the address destination - * \param length the address length - * \return the socket - * \throw net::Error on errors - * \note Wrapper of accept(2) - */ - template <typename Address, typename Protocol> - Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length) - { - Handle handle = ::accept(sc.handle(), address, length); - - if (handle == Invalid) { - throw Error{Error::System, "accept"}; - } - - return Socket<Address, Protocol>{handle, State::Accepted}; - } - - /** - * Continue accept. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &) const noexcept - { - /* no-op */ - } - - /** - * Receive data. - * - * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to - * Condition::Readable. If 0 is returned and condition is not set, then the state is set to - * State::Disconnected. - * - * If the socket is blocking, this function blocks until some data is available or if an error occurs. - * - * In any case, action is never set. - * - * \param sc the socket - * \param data the destination - * \param length the destination length - * \return the number of bytes read - * \throw Error on errors - * \note Wrapper of recv(2) - */ - template <typename Address> - unsigned recv(Socket<Address, Tcp> &sc, void *data, unsigned length) - { - int nbread = ::recv(sc.handle(), (Arg)data, length, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "recv", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - sc.setCondition(Condition::Readable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "recv"}; - } -#endif - } else if (nbread == 0) { - sc.setState(State::Disconnected); - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send some data. - * - * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to - * Condition::Writable. - * - * If the socket is blocking, this function blocks until the data has been sent. - * - * On any other errors, this function throw net::Error. - * - * \param sc the socket - * \param data the buffer to send - * \param length the buffer length - * \return the number of bytes sent - * \throw net::Error on errors - * \note Wrapper of send(2) - */ - template <typename Address> - unsigned send(Socket<Address, Tcp> &sc, const void *data, unsigned length) - { - int nbsent = ::send(sc.handle(), (ConstArg)data, length, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "send", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "send"}; - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Udp */ - -/** - * \class Udp - * \brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class Udp { -public: - /** - * Socket type. - * - * \return SOCK_DGRAM - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Do nothing. - */ - template <typename Address> - inline void create(Socket<Address, Udp> &) noexcept - { - /* No-op */ - } - - /** - * Receive data from an end point. - * - * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to - * Condition::Readable. - * - * If the socket is blocking, this functions blocks until some data is available or if an error occurs. - * - * \param sc the socket - * \param data the destination buffer - * \param length the buffer length - * \param address the address - * \param addrlen the initial address length - * \return the number of bytes received - * \throw Error on error - */ - template <typename Address> - unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - int nbread; - - nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - throw Error{Error::System, "recvfrom"}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - throw Error{Error::System, "recvfrom"}; - } -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send data to an end point. - * - * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to - * Condition::Writable. - * - * If the socket is blocking, this functions blocks until the data has been sent. - * - * \param sc the socket - * \param data the buffer - * \param length the buffer length - * \param address the client address - * \param addrlen the adderss length - * \return the number of bytes sent - * \throw Error on error - */ - template <typename Address> - unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - int nbsent; - - nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address, addrlen); - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - throw Error{Error::System, "sendto", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - throw Error{Error::System, "sendto"}; - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Tls */ - -#if !defined(SOCKET_NO_SSL) - -/** - * \class Tls - * \brief OpenSSL secure layer for TCP. - * - * **Note:** This protocol is much more difficult to use with non-blocking sockets, if some operations would block, the - * user is responsible of calling the function again by waiting for the appropriate condition. See the functions for - * more details. - * - * \see Tls::accept - * \see Tls::connect - * \see Tls::recv - * \see Tls::send - */ -class Tls : private Tcp { -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - /* OpenSSL objects */ - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - /* Status */ - bool m_tcpconnected{false}; - - /* - * User definable parameters - */ - ssl::Method m_method{ssl::Tlsv1}; - std::string m_key; - std::string m_certificate; - bool m_verify{false}; - - /* - * Construct with a context and ssl, for Tls::accept. - */ - Tls(Context context, Ssl ssl) - : m_context{std::move(context)} - , m_ssl{std::move(ssl)} - { - } - - /* - * Get the OpenSSL error message. - */ - inline std::string error(int error) - { - auto msg = ERR_reason_error_string(error); - - return msg == nullptr ? "" : msg; - } - - /* - * Update the states after an uncompleted operation. - */ - template <typename Address, typename Protocol> - inline void updateStates(Socket<Address, Protocol> &sc, State state, Action action, int code) - { - assert(code == SSL_ERROR_WANT_READ || code == SSL_ERROR_WANT_WRITE); - - sc.setState(state); - sc.setAction(action); - - if (code == SSL_ERROR_WANT_READ) { - sc.setCondition(Condition::Readable); - } else { - sc.setCondition(Condition::Writable); - } - } - - /* - * Continue the connect operation. - */ - template <typename Address, typename Protocol> - void processConnect(Socket<Address, Protocol> &sc) - { - int ret = SSL_connect(m_ssl.get()); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - updateStates(sc, State::Connecting, Action::Connect, no); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error(no)}; - } - } else { - sc.setState(State::Connected); - } - } - - /* - * Continue accept. - */ - template <typename Address, typename Protocol> - void processAccept(Socket<Address, Protocol> &sc) - { - int ret = SSL_accept(m_ssl.get()); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - updateStates(sc, State::Accepting, Action::Accept, no); - } else { - sc.setState(State::Disconnected); - throw Error(Error::System, "accept", error(no)); - } - } else { - sc.setState(State::Accepted); - } - } - -public: - /** - * \copydoc Tcp::type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Empty TLS constructor. - */ - Tls() - { -#if !defined(SOCKET_NO_SSL_AUTO_INIT) - net::ssl::init(); -#endif - } - - /** - * Set the method. - * - * \param method the method - * \pre the socket must not be already created - */ - inline void setMethod(ssl::Method method) noexcept - { - assert(!m_context); - assert(!m_ssl); - - m_method = method; - } - - /** - * Use the specified private key file. - * - * \param file the path to the private key - */ - inline void setPrivateKey(std::string file) noexcept - { - m_key = std::move(file); - } - - /** - * Use the specified certificate file. - * - * \param file the path to the file - */ - inline void setCertificate(std::string file) noexcept - { - m_certificate = std::move(file); - } - - /** - * Set to true if we must verify the certificate and private key. - * - * \param verify the mode - */ - inline void setVerify(bool verify = true) noexcept - { - m_verify = verify; - } - - /** - * Initialize the SSL objects after have created. - * - * \param sc the socket - * \throw net::Error on errors - */ - template <typename Address> - inline void create(Socket<Address, Tls> &sc) - { - auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv23_method(); - - m_context = {SSL_CTX_new(method), SSL_CTX_free}; - m_ssl = {SSL_new(m_context.get()), SSL_free}; - - SSL_set_fd(m_ssl.get(), sc.handle()); - - /* Load certificates */ - if (m_certificate.size() > 0) { - SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); - } - if (m_key.size() > 0) { - SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); - } - if (m_verify && !SSL_CTX_check_private_key(m_context.get())) { - throw Error{Error::System, "(openssl)", "unable to verify key"}; - } - } - - /** - * Connect to a secure host. - * - * If the socket is marked non-blocking and the connection cannot be established yet, then the state is set - * to State::Connecting, the condition is set to Condition::Readable or Condition::Writable, the user must - * wait for the appropriate condition before calling the overload connect which takes 0 argument. - * - * If the socket is blocking, this functions blocks until the connection is complete. - * - * If the connection was completed correctly the state is set to State::Connected. - * - * \param sc the socket - * \param address the address - * \param length the address length - * \throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) - { - /* 1. Connect using raw TCP */ - Tcp::connect(sc, address, length); - - /* 2. If the connection is complete (e.g. non-blocking), try handshake */ - if (sc.state() == State::Connected) { - m_tcpconnected = true; - processConnect(sc); - } - } - - /** - * Continue the connection. - * - * This function must be called when the socket is ready for reading or writing (check with Socket::condition), - * the state may change exactly like the initial connect call. - * - * \param sc the socket - * \throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc) - { - /* 1. Be sure to complete standard connect before */ - if (!m_tcpconnected) { - Tcp::connect(sc); - m_tcpconnected = sc.state() == State::Connected; - } - - if (m_tcpconnected) { - processConnect(sc); - } - } - - /** - * Accept a secure client. - * - * Because SSL needs several round-trips, if the socket is marked non-blocking and the connection is not - * completed yet, a new socket is returned but with the State::Accepting state. Its condition is set to - * Condition::Readable or Condition::Writable, the user is responsible of calling accept overload which takes - * 0 arguments on the returned socket when the condition is met. - * - * If the socket is blocking, this function blocks until the client is accepted and returned. - * - * If the client is accepted correctly, its state is set to State::Accepted. This instance does not change. - * - * \param sc the socket - * \param address the address destination - * \param length the address length - * \return the client - * \throw net::Error on errors - */ - template <typename Address> - Socket<Address, Tls> accept(Socket<Address, Tls> &sc, sockaddr *address, socklen_t *length) - { - Socket<Address, Tls> client = Tcp::accept(sc, address, length); - Tls &proto = client.protocol(); - - /* 1. Share the context */ - proto.m_context = m_context; - - /* 2. Create new SSL instance */ - proto.m_ssl = Ssl{SSL_new(m_context.get()), SSL_free}; - SSL_set_fd(proto.m_ssl.get(), client.handle()); - - /* 3. Try accept process on the **new** client */ - proto.processAccept(client); - - return client; - } - - /** - * Continue accept. - * - * This function must be called on the client that is being accepted. - * - * Like accept or connect, user is responsible of calling this function until the connection is complete. - * - * \param sc the socket - * \throw net::Error on errors - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &sc) - { - processAccept(sc); - } - - /** - * Receive some secure data. - * - * If the socket is marked non-blocking, 0 is returned if no data is available yet or if the connection - * needs renegociation. If renegociation is required case, the action is set to Action::Receive and condition - * is set to Condition::Readable or Condition::Writable. The user must wait that the condition is met and - * call this function again. - * - * \param sc the socket - * \param data the destination - * \param len the buffer length - * \return the number of bytes read - * \throw net::Error on errors - */ - template <typename Address> - unsigned recv(Socket<Address, Tls> &sc, void *data, unsigned len) - { - auto nbread = SSL_read(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto no = SSL_get_error(m_ssl.get(), nbread); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbread = 0; - updateStates(sc, sc.state(), Action::Receive, no); - } else { - throw Error{Error::System, "recv", error(no)}; - } - } - - return nbread; - } - - /** - * Send some data. - * - * Like recv, if the socket is marked non-blocking and no data can be sent or a negociation is required, - * condition and action are set. See receive for more details - * - * \param sc the socket - * \param data the data to send - * \param len the buffer length - * \return the number of bytes sent - * \throw net::Error on errors - */ - template <typename Address> - unsigned send(Socket<Address, Tls> &sc, const void *data, unsigned len) - { - auto nbsent = SSL_write(m_ssl.get(), data, len); - - if (nbsent <= 0) { - auto no = SSL_get_error(m_ssl.get(), nbsent); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbsent = 0; - updateStates(sc, sc.state(), Action::Send, no); - } else { - throw Error{Error::System, "send", error(no)}; - } - } - - return nbsent; - } -}; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -} // !protocol - -/* }}} */ - -/* - * Convenient helpers - * ------------------------------------------------------------------ - * - * - SocketTcp<Address>, for TCP sockets, - * - SocketUdp<Address>, for UDP sockets, - * - SocketTls<Address>, for secure TCP sockets. - */ - -/* {{{ Helpers */ - -/** - * Helper to create TCP sockets. - */ -template <typename Address> -using SocketTcp = Socket<Address, protocol::Tcp>; - -/** - * Helper to create TCP/IP sockets. - */ -using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; - -#if !defined(_WIN32) - -/** - * Helper to create TCP/Local sockets. - */ -using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; - -#endif - -/** - * Helper to create UDP sockets. - */ -template <typename Address> -using SocketUdp = Socket<Address, protocol::Udp>; - -/** - * Helper to create UDP/IP sockets. - */ -using SocketUdpIp = Socket<address::Ip, protocol::Udp>; - -#if !defined(SOCKET_NO_SSL) - -/** - * Helper to create OpenSSL TCP sockets. - */ -template <typename Address> -using SocketTls = Socket<Address, protocol::Tls>; - -/** - * Helper to create OpenSSL TCP/Ip sockets. - */ -using SocketTlsIp = Socket<address::Ip, protocol::Tls>; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -/* - * Select wrapper - * ------------------------------------------------------------------ - * - * Wrapper for select(2) and other various implementations. - */ - -/* {{{ Listener */ - -/** - * \class ListenerStatus - * \brief Result of polling - * - * Result of a select call, returns the first ready socket found with its - * flags. - */ -class ListenerStatus { -public: - Handle socket; //!< which socket is ready - Condition flags; //!< the flags -}; - -/** - * Table used in the socket listener to store which sockets have been - * set in which directions. - */ -using ListenerTable = std::map<Handle, Condition>; - -/** - * \class Select - * \brief Implements select(2) - * - * This class is the fallback of any other method, it is not preferred at all for many reasons. - */ -class Select { -public: - /** - * No-op, uses the ListenerTable directly. - */ - inline void set(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * No-op, uses the ListenerTable directly. - */ - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * Return the sockets - */ - IRCCD_EXPORT std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "select"; - } -}; - -#if defined(SOCKET_HAVE_POLL) - -/** - * \class Poll - * \brief Implements poll(2). - * - * Poll is widely supported and is better than select(2). It is still not the - * best option as selecting the sockets is O(n). - */ -class Poll { -private: - std::vector<pollfd> m_fds; - - short toPoll(Condition flags) const noexcept; - Condition toCondition(short &event) const noexcept; - -public: - /** - * Set the handle. - */ - IRCCD_EXPORT void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - IRCCD_EXPORT void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - IRCCD_EXPORT std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "poll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_EPOLL) - -/** - * \class Epoll - * \brief Linux's epoll. - */ -class Epoll { -private: - int m_handle; - std::vector<epoll_event> m_events; - - Epoll(const Epoll &) = delete; - Epoll &operator=(const Epoll &) = delete; - Epoll(const Epoll &&) = delete; - Epoll &operator=(const Epoll &&) = delete; - - uint32_t toEpoll(Condition flags) const noexcept; - Condition toCondition(uint32_t events) const noexcept; - void update(Handle sc, int op, int eflags); - -public: - /** - * Construct the epoll instance. - */ - IRCCD_EXPORT Epoll(); - - /** - * Close the epoll instance. - */ - IRCCD_EXPORT ~Epoll(); - - /** - * Set the handle. - */ - IRCCD_EXPORT void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - IRCCD_EXPORT void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - IRCCD_EXPORT std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "epoll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_KQUEUE) - -/** - * \class Kqueue - * \brief Implements kqueue(2). - * - * This implementation is available on all BSD and Mac OS X. It is better than - * poll(2) because it's O(1), however it's a bit more memory consuming. - */ -class Kqueue { -private: - std::vector<struct kevent> m_result; - int m_handle; - - Kqueue(const Kqueue &) = delete; - Kqueue &operator=(const Kqueue &) = delete; - Kqueue(Kqueue &&) = delete; - Kqueue &operator=(Kqueue &&) = delete; - - void update(Handle sc, int filter, int kflags); - -public: - /** - * Construct the kqueue instance. - */ - IRCCD_EXPORT Kqueue(); - - /** - * Destroy the kqueue instance. - */ - IRCCD_EXPORT ~Kqueue(); - - /** - * Set the handle. - */ - IRCCD_EXPORT void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - IRCCD_EXPORT void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - IRCCD_EXPORT std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "kqueue"; - } -}; - -#endif - -/** - * \class Listener - * \brief Synchronous multiplexing - * - * Convenient wrapper around the select() system call. - * - * This class is implemented using a bridge pattern to allow different uses - * of listener implementation. - * - * You should not reinstanciate a new Listener at each iteartion of your - * main loop as it can be extremely costly. Instead use the same listener that - * you can safely modify on the fly. - * - * Currently, poll, epoll, select and kqueue are available. - * - * To implement the backend, the following functions must be available: - * - * ### Set - * - * \code - * void set(const ListenerTable &, Handle sc, Condition condition, bool add); - * \endcode - * - * This function, takes the socket to be added and the flags. The condition is - * always guaranteed to be correct and the function will never be called twice - * even if the user tries to set the same flag again. - * - * An optional add argument is added for backends which needs to do different - * operation depending if the socket was already set before or if it is the - * first time (e.g EPOLL_CTL_ADD vs EPOLL_CTL_MOD for epoll(7). - * - * ### Unset - * - * \code - * void unset(const ListenerTable &, Handle sc, Condition condition, bool remove); - * \endcode - * - * Like set, this function is only called if the condition is actually set and will - * not be called multiple times. - * - * Also like set, an optional remove argument is set if the socket is being - * completely removed (e.g no more flags are set for this socket). - * - * ### Wait - * - * \code - * std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - * \endcode - * - * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, - * may throw any exceptions. - * - * ### Name - * - * \code - * inline const char *name() const noexcept - * \endcode - * - * Returns the backend name. Usually the class in lower case. - */ -template <typename Backend = SOCKET_DEFAULT_BACKEND> -class Listener { -private: - Backend m_backend; - ListenerTable m_table; - -public: - /** - * Construct an empty listener. - */ - Listener() = default; - - /** - * Get the backend. - * - * \return the backend - */ - inline const Backend &backend() const noexcept - { - return m_backend; - } - - /** - * Get the non-modifiable table. - * - * \return the table - */ - inline const ListenerTable &table() const noexcept - { - return m_table; - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator begin() const noexcept - { - return m_table.begin(); - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator cbegin() const noexcept - { - return m_table.cbegin(); - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator end() const noexcept - { - return m_table.end(); - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator cend() const noexcept - { - return m_table.cend(); - } - - /** - * Add or update a socket to the listener. - * - * If the socket is already placed with the appropriate flags, the - * function is a no-op. - * - * If incorrect flags are passed, the function does nothing. - * - * \param sc the socket - * \param condition the condition (may be OR'ed) - * \throw Error if the backend failed to set - */ - void set(Handle sc, Condition condition) - { - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3) - return; - - auto it = m_table.find(sc); - - /* - * Do not update the table if the backend failed to add - * or update. - */ - if (it == m_table.end()) { - m_backend.set(m_table, sc, condition, true); - m_table.emplace(sc, condition); - } else { - /* Remove flag if already present */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) == Condition::Readable) { - condition &= ~(Condition::Readable); - } - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) == Condition::Writable) { - condition &= ~(Condition::Writable); - } - - /* Still need a call? */ - if (condition != Condition::None) { - m_backend.set(m_table, sc, condition, false); - it->second |= condition; - } - } - } - - /** - * Unset a socket from the listener, only the flags is removed - * unless the two flagss are requested. - * - * For example, if you added a socket for both reading and writing, - * unsetting the write flags will keep the socket for reading. - * - * \param sc the socket - * \param condition the condition (may be OR'ed) - * \see remove - */ - void unset(Handle sc, Condition condition) - { - auto it = m_table.find(sc); - - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) - return; - - /* - * Like set, do not update if the socket is already at the appropriate - * state. - */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) != Condition::Readable) { - condition &= ~(Condition::Readable); - } - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) != Condition::Writable) { - condition &= ~(Condition::Writable); - } - - if (condition != Condition::None) { - /* Determine if it's a complete removal */ - bool removal = ((it->second) & ~(condition)) == Condition::None; - - m_backend.unset(m_table, sc, condition, removal); - - if (removal) { - m_table.erase(it); - } else { - it->second &= ~(condition); - } - } - } - - /** - * Remove completely the socket from the listener. - * - * It is a shorthand for unset(sc, Condition::Readable | Condition::Writable); - * - * \param sc the socket - */ - inline void remove(Handle sc) - { - unset(sc, Condition::Readable | Condition::Writable); - } - - /** - * Remove all sockets. - */ - inline void clear() - { - while (!m_table.empty()) { - remove(m_table.begin()->first); - } - } - - /** - * Get the number of sockets in the listener. - */ - inline ListenerTable::size_type size() const noexcept - { - return m_table.size(); - } - - /** - * Select a socket. Waits for a specific amount of time specified as the duration. - * - * \param duration the duration - * \return the socket ready - */ - template <typename Rep, typename Ratio> - inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) - { - assert(!m_table.empty()); - - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count())[0]; - } - - /** - * Overload with milliseconds. - * - * \param timeout the optional timeout in milliseconds - * \return the socket ready - */ - inline ListenerStatus wait(int timeout = -1) - { - return wait(std::chrono::milliseconds(timeout)); - } - - /** - * Select multiple sockets. - * - * \param duration the duration - * \return the socket ready - */ - template <typename Rep, typename Ratio> - inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) - { - assert(!m_table.empty()); - - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count()); - } - - /** - * Overload with milliseconds. - * - * \return the socket ready - */ - inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -} // !net - -} // !irccd - -#endif // !IRCCD_SOCKETS_HPP
--- a/lib/irccd/transport-client.hpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/transport-client.hpp Wed May 25 12:50:34 2016 +0200 @@ -29,9 +29,9 @@ #include <stdexcept> #include <string> +#include "net.hpp" #include "server.hpp" #include "signals.hpp" -#include "sockets.hpp" #include "sysconfig.hpp" namespace irccd {
--- a/lib/irccd/transport-server.cpp Wed May 25 22:43:59 2016 +0200 +++ b/lib/irccd/transport-server.cpp Wed May 25 12:50:34 2016 +0200 @@ -41,9 +41,9 @@ // Disable or enable IPv4 when using IPv6. if (domain == AF_INET6) - m_socket.set(net::option::Ipv6Only{ipv6only}); + m_socket.set(net::option::Ipv6Only(ipv6only)); - m_socket.bind(net::address::Ip{address, port, static_cast<net::address::Ip::Type>(domain)}); + m_socket.bind(net::address::Ip(address, port, domain)); m_socket.listen(); log::info() << "transport: listening on " << address << ", port " << port << std::endl; @@ -56,7 +56,7 @@ std::shared_ptr<TransportClient> TransportServerIp::accept() { - return std::make_shared<TransportClientBase<net::address::Ip>>(m_socket.accept(nullptr)); + return std::make_shared<TransportClientBase<net::address::Ip>>(m_socket.accept()); } /* @@ -87,7 +87,7 @@ std::shared_ptr<TransportClient> TransportServerUnix::accept() { - return std::make_shared<TransportClientBase<net::address::Local>>(m_socket.accept(nullptr)); + return std::make_shared<TransportClientBase<net::address::Local>>(m_socket.accept()); } #endif