Skip to content

Commit 3fe4b39

Browse files
authored
Merge pull request #370 from MatrixAI/GRPC_call_failure_fix
fix: bug with GPRC client call through proxy
2 parents db3b913 + 9e44d52 commit 3fe4b39

18 files changed

+199
-10
lines changed

src/network/ConnectionReverse.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ class ConnectionReverse extends Connection {
295295
await this.stop();
296296
});
297297
this.tlsSocket.pipe(this.serverSocket, { end: false });
298-
this.serverSocket.pipe(this.tlsSocket, { end: false });
298+
this.tlsSocket.once('data', () => {
299+
this.serverSocket.pipe(this.tlsSocket as TLSSocket, { end: false });
300+
});
299301
this.clientCertChain = clientCertChain;
300302
this.logger.info('Composed Connection Reverse');
301303
} catch (e) {

src/network/Proxy.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,13 @@ class Proxy {
434434
timer,
435435
);
436436
} catch (e) {
437+
if (e instanceof networkErrors.ErrorProxyConnectInvalidUrl) {
438+
if (!clientSocket.destroyed) {
439+
await clientSocketEnd('HTTP/1.1 400 Bad Request\r\n' + '\r\n');
440+
clientSocket.destroy(e);
441+
}
442+
return;
443+
}
437444
if (e instanceof networkErrors.ErrorConnectionStartTimeout) {
438445
if (!clientSocket.destroyed) {
439446
await clientSocketEnd('HTTP/1.1 504 Gateway Timeout\r\n' + '\r\n');
@@ -519,6 +526,9 @@ class Proxy {
519526
proxyPort: Port,
520527
timer?: Timer,
521528
): Promise<ConnectionForward> {
529+
if (networkUtils.isHostWildcard(proxyHost)) {
530+
throw new networkErrors.ErrorProxyConnectInvalidUrl();
531+
}
522532
const proxyAddress = networkUtils.buildAddress(proxyHost, proxyPort);
523533
let conn: ConnectionForward | undefined;
524534
conn = this.connectionsForward.proxy.get(proxyAddress);
@@ -681,6 +691,9 @@ class Proxy {
681691
proxyPort: Port,
682692
timer?: Timer,
683693
): Promise<ConnectionReverse> {
694+
if (networkUtils.isHostWildcard(proxyHost)) {
695+
throw new networkErrors.ErrorProxyConnectInvalidUrl();
696+
}
684697
const proxyAddress = networkUtils.buildAddress(proxyHost, proxyPort);
685698
let conn = this.connectionsReverse.proxy.get(proxyAddress);
686699
if (conn != null) {

src/network/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ function isHost(host: any): host is Host {
2929
return isIPv4 || isIPv6;
3030
}
3131

32+
function isHostWildcard(host: Host): boolean {
33+
return host === '0.0.0.0' || host === '::';
34+
}
35+
3236
/**
3337
* Validates hostname as per RFC 1123
3438
*/
@@ -353,6 +357,7 @@ export {
353357
pingBuffer,
354358
pongBuffer,
355359
isHost,
360+
isHostWildcard,
356361
isHostname,
357362
isPort,
358363
toAuthToken,

src/nodes/NodeConnection.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class NodeConnection<T extends GRPCClient> {
5959
logger?: Logger;
6060
}): Promise<NodeConnection<T>> {
6161
logger.info(`Creating ${this.name}`);
62+
// Checking if attempting to connect to a wildcard IP
63+
if (networkUtils.isHostWildcard(targetHost)) {
64+
throw new nodesErrors.ErrorNodeConnectionHostWildcard();
65+
}
6266
const proxyConfig = {
6367
host: proxy.getForwardHost(),
6468
port: proxy.getForwardPort(),

src/nodes/NodeConnectionManager.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class NodeConnectionManager {
129129
targetNodeId: NodeId,
130130
): Promise<ResourceAcquire<NodeConnection<GRPCClientAgent>>> {
131131
return async () => {
132-
const connAndLock = await this.createConnection(targetNodeId);
132+
const connAndLock = await this.getConnection(targetNodeId);
133133
// Acquire the read lock and the release function
134134
const release = await connAndLock.lock.acquireRead();
135135
// Resetting TTL timer
@@ -161,6 +161,11 @@ class NodeConnectionManager {
161161
return await withF(
162162
[await this.acquireConnection(targetNodeId)],
163163
async ([conn]) => {
164+
this.logger.info(
165+
`withConnF calling function with connection to ${nodesUtils.encodeNodeId(
166+
targetNodeId,
167+
)}`,
168+
);
164169
return await f(conn);
165170
},
166171
);
@@ -219,11 +224,11 @@ class NodeConnectionManager {
219224
* @param targetNodeId Id of node we are creating connection to
220225
* @returns ConnectionAndLock that was create or exists in the connection map.
221226
*/
222-
protected async createConnection(
227+
protected async getConnection(
223228
targetNodeId: NodeId,
224229
): Promise<ConnectionAndLock> {
225230
this.logger.info(
226-
`Creating connection to ${nodesUtils.encodeNodeId(targetNodeId)}`,
231+
`Getting connection to ${nodesUtils.encodeNodeId(targetNodeId)}`,
227232
);
228233
let connection: NodeConnection<GRPCClientAgent> | undefined;
229234
let lock: RWLock;
@@ -241,10 +246,13 @@ class NodeConnectionManager {
241246
targetNodeId.toString() as NodeIdString,
242247
);
243248
if (connAndLock != null && connAndLock.connection != null) {
249+
this.logger.info(
250+
`existing entry found for ${nodesUtils.encodeNodeId(targetNodeId)}`,
251+
);
244252
return connAndLock;
245253
}
246254
this.logger.info(
247-
`existing lock: creating connection to ${nodesUtils.encodeNodeId(
255+
`existing lock, creating connection to ${nodesUtils.encodeNodeId(
248256
targetNodeId,
249257
)}`,
250258
);
@@ -260,7 +268,7 @@ class NodeConnectionManager {
260268
);
261269
return await lock.withWrite(async () => {
262270
this.logger.info(
263-
`no existing entry: creating connection to ${nodesUtils.encodeNodeId(
271+
`no existing entry, creating connection to ${nodesUtils.encodeNodeId(
264272
targetNodeId,
265273
)}`,
266274
);
@@ -318,7 +326,9 @@ class NodeConnectionManager {
318326
nodeConnectionManager: this,
319327
destroyCallback,
320328
connConnectTime: this.connConnectTime,
321-
logger: this.logger.getChild(`${targetHost}:${targetAddress.port}`),
329+
logger: this.logger.getChild(
330+
`${NodeConnection.name} ${targetHost}:${targetAddress.port}`,
331+
),
322332
clientFactory: async (args) =>
323333
GRPCClientAgent.createGRPCClientAgent(args),
324334
});
@@ -521,7 +531,7 @@ class NodeConnectionManager {
521531
// Add the node to the database so that we can find its address in
522532
// call to getConnectionToNode
523533
await this.nodeGraph.setNode(nextNode.id, nextNode.address);
524-
await this.createConnection(nextNode.id);
534+
await this.getConnection(nextNode.id);
525535
} catch (e) {
526536
// If we can't connect to the node, then skip it
527537
continue;
@@ -620,7 +630,7 @@ class NodeConnectionManager {
620630
for (const seedNodeId of this.getSeedNodes()) {
621631
// Check if the connection is viable
622632
try {
623-
await this.createConnection(seedNodeId);
633+
await this.getConnection(seedNodeId);
624634
} catch (e) {
625635
if (e instanceof nodesErrors.ErrorNodeConnectionTimeout) continue;
626636
throw e;

src/nodes/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ class ErrorNodeConnectionManagerNotRunning extends ErrorNodes {
6262
exitCode = sysexits.USAGE;
6363
}
6464

65+
class ErrorNodeConnectionHostWildcard extends ErrorNodes {
66+
description = 'An IP wildcard was provided for the target host';
67+
exitCode = sysexits.USAGE;
68+
}
69+
6570
export {
6671
ErrorNodes,
6772
ErrorNodeGraphRunning,
@@ -76,4 +81,5 @@ export {
7681
ErrorNodeConnectionInfoNotExist,
7782
ErrorNodeConnectionPublicKeyNotFound,
7883
ErrorNodeConnectionManagerNotRunning,
84+
ErrorNodeConnectionHostWildcard,
7985
};

tests/agent/service/nodesCrossSignClaim.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ describe('nodesCrossSignClaim', () => {
5252
rootKeyPairBits: 2048,
5353
},
5454
seedNodes: {}, // Explicitly no seed nodes on startup
55+
networkConfig: {
56+
proxyHost: '127.0.0.1' as Host,
57+
},
5558
logger,
5659
});
5760
localId = pkAgent.keyManager.getNodeId();
@@ -63,6 +66,9 @@ describe('nodesCrossSignClaim', () => {
6366
rootKeyPairBits: 2048,
6467
},
6568
seedNodes: {}, // Explicitly no seed nodes on startup
69+
networkConfig: {
70+
proxyHost: '127.0.0.1' as Host,
71+
},
6672
logger,
6773
});
6874
remoteId = remoteNode.keyManager.getNodeId();

tests/bin/vaults/vaults.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { NodeIdEncoded, NodeAddress, NodeInfo } from '@/nodes/types';
22
import type { VaultId, VaultName } from '@/vaults/types';
3+
import type { Host } from '@/network/types';
34
import os from 'os';
45
import path from 'path';
56
import fs from 'fs';
@@ -207,6 +208,9 @@ describe('CLI vaults', () => {
207208
const targetPolykeyAgent = await PolykeyAgent.createPolykeyAgent({
208209
password,
209210
nodePath: dataDir2,
211+
networkConfig: {
212+
proxyHost: '127.0.0.1' as Host,
213+
},
210214
logger: logger,
211215
});
212216
const vaultId = await targetPolykeyAgent.vaultManager.createVault(
@@ -701,6 +705,9 @@ describe('CLI vaults', () => {
701705
password,
702706
logger,
703707
nodePath: path.join(dataDir, 'remoteOnline'),
708+
networkConfig: {
709+
proxyHost: '127.0.0.1' as Host,
710+
},
704711
});
705712
const remoteOnlineNodeId = remoteOnline.keyManager.getNodeId();
706713
const remoteOnlineNodeIdEncoded =

tests/network/Proxy.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ describe(Proxy.name, () => {
208208
`127.0.0.1:80?nodeId=${encodeURIComponent(nodeIdSomeEncoded)}`,
209209
),
210210
).rejects.toThrow('407');
211+
// Wildcard as host
212+
await expect(() =>
213+
httpConnect(
214+
proxy.getForwardHost(),
215+
proxy.getForwardPort(),
216+
authToken,
217+
`0.0.0.0:80?nodeId=${encodeURIComponent(nodeIdSomeEncoded)}`,
218+
),
219+
).rejects.toThrow('400');
211220
// No node id
212221
await expect(() =>
213222
httpConnect(

tests/nodes/NodeConnection.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,47 @@ describe(`${NodeConnection.name} test`, () => {
433433
});
434434
await conn.destroy();
435435
});
436+
test('connects to its target but proxies connect first', async () => {
437+
await clientproxy.openConnectionForward(
438+
targetNodeId,
439+
localHost,
440+
targetPort,
441+
);
442+
const conn = await NodeConnection.createNodeConnection({
443+
targetNodeId: targetNodeId,
444+
targetHost: localHost,
445+
targetPort: targetPort,
446+
proxy: clientproxy,
447+
keyManager: clientKeyManager,
448+
nodeConnectionManager: dummyNodeConnectionManager,
449+
destroyCallback,
450+
logger: logger,
451+
clientFactory: async (args) =>
452+
GRPCClientAgent.createGRPCClientAgent(args),
453+
});
454+
// Because the connection will not have enough time to compose before we
455+
// attempt to acquire the connection info, we need to wait and poll it
456+
const connInfo = await poll<ConnectionInfo | undefined>(
457+
async () => {
458+
return serverProxy.getConnectionInfoByProxy(localHost, sourcePort);
459+
},
460+
(e) => {
461+
if (e instanceof networkErrors.ErrorConnectionNotComposed) return false;
462+
if (e instanceof networkErrors.ErrorConnectionNotRunning) return false;
463+
return true;
464+
},
465+
);
466+
expect(connInfo).toBeDefined();
467+
expect(connInfo).toMatchObject({
468+
remoteNodeId: sourceNodeId,
469+
remoteCertificates: expect.any(Array),
470+
localHost: localHost,
471+
localPort: targetPort,
472+
remoteHost: localHost,
473+
remotePort: sourcePort,
474+
});
475+
await conn.destroy();
476+
});
436477
test('grpcCall after connection drops', async () => {
437478
let nodeConnection: NodeConnection<GRPCClientAgent> | undefined;
438479
let polykeyAgent: PolykeyAgent | undefined;
@@ -441,6 +482,9 @@ describe(`${NodeConnection.name} test`, () => {
441482
password,
442483
nodePath: path.join(dataDir, 'PolykeyAgent3'),
443484
logger: logger,
485+
networkConfig: {
486+
proxyHost: localHost,
487+
},
444488
});
445489
// Have a nodeConnection try to connect to it
446490
const killSelf = jest.fn();
@@ -629,6 +673,9 @@ describe(`${NodeConnection.name} test`, () => {
629673
password,
630674
nodePath: path.join(dataDir, 'PolykeyAgent3'),
631675
logger: logger,
676+
networkConfig: {
677+
proxyHost: localHost,
678+
},
632679
});
633680
// Have a nodeConnection try to connect to it
634681
const killSelf = jest.fn();

0 commit comments

Comments
 (0)