-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathssh_port_forward.cpp
More file actions
174 lines (142 loc) · 4.65 KB
/
Copy pathssh_port_forward.cpp
File metadata and controls
174 lines (142 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include "ssh_port_forward.h"
#include "ssh_client.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <QThread>
SSHPortForward::SSHPortForward(SSHClient *client, QObject *parent)
: QObject(parent), m_client(client), m_server(nullptr), m_remotePort(0), m_localPort(0), m_isForwarding(false)
{
m_server = new QTcpServer(this);
connect(m_server, &QTcpServer::newConnection, this, &SSHPortForward::handleNewConnection);
}
SSHPortForward::~SSHPortForward()
{
stopForwarding();
}
bool SSHPortForward::startForwarding(quint16 localPort, const QString &remoteHost, quint16 remotePort)
{
// Make sure we're fully stopped first
stopForwarding();
if (!m_client || !m_client->isConnected() || !m_client->isAuthenticated())
{
emit error("SSH client not ready");
return false;
}
// Ensure server is in a clean state
if (m_server->isListening())
{
m_server->close();
}
// Store values before attempting to listen
m_remoteHost = remoteHost;
m_remotePort = remotePort;
if (!m_server->listen(QHostAddress::LocalHost, localPort))
{
emit error(QString("Failed to start listening on port %1: %2").arg(localPort).arg(m_server->errorString()));
m_remoteHost.clear();
m_remotePort = 0;
return false;
}
m_localPort = m_server->serverPort();
m_isForwarding = true;
QMetaObject::invokeMethod(this, [this]() { emit forwardingStarted(m_localPort); }, Qt::QueuedConnection);
return true;
}
void SSHPortForward::stopForwarding()
{
if (!m_isForwarding)
return;
// First set flag to prevent new connections
m_isForwarding = false;
// Stop listening for new connections
if (m_server->isListening())
{
m_server->close();
}
// Close all active channels safely
QList< ForwardingChannel * > channelsToRemove = m_channels; // Make a copy
for (auto channel : channelsToRemove)
{
if (channel)
{
channel->stop();
channel->deleteLater();
}
}
m_channels.clear();
// Reset other members
m_localPort = 0;
m_remotePort = 0;
m_remoteHost.clear();
emit forwardingStopped();
}
bool SSHPortForward::isForwarding() const
{
return m_isForwarding;
}
quint16 SSHPortForward::localPort() const
{
return m_localPort;
}
void SSHPortForward::handleNewConnection()
{
QTcpSocket *socket = m_server->nextPendingConnection();
if (!socket)
return;
// Configure socket for optimal performance
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 256 * 1024);
socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 256 * 1024);
ssh_channel channel = ssh_channel_new(m_client->session());
if (!channel)
{
socket->close();
socket->deleteLater();
emit error("Failed to create SSH channel");
return;
}
// Keep blocking for initial setup
ssh_channel_set_blocking(channel, 1);
int rc = ssh_channel_open_forward(channel, m_remoteHost.toUtf8().constData(), m_remotePort, "localhost",
socket->localPort());
if (rc != SSH_OK)
{
ssh_channel_free(channel);
socket->close();
socket->deleteLater();
emit error(QString("Failed to open forward channel: %1").arg(ssh_get_error(m_client->session())));
return;
}
// Switch to non-blocking for data transfer
ssh_channel_set_blocking(channel, 0);
auto forwardChannel = new ForwardingChannel(channel, this);
m_channels.append(forwardChannel);
// Connect socket -> channel
connect(socket, &QTcpSocket::readyRead, this, [socket, forwardChannel]() {
if (socket->bytesAvailable() > 0)
{
forwardChannel->writeData(socket->readAll());
}
});
// Connect channel -> socket
connect(
forwardChannel, &ForwardingChannel::dataReceived, socket,
[socket](const QByteArray &data) { socket->write(data); }, Qt::DirectConnection);
// Handle closures
connect(forwardChannel, &ForwardingChannel::channelClosed, socket, &QTcpSocket::close);
connect(socket, &QTcpSocket::disconnected, this, [this, socket, forwardChannel]() {
cleanupChannel(forwardChannel);
socket->deleteLater();
});
forwardChannel->start();
emit newConnectionEstablished(m_remoteHost, m_remotePort);
}
void SSHPortForward::cleanupChannel(ForwardingChannel *channel)
{
if (!channel)
return;
m_channels.removeOne(channel);
channel->stop();
channel->deleteLater();
emit connectionClosed();
}