Skip to content

Commit

Permalink
T6294: Service dns forwarding add the ability to configure ZonetoCache
Browse files Browse the repository at this point in the history
  • Loading branch information
HollyGurza authored and c-po committed Aug 5, 2024
1 parent 3ce5485 commit 75f224b
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 0 deletions.
25 changes: 25 additions & 0 deletions data/templates/dns-forwarding/recursor.conf.lua.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,28 @@ dofile("/usr/share/pdns-recursor/lua-config/rootkeys.lua")

-- Load lua from vyos-hostsd --
dofile("{{ config_dir }}/recursor.vyos-hostsd.conf.lua")

-- ZoneToCache --
{% if zone_to_cache is vyos_defined %}
{% set option_mapping = {
'refresh': 'refreshPeriod',
'retry_on_error': 'retryOnErrorPeriod',
'max_received': 'maxReceivedMBytes'
} %}
{% for name, conf in zone_to_cache.items() %}
{% set source = conf.source.items() | first %}
{% set settings = [] %}
{% for key, val in conf.options.items() %}
{% set mapped_key = option_mapping.get(key, key) %}
{% if key in ['dnssec', 'zonemd'] %}
{% set _ = settings.append(mapped_key ~ ' = "' ~ val ~ '"') %}
{% else %}
{% set _ = settings.append(mapped_key ~ ' = ' ~ val) %}
{% endif %}
{% endfor %}

zoneToCache("{{ name }}", "{{ source[0] }}", "{{ source[1] }}", { {{ settings | join(', ') }} })

{% endfor %}

{% endif %}
149 changes: 149 additions & 0 deletions interface-definitions/service_dns_forwarding.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,155 @@
</leafNode>
</children>
</node>
<tagNode name="zone-to-cache">
<properties>
<help>Load a zone into the recursor cache</help>
<valueHelp>
<format>txt</format>
<description>The name of the zone to load</description>
</valueHelp>
</properties>
<children>
<node name="source">
<properties>
<help>Source to load zone</help>
</properties>
<children>
<leafNode name="axfr">
<properties>
<help>Source IP address</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
</constraint>
</properties>
</leafNode>
<leafNode name="url">
<properties>
<help>Source URL</help>
<valueHelp>
<format>url</format>
<description>Set URL location</description>
</valueHelp>
<constraint>
<validator name="url" argument="--scheme http --scheme https"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
<node name="options">
<properties>
<help>Zone to cache options</help>
</properties>
<children>
<leafNode name="timeout">
<properties>
<help>The maximum time a retrieval using the AXFR or URL source may take</help>
<valueHelp>
<format>u32:1-3600</format>
<description>Request timeout in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-3600"/>
</constraint>
</properties>
<defaultValue>20</defaultValue>
</leafNode>
<leafNode name="refresh">
<properties>
<help>The interval to wait between retrievals. A value of zero means the retrieval is done once at startup and on Lua configuration reload</help>
<valueHelp>
<format>u32:0-2147483647</format>
<description>Refresh period in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-2147483647"/>
</constraint>
</properties>
<defaultValue>86400</defaultValue>
</leafNode>
<leafNode name="retry-on-error">
<properties>
<help>The interval to wait before retrying a failed transfer</help>
<valueHelp>
<format>u32:1-2147483647</format>
<description>Retry period in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-2147483647"/>
</constraint>
</properties>
<defaultValue>60</defaultValue>
</leafNode>
<leafNode name="max-received">
<properties>
<help>The maximum size of an update via the AXFR or URL source. The default value of 0 means no restriction</help>
<valueHelp>
<format>u32:0-1024</format>
<description>Size in megabytes</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-1024"/>
</constraint>
</properties>
<defaultValue>0</defaultValue>
</leafNode>
<leafNode name="zonemd">
<properties>
<help>ZONEMD mode</help>
<completionHelp>
<list>ignore validate require</list>
</completionHelp>
<valueHelp>
<format>ignore</format>
<description>Ignore ZONEMD records</description>
</valueHelp>
<valueHelp>
<format>validate</format>
<description>Validate ZONEMD if present</description>
</valueHelp>
<valueHelp>
<format>require</format>
<description>Require valid ZONEMD record to be present</description>
</valueHelp>
<constraint>
<regex>(ignore|validate|require)</regex>
</constraint>
</properties>
<defaultValue>validate</defaultValue>
</leafNode>
<leafNode name="dnssec">
<properties>
<help>DNSSEC mode</help>
<completionHelp>
<list>ignore validate require</list>
</completionHelp>
<valueHelp>
<format>ignore</format>
<description>Do not do DNSSEC validation</description>
</valueHelp>
<valueHelp>
<format>validate</format>
<description>Validate DNSSEC records but accept an insecure (unsigned) zone</description>
</valueHelp>
<valueHelp>
<format>require</format>
<description>Require DNSSEC validation</description>
</valueHelp>
<constraint>
<regex>(ignore|validate|require)</regex>
</constraint>
</properties>
<defaultValue>validate</defaultValue>
</leafNode>
</children>
</node>
</children>
</tagNode>
</children>
</node>
</children>
Expand Down
39 changes: 39 additions & 0 deletions smoketest/scripts/cli/test_service_dns_forwarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

PDNS_REC_RUN_DIR = '/run/pdns-recursor'
CONFIG_FILE = f'{PDNS_REC_RUN_DIR}/recursor.conf'
PDNS_REC_LUA_CONF_FILE = f'{PDNS_REC_RUN_DIR}/recursor.conf.lua'
FORWARD_FILE = f'{PDNS_REC_RUN_DIR}/recursor.forward-zones.conf'
HOSTSD_FILE = f'{PDNS_REC_RUN_DIR}/recursor.vyos-hostsd.conf.lua'
PROCESS_NAME= 'pdns_recursor'
Expand Down Expand Up @@ -300,6 +301,44 @@ def test_multiple_ns_records(self):
self.assertRegex(zone_config, fr'test\s+\d+\s+NS\s+ns1\.{test_zone}\.')
self.assertRegex(zone_config, fr'test\s+\d+\s+NS\s+ns2\.{test_zone}\.')

def test_zone_to_cache_url(self):
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone'])
self.cli_commit()

lua_config = read_file(PDNS_REC_LUA_CONF_FILE)
self.assertIn('zoneToCache("smoketest", "url", "https://www.internic.net/domain/root.zone", { dnssec = "validate", zonemd = "validate", maxReceivedMBytes = 0, retryOnErrorPeriod = 60, refreshPeriod = 86400, timeout = 20 })', lua_config)

def test_zone_to_cache_axfr(self):

self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'source', 'axfr', '127.0.0.1'])
self.cli_commit()

lua_config = read_file(PDNS_REC_LUA_CONF_FILE)
self.assertIn('zoneToCache("smoketest", "axfr", "127.0.0.1", { dnssec = "validate", zonemd = "validate", maxReceivedMBytes = 0, retryOnErrorPeriod = 60, refreshPeriod = 86400, timeout = 20 })', lua_config)

def test_zone_to_cache_options(self):
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'options', 'dnssec', 'ignore'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'options', 'max-received', '100'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'options', 'refresh', '0'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'options', 'retry-on-error', '90'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'options', 'timeout', '50'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'options', 'zonemd', 'require'])
self.cli_commit()

lua_config = read_file(PDNS_REC_LUA_CONF_FILE)
self.assertIn('zoneToCache("smoketest", "url", "https://www.internic.net/domain/root.zone", { dnssec = "ignore", maxReceivedMBytes = 100, refreshPeriod = 0, retryOnErrorPeriod = 90, timeout = 50, zonemd = "require" })', lua_config)

def test_zone_to_cache_wrong_source(self):
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone'])
self.cli_set(base_path + ['zone-to-cache', 'smoketest', 'source', 'axfr', '127.0.0.1'])

with self.assertRaises(ConfigSessionError):
self.cli_commit()
# correct config to correct finish the test
self.cli_delete(base_path + ['zone-to-cache', 'smoketest', 'source', 'axfr'])
self.cli_commit()


if __name__ == '__main__':
unittest.main(verbosity=2)
8 changes: 8 additions & 0 deletions src/conf_mode/service_dns_forwarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,16 @@ def verify(dns):
if not 'system_name_server' in dns:
print('Warning: No "system name-server" configured')

if 'zone_to_cache' in dns:
for name, conf in dns['zone_to_cache'].items():
if ('source' not in conf) \
or ('url' in conf['source'] and 'axfr' in conf['source']):
raise ConfigError(f'Invalid configuration for zone "{name}": '
f'Please select one source type "url" or "axfr".')

return None


def generate(dns):
# bail out early - looks like removal from running config
if not dns:
Expand Down

0 comments on commit 75f224b

Please sign in to comment.