Page MenuHomeVyOS Platform

No OneTemporary

Size
18 KB
Referenced Files
None
Subscribers
None
diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2
index 7a36fe69d..0daec8fb8 100644
--- a/data/templates/chrony/chrony.conf.j2
+++ b/data/templates/chrony/chrony.conf.j2
@@ -1,60 +1,58 @@
### Autogenerated by ntp.py ###
# This would step the system clock if the adjustment is larger than 0.1 seconds,
# but only in the first three clock updates.
makestep 1.0 3
# The rtcsync directive enables a mode where the system time is periodically
# copied to the RTC and chronyd does not try to track its drift. This directive
# cannot be used with the rtcfile directive. On Linux, the RTC copy is performed
# by the kernel every 11 minutes.
rtcsync
# This directive specifies the maximum amount of memory that chronyd is allowed
# to allocate for logging of client accesses and the state that chronyd as an
# NTP server needs to support the interleaved mode for its clients.
clientloglimit 1048576
driftfile /run/chrony/drift
dumpdir /run/chrony
ntsdumpdir /run/chrony
pidfile {{ config_file | replace('.conf', '.pid') }}
# Determine when will the next leap second occur and what is the current offset
leapsectz right/UTC
user {{ user }}
# NTP servers to reach out to
{% if server is vyos_defined %}
{% for server, config in server.items() %}
{% set association = 'server' %}
{% if config.pool is vyos_defined %}
{% set association = 'pool' %}
{% endif %}
{{ association }} {{ server | replace('_', '-') }} iburst {{ 'nts' if config.nts is vyos_defined }} {{ 'noselect' if config.noselect is vyos_defined }} {{ 'prefer' if config.prefer is vyos_defined }}
{% endfor %}
{% endif %}
# Allowed clients configuration
{% if allow_client.address is vyos_defined %}
{% for address in allow_client.address %}
allow {{ address }}
{% endfor %}
{% else %}
deny all
{% endif %}
{% if listen_address is vyos_defined or interface is vyos_defined %}
# NTP should listen on configured addresses only
{% if listen_address is vyos_defined %}
{% for address in listen_address %}
bindaddress {{ address }}
{% endfor %}
{% endif %}
{% if interface is vyos_defined %}
-{% for ifname in interface %}
-binddevice {{ ifname }}
-{% endfor %}
+binddevice {{ interface }}
{% endif %}
{% endif %}
diff --git a/interface-definitions/include/version/ntp-version.xml.i b/interface-definitions/include/version/ntp-version.xml.i
index 9eafbf7f0..155c824dc 100644
--- a/interface-definitions/include/version/ntp-version.xml.i
+++ b/interface-definitions/include/version/ntp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/ntp-version.xml.i -->
-<syntaxVersion component='ntp' version='2'></syntaxVersion>
+<syntaxVersion component='ntp' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in
index 2275dd61c..4e874434b 100644
--- a/interface-definitions/ntp.xml.in
+++ b/interface-definitions/ntp.xml.in
@@ -1,67 +1,67 @@
<?xml version="1.0"?>
<!-- NTP configuration -->
<interfaceDefinition>
<node name="service">
<children>
<node name="ntp" owner="${vyos_conf_scripts_dir}/ntp.py">
<properties>
<help>Network Time Protocol (NTP) configuration</help>
<priority>900</priority>
</properties>
<children>
<tagNode name="server">
<properties>
<help>Network Time Protocol (NTP) server</help>
<valueHelp>
<format>ipv4</format>
<description>IP address of NTP server</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of NTP server</description>
</valueHelp>
<valueHelp>
<format>hostname</format>
<description>Fully qualified domain name of NTP server</description>
</valueHelp>
<constraint>
<validator name="ip-address"/>
<validator name="fqdn"/>
</constraint>
</properties>
<children>
<leafNode name="noselect">
<properties>
<help>Marks the server as unused</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="nts">
<properties>
<help>Enable Network Time Security (NTS) for the server</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="pool">
<properties>
<help>Associate with a number of remote servers</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="prefer">
<properties>
<help>Marks the server as preferred</help>
<valueless/>
</properties>
</leafNode>
</children>
</tagNode>
#include <include/allow-client.xml.i>
- #include <include/generic-interface-multi.xml.i>
+ #include <include/generic-interface.xml.i>
#include <include/listen-address.xml.i>
#include <include/interface/vrf.xml.i>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_service_ntp.py b/smoketest/scripts/cli/test_service_ntp.py
index 9a63fbbd1..5e385d5ad 100755
--- a/smoketest/scripts/cli/test_service_ntp.py
+++ b/smoketest/scripts/cli/test_service_ntp.py
@@ -1,146 +1,146 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019-2023 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 unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
PROCESS_NAME = 'chronyd'
NTP_CONF = '/run/chrony/chrony.conf'
base_path = ['service', 'ntp']
class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
super(TestSystemNTP, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
def tearDown(self):
self.assertTrue(process_named_running(PROCESS_NAME))
self.cli_delete(base_path)
self.cli_commit()
self.assertFalse(process_named_running(PROCESS_NAME))
def test_01_ntp_options(self):
# Test basic NTP support with multiple servers and their options
servers = ['192.0.2.1', '192.0.2.2']
options = ['nts', 'noselect', 'prefer']
pools = ['pool.vyos.io']
for server in servers:
for option in options:
self.cli_set(base_path + ['server', server, option])
# Test NTP pool
for pool in pools:
self.cli_set(base_path + ['server', pool, 'pool'])
# commit changes
self.cli_commit()
# Check generated configuration
# this file must be read with higher permissions
config = cmd(f'sudo cat {NTP_CONF}')
self.assertIn('driftfile /run/chrony/drift', config)
self.assertIn('dumpdir /run/chrony', config)
self.assertIn('ntsdumpdir /run/chrony', config)
self.assertIn('clientloglimit 1048576', config)
self.assertIn('rtcsync', config)
self.assertIn('makestep 1.0 3', config)
self.assertIn('leapsectz right/UTC', config)
for server in servers:
self.assertIn(f'server {server} iburst ' + ' '.join(options), config)
for pool in pools:
self.assertIn(f'pool {pool} iburst', config)
def test_02_ntp_clients(self):
# Test the allowed-networks statement
listen_address = ['127.0.0.1', '::1']
for listen in listen_address:
self.cli_set(base_path + ['listen-address', listen])
networks = ['192.0.2.0/24', '2001:db8:1000::/64', '100.64.0.0', '2001:db8::ffff']
for network in networks:
self.cli_set(base_path + ['allow-client', 'address', network])
# Verify "NTP server not configured" verify() statement
with self.assertRaises(ConfigSessionError):
self.cli_commit()
servers = ['192.0.2.1', '192.0.2.2']
for server in servers:
self.cli_set(base_path + ['server', server])
self.cli_commit()
# Check generated client address configuration
# this file must be read with higher permissions
config = cmd(f'sudo cat {NTP_CONF}')
for network in networks:
self.assertIn(f'allow {network}', config)
# Check listen address
for listen in listen_address:
self.assertIn(f'bindaddress {listen}', config)
def test_03_ntp_interface(self):
- interfaces = ['eth0', 'eth1']
+ interfaces = ['eth0']
for interface in interfaces:
self.cli_set(base_path + ['interface', interface])
servers = ['time1.vyos.net', 'time2.vyos.net']
for server in servers:
self.cli_set(base_path + ['server', server])
self.cli_commit()
# Check generated client address configuration
# this file must be read with higher permissions
config = cmd(f'sudo cat {NTP_CONF}')
for interface in interfaces:
self.assertIn(f'binddevice {interface}', config)
def test_04_ntp_vrf(self):
vrf_name = 'vyos-mgmt'
self.cli_set(['vrf', 'name', vrf_name, 'table', '12345'])
self.cli_set(base_path + ['vrf', vrf_name])
servers = ['time1.vyos.net', 'time2.vyos.net']
for server in servers:
self.cli_set(base_path + ['server', server])
self.cli_commit()
# Check for process in VRF
tmp = cmd(f'ip vrf pids {vrf_name}')
self.assertIn(PROCESS_NAME, tmp)
self.cli_delete(['vrf', 'name', vrf_name])
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 917f6e058..1cc23a7df 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -1,122 +1,136 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018-2023 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 os
from vyos.config import Config
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
from vyos.utils.process import call
from vyos.utils.permission import chmod_750
from vyos.utils.network import get_interface_config
from vyos.template import render
+from vyos.template import is_ipv4
from vyos import ConfigError
from vyos import airbag
airbag.enable()
config_file = r'/run/chrony/chrony.conf'
systemd_override = r'/run/systemd/system/chrony.service.d/override.conf'
user_group = '_chrony'
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
base = ['service', 'ntp']
if not conf.exists(base):
return None
ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
ntp['config_file'] = config_file
ntp['user'] = user_group
tmp = is_node_changed(conf, base + ['vrf'])
if tmp: ntp.update({'restart_required': {}})
return ntp
def verify(ntp):
# bail out early - looks like removal from running config
if not ntp:
return None
if 'server' not in ntp:
raise ConfigError('NTP server not configured')
verify_vrf(ntp)
if 'interface' in ntp:
# If ntpd should listen on a given interface, ensure it exists
- for interface in ntp['interface']:
- verify_interface_exists(interface)
-
- # If we run in a VRF, our interface must belong to this VRF, too
- if 'vrf' in ntp:
- tmp = get_interface_config(interface)
- vrf_name = ntp['vrf']
- if 'master' not in tmp or tmp['master'] != vrf_name:
- raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\
- f'does not belong to this VRF!')
+ interface = ntp['interface']
+ verify_interface_exists(interface)
+
+ # If we run in a VRF, our interface must belong to this VRF, too
+ if 'vrf' in ntp:
+ tmp = get_interface_config(interface)
+ vrf_name = ntp['vrf']
+ if 'master' not in tmp or tmp['master'] != vrf_name:
+ raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\
+ f'does not belong to this VRF!')
+
+ if 'listen_address' in ntp:
+ ipv4_addresses = 0
+ ipv6_addresses = 0
+ for address in ntp['listen_address']:
+ if is_ipv4(address):
+ ipv4_addresses += 1
+ else:
+ ipv6_addresses += 1
+ if ipv4_addresses > 1:
+ raise ConfigError(f'NTP Only admits one ipv4 value for listen-address parameter ')
+ if ipv6_addresses > 1:
+ raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ')
return None
def generate(ntp):
# bail out early - looks like removal from running config
if not ntp:
return None
render(config_file, 'chrony/chrony.conf.j2', ntp, user=user_group, group=user_group)
render(systemd_override, 'chrony/override.conf.j2', ntp, user=user_group, group=user_group)
# Ensure proper permission for chrony command socket
config_dir = os.path.dirname(config_file)
chmod_750(config_dir)
return None
def apply(ntp):
systemd_service = 'chrony.service'
# Reload systemd manager configuration
call('systemctl daemon-reload')
if not ntp:
# NTP support is removed in the commit
call(f'systemctl stop {systemd_service}')
if os.path.exists(config_file):
os.unlink(config_file)
if os.path.isfile(systemd_override):
os.unlink(systemd_override)
return
# we need to restart the service if e.g. the VRF name changed
systemd_action = 'reload-or-restart'
if 'restart_required' in ntp:
systemd_action = 'restart'
call(f'systemctl {systemd_action} {systemd_service}')
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)
diff --git a/src/migration-scripts/ntp/2-to-3 b/src/migration-scripts/ntp/2-to-3
new file mode 100755
index 000000000..7d4e0bd83
--- /dev/null
+++ b/src/migration-scripts/ntp/2-to-3
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2023 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/>.
+
+# T5154: allow only one ip address per family for parameter 'listen-address'
+# Allow only one interface for parameter 'interface'
+# If more than one are specified, remove such entries
+
+import sys
+
+from vyos.configtree import ConfigTree
+from vyos.template import is_ipv4
+from vyos.template import is_ipv6
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base_path = ['service', 'ntp']
+if not config.exists(base_path):
+ # Nothing to do
+ sys.exit(0)
+
+if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv4(addr)]) > 1):
+ for addr in config.return_values(base_path + ['listen-address']):
+ if is_ipv4(addr):
+ config.delete_value(base_path + ['listen-address'], addr)
+
+if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv6(addr)]) > 1):
+ for addr in config.return_values(base_path + ['listen-address']):
+ if is_ipv6(addr):
+ config.delete_value(base_path + ['listen-address'], addr)
+
+if config.exists(base_path + ['interface']):
+ if len(config.return_values(base_path + ['interface'])) > 1:
+ config.delete(base_path + ['interface'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 6, 9:05 AM (22 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4653701
Default Alt Text
(18 KB)

Event Timeline