Skip to content

Commit 479f8b6

Browse files
authored
Merge pull request #555 from MatrixAI/feature-nat-signalling
NAT Signalling (Hole Punching) should be Fire & Forget, Coalesced, and Secured with Signatures
2 parents 9f52ca0 + 6123329 commit 479f8b6

22 files changed

Lines changed: 1244 additions & 706 deletions

src/nodes/NodeConnectionManager.ts

Lines changed: 176 additions & 104 deletions
Large diffs are not rendered by default.

src/nodes/agent/callers/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import nodesClaimsGet from './nodesClaimsGet';
22
import nodesClosestLocalNodesGet from './nodesClosestLocalNodesGet';
3+
import nodesConnectionSignalFinal from './nodesConnectionSignalFinal';
4+
import nodesConnectionSignalInitial from './nodesConnectionSignalInitial';
35
import nodesCrossSignClaim from './nodesCrossSignClaim';
4-
import nodesHolePunchMessageSend from './nodesHolePunchMessageSend';
56
import notificationsSend from './notificationsSend';
67
import vaultsGitInfoGet from './vaultsGitInfoGet';
78
import vaultsGitPackGet from './vaultsGitPackGet';
@@ -13,8 +14,9 @@ import vaultsScan from './vaultsScan';
1314
const manifestClient = {
1415
nodesClaimsGet,
1516
nodesClosestLocalNodesGet,
17+
nodesConnectionSignalFinal,
18+
nodesConnectionSignalInitial,
1619
nodesCrossSignClaim,
17-
nodesHolePunchMessageSend,
1820
notificationsSend,
1921
vaultsGitInfoGet,
2022
vaultsGitPackGet,
@@ -26,8 +28,9 @@ export default manifestClient;
2628
export {
2729
nodesClaimsGet,
2830
nodesClosestLocalNodesGet,
31+
nodesConnectionSignalFinal,
32+
nodesConnectionSignalInitial,
2933
nodesCrossSignClaim,
30-
nodesHolePunchMessageSend,
3134
notificationsSend,
3235
vaultsGitInfoGet,
3336
vaultsGitPackGet,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { HandlerTypes } from '@matrixai/rpc';
2+
import type NodesConnectionSignalFinal from '../handlers/NodesConnectionSignalFinal';
3+
import { UnaryCaller } from '@matrixai/rpc';
4+
5+
type CallerTypes = HandlerTypes<NodesConnectionSignalFinal>;
6+
7+
const nodesConnectionSignalFinal = new UnaryCaller<
8+
CallerTypes['input'],
9+
CallerTypes['output']
10+
>();
11+
12+
export default nodesConnectionSignalFinal;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { HandlerTypes } from '@matrixai/rpc';
2+
import type NodesConnectionSignalInitial from '../handlers/NodesConnectionSignalInitial';
3+
import { UnaryCaller } from '@matrixai/rpc';
4+
5+
type CallerTypes = HandlerTypes<NodesConnectionSignalInitial>;
6+
7+
const nodesConnectionSignalInitial = new UnaryCaller<
8+
CallerTypes['input'],
9+
CallerTypes['output']
10+
>();
11+
12+
export default nodesConnectionSignalInitial;

src/nodes/agent/callers/nodesHolePunchMessageSend.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/nodes/agent/errors.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,22 @@ class ErrorAgentNodeIdMissing<T> extends ErrorAgent<T> {
88
exitCode = sysexits.UNAVAILABLE;
99
}
1010

11-
export { ErrorAgentNodeIdMissing };
11+
class ErrorNodesConnectionSignalRequestVerificationFailed<
12+
T,
13+
> extends ErrorAgent<T> {
14+
static description = 'Failed to verify request message signature';
15+
exitCode = sysexits.UNAVAILABLE;
16+
}
17+
18+
class ErrorNodesConnectionSignalRelayVerificationFailed<
19+
T,
20+
> extends ErrorAgent<T> {
21+
static description = 'Failed to verify relay message signature';
22+
exitCode = sysexits.UNAVAILABLE;
23+
}
24+
25+
export {
26+
ErrorAgentNodeIdMissing,
27+
ErrorNodesConnectionSignalRequestVerificationFailed,
28+
ErrorNodesConnectionSignalRelayVerificationFailed,
29+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type Logger from '@matrixai/logger';
2+
import type {
3+
AgentRPCRequestParams,
4+
AgentRPCResponseResult,
5+
HolePunchRequestMessage,
6+
} from '../types';
7+
import type NodeConnectionManager from '../../NodeConnectionManager';
8+
import type { Host, Port } from '../../../network/types';
9+
import { UnaryHandler } from '@matrixai/rpc';
10+
import * as keysUtils from '../../../keys/utils';
11+
import * as ids from '../../../ids';
12+
import * as agentErrors from '../errors';
13+
import * as agentUtils from '../utils';
14+
15+
class NodesConnectionSignalFinal extends UnaryHandler<
16+
{
17+
nodeConnectionManager: NodeConnectionManager;
18+
logger: Logger;
19+
},
20+
AgentRPCRequestParams<HolePunchRequestMessage>,
21+
AgentRPCResponseResult
22+
> {
23+
public handle = async (
24+
input: AgentRPCRequestParams<HolePunchRequestMessage>,
25+
_cancel,
26+
meta,
27+
): Promise<AgentRPCResponseResult> => {
28+
const { nodeConnectionManager, logger } = this.container;
29+
// Connections should always be validated
30+
const sourceNodeId = ids.parseNodeId(input.sourceNodeIdEncoded);
31+
const targetNodeId = ids.parseNodeId(input.targetNodeIdEncoded);
32+
const relayingNodeId = agentUtils.nodeIdFromMeta(meta);
33+
if (relayingNodeId == null) {
34+
throw new agentErrors.ErrorAgentNodeIdMissing();
35+
}
36+
const requestSignature = Buffer.from(input.requestSignature, 'base64url');
37+
// Checking request requestSignature, requestData is just `<sourceNodeId><targetNodeId>` concatenated
38+
const requestData = Buffer.concat([sourceNodeId, targetNodeId]);
39+
const sourcePublicKey = keysUtils.publicKeyFromNodeId(sourceNodeId);
40+
if (
41+
!keysUtils.verifyWithPublicKey(
42+
sourcePublicKey,
43+
requestData,
44+
requestSignature,
45+
)
46+
) {
47+
throw new agentErrors.ErrorNodesConnectionSignalRequestVerificationFailed();
48+
}
49+
// Checking relay message relaySignature.
50+
// relayData is just `<sourceNodeId><targetNodeId><Address><requestSignature>` concatenated.
51+
const relayData = Buffer.concat([
52+
sourceNodeId,
53+
targetNodeId,
54+
Buffer.from(JSON.stringify(input.address), 'utf-8'),
55+
requestSignature,
56+
]);
57+
const relayPublicKey = keysUtils.publicKeyFromNodeId(relayingNodeId);
58+
const relaySignature = Buffer.from(input.relaySignature, 'base64url');
59+
if (
60+
!keysUtils.verifyWithPublicKey(relayPublicKey, relayData, relaySignature)
61+
) {
62+
throw new agentErrors.ErrorNodesConnectionSignalRelayVerificationFailed();
63+
}
64+
65+
const host = input.address.host as Host;
66+
const port = input.address.port as Port;
67+
logger.debug(`Received signaling message to target ${host}:${port}`);
68+
nodeConnectionManager.handleNodesConnectionSignalFinal(host, port);
69+
return {};
70+
};
71+
}
72+
73+
export default NodesConnectionSignalFinal;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type {
2+
AgentRPCRequestParams,
3+
AgentRPCResponseResult,
4+
HolePunchSignalMessage,
5+
} from '../types';
6+
import type NodeConnectionManager from '../../../nodes/NodeConnectionManager';
7+
import type { Host, Port } from '../../../network/types';
8+
import type { NodeAddress } from '../../../nodes/types';
9+
import type { JSONValue } from '../../../types';
10+
import { UnaryHandler } from '@matrixai/rpc';
11+
import * as agentErrors from '../errors';
12+
import * as agentUtils from '../utils';
13+
import { never } from '../../../utils';
14+
import * as keysUtils from '../../../keys/utils';
15+
import * as ids from '../../../ids';
16+
17+
class NodesConnectionSignalInitial extends UnaryHandler<
18+
{
19+
nodeConnectionManager: NodeConnectionManager;
20+
},
21+
AgentRPCRequestParams<HolePunchSignalMessage>,
22+
AgentRPCResponseResult
23+
> {
24+
public handle = async (
25+
input: AgentRPCRequestParams<HolePunchSignalMessage>,
26+
_cancel,
27+
meta: Record<string, JSONValue> | undefined,
28+
): Promise<AgentRPCResponseResult> => {
29+
const { nodeConnectionManager } = this.container;
30+
// Connections should always be validated
31+
const requestingNodeId = agentUtils.nodeIdFromMeta(meta);
32+
if (requestingNodeId == null) {
33+
throw new agentErrors.ErrorAgentNodeIdMissing();
34+
}
35+
const targetNodeId = ids.parseNodeId(input.targetNodeIdEncoded);
36+
const signature = Buffer.from(input.signature, 'base64url');
37+
// Checking signature, data is just `<sourceNodeId><targetNodeId>` concatenated
38+
const data = Buffer.concat([requestingNodeId, targetNodeId]);
39+
const sourcePublicKey = keysUtils.publicKeyFromNodeId(requestingNodeId);
40+
if (!keysUtils.verifyWithPublicKey(sourcePublicKey, data, signature)) {
41+
throw new agentErrors.ErrorNodesConnectionSignalRelayVerificationFailed();
42+
}
43+
if (meta == null) never('Missing metadata from stream');
44+
const remoteHost = meta.remoteHost;
45+
const remotePort = meta.remotePort;
46+
if (remoteHost == null || typeof remoteHost !== 'string') {
47+
never('Missing or invalid remoteHost');
48+
}
49+
if (remotePort == null || typeof remotePort !== 'number') {
50+
never('Missing or invalid remotePort');
51+
}
52+
const address: NodeAddress = {
53+
host: remoteHost as Host,
54+
port: remotePort as Port,
55+
};
56+
nodeConnectionManager.handleNodesConnectionSignalInitial(
57+
requestingNodeId,
58+
targetNodeId,
59+
address,
60+
input.signature,
61+
);
62+
return {};
63+
};
64+
}
65+
66+
export default NodesConnectionSignalInitial;

src/nodes/agent/handlers/NodesHolePunchMessageSend.ts

Lines changed: 0 additions & 113 deletions
This file was deleted.

src/nodes/agent/handlers/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import type NotificationsManager from '../../../notifications/NotificationsManag
1010
import type VaultManager from '../../../vaults/VaultManager';
1111
import NodesClaimsGet from './NodesClaimsGet';
1212
import NodesClosestLocalNodesGet from './NodesClosestLocalNodesGet';
13+
import NodesConnectionSignalFinal from './NodesConnectionSignalFinal';
14+
import NodesConnectionSignalInitial from './NodesConnectionSignalInitial';
1315
import NodesCrossSignClaim from './NodesCrossSignClaim';
14-
import NodesHolePunchMessageSend from './NodesHolePunchMessageSend';
1516
import NotificationsSend from './NotificationsSend';
1617
import VaultsGitInfoGet from './VaultsGitInfoGet';
1718
import VaultsGitPackGet from './VaultsGitPackGet';
@@ -35,8 +36,9 @@ const manifestServer = (container: {
3536
return {
3637
nodesClaimsGet: new NodesClaimsGet(container),
3738
nodesClosestLocalNodesGet: new NodesClosestLocalNodesGet(container),
39+
nodesConnectionSignalFinal: new NodesConnectionSignalFinal(container),
40+
nodesConnectionSignalInitial: new NodesConnectionSignalInitial(container),
3841
nodesCrossSignClaim: new NodesCrossSignClaim(container),
39-
nodesHolePunchMessageSend: new NodesHolePunchMessageSend(container),
4042
notificationsSend: new NotificationsSend(container),
4143
vaultsGitInfoGet: new VaultsGitInfoGet(container),
4244
vaultsGitPackGet: new VaultsGitPackGet(container),
@@ -49,8 +51,9 @@ export default manifestServer;
4951
export {
5052
NodesClaimsGet,
5153
NodesClosestLocalNodesGet,
54+
NodesConnectionSignalFinal,
55+
NodesConnectionSignalInitial,
5256
NodesCrossSignClaim,
53-
NodesHolePunchMessageSend,
5457
NotificationsSend,
5558
VaultsGitInfoGet,
5659
VaultsGitPackGet,

0 commit comments

Comments
 (0)