Rabbit Remote Control 0.0.30
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 QSharedPointer<CParameterChannelSSH> parameter, QObject *parent)
35 : CChannelSSHTunnel{parameter, 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 }
317 } while(m_BufferLength == nRet);
318 return 0;
319}
320
321int CChannelSSHTunnelForward::SSHReadyRead()
322{
323 int nRet = 0;
324
325 if(SSH_INVALID_SOCKET == m_Connector) {
326 //qDebug(log) << "The connector is invalid";
327 return 0;
328 }
329
330 Q_ASSERT(m_pBuffer);
331 if(!m_pBuffer)
332 return -1;
333
334 do {
335 nRet = read(m_pBuffer, m_BufferLength);
336 if(nRet < 0) {
337 QString szErr = "read data from ssh tunnel fail:" + errorString();
338 qCritical(log) << szErr;
339 setErrorString(szErr);
340 return nRet;
341 }
342 if(nRet > 0) {
343 int n = send(m_Connector, m_pBuffer, nRet, 0);
344 if(n < 0) {
345 //qCritical(log) << "send to socket fail" << errno;
346 if(EAGAIN == errno || EWOULDBLOCK == errno)
347 return 0;
348 else {
349 QString szErr = "send to socket fail, connector fd:" + QString::number(m_Connector)
350 + " - [" + QString::number(errno) + "] - " + strerror(errno);
351 qCritical(log) << szErr;
352 setErrorString(szErr);
353 return errno;
354 }
355 }
356 }
357 } while(m_BufferLength == nRet);
358 return 0;
359}
360
361int CChannelSSHTunnelForward::Process()
362{
363 int nRet = 0;
364
365 if(!m_Session || !m_Channel || !ssh_channel_is_open(m_Channel)
366 || ssh_channel_is_eof(m_Channel)) {
367 QString szErr = "The channel is not open";
368 qCritical(log) << szErr;
369 setErrorString(szErr);
370 return -1;
371 }
372
373 struct timeval timeout = {0, 50000};
374 ssh_channel channels[2], channel_out[2];
375 channels[0] = m_Channel;
376 channels[1] = nullptr;
377
378 fd_set set;
379 FD_ZERO(&set);
380 socket_t fd = SSH_INVALID_SOCKET;
381 socket_t fdSSH = ssh_get_fd(m_Session);
382 //qDebug(log) << "ssh_select:" << m_Server << m_Connector;
383 if(SSH_INVALID_SOCKET != m_Listen) {
384 //qDebug(log) << "server listen";
385 FD_SET(m_Listen, &set);
386 fd = m_Listen;
387 nRet = select(fd + 1, &set, NULL, NULL, &timeout);
388 } else if(SSH_INVALID_SOCKET != m_Connector) {
389 //qDebug(log) << "recv connect";
390 FD_SET(m_Connector, &set);
391 fd = m_Connector;
392 if(SSH_INVALID_SOCKET != fdSSH)
393 {
394 FD_SET(fdSSH, &set);
395 fd = std::max(fd, fdSSH);
396 }
397 nRet = ssh_select(channels, channel_out, fd + 1, &set, &timeout);
398 }
399 //qDebug(log) << "ssh_select end:" << nRet;
400
401 if(nRet < 0) {
402 QString szErr;
403 szErr = "ssh_channel_select failed: " + QString::number(nRet);
404 szErr += ssh_get_error(m_Session);
405 qCritical(log) << szErr;
406 setErrorString(szErr);
407 return nRet;
408 }
409
410 //qDebug(log) << "recv socket" << m_Server << m_Connector;
411 if(SSH_INVALID_SOCKET != m_Listen && FD_ISSET(m_Listen, &set)) {
412 nRet = AcceptConnect();
413 return nRet;
414 } else if(SSH_INVALID_SOCKET != m_Connector && FD_ISSET(m_Connector, &set)) {
415 nRet = ReadConnect();
416 if(nRet) return nRet;
417 }
418
419 if(!channels[0]) {
420 qDebug(log) << "There is not channel";
421 return 0;
422 }
423
424 if(ssh_channel_is_eof(m_Channel)) {
425 qWarning(log) << "Channel is eof";
426 setErrorString(tr("The channel is eof"));
427 return -1;
428 }
429
430 nRet = ssh_channel_poll(m_Channel, 0);
431 //qDebug(log) << "ssh_channel_poll:" << nRet;
432 if(nRet < 0) {
433 QString szErr;
434 szErr = "ssh_channel_poll failed. nRet:";
435 szErr += QString::number(nRet);
436 szErr += ssh_get_error(m_Session);
437 qCritical(log) << szErr;
438 setErrorString(szErr);
439 return nRet;
440 }
441 if(nRet == 0) {
442 //qDebug(log) << "There is not data in channel";
443 return 0;
444 }
445
446 return SSHReadyRead();
447}
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