Skip to content

Commit

Permalink
Merge pull request #555 from MatrixAI/feature-nat-signalling
Browse files Browse the repository at this point in the history
NAT Signalling (Hole Punching) should be Fire & Forget, Coalesced, and Secured with Signatures
  • Loading branch information
tegefaulkes authored Oct 24, 2023
2 parents 9f52ca0 + 6123329 commit 479f8b6
Show file tree
Hide file tree
Showing 22 changed files with 1,244 additions and 706 deletions.
280 changes: 176 additions & 104 deletions src/nodes/NodeConnectionManager.ts

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions src/nodes/agent/callers/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import nodesClaimsGet from './nodesClaimsGet';
import nodesClosestLocalNodesGet from './nodesClosestLocalNodesGet';
import nodesConnectionSignalFinal from './nodesConnectionSignalFinal';
import nodesConnectionSignalInitial from './nodesConnectionSignalInitial';
import nodesCrossSignClaim from './nodesCrossSignClaim';
import nodesHolePunchMessageSend from './nodesHolePunchMessageSend';
import notificationsSend from './notificationsSend';
import vaultsGitInfoGet from './vaultsGitInfoGet';
import vaultsGitPackGet from './vaultsGitPackGet';
Expand All @@ -13,8 +14,9 @@ import vaultsScan from './vaultsScan';
const manifestClient = {
nodesClaimsGet,
nodesClosestLocalNodesGet,
nodesConnectionSignalFinal,
nodesConnectionSignalInitial,
nodesCrossSignClaim,
nodesHolePunchMessageSend,
notificationsSend,
vaultsGitInfoGet,
vaultsGitPackGet,
Expand All @@ -26,8 +28,9 @@ export default manifestClient;
export {
nodesClaimsGet,
nodesClosestLocalNodesGet,
nodesConnectionSignalFinal,
nodesConnectionSignalInitial,
nodesCrossSignClaim,
nodesHolePunchMessageSend,
notificationsSend,
vaultsGitInfoGet,
vaultsGitPackGet,
Expand Down
12 changes: 12 additions & 0 deletions src/nodes/agent/callers/nodesConnectionSignalFinal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type NodesConnectionSignalFinal from '../handlers/NodesConnectionSignalFinal';
import { UnaryCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<NodesConnectionSignalFinal>;

const nodesConnectionSignalFinal = new UnaryCaller<
CallerTypes['input'],
CallerTypes['output']
>();

export default nodesConnectionSignalFinal;
12 changes: 12 additions & 0 deletions src/nodes/agent/callers/nodesConnectionSignalInitial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type NodesConnectionSignalInitial from '../handlers/NodesConnectionSignalInitial';
import { UnaryCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<NodesConnectionSignalInitial>;

const nodesConnectionSignalInitial = new UnaryCaller<
CallerTypes['input'],
CallerTypes['output']
>();

export default nodesConnectionSignalInitial;
12 changes: 0 additions & 12 deletions src/nodes/agent/callers/nodesHolePunchMessageSend.ts

This file was deleted.

20 changes: 19 additions & 1 deletion src/nodes/agent/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,22 @@ class ErrorAgentNodeIdMissing<T> extends ErrorAgent<T> {
exitCode = sysexits.UNAVAILABLE;
}

export { ErrorAgentNodeIdMissing };
class ErrorNodesConnectionSignalRequestVerificationFailed<
T,
> extends ErrorAgent<T> {
static description = 'Failed to verify request message signature';
exitCode = sysexits.UNAVAILABLE;
}

class ErrorNodesConnectionSignalRelayVerificationFailed<
T,
> extends ErrorAgent<T> {
static description = 'Failed to verify relay message signature';
exitCode = sysexits.UNAVAILABLE;
}

export {
ErrorAgentNodeIdMissing,
ErrorNodesConnectionSignalRequestVerificationFailed,
ErrorNodesConnectionSignalRelayVerificationFailed,
};
73 changes: 73 additions & 0 deletions src/nodes/agent/handlers/NodesConnectionSignalFinal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type Logger from '@matrixai/logger';
import type {
AgentRPCRequestParams,
AgentRPCResponseResult,
HolePunchRequestMessage,
} from '../types';
import type NodeConnectionManager from '../../NodeConnectionManager';
import type { Host, Port } from '../../../network/types';
import { UnaryHandler } from '@matrixai/rpc';
import * as keysUtils from '../../../keys/utils';
import * as ids from '../../../ids';
import * as agentErrors from '../errors';
import * as agentUtils from '../utils';

class NodesConnectionSignalFinal extends UnaryHandler<
{
nodeConnectionManager: NodeConnectionManager;
logger: Logger;
},
AgentRPCRequestParams<HolePunchRequestMessage>,
AgentRPCResponseResult
> {
public handle = async (
input: AgentRPCRequestParams<HolePunchRequestMessage>,
_cancel,
meta,
): Promise<AgentRPCResponseResult> => {
const { nodeConnectionManager, logger } = this.container;
// Connections should always be validated
const sourceNodeId = ids.parseNodeId(input.sourceNodeIdEncoded);
const targetNodeId = ids.parseNodeId(input.targetNodeIdEncoded);
const relayingNodeId = agentUtils.nodeIdFromMeta(meta);
if (relayingNodeId == null) {
throw new agentErrors.ErrorAgentNodeIdMissing();
}
const requestSignature = Buffer.from(input.requestSignature, 'base64url');
// Checking request requestSignature, requestData is just `<sourceNodeId><targetNodeId>` concatenated
const requestData = Buffer.concat([sourceNodeId, targetNodeId]);
const sourcePublicKey = keysUtils.publicKeyFromNodeId(sourceNodeId);
if (
!keysUtils.verifyWithPublicKey(
sourcePublicKey,
requestData,
requestSignature,
)
) {
throw new agentErrors.ErrorNodesConnectionSignalRequestVerificationFailed();
}
// Checking relay message relaySignature.
// relayData is just `<sourceNodeId><targetNodeId><Address><requestSignature>` concatenated.
const relayData = Buffer.concat([
sourceNodeId,
targetNodeId,
Buffer.from(JSON.stringify(input.address), 'utf-8'),
requestSignature,
]);
const relayPublicKey = keysUtils.publicKeyFromNodeId(relayingNodeId);
const relaySignature = Buffer.from(input.relaySignature, 'base64url');
if (
!keysUtils.verifyWithPublicKey(relayPublicKey, relayData, relaySignature)
) {
throw new agentErrors.ErrorNodesConnectionSignalRelayVerificationFailed();
}

const host = input.address.host as Host;
const port = input.address.port as Port;
logger.debug(`Received signaling message to target ${host}:${port}`);
nodeConnectionManager.handleNodesConnectionSignalFinal(host, port);
return {};
};
}

export default NodesConnectionSignalFinal;
66 changes: 66 additions & 0 deletions src/nodes/agent/handlers/NodesConnectionSignalInitial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type {
AgentRPCRequestParams,
AgentRPCResponseResult,
HolePunchSignalMessage,
} from '../types';
import type NodeConnectionManager from '../../../nodes/NodeConnectionManager';
import type { Host, Port } from '../../../network/types';
import type { NodeAddress } from '../../../nodes/types';
import type { JSONValue } from '../../../types';
import { UnaryHandler } from '@matrixai/rpc';
import * as agentErrors from '../errors';
import * as agentUtils from '../utils';
import { never } from '../../../utils';
import * as keysUtils from '../../../keys/utils';
import * as ids from '../../../ids';

class NodesConnectionSignalInitial extends UnaryHandler<
{
nodeConnectionManager: NodeConnectionManager;
},
AgentRPCRequestParams<HolePunchSignalMessage>,
AgentRPCResponseResult
> {
public handle = async (
input: AgentRPCRequestParams<HolePunchSignalMessage>,
_cancel,
meta: Record<string, JSONValue> | undefined,
): Promise<AgentRPCResponseResult> => {
const { nodeConnectionManager } = this.container;
// Connections should always be validated
const requestingNodeId = agentUtils.nodeIdFromMeta(meta);
if (requestingNodeId == null) {
throw new agentErrors.ErrorAgentNodeIdMissing();
}
const targetNodeId = ids.parseNodeId(input.targetNodeIdEncoded);
const signature = Buffer.from(input.signature, 'base64url');
// Checking signature, data is just `<sourceNodeId><targetNodeId>` concatenated
const data = Buffer.concat([requestingNodeId, targetNodeId]);
const sourcePublicKey = keysUtils.publicKeyFromNodeId(requestingNodeId);
if (!keysUtils.verifyWithPublicKey(sourcePublicKey, data, signature)) {
throw new agentErrors.ErrorNodesConnectionSignalRelayVerificationFailed();
}
if (meta == null) never('Missing metadata from stream');
const remoteHost = meta.remoteHost;
const remotePort = meta.remotePort;
if (remoteHost == null || typeof remoteHost !== 'string') {
never('Missing or invalid remoteHost');
}
if (remotePort == null || typeof remotePort !== 'number') {
never('Missing or invalid remotePort');
}
const address: NodeAddress = {
host: remoteHost as Host,
port: remotePort as Port,
};
nodeConnectionManager.handleNodesConnectionSignalInitial(
requestingNodeId,
targetNodeId,
address,
input.signature,
);
return {};
};
}

export default NodesConnectionSignalInitial;
113 changes: 0 additions & 113 deletions src/nodes/agent/handlers/NodesHolePunchMessageSend.ts

This file was deleted.

9 changes: 6 additions & 3 deletions src/nodes/agent/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import type NotificationsManager from '../../../notifications/NotificationsManag
import type VaultManager from '../../../vaults/VaultManager';
import NodesClaimsGet from './NodesClaimsGet';
import NodesClosestLocalNodesGet from './NodesClosestLocalNodesGet';
import NodesConnectionSignalFinal from './NodesConnectionSignalFinal';
import NodesConnectionSignalInitial from './NodesConnectionSignalInitial';
import NodesCrossSignClaim from './NodesCrossSignClaim';
import NodesHolePunchMessageSend from './NodesHolePunchMessageSend';
import NotificationsSend from './NotificationsSend';
import VaultsGitInfoGet from './VaultsGitInfoGet';
import VaultsGitPackGet from './VaultsGitPackGet';
Expand All @@ -35,8 +36,9 @@ const manifestServer = (container: {
return {
nodesClaimsGet: new NodesClaimsGet(container),
nodesClosestLocalNodesGet: new NodesClosestLocalNodesGet(container),
nodesConnectionSignalFinal: new NodesConnectionSignalFinal(container),
nodesConnectionSignalInitial: new NodesConnectionSignalInitial(container),
nodesCrossSignClaim: new NodesCrossSignClaim(container),
nodesHolePunchMessageSend: new NodesHolePunchMessageSend(container),
notificationsSend: new NotificationsSend(container),
vaultsGitInfoGet: new VaultsGitInfoGet(container),
vaultsGitPackGet: new VaultsGitPackGet(container),
Expand All @@ -49,8 +51,9 @@ export default manifestServer;
export {
NodesClaimsGet,
NodesClosestLocalNodesGet,
NodesConnectionSignalFinal,
NodesConnectionSignalInitial,
NodesCrossSignClaim,
NodesHolePunchMessageSend,
NotificationsSend,
VaultsGitInfoGet,
VaultsGitPackGet,
Expand Down
Loading

0 comments on commit 479f8b6

Please sign in to comment.