Last active
April 29, 2023 13:27
-
-
Save MossTheFox/443725abc36a51fa6d9913cc94ae7cd9 to your computer and use it in GitHub Desktop.
A node.js script for testing network reliability with UDP packages.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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