Files
IPAM/src/modules/discovery/scanners/arp.scanner.ts
Mathieu BOURBON fbb6138c28 first commit
2026-04-18 16:24:44 +02:00

62 lines
2.0 KiB
TypeScript

import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { env } from '@/config/env';
import type { DiscoveryResult, DiscoveryTarget, Scanner } from '../types';
const execAsync = promisify(exec);
/**
* Lit la table ARP locale via `ip neigh` (Linux) ou `arp -a` (fallback).
* Ne scanne pas activement : retourne ce que le kernel a déjà vu.
* Pour forcer la résolution, il faut ping-sweeper d'abord (voir discovery.service).
*/
export const arpScanner: Scanner = {
kind: 'arp',
label: 'Table ARP locale',
enabled: env.DISCOVERY_ENABLE_ARP,
async run(_target: DiscoveryTarget): Promise<DiscoveryResult[]> {
const { stdout } = await execAsync('ip neigh show').catch(() => ({ stdout: '' }));
if (stdout) return parseIpNeigh(stdout);
const { stdout: arpOut } = await execAsync('arp -a').catch(() => ({ stdout: '' }));
return parseArpA(arpOut);
},
};
function parseIpNeigh(output: string): DiscoveryResult[] {
// Exemple : 192.168.1.10 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
return output
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
.map((line) => {
const ipMatch = line.match(/^(\d{1,3}(?:\.\d{1,3}){3})/);
const macMatch = line.match(/lladdr\s+([0-9a-fA-F:]{17})/);
if (!ipMatch) return null;
return {
ipAddress: ipMatch[1],
macAddress: macMatch?.[1],
} satisfies DiscoveryResult;
})
.filter((r): r is DiscoveryResult => r !== null);
}
function parseArpA(output: string): DiscoveryResult[] {
// Exemple : ? (192.168.1.10) at aa:bb:cc:dd:ee:ff [ether] on eth0
return output
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
.map((line) => {
const ipMatch = line.match(/\((\d{1,3}(?:\.\d{1,3}){3})\)/);
const macMatch = line.match(/([0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5})/);
if (!ipMatch) return null;
return {
ipAddress: ipMatch[1],
macAddress: macMatch?.[1],
} satisfies DiscoveryResult;
})
.filter((r): r is DiscoveryResult => r !== null);
}