DynDNS-Updater

Wenn man aus der Ferne über das Internet den heimischen Server (mit täglich wechselnder IP-Adresse, da über DSL angebunden) erreichen möchte, so bieten sich sog. dynamische DNS-Dienste an. Diese Dienstleister bieten Domains an, die nicht auf eine feste, sondern auch auf eine wechselnde Serveradresse zeigen. Dazu ist es nötig, bei jeder IP-Adressenänderung den Service zu aktualisieren. Üblicherweise werden dazu kleine Clientprogramme auf dem Server gestartet, die diesen Job erledigen. Oder noch besser: der heimische DSL-Router übernimmt diese Aufgabe. Das kann heute eigentlich jeder Router. In der Regel aber nur für eine Adresse und es wird auch kein Protokoll über die verwendeten Adressen geführt.

Aber genau das ist der Punkt: ich möchte mehrere Domains bei unterschiedlichen Dienstleistern aktualisieren und ich möchte ein IP-Adressen-Protokoll haben (man weiß nie, wofür das gut sein kann...). Also war mal wieder der kleine Programmierer gefragt. Was liegt näher, als dafür ein PHP-Script zu schreiben. Die dynDNS-Dienste sind i.d.R. auch über API-Funktionen ansprechbar. Für dtdns.com und dyndns.com beschreibe ich hier eine Lösung. Für letzt genannten Service ist das verbreitete Protokoll "DynDNS v2" von dyndns.org (Beschreibung in englisch) implementiert.

Hier das Script - mal wieder prozedural statt objektorientiert - bei solch übersichtlichen Aktionen ist objektorientiertes Programmieren m.E. unsinnig! (Aber manch Zeitgenosse kann gar nicht mehr anders...)

<?php

// dyndns-Updater für dtdns.net und dyndns.com
// (c) DiLi-Soft 15.8.2011


// Definitionen:

$DTDNS_arr=array("xxxx.dtdns.net",           // meine Domains über dtdns.net:
                 
"xxxy.dtdns.net");

$DYNDNS_arr=array("xxxx.mine.nu",            // meine Domains über dyndns.com:
                  
"xxxx.dyndns.org");

$IP_filename=".\last_IP.txt";                // enthält letzte gesicherte IP

$IP_logfile=".\update_dyndns.log";           // Logfile


# --------------------------------------------------------------------------- #


function get_last_IP() {                     // gesicherte letzte IP aus Datei holen
   
global $IP_filename;

   
$hd=@fopen($IP_filename,'r');
   if (
$hd===FALSE)
      return(
FALSE);

   
$zeile=fgets($hd);
   
$zeile=rtrim($zeile);
   
fclose($hd);
return(
$zeile);                              // IP oder FALSE
}


function 
put_IP($IP) {                       // aktuelle IP in Datei sichern
   
global $IP_filename;

   
$hd=@fopen($IP_filename,'w');
   
$count=fwrite($hd,$IP);
   
fclose($hd);
   return(
$count);                           // Anz. geschriebener Zeichen
}


function 
get_myIP() {                        // IP über ext. Dienstleister ermitteln
   
$hd=@fopen("http://www.eigene-ip.de","r");
   if (
$hd===FALSE)
      return(
$hd);

   
$zeile="";
   while(!
feof($hd))
      
$zeile.=fgets($hd);
   
fclose($hd);

   
$tag1='<p style="text-align: left">';
   
$tag2='</FONT>';           // ... <p style="text-align: left">80.145.67.54</FONT> ...

   
$zeile=strstr($zeile,$tag1);              // IP isolieren
   
$zeile=substr($zeile,strlen($tag1));
   
$zeile=substr($zeile,0,strpos($zeile,$tag2));

   
$count=substr_count($zeile,".");
   if (
$count<>3)                            // 3x "." enthalten?
      
$zeile="";

   
$zeile=trim($zeile);
return(
$zeile);                              // IP-Adresse, leer oder FALSE
}


function 
get_SP500v_IP() {                   // IP im DSL-Modem (hier: Speedport 500V) abfragen
   
$sock fsockopen("192.168.0.1"80$errno$errstr30);
   if (
$sock==FALSE)
      return(
FALSE);

   
fwrite($sock"POST /start.login HTTP/1.1\r\n");                           // mit Passwort einloggen
   
fwrite($sock"Host: 192.168.0.1\r\n");
   
fwrite($sock"Referer: http://192.168.0.1/hcti_start_passwort.htm\r\n");
   
fwrite($sock"Content-Type: application/x-www-form-urlencoded\r\n");
   
fwrite($sock"Content-Length: 7\r\n");
   
fwrite($sock"Connection: close\r\n");
   
fwrite($sock"\r\n");
   
fwrite($sock"P1=0000\r\n");             // = Passwort z.B. 0000

   
$headers "";                            // Header einlesen
   
while ($str trim(fgets($sock)))
      
$headers .= "$str\r\n";
   
/*
   $body = "";
   while (!feof($sock))                      // Body hier nicht nötig
      $body.= fgets($sock);
   */
   
$such1="Set-Cookie: ";                    // zurückgeliefertes Cookie
   
$such2="; path=/";
   
$cookie=strstr($headers,$such1);          // Cookie isolieren
   
$cookie=substr($cookie,strlen($such1));
   
$cookie=substr($cookie,0,strpos($cookie,$such2));
   
$cookie=trim($cookie);

   
fclose($sock);                            // Connection immer wieder schließen!

   
if($cookie=="")
      return(
"");                            // erfolglos: raus!

   
$sock fsockopen("192.168.0.1"80$errno$errstr30);
   if (
$sock==FALSE)
      return(
FALSE);


   
fwrite($sock"GET /hcti_status_netzwerk.htm HTTP/1.1\r\n");               // IP abfragen
   
fwrite($sock"Host: 192.168.0.1\r\n");
   
fwrite($sock"Referer: http://192.168.0.1/hcti_status.htm\r\n");
   
fwrite($sock"Cookie: $cookie\r\n");                                      // Cookie für Sessionverfolgung
   
fwrite($sock"Connection: close\r\n");
   
fwrite($sock"\r\n");

 
//$headers = "";
   
while ($str trim(fgets($sock)))         // Header überspringen
      
//$headers .= "$str\r\n";

   
$body "";
   while (!
feof($sock))                      // und Body abholen
      
$body.= fgets($sock);

   
$such1="var internet_status = 'Verbunden';";
   
$such2='<td width="200">Zugeteilte IP-Adresse</td>'."\n\t\t\t\t\t\t<td>";
   
$such3="</td>";
   
$status=strpos($body,$such1);
   if (
$status<>FALSE) {                     // verbunden!
      
$IP=strstr($body,$such2);
      
$IP=substr($IP,strlen($such2));
      
$IP=substr($IP,0,strpos($IP,$such3));
      
$IP=trim($IP);                         // hier sollte nun die IP stehen
   
} else {
      
$IP="";                                // nicht verbunden!
   
}
   
fclose($sock);

   
$sock fsockopen("192.168.0.1"80$errno$errstr30);                 // am Ende vom Modem abmelden !!
   
if ($sock==FALSE)
      return(
$IP);

   
fwrite($sock"GET /logout.cmd HTTP/1.1\r\n");
   
fwrite($sock"Host: 192.168.0.1\r\n");
   
fwrite($sock"Referer: http://192.168.0.1/m_startseite.htm\r\n");
   
fwrite($sock"Cookie: $cookie\r\n");                                      // Cookie für Sessionverfolgung
   
fwrite($sock"Connection: close\r\n");
   
fwrite($sock"\r\n");                    // und Tschüss
   /*
   $headers = "";                            // hier nicht nötig
   while ($str = trim(fgets($sock)))
      $headers .= "$str\r\n";

   $body = "";
   while (!feof($sock))
      $body.= fgets($sock);
   */
   
fclose($sock);
return(
$IP);                                 // mit "" oder IP zurück
}


function 
update_dtdns($DOMAIN) {             // 1 dtdns-Domain behandeln
   
$hd=@fopen("http://www.dtdns.com/api/autodns.cfm?id=$DOMAIN&pw=Passwd&client=myUpdater","r");
   if (
$hd===FALSE)                          // ohne "&ip=xx.xx.xx.xx" liefert dtdns die aktuelle IP !!!
      
return($hd);

   
$zeile=fgets($hd);
   
$zeile=rtrim($zeile);
   
$len=strlen($zeile);
   if (
$len>0)
      if (
$zeile[$len-1]==".")
         
$zeile=substr($zeile,0,$len-1);     // evtl. Punkt am Ende der IP entfernen
   
fclose($hd);
return(
$zeile);                              // response oder FALSE
}


function 
update_dtdns_domains($arr) {        // alle dtdns-Domains behandeln
   
foreach ($arr as $DOMAIN) {
      
$response=update_dtdns($DOMAIN);       // dtdns lässt mehrfaches Auffrischen zu
      
if ($response===FALSE)
         
$response=$DOMAIN.": ERROR";

      
$response=rtrim($response);            // ggf. CRLF entfernen
      
$IP=trim(str_replace("Host ".$DOMAIN." now points to ","",$response,$count));
      if (
$count<>1)                         // str_replace() fehlerhaft
         
$IP=FALSE;                          // damit nichts gemacht wird

      
$res[]=array($response,$IP);
   }
return(
$res);
}


function 
update_dyndns($DOMAIN,$IP) {        // 1 dyndns-Domain behandeln
   
@$sock fsockopen("members.dyndns.org"80$errno$errstr30);
   if (!
$sock)
      return(
FALSE);

   
fwrite($sock"GET /nic/update?hostname=$DOMAIN&myip=$IP HTTP/1.0\r\n");
   
fwrite($sock"Host: members.dyndns.org\r\n");
   
fwrite($sock"Authorization: Basic ".base64_encode("User:Passwd")."\r\n");
   
fwrite($sock"User-Agent: myUpdater\r\n");
   
fwrite($sock"Accept: */*\r\n");
   
fwrite($sock"\r\n");
   
fwrite($sock"\r\n");

   
$headers "";
   while (
$str trim(fgets($sock4096)))
   
$headers .= "$str\n";

   
$body "";
   while (!
feof($sock))
   
$body .= fgets($sock4096);

   
fclose($sock);
return(
$body);                               // response oder FALSE
}


function 
update_dyndns_domains($arr$IP) {  // alle dyndns-Domains behandeln
   
foreach ($arr as $DOMAIN) {
      
$response=update_dyndns($DOMAIN,$IP);  // zum Testen auskommentieren !!
      
if ($response===FALSE)
         
$response="ERROR";

      
$res[]="Host $DOMAIN: ".$response;
   }
return(
$res);
}


function 
write_log($arr) {                   // Log-File schreiben
   
global $IP_logfile;

   
$hd=fopen($IP_logfile"a");
   if (
$hd===FALSE)
      return (
$dh);

   foreach (
$arr as $zeile)
      
fwrite($hd,date("d.m.Y H:i:s")." : ".$zeile."\r\n");

   
fwrite($hd,"\r\n");
   
fclose($hd);
return;
}



# --------------------------- Main() -------------------------------- #

$last_IP=get_last_IP();             // letzte IP aus Datei holen
$IP=$last_IP;                       // erstmal annehmen, dass unverändert


$dtdns_results=update_dtdns_domains($DTDNS_arr); // updaten + akt. IP holen

if ( ($dtdns_results[0][1] <> "") and ($dtdns_results[0][1] <> FALSE) )
   
$IP=$dtdns_results[0][1];        // IP übernehmen
else {

   if ( (
$dtdns_results[1][1] <> "") and ($dtdns_results[1][1] <> FALSE) )
      
$IP=$dtdns_results[1][1];     // oder hier, wenn 1. DOMAIN fehlerhaft war
   
else {

      
$myIP=get_myIP();             // oder diesen Dienstleister versuchen, wenn auch 2. DOMAIN fehlerhaft
      
if ( ($myIP <> "") and ($myIP <> FALSE) )
         
$IP=$myIP;                 // nur übernehmen, wenn kein Fehler
      
else {

         
$ModemIP=get_SP500v_IP();  // als letzter Weg: IP aus DSL-Modem holen
         
if ( ($ModemIP <> "") and ($ModemIP <> FALSE) )
            
$IP=$ModemIP;
         else {

            if (
$ModemIP===FALSEwrite_log(array("Fehler beim Ermitteln der aktuellen IP-Adresse !"));  // entweder
            
if ($ModemIP==="")    write_log(array("Keine ADSL-Verbindung !"));                           // oder
         
}
      }
   }
}

if (
$IP==$last_IP)                  // IP wurde wg. Fehler nicht gesetzt oder ist unverändert
   
exit;                            // raus:
                                    // dyndns darf nicht mehrfach aufgefrischt werden -> "abuse" !!!

                                    // !nur! wenn IP geändert:
$dyndns_results=update_dyndns_domains($DYNDNS_arr,$IP); // updaten

write_log(array("Neue IP: $IP",
                
$dtdns_results[0][0],$dtdns_results[1][0],
                
$dyndns_results[0],$dyndns_results[1]));

put_IP($IP);                        // letzte gültige (geänderte) IP in Datei sichern

?>

Los geht es im unteren Viertel des Listings. dtdns.com ist sehr pflegeleicht: man kann die API beliebig oft aufrufen und bekommt in der Response auch noch die eigene IP-Adresse geliefert. Sehr bequem. dyndns.com ist ziemlich eigen: die API will die IP-Adresse aktiv mitgeteilt bekommen und das ausschließlich bei einer Änderung. Ansonsten wird man gebannt ("abuse"). Es bietet sich also an, zunächst dtndns zu aktualisieren (regelmäßig) und dann nur bei veränderter IP-Adresse die dyndns-API aufzurufen.

Sollte dyndns ausgefallen sein und somit die eigene IP nicht bekannt sein, so wird ein externer Dienst angesprochen. Sollte auch diese Anfrage scheitern (weil auch diese Seite down ist oder die DSL-Verbindung ausgefallen ist, so bleibt als letzter Weg, im Modem nachzuschauen (das Script ist unverändert ausschließlich für Speedport 500v geeignet). Dazu simuliere ich einen sich einloggenden Benutzer.

Anschließend wird das Protololl aktualisiert (siehe das Beispiel aus dem Protokoll).

Es gibt noch Verbesserungspotential: sollte ein Updaten gescheitert sein (bei dtdns unkritsich, da es regelmäßig wiederholt wird) so wird bei dyndns zurzeit keine Wiederholung durchgeführt. Aktuell läuft das Script alle 10 Minuten.

     Kommentieren Sie hier!