Skip to content

Instantly share code, notes, and snippets.

@marcelschmidtdev
Last active April 10, 2023 20:26
Show Gist options
  • Save marcelschmidtdev/f58f6d4874a6734763fab18d371b850a to your computer and use it in GitHub Desktop.
Save marcelschmidtdev/f58f6d4874a6734763fab18d371b850a to your computer and use it in GitHub Desktop.
Fritz!Box DynDNS update script for Cloudflare
<?php
// Fritz!Box update URL: https://example.com/upd.php?key=<pass>&zone=<domain>&ipv4=<ipaddr>&a=[records to update]
// Replace [records to update] in the url.
// If you want to update multiple records of your zone, just add them comma separated: '&a=sub,sub2,*,@'
// Optionally you can update AAAA records by appending '&ipv6=<ip6addr>&aaaa=[records to update]' to the url.
// If no AAAA record is defined but ipv6 is used, it will update both ip4 and ip6 defined for A records.
// $_SERVER['REMOTE_ADDR'] no longer works as FritzOS 7.50 started using IPv6
$key = $_GET['key'] ?? NULL;
$zone = $_GET['zone'] ?? NULL;
$a_hosts = isset($_GET['a']) ? explode(',', $_GET['a']) : array();
$ip_v4 = $_GET['ipv4'] ?? NULL;
$aaaa_hosts = isset($_GET['aaaa']) ? explode(',', $_GET['aaaa']) : array(); //optional
$ip_v6 = $_GET['ipv6'] ?? NULL; //optional
$api_base = 'https://api.cloudflare.com/client/v4/zones';
function printHelp()
{
$file_name = basename($_SERVER["SCRIPT_FILENAME"]);
$params = htmlspecialchars('?key=<pass>&zone=<domain>&ipv4=<ipaddr>&a=') . '<i>[records to update]</i>';
$optional = htmlspecialchars('&ipv6=<ip6addr>&aaaa=') . '<i>[records to update]</i>';
echo "Fritz!Box update URL: <b>https://{$_SERVER['HTTP_HOST']}/{$file_name}{$params}</b><br>";
echo 'Replace <b><i>[records to update]</i></b> in the url.<br>';
echo 'If you want to update multiple records of your zone, just add them comma separated: <i>\'&a=sub,sub2,*,@\'</i><br>';
echo "Optionally you can update AAAA records by appending <b>'{$optional}'</b> to the url.<br>";
echo 'If no AAAA record is defined but ipv6 is used, it will update both ip4 and ip6 defined for A records.';
};
function transformToRecordNames(&$hosts, $zone_name)
{
if ($hosts == NULL)
{
return;
}
foreach ($hosts as &$host)
{
$host = $host == '@' ? $zone_name : $host . '.' . $zone_name;
}
unset($host);
unset($hosts);
}
if(empty($_GET))
{
printHelp();
return http_response_code(200);
}
if ($key == NULL || $zone == NULL || empty($a_hosts) || $ip_v4 == NULL || (!empty($aaaa_hosts) && $ip_v6 == NULL))
{
printHelp();
return http_response_code(400);
}
transformToRecordNames($a_hosts, $zone);
transformToRecordNames($aaaa_hosts, $zone);
if ($aaaa_hosts == NULL && $ip_v6 != NULL)
{
$aaaa_hosts = $a_hosts;
}
// get zone identifier
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_base . '?name=' . $zone . '&status=active');
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . 'Bearer ' . $key, 'Content-Type: application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$response = json_decode(curl_exec($ch));
curl_close($ch);
if($response -> success == FALSE)
{
foreach ($response -> errors as $error)
{
echo 'Error fetching zone identifier: ';
echo $error -> message;
echo '<br>';
error_log('Error fetching zone identifier: ' . $error -> message);
if(isset($error -> error_chain))
{
foreach ($error -> error_chain as $error_detail)
{
error_log($error_detail -> message);
echo $error_detail -> message;
echo '<br>';
}
}
}
return http_response_code(500);
}
if(empty($response -> result))
{
echo 'Error fetching zone identifier:<br>Check if you have access to the given zone.';
error_log('Error fetching zone identifier: Check if you have access to the given zone.');
return http_response_code(500);
}
$zone_id = $response -> result[0] -> id;
// get record identifier
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_base . '/' . $zone_id . '/dns_records?type=A,AAAA');
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . 'Bearer ' . $key, 'Content-Type: application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$response = json_decode(curl_exec($ch));
curl_close($ch);
if($response -> success == FALSE)
{
foreach ($response -> errors as $value)
{
echo 'Error fetching record identifier:<br>';
echo $value -> message;
echo '<br>';
error_log('Error fetching record identifier: ' . $value -> message);
}
return http_response_code(500);
}
$records = array();
foreach ($response -> result as $value)
{
if(($value -> type == 'A' && in_array($value -> name, $a_hosts)) || ($value -> type == 'AAAA' && in_array($value -> name, $aaaa_hosts)))
{
array_push($records, $value);
}
}
if (count($records) != count($a_hosts) + count($aaaa_hosts))
{
echo 'Error updating records:<br>';
echo 'Check if the records you are trying to update already exist in the given zone.';
error_log('Error updating records: Check if the records you are trying to update already exist in the given zone.');
return http_response_code(500);
}
// update record(s)
$mh = curl_multi_init();
$ch_arr = array();
$results = array();
foreach ($records as $rec)
{
$record_name = $rec -> name;
$record_id = $rec -> id;
$record_type = $rec -> type;
$record_content = $record_type == 'A' ? $ip_v4 : $ip_v6;
$put_data = array('type' => $record_type, 'name' => $record_name, 'content' => $record_content, 'ttl' => 60, 'proxied' => FALSE );
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_base . '/' . $zone_id . '/dns_records/' . $record_id);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . 'Bearer ' . $key, 'Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($put_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
array_push($ch_arr, $ch);
curl_multi_add_handle($mh, $ch);
}
$running = null;
do
{
curl_multi_exec($mh, $running);
}
while($running > 0);
foreach ($ch_arr as $ch)
{
array_push($results, json_decode(curl_multi_getcontent($ch)));
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
foreach ($results as $result)
{
if($result -> success == FALSE)
{
foreach ($result -> errors as $index => $value)
{
echo 'Error updating record:<br>';
echo $value -> message;
echo '<br>';
error_log('Error updating record: ' . $value -> message);
}
return http_response_code(500);
}
}
echo 'SUCCESS';
?>
@cdotsharp
Copy link

I tried to use your script on my fb. But which entries are needed in the fritzbox webinterface when using custom dns? what is my domain, username and password?

@marcelschmidtdev
Copy link
Author

marcelschmidtdev commented Oct 19, 2020

I tried to use your script on my fb. But which entries are needed in the fritzbox webinterface when using custom dns? what is my domain, username and password?

Sorry for the late answer, didn't see that @cdotsharp

  • Domain is your Cloudflare zone name, see here, if you want to update multiple records just concatenate them with a comma. E.g. your Zone/Domain is example.com and you have a sub-domain you want to update, too, then it would be hosts=example.com,my-sub-domain-record.example.com
  • Username is the e-mail you registered with Cloudflare
  • Password is your API token which you have to create

In case of the Fritzbox web interface you need to use all the fields and the URL with the placeholders (e.g. <username> is replaced with the value of user name in the UI field)

@mattiarainieri
Copy link

I'm having some problem: frtizbox reports "Errore Dynamic DNS: l'aggiornamento Dynamic DNS ha avuto esito positivo, ma alla fine si è verificato un errore nella risoluzione DNS."
Checking on Cloudflare Dash the IP adress has not been modified!
I'm trying to update a single subdomain!
Thanks so much

@marcelschmidtdev
Copy link
Author

I'm having some problem: frtizbox reports "Errore Dynamic DNS: l'aggiornamento Dynamic DNS ha avuto esito positivo, ma alla fine si è verificato un errore nella risoluzione DNS."
Checking on Cloudflare Dash the IP adress has not been modified!
I'm trying to update a single subdomain!
Thanks so much

Are you using that exact URL scheme /upd.php?email=<username>&key=<pass>&hosts=<domain>?
Does your PHP server support curl and have you checked your server logs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment