Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAT Signalling (Hole Punching) should be Fire & Forget, Coalesced, and Secured with Signatures #555

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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