Hallo curt,
"Muss dass so aussehen?"
Fast, da stimmt noch etwas mit dem userReading nicht, da der Status nicht 'absent' = rot oder 'present' = grün ist (alles andere ergibt den gelben Punkt).
Aber, diese Konkretisierung des notify von igami, sollte nur die Möglichkeit zeigen, dass man auf Basis von neu erkannten Netz-Klienten ein eigenes fhem device, hier als Beispiel auf Basis des dummy - Moduls erstellen kann.
Es ist der erste Schritt, nicht mehr und nicht weniger.
Der nächste Schritt ist, statt der Anlage von fhem devices auf Basis des dummy - Moduls, ein geeignetes bestehendes oder neues fhem Modul zu verwenden.
Ich habe mich vor einiger Zeit, nur zum Verstehen von fhem, 'mal an einem netClient - Modul versucht. Damals auf Basis von fing und mit der Identifikation über die MAC - Adresse.
In der Anlage stelle ich meinen auf Nmap und auf IP zur Identifikation angepassten ersten Grobentwurf eines 74_NmapClient.pm - Moduls zur ersten Demonstration meines Gedankens ein.
UNBEDINGT BEACHTEN:
ES IST NUR EIN GROBENTWURF AUSSCHLIESSLICH FÜR ENTWICKLUNGSUMGEBUNGEN ZUR DEMONSTRATION DES SCHEMAS, AUF KEINEN FALL PRODUKTIV EINSETZBAR!!!
Ich kann kein Perl, wie man unschwer erkennen kann, und, mit der XML Schnittstelle von Nmap bin ich auch nicht klargekommen!
Vielleicht kannst du oder ein anderer Perl-Kenner es optimieren/verbessern/erweitern und ein fhem - Modul daraus machen, dass entweder einzeln oder auf Basis des notify auf die neuen Geräte aus dem Nmap - Modul von igami anlegt.
Vorgehensweise zum Testen:
1. Schritt: Folgenden Code prüfen und ggf. in eine neue Datei /opt/fhem/FHEM/74_NmapClient.pm kopieren und speichern.
# $Id: 74_NmapClient.pm$
##############################################
#
# 74_NmapClient.pm
# FHEM module to check remote network device using Nmap.
#
# Author:
#
# This file is not part of fhem.
# ---
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the MIT - License.
#
##############################################################################
package main;
use strict;
use warnings;
use Blocking;
use JSON;
use Data::Dumper;
sub NmapClient_Initialize($)
{
my ($hash) = @_;
$hash->{DefFn} = "NmapClient_Define";
$hash->{UndefFn} = "NmapClient_Undefine";
$hash->{DeleteFn} = "NmapClient_Delete";
$hash->{SetFn} = "NmapClient_Set";
$hash->{GetFn} = "NmapClient_Get";
$hash->{AttrFn} = "NmapClient_Attr";
#$hash->{ReadFn} = "NmapClient_Read";
#$hash->{ReadyFn} = "NmapClient_Ready";
$hash->{NotifyFn} = "NmapClient_Notify";
#$hash->{RenameFn} = "NmapClient_Rename";
#$hash->{ShutdownFn} = "NmapClient_Shutdown";
$hash->{AttrList} = ""
."NmapDeviceName "
."PortScan:on,off "
."PortScanInterval "
."Ping:off,on "
."PingInterval "
."disable:0,1"
. $readingFnAttributes;
return undef;
}
#######################################################################################
sub NmapClient_Define($$)
{
my ($hash, $def) = @_;
my @args = split("[ \t][ \t]*", $def);
return "Usage: define <name> NmapClient <ID = Identifier>" if(@args < 3);
my $rc = `sudo nmap -V`;
my $regex = qr/Nmap version\s(.*)\s\(/p;
if ( $rc =~ /$regex/ ) {
$hash->{NmapVersion} = $1;
}
else {
return "Fehler bei der Überprüfung von Nmap!\nKann es sein, dass das Programm nicht installiert ist, oder der sudo Aufruf ohne Passwort nicht definiert ist?\nFür Debian sollte:\n sudo apt update &&sudo apt install Nmap ausreichen um Nmap zu intallieren.\nEintrag in sudoers: sudo visudo -f /etc/sudoers.d/nmap \n'fhem ALL=(ALL) NOPASSWD: /usr/bin/nmap,'";
}
my ($name, $type, $id) = @args;
$hash->{ID} = $id;
readingsSingleUpdate($hash, "state", "Initialized", 1);
$attr{$name}{"NmapDeviceName"} = "nmap" if (!defined($attr{$name}{"NmapDeviceName"}));
$attr{$name}{"PortScan"} = "off" if (!defined($attr{$name}{"PortScan"}));
$attr{$name}{"PortScanInterval"} = 60*60*24 if (!defined($attr{$name}{"PortScanInterval"}));
$attr{$name}{"Ping"} = "off" if (!defined($attr{$name}{"UpdateScan"}));
$attr{$name}{"PingInterval"} = 60*60*24 if (!defined($attr{$name}{"UpdateScanInterval"}));
$attr{$name}{"group"} = "NmapClients" if (!defined($attr{$name}{"group"}));
$attr{$name}{"room"} = AttrVal(AttrVal($name,"NmapDeviceName","nmap"),"room","nmap") if (!defined($attr{$name}{"room"}));
$attr{$name}{"devStateIcon"} = "present:10px-kreis-gruen absent:10px-kreis-rot .*:10px-kreis-gelb" if (!defined($attr{$name}{"devStateIcon"}));
NmapClient_SetNextTimer($hash,"all");
delete $hash->{helper}{PORTSCANRUNNING_PID};
delete $hash->{helper}{PINGRUNNING_PID};
return undef;
}
#######################################################################################
#######################################################################################
sub NmapClient_Notify($$) {
my ($hash, $dev_hash) = @_;
my $name = $hash->{NAME};
my $devName = $dev_hash->{NAME};
my $regex = "";
my $subst = "";
return if(IsDisabled($name)); # Return without any further action if the device is disabled
my $events = deviceEvents($dev_hash,1);
return if( !$events );
if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events}))
{
if (AttrVal($name,"PortScan","off") eq "on") {
NmapClient_SetNextTimer($hash,"PortScan");
}
if (AttrVal($name,"Ping","off") eq "on") {
NmapClient_SetNextTimer($hash,"Ping");
}
NmapClient_Basics($hash);
}
else {
foreach my $event (@{$events}) {
$event = "" if(!defined($event));
if ($devName eq AttrVal($name,"NmapDeviceName","nmap")) {
if (index($event,"done") != -1) {
NmapClient_Basics($hash);
}
}
}
}
}
#######################################################################################
sub NmapClient_Undefine($$)
{
my ($hash,$arg) = @_;
RemoveInternalTimer($hash);
BlockingKill($hash->{helper}{PORTSCANRUNNING_PID}) if(defined($hash->{helper}{SCANRUNNING_PID}));
BlockingKill($hash->{helper}{PINGRUNNING_PID}) if(defined($hash->{helper}{PINGRUNNING_PID}));
return undef;
}
#######################################################################################
#######################################################################################
sub NmapClient_Delete($$)
{
my ($hash,$arg) = @_;
RemoveInternalTimer($hash);
BlockingKill($hash->{helper}{PORTSCANRUNNING_PID}) if(defined($hash->{helper}{SCANRUNNING_PID}));
BlockingKill($hash->{helper}{PINGRUNNING_PID}) if(defined($hash->{helper}{PINGRUNNING_PID}));
return undef;
}
#######################################################################################
#######################################################################################
sub NmapClient_Get($@)
{
return undef;
}
#######################################################################################
#######################################################################################
sub NmapClient_Set($@)
{
my ($hash, $name, $cmd, @args) = @_;
my ($arg, @params) = @args;
my $list = '';
my $regex = '';
my $subst = '';
if ($cmd eq 'PortScan') {
if ($arg eq 'on') {
if (AttrVal($name,'PortScan','off') eq 'off') {
$attr{$name}{"PortScan"} = $arg;
NmapClient_StartPortScan($hash);
}
else {
Log3 ($hash, 5, "$name: $cmd is already $arg");
}
}
else {
$attr{$name}{"PortScan"} = "off";
BlockingKill($hash->{helper}{PORTSCANRUNNING_PID}) if(defined($hash->{helper}{PORTSCANRUNNING_PID}));
RemoveInternalTimer($hash,"NmapClient_StartPortScan");
}
}
elsif ($cmd eq 'PortScanInterval') {
if ($arg < 180 || $arg >60*60*24) {
$attr{$name}{"PortScanInterval"} = 60*60*24;
NmapClient_SetNextTimer($hash,"PortScan");
}
else {
$attr{$name}{"PortScanInterval"} = $arg;
NmapClient_SetNextTimer($hash,"PortScan");
}
}
elsif ($cmd eq 'Ping') {
if ($arg eq 'on') {
if (AttrVal($name,'Ping','off') eq 'off') {
$attr{$name}{"Ping"} = $arg;
NmapClient_StartPing($hash);
}
}
else {
$attr{$name}{"Ping"} = "off";
BlockingKill($hash->{helper}{PINGRUNNING_PID}) if(defined($hash->{helper}{PINGRUNNING_PID}));
RemoveInternalTimer($hash,"NmapClient_StartPing");
}
}
elsif ($cmd eq 'PingInterval') {
if ($arg < 60 || $arg >60) {
$attr{$name}{"PingInterval"} = 60*60*24;
NmapClient_SetNextTimer($hash,"Ping");
}
else {
$attr{$name}{"PingInterval"} = $arg;
NmapClient_SetNextTimer($hash,"Ping");
}
}
elsif ($cmd eq 'BasicData') {
NmapClient_Basics($hash);
}
elsif ($cmd eq 'disable') {
if ($arg eq '1') {
if (AttrVal($name,'disable','0') eq '0') {
$attr{$name}{"disable"} = 1;
BlockingKill($hash->{helper}{PORTSCANRUNNING_PID}) if(defined($hash->{helper}{PORTSCANRUNNING_PID}));
RemoveInternalTimer($hash,"NmapClient_StartPortScan");
BlockingKill($hash->{helper}{PINGRUNNING_PID}) if(defined($hash->{helper}{PINGRUNNING_PID}));
RemoveInternalTimer($hash,"NmapClient_StartPing");
}
}
else {
if (AttrNum($name,"PortScanInterval",0) > 60*60*24 || AttrNum($name,"PortScanInterval",0) < 180) {
$attr{$name}{"PortScanInterval"} = 60*60*24;
NmapClient_SetNextTimer($hash,"PortScan");
}
else {
NmapClient_SetNextTimer($hash,"PortScan");
}
}
}
else {
$list = ""
."Disable:1,0"
." BasicData:noArg"
." PortScan:on,off"
." PortScanInterval"
." Ping:on,off"
." PingInterval";
return "Unknown argument $cmd, choose one of $list";
}
}
#######################################################################################
#######################################################################################
sub NmapClient_Attr($$$$)
{
# Übergabeparamter auslesen
my ($command,$name,$attribute,$val) = @_;
# Einlesen des (device-)hash'es über den (als Paramter übergebenen device-)name
my $hash = $defs{$name};
# Beginn der Auswertung des ausgeführten Befehls:
# Wenn der Befehl "set" ist:
if ($command eq "set") {
return fhem( "set $name disable $val" ) if ($attribute eq "disable");
return fhem( "set $name PortScanInterval $val" ) if ($attribute eq "PortScanInterval");
return fhem( "set $name PortScan $val" ) if ($attribute eq "PortScan");
return fhem( "set $name PingInterval $val" ) if ($attribute eq "PingInterval");
return fhem( "set $name Ping $val" ) if ($attribute eq "Ping");
return fhem( "set $name BasicData" ) if ($attribute eq "BasicData");
}
# Sonst nur den Attributwert ändern
else {
$attr{$name}{$command} = $val;
# Eintrag im Log (Level 5):
Log3 ($hash, 5, "$hash->{NAME}_Attr: $attribute auf Value: ".$val);
}
return undef;
}
#######################################################################################
#######################################################################################
sub NmapClient_SetNextTimer($$)
{
my ($hash,$scanmode) = @_;
my $name = $hash->{NAME};
my $functionName = "";
my $rc = "";
my $interval = "";
if (!$scanmode) {
my $scanmode = "";
}
if ($scanmode eq "PortScan" || $scanmode eq "Ping" || $scanmode eq "all") {
if ($scanmode eq "PortScan" || $scanmode eq "all") {
$functionName = "NmapClient_StartPortScan";
$interval = AttrVal($name,"PortScanInterval",60*60*24);
if (ReadingsVal($name,"PortScan","off") eq "off") {
RemoveInternalTimer($hash,$functionName);
return;
}
RemoveInternalTimer($hash,$functionName);
InternalTimer(gettimeofday() + $interval, $functionName, $hash, 0);
$rc = "Next timer ".$functionName.": ".FmtDateTime(gettimeofday() + $interval)."\n";
}
if ($scanmode eq "Ping" || $scanmode eq "all") {
$functionName = "NmapClient_StartPing";
$interval = AttrVal($name,"PingInterval",60*60*24);
if (ReadingsVal($name,"Ping","off") eq "off") {
RemoveInternalTimer($hash,$functionName);
return;
}
RemoveInternalTimer($hash,$functionName);
InternalTimer(gettimeofday() + $interval, $functionName, $hash, 0);
$rc = "Next timer ".$functionName.": ".FmtDateTime(gettimeofday() + $interval)."\n";
}
return;
}
else {
RemoveInternalTimer($hash);
}
}
#######################################################################################
#######################################################################################
sub NmapClient_StartPortScan($)
{
my ($hash) = @_;
return undef if (IsDisabled($hash->{NAME}));
my $name = $hash->{NAME};
return "PortScan = off!" if (AttrVal($name,"PortScan","off") eq "off");
my $ip = ReadingsVal($name,"IP",undef);
my $arg = $name."|".$ip;
my $portname = "";
my $blockingFn = "NmapClient_PortScan";
my $finishFn = "NmapClient_PortScanDone";
my $abortFn = "NmapClient_PortScanAbort";
if (!(exists($hash->{helper}{PORTSCANRUNNING_PID}))) {
for (my $i=0; $i <= 65000; $i++) {
$portname = "PORT".substr("00000".$i, -5, 5);
readingsSingleUpdate($hash, $portname, "closed", 1) if (defined(ReadingsVal($name,$portname,undef)));
}
$hash->{helper}{PORTSCANRUNNING_PID} = BlockingCall($blockingFn,$arg,$finishFn,600,$abortFn,$hash);
Log3 $hash, 3, $hash->{NAME}." PortScan job run with PID: ".$hash->{helper}{PORTSCANRUNNING_PID}{pid}."!";
}
else {
Log3 $hash, 3, $hash->{NAME}." Blocking Call PortScan is active, no new job started!";
NmapClient_SetNextTimer($hash,"PortScan");
}
}
#######################################################################################
#######################################################################################
sub NmapClient_PortScan($)
{
my ($string) = @_;
my ($name, $ip) = split("\\|", $string);
my $hash = $defs{$name};
my $result = "";
my $rcCmd = "";
my $scannedPorts = "";
my $portPrefix = "";
my $nmapCommand = "sudo nmap ".$ip;
$result = `$nmapCommand`;
#print $result;
#my $regex = qr/Starting[\w\W]*at (?P<lastScan>.*)\sCE[\w\W]*Nmap scan report for (?P<netName>.*)\s\((?P<ip>.*)\)\s[\w\W]*up \((?P<latency>.*)\slatency[\w\W]*SERVICE\s*(?P<ports>.*[\w\W]*)\n\n[\w\W]*scanned\sin\s(?P<elapsed>.*)\sseconds/p;
my $regex = qr/Starting[\w\W]*at (?P<lastScan>.*)\sCE[\w\W]*Nmap scan report for (?P<netName>.*)\s[\w\W]*up \((?P<latency>.*)\slatency[\w\W]*SERVICE\s*(?P<ports>.*[\w\W]*)\n[\w\W]*scanned\sin\s(?P<elapsed>.*)\sseconds/p;
if ( $result =~ /$regex/ ) {
$rcCmd = "deletereading $name PortScan.*;"
."setreading $name PORTScan_lastScan ".$+{lastScan}.";"
."setreading $name PORTScan_Name ".$+{netName}.";"
# ."setreading $name PORTScan_IP ".$+{ip}.";"
."setreading $name PORTScan_latency ".$+{latency}.";"
."setreading $name PORTScan_Time ".$+{elapsed}.";";
}
$scannedPorts = $+{ports};
if (!$scannedPorts || $scannedPorts eq "") {
$rcCmd = $rcCmd."setreading PORTScan_OpenPorts 0;";
return $name."|".$rcCmd;
}
$regex = qr/(?P<line>.*)\s?/p;
my $portlist = "<html><table>";
my @ports = "";
my $i = 0;
$rcCmd = $rcCmd."deletereading $name PORT.*;";
my @matches = ($scannedPorts =~ /$regex/g);
foreach my $xline (@matches) {
if (index($xline,"MAC Address:") == -1) {
$regex = qr/(?P<port_ID>.*)\/(?P<port_Name>.*) [\s]*(?P<port_State>.*) \s(?P<port_Description>.*)\s?/p;
if ( $xline =~ /$regex/g ) {
my $portPrefix = "PORT".substr("00000".$+{port_ID}, -5, 5);
$rcCmd = $rcCmd.""
."setreading ".$name." ".$portPrefix."_ID ".$+{port_ID}.";"
."setreading ".$name." ".$portPrefix."_Name ".$+{port_Name}.";"
."setreading ".$name." ".$portPrefix."_State ".$+{port_State}.";"
."setreading ".$name." ".$portPrefix."_Description ".$+{port_Description}.";"
."";
if ($+{port_ID} eq "80") {
$rcCmd = $rcCmd.""
."setreading ".$name." ".$portPrefix." <html><table><tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"http://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr></table></html>;"
."";
$portlist = $portlist."<tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"http://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr>";
}
elsif ($+{port_ID} eq "443") {
$rcCmd = $rcCmd.""
."setreading ".$name." ".$portPrefix." <html><table><tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"https://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr></table></html>;"
."";
$portlist = $portlist."<tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"https://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr>";
}
elsif ($+{port_ID} eq "21") {
$rcCmd = $rcCmd.""
."setreading ".$name." ".$portPrefix." <html><table><tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"ftp://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr></table></html>;"
."";
$portlist = $portlist."<tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"ftp://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr>";
}
elsif ($+{port_ID} eq "445") {
$rcCmd = $rcCmd.""
."setreading ".$name." ".$portPrefix." <html><table><tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"smb://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr></table></html>;"
."";
$portlist = $portlist."<tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td><a href=\"smb://".ReadingsVal($name,"IP","")."\">".$+{port_Description}."</a></td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr>";
}
else {
$rcCmd = $rcCmd.""
."setreading ".$name." ".$portPrefix." <html><table><tr><td style=\"text-align:Right;; width:10%\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td>".$+{port_Description}."</td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr></table></html>;"
."";
$portlist = $portlist."<tr><td style=\"text-align:Right\">".$+{port_ID}."</td><td style=\"text-align:Center;; width:10%\">".$+{port_Name}."</td><td>".$+{port_Description}."</td><td style=\"text-align:Left;; width:10%\">".$+{port_State}."</td></tr>";
}
push @ports, $+{port_ID};
$i = $i + 1;
}
}
}
$rcCmd = $rcCmd."setreading $name PORTScan_OpenPorts $i;";
$portlist = $portlist."</table></html>";
$rcCmd = $rcCmd."setreading $name PORTScan_List $portlist;";
my $temp = "";
foreach my $port (@ports) {
if ($temp eq "") {
$temp = $port;
}
else {
$temp = $temp.",".$port;
}
}
$rcCmd = $rcCmd." setreading $name PortScan_Ports $temp;";
return $name."|".$rcCmd;
}
#######################################################################################
#######################################################################################
sub NmapClient_PortScanDone($)
{
my ($string) = @_;
my ($name, $rcCmd) = split("\\|", $string);
my $hash = $defs{$name};
my $errors = AnalyzeCommandChain ($hash, $rcCmd);
if (!defined($errors)) {
Log3($name, 5,"Success for PortScan: $rcCmd !");
}
else {
Log3($name, 5, "PortScan for ".$name." causes an error: \n".$errors." in: \n".$rcCmd."\n");
}
# zum Abschluss wird die "RUNNING_PID des helpers des devices gelöscht
delete($hash->{helper}{PORTSCANRUNNING_PID});
# Aufruf der Sub-Routine zum setzten des Timers für die nächste Ausführung
NmapClient_SetNextTimer($hash,"PortScan");
# Abschluss der Sub-Routine ohne Rückgabewert
Log3 $hash, 3, $hash->{NAME}." Success for PortScan job!";
return undef;
}
#######################################################################################
#######################################################################################
sub NmapClient_PortScanAbort($)
{
my ($hash) = @_;
delete($hash->{helper}{PORTSCANRUNNING_PID});
Log3 $hash->{NAME}, 3, "BlockingCall PortScan für ".$hash->{NAME}." wurde abgebrochen";
NmapClient_SetNextTimer($hash,"PortScan");
}
#######################################################################################
#######################################################################################
sub NmapClient_StartPing($)
{
my ($hash) = @_;
return undef if (IsDisabled($hash->{NAME}));
my $name = $hash->{NAME};
return "Ping = off!" if (AttrVal($name,"Ping","off") eq "off");
my $ip = ReadingsVal($name,"IP",undef);
if (!defined(ReadingsVal($name,"IP",undef))) {
if (!$ip =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
Log3 $name, 2, "$name: IP: $ip nicht gültig!" if( $@ );
return "IP nicht gültig!";
}
}
my $arg = $name."|".$ip;
my $blockingFn = "NmapClient_Ping";
my $finishFn = "NmapClient_PingDone";
my $abortFn = "NmapClient_PingAbort";
if (!(exists($hash->{helper}{PINGRUNNING_PID}))) {
$hash->{helper}{PINGRUNNING_PID} = BlockingCall($blockingFn,$arg,$finishFn,20,$abortFn,$hash);
Log3 $hash, 5, "$hash->{NAME} Ping Auftrag wird mit PID: ".$hash->{helper}{PINGRUNNING_PID}{pid}." ausgeführt!";
}
else {
Log3 $hash, 5, "$hash->{NAME} Blocking Call Ping läuft, es wurde kein neuer gestartet!";
NmapClient_SetNextTimer($hash,"Ping");
}
}
#######################################################################################
#######################################################################################
sub NmapClient_Ping($)
{
my ($string) = @_;
my ($name, $ip) = split("\\|", $string);
my $hash = $defs{$name};
my $result = "";
my $rcCmd = "";
my $pingBefehl = "ping ".$ip." -c 5 -q &";
$result = `$pingBefehl`;
my $regex = qr/ping statistics[\w\W]*\n(?P<transmitted>.*)\spackets\stransmitted,\s(?P<received>.*)\sreceived,\s(?P<quote>.*)\spacket\sloss,\stime\s(?P<time>.*)ms\srtt[\w\W]*=\s(?P<min>.*)\/(?P<avg>.*)\/(?P<max>.*)\/(?P<mdev>.*)\sms/p;
if ( $result =~ /$regex/g ) {
$rcCmd = ""
."setreading ".$name." PING_transmitted ".$+{transmitted}.";"
."setreading ".$name." PING_received ".$+{received}.";"
."setreading ".$name." PING_quote ".$+{quote}.";"
."setreading ".$name." PING_time ".$+{time}.";"
."setreading ".$name." PING_min ".$+{min}.";"
."setreading ".$name." PING_avg ".$+{avg}.";"
."setreading ".$name." PING_max ".$+{max}.";"
."setreading ".$name." PING_mdev ".$+{mdev}.";"
}
return $name."|".$rcCmd;
}
#######################################################################################
#######################################################################################
sub NmapClient_PingDone($)
{
my ($string) = @_;
my ($name, $rcCmd) = split("\\|", $string);
my $hash = $defs{$name};
my $errors = AnalyzeCommandChain ($hash, $rcCmd);
if (!defined($errors)) {
Log3($name, 5,"Success for PortScan: $rcCmd !");
}
else {
Log3($name, 5, "PortScan for ".$name." causes an error: \n".$errors." in: \n".$rcCmd."\n");
}
# zum Abschluss wird die "RUNNING_PID des helpers des devices gelöscht
delete($hash->{helper}{PINGRUNNING_PID});
# Aufruf der Sub-Routine zum setzten des Timers für die nächste Ausführung
NmapClient_SetNextTimer($hash,"Ping");
# Abschluss der Sub-Routine ohne Rückgabewert
}
#######################################################################################
#######################################################################################
sub NmapClient_PingAbort($)
{
my ($hash) = @_;
delete($hash->{helper}{PINGRUNNING_PID});
Log3 $hash->{NAME}, 3, "BlockingCall Ping für ".$hash->{NAME}." wurde abgebrochen";
NmapClient_SetNextTimer($hash,"Ping");
}
#######################################################################################
#######################################################################################
sub NmapClient_Basics($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $nmapDeviceName = AttrVal($name,"NmapDeviceName","nmap");
my $ip = ReadingsVal($name,"IP",undef);
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash, "ALIAS", ReadingsVal($nmapDeviceName,$ip."_alias",undef), 1);
readingsBulkUpdateIfChanged($hash, "HOSTNAME", ReadingsVal($nmapDeviceName,$ip."_hostname",undef), 1);
readingsBulkUpdateIfChanged($hash, "LASTSEEN", ReadingsVal($nmapDeviceName,$ip."_lastSeen",undef), 1);
readingsBulkUpdateIfChanged($hash, "MAC", ReadingsVal($nmapDeviceName,$ip."_macAddress",undef), 1);
readingsBulkUpdateIfChanged($hash, "VENDOR", ReadingsVal($nmapDeviceName,$ip."_macVendor",undef), 1);
readingsBulkUpdateIfChanged($hash, "UPTIME", ReadingsVal($nmapDeviceName,$ip."_uptime",undef), 1);
readingsBulkUpdateIfChanged($hash, "UPTIMETEXT", ReadingsVal($nmapDeviceName,$ip."_uptimeText",undef), 1);
readingsBulkUpdateIfChanged($hash, "state", ReadingsVal($nmapDeviceName,$ip."_state",undef), 1);
readingsEndUpdate($hash, 0);
}
#######################################################################################
# NmapClient
# Kennzeichen für das Ende des fhem - Moduls
1;
=pod
=begin html
<a name="NmapClient"></a>
No english description, please look al german
=end html
=begin html_DE
<a name="NmapClient"></a>
<h3>NmapClient</h3>
<ul>
<p>Dieses helper-Modul erzeugt aus den im Nmap-Modul mit dem Programm nmap</p>
<p>gefundenen Netzwerkgeräten einzelen Geräte mit erweiterten Funktionen</p>
<a name="NmapClient_define"></a>
<p><b>Define</b></p>
<ul>
<p>Im "Normalfall" werden die Geräte durch ein notify automatisch angelegt</p>
<p><code>defmod nmapClientAddNew notify nmap:new.host:..+ {\
my $newDeviceName = "";;\
my $ip = "";;\
my $hostname = "";;\
my $regex = qr/host:\s(?P<hostname>.*)\s\((?P<ip>.*)\)\Z/p;;\
\
if ( $EVENT =~ /$regex/g ) \
{\
$hostname = $+{hostname};;\
$ip = $+{ip};;\
}\
$regex = qr/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/p;;\
if ( $ip =~ /$regex/ ) \
{\
$regex = qr/\./p;;\
my ($A,$B,$C,$D) = split(/\./, $ip);;\
$newDeviceName = "nc_".substr("000".$A, -3, 3)."_".substr("000".$B, -3, 3)."_".substr("000".$C, -3, 3)."_".substr("000".$D, -3, 3);;\
}\
else\
{\
$newDeviceName = "nc_".$hostname;;\
return "No valid IP!";;\
}\
fhem("defmod ".$newDeviceName." NmapClient ".$newDeviceName);;\
fhem("setreading ".$newDeviceName." IP ".$ip);;\
fhem("attr ".$newDeviceName." NmapDeviceName ".$NAME);;\
if ($ip ne $hostname) \
{\
fhem("attr ".$newDeviceName." alias ".$hostname);;\
} \
}
attr nmapClientAddNew room nmap</code>
<p><code>define <name> NmapClient <"nc_".ID = MAC-Adress/IP-Adresse></code></p>
<p>definiert das NmapClient device.<br/>
<nc_MAC-Adresse/IP-Adressse> sollte als Indentifikation eingegeben werden</p>
</ul>
<a name="NmapClient_readings"></a>
<p><b>Readings</b></p>
<ul>
<li>
<b>state</b><br/>
[initialized|present|absent]: Zeigt den aktuellen Status.
</li>
<li>
<b>ALIAS | HOSTNAME | IP | LASTSEEN | UPTIME | UPTIMETEXT | MAC | VENDOR </b><br/>
Zeigen die Daten aus dem anlegendem Gerät auf Basis des Nmap - Moduls
</li>
<li>
<b>PING_<Name></b><br/>
Zeigen die Daten aus dem mit <code>set <device> Ping on</code> ausgeführten Ping-Befehls.
</li>
<li>
<b>PORT<00000></b><br/>
Zeigen die offenen Ports aus dem mit <code>set <device> PortScan on</code> ausgeführten nmap-Befehls.
</li>
<li>
<b>PortScan_<Name></b><br/>
Zeigen die Daten aus dem mit <code>set <device> PortScan on</code> ausgeführten Ping-Befehls.
</li>
</ul>
<a name="NmapClient_attr"></a>
<p><b>Attributes</b></p>
<ul>
<li>
<b>PORTScanInterval</b><br/>
Default: 60*60*24. Time after the connection is re-checked.
</li>
<li>
<b>... be continued</b><br/>
... .
</li>
</ul>
</ul>
=end html_DE
=cut
2. Schritt: FHEM über die FHEM-Befehlszeile neu starten:
shutdown restart
3.Schritt: Test mit der manuellen Anlage eines devices auf Basis des NmapClient - Moduls. (ggf. Fehler aus der aktuellen Fhem - LogDatei bereinigen)
define nc_123_123_123_123 NmapClient 123_123_123_123
4. Schritt: Alle Test NmapClient - Devices und alte dummy - Devices löschen z.B. mit den Befehlen in der FHEM Befehlszeile:
delete nc_123_123_123_123
delete 192_168_.*
5. Schritt: Ebenfalls in der FHEM Befehlszeile die "OldReadings" und "Readings" löschen (bitte "Netzwerk" ggf. durch den korrekten Nmap-Device-Namen ersetzen:
set Netzwerk deleteOldReadings 1
set Netzwerk clear Readings
6. Schritt: Nur, wenn die Anlage des Test-Devices funktioniert hat! Nachstehendes notify in FHEM anlegen (raw Definition) und in der 1. Zeile hinter "... notify " den Device Namen "Netzwerk" durch den individuell vergebenen Namen für das Nmap device ersetzen:
defmod nmapClientAddNew notify Netzwerk:new.host:..+ {\
my $newDeviceName = "";;\
my $ip = "";;\
my $hostname = "";;\
my $regex = qr/host:\s(?P<hostname>.*)\s\((?P<ip>.*)\)\Z/p;;\
\
if ( $EVENT =~ /$regex/g ) \
{\
$hostname = $+{hostname};;\
$ip = $+{ip};;\
}\
$regex = qr/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/p;;\
if ( $ip =~ /$regex/ ) \
{\
$regex = qr/\./p;;\
my ($A,$B,$C,$D) = split(/\./, $ip);;\
$newDeviceName = "nc_".substr("000".$A, -3, 3)."_".substr("000".$B, -3, 3)."_".substr("000".$C, -3, 3)."_".substr("000".$D, -3, 3);;\
}\
else\
{\
$newDeviceName = "nc_".$hostname;;\
return "No valid IP!";;\
}\
fhem("defmod ".$newDeviceName." NmapClient ".$newDeviceName);;\
fhem("setreading ".$newDeviceName." IP ".$ip);;\
fhem("attr ".$newDeviceName." NmapDeviceName ".$NAME);;\
if ($ip ne $hostname) \
{\
fhem("attr ".$newDeviceName." alias ".$hostname);;\
} \
}
attr nmapClientAddNew room nmap
7. Schritt: Warten, bis das Nmap Device turnusmäßig aktualisiert wird, oder über die FHEM - Befehlszeile den "StatusRequest" manuell erzwingen:
set Netzwerk statusRequest
Nachdem Abschluss des Nmap statusRequest sollten im Raum nmap in der Gruppe NmapClients alle derzeit erkennbaren Netzwerkgeräte angelegt sein.
Sie haben zunächst nur 3 rudimentäre Funktionen:
1. BasicData: Daten aus dem Nmap Device abfragen (erfolgt auch automatisch, wenn das Ereignis "Nmap .... done" auftritt)
2. PortScan: ausführen des Befehls nmap <ip> und einlesen des Outputs in Readings (dauert mit unter mehrere Minuten!)
3. Ping: ausführen des Befehls ping <ip> und einlesen des Outputs in Readings
Dazu kann die Vorgabe 1 x täglich als Interval sowohl für den PortScan als auch für Ping geändert werden.
Diese Intervalle werden nur aktiv, wenn die Funktion PortScan oder Ping auf "on" gesetzt sind.
Wird Ping oder PorScan erstmalig auf on gesetzt, wird der entsprechende Befehl zunächst aus geführt und anschliessend ein Timer auf Basis des entsprechenden Intervalls für die nächste Ausführung gesetzt.
Ich hoffe ich konnte in ersten Ansätzen zeigen, was ich mir vorstellte und du erkennst die Möglichkeiten.
Ich bin gespannt, was ein Perl Programmierer daraus macht und welche weiteren Funktionen möglich sind.
Gernot
P.S.bzw. Off-Topic: Ich meinte nicht, dass man den Zustand eines Servers von aussen nicht sehen sollte, sondern, dass das was überwacht wird, der Server weitgehend selbst liefert (z.B. ausgewählte Auszüge aus log Dateien bzw. der Zustand ausgewählter Dienste oder ausgewählter Hardware - Werte). Also weder telnet noch sonst eine Schnittstelle für die Ausführung von Befehlen auf dem Server öffnen.