0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-12-27 07:54:05 +01:00

ircd::net: Refine network subsystem; break up interfaces; various.

This commit is contained in:
Jason Volk 2018-01-06 21:34:02 -08:00
parent 7e0c01708a
commit ac3802c0f9
10 changed files with 1050 additions and 635 deletions

View file

@ -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.

75
include/ircd/net/close.h Normal file
View file

@ -0,0 +1,75 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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<void (std::exception_ptr)>;
// Callback-based closer.
void close(socket &, const close_opts &, close_callback);
// Future-based closer.
ctx::future<void> 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}
{}

View file

@ -40,6 +40,7 @@ namespace ircd::net
IRCD_EXCEPTION(error, inauthentic) IRCD_EXCEPTION(error, inauthentic)
struct init; struct init;
struct socket;
// SNOMASK 'N' "net" // SNOMASK 'N' "net"
extern struct log::log log; extern struct log::log log;
@ -48,7 +49,27 @@ namespace ircd::net
#include "remote.h" #include "remote.h"
#include "resolve.h" #include "resolve.h"
#include "listener.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 struct ircd::net::init
{ {

117
include/ircd/net/open.h Normal file
View file

@ -0,0 +1,117 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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<void (std::exception_ptr)>;
// Open existing socket with callback.
void open(socket &, const open_opts &, open_callback);
// Open new socket with callback.
std::shared_ptr<socket> open(const open_opts &, open_callback);
// Open new socket with future.
ctx::future<std::shared_ptr<socket>> 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}
{}

78
include/ircd/net/read.h Normal file
View file

@ -0,0 +1,78 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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<const mutable_buffer> &);
size_t read_any(socket &, const mutable_buffer &);
// Yields until buffers are entirely full.
size_t read_all(socket &, const vector_view<const mutable_buffer> &);
size_t read_all(socket &, const mutable_buffer &);
// Alias to read_any();
size_t read(socket &, const vector_view<const mutable_buffer> &);
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<const mutable_buffer> &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);
}

View file

@ -48,8 +48,8 @@ struct ircd::net::socket
using wait_type = ip::tcp::socket::wait_type; using wait_type = ip::tcp::socket::wait_type;
using message_flags = asio::socket_base::message_flags; using message_flags = asio::socket_base::message_flags;
using handshake_type = asio::ssl::stream<ip::tcp::socket>::handshake_type; using handshake_type = asio::ssl::stream<ip::tcp::socket>::handshake_type;
using handler = std::function<void (const error_code &) noexcept>; using ec_handler = std::function<void (const error_code &) noexcept>;
using xfer_handler = std::function<void (xfer) noexcept>; using eptr_handler = std::function<void (std::exception_ptr) noexcept>;
struct stat struct stat
{ {
@ -63,12 +63,14 @@ struct ircd::net::socket
stat in, out; stat in, out;
bool timedout {false}; bool timedout {false};
void call_user(const handler &, const error_code &ec) noexcept; void call_user(const eptr_handler &, const error_code &) noexcept;
bool handle_verify(bool, asio::ssl::verify_context &, const connopts &) noexcept; void call_user(const ec_handler &, const error_code &) noexcept;
void handle_handshake(std::weak_ptr<socket> wp, const connopts &, handler, const error_code &ec) noexcept; bool handle_verify(bool, asio::ssl::verify_context &, const open_opts &) noexcept;
void handle_connect(std::weak_ptr<socket> wp, const connopts &, handler, const error_code &ec) noexcept; void handle_disconnect(std::shared_ptr<socket>, eptr_handler, const error_code &) noexcept;
void handle_timeout(std::weak_ptr<socket> wp, const error_code &ec) noexcept; void handle_handshake(std::weak_ptr<socket>, const open_opts &, eptr_handler, const error_code &) noexcept;
void handle(std::weak_ptr<socket>, handler, const error_code &) noexcept; void handle_connect(std::weak_ptr<socket>, const open_opts &, eptr_handler, const error_code &) noexcept;
void handle_timeout(std::weak_ptr<socket>, const error_code &) noexcept;
void handle(std::weak_ptr<socket>, ec_handler, const error_code &) noexcept;
public: public:
operator const ip::tcp::socket &() const { return sd; } 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 remote() const; // getpeername(); throws if not conn
endpoint local() const; // getsockname(); throws if not conn/bound endpoint local() const; // getsockname(); throws if not conn/bound
// low level read suite
template<class iov> auto read_some(const iov &, xfer_handler);
template<class iov> auto read_some(const iov &);
template<class iov> auto read(const iov &, xfer_handler);
template<class iov> auto read(const iov &);
// low level write suite
template<class iov> auto write_some(const iov &, xfer_handler);
template<class iov> auto write_some(const iov &);
template<class iov> auto write(const iov &, xfer_handler);
template<class iov> auto write(const iov &);
// Timer for this socket // Timer for this socket
bool has_timeout() const noexcept; bool has_timeout() const noexcept;
void set_timeout(const milliseconds &, handler); void set_timeout(const milliseconds &, ec_handler);
void set_timeout(const milliseconds &); void set_timeout(const milliseconds &);
milliseconds cancel_timeout() noexcept; milliseconds cancel_timeout() noexcept;
// low level write suite
template<class iov> size_t write_one(iov&&);
template<class iov> size_t write_any(iov&&);
template<class iov> size_t write_all(iov&&);
// low level read suite
template<class iov> size_t read_any(iov&&);
template<class iov> size_t read_all(iov&&);
// Asynchronous callback when socket ready // Asynchronous callback when socket ready
void operator()(const wait_type &, const milliseconds &timeout, handler); void operator()(const wait_type &, const milliseconds &timeout, ec_handler);
void operator()(const wait_type &, handler); void operator()(const wait_type &, ec_handler);
bool cancel() noexcept; bool cancel() noexcept;
void handshake(const connopts &, handler callback); void disconnect(const close_opts &, eptr_handler);
void connect(const endpoint &, const connopts &, handler callback); void handshake(const open_opts &, eptr_handler);
void connect(const endpoint &, const open_opts &, eptr_handler);
bool disconnect(const dc &type);
socket(asio::ssl::context &ssl = sslv23_client, socket(asio::ssl::context &ssl = sslv23_client,
boost::asio::io_service *const &ios = ircd::ios); 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 cancel() noexcept; // invoke timer.cancel() before dtor
bool release(); // cancels the cancel; 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(socket &, const milliseconds &timeout);
scope_timeout() = default; scope_timeout() = default;
scope_timeout(scope_timeout &&) noexcept; scope_timeout(scope_timeout &&) noexcept;
@ -134,135 +132,106 @@ class ircd::net::socket::scope_timeout
~scope_timeout() noexcept; ~scope_timeout() noexcept;
}; };
class ircd::net::socket::io /// Yields ircd::ctx until buffers are full.
{
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<size_t ()> &closure);
};
struct ircd::net::socket::xfer
{
std::exception_ptr eptr;
size_t bytes {0};
xfer(const error_code &ec = {}, const size_t &bytes = 0);
};
template<class iov> template<class iov>
auto size_t
ircd::net::socket::write(const iov &bufs) 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<class iov>
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
{
io{*this, out, bytes};
handler(xfer{ec, bytes});
});
}
template<class iov>
auto
ircd::net::socket::write_some(const iov &bufs)
{
return io{*this, out, [&]
{
return ssl.async_write_some(bufs, yield_context{to_asio{}});
}};
}
template<class iov>
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<class iov>
auto
ircd::net::socket::read(const iov &bufs)
{
return io{*this, in, [&]
{
const size_t ret
{
async_read(ssl, bufs, yield_context{to_asio{}})
}; };
if(unlikely(!ret))
throw boost::system::system_error(boost::asio::error::eof);
return ret;
}};
}
template<class iov>
auto
ircd::net::socket::read(const iov &bufs,
xfer_handler handler)
{
async_read(ssl, bufs, [this, handler(std::move(handler))]
(const error_code &ec, const size_t &bytes)
noexcept
{
io{*this, in, bytes};
handler(xfer{ec, bytes});
});
}
template<class iov>
auto
ircd::net::socket::read_some(const iov &bufs)
{
return io{*this, in, [&]
{
const size_t ret const size_t ret
{ {
ssl.async_read_some(bufs, yield_context{to_asio{}}) asio::async_read(ssl, std::forward<iov>(bufs), completion, yield_context{to_asio{}})
}; };
if(unlikely(!ret)) if(!ret)
throw boost::system::system_error(boost::asio::error::eof); throw boost::system::system_error
return ret;
}};
}
template<class iov>
auto
ircd::net::socket::read_some(const iov &bufs,
xfer_handler handler)
{
ssl.async_read_some(bufs, [this, handler(std::move(handler))]
(const error_code &ec, const size_t &bytes)
noexcept
{ {
io{*this, in, bytes}; boost::asio::error::eof
handler(xfer{ec, bytes}); };
});
in.bytes += ret;
++in.calls;
return ret;
}
/// Yields ircd::ctx until remote has sent at least some data.
template<class iov>
size_t
ircd::net::socket::read_any(iov&& bufs)
{
const size_t ret
{
ssl.async_read_some(std::forward<iov>(bufs), yield_context{to_asio{}})
};
if(!ret)
throw boost::system::system_error
{
boost::asio::error::eof
};
in.bytes += ret;
++in.calls;
return ret;
}
/// Yields ircd::ctx until all buffers are sent.
template<class iov>
size_t
ircd::net::socket::write_all(iov&& bufs)
{
static const auto completion
{
asio::transfer_all()
};
const size_t ret
{
asio::async_write(ssl, std::forward<iov>(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<class iov>
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<iov>(bufs), completion)
};
out.bytes += ret;
++out.calls;
return ret;
}
/// Non-blocking; Writes one "unit" of data or less; never more.
template<class iov>
size_t
ircd::net::socket::write_one(iov&& bufs)
{
assert(!blocking(*this));
const size_t ret
{
ssl.write_some(std::forward<iov>(bufs))
};
out.bytes += ret;
++out.calls;
return ret;
} }

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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<int8_t>::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;
};

View file

@ -1,187 +0,0 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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 <ircd/asio.h> 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<mutable_buffer> &); // read_all
size_t read(socket &, const iov<mutable_buffer> &); // read_all
size_t read(socket &, iov<mutable_buffer> &); // read_some
size_t write(socket &, const ilist<const_buffer> &); // write_all
size_t write(socket &, const iov<const_buffer> &); // write_all
size_t write(socket &, iov<const_buffer> &); // write_some
void flush(socket &);
bool disconnect(socket &, const dc &type) noexcept;
bool disconnect(socket &) noexcept;
void open(socket &, const connopts &, std::function<void (std::exception_ptr)>);
ctx::future<std::shared_ptr<socket>> 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<int8_t>::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)
};

100
include/ircd/net/write.h Normal file
View file

@ -0,0 +1,100 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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<const const_buffer> &);
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<const const_buffer> &);
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<const const_buffer> &);
size_t write_all(socket &, const const_buffer &);
// Alias to write_all();
size_t write(socket &, const vector_view<const const_buffer> &);
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<const const_buffer> &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);
}

View file

@ -59,153 +59,6 @@ ircd::net::init::~init()
resolve::resolver.reset(nullptr); 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<std::shared_ptr<ircd::net::socket>>
ircd::net::open(const connopts &opts)
{
ctx::promise<std::shared_ptr<socket>> p;
ctx::future<std::shared_ptr<socket>> f(p);
auto s{std::make_shared<socket>()};
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<void (std::exception_ptr)> 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<const_buffer> &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<const_buffer> &bufs)
{
const size_t wrote(socket.write(bufs));
assert(wrote == size(bufs));
return wrote;
}
size_t
ircd::net::write(socket &socket,
const ilist<const_buffer> &bufs)
{
const size_t wrote(socket.write(bufs));
assert(wrote == size(bufs));
return wrote;
}
size_t
ircd::net::read(socket &socket,
iov<mutable_buffer> &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<mutable_buffer> &bufs)
{
return socket.read(bufs);
}
//
// Active observers
//
ircd::const_raw_buffer ircd::const_raw_buffer
ircd::net::peer_cert_der(const mutable_raw_buffer &buf, ircd::net::peer_cert_der(const mutable_raw_buffer &buf,
const socket &socket) const socket &socket)
@ -269,8 +122,239 @@ catch(...)
return false; 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<const const_buffer> &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<const const_buffer> &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<const const_buffer> &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<const mutable_buffer> &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<const mutable_buffer> &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<void>
ircd::net::close(socket &socket,
const close_opts &opts)
{
ctx::promise<void> p;
ctx::future<void> 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<std::shared_ptr<ircd::net::socket>>
ircd::net::open(const open_opts &opts)
{
ctx::promise<std::shared_ptr<socket>> p;
ctx::future<std::shared_ptr<socket>> f(p);
auto s{std::make_shared<socket>()};
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::socket>
ircd::net::open(const open_opts &opts,
open_callback handler)
{
auto s{std::make_shared<socket>()};
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 /// Construct sockopts with the current options from socket argument
@ -407,6 +491,30 @@ ircd::net::nodelay(socket &socket,
sd.set_option(option); 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 void
ircd::net::blocking(socket &socket, ircd::net::blocking(socket &socket,
const bool &b) const bool &b)
@ -511,11 +619,11 @@ struct ircd::net::listener::acceptor
void configure(const json::object &opts); void configure(const json::object &opts);
// Handshake stack // 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<socket>, std::weak_ptr<acceptor>) noexcept; void handshake(const error_code &ec, std::shared_ptr<socket>, std::weak_ptr<acceptor>) noexcept;
// Acceptance stack // 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<socket>, std::weak_ptr<acceptor>) noexcept; void accept(const error_code &ec, std::shared_ptr<socket>, std::weak_ptr<acceptor>) noexcept;
// Accept next // Accept next
@ -716,21 +824,19 @@ noexcept try
const unwind::exceptional drop{[&sock] const unwind::exceptional drop{[&sock]
{ {
assert(bool(sock)); assert(bool(sock));
disconnect(*sock, dc::RST); close(*sock, dc::RST, close_ignore);
}}; }};
assert(bool(sock)); assert(bool(sock));
if(unlikely(accept_error(ec, *sock))) check_accept_error(ec, *sock);
{
disconnect(*sock, dc::RST);
return;
}
log.debug("%s: socket(%p) accepted %s", log.debug("%s: socket(%p) accepted %s",
std::string(*this), std::string(*this),
sock.get(), sock.get(),
string(sock->remote())); string(sock->remote()));
// Toggles the behavior of non-async functions; see func comment
blocking(*sock, false);
static const socket::handshake_type handshake_type static const socket::handshake_type handshake_type
{ {
socket::handshake_type::server socket::handshake_type::server
@ -766,8 +872,8 @@ catch(const std::exception &e)
/// whether or not the handler should return or continue processing the /// whether or not the handler should return or continue processing the
/// result. /// result.
/// ///
bool void
ircd::net::listener::acceptor::accept_error(const error_code &ec, ircd::net::listener::acceptor::check_accept_error(const error_code &ec,
socket &sock) socket &sock)
{ {
using namespace boost::system::errc; using namespace boost::system::errc;
@ -777,12 +883,12 @@ ircd::net::listener::acceptor::accept_error(const error_code &ec,
throw ctx::interrupted(); throw ctx::interrupted();
if(likely(ec == success)) if(likely(ec == success))
return false; return;
if(ec.category() == system_category()) switch(ec.value()) if(ec.category() == system_category()) switch(ec.value())
{ {
case operation_canceled: case operation_canceled:
return false; return;
default: default:
break; break;
@ -801,18 +907,14 @@ noexcept try
return; return;
--handshaking; --handshaking;
assert(bool(sock));
const unwind::exceptional drop{[&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))) assert(bool(sock));
{ check_handshake_error(ec, *sock);
disconnect(*sock, dc::RST);
return;
}
log.debug("%s socket(%p): SSL handshook %s", log.debug("%s socket(%p): SSL handshook %s",
std::string(*this), std::string(*this),
sock.get(), sock.get(),
@ -842,8 +944,8 @@ catch(const std::exception &e)
/// whether or not the handler should return or continue processing the /// whether or not the handler should return or continue processing the
/// result. /// result.
/// ///
bool void
ircd::net::listener::acceptor::handshake_error(const error_code &ec, ircd::net::listener::acceptor::check_handshake_error(const error_code &ec,
socket &sock) socket &sock)
{ {
using boost::system::system_category; using boost::system::system_category;
@ -853,12 +955,12 @@ ircd::net::listener::acceptor::handshake_error(const error_code &ec,
throw ctx::interrupted(); throw ctx::interrupted();
if(likely(ec == success)) if(likely(ec == success))
return false; return;
if(ec.category() == system_category()) switch(ec.value()) if(ec.category() == system_category()) switch(ec.value())
{ {
case operation_canceled: case operation_canceled:
return false; return;
default: default:
break; break;
@ -1032,8 +1134,8 @@ catch(const std::exception &e)
void void
ircd::net::socket::connect(const endpoint &ep, ircd::net::socket::connect(const endpoint &ep,
const connopts &opts, const open_opts &opts,
handler callback) eptr_handler callback)
{ {
log.debug("socket(%p) attempting connect to remote: %s for the next %ld$ms", log.debug("socket(%p) attempting connect to remote: %s for the next %ld$ms",
this, this,
@ -1050,8 +1152,8 @@ ircd::net::socket::connect(const endpoint &ep,
} }
void void
ircd::net::socket::handshake(const connopts &opts, ircd::net::socket::handshake(const open_opts &opts,
handler callback) eptr_handler callback)
{ {
log.debug("socket(%p) performing handshake with %s for '%s' for the next %ld$ms", log.debug("socket(%p) performing handshake with %s for '%s' for the next %ld$ms",
this, this,
@ -1074,101 +1176,72 @@ ircd::net::socket::handshake(const connopts &opts,
set_timeout(opts.handshake_timeout); set_timeout(opts.handshake_timeout);
} }
bool void
ircd::net::socket::disconnect(const dc &type) ircd::net::socket::disconnect(const close_opts &opts,
eptr_handler callback)
try try
{ {
if(timer.expires_from_now() > 0ms) if(!sd.is_open())
timer.cancel(); {
call_user(callback, {});
return;
}
if(sd.is_open()) const bool cancelation{cancel()};
log.debug("socket(%p): disconnect: %s type:%d user: in:%zu out:%zu", log.debug("socket(%p): disconnect: %s type:%d user: in:%zu out:%zu cancel:%d",
(const void *)this, (const void *)this,
ircd::string(remote_ipport(*this)), ircd::string(remote_ipport(*this)),
uint(type), uint(opts.type),
in.bytes, in.bytes,
out.bytes); out.bytes,
cancelation);
if(sd.is_open()) switch(type) if(opts.sopts)
set(*this, *opts.sopts);
switch(opts.type)
{ {
default:
case dc::RST: case dc::RST:
sd.close(); sd.close();
return true; break;
case dc::FIN: case dc::FIN:
sd.shutdown(ip::tcp::socket::shutdown_both); sd.shutdown(ip::tcp::socket::shutdown_both);
return true; break;
case dc::FIN_SEND: case dc::FIN_SEND:
sd.shutdown(ip::tcp::socket::shutdown_send); sd.shutdown(ip::tcp::socket::shutdown_send);
return true; break;
case dc::FIN_RECV: case dc::FIN_RECV:
sd.shutdown(ip::tcp::socket::shutdown_receive); sd.shutdown(ip::tcp::socket::shutdown_receive);
return true; break;
case dc::SSL_NOTIFY_YIELD: if(likely(ctx::current))
{
const life_guard<socket> 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;
}
case dc::SSL_NOTIFY: case dc::SSL_NOTIFY:
{ {
ssl.async_shutdown([s(shared_from_this())] auto disconnect_handler
(error_code ec)
noexcept
{ {
if(!s->timedout) std::bind(&socket::handle_disconnect, this, shared_from(*this), std::move(callback), ph::_1)
s->cancel_timeout(); };
if(ec) ssl.async_shutdown(std::move(disconnect_handler));
log.warning("socket(%p): SSL_NOTIFY: %s: %s", set_timeout(opts.timeout);
s.get(),
string(ec));
if(!s->sd.is_open())
return; 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;
} }
} }
else return false;
call_user(callback, {});
} }
catch(const boost::system::system_error &e) catch(const boost::system::system_error &e)
{ {
log.warning("socket(%p): disconnect: type: %d: %s", call_user(callback, e.code());
}
catch(const std::exception &e)
{
log.critical("socket(%p): disconnect: type: %d: %s",
(const void *)this, (const void *)this,
uint(type), uint(opts.type),
e.what()); 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));
}
throw; throw;
} }
@ -1193,7 +1266,7 @@ noexcept
/// ///
void void
ircd::net::socket::operator()(const wait_type &type, ircd::net::socket::operator()(const wait_type &type,
handler h) ec_handler h)
{ {
operator()(type, milliseconds(-1), std::move(h)); operator()(type, milliseconds(-1), std::move(h));
} }
@ -1206,7 +1279,7 @@ ircd::net::socket::operator()(const wait_type &type,
void void
ircd::net::socket::operator()(const wait_type &type, ircd::net::socket::operator()(const wait_type &type,
const milliseconds &timeout, const milliseconds &timeout,
handler callback) ec_handler callback)
{ {
auto handle auto handle
{ {
@ -1242,7 +1315,7 @@ ircd::net::socket::operator()(const wait_type &type,
void void
ircd::net::socket::handle(const std::weak_ptr<socket> wp, ircd::net::socket::handle(const std::weak_ptr<socket> wp,
const handler callback, const ec_handler callback,
const error_code &ec) const error_code &ec)
noexcept try noexcept try
{ {
@ -1279,6 +1352,15 @@ noexcept try
call_user(callback, ec); 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) catch(const std::bad_weak_ptr &e)
{ {
// This handler may still be registered with asio after the socket destructs, so // 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)", log.warning("socket(%p): belated callback to handler... (%s)",
this, this,
e.what()); e.what());
assert(0); assert(0);
call_user(callback, ec);
} }
catch(const std::exception &e) catch(const std::exception &e)
{ {
log.critical("socket(%p): handle: %s", log.critical("socket(%p): handle: %s",
this, this,
e.what()); e.what());
assert(0); assert(0);
call_user(callback, ec);
} }
void void
@ -1345,8 +1431,8 @@ catch(const std::exception &e)
void void
ircd::net::socket::handle_connect(std::weak_ptr<socket> wp, ircd::net::socket::handle_connect(std::weak_ptr<socket> wp,
const connopts &opts, const open_opts &opts,
handler callback, eptr_handler callback,
const error_code &ec) const error_code &ec)
noexcept try noexcept try
{ {
@ -1366,16 +1452,13 @@ noexcept try
if(ec) if(ec)
return call_user(callback, ec); return call_user(callback, ec);
// Try to set the user's socket options now; if something fails // Toggles the behavior of non-async functions; see func comment
// we can invoke their callback with the error. blocking(*this, false);
if(opts.sopts) try
{ // 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); 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. // The user can opt out of performing the handshake here.
if(!opts.handshake) if(!opts.handshake)
@ -1388,20 +1471,74 @@ catch(const std::bad_weak_ptr &e)
log.warning("socket(%p): belated callback to handle_connect... (%s)", log.warning("socket(%p): belated callback to handle_connect... (%s)",
this, this,
e.what()); e.what());
assert(0); 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) catch(const std::exception &e)
{ {
log.critical("socket(%p): handle_connect: %s", log.critical("socket(%p): handle_connect: %s",
this, this,
e.what()); e.what());
assert(0); assert(0);
call_user(callback, ec);
}
void
ircd::net::socket::handle_disconnect(std::shared_ptr<socket> 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 void
ircd::net::socket::handle_handshake(std::weak_ptr<socket> wp, ircd::net::socket::handle_handshake(std::weak_ptr<socket> wp,
const connopts &opts, const open_opts &opts,
handler callback, eptr_handler callback,
const error_code &ec) const error_code &ec)
noexcept try noexcept try
{ {
@ -1421,12 +1558,22 @@ noexcept try
// back with or without error here. // back with or without error here.
call_user(callback, ec); 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) catch(const std::bad_weak_ptr &e)
{ {
log.warning("socket(%p): belated callback to handle_handshake... (%s)", log.warning("socket(%p): belated callback to handle_handshake... (%s)",
this, this,
e.what()); e.what());
assert(0); assert(0);
call_user(callback, ec);
} }
catch(const std::exception &e) catch(const std::exception &e)
{ {
@ -1434,12 +1581,13 @@ catch(const std::exception &e)
this, this,
e.what()); e.what());
assert(0); assert(0);
call_user(callback, ec);
} }
bool bool
ircd::net::socket::handle_verify(const bool valid, ircd::net::socket::handle_verify(const bool valid,
asio::ssl::verify_context &vc, asio::ssl::verify_context &vc,
const connopts &opts) const open_opts &opts)
noexcept try noexcept try
{ {
// `valid` indicates whether or not there's an anomaly with the // `valid` indicates whether or not there's an anomaly with the
@ -1546,7 +1694,7 @@ catch(const std::exception &e)
} }
void void
ircd::net::socket::call_user(const handler &callback, ircd::net::socket::call_user(const ec_handler &callback,
const error_code &ec) const error_code &ec)
noexcept try noexcept try
{ {
@ -1559,6 +1707,20 @@ catch(const std::exception &e)
e.what()); 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 boost::asio::ip::tcp::endpoint
ircd::net::socket::local() ircd::net::socket::local()
const const
@ -1602,7 +1764,7 @@ ircd::net::socket::set_timeout(const milliseconds &t)
void void
ircd::net::socket::set_timeout(const milliseconds &t, ircd::net::socket::set_timeout(const milliseconds &t,
handler h) ec_handler h)
{ {
cancel_timeout(); cancel_timeout();
if(t < milliseconds(0)) if(t < milliseconds(0))
@ -1636,36 +1798,6 @@ const
return *ssl.native_handle(); return *ssl.native_handle();
} }
//
// socket::io
//
ircd::net::socket::io::io(struct socket &sock,
struct stat &stat,
const std::function<size_t ()> &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 // 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, ircd::net::socket::scope_timeout::scope_timeout(socket &socket,
const milliseconds &timeout, const milliseconds &timeout,
socket::handler handler) socket::ec_handler handler)
:s{&socket} :s{&socket}
{ {
socket.set_timeout(timeout, std::move(handler)); socket.set_timeout(timeout, std::move(handler));
@ -1736,17 +1868,6 @@ ircd::net::socket::scope_timeout::release()
return s != nullptr; 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 // net/resolve.h