changeset 563:17891017c661

Irccd: keep resolver in irc connection Iterators were invalidated because the resolver is destroyed, keep it in both irc::ip_connection and irc::tls_connection. While here, bring back SSL support in irccd.
author David Demelier <markand@malikania.fr>
date Sun, 26 Nov 2017 12:01:28 +0100
parents 75e3711c95f8
children b8ebbc74da0c
files libirccd/irccd/config.cpp libirccd/irccd/irc.cpp libirccd/irccd/irc.hpp libirccd/irccd/server.cpp tests/CMakeLists.txt
diffstat 5 files changed, 247 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd/irccd/config.cpp	Sun Nov 26 09:02:09 2017 +0100
+++ b/libirccd/irccd/config.cpp	Sun Nov 26 12:01:28 2017 +0100
@@ -333,7 +333,11 @@
     if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::ipv6);
     if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
+#if defined(HAVE_SSL)
         sv->set_flags(sv->flags() | server::ssl);
+#else
+        throw std::invalid_argument(string_util::sprintf("server %s: SSL support disabled", sv->name()));
+#endif
     if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::ssl_verify);
 
--- a/libirccd/irccd/irc.cpp	Sun Nov 26 09:02:09 2017 +0100
+++ b/libirccd/irccd/irc.cpp	Sun Nov 26 12:01:28 2017 +0100
@@ -31,35 +31,128 @@
 using boost::asio::ip::tcp;
 
 template <typename Socket>
-void do_connect(Socket& socket, tcp::resolver::iterator it, connection::connect_t handler)
+void wrap_connect(Socket& socket, tcp::resolver::iterator it, connection::connect_t handler)
 {
+    assert(handler);
+
     socket.close();
     socket.async_connect(*it, [&socket, it, handler] (auto code) mutable {
-        if (code && it != tcp::resolver::iterator())
-            do_connect(socket, ++it, std::move(handler));
+        if (code && ++it != tcp::resolver::iterator())
+            wrap_connect(socket, it, std::move(handler));
         else
             handler(code);
     });
 }
 
 template <typename Socket>
-void do_resolve(const std::string& host,
+void wrap_resolve(Socket& socket,
+                tcp::resolver& resolver,
+                const std::string& host,
                 const std::string& port,
-                Socket& socket,
                 connection::connect_t handler)
 {
-    auto resolver = std::make_shared<tcp::resolver>(socket.get_io_service());
+    assert(handler);
 
-    resolver->async_resolve(tcp::resolver::query(host, port), [&socket, handler, resolver] (auto code, auto it) {
+    tcp::resolver::query query(host, port);
+
+    resolver.async_resolve(query, [&socket, handler] (auto code, auto it) {
         if (code)
             handler(code);
         else
-            do_connect(socket, it, std::move(handler));
+            wrap_connect(socket, it, std::move(handler));
+    });
+}
+
+template <typename Socket>
+void wrap_recv(Socket& socket, boost::asio::streambuf& buffer, connection::recv_t handler)
+{
+    assert(handler);
+
+    boost::asio::async_read_until(socket, buffer, "\r\n", [&socket, &buffer, handler] (auto code, auto xfer) {
+        if (code || xfer == 0U)
+            handler(std::move(code), message());
+        else {
+            std::string str(
+                boost::asio::buffers_begin(buffer.data()),
+                boost::asio::buffers_begin(buffer.data()) + xfer - 2
+            );
+
+            buffer.consume(xfer);
+            handler(std::move(code), message::parse(str));
+        }
+    });
+}
+
+template <typename Socket>
+void wrap_send(Socket& socket, const std::string& message, connection::send_t handler)
+{
+    assert(handler);
+
+    boost::asio::async_write(socket, boost::asio::buffer(message), [handler, message] (auto code, auto) {
+        // TODO: xfer
+        handler(code);
     });
 }
 
 } // !namespace
 
+void connection::rflush()
+{
+    if (input_.empty())
+        return;
+
+    do_recv(buffer_, [this] (auto code, auto message) {
+        input_.front()(code, std::move(message));
+        input_.pop_front();
+
+        if (!code)
+            rflush();
+    });
+}
+
+void connection::sflush()
+{
+    if (output_.empty())
+        return;
+
+    do_send(output_.front().first, [this] (auto code) {
+        if (output_.front().second)
+            output_.front().second(code);
+
+        output_.pop_front();
+
+        if (!code)
+            sflush();
+    });
+}
+
+void connection::connect(const std::string& host, const std::string& service, connect_t handler)
+{
+    assert(handler);
+
+    do_connect(host, service, std::move(handler));
+}
+
+void connection::recv(recv_t handler)
+{
+    auto in_progress = !input_.empty();
+
+    input_.push_back(std::move(handler));
+
+    if (!in_progress)
+        rflush();
+}
+
+void connection::send(std::string message, send_t handler)
+{
+    auto in_progress = !output_.empty();
+
+    output_.emplace_back(std::move(message + "\r\n"), std::move(handler));
+
+    if (!in_progress)
+        sflush();
+}
+
 message message::parse(const std::string& line)
 {
     std::istringstream iss(line);
@@ -134,11 +227,49 @@
     return {line.substr(0, pos), line.substr(pos + 1)};
 }
 
-void ip_connection::connect(const std::string& host, const std::string& service, connect_t handler)
+void ip_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept
+{
+    wrap_resolve(socket_, resolver_, host, service, std::move(handler));
+}
+
+void ip_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept
+{
+    wrap_recv(socket_, buffer, std::move(handler));
+}
+
+void ip_connection::do_send(const std::string& data, send_t handler) noexcept
+{
+    wrap_send(socket_, data, std::move(handler));
+}
+
+#if defined(HAVE_SSL)
+
+void tls_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept
 {
-    do_resolve(host, service, socket_, std::move(handler));
+    using boost::asio::ssl::stream_base;
+
+    wrap_resolve(socket_.lowest_layer(), resolver_, host, service, [this, handler] (auto code) {
+        if (code)
+            handler(code);
+        else
+            socket_.async_handshake(stream_base::client, [this, handler] (auto code) {
+                handler(code);
+            });
+    });
 }
 
+void tls_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept
+{
+    wrap_recv(socket_, buffer, std::move(handler));
+}
+
+void tls_connection::do_send(const std::string& data, send_t handler) noexcept
+{
+    wrap_send(socket_, data, std::move(handler));
+}
+
+#endif // !HAVE_SSL
+
 } // !irc
 
 } // !irccd
--- a/libirccd/irccd/irc.hpp	Sun Nov 26 09:02:09 2017 +0100
+++ b/libirccd/irccd/irc.hpp	Sun Nov 26 12:01:28 2017 +0100
@@ -24,6 +24,8 @@
  * \brief Low level IRC functions.
  */
 
+#include "sysconfig.hpp"
+
 #include <deque>
 #include <functional>
 #include <string>
@@ -371,6 +373,45 @@
      */
     using send_t = std::function<void (boost::system::error_code)>;
 
+private:
+    using buffer_t = boost::asio::streambuf;
+    using input_t = std::deque<recv_t>;
+    using output_t = std::deque<std::pair<std::string, send_t>>;
+
+    buffer_t buffer_;
+    input_t input_;
+    output_t output_;
+
+    void rflush();
+    void sflush();
+
+protected:
+    /**
+     * Do the connection.
+     *
+     * \param host the hostname
+     * \param service the service or port number
+     * \param handler the non-null handler
+     */
+    virtual void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept = 0;
+
+    /**
+     * Receive some data.
+     *
+     * \param buffer the buffer to complete
+     * \param handler the non-null handler
+     */
+    virtual void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept = 0;
+
+    /**
+     * Send data.
+     *
+     * \param data the data to send
+     * \param handler the non-null handler
+     */
+    virtual void do_send(const std::string& data, send_t handler) noexcept = 0;
+
+public:
     /**
      * Default constructor.
      */
@@ -384,18 +425,19 @@
     /**
      * Connect to the host.
      *
+     * \pre handler the handler
      * \param host the host
      * \param service the service or port number
      * \param handler the non-null handler
      */
-    virtual void connect(const std::string& host, const std::string& service, connect_t handler) = 0;
+    void connect(const std::string& host, const std::string& service, connect_t handler);
 
     /**
      * Start receiving data.
      *
      * \param handler the handler to call
      */
-    virtual void recv(recv_t handler) = 0;
+    void recv(recv_t handler);
 
     /**
      * Start sending data.
@@ -403,149 +445,88 @@
      * \param message the raw message
      * \param handler the handler to call
      */
-    virtual void send(std::string message, send_t handler = nullptr) = 0;
+    void send(std::string message, send_t handler = nullptr);
 };
 
 /**
- * \brief Implementation for Boost.Asio sockets.
- *
- * To use this class, derive from it and implement the connect function.
+ * \brief Clear TCP connection
  */
-template <typename Socket>
-class basic_connection : public connection {
-protected:
-    Socket socket_;
-
+class ip_connection : public connection {
 private:
-    using buffer_t = boost::asio::streambuf;
-    using input_t = std::deque<recv_t>;
-    using output_t = std::deque<std::pair<std::string, send_t>>;
+    boost::asio::ip::tcp::socket socket_;
+    boost::asio::ip::tcp::resolver resolver_;
+
+protected:
+    /**
+     * \copydoc connection::do_connect
+     */
+    void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override;
 
-    buffer_t buffer_;
-    input_t input_;
-    output_t output_;
+    /**
+     * \copydoc connection::do_recv
+     */
+    void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override;
 
-    void rflush();
-    void sflush();
-    void do_recv(recv_t);
-    void do_send(const std::string&, send_t);
+    /**
+     * \copydoc connection::do_send
+     */
+    void do_send(const std::string& data, send_t handler) noexcept override;
 
 public:
     /**
      * Constructor.
      *
-     * \param args the arguments to pass to the socket
+     * \param service the io service
      */
-    template <typename... Args>
-    inline basic_connection(Args&&... args)
-        : socket_(std::forward<Args>(args)...)
+    inline ip_connection(boost::asio::io_service& service) noexcept
+        : socket_(service)
+        , resolver_(service)
     {
     }
-
-    /**
-     * \copydoc connection::recv
-     */
-    void recv(recv_t handler) override;
-
-    /**
-     * \copydoc connection::send
-     */
-    void send(std::string message, send_t handler = nullptr) override;
 };
 
-template <typename Socket>
-void basic_connection<Socket>::rflush()
-{
-    if (input_.empty())
-        return;
-
-    do_recv([this] (auto code, auto message) {
-        input_.front()(code, std::move(message));
-        input_.pop_front();
-
-        if (!code)
-            rflush();
-    });
-}
+#if defined(HAVE_SSL)
 
-template <typename Socket>
-void basic_connection<Socket>::sflush()
-{
-    if (output_.empty())
-        return;
-
-    do_send(output_.front().first, [this] (auto code) {
-        if (output_.front().second)
-            output_.front().second(code);
-
-        output_.pop_front();
-
-        if (!code)
-            sflush();
-    });
-}
+/**
+ * \brief SSL connection
+ */
+class tls_connection : public connection {
+private:
+    boost::asio::ssl::context context_;
+    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
+    boost::asio::ip::tcp::resolver resolver_;
 
-template <typename Socket>
-void basic_connection<Socket>::do_recv(recv_t handler)
-{
-    boost::asio::async_read_until(socket_, buffer_, "\r\n", [this, handler] (auto code, auto xfer) {
-        if (code || xfer == 0U)
-            handler(std::move(code), message());
-        else {
-            std::string str(
-                boost::asio::buffers_begin(buffer_.data()),
-                boost::asio::buffers_begin(buffer_.data()) + xfer - 2
-            );
-
-            buffer_.consume(xfer);
-            handler(std::move(code), message::parse(str));
-        }
-    });
-}
+protected:
+    /**
+     * \copydoc connection::do_connect
+     */
+    void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override;
 
-template <typename Socket>
-void basic_connection<Socket>::do_send(const std::string& message, send_t handler)
-{
-    boost::asio::async_write(socket_, boost::asio::buffer(message), [handler, message] (auto code, auto) {
-        // TODO: xfer
-        handler(code);
-    });
-}
-
-template <typename Socket>
-void basic_connection<Socket>::recv(recv_t handler)
-{
-    auto in_progress = !input_.empty();
-
-    input_.push_back(std::move(handler));
+    /**
+     * \copydoc connection::do_recv
+     */
+    void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override;
 
-    if (!in_progress)
-        rflush();
-}
+    /**
+     * \copydoc connection::do_send
+     */
+    void do_send(const std::string& data, send_t handler) noexcept override;
 
-template <typename Socket>
-void basic_connection<Socket>::send(std::string message, send_t handler)
-{
-    auto in_progress = !output_.empty();
-
-    output_.emplace_back(std::move(message + "\r\n"), std::move(handler));
-
-    if (!in_progress)
-        sflush();
-}
-
-class ip_connection : public basic_connection<boost::asio::ip::tcp::socket> {
 public:
     /**
-     * Inherited constructors.
+     * Constructor.
+     *
+     * \param service the io service
      */
-    using basic_connection::basic_connection;
+    inline tls_connection(boost::asio::io_service& service) noexcept
+        : context_(boost::asio::ssl::context::sslv23)
+        , socket_(service, context_)
+        , resolver_(service)
+    {
+    }
+};
 
-    /**
-     * \copydoc basic_connection::connect
-     */
-    void connect(const std::string& host, const std::string& service, connect_t handler) override;
-};
+#endif // !HAVE_SSL
 
 } // !irc
 
--- a/libirccd/irccd/server.cpp	Sun Nov 26 09:02:09 2017 +0100
+++ b/libirccd/irccd/server.cpp	Sun Nov 26 12:01:28 2017 +0100
@@ -534,7 +534,7 @@
 
     if (flags_ & ssl) {
 #if defined(HAVE_SSL)
-        //conn_ = std::make_unique<irc::tls_connection>(service_);
+        conn_ = std::make_unique<irc::tls_connection>(service_);
 #else
         /*
          * If SSL is not compiled in, the caller is responsible of not setting
--- a/tests/CMakeLists.txt	Sun Nov 26 09:02:09 2017 +0100
+++ b/tests/CMakeLists.txt	Sun Nov 26 12:01:28 2017 +0100
@@ -34,7 +34,7 @@
     add_subdirectory(cmd-rule-remove)
     add_subdirectory(cmd-server-cmode)
     add_subdirectory(cmd-server-cnotice)
-    #add_subdirectory(cmd-server-connect)
+    add_subdirectory(cmd-server-connect)
     add_subdirectory(cmd-server-disconnect)
     add_subdirectory(cmd-server-info)
     add_subdirectory(cmd-server-invite)