🌐 öffentliche IP ermitteln, public IPv4 and IPv6

Begonnen von Torxgewinde, 13 Juni 2025, 16:49:15

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Wenn man von FHEM die öffentliche IP ermitteln möchte kann man öffentliche Server ansurfen und die zeigen einem die gerade genutzte IP an. Ich habe mal ein paar Dienste ausprobiert, die man ohne Anmeldung nutzen kann und dazu ein HTTPMOD erstellt:

defmod PublicIP HTTPMOD none 0
attr PublicIP enforceGoodReadingNames 1
attr PublicIP extractAllJSON 1
attr PublicIP get01Name ipify.org
attr PublicIP get01URL https://api64.ipify.org/?format=json
attr PublicIP get02Name ipwho.de
attr PublicIP get02URL https://ipwho.de/json/
attr PublicIP get03Name ipinfo.io
attr PublicIP get03URL https://ipinfo.io/json/
attr PublicIP get04Name myip.com
attr PublicIP get04URL https://api.myip.com/
attr PublicIP stateFormat IP: ip

Testweise/temporär läuft das bei Cooltux: https://demo-fhem.cooltux.net/fhem?detail=PublicIP

enno

...ich bekomme diese Info von meiner Fritzbox und dem Modul geliefert.
box_ipv4_Extern
box_ipv6_Extern
box_ipv6_Prefix

Für die mit der Fritzbox arbeiten.

Gruss
  Enno
Einfacher FHEM Anwender auf Intel®NUC mit Proxmox und Debian

Jamo

Geht auch als Einzeiler:
defmod InternetIP at +*00:10:00 {readingsSingleUpdate( $defs{ $SELF }, 'IPv4', GetFileFromURL( 'https://v4.ident.me/',5,,1,0 ), 1 );return}
Bullseye auf iNUC, Homematic + HMIP(UART/HMUSB), Debmatic, HUEBridge, Zigbee/Conbee III, FB7690, Alexa (fhem-lazy), Livetracking, LaCrosse JeeLink, LoRaWan / TTN / Chirpstack, Sonos, ESPresence

betateilchen

Bei mir werden die Adressen in den %data hash geschrieben, damit sind sie FHEM-weit als Variablen verfügbar.

sub ip_test {
  use Net::Address::IP::Local;
  my $address   = eval {Net::Address::IP::Local->public};
  $data{myip}   = $address unless ($@);
  $address      = eval {Net::Address::IP::Local->public_ipv4};
  $data{myipv4} = $address unless ($@);
  $address      = eval { Net::Address::IP::Local->public_ipv6};
  $data{myipv6} = $address unless ($@);
  return;
}
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Torxgewinde

#4
Hey, danke für die vielen, alternativen Methoden.

defmod PublicIP HTTPMOD none 0
attr PublicIP enforceGoodReadingNames 1
attr PublicIP extractAllJSON 1
attr PublicIP get01Name ipify.org
attr PublicIP get01URL https://api64.ipify.org/?format=json
attr PublicIP get02Name ipwho.de
attr PublicIP get02URL https://ipwho.de/json/
attr PublicIP get03Name ipinfo.io
attr PublicIP get03URL https://ipinfo.io/json/
attr PublicIP get04Name myip.com
attr PublicIP get04URL https://api.myip.com/
attr PublicIP get05Name ident.me
attr PublicIP get05URL https://a.ident.me/json
attr PublicIP stateFormat IP: ip

Wenn man es kompakt haben möchte und man sich für einen Anbieter entscheiden kann (sagen wir mal ident.me, alle zehn Sekunden abfragen) wird die HTTPMOD Definition natürlich auch kürzer:
defmod PublicIP HTTPMOD https://a.ident.me/json 10
attr PublicIP extractAllJSON 1
attr PublicIP stateFormat IP: ip

@Jamo: GetFileFromURL() könnte in der Konfig bis zu fünf Sekunden blockieren, was erstmal nicht so wild ist, aber bei einer Funktion die alle zehn Minuten aufgerufen wird dann ggf. doch mal stört. Den Anbieter aus deinem Snippet habe ich mit aufgenommen, danke!

@enno: Klar, wenn man eine Fritzbox hat ist die Info IMHO sogar zu bevorzugen, da man dann nicht so sehr auf externe Dienste angewiesen ist (die ggf. auch mal ausfallen können). Falls die eigene Fritzbox ausfällt, ist man dann eh nicht im Internet und hat ganz andere Probleme :-)

@betateilchen: Das ist natürlich erstmal jeweils die lokale Adresse, ob einem das hilft hängt vom Netzwerk ab. Die öffentliche Adresse IPv4 Adresse ist häufig eine andere (bei NAT), bei IPv6 kann es ggf. identisch sein. Du weist das bestimmt, aber falls mal hier jemand drüber stolpert schreibe ich es lieber mit dran.


Soll ich das mal in eine Wiki-Seite packen, oder gibt es da schon eine?

betateilchen

Zitat von: Torxgewinde am 13 Juni 2025, 20:03:39Soll ich das mal in eine Wiki-Seite packen, oder gibt es da schon eine?

Das Thema wurde im Forum schon mehrfach, z.B. in 2013 behandelt:

https://forum.fhem.de/index.php?topic=12918.0

Das ist m.E. völlig ausreichend.


(offtopic)

defmod PublicIP HTTPMOD https://a.ident.me/json 10Eine externe URL alle 10 Sekunden aufzurufen, finde ich komplett unsinnig.
Dann lieber auf Betriebssystemebene den IP Wechsel feststellen lassen und FHEM darüber informieren.

Und warum eigentlich HTTPMOD, wenn man in der url schon angibt, dass man json zurückhaben möchte? Dann ist doch JsonMod das Modul der Wahl. Aber vermutlich nutzt Du auch Excel zum Briefeschreiben  8)


--
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Torxgewinde

@betateilchen
Danke für den Link – aber der Thread ist über zehn Jahre alt, und ein nennenserter Teil der damals verlinkten Dienste ist inzwischen offline. Insofern ist das Thema durchaus wieder bzw. immer noch aktuell.

Was den Ton betrifft: Fachliche Kritik ist immer willkommen, aber persönliche Spitzen wie der Excel-Vergleich helfen der Diskussion nicht weiter.

betateilchen

Zitat von: Torxgewinde am 13 Juni 2025, 21:28:04Danke für den Link – aber der Thread ist über zehn Jahre alt, und ein nennenserter Teil der damals verlinkten Dienste ist inzwischen offline.

Aber an den damals beschriebenen Lösungswegen hat sich auch nach 10 Jahren wenig bis nichts geändert. Und darauf kommt es doch an. Wenn Du jetzt einen Wiki-Artikel schreibst, sind die darin enthaltenen Informationen in 10 Jahren vermutlich genau so veraltet.



Zitat von: Torxgewinde am 13 Juni 2025, 20:03:39Die öffentliche Adresse IPv4 Adresse ist häufig eine andere (bei NAT)

ok, aber auch das ist lösbar:

sub myip {
  use STUN::Client;
  my $stun_client = STUN::Client->new;
  $stun_client->stun_server('stun.ekiga.net');
  my (undef, $result) = $stun_client->get;
  return $result->{'attributes'}->{'0001'}->{'address'};
}

Meine Server laufen inzwischen alle ausschließlich mit IPv6, deshalb hatte ich das Thema NAT komplett verdrängt.



Zitat von: Torxgewinde am 13 Juni 2025, 21:28:04Was den Ton betrifft

Oh, sind wir hier im VHS-Kurs "wir töpfern einen Aschenbecher für Vati?"

Zitat von: Torxgewinde am 13 Juni 2025, 21:28:04Fachliche Kritik ist immer willkommen,

Die zwei Sätze vor dem augenzwinkernden Excel-Vergleich waren durchaus als ernstgemeinte fachliche und sachliche Kritik gedacht.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

passibe

Zitat von: betateilchen am 13 Juni 2025, 21:53:35ok, aber auch das ist lösbar:
Naja gut, damit hat man jetzt nicht wirklich viel gewonnen. Ob man sich das nun extern per HTTP(S)-Request holt oder – ebenfalls extern – vom STUN-Server ...

Mit HTTPS ist das Zeug wenigstens verschlüsselt (und vermutlich ist auch das error handling von HTTPMOD ootb besser).

Torxgewinde

#9
Ich habe die Infos als Wikiseite zusammengefasst: https://wiki.fhem.de/wiki/%C3%96ffentliche_IP-Adresse

Damit man nichts extra auf einem typischem Debian installieren muss, habe ich ein AT-Device, dass die IPs des FHEM Servers abfragt und auflistet, ergänzt. Als Testdevice ist es temporär bei Cooltux: https://demo-fhem.cooltux.net/fhem?detail=LocalIPs

define LocalIPs at +*00:01:00 {\
    my $hash = $defs{$SELF};;\
    my (@ipv4, @ipv6);;\
    \
    #Abfragen der IP Adressen mittels CLI:\
    my $json = qx(ip -j addr show scope global);;\
    my $data = decode_json($json);;\
    \
    for my $iface (@$data) {\
        for my $addr (@{$iface->{addr_info} || []}) {\
            next unless $addr->{local};;\
            if ($addr->{family} eq 'inet') {\
                push @ipv4, $addr->{local};;\
            } elsif ($addr->{family} eq 'inet6') {\
                push @ipv6, $addr->{local};;\
            }\
        }\
    }\
    \
    readingsBeginUpdate($hash);;\
    readingsBulkUpdate($hash, "allIPv4", join(',', @ipv4));;\
    readingsBulkUpdate($hash, "allIPv6", join(',', @ipv6));;\
    readingsEndUpdate($hash, 1);;\
}
attr LocalIPs alignTime 00:00:00
attr LocalIPs event-on-change-reading .*
attr LocalIPs stateFormat IPv4: allIPv4<br />IPv6: allIPv6

passibe

Cool, danke!
Vielleicht eine kleine Anmerkung: Mir ist gerade aufgefallen, dass es bei meiner Fritzbox keine Readings mit box_ip* gibt (6660 Cable, Mietgerät von Vodafone, im ex KabelBW/Unitymedia-Gebiet).

Müsste man vermutlich im Fritzbox-Modul anpassen, hat mich bis jetzt aber noch nicht gestört, weil ich die IP in FHEM nicht brauche.
Ich frage die IPv6-GUA außerhalb von FHEM über folgendes cURL ab:

#!/bin/bash

FRITZ_USER="<USER>"
FRITZ_PASS="<PASS>"
FRITZ_HOSTNAME="<HOST>"

FRITZ_IPV6_GUA=$(curl -s --max-time 10 \
    --anyauth --user "${FRITZ_USER}:${FRITZ_PASS}" \
    http://${FRITZ_HOSTNAME}:49000/upnp/control/wanipconnection1 \
    -H "Content-Type: text/xml; charset="utf-8"" \
    -H "SoapAction:urn:dslforum-org:service:WANIPConnection:1#GetExternalIPAddress" \
    -d "<?xml version='1.0' encoding='utf-8'?> <s:Envelope s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'> <s:Body> <u:GetExternalIPAddress xmlns:u='urn:dslforum-org:service:WANIPConnection:1'></u:GetExternalIPAddress> </s:Body> </s:Envelope>" | egrep -o "(::)?[0-9a-fA-F]{1,4}(::?[0-9a-fA-F]{1,4}){1,7}(::)?")
if [ -z "$FRITZ_IPV6_GUA" ]; then
    echo "Error! Can't get IPv6 GUA from FRITZ!Box"
    exit 0
else
    echo "FRITZ!Box IPv6 GUA is: $FRITZ_IPV6_GUA"
fi

Erinnere mich aber nicht mehr, woher ich den Schnipsel habe. Weiß auch nicht, wie man da die IPv4 kriegt, ist bei DS-lite aber sowieso egal. Das volle XML, das man zurückkriegt (wenn man egrep weglässt) lautet:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetExternalIPAddressResponse xmlns:u="urn:dslforum-org:service:WANIPConnection:1">
<NewExternalIPAddress>2a02:xxxx:xxx::xxx</NewExternalIPAddress>
</u:GetExternalIPAddressResponse>
</s:Body>
</s:Envelope>