From a385c4f5ccd5da283fbbcdb7b223b356022479b9 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 28 Jul 2024 15:51:59 +1000 Subject: [PATCH] - updated README description - updated README debug command - updated IP detection and setup dns records logic. IPv4 gets from synology inputs, IPv6 detects via ipify if available --- README.md | 11 +++-- cloudflare.php | 113 ++++++++++++++++++------------------------------- 2 files changed, 48 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 7d42cc8..01f396c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ - 🆕 New hostname input format: `subdomain1.mydomain.com|subdomain2.mydomain.com` (each domain is separated: `|`) used to be with `---` separator - 🆕 Hostname input uses a new source of data (account) and support 256 symbols limit (DSM UI limit) -- 🆕 Autodetect IPv4 and IPv6 addresses +- 🆕 Autodetect IPv6 addresses via [ipify.org](https://www.ipify.org) - 🆕 Optimised request to Cloudflare API - 🆕 Installer script @@ -61,6 +61,8 @@ Before starting the installation process, make sure you have (and know) the foll Ensure the DNS A record(s) for the domain/zone(s) you wish to update with this script have been created (More information: [Managing DNS records](https://support.cloudflare.com/hc/en-us/articles/360019093151-Managing-DNS-records-in-Cloudflare)). + Case for if IpV6 is available (check via https://api6.ipify.org), you can create an AAAA record for the domain/zone(s) you wish to update with this script. + Your DNS records should appear (or already be setup as follows) in Cloudflare: (Note: Having Proxied turned on for your A records isn't necessary, but it will prevent those snooping around from easily finding out your current IP address) @@ -82,7 +84,7 @@ For assistance with vi commands, see: ## How to install -1. **SSH with root privledges on your supported device:** +1. **SSH with root privileges on your supported device:** a. For DSM Users: @@ -131,7 +133,8 @@ For multiple domains: __subdomain.mydomain.com|vpn.mydomain.com__ Finally, press the test connection button to confirm all information is correctly entered, before pressing Ok to save and confirm your details. -4. Enjoy 🍺 and __don't forget to deactivate SSH (step 1) if you don't need it__. OR [![Sponsor](https://img.shields.io/badge/sponsor-GitHub%20Sponsors-brightgreen)](https://github.com/sponsors/mrikirill) +4. Don't forget to deactivate SSH (step 1) if you don't need it. Leaving it active can be a security risk. +5. You're done! Optional, if you're happy with this script you could buy me ☕ or 🍺 here -> [![Sponsor](https://img.shields.io/badge/sponsor-GitHub%20Sponsors-brightgreen)](https://github.com/sponsors/mrikirill) ## Troubleshooting and known issues @@ -198,7 +201,7 @@ You can run this script directly to see output logs * Run this command: ``` -/usr/bin/php -d open_basedir=/usr/syno/bin/ddns -f /usr/syno/bin/ddns/cloudflare.php "" "domain1.com|vpn.domain2.com" "your-CloudFlare-token" "" "" +/usr/bin/php -d open_basedir=/usr/syno/bin/ddns -f /usr/syno/bin/ddns/cloudflare.php "domain1.com|vpn.domain2.com" "your-CloudFlare-token" "" "your-ip-address" ``` * Check output logs diff --git a/cloudflare.php b/cloudflare.php index 562ea47..029e12f 100755 --- a/cloudflare.php +++ b/cloudflare.php @@ -8,18 +8,20 @@ * @author https://github.com/mrikirill */ -// Note: username - $argv[1], password - $argv[2], hostname - $argv[3], ipv4 - $argv[4] -if ($argc !== 5 || count($argv) != 5) { +/** + * Synology passes 5 arguments in order: + * 0 - not in use + * 1 - username - uses for domains: domain1.com|vpn.domain2.com + * 2 - password - Cloudflare API token + * 3 - hostname - the script doesn't use it die to input limits + * 4 - IPv4 - Synology provided IPv4 + */ +if ($argc !== 5) { echo SynologyOutput::BAD_PARAMS; exit(); } -/** - * Note: - * Cloudflare API Key - $argv[2] - * Hostname - $argv[1] we use username as hostname source cause it supports 256 symbols -*/ -$cf = new SynologyCloudflareDDNSAgent($argv[2], $argv[1]); +$cf = new SynologyCloudflareDDNSAgent($argv[2], $argv[1], $argv[4]); $cf->setDnsRecords(); $cf->updateDnsRecords(); @@ -148,13 +150,13 @@ public function updateDnsRecord($zoneId, $recordId, $body) class Ipify { - const API_URL = 'https://api64.ipify.org'; + const API_URL = 'https://api6.ipify.org'; /** - * Universal: IPv4/IPv6 + * Return if external IPv6 address is available * @link https://www.ipify.org * @throws Exception */ - public function getExternalIpAddress() + public function tryGetIpv6() { $options = [ CURLOPT_URL => self::API_URL . "/?format=json", @@ -231,24 +233,16 @@ class SynologyCloudflareDDNSAgent private $cloudflareAPI; private $ipify; - function __construct($apiKey, $hostname) + function __construct($apiKey, $hostname, $ipv4) { $this->cloudflareAPI = new CloudflareAPI($apiKey); $this->ipify = new Ipify(); + $this->ipv4 = $ipv4; try { - $ip = $this->ipify->getExternalIpAddress(); - switch ($this->getIpAddressVersion($ip)) { - case 4: - $this->ipv4 = $ip; - break; - case 6: - $this->ipv6 = $ip; - break; - } + $ipv6 = $this->ipify->tryGetIpv6(); } catch (Exception $e) { - echo 'Error: ' . $e->getMessage(); - $this->exitWithSynologyMsg(); + // IPv6 not available } try { @@ -283,12 +277,16 @@ public function setDnsRecords() } try { - foreach ($this->dnsRecordList as &$dnsRecord) { + foreach ($this->dnsRecordList as $key => $dnsRecord) { $json = $this->cloudflareAPI->getDnsRecords($dnsRecord->zoneId, $dnsRecord->type, $dnsRecord->hostname); if (isset($json['result']['0'])) { - $dnsRecord->id = $json['result']['0']['id']; - $dnsRecord->ttl = $json['result']['0']['ttl']; - $dnsRecord->proxied = $json['result']['0']['proxied']; + // If the DNS record exists, update its ID, TTL, and proxied status + $this->dnsRecordList[$key]->id = $json['result']['0']['id']; + $this->dnsRecordList[$key]->ttl = $json['result']['0']['ttl']; + $this->dnsRecordList[$key]->proxied = $json['result']['0']['proxied']; + } else { + // If the DNS record does not exist, remove it from the list + unset($this->dnsRecordList[$key]); } } } catch (Exception $e) { @@ -341,15 +339,28 @@ private function matchHostnameWithZone($hostnameList = []) $zoneName = $zone['name']; foreach ($hostnameList as $hostname) { if (strpos($hostname, $zoneName) !== false) { - $this->dnsRecordList[$hostname] = new DnsRecordEntity( + // Add an IPv4 DNS record for each hostname that matches a zone + $this->dnsRecordList[] = new DnsRecordEntity( '', - $this->getDnsRecordType(), + 'A', $hostname, - $this->getIpAddress(), + $this->ipv4, $zoneId, '', '' ); + if (isset($this->ipv6)) { + // Add an IPv6 DNS record if an IPv6 address is available + $this->dnsRecordList[] = new DnsRecordEntity( + '', + 'AAAA', + $hostname, + $this->ipv6, + $zoneId, + '', + '' + ); + } } } } @@ -358,28 +369,6 @@ private function matchHostnameWithZone($hostnameList = []) } } - /** - * Determines the DNS record type (A or AAAA) based on the IP version. - * Returns 'AAAA' if IPv6 is present, or 'A' if only IPv4 is available. - * - * @return string The DNS record type, either 'A' or 'AAAA'. - */ - private function getDnsRecordType() - { - return $this->ipv6 ? 'AAAA' : 'A'; - } - - /** - * Retrieves the current external IP address. - * If both IPv4 and IPv6 are available, returns the IPv6 address. - * - * @return string The current external IP address, either IPv4 or IPv6. - */ - private function getIpAddress() - { - return $this->ipv6 ? $this->ipv6 : $this->ipv4; - } - /** * Extracts valid hostnames from a given string of hostnames separated by pipes (|). * @@ -449,25 +438,5 @@ private function exitWithSynologyMsg($msg = '') echo $msg; exit(); } - - /** - * Determines the IP address version (IPv4 or IPv6) of a given IP address. - * - * This method checks if the provided IP address is a valid, public IPv6 or IPv4 address. - * - * @param string $ip The IP address to be evaluated. - * @return int Returns 6 if the IP address is a valid IPv6 address, 4 if it is a valid IPv4 address. - * @throws InvalidArgumentException if the IP address is not valid or is not public. - */ - private function getIpAddressVersion($ip) - { - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { - return 6; - } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { - return 4; - } else { - throw new InvalidArgumentException('Invalid IP address'); - } - } } ?> \ No newline at end of file