Status und Statistiken von AdGuard in Fhem anzeigen

Begonnen von Ralli, 05 März 2023, 12:24:21

Vorheriges Thema - Nächstes Thema

Ralli

Hallo zusammen,

inspiriert von dem Thread https://forum.fhem.de/index.php/topic,84031.0.html habe ich mir die Codeschnipsel von Benni "geklaut" und für AdGuard adaptiert. Wer AdGuard noch nicht kennt: es ist eine Alternative zu Pi-hole mit einigen ergänzenden Features, siehe https://adguard.com/de/adguard-home/overview.html . Ich nutze bei mir beide Systeme parallel. Die hier beschriebenen Funktionalitäten beruhen auf der API Version 107.

Mittels HTTPMOD habe ich folgendes Device gebaut:

defmod adguard HTTPMOD none 60
attr adguard alias AdGuard
attr adguard devStateIcon {adguardDevStateIcon($name)}
attr adguard enforceGoodReadingNames 1
attr adguard event-on-change-reading .*
attr adguard extractAllJSONFilter num_.*|avg_processing_time|running|protection_.*|version|new_version|dns_queries_(0|6|29|89)$|blocked_filtering_(0|6|29|89)$|replaced_parental_(0|6|29|89)$|replaced_safebrowsing_(0|6|29|89)$
attr adguard getHeader Authorization: Basic %%base64_auth%%
attr adguard get01ExtractAllJSON 1
attr adguard get01Name summary
attr adguard get01Poll 1
attr adguard get01URL %%base_url%%/stats
attr adguard get02ExtractAllJSON 1
attr adguard get02Name status
attr adguard get02Poll 1
attr adguard get02URL %%base_url%%/status
attr adguard get03JSON enabled
attr adguard get03Name safebrowsing_enabled
attr adguard get03Poll 1
attr adguard get03URL %%base_url%%/safebrowsing/status
attr adguard get04JSON enabled
attr adguard get04Name safesearch_enabled
attr adguard get04Poll 1
attr adguard get04URL %%base_url%%/safesearch/status
attr adguard get05JSON enabled
attr adguard get05Name parental_enabled
attr adguard get05Poll 1
attr adguard get05URL %%base_url%%/parental/status
attr adguard get06JSON enabled
attr adguard get06Name filtering_enabled
attr adguard get06Poll 1
attr adguard get06URL %%base_url%%/filtering/status
attr adguard get07Data {"recheck_now":true}
attr adguard get07Header Authorization: Basic %%base64_auth%%\
Content-Type: application/json
attr adguard get07JSON new_version
attr adguard get07Name new_version
attr adguard get07Poll 1
attr adguard get07PollDelay 3600
attr adguard get07URL %%base_url%%/version.json
attr adguard icon security
attr adguard replacement01Mode text
attr adguard replacement01Regex %%base_url%%
attr adguard replacement01Value http://%%ip%%/control
attr adguard replacement02Mode text
attr adguard replacement02Regex %%ip%%
attr adguard replacement02Value HIER_DEINE_IP_VON_ADGUARD
attr adguard replacement03Mode text
attr adguard replacement03Regex %%base64_auth%%
attr adguard replacement03Value DEIN_USERNAME_PASSWORT_BASE64_ENCODED
attr adguard room System
attr adguard setHeader Authorization: Basic %%base64_auth%%
attr adguard set02Method POST
attr adguard set02Name systemupdate
attr adguard set02NoArg 1
attr adguard set02URL %%base_url%%/update
attr adguard set03Hint enable,disable
attr adguard set03Method POST
attr adguard set03Name safebrowsing
attr adguard set03TextArg 1
attr adguard set03URL %%base_url%%/safebrowsing/$val
attr adguard set04Data {"enabled":$val, "bing":$val, "duckduckgo":$val, "google":$val, "pixabay":$val, "yandex":$val, \
 "youtube":$val}
attr adguard set04Header Authorization: Basic %%base64_auth%%\
Content-Type: application/json
attr adguard set04Hint false,true
attr adguard set04Method PUT
attr adguard set04Name safesearch
attr adguard set04TextArg 1
attr adguard set04URL %%base_url%%/safesearch/settings
attr adguard set05Hint enable,disable
attr adguard set05Method POST
attr adguard set05Name parental
attr adguard set05TextArg 1
attr adguard set05URL %%base_url%%/parental/$val
attr adguard set06Data {"enabled":$val , "interval":1}
attr adguard set06Header Authorization: Basic %%base64_auth%%\
Content-Type: application/json
attr adguard set06Hint false,true
attr adguard set06Method POST
attr adguard set06Name filtering
attr adguard set06TextArg 1
attr adguard set06URL %%base_url%%/filtering/config
attr adguard set07Data {"enabled":true}
attr adguard set07Header Authorization: Basic %%base64_auth%%\
Content-Type: application/json
attr adguard set07Method POST
attr adguard set07Name enable
attr adguard set07NoArg 1
attr adguard set07URL %%base_url%%/protection
attr adguard set08Data {"enabled":false, "duration":$val}
attr adguard set08Header Authorization: Basic %%base64_auth%%\
Content-Type: application/json
attr adguard set08Method POST
attr adguard set08Name disable
attr adguard set08TextArg 1
attr adguard set08URL %%base_url%%/protection
attr adguard stateFormat {ReadingsVal($name,"protection_enabled",0)==1?"enabled":"disabled"}
attr adguard userReadings ads_percentage_today:num_.* {my $p = ReadingsNum($name,"num_dns_queries",0)/100;; (ReadingsNum($name,"num_blocked_filtering",0)+ReadingsNum($name,"num_replaced_parental",0)+ReadingsNum($name,"num_replaced_safebrowsing",0))/$p}

In der o.a. Definition müsst ihr DEIN_USERNAME_PASSWORT_BASE64_ENCODED durch die Base64-codierte Kombination eures Nutzernamen und Passwortes für AdGuard ersetzen. Mit Linux geht das z.B. so:

echo -n "benutzername:passwort" | base64

Dann müsst ihr noch HIER_DEINE_IP_VON_ADGUARD durch die IP-Adresse eures AdGuard-Systems ersetzen.

Um das hübsche Aussehen im Webfrontend zu bekommen, müsst ihr folgende Funktion in 99_myUtils.pm eintragen:

sub adguardDevStateIcon
{
my $name = shift;
my $noHeader=shift;

$noHeader=0 if(!defined($noHeader));

#get %%base_url%% replacement from httpmod device (using HTTPMOD method)
# -> this is the url to the web-ui of AdGuard
my $baseURL= HTTPMOD::DoReplacement($defs{$name},'get','%%base_url%%');
        $baseURL =~ s/\/control//;
#get required readings from adguard httpmod device
my $state = (ReadingsNum($name,'protection_enabled',0)==1?"enabled":"disabled");
my $totalQueries = sprintf("%07d",ReadingsNum($name,'num_dns_queries',0));
my $blocked = sprintf("%06d",ReadingsNum($name,'num_blocked_filtering',0));
my $blockedPercentage = round(ReadingsNum($name,'ads_percentage_today',0),1);
# my $adListCount =ReadingsVal($name,'domains_being_blocked','0');
   
        my  $ver_info = 'Version: '.ReadingsVal($name,'version','unknown').' => '.ReadingsVal($name,'new_version','unknown');
   
        my $anyUpd=0;
        $anyUpd=1 if (ReadingsVal($name,'new_version','unknown') ne ReadingsVal($name,'version','unknown'));
   

#prepare dedicated css
#hint: html of device overview is by default embedded in a <div> container
#      which has id attribute set to device's NAME
my  $styleClass ='<style>';
        $styleClass.='#'.$name.' svg {height:15px; width:15px; padding-right:1px;} ';
        $styleClass.='#'.$name.' td {padding-left:10px; padding-right:10px} ';
        $styleClass.='</style>';

#prepare images
my $imgQueries=FW_makeImage('it_i-net@cyan');
my $imgBlocked=FW_makeImage('time_manual_mode@red');
my $imgState  =FW_makeImage($state eq 'enabled' ? '15px-green' : '15px-red');
        my $imgUpdate =FW_makeImage($anyUpd > 0 ? '15px-red' : '15px-green',$ver_info);   

#prepare disable/enable command
my $cmd='';
my $callMe='<a href="/fhem?cmd=set '.$name;
        my $annex=urlEncode(';defmod at_adguard at +00:00:01 {fhem("get '.$name.' status",1)}');
if($state eq 'disabled') {
$callMe .=' enable'.$annex;
$cmd =$callMe.'">Enable</a>';
} else {
$callMe .= urlEncode(' disable ');
$cmd  =       $callMe.'60000'.$annex.'">1</a>';
$cmd .= ' | '.$callMe.'300000'.$annex.'">5</a>';
$cmd .= ' | '.$callMe.'600000'.$annex.'">10</a>';
$cmd .= ' | '.$callMe.'900000'.$annex.'">15</a>';
}

#finally build the html for device overview
my $ret  = $styleClass;
$ret .= '<table border=0"><tr>';
#if second argument of this method call is true (=1)
#then do NOT create a table header. Defaults to false (=0)
if(!$noHeader) {    
$ret .= '<th>State</th>';
$ret .= '<th>Queries</th>';
$ret .= '<th>Blocked</th>';
#$ret .= '<th>Adlist</th>';    
$ret .= '<th>'.($state eq 'enabled' ? 'Disable [Minutes]':'Disabled').'</th>';
$ret .= '<th>Upd</th>';           
$ret .= '</tr><tr>';
}    
        $ret .= '<td><a href="'.$baseURL.'" target="_blank">'.$imgState.'</a></td>';
        $ret .= '<td>'.$imgQueries.' '.$totalQueries.'</td>';
        $ret .= '<td>'.$imgBlocked.' '.$blocked.' ('.$blockedPercentage.'%)'.'</td>';
        #$ret .= '<td>'.$imgAdList.' '.$adListCount.'</td>';
        $ret .= '<td>'.$cmd.'</td>';
        if ($anyUpd > 0) {
                $ret .= '<td><a href="/fhem?cmd.'.$name.'=set '.$name.' systemupdate">'.$imgUpdate.'</a></td>';
                fhem("setreading ".$name." available_updates 1")

        } else {
                $ret .= '<td>'.$imgUpdate.'</td>';
                fhem("setreading ".$name." available_updates 0")
        }
        $ret .= '</tr></table>';

return $ret;
}

Es geht bestimmt alles noch einfacher und hübscher, aber es ist ein Anfang und eine funktionierende Adapation der Einbindung von Pi-hole für AdGuard.

Wer deutlich mehr Daten im Device haben möchte, muss lediglich das Attribut

attr adguard extractAllJSONFilter num_.*|avg_processing_time|running|protection_.*|version|new_version|dns_queries_(0|6|29|89)$|blocked_filtering_(0|6|29|89)$|replaced_parental_(0|6|29|89)$|replaced_safebrowsing_(0|6|29|89)$

löschen oder modifizieren. Dann erhält man auch Readings für die Toplisten der angefragten und blockierten Domains sowie der anfragenden Clients usw.
Gruß,
Ralli

Proxmox 8.1 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.6.20240316) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

Benni

Wenn du dein set check_new_version in ein get umbaust, kannst du dir das at sparen.

Wenn du das get dann mit get[0-9]+Poll in den Polling-Lauf von HTTPMOD aufnimmst und dann mit get[0-9]+PollDelay entsprechend nur alle x Intervalle ausführen lässt.

Die base64-Encodierung kannst du auch direkt in FHEM machen:


{encode_base64('blah')}


gb#


Ralli

#2
Zitat
Wenn du dein set check_new_version in ein get umbaust, kannst du dir das at sparen.

Wollte ich. Geht nicht, POST-Methode wird verlangt.

Zitat
Die base64-Encodierung kannst du auch direkt in FHEM machen

Danke für den Tipp - aber das wollte ich nicht, dann müsste ich ja Nutzernamen und Passwort in Fhem hineinbringen - oder man ruft die Funktion einmal manuell über Fhem auf und nimmt daraus das Ergebnis, das ginge natürlich.
Gruß,
Ralli

Proxmox 8.1 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.6.20240316) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

Benni

Du kannst ja mal mit (get|set)[0-9]*Data experimentieren um den get mit Fake-Daten als POST abzusetzen:

Zitat
(get|set)[0-9]*Data
optional data to be sent to the device as POST data when the get oer set command is executed. if this attribute is specified, an HTTP POST method will be sent instead of an HTTP GET

Zitat von: Ralli am 05 März 2023, 14:18:34
oder man ruft die Funktion einmal manuell über Fhem auf und nimmt daraus das Ergebnis, das ginge natürlich.

Ja so war's gemeint!

Eventuell kannst du aber auch storeKeyValue verwenden, dann ist der "benutzername:password" string ausgelagert und kannst beim replacment mittels Perl über encode_base64 vor dem senden encodieren.
Allerdings weiß ich jetzt auf die Schnelle nicht, ob ich beim replacement Perl "aufgelöst" wird.

gb#

Ralli

#4
Ah, ok, ich danke dir.

Die Update-Prüfung habe ich umwandeln können (get07...), im ersten Beitrag bereits alles entsprechend angepasst.
Gruß,
Ralli

Proxmox 8.1 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.6.20240316) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

yersinia

Danke, dass du deinen Code teilst. Dies ist immer Mehrwert für die Community. :)

Das wird jetzt [OT], aber aus reiner Neugier:
Zitat von: Ralli am 05 März 2023, 12:24:21Wer AdGuard noch nicht kennt: es ist eine Alternative zu Pi-hole mit einigen ergänzenden Features [...] Ich nutze bei mir beide Systeme parallel.
Warum betreibst du AGH und pi-hole parallel? Was versprichst du dir davon? Die Unterschiede sind imho nicht so groß.
Und was spricht gegen pihole mit unbound und zusätzlichen Filterlisten?
[/OT]
viele Grüße, yersinia
----
FHEM 6.3 (SVN) on RPi 4B with RasPi OS Bullseye (perl 5.32.1) | FTUI
nanoCUL->2x868(1x ser2net)@tsculfw, 1x433@Sduino | MQTT2 | Tasmota | ESPEasy
VCCU->14xSEC-SCo, 7xCC-RT-DN, 5xLC-Bl1PBU-FM, 3xTC-IT-WM-W-EU, 1xPB-2-WM55, 1xLC-Sw1PBU-FM, 1xES-PMSw1-Pl

Ralli

Redundanz. Pi-hole und AdGuard laufen jeweils in einem Container auf einem jeweils anderen physischen Host. Beide DNS-Server-Adressen werden den Clients per DHCP zugewiesen. Sollte Pi-hole oder AdGuard mal nicht laufen, weil ein Update es zerschossen hat, steht nicht direkt alles still.

Und außerdem: weil ich's kann  ;) 8)
Gruß,
Ralli

Proxmox 8.1 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.6.20240316) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

yersinia

[OT]
Zitat von: Ralli am 06 März 2023, 09:56:17Und außerdem: weil ich's kann  ;) 8)
Das reicht als Begründung. ;D

Zitat von: Ralli am 06 März 2023, 09:56:17Redundanz. Pi-hole und AdGuard laufen jeweils in einem Container auf einem jeweils anderen physischen Host. Beide DNS-Server-Adressen werden den Clients per DHCP zugewiesen. Sollte Pi-hole oder AdGuard mal nicht laufen, weil ein Update es zerschossen hat, steht nicht direkt alles still.
Das heisst auch, dass URLs, welche von AGH aber nicht von pi-hole geblockt werden (und vice versa), doch aufgelöst werden, weil der Client auf den anderen DNS zurückgreift?
Wie verhinderst du, dass Clients eigene DNS Einträge nutzen (und den vom DHCP vorgegebenen ignorieren)?

(ich frag' basierend auf Erfahrungsaustausch und wegen Tellerrand und so)
[/OT]
viele Grüße, yersinia
----
FHEM 6.3 (SVN) on RPi 4B with RasPi OS Bullseye (perl 5.32.1) | FTUI
nanoCUL->2x868(1x ser2net)@tsculfw, 1x433@Sduino | MQTT2 | Tasmota | ESPEasy
VCCU->14xSEC-SCo, 7xCC-RT-DN, 5xLC-Bl1PBU-FM, 3xTC-IT-WM-W-EU, 1xPB-2-WM55, 1xLC-Sw1PBU-FM, 1xES-PMSw1-Pl

Ralli

Zitat von: yersinia am 06 März 2023, 10:38:35
Das heisst auch, dass URLs, welche von AGH aber nicht von pi-hole geblockt werden (und vice versa), doch aufgelöst werden, weil der Client auf den anderen DNS zurückgreift?

Das hängt vom jeweiligen Client ab. Das normale Standardverhalten ist ja, dass der Client nur seinen ersten eingetragenen DNS fragt und mit seiner Antwort (auch wenn die lautet, es gibt keine IP für den Namen) zufrieden ist - erst dann, wenn der Client gar keine Verbindung zu dem ersten DNS hin bekommt, befragt er den zweiten DNS.

Zitat
Wie verhinderst du, dass Clients eigene DNS Einträge nutzen (und den vom DHCP vorgegebenen ignorieren)?

Auch hier kommt es wieder auf den jeweiligen Client an. Da es sich bei den Nutzern der hiesigen IT-Landschaft um eine "Standard-Familie" handelt, habe ich die Konfigurationsmöglichkeiten nicht weiter eingeschränkt, allerdings wäre das technisch ja mittels Policys bei Windows-Clients und ansonsten mittels der entsprechenden Userrechte bei Linux-Clients usw. ja problemlos möglich - zu viel Komplexität schien mir dann doch etwas übertrieben. Mir genügen im Moment technisch-organisatorische Maßnahmen (statistische Auswertung), um mitzubekommen, ob die vorgegebenen DNS genutzt werden oder nicht.
Gruß,
Ralli

Proxmox 8.1 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.6.20240316) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

yersinia

[OT]Danke für die Rückmeldung. Ich bin gespannt, welches Resümee du nach ~sechs Monaten Betrieb ziehst.
Ich tu' mir das nicht an mit Client-Pflege und versuche dies rein über die Vorgaben (DHCP) und Firewall (forwards auf pi-hole; rejecten aller anderen DNS Anfragen über port scope) zu lösen.
Die meisten Clients laufen (Browser-seitig) sowieso zusätzlich noch mit noscript und ublock origin. 8)
Nach dem filtern (derzeit ~8,7mio Einträge), löst pi-hole über unbound (inkl DNSSEC) die URL auf.
Nun aber b2t.[/OT]
viele Grüße, yersinia
----
FHEM 6.3 (SVN) on RPi 4B with RasPi OS Bullseye (perl 5.32.1) | FTUI
nanoCUL->2x868(1x ser2net)@tsculfw, 1x433@Sduino | MQTT2 | Tasmota | ESPEasy
VCCU->14xSEC-SCo, 7xCC-RT-DN, 5xLC-Bl1PBU-FM, 3xTC-IT-WM-W-EU, 1xPB-2-WM55, 1xLC-Sw1PBU-FM, 1xES-PMSw1-Pl