Alternative to Dynamic DNS – Project Lighthouse (part 2)

If you haven’t seen the first posting then you will likely want to start there.

Getting Started

I’m not using any frameworks or anything with this although it can easily be introduced.   Since this started out as a quick solution and has been updated along the way, I haven’t spent much time trying to make it pretty.

The first thing we will need is a few utilities functions for reading and writing data to a file.

/**
 * This is a utility function that will read the contents of a file
 * and return all of the data contained within it.
 */
function read_file($filename) {
    $myFile = $filename;

    //Check for file accessibility, throw error if not.
    if (!file_exists($myFile))
        throw new Exception('File does not exist.');
    if (!is_readable($myFile))
        throw new Exception('Cannot read file.');

    //Open file for reading
    $fh = fopen($myFile, 'r');
    if ($fh == null) {
        throw new Exception('Encountered error when reading file.');
    }
    //Read all of the data from file
    $theData = fread($fh, filesize($myFile));
    //Close the file
    fclose($fh);

    return $theData;
}

/**
 * This is a utility function that will write data to a file.
 * If the file previously existed then it will overwrite it with the new data.
 */
function write_file($filename, $data) {
    $myFile = $filename;
    $fh = fopen($myFile, 'w') or die('can't open file');
    if ($fh == false) {
        $fh = fopen($myFile, 'a');
    }
    fwrite($fh, $data);
    fclose($fh);
}

Lastly, I am also going to include an email utility function. This will allow us to email the IP address changes instead of needing to access the URL.

/**
 * This is a list of common utilities collected over time for trivial tasks
 */
function sendmail($toAddy, $fromAddy, $subject, $body, $replyAddy = null, $priority = false) {
    $to = $toAddy;
    $subject = $subject;
    $headers = "From: $fromAddy";
    if ($replyAddy != null)
        $headers .= "\r\nReply-To: $replyAddy\n";
    if ($priority == true)
        $headers .= "X-Priority: 1\n"; // Urgent message!

    $body = $body;

    mail($to, $subject, $body, $headers);
}

The Data Structure – NetworkVO

Now that we have those pieces we can get down to business. The next step is a class which will handle the data structure for the information we are capturing.  I do this primarily for extendability reasons.  What I mean by that is we are taking the easy way out here and storing the data in a flat file. We could instead store the data in a MySQL, Postgres or SQLite database. For this though, that is overkill.  Since we are storing in a flat file there are some drawbacks which I will get into at the end.

/**
 * This class represents a node or network.
 */
class NetworkVO{
    private $id;
    private $ip;
    private $createdOn;

 	/** Getters and Setters **/
 	public function getId() {
 		return $this->id;
 	}

 	public function getIp() {
 		return $this->ip;
 	}

 	public function getCreatedOn() {
 		return $this->createdOn;
 	}

 	public function setId($id) {
 		$this->id = $id;
 	}

 	public function setIp($ip) {
 		$this->ip = $ip;
 	}

 	public function setCreatedOn($createdOn){
 		$this->createdOn = $createdOn;
 	}

    /**
     * This is the comparison function to compare two of the same objects
     * for sorting purposes.
     */
    public function cmp($a, $b) {
        if ($a->createdOn == $b->createdOn) {
            return 0;
        }
        return ($a->;createdOn > $b->createdOn) ? -1 : 1;
    }

    /**
     * Return a string representation of the Network
     */
    public function toString() {
        return $this->getIp() . " " .$this->getId() . "  ".$this->getCreatedOn();
	}

}

The NetworkVO is a value object which captures a network identifier, IP address and the date that it was created.  The getters and setters for the class members or variables should be straight forward.  In addition to the getters and setters, there are two additional functions which will be of use to us later.  The first being the cmp function which compares two NetworkVO objects based upon the createdOn date.  We will use this for sorting a collection of NetworkVOs later.  The last is a toString function which simply returns a string based representation of the NetworkVO.

The Storage and Retrieval – NetworkDAO

The NetworkDAO class is the data access object or is an abstraction layer that is responsible for CRUD type of operations.  That is creating, reading, updating and deleting from the data store.  I mentioned earlier that we were going to take the easy road here and utilize a flat file to store the data.

There are several drawbacks to this approach with the primary being that writing to a file does not handle concurrent writes without some extra work.  If this was for an enterprise level solution then you would want to consider altering the NetworkDAO for database access.  To do this would be rather simple to do and who knows, maybe I’ll write an update one day to do just that.  The key here is to understand your requirement.  I don’t intend to have heavy traffic hitting the data store and I can easily avoid any concurrent writing to the file.

I will break down this file piece by piece since it isn’t as straight forward as the NetworkVO.  First, we are going to include a few things that are going to be needed.

include_once('NetworkVO.php');
include_once('config.ini.php');
include_once('fns_common.php');
  • NetworkVO.php – This should be familiar since we just covered it above.
  • config.ini.php – We will get to this in the next post.  It merely contains configurable parameters for ease of modification and testing.  Anywhere you see a $conf[‘some-name’] this is where the information is coming from.
  • fns_common.php – We covered this at the very beginning of this post.  It holds the generic file read and write functions and email function.
/**
 *  Storage and retrieval of NetworkVO objects.
 *  Storage method: in serialized array flat file
 *  All associated actions open and close file
 */
class NetworkDAO {

    protected $filename;

    /**
     * Constructor for the NetworkDAO - argument is the location
     * of the data file.
     */
    public function NetworkDAO($filename) {
        if (isset($filename)) {
            $this->filename = $filename;
        }
    }

This is the beginning of the class which includes the constructor. The constructor takes a $filename which is the path of the data store. So far, I’ve talked about the data store as a flat file, but the contents of the file I haven’t described yet. If you have downloaded the source code and are trying to follow along, I am going to jump around within the class a bit to make this easier to understand.

I am going to use the power of php and serialization in order to make this easy.  What the file is going to consist of is a collection of NetworkVO objects.  The collection itself is going to be an array.  If you are new to php, an array within the language is an associative array.  If you are familiar with Java or C++ then it is very similar to a HashTable or Map.  The key will consist of the network id and the value will consist of the NetworkVO object itself.

    /**
     * Retrieves the NetworkVOs from the data store.
     * @return NetworkVO[] or null
     */
    private function retrieve() {
        try {
            $filecontents = read_file($this->filename);
            return unserialize($filecontents);
        } catch (Exception $e) {
            echo 'Caught Exception: ', $e->getMessage(), "\n";
            return null;
        }
    }

    /**
     * Stores the NetworkVO[] to datastore
     * @param NetworkVO[] $networkVOs
     */
    private function store($networkVOs) {
        if (!file_exists($this->filename))
            echo 'Creating data file...';

        write_file($this->filename, serialize($networkVOs));
    }

Here we are, two very simple methods. The retrieve function retrieves the associative array from the file. To do this we read all of the files contents and then unserialize it. The unserialize method turns the plain text, back into an array so that we can access it in php. The store function serializes the associative array, which can be thought of as turning it into text, and then saves it to the file.

Now that we have these two functions the rest of the class will merely be accessing the file to read or manipulating the contents of the associative array. I will not go through each of these line by line because they hopefully are straight forward.

    /**
     * Returns the NetworkVO if the object exists, null otherwise
     */
    public function getById($id) {
        $arr = $this->retrieve();

        if (array_key_exists($id, $arr)) {
            return $arr[$id];
        } else {
            return null;
        }
    }

    /**
     * Gets all NetworkVO objects stored in array, null if file
     * does not exist.
     */
    public function getAll() {
        return $this->retrieve();
    }

    /**
     * This function will delete the provided
     * NetworkVO parameter from the data store.
     */
    public function delete($networkVO) {
        $ipArray = $this->retrieve();
        unset($ipArray[$networkVO->getId()]);

        $this->store($ipArray);
    }

    /**
     * This function will delete the NetworkVO
     * from the data store based upon the provided id.
     */
    public function deleteById($id) {
        $d_vo = new NetworkVO();
        $d_vo->setId($id);
        $this->delete($d_vo);
    }

Fairly straight forward, eh? The only part I left out was the save() function and the email() function. Why? Because I wanted to explain a little bit of that first. So, one day I was off galavanting the world. I got to the airport, on my way out of the country, and cracked open my laptop to get a little work done. I enjoyed the long plane ride. Once I made it to the hotel wouldn’t you know it my IP address at home changed. I pulled up the web page grabbed the new IP address and then thought to myself, “It sure would have been nice to have been notified.” That is where the email notification came into play.


    /**
     * Saves the NetworkVO to storage, if id already exists
     * replaces that entry.  Also, will email IP change
     *
     * @param NetworkVO $networkVO
     * @param boolean $sendEmailOnIpChange
     */
    public function save(NetworkVO $networkVO, $sendEmailOnIpChange) {
        try {
            $arr = $this->getAll();
        } catch (Exception $e) {
            //skip error, in case first save, should handle file does not
            //exist differently than cannot write, for better error handling.
        }

        //Email on IP address changes
        if ($sendEmailOnIpChange && isset($arr[$networkVO->getId()])) {
            $existingVO = $arr[$networkVO->getId()];
            if ($existingVO->getIp() != $networkVO->getIp() && strcmp($networkVO->getId(), "") != 0) {
                $this->sendemail($networkVO->getId(), $networkVO->getIp());
            }
        }

        //Set ip location with the populated info
        $arr[$networkVO->getId()] = $networkVO;

        //Write the current data
        $this->store($arr);
    }

    /**
     * Sends an email based on the server and IP address
     * @param type $server
     * @param type $ip
     */
    private function sendemail($server, $ip) {
        $body = "Server name: $server\n\nNew IP:\n$ip";

        sendmail($conf['mail_to'],
                 $conf['mail_from'],
                 $conf['mail_subj'] . ": $server",
                 $body,
                 $conf['mail_reply'], true);
    }

So basically, prior to storing the IP address, we check to see if the IP address has changed for the network and if it has then we fire away an email. Not all hosting providers allow php mail() functionality, therefore I made this feature configurable.

Now that the ground work has been laid move on to the next post which handles the HTTP portion of all of this.

You may also like...

Leave a Reply