Mercurial > irccd
changeset 102:4777f7e18bf2
Irccd: several improvements in servers, #385
Make server state a class to manage data cleanly where they are needed and to
understand better the transitions.
Add a ping timer that defaults to 5 minutes before marking the server as
disconnected.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 26 Apr 2016 13:19:35 +0200 |
parents | 113d909fdfe1 |
children | 04d672ab41a4 |
files | doc/html/guide/04-irccd/01-config.md doc/man/irccd.conf.5.in extern/libircclient/include/libirc_events.h extern/libircclient/src/libircclient.c lib/irccd/CMakeSources.cmake lib/irccd/config.cpp lib/irccd/server-state-connected.cpp lib/irccd/server-state-connected.hpp lib/irccd/server-state-connecting.cpp lib/irccd/server-state-connecting.hpp lib/irccd/server-state-disconnected.cpp lib/irccd/server-state-disconnected.hpp lib/irccd/server-state.cpp lib/irccd/server-state.hpp lib/irccd/server.cpp lib/irccd/server.hpp |
diffstat | 16 files changed, 501 insertions(+), 273 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/html/guide/04-irccd/01-config.md Mon Apr 25 21:16:47 2016 +0200 +++ b/doc/html/guide/04-irccd/01-config.md Tue Apr 26 13:19:35 2016 +0200 @@ -122,7 +122,8 @@ - **ssl-verify**: (bool) verify the SSL certificates (Optional, default: true), - **reconnect**: (bool) enable reconnection after failure (Optional, default: true), - **reconnect-tries**: (int) number of tries before giving up. A value of -1 means indefinitely (Optional, default: -1), - - **reconnect-timeout**: (int) number of seconds to wait before retrying (Optional, default: 30). + - **reconnect-timeout**: (int) number of seconds to wait before retrying (Optional, default: 30), + - **ping-timeout** (int) number of seconds before ping timeout (Optional, default: 300). <div class="alert alert-info" role="alert"> **Note:** if a channel requires a password, add it after a colon (e.g. "#channel:password").
--- a/doc/man/irccd.conf.5.in Mon Apr 25 21:16:47 2016 +0200 +++ b/doc/man/irccd.conf.5.in Tue Apr 26 13:19:35 2016 +0200 @@ -133,6 +133,8 @@ (int) number of tries before giving up. A value of -1 means indefinitely (Optional, default: -1). .It reconnect-timeout (int) number of seconds to wait before retrying (Optional, default: 30). +.It ping-timeout +(int) number of seconds before ping timeout (Optional, default: 300). .El .\" PLUGINS .Ss plugins
--- a/extern/libircclient/include/libirc_events.h Mon Apr 25 21:16:47 2016 +0200 +++ b/extern/libircclient/include/libirc_events.h Tue Apr 26 13:19:35 2016 +0200 @@ -158,6 +158,11 @@ irc_event_callback_t event_connect; /*! + * The "ping" event is triggered when the client receives PING requests. + */ + irc_event_callback_t event_ping; + + /*! * The "nick" event is triggered when the client receives a NICK message, * meaning that someone (including you) on a channel with the client has * changed their nickname.
--- a/extern/libircclient/src/libircclient.c Mon Apr 25 21:16:47 2016 +0200 +++ b/extern/libircclient/src/libircclient.c Tue Apr 26 13:19:35 2016 +0200 @@ -593,7 +593,11 @@ // Handle PING/PONG if ( command && !strncmp (command, "PING", buf_end - command) && params[0] ) { - irc_send_raw (session, "PONG %s", params[0]); + if (session->callbacks.event_ping) + (*session->callbacks.event_ping)(session, "PING", prefix, params, paramindex); + else + irc_send_raw (session, "PONG %s", params[0]); + return; }
--- a/lib/irccd/CMakeSources.cmake Mon Apr 25 21:16:47 2016 +0200 +++ b/lib/irccd/CMakeSources.cmake Tue Apr 26 13:19:35 2016 +0200 @@ -58,6 +58,9 @@ ${CMAKE_CURRENT_LIST_DIR}/server.hpp ${CMAKE_CURRENT_LIST_DIR}/server-private.hpp ${CMAKE_CURRENT_LIST_DIR}/server-state.hpp + ${CMAKE_CURRENT_LIST_DIR}/server-state-connected.hpp + ${CMAKE_CURRENT_LIST_DIR}/server-state-connecting.hpp + ${CMAKE_CURRENT_LIST_DIR}/server-state-disconnected.hpp ${CMAKE_CURRENT_LIST_DIR}/sockets.hpp ${CMAKE_CURRENT_LIST_DIR}/system.hpp ${CMAKE_CURRENT_LIST_DIR}/timer.hpp @@ -121,7 +124,9 @@ ${CMAKE_CURRENT_LIST_DIR}/plugin.cpp ${CMAKE_CURRENT_LIST_DIR}/rule.cpp ${CMAKE_CURRENT_LIST_DIR}/server.cpp - ${CMAKE_CURRENT_LIST_DIR}/server-state.cpp + ${CMAKE_CURRENT_LIST_DIR}/server-state-connected.cpp + ${CMAKE_CURRENT_LIST_DIR}/server-state-connecting.cpp + ${CMAKE_CURRENT_LIST_DIR}/server-state-disconnected.cpp ${CMAKE_CURRENT_LIST_DIR}/sockets.cpp ${CMAKE_CURRENT_LIST_DIR}/system.cpp ${CMAKE_CURRENT_LIST_DIR}/timer.cpp
--- a/lib/irccd/config.cpp Mon Apr 25 21:16:47 2016 +0200 +++ b/lib/irccd/config.cpp Tue Apr 26 13:19:35 2016 +0200 @@ -303,6 +303,8 @@ settings.recotries = std::stoi(it->value()); if ((it = sc.find("reconnect-timeout")) != sc.end()) settings.recotimeout = std::stoi(it->value()); + if ((it = sc.find("ping-timeout")) != sc.end()) + settings.ping_timeout = std::stoi(it->value()); } catch (const std::exception &) { throw std::invalid_argument("server " + info.name + ": invalid number for " + it->key() + ": " + it->value()); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server-state-connected.cpp Tue Apr 26 13:19:35 2016 +0200 @@ -0,0 +1,58 @@ +/* + * server-state-connected.cpp -- connected state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "logger.hpp" +#include "server-state-connected.hpp" +#include "server-state-disconnected.hpp" +#include "server-private.hpp" + +namespace irccd { + +namespace state { + +void Connected::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) +{ + const ServerInfo &info = server.info(); + const ServerSettings &settings = server.settings(); + + if (!irc_is_connected(server.session())) { + log::warning() << "server " << info.name << ": disconnected" << std::endl; + + if (settings.recotimeout > 0) { + log::warning() << "server " << info.name << ": retrying in " + << settings.recotimeout << " seconds" << std::endl; + } + + server.next(std::make_unique<state::Disconnected>()); + } else if (server.pingTimer().elapsed() >= settings.ping_timeout * 1000) { + log::warning() << "server " << info.name << ": ping timeout after " + << (server.pingTimer().elapsed() / 1000) << " seconds" << std::endl; + server.next(std::make_unique<state::Disconnected>()); + } else { + irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd)); + } +} + +std::string Connected::ident() const +{ + return "Connected"; +} + +} // !state + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server-state-connected.hpp Tue Apr 26 13:19:35 2016 +0200 @@ -0,0 +1,53 @@ +/* + * server-state-connected.hpp -- connected state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_SERVER_STATE_CONNECTED_HPP +#define IRCCD_SERVER_STATE_CONNECTED_HPP + +/** + * \file server-state-connected.hpp + * \brief Connected state. + */ + +#include "server-state.hpp" + +namespace irccd { + +namespace state { + +/** + * \brief Connected state. + */ +class Connected : public ServerState { +public: + /** + * \copydoc ServerState::prepare + */ + void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) override; + + /** + * \copydoc ServerState::ident + */ + std::string ident() const override; +}; + +} // !state + +} // !irccd + +#endif // !IRCCD_SERVER_STATE_CONNECTED_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server-state-connecting.cpp Tue Apr 26 13:19:35 2016 +0200 @@ -0,0 +1,133 @@ +/* + * server-state-connecting.cpp -- connecting state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "server-state-connecting.hpp" +#include "server-state-connected.hpp" +#include "server-state-disconnected.hpp" +#include "server-private.hpp" +#include "sysconfig.hpp" + +#if !defined(IRCCD_SYSTEM_WINDOWS) +# include <sys/types.h> +# include <netinet/in.h> +# include <arpa/nameser.h> +# include <resolv.h> +#endif + +namespace irccd { + +namespace state { + +namespace { + +bool connect(Server &server) +{ + const ServerInfo &info = server.info(); + const ServerIdentity &identity = server.identity(); + const char *password = info.password.empty() ? nullptr : info.password.c_str(); + std::string host = info.host; + int code; + + /* libircclient requires # for SSL connection */ +#if defined(WITH_SSL) + if (info.flags & ServerInfo::Ssl) + host.insert(0, 1, '#'); + if (!(info.flags & ServerInfo::SslVerify)) + irc_option_set(server.session(), LIBIRC_OPTION_SSL_NO_VERIFY); +#endif + + if (info.flags & ServerInfo::Ipv6) { + code = irc_connect6(server.session(), host.c_str(), info.port, password, + identity.nickname.c_str(), + identity.username.c_str(), + identity.realname.c_str()); + } else { + code = irc_connect(server.session(), host.c_str(), info.port, password, + identity.nickname.c_str(), + identity.username.c_str(), + identity.realname.c_str()); + } + + return code == 0; +} + +} // !namespace + +void Connecting::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) +{ + /* + * The connect function will either fail if the hostname wasn't resolved + * or if any of the internal functions fail. + * + * It returns success if the connection was successful but it does not + * mean that connection is established. + * + * Because this function will be called repeatidly, the connection was started and we're still not + * connected in the specified timeout time, we mark the server as disconnected. + * + * Otherwise, the libircclient event_connect will change the state. + */ + const ServerInfo &info = server.info(); + + if (m_started) { + const ServerSettings &settings = server.settings(); + + if (m_timer.elapsed() > static_cast<unsigned>(settings.recotimeout * 1000)) { + log::warning() << "server " << info.name << ": timeout while connecting" << std::endl; + server.next(std::make_unique<state::Disconnected>()); + } else if (!irc_is_connected(server.session())) { + log::warning() << "server " << info.name << ": error while connecting: "; + log::warning() << irc_strerror(irc_errno(server.session())) << std::endl; + + if (settings.recotries != 0) + log::warning() << "server " << info.name << ": retrying in " << settings.recotimeout << " seconds" << std::endl; + + server.next(std::make_unique<state::Disconnected>()); + } else { + irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd)); + } + } else { + /* + * This is needed if irccd is started before DHCP or if + * DNS cache is outdated. + * + * For more information see bug #190. + */ +#if !defined(IRCCD_SYSTEM_WINDOWS) + (void)res_init(); +#endif + log::info() << "server " << info.name << ": trying to connect to " << info.host << ", port " << info.port << std::endl; + + if (!connect(server)) { + log::warning() << "server " << info.name << ": disconnected while connecting: "; + log::warning() << irc_strerror(irc_errno(server.session())) << std::endl; + server.next(std::make_unique<state::Disconnected>()); + } else { + m_started = true; + } + } +} + +std::string Connecting::ident() const +{ + return "Connecting"; +} + +} // !state + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server-state-connecting.hpp Tue Apr 26 13:19:35 2016 +0200 @@ -0,0 +1,58 @@ +/* + * server-state-connecting.hpp -- connecting state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_SERVER_STATE_CONNECTING_HPP +#define IRCCD_SERVER_STATE_CONNECTING_HPP + +/** + * \file server-state-connecting.hpp + * \brief Connecting state. + */ + +#include "elapsed-timer.hpp" +#include "server-state.hpp" + +namespace irccd { + +namespace state { + +/** + * \brief Connecting state. + */ +class Connecting : public ServerState { +private: + bool m_started{false}; + ElapsedTimer m_timer; + +public: + /** + * \copydoc ServerState::prepare + */ + void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) override; + + /** + * \copydoc ServerState::ident + */ + std::string ident() const override; +}; + +} // !state + +} // !irccd + +#endif // !IRCCD_SERVER_STATE_CONNECTING_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server-state-disconnected.cpp Tue Apr 26 13:19:35 2016 +0200 @@ -0,0 +1,56 @@ +/* + * server-state-disconnected.cpp -- disconnected state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "logger.hpp" +#include "server-state-connecting.hpp" +#include "server-state-disconnected.hpp" +#include "server-private.hpp" + +namespace irccd { + +namespace state { + +void Disconnected::prepare(Server &server, fd_set &, fd_set &, net::Handle &) +{ + const ServerInfo &info = server.info(); + ServerSettings &settings = server.settings(); + + if (settings.recotries == 0) { + log::warning() << "server " << info.name << ": reconnection disabled, skipping" << std::endl; + server.onDie(); + } else if (settings.recotries > 0 && settings.recocurrent > settings.recotries) { + log::warning() << "server " << info.name << ": giving up" << std::endl; + server.onDie(); + } else { + if (m_timer.elapsed() > static_cast<unsigned>(settings.recotimeout * 1000)) { + irc_disconnect(server.session()); + + settings.recocurrent ++; + server.next(std::make_unique<state::Connecting>()); + } + } +} + +std::string Disconnected::ident() const +{ + return "Disconnected"; +} + +} // !state + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server-state-disconnected.hpp Tue Apr 26 13:19:35 2016 +0200 @@ -0,0 +1,57 @@ +/* + * server-state-disconnected.hpp -- disconnected state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_SERVER_STATE_DISCONNECTED_HPP +#define IRCCD_SERVER_STATE_DISCONNECTED_HPP + +/** + * \file server-state-disconnected.hpp + * \brief Connecting state. + */ + +#include "elapsed-timer.hpp" +#include "server-state.hpp" + +namespace irccd { + +namespace state { + +/** + * \brief Disconnected state. + */ +class Disconnected : public ServerState { +private: + ElapsedTimer m_timer; + +public: + /** + * \copydoc ServerState::prepare + */ + void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) override; + + /** + * \copydoc ServerState::ident + */ + std::string ident() const override; +}; + +} // !state + +} // !irccd + +#endif // !IRCCD_SERVER_STATE_DISCONNECTED_HPP
--- a/lib/irccd/server-state.cpp Mon Apr 25 21:16:47 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,184 +0,0 @@ -/* - * server-state.cpp -- server current state - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * 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 appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> - -#include "sysconfig.hpp" - -#if !defined(_WIN32) -# include <sys/types.h> -# include <netinet/in.h> -# include <arpa/nameser.h> -# include <resolv.h> -#endif - -#include "server-state.hpp" -#include "server-private.hpp" - -namespace irccd { - -bool ServerState::connect(Server &server) -{ - const ServerInfo &info = server.info(); - const ServerIdentity &identity = server.identity(); - const char *password = info.password.empty() ? nullptr : info.password.c_str(); - std::string host = info.host; - int code; - - /* libircclient requires # for SSL connection */ -#if defined(WITH_SSL) - if (info.flags & ServerInfo::Ssl) - host.insert(0, 1, '#'); - if (!(info.flags & ServerInfo::SslVerify)) - irc_option_set(server.session(), LIBIRC_OPTION_SSL_NO_VERIFY); -#endif - - if (info.flags & ServerInfo::Ipv6) { - code = irc_connect6(server.session(), host.c_str(), info.port, password, - identity.nickname.c_str(), - identity.username.c_str(), - identity.realname.c_str()); - } else { - code = irc_connect(server.session(), host.c_str(), info.port, password, - identity.nickname.c_str(), - identity.username.c_str(), - identity.realname.c_str()); - } - - return code == 0; -} - -void ServerState::prepareConnected(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) -{ - if (!irc_is_connected(server.session())) { - const ServerSettings &settings = server.settings(); - - log::warning() << "server " << server.info().name << ": disconnected" << std::endl; - - if (settings.recotimeout > 0) { - log::warning() << "server " << server.info().name << ": retrying in " - << settings.recotimeout << " seconds" << std::endl; - } - - server.next(ServerState::Disconnected); - } else { - irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd)); - } -} - -void ServerState::prepareConnecting(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) -{ - /* - * The connect function will either fail if the hostname wasn't resolved - * or if any of the internal functions fail. - * - * It returns success if the connection was successful but it does not - * mean that connection is established. - * - * Because this function will be called repeatidly, the connection was started and we're still not - * connected in the specified timeout time, we mark the server as disconnected. - * - * Otherwise, the libircclient event_connect will change the state. - */ - const ServerInfo &info = server.info(); - - if (m_started) { - const ServerSettings &settings = server.settings(); - - if (m_timer.elapsed() > static_cast<unsigned>(settings.recotimeout * 1000)) { - log::warning() << "server " << info.name << ": timeout while connecting" << std::endl; - server.next(ServerState::Disconnected); - } else if (!irc_is_connected(server.session())) { - log::warning() << "server " << info.name << ": error while connecting: "; - log::warning() << irc_strerror(irc_errno(server.session())) << std::endl; - - if (settings.recotries != 0) - log::warning() << "server " << info.name << ": retrying in " << settings.recotimeout << " seconds" << std::endl; - - server.next(ServerState::Disconnected); - } else { - irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd)); - } - } else { - /* - * This is needed if irccd is started before DHCP or if - * DNS cache is outdated. - * - * For more information see bug #190. - */ -#if !defined(_WIN32) - (void)res_init(); -#endif - log::info() << "server " << info.name << ": trying to connect to " << info.host << ", port " << info.port << std::endl; - - if (!connect(server)) { - log::warning() << "server " << info.name << ": disconnected while connecting: "; - log::warning() << irc_strerror(irc_errno(server.session())) << std::endl; - server.next(ServerState::Disconnected); - } else { - m_started = true; - } - } -} - -void ServerState::prepareDisconnected(Server &server, fd_set &, fd_set &, net::Handle &) -{ - const ServerInfo &info = server.info(); - ServerSettings &settings = server.settings(); - - if (settings.recotries == 0) { - log::warning() << "server " << info.name << ": reconnection disabled, skipping" << std::endl; - server.onDie(); - } else if (settings.recotries > 0 && settings.recocurrent > settings.recotries) { - log::warning() << "server " << info.name << ": giving up" << std::endl; - server.onDie(); - } else { - if (m_timer.elapsed() > static_cast<unsigned>(settings.recotimeout * 1000)) { - irc_disconnect(server.session()); - - settings.recocurrent ++; - server.next(ServerState::Connecting); - } - } -} - -ServerState::ServerState(Type type) - : m_type(type) -{ - assert(static_cast<int>(m_type) >= static_cast<int>(ServerState::Undefined)); - assert(static_cast<int>(m_type) <= static_cast<int>(ServerState::Disconnected)); -} - -void ServerState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) -{ - switch (m_type) { - case Connecting: - prepareConnecting(server, setinput, setoutput, maxfd); - break; - case Connected: - prepareConnected(server, setinput, setoutput, maxfd); - break; - case Disconnected: - prepareDisconnected(server, setinput, setoutput, maxfd); - break; - default: - break; - } -} - -} // !irccd
--- a/lib/irccd/server-state.hpp Mon Apr 25 21:16:47 2016 +0200 +++ b/lib/irccd/server-state.hpp Tue Apr 26 13:19:35 2016 +0200 @@ -39,39 +39,14 @@ class ServerState { public: /** - * \enum Type - * \brief Server state + * Default constructor. */ - enum Type { - Undefined, //!< Not defined yet - Connecting, //!< Connecting to the server - Connected, //!< Connected and running - Disconnected, //!< Disconnected and waiting before retrying - }; - -private: - Type m_type; - - /* For ServerState::Connecting */ - bool m_started{false}; - ElapsedTimer m_timer; + ServerState() = default; - /* Private helpers */ - bool connect(Server &server); - - /* Different preparation */ - void prepareConnected(Server &, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd); - void prepareConnecting(Server &, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd); - void prepareDisconnected(Server &, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd); - -public: /** - * Create the server state. - * - * \pre type must be valid - * \param type the type + * Virtual default destructor. */ - ServerState(Type type); + virtual ~ServerState() = default; /** * Prepare the state. @@ -81,17 +56,14 @@ * \param setoutput the write set * \param maxfd the maximum fd */ - void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd); + virtual void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) = 0; /** - * Get the state type. + * Return the state identifier, only for information purposes. * - * \return the type + * \return the identifier */ - inline Type type() const noexcept - { - return m_type; - } + virtual std::string ident() const = 0; }; } // !irccd
--- a/lib/irccd/server.cpp Mon Apr 25 21:16:47 2016 +0200 +++ b/lib/irccd/server.cpp Tue Apr 26 13:19:35 2016 +0200 @@ -25,6 +25,8 @@ #include "logger.hpp" #include "server-private.hpp" +#include "server-state-connected.hpp" +#include "server-state-connecting.hpp" #include "util.hpp" namespace irccd { @@ -87,8 +89,11 @@ /* Reset the number of tried reconnection. */ m_settings.recocurrent = 0; + /* Reset the timer. */ + m_ping_timer.reset(); + /* Don't forget to change state and notify. */ - next(ServerState::Connected); + next(std::make_unique<state::Connected>()); onConnect(); /* Auto join listed channels. */ @@ -286,6 +291,15 @@ onPart(strify(orig), strify(params[0]), strify(params[1])); } +void Server::handlePing(const char *, const char **params) noexcept +{ + /* Reset the timer to detect disconnection. */ + m_ping_timer.reset(); + + /* Don't forget to respond */ + send(params[0]); +} + void Server::handleQuery(const char *orig, const char **params) noexcept { onQuery(strify(orig), strify(params[1])); @@ -311,8 +325,7 @@ , m_settings(std::move(settings)) , m_identity(std::move(identity)) , m_session(std::make_unique<Session>()) - , m_state(ServerState::Connecting) - , m_next(ServerState::Undefined) + , m_state(std::make_unique<state::Connecting>()) { irc_callbacks_t callbacks; @@ -364,6 +377,9 @@ callbacks.event_part = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) { static_cast<Server *>(irc_get_ctx(session))->handlePart(orig, params); }; + callbacks.event_ping = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) { + static_cast<Server *>(irc_get_ctx(session))->handlePing(orig, params); + }; callbacks.event_privmsg = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) { static_cast<Server *>(irc_get_ctx(session))->handleQuery(orig, params); }; @@ -388,25 +404,12 @@ void Server::update() noexcept { - if (m_next.type() != ServerState::Undefined) { - log::debug() << "server " << m_info.name << ": switching to state "; + if (m_state_next) { + log::debug() << "server " << m_info.name << ": switching state " + << m_state->ident() << " -> " << m_state_next->ident() << std::endl; - switch (m_next.type()) { - case ServerState::Connecting: - log::debug() << "\"Connecting\"" << std::endl; - break; - case ServerState::Connected: - log::debug() << "\"Connected\"" << std::endl; - break; - case ServerState::Disconnected: - log::debug() << "\"Disconnected\"" << std::endl; - break; - default: - break; - } - - m_state = std::move(m_next); - m_next = ServerState::Undefined; + m_state = std::move(m_state_next); + m_state_next = nullptr; } } @@ -421,14 +424,14 @@ void Server::reconnect() noexcept { irc_disconnect(*m_session); - next(ServerState::Type::Connecting); + next(std::make_unique<state::Connecting>()); } void Server::sync(fd_set &setinput, fd_set &setoutput) noexcept { /* - * 1. Send maximum of command possible if available for write */ - /* + * 1. Send maximum of command possible if available for write + * * Break on the first failure to avoid changing the order of the * commands if any of them fails. */
--- a/lib/irccd/server.hpp Mon Apr 25 21:16:47 2016 +0200 +++ b/lib/irccd/server.hpp Tue Apr 26 13:19:35 2016 +0200 @@ -35,6 +35,7 @@ #include <utility> #include <vector> +#include "elapsed-timer.hpp" #include "logger.hpp" #include "server-state.hpp" #include "signals.hpp" @@ -139,6 +140,8 @@ std::uint16_t recotimeout{30}; //!< Number of seconds to wait before trying to connect std::uint8_t flags{0}; //!< Optional flags + std::uint16_t ping_timeout{300}; //!< Ping timeout in milliseconds + /* Private */ std::int8_t recocurrent{1}; //!< number of tries tested }; @@ -399,10 +402,17 @@ ServerSettings m_settings; ServerIdentity m_identity; SessionPtr m_session; - ServerState m_state; - ServerState m_next; Queue m_queue; + /* States */ + std::unique_ptr<ServerState> m_state; + std::unique_ptr<ServerState> m_state_next; + + /* + * Detect timeout from server. + */ + ElapsedTimer m_ping_timer; + /* * The names map is being built by a successive call to handleNumeric so we need to store a temporary * map by channels to list of names. Then, when we receive the end of names listing, we remove the @@ -433,6 +443,7 @@ void handleNotice(const char *, const char **) noexcept; void handleNumeric(unsigned int, const char **, unsigned int) noexcept; void handlePart(const char *, const char **) noexcept; + void handlePing(const char *, const char **) noexcept; void handleQuery(const char *, const char **) noexcept; void handleTopic(const char *, const char **) noexcept; @@ -460,18 +471,23 @@ virtual ~Server(); /** - * Set the next state to be used. This function is thread safe because - * the server manager may set the next state to the current state. - * - * If the server is installed into the ServerManager, it is called - * automatically. + * Set the next state, it is not changed immediately but on next iteration. * - * \param type the new state type - * \warning Not thread-safe + * \param state the new state */ - inline void next(ServerState::Type type) + inline void next(std::unique_ptr<ServerState> state) { - m_next = ServerState(type); + m_state_next = std::move(state); + } + + /** + * Get the ping timer. + * + * \return the ping timer + */ + inline ElapsedTimer &pingTimer() noexcept + { + return m_ping_timer; } /** @@ -513,16 +529,13 @@ void flush() noexcept; /** - * Prepare the IRC Session to the socket. - * - * If the server is installed into the ServerManager, it is called - * automatically. + * Prepare the IRC session. * * \warning Not thread-safe */ inline void prepare(fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) noexcept { - m_state.prepare(*this, setinput, setoutput, maxfd); + m_state->prepare(*this, setinput, setoutput, maxfd); } /** @@ -602,16 +615,6 @@ } /** - * Get the current state identifier. Should not be used by user code. - * - * \note Thread-safe but the state may change just after the call - */ - inline ServerState::Type type() const noexcept - { - return m_state.type(); - } - - /** * Get the private session. * * \return the session