# DDNS Manager
DDNS manager is a lightweight program to manage Device.DynamicDNS. TR069 datamodel as per 2.16 version.
This package is dependent on openwrt ddns-script package for the backend job of managing dynamic DNS updation.
We need to create this separate because there is a limitation in ddns-script package that it supports only
one host in every DNS client section in the UCI. Where as datamodel definition allows multiple host under
one DNS client object.
So to achieve the datamodel specification there was one option to patch the openwrt ddns-script package.

We found it not good enough to patch the ddns-script package in openwrt and hence decided to create this
'ddnsmngr' package to achive the datamodel specification.

## Package details
1. In this package we have used bbfdm micro-service mechanism to register the Device.DynamicDNS. datamodel
tree.
2. We have created separate UCI and init script namely "/etc/config/ddnsmngr" and "/etc/init.d/ddnsmngr"
respecively. Datamodel interaction will be with these files rather than the standard ddns-script UCI and
init script.
3. We have added a shell script in this package namely "ddnsmngr_updater.sh" which will spawn one ddns process
for each configured hosts as it is now being done from the standard "dynamic_dns_updater.sh" in the ddns-script
package.
We avoided the patch work in "ddns-script" package by introducing this script.  This script uses apis from the
"dynamic_dns_functions.sh" script in ddns-script package and does the ddns work.

## UCI format
Below is the format of the UCI:
```
config ddnsmngr 'global'
	option configfile '/var/run/ddnsmngr/ddnsmngr.json'
	option ddns_dateformat '%F %R'
	option ddns_rundir '/var/run/ddnsmngr'
	option ddns_logdir '/var/log/ddnsmngr'
	option ddns_loglines '250'
	option upd_privateip '0'
	option use_curl '1'
	option min_check_interval '300'   # floor for Server.CheckInterval
	option min_retry_interval '60'    # floor for Server.RetryInterval
	option default_max_retries '100'  # Server.MaxRetries when unset

config server 'ddns_server_1'
	option enabled '1'
	option service 'dynu.com'
	option name 'dynu.com'
	# optional authority overrides - unset means derive from the provider's
	# service file in /etc/ddnsmngr/servers/<service>.json
	# option protocol 'https'              # http | https
	# option server_address 'updates.dynu.com'
	# option server_port '443'
	# optional tuning - unset means use the global default / historical value
	# option check_interval '600'          # seconds, floored by min_check_interval
	# option retry_interval '600'          # seconds, floored by min_retry_interval
	# option max_retries '100'             # 0 = no retries

config client 'client_1'
	option enabled '1'
	option service_name 'dynu.com'
	option interface 'wan'
	option ip_network 'wan'
	option username 'xxxxxx'
	option password 'xxxxxx'
	option use_ipv6 '0'
	option force_ipversion '0'
	option use_https '0'
	option force_dnstcp '0'

config host 'client_1_host_1'
	option enabled '1'
	option dm_parent 'client_1'
	option lookup_host 'device1.loseyourip.com'

config host 'client_1_host_2'
	option enabled '1'
	option dm_parent 'client_1'
	option lookup_host 'device2.loseyourip.com'
```

## How it works
1. The init script of 'ddnsmngr' package parses each host section in the UCI and creates a 
1:1 client-host json configuration in the file mentioned in 'configfile' option of UCI. The
default file is "/var/run/ddnsmngr/ddnsmngr.json".
2. For above UCI config the json configuration would be like:
```
{
  "services": [
    {
      "interface": "wan",
      "service_name": "dynu.com",
      "username": "xxxxxx",
      "password": "xxxxxx",
      "lookup_host": "device1.loseyourip.com",
      "ip_network": "wan",
      "proc_info_file": "client_1_host_1",
      "use_ipv6": "0",
      "force_ipversion": "0",
      "use_https": "0",
      "force_dnstcp": "0",
      "server_address": "",
      "server_port": "",
      "protocol": "",
      "check_interval": "",
      "retry_interval": "",
      "max_retries": ""
    },
    {
      "interface": "wan",
      "service_name": "dynu.com",
      "username": "xxxxxx",
      "password": "xxxxxx",
      "lookup_host": "device2.loseyourip.com",
      "ip_network": "wan",
      "proc_info_file": "client_1_host_2",
      "use_ipv6": "0",
      "force_ipversion": "0",
      "use_https": "0",
      "force_dnstcp": "0",
      "server_address": "",
      "server_port": "",
      "protocol": "",
      "check_interval": "",
      "retry_interval": "",
      "max_retries": ""
    }
  ]
}
```
3. Init script then calls the "ddnsmngr_updater.sh" script with this json object as an argument.
4. "ddnsmngr_updater.sh" script then validates each object in the "services" array and starts
process for every valid object.

## Server parameters
`Device.DynamicDNS.Server.{i}.` maps to a `config server` section. The authority
parameters are derived from the provider's service file
(`/etc/ddnsmngr/servers/<service>.json`) and can be overridden per server:

| DM parameter | UCI option | Behaviour when unset |
| ------------ | ---------- | -------------------- |
| `ServiceName` | `service` | - (selects the service file) |
| `Protocol` | `protocol` | scheme from the service file URL (`http`/`https`) |
| `ServerAddress` | `server_address` | host from the service file URL |
| `ServerPort` | `server_port` | port from the service file URL (80/443) |
| `SupportedProtocols` | - (read only) | `HTTP,HTTPS`, or `HTTPS` only for an HTTPS service |
| `CheckInterval` | `check_interval` | 600s (floored by `min_check_interval`, default 300s) |
| `RetryInterval` | `retry_interval` | 600s (floored by `min_retry_interval`, default 60s) |
| `MaxRetries` | `max_retries` | `default_max_retries` (default 100); `0` = no retries |

When an authority override is set, `ddnsmngr_updater.sh` rebuilds the update URL,
keeping the userinfo and path/query from the service file and substituting only
the scheme/host/port. Unset overrides leave the service file URL untouched.

Note: `ServerAddress`, `ServerPort`, `CheckInterval`, `RetryInterval` and
`MaxRetries` are available only with the **ddns-script** backend. `Protocol` and
`SupportedProtocols` are available on both backends (derived from the service
file); a `Protocol` override only takes effect with the ddns-script backend.

## Client parameters

### Client.{i}.IPVersion
Selects which DNS record the client updates (backed by the `use_ipv6` UCI option):
- `4`  - A record (IPv4); `use_ipv6=0`
- `6`  - AAAA record (IPv6), `use_ipv6=1`; only valid for services that provide an ipv6 update URL
- `-1` - no preference (`use_ipv6` unset); **falls back to IPv4** (A record only)

Note: in this implementation `-1` means "auto -> IPv4", it does NOT update both
A and AAAA. ddns-scripts updates one address family per client, so to keep both
records updated configure two clients - one with IPVersion 4 and one with 6.

`IPVersion` is available only with the **ddns-script** backend.

## ddnsmngr_updater.sh VS dynamic_dns_updater.sh
In ddns-script package "dynamic_dns_updater.sh" script spawn processes for every DDNS client
object and every process can take care of a single host.

"ddnsmngr_updater.sh" spawn a separate process for each host and hence every hostname is being
updated by a dedicated process.

## Backend Support
We have analysed several dynamic DNS client packages for the purpose of using as a backend of
this ddnsmngr package, out of which "ddns-script" and "inadyn" are the two packages we have
found suitable for the implementation.
Below are the details of the packages which were analysed

| Package      | Language | License | Pros/Cons                                        |
| ------------ | -------- | ------- | ------------------------------------------------ |
| Enhanced-duc | C        | NA      | Update URL is hard coded and so a few service provider which uses this url format only work with this package. It also can not support custom providers |
| ddclient     | perl     | GPLv2   | Can not support custom providers at runtime, to add custom providers need to make changes in code |
| ddnsc        | python & REST | GPLv3 | Only a few providers are supported and with this package also custom providers can not be supported at runtime. A python plugin with REST api is need to be written for every custom providers |
| ndyndns      | C        | BSD     | Only supports DynDNS and Namecheap providers. Also this package is no longer in development or maintenance |
| inadyn       | C        | GPLv2   | Custom providers can be added at runtime. It is available in openwrt_package. It do not rely on DNS to identify if an update is required and supports multiple hostname configuration. We need to do some changes in its logging mechanism |
| ddns-script  | Shell    |         | Custom providers can be added at runtime. It is also available in openwrt_package. It does rely on DNS to identify if an update is required. Do not support multiple hostname but with some alternative implementation mutiple host can be supported |
