Rabbit Remote Control 0.0.36
Loading...
Searching...
No Matches
ChannelSSHTunnelForward.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QLoggingCategory>
4#include <QStandardPaths>
5#include <QThread>
6#include <QDir>
7
8#if defined(Q_OS_WIN)
9 #if defined(HAVE_UNIX_DOMAIN_SOCKET)
10 #include <WinSock2.h>
11 /* [AF_UNIX comes to Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/)
12 * How can I write a Windows AF_UNIX app?
13 * - Download the Windows Insiders SDK for the Windows build 17061 — available here.
14 * - Check whether your Windows build has support for unix socket by running “sc query afunix” from a Windows admin command prompt.
15 * - #include <afunix.h> in your Windows application and write a Windows unix socket winsock application as you would write any other unix socket application, but, using Winsock API’s.
16 */
17 #include <afunix.h>
18 #endif
19 #define socklen_t int
20#else
21 #include <arpa/inet.h>
22 #include <sys/socket.h>
23 #include <netinet/tcp.h>
24 #if defined(HAVE_UNIX_DOMAIN_SOCKET)
25 #include <sys/un.h>
26 #endif
27#endif
28
29#include "ChannelSSHTunnelForward.h"
30
31static Q_LOGGING_CATEGORY(log, "Channel.SSH.Tunnel.Forward")
32
34 CParameterSSHTunnel *parameter, CParameterNet *remote, CConnect *pConnect, QObject *parent)
35 : CChannelSSHTunnel{parameter, remote, pConnect, false, parent},
36 m_Listen(SSH_INVALID_SOCKET),
37 m_Connector(SSH_INVALID_SOCKET),
38 m_pBuffer(nullptr)
39{
40 qDebug(log) << "ChannelSSHTunnelForward::ChannelSSHTunnelForward()";
41 m_pBuffer = new char[m_BufferLength];
42}
43
44CChannelSSHTunnelForward::~CChannelSSHTunnelForward()
45{
46 qDebug(log) << "ChannelSSHTunnelForward::~ChannelSSHTunnelForward()";
47 if(m_pBuffer)
48 delete []m_pBuffer;
49}
50
51int CChannelSSHTunnelForward::OpenSocket()
52{
53 int nRet = 0;
54 bool bRet = false;
55 int family = AF_INET;
56 int type = SOCK_STREAM;
57 QString szErr;
58
59 socklen_t size = 0;
60 struct sockaddr_in listen_addr;
61 memset(&listen_addr, 0, sizeof(listen_addr));
62 listen_addr.sin_family = AF_INET;
63 listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
64 listen_addr.sin_port = 0; /* kernel chooses port. */
65
66 m_Listen = socket(family, type, 0);
67 if(SSH_INVALID_SOCKET == m_Listen) {
68 szErr = "Create socket fail:" + QString::number(errno);
69 qCritical(log) << szErr;
70 setErrorString(szErr);
71 return false;
72 }
73
74 Channel::CEvent::SetSocketNonBlocking(m_Listen);
75 //Channel::CEvent::EnableNagles(m_Server, false);
76
77 do {
78 if (bind(m_Listen, (struct sockaddr *) &listen_addr, sizeof(listen_addr))
79 == -1) {
80 szErr = "bind fail:" + QString(inet_ntoa(listen_addr.sin_addr))
81 + QString(":") + QString::number(ntohs(listen_addr.sin_port))
82 + " - " + QString::number(errno);
83 qCritical(log) << szErr;
84 setErrorString(szErr);
85 bRet = false;
86 break;
87 }
88 if (listen(m_Listen, 1) == -1) {
89 szErr = "listen fail:" + QString(inet_ntoa(listen_addr.sin_addr))
90 + QString(":") + QString::number(ntohs(listen_addr.sin_port))
91 + " - " + QString::number(errno);
92 qCritical(log) << szErr;
93 setErrorString(szErr);
94 bRet = false;
95 break;
96 }
97 /* We want to find out the port number to connect to. */
98 size = sizeof(listen_addr);
99 if (getsockname(m_Listen, (struct sockaddr *) &listen_addr, &size) == -1)
100 {
101 bRet = false;
102 break;
103 }
104 if (size != sizeof (listen_addr))
105 break;
106 qDebug(log) << "listener in:"
107 << inet_ntoa(listen_addr.sin_addr)
108 + QString(":") + QString::number(ntohs(listen_addr.sin_port));
109 emit sigServer(inet_ntoa(listen_addr.sin_addr), ntohs(listen_addr.sin_port));
110 return 0;
111 } while(0);
112
113 if(!bRet) {
114 CloseSocket(m_Listen);
115 m_Listen = SSH_INVALID_SOCKET;
116 }
117
118 return -1;
119}
120
121#if defined(HAVE_UNIX_DOMAIN_SOCKET)
122int CChannelSSHTunnelForward::OpenUnixSocket()
123{
124 bool bRet = false;
125 int family = AF_UNIX;
126 int type = SOCK_STREAM;
127 QString szErr;
128
129 socklen_t size = 0;
130 struct sockaddr_un listen_addr;
131 QString szPath;
132 QString szUnixDomainSocket;
133 memset(&listen_addr, 0, sizeof(listen_addr));
134 szPath = QStandardPaths::writableLocation(
135 QStandardPaths::TempLocation)
136 + QDir::separator() + "RabbitRemoteControl";
137 auto now = std::chrono::system_clock::now();
138 auto tick = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
139 szUnixDomainSocket = szPath + QDir::separator() + "RRC_" + QString::number(tick) + "_"
140 + QString::number((unsigned long)QThread::currentThreadId()) + ".socket";
141 QDir p(szPath);
142 if(!p.exists())
143 p.mkpath(szPath);
144 QDir f(szUnixDomainSocket);
145 if(f.exists())
146 f.remove(szUnixDomainSocket);
147 size = sizeof(sockaddr_un::sun_path);
148 if(szUnixDomainSocket.size() > size)
149 {
150 qCritical(log) << "The unix domain socket length greater then" << size;
151 return -2;
152 }
153 memset(&listen_addr, 0, sizeof(listen_addr));
154 listen_addr.sun_family = AF_UNIX;
155 strcpy(listen_addr.sun_path, szUnixDomainSocket.toStdString().c_str());
156
157 m_Listen = socket(family, type, 0);
158 if(SSH_INVALID_SOCKET == m_Listen) {
159 szErr = "Create socket fail:" + QString::number(errno);
160 qCritical(log) << szErr;
161 setErrorString(szErr);
162 return false;
163 }
164
165 Channel::CEvent::SetSocketNonBlocking(m_Listen);
166 //Channel::CEvent::EnableNagles(m_Server, false);
167
168 do {
169 if (bind(m_Listen, (struct sockaddr *) &listen_addr, sizeof(listen_addr))
170 == -1) {
171 szErr = "bind fail:" + QString(listen_addr.sun_path)
172 + " - " + QString::number(errno);
173 qCritical(log) << szErr;
174 setErrorString(szErr);
175 bRet = false;
176 break;
177 }
178 if (listen(m_Listen, 1) == -1) {
179 szErr = "listen fail:" + QString(listen_addr.sun_path)
180 + " - " + QString::number(errno);
181 qCritical(log) << szErr;
182 setErrorString(szErr);
183 bRet = false;
184 break;
185 }
186
187 emit sigServer(szUnixDomainSocket);
188 qDebug(log) << "listener in:" << listen_addr.sun_path;
189 return 0;
190 } while(0);
191
192 if(!bRet) {
193 CloseSocket(m_Listen);
194 m_Listen = SSH_INVALID_SOCKET;
195 }
196
197 return -1;
198}
199#endif
200
202{
203 bool bRet = false;
204
205 bRet = CChannelSSHTunnel::open(mode);
206 if(!bRet)
207 return false;
208
209 int nRet = OpenSocket();
210 if(nRet) return false;
211
212 return bRet;
213}
214
215void CChannelSSHTunnelForward::close()
216{
217 qDebug(log) << "CChannelSSHTunnelForward::close()";
218 if(SSH_INVALID_SOCKET != m_Listen)
219 CloseSocket(m_Listen);
220 if(SSH_INVALID_SOCKET != m_Connector)
221 CloseSocket(m_Connector);
222 CChannelSSHTunnel::close();
223}
224
225int CChannelSSHTunnelForward::CloseSocket(socket_t &s)
226{
227 int nRet = 0;
228 if(SSH_INVALID_SOCKET == s)
229 return 0;
230#if defined(Q_OS_WIN)
231 nRet = ::closesocket(s);
232#else
233 nRet = ::close(s);
234#endif
235 if(nRet)
236 qCritical(log) << "Close socket fail" << errno << ":" << strerror(errno);
237 s = SSH_INVALID_SOCKET;
238 return nRet;
239}
240
241int CChannelSSHTunnelForward::AcceptConnect()
242{
243 //qDebug(log) << "CChannelSSHTunnelForward::AcceptConnect()";
244 if(SSH_INVALID_SOCKET == m_Listen) return 0;
245#if defined(HAVE_UNIX_DOMAIN_SOCKET)
246 struct sockaddr_un connect_addr;
247#else
248 struct sockaddr_in connect_addr;
249#endif
250 memset(&connect_addr, 0, sizeof(connect_addr));
251 socklen_t size = sizeof(connect_addr);
252 m_Connector = accept(m_Listen, (struct sockaddr *)&connect_addr, &size);
253 if(SSH_INVALID_SOCKET == m_Connector)
254 {
255 /*
256 qCritical(log) << "accept fail" << errno << "[" << strerror(errno) << "]"
257 << "m_Server" << m_Server;//*/
258 if(EAGAIN == errno || EWOULDBLOCK == errno)
259 return 0;
260 else {
261 QString szErr = "The server accept is fail:";
262 szErr += QString::number(errno) + " [" + strerror(errno) + "]";
263 qCritical(log) << szErr;
264 setErrorString(szErr);
265 return errno;
266 }
267 }
268 qDebug(log) << "accept connector fd:" << m_Connector;
269 Channel::CEvent::SetSocketNonBlocking(m_Connector);
270 //Channel::CEvent::EnableNagles(m_Connector, false);
271 CloseSocket(m_Listen);
272 m_Listen = SSH_INVALID_SOCKET;
273 return 0;
274}
275
276int CChannelSSHTunnelForward::ReadConnect()
277{
278 int nRet = 0;
279
280 if(SSH_INVALID_SOCKET == m_Connector) {
281 qDebug(log) << "The connector is invalid";
282 return 0;
283 }
284
285 Q_ASSERT(m_pBuffer);
286 if(!m_pBuffer)
287 return -1;
288
289 //qDebug(log) << "CChannelSSHTunnelForward::ReadConnect()";
290 do {
291 nRet = recv(m_Connector, m_pBuffer, m_BufferLength, 0);
292 if(nRet < 0)
293 {
294 /*
295 qCritical(log) << "read data from socket fail" << errno<< "[" << strerror(errno) << "]"
296 << "m_Server" << m_Server;//*/
297 if(EAGAIN == errno || EWOULDBLOCK == errno)
298 return 0;
299 else {
300 QString szErr = "read data from socket fail:"
301 + QString::number(errno) + " - " + strerror(errno)
302 + " m_Server:" + QString::number(m_Listen);
303 qCritical(log) << szErr;
304 setErrorString(szErr);
305 return errno;
306 }
307 }
308 if(nRet > 0) {
309 int n = write(m_pBuffer, nRet);
310 if(n < 0) {
311 QString szErr = "write ssh tunnel fail:" + errorString();
312 qCritical(log) << szErr;
313 setErrorString(szErr);
314 return -1;
315 }
316 // TODO: 检查发送的数据。因为系统 socket 有发送缓存,所以此情况一般不会出现,只是提醒。
317 if(n < nRet)
318 qWarning(log) << "The send data" << n << "<" << nRet;
319 }
320 } while(m_BufferLength == nRet);
321 return 0;
322}
323
324int CChannelSSHTunnelForward::SSHReadyRead()
325{
326 int nRet = 0;
327
328 if(SSH_INVALID_SOCKET == m_Connector) {
329 //qDebug(log) << "The connector is invalid";
330 return 0;
331 }
332
333 Q_ASSERT(m_pBuffer);
334 if(!m_pBuffer)
335 return -1;
336
337 do {
338 nRet = read(m_pBuffer, m_BufferLength);
339 if(nRet < 0) {
340 QString szErr = "read data from ssh tunnel fail:" + errorString();
341 qCritical(log) << szErr;
342 setErrorString(szErr);
343 return nRet;
344 }
345 if(nRet > 0) {
346 int n = send(m_Connector, m_pBuffer, nRet, 0);
347 if(n < 0) {
348 //qCritical(log) << "send to socket fail" << errno;
349 if(EAGAIN == errno || EWOULDBLOCK == errno) {
350 // TODO: add resend
351 return 0;
352 }
353 else {
354 QString szErr = "send to socket fail, connector fd:" + QString::number(m_Connector)
355 + " - [" + QString::number(errno) + "] - " + strerror(errno);
356 qCritical(log) << szErr;
357 setErrorString(szErr);
358 return errno;
359 }
360 }
361 }
362 } while(m_BufferLength == nRet);
363 return 0;
364}
365
373{
374 int nRet = 0;
375
376 if(!m_Session || !m_Channel || !ssh_channel_is_open(m_Channel)
377 || ssh_channel_is_eof(m_Channel)) {
378 QString szErr = "The channel is not open";
379 qCritical(log) << szErr;
380 setErrorString(szErr);
381 return -1;
382 }
383
384 struct timeval timeout = {0, 50000};
385 ssh_channel channels[2], channel_out[2];
386 channels[0] = m_Channel;
387 channels[1] = nullptr;
388 channel_out[0] = channel_out[1] = nullptr;
389
390 fd_set set;
391 FD_ZERO(&set);
392 socket_t fd = SSH_INVALID_SOCKET;
393 socket_t fdSSH = ssh_get_fd(m_Session);
394 //qDebug(log) << "ssh_select:" << m_Server << m_Connector;
395 if(SSH_INVALID_SOCKET != m_Listen) {
396 //qDebug(log) << "server listen";
397 FD_SET(m_Listen, &set);
398 fd = m_Listen;
399 //nRet = select(fd + 1, &set, NULL, NULL, &timeout);
400 nRet = ssh_select(channels, channel_out, fd + 1, &set, &timeout);
401 } else if(SSH_INVALID_SOCKET != m_Connector) {
402 //qDebug(log) << "recv connect";
403 FD_SET(m_Connector, &set);
404 fd = m_Connector;
405 if(SSH_INVALID_SOCKET != fdSSH)
406 {
407 FD_SET(fdSSH, &set);
408 fd = std::max(fd, fdSSH);
409 }
410 nRet = ssh_select(channels, channel_out, fd + 1, &set, &timeout);
411 }
412 //qDebug(log) << "ssh_select end:" << nRet;
413 if(EINTR == nRet)
414 return 0;
415 if(SSH_OK != nRet) {
416 QString szErr;
417 szErr = "ssh_channel_select failed: " + QString::number(nRet);
418 szErr += ssh_get_error(m_Session);
419 qCritical(log) << szErr;
420 setErrorString(szErr);
421 return nRet;
422 }
423
424 //qDebug(log) << "recv socket" << m_Server << m_Connector;
425 if(SSH_INVALID_SOCKET != m_Listen && FD_ISSET(m_Listen, &set)) {
426 nRet = AcceptConnect();
427 return nRet;
428 } else if(SSH_INVALID_SOCKET != m_Connector && FD_ISSET(m_Connector, &set)) {
429 nRet = ReadConnect();
430 if(nRet) return nRet;
431 }
432
433 if(!channel_out[0]) {
434 //qDebug(log) << "The channel is not select";
435 return 0;
436 }
437
438 if(ssh_channel_is_eof(m_Channel)) {
439 qWarning(log) << "Channel is eof";
440 setErrorString(tr("The channel is eof"));
441 // Stop
442 return -1;
443 }
444
445 // Get channel data length
446 nRet = ssh_channel_poll(m_Channel, 0);
447 //qDebug(log) << "Get channel data length:" << nRet;
448 if(SSH_ERROR == nRet)
449 {
450 QString szErr;
451 szErr = "ssh_channel_poll failed. nRet:";
452 szErr += QString::number(nRet);
453 szErr += ssh_get_error(m_Session);
454 setErrorString(szErr);
455 qCritical(log) << szErr;
456 return -6;
457 } else if(SSH_EOF == nRet) {
458 // Stop
459 return -1;
460 } else if(0 > nRet) {
461 QString szErr;
462 szErr = "ssh_channel_poll failed. nRet:";
463 szErr += QString::number(nRet);
464 szErr += ssh_get_error(m_Session);
465 setErrorString(szErr);
466 qCritical(log) << szErr;
467 // Error
468 return -7;
469 } else if(0 == nRet) {
470 //qDebug(log) << "The channel has not data";
471 return 0;
472 }
473
474 return SSHReadyRead();
475}
Includes SSH tunneling and a local socket service for forwarding data.
virtual bool open(OpenMode mode) override
ssh tunnel class
virtual bool open(OpenMode mode) override
Connect interface.
Definition Connect.h:45
Basic network parameters.