diff --git a/dns/ddclient/Makefile b/dns/ddclient/Makefile
new file mode 100644
index 000000000..5e3081e0a
--- /dev/null
+++ b/dns/ddclient/Makefile
@@ -0,0 +1,7 @@
+PLUGIN_NAME= ddclient
+PLUGIN_VERSION= 1
+PLUGIN_REVISION= 1
+PLUGIN_COMMENT= Dynamic DNS client
+PLUGIN_MAINTAINER= ad@opnsense.org
+
+.include "../../Mk/plugins.mk"
diff --git a/dns/ddclient/pkg-descr b/dns/ddclient/pkg-descr
new file mode 100644
index 000000000..0aed7a9f6
--- /dev/null
+++ b/dns/ddclient/pkg-descr
@@ -0,0 +1 @@
+Support for numerous Dynamic DNS services using ddclient
diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php
new file mode 100644
index 000000000..55e775635
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php
@@ -0,0 +1,73 @@
+searchBase(
+ "accounts.account",
+ ['enabled', 'service', 'description', 'username', 'hostnames'],
+ "description"
+ );
+ }
+
+ public function setItemAction($uuid)
+ {
+ return $this->setBase("account", "accounts.account", $uuid);
+ }
+
+ public function addItemAction()
+ {
+ return $this->addBase("account", "accounts.account");
+ }
+
+ public function getItemAction($uuid = null)
+ {
+ return $this->getBase("account", "accounts.account", $uuid);
+ }
+
+ public function delItemAction($uuid)
+ {
+ return $this->delBase("accounts.account", $uuid);
+ }
+
+ public function toggleItemAction($uuid, $enabled = null)
+ {
+ return $this->toggleBase("accounts.account", $uuid, $enabled);
+ }
+}
diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/ServiceController.php b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/ServiceController.php
new file mode 100644
index 000000000..a2fd9644c
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/ServiceController.php
@@ -0,0 +1,49 @@
+view->formDialogAccount = $this->getForm("dialogAccount");
+ // choose template
+ $this->view->pick('OPNsense/DynDNS/index');
+ }
+}
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
new file mode 100644
index 000000000..9a946d140
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml
@@ -0,0 +1,39 @@
+
diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/ACL/ACL.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/ACL/ACL.xml
new file mode 100644
index 000000000..4503995ce
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/ACL/ACL.xml
@@ -0,0 +1,9 @@
+
+
+ Services: Dynamic DNS
+
+ ui/dyndns/*
+ api/dyndns/*
+
+
+
diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.php
new file mode 100644
index 000000000..1515e1693
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.php
@@ -0,0 +1,39 @@
+
+ //OPNsense/DynDNS
+ 1.0.0
+
+ Dynamic DNS client
+
+
+
+
+ 1
+ Y
+
+
+
+
+
+ 1
+ Y
+
+
+ Y
+ A service type is required.
+
+ Noip
+ nsupdate (IPv4)
+ nsupdate (IPv6)
+
+
+
+ Y
+ /^([a-z0-9\-.@_:+])*$/u
+ The username contains invalid characters.
+
+
+ Y
+ /^[^\n]*$/
+
+
+ Y
+ N
+ Y
+ ,
+
+
+ N
+ /^([\t\n\v\f\r 0-9a-zA-Z.,_\x{00A0}-\x{FFFF}]){1,255}$/u
+ Description should be a string between 1 and 255 characters
+
+
+
+
+
diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Menu/Menu.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Menu/Menu.xml
new file mode 100644
index 000000000..633e9ccc1
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Menu/Menu.xml
@@ -0,0 +1,5 @@
+
diff --git a/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt b/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt
new file mode 100644
index 000000000..e4811c564
--- /dev/null
+++ b/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt
@@ -0,0 +1,96 @@
+{#
+
+OPNsense® is Copyright © 2021 by Deciso B.V.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+#}
+
+
+
+
+
+
+
+
+
+
+ | {{ lang._('ID') }} |
+ {{ lang._('Enabled') }} |
+ {{ lang._('Service') }} |
+ {{ lang._('Username') }} |
+ {{ lang._('Description') }} |
+ {{ lang._('Commands') }} |
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+ {{ lang._('After changing settings, please remember to apply them with the button below') }}
+
+
+
+
+
+
+
+
+{# include dialogs #}
+{{ partial("layout_partials/base_dialog",['fields':formDialogAccount,'id':'DialogAccount','label':lang._('Edit Account')])}}
diff --git a/dns/ddclient/src/opnsense/service/conf/actions.d/actions_ddclient.conf b/dns/ddclient/src/opnsense/service/conf/actions.d/actions_ddclient.conf
new file mode 100644
index 000000000..5ceb8a787
--- /dev/null
+++ b/dns/ddclient/src/opnsense/service/conf/actions.d/actions_ddclient.conf
@@ -0,0 +1,25 @@
+[start]
+command:/usr/local/etc/rc.d/ddclient start
+type:script
+message:starting ddclient
+
+[stop]
+command:/usr/local/etc/rc.d/ddclient stop
+type:script
+message:stopping ddclient
+
+[status]
+command:/usr/local/etc/rc.d/ddclient status; exit 0
+type:script_output
+message:get ddclient status
+
+[restart]
+command:/usr/local/etc/rc.d/ddclient restart
+type:script
+message:restarting ddclient
+description:Restart ddclient service
+
+[reload]
+command:/usr/local/etc/rc.d/ddclient reload
+type:script
+message:reload ddclient configuration
diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/+TARGETS b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/+TARGETS
new file mode 100644
index 000000000..0ec7e572f
--- /dev/null
+++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/+TARGETS
@@ -0,0 +1,2 @@
+rc.conf.d:/etc/rc.conf.d/ddclient
+ddclient.conf:/usr/local/etc/ddclient.conf
diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf
new file mode 100644
index 000000000..d6ee57eff
--- /dev/null
+++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf
@@ -0,0 +1,45 @@
+daemon=300 # check every 300 seconds
+syslog=yes # log update msgs to syslog
+pid=/var/run/ddclient.pid # record PID in file.
+
+{% set websettings = {
+ 'noip': 'use=web,',
+ 'nsupdate': 'use=web, web=http://ipv4.nsupdate.info/myip,'
+} %}
+
+
+{% if helpers.exists('OPNsense.DynDNS.accounts.account') %}
+{% for account in helpers.toList('OPNsense.DynDNS.accounts.account') %}
+{% if account.enabled|default('0') == '1' %}
+{% if account.service == 'noip' %}
+
+protocol=noip, \
+use=web, \
+ssl=yes, \
+login={{account.username}}, \
+password={{account.password}} \
+{{account.hostnames}}
+
+{% elif account.service == 'nsupdatev4' %}
+
+protocol=dyndns2, \
+use=web, web=https://ipv4.nsupdate.info/myip, \
+ssl=yes, \
+server=ipv4.nsupdate.info, \
+login={{account.username}}, \
+password='{{account.password}}' \
+{{account.hostnames}}
+
+{% elif account.service == 'nsupdatev6' %}
+protocol=dyndns2, \
+use=web, web=https://ipv6.nsupdate.info/myip, \
+ssl=yes, \
+server=ipv6.nsupdate.info, \
+login={{account.username}}, \
+password='{{account.password}}' \
+{{account.hostnames}}
+
+{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/rc.conf.d b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/rc.conf.d
new file mode 100644
index 000000000..c89c76bd5
--- /dev/null
+++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/rc.conf.d
@@ -0,0 +1,5 @@
+{% if not helpers.empty('OPNsense.DynDNS.general.enabled') %}
+ddclient_enable="YES"
+{% else %}
+ddclient_enable="NO"
+{% endif %}