Dynamic DNS doesn't work with Dual Wan



  • I have been testing Dynamic DNS setup using dyndns.org server.  It works OK with updating the WAN IP to the server.  But there are couple issues that I don't see that it's supported with pfsense:

    o The WAN interface is the actual WAN interface address.  So if you have a dual wan connection and the connection is failed over to second WAN, you actual WAN IP to the world is the IP of the second WAN, not WAN1.  Here is what is see it's in the dyndns.class

    $wan_ip = get_current_wan_address();

    o Dynamic DNS doesn't really update with the server when the connection is switched to the second WAN.  The only way to update it is manually to click on the Save button to force it update.  There should be some detection with slbd to trigger the Dynamic DNS update when it sees that the connection switches to another interface.

    o Dynamic DNS don't have the option to force it to update through VIP interface if you are using CARP.  It should be good to have an option to specify with interface/IP that you want to use as your ip.  That way when you have your clusters setup, the VIP can be your outside internet IP and it can update with Dyndns when it's switched over to the backup machine.

    With a little update on the codes, I think this will be rock.



  • CARP won't work with dynamic adresses like DHCP or PPPoE, so there is no real point to make it work with with that I think.



  • I'm using static IPs with dual wan from two separate ISP connections.  In this case, the two front-end VIP can be either from ISP1 or ISP2 IP.  Please see the attached diagram.

    I can make this work with Dyndns.org using a script that I wrote to detect the connection with the monitor IPs between the ISPs.  When one of the connection goes down, switch the default GW (b/c local FW only use the default router when it's connect directly from the console) and update the new IP to members.dyndns.org

    I'm adding another machine in as failover using CARP and I can have the same script to detect the connection status on the second machine.  The script I wrote in perl.  That why that I wish it can have something similar from pfsense so I don't have to use my script.




  • By looking at the dyndns.class, I have modified it to get the real IP directly with checkip.dyndns.org  If anyone is interest, you can modify the /etc/inc/dyndns.class with $wan_ip to use this private function

    $wan_ip = $this->_checkip();

    /* Private function for getting real IP /
                    /
    Author: Tri Tu */

    function _checkip() {

    log_error("DynDns: Running _checkip() for real WAN IP");

    $ch = curl_init();
                            curl_setopt($ch, CURLOPT_URL, 'http://checkip.dyndns.com');
                            curl_setopt($ch, CURLOPT_HEADER, 0);
                            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

    $data = curl_exec($ch);
                            curl_close($ch);

    list($part1, $part2) = split(': ', $data, 2);
                            list($ip, $junk) = split('<', $part2);

    return $ip;
                    }
                    /* End of function */

    Below is the log when I updated it on my box:

    Feb 4 13:08:24 php: /services_dyndns.php: phpDynDNS: (Success) IP Address Changed Successfully! (xxx.xxx.xxx.91)
    Feb 4 13:08:24 php: /services_dyndns.php: phpDynDNS: updating cache file /cf/conf/dyndns.cache: xxx.xxx.xxx.91
    Feb 4 13:08:24 php: /services_dyndns.php: DynDns: Running checkip() for real WAN IP
    Feb 4 13:08:24 php: /services_dyndns.php: DynDns: Current Service: dyndns
    Feb 4 13:08:24 php: /services_dyndns.php: DynDns: DynDns _checkStatus() starting.
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: DynDns _update() starting. Dynamic
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: DynDns _update() starting.
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: cacheIP != wan_ip. Updating.
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: Cached IP:
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: Current WAN IP: xxx.xxx.xxx.91
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: Running _checkip() for real WAN IP
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: _detectChange() starting.
    Feb 4 13:08:23 php: /services_dyndns.php: DynDns: Real WAN IP is xxx.xxx.xxx.91
    Feb 4 13:08:22 php: /services_dyndns.php: DynDns: Running _checkip() for real WAN IP
    Feb 4 13:08:22 php: /services_dyndns.php: DynDns: updatedns() starting
    Feb 4 13:08:22 php: /services_dyndns.php: DynDns: Running updatedns()



  • I think this won't really work as the pfSense itself can't make use of multiple WANs. It only will be able to use the default gateway at WAN, so it won't be able to do the update if WAN is really down unless you add a route to the updateserver if wan goes down.



  • Sure that is only the initial of checking for the real IP of the WAN IP address.  There must be another function to handle connection monitoring and if it detects a failure connection, then switch the route.  In addition, the slbd must also include something to call the function and update with dynamic DNS site.

    If it's a developer, it will be easier to find when to add them in.  For me, it will take a little more time to search around the codes and modify it to work it in my way.

    I already the procedure in perl.  Since php from pfsense doesn't come with write_socket to check for icmp ping, it uses the local ping and grep for status.  I can use exec system call command but I'm sure it already in there.  Just have to find it.

    I think that rather than having the case of if it's 'ppoe', 'dhcp', etc.. just use the direct link checking for real IP.  Maybe there must be a reason for it's not doing that way.



  • Looking into the source codes to understand the way that slbd and filter reload work with pfsense, I have created a new function with php to monitor the connection, switch the route and update with dyndns.org.  I have been testing it with pulling in & out cables, turn off machine, etc… and it's working great.

    Thanks for the PfSense team to create a great tool.

    ;D ;D ;D



  • Maybe you want to offer your code here at the forum for others that would need this as well? If it's useful we even could consider integrating it into pfSense if you give us the permission.



  • I'm not a fulltime programmer so I don't think like a geek.  I just made it work for my case.  If it's usefull, please feel free to use it.  I will be very happy if I know that it's intergrated into pfsense.  I can sleep with a happy face  :)

    The three main files that I modified are: filter.inc, dyndns.class, and pfsense-utils.inc  (see attached files).  As always, diff is really useful to checkout the differences.  It's not much coding, here are all of my codes & modifications:

    /********************************************************/
    /
    File: /etc/inc/filter.inc                                                                    /
    /
    At the end of the function filter_configure_sync add                            /
    /
    the new wan_monitor() function in                                                    /
    /
    ********************************************************/

    /* reload filter sync /
    function filter_configure_sync() {
      …
            /
    sync carp entries to other firewalls */
            update_filter_reload_status("Syncing CARP data");
            carp_sync_client();

    /* WAN monitoring */
            wan_monitor();

    update_filter_reload_status("Done");

    return 0;
    }

    function wan_monitor() {
            global $config;
            $failure = 0;
            update_filter_reload_status("Debug: WAN Monitoring");

    foreach ($config['filter']['rule'] as $rule) {
                    if ($rule['gateway'] <> "") {
                            $rulegw = $rule['gateway'];
                    }
            }

    foreach($config['load_balancer']['lbpool'] as $lb) {
                    $poolname = $lb['name'];
                    $servers = $lb['servers'];

    if ($poolname == $rulegw) {
                            if($lb['behaviour'] == "failover") {
                                    $routeto = exec("cat /tmp/rules.debug | grep route-to | cut -d '(' -f2 | cut -d ')' -f1 | /usr/bin/sed -e 's/^ //g'");
                                    list($int, $gateway) = split(" ", $routeto);

    $default_route = exec("netstat -rn | grep default | awk '{print $2, $6}'");
                                    list($default_gw, $default_int) = split(" ", $default_route);

    if ($default_gw != $gateway && $gateway <> "" ) {

    $wan1_gw = $config['interfaces']['wan']['gateway'];
                                            list($int_name, $monIP) = split("|", $servers[0]);

    /* Double check by ping to the host monitor IP 3 times */
                                            for ($i = 1; $i <= 3; $i++) {
                                                    $pingstatus = exec("/sbin/ping -c 1 -t 2 -q -Q $monIP | grep 'packet loss' | cut -d ',' -f3 | /usr/bin/sed -e 's/^ //g' | cut -d '%' -f1");

    if ($pingstatus == 100) {
                                                            $failure++;
                                                    }
                                                    sleep (5);
                                            }

    /* If total failure is 3 times, then switch the default route /
                                            if ($failure == 3) {
                                                    $switchroute = 1;
                                            }
                                            /
    Switch back to WAN1 if the host monitor IP is pingable /
                                            /
    and the default gateway is on WAN2                    */
                                            else if ($failure == 0 && $default_gw != $wan1_gw) {
                                                    $switchroute = 1;
                                            }
                                            else {
                                                    $switchroute = 0;
                                            }

    update_filter_reload_status("Debug: Switch Route = $switchroute");
                                            update_filter_reload_status("Debug: Total Failure time = $failure");
                                    }
                                    else {
                                            update_filter_reload_status("Debug: Do nothing. Same route");
                                    }

    if ($switchroute == 1) {
                                            update_filter_reload_status("Debug: Changing the default gateway to $gateway");
                                            exec("/sbin/route delete default");
                                            exec("/sbin/route add default $gateway");

    update_filter_reload_status("Debug: Sending email notification");
                                            $hostname = exec("hostname");
                                            $subject = "$hostname has switched the default gateway to $gateway";
                                            $msg = "This is an automate email notification that the default gateway has switched over to ";
                                            $msg .= "$gateway\n";
                                            $to = "email@company.com";
                                            exec("/usr/local/bin/php /root/my-scripts/phpmailer/smtp.php "$subject" "$msg" $to");
                                    }

    $is_carp_enable = get_carp_status();

    if ($is_carp_enable == 1) {
                                            foreach($config['virtualip']['vip'] as $carp) {
                                                    if ($carp['mode'] != "carp") continue;
                                                    $ipaddress = $carp['subnet'];
                                                    $carp_int = find_carp_interface($ipaddress);
                                                    $carp_status = get_carp_interface_status($carp_int);
                                                    update_filter_reload_status("Debug: $carp_status");
                                            }
                                    }

    if ($carp_status == "MASTER") {
                                            update_filter_reload_status("Debug: Updating with DynDNS");
                                            exec("/etc/rc.dyndns.update");
                                    }
                                    update_filter_reload_status("Debug: End");
                            }
                    }
            }
    }

    /********************************************************/
    /
    File: /etc/inc/dyndns.class                                                              /
    /
    Replace $wan_ip to use $this->_checkip() function to                          /
    /
    get the real WAN IP to use with Dyndns                                            /
    /
    ********************************************************/

    if(!$wan_ip)
                                    $wan_ip = $this->_checkip();

    function _checkip() {

    //log_error("DynDns: Running _checkip() for real WAN IP");

    exec("/usr/bin/netstat -rn | grep carp | awk '{print $1, $6}'", $getcarp);

    list($gwip, $gwint) = split(" ", /usr/bin/netstat -rn | grep default | awk '{print $2, $6}');
                            $gwip = str_replace("\n", "", $gwip);
                            $gwint = str_replace("\n", "", $gwint);

    if ($getcarp[0] <> "") {
                                    foreach ($getcarp as $carpinfo) {

    list($carpip, $carpname) = split(" ", $carpinfo);

    $carp_int = $this->_getcarp_int($carpip);

    if ($gwint == "$carp_int") {
                                                    $match = exec("echo $carpinfo | grep $carpname");
                                                    list($gwvip, $gwcarp) = split(" ", $match);
                                                    $ip = $gwvip;
                                            }
                                    }
                            }
                            else {
                                    $ch = curl_init();
                                    curl_setopt($ch, CURLOPT_URL, 'http://checkip.dyndns.com');
                                    curl_setopt($ch, CURLOPT_HEADER, 0);
                                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

    $data = curl_exec($ch);
                                    curl_close($ch);

    list($part1, $part2) = split(': ', $data, 2);
                                    list($ip, $junk) = split('<', $part2);
                            }
                            return $ip;
                    }
                    /* End of function */

    function _getcarp_int($carpip) {

    global $config;

    foreach($config['virtualip']['vip'] as $vip) {

    if ($vip['subnet'] == "$carpip") {
                                            $int_name = $vip['interface'];
                                            $int = convert_friendly_interface_to_real_interface_name($int_name);
                                    }

    }
                            return $int;
                    }

    /*
                    * Private Function (added 12 July 05) [beta]
                    *  - Detect whether or not IP needs to be updated.
                    *      | Written Specifically for pfSense (pfsense.com) may
                    *      | work with other systems. pfSense base is FreeBSD.
                    */
                    function _detectChange() {
                            global $config;
                            log_error("DynDns: _detectChange() starting.");

    $currentTime = time();

    $wan_ip = $this->_checkip();
                            $this->_dnsIP = $wan_ip;
                            $this->_dnsHost = $config['dyndns']['host'];

    $previousIP = exec("/sbin/ping -c1 $this->_dnsHost | grep PING | cut -d '(' -f2 | cut -d ')' -f1");

    log_error("DynDns: Previous DNS IP: {$previousIP}");
                            log_error("DynDns: Current WAN IP: {$wan_ip}");

    if (file_exists($this->_cacheFile)) {
                                    if(file_exists($this->_cacheFile))
                                            $contents = file_get_contents($this->_cacheFile);
                                    else
                                            $contents = "";
                                    list($cacheIP,$cacheTime) = split(':', $contents);

    $this->_debug($cacheIP.'/'.$cacheTime);
                                    $initial = false;
                                    log_error("DynDns: Cached IP: {$cacheIP}");
                            } else {
                                    conf_mount_rw();
                                    $file = fopen($this->_cacheFile, 'w');
                                    fwrite($file, '0.0.0.0:'.$currentTime);
                                    fclose($file);
                                    conf_mount_ro();
                                    $cacheIP = '0.0.0.0';
                                    $cacheTime = $currentTime;
                                    $initial = true;
                                    log_error("DynDns: No Cached IP found.");
                            }

    /*  use 2419200 for dyndns, dhs, easydns, noip, hn
                            *  zoneedit, dyns, ods
                            */
                            $time = '2160000';

    $needs_updating = FALSE;

    /* lets deterimine if the item needs updating /
                            if ($previousIP != $wan_ip) {
                                    $needs_updating = TRUE;
                                    log_error("DynDns: previousIP != wan_ip.  Updating.");
                            }
                            if ($cacheIP != $wan_ip) {
                                    $needs_updating = TRUE;
                                    log_error("DynDns: cacheIP != wan_ip.  Updating.");
                            }
                            $update_reason = "Cached IP: {$cacheIP} WAN IP: {$wan_ip} ";
                            if (($currentTime - $cacheTime) > $time ) {
                                    $needs_updating = TRUE;
                                    log_error("DynDns: More than 25 days.  Updating.");
                            }
                            $update_reason .= "{$currentTime} - {$cacheTime} > {$time} ";
                            if ($initial == TRUE) {
                                    $needs_updating = TRUE;
                                    $update_reason .= "Inital update. ";
                                    log_error("DynDns: Initial run.  Updating.");
                            }
                            /
      finally if we need updating then store the
                            *  new cache value and return true
                            */
                            if($needs_updating == TRUE) {
                                    return TRUE;
                            } else {
                                    return FALSE;
                            }

    log_error("DynDns debug information: {$update_reason}");

    }

    filter.inc.txt
    dyndns.class.txt
    pfsense-utils.inc.txt


Log in to reply