diff --git a/package-lock.json b/package-lock.json index acdf5c67a..be8b574bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "GPL-3.0", "dependencies": { "@grpc/grpc-js": "1.6.7", - "@matrixai/async-init": "^1.7.3", - "@matrixai/async-locks": "^2.2.5", + "@matrixai/async-init": "^1.8.1", + "@matrixai/async-locks": "^2.3.1", "@matrixai/db": "^4.0.5", "@matrixai/errors": "^1.1.1", "@matrixai/id": "^3.3.3", - "@matrixai/logger": "^2.1.1", + "@matrixai/logger": "^2.2.2", "@matrixai/resources": "^1.1.3", "@matrixai/workers": "^1.3.3", "ajv": "^7.0.4", @@ -76,6 +76,7 @@ "node-gyp-build": "^4.4.0", "pkg": "5.6.0", "prettier": "^2.6.2", + "shelljs": "^0.8.5", "shx": "^0.3.4", "ts-jest": "^27.0.5", "ts-node": "10.7.0", @@ -2536,18 +2537,18 @@ } }, "node_modules/@matrixai/async-init": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.7.3.tgz", - "integrity": "sha512-Sf3q5ODhVJqrYiAdGXmwj606956lgEMKGM9LMFU5scIOh13WokHo3GthjB1yh/umCV75NYvHJn60R9gnudVZ3Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.8.1.tgz", + "integrity": "sha512-ZAS1yd/PC+r3NwvT9fEz3OtAm68A8mKXXGdZRcYQF1ajl43jsV8/B4aDwr2oLFlV+RYZgWl7UwjZj4rtoZSycQ==", "dependencies": { - "@matrixai/async-locks": "^2.2.4", + "@matrixai/async-locks": "^2.3.1", "@matrixai/errors": "^1.1.1" } }, "node_modules/@matrixai/async-locks": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@matrixai/async-locks/-/async-locks-2.2.5.tgz", - "integrity": "sha512-Yokd3p64FciLNSW04Qox+UHJulxnWQIhHt3h9sMmgoiTsyZcOgeYfPAJrtZiRnhaUXhfBczPy4VPP2e5lrXgig==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-locks/-/async-locks-2.3.1.tgz", + "integrity": "sha512-STz8VyiIXleaa72zMsq01x/ZO1gPzukUgMe25+uqMWn/nPrC9EtJOR7e3CW0DODfYDZ0748z196GeOjS3jh+4g==", "dependencies": { "@matrixai/errors": "^1.1.1", "@matrixai/resources": "^1.1.3", @@ -2587,9 +2588,9 @@ } }, "node_modules/@matrixai/logger": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.1.1.tgz", - "integrity": "sha512-79KM0PyJTpfkALf9DK2xGniU+9gngsb5O8hcdUviWz+zR2W0hnTQq/g7tJW0YnIEhmDe/GkJf0Bnbs+gWfj3BA==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.2.2.tgz", + "integrity": "sha512-6/G1svkcFiBMvmIdBv6YbxoLKwMWpXNzt93Cc4XbXXygCQrsn6oYwLvnRk/JNr6uM29M2T+Aa7K1o3n2XMTuLw==" }, "node_modules/@matrixai/resources": { "version": "1.1.3", @@ -13643,18 +13644,18 @@ } }, "@matrixai/async-init": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.7.3.tgz", - "integrity": "sha512-Sf3q5ODhVJqrYiAdGXmwj606956lgEMKGM9LMFU5scIOh13WokHo3GthjB1yh/umCV75NYvHJn60R9gnudVZ3Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.8.1.tgz", + "integrity": "sha512-ZAS1yd/PC+r3NwvT9fEz3OtAm68A8mKXXGdZRcYQF1ajl43jsV8/B4aDwr2oLFlV+RYZgWl7UwjZj4rtoZSycQ==", "requires": { - "@matrixai/async-locks": "^2.2.4", + "@matrixai/async-locks": "^2.3.1", "@matrixai/errors": "^1.1.1" } }, "@matrixai/async-locks": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@matrixai/async-locks/-/async-locks-2.2.5.tgz", - "integrity": "sha512-Yokd3p64FciLNSW04Qox+UHJulxnWQIhHt3h9sMmgoiTsyZcOgeYfPAJrtZiRnhaUXhfBczPy4VPP2e5lrXgig==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-locks/-/async-locks-2.3.1.tgz", + "integrity": "sha512-STz8VyiIXleaa72zMsq01x/ZO1gPzukUgMe25+uqMWn/nPrC9EtJOR7e3CW0DODfYDZ0748z196GeOjS3jh+4g==", "requires": { "@matrixai/errors": "^1.1.1", "@matrixai/resources": "^1.1.3", @@ -13694,9 +13695,9 @@ } }, "@matrixai/logger": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.1.1.tgz", - "integrity": "sha512-79KM0PyJTpfkALf9DK2xGniU+9gngsb5O8hcdUviWz+zR2W0hnTQq/g7tJW0YnIEhmDe/GkJf0Bnbs+gWfj3BA==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.2.2.tgz", + "integrity": "sha512-6/G1svkcFiBMvmIdBv6YbxoLKwMWpXNzt93Cc4XbXXygCQrsn6oYwLvnRk/JNr6uM29M2T+Aa7K1o3n2XMTuLw==" }, "@matrixai/resources": { "version": "1.1.3", diff --git a/package.json b/package.json index acffa9aa7..47f502c95 100644 --- a/package.json +++ b/package.json @@ -76,12 +76,12 @@ }, "dependencies": { "@grpc/grpc-js": "1.6.7", - "@matrixai/async-init": "^1.7.3", - "@matrixai/async-locks": "^2.2.5", + "@matrixai/async-init": "^1.8.1", + "@matrixai/async-locks": "^2.3.1", "@matrixai/db": "^4.0.5", "@matrixai/errors": "^1.1.1", "@matrixai/id": "^3.3.3", - "@matrixai/logger": "^2.1.1", + "@matrixai/logger": "^2.2.2", "@matrixai/resources": "^1.1.3", "@matrixai/workers": "^1.3.3", "ajv": "^7.0.4", @@ -138,6 +138,7 @@ "node-gyp-build": "^4.4.0", "pkg": "5.6.0", "prettier": "^2.6.2", + "shelljs": "^0.8.5", "shx": "^0.3.4", "ts-jest": "^27.0.5", "ts-node": "10.7.0", diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index e3f033c71..3cd247700 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -606,7 +606,7 @@ class PolykeyAgent { acl: this.acl, gestaltGraph: this.gestaltGraph, proxy: this.proxy, - logger: this.logger.getChild(createAgentService.name), + logger: this.logger.getChild('GRPCClientAgentService'), }); const clientService = createClientService({ pkAgent: this, @@ -627,7 +627,7 @@ class PolykeyAgent { grpcServerAgent: this.grpcServerAgent, proxy: this.proxy, fs: this.fs, - logger: this.logger.getChild(createClientService.name), + logger: this.logger.getChild('GRPCClientClientService'), }); // Starting modules await this.keyManager.start({ @@ -696,6 +696,10 @@ class PolykeyAgent { await this.notificationsManager?.stop(); await this.vaultManager?.stop(); await this.discovery?.stop(); + await this.queue?.stop(); + await this.nodeGraph?.stop(); + await this.nodeConnectionManager?.stop(); + await this.nodeManager?.stop(); await this.proxy?.stop(); await this.grpcServerAgent?.stop(); await this.grpcServerClient?.stop(); diff --git a/src/agent/service/index.ts b/src/agent/service/index.ts index 6342c2ba5..2e51b699e 100644 --- a/src/agent/service/index.ts +++ b/src/agent/service/index.ts @@ -27,7 +27,7 @@ import * as agentUtils from '../utils'; function createService({ proxy, db, - logger = new Logger(createService.name), + logger = new Logger('GRPCClientAgentService'), ...containerRest }: { db: DB; diff --git a/src/agent/service/nodesChainDataGet.ts b/src/agent/service/nodesChainDataGet.ts index e0f838b33..10175c706 100644 --- a/src/agent/service/nodesChainDataGet.ts +++ b/src/agent/service/nodesChainDataGet.ts @@ -51,7 +51,8 @@ function nodesChainDataGet({ return; } catch (e) { callback(grpcUtils.fromError(e, true)); - !agentUtils.isAgentClientError(e) && logger.error(e); + !agentUtils.isAgentClientError(e) && + logger.error(`${nodesChainDataGet.name}:${e}`); return; } }; diff --git a/src/agent/service/nodesClosestLocalNodesGet.ts b/src/agent/service/nodesClosestLocalNodesGet.ts index 36a172b12..4c987667d 100644 --- a/src/agent/service/nodesClosestLocalNodesGet.ts +++ b/src/agent/service/nodesClosestLocalNodesGet.ts @@ -63,7 +63,8 @@ function nodesClosestLocalNodesGet({ return; } catch (e) { callback(grpcUtils.fromError(e, true)); - !agentUtils.isAgentClientError(e) && logger.error(e); + !agentUtils.isAgentClientError(e) && + logger.error(`${nodesClosestLocalNodesGet.name}:${e}`); return; } }; diff --git a/src/agent/service/nodesCrossSignClaim.ts b/src/agent/service/nodesCrossSignClaim.ts index 20d5ac575..2c9793ba0 100644 --- a/src/agent/service/nodesCrossSignClaim.ts +++ b/src/agent/service/nodesCrossSignClaim.ts @@ -179,7 +179,7 @@ function nodesCrossSignClaim({ claimsErrors.ErrorUndefinedSignature, claimsErrors.ErrorNodesClaimType, claimsErrors.ErrorUndefinedDoublySignedClaim, - ]) && logger.error(e); + ]) && logger.error(`${nodesCrossSignClaim.name}:${e}`); return; } }; diff --git a/src/agent/service/nodesHolePunchMessageSend.ts b/src/agent/service/nodesHolePunchMessageSend.ts index 088787264..c610d7428 100644 --- a/src/agent/service/nodesHolePunchMessageSend.ts +++ b/src/agent/service/nodesHolePunchMessageSend.ts @@ -74,7 +74,8 @@ function nodesHolePunchMessageSend({ return; } catch (e) { callback(grpcUtils.fromError(e, true)); - !agentUtils.isAgentClientError(e) && logger.error(e); + !agentUtils.isAgentClientError(e) && + logger.error(`${nodesHolePunchMessageSend.name}:${e}`); return; } }; diff --git a/src/agent/service/notificationsSend.ts b/src/agent/service/notificationsSend.ts index eb336243c..cd1b43c76 100644 --- a/src/agent/service/notificationsSend.ts +++ b/src/agent/service/notificationsSend.ts @@ -41,7 +41,7 @@ function notificationsSend({ notificationsErrors.ErrorNotificationsValidationFailed, notificationsErrors.ErrorNotificationsParse, notificationsErrors.ErrorNotificationsPermissionsNotFound, - ]) && logger.error(e); + ]) && logger.error(`${notificationsSend.name}:${e}`); return; } }; diff --git a/src/agent/service/vaultsGitInfoGet.ts b/src/agent/service/vaultsGitInfoGet.ts index 7fe9831ff..0fb18c96a 100644 --- a/src/agent/service/vaultsGitInfoGet.ts +++ b/src/agent/service/vaultsGitInfoGet.ts @@ -111,7 +111,7 @@ function vaultsGitInfoGet({ vaultsErrors.ErrorVaultsVaultUndefined, agentErrors.ErrorConnectionInfoMissing, vaultsErrors.ErrorVaultsPermissionDenied, - ]) && logger.error(e); + ]) && logger.error(`${vaultsGitInfoGet.name}:${e}`); return; } }; diff --git a/src/agent/service/vaultsGitPackGet.ts b/src/agent/service/vaultsGitPackGet.ts index 50db9da28..f8aa5dc3d 100644 --- a/src/agent/service/vaultsGitPackGet.ts +++ b/src/agent/service/vaultsGitPackGet.ts @@ -124,7 +124,7 @@ function vaultsGitPackGet({ agentErrors.ErrorConnectionInfoMissing, vaultsErrors.ErrorVaultsPermissionDenied, vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsGitPackGet.name}:${e}`); return; } }; diff --git a/src/agent/service/vaultsScan.ts b/src/agent/service/vaultsScan.ts index f1a94c2a9..f82719108 100644 --- a/src/agent/service/vaultsScan.ts +++ b/src/agent/service/vaultsScan.ts @@ -56,7 +56,7 @@ function vaultsScan({ !agentUtils.isAgentClientError(e, [ agentErrors.ErrorConnectionInfoMissing, vaultsErrors.ErrorVaultsPermissionDenied, - ]) && logger.error(e); + ]) && logger.error(`${vaultsScan.name}:${e}`); return; } }; diff --git a/src/client/GRPCClientClient.ts b/src/client/GRPCClientClient.ts index 8b9816536..2a0a4626f 100644 --- a/src/client/GRPCClientClient.ts +++ b/src/client/GRPCClientClient.ts @@ -909,7 +909,7 @@ class GRPCClientClient extends GRPCClient { nodeId: this.nodeId, host: this.host, port: this.port, - command: this.identitiesAuthenticate.name, + command: this.nodesGetAll.name, }, this.client.nodesGetAll, )(...args); diff --git a/src/client/service/agentLockAll.ts b/src/client/service/agentLockAll.ts index 29e64bcad..2c2c7505e 100644 --- a/src/client/service/agentLockAll.ts +++ b/src/client/service/agentLockAll.ts @@ -33,7 +33,8 @@ function agentLockAll({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${agentLockAll.name}:${e}`); return; } }; diff --git a/src/client/service/agentStatus.ts b/src/client/service/agentStatus.ts index 110555cf8..3ebb00b5d 100644 --- a/src/client/service/agentStatus.ts +++ b/src/client/service/agentStatus.ts @@ -50,7 +50,8 @@ function agentStatus({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${agentStatus.name}:${e}`); return; } }; diff --git a/src/client/service/agentStop.ts b/src/client/service/agentStop.ts index f88885f09..2332c732e 100644 --- a/src/client/service/agentStop.ts +++ b/src/client/service/agentStop.ts @@ -33,7 +33,8 @@ function agentStop({ callback(null, response); } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${agentStop.name}:${e}`); return; } // Stop is called after GRPC resources are cleared diff --git a/src/client/service/agentUnlock.ts b/src/client/service/agentUnlock.ts index 5147e2718..991a51c9f 100644 --- a/src/client/service/agentUnlock.ts +++ b/src/client/service/agentUnlock.ts @@ -24,7 +24,8 @@ function agentUnlock({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${agentUnlock.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsGetByIdentity.ts b/src/client/service/gestaltsActionsGetByIdentity.ts index ef45d57e7..3375ed15d 100644 --- a/src/client/service/gestaltsActionsGetByIdentity.ts +++ b/src/client/service/gestaltsActionsGetByIdentity.ts @@ -63,7 +63,8 @@ function gestaltsActionsGetByIdentity({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsActionsGetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsGetByNode.ts b/src/client/service/gestaltsActionsGetByNode.ts index b5652d3bb..ea0e4298d 100644 --- a/src/client/service/gestaltsActionsGetByNode.ts +++ b/src/client/service/gestaltsActionsGetByNode.ts @@ -57,7 +57,8 @@ function gestaltsActionsGetByNode({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsActionsGetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsSetByIdentity.ts b/src/client/service/gestaltsActionsSetByIdentity.ts index 0628b2a98..b60d3aa84 100644 --- a/src/client/service/gestaltsActionsSetByIdentity.ts +++ b/src/client/service/gestaltsActionsSetByIdentity.ts @@ -71,7 +71,7 @@ function gestaltsActionsSetByIdentity({ !clientUtils.isClientClientError(e, [ gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${gestaltsActionsSetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsSetByNode.ts b/src/client/service/gestaltsActionsSetByNode.ts index 2aa9a7050..187c634a7 100644 --- a/src/client/service/gestaltsActionsSetByNode.ts +++ b/src/client/service/gestaltsActionsSetByNode.ts @@ -56,7 +56,7 @@ function gestaltsActionsSetByNode({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${gestaltsActionsSetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsUnsetByIdentity.ts b/src/client/service/gestaltsActionsUnsetByIdentity.ts index 88745bc64..b2467bee5 100644 --- a/src/client/service/gestaltsActionsUnsetByIdentity.ts +++ b/src/client/service/gestaltsActionsUnsetByIdentity.ts @@ -71,7 +71,7 @@ function gestaltsActionsUnsetByIdentity({ !clientUtils.isClientClientError(e, [ gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${gestaltsActionsUnsetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsUnsetByNode.ts b/src/client/service/gestaltsActionsUnsetByNode.ts index d7a62d68a..bc39dc569 100644 --- a/src/client/service/gestaltsActionsUnsetByNode.ts +++ b/src/client/service/gestaltsActionsUnsetByNode.ts @@ -56,7 +56,7 @@ function gestaltsActionsUnsetByNode({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${gestaltsActionsUnsetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsDiscoveryByIdentity.ts b/src/client/service/gestaltsDiscoveryByIdentity.ts index 37efbf7d3..08f2df64e 100644 --- a/src/client/service/gestaltsDiscoveryByIdentity.ts +++ b/src/client/service/gestaltsDiscoveryByIdentity.ts @@ -52,7 +52,8 @@ function gestaltsDiscoveryByIdentity({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsDiscoveryByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsDiscoveryByNode.ts b/src/client/service/gestaltsDiscoveryByNode.ts index 0c8d1ea4c..f6ed454b0 100644 --- a/src/client/service/gestaltsDiscoveryByNode.ts +++ b/src/client/service/gestaltsDiscoveryByNode.ts @@ -48,7 +48,8 @@ function gestaltsDiscoveryByNode({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsDiscoveryByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltGetByIdentity.ts b/src/client/service/gestaltsGestaltGetByIdentity.ts index 81e3ffc54..8768ad136 100644 --- a/src/client/service/gestaltsGestaltGetByIdentity.ts +++ b/src/client/service/gestaltsGestaltGetByIdentity.ts @@ -60,7 +60,8 @@ function gestaltsGestaltGetByIdentity({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsGestaltGetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltGetByNode.ts b/src/client/service/gestaltsGestaltGetByNode.ts index fb3e63f56..207859fb5 100644 --- a/src/client/service/gestaltsGestaltGetByNode.ts +++ b/src/client/service/gestaltsGestaltGetByNode.ts @@ -56,7 +56,8 @@ function gestaltsGestaltGetByNode({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsGestaltGetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltList.ts b/src/client/service/gestaltsGestaltList.ts index 7b4725892..d07fb9f32 100644 --- a/src/client/service/gestaltsGestaltList.ts +++ b/src/client/service/gestaltsGestaltList.ts @@ -40,7 +40,8 @@ function gestaltsGestaltList({ return; } catch (e) { await genWritable.throw(e); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsGestaltList.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltTrustByIdentity.ts b/src/client/service/gestaltsGestaltTrustByIdentity.ts index 36a0aa982..06a9eb6c4 100644 --- a/src/client/service/gestaltsGestaltTrustByIdentity.ts +++ b/src/client/service/gestaltsGestaltTrustByIdentity.ts @@ -87,7 +87,7 @@ function gestaltsGestaltTrustByIdentity({ !clientUtils.isClientClientError(e, [ gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${gestaltsGestaltTrustByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltTrustByNode.ts b/src/client/service/gestaltsGestaltTrustByNode.ts index 8fa7299a0..4e01de903 100644 --- a/src/client/service/gestaltsGestaltTrustByNode.ts +++ b/src/client/service/gestaltsGestaltTrustByNode.ts @@ -73,7 +73,7 @@ function gestaltsGestaltTrustByNode({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${gestaltsGestaltTrustByNode.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesAuthenticate.ts b/src/client/service/identitiesAuthenticate.ts index d9c216c6c..0950abddd 100644 --- a/src/client/service/identitiesAuthenticate.ts +++ b/src/client/service/identitiesAuthenticate.ts @@ -77,7 +77,7 @@ function identitiesAuthenticate({ await genWritable.throw(e); !clientUtils.isClientClientError(e, [ identitiesErrors.ErrorProviderMissing, - ]) && logger.error(e); + ]) && logger.error(`${identitiesAuthenticate.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesAuthenticatedGet.ts b/src/client/service/identitiesAuthenticatedGet.ts index 3ab4cc941..106a1f53a 100644 --- a/src/client/service/identitiesAuthenticatedGet.ts +++ b/src/client/service/identitiesAuthenticatedGet.ts @@ -64,7 +64,8 @@ function identitiesAuthenticatedGet({ return; } catch (e) { await genWritable.throw(e); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesAuthenticatedGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesClaim.ts b/src/client/service/identitiesClaim.ts index de5e1df57..6677c77d4 100644 --- a/src/client/service/identitiesClaim.ts +++ b/src/client/service/identitiesClaim.ts @@ -96,7 +96,7 @@ function identitiesClaim({ !clientUtils.isClientClientError(e, [ identitiesErrors.ErrorProviderMissing, identitiesErrors.ErrorProviderUnauthenticated, - ]) && logger.error(e); + ]) && logger.error(`${identitiesClaim.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesInfoConnectedGet.ts b/src/client/service/identitiesInfoConnectedGet.ts index 63d681700..f8f906807 100644 --- a/src/client/service/identitiesInfoConnectedGet.ts +++ b/src/client/service/identitiesInfoConnectedGet.ts @@ -124,7 +124,7 @@ function identitiesInfoConnectedGet({ identitiesErrors.ErrorProviderMissing, identitiesErrors.ErrorProviderUnauthenticated, identitiesErrors.ErrorProviderUnimplemented, - ]) && logger.error(e); + ]) && logger.error(`${identitiesInfoConnectedGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesInfoGet.ts b/src/client/service/identitiesInfoGet.ts index e8df1153b..3fa2bdbc1 100644 --- a/src/client/service/identitiesInfoGet.ts +++ b/src/client/service/identitiesInfoGet.ts @@ -116,7 +116,7 @@ function identitiesInfoGet({ !clientUtils.isClientClientError(e, [ identitiesErrors.ErrorProviderMissing, identitiesErrors.ErrorProviderUnauthenticated, - ]) && logger.error(e); + ]) && logger.error(`${identitiesInfoGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesProvidersList.ts b/src/client/service/identitiesProvidersList.ts index 70cbc9ccd..17ae7bf31 100644 --- a/src/client/service/identitiesProvidersList.ts +++ b/src/client/service/identitiesProvidersList.ts @@ -30,7 +30,8 @@ function identitiesProvidersList({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesProvidersList.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesTokenDelete.ts b/src/client/service/identitiesTokenDelete.ts index 779ffea47..2b4a78b9b 100644 --- a/src/client/service/identitiesTokenDelete.ts +++ b/src/client/service/identitiesTokenDelete.ts @@ -57,7 +57,8 @@ function identitiesTokenDelete({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesTokenDelete.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesTokenGet.ts b/src/client/service/identitiesTokenGet.ts index 5ca76f585..c829da281 100644 --- a/src/client/service/identitiesTokenGet.ts +++ b/src/client/service/identitiesTokenGet.ts @@ -57,7 +57,8 @@ function identitiesTokenGet({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesTokenGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesTokenPut.ts b/src/client/service/identitiesTokenPut.ts index 261b88a12..b7ae0139f 100644 --- a/src/client/service/identitiesTokenPut.ts +++ b/src/client/service/identitiesTokenPut.ts @@ -67,7 +67,8 @@ function identitiesTokenPut({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesTokenPut.name}:${e}`); return; } }; diff --git a/src/client/service/index.ts b/src/client/service/index.ts index d6b1dff6f..68f98ac8c 100644 --- a/src/client/service/index.ts +++ b/src/client/service/index.ts @@ -91,7 +91,7 @@ function createService({ keyManager, sessionManager, db, - logger = new Logger(createService.name), + logger = new Logger('GRPCClientClientService'), fs = require('fs'), ...containerRest }: { diff --git a/src/client/service/keysCertsChainGet.ts b/src/client/service/keysCertsChainGet.ts index fed99bf2a..fc074a239 100644 --- a/src/client/service/keysCertsChainGet.ts +++ b/src/client/service/keysCertsChainGet.ts @@ -34,7 +34,8 @@ function keysCertsChainGet({ return; } catch (e) { await genWritable.throw(e); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysCertsChainGet.name}:${e}`); return; } }; diff --git a/src/client/service/keysCertsGet.ts b/src/client/service/keysCertsGet.ts index 1b8222612..ff5e3d525 100644 --- a/src/client/service/keysCertsGet.ts +++ b/src/client/service/keysCertsGet.ts @@ -30,7 +30,8 @@ function keysCertsGet({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysCertsGet.name}:${e}`); return; } }; diff --git a/src/client/service/keysDecrypt.ts b/src/client/service/keysDecrypt.ts index b1a0f6bad..c155c0d8d 100644 --- a/src/client/service/keysDecrypt.ts +++ b/src/client/service/keysDecrypt.ts @@ -31,7 +31,8 @@ function keysDecrypt({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysDecrypt.name}:${e}`); return; } }; diff --git a/src/client/service/keysEncrypt.ts b/src/client/service/keysEncrypt.ts index b8c5c60bb..71a5a84ff 100644 --- a/src/client/service/keysEncrypt.ts +++ b/src/client/service/keysEncrypt.ts @@ -31,7 +31,8 @@ function keysEncrypt({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysEncrypt.name}:${e}`); return; } }; diff --git a/src/client/service/keysKeyPairRenew.ts b/src/client/service/keysKeyPairRenew.ts index ada854cad..a44df0628 100644 --- a/src/client/service/keysKeyPairRenew.ts +++ b/src/client/service/keysKeyPairRenew.ts @@ -31,7 +31,8 @@ function keysKeyPairRenew({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysKeyPairRenew.name}:${e}`); return; } }; diff --git a/src/client/service/keysKeyPairReset.ts b/src/client/service/keysKeyPairReset.ts index af3b25426..a1ba20365 100644 --- a/src/client/service/keysKeyPairReset.ts +++ b/src/client/service/keysKeyPairReset.ts @@ -31,7 +31,8 @@ function keysKeyPairReset({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysKeyPairReset.name}:${e}`); return; } }; diff --git a/src/client/service/keysKeyPairRoot.ts b/src/client/service/keysKeyPairRoot.ts index 14f543d9f..2b1cdce82 100644 --- a/src/client/service/keysKeyPairRoot.ts +++ b/src/client/service/keysKeyPairRoot.ts @@ -31,7 +31,8 @@ function keysKeyPairRoot({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysKeyPairRoot.name}:${e}`); return; } }; diff --git a/src/client/service/keysPasswordChange.ts b/src/client/service/keysPasswordChange.ts index b1d116db5..a7f4ed029 100644 --- a/src/client/service/keysPasswordChange.ts +++ b/src/client/service/keysPasswordChange.ts @@ -29,7 +29,8 @@ function keysPasswordChange({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysPasswordChange.name}:${e}`); return; } }; diff --git a/src/client/service/keysSign.ts b/src/client/service/keysSign.ts index 9c3aa2ab3..126d0c149 100644 --- a/src/client/service/keysSign.ts +++ b/src/client/service/keysSign.ts @@ -32,7 +32,8 @@ function keysSign({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysSign.name}:${e}`); return; } }; diff --git a/src/client/service/keysVerify.ts b/src/client/service/keysVerify.ts index 1c53fff74..d2c82bfba 100644 --- a/src/client/service/keysVerify.ts +++ b/src/client/service/keysVerify.ts @@ -33,7 +33,8 @@ function keysVerify({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysVerify.name}:${e}`); return; } }; diff --git a/src/client/service/nodesAdd.ts b/src/client/service/nodesAdd.ts index 3b6043219..92de5581d 100644 --- a/src/client/service/nodesAdd.ts +++ b/src/client/service/nodesAdd.ts @@ -89,7 +89,8 @@ function nodesAdd({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${nodesAdd.name}:${e}`); return; } }; diff --git a/src/client/service/nodesClaim.ts b/src/client/service/nodesClaim.ts index c9e1d6db9..991ed9548 100644 --- a/src/client/service/nodesClaim.ts +++ b/src/client/service/nodesClaim.ts @@ -79,7 +79,7 @@ function nodesClaim({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(e); + ]) && logger.error(`${nodesClaim.name}:${e}`); return; } }; diff --git a/src/client/service/nodesFind.ts b/src/client/service/nodesFind.ts index 324e1c0e9..7b02f9347 100644 --- a/src/client/service/nodesFind.ts +++ b/src/client/service/nodesFind.ts @@ -62,7 +62,7 @@ function nodesFind({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(e); + ]) && logger.error(`${nodesFind.name}:${e}`); return; } }; diff --git a/src/client/service/nodesGetAll.ts b/src/client/service/nodesGetAll.ts index 8c021a248..ad6ab9b6e 100644 --- a/src/client/service/nodesGetAll.ts +++ b/src/client/service/nodesGetAll.ts @@ -1,25 +1,30 @@ import type * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; import type { Authenticate } from '../types'; import type KeyManager from '../../keys/KeyManager'; import type { NodeId } from '../../nodes/types'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import type NodeGraph from '../../nodes/NodeGraph'; import { IdInternal } from '@matrixai/id'; -import { utils as nodesUtils } from '../../nodes'; -import { utils as grpcUtils } from '../../grpc'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import * as nodesUtils from '../../nodes/utils'; +import * as nodesErrors from '../../nodes/errors'; +import * as grpcUtils from '../../grpc/utils'; +import * as clientUtils from '../utils'; /** * Retrieves all nodes from all buckets in the NodeGraph. */ function nodesGetAll({ + authenticate, nodeGraph, keyManager, - authenticate, + logger, }: { + authenticate: Authenticate; nodeGraph: NodeGraph; keyManager: KeyManager; - authenticate: Authenticate; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -62,6 +67,9 @@ function nodesGetAll({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${nodesGetAll.name}:${e}`); return; } }; diff --git a/src/client/service/nodesPing.ts b/src/client/service/nodesPing.ts index 5adc83f54..eaf69983c 100644 --- a/src/client/service/nodesPing.ts +++ b/src/client/service/nodesPing.ts @@ -55,7 +55,7 @@ function nodesPing({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(e); + ]) && logger.error(`${nodesPing.name}:${e}`); return; } }; diff --git a/src/client/service/notificationsClear.ts b/src/client/service/notificationsClear.ts index acc05724e..ebcea2af0 100644 --- a/src/client/service/notificationsClear.ts +++ b/src/client/service/notificationsClear.ts @@ -33,7 +33,8 @@ function notificationsClear({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${notificationsClear.name}:${e}`); return; } }; diff --git a/src/client/service/notificationsRead.ts b/src/client/service/notificationsRead.ts index 4e6cbb92d..f706b5bd2 100644 --- a/src/client/service/notificationsRead.ts +++ b/src/client/service/notificationsRead.ts @@ -75,7 +75,8 @@ function notificationsRead({ return; } catch (e) { callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${notificationsRead.name}:${e}`); return; } }; diff --git a/src/client/service/notificationsSend.ts b/src/client/service/notificationsSend.ts index fcdb72606..118d5edbf 100644 --- a/src/client/service/notificationsSend.ts +++ b/src/client/service/notificationsSend.ts @@ -58,7 +58,7 @@ function notificationsSend({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(e); + ]) && logger.error(`${notificationsSend.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsClone.ts b/src/client/service/vaultsClone.ts index ec60df21a..14b799837 100644 --- a/src/client/service/vaultsClone.ts +++ b/src/client/service/vaultsClone.ts @@ -68,7 +68,7 @@ function vaultsClone({ nodesErrors.ErrorNodeGraphNodeIdNotFound, vaultsErrors.ErrorVaultsNameConflict, [grpcErrors.ErrorPolykeyRemote, vaultsErrors.ErrorVaultsVaultUndefined], - ]) && logger.error(e); + ]) && logger.error(`${vaultsClone.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsCreate.ts b/src/client/service/vaultsCreate.ts index c181b4b76..df7c6cfac 100644 --- a/src/client/service/vaultsCreate.ts +++ b/src/client/service/vaultsCreate.ts @@ -41,7 +41,7 @@ function vaultsCreate({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultDefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsCreate.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsDelete.ts b/src/client/service/vaultsDelete.ts index 61fbfbc27..8e04bf0ab 100644 --- a/src/client/service/vaultsDelete.ts +++ b/src/client/service/vaultsDelete.ts @@ -50,7 +50,7 @@ function vaultsDelete({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsDelete.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsList.ts b/src/client/service/vaultsList.ts index 2e9f1a79a..c7d3da737 100644 --- a/src/client/service/vaultsList.ts +++ b/src/client/service/vaultsList.ts @@ -40,7 +40,8 @@ function vaultsList({ return; } catch (e) { await genWritable.throw(e); - !clientUtils.isClientClientError(e) && logger.error(e); + !clientUtils.isClientClientError(e) && + logger.error(`${vaultsList.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsLog.ts b/src/client/service/vaultsLog.ts index 32d014b05..c028006dc 100644 --- a/src/client/service/vaultsLog.ts +++ b/src/client/service/vaultsLog.ts @@ -69,7 +69,7 @@ function vaultsLog({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorVaultReferenceInvalid, - ]) && logger.error(e); + ]) && logger.error(`${vaultsLog.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPermissionGet.ts b/src/client/service/vaultsPermissionGet.ts index 86377f531..e89d9b500 100644 --- a/src/client/service/vaultsPermissionGet.ts +++ b/src/client/service/vaultsPermissionGet.ts @@ -75,7 +75,7 @@ function vaultsPermissionGet({ await genWritable.throw(e); !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsPermissionGet.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPermissionSet.ts b/src/client/service/vaultsPermissionSet.ts index 9bc2a3b8d..37b30c29a 100644 --- a/src/client/service/vaultsPermissionSet.ts +++ b/src/client/service/vaultsPermissionSet.ts @@ -103,7 +103,7 @@ function vaultsPermissionSet({ vaultsErrors.ErrorVaultsVaultUndefined, aclErrors.ErrorACLNodeIdMissing, nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(e); + ]) && logger.error(`${vaultsPermissionSet.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPermissionUnset.ts b/src/client/service/vaultsPermissionUnset.ts index 7906207a3..5aa386d6b 100644 --- a/src/client/service/vaultsPermissionUnset.ts +++ b/src/client/service/vaultsPermissionUnset.ts @@ -102,7 +102,7 @@ function vaultsPermissionUnset({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(e); + ]) && logger.error(`${vaultsPermissionUnset.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPull.ts b/src/client/service/vaultsPull.ts index 00fa44880..ea83519a4 100644 --- a/src/client/service/vaultsPull.ts +++ b/src/client/service/vaultsPull.ts @@ -90,7 +90,7 @@ function vaultsPull({ vaultsErrors.ErrorVaultsVaultUndefined, nodesErrors.ErrorNodeGraphNodeIdNotFound, [grpcErrors.ErrorPolykeyRemote, vaultsErrors.ErrorVaultsVaultUndefined], - ]) && logger.error(e); + ]) && logger.error(`${vaultsPull.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsRename.ts b/src/client/service/vaultsRename.ts index 29d2bb04b..21bcd7626 100644 --- a/src/client/service/vaultsRename.ts +++ b/src/client/service/vaultsRename.ts @@ -51,7 +51,7 @@ function vaultsRename({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorVaultsVaultDefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsRename.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsScan.ts b/src/client/service/vaultsScan.ts index c7a0def19..248a3c5d8 100644 --- a/src/client/service/vaultsScan.ts +++ b/src/client/service/vaultsScan.ts @@ -60,7 +60,7 @@ function vaultsScan({ await genWritable.throw(e); !clientUtils.isClientClientError(e, [ nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(e); + ]) && logger.error(`${vaultsScan.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsDelete.ts b/src/client/service/vaultsSecretsDelete.ts index 625c03cab..c35ba8ce8 100644 --- a/src/client/service/vaultsSecretsDelete.ts +++ b/src/client/service/vaultsSecretsDelete.ts @@ -59,7 +59,7 @@ function vaultsSecretsDelete({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsDelete.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsEdit.ts b/src/client/service/vaultsSecretsEdit.ts index a40ea71e9..d114e56d2 100644 --- a/src/client/service/vaultsSecretsEdit.ts +++ b/src/client/service/vaultsSecretsEdit.ts @@ -61,7 +61,7 @@ function vaultsSecretsEdit({ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorSecretsSecretUndefined, vaultsErrors.ErrorVaultRemoteDefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsEdit.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsGet.ts b/src/client/service/vaultsSecretsGet.ts index 4f826551f..61eafbf16 100644 --- a/src/client/service/vaultsSecretsGet.ts +++ b/src/client/service/vaultsSecretsGet.ts @@ -59,7 +59,7 @@ function vaultsSecretsGet({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsGet.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsList.ts b/src/client/service/vaultsSecretsList.ts index a0774565f..c55aa59d0 100644 --- a/src/client/service/vaultsSecretsList.ts +++ b/src/client/service/vaultsSecretsList.ts @@ -61,7 +61,7 @@ function vaultsSecretsList({ await genWritable.throw(e); !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsList.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsMkdir.ts b/src/client/service/vaultsSecretsMkdir.ts index 623a28ec9..a9a545704 100644 --- a/src/client/service/vaultsSecretsMkdir.ts +++ b/src/client/service/vaultsSecretsMkdir.ts @@ -60,7 +60,7 @@ function vaultsSecretsMkdir({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorVaultsRecursive, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsMkdir.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsNew.ts b/src/client/service/vaultsSecretsNew.ts index c481a4cda..2414e6bf6 100644 --- a/src/client/service/vaultsSecretsNew.ts +++ b/src/client/service/vaultsSecretsNew.ts @@ -60,7 +60,7 @@ function vaultsSecretsNew({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsNew.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsNewDir.ts b/src/client/service/vaultsSecretsNewDir.ts index e9a0d0878..3ed67ce68 100644 --- a/src/client/service/vaultsSecretsNewDir.ts +++ b/src/client/service/vaultsSecretsNewDir.ts @@ -61,7 +61,7 @@ function vaultsSecretsNewDir({ callback(grpcUtils.fromError(e)); !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsNewDir.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsRename.ts b/src/client/service/vaultsSecretsRename.ts index 9362c2b08..d65a87f02 100644 --- a/src/client/service/vaultsSecretsRename.ts +++ b/src/client/service/vaultsSecretsRename.ts @@ -65,7 +65,7 @@ function vaultsSecretsRename({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsRename.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsStat.ts b/src/client/service/vaultsSecretsStat.ts index 78a7d2800..9d4477443 100644 --- a/src/client/service/vaultsSecretsStat.ts +++ b/src/client/service/vaultsSecretsStat.ts @@ -58,7 +58,7 @@ function vaultsSecretsStat({ !clientUtils.isClientClientError(e, [ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(e); + ]) && logger.error(`${vaultsSecretsStat.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsVersion.ts b/src/client/service/vaultsVersion.ts index a3871b526..df1dc3300 100644 --- a/src/client/service/vaultsVersion.ts +++ b/src/client/service/vaultsVersion.ts @@ -68,7 +68,7 @@ function vaultsVersion({ vaultsErrors.ErrorVaultsVaultUndefined, vaultsErrors.ErrorVaultReferenceInvalid, vaultsErrors.ErrorVaultReferenceMissing, - ]) && logger.error(e); + ]) && logger.error(`${vaultsVersion.name}:${e}`); return; } }; diff --git a/src/network/ConnectionReverse.ts b/src/network/ConnectionReverse.ts index ada434649..d7000d9d0 100644 --- a/src/network/ConnectionReverse.ts +++ b/src/network/ConnectionReverse.ts @@ -217,7 +217,6 @@ class ConnectionReverse extends Connection { if (this._composed) { throw new networkErrors.ErrorConnectionComposed(); } - this._composed = true; this.logger.info('Composing Connection Reverse'); // Promise for secure establishment const { p: secureP, resolveP: resolveSecureP } = promise(); @@ -306,6 +305,7 @@ class ConnectionReverse extends Connection { }); this.clientCertChain = clientCertChain; this.logger.info('Composed Connection Reverse'); + this._composed = true; } catch (e) { this._composed = false; throw e; diff --git a/src/nodes/NodeConnection.ts b/src/nodes/NodeConnection.ts index f4f4fbfb3..c90260afc 100644 --- a/src/nodes/NodeConnection.ts +++ b/src/nodes/NodeConnection.ts @@ -128,7 +128,7 @@ class NodeConnection { }, timer: timer, }), - holePunchPromises, + ...holePunchPromises, ]); // 5. When finished, you have a connection to other node // The GRPCClient is ready to be used for requests diff --git a/src/nodes/NodeConnectionManager.ts b/src/nodes/NodeConnectionManager.ts index f39f333d8..30550b6a4 100644 --- a/src/nodes/NodeConnectionManager.ts +++ b/src/nodes/NodeConnectionManager.ts @@ -610,21 +610,23 @@ class NodeConnectionManager { timer, ); for (const [nodeId, nodeData] of nodes) { - const pingAndAddNode = async () => { - const port = nodeData.address.port; - const host = await networkUtils.resolveHost(nodeData.address.host); - if (await this.pingNode(nodeId, host, port)) { - await this.nodeManager!.setNode(nodeId, nodeData.address, true); - } - }; + if (!nodeId.equals(this.keyManager.getNodeId())) { + const pingAndAddNode = async () => { + const port = nodeData.address.port; + const host = await networkUtils.resolveHost(nodeData.address.host); + if (await this.pingNode(nodeId, host, port)) { + await this.nodeManager!.setNode(nodeId, nodeData.address, true); + } + }; - if (!block) { - this.queue.push(pingAndAddNode); - } else { - try { - await pingAndAddNode(); - } catch (e) { - if (!(e instanceof nodesErrors.ErrorNodeGraphSameNodeId)) throw e; + if (!block) { + this.queue.push(pingAndAddNode); + } else { + try { + await pingAndAddNode(); + } catch (e) { + if (!(e instanceof nodesErrors.ErrorNodeGraphSameNodeId)) throw e; + } } } } @@ -696,11 +698,23 @@ class NodeConnectionManager { message: nodesPB.Relay, timer?: Timer, ): Promise { + // First check if we already have an existing ID -> address record + // If we're relaying then we trust our own node graph records over + // what was provided in the message + const sourceNode = validationUtils.parseNodeId(message.getSrcId()); + const knownAddress = (await this.nodeGraph.getNode(sourceNode))?.address; + let proxyAddress = message.getProxyAddress(); + if (knownAddress != null) { + proxyAddress = networkUtils.buildAddress( + knownAddress.host as Host, + knownAddress.port, + ); + } await this.sendHolePunchMessage( validationUtils.parseNodeId(message.getTargetId()), - validationUtils.parseNodeId(message.getSrcId()), + sourceNode, validationUtils.parseNodeId(message.getTargetId()), - message.getProxyAddress(), + proxyAddress, Buffer.from(message.getSignature()), timer, ); @@ -742,6 +756,7 @@ class NodeConnectionManager { const signature = await this.keyManager.signWithRootKeyPair( Buffer.from(proxyAddress), ); + // FIXME: this needs to handle aborting const holePunchPromises = Array.from(this.getSeedNodes(), (seedNodeId) => { return this.sendHolePunchMessage( seedNodeId, @@ -759,7 +774,7 @@ class NodeConnectionManager { ); try { - await Promise.all([forwardPunchPromise, ...holePunchPromises]); + await Promise.any([forwardPunchPromise, ...holePunchPromises]); } catch (e) { return false; } diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index bb264de4f..7245ab5c4 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -47,6 +47,7 @@ class NodeManager { protected refreshBucketQueueRunner: Promise; protected refreshBucketQueuePlug_: PromiseDeconstructed = promise(); protected refreshBucketQueueDrained_: PromiseDeconstructed = promise(); + protected refreshBucketQueuePause_: PromiseDeconstructed = promise(); protected refreshBucketQueueAbortController: AbortController; constructor({ @@ -664,12 +665,25 @@ class NodeManager { await this.refreshBucketQueueDrained_.p; } + public refreshBucketQueuePause() { + this.logger.debug('Pausing refreshBucketQueue'); + this.refreshBucketQueuePause_ = promise(); + } + + public refreshBucketQueueResume() { + this.logger.debug('Resuming refreshBucketQueue'); + this.refreshBucketQueuePause_.resolveP(); + } + private async startRefreshBucketQueue(): Promise { this.refreshBucketQueueRunning = true; this.refreshBucketQueuePlug(); + this.refreshBucketQueueResume(); let iterator: IterableIterator | undefined; this.refreshBucketQueueAbortController = new AbortController(); const pace = async () => { + // Wait if paused + await this.refreshBucketQueuePause_.p; // Wait for plug await this.refreshBucketQueuePlug_.p; if (iterator == null) { @@ -709,14 +723,17 @@ class NodeManager { this.refreshBucketQueueAbortController.abort(); this.refreshBucketQueueRunning = false; this.refreshBucketQueueUnplug(); + this.refreshBucketQueueResume(); } private refreshBucketQueuePlug() { + this.logger.debug('refresh bucket queue has plugged'); this.refreshBucketQueuePlug_ = promise(); this.refreshBucketQueueDrained_?.resolveP(); } private refreshBucketQueueUnplug() { + this.logger.debug('refresh bucket queue has unplugged'); this.refreshBucketQueueDrained_ = promise(); this.refreshBucketQueuePlug_?.resolveP(); } diff --git a/tests/bin/utils.ts b/tests/bin/utils.ts index b0acefed2..c6cc42c54 100644 --- a/tests/bin/utils.ts +++ b/tests/bin/utils.ts @@ -12,6 +12,35 @@ import nexpect from 'nexpect'; import Logger from '@matrixai/logger'; import main from '@/bin/polykey'; +/** + * Wrapper for execFile to make it asynchronous and non-blocking + */ +async function exec( + command: string, + args: Array = [], +): Promise<{ + stdout: string; + stderr: string; +}> { + return new Promise((resolve, reject) => { + child_process.execFile( + command, + args, + { windowsHide: true }, + (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + return resolve({ + stdout, + stderr, + }); + } + }, + ); + }); +} + /** * Runs pk command functionally */ @@ -361,6 +390,7 @@ function expectProcessError( } export { + exec, pk, pkStdio, pkExec, diff --git a/tests/nat/DMZ.test.ts b/tests/nat/DMZ.test.ts new file mode 100644 index 000000000..ae54d2d15 --- /dev/null +++ b/tests/nat/DMZ.test.ts @@ -0,0 +1,280 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import readline from 'readline'; +import process from 'process'; +import shell from 'shelljs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import Status from '@/status/Status'; +import config from '@/config'; +import * as testNatUtils from './utils'; +import { describeIf } from '../utils'; +import * as testBinUtils from '../bin/utils'; + +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'DMZ', + () => { + const logger = new Logger('DMZ test', LogLevel.WARN, [new StreamHandler()]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test( + 'can create an agent in a namespace', + async () => { + const password = 'abc123'; + const usrns = testNatUtils.createUserNamespace(logger); + const netns = testNatUtils.createNetworkNamespace(usrns.pid!, logger); + const agentProcess = await testNatUtils.pkSpawnNs( + usrns.pid!, + netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'polykey'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + '127.0.0.1', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agentProcess'), + ); + const rlOut = readline.createInterface(agentProcess.stdout!); + const stdout = await new Promise((resolve, reject) => { + rlOut.once('line', resolve); + rlOut.once('close', reject); + }); + const statusLiveData = JSON.parse(stdout); + expect(statusLiveData).toMatchObject({ + pid: agentProcess.pid, + nodeId: expect.any(String), + clientHost: expect.any(String), + clientPort: expect.any(Number), + agentHost: expect.any(String), + agentPort: expect.any(Number), + forwardHost: expect.any(String), + forwardPort: expect.any(Number), + proxyHost: expect.any(String), + proxyPort: expect.any(Number), + recoveryCode: expect.any(String), + }); + expect( + statusLiveData.recoveryCode.split(' ').length === 12 || + statusLiveData.recoveryCode.split(' ').length === 24, + ).toBe(true); + agentProcess.kill('SIGTERM'); + let exitCode, signal; + [exitCode, signal] = await testBinUtils.processExit(agentProcess); + expect(exitCode).toBe(null); + expect(signal).toBe('SIGTERM'); + // Check for graceful exit + const status = new Status({ + statusPath: path.join(dataDir, 'polykey', config.defaults.statusBase), + statusLockPath: path.join( + dataDir, + 'polykey', + config.defaults.statusLockBase, + ), + fs, + logger, + }); + const statusInfo = (await status.readStatus())!; + expect(statusInfo.status).toBe('DEAD'); + netns.kill('SIGTERM'); + [exitCode, signal] = await testBinUtils.processExit(netns); + expect(exitCode).toBe(null); + expect(signal).toBe('SIGTERM'); + usrns.kill('SIGTERM'); + [exitCode, signal] = await testBinUtils.processExit(usrns); + expect(exitCode).toBe(null); + expect(signal).toBe('SIGTERM'); + }, + global.defaultTimeout * 2, + ); + test( + 'agents in different namespaces can ping each other', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'dmz', logger); + // Namespace1 Namespace2 + // ┌────────────────────────────────────┐ ┌────────────────────────────────────┐ + // │ │ │ │ + // │ ┌────────┐ ┌─────────┐ │ │ ┌─────────┐ ┌────────┐ │ + // │ │ Agent1 ├────────┤ Router1 │ │ │ │ Router2 ├────────┤ Agent2 │ │ + // │ └────────┘ └─────────┘ │ │ └─────────┘ └────────┘ │ + // │ 10.0.0.2:55551 192.168.0.1:55555 │ │ 192.168.0.2:55555 10.0.0.2:55552 │ + // │ │ │ │ + // └────────────────────────────────────┘ └────────────────────────────────────┘ + // Since neither node is behind a NAT can directly add eachother's + // details using pk nodes add + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'agents in different namespaces can ping each other via seed node', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('dmz', 'dmz', logger); + // Namespace1 Namespace3 Namespace2 + // ┌────────────────────────────────────┐ ┌──────────────────┐ ┌────────────────────────────────────┐ + // │ │ │ │ │ │ + // │ ┌────────┐ ┌─────────┐ │ │ ┌──────────┐ │ │ ┌─────────┐ ┌────────┐ │ + // │ │ Agent1 ├────────┤ Router1 │ │ │ │ SeedNode │ │ │ │ Router2 ├────────┤ Agent2 │ │ + // │ └────────┘ └─────────┘ │ │ └──────────┘ │ │ └─────────┘ └────────┘ │ + // │ 10.0.0.2:55551 192.168.0.1:55555 │ │ 192.168.0.3:PORT │ │ 192.168.0.2:55555 10.0.0.2:55552 │ + // │ │ │ │ │ │ + // └────────────────────────────────────┘ └──────────────────┘ └────────────────────────────────────┘ + // Should be able to ping straight away using the details from the + // seed node + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/endpointDependentNAT.test.ts b/tests/nat/endpointDependentNAT.test.ts new file mode 100644 index 000000000..663293f4a --- /dev/null +++ b/tests/nat/endpointDependentNAT.test.ts @@ -0,0 +1,261 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import process from 'process'; +import shell from 'shelljs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testNatUtils from './utils'; +import { describeIf } from '../utils'; + +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'endpoint dependent NAT traversal', + () => { + const logger = new Logger('EDM NAT test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test( + 'node1 behind EDM NAT connects to node2', + async () => { + const { + userPid, + agent1Pid, + password, + dataDir, + agent1NodePath, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('edm', 'dmz', logger); + // Since node2 is not behind a NAT can directly add its details + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const { exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 connects to node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'edm', logger); + // Agent 2 must ping Agent 1 first, since Agent 2 is behind a NAT + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + // Can now ping Agent 2 (it will be expecting a response) + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EDM NAT cannot connect to node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('edm', 'edm', logger); + // Contact details are retrieved from the seed node, but cannot be used + // since port mapping changes between targets in EDM mapping + // Node 2 -> Node 1 ping should fail (Node 1 behind NAT) + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent1NodeId} to an address.`, + }); + // Node 1 -> Node 2 ping should also fail for the same reason + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent2NodeId} to an address.`, + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EDM NAT cannot connect to node2 behind EIM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('edm', 'eim', logger); + // Since one of the nodes uses EDM NAT we cannot punch through + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent1NodeId} to an address.`, + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent2NodeId} to an address.`, + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/endpointIndependentNAT.test.ts b/tests/nat/endpointIndependentNAT.test.ts new file mode 100644 index 000000000..9bdbf2abd --- /dev/null +++ b/tests/nat/endpointIndependentNAT.test.ts @@ -0,0 +1,400 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import process from 'process'; +import shell from 'shelljs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testNatUtils from './utils'; +import { describeIf } from '../utils'; + +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'endpoint independent NAT traversal', + () => { + const logger = new Logger('EIM NAT test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test( + 'node1 behind EIM NAT connects to node2', + async () => { + const { + userPid, + agent1Pid, + password, + dataDir, + agent1NodePath, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('eim', 'dmz', logger); + // Since node2 is not behind a NAT can directly add its details + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const { exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 connects to node2 behind EIM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'eim', logger); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // If we try to ping Agent 2 it will fail + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + // But Agent 2 can ping Agent 1 because Agent 1 is not behind a NAT + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + // Can now ping Agent 2 (it will be expecting a response) + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EIM NAT connects to node2 behind EIM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'eim', logger); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // If we try to ping Agent 2 it will fail + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + // But Agent 2 can ping Agent 1 because it's expecting a response now + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + // Can now ping Agent 2 (it will be expecting a response too) + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EIM NAT connects to node2 behind EIM NAT via seed node', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('eim', 'eim', logger); + // Should be able to ping straight away using the seed node as a + // signaller + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EIM NAT cannot connect to node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('eim', 'edm', logger); + // Since one of the nodes uses EDM NAT we cannot punch through + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent1NodeId} to an address.`, + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent2NodeId} to an address.`, + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/utils.ts b/tests/nat/utils.ts new file mode 100644 index 000000000..4509ebacc --- /dev/null +++ b/tests/nat/utils.ts @@ -0,0 +1,1546 @@ +import type { ChildProcess } from 'child_process'; +import os from 'os'; +import fs from 'fs'; +import path from 'path'; +import process from 'process'; +import child_process from 'child_process'; +import readline from 'readline'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testBinUtils from '../bin/utils'; + +type NATType = 'eim' | 'edm' | 'dmz'; + +/** + * Veth end for Agent 1 + * Connects to Router 1 + */ +const AGENT1_VETH = 'agent1'; +/** + * Veth end for Agent 2 + * Connects to Router 2 + */ +const AGENT2_VETH = 'agent2'; +/** + * Internal veth end for Router 1 + * Connects to Agent 1 + */ +const ROUTER1_VETH_INT = 'router1-int'; +/** + * External veth end for Router 1 + * Connects to Router 2 + */ +const ROUTER1_VETH_EXT = 'router1-ext'; +/** + * Internal veth end for Router 2 + * Connects to Agent 2 + */ +const ROUTER2_VETH_INT = 'router2-int'; +/** + * External veth end for Router 2 + * Connects to Router 1 + */ +const ROUTER2_VETH_EXT = 'router2-ext'; +/** + * External veth end for Router 1 + * Connects to a seed node + */ +const ROUTER1_VETH_SEED = 'router1-seed'; +/** + * External veth end for Router 2 + * Connects to a seed node + */ +const ROUTER2_VETH_SEED = 'router2-seed'; +/** + * Veth end for a seed node + * Connects to Router 1 + */ +const SEED_VETH_ROUTER1 = 'seed-router1'; +/** + * Veth end for a seed node + * Connects to Router 2 + */ +const SEED_VETH_ROUTER2 = 'seed-router2'; + +/** + * Subnet for Agent 1 + */ +const AGENT1_HOST = '10.0.0.2'; +/** + * Subnet for Agent 2 + */ +const AGENT2_HOST = '10.0.0.2'; +/** + * Subnet for internal communication from Router 1 + * Forwards to Agent 1 + */ +const ROUTER1_HOST_INT = '10.0.0.1'; +/** + * Subnet for internal communication from Router 2 + * Forwards to Agent 2 + */ +const ROUTER2_HOST_INT = '10.0.0.1'; +/** + * Subnet for external communication from Router 1 + * Forwards to Router 2 + */ +const ROUTER1_HOST_EXT = '192.168.0.1'; +/** + * Subnet for external communication from Router 2 + * Forwards to Router 1 + */ +const ROUTER2_HOST_EXT = '192.168.0.2'; +/** + * Subnet for external communication from Router 1 + * Forwards to a seed node + */ +const ROUTER1_HOST_SEED = '192.168.0.1'; +/** + * Subnet for external communication from a seed node + */ +const SEED_HOST = '192.168.0.3'; +/** + * Subnet for external communication from Router 2 + * Forwards to a seed node + */ +const ROUTER2_HOST_SEED = '192.168.0.2'; + +/** + * Subnet mask + */ +const SUBNET_MASK = '/24'; + +/** + * Port on Agent 1 + */ +const AGENT1_PORT = '55551'; +/** + * Port on Agent 2 + */ +const AGENT2_PORT = '55552'; +/** + * Mapped port for DMZ + */ +const DMZ_PORT = '55555'; + +/** + * Formats the command to enter a namespace to run a process inside it + */ +const nsenter = (usrnsPid: number, netnsPid: number) => { + return [ + '--target', + usrnsPid.toString(), + '--user', + '--preserve-credentials', + 'nsenter', + '--target', + netnsPid.toString(), + '--net', + ]; +}; + +/** + * Create a user namespace from which network namespaces can be created without + * requiring sudo + */ +function createUserNamespace( + logger: Logger = new Logger(createUserNamespace.name), +): ChildProcess { + logger.info('unshare --user --map-root-user'); + const subprocess = child_process.spawn( + 'unshare', + ['--user', '--map-root-user'], + { + shell: true, + }, + ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; +} + +/** + * Create a network namespace inside a user namespace + */ +function createNetworkNamespace( + usrnsPid: number, + logger: Logger = new Logger(createNetworkNamespace.name), +): ChildProcess { + logger.info( + `nsenter --target ${usrnsPid.toString()} --user --preserve-credentials unshare --net`, + ); + const subprocess = child_process.spawn( + 'nsenter', + [ + '--target', + usrnsPid.toString(), + '--user', + '--preserve-credentials', + 'unshare', + '--net', + ], + { shell: true }, + ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; +} + +/** + * Set up four network namespaces to allow communication between two agents + * each behind a router + * Brings up loopback interfaces, creates and brings up a veth pair + * between each pair of adjacent namespaces, and adds default routing to allow + * cross-communication + */ +async function setupNetworkNamespaceInterfaces( + usrnsPid: number, + agent1NetnsPid: number, + router1NetnsPid: number, + router2NetnsPid: number, + agent2NetnsPid: number, + logger: Logger = new Logger(setupNetworkNamespaceInterfaces.name), +) { + let args: Array = []; + try { + // Bring up loopback + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Create veth pair to link the namespaces + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'add', + AGENT1_VETH, + 'type', + 'veth', + 'peer', + 'name', + ROUTER1_VETH_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'add', + ROUTER1_VETH_EXT, + 'type', + 'veth', + 'peer', + 'name', + ROUTER2_VETH_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'add', + ROUTER2_VETH_INT, + 'type', + 'veth', + 'peer', + 'name', + AGENT2_VETH, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Link up the ends to the correct namespaces + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + ROUTER1_VETH_INT, + 'netns', + router1NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + ROUTER2_VETH_EXT, + 'netns', + router2NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + AGENT2_VETH, + 'netns', + agent2NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Bring up each end + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + AGENT1_VETH, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + ROUTER1_VETH_INT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + ROUTER1_VETH_EXT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + ROUTER2_VETH_EXT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + ROUTER2_VETH_INT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'link', + 'set', + AGENT2_VETH, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Assign ip addresses to each end + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'addr', + 'add', + `${AGENT1_HOST}${SUBNET_MASK}`, + 'dev', + AGENT1_VETH, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER1_HOST_INT}${SUBNET_MASK}`, + 'dev', + ROUTER1_VETH_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER1_HOST_EXT}${SUBNET_MASK}`, + 'dev', + ROUTER1_VETH_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER2_HOST_EXT}${SUBNET_MASK}`, + 'dev', + ROUTER2_VETH_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER2_HOST_INT}${SUBNET_MASK}`, + 'dev', + ROUTER2_VETH_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'addr', + 'add', + `${AGENT2_HOST}${SUBNET_MASK}`, + 'dev', + AGENT2_VETH, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Add default routing + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER1_HOST_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER2_HOST_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER1_HOST_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER2_HOST_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Set up four network namespaces to allow communication between two agents + * each behind a router + * Brings up loopback interfaces, creates and brings up a veth pair + * between each pair of adjacent namespaces, and adds default routing to allow + * cross-communication + */ +async function setupSeedNamespaceInterfaces( + usrnsPid: number, + seedNetnsPid: number, + router1NetnsPid: number, + router2NetnsPid: number, + logger: Logger = new Logger(setupSeedNamespaceInterfaces.name), +) { + let args: Array = []; + try { + // Bring up loopback + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Create veth pairs to link the namespaces + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'add', + ROUTER1_VETH_SEED, + 'type', + 'veth', + 'peer', + 'name', + SEED_VETH_ROUTER1, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'add', + ROUTER2_VETH_SEED, + 'type', + 'veth', + 'peer', + 'name', + SEED_VETH_ROUTER2, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Move seed ends into seed network namespace + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + SEED_VETH_ROUTER1, + 'netns', + seedNetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + SEED_VETH_ROUTER2, + 'netns', + seedNetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Bring up each end + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + ROUTER1_VETH_SEED, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + SEED_VETH_ROUTER1, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + SEED_VETH_ROUTER2, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + ROUTER2_VETH_SEED, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Assign ip addresses to each end + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER1_HOST_SEED}${SUBNET_MASK}`, + 'dev', + ROUTER1_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'addr', + 'add', + `${SEED_HOST}${SUBNET_MASK}`, + 'dev', + SEED_VETH_ROUTER1, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'addr', + 'add', + `${SEED_HOST}${SUBNET_MASK}`, + 'dev', + SEED_VETH_ROUTER2, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER2_HOST_SEED}${SUBNET_MASK}`, + 'dev', + ROUTER2_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Add default routing + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'route', + 'add', + SEED_HOST, + 'dev', + ROUTER1_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'route', + 'add', + SEED_HOST, + 'dev', + ROUTER2_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'route', + 'add', + ROUTER1_HOST_SEED, + 'dev', + SEED_VETH_ROUTER1, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'route', + 'add', + ROUTER2_HOST_SEED, + 'dev', + SEED_VETH_ROUTER2, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Runs pk command through subprocess inside a network namespace + * This is used when a subprocess functionality needs to be used + * This is intended for terminating subprocesses + * Both stdout and stderr are the entire output including newlines + * @param env Augments env for command execution + * @param cwd Defaults to temporary directory + */ +async function pkExecNs( + usrnsPid: number, + netnsPid: number, + args: Array = [], + env: Record = {}, + cwd?: string, +): Promise<{ + exitCode: number; + stdout: string; + stderr: string; +}> { + cwd = + cwd ?? (await fs.promises.mkdtemp(path.join(os.tmpdir(), 'polykey-test-'))); + env = { + ...process.env, + ...env, + }; + // Recall that we attempt to connect to all specified seed nodes on agent start. + // Therefore, for testing purposes only, we default the seed nodes as empty + // (if not defined in the env) to ensure no attempted connections. A regular + // PolykeyAgent is expected to initially connect to the mainnet seed nodes + env['PK_SEED_NODES'] = env['PK_SEED_NODES'] ?? ''; + const tsConfigPath = path.resolve( + path.join(global.projectDir, 'tsconfig.json'), + ); + const tsConfigPathsRegisterPath = path.resolve( + path.join(global.projectDir, 'node_modules/tsconfig-paths/register'), + ); + const polykeyPath = path.resolve( + path.join(global.projectDir, 'src/bin/polykey.ts'), + ); + return new Promise((resolve, reject) => { + child_process.execFile( + 'nsenter', + [ + ...nsenter(usrnsPid, netnsPid), + 'ts-node', + '--project', + tsConfigPath, + '--require', + tsConfigPathsRegisterPath, + '--compiler', + 'typescript-cached-transpile', + '--transpile-only', + polykeyPath, + ...args, + ], + { + env, + cwd, + windowsHide: true, + }, + (error, stdout, stderr) => { + if (error != null && error.code === undefined) { + // This can only happen when the command is killed + return reject(error); + } else { + // Success and Unsuccessful exits are valid here + return resolve({ + exitCode: error && error.code != null ? error.code : 0, + stdout, + stderr, + }); + } + }, + ); + }); +} + +/** + * Launch pk command through subprocess inside a network namespace + * This is used when a subprocess functionality needs to be used + * This is intended for non-terminating subprocesses + * @param env Augments env for command execution + * @param cwd Defaults to temporary directory + */ +async function pkSpawnNs( + usrnsPid: number, + netnsPid: number, + args: Array = [], + env: Record = {}, + cwd?: string, + logger: Logger = new Logger(pkSpawnNs.name), +): Promise { + cwd = + cwd ?? (await fs.promises.mkdtemp(path.join(os.tmpdir(), 'polykey-test-'))); + env = { + ...process.env, + ...env, + }; + // Recall that we attempt to connect to all specified seed nodes on agent start. + // Therefore, for testing purposes only, we default the seed nodes as empty + // (if not defined in the env) to ensure no attempted connections. A regular + // PolykeyAgent is expected to initially connect to the mainnet seed nodes + env['PK_SEED_NODES'] = env['PK_SEED_NODES'] ?? ''; + const tsConfigPath = path.resolve( + path.join(global.projectDir, 'tsconfig.json'), + ); + const tsConfigPathsRegisterPath = path.resolve( + path.join(global.projectDir, 'node_modules/tsconfig-paths/register'), + ); + const polykeyPath = path.resolve( + path.join(global.projectDir, 'src/bin/polykey.ts'), + ); + const subprocess = child_process.spawn( + 'nsenter', + [ + ...nsenter(usrnsPid, netnsPid), + 'ts-node', + '--project', + tsConfigPath, + '--require', + tsConfigPathsRegisterPath, + '--compiler', + 'typescript-cached-transpile', + '--transpile-only', + polykeyPath, + ...args, + ], + { + env, + cwd, + stdio: ['pipe', 'pipe', 'pipe'], + windowsHide: true, + shell: true, + }, + ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; +} + +/** + * Setup routing between an agent and router with no NAT rules + */ +async function setupDMZ( + usrnsPid: number, + routerNsPid: number, + agentIp: string, + agentPort: string, + routerExt: string, + routerExtIp: string, + logger: Logger = new Logger(setupDMZ.name), +) { + const postroutingCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${SUBNET_MASK}`, + '--out-interface', + routerExt, + '--jump', + 'SNAT', + '--to-source', + `${routerExtIp}:${DMZ_PORT}`, + ]; + const preroutingCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'PREROUTING', + '--protocol', + 'udp', + '--destination-port', + DMZ_PORT, + '--in-interface', + routerExt, + '--jump', + 'DNAT', + '--to-destination', + `${agentIp}:${agentPort}`, + ]; + try { + logger.info(['nsenter', ...postroutingCommand].join(' ')); + await testBinUtils.exec('nsenter', postroutingCommand); + logger.info(['nsenter', ...preroutingCommand].join(' ')); + await testBinUtils.exec('nsenter', preroutingCommand); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Setup Port-Restricted Cone NAT for a namespace (on the router namespace) + */ +async function setupNATEndpointIndependentMapping( + usrnsPid: number, + routerNsPid: number, + agentIp: string, + routerExt: string, + routerInt: string, + logger: Logger = new Logger(setupNATEndpointIndependentMapping.name), +) { + const natCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${SUBNET_MASK}`, + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', + ]; + const acceptLocalCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'filter', + '--append', + 'INPUT', + '--in-interface', + routerInt, + '--jump', + 'ACCEPT', + ]; + const acceptEstablishedCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'filter', + '--append', + 'INPUT', + '--match', + 'conntrack', + '--ctstate', + 'RELATED,ESTABLISHED', + '--jump', + 'ACCEPT', + ]; + const dropCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'filter', + '--append', + 'INPUT', + '--jump', + 'DROP', + ]; + try { + logger.info(['nsenter', ...acceptLocalCommand].join(' ')); + await testBinUtils.exec('nsenter', acceptLocalCommand); + logger.info(['nsenter', ...acceptEstablishedCommand].join(' ')); + await testBinUtils.exec('nsenter', acceptEstablishedCommand); + logger.info(['nsenter', ...dropCommand].join(' ')); + await testBinUtils.exec('nsenter', dropCommand); + logger.info(['nsenter', ...natCommand].join(' ')); + await testBinUtils.exec('nsenter', natCommand); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Setup Symmetric NAT for a namespace (on the router namespace) + */ +async function setupNATEndpointDependentMapping( + usrnsPid: number, + routerNsPid: number, + routerExt: string, + logger: Logger = new Logger(setupNATEndpointDependentMapping.name), +) { + const command = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', + `--random`, + ]; + try { + logger.info(['nsenter', ...command].join(' ')); + await testBinUtils.exec('nsenter', command); + } catch (e) { + logger.error(e.message); + } +} + +async function setupNATWithSeedNode( + agent1NAT: NATType, + agent2NAT: NATType, + logger: Logger = new Logger(setupNAT.name, LogLevel.WARN, [ + new StreamHandler(), + ]), +) { + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const password = 'password'; + // Create a user namespace containing five network namespaces + // Two agents, two routers, one seed node + const usrns = createUserNamespace(logger); + const seedNetns = createNetworkNamespace(usrns.pid!, logger); + const agent1Netns = createNetworkNamespace(usrns.pid!, logger); + const agent2Netns = createNetworkNamespace(usrns.pid!, logger); + const router1Netns = createNetworkNamespace(usrns.pid!, logger); + const router2Netns = createNetworkNamespace(usrns.pid!, logger); + // Apply appropriate NAT rules + switch (agent1NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + AGENT1_PORT, + ROUTER1_VETH_EXT, + ROUTER1_HOST_EXT, + logger, + ); + await setupDMZ( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + AGENT1_PORT, + ROUTER1_VETH_SEED, + ROUTER1_HOST_SEED, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + ROUTER1_VETH_EXT, + ROUTER1_VETH_INT, + logger, + ); + await setupNATEndpointIndependentMapping( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + ROUTER1_VETH_SEED, + ROUTER1_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router1Netns.pid!, + ROUTER1_VETH_EXT, + logger, + ); + await setupNATEndpointDependentMapping( + usrns.pid!, + router1Netns.pid!, + ROUTER1_VETH_SEED, + logger, + ); + break; + } + } + switch (agent2NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + AGENT2_PORT, + ROUTER2_VETH_EXT, + ROUTER2_HOST_EXT, + logger, + ); + await setupDMZ( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + AGENT2_PORT, + ROUTER2_VETH_SEED, + ROUTER2_HOST_SEED, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + ROUTER2_VETH_EXT, + ROUTER2_VETH_INT, + logger, + ); + await setupNATEndpointIndependentMapping( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + ROUTER2_VETH_SEED, + ROUTER2_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router2Netns.pid!, + ROUTER2_VETH_EXT, + logger, + ); + await setupNATEndpointDependentMapping( + usrns.pid!, + router2Netns.pid!, + ROUTER2_VETH_SEED, + logger, + ); + break; + } + } + await setupNetworkNamespaceInterfaces( + usrns.pid!, + agent1Netns.pid!, + router1Netns.pid!, + router2Netns.pid!, + agent2Netns.pid!, + logger, + ); + await setupSeedNamespaceInterfaces( + usrns.pid!, + seedNetns.pid!, + router1Netns.pid!, + router2Netns.pid!, + logger, + ); + const seedNode = await pkSpawnNs( + usrns.pid!, + seedNetns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'seed'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + '0.0.0.0', + '--connection-timeout', + '1000', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('seed'), + ); + const rlOutSeed = readline.createInterface(seedNode.stdout!); + const stdoutSeed = await new Promise((resolve, reject) => { + rlOutSeed.once('line', resolve); + rlOutSeed.once('close', reject); + }); + const nodeIdSeed = JSON.parse(stdoutSeed).nodeId; + const proxyPortSeed = JSON.parse(stdoutSeed).proxyPort; + const agent1 = await pkSpawnNs( + usrns.pid!, + agent1Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent1'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT1_HOST}`, + '--proxy-port', + `${AGENT1_PORT}`, + '--workers', + '0', + '--connection-timeout', + '1000', + '--seed-nodes', + `${nodeIdSeed}@${SEED_HOST}:${proxyPortSeed}`, + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent1'), + ); + const rlOutNode1 = readline.createInterface(agent1.stdout!); + const stdoutNode1 = await new Promise((resolve, reject) => { + rlOutNode1.once('line', resolve); + rlOutNode1.once('close', reject); + }); + const nodeId1 = JSON.parse(stdoutNode1).nodeId; + const agent2 = await pkSpawnNs( + usrns.pid!, + agent2Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent2'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT2_HOST}`, + '--proxy-port', + `${AGENT2_PORT}`, + '--workers', + '0', + '--connection-timeout', + '1000', + '--seed-nodes', + `${nodeIdSeed}@${SEED_HOST}:${proxyPortSeed}`, + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent2'), + ); + const rlOutNode2 = readline.createInterface(agent2.stdout!); + const stdoutNode2 = await new Promise((resolve, reject) => { + rlOutNode2.once('line', resolve); + rlOutNode2.once('close', reject); + }); + const nodeId2 = JSON.parse(stdoutNode2).nodeId; + return { + userPid: usrns.pid, + agent1Pid: agent1Netns.pid, + agent2Pid: agent2Netns.pid, + password, + dataDir, + agent1NodePath: path.join(dataDir, 'agent1'), + agent2NodePath: path.join(dataDir, 'agent2'), + agent1NodeId: nodeId1, + agent2NodeId: nodeId2, + tearDownNAT: async () => { + agent2.kill('SIGTERM'); + await testBinUtils.processExit(agent2); + agent1.kill('SIGTERM'); + await testBinUtils.processExit(agent1); + seedNode.kill('SIGTERM'); + await testBinUtils.processExit(seedNode); + router2Netns.kill('SIGTERM'); + await testBinUtils.processExit(router2Netns); + router1Netns.kill('SIGTERM'); + await testBinUtils.processExit(router1Netns); + agent2Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent2Netns); + agent1Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent1Netns); + seedNetns.kill('SIGTERM'); + await testBinUtils.processExit(seedNetns); + usrns.kill('SIGTERM'); + await testBinUtils.processExit(usrns); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }, + }; +} + +async function setupNAT( + agent1NAT: NATType, + agent2NAT: NATType, + logger: Logger = new Logger(setupNAT.name, LogLevel.WARN, [ + new StreamHandler(), + ]), +) { + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const password = 'password'; + // Create a user namespace containing four network namespaces + // Two agents and two routers + const usrns = createUserNamespace(logger); + const agent1Netns = createNetworkNamespace(usrns.pid!, logger); + const agent2Netns = createNetworkNamespace(usrns.pid!, logger); + const router1Netns = createNetworkNamespace(usrns.pid!, logger); + const router2Netns = createNetworkNamespace(usrns.pid!, logger); + // Apply appropriate NAT rules + switch (agent1NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + AGENT1_PORT, + ROUTER1_VETH_EXT, + ROUTER1_HOST_EXT, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + ROUTER1_VETH_EXT, + ROUTER1_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router1Netns.pid!, + ROUTER1_VETH_EXT, + logger, + ); + break; + } + } + switch (agent2NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + AGENT2_PORT, + ROUTER2_VETH_EXT, + ROUTER2_HOST_EXT, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + ROUTER2_VETH_EXT, + ROUTER2_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router2Netns.pid!, + ROUTER2_VETH_EXT, + logger, + ); + break; + } + } + await setupNetworkNamespaceInterfaces( + usrns.pid!, + agent1Netns.pid!, + router1Netns.pid!, + router2Netns.pid!, + agent2Netns.pid!, + logger, + ); + const agent1 = await pkSpawnNs( + usrns.pid!, + agent1Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent1'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT1_HOST}`, + '--proxy-port', + `${AGENT1_PORT}`, + '--connection-timeout', + '1000', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent1'), + ); + const rlOutNode1 = readline.createInterface(agent1.stdout!); + const stdoutNode1 = await new Promise((resolve, reject) => { + rlOutNode1.once('line', resolve); + rlOutNode1.once('close', reject); + }); + const nodeId1 = JSON.parse(stdoutNode1).nodeId; + const agent2 = await pkSpawnNs( + usrns.pid!, + agent2Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent2'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT2_HOST}`, + '--proxy-port', + `${AGENT2_PORT}`, + '--connection-timeout', + '1000', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent2'), + ); + const rlOutNode2 = readline.createInterface(agent2.stdout!); + const stdoutNode2 = await new Promise((resolve, reject) => { + rlOutNode2.once('line', resolve); + rlOutNode2.once('close', reject); + }); + const nodeId2 = JSON.parse(stdoutNode2).nodeId; + return { + userPid: usrns.pid, + agent1Pid: agent1Netns.pid, + agent2Pid: agent2Netns.pid, + password, + dataDir, + agent1NodePath: path.join(dataDir, 'agent1'), + agent2NodePath: path.join(dataDir, 'agent2'), + agent1NodeId: nodeId1, + agent1Host: ROUTER1_HOST_EXT, + agent1ProxyPort: agent1NAT === 'dmz' ? DMZ_PORT : AGENT1_PORT, + agent2NodeId: nodeId2, + agent2Host: ROUTER2_HOST_EXT, + agent2ProxyPort: agent2NAT === 'dmz' ? DMZ_PORT : AGENT2_PORT, + tearDownNAT: async () => { + agent2.kill('SIGTERM'); + await testBinUtils.processExit(agent2); + agent1.kill('SIGTERM'); + await testBinUtils.processExit(agent1); + router2Netns.kill('SIGTERM'); + await testBinUtils.processExit(router2Netns); + router1Netns.kill('SIGTERM'); + await testBinUtils.processExit(router1Netns); + agent2Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent2Netns); + agent1Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent1Netns); + usrns.kill('SIGTERM'); + await testBinUtils.processExit(usrns); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }, + }; +} + +export { + createUserNamespace, + createNetworkNamespace, + setupNetworkNamespaceInterfaces, + pkExecNs, + pkSpawnNs, + setupNAT, + setupNATWithSeedNode, +}; diff --git a/tests/nodes/NodeManager.test.ts b/tests/nodes/NodeManager.test.ts index 66bd40999..d32c869d9 100644 --- a/tests/nodes/NodeManager.test.ts +++ b/tests/nodes/NodeManager.test.ts @@ -1086,4 +1086,63 @@ describe(`${NodeManager.name} test`, () => { await queue.stop(); } }); + test('should pause, resume and stop queue while paused', async () => { + const refreshBucketTimeout = 1000000; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + refreshBucketTimerDefault: refreshBucketTimeout, + logger, + }); + const mockRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + try { + logger.setLevel(LogLevel.DEBUG); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + mockRefreshBucket.mockImplementation( + async (bucket, options: { signal?: AbortSignal } = {}) => { + const { signal } = { ...options }; + const prom = promise(); + const timer = setTimeout(prom.resolveP, 10); + signal?.addEventListener('abort', () => { + clearTimeout(timer); + prom.rejectP(new nodesErrors.ErrorNodeAborted()); + }); + await prom.p; + }, + ); + nodeManager.refreshBucketQueueAdd(1); + nodeManager.refreshBucketQueueAdd(2); + nodeManager.refreshBucketQueueAdd(3); + nodeManager.refreshBucketQueueAdd(4); + nodeManager.refreshBucketQueueAdd(5); + + // Can pause and resume + nodeManager.refreshBucketQueuePause(); + nodeManager.refreshBucketQueueAdd(6); + nodeManager.refreshBucketQueueAdd(7); + nodeManager.refreshBucketQueueResume(); + await nodeManager.refreshBucketQueueDrained(); + + // Can pause and stop + nodeManager.refreshBucketQueuePause(); + nodeManager.refreshBucketQueueAdd(8); + nodeManager.refreshBucketQueueAdd(9); + nodeManager.refreshBucketQueueAdd(10); + await nodeManager.stop(); + } finally { + mockRefreshBucket.mockRestore(); + await nodeManager.stop(); + await queue.stop(); + } + }); }); diff --git a/tests/utils.ts b/tests/utils.ts index 3ac9a7499..e607faff1 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -198,9 +198,27 @@ const expectRemoteError = async ( } }; +function describeIf(condition, name, f) { + if (condition) { + describe(name, f); + } else { + describe.skip(name, f); + } +} + +function testIf(condition, name, f, timeout?) { + if (condition) { + test(name, f, timeout); + } else { + test.skip(name, f, timeout); + } +} + export { setupGlobalKeypair, generateRandomNodeId, expectRemoteError, setupGlobalAgent, + describeIf, + testIf, };