mirror of
https://github.com/matrix-construct/construct
synced 2024-11-15 14:31:11 +01:00
ircd::net::acceptor: Track and limit operations based on configuration.
This commit is contained in:
parent
d7edd1960e
commit
dafb8bd42f
3 changed files with 149 additions and 43 deletions
|
@ -23,11 +23,15 @@ struct ircd::net::acceptor
|
|||
using error_code = boost::system::error_code;
|
||||
using callback = listener::callback;
|
||||
using proffer = listener::proffer;
|
||||
using sockets = std::list<std::shared_ptr<socket>>;
|
||||
|
||||
IRCD_EXCEPTION(listener::error, error)
|
||||
IRCD_EXCEPTION(error, sni_warning)
|
||||
|
||||
static log::log log;
|
||||
static conf::item<size_t> accepting_max;
|
||||
static conf::item<size_t> handshaking_max;
|
||||
static conf::item<size_t> handshaking_max_per_peer;
|
||||
static conf::item<milliseconds> timeout;
|
||||
static conf::item<std::string> ssl_curve_list;
|
||||
static conf::item<std::string> ssl_cipher_list;
|
||||
|
@ -42,10 +46,9 @@ struct ircd::net::acceptor
|
|||
asio::ssl::context ssl;
|
||||
ip::tcp::endpoint ep;
|
||||
ip::tcp::acceptor a;
|
||||
size_t accepting {0};
|
||||
size_t handshaking {0};
|
||||
sockets accepting;
|
||||
sockets handshaking;
|
||||
bool interrupting {false};
|
||||
bool handle_set {false};
|
||||
ctx::dock joining;
|
||||
|
||||
void configure(const json::object &opts);
|
||||
|
@ -54,14 +57,15 @@ struct ircd::net::acceptor
|
|||
bool handle_sni(SSL &, int &ad);
|
||||
string_view handle_alpn(SSL &, const vector_view<const string_view> &in);
|
||||
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 &, const std::shared_ptr<socket>, const decltype(handshaking)::const_iterator) noexcept;
|
||||
|
||||
// Acceptance stack
|
||||
bool 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 &, const std::shared_ptr<socket>, const decltype(accepting)::const_iterator) noexcept;
|
||||
|
||||
// Accept next
|
||||
bool set_handle();
|
||||
size_t set_handles();
|
||||
|
||||
// Acceptor shutdown
|
||||
bool interrupt() noexcept;
|
||||
|
|
|
@ -23,6 +23,11 @@ namespace ircd::net
|
|||
string_view name(const acceptor &);
|
||||
ipport binder(const acceptor &);
|
||||
ipport local(const acceptor &);
|
||||
|
||||
size_t handshaking_count(const acceptor &, const ipaddr &);
|
||||
size_t handshaking_count(const acceptor &);
|
||||
size_t accepting_count(const acceptor &);
|
||||
|
||||
string_view loghead(const mutable_buffer &, const acceptor &);
|
||||
string_view loghead(const acceptor &);
|
||||
std::ostream &operator<<(std::ostream &s, const acceptor &);
|
||||
|
|
173
ircd/net.cc
173
ircd/net.cc
|
@ -1304,6 +1304,32 @@ ircd::net::acceptor::timeout
|
|||
{ "default", 12000L },
|
||||
};
|
||||
|
||||
/// The number of sockets we precreate and set accept handles for.
|
||||
decltype(ircd::net::acceptor::accepting_max)
|
||||
ircd::net::acceptor::accepting_max
|
||||
{
|
||||
{ "name", "ircd.net.acceptor.accepting.max" },
|
||||
{ "default", 1L },
|
||||
};
|
||||
|
||||
/// The number of simultaneous handshakes we conduct across all clients.
|
||||
decltype(ircd::net::acceptor::handshaking_max)
|
||||
ircd::net::acceptor::handshaking_max
|
||||
{
|
||||
{ "name", "ircd.net.acceptor.handshaking.max" },
|
||||
{ "default", 64L },
|
||||
};
|
||||
|
||||
/// The number of simultaneous handshakes we conduct for a single peer (which
|
||||
/// is an IP without a port in this context). This prevents a peer from
|
||||
/// reaching the handshaking.max limit to DoS out other peers.
|
||||
decltype(ircd::net::acceptor::handshaking_max_per_peer)
|
||||
ircd::net::acceptor::handshaking_max_per_peer
|
||||
{
|
||||
{ "name", "ircd.net.acceptor.handshaking.max_per_peer" },
|
||||
{ "default", 16L },
|
||||
};
|
||||
|
||||
decltype(ircd::net::acceptor::ssl_curve_list)
|
||||
ircd::net::acceptor::ssl_curve_list
|
||||
{
|
||||
|
@ -1348,10 +1374,7 @@ ircd::net::allow(acceptor &a)
|
|||
if(unlikely(!a.a.is_open()))
|
||||
return false;
|
||||
|
||||
if(unlikely(a.handle_set))
|
||||
return false;
|
||||
|
||||
a.set_handle();
|
||||
a.set_handles();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1407,6 +1430,29 @@ ircd::net::config(const acceptor &a)
|
|||
return a.opts;
|
||||
}
|
||||
|
||||
size_t
|
||||
ircd::net::accepting_count(const acceptor &a)
|
||||
{
|
||||
return a.accepting.size();
|
||||
}
|
||||
|
||||
size_t
|
||||
ircd::net::handshaking_count(const acceptor &a)
|
||||
{
|
||||
return a.handshaking.size();
|
||||
}
|
||||
|
||||
size_t
|
||||
ircd::net::handshaking_count(const acceptor &a,
|
||||
const ipaddr &ipaddr)
|
||||
{
|
||||
return std::count_if(begin(a.handshaking), end(a.handshaking), [&ipaddr]
|
||||
(const auto &socket_p)
|
||||
{
|
||||
return remote_ipport(*socket_p) == ipaddr;
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// acceptor::acceptor
|
||||
//
|
||||
|
@ -1474,13 +1520,13 @@ catch(const boost::system::system_error &e)
|
|||
ircd::net::acceptor::~acceptor()
|
||||
noexcept
|
||||
{
|
||||
if(accepting || handshaking)
|
||||
if(!accepting.empty() || handshaking.empty())
|
||||
log::critical
|
||||
{
|
||||
"The acceptor must not have clients during destruction!"
|
||||
" (accepting:%zu handshaking:%zu)",
|
||||
accepting,
|
||||
handshaking
|
||||
accepting.size(),
|
||||
handshaking.size(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1534,6 +1580,9 @@ ircd::net::acceptor::close()
|
|||
if(a.is_open())
|
||||
a.close();
|
||||
|
||||
for(const auto &sock : handshaking)
|
||||
sock->cancel();
|
||||
|
||||
join();
|
||||
log::debug
|
||||
{
|
||||
|
@ -1553,7 +1602,7 @@ noexcept try
|
|||
|
||||
joining.wait([this]
|
||||
{
|
||||
return !accepting && !handshaking;
|
||||
return accepting.empty() && handshaking.empty();
|
||||
});
|
||||
|
||||
interrupting = false;
|
||||
|
@ -1591,6 +1640,16 @@ catch(const boost::system::system_error &e)
|
|||
return false;
|
||||
}
|
||||
|
||||
size_t
|
||||
ircd::net::acceptor::set_handles()
|
||||
{
|
||||
size_t ret(0);
|
||||
while(set_handle())
|
||||
++ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Sets the next asynchronous handler to start the next accept sequence.
|
||||
/// Each call to next() sets one handler which handles the connect for one
|
||||
/// socket. After the connect, an asynchronous SSL handshake handler is set
|
||||
|
@ -1604,20 +1663,25 @@ try
|
|||
"ircd::net::acceptor accept"
|
||||
};
|
||||
|
||||
assert(!handle_set);
|
||||
handle_set = true;
|
||||
auto sock
|
||||
if(accepting.size() >= size_t(accepting_max))
|
||||
return false;
|
||||
|
||||
const auto it
|
||||
{
|
||||
std::make_shared<ircd::socket>(ssl)
|
||||
accepting.emplace(end(accepting), std::make_shared<ircd::socket>(ssl))
|
||||
};
|
||||
|
||||
const auto &sock
|
||||
{
|
||||
*it
|
||||
};
|
||||
|
||||
++accepting;
|
||||
ip::tcp::socket &sd(*sock);
|
||||
auto handler
|
||||
{
|
||||
std::bind(&acceptor::accept, this, ph::_1, sock, weak_from(*this))
|
||||
std::bind(&acceptor::accept, this, ph::_1, sock, it)
|
||||
};
|
||||
|
||||
ip::tcp::socket &sd(*sock);
|
||||
a.async_accept(sd, ios::handle(desc, std::move(handler)));
|
||||
return true;
|
||||
}
|
||||
|
@ -1635,40 +1699,68 @@ catch(const std::exception &e)
|
|||
void
|
||||
ircd::net::acceptor::accept(const error_code &ec,
|
||||
const std::shared_ptr<socket> sock,
|
||||
const std::weak_ptr<acceptor> a)
|
||||
const decltype(accepting)::const_iterator it)
|
||||
noexcept try
|
||||
{
|
||||
if(unlikely(a.expired()))
|
||||
return;
|
||||
|
||||
assert(bool(sock));
|
||||
assert(handle_set);
|
||||
assert(accepting > 0);
|
||||
|
||||
handle_set = false;
|
||||
--accepting;
|
||||
|
||||
assert(!accepting.empty());
|
||||
assert(it != end(accepting));
|
||||
thread_local char ecbuf[64];
|
||||
log::debug
|
||||
{
|
||||
log, "%s: accepted(%zu) %s %s",
|
||||
log, "%s: %s accepted(%zd:%zu) %s",
|
||||
loghead(*this),
|
||||
accepting,
|
||||
loghead(*sock),
|
||||
std::distance(cbegin(accepting), it),
|
||||
accepting.size(),
|
||||
string(ecbuf, ec)
|
||||
};
|
||||
|
||||
accepting.erase(it);
|
||||
if(!check_accept_error(ec, *sock))
|
||||
return;
|
||||
|
||||
const auto &remote
|
||||
{
|
||||
remote_ipport(*sock)
|
||||
};
|
||||
|
||||
// Call the proffer-callback if available. This allows the application
|
||||
// to check whether to allow or deny this remote before the handshake.
|
||||
if(pcb && !pcb(*listener_, remote_ipport(*sock)))
|
||||
if(pcb && !pcb(*listener_, remote))
|
||||
{
|
||||
net::close(*sock, dc::RST, close_ignore);
|
||||
return;
|
||||
}
|
||||
|
||||
if(unlikely(handshaking_count(*this) >= size_t(handshaking_max)))
|
||||
{
|
||||
log::dwarning
|
||||
{
|
||||
log, "%s: refusing to handshake %s; exceeds maximum of %zu handshakes.",
|
||||
loghead(*this),
|
||||
loghead(*sock),
|
||||
size_t(handshaking_max),
|
||||
};
|
||||
|
||||
net::close(*sock, dc::RST, close_ignore);
|
||||
return;
|
||||
}
|
||||
|
||||
if(unlikely(handshaking_count(*this, remote) >= size_t(handshaking_max_per_peer)))
|
||||
{
|
||||
log::dwarning
|
||||
{
|
||||
log, "%s: refusing to handshake %s; exceeds maximum of %zu handshakes to them.",
|
||||
loghead(*this),
|
||||
loghead(*sock),
|
||||
size_t(handshaking_max_per_peer),
|
||||
};
|
||||
|
||||
net::close(*sock, dc::RST, close_ignore);
|
||||
return;
|
||||
}
|
||||
|
||||
static const socket::handshake_type handshake_type
|
||||
{
|
||||
socket::handshake_type::server
|
||||
|
@ -1679,12 +1771,16 @@ noexcept try
|
|||
"ircd::net::acceptor async_handshake"
|
||||
};
|
||||
|
||||
auto handshake
|
||||
const auto it
|
||||
{
|
||||
std::bind(&acceptor::handshake, this, ph::_1, sock, a)
|
||||
handshaking.emplace(end(handshaking), sock)
|
||||
};
|
||||
|
||||
auto handshake
|
||||
{
|
||||
std::bind(&acceptor::handshake, this, ph::_1, sock, it)
|
||||
};
|
||||
|
||||
++handshaking;
|
||||
sock->set_timeout(milliseconds(timeout));
|
||||
sock->ssl.async_handshake(handshake_type, ios::handle(desc, std::move(handshake)));
|
||||
}
|
||||
|
@ -1771,14 +1867,12 @@ ircd::net::acceptor::check_accept_error(const error_code &ec,
|
|||
void
|
||||
ircd::net::acceptor::handshake(const error_code &ec,
|
||||
const std::shared_ptr<socket> sock,
|
||||
const std::weak_ptr<acceptor> a)
|
||||
const decltype(handshaking)::const_iterator it)
|
||||
noexcept try
|
||||
{
|
||||
if(unlikely(a.expired()))
|
||||
return;
|
||||
|
||||
--handshaking;
|
||||
assert(bool(sock));
|
||||
assert(!handshaking.empty());
|
||||
assert(it != end(handshaking));
|
||||
|
||||
#ifdef RB_DEBUG
|
||||
const auto *const current_cipher
|
||||
|
@ -1791,9 +1885,11 @@ noexcept try
|
|||
thread_local char ecbuf[64];
|
||||
log::debug
|
||||
{
|
||||
log, "%s handshook(%zu) cipher:%s %s",
|
||||
log, "%s: %s handshook(%zd:%zu) cipher:%s %s",
|
||||
loghead(*this),
|
||||
loghead(*sock),
|
||||
handshaking,
|
||||
std::distance(cbegin(handshaking), it),
|
||||
handshaking.size(),
|
||||
current_cipher?
|
||||
openssl::name(*current_cipher):
|
||||
"<NO CIPHER>"_sv,
|
||||
|
@ -1801,6 +1897,7 @@ noexcept try
|
|||
};
|
||||
#endif
|
||||
|
||||
handshaking.erase(it);
|
||||
check_handshake_error(ec, *sock);
|
||||
sock->cancel_timeout();
|
||||
assert(bool(cb));
|
||||
|
|
Loading…
Reference in a new issue