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 }}",