diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml index b47b38ddac..a3661651c5 100644 --- a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml @@ -91,6 +91,13 @@ dropdown + + account.dynipv6host + + text + true + Swap the interface identifier of the ipv6 address with the given partial ipv6 address + account.checkip_timeout diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml index 0888c87ace..600e17517b 100644 --- a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml @@ -165,6 +165,11 @@ + + N + /^::(([0-9a-fA-F]{1,4}:){0,3}[0-9a-fA-F]{1,4})?$/u + Entry is not a valid partial ipv6 address definition (e.g. ::1000). + 10 Y diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py index 4c3716b95e..7b600eb579 100755 --- a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py @@ -118,7 +118,8 @@ def execute(self): service = self.settings.get('checkip'), proto = 'https' if self.settings.get('force_ssl', False) else 'http', timeout = str(self.settings.get('checkip_timeout', '10')), - interface = self.settings['interface'] if self.settings.get('interface' ,'').strip() != '' else None + interface = self.settings['interface'] if self.settings.get('interface' ,'').strip() != '' else None, + dynipv6host = self.settings['dynipv6host'] if self.settings.get('dynipv6host' ,'').strip() != '' else None ) if self._current_address == None: diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py index 5618d9aeb6..5d9866ccfa 100755 --- a/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py @@ -67,11 +67,30 @@ def extract_address(host, txt): return "" -def checkip(service, proto='https', timeout='10', interface=None): +def transform_ip(ip, ipv6host=None): + """ Changes ipv6 addresses if interface identifier is given + :param ip: ip address + :param ipv6host: 64 bit interface identifier + :return str + """ + if ( + ipv6host and + isinstance(ip, ipaddress.IPv6Address) + ): + # extract 64 bit long prefix + prefix = ':'.join(str(ip).split(':')[:4]) + # normalize 64 bit interface identifier + host = ':'.join(str(ipaddress.ip_address(ipv6host).exploded).split(':')[4:]) + ip = ipaddress.ip_address(f"{prefix}:{host}") + return ip + + +def checkip(service, proto='https', timeout='10', interface=None, dynipv6host=None): """ find ip address using external services defined in checkip_service_list :param proto: protocol :param timeout: timeout in seconds :param interface: bind to interface + :param dynipv6host: optional partial ipv6 address :return: str """ if service.startswith('web_'): @@ -84,8 +103,10 @@ def checkip(service, proto='https', timeout='10', interface=None): params.append(interface) url = checkip_service_list[service] % proto params.append(url) - return extract_address(urlparse(url).hostname, + extracted_address = extract_address(urlparse(url).hostname, subprocess.run(params, capture_output=True, text=True).stdout) + address = ipaddress.ip_address(extracted_address) + return str(transform_ip(address, dynipv6host)) elif service in ['if', 'if6'] and interface is not None: # return first non private IPv[4|6] interface address ifcfg = subprocess.run(['/sbin/ifconfig', interface], capture_output=True, text=True).stdout @@ -96,7 +117,7 @@ def checkip(service, proto='https', timeout='10', interface=None): try: address = ipaddress.ip_address(parts[1]) if address.is_global: - return str(address) + return str(transform_ip(address, dynipv6host)) except ValueError: continue else: diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json index 79cf6fbbd1..4a2cc7a852 100644 --- a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json @@ -22,6 +22,7 @@ "zone": "{{ account.zone }}", "checkip": "{{ account.checkip }}", "interface": "{% if account.interface %}{{physical_interface(account.interface)}}{% endif %}", + "dynipv6host": "{{ account.dynipv6host }}", "checkip_timeout": {{ account.checkip_timeout }}, "force_ssl": {{ "true" if account.force_ssl == '1' else "false"}}, "ttl": "{{ account.ttl }}",