SSH: Introduce keep-alive mechanism.

Neither TCP nor the SSH protocol offer a built-in way to reliably
notice connection loss, so we implement our own.

Task-number: QTCREATORBUG-3783
This commit is contained in:
Christian Kandeler
2011-02-11 14:00:54 +01:00
parent 8ae0ad9442
commit 0d430277d4
9 changed files with 97 additions and 2 deletions

View File

@@ -188,6 +188,8 @@ SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn)
{ {
setupPacketHandlers(); setupPacketHandlers();
m_timeoutTimer.setSingleShot(true); m_timeoutTimer.setSingleShot(true);
m_keepAliveTimer.setSingleShot(true);
m_keepAliveTimer.setInterval(10000);
connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout())); connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
} }
@@ -255,6 +257,9 @@ void SshConnectionPrivate::setupPacketHandlers()
<< KeyExchangeStarted << KeyExchangeSuccess << KeyExchangeStarted << KeyExchangeSuccess
<< UserAuthServiceRequested << UserAuthRequested << UserAuthServiceRequested << UserAuthRequested
<< ConnectionEstablished, &This::handleDisconnect); << ConnectionEstablished, &This::handleDisconnect);
setupPacketHandler(SSH_MSG_UNIMPLEMENTED,
StateList() << ConnectionEstablished, &This::handleUnimplementedPacket);
} }
void SshConnectionPrivate::setupPacketHandler(SshPacketType type, void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
@@ -436,6 +441,9 @@ void SshConnectionPrivate::handleUserAuthSuccessPacket()
m_state = ConnectionEstablished; m_state = ConnectionEstablished;
m_timeoutTimer.stop(); m_timeoutTimer.stop();
emit connected(); emit connected();
m_lastInvalidMsgSeqNr = InvalidSeqNr;
connect(&m_keepAliveTimer, SIGNAL(timeout()), SLOT(sendKeepAlivePacket()));
m_keepAliveTimer.start();
} }
void SshConnectionPrivate::handleUserAuthFailurePacket() void SshConnectionPrivate::handleUserAuthFailurePacket()
@@ -452,6 +460,19 @@ void SshConnectionPrivate::handleDebugPacket()
emit dataAvailable(msg.message); emit dataAvailable(msg.message);
} }
void SshConnectionPrivate::handleUnimplementedPacket()
{
const SshUnimplemented &msg = m_incomingPacket.extractUnimplemented();
if (msg.invalidMsgSeqNr != m_lastInvalidMsgSeqNr) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected packet", tr("The server sent an unexpected SSH packet "
"of type SSH_MSG_UNIMPLEMENTED."));
}
m_lastInvalidMsgSeqNr = InvalidSeqNr;
m_timeoutTimer.stop();
m_keepAliveTimer.start();
}
void SshConnectionPrivate::handleChannelRequest() void SshConnectionPrivate::handleChannelRequest()
{ {
m_channelManager->handleChannelRequest(m_incomingPacket); m_channelManager->handleChannelRequest(m_incomingPacket);
@@ -514,6 +535,8 @@ void SshConnectionPrivate::handleDisconnect()
"", tr("Server closed connection: %1").arg(msg.description)); "", tr("Server closed connection: %1").arg(msg.description));
} }
void SshConnectionPrivate::sendData(const QByteArray &data) void SshConnectionPrivate::sendData(const QByteArray &data)
{ {
if (canUseSocket()) if (canUseSocket())
@@ -541,6 +564,14 @@ void SshConnectionPrivate::handleTimeout()
tr("Timeout waiting for reply from server.")); tr("Timeout waiting for reply from server."));
} }
void SshConnectionPrivate::sendKeepAlivePacket()
{
Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr);
m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr();
m_sendFacility.sendInvalidPacket();
m_timeoutTimer.start(5000);
}
void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo) void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo)
{ {
m_incomingData.clear(); m_incomingData.clear();
@@ -577,6 +608,8 @@ void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
m_timeoutTimer.stop(); m_timeoutTimer.stop();
disconnect(m_socket, 0, this, 0); disconnect(m_socket, 0, this, 0);
disconnect(&m_timeoutTimer, 0, this, 0); disconnect(&m_timeoutTimer, 0, this, 0);
m_keepAliveTimer.stop();
disconnect(&m_keepAliveTimer, 0, this, 0);
try { try {
m_channelManager->closeAllChannels(); m_channelManager->closeAllChannels();
m_sendFacility.sendDisconnectPacket(sshError, serverErrorString); m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
@@ -606,5 +639,7 @@ QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
return m_channelManager->createSftpChannel(); return m_channelManager->createSftpChannel();
} }
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
} // namespace Internal } // namespace Internal
} // namespace Core } // namespace Core

View File

@@ -102,6 +102,7 @@ private:
Q_SLOT void handleSocketError(); Q_SLOT void handleSocketError();
Q_SLOT void handleSocketDisconnected(); Q_SLOT void handleSocketDisconnected();
Q_SLOT void handleTimeout(); Q_SLOT void handleTimeout();
Q_SLOT void sendKeepAlivePacket();
void handleServerId(); void handleServerId();
void handlePackets(); void handlePackets();
@@ -116,6 +117,7 @@ private:
void handleUserAuthBannerPacket(); void handleUserAuthBannerPacket();
void handleGlobalRequest(); void handleGlobalRequest();
void handleDebugPacket(); void handleDebugPacket();
void handleUnimplementedPacket();
void handleChannelRequest(); void handleChannelRequest();
void handleChannelOpen(); void handleChannelOpen();
void handleChannelOpenFailure(); void handleChannelOpenFailure();
@@ -141,6 +143,8 @@ private:
typedef QPair<StateList, PacketHandler> HandlerInStates; typedef QPair<StateList, PacketHandler> HandlerInStates;
QHash<SshPacketType, HandlerInStates> m_packetHandlers; QHash<SshPacketType, HandlerInStates> m_packetHandlers;
static const quint64 InvalidSeqNr;
QTcpSocket *m_socket; QTcpSocket *m_socket;
SshStateInternal m_state; SshStateInternal m_state;
SshIncomingPacket m_incomingPacket; SshIncomingPacket m_incomingPacket;
@@ -152,8 +156,10 @@ private:
QString m_errorString; QString m_errorString;
QScopedPointer<SshKeyExchange> m_keyExchange; QScopedPointer<SshKeyExchange> m_keyExchange;
QTimer m_timeoutTimer; QTimer m_timeoutTimer;
QTimer m_keepAliveTimer;
bool m_ignoreNextPacket; bool m_ignoreNextPacket;
SshConnection *m_conn; SshConnection *m_conn;
quint64 m_lastInvalidMsgSeqNr;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -257,7 +257,23 @@ SshDebug SshIncomingPacket::extractDebug() const
return msg; return msg;
} catch (SshPacketParseException &) { } catch (SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_MSG_USERAUTH_BANNER."); "Invalid SSH_MSG_DEBUG.");
}
}
SshUnimplemented SshIncomingPacket::extractUnimplemented() const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_MSG_UNIMPLEMENTED);
try {
SshUnimplemented msg;
quint32 offset = TypeOffset + 1;
msg.invalidMsgSeqNr = SshPacketParser::asUint32(m_data, &offset);
return msg;
} catch (SshPacketParseException &) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Invalid SSH_MSG_UNIMPLEMENTED.");
} }
} }

View File

@@ -91,6 +91,11 @@ struct SshDebug
QByteArray language; QByteArray language;
}; };
struct SshUnimplemented
{
quint32 invalidMsgSeqNr;
};
struct SshChannelOpenFailure struct SshChannelOpenFailure
{ {
quint32 localChannel; quint32 localChannel;
@@ -156,6 +161,7 @@ public:
SshDisconnect extractDisconnect() const; SshDisconnect extractDisconnect() const;
SshUserAuthBanner extractUserAuthBanner() const; SshUserAuthBanner extractUserAuthBanner() const;
SshDebug extractDebug() const; SshDebug extractDebug() const;
SshUnimplemented extractUnimplemented() const;
SshChannelOpenFailure extractChannelOpenFailure() const; SshChannelOpenFailure extractChannelOpenFailure() const;
SshChannelOpenConfirmation extractChannelOpenConfirmation() const; SshChannelOpenConfirmation extractChannelOpenConfirmation() const;

View File

@@ -130,6 +130,16 @@ void SshOutgoingPacket::generateRequestFailurePacket()
init(SSH_MSG_REQUEST_FAILURE).finalize(); init(SSH_MSG_REQUEST_FAILURE).finalize();
} }
void SshOutgoingPacket::generateIgnorePacket()
{
init(SSH_MSG_IGNORE).finalize();
}
void SshOutgoingPacket::generateInvalidMessagePacket()
{
init(SSH_MSG_INVALID).finalize();
}
void SshOutgoingPacket::generateSessionPacket(quint32 channelId, void SshOutgoingPacket::generateSessionPacket(quint32 channelId,
quint32 windowSize, quint32 maxPacketSize) quint32 windowSize, quint32 maxPacketSize)
{ {

View File

@@ -59,6 +59,8 @@ public:
void generateUserAuthByKeyRequestPacket(const QByteArray &user, void generateUserAuthByKeyRequestPacket(const QByteArray &user,
const QByteArray &service); const QByteArray &service);
void generateRequestFailurePacket(); void generateRequestFailurePacket();
void generateIgnorePacket();
void generateInvalidMessagePacket();
void generateSessionPacket(quint32 channelId, quint32 windowSize, void generateSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize); quint32 maxPacketSize);
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var, void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,

View File

@@ -79,7 +79,12 @@ enum SshPacketType {
SSH_MSG_CHANNEL_CLOSE = 97, SSH_MSG_CHANNEL_CLOSE = 97,
SSH_MSG_CHANNEL_REQUEST = 98, SSH_MSG_CHANNEL_REQUEST = 98,
SSH_MSG_CHANNEL_SUCCESS = 99, SSH_MSG_CHANNEL_SUCCESS = 99,
SSH_MSG_CHANNEL_FAILURE = 100 SSH_MSG_CHANNEL_FAILURE = 100,
// Not completely safe, since the server may actually understand this
// message type as an extension. Switch to a different value in that case
// (between 128 and 191).
SSH_MSG_INVALID = 128
}; };
enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 }; enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 };

View File

@@ -133,6 +133,18 @@ void SshSendFacility::sendRequestFailurePacket()
sendPacket(); sendPacket();
} }
void SshSendFacility::sendIgnorePacket()
{
m_outgoingPacket.generateIgnorePacket();
sendPacket();
}
void SshSendFacility::sendInvalidPacket()
{
m_outgoingPacket.generateInvalidMessagePacket();
sendPacket();
}
void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize, void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize) quint32 maxPacketSize)
{ {

View File

@@ -66,6 +66,8 @@ public:
void sendUserAuthByKeyRequestPacket(const QByteArray &user, void sendUserAuthByKeyRequestPacket(const QByteArray &user,
const QByteArray &service); const QByteArray &service);
void sendRequestFailurePacket(); void sendRequestFailurePacket();
void sendIgnorePacket();
void sendInvalidPacket();
void sendSessionPacket(quint32 channelId, quint32 windowSize, void sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize); quint32 maxPacketSize);
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var, void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
@@ -78,6 +80,7 @@ public:
const QByteArray &signalName); const QByteArray &signalName);
void sendChannelEofPacket(quint32 remoteChannel); void sendChannelEofPacket(quint32 remoteChannel);
void sendChannelClosePacket(quint32 remoteChannel); void sendChannelClosePacket(quint32 remoteChannel);
quint32 nextClientSeqNr() const { return m_clientSeqNr; }
private: private:
void sendPacket(); void sendPacket();