Skip to content

Commit 9727f44

Browse files
authored
Merge pull request #2771 from uProxy/bemasc-retry-core
Refactor GettingState management, including retry logic.
2 parents db7677a + f6f6caa commit 9727f44

22 files changed

+304
-199
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@
3838
"compare-version": "^0.1.2",
3939
"es6-promise": "^4.0.5",
4040
"forge-min": "^0.6.20",
41-
"freedom": "^0.6.31",
42-
"freedom-for-chrome": "^0.4.25",
43-
"freedom-for-firefox": "^0.6.24",
41+
"freedom": "^0.6.36",
42+
"freedom-for-chrome": "^0.4.28",
43+
"freedom-for-firefox": "^0.6.26",
4444
"freedom-for-node": "^0.2.20",
4545
"freedom-pgp-e2e": "^0.6.2",
4646
"freedom-port-control": "^0.9.0",
4747
"freedom-social-firebase": "^2.0.0",
4848
"freedom-social-github": "^0.1.3",
49-
"freedom-social-quiver": "^0.1.0",
49+
"freedom-social-quiver": "^0.1.5",
5050
"freedom-social-wechat": "^0.1.5",
5151
"freedom-xhr": "^0.0.11",
5252
"freedomjs-anonymized-metrics": "^0.7.4",

src/generic_core/cca_dist_build/freedom-module.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
},
5151
"permissions": [
5252
"core.crypto",
53+
"core.online",
5354
"core.rtcpeerconnection",
5455
"core.rtcdatachannel",
5556
"core.storage",

src/generic_core/dev_build/freedom-module.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
},
6363
"permissions": [
6464
"core.crypto",
65+
"core.online",
6566
"core.rtcpeerconnection",
6667
"core.rtcdatachannel",
6768
"core.storage",

src/generic_core/dist_build/freedom-module.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
},
6363
"permissions": [
6464
"core.crypto",
65+
"core.online",
6566
"core.rtcpeerconnection",
6667
"core.rtcdatachannel",
6768
"core.storage",

src/generic_core/remote-connection.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as freedomMocker from '../lib/freedom/mocks/mock-freedom-in-module-env'
1313
import * as freedom_mocks from '../mocks/freedom-mocks';
1414
declare var freedom: freedom.FreedomInModuleEnv;
1515
freedom = freedomMocker.makeMockFreedomInModuleEnv({
16+
'core.online': () => { return new freedom_mocks.MockFreedomOnline(); },
1617
'core.storage': () => { return new freedom_mocks.MockFreedomStorage(); },
1718
'core.tcpsocket': () => { return new freedom_mocks.MockTcpSocket(); },
1819
'metrics': () => { return new freedom_mocks.MockMetrics(); },

src/generic_core/remote-connection.ts

Lines changed: 133 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import * as tcp from '../lib/net/tcp';
2121
import * as uproxy_core_api from '../interfaces/uproxy_core_api';
2222

2323
declare var freedom: freedom.FreedomInModuleEnv;
24+
const onlineMonitor = freedom['core.online']();
2425

2526
var 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
// }

src/generic_core/remote-instance.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as freedomMocker from '../lib/freedom/mocks/mock-freedom-in-module-env'
1414
import * as freedom_mocks from '../mocks/freedom-mocks';
1515
declare var freedom: freedom.FreedomInModuleEnv;
1616
freedom = freedomMocker.makeMockFreedomInModuleEnv({
17+
'core.online': () => { return new freedom_mocks.MockFreedomOnline(); },
1718
'core.storage': () => { return new freedom_mocks.MockFreedomStorage(); },
1819
'core.tcpsocket': () => { return new freedom_mocks.MockTcpSocket(); },
1920
'metrics': () => { return new freedom_mocks.MockMetrics(); },

0 commit comments

Comments
 (0)