Mercurial > code
changeset 437:378699c81257
Socket: massive cleanup, documentation
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 22 Oct 2015 21:38:28 +0200 |
parents | 2dbaf2fb03ef |
children | 5f837e40b569 |
files | C++/modules/Socket/SocketAddress.cpp C++/modules/Socket/SocketAddress.h C++/modules/Socket/SocketListener.cpp C++/modules/Socket/SocketListener.h C++/modules/Socket/SocketSsl.cpp C++/modules/Socket/SocketSsl.h C++/modules/Socket/Sockets.cpp C++/modules/Socket/Sockets.h C++/tests/Socket/main.cpp |
diffstat | 9 files changed, 440 insertions(+), 1880 deletions(-) [+] |
line wrap: on
line diff
--- a/C++/modules/Socket/SocketAddress.cpp Thu Oct 22 20:00:51 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -/* - * SocketAddress.cpp -- socket addresses management - * - * Copyright (c) 2013-2015 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. - */ - -#include <algorithm> -#include <cstring> - -#include "Socket.h" -#include "SocketAddress.h" - -bool operator==(const SocketAddressAbstract &address1, const SocketAddressAbstract &address2) noexcept -{ - const char *addr1 = reinterpret_cast<const char *>(&address1.address()); - const char *addr2 = reinterpret_cast<const char *>(&address2.address()); - - return std::equal( - addr1, addr1 + address1.length(), - addr2, addr2 + address2.length() - ); -} - -bool operator<(const SocketAddressAbstract &address1, const SocketAddressAbstract &address2) noexcept -{ - const char *addr1 = reinterpret_cast<const char *>(&address1.address()); - const char *addr2 = reinterpret_cast<const char *>(&address2.address()); - - return std::lexicographical_compare( - addr1, addr1 + address1.length(), - addr2, addr2 + address2.length() - ); -} - -namespace address { - -/* -------------------------------------------------------- - * Ip implementation - * -------------------------------------------------------- */ - -Ip::Ip() -{ - // Default uses IPv4 - std::memset(&m_sin, 0, sizeof (sockaddr_in)); -} - -Ip::Ip(const std::string &host, unsigned port, int domain) - : m_domain{domain} -{ - if (host == "*") { - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, 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_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 SocketError{SocketError::System, "getaddrinfo", gai_strerror(error)}; - } - - if (m_domain == AF_INET6) { - std::memcpy(&m_sin6, res->ai_addr, sizeof (sockaddr_in6)); - } else { - std::memcpy(&m_sin, res->ai_addr, sizeof (sockaddr_in)); - } - - freeaddrinfo(res); - } -} - -Ip::Ip(const sockaddr_storage &ss, socklen_t length) - : m_domain{ss.ss_family} -{ - if (ss.ss_family == AF_INET6) { - std::memcpy(&m_sin6, &ss, length); - } else { - std::memcpy(&m_sin, &ss, length); - } -} - -SocketAddressInfo Ip::info() const -{ - std::string type = (m_domain == AF_INET6) ? "ipv6" : "ipv4"; - std::string port = std::to_string(m_domain == AF_INET6 ? ntohs(m_sin6.sin6_port) : ntohs(m_sin.sin_port)); - - // TODO: add IP here - return SocketAddressInfo{ - { "type", type }, - { "port", port } - }; -} - -/* -------------------------------------------------------- - * Unix implementation - * -------------------------------------------------------- */ - -#if !defined(_WIN32) - -Unix::Unix(std::string path, bool rm) - : 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_UNIX; -} - -Unix::Unix(const sockaddr_storage &ss, socklen_t length) -{ - std::memcpy(&m_sun, &ss, length); - - if (ss.ss_family == AF_UNIX) { - m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } -} - -SocketAddressInfo Unix::info() const -{ - return SocketAddressInfo{ - { "type", "unix" }, - { "path", m_path } - }; -} - -#endif // _WIN32 - -} // !address
--- a/C++/modules/Socket/SocketAddress.h Thu Oct 22 20:00:51 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -/* - * SocketAddress.h -- socket addresses management - * - * Copyright (c) 2013-2015 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 _SOCKET_ADDRESS_NG_H_ -#define _SOCKET_ADDRESS_NG_H_ - -/** - * @file SocketAddress.h - * @brief Describe addresses - * - * User may set the following variables before compiling these files: - * - * SOCKET_HAVE_SUN_LEN - (bool) Some systems do not have SUN_LEN which is the preferred way to - * compute the address size for a Unix address. Otherwise, sizeof is used. - * - * Addresses are used in many places such as bind, recvfrom, accept and such. They describe different - * parameters depending on the families. - * - * For example, when using IPv4, one should use Ipv4 class. - * - * All addresses are used directly as template parameter for a stronger type security. To be compatible - * with the sockets classes, an address must have the following requirements: - * - * - Default constructible - * - Copyable or Moveable - * - * Constructors: - * - * # With a storage address - * - * @code - * Address(const sockaddr_storage &ss, socklen_t size); - * @endcode - * - * The address is free to use the storage. - * - * Member functions: - * - * # Address - * - * @code - * inline const sockaddr &address() const noexcept - * @endcode - * - * Return the address converted to the C sockaddr structure. - * - * # Length - * - * @code - * inline socklen_t length() const noexcept - * @endcode - * - * Return the length of an address. - * - * # Info - * - * @code - * SocketAddressInfo info() const - * @endcode - * - * Return an information table about the address. - */ - -#include <memory> -#include <string> -#include <unordered_map> - -#if defined(_WIN32) -# include <Winsock2.h> -# include <Ws2tcpip.h> -#else -# include <sys/socket.h> -# include <sys/un.h> -# include <arpa/inet.h> -# include <netinet/in.h> -#endif - -/** - * Generic information table for an address. - */ -using SocketAddressInfo = std::unordered_map<std::string, std::string>; - -/** - * @class SocketAddressAbstract - * @brief Generic base class for addresses - */ -class SocketAddressAbstract { -public: - /** - * Get the address as base type. - * - * @return the base address reference - */ - virtual const sockaddr &address() const noexcept = 0; - - /** - * Get the address length. - * - * @return the length - */ - virtual socklen_t length() const noexcept = 0; -}; - -/** - * Compare two socket addresses, std::equal is used. - * - * @param addr1 the first address - * @param addr2 the second address - * @return true if equals - */ -bool operator==(const SocketAddressAbstract &addr1, const SocketAddressAbstract &addr2) noexcept; - -/** - * Compare two socket addresses, std::lexicographical_compare is used. - * - * @param addr1 the first address - * @param addr2 the second address - * @return true if addr1 < addr2 - */ -bool operator<(const SocketAddressAbstract &addr1, const SocketAddressAbstract &addr2) noexcept; - -/** - * @brief Predefined addresses. - */ -namespace address { - -/** - * @class Ip - * @brief Generic internet protocol address - * - * Create a connect address for internet protocol, - * using getaddrinfo(3). - * - * @see Ipv4 - * @see Ipv6 - */ -class Ip : public SocketAddressAbstract { -private: - union { - sockaddr_in m_sin; - sockaddr_in6 m_sin6; - }; - - int m_domain{AF_INET}; - -public: - /** - * Default constructor. - */ - Ip(); - - /** - * Create an IPv4 or IPV6 end point. - * - * @param host the hostname - * @param port the port - * @param domain AF_INET or AF_INET6 - * @throw SocketError on error - */ - Ip(const std::string &host, unsigned port, int domain); - - /** - * Construct an internet address from a storage address. - * - * @param ss the storage - * @param length the length - */ - Ip(const sockaddr_storage &ss, socklen_t length); - - /** - * @copydoc SocketAddress::address - */ - const sockaddr &address() const noexcept override - { - // Can't get a ternary operator to work here. - if (m_domain == AF_INET6) - return reinterpret_cast<const sockaddr &>(m_sin6); - - return reinterpret_cast<const sockaddr &>(m_sin); - } - - /** - * @copydoc SocketAddress::length - */ - socklen_t length() const noexcept override - { - return (m_domain == AF_INET6) ? sizeof (sockaddr_in6) : sizeof (sockaddr_in); - } - - /** - * @copydoc SocketAddress::info - */ - SocketAddressInfo info() const; -}; - -/** - * @class Ipv6 - * @brief Convenient helper for IPv6 protocol - */ -class Ipv6 : public Ip { -public: - /** - * Default constructor. - */ - Ipv6() = default; - - /** - * Construct an IPv6 address from storage. - * - * @param ss the storage - * @param length the length - */ - inline Ipv6(const sockaddr_storage &ss, socklen_t length) - : Ip(ss, length) - { - } - - /** - * Construct an IPv6 address. - * - * @param host the host - * @param port the port - * @throw SocketError on error - */ - inline Ipv6(const std::string &host, unsigned port) - : Ip(host, port, AF_INET6) - { - } -}; - -/** - * @class Ipv4 - * @brief Convenient helper for IPv4 protocol - */ -class Ipv4 : public Ip { -public: - /** - * Default constructor. - */ - Ipv4() = default; - - /** - * Construct an IPv4 address from storage. - * - * @param ss the storage - * @param length the length - */ - inline Ipv4(const sockaddr_storage &ss, socklen_t length) - : Ip(ss, length) - { - } - - /** - * Construct an IPv4 address. - * - * @param host the host - * @param port the port - * @throw SocketError on error - */ - inline Ipv4(const std::string &host, unsigned port) - : Ip(host, port, AF_INET) - { - } -}; - - -} // !address - -#endif // !_SOCKET_ADDRESS_NG_H_
--- a/C++/modules/Socket/SocketListener.cpp Thu Oct 22 20:00:51 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -/* - * SocketListener.cpp -- portable select() wrapper - * - * Copyright (c) 2013-2015 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. - */ - -#include <algorithm> -#include <set> - -#include "SocketListener.h" - -/* -------------------------------------------------------- - * Select implementation - * -------------------------------------------------------- */ - -namespace backend { - -std::vector<SocketStatus> Select::wait(const SocketTable &table, int ms) -{ - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - SocketAbstract::Handle max = 0; - - for (const auto &s : table) { - if (s.second.second & SocketListener::Read) { - FD_SET(s.first, &readset); - } - if (s.second.second & SocketListener::Write) { - FD_SET(s.first, &writeset); - } - - if (s.first > max) { - max = s.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 == SocketAbstract::Error) { - throw SocketError(SocketError::System, "select"); - } - if (error == 0) { - throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); - } - - std::vector<SocketStatus> sockets; - - for (auto &c : table) { - if (FD_ISSET(c.first, &readset)) { - sockets.push_back(SocketStatus{*c.second.first, SocketListener::Read}); - } - if (FD_ISSET(c.first, &writeset)) { - sockets.push_back(SocketStatus{*c.second.first, SocketListener::Write}); - } - } - - return sockets; -} - -/* -------------------------------------------------------- - * Poll implementation - * -------------------------------------------------------- */ - -#if defined(SOCKET_HAVE_POLL) - -#if defined(_WIN32) -# define poll WSAPoll -#endif - -short Poll::topoll(int flags) const noexcept -{ - short result(0); - - if (flags & SocketListener::Read) { - result |= POLLIN; - } - if (flags & SocketListener::Write) { - result |= POLLOUT; - } - - return result; -} - -int Poll::toflags(short &event) const noexcept -{ - int flags = 0; - - /* - * 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)) { - flags |= SocketListener::Read; - } - if (event & POLLOUT) { - flags |= SocketListener::Write; - } - - // Reset event for safety - event = 0; - - return flags; -} - -void Poll::set(const SocketTable &, SocketAbstract &s, int flags, bool add) -{ - if (add) { - m_fds.push_back(pollfd{s.handle(), topoll(flags), 0}); - } else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const struct pollfd &pfd) { - return pfd.fd == s.handle(); - }); - - it->events |= topoll(flags); - } -} - -void Poll::unset(const SocketTable &, SocketAbstract &s, int flags, bool remove) -{ - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const struct pollfd &pfd) { - return pfd.fd == s.handle(); - }); - - if (remove) { - m_fds.erase(it); - } else { - it->events &= ~(topoll(flags)); - } -} - -std::vector<SocketStatus> Poll::wait(const SocketTable &table, int ms) -{ - auto result = poll(m_fds.data(), m_fds.size(), ms); - if (result == 0) { - throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); - } - if (result < 0) { - throw SocketError(SocketError::System, "poll"); - } - - std::vector<SocketStatus> sockets; - for (auto &fd : m_fds) { - if (fd.revents != 0) { - sockets.push_back(SocketStatus{*table.at(fd.fd).first, toflags(fd.revents)}); - } - } - - return sockets; -} - -#endif // !SOCKET_HAVE_POLL - -/* -------------------------------------------------------- - * Epoll implementation - * -------------------------------------------------------- */ - -#if defined(SOCKET_HAVE_EPOLL) - -uint32_t Epoll::toepoll(int flags) const noexcept -{ - uint32_t events = 0; - - if (flags & SocketListener::Read) { - events |= EPOLLIN; - } - if (flags & SocketListener::Write) { - events |= EPOLLOUT; - } - - return events; -} - -int Epoll::toflags(uint32_t events) const noexcept -{ - int flags = 0; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) { - flags |= SocketListener::Read; - } - if (events & EPOLLOUT) { - flags |= SocketListener::Write; - } - - return flags; -} - -void Epoll::update(SocketAbstract &sc, int op, int flags) -{ - struct epoll_event ev; - - std::memset(&ev, 0, sizeof (struct epoll_event)); - - ev.events = flags; - ev.data.fd = sc.handle(); - - if (epoll_ctl(m_handle, op, sc.handle(), &ev) < 0) { - throw SocketError{SocketError::System, "epoll_ctl"}; - } -} - -Epoll::Epoll() - : m_handle(epoll_create1(0)) -{ - if (m_handle < 0) { - throw SocketError(SocketError::System, "epoll_create"); - } -} - -Epoll::~Epoll() -{ - close(m_handle); -} - -/* - * Add a new epoll_event or just update it. - */ -void Epoll::set(const SocketTable &, SocketAbstract &sc, int flags, bool add) -{ - update(sc, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, toepoll(flags)); - - if (add) { - m_events.resize(m_events.size() + 1); - } -} - -/* - * Unset is a bit complicated case because SocketListener 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 SocketTable &table, SocketAbstract &sc, int flags, bool remove) -{ - if (remove) { - update(sc, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else { - update(sc, EPOLL_CTL_MOD, table.at(sc.handle()).second & ~(toepoll(flags))); - } -} - -std::vector<SocketStatus> Epoll::wait(const SocketTable &table, int ms) -{ - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<SocketStatus> result; - - if (ret == 0) { - throw SocketError(SocketError::Timeout, "epoll_wait"); - } - if (ret < 0) { - throw SocketError(SocketError::System, "epoll_wait"); - } - - for (int i = 0; i < ret; ++i) { - result.push_back(SocketStatus{*table.at(m_events[i].data.fd).first, toflags(m_events[i].events)}); - } - - return result; -} - -#endif // !SOCKET_HAVE_EPOLL - -/* -------------------------------------------------------- - * Kqueue implementation - * -------------------------------------------------------- */ - -#if defined(SOCKET_HAVE_KQUEUE) - -Kqueue::Kqueue() - : m_handle(kqueue()) -{ - if (m_handle < 0) { - throw SocketError(SocketError::System, "kqueue"); - } -} - -Kqueue::~Kqueue() -{ - close(m_handle); -} - -void Kqueue::update(SocketAbstract &sc, int filter, int flags) -{ - struct kevent ev; - - EV_SET(&ev, sc.handle(), filter, flags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { - throw SocketError(SocketError::System, "kevent"); - } -} - -void Kqueue::set(const SocketTable &, SocketAbstract &sc, int flags, bool add) -{ - if (flags & SocketListener::Read) { - update(sc, EVFILT_READ, EV_ADD | EV_ENABLE); - } - if (flags & SocketListener::Write) { - update(sc, EVFILT_WRITE, EV_ADD | EV_ENABLE); - } - - if (add) { - m_result.resize(m_result.size() + 1); - } -} - -void Kqueue::unset(const SocketTable &, SocketAbstract &sc, int flags, bool remove) -{ - if (flags & SocketListener::Read) { - update(sc, EVFILT_READ, EV_DELETE); - } - if (flags & SocketListener::Write) { - update(sc, EVFILT_WRITE, EV_DELETE); - } - - if (remove) { - m_result.resize(m_result.size() - 1); - } -} - -std::vector<SocketStatus> Kqueue::wait(const SocketTable &table, int ms) -{ - std::vector<SocketStatus> 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 SocketError(SocketError::Timeout, "kevent"); - } - if (nevents < 0) { - throw SocketError(SocketError::System, "kevent"); - } - - for (int i = 0; i < nevents; ++i) { - SocketAbstract *sc = table.at(m_result[i].ident).first; - int flags = m_result[i].filter == EVFILT_READ ? SocketListener::Read : SocketListener::Write; - - sockets.push_back(SocketStatus{*sc, flags}); - } - - return sockets; -} - -#endif // !SOCKET_HAVE_KQUEUE - -} // !backend
--- a/C++/modules/Socket/SocketListener.h Thu Oct 22 20:00:51 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,592 +0,0 @@ -/* - * SocketListener.h -- portable select() wrapper - * - * Copyright (c) 2013-2015 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 _SOCKET_LISTENER_NG_H_ -#define _SOCKET_LISTENER_NG_H_ - -/** - * @file SocketListener.h - * @brief Portable synchronous multiplexer - * - * 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. - */ -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 -# define SOCKET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# define SOCKET_HAVE_KQUEUE -# define SOCKET_HAVE_POLL -#elif defined(__linux__) -# define SOCKET_HAVE_EPOLL -# define SOCKET_HAVE_POLL -#endif - -/** - * This sets the default backend to use depending on the system. The following - * table summaries. - * - * The preference priority is ordered from left to right. - * - * | System | Backend | - * |---------------|-------------------------| - * | Linux | epoll(7) | - * | *BSD | kqueue(2) | - * | Windows | poll(2), select(2) | - * | Mac OS X | kqueue(2) | - */ - -#if defined(_WIN32) -# if defined(SOCKET_HAVE_POLL) -# define SOCKET_DEFAULT_BACKEND backend::Poll -# else -# define SOCKET_DEFAULT_BACKEND backend::Select -# endif -#elif defined(__linux__) -# include <sys/epoll.h> -# include <cstring> - -# define SOCKET_DEFAULT_BACKEND backend::Epoll -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - -# define SOCKET_DEFAULT_BACKEND backend::Kqueue -#else -# define SOCKET_DEFAULT_BACKEND backend::Select -#endif - -#include <chrono> -#include <functional> -#include <initializer_list> -#include <map> -#include <memory> -#include <utility> -#include <vector> - -#include "Socket.h" - -#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -/** - * @struct SocketStatus - * @brief The SocketStatus class - * - * Result of a select call, returns the first ready socket found with its - * flags. - */ -class SocketStatus { -public: - SocketAbstract &socket; //!< which socket is ready - int flags; //!< the flags -}; - -/** - * Table used in the socket listener to store which sockets have been - * set in which directions. - */ -using SocketTable = std::map<SocketAbstract::Handle, std::pair<SocketAbstract *, int>>; - -/** - * @brief Namespace for predefined backends - */ -namespace backend { - -/** - * @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: - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "select"; - } - - /** - * No-op, uses the SocketTable directly. - */ - inline void set(const SocketTable &, SocketAbstract &, int, bool) noexcept {} - - /** - * No-op, uses the SocketTable directly. - */ - inline void unset(const SocketTable &, SocketAbstract &, int, bool) noexcept {} - - /** - * Return the sockets - */ - std::vector<SocketStatus> wait(const SocketTable &table, int ms); -}; - -#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(int flags) const noexcept; - int toflags(short &event) const noexcept; - -public: - void set(const SocketTable &, SocketAbstract &sc, int flags, bool add); - void unset(const SocketTable &, SocketAbstract &sc, int flags, bool remove); - std::vector<SocketStatus> wait(const SocketTable &, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "poll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_EPOLL) - -class Epoll { -private: - int m_handle; - std::vector<struct 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(int flags) const noexcept; - int toflags(uint32_t events) const noexcept; - void update(SocketAbstract &sc, int op, int flags); - -public: - Epoll(); - ~Epoll(); - void set(const SocketTable &, SocketAbstract &sc, int flags, bool add); - void unset(const SocketTable &, SocketAbstract &sc, int flags, bool remove); - std::vector<SocketStatus> wait(const SocketTable &table, int ms); - - /** - * 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(SocketAbstract &sc, int filter, int flags); - -public: - Kqueue(); - ~Kqueue(); - - void set(const SocketTable &, SocketAbstract &sc, int flags, bool add); - void unset(const SocketTable &, SocketAbstract &sc, int flags, bool remove); - std::vector<SocketStatus> wait(const SocketTable &, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "kqueue"; - } -}; - -#endif - -} // !backend - -/** - * @class SocketListenerAbstract - * @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 SocketListener 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 SocketTable &, const SocketAbstract &sc, int flags, bool add); - * @endcode - * - * This function, takes the socket to be added and the flags. The flags are - * 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 SocketTable &, const SocketAbstract &sc, int flags, bool remove); - * @endcode - * - * Like set, this function is only called if the flags are 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<SocketStatus> wait(const SocketTable &, int ms); - * @encode - * - * Wait for the sockets to be ready with the specified milliseconds. Must return a list of SocketStatus, - * 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 SocketListenerAbstract final { -public: - /** - * Mark the socket for read operation. - */ - static const int Read; - - /** - * Mark the socket for write operation. - */ - static const int Write; - -private: - Backend m_backend; - SocketTable m_table; - -public: - /** - * Construct an empty listener. - */ - SocketListenerAbstract() = 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 SocketTable &table() const noexcept - { - return m_table; - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline SocketTable::const_iterator begin() const noexcept - { - return m_table.begin(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline SocketTable::const_iterator cbegin() const noexcept - { - return m_table.cbegin(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline SocketTable::const_iterator end() const noexcept - { - return m_table.end(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline SocketTable::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 flags (may be OR'ed) - * @throw SocketError if the backend failed to set - */ - void set(SocketAbstract &sc, int flags); - - /** - * 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 flags the flags (may be OR'ed) - * @see remove - */ - void unset(SocketAbstract &sc, int flags); - - /** - * Remove completely the socket from the listener. - * - * It is a shorthand for unset(sc, SocketListener::Read | SocketListener::Write); - * - * @param sc the socket - */ - inline void remove(SocketAbstract &sc) - { - unset(sc, Read | Write); - } - - /** - * Remove all sockets. - */ - inline void clear() - { - while (!m_table.empty()) { - remove(m_table.begin()->second.first); - } - } - - /** - * Get the number of sockets in the listener. - */ - inline SocketTable::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 SocketStatus wait(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())[0]; - } - - /** - * Overload with milliseconds. - * - * @param timeout the optional timeout in milliseconds - * @return the socket ready - */ - inline SocketStatus 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<SocketStatus> 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. - * - * @return the socket ready - */ - inline std::vector<SocketStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -template <typename Backend> -void SocketListenerAbstract<Backend>::set(SocketAbstract &sc, int flags) -{ - /* Invalid or useless flags */ - if (flags == 0 || flags > 0x3) - return; - - auto it = m_table.find(sc.handle()); - - /* - * Do not update the table if the backend failed to add - * or update. - */ - if (it == m_table.end()) { - m_backend.set(m_table, sc, flags, true); - m_table.emplace(sc.handle(), std::make_pair(std::addressof(sc), flags)); - } else { - if ((flags & Read) && (it->second.second & Read)) { - flags &= ~(Read); - } - if ((flags & Write) && (it->second.second & Write)) { - flags &= ~(Write); - } - - /* Still need a call? */ - if (flags != 0) { - m_backend.set(m_table, sc, flags, false); - it->second.second |= flags; - } - } -} - -template <typename Backend> -void SocketListenerAbstract<Backend>::unset(SocketAbstract &sc, int flags) -{ - auto it = m_table.find(sc.handle()); - - /* Invalid or useless flags */ - if (flags == 0 || flags > 0x3 || it == m_table.end()) - return; - - /* - * Like set, do not update if the socket is already at the appropriate - * state. - */ - if ((flags & Read) && !(it->second.second & Read)) { - flags &= ~(Read); - } - if ((flags & Write) && !(it->second.second & Write)) { - flags &= ~(Write); - } - - if (flags != 0) { - /* Determine if it's a complete removal */ - bool removal = ((it->second.second) & ~(flags)) == 0; - - m_backend.unset(m_table, sc, flags, removal); - - if (removal) { - m_table.erase(it); - } else { - it->second.second &= ~(flags); - } - } -} - -/** - * Helper to use the default. - */ -using SocketListener = SocketListenerAbstract<>; - -template <typename Backend> -const int SocketListenerAbstract<Backend>::Read{1 << 0}; - -template <typename Backend> -const int SocketListenerAbstract<Backend>::Write{1 << 1}; - -#endif // !_SOCKET_LISTENER_NG_H_
--- a/C++/modules/Socket/SocketSsl.cpp Thu Oct 22 20:00:51 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * SocketSsl.cpp -- OpenSSL extension for sockets - * - * Copyright (c) 2013-2015 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. - */ - -#include "SocketAddress.h" -#include "SocketSsl.h" - -namespace detail { - -std::mutex mutex; -std::atomic<bool> initialized{false}; - -void terminate() -{ - ERR_free_strings(); -} - -void initialize() -{ - std::lock_guard<std::mutex> lock(mutex); - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - - atexit(terminate); - } -} - -} // !detail
--- a/C++/modules/Socket/SocketSsl.h Thu Oct 22 20:00:51 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,342 +0,0 @@ -/* - * SocketSsl.h -- OpenSSL extension for sockets - * - * Copyright (c) 2013-2015 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 _SOCKET_SSL_NG_H_ -#define _SOCKET_SSL_NG_H_ - -/** - * @file SocketSsl.h - * @brief Bring SSL support to socket module - * @note This code is considered experimental - * - * User may set the following variables before compiling these files: - * - * - **SOCKET_NO_SSL_INIT**: (bool) Set to false if you don't want OpenSSL to be - * initialized when the first SocketSsl object is created. - */ - -#include <cstdint> -#include <atomic> -#include <memory> -#include <mutex> - -#include <openssl/err.h> -#include <openssl/evp.h> -#include <openssl/ssl.h> - -#include "Socket.h" - -/** - * @class SocketSslOptions - * @brief Options for SocketSsl - */ -class SocketSslOptions { -public: - /** - * @brief Method - * - * It is highly recommended to only use TLSv1. - */ - enum { - SSLv3, - TLSv1 - }; - - int method{TLSv1}; //!< The method - std::string certificate; //!< The certificate path - std::string privateKey; //!< The private key file - bool verify{false}; //!< Verify or not - - /** - * Default constructor. - */ - SocketSslOptions() = default; - - /** - * More advanced constructor. - * - * @param method the method requested - * @param certificate the certificate file - * @param key the key file - * @param verify set to true to verify - */ - SocketSslOptions(std::string certificate, std::string key, int method = TLSv1, bool verify = false) - : method(method) - , certificate(std::move(certificate)) - , privateKey(std::move(key)) - , verify(verify) - { - } -}; - -/** - * This namespace is private. - */ -namespace detail { - -/** - * Mutex for thread-safe initialization. - */ -extern std::mutex mutex; - -/** - * Boolean that marks the SSL module initialized. - */ -extern std::atomic<bool> initialized; - -/** - * Get the appropriate method. - */ -inline auto method(int type) noexcept -{ - if (type == SocketSslOptions::SSLv3) - return SSLv3_method(); - if (type == SocketSslOptions::TLSv1) - return TLSv1_method(); - - throw std::invalid_argument("unknown method selected"); -} - -/** - * Get the error - */ -inline std::string error(int error) -{ - return ERR_reason_error_string(error); -} - -/** - * Close OpenSSL library. - */ -void terminate(); - -/** - * Open SSL library. - */ -void initialize(); - -} // !ssl - -/** - * @class SocketSsl - * @brief SSL interface for sockets - * - * This class derives from SocketAbstractTcp and provide SSL support through OpenSSL. - */ -template <typename Address> -class SocketSsl : public SocketAbstractTcp<Address> { -private: - using ContextHandle = std::unique_ptr<SSL_CTX, void (*)(SSL_CTX *)>; - using SslHandle = std::unique_ptr<SSL, void (*)(SSL *)>; - - ContextHandle m_context{nullptr, nullptr}; - SslHandle m_ssl{nullptr, nullptr}; - SocketSslOptions m_options; - -public: - /** - * Create a SocketSsl from an already created one. - * - * @param sc the standard TCP socket - * @param context the context - * @param ssl the ssl object - */ - explicit SocketSsl(SocketTcp<Address> sc, SSL_CTX *context, SSL *ssl); - - /** - * Open a SSL socket with the specified family. Automatically - * use SOCK_STREAM as the type. - * - * @param family the family - * @param protocol the protocol - * @param options the options - */ - SocketSsl(int family, int protocol, SocketSslOptions options = {}); - - /** - * Accept a SSL TCP socket. - * - * @param info the client information - * @return the socket - * @throw SocketError on error - */ - SocketSsl accept(Address &info); - - /** - * Connect to an end point. - * - * @param address the address - * @throw SocketError on error - */ - void connect(const Address &address); - - /** - * @copydoc SocketTcp::recv - */ - unsigned recv(void *data, unsigned length) override; - - /** - * @copydoc SocketTcp::recv - */ - unsigned send(const void *data, unsigned length) override; - - /** - * Bring back send overloads. - */ - using SocketAbstractTcp<Address>::send; - - /** - * Bring back recv overloads; - */ - using SocketAbstractTcp<Address>::recv; -}; - -template <typename Address> -SocketSsl<Address>::SocketSsl(SocketTcp<Address> sc, SSL_CTX *context, SSL *ssl) - : SocketAbstractTcp<Address>{sc.handle()} - , m_context{context, SSL_CTX_free} - , m_ssl{ssl, SSL_free} -{ -#if !defined(SOCKET_NO_SSL_INIT) - if (!detail::initialized) { - detail::initialize(); - } -#endif - - // Invalid other - sc.m_handle = -1; -} - -template <typename Address> -SocketSsl<Address>::SocketSsl(int family, int protocol, SocketSslOptions options) - : SocketAbstractTcp<Address>{family, protocol} - , m_context{nullptr, nullptr} - , m_ssl{nullptr, nullptr} - , m_options{std::move(options)} -{ -#if !defined(SOCKET_NO_SSL_INIT) - if (!detail::initialized) { - detail::initialize(); - } -#endif - m_context = ContextHandle{SSL_CTX_new(detail::method(m_options.method)), SSL_CTX_free}; - m_ssl = SslHandle{SSL_new(m_context.get()), SSL_free}; - - SSL_set_fd(m_ssl.get(), SocketAbstract::m_handle); -} - - -template <typename Address> -void SocketSsl<Address>::connect(const Address &address) -{ - // 1. Standard connect - SocketAbstractTcp<Address>::standardConnect(address); - - // 2. OpenSSL handshake - auto ret = SSL_connect(m_ssl.get()); - - if (ret <= 0) { - auto error = SSL_get_error(m_ssl.get(), ret); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError{SocketError::WouldBlockRead, "connect", "Operation in progress"}; - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError{SocketError::WouldBlockWrite, "connect", "Operation in progress"}; - } else { - throw SocketError{SocketError::System, "connect", detail::error(error)}; - } - } -} - -template <typename Address> -SocketSsl<Address> SocketSsl<Address>::accept(Address &info) -{ - auto client = SocketAbstractTcp<Address>::standardAccept(info); - auto context = SSL_CTX_new(detail::method(m_options.method)); - - if (m_options.certificate.size() > 0) - SSL_CTX_use_certificate_file(context, m_options.certificate.c_str(), SSL_FILETYPE_PEM); - if (m_options.privateKey.size() > 0) - SSL_CTX_use_PrivateKey_file(context, m_options.privateKey.c_str(), SSL_FILETYPE_PEM); - if (m_options.verify && !SSL_CTX_check_private_key(context)) { - throw SocketError(SocketError::System, "accept", "certificate failure"); - } - - // SSL object - auto ssl = SSL_new(context); - - SSL_set_fd(ssl, client->handle()); - - auto ret = SSL_accept(ssl); - - if (ret <= 0) { - auto error = SSL_get_error(ssl, ret); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError(SocketError::WouldBlockRead, "accept", "Operation would block"); - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError(SocketError::WouldBlockWrite, "accept", "Operation would block"); - } else { - throw SocketError(SocketError::System, "accept", detail::error(error)); - } - } - - return SocketSsl(std::move(client), context, ssl); -} - -template <typename Address> -unsigned SocketSsl<Address>::recv(void *data, unsigned len) -{ - auto nbread = SSL_read(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto error = SSL_get_error(m_ssl.get(), nbread); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError{SocketError::WouldBlockRead, "recv", "Operation would block"}; - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError{SocketError::WouldBlockWrite, "recv", "Operation would block"}; - } else { - throw SocketError{SocketError::System, "recv", detail::error(error)}; - } - } - - return nbread; -} - -template <typename Address> -unsigned SocketSsl<Address>::send(const void *data, unsigned len) -{ - auto nbread = SSL_write(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto error = SSL_get_error(m_ssl.get(), nbread); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError{SocketError::WouldBlockRead, "send", "Operation would block"}; - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError{SocketError::WouldBlockWrite, "send", "Operation would block"}; - } else { - throw SocketError{SocketError::System, "send", detail::error(error)}; - } - } - - return nbread; -} - -#endif // !_SOCKET_SSL_NG_H_
--- a/C++/modules/Socket/Sockets.cpp Thu Oct 22 20:00:51 2015 +0200 +++ b/C++/modules/Socket/Sockets.cpp Thu Oct 22 21:38:28 2015 +0200 @@ -16,13 +16,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#if defined(_WIN32) -# include <atomic> -# include <mutex> -#endif - #include <algorithm> +#include <atomic> #include <cstring> +#include <mutex> #include "Sockets.h" @@ -58,9 +55,13 @@ #if defined(_WIN32) +namespace { + static std::mutex s_mutex; static std::atomic<bool> s_initialized{false}; +} // !namespace + #endif // !_WIN32 void init() noexcept @@ -608,4 +609,40 @@ #endif // !SOCKET_HAVE_KQUEUE +#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(); + +#if !defined(SOCKET_NO_AUTO_SSL_INIT) + atexit(finish); +#endif // SOCKET_NO_AUTO_SSL_INIT + } +} + +#endif // SOCKET_NO_SSL + +} // !ssl + } // !net
--- a/C++/modules/Socket/Sockets.h Thu Oct 22 20:00:51 2015 +0200 +++ b/C++/modules/Socket/Sockets.h Thu Oct 22 21:38:28 2015 +0200 @@ -23,12 +23,19 @@ * @file Sockets.h * @brief Portable socket abstraction * + * This file is a portable network library. + * + * ### User definable options + * * User may set the following variables before compiling these files: * - * - **SOCKET_NO_AUTO_INIT**:(bool) Set to false if you don't want Socket class to + * - **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. * - * # For Listener objects + * ### Options for Listener class * * Feature detection, multiple implementations may be avaible, for example, * Linux has poll, select and epoll. @@ -62,12 +69,12 @@ * * The preference priority is ordered from left to right. * - * | System | Backend | - * |---------------|-------------------------| - * | Linux | epoll(7) | - * | *BSD | kqueue(2) | - * | Windows | poll(2), select(2) | - * | Mac OS X | kqueue(2) | + * | 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 | */ #if defined(_WIN32) @@ -116,6 +123,12 @@ # include <unistd.h> #endif +#if !defined(SOCKET_NO_SSL) +# include <openssl/err.h> +# include <openssl/evp.h> +# include <openssl/ssl.h> +#endif + #include <chrono> #include <cstdlib> #include <cstring> @@ -125,6 +138,9 @@ #include <string> #include <vector> +/** + * General network namespace. + */ namespace net { /* @@ -188,6 +204,28 @@ */ 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. + */ +void init() noexcept; + +/** + * Close the OpenSSL library. + */ +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. @@ -372,19 +410,26 @@ public: /** * This tries to create a socket. + * + * @param type the type instance */ - inline Socket() noexcept + inline Socket(Type type = Type{}) noexcept : Socket{Address::domain(), Type::type(), 0} { + /* Some implementation requires more things */ + m_type = std::move(type); + m_type.create(*this); } /** * Construct a socket with an already created descriptor. * * @param handle the native descriptor + * @param type the type of socket implementation */ - explicit inline Socket(Handle handle) noexcept - : m_handle{handle} + explicit inline Socket(Handle handle, Type type = Type{}) noexcept + : m_type(std::move(type)) + , m_handle{handle} { } @@ -525,12 +570,12 @@ /** * Bind using a native address. * - * @param addr the address - * @param size the size + * @param address the address + * @param length the size */ - inline void bind(const sockaddr *addr, socklen_t size) + inline void bind(const sockaddr *address, socklen_t length) { - if (::bind(m_handle, addr, size) == Failure) { + if (::bind(m_handle, address, length) == Failure) { throw Error{Error::System, "bind"}; } } @@ -1103,35 +1148,21 @@ * @brief Clear TCP implementation. */ class Tcp { -public: +protected: /** - * Socket type. - * - * @return SOCK_STREAM - */ - static inline int type() noexcept - { - return SOCK_STREAM; - } - - /** - * Accept a clear client. Wrapper of accept(2). + * Standard accept. * * @param sc the socket * @param address the address destination - * @return the socket + * @param length the address initial length + * @return the client handle * @throw Error on errors */ - template <typename Address> - Socket<Address, Tcp> accept(Socket<Address, Tcp> &sc, Address &address) + Handle accept(Handle sc, sockaddr *address, socklen_t *length) { - Handle handle; - sockaddr_storage storage; - socklen_t addrlen = sizeof (sockaddr_storage); + Handle client = ::accept(sc, address, length); - handle = ::accept(sc.handle(), reinterpret_cast<sockaddr *>(&storage), &addrlen); - - if (handle == Invalid) { + if (client == Invalid) { #if defined(_WIN32) int error = WSAGetLastError(); @@ -1149,22 +1180,19 @@ #endif } - address = Address{&storage, addrlen}; - - return Socket<Address, Tcp>{handle}; + return client; } /** - * Connect to the end point. Wrapper for connect(2). + * Standard connect. * * @param sc the socket * @param address the address - * @throw Error on errors + * @param length the length */ - template <typename Address> - void connect(Socket<Address, Tcp> &sc, const Address &address) + void connect(Handle sc, const sockaddr *address, socklen_t length) { - if (::connect(sc.handle(), address.address(), address.length()) == Failure) { + if (::connect(sc, address, length) == Failure) { /* * Determine if the error comes from a non-blocking connect that cannot be * accomplished yet. @@ -1187,6 +1215,58 @@ #endif } +public: + /** + * Socket type. + * + * @return SOCK_STREAM + */ + static inline int type() noexcept + { + return SOCK_STREAM; + } + + /** + * Do nothing. + */ + template <typename Address> + inline void create(Socket<Address, Tcp> &) noexcept + { + /* No-op */ + } + + /** + * Accept a clear client. Wrapper of accept(2). + * + * @param sc the socket + * @param address the address destination + * @return the socket + * @throw Error on errors + */ + template <typename Address> + Socket<Address, Tcp> accept(Socket<Address, Tcp> &sc, Address &address) + { + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + Handle handle = accept(sc.handle(), reinterpret_cast<sockaddr *>(&ss), &length); + address = Address{&ss, length}; + + return Socket<Address, Tcp>{handle}; + } + + /** + * Connect to the end point. Wrapper for connect(2). + * + * @param sc the socket + * @param address the address + * @throw Error on errors + */ + template <typename Address> + void connect(Socket<Address, Tcp> &sc, const Address &address) + { + connect(sc.handle(), address.address(), address.length()); + } + /** * Receive data. Wrapper of recv(2). * @@ -1283,6 +1363,15 @@ } /** + * Do nothing. + */ + template <typename Address> + inline void create(Socket<Address, Udp> &) noexcept + { + /* No-op */ + } + + /** * Receive data from an end point. * * @param sc the socket @@ -1365,6 +1454,237 @@ /* }}} */ +#if !defined(SOCKET_NO_SSL) + +/** + * @class Tls + * @brief OpenSSL secure layer for TCP + */ +class Tls : private Tcp { +public: + /** + * OpenSSL method to use. + */ + enum Method { + Tlsv1, //!< Tlsv1 (recommended) + Sslv3 //!< SSL v3 + }; + +private: + using Context = std::unique_ptr<SSL_CTX, void (*)(SSL_CTX *)>; + using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; + + /* OpenSSL objects */ + Context m_context{nullptr, nullptr}; + Ssl m_ssl{nullptr, nullptr}; + + /* Parameters */ + Method m_method; + std::string m_key; + std::string m_certificate; + bool m_verify{false}; + + Tls(Context context, Ssl ssl) + : m_context{std::move(context)} + , m_ssl{std::move(ssl)} + { + } + + inline std::string error(int error) + { + return ERR_reason_error_string(error); + } + +public: + /** + * @copydoc Tcp::type + */ + static inline int type() noexcept + { + return SOCK_STREAM; + } + + /** + * Empty TLS constructor. + */ + Tls() + { +#if !defined(SOCKET_NO_SSL_AUTO_INIT) + ssl::init(); +#endif + } + + /** + * Construct a specific Tls object. + * + * @param method the method to use + * @param verify true to verify the certificate + * @param key the private key + * @param certificate the certificate file + */ + Tls(Method method, bool verify = true, std::string key = "", std::string certificate = "") + : Tls() + { + m_method = method; + m_verify = verify; + m_key = std::move(key); + m_certificate = std::move(certificate); + } + + /** + * Initialize the SSL objects after have created. + * + * @param sc the socket + */ + template <typename Address> + inline void create(Socket<Address, Tls> &sc) + { + auto method = (m_method == Tlsv1) ? TLSv1_method() : SSLv3_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, "accept", "certificate failure"); + } + } + + /** + * Connect to a secure host. + * + * @param sc the socket + * @param address the address + * @throw Error on errors + */ + template <typename Address> + void connect(Socket<Address, Tls> &sc, const Address &address) + { + // 1. Standard connect + Tcp::connect(sc.handle(), address.address(), address.length()); + + // 2. OpenSSL handshake + auto ret = SSL_connect(m_ssl.get()); + + if (ret <= 0) { + auto no = SSL_get_error(m_ssl.get(), ret); + + if (no == SSL_ERROR_WANT_READ) { + throw Error{Error::WouldBlockRead, "connect", "Operation in progress"}; + } else if (no == SSL_ERROR_WANT_WRITE) { + throw Error{Error::WouldBlockWrite, "connect", "Operation in progress"}; + } else { + throw Error{Error::System, "connect", error(no)}; + } + } + } + + /** + * Accept a secure client. + * + * @param sc the socket + * @param address the address destination + * @return the client + */ + template <typename Address> + Socket<Address, Tls> accept(Socket<Address, Tls> &sc, Address &address) + { + /* 1. Do standard accept */ + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + Handle handle = Tcp::accept(sc.handle(), reinterpret_cast<sockaddr *>(&ss), &length); + address = Address{&ss, length}; + + /* 2. Create OpenSSL related stuff */ + auto method = (m_method == Tlsv1) ? TLSv1_method() : SSLv3_method(); + auto context = Context{SSL_CTX_new(method), SSL_CTX_free}; + auto ssl = Ssl{SSL_new(context.get()), SSL_free}; + + SSL_set_fd(ssl.get(), handle); + + /* 3. Do the OpenSSL accept */ + auto ret = SSL_accept(ssl.get()); + + if (ret <= 0) { + auto no = SSL_get_error(ssl.get(), ret); + + if (no == SSL_ERROR_WANT_READ) { + throw Error(Error::WouldBlockRead, "accept", "Operation would block"); + } else if (no == SSL_ERROR_WANT_WRITE) { + throw Error(Error::WouldBlockWrite, "accept", "Operation would block"); + } else { + throw Error(Error::System, "accept", error(no)); + } + } + + return Socket<Address, Tls>{handle, Tls{std::move(context), std::move(ssl)}}; + } + + /** + * Receive some secure data. + * + * @param data the destination + * @param len the buffer length + * @return the number of bytes read + * @throw Error on errors + */ + template <typename Address> + unsigned recv(Socket<Address, Tls> &, 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) { + throw Error{Error::WouldBlockRead, "recv", "Operation would block"}; + } else if (no == SSL_ERROR_WANT_WRITE) { + throw Error{Error::WouldBlockWrite, "recv", "Operation would block"}; + } else { + throw Error{Error::System, "recv", error(no)}; + } + } + + return nbread; + } + + /** + * Send some data. + * + * @param data the data to send + * @param len the buffer length + * @return the number of bytes sent + * @throw Error on errors + */ + template <typename Address> + unsigned send(Socket<Address, Tls> &, const void *data, unsigned len) + { + auto nbread = SSL_write(m_ssl.get(), data, len); + + if (nbread <= 0) { + auto no = SSL_get_error(m_ssl.get(), nbread); + + if (no == SSL_ERROR_WANT_READ) { + throw Error{Error::WouldBlockRead, "send", "Operation would block"}; + } else if (no == SSL_ERROR_WANT_WRITE) { + throw Error{Error::WouldBlockWrite, "send", "Operation would block"}; + } else { + throw Error{Error::System, "send", error(no)}; + } + } + + return nbread; + } +}; + +#endif // !SOCKET_NO_SSL + /* }}} */ /* @@ -1389,6 +1709,16 @@ template <typename Address> using SocketUdp = Socket<Address, Udp>; +#if !defined(SOCKET_NO_SSL) + +/** + * Helper to create OpenSSL TCP sockets. + */ +template <typename Address> +using SocketTls = Socket<Address, Tls>; + +#endif + /* }}} */ /* @@ -1576,7 +1906,7 @@ * This class is implemented using a bridge pattern to allow different uses * of listener implementation. * - * You should not reinstanciate a new SocketListener at each iteartion of your + * 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. * @@ -1584,10 +1914,10 @@ * * To implement the backend, the following functions must be available: * - * # Set + * ### Set * * @code - * void set(const ListenerTable &, const Handle &sc, int flags, bool add); + * void set(const ListenerTable &, Handle sc, int flags, bool add); * @endcode * * This function, takes the socket to be added and the flags. The flags are @@ -1598,10 +1928,10 @@ * 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 + * ### Unset * * @code - * void unset(const ListenerTable &, const Handle &sc, int flags, bool remove); + * void unset(const ListenerTable &, Handle sc, int flags, bool remove); * @endcode * * Like set, this function is only called if the flags are actually set and will @@ -1610,16 +1940,16 @@ * 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 + * ### Wait * * @code * std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - * @encode + * @endcode * * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, * may throw any exceptions. * - * # Name + * ### Name * * @code * inline const char *name() const noexcept @@ -1711,7 +2041,7 @@ * * @param sc the socket * @param flags (may be OR'ed) - * @throw SocketError if the backend failed to set + * @throw Error if the backend failed to set */ void set(Handle sc, int flags) { @@ -1783,7 +2113,7 @@ if (removal) { m_table.erase(it); } else { - it->second.second &= ~(flags); + it->second &= ~(flags); } } }
--- a/C++/tests/Socket/main.cpp Thu Oct 22 20:00:51 2015 +0200 +++ b/C++/tests/Socket/main.cpp Thu Oct 22 21:38:28 2015 +0200 @@ -264,8 +264,6 @@ ASSERT_EQ(0U, listener.size()); } -#if 0 - /* -------------------------------------------------------- * Listener: unset / remove functions * -------------------------------------------------------- */ @@ -397,7 +395,7 @@ class ListenerTest : public testing::Test { protected: - Listener<backend::Select> m_listener; + Listener<Select> m_listener; SocketTcp<Ipv4> m_masterTcp; SocketTcp<Ipv4> m_clientTcp; @@ -406,8 +404,6 @@ public: ListenerTest() - : m_masterTcp{AF_INET, 0} - , m_clientTcp{AF_INET, 0} { m_masterTcp.set(SOL_SOCKET, SO_REUSEADDR, 1); m_masterTcp.bind(Ipv4{"*", 16000}); @@ -429,7 +425,7 @@ { m_tserver = std::thread([this] () { try { - m_listener.set(m_masterTcp, FlagRead); + m_listener.set(m_masterTcp.handle(), FlagRead); m_listener.wait(); m_masterTcp.accept(); m_masterTcp.close(); @@ -449,7 +445,7 @@ { m_tserver = std::thread([this] () { try { - m_listener.set(m_masterTcp, FlagRead); + m_listener.set(m_masterTcp.handle(), FlagRead); m_listener.wait(); auto sc = m_masterTcp.accept(); @@ -474,8 +470,8 @@ class NonBlockingConnectTest : public testing::Test { protected: - SocketTcp<Ipv4> m_server{AF_INET, 0}; - SocketTcp<Ipv4> m_client{AF_INET, 0}; + SocketTcp<Ipv4> m_server; + SocketTcp<Ipv4> m_client; std::thread m_tserver; std::thread m_tclient; @@ -501,8 +497,8 @@ class TcpAcceptTest : public testing::Test { protected: - SocketTcp<Ipv4> m_server{AF_INET, 0}; - SocketTcp<Ipv4> m_client{AF_INET, 0}; + SocketTcp<Ipv4> m_server; + SocketTcp<Ipv4> m_client; std::thread m_tserver; std::thread m_tclient; @@ -530,8 +526,8 @@ class TcpRecvTest : public testing::Test { protected: - SocketTcp<Ipv4> m_server{AF_INET, 0}; - SocketTcp<Ipv4> m_client{AF_INET, 0}; + SocketTcp<Ipv4> m_server; + SocketTcp<Ipv4> m_client; std::thread m_tserver; std::thread m_tclient; @@ -576,15 +572,14 @@ class SslTest : public testing::Test { protected: - SocketSsl<Ipv4> client{AF_INET, 0}; + SocketTls<Ipv4> client; }; TEST_F(SslTest, connect) { try { client.connect(Ipv4{"google.fr", 443}); - client.close(); - } catch (const SocketError &error) { + } catch (const Error &error) { FAIL() << error.what(); } } @@ -599,13 +594,13 @@ std::string content = msg.substr(0, 18); ASSERT_EQ("HTTP/1.0 302 Found", content); - - client.close(); - } catch (const SocketError &error) { + } catch (const Error &error) { FAIL() << error.what(); } } +#if 0 + /* -------------------------------------------------------- * Operators * -------------------------------------------------------- */