API
 
Loading...
Searching...
No Matches
sshDigger.hpp
Go to the documentation of this file.
1/** \file sshDigger.hpp
2 * \brief The MagAO-X SSH tunnel manager
3 *
4 * \ingroup sshDigger_files
5 */
6
7#ifndef sshDigger_hpp
8#define sshDigger_hpp
9
10#include <sys/wait.h>
11
12#include <iostream>
13
14#include <mx/sys/timeUtils.hpp>
15
16#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
17#include "../../magaox_git_version.h"
18
19/** \defgroup sshDigger SSH Tunnel Manager
20 * \brief Manage the SSH tunnels for MagAO-X communications..
21 *
22 * <a href="../handbook/operating/software/apps/sshDigger.html">Application Documentation</a>
23 *
24 * \ingroup apps
25 *
26 */
27
28/** \defgroup sshDigger_files SSH Tunnel Files
29 * \ingroup sshDigger
30 */
31
32//Return codes, these are primarily for testing purposes
33#define SSHDIGGER_E_NOTUNNELS (-10)
34#define SSHDIGGER_E_NOTUNNELFOUND (-11)
35#define SSHDIGGER_E_NOHOSTNAME (-12)
36#define SSHDIGGER_E_NOLOCALPORT (-13)
37#define SSHDIGGER_E_NOREMOTEPORT (-14)
38
39namespace MagAOX
40{
41namespace app
42{
43
44/// The MagAO-X SSH tunnel manager
45/** Each instance of this app manages one SSH tunnel to another computer.
46 * These tunnels are opened via the `autossh` app, which itself calls `ssh`.
47 *
48 *
49 * \todo add options for verboseness of ssh and autossh (loglevel)
50 * \todo add options for ssh and autossh log thread priorities
51 *
52 * \ingroup sshDigger
53 */
54class sshDigger : public MagAOXApp<false>
55{
56
57 //Give the test harness access.
58 friend class sshDigger_test;
59
60protected:
61
62 /** \name Configurable Parameters
63 *@{
64 */
65 std::string m_remoteHost; ///< The name of the remote host
66 int m_localPort {0}; ///< The local port to forward from
67 int m_remotePort {0}; ///< The remote port to forward to
68 int m_monitorPort {0}; ///< The monitor port
69 bool m_compress {false}; ///< Control compression on this tunnel. True is on, false is off.
70 ///@}
71
72 int m_tunnelPID {0}; ///< The PID of the autossh process
73
74 /** \name ssh log capture
75 * @{
76 */
77 int m_sshSTDERR {-1}; ///< The output of stderr of the ssh process
78 int m_sshSTDERR_input {-1}; ///< The input end of stderr, used to wake up the log thread on shutdown.
79
80 int m_sshLogThreadPrio {0}; ///< Priority of the ssh log capture thread, should normally be 0.
81
82 std::thread m_sshLogThread; ///< A separate thread for capturing ssh logs
83
84 std::string m_lastSSHLogs;
85 int m_sshError {0}; ///< Flag to signal when ssh logs an error, and should be restarted via SIGUSR1 to autossh.
86
87 ///@}
88
89 /** \name autossh log capture
90 * @{
91 */
92 std::string m_autosshLogFile; ///< Name of the autossh logfile.
93 int m_autosshLogFD {0}; ///< File descriptor of the autossh logfile.
94
95 int m_autosshLogThreadPrio {0}; ///< Priority of the autossh log capture thread, should normally be 0.
96
97 std::thread m_autosshLogThread; ///< A separate thread for capturing autossh logs
98 ///@}
99
100
101public:
102 /// Default c'tor.
103 sshDigger();
104
105 /// D'tor, declared and defined for noexcept.
108
109 virtual void setupConfig();
110
111 /// Implementation of loadConfig logic, separated for testing.
112 /** This is called by loadConfig().
113 */
114 int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
115
116 virtual void loadConfig();
117
118 /// Create the tunnel specification string, [localPort]:localhost:[remotePort].
119 /**
120 * \returns a string containing the tunnel specification.
121 */
122 std::string tunnelSpec();
123
124 /// Generate the argv vector for the exec of autossh.
125 void genArgsV( std::vector<std::string> & argsV /**< [out] will contain the argv vector for an autssh exec call */);
126
127 /// Generate the envp vector for the exec of autossh.
128 void genEnvp( std::vector<std::string> & envp /**< [out] will contain the envp vector for an autssh exec call */);
129
130 /// Creates the tunnel in a child process using exec.
131 int execTunnel();
132
133 ///Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
134 static void _sshLogThreadStart( sshDigger * s /**< [in] a pointer to an sshDigger instance (normally this) */);
135
136 /// Start the log capture.
137 int sshLogThreadStart();
138
139 /// Execute the log capture.
140 void sshLogThreadExec();
141
142 /// Process a log entry from indiserver, putting it into MagAO-X standard form
143 int processSSHLog( const std::string & logs );
144
145 ///Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
146 static void _autosshLogThreadStart( sshDigger * s /**< [in] a pointer to an sshDigger instance (normally this) */);
147
148 /// Start the log capture.
150
151 /// Execute the log capture.
153
154 /// Process a log entry from indiserver, putting it into MagAO-X standard form
155 int processAutoSSHLog( const std::string & logs );
156
157 /// Startup function
158 /**
159 *
160 */
161 virtual int appStartup();
162
163 /// Implementation of the FSM for sshDigger.
164 /** Monitors status of m_sshError flag, and sends a signal to autossh if an error is indicated.
165 *
166 * Checks that autossh is still alive, and if it has died restarts it.
167 *
168 * \returns 0 on no critical error
169 * \returns -1 on an error requiring shutdown
170 */
171 virtual int appLogic();
172
173 /// Shutdown the app.
174 /** Sends SIGTERM to autossh.
175 *
176 * Tells the two logging threads to exit, and waits for them to complete.
177 *
178 */
179 virtual int appShutdown();
180
181
182};
183
184sshDigger::sshDigger() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
185{
186 //Use the sshTunnels.conf config file
187 m_configBase = "sshTunnels";
188
189 //set mx::app::application flag to not report lack of config file for this app.
191
192 return;
193}
194
196{
197}
198
199int sshDigger::loadConfigImpl( mx::app::appConfigurator & _config )
200{
201
202 std::vector<std::string> sections;
203
204 _config.unusedSections(sections);
205
206 if( sections.size() == 0 )
207 {
208 log<text_log>("No tunnels found in config.", logPrio::LOG_CRITICAL);
209
211 }
212
213 //Now see if any sections match our m_configName
214
215 bool found =false;
216 for(size_t i=0;i<sections.size(); ++i)
217 {
218 if( sections[i] == m_configName )
219 {
220 found = true;
221 }
222 }
223
224 if( found == false )
225 {
226 log<text_log>("No matching tunnel configuration found.", logPrio::LOG_CRITICAL);
228 }
229
230 //Now we configure the tunnel.
231
232 _config.configUnused( m_remoteHost, mx::app::iniFile::makeKey(m_configName, "remoteHost" ) );
233 if( m_remoteHost == "" )
234 {
235 log<text_log>("No remote host specified.", logPrio::LOG_CRITICAL);
237 }
238
239 _config.configUnused( m_localPort, mx::app::iniFile::makeKey(m_configName, "localPort" ) );
240 if( m_localPort == 0 )
241 {
242 log<text_log>("No local port specified.", logPrio::LOG_CRITICAL);
243
245 }
246
247 _config.configUnused( m_remotePort, mx::app::iniFile::makeKey(m_configName, "remotePort" ) );
248 if( m_remotePort == 0 )
249 {
250 log<text_log>("No remote port specified.", logPrio::LOG_CRITICAL);
251
253 }
254
255 _config.configUnused( m_monitorPort, mx::app::iniFile::makeKey(m_configName, "monitorPort" ) );
256
257 _config.configUnused( m_compress, mx::app::iniFile::makeKey(m_configName, "compress" ) );
258
259 //Here we go through and access each unused config just to avoid the critical error for unrecognized configs.
260 for(size_t i=0;i<sections.size(); ++i)
261 {
262 if( sections[i] == "") continue; //this is an error
263 if( sections[i] == m_configName ) continue; //already done.
264 std::string rh;
265 _config.configUnused(rh, mx::app::iniFile::makeKey(sections[i], "remoteHost" ) );
266 if( rh == "" )
267 {
268 log<text_log>( "Config section [" + sections[i] + "] may be an invalid tunnel configuration (no remoteHost).", logPrio::LOG_WARNING);
269 }
270 int lp=0;
271 _config.configUnused(lp, mx::app::iniFile::makeKey(sections[i], "localPort" ) );
272 if( lp == 0 )
273 {
274 log<text_log>( "Config section [" + sections[i] + "] may be an invalid tunnel configuration (no localPort).", logPrio::LOG_WARNING);
275 }
276 int rp = 0;
277 _config.configUnused(rp, mx::app::iniFile::makeKey(sections[i], "remotePort" ) );
278 if( rp == 0 )
279 {
280 log<text_log>( "Config section [" + sections[i] + "] may be an invalid tunnel configuration (no remotePort).", logPrio::LOG_WARNING);
281 }
282 int mp = 0;
283 _config.configUnused( mp, mx::app::iniFile::makeKey(sections[i], "monitorPort" ) );
284 bool cmp = false;
285 _config.configUnused( cmp, mx::app::iniFile::makeKey(sections[i], "compress" ) );
286 }
287
288 return 0;
289}
290
292{
293 loadConfigImpl(config);
294}
295
297{
298 std::string ts = mx::ioutils::convertToString(m_localPort) + ":localhost:" + mx::ioutils::convertToString(m_remotePort);
299
300 return ts;
301}
302
303void sshDigger::genArgsV( std::vector<std::string> & argsV )
304{
305 std::string comp = "";
306 if(m_compress) comp = "-C";
307 argsV = {"autossh", "-M" + std::to_string(m_monitorPort), comp, "-nNTL", tunnelSpec(), m_remoteHost};
308}
309
310void sshDigger::genEnvp( std::vector<std::string> & envp )
311{
312 std::string logenv="AUTOSSH_LOGFILE=" + m_autosshLogFile;
313
314 envp = {logenv};
315}
316
318{
319 std::vector<std::string> argsV;
321
322 std::vector<std::string> envps;
323 genEnvp(envps);
324
326 {
327 std::string coml = "Starting autossh with command: ";
328 for(size_t i=0;i<argsV.size();++i)
329 {
330 coml += argsV[i];
331 coml += " ";
332 }
334 }
335
336 int filedes[2];
337 if (pipe(filedes) == -1)
338 {
340 return -1;
341 }
342
343 m_tunnelPID = fork();
344
345 if(m_tunnelPID < 0)
346 {
347 log<software_error>({__FILE__, __LINE__, errno, std::string("fork failed: ") + strerror(errno)});
348 return -1;
349 }
350
351 if(m_tunnelPID == 0)
352 {
353 //Route STDERR of child to pipe input.
354 while ((dup2(filedes[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
355 close(filedes[1]);
356 close(filedes[0]);
357
358 const char ** args = new const char*[argsV.size() + 1];
359 for(size_t i=0; i< argsV.size();++i) args[i] = argsV[i].data();
360 args[argsV.size()] = NULL;
361
362
363 const char ** envp = new const char*[envps.size() + 1];
364 for(size_t i=0; i< envps.size();++i) envp[i] = envps[i].data();
365 envp[envps.size()] = NULL;
366
367 execvpe("autossh", (char * const*) args, (char * const*) envp);
368
369 std::cerr << "returned\n";
370 log<software_error>({__FILE__, __LINE__, errno, std::string("execvp returned: ") + strerror(errno)});
371
372 delete[] args;
373
374 return -1;
375 }
376
377
378
379 m_sshSTDERR = filedes[0];
381
383 {
384 std::string coml = "autossh tunnel started with PID " + mx::ioutils::convertToString(m_tunnelPID);
386 }
387 return 0;
388}
389
390inline
392{
393 s->sshLogThreadExec();
394}
395
396inline
398{
399 try
400 {
401 m_sshLogThread = std::thread( _sshLogThreadStart, this);
402 }
403 catch( const std::exception & e )
404 {
405 log<software_error>({__FILE__,__LINE__, std::string("Exception on ssh log thread start: ") + e.what()});
406 return -1;
407 }
408 catch( ... )
409 {
410 log<software_error>({__FILE__,__LINE__, "Unkown exception on ssh log thread start"});
411 return -1;
412 }
413
414 if(!m_sshLogThread.joinable())
415 {
416 log<software_error>({__FILE__, __LINE__, "ssh log thread did not start"});
417 return -1;
418 }
419
421 sp.sched_priority = m_sshLogThreadPrio;
422
423 int rv = pthread_setschedparam( m_sshLogThread.native_handle(), SCHED_OTHER, &sp);
424
425 if(rv != 0)
426 {
427 log<software_error>({__FILE__, __LINE__, rv, "Error setting thread params."});
428 return -1;
429 }
430
431 return 0;
432
433}
434
435inline
437{
438 char buffer[4096];
439
440 std::string logs;
441 while(m_shutdown == 0 && m_sshSTDERR > 0)
442 {
443 ssize_t count = read(m_sshSTDERR, buffer, sizeof(buffer)-1);
444 if (count <= 0)
445 {
446 continue;
447 }
448 else
449 {
450 buffer[count] = '\0';
451
452 logs += buffer;
453
454 //Keep reading until \n found, then process.
455 if(logs.back() == '\n')
456 {
457 size_t bol = 0;
458 while(bol < logs.size())
459 {
460 size_t eol = logs.find('\n', bol);
461 if(eol == std::string::npos) break;
462
463 processSSHLog(logs.substr(bol, eol-bol));
464 bol = eol + 1;
465 }
466 logs = "";
467 }
468 }
469 }
470
471}
472
473inline
474int sshDigger::processSSHLog( const std::string & logs )
475{
477
478 if(logs.find("bind: Address already in use") != std::string::npos)
479 {
481 m_sshError = 1; //Means we'll need a restart
482 }
483
484 if(logs.find("channel_setup_fwd_listener_tcpip: cannot listen to port:") != std::string::npos)
485 {
487 m_sshError = 1; //Means we'll need a restart
488 }
489
490 if(logs.find("connect failed:") != std::string::npos)
491 {
493 m_sshError = 2;
494 }
495
496 if(logs != m_lastSSHLogs)
497 {
498 m_log.log<text_log>({"SSH: " + logs}, lp);
500 }
501
502 return 0;
503}
504
505
506inline
508{
509 s->autosshLogThreadExec();
510}
511
512inline
514{
515 try
516 {
517 m_autosshLogThread = std::thread( _autosshLogThreadStart, this);
518 }
519
520 catch( const std::exception & e )
521 {
522 log<software_error>({__FILE__,__LINE__, std::string("Exception on autossh log thread start: ") + e.what()});
523 return -1;
524 }
525 catch( ... )
526 {
527 log<software_error>({__FILE__,__LINE__, "Unkown exception on autossh log thread start"});
528 return -1;
529 }
530
531 if(!m_autosshLogThread.joinable())
532 {
533 log<software_error>({__FILE__, __LINE__, "autossh log thread did not start"});
534 return -1;
535 }
536
538 sp.sched_priority = m_autosshLogThreadPrio;
539
540 int rv = pthread_setschedparam( m_autosshLogThread.native_handle(), SCHED_OTHER, &sp);
541
542 if(rv != 0)
543 {
544 log<software_error>({__FILE__, __LINE__, rv, "Error setting thread params."});
545 return -1;
546 }
547
548 return 0;
549
550}
551
552inline
554{
555 char buffer[4096];
556
557 //Open the FIFO
558 m_autosshLogFD = 0;
559
560 //We currently aren't monitoring this thread status, so we might as well retry if there is an error
561 while(m_autosshLogFD <= 0)
562 {
564
565 if( m_autosshLogFD < 0 )
566 {
568 log<software_error>({__FILE__, __LINE__, "unable to open auto ssh log fifo"});
569 mx::sys::sleep(1);
570 }
571 }
572
573
574 std::string logs;
575 while(m_shutdown == 0)
576 {
577 ssize_t count = read(m_autosshLogFD, buffer, sizeof(buffer)-1);
578 if (count <= 0)
579 {
580 continue;
581 }
582 else
583 {
584 buffer[count] = '\0';
585
586 logs += buffer;
587
588 //Keep reading until \n found, then process.
589 if(logs.back() == '\n')
590 {
591 size_t bol = 0;
592 while(bol < logs.size())
593 {
594 size_t eol = logs.find('\n', bol);
595 if(eol == std::string::npos) break;
596
597 processAutoSSHLog(logs.substr(bol, eol-bol));
598 bol = eol + 1;
599 }
600 logs = "";
601 }
602 }
603 }
604
605}
606
607inline
608int sshDigger::processAutoSSHLog( const std::string & logs )
609{
610 ///\todo interpret logs, giving errors vs info vs debug, strip timestamps, etc.
611
612 m_log.log<text_log>({"AUTOSSH: " + logs});
613
614 return 0;
615}
616
618{
619 m_autosshLogFile = "/dev/shm/sshDigger_autossh_" + m_configName + "_" + std::to_string(m_pid);
620
621 if( mkfifo( m_autosshLogFile.c_str(), S_IRUSR | S_IWUSR) < 0)
622 {
624 log<software_critical>({__FILE__, __LINE__, "unable to create autossh log fifo"});
625 return -1;
626 }
627
628
629 if(execTunnel() < 0)
630 {
632 return -1;
633 }
634
635 if(autosshLogThreadStart() < 0)
636 {
638 return -1;
639 }
640
641 if(sshLogThreadStart() < 0)
642 {
644 return -1;
645 }
646
647
648 return 0;
649}
650
652{
653 if(!m_sshError)
654 {
655 m_lastSSHLogs = "";
657 }
658
659 if(m_sshError == 1)
660 {
661 log<text_log>("sending SIGUSR1 to restart ssh");
664 m_sshError = 0;
665 }
666
667 if(m_sshError == 2)
668 {
670 m_sshError = 0;
671 }
672
673 //Check if autossh has died for any reason
674 int status;
676
677 if(exited == m_tunnelPID)
678 {
679 log<text_log>("autossh exited, restarting.");
680 m_sshSTDERR = -1; //This tells the sshLogThread to exit
681 char w = '\0';
682 ssize_t nwr = write(m_sshSTDERR_input, &w, 1); //And this wakes it up from the blocking read
683
684 if(m_sshLogThread.joinable()) m_sshLogThread.join();
685 if(nwr != 1)
686 {
688 log<software_error>({__FILE__, __LINE__, "Error on write to ssh log thread. restart failed."});
689 return -1;
690 }
691
692 //And now we can restart autossh
693 if(execTunnel() < 0)
694 {
695 log<software_critical>({__FILE__,__LINE__,"restart of tunnel failed."});
696 return -1;
697 }
698
699 //And the ssh log thread.
700 if(sshLogThreadStart() < 0)
701 {
702 log<software_critical>({__FILE__, __LINE__, "restart of ssh log thread failed."});
703 return -1;
704 }
705
706 //Don't need to restart the autossh log thread, becuase we'll give it the same file as log file.
707 }
708
709 return 0;
710}
711
713{
715 waitpid(m_tunnelPID, 0, 0);
716
717 //Write the the ssh stderr to wake up the ssh log thread, which will then see shutdown is set.
718 char w = '\0';
719 ssize_t nwr = write(m_sshSTDERR_input, &w, 1);
720 if(nwr != 1)
721 {
723 log<software_error>({__FILE__, __LINE__, "Error on write to ssh log thread. Sending SIGTERM."});
724 pthread_kill(m_sshLogThread.native_handle(), SIGTERM);
725
726 }
727
728 if(m_sshLogThread.joinable()) m_sshLogThread.join();
729
730 //Close the autossh FD to get that thread to shutdown.
731 close(m_autosshLogFD);
732 if(m_autosshLogThread.joinable()) m_autosshLogThread.join();
733
734 return 0;
735}
736
737} //namespace app
738} //namespace MagAOX
739
740#endif //sshDigger_hpp
The base-class for MagAO-X applications.
Definition MagAOXApp.hpp:73
std::string m_configName
The name of the configuration file (minus .conf).
Definition MagAOXApp.hpp:83
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
pid_t m_pid
This process's PID.
std::string m_configBase
The name of a base config class for this app (minus .conf).
Definition MagAOXApp.hpp:87
The MagAO-X SSH tunnel manager.
Definition sshDigger.hpp:55
~sshDigger() noexcept
D'tor, declared and defined for noexcept.
std::thread m_sshLogThread
A separate thread for capturing ssh logs.
Definition sshDigger.hpp:82
int m_autosshLogThreadPrio
Priority of the autossh log capture thread, should normally be 0.
Definition sshDigger.hpp:95
int sshLogThreadStart()
Start the log capture.
virtual int appLogic()
Implementation of the FSM for sshDigger.
int m_sshSTDERR_input
The input end of stderr, used to wake up the log thread on shutdown.
Definition sshDigger.hpp:78
int execTunnel()
Creates the tunnel in a child process using exec.
std::string m_remoteHost
The name of the remote host.
Definition sshDigger.hpp:65
static void _sshLogThreadStart(sshDigger *s)
Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
int m_localPort
The local port to forward from.
Definition sshDigger.hpp:66
int m_tunnelPID
The PID of the autossh process.
Definition sshDigger.hpp:72
void autosshLogThreadExec()
Execute the log capture.
int processSSHLog(const std::string &logs)
Process a log entry from indiserver, putting it into MagAO-X standard form.
static void _autosshLogThreadStart(sshDigger *s)
Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
int m_autosshLogFD
File descriptor of the autossh logfile.
Definition sshDigger.hpp:93
int m_sshLogThreadPrio
Priority of the ssh log capture thread, should normally be 0.
Definition sshDigger.hpp:80
std::string m_lastSSHLogs
Definition sshDigger.hpp:84
int m_remotePort
The remote port to forward to.
Definition sshDigger.hpp:67
int m_sshSTDERR
The output of stderr of the ssh process.
Definition sshDigger.hpp:77
int m_monitorPort
The monitor port.
Definition sshDigger.hpp:68
void sshLogThreadExec()
Execute the log capture.
int autosshLogThreadStart()
Start the log capture.
virtual int appShutdown()
Shutdown the app.
virtual int appStartup()
Startup function.
sshDigger()
Default c'tor.
virtual void setupConfig()
int processAutoSSHLog(const std::string &logs)
Process a log entry from indiserver, putting it into MagAO-X standard form.
int m_sshError
Flag to signal when ssh logs an error, and should be restarted via SIGUSR1 to autossh.
Definition sshDigger.hpp:85
void genEnvp(std::vector< std::string > &envp)
Generate the envp vector for the exec of autossh.
std::string tunnelSpec()
Create the tunnel specification string, [localPort]:localhost:[remotePort].
void genArgsV(std::vector< std::string > &argsV)
Generate the argv vector for the exec of autossh.
std::string m_autosshLogFile
Name of the autossh logfile.
Definition sshDigger.hpp:92
virtual void loadConfig()
std::thread m_autosshLogThread
A separate thread for capturing autossh logs.
Definition sshDigger.hpp:97
bool m_compress
Control compression on this tunnel. True is on, false is off.
Definition sshDigger.hpp:69
int8_t logPrioT
The type of the log priority code.
Definition logDefs.hpp:21
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
Definition dm.hpp:24
static constexpr logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
#define SSHDIGGER_E_NOLOCALPORT
Definition sshDigger.hpp:36
#define SSHDIGGER_E_NOTUNNELFOUND
Definition sshDigger.hpp:34
#define SSHDIGGER_E_NOREMOTEPORT
Definition sshDigger.hpp:37
#define SSHDIGGER_E_NOHOSTNAME
Definition sshDigger.hpp:35
#define SSHDIGGER_E_NOTUNNELS
Definition sshDigger.hpp:33
void log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
int logLevel(logPrioT newLev)
Set a new value of logLevel.
A simple text log, a string-type log.
Definition text_log.hpp:24