# HG changeset patch # User David Demelier # Date 1541706499 -3600 # Node ID 3c090c1ff4f02fd42721e708ccf95ec9d68b599d # Parent 86c9d58ed3eece9523ed4507ebd9a008bed60f3b irccd: support IPv4, IPv6 in IRC servers, closes #945 @2h diff -r 86c9d58ed3ee -r 3c090c1ff4f0 MIGRATING.md --- a/MIGRATING.md Thu Nov 08 07:03:37 2018 +0100 +++ b/MIGRATING.md Thu Nov 08 20:48:19 2018 +0100 @@ -6,6 +6,15 @@ Migrating from 2.x to 3.x ------------------------- +### Irccd + +- The option `reconnect-tries` has been removed from `[server]` section, use + `auto-reconnect` boolean option instead, +- The option `ipv6` has been removed, use `family` instead, +- The option `reconnect-timeout` has been renamed to `auto-reconnect-delay`. +- The section `[identity]` has been removed, instead move those values inside + each server in their `[server]` section. + ### Irccdctl - The functions `server-cnotice` and `server-cmode` have been removed, use @@ -28,8 +37,10 @@ - The request `server-mode` command requires a new argument `channel`. - The property `host` in request `server-connect` has been renamed to `hostname`, +- The property `ipv6` in request `server-connect` has been renamed to + `family`, - The request `server-info` sends `hostname` property instead of `host`, -- The event `onWhois` sends `hostname` property instead of `host`. +- The event `onWhois` sends `hostname` property instead of `host`, ### CMake options @@ -61,6 +72,10 @@ - The method `Server.mode` requires a new argument `channel`, - The object returned in the method `Server.info` now has a `hostname` property instead of `host`. +- The property `host` in constructor `Server` has been renamed to + `hostname`, +- The property `ipv6` in constructor `Server` has been renamed to + `family`, #### Module ElapsedTimer @@ -90,13 +105,3 @@ #### Module System - The function `Irccd.System.name` has now well defined return value. - -### Irccd - -#### Configuration - -- The option `reconnect-tries` has been removed from `[server]` section, use - `auto-reconnect` boolean option instead. -- The option `reconnect-timeout` has been renamed to `auto-reconnect-delay`. -- The section `[identity]` has been removed, instead move those values inside - each server in their `[server]` section. diff -r 86c9d58ed3ee -r 3c090c1ff4f0 irccdctl/cli.cpp --- a/irccdctl/cli.cpp Thu Nov 08 07:03:37 2018 +0100 +++ b/irccdctl/cli.cpp Thu Nov 08 20:48:19 2018 +0100 @@ -210,6 +210,8 @@ auto parse(std::vector &args) -> option::result { option::options options{ + { "-4", false }, + { "-6", false }, { "-c", true }, { "--command", true }, { "-n", true }, @@ -775,7 +777,7 @@ auto object = nlohmann::json::object({ { "command", "server-connect" }, { "name", copy[0] }, - { "hostname", copy[1] } + { "hostname", copy[1] }, }); if (copy.size() == 3) { @@ -798,6 +800,12 @@ if ((it = result.find("-u")) != result.end() || (it = result.find("--username")) != result.end()) object["username"] = it->second; + // Mutually exclusive. + if ((it = result.find("-4")) != result.end()) + object["family"] = nlohmann::json::array({ "ipv4" }); + if ((it = result.find("-6")) != result.end()) + object["family"] = nlohmann::json::array({ "ipv6" }); + request(ctl, object); } diff -r 86c9d58ed3ee -r 3c090c1ff4f0 libirccd/irccd/daemon/server.cpp --- a/libirccd/irccd/daemon/server.cpp Thu Nov 08 07:03:37 2018 +0100 +++ b/libirccd/irccd/daemon/server.cpp Thu Nov 08 20:48:19 2018 +0100 @@ -172,7 +172,7 @@ auto server::dispatch_invite(const irc::message& msg, const recv_handler& handler) -> bool { // If join-invite is set, join the channel. - if ((flags_ & options::join_invite) == options::join_invite && is_self(msg.get(0))) + if ((options_ & options::join_invite) == options::join_invite && is_self(msg.get(0))) join(msg.get(1)); handler({}, invite_event{shared_from_this(), msg.prefix, msg.get(1), msg.get(0)}); @@ -209,7 +209,7 @@ jchannels_.erase(msg.get(0)); // Rejoin the channel if the option has been set and I was kicked. - if ((flags_ & options::auto_rejoin) == options::auto_rejoin) + if ((options_ & options::auto_rejoin) == options::auto_rejoin) join(msg.get(0)); } @@ -500,6 +500,7 @@ server::server(boost::asio::io_service& service, std::string id, std::string hostname) : id_(std::move(id)) , hostname_(std::move(hostname)) + , options_(options::ipv4 | options::ipv6) , service_(service) , timer_(service) { @@ -555,7 +556,7 @@ auto server::get_options() const noexcept -> options { - return flags_; + return options_; } void server::set_options(options flags) noexcept @@ -564,7 +565,7 @@ assert((flags & options::ssl) != options::ssl); #endif - flags_ = flags; + options_ = flags; } auto server::get_nickname() const noexcept -> const std::string& @@ -655,6 +656,7 @@ void server::connect(connect_handler handler) noexcept { assert(state_ == state::disconnected); + assert((options_ & options::ipv4) == options::ipv4 || (options_ & options::ipv6) == options::ipv6); /* * This is needed if irccd is started before DHCP or if DNS cache is @@ -664,9 +666,10 @@ (void)res_init(); #endif - // TODO: use_ipv4, use_ipv6. conn_ = std::make_unique(service_); - conn_->use_ssl((flags_ & options::ssl) == options::ssl); + conn_->use_ssl((options_ & options::ssl) == options::ssl); + conn_->use_ipv4((options_ & options::ipv4) == options::ipv4); + conn_->use_ipv6((options_ & options::ipv6) == options::ipv6); jchannels_.clear(); state_ = state::connecting; @@ -881,6 +884,8 @@ return "invalid message"; case server_error::ssl_disabled: return "ssl is not enabled"; + case server_error::invalid_family: + return "invalid family"; default: return "no error"; } diff -r 86c9d58ed3ee -r 3c090c1ff4f0 libirccd/irccd/daemon/server.hpp --- a/libirccd/irccd/daemon/server.hpp Thu Nov 08 07:03:37 2018 +0100 +++ b/libirccd/irccd/daemon/server.hpp Thu Nov 08 20:48:19 2018 +0100 @@ -251,12 +251,13 @@ */ enum class options : std::uint8_t { none = 0, //!< No options - ipv6 = (1 << 0), //!< Connect using IPv6 - ssl = (1 << 1), //!< Use SSL - ssl_verify = (1 << 2), //!< Verify SSL - auto_rejoin = (1 << 3), //!< Auto rejoin a kick - auto_reconnect = (1 << 4), //!< Auto reconnect on disconnection - join_invite = (1 << 5) //!< Join a channel on invitation + ipv4 = (1 << 0), //!< Connect using IPv4 + ipv6 = (1 << 1), //!< Connect using IPv6 + ssl = (1 << 2), //!< Use SSL + ssl_verify = (1 << 3), //!< Verify SSL + auto_rejoin = (1 << 4), //!< Auto rejoin a kick + auto_reconnect = (1 << 5), //!< Auto reconnect on disconnection + join_invite = (1 << 6) //!< Join a channel on invitation }; /** @@ -287,7 +288,7 @@ std::string hostname_; std::string password_; std::uint16_t port_{6667}; - options flags_{options::none}; + options options_; // Identity. std::string nickname_; @@ -837,6 +838,9 @@ //!< SSL was requested but is disabled. ssl_disabled, + + //!< IPv4 or IPv6 must be defined. + invalid_family }; public: diff -r 86c9d58ed3ee -r 3c090c1ff4f0 libirccd/irccd/daemon/server_util.cpp --- a/libirccd/irccd/daemon/server_util.cpp Thu Nov 08 07:03:37 2018 +0100 +++ b/libirccd/irccd/daemon/server_util.cpp Thu Nov 08 20:48:19 2018 +0100 @@ -71,15 +71,28 @@ void from_config_load_flags(server& sv, const ini::section& sc) { - const auto ipv6 = sc.get("ipv6"); const auto ssl = sc.get("ssl"); const auto ssl_verify = sc.get("ssl-verify"); const auto auto_rejoin = sc.get("auto-rejoin"); const auto auto_reconnect = sc.get("auto-reconnect"); const auto join_invite = sc.get("join-invite"); - if (string_util::is_boolean(ipv6.get_value())) - sv.set_options(sv.get_options() | server::options::ipv6); + if (const auto family = sc.find("family"); family != sc.end()) { + sv.set_options(sv.get_options() & ~(server::options::ipv4)); + sv.set_options(sv.get_options() & ~(server::options::ipv6)); + + for (const auto& v : *family) { + if (v == "ipv4") + sv.set_options(sv.get_options() | server::options::ipv4); + if (v == "ipv6") + sv.set_options(sv.get_options() | server::options::ipv6); + } + } + + if ((sv.get_options() & server::options::ipv4) != server::options::ipv4 && + (sv.get_options() & server::options::ipv6) != server::options::ipv6) + throw server_error(server_error::invalid_family); + if (string_util::is_boolean(ssl.get_value())) sv.set_options(sv.get_options() | server::options::ssl); if (string_util::is_boolean(ssl_verify.get_value())) @@ -129,6 +142,25 @@ const auto command = parser.optional("commandChar", sv.get_command_char()); const auto password = parser.optional("password", sv.get_password()); + if (const auto family = parser.find("family"); family != parser.end()) { + if (!family->is_array()) + throw server_error(server_error::invalid_family); + + sv.set_options(sv.get_options() & ~(server::options::ipv4)); + sv.set_options(sv.get_options() & ~(server::options::ipv6)); + + for (const auto& v : *family) { + if (v.is_string() && v.get() == "ipv4") + sv.set_options(sv.get_options() | server::options::ipv4); + if (v.is_string() && v.get() == "ipv6") + sv.set_options(sv.get_options() | server::options::ipv6); + } + } + + if ((sv.get_options() & server::options::ipv4) != server::options::ipv4 && + (sv.get_options() & server::options::ipv6) != server::options::ipv6) + throw server_error(server_error::invalid_family); + if (!port || *port > std::numeric_limits::max()) throw server_error(server_error::invalid_port); if (!nickname) @@ -155,14 +187,11 @@ void from_json_load_flags(server& sv, const deserializer& parser) { - const auto ipv6 = parser.get("ipv6"); const auto auto_rejoin = parser.get("autoRejoin"); const auto join_invite = parser.get("joinInvite"); const auto ssl = parser.get("ssl"); const auto ssl_verify = parser.get("sslVerify"); - if (ipv6.value_or(false)) - sv.set_options(sv.get_options() | server::options::ipv6); if (auto_rejoin.value_or(false)) sv.set_options(sv.get_options() | server::options::auto_rejoin); if (join_invite.value_or(false)) diff -r 86c9d58ed3ee -r 3c090c1ff4f0 tests/src/libirccd/command-server-connect/main.cpp --- a/tests/src/libirccd/command-server-connect/main.cpp Thu Nov 08 07:03:37 2018 +0100 +++ b/tests/src/libirccd/command-server-connect/main.cpp Thu Nov 08 20:48:19 2018 +0100 @@ -58,6 +58,7 @@ { "nickname", "francis" }, { "realname", "the_francis" }, { "username", "frc" }, + { "family", { "ipv6" } }, { "ctcpVersion", "ultra bot" }, { "commandChar", "::" }, { "port", 18000 }, @@ -80,6 +81,8 @@ BOOST_TEST(s->get_username() == "frc"); BOOST_TEST(s->get_command_char() == "::"); BOOST_TEST(s->get_ctcp_version() == "ultra bot"); + BOOST_TEST(!static_cast(s->get_options() & server::options::ipv4)); + BOOST_TEST(static_cast(s->get_options() & server::options::ipv6)); BOOST_TEST(static_cast(s->get_options() & server::options::ssl)); BOOST_TEST(static_cast(s->get_options() & server::options::ssl_verify)); BOOST_TEST(static_cast(s->get_options() & server::options::auto_rejoin)); @@ -216,6 +219,36 @@ #endif +BOOST_AUTO_TEST_CASE(invalid_family_1) +{ + const auto [json, code] = request({ + { "command", "server-connect" }, + { "name", "new" }, + { "hostname", "127.0.0.1" }, + { "port", 1000000 }, + { "family", "invalid" } + }); + + BOOST_TEST(code == server_error::invalid_family); + BOOST_TEST(json["error"].get() == server_error::invalid_family); + BOOST_TEST(json["errorCategory"].get() == "server"); +} + +BOOST_AUTO_TEST_CASE(invalid_family_2) +{ + const auto [json, code] = request({ + { "command", "server-connect" }, + { "name", "new" }, + { "hostname", "127.0.0.1" }, + { "port", 1000000 }, + { "family", { 123, 456 } } + }); + + BOOST_TEST(code == server_error::invalid_family); + BOOST_TEST(json["error"].get() == server_error::invalid_family); + BOOST_TEST(json["errorCategory"].get() == "server"); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()