diff --git a/keyserver/src/socket/tunnelbroker-socket.js b/keyserver/src/socket/tunnelbroker-socket.js --- a/keyserver/src/socket/tunnelbroker-socket.js +++ b/keyserver/src/socket/tunnelbroker-socket.js @@ -3,7 +3,9 @@ import uuid from 'uuid'; import WebSocket from 'ws'; +import { tunnelbrokerHeartbeatTimeout } from 'lib/shared/timeouts.js'; import type { ClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js'; +import type { Heartbeat } from 'lib/types/tunnelbroker/heartbeat-types.js'; import { type RefreshKeyRequest, refreshKeysRequestValidator, @@ -30,6 +32,7 @@ ws: WebSocket; connected: boolean; promises: Promises; + heartbeatTimeoutID: ?TimeoutID; constructor(socketURL: string, initMessage: ConnectionInitializationMessage) { this.connected = false; @@ -42,6 +45,7 @@ }); socket.on('close', async () => { + this.stopHeartbeatTimeout(); this.connected = false; console.error('Connection to Tunnelbroker closed'); }); @@ -72,6 +76,8 @@ } const message: TunnelbrokerMessage = rawMessage; + this.resetHeartbeatTimeout(); + if ( message.type === tunnelbrokerMessageTypes.CONNECTION_INITIALIZATION_RESPONSE @@ -126,6 +132,11 @@ console.log('Tunnelbroker recorded InvalidRequest'); } } + } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) { + const heartbeat: Heartbeat = { + type: tunnelbrokerMessageTypes.HEARTBEAT, + }; + this.ws.send(JSON.stringify(heartbeat)); } }; @@ -151,6 +162,21 @@ this.ws.send(JSON.stringify(messageToDevice)); }); }; + + stopHeartbeatTimeout() { + if (this.heartbeatTimeoutID) { + clearTimeout(this.heartbeatTimeoutID); + this.heartbeatTimeoutID = null; + } + } + + resetHeartbeatTimeout() { + this.stopHeartbeatTimeout(); + this.heartbeatTimeoutID = setTimeout(() => { + this.ws.close(); + this.connected = false; + }, tunnelbrokerHeartbeatTimeout); + } } export default TunnelbrokerSocket; diff --git a/lib/shared/timeouts.js b/lib/shared/timeouts.js --- a/lib/shared/timeouts.js +++ b/lib/shared/timeouts.js @@ -32,3 +32,7 @@ // response. This is better than letting the request timeout on the // client, since the client will assume network issues and close the socket. export const serverResponseTimeout = 5000; // in milliseconds + +// Time after which the client consider the Tunnelbroker connection +// as unhealthy and chooses to close the socket. +export const tunnelbrokerHeartbeatTimeout = 9000; // in milliseconds