changeset 788:3c090c1ff4f0

irccd: support IPv4, IPv6 in IRC servers, closes #945 @2h
author David Demelier <markand@malikania.fr>
date Thu, 08 Nov 2018 20:48:19 +0100
parents 86c9d58ed3ee
children 17367b6a99b4
files MIGRATING.md irccdctl/cli.cpp libirccd/irccd/daemon/server.cpp libirccd/irccd/daemon/server.hpp libirccd/irccd/daemon/server_util.cpp tests/src/libirccd/command-server-connect/main.cpp
diffstat 6 files changed, 115 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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.
--- 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<std::string> &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);
 }
 
--- 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<irc::connection>(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";
 			}
--- 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:
--- 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<std::string>("commandChar", sv.get_command_char());
 	const auto password = parser.optional<std::string>("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<std::string>() == "ipv4")
+				sv.set_options(sv.get_options() | server::options::ipv4);
+			if (v.is_string() && v.get<std::string>() == "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<std::uint16_t>::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<bool>("ipv6");
 	const auto auto_rejoin = parser.get<bool>("autoRejoin");
 	const auto join_invite = parser.get<bool>("joinInvite");
 	const auto ssl = parser.get<bool>("ssl");
 	const auto ssl_verify = parser.get<bool>("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))
--- 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<bool>(s->get_options() & server::options::ipv4));
+	BOOST_TEST(static_cast<bool>(s->get_options() & server::options::ipv6));
 	BOOST_TEST(static_cast<bool>(s->get_options() & server::options::ssl));
 	BOOST_TEST(static_cast<bool>(s->get_options() & server::options::ssl_verify));
 	BOOST_TEST(static_cast<bool>(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<int>() == server_error::invalid_family);
+	BOOST_TEST(json["errorCategory"].get<std::string>() == "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<int>() == server_error::invalid_family);
+	BOOST_TEST(json["errorCategory"].get<std::string>() == "server");
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 BOOST_AUTO_TEST_SUITE_END()