@@ -21,6 +21,7 @@ import * as tcp from '../lib/net/tcp';
2121import * as uproxy_core_api from '../interfaces/uproxy_core_api' ;
2222
2323declare var freedom : freedom . FreedomInModuleEnv ;
24+ const onlineMonitor = freedom [ 'core.online' ] ( ) ;
2425
2526var PROXYING_SESSION_ID_LENGTH = 16 ;
2627
@@ -44,8 +45,22 @@ var generateProxyingSessionId_ = (): string => {
4445// module Core {
4546 var log :logging . Log = new logging . Log ( 'remote-connection' ) ;
4647
48+ // Connections to remember, either because they're currently
49+ // connected or because they'll retry given the opportunity.
50+ let rememberedConnections : { [ instanceId :string ] : RemoteConnection } = { } ;
51+
4752 export class RemoteConnection {
4853
54+ // Number of milliseconds before timing out socksToRtc_.start
55+ private SOCKS_TO_RTC_TIMEOUT_ :number = 30000 ;
56+ // Ensure RtcToNet is only closed after SocksToRtc times out (i.e. finishes
57+ // trying to connect) by timing out rtcToNet_.start 15 seconds later than
58+ // socksToRtc_.start
59+ private RTC_TO_NET_TIMEOUT_ :number = this . SOCKS_TO_RTC_TIMEOUT_ + 15000 ;
60+ // Timeouts for when to abort starting up SocksToRtc and RtcToNet.
61+ private startSocksToRtcTimeout_ :NodeJS . Timer = null ;
62+ private startRtcToNetTimeout_ :NodeJS . Timer = null ;
63+
4964 public localGettingFromRemote = social . GettingState . NONE ;
5065 public localSharingWithRemote = social . SharingState . NONE ;
5166
@@ -74,13 +89,33 @@ var generateProxyingSessionId_ = (): string => {
7489 // Unique ID of the most recent proxying attempt.
7590 private proxyingId_ : string ;
7691
92+ // The remote version of the sharer that we're currently trying to get
93+ // access from.
94+ private currentGetRemoteVersion_ : number ;
95+
7796 constructor (
7897 sendUpdate :( x :uproxy_core_api . Update , data ?:Object ) => void ,
98+ private instanceId_ :string ,
7999 private userId_ ?:string ,
80100 private portControl_ ?:freedom . PortControl . PortControl
81101 ) {
82102 this . sendUpdate_ = sendUpdate ;
83103 this . resetSharerCreated ( ) ;
104+ onlineMonitor . on ( 'online' , this . onOnline_ ) ;
105+ }
106+
107+ private onOnline_ = ( ) => {
108+ setTimeout ( ( ) => {
109+ if ( this . localGettingFromRemote === social . GettingState . FAILED ) {
110+ log . info ( 'Back online in the failed state; restarting' ) ;
111+ this . startGet ( this . currentGetRemoteVersion_ ) ;
112+ }
113+ } , 5000 ) ; // 5 second delay for DHCP, etc. TODO: Tune or remove.
114+ }
115+
116+ public setSendUpdate =
117+ ( sender :( x :uproxy_core_api . Update , data ?:Object ) => void ) => {
118+ this . sendUpdate_ = sender ;
84119 }
85120
86121 private createSender_ = ( type :social . PeerMessageType ) => {
@@ -150,6 +185,14 @@ var generateProxyingSessionId_ = (): string => {
150185 pc = bridge . best ( 'rtctonet' , config , this . portControl_ ) ;
151186 }
152187
188+ // Set timeout to close rtcToNet_ if start() takes too long.
189+ // Calling stopShare() at the end of the timeout makes the
190+ // assumption that our peer failed to start getting access.
191+ this . startRtcToNetTimeout_ = setTimeout ( ( ) => {
192+ log . warn ( 'Timing out rtcToNet_ connection' ) ;
193+ this . stopShare ( ) ;
194+ } , this . RTC_TO_NET_TIMEOUT_ ) ;
195+
153196 this . rtcToNet_ = new rtc_to_net . RtcToNet ( this . userId_ ) ;
154197 this . rtcToNet_ . start ( {
155198 allowNonUnicast : globals . settings . allowNonUnicast ,
@@ -209,7 +252,8 @@ var generateProxyingSessionId_ = (): string => {
209252 }
210253
211254 public startGet = ( remoteVersion :number ) :Promise < net . Endpoint > => {
212- if ( this . localGettingFromRemote !== social . GettingState . NONE ) {
255+ if ( this . localGettingFromRemote !== social . GettingState . NONE &&
256+ this . localGettingFromRemote !== social . GettingState . FAILED ) {
213257 // This should not happen. If it does, something else is broken. Still, we
214258 // continue to actually proxy through the instance.
215259 log . error ( 'Currently have a connection open' ) ;
@@ -240,35 +284,11 @@ var generateProxyingSessionId_ = (): string => {
240284 this . socksToRtc_ . bytesReceivedFromPeer . setSyncHandler ( this . handleBytesReceived_ ) ;
241285 this . socksToRtc_ . bytesSentToPeer . setSyncHandler ( this . handleBytesSent_ ) ;
242286
243- // TODO: Change this back to listening to the 'stopped' callback
244- // once https://github.com/uProxy/uproxy/issues/1264 is resolved.
245- // Currently socksToRtc's 'stopped' callback does not get called on
246- // Firefox, possibly due to issues cleaning up sockets.
247- // onceStopping_, unlike 'stopped', gets fired as soon as stopping begins
248- // and doesn't wait for all cleanup to finish
249- this . socksToRtc_ [ 'onceStopping_' ] . then ( ( ) => {
250- // Stopped event is only considered an error if the user had been
251- // getting access and we hadn't called this.socksToRtc_.stop
252- // If there is an error when trying to start proxying, and a stopped
253- // event is fired, an error will be displayed as a result of the start
254- // promise rejecting.
255- // TODO: consider removing error field from STOP_GETTING_FROM_FRIEND
256- // The UI should know whether it was a user-initiated stopped event
257- // or not (based on whether they clicked stop/logout, or based on
258- // whether the browser's proxy was set).
259-
260- var isError = social . GettingState . GETTING_ACCESS === this . localGettingFromRemote ;
261- this . sendUpdate_ ( uproxy_core_api . Update . STOP_GETTING , isError ) ;
262-
263- this . localGettingFromRemote = social . GettingState . NONE ;
264- this . bytesSent_ = 0 ;
265- this . bytesReceived_ = 0 ;
266- this . stateRefresh_ ( ) ;
267- this . socksToRtc_ = null ;
268- this . activeEndpoint = null ;
269- } ) ;
270-
271- this . localGettingFromRemote = social . GettingState . TRYING_TO_GET_ACCESS ;
287+ if ( this . localGettingFromRemote === social . GettingState . NONE ) {
288+ this . localGettingFromRemote = social . GettingState . TRYING_TO_GET_ACCESS ;
289+ } else {
290+ this . localGettingFromRemote = social . GettingState . RETRYING ;
291+ }
272292 this . stateRefresh_ ( ) ;
273293
274294 var tcpServer = new tcp . Server ( {
@@ -282,6 +302,7 @@ var generateProxyingSessionId_ = (): string => {
282302
283303 var pc : peerconnection . PeerConnection < Object > ;
284304
305+ this . currentGetRemoteVersion_ = remoteVersion ;
285306 var localVersion = globals . effectiveMessageVersion ( ) ;
286307 var commonVersion = Math . min ( localVersion , remoteVersion ) ;
287308 log . info ( 'lowest shared client version is %1 (me: %2, peer: %3)' ,
@@ -320,6 +341,13 @@ var generateProxyingSessionId_ = (): string => {
320341
321342 globals . metrics . increment ( 'attempt' ) ;
322343
344+ // Cancel socksToRtc_ connection if start hasn't completed in 30 seconds.
345+ this . startSocksToRtcTimeout_ = setTimeout ( ( ) => {
346+ log . warn ( 'Timing out socksToRtc_ connection' ) ;
347+ this . socksToRtc_ . stop ( ) ;
348+ this . onConnectFailed_ ( ) ;
349+ } , this . SOCKS_TO_RTC_TIMEOUT_ ) ;
350+
323351 const start = this . socksToRtc_ . start ( tcpServer , pc ) . then ( ( endpoint :net . Endpoint ) => {
324352 log . info ( 'SOCKS proxy listening on %1' , endpoint ) ;
325353 this . localGettingFromRemote = social . GettingState . GETTING_ACCESS ;
@@ -328,18 +356,60 @@ var generateProxyingSessionId_ = (): string => {
328356 this . activeEndpoint = endpoint ;
329357 return endpoint ;
330358 } ) . catch ( ( e :Error ) => {
331- this . localGettingFromRemote = social . GettingState . NONE ;
332- this . stateRefresh_ ( ) ;
359+ this . onConnectFailed_ ( ) ;
333360 return Promise . reject ( Error ( 'Could not start proxy' ) ) ;
334361 } ) ;
335362
336363 // Ugh, this needs to be called after start.
337364 this . socksToRtc_ . signalsForPeer . setSyncHandler (
338365 this . createSender_ ( social . PeerMessageType . SIGNAL_FROM_CLIENT_PEER ) ) ;
339366
367+ // onceStopped isn't defined until after calling start().
368+ this . socksToRtc_ . onceStopped . then ( ( ) => {
369+ // Stopped event is only considered an error if the user had been
370+ // getting access and we hadn't called this.socksToRtc_.stop
371+ // If there is an error when trying to start proxying, and a stopped
372+ // event is fired, an error will be displayed as a result of the start
373+ // promise rejecting.
374+
375+ let isError = social . GettingState . NONE !== this . localGettingFromRemote &&
376+ social . GettingState . TRYING_TO_GET_ACCESS !== this . localGettingFromRemote ;
377+
378+ this . localGettingFromRemote = isError ? social . GettingState . FAILED :
379+ social . GettingState . NONE ;
380+ this . bytesSent_ = 0 ;
381+ this . bytesReceived_ = 0 ;
382+ this . stateRefresh_ ( ) ;
383+ this . socksToRtc_ = null ;
384+ this . activeEndpoint = null ;
385+
386+ if ( isError ) {
387+ // Check if we're online. If we are, the error-stop may have been
388+ // due to a transient condition that has already resolved, so
389+ // trigger a retry (if we're still in the FAILED state). This means
390+ // that while online, broken connections will retry forever.
391+ onlineMonitor . isOnline ( ) . then ( ( online :boolean ) => {
392+ if ( online ) {
393+ this . onOnline_ ( ) ;
394+ }
395+ } )
396+ }
397+ } ) ;
398+
340399 return start ;
341400 }
342401
402+ private onConnectFailed_ = ( ) : void => {
403+ if ( this . localGettingFromRemote === social . GettingState . TRYING_TO_GET_ACCESS ||
404+ this . localGettingFromRemote === social . GettingState . NONE ) {
405+ this . localGettingFromRemote = social . GettingState . NONE ;
406+ } else {
407+ this . localGettingFromRemote = social . GettingState . FAILED ;
408+ }
409+ this . socksToRtc_ = null ;
410+ this . stateRefresh_ ( ) ;
411+ }
412+
343413 public stopGet = ( ) :Promise < void > => {
344414 if ( this . localGettingFromRemote === social . GettingState . NONE ) {
345415 log . warn ( 'Cannot stop proxying when neither proxying nor trying to proxy.' ) ;
@@ -348,7 +418,10 @@ var generateProxyingSessionId_ = (): string => {
348418 globals . metrics . increment ( 'stop' ) ;
349419 this . localGettingFromRemote = social . GettingState . NONE ;
350420 this . stateRefresh_ ( ) ;
351- return this . socksToRtc_ . stop ( ) ;
421+ if ( this . socksToRtc_ ) {
422+ return this . socksToRtc_ . stop ( ) ;
423+ }
424+ return Promise . resolve ( ) ;
352425 }
353426
354427 /*
@@ -391,6 +464,19 @@ var generateProxyingSessionId_ = (): string => {
391464 }
392465
393466 private stateRefresh_ = ( ) => {
467+ if ( this . localGettingFromRemote !== social . GettingState . NONE ||
468+ this . localSharingWithRemote !== social . SharingState . NONE ) {
469+ rememberedConnections [ this . instanceId_ ] = this ;
470+ } else {
471+ delete rememberedConnections [ this . instanceId_ ] ;
472+ }
473+ if ( this . localGettingFromRemote !== social . GettingState . TRYING_TO_GET_ACCESS &&
474+ this . localGettingFromRemote !== social . GettingState . RETRYING ) {
475+ clearTimeout ( this . startSocksToRtcTimeout_ ) ;
476+ }
477+ if ( this . localSharingWithRemote !== social . SharingState . TRYING_TO_SHARE_ACCESS ) {
478+ clearTimeout ( this . startRtcToNetTimeout_ ) ;
479+ }
394480 this . sendUpdate_ ( uproxy_core_api . Update . STATE , this . getCurrentState ( ) ) ;
395481 }
396482
@@ -401,11 +487,25 @@ var generateProxyingSessionId_ = (): string => {
401487 localGettingFromRemote : this . localGettingFromRemote ,
402488 localSharingWithRemote : this . localSharingWithRemote ,
403489 activeEndpoint : this . activeEndpoint ,
490+ proxyingId : this . proxyingId_ ,
404491 } ;
405492 }
406493
407494 public getProxyingId = ( ) : string => {
408495 return this . proxyingId_ ;
409496 }
410497 }
498+
499+ export function getOrCreateRemoteConnection (
500+ sendUpdate :( x :uproxy_core_api . Update , data ?:Object ) => void ,
501+ instanceId :string ,
502+ userId ?:string ,
503+ portControl ?:freedom . PortControl . PortControl ) :RemoteConnection {
504+ if ( instanceId in rememberedConnections ) {
505+ let connection = rememberedConnections [ instanceId ] ;
506+ connection . setSendUpdate ( sendUpdate ) ;
507+ return connection ;
508+ }
509+ return new RemoteConnection ( sendUpdate , instanceId , userId , portControl ) ;
510+ }
411511// }
0 commit comments