TalkTalk Super Router v3 (HG635) DDNS with Cloudflare

Hopefully you’ve reached this page through Google, otherwise it’s unlikely you’ll have reason to read the rest of this blog post.

I recently received a new TalkTalk Super Router v3, which is made by Huawei and has the model number HG635. I was interested in getting some kind of Dynamic DNS set up and was pleased to see that it had some settings under Internet->Internet Services.

The options available were DynDns.org, TZO and other. As DynDns had just gone down the paid router after many years of being free I was keen to see if I could do something myself with “Other”. From here were a collection of settings including Protocol set to GNUDip.http. After a while of Googling it looked possible to set up my own GNUDip running on Perl speaking to my own DNS server. Except I couldn’t be bother to install Perl and I no longer managed the DNS server but now used CloudFlare.

I already had some code through the CloudFlare API where I opened a webpage from my home wifi, it took the IP I was coming from and update a CloudFlare record, I just needed to get my router to do that bit for me.

So I read through the GNUDip spec, and worked out each stage and created a PHP script which would provide the seed information, verify a change request and then update CloudFlare for me.

The PHP code is below and can be used to not only update CloudFlare, but any DNS provider if they have an API you can call.

Step 1:

In CloudFlare create a new subdomain to be used for your DDNS record. You want to make sure that it’s Off Cloud.

cloudflare-dns-entry

Step 2:

Find your CloudFlare API key

cloudflare-api-key

Step 3:

Run this PHP code, it’s only needed once to find the record ID of the subdomain you are using:

<html>
<head>
<title>Zones</title>
<style>
table { font-family: sans-serif; }
tr:nth-child(odd) { background: #ddd; }
td, th { padding: 3px; }
tr.header { background: pink; }
</style>
</head>
<body>
<table>
<tr class="header">
<th>Record ID</th>
<th>Name</th>
<th>Content</th>
<th>Type</th>
<th>Cloudflare On</th>
</tr>
<?php
$file = file_get_contents(
'https://www.cloudflare.com/api_json.html' .
'?a=rec_load_all'.
'&tkn=cd9843940935390a03939e9302094b43529ab'.
'&email=[email protected]'.
'&z=artesea.co.uk'
);
$json = json_decode($file,1);
foreach($json['response']['recs']['objs'] as $host) {
?>
<tr>
<td><?=$host['rec_id']?></td>
<td><?=$host['name']?></td>
<td><?=$host['content']?></td>
<td><?=$host['type']?></td>
<td><?=($host['props']['cloud_on'])?'Yes':'No'?></td>
</tr>
<?php
}
?>
</table>
</body>
</html>

zone-list

Step 4:

Now you’ve found the Record ID (in this case 138325147) you can create this PHP file. This is the one your router will speak to, so it needs to be on a webserver outside of your home network. I’ve also called the file cfddns.php

<?php
$timeout = 60; //seconds
$remote_ip = $_SERVER['REMOTE_ADDR'];
$key = 'JKLDlfmmkgkweglrkegj4:fjksd489ikjklJS|Ld'; #random gibberish, I just keyboard smash
$username = 'myddnsuser';
$password = 'reallysafepassword';
$cloudflare_api_key = 'cd9843940935390a03939e9302094b43529ab';
$cloudflare_email = '[email protected]';
$cloudflare_domain = 'artesea.co.uk';
$cloudflare_subdomain = 'ddns';
$cloudflare_record_id = '138325147';
// have we been passed data?
if($_GET['user']) {
$salt = $_GET['salt'];
$time = $_GET['time'];
$sign = $_GET['sign'];
$domn = $_GET['domn'];
$user = $_GET['user'];
$pass = $_GET['pass'];
$reqc = (int)$_GET['reqc'];
$addr = $_GET['addr']; //IP address
log_good("REMOTE IP: " . $remote_ip . " QUERY STRING: " . $_SERVER['QUERY_STRING']);
if($salt && $time && $sign && $domn && $user && $pass) {
//check and update DNS
# validate the signature
if(md5($salt.$time.$key) != $sign) {
log_fail("Invalid signature for " . $user);
}
# check salt timeout
if(time() > $time + $timeout) {
log_fail("Salt value to old for " . $user);
}
#confirm request code
if($reqc < 0 || $reqc > 2) {
log_fail("Invalid client request code for " . $user);
}
# only one user so check
if($user != $username) {
log_fail("Unknown user " . $user);
}
# only one domain so check
if($domn != $cloudflare_subdomain.'.'.$cloudflare_domain) {
log_fail("Unknown domain " . $domn);
}
if($pass != md5(md5($password).'.'.$salt)) {
log_fail("Invalid password for " . $user);
}
if($addr == '0.0.0.0' && $reqc = 0) {
$reqc = 1;
}
if($addr == '' && $reqc = 2) {
$addr = $remote_ip;
}
if($reqc == 1) {
// GO OFFLINE
// No idea what to do with this request so just return back success
html_output(array("retc" => 2), 'Successful offline');
log_good("SUCCESS: Offline request made");
}
else {
//DO STUFF WITH CLOUDFLARE
$file = file_get_contents(
'https://www.cloudflare.com/api_json.html' .
'?a=rec_edit'.
'&type=A'.
'&id='.$cloudflare_record_id.
'&name='.$cloudflare_subdomain.
'&content='.$remote_ip.
'&ttl=1'.
'&service_mode=0'.
'&tkn='.$cloudflare_api_key.
'&email='.$cloudflare_email.
'&z='.$cloudflare_domain
);
$meta['retc'] = 0;
if($reqc == 2) {
$meta['addr'] = $addr;
}
html_output($meta, 'Successful update');
log_good("SUCCESS: IP updated to " . $addr);
}
}
else {
log_fail("Missing information");
}
}
else {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$len = strlen($chars) - 1;
$salt = '';
for($i=0; $i<10; $i++) {
$salt .= $chars[mt_rand(0, $len)];
}
$meta['time'] = time();
$meta['salt'] = $salt;
$meta['sign'] = md5($salt.$meta['time'].$key);
html_output($meta, 'Salt generated');
log_good("SALT SET: time=".$meta['time']." salt=".$meta['salt']." sign=".$meta['sign'] . " REMOTE IP: ".$remote_ip);
}
function html_output($meta, $body) {
header("Content-Type: text/html; charset=iso-8859-1");
$body .= "\n<pre>" . print_r($meta,1) . "</pre>";
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd\">
<html>
<head>
<title>GnuDIP Update Server</title>
<?php
foreach($meta as $name => $content) {
?>
<meta name="<?=$name?>" content="<?=$content?>">
<?php
}
?>
</head>
<body>
<h2>GnuDIP Update Server</h2>
<?=$body?>
</body>
</html>
<?php
}
function log_good($text) {
error_log(date("y/m/d H:i:s")." ".$text."\n", 3, "ddns.log");
}
function log_fail($text) {
error_log(date("y/m/d H:i:s")." ERROR: ".$text."\n", 3, "ddns.log");
html_output(array("retc" => 1), $text);
exit();
}

Step 5:

Get the router to speak to the script

superrouter-screenshot

Step 6:

Sit back, hopefully from now onwards your router will update the IP address every time it changes.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Human test: Enter Ryan backwards