From a10ba4000141366338a1152e98672287efc77236 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 11 Nov 2025 16:23:59 -0800 Subject: [PATCH] dynamicdns: Use only IPv4 for GnuDIP protocol - The following messages was seen on the ddns.freedombox.org server: "Unserviceable IP address from : user .fbx.one - IP: ". This is due to code that checks for validity of incoming IP address and fails. The current configuration only handles IPv4 address. Even if this restriction is lifted, GnuDIP code does not contain code to add/remove AAAA records. - Fix this by forcing GnuDIP HTTP update requests to go on IPv4. Tests: - Copy the code for _request_get_ipv4() into a python3 console and run _request_get_ipv4('https://ddns.freedombox.org/ip'). Do this on a dual stack machine with both public IPv4 and IPv6 addresses. Only IPv4 address returned. Changing the AF to AF_INET6 returns only the IPv6 address. - Take a test DDNS account offline. Configure it in FreedomBox stable VM. The IP address is properly updated. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/dynamicdns/gnudip.py | 30 +++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/plinth/modules/dynamicdns/gnudip.py b/plinth/modules/dynamicdns/gnudip.py index 3520b95ed..6f6b93b6a 100644 --- a/plinth/modules/dynamicdns/gnudip.py +++ b/plinth/modules/dynamicdns/gnudip.py @@ -5,6 +5,7 @@ GnuDIP client for updating Dynamic DNS records. import hashlib import logging +import socket from html.parser import HTMLParser import requests @@ -45,18 +46,43 @@ def _check_required_keys(dictionary: dict[str, str], keys: list[str]) -> None: f"Missing required keys in response: {', '.join(missing_keys)}") +def _request_get_ipv4(*args, **kwargs): + """Make a IPv4-only request. + + XXX: This monkey-patches socket.getaddrinfo which may causes issues when + running multiple threads. With urllib3 >= 2.4 (Trixie has 2.3), it is + possible to implement more cleanly. Use a session for requests library. In + the session add custom adapter for https:. In the adapter, override + creation of pool manager, and pass socket_family parameter. + """ + original = socket.getaddrinfo + + def getaddrinfo_ipv4(*args, **kwargs): + return original(args[0], args[1], socket.AF_INET, *args[3:], **kwargs) + + socket.getaddrinfo = getaddrinfo_ipv4 + try: + return requests.get(*args, **kwargs) + finally: + socket.getaddrinfo = original + + def update(server: str, domain: str, username: str, password: str) -> tuple[bool, str | None]: """Update Dynamic DNS record using GnuDIP protocol. Protocol documentation: https://gnudip2.sourceforge.net/gnudip-www/latest/gnudip/html/protocol.html + + GnuDIP at least as deployed on the FreedomBox foundation servers does not + support IPv6 (it does have any code to update AAAA records). So, make a + request only using IPv4 stack. """ domain = domain.removeprefix(username + '.') password_digest = hashlib.md5(password.encode()).hexdigest() http_server = f'https://{server}/gnudip/cgi-bin/gdipupdt.cgi' - response = requests.get(http_server) + response = _request_get_ipv4(http_server) salt_response = _extract_content_from_meta_tags(response.text) _check_required_keys(salt_response, ['salt', 'time', 'sign']) @@ -74,7 +100,7 @@ def update(server: str, domain: str, username: str, 'pass': password_digest, 'reqc': '2' } - update_response = requests.get(http_server, params=query_params) + update_response = _request_get_ipv4(http_server, params=query_params) update_result = _extract_content_from_meta_tags(update_response.text) _check_required_keys(update_result, ['retc'])