Replacement for DynDNS


The problem:
as you probably will know, some time ago the Dyn company, aka Dyndns, cancelled its free service. But unfortunately, there are still many routers around which do not provide entries for other free services offered recently.

So, how can you get access to your local web server, your USB pen drive or hard disk, your IP-camera or weather station connected to your router when your ISP will not provide you a static ip address?

The solution shown here uses

Well, what you need is a super mini web site where you can place these two files:

  1. a PHP script that returns your WAN ip address whenever you call it.
  2. a file which contains a link to your WAN ip address which can be updated as often as you want
#1 could look like this:
<?php
$ip=$_SERVER['REMOTE_ADDR'];
echo "$ip";
?>

#2 could look like this:
<html><head><meta http-equiv="refresh" content="5; url=ftp://999.999.999.999"></head><body></body></html>

Well, the 999.999.999.999 has to be replaced by your current WAN address, of course.

Actually, you don't really need #1. There are a lot of web sites which give you your WAN address for free: http://checkip.dyndns.com/, https://www.iplocation.net/find-ip-address and many others do the job. But if you go for this you have to parse one or more lines to find your WAN ip. It might be like looking for a needle in a haystack.

Also you have to make sure the PHP script will be called regularly. This can be done by a simple Arduino running a web client sketch. You can easily modify this example found on the Arduino web site. But how to update the link on the web site? There are at least two ways to achieve this:

  1. a PHP script running on the web server
  2. another Arduino sketch. But it can be run on the same one which triggers the PHP script

Of course version 1. is the easiest way. But there are ISPs which do not permit writing access to your PHP scripts. In this case you have to go for version 2.

And there is a cetain risk in version #1: if somebody else is calling you PHP script the redirection will point to his or her WAN address. Once this is done your visitor will be directed to someone elses site. Well, as soon as your Arduino triggers the PHP script again the redirection will point to you again.
You might avoid this risk by giving your PHP script a cryptic name or even protecting it with a password.

If you choose version #2 and somebody else is calling your PHP script it will return his or her WAN address but nothing serious will happen. So anybody might misuse your script without any damage.

Version 1:

ipaddress.php:

<?php
$newIp = $_SERVER['REMOTE_ADDR'];
echo "Aktuelle WAN-IP-Adresse: ";
echo "$newIp";
echo "\n";
echo "getting last ip: ";
$ipFile = fopen("ipaddress.txt","r+");
$lastIp = fgets($ipFile,16);
echo "$lastIp";
echo "\n";
if ($newIp != $lastIp) 
   {
      rewind($ipFile);
      fwrite($ipFile,$newIp);
      echo "ip updated";
      $index = fopen("index.html","r+");
      rewind($index);
      fwrite($index,'<html><head><meta http-equiv="refresh" content="1; url=ftp://');
      fwrite($index,$newIp);
      fwrite($index,'/index.html"></head><body></body></html>');
      fclose($index);
      echo "index rewritten";
   }
fclose($ipFile);
?>

Version 2:

ipaddress.ino

/* replacement for dyndns
 * periodically checks WAN ip address. Rate: 1 hour
 * if any change was detected a link on a web site will be updated
 * Update: WAN ip stored as 4 bytes rather a string
 * Hardware: 
 * Arduino UNO; can be powered by the router if possible
 * Ethernet shield, connected to the router
 * lines with xxxxxxxxxxxxxxxx have to be edited
*/

#include <Ethernet.h>
#include <EEPROM.h>

EthernetClient client;   // commands: port 21
EthernetClient dclient;  // data
//                          <meta http-equiv="refresh" content="">
String line1 = "<html><head><meta http-equiv=\"refresh\" content=\"5; url=ftp://";
// redirect after 5 seconds
String line2; // the WAN ip comes here
String line3 = "\"></head><body>Change counter: ";
String line4 = String(word(EEPROM.read(4),EEPROM.read(5)));
String line5 = "</body></html>";
const byte bufLen = 128;
char outBuf[bufLen];
long waitingTime = 60 * 60; // one hour

void setup() {
  Serial.begin(9600);
  Serial.print(F("Program name: "));
  String s = __FILE__;
  int i;
  for (i = s.length() - 1; i > 0; i--) if (s.charAt(i) == '\\') break;
  s = s.substring(i + 1);
  Serial.println(s);
  Serial.print(F("UPDATE NUMBER: "));
  Serial.println(line4);
  // ************* MAC **************
  byte mac[] = {123,123,123,123,123,123}; // xxxxxxxxxxxxxxxx
  Serial.print(F("MAC address: "));
  for (int i = 0; i < 6; i++) {
    Serial.print(mac[i]);
    Serial.print(F("."));
  }
  Serial.println();
  IPAddress ip     (123,123,123,123); // xxxxxxxxxxxxxxxx
  IPAddress gateway(123,123,123,123); // xxxxxxxxxxxxxxxx
  IPAddress subnet (123,123,123,123); // xxxxxxxxxxxxxxxx
  IPAddress server (123,123,123,123); // xxxxxxxxxxxxxxxx
  byte      wan[4];
  String ipaddress = "ipaddress.php";
  String host      = "www.xxxxxxxxxxxxxxxx.de";
  Ethernet.begin(mac, ip, gateway, gateway, subnet);
  Serial.println(Ethernet.localIP());
  // ******************************************************
  Serial.println(F("CONNECTING TO WEB SERVER ..."));
  if (client.connect(server, 80)) {
    Serial.println(F("CONNECTED"));
    // Make a HTTP request:
    client.print(F("GET /"));
    client.print(ipaddress);  // the PHP script
    client.println(F(" HTTP/1.1"));
    // host:
    client.print(F("Host: "));
    client.println(host);
    // close:
    client.println(F("Connection: close"));
    client.println();
  } else {
    Serial.println(F("CONNECTION FAILED"));
    return;
  }
  Serial.println(F("REQUEST HAS BEEN SENT"));
  delay(1000);
  byte lineNum = 0;
  char getData[] = " .123.123.123.123"; // the first 2 characters for strtok
  byte j = 2;
  while (client.available()) {
    char c = client.read();
    Serial.print(c);
    if (c == '\n') {
      lineNum++;
      Serial.print(lineNum);
      Serial.print(F(" "));
    }
    if ((lineNum == 9) && (c > ' ')) {
      getData[j] = c;
      j++;
    }
  }
  if (!client.connected()) {
    Serial.println();
    Serial.println(F("DISCONNECTING FROM WEB SERVER."));
    client.stop();
  }
  getData[j] = 0; // end of string
  line2 = getData;
  line2 = line2.substring(2);
  // parse getData, result: wan[]:
  char *search = strtok(getData,".");
  for (int i = 0; i < 4; i++) {
    search = strtok(NULL, ".");
    wan[i] = atoi(search);
    if (search == NULL) {
      Serial.println(F("ERROR PARSING WAN IP"));
      return;
    }
  } 
  printIP("FOUND THIS WAN ADDRESS: >",wan[0],wan[1],wan[2],wan[3]);
  printIP("LAST TIME IT WAS:       >",EEPROM.read(0),EEPROM.read(1),EEPROM.read(2),EEPROM.read(3)); 
  // test if WAN IP changed since last time:  
  boolean ipChanged = false;
  for (int i = 0; i < 4; i++) 
    if (wan[i] != EEPROM.read(i)) ipChanged = true; 
  if (!ipChanged) {
    Serial.println(F("NO CHANGE - SO QUIT"));
    return; // and go to loop
  }
  // yes, ip has been changed
  // so update actual value:
  for (int i = 0; i < 4; i++) 
    if (EEPROM.read(i) != wan[i]) EEPROM.write(i,wan[i]);
  // update counter:
  byte b = EEPROM.read(5) + 1; // low byte
  EEPROM.write(5,b);
  if (b == 0) EEPROM.write(4,EEPROM.read(4) + 1); // high byte
  // update link:
  line1.concat(line2);
  line1.concat(line3);
  line1.concat(line4);
  line1.concat(line5);
  if (client.connect(server, 21)) {
    Serial.println(F("COMMAND CONNECTED"));
  }
  else {
    Serial.println(F("COMMAND CONNECTION FAILED"));
    return;
  }
  Serial.println(F("Start ftp login:"));
  delay(2000); // no idea if you need that time
  if (!eRcv()) return;
  Serial.println(F("got a response"));
  char user[] = "USER xxxxxxxxxxxxxxxx";
  char pass[] = "PASS xxxxxxxxxxxxxxxx";
  char file[] = "index.html";
  client.println(user);
  if (!eRcv()) return;
  Serial.println(F("sent USER"));
  client.println(pass);
  if (!eRcv()) return;
  Serial.println(F("sent Password"));
  client.println("SYST");
  if (!eRcv()) return;
  Serial.println(F("sent SYST"));
  client.println("PASV");
  if (!eRcv()) return;
  Serial.println(F("sent PASV"));
  //------------------------------
  // in outBuf after the text there will be a bracket
  // followed by the IP-address and the port (6 numbers)
  char *tStr = strtok(outBuf, "(,");
  // these 6 numbers are copied into an integer-array:
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL, "(,");
    array_pasv[i] = atoi(tStr);
    if (tStr == NULL) {
      Serial.println(F("BAD PASV ANSWER"));
    }
  }
  unsigned int port; // the last two numbers give the port
  port = word(array_pasv[4], array_pasv[5]);
  if (dclient.connect(server, port)) {
    Serial.print(F("DATA PORT CONNECTED: "));
    Serial.println(port);
  }
  else {
    client.stop();  // this should be dclient?
    Serial.println(F("DATA CONNECTION FAILED"));
    return;
  }
  // ******* now start ftp upload ******* :
  client.write("STOR ");
  client.println(file); 
  Serial.print(F("UPLOADING FILE: "));
  Serial.println(file);
  if (!eRcv()) {
    dclient.stop();
    return;
  }
  Serial.println(F("WRITING ..."));
  byte clientBuf[64];
  int clientCount = 0;
  for (int i = 0; i < line1.length(); i++) {
    clientBuf[clientCount] = line1.charAt(i);
    clientCount++;
    if (clientCount > 63) {
      Serial.print(F("."));
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if (clientCount > 0) dclient.write(clientBuf, clientCount);
  dclient.stop();
  Serial.println(F("DATA DISCONNECTED"));
  if (!eRcv()) return;
  client.println("QUIT");
  if (!eRcv()) return;
  client.stop();
  Serial.println(F("COMMAND DISCONNECTED"));
  return;
}  // End of SETUP()

void loop() { 
  Serial.print(F("WAITING FOR "));
  Serial.print(waitingTime);
  Serial.println(F(" SECONDS\n"));
  delay(waitingTime*1000);
  asm volatile ("  jmp 0");  
}

void printIP(String s, int a, int b, int c, int d) {
  Serial.print(s);
  Serial.print(" ");
  Serial.print(a);
  Serial.print(".");
  Serial.print(b);
  Serial.print(".");
  Serial.print(c);
  Serial.print(".");
  Serial.print(d);
  Serial.println("<");
}

// gets the response from the server
byte eRcv() {
  byte respCode;
  byte thisByte;
  while (!client.available()) delay(1);
  respCode = client.peek();
  uint8_t outCount = 0;
  while (client.available()) {  
    thisByte = client.read();    
    Serial.write(thisByte); 
    if (outCount < bufLen-1) {
      outBuf[outCount] = thisByte;
      outCount++;      
      outBuf[outCount] = 0;
    }
  }
  if (respCode >= '4') {
    efail();
    return 0;  
  }
  return 1;
}

// controls what happens if an error ocours 
void efail() {
  byte thisByte = 0;
  client.println("QUIT");
  while (!client.available()) delay(1);
  while (client.available()) {  
    thisByte = client.read();    
    Serial.write(thisByte);
  }
  client.stop();
}




contact: nji(at)gmx.de