← Back to team overview

linuxdcpp-team team mailing list archive

[Merge] lp:~maksis/adchpp/adchpp-hbri into lp:adchpp

 

maksis has proposed merging lp:~maksis/adchpp/adchpp-hbri into lp:adchpp.

Requested reviews:
  Dcplusplus-team (dcplusplus-team)

For more details, see:
https://code.launchpad.net/~maksis/adchpp/adchpp-hbri/+merge/194959

* Add support for hybrid IPv4/IPv6 client connectivity: http://www.dcbase.org/forum/viewtopic.php?f=55&t=771
* [L#1088638] Allow clients to reconnect in event of overflow
* [L#1106226] Fix a crash when joining the hub with a local IPv6 address
* Make login timeout work independently from other connecting users
* Allow configuring custom bind addresses
-- 
https://code.launchpad.net/~maksis/adchpp/adchpp-hbri/+merge/194959
Your team Dcplusplus-team is requested to review the proposed merge of lp:~maksis/adchpp/adchpp-hbri into lp:adchpp.
=== modified file 'adchpp/AdcCommand.cpp'
--- adchpp/AdcCommand.cpp	2013-01-18 21:41:53 +0000
+++ adchpp/AdcCommand.cpp	2013-11-12 23:29:50 +0000
@@ -40,7 +40,7 @@
 AdcCommand::AdcCommand() : cmdInt(0), priority(PRIORITY_NORMAL), from(INVALID_SID), to(INVALID_SID), type(TYPE_INFO) { }
 
 AdcCommand::AdcCommand(Severity sev, Error err, const string& desc, char aType /* = TYPE_INFO */) : cmdInt(CMD_STA), priority(PRIORITY_NORMAL), from(HUB_SID), to(INVALID_SID), type(aType) {
-	addParam(Util::toString(sev * 100 + err));
+	addParam((sev == SEV_SUCCESS && err == SUCCESS) ? "000" : Util::toString(sev * 100 + err));
 	addParam(desc);
 }
 

=== modified file 'adchpp/AdcCommand.h'
--- adchpp/AdcCommand.h	2013-01-18 21:41:53 +0000
+++ adchpp/AdcCommand.h	2013-11-12 23:29:50 +0000
@@ -36,6 +36,7 @@
 	};
 
 	enum Error {
+		SUCCESS = 0,
 		ERROR_GENERIC = 0,
 		ERROR_HUB_GENERIC = 10,
 		ERROR_HUB_FULL = 11,
@@ -63,7 +64,8 @@
 		ERROR_FILE_NOT_AVAILABLE = 51,
 		ERROR_FILE_PART_NOT_AVAILABLE = 52,
 		ERROR_SLOTS_FULL = 53,
-		ERROR_NO_CLIENT_HASH = 54
+		ERROR_NO_CLIENT_HASH = 54,
+		ERROR_HBRI_TIMEOUT = 55
 	};
 
 	enum Severity {
@@ -109,6 +111,7 @@
 	C(CMD, 'C','M','D');
 	C(NAT, 'N','A','T');
 	C(RNT, 'R','N','T');
+	C(TCP, 'T','C','P');
 #undef C
 
 	static const uint32_t HUB_SID = static_cast<uint32_t>(-1);
@@ -225,6 +228,7 @@
 			C(CMD);
 			C(NAT);
 			C(RNT);
+			C(TCP);
 			default:
 				dcdebug("Unknown ADC command: %.50s\n", cmd.toString().c_str());
 				return true;

=== modified file 'adchpp/Client.h'
--- adchpp/Client.h	2013-03-07 00:41:35 +0000
+++ adchpp/Client.h	2013-11-12 23:29:50 +0000
@@ -43,6 +43,8 @@
 	/** @param reason The statistic to update */
 	ADCHPP_DLL virtual void disconnect(Util::Reason reason, const std::string &info = Util::emptyString) throw();
 	const std::string& getIp() const throw() { return socket->getIp(); }
+	bool getHbriParams(AdcCommand& cmd) const throw() { return socket->getHbriParams(cmd); }
+	bool isV6() const { return socket->isV6(); }
 
 	/**
 	 * Set data mode for aBytes bytes.

=== modified file 'adchpp/ClientManager.cpp'
--- adchpp/ClientManager.cpp	2013-07-16 22:48:12 +0000
+++ adchpp/ClientManager.cpp	2013-11-12 23:29:50 +0000
@@ -20,6 +20,7 @@
 
 #include "ClientManager.h"
 
+#include "Core.h"
 #include "File.h"
 #include "Client.h"
 #include "LogManager.h"
@@ -33,6 +34,15 @@
 #include <boost/asio/ip/address_v6.hpp>
 #include <boost/locale.hpp>
 
+#include <boost/range/algorithm/find.hpp>
+#include <boost/range/algorithm/find_if.hpp>
+#include <boost/range/algorithm/remove_if.hpp>
+#include <boost/range/adaptor/map.hpp>
+
+using boost::adaptors::map_values;
+using boost::range::find;
+using boost::range::find_if;
+
 namespace adchpp {
 
 using namespace std;
@@ -43,10 +53,52 @@
 core(core),
 hub(*this),
 maxCommandSize(16 * 1024),
-logTimeout(30 * 1000)
+logTimeout(30 * 1000),
+hbriTimeout(5000)
 {
+	core.getSocketManager().addTimedJob(1000, std::bind(&ClientManager::onTimerSecond, this));
+}
+
+void ClientManager::prepareSupports(bool addHbri) {
 	hub.addSupports(AdcCommand::toFourCC("BASE"));
 	hub.addSupports(AdcCommand::toFourCC("TIGR"));
+
+	if (addHbri)
+		hub.addSupports(AdcCommand::toFourCC("HBRI"));
+}
+
+void ClientManager::onTimerSecond() {
+	// HBRI
+	auto timeoutHbri = time::now() - time::millisec(hbriTimeout);
+	for (auto i = hbriTokens.begin(); i != hbriTokens.end();) {
+		if (timeoutHbri > i->second.second) {
+			auto cc = dynamic_cast<Client*>(i->second.first);
+			i = hbriTokens.erase(i);
+
+			dcdebug("ClientManager: HBRI timeout in state %d\n", cc->getState());
+
+			std::string proto = cc->isV6() ? "IPv4" : "IPv6";
+			AdcCommand sta(AdcCommand::SEV_RECOVERABLE, AdcCommand::ERROR_HBRI_TIMEOUT, proto + " validation timed out");
+			cc->send(sta);
+
+			cc->unsetFlag(Entity::FLAG_VALIDATE_HBRI);
+			cc->stripProtocolSupports();
+			if (cc->getState() == Entity::STATE_HBRI)
+				enterNormal(*cc, true, true);
+		} else {
+			i++;
+		}
+	}
+
+	// Logins
+	auto timeoutLogin = time::now() - time::millisec(getLogTimeout());
+	while (!logins.empty() && (timeoutLogin > logins.front().second)) {
+		auto cc = logins.front().first;
+
+		dcdebug("ClientManager: Login timeout in state %d\n", cc->getState());
+		cc->disconnect(Util::REASON_LOGIN_TIMEOUT);
+		logins.pop_front();
+	}
 }
 
 Bot* ClientManager::createBot(const Bot::SendHandler& handler) {
@@ -149,16 +201,6 @@
 
 void ClientManager::onConnected(Client& c) throw() {
 	dcdebug("%s connected\n", AdcCommand::fromSID(c.getSID()).c_str());
-	// First let's check if any clients have passed the login timeout...
-	auto timeout = time::now() - time::millisec(getLogTimeout());
-
-	while(!logins.empty() && (timeout > logins.front().second)) {
-		Client* cc = logins.front().first;
-
-		dcdebug("ClientManager: Login timeout in state %d\n", cc->getState());
-		cc->disconnect(Util::REASON_LOGIN_TIMEOUT);
-		logins.pop_front();
-	}
 
 	logins.push_back(make_pair(&c, time::now()));
 
@@ -225,6 +267,7 @@
 		badState(c, cmd);
 		return false;
 	}
+
 	return true;
 }
 
@@ -245,26 +288,30 @@
 }
 
 bool ClientManager::verifyINF(Entity& c, AdcCommand& cmd) throw() {
+	if (!verifyCID(c, cmd))
+		return false;
+
+	if (!verifyNick(c, cmd))
+		return false;
+
+	if (cmd.getParam("DE", 0, strtmp)) {
+		if (!Util::validateCharset(strtmp, 32)) {
+			disconnect(c, Util::REASON_INVALID_DESCRIPTION, "Invalid character in description");
+			return false;
+		}
+	}
+
 	Client* cc = dynamic_cast<Client*>(&c);
 
 	if(cc) {
-		if(!verifyIp(*cc, cmd))
-			return false;
-	}
-
-	if(!verifyCID(c, cmd))
-		return false;
-
-	if(!verifyNick(c, cmd))
-		return false;
-	
-	if(cmd.getParam("DE", 0, strtmp)) {
-		if(!Util::validateCharset(strtmp, 32)) {
-			disconnect(c, Util::REASON_INVALID_DESCRIPTION, "Invalid character in description");
-			return false;
-		}
-	}
+		if(!verifyIp(*cc, cmd, false))
+			return false;
+	}
+
 	c.updateFields(cmd);
+	if (!c.isSet(Entity::FLAG_VALIDATE_HBRI) && c.getState() != Entity::STATE_HBRI)
+		c.stripProtocolSupports();
+
 	return true;
 }
 
@@ -307,13 +354,35 @@
 	}
 
 	if(overflowing > 3 && overflowing > (entities.size() / 4)) {
-		disconnect(c, Util::REASON_NO_BANDWIDTH, "Not enough bandwidth available, please try again later", AdcCommand::ERROR_HUB_FULL);
+		disconnect(c, Util::REASON_NO_BANDWIDTH, "Not enough bandwidth available, please try again later", AdcCommand::ERROR_HUB_FULL, Util::emptyString, 1);
 		return false;
 	}
 
 	return true;
 }
 
+bool ClientManager::sendHBRI(Entity& c) {
+	if (c.hasSupport(AdcCommand::toFourCC("HBRI"))) {
+		AdcCommand cmd(AdcCommand::CMD_TCP);
+		if (!dynamic_cast<Client*>(&c)->getHbriParams(cmd)) {
+			return false;
+		}
+
+		c.setFlag(Entity::FLAG_VALIDATE_HBRI);
+		if (c.getState() != Entity::STATE_NORMAL)
+			c.setState(Entity::STATE_HBRI);
+
+		auto token = Util::toString(Util::rand());
+		hbriTokens.insert(make_pair(token, make_pair(&c, time::now())));
+
+		cmd.addParam("TO", token);
+		c.send(cmd);
+		return true;
+	}
+
+	return false;
+}
+
 bool ClientManager::handle(AdcCommand::INF, Entity& c, AdcCommand& cmd) throw() {
 	if(c.getState() != Entity::STATE_IDENTIFY && c.getState() != Entity::STATE_NORMAL) {
 		badState(c, cmd);
@@ -335,23 +404,98 @@
 	return true;
 }
 
-bool ClientManager::verifyIp(Client& c, AdcCommand& cmd) throw() {
+static const int allowedCount = 3;
+static const char* allowedV4[allowedCount] = { "I4", "U4", "SU" };
+static const char* allowedV6[allowedCount] = { "I6", "U6", "SU" };
+bool ClientManager::handle(AdcCommand::TCP, Entity& c, AdcCommand& cmd) throw() {
+	dcdebug("Received HBRI TCP: %s", cmd.toString().c_str());
+	
+	string error;
+	string token;
+	if(cmd.getParam("TO", 0, token)) {
+		auto p = hbriTokens.find(token);
+		if (p != hbriTokens.end()) {
+			Client* mainCC = dynamic_cast<Client*>(p->second.first);
+			mainCC->unsetFlag(Entity::FLAG_VALIDATE_HBRI);
+
+			if (mainCC->getState() != Entity::STATE_HBRI && mainCC->getState() != Entity::STATE_NORMAL) {
+				badState(c, cmd);
+				return false;
+			}
+
+			hbriTokens.erase(p);
+
+			if (!verifyIp(*dynamic_cast<Client*>(&c), cmd, true))
+				return false;
+
+			// disconnect the validation connection
+			AdcCommand sta(AdcCommand::SEV_SUCCESS, AdcCommand::SUCCESS, "Validation succeed");
+			c.send(sta);
+			c.disconnect(Util::REASON_HBRI);
+
+			// remove extra parameters
+			auto& params = cmd.getParameters();
+			const auto& allowed = dynamic_cast<Client*>(&c)->isV6() ? allowedV6 : allowedV4;
+
+			params.erase(boost::remove_if(params, [&](const string& s) {
+				return find(allowed, allowed + allowedCount, s.substr(0, 2)) == &allowed[allowedCount];
+			}), params.end());
+
+			// update the fields for the main entity
+			mainCC->updateFields(cmd);
+
+			if (mainCC->getState() == Entity::STATE_HBRI) {
+				// continue with the normal login
+				enterNormal(*mainCC, true, true);
+			} else {
+				// send the updated fields
+				AdcCommand inf(AdcCommand::CMD_INF, AdcCommand::TYPE_BROADCAST, mainCC->getSID());
+				inf.getParameters() = cmd.getParameters();
+				sendToAll(inf.getBuffer());
+			}
+			return true;
+		} else {
+			error = "Unknown validation token";
+		}
+	} else {
+		error = "Validation token missing";
+	}
+
+	dcassert(!error.empty());
+	AdcCommand sta(AdcCommand::SEV_FATAL, AdcCommand::ERROR_LOGIN_GENERIC, error);
+	c.send(sta);
+
+	c.disconnect(Util::REASON_HBRI);
+	return true;
+}
+
+bool ClientManager::verifyIp(Client& c, AdcCommand& cmd, bool isHbriConn) throw() {
 	if(c.isSet(Entity::FLAG_OK_IP))
 		return true;
 
 	using namespace boost::asio::ip;
 
-	auto remote = address::from_string(c.getIp());
+    address remote;
+
+    try { 
+		remote = address::from_string(c.getIp()); 
+	} catch(const boost::system::system_error&) {
+		printf("Error when reading IP %s\n", c.getIp().c_str());
+		return false;
+    }
+
 	std::string ip;
 
-	if(remote.is_v4() || (remote.is_v6() && remote.to_v6().is_v4_mapped())) {
+	bool doValidation = false;
+	if (!c.isV6()) {
 		auto v4 = remote.is_v4() ? remote.to_v4() : remote.to_v6().to_v4();
 
 		if(cmd.getParam("I4", 0, ip)) {
 			dcdebug("%s verifying IP %s\n", AdcCommand::fromSID(c.getSID()).c_str(), ip.c_str());
 			if(ip.empty() || address_v4::from_string(ip) == address_v4::any()) {
 				cmd.delParam("I4", 0);
-			} else if(address_v4::from_string(ip) != v4 && !Util::isPrivateIp(c.getIp())) {
+				cmd.addParam("I4", c.getIp());
+			} else if(address_v4::from_string(ip) != v4 && !Util::isPrivateIp(c.getIp(), false)) {
 				disconnect(c, Util::REASON_INVALID_IP, "Your IP is " + c.getIp() +
 					", reconfigure your client settings", AdcCommand::ERROR_BAD_IP, "IP" + c.getIp());
 				return false;
@@ -360,21 +504,24 @@
 			}
 		}
 
-		if(!c.hasField("I4")) {
-			c.setField("I4", v4.to_string());
-		}
-
-		if(c.getState() != Entity::STATE_NORMAL) {
-			cmd.addParam("I4", v4.to_string());
-		}
-
-		cmd.delParam("I6", 0); // We can't check this so we remove it instead...fix?
-	} else if(remote.is_v6()) {
+		if (!isHbriConn) {
+			if(!c.hasField("I4")) {
+				c.setField("I4", v4.to_string());
+			}
+
+			string tmp;
+			doValidation = cmd.getParam("I6", 0, tmp) && !tmp.empty();
+		}
+
+		cmd.delParam("U6", 0);
+		cmd.delParam("I6", 0);
+	} else {
 		if(cmd.getParam("I6", 0, ip)) {
 			dcdebug("%s verifying IPv6 %s\n", AdcCommand::fromSID(c.getSID()).c_str(), ip.c_str());
 			if(ip.empty() || address_v6::from_string(ip) == address_v6::any()) {
 				cmd.delParam("I6", 0);
-			} else if(address_v6::from_string(ip) != remote.to_v6() && !Util::isPrivateIp(c.getIp())) {
+				cmd.addParam("I6", c.getIp());
+			} else if(address_v6::from_string(ip) != remote.to_v6() && !Util::isPrivateIp(c.getIp(), true)) {
 				disconnect(c, Util::REASON_INVALID_IP, "Your IP is " + c.getIp() +
 					", reconfigure your client settings", AdcCommand::ERROR_BAD_IP, "IP" + c.getIp());
 				return false;
@@ -383,15 +530,25 @@
 			}
 		}
 
-		if(!c.hasField("I6")) {
-			c.setField("I6", c.getIp());
-		}
-
-		if(c.getState() != Entity::STATE_NORMAL) {
-			cmd.addParam("I6", c.getIp());
-		}
-
-		cmd.delParam("I4", 0); // We can't check this so we remove it instead...fix?
+		if (!isHbriConn) {
+			if (!c.hasField("I6")) {
+				c.setField("I6", c.getIp());
+			}
+
+			string tmp;
+			doValidation = cmd.getParam("I4", 0, tmp) && !tmp.empty();
+		}
+
+		cmd.delParam("I4", 0);
+		cmd.delParam("U4", 0);
+	}
+
+	if (doValidation) {
+		if (c.getState() == Entity::STATE_NORMAL) {
+			sendHBRI(c);
+		} else {
+			c.setFlag(Entity::FLAG_VALIDATE_HBRI);
+		}
 	}
 
 	return true;
@@ -516,7 +673,7 @@
 	signalState_(c, oldState);
 }
 
-void ClientManager::disconnect(Entity& c, Util::Reason reason, const std::string& info, AdcCommand::Error error, const std::string& staParam) {
+void ClientManager::disconnect(Entity& c, Util::Reason reason, const std::string& info, AdcCommand::Error error, const std::string& staParam, int aReconnectTime) {
 	// send a fatal STA
 	AdcCommand sta(AdcCommand::SEV_FATAL, error, info);
 	if(!staParam.empty())
@@ -525,7 +682,7 @@
 
 	// send a QUI
 	c.send(AdcCommand(AdcCommand::CMD_QUI).addParam(AdcCommand::fromSID(c.getSID()))
-		.addParam("DI", "1").addParam("MS", info).addParam("TL", "-1"));
+		.addParam("DI", "1").addParam("MS", info).addParam("TL", Util::toString(aReconnectTime)));
 
 	c.disconnect(reason);
 }
@@ -562,6 +719,14 @@
 }
 
 bool ClientManager::enterNormal(Entity& c, bool sendData, bool sendOwnInf) throw() {
+	if (c.isSet(Entity::FLAG_VALIDATE_HBRI)) {
+		if (sendHBRI(c)) {
+			return false;
+		}
+
+		c.unsetFlag(Entity::FLAG_VALIDATE_HBRI);
+	}
+
 	dcassert(c.getState() == Entity::STATE_IDENTIFY || c.getState() == Entity::STATE_VERIFY);
 	dcdebug("%s entering NORMAL\n", AdcCommand::fromSID(c.getSID()).c_str());
 
@@ -596,6 +761,13 @@
 	if(i != logins.end()) {
 		logins.erase(i);
 	}
+
+	if (e.hasSupport(AdcCommand::toFourCC("HBRI"))) {
+		auto i = find_if(hbriTokens | map_values, CompareFirst<Entity*, time::ptime>(c)).base();
+		if (i != hbriTokens.end()) {
+			hbriTokens.erase(i);
+		}
+	}
 }
 
 void ClientManager::removeEntity(Entity& c, Util::Reason reason, const std::string &info) throw() {
@@ -644,4 +816,4 @@
 	removeEntity(c, reason, info);
 }
 
-}
+}
\ No newline at end of file

=== modified file 'adchpp/ClientManager.h'
--- adchpp/ClientManager.h	2013-01-18 21:41:53 +0000
+++ adchpp/ClientManager.h	2013-11-12 23:29:50 +0000
@@ -131,7 +131,7 @@
 	/**
 	 * Verify that IP is correct and replace any zero addresses.
 	 */
-	ADCHPP_DLL bool verifyIp(Client& c, AdcCommand& cmd) throw();
+	ADCHPP_DLL bool verifyIp(Client& c, AdcCommand& cmd, bool isHbriConn) throw();
 
 	/**
 	 * Verify that CID is correct and corresponds to PID
@@ -169,10 +169,13 @@
 	void setMaxCommandSize(size_t newSize) { maxCommandSize = newSize; }
 	size_t getMaxCommandSize() const { return maxCommandSize; }
 
+	void setHbriTimeout(size_t millis) { hbriTimeout = millis; }
+	size_t getHbriTimeout() const { return hbriTimeout; }
 	void setLogTimeout(size_t millis) { logTimeout = millis; }
 	size_t getLogTimeout() const { return logTimeout; }
 
 	Core &getCore() const { return core; }
+	void prepareSupports(bool addHbri);
 private:
 	friend class Core;
 	friend class Client;
@@ -183,6 +186,9 @@
 
 	std::list<std::pair<Client*, time::ptime> > logins;
 
+	typedef std::unordered_map<std::string, std::pair<Entity*, time::ptime>> TokenMap;
+	TokenMap hbriTokens;
+
 	EntityMap entities;
 	typedef std::unordered_map<std::string, Entity*> NickMap;
 	NickMap nicks;
@@ -193,6 +199,7 @@
 
 	size_t maxCommandSize;
 	size_t logTimeout;
+	size_t hbriTimeout;
 
 	// Temporary string to use whenever a temporary string is needed (to avoid (de)allocating memory all the time...)
 	std::string strtmp;
@@ -203,6 +210,7 @@
 
 	uint32_t makeSID();
 
+	bool sendHBRI(Entity& c);
 	void maybeSend(Entity& c, const AdcCommand& cmd);
 
 	void removeLogins(Entity& c) throw();
@@ -210,6 +218,7 @@
 
 	bool handle(AdcCommand::SUP, Entity& c, AdcCommand& cmd) throw();
 	bool handle(AdcCommand::INF, Entity& c, AdcCommand& cmd) throw();
+	bool handle(AdcCommand::TCP, Entity& c, AdcCommand& cmd) throw();
 	bool handleDefault(Entity& c, AdcCommand& cmd) throw();
 
 	template<typename T> bool handle(T, Entity& c, AdcCommand& cmd) throw() { return handleDefault(c, cmd); }
@@ -225,7 +234,7 @@
 	void badState(Entity& c, const AdcCommand& cmd) throw();
 	/** send a fatal STA, a QUI with TL-1, then disconnect. */
 	void disconnect(Entity& c, Util::Reason reason, const std::string& info,
-		AdcCommand::Error error = AdcCommand::ERROR_PROTOCOL_GENERIC, const std::string& staParam = Util::emptyString);
+		AdcCommand::Error error = AdcCommand::ERROR_PROTOCOL_GENERIC, const std::string& staParam = Util::emptyString, int aReconnectTime = -1);
 
 	SignalConnected::Signal signalConnected_;
 	SignalReady::Signal signalReady_;
@@ -236,6 +245,7 @@
 	SignalDisconnected::Signal signalDisconnected_;
 
 	ClientManager(Core &core) throw();
+	void onTimerSecond();
 };
 
 }

=== modified file 'adchpp/Entity.cpp'
--- adchpp/Entity.cpp	2013-01-18 21:41:53 +0000
+++ adchpp/Entity.cpp	2013-11-12 23:29:50 +0000
@@ -48,10 +48,9 @@
 
 	if(code == AdcCommand::toField("SU")) {
 		filters.clear();
-
-		if((value.size() + 1) % 5 == 0) {
+		if ((value.size() + 1) % 5 == 0) {
 			filters.reserve((value.size() + 1) / 5);
-			for(size_t i = 0; i < value.size(); i += 5) {
+			for (size_t i = 0; i < value.size(); i += 5) {
 				filters.push_back(AdcCommand::toFourCC(value.data() + i));
 			}
 		}
@@ -154,6 +153,41 @@
 	}
 }
 
+bool Entity::hasClientSupport(uint32_t feature) const {
+	return std::find(filters.begin(), filters.end(), feature) != filters.end();
+}
+
+bool Entity::removeClientSupport(uint32_t feature) {
+	auto f = std::find(filters.begin(), filters.end(), feature);
+	if (f == filters.end()) {
+		return false;
+	}
+
+	filters.erase(f);
+
+	auto& supports = fields.find(AdcCommand::toField("SU"))->second;
+
+	auto p = supports.find(AdcCommand::fromFourCC(feature));
+	dcassert(p != std::string::npos);
+	supports.erase(p, 5);
+
+	if (!supports.empty() && supports.back() == ',')
+		supports.pop_back();
+
+	return true;
+}
+
+static const int protoSupportCount = 2;
+static uint32_t supports4[protoSupportCount] = { AdcCommand::toFourCC("TCP4"), AdcCommand::toFourCC("UDP4") };
+static uint32_t supports6[protoSupportCount] = { AdcCommand::toFourCC("TCP6"), AdcCommand::toFourCC("UDP6") };
+
+void Entity::stripProtocolSupports() throw() {
+	const auto& sup = dynamic_cast<Client*>(this)->isV6() ? supports4 : supports6;
+	for (auto i = 0; i < protoSupportCount; ++i) {
+		removeClientSupport(sup[i]);
+	}
+}
+
 bool Entity::isFiltered(const std::string& features) const {
 	if(filters.empty()) {
 		return true;

=== modified file 'adchpp/Entity.h'
--- adchpp/Entity.h	2013-01-18 21:41:53 +0000
+++ adchpp/Entity.h	2013-11-12 23:29:50 +0000
@@ -33,6 +33,8 @@
 	enum State {
 		/** Initial protocol negotiation (wait for SUP) */
 		STATE_PROTOCOL,
+		/** Validating the secondary address */
+		STATE_HBRI,
 		/** Identify the connecting client (wait for INF) */
 		STATE_IDENTIFY,
 		/** Verify the client (wait for PAS) */
@@ -63,7 +65,10 @@
 		FLAG_OK_IP = 0x400,
 
 		/** This entity is now a ghost being disconnected, totally ignored by ADCH++ */
-		FLAG_GHOST = 0x800
+		FLAG_GHOST = 0x800,
+
+		/** Set in the login phase if the users provides an IP for another protocol **/
+		FLAG_VALIDATE_HBRI = 0x1000
 	};
 
 
@@ -87,6 +92,12 @@
 	ADCHPP_DLL bool hasSupport(uint32_t feature) const;
 	ADCHPP_DLL bool removeSupports(uint32_t feature);
 
+	ADCHPP_DLL bool hasClientSupport(uint32_t feature) const;
+	ADCHPP_DLL bool removeClientSupport(uint32_t feature);
+
+	/** Remove supports for the protocol that wasn't used for connecting **/
+	ADCHPP_DLL void stripProtocolSupports() throw();
+
 	ADCHPP_DLL const BufferPtr& getSUP() const;
 
 	uint32_t getSID() const { return sid; }

=== modified file 'adchpp/ManagedSocket.cpp'
--- adchpp/ManagedSocket.cpp	2013-07-19 19:47:27 +0000
+++ adchpp/ManagedSocket.cpp	2013-11-12 23:29:50 +0000
@@ -22,18 +22,21 @@
 
 #include "SocketManager.h"
 
+#include <boost/asio/ip/address.hpp>
+
 namespace adchpp {
 
 using namespace std;
 
 using namespace boost::asio;
 
-ManagedSocket::ManagedSocket(SocketManager &sm, const AsyncStreamPtr &sock_) :
+ManagedSocket::ManagedSocket(SocketManager &sm, const AsyncStreamPtr &sock_, const ServerInfoPtr& aServer) :
 	sock(sock_),
 	overflow(time::not_a_date_time),
 	disc(time::not_a_date_time),
 	lastWrite(time::not_a_date_time),
-	sm(sm)
+	sm(sm),
+	server(aServer)
 { }
 
 ManagedSocket::~ManagedSocket() throw() {
@@ -251,6 +254,39 @@
 	std::string info;
 };
 
+bool ManagedSocket::getHbriParams(AdcCommand& cmd) const throw() {
+	if (!isV6()) {
+		if (!server->address6.empty()) {
+			cmd.addParam("I6", server->address6);
+		} else {
+			return false;
+		}
+
+		cmd.addParam("P6", server->port);
+	} else {
+		if (!server->address4.empty()) {
+			cmd.addParam("I4", server->address4);
+		} else {
+			return false;
+		}
+
+		cmd.addParam("P4", server->port);
+	}
+
+	return true;
+}
+
+bool ManagedSocket::isV6() const throw() {
+	using namespace boost::asio::ip;
+
+	address remote;
+	remote = address::from_string(ip);
+	if (remote.is_v4() || (remote.is_v6() && remote.to_v6().is_v4_mapped()))
+		return false;
+
+	return true;
+}
+
 void ManagedSocket::disconnect(Util::Reason reason, const std::string &info) throw() {
 	if(disconnecting()) {
 		return;

=== modified file 'adchpp/ManagedSocket.h'
--- adchpp/ManagedSocket.h	2013-07-19 19:47:27 +0000
+++ adchpp/ManagedSocket.h	2013-11-12 23:29:50 +0000
@@ -22,6 +22,8 @@
 #include "common.h"
 
 #include "forward.h"
+
+#include "AdcCommand.h"
 #include "Signal.h"
 #include "Util.h"
 #include "Buffer.h"
@@ -35,7 +37,7 @@
  */
 class ManagedSocket : private boost::noncopyable, public enable_shared_from_this<ManagedSocket> {
 public:
-	ManagedSocket(SocketManager &sm, const AsyncStreamPtr& sock_);
+	ManagedSocket(SocketManager &sm, const AsyncStreamPtr& sock_, const ServerInfoPtr& aServer);
 
 	/** Asynchronous write */
 	ADCHPP_DLL void write(const BufferPtr& buf, bool lowPrio = false) throw();
@@ -68,6 +70,8 @@
 
 	~ManagedSocket() throw();
 
+	bool getHbriParams(AdcCommand& cmd) const throw();
+	bool isV6() const throw();
 private:
 	friend class SocketManager;
 	friend class SocketFactory;
@@ -110,6 +114,8 @@
 	FailedHandler failedHandler;
 
 	SocketManager &sm;
+
+	ServerInfoPtr server;
 };
 
 }

=== modified file 'adchpp/ServerInfo.h'
--- adchpp/ServerInfo.h	2013-01-18 21:41:53 +0000
+++ adchpp/ServerInfo.h	2013-11-12 23:29:50 +0000
@@ -22,7 +22,10 @@
 namespace adchpp {
 
 struct ServerInfo {
-	std::string ip;
+	std::string bind4;
+	std::string bind6;
+	std::string address4;
+	std::string address6;
 	std::string port;
 
 	struct TLSInfo {

=== modified file 'adchpp/SocketManager.cpp'
--- adchpp/SocketManager.cpp	2013-07-19 19:47:27 +0000
+++ adchpp/SocketManager.cpp	2013-11-12 23:29:50 +0000
@@ -20,6 +20,7 @@
 
 #include "SocketManager.h"
 
+#include "ClientManager.h"
 #include "LogManager.h"
 #include "ManagedSocket.h"
 #include "ServerInfo.h"
@@ -47,7 +48,9 @@
 bufferSize(1024),
 maxBufferSize(16 * 1024),
 overflowTimeout(60 * 1000),
-disconnectTimeout(10 * 1000)
+disconnectTimeout(10 * 1000),
+hasV4Address(false),
+hasV6Address(false)
 {
 }
 
@@ -192,10 +195,11 @@
 
 class SocketFactory : public enable_shared_from_this<SocketFactory>, boost::noncopyable {
 public:
-	SocketFactory(SocketManager& sm, const SocketManager::IncomingHandler& handler_, const ServerInfo& info, const ip::tcp::endpoint& endpoint) :
+	SocketFactory(SocketManager& sm, const SocketManager::IncomingHandler& handler_, ServerInfoPtr& info, const ip::tcp::endpoint& endpoint) :
 		sm(sm),
 		acceptor(sm.io),
-		handler(handler_)
+		handler(handler_),
+		si(info)
 	{
 		acceptor.open(endpoint.protocol());
 		acceptor.set_option(socket_base::reuse_address(true));
@@ -203,21 +207,23 @@
 			acceptor.set_option(ip::v6_only(true));
 		}
 
+		auto a = endpoint.address().to_string();
+
 		acceptor.bind(endpoint);
 		acceptor.listen(socket_base::max_connections);
 
 		LOGC(sm.getCore(), SocketManager::className,
 			"Listening on " + formatEndpoint(endpoint) +
-			" (Encrypted: " + (info.secure() ? "Yes)" : "No)"));
+			" (Encrypted: " + (info->secure() ? "Yes)" : "No)"));
 
 #ifdef HAVE_OPENSSL
-		if(info.secure()) {
+		if(info->secure()) {
 			context.reset(new ssl::context(sm.io, ssl::context::tlsv1_server));
 		    context->set_options(ssl::context::no_sslv2 | ssl::context::no_sslv3 | ssl::context::single_dh_use);
 		    //context->set_password_callback(boost::bind(&server::get_password, this));
-		    context->use_certificate_chain_file(info.TLSParams.cert);
-		    context->use_private_key_file(info.TLSParams.pkey, ssl::context::pem);
-		    context->use_tmp_dh_file(info.TLSParams.dh);
+		    context->use_certificate_chain_file(info->TLSParams.cert);
+		    context->use_private_key_file(info->TLSParams.pkey, ssl::context::pem);
+		    context->use_tmp_dh_file(info->TLSParams.dh);
 		}
 #endif
 	}
@@ -230,12 +236,12 @@
 #ifdef HAVE_OPENSSL
 		if(context) {
 			auto s = make_shared<TLSSocketStream>(sm.io, *context);
-			auto socket = make_shared<ManagedSocket>(sm, s);
+			auto socket = make_shared<ManagedSocket>(sm, s, si);
 			acceptor.async_accept(s->sock.lowest_layer(), std::bind(&SocketFactory::handleAccept, shared_from_this(), std::placeholders::_1, socket));
 		} else {
 #endif
 			auto s = make_shared<SimpleSocketStream>(sm.io);
-			auto socket = make_shared<ManagedSocket>(sm, s);
+			auto socket = make_shared<ManagedSocket>(sm, s, si);
 			acceptor.async_accept(s->sock.lowest_layer(), std::bind(&SocketFactory::handleAccept, shared_from_this(), std::placeholders::_1, socket));
 #ifdef HAVE_OPENSSL
 		}
@@ -245,7 +251,9 @@
 	void handleAccept(const error_code& ec, const ManagedSocketPtr& socket) {
 		if(!ec) {
 			socket->sock->setOptions(sm.getBufferSize());
-			socket->setIp(socket->sock->getIp());
+			auto ip = socket->sock->getIp();
+			auto p = ip.find("%");
+			socket->setIp(p != string::npos ? ip.substr(0, p) : ip);
 		}
 
 		completeAccept(ec, socket);
@@ -263,37 +271,68 @@
 	SocketManager &sm;
 	ip::tcp::acceptor acceptor;
 	SocketManager::IncomingHandler handler;
-
+	ServerInfoPtr si;
 #ifdef HAVE_OPENSSL
 	unique_ptr<ssl::context> context;
 #endif
 
 };
 
+void SocketManager::prepareProtocol(ServerInfoPtr& si, bool v6) {
+	const string proto = v6 ? "IPv6" : "IPv4";
+	try {
+		using ip::tcp;
+		tcp::resolver r(io);
+
+		// Resolve the public address
+		string& hubAddress = v6 ? si->address6 : si->address4;
+		if (!hubAddress.empty()) {
+			try {
+				auto remote = r.resolve(tcp::resolver::query(v6 ? tcp::v6() : tcp::v4(), hubAddress, si->port,
+					tcp::resolver::query::address_configured | tcp::resolver::query::passive));
+				hubAddress = remote->endpoint().address().to_string();
+				v6 ? hasV6Address = true : hasV4Address = true;
+			} catch (const std::exception& e) {
+				LOG(SocketManager::className, "Error when resolving the " + proto + " hub address " + hubAddress + ": " + e.what());
+				hubAddress = Util::emptyString;
+			}
+		}
+
+		// Resolve the bind address
+		auto local = r.resolve(tcp::resolver::query(v6 ? tcp::v6() : tcp::v4(), v6 ? si->bind6 : si->bind4, si->port,
+			tcp::resolver::query::address_configured | tcp::resolver::query::passive));
+
+		for (auto i = local; i != tcp::resolver::iterator(); ++i) {
+			auto factory = make_shared<SocketFactory>(*this, incomingHandler, si, *i);
+			factory->prepareAccept();
+			factories.push_back(factory);
+		}
+	} catch (const std::exception& e) {
+		LOG(SocketManager::className, "Error while loading " + proto + " server on port " + si->port + ": " + e.what());
+	}
+}
+
 int SocketManager::run() {
+	//Sleep(10000);
 	LOG(SocketManager::className, "Starting");
 
 	work.reset(new io_service::work(io));
 
 	for(auto i = servers.begin(), iend = servers.end(); i != iend; ++i) {
 		auto& si = *i;
-
-		try {
-			using ip::tcp;
-			tcp::resolver r(io);
-			auto local = r.resolve(tcp::resolver::query(si->ip, si->port,
-				tcp::resolver::query::address_configured | tcp::resolver::query::passive));
-
-			for(auto i = local; i != tcp::resolver::iterator(); ++i) {
-				SocketFactoryPtr factory = make_shared<SocketFactory>(*this, incomingHandler, *si, *i);
-				factory->prepareAccept();
-				factories.push_back(factory);
-			}
-		} catch(const std::exception& e) {
-			LOG(SocketManager::className, "Error while loading server on port " + si->port +": " + e.what());
+		bool listenAll = si->bind4.empty() && si->bind6.empty();
+		
+		if (!si->bind4.empty() || listenAll) {
+			prepareProtocol(si, false);
+		}
+			
+		if (!si->bind6.empty() || listenAll) {
+			prepareProtocol(si, true);
 		}
 	}
 
+	core.getClientManager().prepareSupports(hasV4Address && hasV6Address);
+
 	io.run();
 
 	io.reset();

=== modified file 'adchpp/SocketManager.h'
--- adchpp/SocketManager.h	2013-07-19 19:47:27 +0000
+++ adchpp/SocketManager.h	2013-11-12 23:29:50 +0000
@@ -94,6 +94,7 @@
 	friend class ManagedSocket;
 	friend class SocketFactory;
 
+	void prepareProtocol(ServerInfoPtr& si, bool v6);
 	void closeFactories();
 
 	Core &core;
@@ -126,6 +127,9 @@
 	void onLoad(const SimpleXML& xml) throw();
 
 	SocketManager(Core &core);
+
+	bool hasV4Address;
+	bool hasV6Address;
 };
 
 }

=== modified file 'adchpp/Util.cpp'
--- adchpp/Util.cpp	2013-01-18 21:41:53 +0000
+++ adchpp/Util.cpp	2013-11-12 23:29:50 +0000
@@ -338,7 +338,11 @@
 }
 #endif
 
-bool Util::isPrivateIp(std::string const& ip) {
+bool Util::isPrivateIp(std::string const& ip, bool v6) {
+	if (v6) {
+		return strncmp(ip.c_str(), "fe80", 4) == 0;
+	}
+
 	struct in_addr addr;
 
 	addr.s_addr = inet_addr(ip.c_str());

=== modified file 'adchpp/Util.h'
--- adchpp/Util.h	2013-01-18 21:41:53 +0000
+++ adchpp/Util.h	2013-11-12 23:29:50 +0000
@@ -102,6 +102,7 @@
 		REASON_INVALID_DESCRIPTION,
 		REASON_WRITE_TIMEOUT,
 		REASON_SOCKET_ERROR,
+		REASON_HBRI,
 		REASON_LAST,
 	};
 
@@ -228,7 +229,7 @@
 	static uint32_t rand(uint32_t low, uint32_t high) { return rand(high-low) + low; }
 	static double randd() { return ((double)rand()) / ((double)0xffffffff); }
 
-	ADCHPP_DLL static bool isPrivateIp(std::string const& ip);
+	ADCHPP_DLL static bool isPrivateIp(std::string const& ip, bool v6);
 	ADCHPP_DLL static bool validateCharset(std::string const& field, int p);
 };
 

=== modified file 'adchppd/adchppd.cpp'
--- adchppd/adchppd.cpp	2013-07-19 19:47:27 +0000
+++ adchppd/adchppd.cpp	2013-11-12 23:29:50 +0000
@@ -72,6 +72,8 @@
 						core.getSocketManager().setDisconnectTimeout(Util::toInt(xml.getChildData()));
 					} else if(xml.getChildName() == "LogTimeout") {
 						core.getClientManager().setLogTimeout(Util::toInt(xml.getChildData()));
+					} else if (xml.getChildName() == "HbriTimeout") {
+						core.getClientManager().setHbriTimeout(Util::toInt(xml.getChildData()));
 					}
 				}
 
@@ -85,6 +87,11 @@
 					ServerInfoPtr server = make_shared<ServerInfo>();
 					server->port = xml.getChildAttrib("Port", Util::emptyString);
 
+					server->bind4 = xml.getChildAttrib("BindAddress4", Util::emptyString);
+					server->bind6 = xml.getChildAttrib("BindAddress6", Util::emptyString);
+					server->address4 = xml.getChildAttrib("HubAddress4", Util::emptyString);
+					server->address6 = xml.getChildAttrib("HubAddress6", Util::emptyString);
+
 					if(xml.getBoolChildAttrib("TLS")) {
 						server->TLSParams.cert = File::makeAbsolutePath(xml.getChildAttrib("Certificate"));
 						server->TLSParams.pkey = File::makeAbsolutePath(xml.getChildAttrib("PrivateKey"));

=== modified file 'changelog.txt'
--- changelog.txt	2013-09-20 17:37:55 +0000
+++ changelog.txt	2013-11-12 23:29:50 +0000
@@ -1,3 +1,8 @@
+* Add support for hybrid IPv4/IPv6 client connectivity (maksis)
+* [L#1088638] Allow clients to reconnect in event of overflow (maksis)
+* [L#1106226] Fix a crash when joining the hub with a local IPv6 address (maksis)
+* Make login timeout work independently from other connecting users (maksis)
+* Allow configuring custom bind addresses (maksis)
 * Improve Lua scripts
 
 -- 2.11.0 2013-07-19 --

=== modified file 'etc/adchpp.xml'
--- etc/adchpp.xml	2013-07-19 19:47:27 +0000
+++ etc/adchpp.xml	2013-11-12 23:29:50 +0000
@@ -51,6 +51,10 @@
 
 		<!-- Timeout (ms) before disconnecting users whose login process is taking too long. -->
 		<LogTimeout>10000</LogTimeout>
+		
+		<!-- Timeout (ms) before aborting validation for the secondary IP protocol. 
+			 The login procedure will continue normally with the primary IP protocol then. -->
+		<HbriTimeout>3000</HbriTimeout>
 	</Settings>
 
 	<Servers>
@@ -59,15 +63,22 @@
 
 		To create secure connections, set TLS="1" and define the following (preferably absolute)
 		paths: Certificate, PrivateKey, TrustedPath, DHParams. An example secure server setting:
-		<Server Port="2780" TLS="1" Certificate="certs/cacert.pem" PrivateKey="certs/privkey.pem" TrustedPath="certs/trusted/" DHParams="certs/dhparam.pem"/>
+		<Server Port="2780" TLS="1" Certificate="certs/cacert.pem" PrivateKey="certs/privkey.pem" TrustedPath="certs/trusted/" DHParams="certs/dhparam.pem" BindAddress4="0.0.0.0" BindAddress6="::"/>
 
 		Simple OpenSSL commands to generate files used for secure connections:
 		openssl genrsa -out privkey.pem 2048
 		openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095
 		openssl dhparam -outform PEM -out dhparam.pem 1024
-
+		
 		Alternatively, you can use the cert generator contributed on
 		<http://launchpadlibrarian.net/31960965/Cert_Generator.7z>.
+		
+		To allow hybrid connectivity, add address fields for the used protocols: HubAddress4 and HubAddress6. You may also use the same 
+		DNS entry for both if it has A and AAAA records. You may also use plain IP addresses.
+		Example:
+		<Server Port="2780" HubAddress4="mydomain4.net" HubAddress6="mydomain6.net"/>
+		
+		Bind addresses may be set with: BindAddress4 and BindAddress6 (connections will be accepted for both protocols if none is set)
 
 		-->
 		<Server Port="2780"/>

=== modified file 'swig/adchpp.i'
--- swig/adchpp.i	2013-07-19 19:47:27 +0000
+++ swig/adchpp.i	2013-11-12 23:29:50 +0000
@@ -126,7 +126,10 @@
 typedef shared_ptr<ManagedConnection> ManagedConnectionPtr;
 
 struct ServerInfo {
-	std::string ip;
+	std::string bind4;
+	std::string bind6;
+	std::string address4;
+	std::string address6;
 	std::string port;
 
 	TLSInfo TLSParams;
@@ -291,7 +294,7 @@
 	static uint32_t rand(uint32_t low, uint32_t high);
 	static double randd();
 	
-	static bool isPrivateIp(std::string const& ip);
+	static bool isPrivateIp(std::string const& ip, bool v6);
 	static bool validateCharset(std::string const& field, int p);
 
 };
@@ -723,7 +726,7 @@
 	bool verifyPassword(Entity& c, const std::string& password, const ByteVector& salt, const std::string& suppliedHash);
 	bool verifyHashedPassword(Entity& c, const ByteVector& hashedPassword, int64_t hashedPasswordLen,
 				  const ByteVector& salt, const std::string& suppliedHash) throw();
-	bool verifyIp(Client& c, AdcCommand& cmd) throw();
+	bool verifyIp(Client& c, AdcCommand& cmd, bool isHbriConn) throw();
 	bool verifyCID(Entity& c, AdcCommand& cmd) throw();
 	bool verifyOverflow(Entity& c);
 	void setState(Entity& c, Entity::State newState) throw();

=== modified file 'swig/lua.i'
--- swig/lua.i	2013-02-22 22:36:03 +0000
+++ swig/lua.i	2013-11-12 23:29:50 +0000
@@ -322,7 +322,7 @@
 		if(ret) {
 			return *reinterpret_cast<SWIGLUA_REF*>(ret);
 		}
-		return {0, 0};
+		return SWIGLUA_REF();
 	}
 
 	void setPluginData(const PluginDataHandle& handle, SWIGLUA_REF data) {


Follow ups