iobroker.kisshome-defender
Version:
Collection of information for KISSHome defender
518 lines • 23.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_PACKET_LENGTH = void 0;
exports.stopAllRecordingsOnFritzBox = stopAllRecordingsOnFritzBox;
exports.getRecordURL = getRecordURL;
exports.startRecordingOnFritzBox = startRecordingOnFritzBox;
const node_http_1 = __importDefault(require("node:http"));
const axios_1 = __importDefault(require("axios"));
const SIMULATE = process.env.SIMULATE === 'true' || process.env.SIMULATE === '1';
// Max packet length to capture
exports.MAX_PACKET_LENGTH = 1538;
const debug = false;
const NO_FILTER = false;
function analyzePacket(context) {
if (!context.filtered.buffer) {
return false;
}
const len = context.filtered.buffer.byteLength || 0;
// Normal header is 16 bytes
// modifiedMagic is true if the header is in Little-Endian format, and extended packet header (8 bytes more)
// libpCapFormat is true if the header is in Big-Endian format and extended packet header (8 bytes more)
// first 4 bytes are timestamp in seconds
// next 4 bytes are timestamp in microseconds
// next 4 bytes are packet length saved in file
// next 4 bytes are packet length sent over the network
// by modified
// next 4 bytes ifindex
// next 2 bytes is protocol
// next byte is pkt_type: broadcast/multicast/etc. indication
// next byte is padding
const headerLength = context.libpCapFormat || context.modifiedMagic ? 24 : 16;
if (len < headerLength) {
return false;
}
const seconds = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(0)
: context.filtered.buffer.readUInt32LE(0);
const microseconds = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(4)
: context.filtered.buffer.readUInt32LE(4);
const packageLen = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(8)
: context.filtered.buffer.readUInt32LE(8);
const packageLenSent = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(12)
: context.filtered.buffer.readUInt32LE(12);
if (debug) {
let MAC1;
let MAC2;
if (context.networkType === 0x69) {
MAC1 = context.filtered.buffer.subarray(headerLength + 4, headerLength + 4 + 6);
MAC2 = context.filtered.buffer.subarray(headerLength + 4 + 6, headerLength + 4 + 12);
}
else {
MAC1 = context.filtered.buffer.subarray(headerLength, headerLength + 6);
MAC2 = context.filtered.buffer.subarray(headerLength + 6, headerLength + 12);
}
console.log(`Packet: ${new Date(seconds * 1000 + Math.round(microseconds / 1000)).toISOString()} ${packageLen} ${packageLenSent} ${MAC1.toString('hex')} => ${MAC2.toString('hex')}`);
}
if (packageLen > 10000) {
// error of capturing
throw new Error(`Packet length is too big: ${packageLen}`);
}
if (len < headerLength + packageLen) {
return false;
}
// next 6 bytes are MAC address of a source
// next 6 bytes are MAC address of destination
const offset = headerLength + 12;
let maxBytes = 0;
if (offset + 2 <= len) {
// next 2 bytes are Ethernet type
const ethType = context.filtered.buffer.readUInt16BE(offset);
// If IPv4
if (ethType === 0x0800) {
const ipHeaderStart = offset + 2;
const ipVersionAndIHL = context.filtered.buffer[ipHeaderStart];
const ipHeaderLength = (ipVersionAndIHL & 0x0f) * 4; // IHL field gives the length of the IP header
// read protocol type (TCP/UDP/ICMP/etc.)
const protocolType = context.filtered.buffer[ipHeaderStart + 9]; // Protocol field in IP header
if (protocolType === 6) {
// TCP
const tcpHeaderStart = ipHeaderStart + ipHeaderLength;
const tcpOffsetAndFlags = context.filtered.buffer[tcpHeaderStart + 12];
const tcpHeaderLength = (tcpOffsetAndFlags >> 4) * 4; // Data offset in TCP header
maxBytes = ipHeaderLength + tcpHeaderLength + 14; // Total length: IP header + TCP header + Ethernet header
}
else if (protocolType === 17) {
// UDP
maxBytes = ipHeaderLength + 8 + 14; // IP header + 8 bytes UDP header + Ethernet header
}
else {
maxBytes = 0;
}
}
// todo: which more protocols to collect?
// If ICMP
// if (ethType === 1) {
// return offset + 40;
// }
// If IPv6
// if (ethType === 0x86DD) {
// return offset + 40;
// }
}
if (maxBytes) {
if (packageLen < maxBytes) {
// remove from buffer packageLen + 16 bytes
const packetBuffer = context.filtered.buffer.subarray(0, headerLength + packageLen);
if (context.libpCapFormat) {
// write header in LE notation
packetBuffer.writeUInt32LE(seconds, 0);
packetBuffer.writeUInt32LE(microseconds, 4);
packetBuffer.writeUInt32LE(packageLen, 8);
packetBuffer.writeUInt32LE(packageLenSent, 12);
const ifindex = packetBuffer.readUInt32BE(16);
const protocol = packetBuffer.readUInt16BE(20);
packetBuffer.writeUInt32LE(ifindex, 16);
packetBuffer.writeUInt16LE(protocol, 20);
}
context.filtered.packets.push(packetBuffer);
context.filtered.totalBytes += headerLength + packageLen;
if (debug) {
console.log(`Saved packet: ${headerLength + packageLen}`);
}
}
else {
const packetBuffer = context.filtered.buffer.subarray(0, headerLength + maxBytes);
if (context.libpCapFormat) {
// write header in LE notation
packetBuffer.writeUInt32LE(seconds, 0);
packetBuffer.writeUInt32LE(microseconds, 4);
packetBuffer.writeUInt32LE(packageLenSent, 12);
const ifindex = packetBuffer.readUInt32BE(16);
const protocol = packetBuffer.readUInt16BE(20);
packetBuffer.writeUInt32LE(ifindex, 16);
packetBuffer.writeUInt16LE(protocol, 20);
}
// save new length in the packet
packetBuffer.writeUInt32LE(maxBytes, 8);
context.filtered.packets.push(packetBuffer);
context.filtered.totalBytes += headerLength + maxBytes;
if (debug) {
console.log(`Saved packet: ${headerLength + maxBytes}`);
}
}
context.filtered.totalPackets++;
}
// remove this packet
context.filtered.buffer = context.filtered.buffer.subarray(headerLength + packageLen);
return true;
}
function analyzePacketFull(context) {
if (!context.full.buffer) {
return false;
}
const len = context.full.buffer.byteLength || 0;
// Normal header is 16 bytes
// modifiedMagic is true if the header is in Little-Endian format, and extended packet header (8 bytes more)
// libpCapFormat is true if the header is in Big-Endian format and extended packet header (8 bytes more)
// first 4 bytes are timestamp in seconds
// next 4 bytes are timestamp in microseconds
// next 4 bytes are packet length saved in file
// next 4 bytes are packet length sent over the network
// by modified
// next 4 bytes ifindex
// next 2 bytes is protocol
// next byte is pkt_type: broadcast/multicast/etc. indication
// next byte is padding
const headerLength = context.libpCapFormat || context.modifiedMagic ? 24 : 16;
if (len < headerLength) {
return false;
}
const seconds = context.libpCapFormat ? context.full.buffer.readUInt32BE(0) : context.full.buffer.readUInt32LE(0);
const microseconds = context.libpCapFormat
? context.full.buffer.readUInt32BE(4)
: context.full.buffer.readUInt32LE(4);
const packageLen = context.libpCapFormat
? context.full.buffer.readUInt32BE(8)
: context.full.buffer.readUInt32LE(8);
const packageLenSent = context.libpCapFormat
? context.full.buffer.readUInt32BE(12)
: context.full.buffer.readUInt32LE(12);
if (debug) {
let MAC1;
let MAC2;
if (context.networkType === 0x69) {
MAC1 = context.full.buffer.subarray(headerLength + 4, headerLength + 4 + 6);
MAC2 = context.full.buffer.subarray(headerLength + 4 + 6, headerLength + 4 + 12);
}
else {
MAC1 = context.full.buffer.subarray(headerLength, headerLength + 6);
MAC2 = context.full.buffer.subarray(headerLength + 6, headerLength + 12);
}
console.log(`Packet: ${new Date(seconds * 1000 + Math.round(microseconds / 1000)).toISOString()} ${packageLen} ${packageLenSent} ${MAC1.toString('hex')} => ${MAC2.toString('hex')}`);
}
if (packageLen > 10000) {
// error of capturing
throw new Error(`Packet length is too big: ${packageLen}`);
}
if (len < headerLength + packageLen) {
return false;
}
// next 6 bytes are MAC address of a source
// next 6 bytes are MAC address of destination
const offset = headerLength + 12;
let save = false;
if (offset + 2 <= len) {
// next 2 bytes are Ethernet type
const ethType = context.full.buffer.readUInt16BE(offset);
// If IPv4
if (ethType === 0x0800) {
const ipHeaderStart = offset + 2;
// read protocol type (TCP/UDP/ICMP/etc.)
const protocolType = context.full.buffer[ipHeaderStart + 9]; // Protocol field in IP header
if (protocolType === 6) {
// TCP
save = true; // Total length: IP header + TCP header + Ethernet header
}
else if (protocolType === 17) {
// UDP
save = true; // IP header + 8 bytes UDP header + Ethernet header
}
else {
save = false;
}
}
}
if (save) {
const packetBuffer = context.full.buffer.subarray(0, headerLength + packageLen);
if (context.libpCapFormat) {
// write header in LE notation
packetBuffer.writeUInt32LE(seconds, 0);
packetBuffer.writeUInt32LE(microseconds, 4);
packetBuffer.writeUInt32LE(packageLenSent, 12);
const ifindex = packetBuffer.readUInt32BE(16);
const protocol = packetBuffer.readUInt16BE(20);
packetBuffer.writeUInt32LE(ifindex, 16);
packetBuffer.writeUInt16LE(protocol, 20);
}
// save new length in the packet
packetBuffer.writeUInt32LE(packageLen, 8);
context.full.packets.push(packetBuffer);
context.full.totalBytes += headerLength + packageLen;
if (debug) {
console.log(`Saved packet: ${headerLength + packageLen}`);
}
context.full.totalPackets++;
}
// remove this packet
context.full.buffer = context.full.buffer.subarray(headerLength + packageLen);
return true;
}
async function stopAllRecordingsOnFritzBox(ip, sid) {
const captureUrl = `http://${ip.trim()}/cgi-bin/capture_notimeout?iface=stopall&capture=Stop&sid=${sid}`;
const response = await axios_1.default.get(captureUrl);
return response.data;
}
function getRecordURL(ip, sid, iface, MACs) {
const filter = MACs.filter(m => m === null || m === void 0 ? void 0 : m.trim()).length ? `ether host ${MACs.filter(m => m === null || m === void 0 ? void 0 : m.trim()).join(' || ')}` : '';
return `http://${ip.trim()}/cgi-bin/capture_notimeout?ifaceorminor=${encodeURIComponent(iface.trim())}&snaplen=${exports.MAX_PACKET_LENGTH}${filter ? `&filter=${encodeURIComponent(filter)}` : ''}&capture=Start&sid=${sid}`;
}
function _writeHeader(context) {
// if the header of PCAP file is not written yet
if (!context.first) {
// check if we have at least 6 * 4 bytes
if (context.full.buffer && context.full.buffer.length > 6 * 4) {
context.first = true;
const magic = context.full.buffer.readUInt32LE(0);
context.modifiedMagic = magic === 0xa1b2cd34;
context.libpCapFormat = magic === 0x34cdb2a1;
const versionMajor = context.libpCapFormat
? context.full.buffer.readUInt16BE(4)
: context.full.buffer.readUInt16LE(4);
const versionMinor = context.libpCapFormat
? context.full.buffer.readUInt16BE(4 + 2)
: context.full.buffer.readUInt16LE(4 + 2);
const reserved1 = context.libpCapFormat
? context.full.buffer.readUInt32BE(4 * 2)
: context.full.buffer.readUInt32LE(4 * 2);
const reserved2 = context.libpCapFormat
? context.full.buffer.readUInt32BE(4 * 3)
: context.full.buffer.readUInt32LE(4 * 3);
const snapLen = context.libpCapFormat
? context.full.buffer.readUInt32BE(4 * 4)
: context.full.buffer.readUInt32LE(4 * 4);
context.networkType = context.libpCapFormat
? context.full.buffer.readUInt32BE(4 * 5)
: context.full.buffer.readUInt32LE(4 * 5);
if (debug) {
console.log(`PCAP: ${magic.toString(16)} v${versionMajor}.${versionMinor} res1=${reserved1} res2=${reserved2} snaplen=${snapLen} network=${context.networkType.toString(16)}`);
}
// remove header
context.full.buffer = context.full.buffer.subarray(6 * 4);
// No return here, because we need to write header to filtered buffer too
}
// check if we have at least 6 * 4 bytes
if (context.filtered.buffer && context.filtered.buffer.length > 6 * 4) {
context.first = true;
const magic = context.filtered.buffer.readUInt32LE(0);
context.modifiedMagic = magic === 0xa1b2cd34;
context.libpCapFormat = magic === 0x34cdb2a1;
const versionMajor = context.libpCapFormat
? context.filtered.buffer.readUInt16BE(4)
: context.filtered.buffer.readUInt16LE(4);
const versionMinor = context.libpCapFormat
? context.filtered.buffer.readUInt16BE(4 + 2)
: context.filtered.buffer.readUInt16LE(4 + 2);
const reserved1 = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(4 * 2)
: context.filtered.buffer.readUInt32LE(4 * 2);
const reserved2 = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(4 * 3)
: context.filtered.buffer.readUInt32LE(4 * 3);
const snapLen = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(4 * 4)
: context.filtered.buffer.readUInt32LE(4 * 4);
context.networkType = context.libpCapFormat
? context.filtered.buffer.readUInt32BE(4 * 5)
: context.filtered.buffer.readUInt32LE(4 * 5);
if (debug) {
console.log(`PCAP: ${magic.toString(16)} v${versionMajor}.${versionMinor} res1=${reserved1} res2=${reserved2} snaplen=${snapLen} network=${context.networkType.toString(16)}`);
}
// remove header
context.filtered.buffer = context.filtered.buffer.subarray(6 * 4);
return true;
}
// wait for more data
return false;
}
return true;
}
function startRecordingOnFritzBox(ip, sid, iface, MACs, onEnd, context, progress, log) {
const captureUrl = getRecordURL(ip, sid, iface, MACs);
context.filtered.buffer = Buffer.from([]);
context.full.buffer = Buffer.from([]);
context.first = false;
let simulateInterval = null;
let timeout = null;
let lastProgress = Date.now();
const informProgress = () => {
const now = Date.now();
// inform about progress every 2 seconds
if (now - lastProgress > 2000) {
lastProgress = now;
progress === null || progress === void 0 ? void 0 : progress();
}
};
const executeOnEnd = (error) => {
if (debug) {
console.log(`FINISH receiving of data...: ${error === null || error === void 0 ? void 0 : error.toString()}`);
}
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
if (onEnd) {
onEnd(error);
onEnd = null;
}
};
const controller = context.controller || new AbortController();
context.controller = controller;
context.started = Date.now();
console.log(`START capture: ${captureUrl}`);
if (SIMULATE) {
if (simulateInterval) {
clearInterval(simulateInterval);
simulateInterval = null;
}
simulateInterval = setInterval(() => {
// Write nulls to the buffer to simulate data
if (context === null || context === void 0 ? void 0 : context.terminate) {
if (simulateInterval) {
clearInterval(simulateInterval);
simulateInterval = null;
}
executeOnEnd(null);
return;
}
const chunkBuffer = Buffer.alloc(1024, 0); // Simulate 1KB of data
context.full.packets.push(chunkBuffer);
context.full.totalPackets++;
context.full.totalBytes += chunkBuffer.length;
// just save all data to file
context.filtered.packets.push(chunkBuffer);
context.filtered.totalPackets++;
context.filtered.totalBytes += chunkBuffer.length;
informProgress();
}, 1000);
}
else {
const req = node_http_1.default.request(captureUrl, {
method: 'GET',
signal: controller.signal,
}, res => {
if (res.statusCode !== 200) {
if (res.statusCode === 401 || res.statusCode === 403) {
executeOnEnd(new Error('Unauthorized'));
return;
}
executeOnEnd(new Error(`Unexpected status code: ${res.statusCode}`));
try {
controller.abort();
}
catch {
// ignore
}
return;
}
res.setEncoding('binary');
if (debug && log) {
log(`Starting receiving of data...: ${JSON.stringify(res.headers)}`, 'debug');
}
informProgress();
res.on('data', (chunk) => {
const chunkBuffer = Buffer.from(chunk, 'binary');
if (debug && log) {
log(`Received ${chunkBuffer.length} bytes`, 'debug');
}
// add data to filtered buffer
context.filtered.buffer = context.filtered.buffer
? Buffer.concat([context.filtered.buffer, chunkBuffer])
: chunkBuffer;
// add data to full buffer
context.full.buffer = context.full.buffer
? Buffer.concat([context.full.buffer, chunkBuffer])
: chunkBuffer;
if (!NO_FILTER) {
// if the header of PCAP file is not written yet
if (!_writeHeader(context)) {
// wait for more data
return;
}
// analyze packets in filtered buffer
let more = false;
do {
try {
more = analyzePacket(context);
}
catch (e) {
try {
controller.abort();
}
catch {
// ignore
}
executeOnEnd(e);
return;
}
} while (more);
// analyze packets in full buffer
more = false;
do {
try {
more = analyzePacketFull(context);
}
catch (e) {
try {
controller.abort();
}
catch {
// ignore
}
executeOnEnd(e);
return;
}
} while (more);
}
else {
// just save all data to file
context.filtered.packets.push(chunkBuffer);
context.filtered.totalPackets++;
context.filtered.totalBytes += chunkBuffer.length;
context.full.packets.push(chunkBuffer);
context.full.totalPackets++;
context.full.totalBytes += chunkBuffer.length;
}
informProgress();
if (context === null || context === void 0 ? void 0 : context.terminate) {
try {
controller.abort();
}
catch {
// ignore
}
executeOnEnd(null);
}
});
res.on('end', () => {
log === null || log === void 0 ? void 0 : log(`File closed by fritzbox after ${context.full.totalBytes} bytes received in ${Math.floor((Date.now() - context.started) / 100) / 10} seconds`, 'debug');
if (!context.full.totalBytes && log && Date.now() - context.started < 3000) {
log(`No bytes received and file was closed by Fritzbox very fast. May be wrong interface selected`, 'info');
log(`Keine Bytes empfangen und Datei wurde von Fritzbox sehr schnell geschlossen. Möglicherweise falsche Schnittstelle ausgewählt`, 'info');
}
executeOnEnd(null);
});
res.on('error', (error) => {
if (!error && log) {
log(`Error by receiving, but no error provided!`, 'error');
}
try {
controller.abort();
}
catch {
// ignore
}
executeOnEnd(error);
});
});
req.on('error', error => executeOnEnd(error));
req.end();
}
}
//# sourceMappingURL=recording.js.map