Commit 0d430277 authored by Christian Kandeler's avatar Christian Kandeler
Browse files

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
parent 8ae0ad94
......@@ -188,6 +188,8 @@ SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn)
{
setupPacketHandlers();
m_timeoutTimer.setSingleShot(true);
m_keepAliveTimer.setSingleShot(true);
m_keepAliveTimer.setInterval(10000);
connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
}
......@@ -255,6 +257,9 @@ void SshConnectionPrivate::setupPacketHandlers()
<< KeyExchangeStarted << KeyExchangeSuccess
<< UserAuthServiceRequested << UserAuthRequested
<< ConnectionEstablished, &This::handleDisconnect);
setupPacketHandler(SSH_MSG_UNIMPLEMENTED,
StateList() << ConnectionEstablished, &This::handleUnimplementedPacket);
}
void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
......@@ -436,6 +441,9 @@ void SshConnectionPrivate::handleUserAuthSuccessPacket()
m_state = ConnectionEstablished;
m_timeoutTimer.stop();
emit connected();
m_lastInvalidMsgSeqNr = InvalidSeqNr;
connect(&m_keepAliveTimer, SIGNAL(timeout()), SLOT(sendKeepAlivePacket()));
m_keepAliveTimer.start();
}
void SshConnectionPrivate::handleUserAuthFailurePacket()
......@@ -452,6 +460,19 @@ void SshConnectionPrivate::handleDebugPacket()
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()
{
m_channelManager->handleChannelRequest(m_incomingPacket);
......@@ -514,6 +535,8 @@ void SshConnectionPrivate::handleDisconnect()
"", tr("Server closed connection: %1").arg(msg.description));
}
void SshConnectionPrivate::sendData(const QByteArray &data)
{
if (canUseSocket())
......@@ -541,6 +564,14 @@ void SshConnectionPrivate::handleTimeout()
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)
{
m_incomingData.clear();
......@@ -577,6 +608,8 @@ void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
m_timeoutTimer.stop();
disconnect(m_socket, 0, this, 0);
disconnect(&m_timeoutTimer, 0, this, 0);
m_keepAliveTimer.stop();
disconnect(&m_keepAliveTimer, 0, this, 0);
try {
m_channelManager->closeAllChannels();
m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
......@@ -606,5 +639,7 @@ QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
return m_channelManager->createSftpChannel();
}
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
} // namespace Internal
} // namespace Core
......@@ -102,6 +102,7 @@ private:
Q_SLOT void handleSocketError();
Q_SLOT void handleSocketDisconnected();
Q_SLOT void handleTimeout();
Q_SLOT void sendKeepAlivePacket();
void handleServerId();
void handlePackets();
......@@ -116,6 +117,7 @@ private:
void handleUserAuthBannerPacket();
void handleGlobalRequest();
void handleDebugPacket();
void handleUnimplementedPacket();
void handleChannelRequest();
void handleChannelOpen();
void handleChannelOpenFailure();
......@@ -141,6 +143,8 @@ private:
typedef QPair<StateList, PacketHandler> HandlerInStates;
QHash<SshPacketType, HandlerInStates> m_packetHandlers;
static const quint64 InvalidSeqNr;
QTcpSocket *m_socket;
SshStateInternal m_state;
SshIncomingPacket m_incomingPacket;
......@@ -152,8 +156,10 @@ private:
QString m_errorString;
QScopedPointer<SshKeyExchange> m_keyExchange;
QTimer m_timeoutTimer;
QTimer m_keepAliveTimer;
bool m_ignoreNextPacket;
SshConnection *m_conn;
quint64 m_lastInvalidMsgSeqNr;
};
} // namespace Internal
......
......@@ -257,7 +257,23 @@ SshDebug SshIncomingPacket::extractDebug() const
return msg;
} catch (SshPacketParseException &) {
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.");
}
}
......
......@@ -91,6 +91,11 @@ struct SshDebug
QByteArray language;
};
struct SshUnimplemented
{
quint32 invalidMsgSeqNr;
};
struct SshChannelOpenFailure
{
quint32 localChannel;
......@@ -156,6 +161,7 @@ public:
SshDisconnect extractDisconnect() const;
SshUserAuthBanner extractUserAuthBanner() const;
SshDebug extractDebug() const;
SshUnimplemented extractUnimplemented() const;
SshChannelOpenFailure extractChannelOpenFailure() const;
SshChannelOpenConfirmation extractChannelOpenConfirmation() const;
......
......@@ -130,6 +130,16 @@ void SshOutgoingPacket::generateRequestFailurePacket()
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,
quint32 windowSize, quint32 maxPacketSize)
{
......
......@@ -59,6 +59,8 @@ public:
void generateUserAuthByKeyRequestPacket(const QByteArray &user,
const QByteArray &service);
void generateRequestFailurePacket();
void generateIgnorePacket();
void generateInvalidMessagePacket();
void generateSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize);
void generateEnvPacket(quint32 remoteChannel, const QByteArray &var,
......
......@@ -79,7 +79,12 @@ enum SshPacketType {
SSH_MSG_CHANNEL_CLOSE = 97,
SSH_MSG_CHANNEL_REQUEST = 98,
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 };
......
......@@ -133,6 +133,18 @@ void SshSendFacility::sendRequestFailurePacket()
sendPacket();
}
void SshSendFacility::sendIgnorePacket()
{
m_outgoingPacket.generateIgnorePacket();
sendPacket();
}
void SshSendFacility::sendInvalidPacket()
{
m_outgoingPacket.generateInvalidMessagePacket();
sendPacket();
}
void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize)
{
......
......@@ -66,6 +66,8 @@ public:
void sendUserAuthByKeyRequestPacket(const QByteArray &user,
const QByteArray &service);
void sendRequestFailurePacket();
void sendIgnorePacket();
void sendInvalidPacket();
void sendSessionPacket(quint32 channelId, quint32 windowSize,
quint32 maxPacketSize);
void sendEnvPacket(quint32 remoteChannel, const QByteArray &var,
......@@ -78,6 +80,7 @@ public:
const QByteArray &signalName);
void sendChannelEofPacket(quint32 remoteChannel);
void sendChannelClosePacket(quint32 remoteChannel);
quint32 nextClientSeqNr() const { return m_clientSeqNr; }
private:
void sendPacket();
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment