fix: packet capture SFTP path + pre-stop + flush delay
RouterOS SFTP root maps to the device filesystem root, not /flash/. Capture file is written as /<name>.pcap, not /flash/<name>.pcap. Also stop any in-progress capture before reconfiguring, add a 2s flush delay between sniffer stop and SFTP download, improve error messages to expose exact failure point, and delete remote file after download. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
63a7f4d785
commit
c311ddb5d2
@@ -1788,11 +1788,14 @@ router.post('/:id/tools/capture', requireWrite, async (req: Request, res: Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileName = `cap-${Date.now()}`;
|
const fileName = `cap-${Date.now()}`;
|
||||||
const remotePath = `/flash/${fileName}.pcap`;
|
// RouterOS SFTP root = device file system root; sniffer writes <file-name>.pcap there
|
||||||
|
const remotePath = `/${fileName}.pcap`;
|
||||||
const client = makeToolClient(device);
|
const client = makeToolClient(device);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
// Stop any previous capture before reconfiguring
|
||||||
|
await client.execute('/tool/sniffer/stop').catch(() => {});
|
||||||
const setParams: Record<string, string> = { 'file-name': fileName, 'file-limit': '10240' };
|
const setParams: Record<string, string> = { 'file-name': fileName, 'file-limit': '10240' };
|
||||||
if (iface) setParams['interface'] = iface;
|
if (iface) setParams['interface'] = iface;
|
||||||
if (filter_ip) setParams['filter-ip-address'] = filter_ip;
|
if (filter_ip) setParams['filter-ip-address'] = filter_ip;
|
||||||
@@ -1801,21 +1804,30 @@ router.post('/:id/tools/capture', requireWrite, async (req: Request, res: Respon
|
|||||||
await new Promise((r) => setTimeout(r, captureSec * 1000));
|
await new Promise((r) => setTimeout(r, captureSec * 1000));
|
||||||
await client.execute('/tool/sniffer/stop').catch(() => {});
|
await client.execute('/tool/sniffer/stop').catch(() => {});
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
|
// Allow RouterOS a moment to flush the PCAP file before SFTP download
|
||||||
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|
||||||
// Download PCAP via SFTP
|
// Download PCAP via SFTP then delete remote file
|
||||||
const pcapBuffer = await new Promise<Buffer>((resolve, reject) => {
|
const pcapBuffer = await new Promise<Buffer>((resolve, reject) => {
|
||||||
const ssh = new SshClient();
|
const ssh = new SshClient();
|
||||||
ssh.on('ready', () => {
|
ssh.on('ready', () => {
|
||||||
ssh.sftp((err, sftp) => {
|
ssh.sftp((err, sftp) => {
|
||||||
if (err) { ssh.end(); return reject(err); }
|
if (err) { ssh.end(); return reject(new Error(`SFTP init failed: ${err.message}`)); }
|
||||||
const chunks: Buffer[] = [];
|
const chunks: Buffer[] = [];
|
||||||
const stream = sftp.createReadStream(remotePath);
|
const stream = sftp.createReadStream(remotePath);
|
||||||
stream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
stream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
stream.on('end', () => { ssh.end(); resolve(Buffer.concat(chunks)); });
|
stream.on('end', () => {
|
||||||
stream.on('error', (e: Error) => { ssh.end(); reject(e); });
|
// Best-effort delete; don't block the response on it
|
||||||
|
sftp.unlink(remotePath, () => { ssh.end(); });
|
||||||
|
resolve(Buffer.concat(chunks));
|
||||||
|
});
|
||||||
|
stream.on('error', (e: Error) => {
|
||||||
|
ssh.end();
|
||||||
|
reject(new Error(`SFTP read failed (path=${remotePath}): ${e.message}`));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ssh.on('error', reject);
|
ssh.on('error', (e) => reject(new Error(`SSH connect failed: ${e.message}`)));
|
||||||
ssh.connect({
|
ssh.connect({
|
||||||
host: device.ip_address,
|
host: device.ip_address,
|
||||||
port: device.ssh_port ?? 22,
|
port: device.ssh_port ?? 22,
|
||||||
@@ -1830,6 +1842,7 @@ router.post('/:id/tools/capture', requireWrite, async (req: Request, res: Respon
|
|||||||
return res.send(pcapBuffer);
|
return res.send(pcapBuffer);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
|
console.error('[capture]', (err as Error).message);
|
||||||
return res.status(500).json({ error: (err as Error).message });
|
return res.status(500).json({ error: (err as Error).message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user