diff --git a/include/ircd/listen.h b/include/ircd/listen.h new file mode 100644 index 000000000..991eccfbd --- /dev/null +++ b/include/ircd/listen.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 Charybdis Development Team + * Copyright (C) 2016 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. + */ + +#include "socket.h" + +namespace ircd { + +struct listener +{ + struct acceptor; + + IRCD_EXCEPTION(ircd::error, error) + + private: + std::unique_ptr<struct acceptor> acceptor; + + public: + listener(const json::doc &options); + ~listener() noexcept; +}; + +} // namespace ircd diff --git a/ircd/Makefile.am b/ircd/Makefile.am index 5c4436800..c7bcec150 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -43,9 +43,10 @@ libircd_la_SOURCES = \ locale.cc \ http.cc \ json.cc \ - matrix.cc \ parse.cc \ - conf.cc + conf.cc \ + listen.cc \ + matrix.cc if JS libircd_la_SOURCES += \ diff --git a/ircd/ircd.cc b/ircd/ircd.cc index cee263bcd..3489af364 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -24,6 +24,7 @@ */ #include <ircd/socket.h> +#include <ircd/listen.h> #include <ircd/ctx/continuation.h> namespace ircd @@ -103,11 +104,47 @@ try //json::test(); //exit(0); - module listener("listen.so"); module client_versions("client_versions.so"); module client_register("client_register.so"); module client_login("client_login.so"); + listener matrics + { + std::string { json::obj + { + { "name", "Chat Matrix" }, + { "host", "127.0.0.1" }, + { "port", 6667 }, + { + "ssl", + { + { + "certificate", + { + { + "file", + { + { "pem", "/home/jason/cdc.z.cert" } + } + } + } + }, + { + "private_key", + { + { + "file", + { + { "pem", "/home/jason/cdc.z.key" } + } + } + } + } + } + } + }} + }; + // This is the main program loop. Right now all it does is sleep until notified // to shutdown, but it can do other things eventually. Other subsystems may have // spawned their own main loops. diff --git a/ircd/listen.cc b/ircd/listen.cc new file mode 100644 index 000000000..c8b0e0d56 --- /dev/null +++ b/ircd/listen.cc @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2016 Charybdis Development Team + * Copyright (C) 2016 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. + */ + +#include <ircd/listen.h> + +namespace ircd { + +const size_t DEFAULT_STACK_SIZE +{ + 256_KiB // can be optimized +}; + +struct listener::acceptor +{ + static log::log log; + + std::string name; + size_t backlog; + + asio::ssl::context ssl; + ip::tcp::endpoint ep; + ip::tcp::acceptor a; + ctx::context context; + + explicit operator std::string() const; + + bool accept(); + void main(); + + acceptor(const json::doc &opts); + ~acceptor() noexcept; +}; + +} // namespace ircd + +/////////////////////////////////////////////////////////////////////////////// +// +// ircd::listener +// + +ircd::listener::listener(const json::doc &opts) +:acceptor{std::make_unique<struct acceptor>(opts)} +{ +} + +ircd::listener::~listener() +noexcept +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// +// ircd::listener::acceptor +// + +ircd::log::log +ircd::listener::acceptor::log +{ + "listener" +}; + +ircd::listener::acceptor::acceptor(const json::doc &opts) +try +:name +{ + unquote(opts.get("name", "IRCd (ssl)"s)) +} +,backlog +{ + opts.get<size_t>("backlog", asio::socket_base::max_connections) +} +,ssl +{ + asio::ssl::context::method::sslv23_server +} +,ep +{ + ip::address::from_string(unquote(opts.get("host", "127.0.0.1"s))), + opts.get<uint16_t>("port", 6667) +} +,a +{ + *ircd::ios +} +,context +{ + "listener", + opts.get<size_t>("stack_size", DEFAULT_STACK_SIZE), + std::bind(&acceptor::main, this), + context.POST // defer until ctor body binds the socket +} +{ + log.debug("%s attempting to open listening socket", std::string(*this)); + + a.open(ep.protocol()); + a.set_option(ip::tcp::acceptor::reuse_address(true)); + a.bind(ep); + + if(opts.has("ssl.certificate.file.pem")) + { + const std::string filename{opts["ssl.certificate.file.pem"]}; + ssl.use_certificate_file(filename, asio::ssl::context::pem); + log.info("%s using certificate file '%s'", + std::string(*this), + filename); + } + + if(opts.has("ssl.private_key.file.pem")) + { + const std::string filename{opts["ssl.private_key.file.pem"]}; + ssl.use_private_key_file(filename, asio::ssl::context::pem); + log.info("%s using private key file '%s'", + std::string(*this), + filename); + } + + a.listen(backlog); + + // Allows main() to run and print its log message + ctx::yield(); +} +catch(const boost::system::system_error &e) +{ + throw error("listener: %s", e.what()); +} + +ircd::listener::acceptor::~acceptor() +noexcept +{ + a.cancel(); +} + +void +ircd::listener::acceptor::main() +try +{ + log.info("%s ready", std::string(*this)); + + while(accept()); + + log.info("%s closing", std::string(*this)); +} +catch(const ircd::ctx::interrupted &e) +{ + log.warning("%s interrupted", std::string(*this)); +} +catch(const std::exception &e) +{ + log.error("%s %s", std::string(*this), e); +} + +bool +ircd::listener::acceptor::accept() +try +{ + auto sock(std::make_shared<ircd::socket>(ssl)); + a.async_accept(sock->ssl.lowest_layer(), yield(continuation())); + sock->ssl.async_handshake(socket::handshake_type::server, yield(continuation())); + add_client(std::move(sock)); + return true; +} +catch(const boost::system::system_error &e) +{ + switch(e.code().value()) + { + using namespace boost::system::errc; + + case success: return true; + case operation_canceled: return false; + default: throw; + } +} +catch(const std::exception &e) +{ + log.error("%s: in accept(): %s", std::string(*this), e); + return true; +} + +ircd::listener::acceptor::operator std::string() +const +{ + std::string ret(256, char()); + const auto length + { + fmt::snprintf(&ret.front(), ret.size(), "'%s' @ [%s]:%u", + name, + string(ep.address()), + ep.port()) + }; + + ret.resize(length); + return ret; +} diff --git a/modules/Makefile.am b/modules/Makefile.am index 44dbd4be6..cb3a8ae53 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -34,6 +34,4 @@ client_module_LTLIBRARIES = \ client/client_login.la moduledir=@moduledir@ -listen_la_SOURCES = listen.cc module_LTLIBRARIES = \ - listen.la diff --git a/modules/listen.cc b/modules/listen.cc deleted file mode 100644 index b5d1ebe26..000000000 --- a/modules/listen.cc +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2016 Charybdis Development Team - * Copyright (C) 2016 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. - */ - -#include <boost/asio.hpp> -#include <ircd/ctx/continuation.h> -#include <ircd/socket.h> - -using namespace ircd; - -const size_t STACK_SIZE -{ - 256_KiB // can be optimized -}; - -struct listener -{ - static struct log::log log; - - std::string name; - size_t backlog; - ip::address host; - ip::tcp::endpoint ep; - ctx::dock cond; - ircd::context context; - asio::ssl::context ssl; - ip::tcp::acceptor acceptor; - - bool configured() const; - - private: - bool accept(); - void main(); - - public: - listener(const std::string &name = {}); - listener(listener &&) = default; -}; - -struct log::log listener::log -{ - "listener", 'P' -}; - -listener::listener(const std::string &name) -try -:name -{ - name -} -,backlog -{ - boost::asio::socket_base::max_connections -} -,context -{ - "listener", STACK_SIZE, std::bind(&listener::main, this) -} -,ssl -{ - asio::ssl::context::method::sslv23_server -} -,acceptor -{ - *ios -} -{ - ssl.use_certificate_file("/home/jason/cdc.z.cert", asio::ssl::context::pem); - ssl.use_private_key_file("/home/jason/cdc.z.key", asio::ssl::context::pem); -} -catch(const boost::system::system_error &e) -{ - throw error("listener: %s", e.what()); -} - -bool -listener::configured() -const -{ - return ep != ip::tcp::endpoint{}; -} - -void -listener::main() -try -{ - // The listener context starts after there is a valid configuration - cond.wait([this] - { - return configured(); - }); - - log.debug("Attempting bind() to [%s]:%u", - ep.address().to_string().c_str(), - ep.port()); - - acceptor.open(ep.protocol()); - acceptor.set_option(ip::tcp::acceptor::reuse_address(true)); - acceptor.bind(ep); - acceptor.listen(backlog); - log.info("Listener bound to [%s]:%u", - ep.address().to_string().c_str(), - ep.port()); - - while(accept()); - - log.info("Listener closing @ [%s]:%u", - ep.address().to_string().c_str(), - ep.port()); -} -catch(const ircd::ctx::interrupted &e) -{ - log.warning("Listener closing @ [%s]:%u: %s", - ep.address().to_string().c_str(), - ep.port(), - e.what()); -} -catch(const std::exception &e) -{ - log.error("Listener closing @ [%s]:%u: %s", - ep.address().to_string().c_str(), - ep.port(), - e.what()); -} - -bool -listener::accept() -try -{ - auto sock(std::make_shared<ircd::socket>(ssl)); - acceptor.async_accept(sock->ssl.lowest_layer(), yield(continuation())); - sock->ssl.async_handshake(socket::handshake_type::server, yield(continuation())); - add_client(std::move(sock)); - return true; -} -catch(const boost::system::system_error &e) -{ - switch(e.code().value()) - { - using namespace boost::system::errc; - - case success: return true; - case operation_canceled: return false; - default: throw; - } -} -catch(const std::exception &e) -{ - log.error("Listener @ [%s]:%u: accept(): %s", - ep.address().to_string().c_str(), - ep.port(), - e.what()); - - return true; -} - -std::map<std::string, listener> listeners; - -extern "C" void gogo() -{ - const auto iit(listeners.emplace("foo"s, "foo"s)); - auto &foo(iit.first->second); - foo.host = ip::address::from_string("127.0.0.1"); - foo.ep = ip::tcp::endpoint(foo.host, 6667); - foo.cond.notify_one(); -} - -mapi::header IRCD_MODULE -{ - "P-Line - instructions for listening sockets", &gogo -};