Skip to content

Commit d1cb8b4

Browse files
committed
src,lib: add dtls interop tests
1 parent 3b97833 commit d1cb8b4

7 files changed

Lines changed: 224 additions & 1 deletion

File tree

lib/internal/dtls/dtls.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
// TODO(@jasnell) Temporarily ignoring c8 coverage for this file while tests
4+
// are still being developed.
5+
/* c8 ignore start */
6+
37
const {
48
ArrayIsArray,
59
FunctionPrototypeBind,
@@ -648,3 +652,5 @@ module.exports = {
648652
DTLSEndpoint,
649653
DTLSSession,
650654
};
655+
656+
/* c8 ignore stop */

lib/internal/dtls/state.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
// TODO(@jasnell) Temporarily ignoring c8 coverage for this file while tests
4+
// are still being developed.
5+
/* c8 ignore start */
6+
37
const {
48
DataView,
59
DataViewPrototypeGetByteLength,
@@ -166,3 +170,5 @@ module.exports = {
166170
DTLSEndpointState,
167171
DTLSSessionState,
168172
};
173+
174+
/* c8 ignore stop */

lib/internal/dtls/stats.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
// TODO(@jasnell) Temporarily ignoring c8 coverage for this file while tests
4+
// are still being developed.
5+
/* c8 ignore start */
6+
37
const {
48
BigUint64Array,
59
JSONStringify,
@@ -333,3 +337,5 @@ module.exports = {
333337
DTLSEndpointStats,
334338
DTLSSessionStats,
335339
};
340+
341+
/* c8 ignore stop */

lib/internal/dtls/symbols.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
// TODO(@jasnell) Temporarily ignoring c8 coverage for this file while tests
4+
// are still being developed.
5+
/* c8 ignore start */
6+
37
const {
48
Symbol,
59
} = primordials;
@@ -35,3 +39,5 @@ module.exports = {
3539
kSessionClose: Symbol('dtls.session.close'),
3640
kSessionKeylog: Symbol('dtls.session.keylog'),
3741
};
42+
43+
/* c8 ignore stop */

test/doctool/test-make-doc.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ for (const actualDoc of actualDocs) {
6161
// Unless the old file is still available pointing to the correct location
6262
// 301 redirects are not yet automated. So keeping the old URL is a
6363
// reasonable workaround.
64-
if (renamedDocs.includes(actualDoc) || actualDoc === 'apilinks.json') continue;
64+
if (renamedDocs.includes(actualDoc) || skipedDocs.includes(actualDoc) ||
65+
actualDoc === 'apilinks.json') continue;
6566
assert.ok(
6667
expectedDocs.includes(actualDoc), `${actualDoc} does not match TOC`);
6768

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Flags: --experimental-dtls --no-warnings
2+
3+
// Test: DTLS interop -- Node.js DTLS server with OpenSSL s_client.
4+
// Verifies that an external DTLS client (OpenSSL CLI) can complete a
5+
// handshake with Node's DTLS server and exchange application data.
6+
7+
import { hasCrypto, skip, mustCall } from '../common/index.mjs';
8+
import { createRequire } from 'module';
9+
import assert from 'node:assert';
10+
import { spawn } from 'node:child_process';
11+
import { setTimeout } from 'node:timers/promises';
12+
import * as fixtures from '../common/fixtures.mjs';
13+
14+
if (!hasCrypto) {
15+
skip('missing crypto');
16+
}
17+
18+
if (!process.features.dtls) {
19+
skip('DTLS is not enabled');
20+
}
21+
22+
const require = createRequire(import.meta.url);
23+
const { opensslCli } = require('../common/crypto');
24+
25+
if (!opensslCli) {
26+
skip('missing openssl-cli');
27+
}
28+
29+
const { listen } = await import('node:dtls');
30+
31+
const reply = 'I AM THE WALRUS'; // Something recognizable
32+
const serverReceivedData = Promise.withResolvers();
33+
34+
// Start Node.js DTLS server.
35+
const endpoint = listen(mustCall((session) => {
36+
session.onmessage = mustCall((data) => {
37+
assert.strictEqual(data.toString().trim(), 'hello from openssl');
38+
session.send(reply);
39+
serverReceivedData.resolve();
40+
});
41+
session.onhandshake = mustCall();
42+
}), {
43+
cert: fixtures.readKey('agent1-cert.pem').toString(),
44+
key: fixtures.readKey('agent1-key.pem').toString(),
45+
port: 0,
46+
host: '127.0.0.1',
47+
});
48+
49+
const { port } = endpoint.address;
50+
51+
// Spawn OpenSSL s_client to connect to the Node.js server.
52+
const args = [
53+
's_client',
54+
'-dtls',
55+
'-connect', `127.0.0.1:${port}`,
56+
'-CAfile', fixtures.path('keys/ca1-cert.pem'),
57+
];
58+
59+
const client = spawn(opensslCli, args, { stdio: 'pipe' });
60+
61+
let stdout = '';
62+
client.stdout.on('data', (data) => { stdout += data; });
63+
64+
let stderr = '';
65+
client.stderr.on('data', (data) => { stderr += data; });
66+
67+
const timeout = setTimeout(() => {
68+
client.kill();
69+
endpoint.close();
70+
assert.fail('Test timed out');
71+
}, 10000);
72+
73+
// Wait for the handshake to start (s_client writes TLS info to stdout),
74+
// then send data.
75+
await new Promise((resolve) => client.stdout.once('data', resolve));
76+
await setTimeout(500);
77+
78+
client.stdin.write('hello from openssl\n');
79+
80+
// Wait for the server to receive and reply.
81+
await serverReceivedData.promise;
82+
await setTimeout(500);
83+
84+
// Close stdin so s_client exits.
85+
client.stdin.end();
86+
87+
// Wait for s_client to exit.
88+
const code = await new Promise((resolve) => client.on('close', resolve));
89+
clearTimeout(timeout);
90+
91+
// s_client should exit cleanly.
92+
assert.strictEqual(code, 0,
93+
`openssl s_client exited with code ${code}\n${stderr}`);
94+
95+
// Verify the reply from Node's server appeared in s_client's stdout.
96+
assert(stdout.includes(reply),
97+
`Expected stdout to include "${reply}"\n${stdout}`);
98+
99+
// Verify it was a DTLS connection.
100+
assert(stdout.includes('DTLS'),
101+
`Expected stdout to include "DTLS"\n${stdout}`);
102+
103+
await endpoint.close();
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Flags: --experimental-dtls --no-warnings
2+
3+
// Test: DTLS interop -- OpenSSL s_server with Node.js DTLS client.
4+
// Verifies that Node's DTLS client can complete a handshake with an
5+
// external DTLS server (OpenSSL CLI) and exchange application data.
6+
7+
import { hasCrypto, skip, mustCall, mustNotCall } from '../common/index.mjs';
8+
import { createRequire } from 'module';
9+
import assert from 'node:assert';
10+
import { spawn } from 'node:child_process';
11+
import * as fixtures from '../common/fixtures.mjs';
12+
13+
if (!hasCrypto) {
14+
skip('missing crypto');
15+
}
16+
17+
if (!process.features.dtls) {
18+
skip('DTLS is not enabled');
19+
}
20+
21+
const require = createRequire(import.meta.url);
22+
const common = require('../common');
23+
const { opensslCli } = require('../common/crypto');
24+
25+
if (!opensslCli) {
26+
skip('missing openssl-cli');
27+
}
28+
29+
const { connect } = await import('node:dtls');
30+
31+
const reply = 'I AM THE WALRUS'; // Something recognizable
32+
33+
// Start OpenSSL DTLS server.
34+
const server = spawn(opensslCli, [
35+
's_server',
36+
'-dtls1_2',
37+
'-accept', String(common.PORT),
38+
'-cert', fixtures.path('keys/agent1-cert.pem'),
39+
'-key', fixtures.path('keys/agent1-key.pem'),
40+
'-listen',
41+
], { stdio: 'pipe' });
42+
43+
let serverOut = '';
44+
server.stdout.on('data', (data) => { serverOut += data; });
45+
let serverErr = '';
46+
server.stderr.on('data', (data) => { serverErr += data; });
47+
server.on('error', mustNotCall());
48+
49+
const timeout = setTimeout(() => {
50+
server.kill();
51+
assert.fail(`Test timed out\nstdout: ${serverOut}\nstderr: ${serverErr}`);
52+
}, 10000);
53+
54+
// Wait for "ACCEPT" on stdout -- this means s_server is ready.
55+
await new Promise((resolve) => {
56+
server.stdout.on('data', function onReady() {
57+
if (!serverOut.includes('ACCEPT')) return;
58+
server.stdout.removeListener('data', onReady);
59+
resolve();
60+
});
61+
});
62+
63+
// Connect Node.js DTLS client.
64+
const session = connect('127.0.0.1', common.PORT, {
65+
ca: [fixtures.readKey('ca1-cert.pem').toString()],
66+
rejectUnauthorized: false,
67+
});
68+
69+
const { protocol } = await session.opened;
70+
assert.match(protocol, /DTLS/i);
71+
72+
// Send data from Node to OpenSSL server.
73+
session.send('hello from node');
74+
75+
// Send data from OpenSSL server to Node client via s_server stdin.
76+
// s_server forwards its stdin to the connected client.
77+
server.stdin.write(reply + '\n');
78+
79+
// Wait for Node client to receive the message.
80+
const data = await new Promise((resolve) => {
81+
session.onmessage = mustCall(resolve);
82+
});
83+
assert.strictEqual(data.toString().trim(), reply);
84+
85+
// Clean up.
86+
await session.close();
87+
await session.endpoint.close();
88+
clearTimeout(timeout);
89+
server.kill();
90+
91+
// Wait for server to exit.
92+
const [, signal] = await new Promise((resolve) => {
93+
server.on('exit', mustCall((...args) => resolve(args)));
94+
});
95+
assert.strictEqual(signal, 'SIGTERM');

0 commit comments

Comments
 (0)