Skip to content

Instantly share code, notes, and snippets.

@MossTheFox
Last active April 29, 2023 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MossTheFox/443725abc36a51fa6d9913cc94ae7cd9 to your computer and use it in GitHub Desktop.
Save MossTheFox/443725abc36a51fa6d9913cc94ae7cd9 to your computer and use it in GitHub Desktop.
A node.js script for testing network reliability with UDP packages.
const TIP_TEXT = `udp_ping_pong.mjs
Server:
node ./udp_ping_pong.mjs --server --port 8999
Client (assume the target server ip is 1.1.1.1, port 8999):
node ./udp_ping_pong.mjs --client 1.1.1.1:8999 --interval 100 --timeout 1000
Ctrl + C to end the ping and see results.
Args:
For Server:
-s (--server): use as server.
-p (--port) [number]: specify UDP4 port to listen.
For Client:
-c (--client) [targetIP:port]: use as client. Target ping server and port is required.
-i (--interval) [number]: interval milliseconds, default 100.
-t (--timeout) [number]: timeout milliseconds, default 1000.
`
import dgram from "dgram";
if (process.argv.length <= 2) {
console.log(TIP_TEXT);
// process.exit(0);
}
/**
*
* @param {string[] | string} arr
*/
function checkArgs(arr) {
for (const arg of process.argv) {
if (
(Array.isArray(arr) && arr.includes(arg.toLowerCase()))
|| arr === arg.toLowerCase()
) {
return true;
}
}
return false;
}
function getMicroSec() {
let hrTime = process.hrtime();
return hrTime[0] * 1000000 + hrTime[1] / 1000;
}
function waitForMS(ms) {
return new Promise((r) => setTimeout(r, ms));
}
if (checkArgs(['-s', '--server'])) {
let port = 8999;
for (const [index, arg] of process.argv.entries()) {
if (['-p', '--port'].includes(arg.toLowerCase())) {
let _port = +process.argv[index + 1];
port = (_port && port > 0 && port < 65536 && Number.isInteger(port)) ? _port : port;
}
}
const server = dgram.createSocket("udp4", (buffer, rinfo) => {
const str = buffer.toString("utf-8");
const match = str.match(/ping\((\d+)\)/);
const result = match ? +match[1] : NaN;
if (isNaN(result)) return;
server.send(`pong(${result})`, rinfo.port, rinfo.address);
console.log(`Ping #${result} from ${rinfo.address}:${rinfo.port}`)
});
server.bind(port);
console.log('Server started on UDP4 port ' + port);
}
if (checkArgs(['-c', '--client'])) {
let target = '0.0.0.0';
let port = 0;
let interval = 100;
let timeout = 1000;
for (const [index, arg] of process.argv.entries()) {
if (['-c', '--client'].includes(arg.toLowerCase())) {
let str = process.argv[index + 1];
if (!str) {
console.log('No target specified. (Usage Example: -c 127.0.0.1:2333)');
process.exit(0);
}
[target, port] = str.split(':');
port = +port;
if (port <= 0 || isNaN(port) || !target) {
console.log('Invalid target or port. (Usage Example: -c 127.0.0.1:2333)');
process.exit(0);
}
}
if (['-i', '--interval'].includes(arg.toLowerCase())) {
let _interval = +process.argv[index + 1];
interval = _interval ? _interval : interval;
}
if (['-t', '--timeout'].includes(arg.toLowerCase())) {
let _timeout = +process.argv[index + 1];
timeout = _timeout ? _timeout : timeout;
}
}
console.log(`UDP ping client. Target Server: ${target}:${port}, interval: ${interval}.`);
/** @type {Map<number, number>} */
const delayMap = new Map();
const FINAL_RESULT = {
send: 0,
received: 0,
/** @type {number[]} */
queue: [],
/** @type {number[]} */
missed: []
};
const client = dgram.createSocket('udp4', (msg, rinfo) => {
const str = msg.toString("utf-8");
const match = str.match(/pong\((\d+)\)/);
const thisPongQueue = match ? +match[1] : NaN;
if (isNaN(thisPongQueue)) return;
const started = delayMap.get(thisPongQueue);
if (!started || rinfo.address !== target) {
console.log('Ignored pong ' + thisPongQueue);
return;
}
const current = getMicroSec();
const delayMS = (current - started) / 1000;
delayMap.delete(thisPongQueue);
console.log(`>> Pong #${thisPongQueue}, ${delayMS.toFixed(3)} ms`);
FINAL_RESULT.received++;
FINAL_RESULT.queue.push(delayMS);
});
let queue = 0;
const taskInterval = setInterval(() => {
client.send(`ping(${queue})`, port, target, (err, bytes) => {
if (err) {
console.log('Error when sending package.', err);
}
// console.log(`Ping #${queue}`);
delayMap.set(queue, getMicroSec());
let marked = queue;
queue++;
FINAL_RESULT.send++;
setTimeout(() => {
if (delayMap.has(marked)) {
console.log(`NO RESPONSE (#${marked}) in ${timeout} ms.`);
delayMap.delete(marked);
FINAL_RESULT.missed.push(marked);
}
}, timeout);
})
}, interval);
const startDate = new Date();
process.on('SIGINT', async (sig) => {
clearInterval(taskInterval);
console.log('-- Hold for 3 * interval times before ending... --');
await waitForMS(3 * interval);
console.log('==================================================');
const SEND_TOTAL = FINAL_RESULT.send;
const RECEIVED_TOTAL = FINAL_RESULT.received;
const AVERAGE_DELAY = FINAL_RESULT.queue.reduce((prev, curr, i, arr) => {
return prev + curr / arr.length
}, 0);
const MAX_PING = (FINAL_RESULT.queue.sort((a, b) => a - b)[FINAL_RESULT.queue.length - 1])?.toFixed(4) ?? "--";
const MIN_PING = FINAL_RESULT.queue[0]?.toFixed(4) ?? "--";
const A_D = FINAL_RESULT.queue.reduce((prev, curr) => {
return prev + Math.abs(AVERAGE_DELAY - curr) / RECEIVED_TOTAL
}, 0);
console.log(`UDP Ping Pong (From ${startDate.toLocaleString()} to ${new Date().toLocaleString()})
Result:
- ${SEND_TOTAL} send
- ${RECEIVED_TOTAL} received
Average: ${AVERAGE_DELAY.toFixed(4)} ms
Max: ${MAX_PING}, Min: ${MIN_PING}
Loss: ${(1 - RECEIVED_TOTAL / SEND_TOTAL).toFixed(4)}% (${RECEIVED_TOTAL}/${SEND_TOTAL})
Average Deviation: ${A_D.toFixed(4)} ms
Lost queue: [${FINAL_RESULT.missed.join(', ')}]`);
process.exit();
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment