@@ -6,8 +6,11 @@ var tls = require('tls');
66var url = require ( 'url' ) ;
77var Tunnel = require ( './tunnel' ) ;
88var utils = require ( './utils' ) ;
9+ var util = require ( 'util' ) ;
910var ProxyAgent = require ( 'https-proxy-agent' ) ;
1011
12+ var Logger = require ( './log' ) ;
13+
1114var RECONNECTING = false ;
1215
1316// simple arg parser
@@ -29,21 +32,56 @@ var argv = require('yargs')
2932 description : false ,
3033 hide : true ,
3134 default : false
32- }
35+ } ,
36+ 'verbose' : {
37+ alias : 'v' ,
38+ count : true ,
39+ conflicts : 'quiet'
3340
41+ } ,
42+ 'quiet' : {
43+ alias : 'q' ,
44+ boolean : true ,
45+ conflicts : 'verbose'
46+ }
3447 } )
3548 . argv ;
3649
50+ // parse logging args and create logger
51+ let initialLogLevel ;
52+ if ( argv . quiet ) {
53+ // no logs if quiet option specified
54+ initialLogLevel = 'OFF' ;
55+ } else {
56+ // otherwise, log level based on count of verbose flags
57+ switch ( argv . verbose ) {
58+ case 0 :
59+ initialLogLevel = 'WARN' ;
60+ break ;
61+ case 1 :
62+ initialLogLevel = 'INFO' ;
63+ break ;
64+ case 2 :
65+ initialLogLevel = 'DEBUG' ;
66+ break ;
67+ case 3 :
68+ default :
69+ initialLogLevel = 'TRACE' ;
70+ break ;
71+ }
72+ }
73+ const log = new Logger ( initialLogLevel ) ;
74+ // bind to global
75+ global . log = log
3776
38-
39- var httpProxy = process . env . http_proxy || process . env . HTTP_PROXY || null
40- var httpsProxy = process . env . https_proxy || process . env . HTTPS_PROXY || null
41-
77+ // apply acceptAllCerts arg by setting env variable for NodeJS
4278if ( argv . acceptAllCerts ) {
43- console . log ( 'setting reject_unauthorized = 0' )
4479 process . env . NODE_TLS_REJECT_UNAUTHORIZED = "0" ;
4580}
4681
82+ // parse env variables for proxy options
83+ let proxyUrl = utils . getProxyFromEnv ( ) ;
84+
4785var tunnels = [ ] ;
4886
4987function getApiUrl ( env ) {
@@ -61,102 +99,126 @@ function getApiUrl(env){
6199 }
62100}
63101
102+ function createProxyAgent ( proxyUrl ) {
103+ proxyUrl = new url . URL ( proxyUrl )
104+ let proxyAgentOpts = {
105+ host :proxyUrl . hostname ,
106+ port : proxyUrl . port ,
107+ auth : proxyUrl . username ? `${ proxyUrl . username } :${ proxyUrl . password } ` : `` ,
108+ procotol : proxyUrl . protocol
109+ }
110+ log . debug ( 'ProxyAgent options: ' + util . inspect ( proxyAgentOpts ) )
111+ let proxyAgent = new ProxyAgent ( proxyAgentOpts )
112+ }
113+
64114var socket
65115
66116function cbtConnect ( ) {
67117
68- // var socket = socketIo("http://localhost:3000/socket", { path: "/socket"} );
69- // var socket = socketIo.connect("http://localhost:3000/socket/socket");
70- let proxy = httpProxy || httpsProxy
118+ let socketUrl = getApiUrl ( argv . env )
71119
72- if ( proxy ) {
73- console . log ( 'going to setup proxy agent' )
74- if ( proxy . indexOf ( 'http' ) !== 0 ) {
75- proxy = 'http://' + proxy
76- }
77- let proxyURL = new url . URL ( proxy )
78- let proxyAgentOpts = {
79- host :proxyURL . hostname ,
80- port : proxyURL . port ,
81- auth : proxyURL . username ? `${ proxyURL . username } :${ proxyURL . password } ` : `` ,
82- secureProxy :true
83- }
84- let proxyAgent = new ProxyAgent ( proxyAgentOpts )
85- socket = new WebSocket ( getApiUrl ( argv . env ) , { agent : proxyAgent } ) ;
120+ // create websocket connection (with proxy if specified)
121+ if ( proxyUrl ) {
122+ let proxyAgent = createProxyAgent ( proxyUrl )
123+ socket = new WebSocket ( socketUrl , { agent : proxyAgent } ) ;
86124 } else {
87- socket = new WebSocket ( getApiUrl ( argv . env ) ) ;
125+ socket = new WebSocket ( socketUrl ) ;
88126 }
89127
90-
91-
92128 socket . on ( 'error' , function ( err ) {
93129 if ( RECONNECTING ) {
94- // console.log('reconnecting...')
130+ log . debug ( 'Socket error while reconnecting' ) ;
131+ log . trace ( 'Socket error: ' + ( err || err . stack ) ) ;
95132 } else {
96- console . log ( "Socket error!!" ) ;
97- console . dir ( err ) ;
133+ log . error ( 'Socket error: ' + ( err || err . stack ) ) ;
98134 let apiUrl = new url . URL ( getApiUrl ( argv . env ) ) ;
99135 var tlsSocket = tls . connect ( { host : apiUrl . hostname , port :443 , rejectUnauthorized : false } , ( ) => {
100- console . log ( 'TLS connect successful, getting certificates from peer' )
101- console . log ( tlsSocket . getPeerCertificate ( true ) )
136+ log . debug ( 'TLS connect successful, getting certificates from peer' )
137+ log . debug ( tlsSocket . getPeerCertificate ( true ) )
102138 } )
103139 }
104140 } )
105141
106142 socket . on ( 'close' , ( ) => {
107- console . log ( 'Signalling socket disconnected' )
143+ log . error ( 'Signalling socket disconnected. Attempting to reconnect. ' )
108144 RECONNECTING = true
109145 let reconnectInterval = setInterval ( ( ) => {
110146 if ( socket . readyState === 0 ) {
111147 // socket is still connecting... just wait
112148 } else if ( socket . readyState === 1 ) {
113149 // socket is reconnected!
150+ log . info ( 'Socket reconnect successful!' )
114151 RECONNECTING = false
115152 clearInterval ( reconnectInterval )
116153 } else {
117- console . log ( 'Signalling socket reconnecting...' )
154+ log . debug ( 'Signalling socket reconnecting...' )
118155 cbtConnect ( )
119156 }
120157 } , 500 )
121158 } )
122159
123160 socket . once ( 'open' , ( ) => {
124- console . log ( "connection established! Initiating auth!" ) ;
125- socket . send ( JSON . stringify ( { action : 'authenticate' , username : argv . username , authkey : argv . authkey } ) ) ;
126161
127- socket . on ( 'message' , msg => {
128- // handle incoming messages through the signaling socket
129- try {
130- msg = JSON . parse ( msg )
131- } catch ( parseErr ) {
132- console . error ( 'could not parse inbound message: ' + msg )
133- return
162+ // setup handler for if server stops pinging
163+ socket . lastPing = new Date ( ) ;
164+ let serverKeepalive = setInterval ( ( ) => {
165+ let now = new Date ( ) ;
166+ let timeSincePing = ( now - socket . lastPing ) / 1000 ;
167+ if ( timeSincePing > 15 ) {
168+ log . error ( `No ping from CBT in ${ timeSincePing } seconds. Closing socket to trigger reconnect` )
169+ clearInterval ( serverKeepalive )
170+ socket . terminate ( )
134171 }
172+ } , 500 )
135173
136- // after parsing successful, each message should come in with an "action"
137- switch ( msg . action ) {
138- case 'authenticated' :
139- console . log ( "Authentication successful! Waiting for a request to open a tunnel..." ) ;
140- break
141- case 'start' :
142- if ( argv . env == 'test' || argv . env == 'local' ) {
143- msg . options . test = 'test' ;
144- }
145- var t = new Tunnel ( msg . user . username , msg . user . authkey , msg . options )
146- tunnels . push ( t )
147- t . start ( )
148- break
149- case 'keepalive_check' :
150- socket . send ( JSON . stringify ( { action :'keepalive_ack' } ) )
151- break
152- case 'unauthorized' :
153- console . error ( "authentication failed! " + msg . message )
154- process . exit ( 1 )
155- break
156- case 'bye' :
157- console . log ( "got bye" ) ;
158- socket . close ( ) ;
159- }
174+ log . info ( "Socket connection established! Authenticating..." ) ;
175+ let payload = { action : 'authenticate' , username : argv . username , authkey : argv . authkey } ;
176+
177+ // clone payload to log it without logging creds
178+ let loggablePayload = JSON . parse ( JSON . stringify ( payload ) ) ;
179+ loggablePayload . authkey = '********' ;
180+ log . trace ( "ECM => CBT: " + JSON . stringify ( loggablePayload ) ) ;
181+
182+ socket . send ( JSON . stringify ( payload ) ) ;
183+
184+ socket . on ( 'message' , msg => {
185+ // handle incoming messages through the signaling socket
186+ try {
187+ msg = JSON . parse ( msg ) ;
188+ } catch ( parseErr ) {
189+ log . warn ( 'Could not parse message from CBT. Offending message:' + msg ) ;
190+ log . debug ( 'Parse error: ' + ( parseErr . stack || parseErr ) ) ;
191+ return ;
192+ }
193+
194+ // after parsing successful, each message should come in with an "action"
195+ switch ( msg . action ) {
196+ case 'authenticated' :
197+ log . trace ( "ECM <= CBT: " + util . inspect ( msg ) ) ;
198+ log . info ( "Authentication successful! Waiting for a request to open a tunnel." ) ;
199+ break ;
200+ case 'start' :
201+ log . trace ( "ECM <= CBT: " + util . inspect ( msg ) ) ;
202+ if ( argv . env == 'test' || argv . env == 'local' ) {
203+ msg . options . test = 'test' ;
204+ }
205+ var t = new Tunnel ( msg . user . username , msg . user . authkey , msg . options ) ;
206+ tunnels . push ( t ) ;
207+ t . start ( ) ;
208+ break ;
209+ case 'keepalive_check' :
210+ socket . lastPing = new Date ( ) ;
211+ socket . send ( JSON . stringify ( { action :'keepalive_ack' } ) ) ;
212+ break ;
213+ case 'unauthorized' :
214+ log . trace ( "ECM <= CBT: " + util . inspect ( msg ) ) ;
215+ log . error ( "Authentication failed! " + msg . message ) ;
216+ process . exit ( 1 ) ;
217+ break
218+ case 'bye' :
219+ log . info ( "CBT requested this socket close. Closing socket..." ) ;
220+ socket . close ( ) ;
221+ }
160222 } )
161223 } )
162224}
@@ -166,16 +228,41 @@ function cbtConnect() {
166228// ( or if we can't reach cbt to check... )
167229utils . checkVersion ( argv . env , ( ) => {
168230 cbtConnect ( )
231+
232+
233+ // attempt graceful shutdown on sigint
169234 process . on ( 'SIGINT' , ( ) => {
170- console . log ( '\nECM is shutting down' ) ;
235+ log . info ( 'ECM is shutting down' ) ;
236+
171237 if ( tunnels . length === 0 ) {
238+ log . trace ( 'No tunnels running, quitting now' )
172239 process . exit ( 0 )
173- }
174- tunnels . map ( ( tunnel , tunnelIndex ) => {
175- tunnel . stop ( ( ) => {
240+ } else {
241+
242+ log . trace ( `Currently ${ tunnels . length } tunnels running. Going to shut them down before quitting.` )
243+ tunnels . map ( ( tunnel , tunnelIndex ) => {
244+ log . trace ( `Stopping tunnel ${ tunnelIndex } ` )
245+ tunnel . stop ( ( ) => { } )
176246 } )
177- } )
178247
248+ // quit when all tunnels are stopped
249+ setInterval ( ( ) => {
250+
251+ // get status of all tunnels
252+ let tunnelStatus = tunnels . map ( tunnel => {
253+ return tunnel . status ( )
254+ } )
255+
256+ // quit if all tunnels are stopped (status() returns false)
257+ if ( tunnelStatus . every ( tunnel => ! tunnel ) ) {
258+ log . info ( 'All tunnels stopped' )
259+ process . exit ( 0 )
260+ }
261+
262+ } , 250 )
263+ }
264+
265+ // exit after 7 seconds if there are stubborn tunnels
179266 setTimeout ( ( ) => { process . exit ( 0 ) } , 7000 ) ;
180267 } )
181268} )
0 commit comments