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) {
_______________________________________________ Mailing list: https://launchpad.net/~linuxdcpp-team Post to : linuxdcpp-team@lists.launchpad.net Unsubscribe : https://launchpad.net/~linuxdcpp-team More help : https://help.launchpad.net/ListHelp