From ac3802c0f99df1a4a8fb53ab9b4b5b48b3f66972 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sat, 6 Jan 2018 21:34:02 -0800 Subject: [PATCH] ircd::net: Refine network subsystem; break up interfaces; various. --- include/ircd/net/README.md | 51 +++ include/ircd/net/close.h | 75 ++++ include/ircd/net/net.h | 23 +- include/ircd/net/open.h | 117 ++++++ include/ircd/net/read.h | 78 ++++ include/ircd/net/socket.h | 241 +++++------- include/ircd/net/sockopts.h | 70 ++++ include/ircd/net/sockpub.h | 187 --------- include/ircd/net/write.h | 100 +++++ ircd/net.cc | 743 +++++++++++++++++++++--------------- 10 files changed, 1050 insertions(+), 635 deletions(-) create mode 100644 include/ircd/net/README.md create mode 100644 include/ircd/net/close.h create mode 100644 include/ircd/net/open.h create mode 100644 include/ircd/net/read.h create mode 100644 include/ircd/net/sockopts.h delete mode 100644 include/ircd/net/sockpub.h create mode 100644 include/ircd/net/write.h diff --git a/include/ircd/net/README.md b/include/ircd/net/README.md new file mode 100644 index 000000000..c348cee02 --- /dev/null +++ b/include/ircd/net/README.md @@ -0,0 +1,51 @@ +## IRCd Networking + +#### open() + +The open() call combines DNS resolution, TCP connecting and SSL handshaking. +A dedicated options structure is provided to tweak the specifics of the process. + +#### close() + +Like the open() sequence, the close() sequence composes a complex of +multiple operations to close_notify the SSL session with cryptographic +soundness and then disconnect the TCP session. A dedicated options +structure is provided to tweak details. + +#### read() + +To keep things simple, this system has no notion of non-blocking or even +asynchronous reads. In other words, you call read() when you know there +is something to be read(). If there is nothing your ircd::ctx will yield +until the call is interrupted/canceled by some timeout etc (which is +something you might want too in certain contexts). + +There are two possibilities: either the remote is faster than us or they +are slower than us. + +* If the remote is faster than us, or faster than we want: we'll know data +is available but ignore it to slow them down (by not calling read()) -- +a matter in which we may not have a choice anyway. + +* If the remote is slower than us: you have to use a timer to put a limit +on how much time (and resource) is going to be spent on receiving their +data; eventually the daemon has to move on to serving other clients. + +#### write() + +This system uses two different techniques for sending data to remotes +intended for two different categories of transmission: + +* write_all() is an intuitive synchronous send which yields the ircd::ctx +until all bytes have been written to the remote. This is intended for +serving simple requests or most other ctx-per-client models. A coarse timer +is generally used to prevent the remote from being too slow to receive but +we have some tolerance to wait a little for them. + +* write_any()/write_one() is a non-blocking send intended for small message +mass-push models or some other custom flow control on larger messages. The +goal here is to figure out what to do when the socket buffer has filled +up because the remote has been too slow to receive data. The choice is +usually to either propagate the slowdown to the source or to drop the +remote so the daemon can move on without using up memory. + diff --git a/include/ircd/net/close.h b/include/ircd/net/close.h new file mode 100644 index 000000000..a19ac97d9 --- /dev/null +++ b/include/ircd/net/close.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 Charybdis Development Team + * Copyright (C) 2017 Jason Volk + * + * 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 is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_NET_CLOSE_H + +namespace ircd::net +{ + enum class dc; + struct close_opts extern const close_opts_default; + using close_callback = std::function; + + // Callback-based closer. + void close(socket &, const close_opts &, close_callback); + + // Future-based closer. + ctx::future close(socket &, const close_opts & = close_opts_default); + + // Fire-and-forget helper callback for close(). + extern const close_callback close_ignore; +} + +/// Types of disconnection. SSL_NOTIFY is the recommended type of +/// disconnection and is usually default. RST is an immediate +/// alternative which has no asynchronous operations. +enum class ircd::net::dc +{ + RST, ///< hardest immediate termination + FIN, ///< sd graceful shutdown both directions + FIN_SEND, ///< sd graceful shutdown send side + FIN_RECV, ///< sd graceful shutdown recv side + SSL_NOTIFY, ///< SSL close_notify +}; + +/// Close options structure. +struct ircd::net::close_opts +{ + close_opts() = default; + close_opts(const net::dc &); + + /// The type of close() to be conducted is specified here. + net::dc type { dc::SSL_NOTIFY }; + + /// The coarse duration allowed for the close() process. + milliseconds timeout { 5000ms }; + + /// If specified, these socket options will be applied when conducting + /// the disconnect (useful for adding an SO_LINGER time etc). + const sockopts *sopts { nullptr }; +}; + +/// Allows for implicit construction of close_opts in arguments to close() +/// without requiring brackets for the close_opts& argument. +inline +ircd::net::close_opts::close_opts(const net::dc &type) +:type{type} +{} diff --git a/include/ircd/net/net.h b/include/ircd/net/net.h index 222ed86bf..42ed94c3c 100644 --- a/include/ircd/net/net.h +++ b/include/ircd/net/net.h @@ -40,6 +40,7 @@ namespace ircd::net IRCD_EXCEPTION(error, inauthentic) struct init; + struct socket; // SNOMASK 'N' "net" extern struct log::log log; @@ -48,7 +49,27 @@ namespace ircd::net #include "remote.h" #include "resolve.h" #include "listener.h" -#include "sockpub.h" +#include "sockopts.h" +#include "open.h" +#include "close.h" +#include "read.h" +#include "write.h" + +namespace ircd::net +{ + bool connected(const socket &) noexcept; + size_t readable(const socket &); + size_t available(const socket &) noexcept; + ipport local_ipport(const socket &) noexcept; + ipport remote_ipport(const socket &) noexcept; + const_raw_buffer peer_cert_der(const mutable_raw_buffer &, const socket &); +} + +// Exports to ircd:: +namespace ircd +{ + using net::socket; +} struct ircd::net::init { diff --git a/include/ircd/net/open.h b/include/ircd/net/open.h new file mode 100644 index 000000000..41b7ffc32 --- /dev/null +++ b/include/ircd/net/open.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 Charybdis Development Team + * Copyright (C) 2017 Jason Volk + * + * 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 is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_NET_OPEN_H + +namespace ircd::net +{ + struct open_opts; + using open_callback = std::function; + + // Open existing socket with callback. + void open(socket &, const open_opts &, open_callback); + + // Open new socket with callback. + std::shared_ptr open(const open_opts &, open_callback); + + // Open new socket with future. + ctx::future> open(const open_opts &); +} + +/// Connection options structure. This is provided when making a client +/// connection with a socket. Unless otherwise noted it usually has to +/// remain in scope as a const reference for the duration of that process. +/// Some of its members are also thin and will have to remain in scope along +/// with it. +struct ircd::net::open_opts +{ + open_opts() = default; + open_opts(const net::ipport &ipport); + open_opts(const net::hostport &hostport); + explicit open_opts(const net::remote &remote); + + /// Remote's hostname and port. This will be used for address resolution + /// if an ipport is not also provided later. The hostname will also be used + /// for certificate /CN verification if common_name is not provided later. + net::hostport hostport; + + /// Remote's resolved IP and port. Providing this skips DNS resolution if + /// hostport is not given; required if so. + net::ipport ipport; + + /// The duration allowed for the TCP connection. + milliseconds connect_timeout { 8000ms }; + + /// Pointer to a sockopts structure which will be applied to this socket + /// if given. Defaults to null; no application is made. + const sockopts *sopts { nullptr }; + + /// Option to toggle whether to perform the SSL handshake; you want true. + bool handshake { true }; + + /// The duration allowed for the SSL handshake + milliseconds handshake_timeout { 8000ms }; + + /// Option to toggle whether to perform any certificate verification; if + /// false, everything no matter what is considered valid; you want true. + bool verify_certificate { true }; + + /// Option to toggle whether to perform CN verification to ensure the + /// certificate is signed to the actual host we want to talk to. When + /// true, see the comments for `common_name`. Otherwise if false, any + /// common_name will pass muster. + bool verify_common_name { true }; + + /// The expected /CN of the target. This should be the remote's hostname, + /// If it is empty then `hostport.host` is used. If the signed /CN has + /// some rfc2818/rfc2459 wildcard we will properly match that for you. + string_view common_name; + + /// Option to toggle whether to allow self-signed certificates. This + /// currently defaults to true to not break Matrix development but will + /// likely change later and require setting to true for specific conns. + bool allow_self_signed { true }; + + /// Option to toggle whether to allow self-signed certificate authorities + /// in the chain. This is what corporate network nanny's may use to spy. + bool allow_self_chain { false }; +}; + +/// Constructor intended to provide implicit conversions (no-brackets required) +/// in arguments to open(); i.e open(hostport) rather than open({hostport}); +inline +ircd::net::open_opts::open_opts(const net::hostport &hostport) +:hostport{hostport} +{} + +/// Constructor intended to provide implicit conversions (no-brackets required) +/// in arguments to open(); i.e open(ipport) rather than open({ipport}); +inline +ircd::net::open_opts::open_opts(const net::ipport &ipport) +:ipport{ipport} +{} + +inline +ircd::net::open_opts::open_opts(const net::remote &remote) +:hostport{remote.hostname} +,ipport{remote} +{} diff --git a/include/ircd/net/read.h b/include/ircd/net/read.h new file mode 100644 index 000000000..bcb9bfef3 --- /dev/null +++ b/include/ircd/net/read.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 Charybdis Development Team + * Copyright (C) 2017 Jason Volk + * + * 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 is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_NET_READ_H + +namespace ircd::net +{ + // Yields until something is read into buffers. + size_t read_any(socket &, const vector_view &); + size_t read_any(socket &, const mutable_buffer &); + + // Yields until buffers are entirely full. + size_t read_all(socket &, const vector_view &); + size_t read_all(socket &, const mutable_buffer &); + + // Alias to read_any(); + size_t read(socket &, const vector_view &); + size_t read(socket &, const mutable_buffer &); +} + +/// Alias to read_any(); +inline size_t +ircd::net::read(socket &socket, + const mutable_buffer &buffer) +{ + return read_any(socket, buffer); +} + +/// Alias to read_any(); +inline size_t +ircd::net::read(socket &socket, + const vector_view &buffers) +{ + return read_any(socket, buffers); +} + +inline size_t +ircd::net::read_all(socket &socket, + const mutable_buffer &buffer) +{ + const mutable_buffer buffers[] + { + buffer + }; + + return read_all(socket, buffers); +} + +inline size_t +ircd::net::read_any(socket &socket, + const mutable_buffer &buffer) +{ + const mutable_buffer buffers[] + { + buffer + }; + + return read_any(socket, buffers); +} diff --git a/include/ircd/net/socket.h b/include/ircd/net/socket.h index 3031b3429..7d74dbf46 100644 --- a/include/ircd/net/socket.h +++ b/include/ircd/net/socket.h @@ -48,8 +48,8 @@ struct ircd::net::socket using wait_type = ip::tcp::socket::wait_type; using message_flags = asio::socket_base::message_flags; using handshake_type = asio::ssl::stream::handshake_type; - using handler = std::function; - using xfer_handler = std::function; + using ec_handler = std::function; + using eptr_handler = std::function; struct stat { @@ -63,12 +63,14 @@ struct ircd::net::socket stat in, out; bool timedout {false}; - void call_user(const handler &, const error_code &ec) noexcept; - bool handle_verify(bool, asio::ssl::verify_context &, const connopts &) noexcept; - void handle_handshake(std::weak_ptr wp, const connopts &, handler, const error_code &ec) noexcept; - void handle_connect(std::weak_ptr wp, const connopts &, handler, const error_code &ec) noexcept; - void handle_timeout(std::weak_ptr wp, const error_code &ec) noexcept; - void handle(std::weak_ptr, handler, const error_code &) noexcept; + void call_user(const eptr_handler &, const error_code &) noexcept; + void call_user(const ec_handler &, const error_code &) noexcept; + bool handle_verify(bool, asio::ssl::verify_context &, const open_opts &) noexcept; + void handle_disconnect(std::shared_ptr, eptr_handler, const error_code &) noexcept; + void handle_handshake(std::weak_ptr, const open_opts &, eptr_handler, const error_code &) noexcept; + void handle_connect(std::weak_ptr, const open_opts &, eptr_handler, const error_code &) noexcept; + void handle_timeout(std::weak_ptr, const error_code &) noexcept; + void handle(std::weak_ptr, ec_handler, const error_code &) noexcept; public: operator const ip::tcp::socket &() const { return sd; } @@ -79,33 +81,29 @@ struct ircd::net::socket endpoint remote() const; // getpeername(); throws if not conn endpoint local() const; // getsockname(); throws if not conn/bound - // low level read suite - template auto read_some(const iov &, xfer_handler); - template auto read_some(const iov &); - template auto read(const iov &, xfer_handler); - template auto read(const iov &); - - // low level write suite - template auto write_some(const iov &, xfer_handler); - template auto write_some(const iov &); - template auto write(const iov &, xfer_handler); - template auto write(const iov &); - // Timer for this socket bool has_timeout() const noexcept; - void set_timeout(const milliseconds &, handler); + void set_timeout(const milliseconds &, ec_handler); void set_timeout(const milliseconds &); milliseconds cancel_timeout() noexcept; + // low level write suite + template size_t write_one(iov&&); + template size_t write_any(iov&&); + template size_t write_all(iov&&); + + // low level read suite + template size_t read_any(iov&&); + template size_t read_all(iov&&); + // Asynchronous callback when socket ready - void operator()(const wait_type &, const milliseconds &timeout, handler); - void operator()(const wait_type &, handler); + void operator()(const wait_type &, const milliseconds &timeout, ec_handler); + void operator()(const wait_type &, ec_handler); bool cancel() noexcept; - void handshake(const connopts &, handler callback); - void connect(const endpoint &, const connopts &, handler callback); - - bool disconnect(const dc &type); + void disconnect(const close_opts &, eptr_handler); + void handshake(const open_opts &, eptr_handler); + void connect(const endpoint &, const open_opts &, eptr_handler); socket(asio::ssl::context &ssl = sslv23_client, boost::asio::io_service *const &ios = ircd::ios); @@ -124,7 +122,7 @@ class ircd::net::socket::scope_timeout bool cancel() noexcept; // invoke timer.cancel() before dtor bool release(); // cancels the cancel; - scope_timeout(socket &, const milliseconds &timeout, socket::handler handler); + scope_timeout(socket &, const milliseconds &timeout, socket::ec_handler handler); scope_timeout(socket &, const milliseconds &timeout); scope_timeout() = default; scope_timeout(scope_timeout &&) noexcept; @@ -134,135 +132,106 @@ class ircd::net::socket::scope_timeout ~scope_timeout() noexcept; }; -class ircd::net::socket::io -{ - struct socket &sock; - struct stat &stat; - size_t bytes; - - public: - operator size_t() const; - - io(struct socket &, struct stat &, const size_t &bytes); - io(struct socket &, struct stat &, const std::function &closure); -}; - -struct ircd::net::socket::xfer -{ - std::exception_ptr eptr; - size_t bytes {0}; - - xfer(const error_code &ec = {}, const size_t &bytes = 0); -}; - +/// Yields ircd::ctx until buffers are full. template -auto -ircd::net::socket::write(const iov &bufs) +size_t +ircd::net::socket::read_all(iov&& bufs) { - return io{*this, out, [&] + static const auto completion { - return async_write(ssl, bufs, asio::transfer_all(), yield_context{to_asio{}}); - }}; -} + asio::transfer_all() + }; -template -auto -ircd::net::socket::write(const iov &bufs, - xfer_handler handler) -{ - async_write(ssl, bufs, asio::transfer_all(), [this, handler(std::move(handler))] - (const error_code &ec, const size_t &bytes) - noexcept + const size_t ret { - io{*this, out, bytes}; - handler(xfer{ec, bytes}); - }); -} + asio::async_read(ssl, std::forward(bufs), completion, yield_context{to_asio{}}) + }; -template -auto -ircd::net::socket::write_some(const iov &bufs) -{ - return io{*this, out, [&] - { - return ssl.async_write_some(bufs, yield_context{to_asio{}}); - }}; -} - -template -auto -ircd::net::socket::write_some(const iov &bufs, - xfer_handler handler) -{ - ssl.async_write_some(bufs, [this, handler(std::move(handler))] - (const error_code &ec, const size_t &bytes) - noexcept - { - io{*this, out, bytes}; - handler(xfer{ec, bytes}); - }); -} - -template -auto -ircd::net::socket::read(const iov &bufs) -{ - return io{*this, in, [&] - { - const size_t ret + if(!ret) + throw boost::system::system_error { - async_read(ssl, bufs, yield_context{to_asio{}}) + boost::asio::error::eof }; - if(unlikely(!ret)) - throw boost::system::system_error(boost::asio::error::eof); - - return ret; - }}; + in.bytes += ret; + ++in.calls; + return ret; } +/// Yields ircd::ctx until remote has sent at least some data. template -auto -ircd::net::socket::read(const iov &bufs, - xfer_handler handler) +size_t +ircd::net::socket::read_any(iov&& bufs) { - async_read(ssl, bufs, [this, handler(std::move(handler))] - (const error_code &ec, const size_t &bytes) - noexcept + const size_t ret { - io{*this, in, bytes}; - handler(xfer{ec, bytes}); - }); -} + ssl.async_read_some(std::forward(bufs), yield_context{to_asio{}}) + }; -template -auto -ircd::net::socket::read_some(const iov &bufs) -{ - return io{*this, in, [&] - { - const size_t ret + if(!ret) + throw boost::system::system_error { - ssl.async_read_some(bufs, yield_context{to_asio{}}) + boost::asio::error::eof }; - if(unlikely(!ret)) - throw boost::system::system_error(boost::asio::error::eof); - - return ret; - }}; + in.bytes += ret; + ++in.calls; + return ret; } +/// Yields ircd::ctx until all buffers are sent. template -auto -ircd::net::socket::read_some(const iov &bufs, - xfer_handler handler) +size_t +ircd::net::socket::write_all(iov&& bufs) { - ssl.async_read_some(bufs, [this, handler(std::move(handler))] - (const error_code &ec, const size_t &bytes) - noexcept + static const auto completion { - io{*this, in, bytes}; - handler(xfer{ec, bytes}); - }); + asio::transfer_all() + }; + + const size_t ret + { + asio::async_write(ssl, std::forward(bufs), completion, yield_context{to_asio{}}) + }; + + out.bytes += ret; + ++out.calls; + return ret; +} + +/// Non-blocking; writes as much as possible by with multiple write_one()'s +template +size_t +ircd::net::socket::write_any(iov&& bufs) +{ + static const auto completion + { + asio::transfer_all() + }; + + assert(!blocking(*this)); + const size_t ret + { + asio::write(ssl, std::forward(bufs), completion) + }; + + out.bytes += ret; + ++out.calls; + return ret; +} + +/// Non-blocking; Writes one "unit" of data or less; never more. +template +size_t +ircd::net::socket::write_one(iov&& bufs) +{ + assert(!blocking(*this)); + const size_t ret + { + ssl.write_some(std::forward(bufs)) + }; + + out.bytes += ret; + ++out.calls; + return ret; } diff --git a/include/ircd/net/sockopts.h b/include/ircd/net/sockopts.h new file mode 100644 index 000000000..990093e7b --- /dev/null +++ b/include/ircd/net/sockopts.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 Charybdis Development Team + * Copyright (C) 2017 Jason Volk + * + * 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 is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_NET_SOCKOPTS_H + +namespace ircd::net +{ + struct sockopts; + + bool blocking(const socket &); + bool nodelay(const socket &); + bool keepalive(const socket &); + time_t linger(const socket &); + size_t read_bufsz(const socket &); + size_t write_bufsz(const socket &); + size_t read_lowat(const socket &); + size_t write_lowat(const socket &); + + void blocking(socket &, const bool &); + void nodelay(socket &, const bool &); + void keepalive(socket &, const bool &); + void linger(socket &, const time_t &); // -1 is OFF; >= 0 is ON + void read_bufsz(socket &, const size_t &bytes); + void write_bufsz(socket &, const size_t &bytes); + void read_lowat(socket &, const size_t &bytes); + void write_lowat(socket &, const size_t &bytes); + + void set(socket &, const sockopts &); +} + +/// Socket options convenience aggregate. This structure allows observation +/// or manipulation of socket options all together. Pass an active socket to +/// the constructor to observe all options. Use net::set(socket, sockopts) to +/// set all non-ignored options. +struct ircd::net::sockopts +{ + /// Magic value to not set this option on a set() pass. + static constexpr int8_t IGN { std::numeric_limits::min() }; + + int8_t blocking { IGN }; // Simulates blocking behavior + int8_t nodelay { IGN }; + int8_t keepalive { IGN }; + time_t linger { IGN }; // -1 is OFF; >= 0 is ON + ssize_t read_bufsz { IGN }; + ssize_t write_bufsz { IGN }; + ssize_t read_lowat { IGN }; + ssize_t write_lowat { IGN }; + + sockopts(const socket &); // Get options from socket + sockopts() = default; +}; diff --git a/include/ircd/net/sockpub.h b/include/ircd/net/sockpub.h deleted file mode 100644 index eac56d001..000000000 --- a/include/ircd/net/sockpub.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2017 Charybdis Development Team - * Copyright (C) 2017 Jason Volk - * - * 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 is present in all copies. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once -#define HAVE_IRCD_NET_SOCKPUB_H - -// This is the public interface to net::socket because socket.h is not part of -// the standard include group as it directly involves boost headers. For -// direct access you may include in your definition file if -// absolutely necessary. -// -// Any operation on the socket can trigger a pending error (i.e disconnection -// userspace doesn't know about yet) and thus make any call after related to -// the socket invalid and throw. We use noexcept here when there is a -// reasonable default value returned instead of throwing. The goal there is to -// reduce the number of places where the stack can blow up: for example, a -// debug log call that prints the bytes available for reading. During testing -// that may be the place of first observation where an exception keeps getting -// thrown but during release that call won't be there and thus lays the -// foundation for surprise heisenbugs. - -namespace ircd::net -{ - struct socket; - struct sockopts; - struct connopts; - enum class dc; -} - -namespace ircd -{ - using net::socket; -} - -namespace ircd::net -{ - bool connected(const socket &) noexcept; - size_t readable(const socket &); - size_t available(const socket &) noexcept; - ipport local_ipport(const socket &) noexcept; - ipport remote_ipport(const socket &) noexcept; - const_raw_buffer peer_cert_der(const mutable_raw_buffer &, const socket &); - - size_t read(socket &, const ilist &); // read_all - size_t read(socket &, const iov &); // read_all - size_t read(socket &, iov &); // read_some - - size_t write(socket &, const ilist &); // write_all - size_t write(socket &, const iov &); // write_all - size_t write(socket &, iov &); // write_some - void flush(socket &); - - bool disconnect(socket &, const dc &type) noexcept; - bool disconnect(socket &) noexcept; - - void open(socket &, const connopts &, std::function); - ctx::future> open(const connopts &); -} - -/// Connection options structure. This is provided when making a client -/// connection with a socket. Unless otherwise noted it usually has to -/// remain in scope as a const reference for the duration of that process. -/// Some of its members are also thin and will have to remain in scope along -/// with it. -struct ircd::net::connopts -{ - /// Remote's hostname and port. This will be used for address resolution - /// if an ipport is not also provided later. The hostname will also be used - /// for certificate /CN verification if common_name is not provided later. - net::hostport hostport; - - /// Remote's resolved IP and port. Providing this skips DNS resolution if - /// hostport is not given; required if so. - net::ipport ipport; - - /// The duration allowed for the TCP connection. - milliseconds connect_timeout { 8000ms }; - - /// Pointer to a sockopts structure which will be applied to this socket - /// if given. Defaults to null; no application is made. - const sockopts *sopts { nullptr }; - - /// Option to toggle whether to perform the SSL handshake; you want true. - bool handshake { true }; - - /// The duration allowed for the SSL handshake - milliseconds handshake_timeout { 8000ms }; - - /// Option to toggle whether to perform any certificate verification; if - /// false, everything no matter what is considered valid; you want true. - bool verify_certificate { true }; - - /// Option to toggle whether to perform CN verification to ensure the - /// certificate is signed to the actual host we want to talk to. When - /// true, see the comments for `common_name`. Otherwise if false, any - /// common_name will pass muster. - bool verify_common_name { true }; - - /// The expected /CN of the target. This should be the remote's hostname, - /// If it is empty then `hostport.host` is used. If the signed /CN has - /// some rfc2818/rfc2459 wildcard we will properly match that for you. - string_view common_name; - - /// Option to toggle whether to allow self-signed certificates. This - /// currently defaults to true to not break Matrix development but will - /// likely change later and require setting to true for specific conns. - bool allow_self_signed { true }; - - /// Option to toggle whether to allow self-signed certificate authorities - /// in the chain. This is what corporate network nanny's may use to spy. - bool allow_self_chain { false }; -}; - -// Socket options section -namespace ircd::net -{ - bool blocking(const socket &); - bool nodelay(const socket &); - bool keepalive(const socket &); - time_t linger(const socket &); - size_t read_bufsz(const socket &); - size_t write_bufsz(const socket &); - size_t read_lowat(const socket &); - size_t write_lowat(const socket &); - - void blocking(socket &, const bool &); - void nodelay(socket &, const bool &); - void keepalive(socket &, const bool &); - void linger(socket &, const time_t &); // -1 is OFF; >= 0 is ON - void read_bufsz(socket &, const size_t &bytes); - void write_bufsz(socket &, const size_t &bytes); - void read_lowat(socket &, const size_t &bytes); - void write_lowat(socket &, const size_t &bytes); - - void set(socket &, const sockopts &); -} - -/// Socket options convenience aggregate. This structure allows observation -/// or manipulation of socket options all together. Pass an active socket to -/// the constructor to observe all options. Use net::set(socket, sockopts) to -/// set all non-ignored options. -struct ircd::net::sockopts -{ - /// Magic value to not set this option on a set() pass. - static constexpr int8_t IGN { std::numeric_limits::min() }; - - int8_t blocking = IGN; // Simulates blocking behavior - int8_t nodelay = IGN; - int8_t keepalive = IGN; - time_t linger = IGN; // -1 is OFF; >= 0 is ON - ssize_t read_bufsz = IGN; - ssize_t write_bufsz = IGN; - ssize_t read_lowat = IGN; - ssize_t write_lowat = IGN; - - sockopts(const socket &); // Get options from socket - sockopts() = default; -}; - -/// Arguments for disconnecting. -enum class ircd::net::dc -{ - RST, ///< hardest immediate termination - FIN, ///< sd graceful shutdown both directions - FIN_SEND, ///< sd graceful shutdown send side - FIN_RECV, ///< sd graceful shutdown recv side - SSL_NOTIFY, ///< SSL close_notify (async, errors ignored) - SSL_NOTIFY_YIELD, ///< SSL close_notify (yields context, throws) -}; diff --git a/include/ircd/net/write.h b/include/ircd/net/write.h new file mode 100644 index 000000000..b6b7dfa17 --- /dev/null +++ b/include/ircd/net/write.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 Charybdis Development Team + * Copyright (C) 2017 Jason Volk + * + * 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 is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_NET_WRITE_H + +namespace ircd::net +{ + // Non-blocking; writes at most one system-determined amount of + // bytes or less with at most a single syscall. + size_t write_one(socket &, const vector_view &); + size_t write_one(socket &, const const_buffer &); + + // Non-blocking; writes as much as possible until the socket buffer + // is full. This composes multiple write_one()'s. + size_t write_any(socket &, const vector_view &); + size_t write_any(socket &, const const_buffer &); + + // Blocking; Yields your ircd::ctx until all bytes have been written; + // advise one uses a timeout in conjunction to prevent DoS. + size_t write_all(socket &, const vector_view &); + size_t write_all(socket &, const const_buffer &); + + // Alias to write_all(); + size_t write(socket &, const vector_view &); + size_t write(socket &, const const_buffer &); + + // Toggles nodelay to force a transmission. + void flush(socket &); +} + +/// Alias to write_all(); +inline size_t +ircd::net::write(socket &socket, + const const_buffer &buffer) +{ + return write_all(socket, buffer); +} + +/// Alias to write_all(); +inline size_t +ircd::net::write(socket &socket, + const vector_view &buffers) +{ + return write_all(socket, buffers); +} + +inline size_t +ircd::net::write_all(socket &socket, + const const_buffer &buffer) +{ + const const_buffer buffers[] + { + buffer + }; + + return write_all(socket, buffers); +} + +inline size_t +ircd::net::write_any(socket &socket, + const const_buffer &buffer) +{ + const const_buffer buffers[] + { + buffer + }; + + return write_all(socket, buffers); +} + +inline size_t +ircd::net::write_one(socket &socket, + const const_buffer &buffer) +{ + const const_buffer buffers[] + { + buffer + }; + + return write_one(socket, buffers); +} diff --git a/ircd/net.cc b/ircd/net.cc index 63f431a38..86eae0e3f 100644 --- a/ircd/net.cc +++ b/ircd/net.cc @@ -59,153 +59,6 @@ ircd::net::init::~init() resolve::resolver.reset(nullptr); } -/////////////////////////////////////////////////////////////////////////////// -// -// net/sockpub.h -// - -bool -ircd::net::disconnect(socket &socket) -noexcept -{ - return disconnect(socket, dc::SSL_NOTIFY); -} - -bool -ircd::net::disconnect(socket &socket, - const dc &type) -noexcept try -{ - socket.disconnect(type); - return true; -} -catch(const std::exception &e) -{ -/* - log::error("socket(%p): disconnect: type: %d: %s", - this, - int(type), - e.what()); -*/ - return false; -} - -/// Attempt to connect and ssl handshake; future. -/// -ircd::ctx::future> -ircd::net::open(const connopts &opts) -{ - ctx::promise> p; - ctx::future> f(p); - auto s{std::make_shared()}; - open(*s, opts, [s, p(std::move(p))] - (std::exception_ptr eptr) - mutable - { - if(eptr) - p.set_exception(std::move(eptr)); - else - p.set_value(s); - }); - - return f; -} - -/// Attempt to connect and ssl handshake; asynchronous, callback when done. -/// -void -ircd::net::open(socket &socket, - const connopts &opts, - std::function handler) -{ - auto complete{[&socket, &opts, handler(std::move(handler))] - (std::exception_ptr eptr) - { - if(eptr) - disconnect(socket, dc::RST); - - handler(std::move(eptr)); - }}; - - auto connector{[&socket, &opts, complete(std::move(complete))] - (std::exception_ptr eptr, const ipport &ipport) - { - if(eptr) - return complete(std::move(eptr)); - - const auto ep{make_endpoint(ipport)}; - socket.connect(ep, opts, [&socket, complete(std::move(complete))] - (const error_code &ec) - { - complete(make_eptr(ec)); - }); - }}; - - if(!opts.ipport) - resolve(opts.hostport, std::move(connector)); - else - connector({}, opts.ipport); -} - -void -ircd::net::flush(socket &socket) -{ - if(nodelay(socket)) - return; - - nodelay(socket, true); - nodelay(socket, false); -} - -size_t -ircd::net::write(socket &socket, - iov &bufs) -{ - const size_t wrote(socket.write_some(bufs)); - const size_t consumed(consume(bufs, wrote)); - assert(wrote == consumed); - return consumed; -} - -size_t -ircd::net::write(socket &socket, - const iov &bufs) -{ - const size_t wrote(socket.write(bufs)); - assert(wrote == size(bufs)); - return wrote; -} - -size_t -ircd::net::write(socket &socket, - const ilist &bufs) -{ - const size_t wrote(socket.write(bufs)); - assert(wrote == size(bufs)); - return wrote; -} - -size_t -ircd::net::read(socket &socket, - iov &bufs) -{ - const size_t read(socket.read_some(bufs)); - const size_t consumed(buffer::consume(bufs, read)); - assert(read == consumed); - return read; -} - -size_t -ircd::net::read(socket &socket, - const iov &bufs) -{ - return socket.read(bufs); -} - -// -// Active observers -// - ircd::const_raw_buffer ircd::net::peer_cert_der(const mutable_raw_buffer &buf, const socket &socket) @@ -269,8 +122,239 @@ catch(...) return false; } +/////////////////////////////////////////////////////////////////////////////// // -// Options +// net/write.h +// + +void +ircd::net::flush(socket &socket) +{ + if(nodelay(socket)) + return; + + nodelay(socket, true); + nodelay(socket, false); +} + +/// Yields ircd::ctx until all buffers are sent. +/// +/// This is blocking behavior; use this if the following are true: +/// +/// * You put a timer on the socket so if the remote slows us down the data +/// will not occupy the daemon's memory for a long time. Remember, *all* of +/// the data will be sitting in memory even after some of it was ack'ed by +/// the remote. +/// +/// * You are willing to dedicate the ircd::ctx to sending all the data to +/// the remote. The ircd::ctx will be yielding until everything is sent. +/// +size_t +ircd::net::write_all(socket &socket, + const vector_view &buffers) +{ + return socket.write_all(buffers); +} + +/// Writes as much as possible until one of the following is true: +/// +/// * The kernel buffer for the socket is full. +/// * The user buffer is exhausted. +/// +/// This is non-blocking behavior. No yielding will take place; no timer is +/// needed. Multiple syscalls will be composed to fulfill the above points. +/// +size_t +ircd::net::write_any(socket &socket, + const vector_view &buffers) +{ + return socket.write_any(buffers); +} + +/// Writes one "unit" of data or less; never more. The size of that unit +/// is determined by the system. Less may be written if one of the following +/// is true: +/// +/// * The kernel buffer for the socket is full. +/// * The user buffer is exhausted. +/// +/// If neither are true, more can be written using additional calls; +/// alternatively, use other variants of write_ for that. +/// +/// This is non-blocking behavior. No yielding will take place; no timer is +/// needed. Only one syscall will occur. +/// +size_t +ircd::net::write_one(socket &socket, + const vector_view &buffers) +{ + return socket.write_one(buffers); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// net/read.h +// + +/// Yields ircd::ctx until buffers are full. +/// +/// Use this only if the following are true: +/// +/// * You know the remote has made a guarantee to send you a specific amount +/// of data. +/// +/// * You put a timer on the socket so that if the remote runs short this +/// call doesn't hang the ircd::ctx forever, otherwise it will until cancel. +/// +/// * You are willing to dedicate the ircd::ctx to just this operation for +/// that amount of time. +/// +size_t +ircd::net::read_all(socket &socket, + const vector_view &buffers) +{ + return socket.read_all(buffers); +} + +/// Yields ircd::ctx until remote has sent at least one frame. The buffers may +/// be filled with any amount of data depending on what has accumulated. +/// +/// Use this if the following are true: +/// +/// * You know there is data to be read; you can do this asynchronously with +/// other features of the socket. Otherwise this will hang the ircd::ctx. +/// +/// * You are willing to dedicate the ircd::ctx to just this operation, +/// which is non-blocking if data is known to be available, but may be +/// blocking if this call is made in the blind. +/// +size_t +ircd::net::read_any(socket &socket, + const vector_view &buffers) +{ + return socket.read_any(buffers); +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// net/close.h +// + +/// Static instance of default close options. +ircd::net::close_opts +const ircd::net::close_opts_default +{ +}; + +/// Static helper callback which may be passed to the callback-based overload +/// of close(). This callback does nothing. +ircd::net::close_callback +const ircd::net::close_ignore{[] +(std::exception_ptr eptr) +{ + return; +}}; + +ircd::ctx::future +ircd::net::close(socket &socket, + const close_opts &opts) +{ + ctx::promise p; + ctx::future f(p); + close(socket, opts, [p(std::move(p))] + (std::exception_ptr eptr) + mutable + { + if(eptr) + p.set_exception(std::move(eptr)); + else + p.set_value(); + }); + + return f; +} + +void +ircd::net::close(socket &socket, + const close_opts &opts, + close_callback callback) +{ + socket.disconnect(opts, std::move(callback)); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// net/open.h +// + +/// Open new socket with future-based report. +/// +ircd::ctx::future> +ircd::net::open(const open_opts &opts) +{ + ctx::promise> p; + ctx::future> f(p); + auto s{std::make_shared()}; + open(*s, opts, [s, p(std::move(p))] + (std::exception_ptr eptr) + mutable + { + if(eptr) + p.set_exception(std::move(eptr)); + else + p.set_value(s); + }); + + return f; +} + +/// Open existing socket with callback-based report. +/// +std::shared_ptr +ircd::net::open(const open_opts &opts, + open_callback handler) +{ + auto s{std::make_shared()}; + open(*s, opts, std::move(handler)); + return s; +} + +/// Open existing socket with callback-based report. +/// +void +ircd::net::open(socket &socket, + const open_opts &opts, + open_callback handler) +{ + auto complete{[s(shared_from(socket)), &opts, handler(std::move(handler))] + (std::exception_ptr eptr) + { + if(eptr) + close(*s, dc::RST); + + handler(std::move(eptr)); + }}; + + auto connector{[&socket, &opts, complete(std::move(complete))] + (std::exception_ptr eptr, const ipport &ipport) + { + if(eptr) + return complete(std::move(eptr)); + + const auto ep{make_endpoint(ipport)}; + socket.connect(ep, opts, std::move(complete)); + }}; + + if(!opts.ipport) + resolve(opts.hostport, std::move(connector)); + else + connector({}, opts.ipport); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// net/sopts.h // /// Construct sockopts with the current options from socket argument @@ -407,6 +491,30 @@ ircd::net::nodelay(socket &socket, sd.set_option(option); } +/// Toggles the behavior of non-async asio calls. +/// +/// This option affects very little in practice and only sets a flag in +/// userspace in asio, not an actual ioctl(). Specifically: +/// +/// * All sockets are already set by asio to FIONBIO=1 no matter what, thus +/// nothing really blocks the event loop ever by default unless you try hard. +/// +/// * All asio::async_ and sd.async_ and ssl.async_ calls will always do what +/// the synchronous/blocking alternative would have accomplished but using +/// the async methodology. i.e if a buffer is full you will always wait +/// asynchronously: async_write() will wait for everything, async_write_some() +/// will wait for something, etc -- but there will never be true non-blocking +/// _effective behavior_ from these calls. +/// +/// * All asio non-async calls conduct blocking by (on linux) poll()'ing the +/// socket to get a real kernel-blocking operation out of it (this is the +/// try-hard part). +/// +/// This flag only controls the behavior of the last bullet. In practice, +/// in this project there is never a reason to ever set this to true, +/// however, sockets do get constructed by asio in blocking mode by default +/// so we mostly use this function to set it to non-blocking. +/// void ircd::net::blocking(socket &socket, const bool &b) @@ -511,11 +619,11 @@ struct ircd::net::listener::acceptor void configure(const json::object &opts); // Handshake stack - bool handshake_error(const error_code &ec, socket &); + void check_handshake_error(const error_code &ec, socket &); void handshake(const error_code &ec, std::shared_ptr, std::weak_ptr) noexcept; // Acceptance stack - bool accept_error(const error_code &ec, socket &); + void check_accept_error(const error_code &ec, socket &); void accept(const error_code &ec, std::shared_ptr, std::weak_ptr) noexcept; // Accept next @@ -716,21 +824,19 @@ noexcept try const unwind::exceptional drop{[&sock] { assert(bool(sock)); - disconnect(*sock, dc::RST); + close(*sock, dc::RST, close_ignore); }}; assert(bool(sock)); - if(unlikely(accept_error(ec, *sock))) - { - disconnect(*sock, dc::RST); - return; - } - + check_accept_error(ec, *sock); log.debug("%s: socket(%p) accepted %s", std::string(*this), sock.get(), string(sock->remote())); + // Toggles the behavior of non-async functions; see func comment + blocking(*sock, false); + static const socket::handshake_type handshake_type { socket::handshake_type::server @@ -766,9 +872,9 @@ catch(const std::exception &e) /// whether or not the handler should return or continue processing the /// result. /// -bool -ircd::net::listener::acceptor::accept_error(const error_code &ec, - socket &sock) +void +ircd::net::listener::acceptor::check_accept_error(const error_code &ec, + socket &sock) { using namespace boost::system::errc; using boost::system::system_category; @@ -777,12 +883,12 @@ ircd::net::listener::acceptor::accept_error(const error_code &ec, throw ctx::interrupted(); if(likely(ec == success)) - return false; + return; if(ec.category() == system_category()) switch(ec.value()) { case operation_canceled: - return false; + return; default: break; @@ -801,18 +907,14 @@ noexcept try return; --handshaking; - assert(bool(sock)); const unwind::exceptional drop{[&sock] { - disconnect(*sock, dc::RST); + if(bool(sock)) + close(*sock, dc::RST, close_ignore); }}; - if(unlikely(handshake_error(ec, *sock))) - { - disconnect(*sock, dc::RST); - return; - } - + assert(bool(sock)); + check_handshake_error(ec, *sock); log.debug("%s socket(%p): SSL handshook %s", std::string(*this), sock.get(), @@ -842,9 +944,9 @@ catch(const std::exception &e) /// whether or not the handler should return or continue processing the /// result. /// -bool -ircd::net::listener::acceptor::handshake_error(const error_code &ec, - socket &sock) +void +ircd::net::listener::acceptor::check_handshake_error(const error_code &ec, + socket &sock) { using boost::system::system_category; using namespace boost::system::errc; @@ -853,12 +955,12 @@ ircd::net::listener::acceptor::handshake_error(const error_code &ec, throw ctx::interrupted(); if(likely(ec == success)) - return false; + return; if(ec.category() == system_category()) switch(ec.value()) { case operation_canceled: - return false; + return; default: break; @@ -1032,8 +1134,8 @@ catch(const std::exception &e) void ircd::net::socket::connect(const endpoint &ep, - const connopts &opts, - handler callback) + const open_opts &opts, + eptr_handler callback) { log.debug("socket(%p) attempting connect to remote: %s for the next %ld$ms", this, @@ -1050,8 +1152,8 @@ ircd::net::socket::connect(const endpoint &ep, } void -ircd::net::socket::handshake(const connopts &opts, - handler callback) +ircd::net::socket::handshake(const open_opts &opts, + eptr_handler callback) { log.debug("socket(%p) performing handshake with %s for '%s' for the next %ld$ms", this, @@ -1074,101 +1176,72 @@ ircd::net::socket::handshake(const connopts &opts, set_timeout(opts.handshake_timeout); } -bool -ircd::net::socket::disconnect(const dc &type) +void +ircd::net::socket::disconnect(const close_opts &opts, + eptr_handler callback) try { - if(timer.expires_from_now() > 0ms) - timer.cancel(); - - if(sd.is_open()) - log.debug("socket(%p): disconnect: %s type:%d user: in:%zu out:%zu", - (const void *)this, - ircd::string(remote_ipport(*this)), - uint(type), - in.bytes, - out.bytes); - - if(sd.is_open()) switch(type) + if(!sd.is_open()) + { + call_user(callback, {}); + return; + } + + const bool cancelation{cancel()}; + log.debug("socket(%p): disconnect: %s type:%d user: in:%zu out:%zu cancel:%d", + (const void *)this, + ircd::string(remote_ipport(*this)), + uint(opts.type), + in.bytes, + out.bytes, + cancelation); + + if(opts.sopts) + set(*this, *opts.sopts); + + switch(opts.type) { - default: case dc::RST: sd.close(); - return true; + break; case dc::FIN: sd.shutdown(ip::tcp::socket::shutdown_both); - return true; + break; case dc::FIN_SEND: sd.shutdown(ip::tcp::socket::shutdown_send); - return true; + break; case dc::FIN_RECV: sd.shutdown(ip::tcp::socket::shutdown_receive); - return true; - - case dc::SSL_NOTIFY_YIELD: if(likely(ctx::current)) - { - const life_guard lg{*this}; - const scope_timeout ts{*this, 8s}; - ssl.async_shutdown(yield_context{to_asio{}}); - error_code ec; - sd.close(ec); - if(ec) - log.error("socket(%p): close: %s: %s", - this, - string(ec)); - return true; - } + break; case dc::SSL_NOTIFY: { - ssl.async_shutdown([s(shared_from_this())] - (error_code ec) - noexcept + auto disconnect_handler { - if(!s->timedout) - s->cancel_timeout(); + std::bind(&socket::handle_disconnect, this, shared_from(*this), std::move(callback), ph::_1) + }; - if(ec) - log.warning("socket(%p): SSL_NOTIFY: %s: %s", - s.get(), - string(ec)); - - if(!s->sd.is_open()) - return; - - s->sd.close(ec); - - if(ec) - log.warning("socket(%p): after SSL_NOTIFY: %s: %s", - s.get(), - string(ec)); - }); - set_timeout(8s); - return true; + ssl.async_shutdown(std::move(disconnect_handler)); + set_timeout(opts.timeout); + return; } } - else return false; + + call_user(callback, {}); } catch(const boost::system::system_error &e) { - log.warning("socket(%p): disconnect: type: %d: %s", - (const void *)this, - uint(type), - e.what()); - - if(sd.is_open()) - { - boost::system::error_code ec; - sd.close(ec); - if(ec) - log.warning("socket(%p): after disconnect: %s: %s", - this, - string(ec)); - } - + call_user(callback, e.code()); +} +catch(const std::exception &e) +{ + log.critical("socket(%p): disconnect: type: %d: %s", + (const void *)this, + uint(opts.type), + e.what()); throw; } @@ -1193,7 +1266,7 @@ noexcept /// void ircd::net::socket::operator()(const wait_type &type, - handler h) + ec_handler h) { operator()(type, milliseconds(-1), std::move(h)); } @@ -1206,7 +1279,7 @@ ircd::net::socket::operator()(const wait_type &type, void ircd::net::socket::operator()(const wait_type &type, const milliseconds &timeout, - handler callback) + ec_handler callback) { auto handle { @@ -1242,7 +1315,7 @@ ircd::net::socket::operator()(const wait_type &type, void ircd::net::socket::handle(const std::weak_ptr wp, - const handler callback, + const ec_handler callback, const error_code &ec) noexcept try { @@ -1279,6 +1352,15 @@ noexcept try call_user(callback, ec); } +catch(const boost::system::system_error &e) +{ + log.error("socket(%p): handle: %s", + this, + e.what()); + + assert(0); + call_user(callback, e.code()); +} catch(const std::bad_weak_ptr &e) { // This handler may still be registered with asio after the socket destructs, so @@ -1287,14 +1369,18 @@ catch(const std::bad_weak_ptr &e) log.warning("socket(%p): belated callback to handler... (%s)", this, e.what()); + assert(0); + call_user(callback, ec); } catch(const std::exception &e) { log.critical("socket(%p): handle: %s", this, e.what()); + assert(0); + call_user(callback, ec); } void @@ -1345,8 +1431,8 @@ catch(const std::exception &e) void ircd::net::socket::handle_connect(std::weak_ptr wp, - const connopts &opts, - handler callback, + const open_opts &opts, + eptr_handler callback, const error_code &ec) noexcept try { @@ -1366,16 +1452,13 @@ noexcept try if(ec) return call_user(callback, ec); - // Try to set the user's socket options now; if something fails - // we can invoke their callback with the error. - if(opts.sopts) try - { + // Toggles the behavior of non-async functions; see func comment + blocking(*this, false); + + // Try to set the user's socket options now; if something fails we can + // invoke their callback with the error from the exception handler. + if(opts.sopts) set(*this, *opts.sopts); - } - catch(const boost::system::system_error &e) - { - return call_user(callback, e.code()); - } // The user can opt out of performing the handshake here. if(!opts.handshake) @@ -1388,20 +1471,74 @@ catch(const std::bad_weak_ptr &e) log.warning("socket(%p): belated callback to handle_connect... (%s)", this, e.what()); + assert(0); + call_user(callback, ec); +} +catch(const boost::system::system_error &e) +{ + log.error("socket(%p): after connect: %s", + this, + e.what()); + + assert(0); + call_user(callback, e.code()); } catch(const std::exception &e) { log.critical("socket(%p): handle_connect: %s", this, e.what()); + assert(0); + call_user(callback, ec); +} + +void +ircd::net::socket::handle_disconnect(std::shared_ptr s, + eptr_handler callback, + const error_code &ec) +noexcept try +{ + assert(!timedout || ec == boost::system::errc::operation_canceled); + log.debug("socket(%p) disconnect from local: %s to remote: %s: %s", + this, + string(local_ipport(*this)), + string(remote_ipport(*this)), + string(ec)); + + // The timer was set by socket::disconnect() and may need to be canceled. + if(!timedout) + cancel_timeout(); + + if(unlikely(sd.is_open())) + sd.close(); + + call_user(callback, ec); +} +catch(const boost::system::system_error &e) +{ + log.error("socket(%p): disconnect: %s", + this, + e.what()); + + assert(0); + call_user(callback, e.code()); +} +catch(const std::exception &e) +{ + log.critical("socket(%p): disconnect: %s", + this, + e.what()); + + assert(0); + call_user(callback, ec); } void ircd::net::socket::handle_handshake(std::weak_ptr wp, - const connopts &opts, - handler callback, + const open_opts &opts, + eptr_handler callback, const error_code &ec) noexcept try { @@ -1421,12 +1558,22 @@ noexcept try // back with or without error here. call_user(callback, ec); } +catch(const boost::system::system_error &e) +{ + log.error("socket(%p): after handshake: %s", + this, + e.what()); + + assert(0); + call_user(callback, e.code()); +} catch(const std::bad_weak_ptr &e) { log.warning("socket(%p): belated callback to handle_handshake... (%s)", this, e.what()); assert(0); + call_user(callback, ec); } catch(const std::exception &e) { @@ -1434,12 +1581,13 @@ catch(const std::exception &e) this, e.what()); assert(0); + call_user(callback, ec); } bool ircd::net::socket::handle_verify(const bool valid, asio::ssl::verify_context &vc, - const connopts &opts) + const open_opts &opts) noexcept try { // `valid` indicates whether or not there's an anomaly with the @@ -1546,7 +1694,7 @@ catch(const std::exception &e) } void -ircd::net::socket::call_user(const handler &callback, +ircd::net::socket::call_user(const ec_handler &callback, const error_code &ec) noexcept try { @@ -1559,6 +1707,20 @@ catch(const std::exception &e) e.what()); } +void +ircd::net::socket::call_user(const eptr_handler &callback, + const error_code &ec) +noexcept try +{ + callback(make_eptr(ec)); +} +catch(const std::exception &e) +{ + log.critical("socket(%p): async handler: unhandled exception: %s", + this, + e.what()); +} + boost::asio::ip::tcp::endpoint ircd::net::socket::local() const @@ -1602,7 +1764,7 @@ ircd::net::socket::set_timeout(const milliseconds &t) void ircd::net::socket::set_timeout(const milliseconds &t, - handler h) + ec_handler h) { cancel_timeout(); if(t < milliseconds(0)) @@ -1636,36 +1798,6 @@ const return *ssl.native_handle(); } -// -// socket::io -// - -ircd::net::socket::io::io(struct socket &sock, - struct stat &stat, - const std::function &closure) -:io -{ - sock, stat, closure() -} -{} - -ircd::net::socket::io::io(struct socket &sock, - struct stat &stat, - const size_t &bytes) -:sock{sock} -,stat{stat} -,bytes{bytes} -{ - stat.bytes += bytes; - stat.calls++; -} - -ircd::net::socket::io::operator size_t() -const -{ - return bytes; -} - // // socket::scope_timeout // @@ -1679,7 +1811,7 @@ ircd::net::socket::scope_timeout::scope_timeout(socket &socket, ircd::net::socket::scope_timeout::scope_timeout(socket &socket, const milliseconds &timeout, - socket::handler handler) + socket::ec_handler handler) :s{&socket} { socket.set_timeout(timeout, std::move(handler)); @@ -1736,17 +1868,6 @@ ircd::net::socket::scope_timeout::release() return s != nullptr; } -// -// socket::xfer -// - -ircd::net::socket::xfer::xfer(const error_code &error_code, - const size_t &bytes) -:eptr{make_eptr(error_code)} -,bytes{bytes} -{ -} - /////////////////////////////////////////////////////////////////////////////// // // net/resolve.h