From: Sebastian Goth Date: Thu, 30 Jul 2009 15:33:44 +0000 (+0200) Subject: Introduce netsplit detection/handling X-Git-Tag: 0.5-rc1~45 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=fd80315c076ede937f5d51ba6f8061a32d0107ec Introduce netsplit detection/handling --- diff --git a/src/common/message.h b/src/common/message.h index 1cf0f678..b5d52a4b 100644 --- a/src/common/message.h +++ b/src/common/message.h @@ -33,21 +33,23 @@ class Message { public: /** The different types a message can have for display */ enum Type { - Plain = 0x0001, - Notice = 0x0002, - Action = 0x0004, - Nick = 0x0008, - Mode = 0x0010, - Join = 0x0020, - Part = 0x0040, - Quit = 0x0080, - Kick = 0x0100, - Kill = 0x0200, - Server = 0x0400, - Info = 0x0800, - Error = 0x1000, - DayChange = 0x2000, - Topic = 0x4000 + Plain = 0x00001, + Notice = 0x00002, + Action = 0x00004, + Nick = 0x00008, + Mode = 0x00010, + Join = 0x00020, + Part = 0x00040, + Quit = 0x00080, + Kick = 0x00100, + Kill = 0x00200, + Server = 0x00400, + Info = 0x00800, + Error = 0x01000, + DayChange = 0x02000, + Topic = 0x04000, + NetsplitJoin = 0x08000, + NetsplitQuit = 0x10000, }; // DO NOT CHANGE without knowing what you do, some of these flags are stored in the database diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3ec5df22..d5624d6c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES coreusersettings.cpp ctcphandler.cpp ircserverhandler.cpp + netsplit.cpp postgresqlstorage.cpp sessionthread.cpp sqlitestorage.cpp @@ -52,6 +53,7 @@ set(MOC_HDRS coresession.h ctcphandler.h ircserverhandler.h + netsplit.h postgresqlstorage.h sqlitestorage.h storage.h diff --git a/src/core/ircserverhandler.cpp b/src/core/ircserverhandler.cpp index b942bc26..b03b0d71 100644 --- a/src/core/ircserverhandler.cpp +++ b/src/core/ircserverhandler.cpp @@ -186,9 +186,23 @@ void IrcServerHandler::handleJoin(const QString &prefix, const QList QString channel = serverDecode(params[0]); IrcUser *ircuser = network()->updateNickFromMask(prefix); - emit displayMsg(Message::Join, BufferInfo::ChannelBuffer, channel, channel, prefix); + + bool handledByNetsplit = false; + if(!_netsplits.empty()) { + foreach(Netsplit* n, _netsplits) { + handledByNetsplit = n->userJoined(prefix, channel); + if(handledByNetsplit) + break; + } + } + + // normal join + if(!handledByNetsplit) { + emit displayMsg(Message::Join, BufferInfo::ChannelBuffer, channel, channel, prefix); + ircuser->joinChannel(channel); + } //qDebug() << "IrcServerHandler::handleJoin()" << prefix << params; - ircuser->joinChannel(channel); + if(network()->isMe(ircuser)) { network()->setChannelJoined(channel); putCmd("MODE", params[0]); // we want to know the modes of the channel we just joined, so we ask politely @@ -461,10 +475,28 @@ void IrcServerHandler::handleQuit(const QString &prefix, const QList if(params.count() > 0) msg = userDecode(ircuser->nick(), params[0]); - foreach(QString channel, ircuser->channels()) - emit displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, msg, prefix); - - ircuser->quit(); + // check if netsplit + if(Netsplit::isNetsplit(msg)) { + Netsplit *n; + if(!_netsplits.contains(msg)) { + n = new Netsplit(); + connect(n, SIGNAL(finished()), this, SLOT(handleNetsplitFinished())); + connect(n, SIGNAL(netsplitJoin(QString,QStringList,QString)), this, SLOT(handleNetsplitJoin(QString,QStringList,QString))); + connect(n, SIGNAL(netsplitQuit(QString,QStringList,QString)), this, SLOT(handleNetsplitQuit(QString,QStringList,QString))); + _netsplits.insert(msg, n); + } + else { + n = _netsplits[msg]; + } + // add this user to the netsplit + n->userQuit(prefix, ircuser->channels(),msg); + } + // normal quit + else { + foreach(QString channel, ircuser->channels()) + emit displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, msg, prefix); + ircuser->quit(); + } } void IrcServerHandler::handleTopic(const QString &prefix, const QList ¶ms) { @@ -1013,6 +1045,38 @@ void IrcServerHandler::handle433(const QString &prefix, const QList tryNextNick(errnick); } +/* Handle signals from Netsplit objects */ + +void IrcServerHandler::handleNetsplitJoin(const QString &channel, const QStringList &users, const QString& quitMessage) +{ + QString msg = users.join(":").append(':').append(quitMessage); + emit displayMsg(Message::NetsplitJoin, BufferInfo::ChannelBuffer, channel, msg); + + foreach(QString user, users) { + IrcUser *iu = network()->ircUser(nickFromMask(user)); + if(iu) + iu->joinChannel(channel); + } +} + +void IrcServerHandler::handleNetsplitQuit(const QString &channel, const QStringList &users, const QString& quitMessage) +{ + QString msg = users.join(":").append(':').append(quitMessage); + emit displayMsg(Message::NetsplitQuit, BufferInfo::ChannelBuffer, channel, msg); + foreach(QString user, users) { + IrcUser *iu = network()->ircUser(nickFromMask(user)); + if(iu) + iu->quit(); + } +} + +void IrcServerHandler::handleNetsplitFinished() +{ + Netsplit* n = qobject_cast(sender()); + _netsplits.remove(_netsplits.key(n)); + n->deleteLater(); +} + /* */ // FIXME networkConnection()->setChannelKey("") for all ERR replies indicating that a JOIN went wrong diff --git a/src/core/ircserverhandler.h b/src/core/ircserverhandler.h index 90db4b45..f035eb9a 100644 --- a/src/core/ircserverhandler.h +++ b/src/core/ircserverhandler.h @@ -22,6 +22,7 @@ #define IRCSERVERHANDLER_H #include "basichandler.h" +#include "netsplit.h" class IrcServerHandler : public BasicHandler { Q_OBJECT @@ -81,6 +82,28 @@ public slots: void defaultHandler(QString cmd, const QString &prefix, const QList ¶ms); +private slots: + //! Joins after a netsplit + /** This slot handles a bulk-join after a netsplit is over + * \param channel The channel the users joined + * \param users The list of users that joind the channel + * \param quitMessage The message we received when the netsplit occured + */ + void handleNetsplitJoin(const QString &channel, const QStringList &users, const QString &quitMessage); + + //! Quits after a netsplit + /** This slot handles a bulk-quit after a netsplit occured + * \param channel The channel the users quitted + * \param users The list of users that got split + * \param quitMessage The message we received when the netsplit occured + */ + void handleNetsplitQuit(const QString &channel, const QStringList &users, const QString &quitMessage); + + //! Netsplit finished + /** This slot deletes the netsplit object that sent the finished() signal + */ + void handleNetsplitFinished(); + private: void tryNextNick(const QString &errnick, bool erroneus = false); bool checkParamCount(const QString &methodName, const QList ¶ms, int minParams); @@ -90,6 +113,11 @@ private: bool _whois; QString _target; + + // structure to organize netsplits + // key: quit message + // value: the corresponding netsplit object + QHash _netsplits; }; diff --git a/src/core/netsplit.cpp b/src/core/netsplit.cpp new file mode 100644 index 00000000..89d27a4e --- /dev/null +++ b/src/core/netsplit.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2005-09 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "netsplit.h" + +#include + +Netsplit::Netsplit() + : _quitMsg("") +{ + _discardTimer.setSingleShot(true); + _joinTimer.setSingleShot(true); + _quitTimer.setSingleShot(true); + + connect(&_discardTimer, SIGNAL(timeout()), this, SIGNAL(finished())); + + connect(&_joinTimer, SIGNAL(timeout()), this, SLOT(joinTimeout())); + connect(&_quitTimer, SIGNAL(timeout()), this, SLOT(quitTimeout())); + + // wait for a maximum of 1 hour until we discard the netsplit + _discardTimer.start(3600000); +} + +void Netsplit::userQuit(const QString &sender, const QStringList &channels, const QString &msg) +{ + if(_quitMsg.isEmpty()) + _quitMsg = msg; + foreach(QString channel, channels) { + _quits[channel].append(sender); + } + // now let's wait 5s to finish the netsplit-quit + _quitTimer.start(5000); +} + +bool Netsplit::userJoined(const QString &sender, const QString &channel) { + if(!_quits.contains(channel)) + return false; + + QStringList &users = _quits[channel]; + int idx = users.indexOf(sender); + if(idx == -1) + return false; + + _joins[channel].append(users.takeAt(idx)); + if(users.empty()) + _quits.remove(channel); + + // now let's wait 5s to finish the netsplit-join + _joinTimer.start(5000); + return true; +} + +bool Netsplit::isNetsplit(const QString &quitMessage) +{ + // check if we find some common chars that disqualify the netsplit as such + if(quitMessage.contains(':') || quitMessage.contains('/')) + return false; + + // now test if message consists only of two dns names as the RFC requests + // but also allow the commonly used "*.net *.split" + QRegExp hostRx("^(?:[\\w\\d-.]+|\\*)\\.[\\w\\d-]+\\s(?:[\\w\\d-.]+|\\*)\\.[\\w\\d-]+$"); + if(hostRx.exactMatch(quitMessage)) + return true; + + return false; +} + +void Netsplit::joinTimeout() +{ + QHash::iterator it; + for(it = _joins.begin(); it != _joins.end(); ++it) + emit netsplitJoin(it.key(), it.value(),_quitMsg); + _joins.clear(); + _discardTimer.stop(); + emit finished(); +} + +void Netsplit::quitTimeout() +{ + QHash::iterator it; + for(it = _quits.begin(); it != _quits.end(); ++it) + emit netsplitQuit(it.key(), it.value(),_quitMsg); +} diff --git a/src/core/netsplit.h b/src/core/netsplit.h new file mode 100644 index 00000000..135a3f00 --- /dev/null +++ b/src/core/netsplit.h @@ -0,0 +1,102 @@ +/*************************************************************************** + * Copyright (C) 2005-09 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef NETSPLIT_H +#define NETSPLIT_H + +#include +#include +#include +#include + +class Netsplit : public QObject +{ + Q_OBJECT +public: + Netsplit(); + + //! Add a user to the netsplit + /** Call this method if you noticed a netsplit. + * \note This method doesn't check if it really is a netsplit. + * \note Check with isNetsplit(const QString &quitMessage) before calling it! + * + * \param sender The sender string of the quitting user + * \param channels The channels that user shared with us + * \param msg The quit message + */ + void userQuit(const QString &sender, const QStringList &channels, const QString &msg); + + //! Remove a user from the netsplit + /** Call this method if a user joined after a netsplit occured. + + * + * \param sender The sender string of the joined user + * \param channels The channels that user shares with us + * \return true if user was found in the netsplit + */ + bool userJoined(const QString &sender, const QString &channel); + + //! Check if a string matches the criteria for a netsplit + /** \param quitMessage The message to be checked + * \return true if the message is a netsplit + */ + static bool isNetsplit(const QString &quitMessage); + +signals: + //! A bulk-join of netsplitted users timed out + /** Whenever _joinTimer() times out, we consider the bulk-join to be finished and emit that signal + * for every channel + * \param channel The IRC channel + * \param users A list of all users that joined that channel + * \param quitMessage The Quitmessage and thus the servers that got split + */ + void netsplitJoin(const QString &channel, const QStringList &users, const QString &quitMessage); + + //! A bulk-quit of netsplitted users timed out + /** Whenever _quitTimer() times out, we consider the bulk-quit to be finished and emit that signal + * for every channel + * \param channel The IRC channel + * \param users A list of all users that quitted in that channel + * \param quitMessage The Quitmessage and thus the servers that got split + */ + void netsplitQuit(const QString &channel, const QStringList &users, const QString &quitMessage); + + //! The Netsplit is considered finished + /** This signal is emitted right after all netsplitJoin signals have been sent or whenever the + * internal timer signals a timeout. + * Simply delete the object and remove it from structures when you receive that signal. + */ + void finished(); + +private slots: + void joinTimeout(); + void quitTimeout(); + +private: + QString _quitMsg; + QHash _joins; + QHash _quits; + + QTimer _joinTimer; + QTimer _quitTimer; + QTimer _discardTimer; +}; + +#endif // NETSPLIT_H