diff --git a/package-lock.json b/package-lock.json index 9d770f447a..371b221c13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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.4.0", diff --git a/package.json b/package.json index 292dff2e19..344c65e8c5 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,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.4.0", diff --git a/shell.nix b/shell.nix index 37475b20e5..961a13c8ad 100644 --- a/shell.nix +++ b/shell.nix @@ -10,7 +10,6 @@ in utils.node2nix grpc-tools grpcurl - iptables-legacy ]; PKG_CACHE_PATH = utils.pkgCachePath; PKG_IGNORE_TAG = 1; diff --git a/tests/bin/utils.ts b/tests/bin/utils.ts index b0acefed28..c6cc42c541 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/endpointDependentNAT.test.ts b/tests/nat/endpointDependentNAT.test.ts index 113c0004a3..4d8cfcec75 100644 --- a/tests/nat/endpointDependentNAT.test.ts +++ b/tests/nat/endpointDependentNAT.test.ts @@ -1,268 +1,279 @@ 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'; -describe('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], - { - 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, - agent2Host, - agent2ProxyPort, - tearDownNAT, - } = await testNatUtils.setupNAT('dmz', 'edmSimple', logger); - await testNatUtils.pkExecNs( - userPid!, - agent2Pid!, - ['nodes', 'add', agent1NodeId, agent1Host, agent1ProxyPort], - { - PK_NODE_PATH: agent2NodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - await testNatUtils.pkExecNs( - userPid!, - agent1Pid!, - ['nodes', 'add', agent2NodeId, agent2Host, agent2ProxyPort], - { - 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 EDM NAT cannot connect to Node2 behind EDM NAT', - async () => { - const { - userPid, - agent1Pid, - agent2Pid, - password, - dataDir, - agent1NodePath, - agent2NodePath, - agent1NodeId, - agent2NodeId, - tearDownNAT, - } = await testNatUtils.setupNATWithSeedNode( - 'edmSimple', - 'edmSimple', - logger, +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-'), ); - // 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: 'No response received', - }); - // Node 1 -> Node 2 ping should also fail - ({ 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', - }); - 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('edmSimple', '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', '-vv'], - { - PK_NODE_PATH: agent2NodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(1); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: 'No response received', - }); - ({ exitCode, stdout } = await testNatUtils.pkExecNs( - userPid!, - agent1Pid!, - ['nodes', 'ping', agent2NodeId, '--format', 'json', '-vv'], - { - PK_NODE_PATH: agent1NodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(1); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: 'No response received', + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, }); - await tearDownNAT(); - }, - global.defaultTimeout * 2, - ); -}); + }); + 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], + { + 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, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'edmSimple', logger); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'add', agent1NodeId, agent1Host, agent1ProxyPort], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'add', agent2NodeId, agent2Host, agent2ProxyPort], + { + 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 EDM NAT cannot connect to Node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode( + 'edmSimple', + 'edmSimple', + 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: 'No response received', + }); + // Node 1 -> Node 2 ping should also fail + ({ 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', + }); + 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('edmSimple', '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', '-vv'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json', '-vv'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/endpointIndependentNAT.test.ts b/tests/nat/endpointIndependentNAT.test.ts index 357db3d17b..ff44117ffa 100644 --- a/tests/nat/endpointIndependentNAT.test.ts +++ b/tests/nat/endpointIndependentNAT.test.ts @@ -1,372 +1,383 @@ 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'; -describe('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], - { - PK_NODE_PATH: agent1NodePath, - PK_PASSWORD: password, - }, - dataDir, +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-'), ); - 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], - { - PK_NODE_PATH: agent2NodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - await testNatUtils.pkExecNs( - userPid!, - agent1Pid!, - ['nodes', 'add', agent2NodeId, agent2Host, agent2ProxyPort], - { - 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], - { - PK_NODE_PATH: agent2NodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - await testNatUtils.pkExecNs( - userPid!, - agent1Pid!, - ['nodes', 'add', agent2NodeId, agent2Host, agent2ProxyPort], - { - 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); - // Contact details can be retrieved from the seed node so don't need to - // add manually - // 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 now because it's expecting a response - ({ 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 also 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 cannot connect to Node2 behind EDM NAT', - async () => { - const { - userPid, - agent1Pid, - agent2Pid, - password, - dataDir, - agent1NodePath, - agent2NodePath, - agent1NodeId, - agent2NodeId, - tearDownNAT, - } = await testNatUtils.setupNATWithSeedNode('eim', 'edmSimple', 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', '-vv'], - { - PK_NODE_PATH: agent2NodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(1); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: 'No response received', - }); - ({ exitCode, stdout } = await testNatUtils.pkExecNs( - userPid!, - agent1Pid!, - ['nodes', 'ping', agent2NodeId, '--format', 'json', '-vv'], - { - PK_NODE_PATH: agent1NodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(1); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: 'No response received', + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, }); - await tearDownNAT(); - }, - global.defaultTimeout * 2, - ); -}); + }); + 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], + { + 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], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'add', agent2NodeId, agent2Host, agent2ProxyPort], + { + 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], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'add', agent2NodeId, agent2Host, agent2ProxyPort], + { + 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); + // Contact details can be retrieved from the seed node so don't need to + // add manually + // 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 now because it's expecting a response + ({ 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 also 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 cannot connect to Node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('eim', 'edmSimple', 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', '-vv'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json', '-vv'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/noNAT.test.ts b/tests/nat/noNAT.test.ts index bd8a85623e..737f36c080 100644 --- a/tests/nat/noNAT.test.ts +++ b/tests/nat/noNAT.test.ts @@ -2,238 +2,249 @@ 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'; -describe('no NAT', () => { - const logger = new Logger('no 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( - 'can create an agent in a namespace', - async () => { - const password = 'abc123'; - const usrns = testNatUtils.createUserNamespace(); - const netns = testNatUtils.createNetworkNamespace(usrns.pid!); - 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'), +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'no NAT', + () => { + const logger = new Logger('no NAT test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), ); - 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), + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, }); - 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( + }); + 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, - '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); - // 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], - { - PK_NODE_PATH: agent1NodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - await testNatUtils.pkExecNs( - userPid!, - agent2Pid!, - ['nodes', 'add', agent1NodeId, agent1Host, agent1ProxyPort], - { - PK_NODE_PATH: agent2NodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - let exitCode, stdout; - ({ exitCode, stdout } = await testNatUtils.pkExecNs( - userPid!, - agent1Pid!, - ['nodes', 'ping', agent2NodeId, '--format', 'json', '--verbose'], - { - 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', '--verbose'], - { - 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); - // 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', '--verbose'], - { - 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', '--verbose'], - { - 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, - ); -}); + 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); + // 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], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'add', agent1NodeId, agent1Host, agent1ProxyPort], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json', '--verbose'], + { + 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', '--verbose'], + { + 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); + // 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', '--verbose'], + { + 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', '--verbose'], + { + 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/utils.ts b/tests/nat/utils.ts index 95125adf8c..9b83fdab3a 100644 --- a/tests/nat/utils.ts +++ b/tests/nat/utils.ts @@ -43,26 +43,52 @@ const mappedPort = '55555'; * Formats the command to enter a namespace to run a process inside it */ const nsenter = (usrnsPid: number, netnsPid: number) => { - return `nsenter --target ${usrnsPid} --user --preserve-credentials `.concat( - `nsenter --target ${netnsPid} --net `, - ); + 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(): ChildProcess { - return child_process.spawn('unshare', ['--user', '--map-root-user'], { - shell: true, +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): ChildProcess { - return child_process.spawn( +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', @@ -74,6 +100,12 @@ function createNetworkNamespace(usrnsPid: number): ChildProcess { ], { shell: true }, ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; } /** @@ -83,109 +115,313 @@ function createNetworkNamespace(usrnsPid: number): ChildProcess { * between each pair of adjacent namespaces, and adds default routing to allow * cross-communication */ -function setupNetworkNamespaceInterfaces( +async function setupNetworkNamespaceInterfaces( usrnsPid: number, agent1NetnsPid: number, router1NetnsPid: number, router2NetnsPid: number, agent2NetnsPid: number, + logger: Logger = new Logger(setupNetworkNamespaceInterfaces.name), ) { - // Bring up loopback - child_process.exec(nsenter(usrnsPid, agent1NetnsPid) + `ip link set lo up`); - child_process.exec(nsenter(usrnsPid, router1NetnsPid) + `ip link set lo up`); - child_process.exec(nsenter(usrnsPid, router2NetnsPid) + `ip link set lo up`); - child_process.exec(nsenter(usrnsPid, agent2NetnsPid) + `ip link set lo up`); - // Create veth pair to link the namespaces - child_process.exec( - nsenter(usrnsPid, agent1NetnsPid) + - `ip link add ${agent1Host} type veth peer name ${agent1RouterHostInt}`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip link add ${agent1RouterHostExt} type veth peer name ${agent2RouterHostExt}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip link add ${agent2RouterHostInt} type veth peer name ${agent2Host}`, - ); - // Link up the ends to the correct namespaces - child_process.exec( - nsenter(usrnsPid, agent1NetnsPid) + - `ip link set dev ${agent1RouterHostInt} netns ${router1NetnsPid}`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip link set dev ${agent2RouterHostExt} netns ${router2NetnsPid}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip link set dev ${agent2Host} netns ${agent2NetnsPid}`, - ); - // Bring up each end - child_process.exec( - nsenter(usrnsPid, agent1NetnsPid) + `ip link set ${agent1Host} up`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip link set ${agent1RouterHostInt} up`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip link set ${agent1RouterHostExt} up`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip link set ${agent2RouterHostExt} up`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip link set ${agent2RouterHostInt} up`, - ); - child_process.exec( - nsenter(usrnsPid, agent2NetnsPid) + `ip link set ${agent2Host} up`, - ); - // Assign ip addresses to each end - child_process.exec( - nsenter(usrnsPid, agent1NetnsPid) + - `ip addr add ${agent1HostIp}${subnetMask} dev ${agent1Host}`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip addr add ${agent1RouterHostIntIp}${subnetMask} dev ${agent1RouterHostInt}`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip addr add ${agent1RouterHostExtIp}${subnetMask} dev ${agent1RouterHostExt}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip addr add ${agent2RouterHostExtIp}${subnetMask} dev ${agent2RouterHostExt}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip addr add ${agent2RouterHostIntIp}${subnetMask} dev ${agent2RouterHostInt}`, - ); - child_process.exec( - nsenter(usrnsPid, agent2NetnsPid) + - `ip addr add ${agent2HostIp}${subnetMask} dev ${agent2Host}`, - ); - // Add default routing - child_process.exec( - nsenter(usrnsPid, agent1NetnsPid) + - `ip route add default via ${agent1RouterHostIntIp}`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip route add default via ${agent2RouterHostExtIp}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip route add default via ${agent1RouterHostExtIp}`, - ); - child_process.exec( - nsenter(usrnsPid, agent2NetnsPid) + - `ip route add default via ${agent2RouterHostIntIp}`, - ); + 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', + agent1Host, + 'type', + 'veth', + 'peer', + 'name', + agent1RouterHostInt, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'add', + agent1RouterHostExt, + 'type', + 'veth', + 'peer', + 'name', + agent2RouterHostExt, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'add', + agent2RouterHostInt, + 'type', + 'veth', + 'peer', + 'name', + agent2Host, + ]; + 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', + agent1RouterHostInt, + 'netns', + router1NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + agent2RouterHostExt, + 'netns', + router2NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + agent2Host, + 'netns', + agent2NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Bring up each end + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + agent1Host, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + agent1RouterHostInt, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + agent1RouterHostExt, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + agent2RouterHostExt, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + agent2RouterHostInt, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'link', + 'set', + agent2Host, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Assign ip addresses to each end + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'addr', + 'add', + `${agent1HostIp}${subnetMask}`, + 'dev', + agent1Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${agent1RouterHostIntIp}${subnetMask}`, + 'dev', + agent1RouterHostInt, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${agent1RouterHostExtIp}${subnetMask}`, + 'dev', + agent1RouterHostExt, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${agent2RouterHostExtIp}${subnetMask}`, + 'dev', + agent2RouterHostExt, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${agent2RouterHostIntIp}${subnetMask}`, + 'dev', + agent2RouterHostInt, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'addr', + 'add', + `${agent2HostIp}${subnetMask}`, + 'dev', + agent2Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Add default routing + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + agent1RouterHostIntIp, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + agent2RouterHostExtIp, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + agent1RouterHostExtIp, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + agent2RouterHostIntIp, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + } catch (e) { + logger.error(e.message); + } } /** @@ -195,79 +431,214 @@ function setupNetworkNamespaceInterfaces( * between each pair of adjacent namespaces, and adds default routing to allow * cross-communication */ -function setupSeedNamespaceInterfaces( +async function setupSeedNamespaceInterfaces( usrnsPid: number, seedNetnsPid: number, router1NetnsPid: number, router2NetnsPid: number, + logger: Logger = new Logger(setupSeedNamespaceInterfaces.name), ) { - // Bring up loopback - child_process.exec(nsenter(usrnsPid, seedNetnsPid) + `ip link set lo up`); - // Create veth pairs to link the namespaces - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip link add ${router1SeedHost} type veth peer name ${seedRouter1Host}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip link add ${router2SeedHost} type veth peer name ${seedRouter2Host}`, - ); - // Move seed ends into seed network namespace - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip link set dev ${seedRouter1Host} netns ${seedNetnsPid}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip link set dev ${seedRouter2Host} netns ${seedNetnsPid}`, - ); - // Bring up each end - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + `ip link set ${router1SeedHost} up`, - ); - child_process.exec( - nsenter(usrnsPid, seedNetnsPid) + `ip link set ${seedRouter1Host} up`, - ); - child_process.exec( - nsenter(usrnsPid, seedNetnsPid) + `ip link set ${seedRouter2Host} up`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + `ip link set ${router2SeedHost} up`, - ); - // Assign ip addresses to each end - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip addr add ${router1SeedHostIp}${subnetMask} dev ${router1SeedHost}`, - ); - child_process.exec( - nsenter(usrnsPid, seedNetnsPid) + - `ip addr add ${seedHostIp}${subnetMask} dev ${seedRouter1Host}`, - ); - child_process.exec( - nsenter(usrnsPid, seedNetnsPid) + - `ip addr add ${seedHostIp}${subnetMask} dev ${seedRouter2Host}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip addr add ${router2SeedHostIp}${subnetMask} dev ${router2SeedHost}`, - ); - child_process.exec( - nsenter(usrnsPid, router1NetnsPid) + - `ip route add ${seedHostIp} dev ${router1SeedHost}`, - ); - child_process.exec( - nsenter(usrnsPid, router2NetnsPid) + - `ip route add ${seedHostIp} dev ${router2SeedHost}`, - ); - // Add default routing - child_process.exec( - nsenter(usrnsPid, seedNetnsPid) + - `ip route add ${router1SeedHostIp} dev ${seedRouter1Host}`, - ); - child_process.exec( - nsenter(usrnsPid, seedNetnsPid) + - `ip route add ${router2SeedHostIp} dev ${seedRouter2Host}`, - ); + 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', + router1SeedHost, + 'type', + 'veth', + 'peer', + 'name', + seedRouter1Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'add', + router2SeedHost, + 'type', + 'veth', + 'peer', + 'name', + seedRouter2Host, + ]; + 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', + seedRouter1Host, + 'netns', + seedNetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + seedRouter2Host, + 'netns', + seedNetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Bring up each end + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + router1SeedHost, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + seedRouter1Host, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + seedRouter2Host, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + router2SeedHost, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Assign ip addresses to each end + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${router1SeedHostIp}${subnetMask}`, + 'dev', + router1SeedHost, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'addr', + 'add', + `${seedHostIp}${subnetMask}`, + 'dev', + seedRouter1Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'addr', + 'add', + `${seedHostIp}${subnetMask}`, + 'dev', + seedRouter2Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${router2SeedHostIp}${subnetMask}`, + 'dev', + router2SeedHost, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Add default routing + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'route', + 'add', + seedHostIp, + 'dev', + router1SeedHost, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'route', + 'add', + seedHostIp, + 'dev', + router2SeedHost, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'route', + 'add', + router1SeedHostIp, + 'dev', + seedRouter1Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'route', + 'add', + router2SeedHostIp, + 'dev', + seedRouter2Host, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + } catch (e) { + logger.error(e.message); + } } /** @@ -313,14 +684,7 @@ async function pkExecNs( child_process.execFile( 'nsenter', [ - '--target', - usrnsPid.toString(), - '--user', - '--preserve-credentials', - 'nsenter', - '--target', - netnsPid.toString(), - '--net', + ...nsenter(usrnsPid, netnsPid), 'ts-node', '--project', tsConfigPath, @@ -392,14 +756,7 @@ async function pkSpawnNs( const subprocess = child_process.spawn( 'nsenter', [ - '--target', - usrnsPid.toString(), - '--user', - '--preserve-credentials', - 'nsenter', - '--target', - netnsPid.toString(), - '--net', + ...nsenter(usrnsPid, netnsPid), 'ts-node', '--project', tsConfigPath, @@ -430,159 +787,283 @@ async function pkSpawnNs( /** * Setup routing between an agent and router with no NAT rules */ -function setupDMZ( +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).concat( - 'iptables --table nat ', - '--append POSTROUTING ', - '--protocol udp ', - `--source ${agentIp}${subnetMask} `, - `--out-interface ${routerExt} `, - '--jump SNAT ', - `--to-source ${routerExtIp}:${mappedPort}`, - ); - const preroutingCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table nat ', - '--append PREROUTING ', - '--protocol udp ', - `--destination-port ${mappedPort} `, - `--in-interface ${routerExt} `, - '--jump DNAT ', - `--to-destination ${agentIp}:${agentPort}`, - ); - child_process.exec(postroutingCommand); - child_process.exec(preroutingCommand); + const postroutingCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${subnetMask}`, + '--out-interface', + routerExt, + '--jump', + 'SNAT', + '--to-source', + `${routerExtIp}:${mappedPort}`, + ]; + const preroutingCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'PREROUTING', + '--protocol', + 'udp', + '--destination-port', + mappedPort, + '--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) */ -function setupNATEndpointIndependentMapping( +async function setupNATEndpointIndependentMapping( usrnsPid: number, routerNsPid: number, agentIp: string, routerExt: string, routerInt: string, + logger: Logger = new Logger(setupNATEndpointIndependentMapping.name), ) { - const natCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table nat ', - '--append POSTROUTING ', - '--protocol udp ', - `--source ${agentIp}${subnetMask} `, - `--out-interface ${routerExt} `, - '--jump MASQUERADE', - ); - const acceptLocalCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table filter ', - '--append INPUT ', - `--in-interface ${routerInt} `, - '--jump ACCEPT', - ); - const acceptEstablishedCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table filter ', - '--append INPUT ', - `--match conntrack `, - '--cstate RELATED,ESTABLISHED ', - '--jump ACCEPT', - ); - const dropCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table filter ', - '--append INPUT ', - '--jump DROP', - ); - child_process.exec(acceptLocalCommand); - child_process.exec(acceptEstablishedCommand); - child_process.exec(dropCommand); - child_process.exec(natCommand); + const natCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${subnetMask}`, + '--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) */ -function setupNATEndpointDependentMapping( +async function setupNATEndpointDependentMapping( usrnsPid: number, routerNsPid: number, routerExt: string, + logger: Logger = new Logger(setupNATEndpointDependentMapping.name), ) { - const command = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table nat ', - '--append POSTROUTING ', - '--protocol udp ', - `--out-interface ${routerExt} `, - '--jump MASQUERADE ', + const command = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', `--random`, - ); - child_process.exec(command); + ]; + try { + logger.info(['nsenter', ...command].join(' ')); + await testBinUtils.exec('nsenter', command); + } catch (e) { + logger.error(e.message); + } } /** * Setup Port-Restricted Cone NAT for a namespace (on the router namespace) */ -function setupNATSimplifiedEDMAgent( +async function setupNATSimplifiedEDMAgent( usrnsPid: number, routerNsPid: number, agentIp: string, routerExt: string, routerInt: string, + logger: Logger = new Logger(setupNATSimplifiedEDMAgent.name), ) { - const natCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table nat ', - '--append POSTROUTING ', - '--protocol udp ', - `--source ${agentIp}${subnetMask} `, - `--out-interface ${routerExt} `, - '--jump MASQUERADE ', - '--to-ports 44444', - ); - const acceptLocalCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table filter ', - '--append INPUT ', - `--in-interface ${routerInt} `, - '--jump ACCEPT', - ); - const acceptEstablishedCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table filter ', - '--append INPUT ', - `--match conntrack `, - '--cstate RELATED,ESTABLISHED ', - '--jump ACCEPT', - ); - const dropCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table filter ', - '--append INPUT ', - '--jump DROP', - ); - child_process.exec(acceptLocalCommand); - child_process.exec(acceptEstablishedCommand); - child_process.exec(dropCommand); - child_process.exec(natCommand); + const natCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${subnetMask}`, + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', + '--to-ports', + '44444', + ]; + 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 Port-Restricted Cone NAT for a namespace (on the router namespace) */ -function setupNATSimplifiedEDMSeed( +async function setupNATSimplifiedEDMSeed( usrnsPid: number, routerNsPid: number, agentIp: string, routerExt: string, + logger: Logger = new Logger(setupNATSimplifiedEDMSeed.name), ) { - const natCommand = nsenter(usrnsPid, routerNsPid).concat( - 'iptables --table nat ', - '--append POSTROUTING ', - '--protocol udp ', - `--source ${agentIp}${subnetMask} `, - `--out-interface ${routerExt} `, - '--jump MASQUERADE ', - '--to-ports 55555', - ); - child_process.exec(natCommand); + const natCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${subnetMask}`, + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', + '--to-ports', + '55555', + ]; + try { + logger.info(['nsenter', ...natCommand].join(' ')); + await testBinUtils.exec('nsenter', natCommand); + } catch (e) { + logger.error(e.message); + } } async function setupNATWithSeedNode( @@ -598,159 +1079,177 @@ async function setupNATWithSeedNode( const password = 'password'; // Create a user namespace containing five network namespaces // Two agents, two routers, one seed node - const usrns = createUserNamespace(); - const seedNetns = createNetworkNamespace(usrns.pid!); - const agent1Netns = createNetworkNamespace(usrns.pid!); - const agent2Netns = createNetworkNamespace(usrns.pid!); - const router1Netns = createNetworkNamespace(usrns.pid!); - const router2Netns = createNetworkNamespace(usrns.pid!); + 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': { - setupDMZ( + await setupDMZ( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1Port, agent1RouterHostExt, agent1RouterHostExtIp, + logger, ); - setupDMZ( + await setupDMZ( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1Port, router1SeedHost, router1SeedHostIp, + logger, ); break; } case 'eim': { - setupNATEndpointIndependentMapping( + await setupNATEndpointIndependentMapping( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1RouterHostExt, agent1RouterHostInt, + logger, ); - setupNATEndpointIndependentMapping( + await setupNATEndpointIndependentMapping( usrns.pid!, router1Netns.pid!, agent1HostIp, router1SeedHost, agent1RouterHostInt, + logger, ); break; } case 'edm': { - setupNATEndpointDependentMapping( + await setupNATEndpointDependentMapping( usrns.pid!, router1Netns.pid!, agent1RouterHostExt, + logger, ); - setupNATEndpointDependentMapping( + await setupNATEndpointDependentMapping( usrns.pid!, router1Netns.pid!, router1SeedHost, + logger, ); break; } case 'edmSimple': { - setupNATSimplifiedEDMAgent( + await setupNATSimplifiedEDMAgent( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1RouterHostExt, agent1RouterHostInt, + logger, ); - setupNATSimplifiedEDMSeed( + await setupNATSimplifiedEDMSeed( usrns.pid!, router1Netns.pid!, agent1HostIp, router1SeedHost, + logger, ); break; } } switch (agent2NAT) { case 'dmz': { - setupDMZ( + await setupDMZ( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2Port, agent2RouterHostExt, agent2RouterHostExtIp, + logger, ); - setupDMZ( + await setupDMZ( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2Port, router2SeedHost, router2SeedHostIp, + logger, ); break; } case 'eim': { - setupNATEndpointIndependentMapping( + await setupNATEndpointIndependentMapping( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2RouterHostExt, agent2RouterHostInt, + logger, ); - setupNATEndpointIndependentMapping( + await setupNATEndpointIndependentMapping( usrns.pid!, router2Netns.pid!, agent2HostIp, router2SeedHost, agent2RouterHostInt, + logger, ); break; } case 'edm': { - setupNATEndpointDependentMapping( + await setupNATEndpointDependentMapping( usrns.pid!, router2Netns.pid!, agent2RouterHostExt, + logger, ); - setupNATEndpointDependentMapping( + await setupNATEndpointDependentMapping( usrns.pid!, router2Netns.pid!, router2SeedHost, + logger, ); break; } case 'edmSimple': { - setupNATSimplifiedEDMAgent( + await setupNATSimplifiedEDMAgent( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2RouterHostExt, agent2RouterHostInt, + logger, ); - setupNATSimplifiedEDMSeed( + await setupNATSimplifiedEDMSeed( usrns.pid!, router2Netns.pid!, agent2HostIp, router2SeedHost, + logger, ); break; } } - setupNetworkNamespaceInterfaces( + await setupNetworkNamespaceInterfaces( usrns.pid!, agent1Netns.pid!, router1Netns.pid!, router2Netns.pid!, agent2Netns.pid!, + logger, ); - setupSeedNamespaceInterfaces( + await setupSeedNamespaceInterfaces( usrns.pid!, seedNetns.pid!, router1Netns.pid!, router2Netns.pid!, + logger, ); const seedNode = await pkSpawnNs( usrns.pid!, @@ -939,100 +1438,109 @@ async function setupNAT( const password = 'password'; // Create a user namespace containing four network namespaces // Two agents and two routers - const usrns = createUserNamespace(); - const agent1Netns = createNetworkNamespace(usrns.pid!); - const agent2Netns = createNetworkNamespace(usrns.pid!); - const router1Netns = createNetworkNamespace(usrns.pid!); - const router2Netns = createNetworkNamespace(usrns.pid!); + 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': { - setupDMZ( + await setupDMZ( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1Port, agent1RouterHostExt, agent1RouterHostExtIp, + logger, ); break; } case 'eim': { - setupNATEndpointIndependentMapping( + await setupNATEndpointIndependentMapping( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1RouterHostExt, agent1RouterHostInt, + logger, ); break; } case 'edm': { - setupNATEndpointDependentMapping( + await setupNATEndpointDependentMapping( usrns.pid!, router1Netns.pid!, agent1RouterHostExt, + logger, ); break; } case 'edmSimple': { - setupNATSimplifiedEDMAgent( + await setupNATSimplifiedEDMAgent( usrns.pid!, router1Netns.pid!, agent1HostIp, agent1RouterHostExt, agent1RouterHostInt, + logger, ); break; } } switch (agent2NAT) { case 'dmz': { - setupDMZ( + await setupDMZ( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2Port, agent2RouterHostExt, agent2RouterHostExtIp, + logger, ); break; } case 'eim': { - setupNATEndpointIndependentMapping( + await setupNATEndpointIndependentMapping( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2RouterHostExt, agent2RouterHostInt, + logger, ); break; } case 'edm': { - setupNATEndpointDependentMapping( + await setupNATEndpointDependentMapping( usrns.pid!, router2Netns.pid!, agent2RouterHostExt, + logger, ); break; } case 'edmSimple': { - setupNATSimplifiedEDMAgent( + await setupNATSimplifiedEDMAgent( usrns.pid!, router2Netns.pid!, agent2HostIp, agent2RouterHostExt, agent2RouterHostInt, + logger, ); break; } } - setupNetworkNamespaceInterfaces( + await setupNetworkNamespaceInterfaces( usrns.pid!, agent1Netns.pid!, router1Netns.pid!, router2Netns.pid!, agent2Netns.pid!, + logger, ); const agent1 = await pkSpawnNs( usrns.pid!, diff --git a/tests/utils.ts b/tests/utils.ts index 311743565c..b90efd470c 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -197,9 +197,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, };