OpenWRT DDNS

Wiki.TerraBase.info
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

This article was written about DDNS (Dynamic DNS (Domain Naming System (not Service))) on OpenWRT, but some of it could apply to DDNS using other platforms. On the server side, BIND / NAMED is used instead of the default OpenWRT DNS Daemon / Service, DNSMASQ. DDNS-SCRIPTS is / are the service(s) used on the client side.

To help readers understand things, the terms "user name" and "password" are used in place of TSIG sometimes. Functionally, both are the same. Anyway, the point is to avoid getting sidetracked on the TSIG subject and putting the focus on DDNS and BIND / NAMED.

Problems

OK, first, there are some problems getting the entire DDNS infrastructure to work. If you're not interested in these items, skip down a few sections (don't worry, they're addressed there in terms of proper configuration).

Encryption

The DDNS Client scripts for updating a DNS Server via NSUPDATE expects the "password" (TSIG) to be encrypted / signed using MD5 (HMAC-MD5). The version of the DNSSEC-KEYGEN tool provided by OpenWRT, which is typically used for generating / encrypting / signing a "password" in this form does not support MD5 (HMAC-MD5). Regardless of the arguments pertaining to MD5 encryption / signing, this issue presents a problem. The workaround is easy if you've got a CentOS, Debian, Ubuntu, etc. version of Linux available, as their version of the DNSSEC-KEYGEN utility does support MD5 (HMAC-MD5). As of 11.2020, there is very little information on this subject. This web page notes the issue, but there is no answer: https://github.com/openwrt/packages/issues/8927 According to this documentation, it is sort of implied that BIND / NAMED only uses MD5 (HMAC-MD5) when using TSIG.

Permissions

Out of the box, the permissions on the /etc/bind Directory will cause issues (assuming that's where master and slave files are stored, CentOS stores them in /var/named for example). Regardless of the location, the permissions must be set such that the BIND / NAMED Daemon / Service can write to the Directory (a specific example for this is 'journal' files).

Documenation

OpenWRT provides documentation for DDNS (the client side of things, not the server side) here, https://openwrt.org/docs/guide-user/services/ddns/client and here, https://openwrt.org/docs/guide-user/base-system/ddns It isn't the best written documentation in the world. But those guys are busy with other stuff, so that's OK. It also appears to be written by a non-English speaker, so be prepared for some bad grammar and sentences. But again, that's fine, as that person speaks at least one more language than I do (that's a compliment to them). There are also a couple of additional items that have been added to the DDNS Scripts that aren't addressed by the standard OpenWRT documentation and are only mentioned in passing here: https://github.com/openwrt/packages/issues/2348

Installation and Configuration

Installation for Client and Server

opkg update

Client: opkg install ddns-scripts ddns-scripts-nsupdate luci-app-ddns wget curl *

Server: opkg install bind-server bind-tools (bind-tools includes: bind-rndc bind-check, plus dependencies are all installed)**

If it isn't obvious, the client and server software will not be installed on the same router as DNS server software such as BIND / NAMED rely on having a static IP Address assigned to the OS they're installed on.

See the BIND / NAMED section in this article for additional information on configuration.

The LuCI GUI for the DDNS client is located here: Services, Dynamic DNS

See the Webmin section of this article for a GUI for BIND / NAMED.

* This OpenWRT article discusses and addresses several subjects related to WGET and CURL.

** It is necessary to disable the DNS functionality of DNSMASQ for BIND / NAMED to function properly. If DHCPD is used, and none of DNSMASQ's functionality is necessary, it is highly recommended to remove DNSMASQ instead of disabling it. Odd issues seem to crop up if it remains installed, even if disabled.

Directories and Configuration File Locations

  • /etc/config/ddns: File for configuring DDNS (this file is the one the LuCI GUI uses, but can be manually edited too)
  • /usr/share/ddns: Location for other DDNS items (do not edit these)
  • /usr/lib/ddns: Location for DDNS Scripts (do not edit these)
  • /tmp/run/ddns (Default storage location for DDNS information, can be changed)
  • /tmp/log/ddns (Default location for log files, can be changed)

DDNS References from OpenWRT

Various Options Defined: https://openwrt.org/docs/guide-user/base-system/ddns (keep in mind for this page, it seems to be a bit out of date, there are errors like the stated directory for services files is /usr/lib/ddns/services/"WhatEverFileName" which is actually /usr/share/ddns/list

Configuration for Client

Below is a working /etc/config/ddns configuration file for DDNS Scripts (with Domain Names removed);

config ddns 'global'
	option ddns_dateformat '%F %R'
	option ddns_loglines '250'
	option upd_privateip '0'
	option use_curl '1'

config service 'WhatEverArbitraryName'
	option service_name 'bind-nsupdate'
	option lookup_host 'WhatEverHostName.WhatEverDomainName.WhatEverSuffix'
	option username 'WhatEverUserName'
	option password 'WhatEverMD5EncryptedPassword'
	option domain 'WhatEverHostName.WhatEverDomainName.WhatEverSuffix'
	option enabled '1'
	option ip_source 'web'
	option ip_url 'https://domains.google.com/checkip'
	option bind_network 'wan'
	option force_ipversion '1'
	option interface 'wan'
	option dns_server 'WhatEverDNSServer.WhatEverDomainName.WhatEverSuffix'

It was found that WGET did not work reliably in situation where the router running the DDNS Client Scripts with a private WAN IP Address was behind another router with a public WAN IP Address. So CURL was used to address the issue. DD-WRT addresses this very issue with a radio button choice titled "Do not use external ip check" (worded poorly as negative questions are always a bit confusing), but OpenWRT offers no similar configuration via its GUI or in the configuration file (/etc/config/ddns).

Additional Configuration Example for a Router with Dual WAN Capability

The below example assumes the WAN interfaces (OpenWRT perspective, not from ifconfig perspective) are "wan" and "wwan"

config ddns 'global'
	option ddns_dateformat '%F %R'
	option ddns_loglines '250'
	option upd_privateip '0'
	option use_curl '1'

config service 'WhatEverArbitraryName'
	option service_name 'bind-nsupdate'
	option lookup_host 'WhatEverHostName.WhatEverDomainName.WhatEverSuffix'
	option username 'WhatEverUserName'
	option password 'WhatEverMD5EncryptedPassword'
	option domain 'WhatEverHostName.WhatEverDomainName.WhatEverSuffix'
	option enabled '1'
	option ip_source 'web'
	option ip_url 'https://domains.google.com/checkip'
	option bind_network 'wan'
	option force_ipversion '1'
	option interface 'wan'
	option dns_server 'WhatEverDNSServer.WhatEverDomainName.WhatEverSuffix'

config service 'WhatEverArbitraryName2'
	option service_name 'bind-nsupdate'
	option lookup_host 'WhatEverHostName2.WhatEverDomainName.WhatEverSuffix'
	option username 'WhatEverUserName'
	option password 'WhatEverMD5EncryptedPassword'
	option domain 'WhatEverHostName2.WhatEverDomain.WhatEverDomainName.WhatEverSuffix'
	option enabled '1'
	option ip_source 'web'
	option ip_url 'https://domains.google.com/checkip'
	option bind_network 'wwan'
	option force_ipversion '1'
	option interface 'wwan'
	option dns_server 'WhatEverDNSServer.WhatEverDomainName.WhatEverSuffix'

SPECIAL NOTES;

  • The OpenWRT DDNS Scripts do NOT like or tolerate dashes or hyphens ( - ) in the service name (IE: config service 'What-Ever-Name' will not work and result in nothing showing up in the LuCI GUI)
  • DDNS Scripts (and maybe BIND / NAMED) do NOT like or tolerate underscores ( _ ) in the lookup_host or domain directives (IE: What_Ever_Host_Name.WhatEverDomain.WhatEverSuffix will cause an error)

Configuration for Server

It is not the intent of this article to provide complete documentation on configuring a BIND / NAMED DNS server. This section assumes one has a functioning BIND / NAMED DNS Daemon / Service running.

Generating a "User Name" / "Password"

As noted, the DDNS Scripts only seem capable of using passwords that have been encrypted with MD5 (HMAC-MD5). Some documentation for BIND seems to indicate this is a choice made by BIND / NAMED. DH and SHA256 encryption methods were attempted, but did NOT work. An error in the DDNS log file indicated it was attempting to use an MD5 encrypted password, regardless of how the "password" / "secret" was encrypted.

And also, as noted previously, the dnssec-keygen program (opkg install bind-dnssec) provided by OpenWRT does not support generating MD5 passwords. If one attempts to do so, an error occurs. Solution, again as noted previously, use a different CentOS, or whatever Linux distribution to generate an MD5 encrypted "password" / "secret"

This command will generate a "password" / "secret" for the "user" named "ddns": dnssec-keygen -a HMAC-MD5 -b 512 -n USER ddns

The DDNS Script on the client will also complain that the "password isn't complex enough", but will still function, if a value of less than 512 is used in the above command.

Other utilities, noted here, such as tsig-keygen and ddns-confgen can also generate the appropriate "user name" / "password". But they are not available in any OpenWRT packages.

Directives for /etc/bind/named.conf

Below are the directives to add to named.conf (in OpenWRT, the path is /etc/bind/named.conf);

key "ddns" {
     algorithm hmac-md5;
     secret "WhatEverMD5EncryptedPassword";
};

zone "WhatEverDomainName.WhatEverSuffix" {
     type master;
     allow-update {
     key ddns;
     };
     file "/etc/bind/masters/WhatEverDomainName.WhatEverSuffix.hosts";
     };

The above directives essentially adds a User Name / Password (in encrypted form) that the BIND / NAMED DNS Daemon / Service use to authenticate clients. "ddns" is the User name and the text within, but not including the double quotes, is the password. The zone directive allow-update is one of several ways to restrict dynamic updates (IE, restricting updates to a set group and preventing anyone in the world from sending updates). The above allow-update directive allows anyone using the user name "ddns" (with the appropriate "password") to update the WhatEverDomain.WhatEverSuffix Domain (Example: Google.com). More information on allow-update and update-policy can be found here: https://docstore.mik.ua/orelly/networking_2ndEd/dns/ch10_02.htm

File Permissions for BIND / NAMED Daemon / Server

As it comes from OpenWRT, the bind-server (opkg install bind-server) does not include, specify, configure, or make provisions for any directories that have additional BIND / NAMED files for things such as master and slave zone storage files. Whatever directory is configured for BIND / NAMED to uses for purposes such as that, it must have the proper permissions to create "journal files" as that's where DDNS Client updates are initially stored. IE, updates are not written directly to WhatEverZoneFile.

  • chown bind:bind /etc/bind (for example, as the location for files can anywhere)
  • chmod 644 WhatEverMasterDirectoryForBIND and WhatEverFilesInThatDirectory

The bind User and Group are configured when the bind-server package is installed. If the BIND / NAMED Daemon / Service doesn't have write permissions, the journal file cannot be created. It results in a very, very obscure error in one of the below mentioned log files.

Secure Encrypted Communication Between Client and Server

...coming soon.

Troubleshooting

For troubleshooting on the client side, under Services, Dynamic DNS, Advanced Settings Tab, enable the Log to file Check Box.

For troubleshooting on the server side, the below directives can be added to /etc/bind/named.conf for a "Kick Ash" * amount of logging for BIND / NAMED;

logging {
    channel default_log {
          file "/tmp/log/named/default.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel general_log {
          file "/tmp/log/named/general.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel database_log {
          file "/tmp/log/named/database.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel security_log {
          file "/tmp/log/named/security.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel config_log {
          file "/tmp/log/named/config.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel resolver_log {
          file "/tmp/log/named/resolver.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel xfer-in_log {
          file "/tmp/log/named/xfer-in.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel xfer-out_log {
          file "/tmp/log/named/xfer-out.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel notify_log {
          file "/tmp/log/named/notify.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel client_log {
          file "/tmp/log/named/client.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel unmatched_log {
          file "/tmp/log/named/unmatched.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel queries_log {
          file "/tmp/log/named/queries.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel network_log {
          file "/tmp/log/named/network.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel update_log {
          file "/tmp/log/named/update.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel dispatch_log {
          file "/tmp/log/named/dispatch.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel dnssec_log {
          file "/tmp/log/named/dnssec.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };
    channel lame-servers_log {
          file "/tmp/log/named/lame-servers.log" versions 3 size 20m;
          print-time yes;
          print-category yes;
          print-severity yes;
          severity dynamic;
    };

    category default { default_log; };
    category general { general_log; };
    category database { database_log; };
    category security { security_log; };
    category config { config_log; };
    category resolver { resolver_log; };
    category xfer-in { xfer-in_log; };
    category xfer-out { xfer-out_log; };
    category notify { notify_log; };
    category client { client_log; };
    category unmatched { unmatched_log; };
    category queries { queries_log; };
    category network { network_log; };
    category update { update_log; };
    category dispatch { dispatch_log; };
    category dnssec { dnssec_log; };
    category lame-servers { lame-servers_log; };
};

Additional Subjects

DDNS on a Router Behind Another Router

To get it to work properly, below is an example of a router with two WAN connections to the internet where both interfaces are behind other routers (IE, the router with the DDNS service has local IP Addresses assigned to its WAN interfaces);

config ddns 'global'
        option upd_privateip '1'
        option use_curl '1'

config service 'WhatEverService1'
        option enabled '1'
        option interface 'wan1'
        option service_name 'WhatEverService'
        option lookup_host 'WhatEverHost1'
        option username 'WhatEverUserName'
        option password 'WhatEverPassword'
        option domain 'WhatEverDomain2'
        option ip_source 'web'
        option ip_url 'https://domains.google.com/checkip'
        option bind_network 'wan1'
        option force_ipversion '1'

config service 'WhatEverService2'
        option enabled '1'
        option interface 'wan2'
        option service_name 'WhatEverService'
        option lookup_host 'WhatEverHost2'
        option username 'WhatEverUserName'
        option password 'WhatEverPassword'
        option domain 'WhatEverDomain2'
        option ip_source 'web'
        option ip_url 'https://domains.google.com/checkip'
        option bind_network 'wan2'
        option force_ipversion '1'
  • Use the option use_curl '1' directive when using DDNS with a Dual WAN Router
  • If the router with the DDNS functionality is behind another router (like a hotspot, etc.), then the following directives should be configured for proper functionality;
    • use_curl '1'
    • interface 'WhatEverInterface' (not the "ifconfig" or "ip a" name, but the OpenWRT Name found in the LuCI GUI, Network, Interfaces)*
    • ip_source 'web'
    • ip_url 'WhatEverServiceLikeGoogle'
    • bind_network 'WhatEverInterface' (not the "ifconfig" or "ip a" name, but the OpenWRT Name found in the LuCI GUI, Network, Interfaces)*
    • force_ipversion '1'
    • ...and his requires CURL be installed: opkg install curl

* In the above example, wan1 and wan2 are just place holders. In real world circumstances, wan, wwan, etc. would be used, NOT eth1.2

The OpenWRT documentation states WGET is used by default, but for dual WAN routers, make sure the above noted use_curl directive is set.

Some good hints came from here: https://github.com/openwrt/packages/issues/8277

Private IP Address on WAN Interface

It can happen... It's possible an OpenWRT router could be located behind a service provider equipment that provides some sort of "pass through" functionality to the OpenWRT router. If that's the case, it appears from testing the DDNS client service can detect the proper public IP Address, but will not report it through the "Interface Name" method or the "Network Name" method (described above). No further testing was done to see if there were variations that could correct this minor issue as the "Web Service" method (like Google's) works as expected.

Client on DD-WRT

...as of early 2021, this section is not yet complete (and may never be completed because it would take less time to just switch from DD-WRT to OpenWRT)

Assumptions

Sadly, all of the below is predicated on having Entware installed on a DD-WRT router. That's a bit of an undertaking, but is described here: Entware Installation on DD-WRT

If you don't want to try installing Entware, then a good starting point might be the "Do NOT try these things..." in the next section (a bit ironic, but read the last paragraph of that section).

Do NOT try these things...

It was worth it to put this in here because it useful. Unlike every other well meaning tutorial on this, it actually works (IE, the others don't)

First of all, don't try the following;

Having made those disparaging remarks, the below configuration was based on all of the above (sort of). So even though the above seems a bit derogatory, it isn't. Thanks to the pioneering work of those nice people, the below configuration was created.

IP Address from Host Name

This is a prerequisite for having everything for DDNS on DD-WRT

The easiest way to get an IP Address from a host name on DD-WRT with Entware is;

  • opkg update
  • opkg install resolveip
  • Run this command: resolveip -4 www.google.com (and that will result in 172.217.6.36 or any number of other IP Addresses, depending on your physical location)

Special Thanks

Several authors of several web pages stand out in terms of offering really good tips this article is based on. The are noted below;

Testing Things Using NSUPDATE

  • Create a "Key File" in Bind Format using the Key to test against a BIND / NAMED DDNS server;
key "WhatEverKeyName" {
      algorithm WhatEverAlgorithmType;
      secret "WhatEverSecret";
};
  • Create a "Script File" for NSUPDATE (so the individual commands do not need to be entered);
server IPAddressOrURLofWhatEverDNSServer (Example: 1.2.3.4 My.DNSServer.com)
key WhatEverAlgorithm:KeyName KeySecret (Example hmac-sha256:DDNS weiu1-9{}@#$!adk==)
debug yes
zone WhatEverZoneName.Whatever (Example: google.com)
update add WhatEverHostName.DomainName.DomainSuffix. 86400 CNAME ns1 (Example: www.google.com 86400 CNAME ns1)
show
send
  • Run the Command: nsupdate -v WhatScriptFileName -y
  • Or exclude the above key directive in the 'Script File", then run the Command: nsupdate -k WhatEverKeyFileName -v WhatScriptFileName -y

Funky Errors with NSUPDATE?

 nsupdate --version
Error loading shared library libisc-9.18.7.so: No such file or directory (needed by /usr/bin/nsupdate)
Error loading shared library libdns-9.18.7.so: No such file or directory (needed by /usr/bin/nsupdate)
Error loading shared library libisccfg-9.18.7.so: No such file or directory (needed by /usr/bin/nsupdate)
Error loading shared library libirs-9.18.7.so: No such file or directory (needed by /usr/bin/nsupdate)
Error loading shared library libbind9-9.18.7.so: No such file or directory (needed by /usr/bin/nsupdate)

Try reinstalling the bind-libs package: opkg update bind-libs (or remove it and reinstall it, etc.)

Also make sure nslookup is installed: opkg install bind-nslookup

Oh, and just noticed (because generally BIND / NAMED is installed on all documented OpenWRT Routers, not DNSMASQ, it looks like full blown BIND / NAMED must be installed too with DNSMASQ disabled for the Network and Interface Method(s) to function.

Script Error for NSUPDATE (and possibly other scripts too)

If a DDNS update is being sent to a "non-default" port, IE instead of port 53 (udp or tcp), say port 5353, how is that done?

...well, the OpenWRT LuCI interface doesn't give any sort of hint. But, if one looks inside the /usr/lib/ddns/dynamic_dns_lucihelper.sh Script, there's a line that says this: -d DNS-SERVER       => dns_server=SERVER[:PORT]

OK, great. The dns_server / DNS-SERVER variable(s) equate to the DNS-Server Field in the LuCI GUI, so the format would be: W.X.Y.Z:WhatEverPort (where W.X.Y.Z can be an IP Address or Host Name) Right? Nope. It will give this error when the DDNS service is restarted: CRIT : sanitize on dns_server found characters outside allowed subset - TERMINATE

Hmmm, what could it be. Ah! "Behind the scenes", the OpenWRT DDNS update scripts are using the NSUPDATE command / binary from BIND / NAMED (look closely at what gets installed when the ddns-scripts-nsupdate package is installed and you'll notice that the bind-tools package is also installed, which includes the NSUPDATE command). So how does NSUPDATE format the IPAddress:Port thing? They use a space, instead of a colon ( : ). From NSUPDATE help: server address [port]

OK, great! So instead of W.X.Y.Z:WhatEverPort, it would be W.X.Y.Z WhatEverPort. Nope. Same error: CRIT : sanitize on dns_server found characters outside allowed subset - TERMINATE

Why is that?


So in conclusion, here's are the issues;

  • The LuCI GUI gives no indication of what the Host / IP Address [Port] Syntax should be (maybe because nothing will work)
  • The dynamic_dns_lucihelper.sh script has a clear indication of the syntax. But sadly it doesn't work (anymore?, maybe it did in the past or someone has it in mind for the future?).
  • The Regular Express in the dynamic_dns_functions.sh script file does not facilitate or allow spaces or colons (it shouldn't allow colons, but should allow a space because the NSUPDATE command allows for spaces), so this makes it impossible to add / change the default DNS port number (which is NECESSARY sometimes!, see comment below)
  • The "NSUPDATE Script" that is generated and "temporarily" (it only seems to keep the script there if there is an error, otherwise the IP address is stored there) stored in /tmp/run/ddns/WhateverName.DAT contains the NSUPDATE commands, one line of which is: server W.X.Y.Z [and can have the port number, but isn't put there by OpenWRT's DDNS script, but could be put there, but a space, not a colon is the correct syntax]. This means the "NSUPDATE Script" generated by OpenWRT's DDNS script never has the port number included (but it could).

Solution: Change the "Sanitize Regular Expression" (DNS_CHARSET, about twenty or thirty lines down in /usr/lib/ddns/dynamic_dns_functions.sh) to include a space!

  • Current Line of Code: DNS_CHARSET="[@a-zA-Z0-9._-]"
  • Should modified to be (allowing a single space to occur in-between an IP Address or Host Name and a Port Number, in the range of allowed port numbers 0-65535);
    • Allows multiple spaces anywhere: [@a-zA-Z0-9 ._-]
    • Allows a single space in-between the IP Address OR Host Name and Port Number: [@a-zA-Z0-9]*([ ][0-9._-]*)
    • To add to the one immediately above, if one wanted to strip spaces before and after, something like this (but NSUPDATE doesn't seem to care, and in fairness doesn't seem to care about multiple spaces in-between the IP / Host and Port);
      • [@a-zA-^Z0-9]*([ ][0-9._-]*)[\n]$

And I must say, it's easy to change the original Regular Expression to allow a space. But to also restrict the position of the space and the range of numbers is tough. So thanks to this site: https://regexr.com/ for making that testing possible.

It should also be mentioned that there's a comment in the dynamic_dns_functions.sh file that states: # dns character set. "-" must be the last character, so that's why the ._- is at the end of the Regular Expression. Best guess is that it's equivalent to the ; in C or similar type languages, etc. to mark the end of a line.

...didn't want to get too deep into the regular expression stuff, because even the beginning one doesn't check for a valid IPv4 'dotted' IP Address, etc., so there's a limit to the validation here. Decided to cap it at W.X.Y.Z "a single space only here" and a port number. IE, not going to check for or validate a good IPv4 'dotted' IP Address or a Host.DomainName.DomainSuffix, or DomainName.DomainSuffix, etc., again IE, it will be up to the user to establish / know what a valid IPv4 Address is, the proper Domain Name format, and the limit of Port ranges.

Also thought some of the characters in the Regular Expression might need to be escaped with a backslash for the sake of PHP or something, but didn't need to, for example: [@a-zA-^Z0-9]\*\([ ][0-9._-]\*\)[\n]$

And all of the above might be putting the cart before the horse, so here's the 'cart' (IE, the problem that being able to put in a different port number solves): Some internet providers like Comcast use some type of 'transparent' (in real world terms that would be evil and selfish) DNS proxy service that interferes with OpenWRT DDNS updates. This is a known 'issue' (Google it) was tested extensively to verify (a subject for another article coming soon).

Below is a submission to the OpenWRT maintainer of the code to modify the DDNS code to allow for a custom port number;

Maintainer: Michal Vasilek, AKA paper42 
Environment: OpenWRT 22.03.0 r19685-512e76967f (Linksys WRT3200ACM, Marvell mvebu CortexA9)
Source: feeds/packages/net/ddns-scripts
Section: net
Package Version: 2.8.2-25 (It could be solved in other versions, past and future, but this one has the issue)

Description: Neither the LuCI GUI or editing the /etc/config/ddns File allow for including a custom Port Number for the DNS Server.

Why?: Because there is 'sanitation code' in the dynamic_dns_functions.sh file on line 74 in the form of a Regular Expression Variable definition that prevents this from being done.

The LuCI GUI makes no mention of being able to configure a custom port.  However, the dynamic_dns_lucihelper.sh File includes the following line, which implies adding a custom Port Number is allowable or intended or used to be a feature, etc.;

```
 -d DNS-SERVER       => dns_server=SERVER[:PORT]
```

But as noted above, the 'sanitation code' does not allow for a Port Number to be added after the IP Address or Host Name of the DNS Server.

Good News!  The underlying BIND / NAMED command (NSUPDATE) that the script uses, does allow for a custom port number to be used, so this should be easy to implement.  This is from the NSUPDATE help information: server address [port]     (set primary server for zone).

Possible Solution?

The current code for /etc/lib/ddns/dynamic_dns_functions.sh Line 74 is below;
```
DNS_CHARSET="[@a-zA-Z0-9._-]"
```

Suggested Change would be to modify the Regular Expression to allow for a single space between the IP Address OR Host and a Port Number;
```
DNS_CHARSET=[@a-zA-Z0-9]*([ ][0-9._-]*)
```

Important Note: As noted above the dynamic_dns_lucihelper.sh File specifies: SERVER[:PORT]  That should be changed to SERVER [PORT]  The simplest solution seems to be to use a space since NSUPDATE uses a space instead of a colon.  Even though the convention is to use a colon as a separator between the SERVER and [:PORT], there would have to be additional code written to change the colon to a space for the sake of NSUPDATE.


I'm also assuming since it's a script rather than a compile binary file that the package is the same across all distribution of OpenWRT, so is hopefully a quick fix.  This is implied in the package file with this comment in the control section: Architecture: all

And finally: Why make this change? / Why would it be helpful?: There are several internet providers like Comcast X-Finity that impose a 'transparent' DNS proxy service that prevents NSUPDATE, and thus the OpenWRT DDNS Scripts from passing updates to a DNS server like BIND / NAMED.  Without going into detail, the proxy service strips out the TSIG information NSUPDATE sends so when it arrives at the destination DNS server, the update or change request is rejected.

Example(s) of some tests of this 'transparent' DNS proxy service are below;

Attempted DDNS update via NSUPDATE via TCP port 53 through Comcast internet;

;; TSIG PSEUDOSECTION:
ddns.                   0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1668702339 300 16 vG1mnvKCwArp+t3IFL5NbQ== 32700 NOERROR 0 

; TSIG error with server: expected a TSIG or SIG(0)

Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: REFUSED, id:  32700
;; flags: qr ra; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
Answer:
;; ->>HEADER<<- opcode: UPDATE, status: REFUSED, id:  32700
;; flags: qr ra; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0

Attempted DDNS update via NSUPDATE via TCP port 5353 through Comcast internet;

;; TSIG PSEUDOSECTION:
ddns.                   0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1668702432 300 16 U2Zm8vhayWBDSKAB1yJNlg== 50001 NOERROR 0 

Answer:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  50001
;; flags: qr; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; ZONE SECTION:
;fsddns.us.                     IN      SOA

;; TSIG PSEUDOSECTION:
ddns.                   0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1668702432 300 16 U2Zm8vhayWBDSKAB1yJNlg== 50001 NOERROR 0 

In conclusion, I believe this small modification of the Regular Expression that 'sanitizes' the DNS Server ( DNS-SERVER / dns_server ) Variable(s) and allowing for a custom DNS Port Number to be included could save a lot people from a lot of wasted time.