diff --git a/data/templates/vyos-hostsd/hosts.tmpl b/data/templates/vyos-hostsd/hosts.tmpl
index 8b73c6e51..03662d562 100644
--- a/data/templates/vyos-hostsd/hosts.tmpl
+++ b/data/templates/vyos-hostsd/hosts.tmpl
@@ -1,24 +1,25 @@
 ### Autogenerated by VyOS ###
 ### Do not edit, your changes will get overwritten ###
 
 # Local host
 127.0.0.1       localhost
 127.0.1.1       {{ host_name }}{% if domain_name %}.{{ domain_name }} {{ host_name }}{% endif %}
 
 # The following lines are desirable for IPv6 capable hosts
 ::1             localhost ip6-localhost ip6-loopback
 fe00::0         ip6-localnet
 ff00::0         ip6-mcastprefix
 ff02::1         ip6-allnodes
 ff02::2         ip6-allrouters
 
 {% if hosts is defined and hosts is not none %}
 # From 'system static-host-mapping' and DHCP server
 {%   for tag, taghosts in hosts.items() %}
 # {{ tag }}
 {%     for host, hostprops in taghosts.items() if hostprops.address is defined %}
-{{ "%-15s" | format(hostprops.address) }} {{ host }} {{ hostprops.aliases|join(' ') if hostprops.aliases is defined }}
+{%       for addr in hostprops.address %}
+{{ "%-15s" | format(addr) }} {{ host }} {{ hostprops.aliases|join(' ') if hostprops.aliases is defined }}
+{%       endfor %}
 {%     endfor %}
 {%   endfor %}
 {% endif %}
-
diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in
index 2b1644609..005a55ab3 100644
--- a/interface-definitions/dns-domain-name.xml.in
+++ b/interface-definitions/dns-domain-name.xml.in
@@ -1,114 +1,114 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="system">
     <children>
       <leafNode name="name-server" owner="${vyos_conf_scripts_dir}/host_name.py">
         <properties>
           <help>System Domain Name Servers (DNS)</help>
           <priority>400</priority>
           <completionHelp>
             <script>${vyos_completion_dir}/list_interfaces.py</script>
           </completionHelp>
           <valueHelp>
             <format>ipv4</format>
             <description>Domain Name Server IPv4 address</description>
           </valueHelp>
           <valueHelp>
             <format>ipv6</format>
             <description>Domain Name Server IPv6 address</description>
           </valueHelp>
           <valueHelp>
             <format>txt</format>
             <description>Use Domain Name Server from DHCP interface</description>
           </valueHelp>
           <multi/>
           <constraint>
             <validator name="ipv4-address"/>
             <validator name="ipv6-address"/>
             <validator name="interface-name"/>
           </constraint>
         </properties>
       </leafNode>
       <leafNode name="host-name" owner="${vyos_conf_scripts_dir}/host_name.py">
         <properties>
           <help>System host name (default: vyos)</help>
           <constraint>
             <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
           </constraint>
         </properties>
       </leafNode>
       <leafNode name="domain-name" owner="${vyos_conf_scripts_dir}/host_name.py">
         <properties>
           <help>System domain name</help>
           <constraint>
             <validator name="fqdn"/>
           </constraint>
         </properties>
       </leafNode>
       <node name="domain-search" owner="${vyos_conf_scripts_dir}/host_name.py">
         <properties>
           <help>Domain Name Server (DNS) domain completion order</help>
           <priority>400</priority>
         </properties>
         <children>
           <leafNode name="domain">
             <properties>
               <help>DNS domain completion order</help>
               <constraint>
                 <regex>[-a-zA-Z0-9.]+$</regex>
               </constraint>
               <constraintErrorMessage>Invalid domain name</constraintErrorMessage>
               <multi/>
             </properties>
           </leafNode>
         </children>
       </node>
       <node name="static-host-mapping" owner="${vyos_conf_scripts_dir}/host_name.py">
         <properties>
           <help>Map host names to addresses</help>
           <priority>400</priority>
         </properties>
         <children>
           <tagNode name="host-name">
             <properties>
               <help>Host name for static address mapping</help>
               <constraint>
                 <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$</regex>
               </constraint>
               <constraintErrorMessage>invalid hostname</constraintErrorMessage>
             </properties>
             <children>
               <leafNode name="alias">
                 <properties>
                   <help>Alias for this address</help>
                   <constraint>
                     <regex>.{1,63}$</regex>
                   </constraint>
                   <constraintErrorMessage>invalid alias hostname, needs to be between 1 and 63 charactes</constraintErrorMessage>
                   <multi />
                 </properties>
               </leafNode>
               <leafNode name="inet">
                 <properties>
                   <help>IP Address [REQUIRED]</help>
                   <valueHelp>
                     <format>ipv4</format>
                     <description>IPv4 address</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ipv6</format>
                     <description>IPv6 address</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ip-address"/>
                   </constraint>
+                  <multi/>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
-
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index a7135911d..87bad0dc6 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -1,187 +1,187 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
 import sys
 import copy
 
 import vyos.util
 import vyos.hostsd_client
 
 from vyos import ConfigError
 from vyos.config import Config
 from vyos.ifconfig import Section
 from vyos.template import is_ip
 from vyos.util import cmd
 from vyos.util import call
 from vyos.util import process_named_running
 from vyos import airbag
 airbag.enable()
 
 default_config_data = {
     'hostname': 'vyos',
     'domain_name': '',
     'domain_search': [],
     'nameserver': [],
     'nameservers_dhcp_interfaces': {},
     'static_host_mapping': {}
 }
 
 hostsd_tag = 'system'
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     hosts = copy.deepcopy(default_config_data)
 
     hosts['hostname'] = conf.return_value(['system', 'host-name'])
 
     # This may happen if the config is not loaded yet,
     # e.g. if run by cloud-init
     if not hosts['hostname']:
         hosts['hostname'] = default_config_data['hostname']
 
     if conf.exists(['system', 'domain-name']):
         hosts['domain_name'] = conf.return_value(['system', 'domain-name'])
         hosts['domain_search'].append(hosts['domain_name'])
 
     for search in conf.return_values(['system', 'domain-search', 'domain']):
         hosts['domain_search'].append(search)
 
     if conf.exists(['system', 'name-server']):
         for ns in conf.return_values(['system', 'name-server']):
             if is_ip(ns):
                 hosts['nameserver'].append(ns)
             else:
                 tmp = ''
                 if_type = Section.section(ns)
                 if conf.exists(['interfaces', if_type, ns, 'address']):
                     tmp = conf.return_values(['interfaces', if_type, ns, 'address'])
 
                 hosts['nameservers_dhcp_interfaces'].update({ ns : tmp })
 
     # system static-host-mapping
     for hn in conf.list_nodes(['system', 'static-host-mapping', 'host-name']):
         hosts['static_host_mapping'][hn] = {}
-        hosts['static_host_mapping'][hn]['address'] = conf.return_value(['system', 'static-host-mapping', 'host-name', hn, 'inet'])
+        hosts['static_host_mapping'][hn]['address'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'inet'])
         hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'alias'])
 
     return hosts
 
 
 def verify(hosts):
     if hosts is None:
         return None
 
     # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)"
     hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$")
     if not hostname_regex.match(hosts['hostname']):
         raise ConfigError('Invalid host name ' + hosts["hostname"])
 
     # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length"
     length = len(hosts['hostname'])
     if length < 1 or length > 63:
         raise ConfigError(
             'Invalid host-name length, must be less than 63 characters')
 
     all_static_host_mapping_addresses = []
     # static mappings alias hostname
     for host, hostprops in hosts['static_host_mapping'].items():
         if not hostprops['address']:
             raise ConfigError(f'IP address required for static-host-mapping "{host}"')
         all_static_host_mapping_addresses.append(hostprops['address'])
         for a in hostprops['aliases']:
             if not hostname_regex.match(a) and len(a) != 0:
                 raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"')
 
     for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items():
         # Warnin user if interface does not have DHCP or DHCPv6 configured
         if not set(interface_config).intersection(['dhcp', 'dhcpv6']):
             print(f'WARNING: "{interface}" is not a DHCP interface but uses DHCP name-server option!')
 
     return None
 
 
 def generate(config):
     pass
 
 def apply(config):
     if config is None:
         return None
 
     ## Send the updated data to vyos-hostsd
     try:
         hc = vyos.hostsd_client.Client()
 
         hc.set_host_name(config['hostname'], config['domain_name'])
 
         hc.delete_search_domains([hostsd_tag])
         if config['domain_search']:
             hc.add_search_domains({hostsd_tag: config['domain_search']})
 
         hc.delete_name_servers([hostsd_tag])
         if config['nameserver']:
             hc.add_name_servers({hostsd_tag: config['nameserver']})
 
         # add our own tag's (system) nameservers and search to resolv.conf
         hc.delete_name_server_tags_system(hc.get_name_server_tags_system())
         hc.add_name_server_tags_system([hostsd_tag])
 
         # this will add the dhcp client nameservers to resolv.conf
         for intf in config['nameservers_dhcp_interfaces']:
             hc.add_name_server_tags_system([f'dhcp-{intf}', f'dhcpv6-{intf}'])
 
         hc.delete_hosts([hostsd_tag])
         if config['static_host_mapping']:
             hc.add_hosts({hostsd_tag: config['static_host_mapping']})
 
         hc.apply()
     except vyos.hostsd_client.VyOSHostsdError as e:
         raise ConfigError(str(e))
 
     ## Actually update the hostname -- vyos-hostsd doesn't do that
 
     # No domain name -- the Debian way.
     hostname_new = config['hostname']
 
     # rsyslog runs into a race condition at boot time with systemd
     # restart rsyslog only if the hostname changed.
     hostname_old = cmd('hostnamectl --static')
     call(f'hostnamectl set-hostname --static {hostname_new}')
 
     # Restart services that use the hostname
     if hostname_new != hostname_old:
         call("systemctl restart rsyslog.service")
 
     # If SNMP is running, restart it too
     if process_named_running('snmpd'):
         call('systemctl restart snmpd.service')
 
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         sys.exit(1)
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 4c4bb036e..f4b1d0fc2 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -1,619 +1,619 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019-2020 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 #########
 # USAGE #
 #########
 # This daemon listens on its socket for JSON messages.
 # The received message format is:
 #
 # { 'type': '<message type>',
 #   'op': '<message operation>',
 #   'data': <data list or dict>
 # }
 #
 # For supported message types, see below.
 # 'op' can be 'add', delete', 'get', 'set' or 'apply'.
 # Different message types support different sets of operations and different
 # data formats.
 #
 # Changes to configuration made via add or delete don't take effect immediately,
 # they are remembered in a state variable and saved to disk to a state file.
 # State is remembered across daemon restarts but not across system reboots
 # as it's saved in a temporary filesystem (/run).
 #
 # 'apply' is a special operation that applies the configuration from the cached
 # state, rendering all config files and reloading relevant daemons (currently
 # just pdns-recursor via rec-control).
 #
 # note: 'add' operation also acts as 'update' as it uses dict.update, if the
 # 'data' dict item value is a dict. If it is a list, it uses list.append.
 #
 ### tags
 # Tags can be arbitrary, but they are generally in this format:
 # 'static', 'system', 'dhcp(v6)-<intf>' or 'dhcp-server-<client ip>'
 # They are used to distinguish entries created by different scripts so they can
 # be removed and recreated without having to track what needs to be changed.
 # They are also used as a way to control which tags settings (e.g. nameservers)
 # get added to various config files via name_server_tags_(recursor|system)
 #
 ### name_server_tags_(recursor|system)
 # A list of tags whose nameservers and search domains is used to generate
 # /etc/resolv.conf and pdns-recursor config.
 # system list is used to generate resolv.conf.
 # recursor list is used to generate pdns-rec forward-zones.
 # When generating each file, the order of nameservers is as per the order of
 # name_server_tags (the order in which tags were added), then the order in
 # which the name servers for each tag were added.
 #
 #### Message types
 #
 ### name_servers
 #
 # { 'type': 'name_servers',
 #   'op': 'add',
 #   'data': {
 #       '<str tag>': ['<str nameserver>', ...],
 #       ...
 #     }
 # }
 #
 # { 'type': 'name_servers',
 #   'op': 'delete',
 #   'data': ['<str tag>', ...]
 # }
 #
 # { 'type': 'name_servers',
 #   'op': 'get',
 #   'tag_regex': '<str regex>'
 # }
 # response:
 # { 'data': {
 #       '<str tag>': ['<str nameserver>', ...],
 #       ...
 #     }
 # }
 #
 ### name_server_tags
 #
 # { 'type': 'name_server_tags',
 #   'op': 'add',
 #   'data':  ['<str tag>', ...]
 # }
 #
 # { 'type': 'name_server_tags',
 #   'op': 'delete',
 #   'data': ['<str tag>', ...]
 # }
 #
 # { 'type': 'name_server_tags',
 #   'op': 'get',
 # }
 # response:
 # { 'data': ['<str tag>', ...] }
 #
 ### forward_zones
 ## Additional zones added to pdns-recursor forward-zones-file.
 ## If recursion_desired is true, '+' will be prepended to the zone line.
 ## If addnta is true, a NTA (Negative Trust Anchor) will be added via
 ## lua-config-file.
 #
 # { 'type': 'forward_zones',
 #   'op': 'add',
 #   'data': {
 #       '<str zone>': {
 #           'server': ['<str nameserver>', ...],
 #           'addnta': <bool>,
 #           'recursion_desired': <bool>
 #         }
 #       ...
 #     }
 # }
 #
 # { 'type': 'forward_zones',
 #   'op': 'delete',
 #   'data': ['<str zone>', ...]
 # }
 #
 # { 'type': 'forward_zones',
 #   'op': 'get',
 # }
 # response:
 # { 'data': {
 #       '<str zone>': { ... },
 #       ...
 #     }
 # }
 #
 #
 ### search_domains
 #
 # { 'type': 'search_domains',
 #   'op': 'add',
 #   'data': {
 #       '<str tag>': ['<str domain>', ...],
 #       ...
 #     }
 # }
 #
 # { 'type': 'search_domains',
 #   'op': 'delete',
 #   'data': ['<str tag>', ...]
 # }
 #
 # { 'type': 'search_domains',
 #   'op': 'get',
 # }
 # response:
 # { 'data': {
 #       '<str tag>': ['<str domain>', ...],
 #       ...
 #     }
 # }
 #
 ### hosts
 #
 # { 'type': 'hosts',
 #   'op': 'add',
 #   'data': {
 #       '<str tag>': {
 #           '<str host>': {
 #               'address': '<str address>',
 #               'aliases': ['<str alias>, ...]
 #             },
 #           ...
 #         },
 #       ...
 #     }
 # }
 #
 # { 'type': 'hosts',
 #   'op': 'delete',
 #   'data': ['<str tag>', ...]
 # }
 #
 # { 'type': 'hosts',
 #   'op': 'get'
 #   'tag_regex': '<str regex>'
 # }
 # response:
 # { 'data': {
 #       '<str tag>': {
 #           '<str host>': {
 #               'address': '<str address>',
 #               'aliases': ['<str alias>, ...]
 #             },
 #           ...
 #         },
 #       ...
 #     }
 # }
 ### host_name
 #
 # { 'type': 'host_name',
 #   'op': 'set',
 #   'data': {
 #       'host_name': '<str hostname>'
 #       'domain_name': '<str domainname>'
 #     }
 # }
 
 import os
 import sys
 import time
 import json
 import signal
 import traceback
 import re
 import logging
 import zmq
 from voluptuous import Schema, MultipleInvalid, Required, Any
 from collections import OrderedDict
 from vyos.util import popen, chown, chmod_755, makedir, process_named_running
 from vyos.template import render
 
 debug = True
 
 # Configure logging
 logger = logging.getLogger(__name__)
 # set stream as output
 logs_handler = logging.StreamHandler()
 logger.addHandler(logs_handler)
 
 if debug:
     logger.setLevel(logging.DEBUG)
 else:
     logger.setLevel(logging.INFO)
 
 RUN_DIR = "/run/vyos-hostsd"
 STATE_FILE = os.path.join(RUN_DIR, "vyos-hostsd.state")
 SOCKET_PATH = "ipc://" + os.path.join(RUN_DIR, 'vyos-hostsd.sock')
 
 RESOLV_CONF_FILE = '/etc/resolv.conf'
 HOSTS_FILE = '/etc/hosts'
 
 PDNS_REC_USER = PDNS_REC_GROUP = 'pdns'
 PDNS_REC_RUN_DIR = '/run/powerdns'
 PDNS_REC_LUA_CONF_FILE = f'{PDNS_REC_RUN_DIR}/recursor.vyos-hostsd.conf.lua'
 PDNS_REC_ZONES_FILE = f'{PDNS_REC_RUN_DIR}/recursor.forward-zones.conf'
 
 STATE = {
     "name_servers": {},
     "name_server_tags_recursor": [],
     "name_server_tags_system": [],
     "forward_zones": {},
     "hosts": {},
     "host_name": "vyos",
     "domain_name": "",
     "search_domains": {},
     "changes": 0
     }
 
 # the base schema that every received message must be in
 base_schema = Schema({
     Required('op'): Any('add', 'delete', 'set', 'get', 'apply'),
     'type': Any('name_servers',
         'name_server_tags_recursor', 'name_server_tags_system',
         'forward_zones', 'search_domains', 'hosts', 'host_name'),
     'data': Any(list, dict),
     'tag': str,
     'tag_regex': str
     })
 
 # more specific schemas
 op_schema = Schema({
     'op': str,
     }, required=True)
 
 op_type_schema = op_schema.extend({
     'type': str,
     }, required=True)
 
 host_name_add_schema = op_type_schema.extend({
     'data': {
         'host_name': str,
         'domain_name': Any(str, None)
         }
     }, required=True)
 
 data_dict_list_schema = op_type_schema.extend({
     'data': {
         str: [str]
         }
     }, required=True)
 
 data_list_schema = op_type_schema.extend({
     'data': [str]
     }, required=True)
 
 tag_regex_schema = op_type_schema.extend({
     'tag_regex': str
     }, required=True)
 
 forward_zone_add_schema = op_type_schema.extend({
     'data': {
         str: {
             'server': [str],
             'addnta': Any({}, None),
             'recursion_desired': Any({}, None),
             }
         }
     }, required=False)
 
 hosts_add_schema = op_type_schema.extend({
     'data': {
         str: {
             str: {
-            'address': str,
+            'address': [str],
             'aliases': [str]
                 }
             }
         }
     }, required=True)
 
 
 # op and type to schema mapping
 msg_schema_map = {
     'name_servers': {
         'add': data_dict_list_schema,
         'delete': data_list_schema,
         'get': tag_regex_schema
         },
     'name_server_tags_recursor': {
         'add': data_list_schema,
         'delete': data_list_schema,
         'get': op_type_schema
         },
     'name_server_tags_system': {
         'add': data_list_schema,
         'delete': data_list_schema,
         'get': op_type_schema
         },
     'forward_zones': {
         'add': forward_zone_add_schema,
         'delete': data_list_schema,
         'get': op_type_schema
         },
     'search_domains': {
         'add': data_dict_list_schema,
         'delete': data_list_schema,
         'get': tag_regex_schema
         },
     'hosts': {
         'add': hosts_add_schema,
         'delete': data_list_schema,
         'get': tag_regex_schema
         },
     'host_name': {
         'set': host_name_add_schema
         },
     None: {
         'apply': op_schema
         }
     }
 
 def validate_schema(data):
     base_schema(data)
 
     try:
         schema = msg_schema_map[data['type'] if 'type' in data else None][data['op']]
         schema(data)
     except KeyError:
         raise ValueError((
             'Invalid or unknown combination: '
             f'op: "{data["op"]}", type: "{data["type"]}"'))
 
 
 def pdns_rec_control(command):
     # pdns-r process name is NOT equal to the name shown in ps
     if not process_named_running('pdns-r/worker'):
         logger.info(f'pdns_recursor not running, not sending "{command}"')
         return
 
     logger.info(f'Running "rec_control {command}"')
     (ret,ret_code) = popen((
             f"rec_control --socket-dir={PDNS_REC_RUN_DIR} {command}"))
     if ret_code > 0:
         logger.exception((
             f'"rec_control {command}" failed with exit status {ret_code}, '
             f'output: "{ret}"'))
 
 def make_resolv_conf(state):
     logger.info(f"Writing {RESOLV_CONF_FILE}")
     render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.tmpl', state,
             user='root', group='root')
 
 def make_hosts(state):
     logger.info(f"Writing {HOSTS_FILE}")
     render(HOSTS_FILE, 'vyos-hostsd/hosts.tmpl', state,
             user='root', group='root')
 
 def make_pdns_rec_conf(state):
     logger.info(f"Writing {PDNS_REC_LUA_CONF_FILE}")
 
     # on boot, /run/powerdns does not exist, so create it
     makedir(PDNS_REC_RUN_DIR, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
     chmod_755(PDNS_REC_RUN_DIR)
 
     render(PDNS_REC_LUA_CONF_FILE,
             'dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl',
             state, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
 
     logger.info(f"Writing {PDNS_REC_ZONES_FILE}")
     render(PDNS_REC_ZONES_FILE,
             'dns-forwarding/recursor.forward-zones.conf.tmpl',
             state, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
 
 def set_host_name(state, data):
     if data['host_name']:
         state['host_name'] = data['host_name']
     if 'domain_name' in data:
         state['domain_name'] = data['domain_name']
 
 def add_items_to_dict(_dict, items):
     """
     Dedupes and preserves sort order.
     """
     assert isinstance(_dict, dict)
     assert isinstance(items, dict)
 
     if not items:
         return
 
     _dict.update(items)
 
 def add_items_to_dict_as_keys(_dict, items):
     """
     Added item values are converted to OrderedDict with the value as keys
     and null values. This is to emulate a list but with inherent deduplication.
     Dedupes and preserves sort order.
     """
     assert isinstance(_dict, dict)
     assert isinstance(items, dict)
 
     if not items:
         return
 
     for item, item_val in items.items():
         if item not in _dict:
             _dict[item] = OrderedDict({})
         _dict[item].update(OrderedDict.fromkeys(item_val))
 
 def add_items_to_list(_list, items):
     """
     Dedupes and preserves sort order.
     """
     assert isinstance(_list, list)
     assert isinstance(items, list)
 
     if not items:
         return
 
     for item in items:
         if item not in _list:
             _list.append(item)
 
 def delete_items_from_dict(_dict, items):
     """
     items is a list of keys to delete.
     Doesn't error if the key doesn't exist.
     """
     assert isinstance(_dict, dict)
     assert isinstance(items, list)
 
     for item in items:
         if item in _dict:
             del _dict[item]
 
 def delete_items_from_list(_list, items):
     """
     items is a list of items to remove.
     Doesn't error if the key doesn't exist.
     """
     assert isinstance(_list, list)
     assert isinstance(items, list)
 
     for item in items:
         if item in _list:
             _list.remove(item)
 
 def get_items_from_dict_regex(_dict, item_regex_string):
     """
     Returns the items whose keys match item_regex_string.
     """
     assert isinstance(_dict, dict)
     assert isinstance(item_regex_string, str)
 
     tmp = {}
     regex = re.compile(item_regex_string)
     for item in _dict:
         if regex.match(item):
             tmp[item] = _dict[item]
     return tmp
 
 def get_option(msg, key):
     if key in msg:
         return msg[key]
     else:
         raise ValueError("Missing required option \"{0}\"".format(key))
 
 def handle_message(msg):
     result = None
     op = get_option(msg, 'op')
 
     if op in ['add', 'delete', 'set']:
         STATE['changes'] += 1
 
     if op == 'delete':
         _type = get_option(msg, 'type')
         data = get_option(msg, 'data')
         if _type in ['name_servers', 'forward_zones', 'search_domains', 'hosts']:
             delete_items_from_dict(STATE[_type], data)
         elif _type in ['name_server_tags_recursor', 'name_server_tags_system']:
             delete_items_from_list(STATE[_type], data)
         else:
             raise ValueError(f'Operation "{op}" unknown data type "{_type}"')
     elif op == 'add':
         _type = get_option(msg, 'type')
         data = get_option(msg, 'data')
         if _type in ['name_servers', 'search_domains']:
             add_items_to_dict_as_keys(STATE[_type], data)
         elif _type in ['forward_zones', 'hosts']:
             add_items_to_dict(STATE[_type], data)
             # maybe we need to rec_control clear-nta each domain that was removed here?
         elif _type in ['name_server_tags_recursor', 'name_server_tags_system']:
             add_items_to_list(STATE[_type], data)
         else:
             raise ValueError(f'Operation "{op}" unknown data type "{_type}"')
     elif op == 'set':
         _type = get_option(msg, 'type')
         data = get_option(msg, 'data')
         if _type == 'host_name':
             set_host_name(STATE, data)
         else:
             raise ValueError(f'Operation "{op}" unknown data type "{_type}"')
     elif op == 'get':
         _type = get_option(msg, 'type')
         if _type in ['name_servers', 'search_domains', 'hosts']:
             tag_regex = get_option(msg, 'tag_regex')
             result = get_items_from_dict_regex(STATE[_type], tag_regex)
         elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones']:
             result = STATE[_type]
         else:
             raise ValueError(f'Operation "{op}" unknown data type "{_type}"')
     elif op == 'apply':
         logger.info(f"Applying {STATE['changes']} changes")
         make_resolv_conf(STATE)
         make_hosts(STATE)
         make_pdns_rec_conf(STATE)
         pdns_rec_control('reload-lua-config')
         pdns_rec_control('reload-zones')
         logger.info("Success")
         result = {'message': f'Applied {STATE["changes"]} changes'}
         STATE['changes'] = 0
 
     else:
         raise ValueError(f"Unknown operation {op}")
 
     logger.debug(f"Saving state to {STATE_FILE}")
     with open(STATE_FILE, 'w') as f:
         json.dump(STATE, f)
 
     return result
 
 if __name__ == '__main__':
     # Create a directory for state checkpoints
     os.makedirs(RUN_DIR, exist_ok=True)
     if os.path.exists(STATE_FILE):
         with open(STATE_FILE, 'r') as f:
             try:
                 STATE = json.load(f)
             except:
                 logger.exception(traceback.format_exc())
                 logger.exception("Failed to load the state file, using default")
 
     context = zmq.Context()
     socket = context.socket(zmq.REP)
 
     # Set the right permissions on the socket, then change it back
     o_mask = os.umask(0o000)
     socket.bind(SOCKET_PATH)
     os.umask(o_mask)
 
     while True:
         #  Wait for next request from client
         msg_json = socket.recv().decode()
         logger.debug(f"Request data: {msg_json}")
 
         try:
             msg = json.loads(msg_json)
             validate_schema(msg)
 
             resp = {}
             resp['data'] = handle_message(msg)
         except ValueError as e:
             resp['error'] = str(e)
         except MultipleInvalid as e:
             # raised by schema
             resp['error'] = f'Invalid message: {str(e)}'
             logger.exception(resp['error'])
         except:
             logger.exception(traceback.format_exc())
             resp['error'] = "Internal error"
 
         #  Send reply back to client
         socket.send(json.dumps(resp).encode())
         logger.debug(f"Sent response: {resp}")
diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client
index d4d38315a..a0515951a 100755
--- a/src/utils/vyos-hostsd-client
+++ b/src/utils/vyos-hostsd-client
@@ -1,165 +1,166 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 #
 
 import sys
 import argparse
 
 import vyos.hostsd_client
 
 parser = argparse.ArgumentParser(allow_abbrev=False)
 group = parser.add_mutually_exclusive_group()
 
 group.add_argument('--add-name-servers', type=str, nargs='*')
 group.add_argument('--delete-name-servers', action='store_true')
 group.add_argument('--get-name-servers', type=str, const='.*', nargs='?')
 
 group.add_argument('--add-name-server-tags-recursor', type=str, nargs='*')
 group.add_argument('--delete-name-server-tags-recursor', type=str, nargs='*')
 group.add_argument('--get-name-server-tags-recursor', action='store_true')
 
 group.add_argument('--add-name-server-tags-system', type=str, nargs='*')
 group.add_argument('--delete-name-server-tags-system', type=str, nargs='*')
 group.add_argument('--get-name-server-tags-system', action='store_true')
 
 group.add_argument('--add-forward-zone', type=str, nargs='?')
 group.add_argument('--delete-forward-zones', type=str, nargs='*')
 group.add_argument('--get-forward-zones', action='store_true')
 
 group.add_argument('--add-search-domains', type=str, nargs='*')
 group.add_argument('--delete-search-domains', action='store_true')
 group.add_argument('--get-search-domains', type=str, const='.*', nargs='?')
 
 group.add_argument('--add-hosts', type=str, nargs='*')
 group.add_argument('--delete-hosts', action='store_true')
 group.add_argument('--get-hosts', type=str, const='.*', nargs='?')
 
 group.add_argument('--set-host-name', type=str)
 
 # for --set-host-name
 parser.add_argument('--domain-name', type=str)
 
 # for forward zones
 parser.add_argument('--nameservers', type=str, nargs='*')
 parser.add_argument('--addnta', action='store_true')
 parser.add_argument('--recursion-desired', action='store_true')
 
 parser.add_argument('--tag', type=str)
 
 # users must call --apply either in the same command or after they're done
 parser.add_argument('--apply', action="store_true")
 
 args = parser.parse_args()
 
 try:
     client = vyos.hostsd_client.Client()
     ops = 1
 
     if args.add_name_servers:
         if not args.tag:
             raise ValueError("--tag is required for this operation")
         client.add_name_servers({args.tag: args.add_name_servers})
     elif args.delete_name_servers:
         if not args.tag:
             raise ValueError("--tag is required for this operation")
         client.delete_name_servers([args.tag])
     elif args.get_name_servers:
         print(client.get_name_servers(args.get_name_servers))
 
     elif args.add_name_server_tags_recursor:
         client.add_name_server_tags_recursor(args.add_name_server_tags_recursor)
     elif args.delete_name_server_tags_recursor:
         client.delete_name_server_tags_recursor(args.delete_name_server_tags_recursor)
     elif args.get_name_server_tags_recursor:
         print(client.get_name_server_tags_recursor())
 
     elif args.add_name_server_tags_system:
         client.add_name_server_tags_system(args.add_name_server_tags_system)
     elif args.delete_name_server_tags_system:
         client.delete_name_server_tags_system(args.delete_name_server_tags_system)
     elif args.get_name_server_tags_system:
         print(client.get_name_server_tags_system())
 
     elif args.add_forward_zone:
         if not args.nameservers:
             raise ValueError("--nameservers is required for this operation")
         client.add_forward_zones(
                 { args.add_forward_zone: {
                     'server': args.nameservers,
                     'addnta': args.addnta,
                     'recursion_desired': args.recursion_desired
                     }
                 })
     elif args.delete_forward_zones:
         client.delete_forward_zones(args.delete_forward_zones)
     elif args.get_forward_zones:
         print(client.get_forward_zones())
 
     elif args.add_search_domains:
         if not args.tag:
             raise ValueError("--tag is required for this operation")
         client.add_search_domains({args.tag: args.add_search_domains})
     elif args.delete_search_domains:
         if not args.tag:
             raise ValueError("--tag is required for this operation")
         client.delete_search_domains([args.tag])
     elif args.get_search_domains:
         print(client.get_search_domains(args.get_search_domains))
 
     elif args.add_hosts:
         if not args.tag:
             raise ValueError("--tag is required for this operation")
         data = {}
         for h in args.add_hosts:
             entry = {}
             params = h.split(",")
             if len(params) < 2:
                 raise ValueError("Malformed host entry")
-            entry['address'] = params[1]
+            # Address needs to be a list because of changes made in T2683
+            entry['address'] = [params[1]]
             entry['aliases'] = params[2:]
             data[params[0]] = entry
         client.add_hosts({args.tag: data})
     elif args.delete_hosts:
         if not args.tag:
             raise ValueError("--tag is required for this operation")
         client.delete_hosts([args.tag])
     elif args.get_hosts:
         print(client.get_hosts(args.get_hosts))
 
     elif args.set_host_name:
         if not args.domain_name:
             raise ValueError('--domain-name is required for this operation')
         client.set_host_name({'host_name': args.set_host_name, 'domain_name': args.domain_name})
 
     elif args.apply:
         pass
     else:
         ops = 0
 
     if args.apply:
         client.apply()
 
     if ops == 0:
         raise ValueError("Operation required")
 
 except ValueError as e:
     print("Incorrect options: {0}".format(e))
     sys.exit(1)
 except vyos.hostsd_client.VyOSHostsdError as e:
     print("Server returned an error: {0}".format(e))
     sys.exit(1)