magic-packet
Version:
发送Wake-on-LAN魔术包唤醒局域网电脑
174 lines (149 loc) • 5.46 kB
JavaScript
const dgram = require('dgram');
const { program } = require('commander');
/**
* 验证MAC地址格式
* @param {string} macAddress - MAC地址 (格式: XX:XX:XX:XX:XX:XX 或 XX-XX-XX-XX-XX-XX)
* @returns {boolean} 是否是有效的MAC地址
*/
function isValidMacAddress(macAddress) {
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
return macRegex.test(macAddress);
}
/**
* 创建魔术包
* @param {string} macAddress - MAC地址 (格式: XX:XX:XX:XX:XX:XX 或 XX-XX-XX-XX-XX-XX)
* @returns {Buffer} 魔术包字节
* @throws {Error} MAC地址格式错误时抛出异常
*/
function createMagicPacket(macAddress) {
// 验证MAC地址格式
if (!isValidMacAddress(macAddress)) {
throw new Error('MAC地址格式错误,请使用格式 XX:XX:XX:XX:XX:XX 或 XX-XX-XX-XX-XX-XX');
}
// 移除MAC地址中的分隔符
const mac = macAddress.replace(/[:-]/g, '');
// 创建魔术包: 6字节的0xFF,后面跟着目标MAC地址重复16次
const magic = Buffer.alloc(102); // 6 + 16*6 = 102 bytes
// 填充前6个字节为0xFF
magic.fill(0xFF, 0, 6);
// 将MAC地址解析为字节并复制到缓冲区
const macBytes = [];
for (let i = 0; i < 6; i++) {
macBytes.push(parseInt(mac.substr(i * 2, 2), 16));
}
// 重复16次MAC地址
for (let i = 0; i < 16; i++) {
macBytes.forEach((byte, j) => {
magic[6 + i * 6 + j] = byte;
});
}
return magic;
}
/**
* 发送魔术包
* @param {string} macAddress - 目标计算机的MAC地址
* @param {string} [ipAddress='255.255.255.255'] - 广播地址
* @param {number} [port=9] - 目标端口
* @param {Object} [options={}] - 额外选项
* @param {number} [options.retries=0] - 失败时重试次数
* @param {boolean} [options.silent=false] - 是否静默模式(不输出日志)
* @returns {Promise<void>}
*/
async function sendMagicPacket(macAddress, ipAddress = '255.255.255.255', port = 9, options = {}) {
const { retries = 0, silent = false } = options;
let attempts = 0;
let lastError = null;
// 尝试发送,如果失败则重试
while (attempts <= retries) {
try {
// 创建魔术包
const magicPacket = createMagicPacket(macAddress);
// 发送数据包
await sendUdpPacket(magicPacket, ipAddress, port);
if (!silent) {
console.log(`魔术包已成功发送到 ${macAddress}`);
}
return; // 成功发送
} catch (err) {
lastError = err;
attempts++;
if (attempts <= retries) {
if (!silent) {
console.log(`发送失败,正在尝试第 ${attempts} 次重试...`);
}
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
// 所有尝试都失败
throw lastError || new Error('发送魔术包失败');
}
/**
* 发送UDP数据包
* @param {Buffer} packet - 要发送的数据包
* @param {string} ipAddress - 目标IP地址
* @param {number} port - 目标端口
* @returns {Promise<void>}
* @private
*/
function sendUdpPacket(packet, ipAddress, port) {
return new Promise((resolve, reject) => {
// 创建UDP socket
const socket = dgram.createSocket('udp4');
// 设置错误处理
socket.on('error', (err) => {
socket.close();
reject(new Error(`UDP发送错误: ${err.message}`));
});
// 设置广播选项并发送数据包
socket.on('listening', () => {
socket.setBroadcast(true);
socket.send(packet, port, ipAddress, (err) => {
socket.close();
if (err) {
reject(new Error(`发送数据包失败: ${err.message}`));
} else {
resolve();
}
});
});
// 绑定到随机端口
socket.bind();
});
}
// 如果直接运行此文件(而不是作为模块导入),则执行命令行功能
if (require.main === module) {
// 设置命令行参数
program
.name('magic-packet')
.description('发送Wake-on-LAN魔术包唤醒局域网中的计算机')
.version('1.1.0')
.requiredOption('-m, --mac <address>', '目标计算机的MAC地址 (格式: XX:XX:XX:XX:XX:XX)')
.option('-i, --ip <address>', '广播地址', '255.255.255.255')
.option('-p, --port <number>', '目标端口', '9')
.option('-r, --retries <number>', '发送失败时的重试次数', '0')
.option('-s, --silent', '静默模式,不输出日志', false)
.parse(process.argv);
const options = program.opts();
// 发送魔术包
sendMagicPacket(
options.mac,
options.ip,
parseInt(options.port),
{
retries: parseInt(options.retries),
silent: options.silent
}
).catch(err => {
console.error(`错误: ${err.message}`);
process.exit(1);
});
}
// 导出函数,使其可以作为模块使用
module.exports = {
isValidMacAddress,
createMagicPacket,
sendMagicPacket
};