Refactor VPN proxy client implementation
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const ACTIVE_STATES = new Set(['REACHABLE', 'DELAY', 'PROBE', 'PERMANENT', 'NOARP']);
|
||||
const IGNORED_STATES = new Set(['FAILED', 'INCOMPLETE']);
|
||||
const MAC_PATTERN = /^[0-9a-f]{2}(?::[0-9a-f]{2}){5}$/i;
|
||||
const INTERFACE_PATTERN = /^[a-z0-9_.:-]{1,15}$/i;
|
||||
|
||||
interface NeighborEntry extends Record<string, unknown> {
|
||||
state?: unknown;
|
||||
lladdr?: unknown;
|
||||
dev?: unknown;
|
||||
dst?: unknown;
|
||||
}
|
||||
|
||||
export interface NeighborObservation {
|
||||
ip: string;
|
||||
mac: string;
|
||||
interface: string;
|
||||
active: boolean;
|
||||
observedAt: string;
|
||||
source: 'neighbor';
|
||||
}
|
||||
|
||||
function neighborEntry(value: unknown): NeighborEntry {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as NeighborEntry
|
||||
: {};
|
||||
}
|
||||
|
||||
export function isDeviceInterface(value: unknown) {
|
||||
const name = String(value || '');
|
||||
return INTERFACE_PATTERN.test(name)
|
||||
&& name !== 'docker0' && !name.startsWith('br-') && !name.startsWith('veth');
|
||||
}
|
||||
|
||||
export function parseNeighborSnapshot(value: unknown, observedAt = new Date().toISOString()): NeighborObservation[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.flatMap((value) => {
|
||||
const entry = neighborEntry(value);
|
||||
const states = (Array.isArray(entry.state) ? entry.state : [entry.state])
|
||||
.filter(Boolean)
|
||||
.map((state: unknown) => String(state).toUpperCase());
|
||||
const mac = String(entry.lladdr || '').toLowerCase();
|
||||
const deviceInterface = String(entry.dev || '');
|
||||
if (!entry.dst || !isDeviceInterface(deviceInterface) || !MAC_PATTERN.test(mac)
|
||||
|| states.some((state) => IGNORED_STATES.has(state))) {
|
||||
return [];
|
||||
}
|
||||
return [{
|
||||
ip: String(entry.dst),
|
||||
mac,
|
||||
interface: deviceInterface,
|
||||
active: states.some((state) => ACTIVE_STATES.has(state)),
|
||||
observedAt,
|
||||
source: 'neighbor',
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
export function readNeighborSnapshot(run: typeof spawnSync = spawnSync, now = () => new Date()) {
|
||||
const observedAt = now().toISOString();
|
||||
const result = run('ip', ['-j', 'neigh', 'show'], {
|
||||
encoding: 'utf8',
|
||||
timeout: 1500,
|
||||
});
|
||||
if (result.error || result.status !== 0) {
|
||||
return {
|
||||
observedAt,
|
||||
observations: [],
|
||||
error: result.error?.message || String(result.stderr || 'ip neigh завершился с ошибкой').trim(),
|
||||
};
|
||||
}
|
||||
try {
|
||||
return {
|
||||
observedAt,
|
||||
observations: parseNeighborSnapshot(JSON.parse(result.stdout || '[]'), observedAt),
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return { observedAt, observations: [], error: `ip neigh вернул невалидный JSON: ${message}` };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user