Files
Network-MGMT/app/Services/NetworkScanImporter.php
T

223 lines
7.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\NetworkDevice;
use App\Models\NetworkDeviceEvent;
use App\Models\NetworkHost;
use App\Models\NetworkScan;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class NetworkScanImporter
{
private NetworkScan $scan;
private int $newDevices = 0;
private int $changedDevices = 0;
private int $onlineHosts = 0;
/**
* Importiert eine Angry IP Scanner .txt Exportdatei.
*/
public function importAngryIpScannerFile(string $filePath, int $createdBy): NetworkScan
{
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$scanner = '';
$subnet = '';
$headers = [];
$rows = [];
foreach ($lines as $line) {
if (str_starts_with($line, 'Erstellt von') || str_starts_with($line, 'Created by')) {
$scanner = trim(str_replace(['Erstellt von ', 'Created by '], '', $line));
continue;
}
if (str_starts_with($line, 'Gescannt') || str_starts_with($line, 'Scanned')) {
// "Gescannt 192.168.86.0 - 192.168.86.255"
preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/', $line, $m);
$subnet = $m[1] ?? '0.0.0.0';
continue;
}
if (str_starts_with($line, 'http') || str_starts_with($line, 'https')) {
continue; // URL-Zeile überspringen
}
// Header-Zeile (beginnt mit "IP")
if (str_starts_with($line, 'IP') && empty($headers)) {
$headers = preg_split('/\t+/', $line);
$headers = array_map('trim', $headers);
continue;
}
// Datenzeile
if (!empty($headers) && str_starts_with(trim($line), '') && preg_match('/^\d{1,3}\./', trim($line))) {
$cols = preg_split('/\t+/', $line);
$row = [];
foreach ($headers as $i => $h) {
$row[$h] = trim($cols[$i] ?? '');
}
$rows[] = $row;
}
}
return DB::transaction(function () use ($rows, $scanner, $subnet, $createdBy) {
$this->scan = NetworkScan::create([
'subnet' => $subnet,
'source' => 'import',
'scanner' => $scanner,
'created_by' => $createdBy,
]);
foreach ($rows as $row) {
$this->processRow($row);
}
$this->scan->update([
'total_hosts' => count($rows),
'online_hosts' => $this->onlineHosts,
'new_devices' => $this->newDevices,
'changed_devices'=> $this->changedDevices,
]);
return $this->scan;
});
}
private function processRow(array $row): void
{
$ip = $this->extractColumn($row, ['IP']);
$ping = $this->extractColumn($row, ['Ping']);
$host = $this->extractColumn($row, ['Hostname']);
$mac = $this->normalizeMAC($this->extractColumn($row, ['MAC Addresse', 'MAC Address']));
$vendor = $this->extractColumn($row, ['MAC Hersteller', 'MAC Vendor']);
$ttl = (int) $this->extractColumn($row, ['TTL']) ?: null;
$ports = $this->extractColumn($row, ['Ports']);
$netbios= $this->extractColumn($row, ['NetBIOS Info']);
$http = $this->extractColumn($row, ['HTTP Sender']);
$web = $this->extractColumn($row, ['Web Erkennung', 'Web Detection']);
$pingMs = null;
$status = 'offline';
if (!empty($ping) && $ping !== 'n/a' && $ping !== '-') {
preg_match('/(\d+)/', $ping, $m);
$pingMs = isset($m[1]) ? (int)$m[1] : null;
$status = 'online';
$this->onlineHosts++;
}
// Host-Eintrag speichern
$host_record = NetworkHost::create([
'scan_id' => $this->scan->id,
'ip_address' => $ip,
'mac_address' => $mac ?: null,
'hostname' => $host ?: null,
'mac_vendor' => $vendor ?: null,
'status' => $status,
'ping_ms' => $pingMs,
'netbios_info' => $netbios ?: null,
'ttl' => $ttl,
'ports' => $ports ?: null,
'http_sender' => $http ?: null,
'web_detection' => $web ?: null,
]);
// Geräte-Master nur wenn MAC bekannt
if (!empty($mac)) {
$this->processDevice($host_record, $ip, $mac, $host, $vendor, $netbios, $ttl, $ports, $status);
}
}
private function processDevice(
NetworkHost $hostRecord,
string $ip, string $mac, ?string $hostname,
?string $vendor, ?string $netbios, ?int $ttl,
?string $ports, string $status
): void {
$device = NetworkDevice::firstOrNew(['mac_address' => $mac]);
$isNew = !$device->exists;
if ($isNew) {
$device->fill([
'current_ip' => $ip,
'hostname' => $hostname,
'mac_vendor' => $vendor,
'status' => $status,
'netbios_name' => $netbios,
'ttl' => $ttl,
'ports' => $ports,
'first_seen_at'=> now(),
'last_seen_at' => now(),
])->save();
NetworkDeviceEvent::create([
'device_id' => $device->id,
'scan_id' => $this->scan->id,
'event_type' => 'new_device',
'new_value' => $ip,
'description'=> "Erstes Erscheinen: {$ip} ({$vendor})",
]);
$this->newDevices++;
} else {
$events = [];
// IP-Änderung erkennen
if ($device->current_ip !== $ip) {
$events[] = [
'event_type' => 'ip_changed',
'old_value' => $device->current_ip,
'new_value' => $ip,
'description'=> "IP geändert von {$device->current_ip} zu {$ip}",
];
$this->changedDevices++;
}
// Online/Offline-Status
if ($device->status !== $status) {
$events[] = [
'event_type' => $status === 'online' ? 'came_online' : 'went_offline',
'old_value' => $device->status,
'new_value' => $status,
'description'=> $status === 'online'
? "Gerät wieder online ({$ip})"
: "Gerät offline ({$device->current_ip})",
];
}
$device->update([
'current_ip' => $ip,
'hostname' => $hostname ?? $device->hostname,
'status' => $status,
'last_seen_at' => now(),
]);
foreach ($events as $event) {
NetworkDeviceEvent::create(array_merge($event, [
'device_id' => $device->id,
'scan_id' => $this->scan->id,
]));
}
}
$hostRecord->update(['device_id' => $device->id]);
}
private function extractColumn(array $row, array $keys): string
{
foreach ($keys as $key) {
if (isset($row[$key]) && $row[$key] !== '') {
return $row[$key];
}
}
return '';
}
private function normalizeMAC(string $mac): string
{
// Verschiedene MAC-Formate normalisieren zu XX:XX:XX:XX:XX:XX
$clean = preg_replace('/[^a-fA-F0-9]/', '', $mac);
if (strlen($clean) !== 12) {
return '';
}
return strtoupper(implode(':', str_split($clean, 2)));
}
}