Page MenuHomeVyOS Platform

No OneTemporary

Size
25 KB
Referenced Files
None
Subscribers
None
diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2
index 838813866..1fc488d24 100644
--- a/data/templates/chrony/chrony.conf.j2
+++ b/data/templates/chrony/chrony.conf.j2
@@ -1,79 +1,84 @@
### Autogenerated by service_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
{% if leap_second is vyos_defined('timezone') %}
leapsectz right/UTC
{% elif leap_second is vyos_defined('ignore') %}
leapsecmode ignore
{% elif leap_second is vyos_defined('smear') %}
leapsecmode slew
maxslewrate 1000
smoothtime 400 0.001024 leaponly
{% elif leap_second is vyos_defined('system') %}
leapsecmode system
{% endif %}
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 }} {{- ' xleave' if config.interleave is vyos_defined }}
+{{ 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 }} {{- ' xleave' if config.interleave is vyos_defined }} {{- ' port 319' if config.ptp_transport 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 %}
binddevice {{ interface }}
{% endif %}
{% endif %}
{% if offload.timestamp.interface is vyos_defined %}
# Enable hardware timestamping on the specified interfaces
{% for interface, config in offload.timestamp.interface.items() %}
hwtimestamp {{ interface }} {{- ' rxfilter ' ~ config.receive_filter if config.receive_filter is vyos_defined }}
{% endfor %}
{% endif %}
{% if offload.timestamp.default_enable is vyos_defined %}
# Enable hardware timestamping on all supported interfaces not otherwise configured
hwtimestamp *
{% endif %}
+
+{% if ptp_transport is vyos_defined %}
+# Enable sending and receiving NTP over PTP packets (PTP transport)
+ptpport 319
+{% endif %}
diff --git a/interface-definitions/service_ntp.xml.in b/interface-definitions/service_ntp.xml.in
index 005499abd..c4f3116ff 100644
--- a/interface-definitions/service_ntp.xml.in
+++ b/interface-definitions/service_ntp.xml.in
@@ -1,161 +1,177 @@
<?xml version="1.0"?>
<!-- NTP configuration -->
<interfaceDefinition>
<node name="service">
<children>
<node name="ntp" owner="${vyos_conf_scripts_dir}/service_ntp.py">
<properties>
<help>Network Time Protocol (NTP) configuration</help>
<priority>900</priority>
</properties>
<children>
#include <include/allow-client.xml.i>
#include <include/generic-interface.xml.i>
#include <include/listen-address.xml.i>
#include <include/interface/vrf.xml.i>
<node name="offload">
<properties>
<help>Configurable offload options</help>
</properties>
<children>
<node name="timestamp">
<properties>
<help>Enable timestamping of packets in the NIC hardware</help>
</properties>
<children>
<leafNode name="default-enable">
<properties>
<help>Enable timestamping on all supported interfaces</help>
<valueless/>
</properties>
</leafNode>
<tagNode name="interface">
<properties>
<help>Interface to enable timestamping on</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
#include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
<leafNode name="receive-filter">
<properties>
<help>Selects which inbound packets are timestamped by the NIC</help>
<completionHelp>
- <list>all ntp none</list>
+ <list>all ntp ptp none</list>
</completionHelp>
<valueHelp>
<format>all</format>
<description>All received packets are timestamped</description>
</valueHelp>
<valueHelp>
<format>ntp</format>
<description>Only NTP packets are timestamped</description>
</valueHelp>
+ <valueHelp>
+ <format>ptp</format>
+ <description>Only PTP packets, or NTP packets using the PTP transport, are timestamped</description>
+ </valueHelp>
<valueHelp>
<format>none</format>
<description>No received packets are timestamped</description>
</valueHelp>
<constraint>
- <regex>(all|ntp|none)</regex>
+ <regex>(all|ntp|ptp|none)</regex>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
+ <leafNode name="ptp-transport">
+ <properties>
+ <help>Enables the PTP transport for NTP packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="leap-second">
<properties>
<help>Leap second behavior</help>
<completionHelp>
<list>ignore smear system timezone</list>
</completionHelp>
<valueHelp>
<format>ignore</format>
<description>No correction is applied to the clock for the leap second</description>
</valueHelp>
<valueHelp>
<format>smear</format>
<description>Correct served time slowly be slewing instead of stepping</description>
</valueHelp>
<valueHelp>
<format>system</format>
<description>Kernel steps the system clock forward or backward</description>
</valueHelp>
<valueHelp>
<format>timezone</format>
<description>Use UTC timezone database to determine when will the next leap second occur</description>
</valueHelp>
<constraint>
<regex>(ignore|smear|system|timezone)</regex>
</constraint>
</properties>
<defaultValue>timezone</defaultValue>
</leafNode>
<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>
+ <leafNode name="ptp-transport">
+ <properties>
+ <help>Use the PTP transport for the server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="interleave">
<properties>
<help>Use the interleaved mode for the server</help>
<valueless/>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_service_ntp.py b/smoketest/scripts/cli/test_service_ntp.py
index 644894914..a39431c1b 100755
--- a/smoketest/scripts/cli/test_service_ntp.py
+++ b/smoketest/scripts/cli/test_service_ntp.py
@@ -1,228 +1,262 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019-2024 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_base_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_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_interface(self):
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_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])
def test_leap_seconds(self):
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}')
self.assertIn('leapsectz right/UTC', config) # CLI default
for mode in ['ignore', 'system', 'smear']:
self.cli_set(base_path + ['leap-second', mode])
self.cli_commit()
config = cmd(f'sudo cat {NTP_CONF}')
if mode != 'smear':
self.assertIn(f'leapsecmode {mode}', config)
else:
self.assertIn(f'leapsecmode slew', config)
self.assertIn(f'maxslewrate 1000', config)
self.assertIn(f'smoothtime 400 0.001024 leaponly', config)
def test_interleave_option(self):
# "interleave" option differs from some others in that the
# name is not a 1:1 mapping from VyOS config
servers = ['192.0.2.1', '192.0.2.2']
options = ['prefer']
offload_interface = 'eth0'
for server in servers:
for option in options:
self.cli_set(base_path + ['server', server, option])
self.cli_set(base_path + ['server', server, 'interleave'])
# 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) + ' xleave', config)
def test_offload_timestamp_default(self):
# Test offloading of NIC timestamp
servers = ['192.0.2.1', '192.0.2.2']
options = ['prefer']
for server in servers:
for option in options:
self.cli_set(base_path + ['server', server, option])
self.cli_set(base_path + ['offload', 'timestamp', 'default-enable'])
# 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)
self.assertIn('hwtimestamp *', config)
+ def test_ptp_transport(self):
+ # Test offloading of NIC timestamp
+ servers = ['192.0.2.1', '192.0.2.2']
+ options = ['prefer']
+
+ for server in servers:
+ for option in options:
+ self.cli_set(base_path + ['server', server, option])
+ self.cli_set(base_path + ['server', server, 'ptp-transport'])
+
+ # commit changes (expected to fail)
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # add the required top-level option and commit
+ self.cli_set(base_path + ['ptp-transport'])
+ 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) + ' port 319', config)
+
+ self.assertIn('ptpport 319', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/service_ntp.py b/src/conf_mode/service_ntp.py
index 83880fd72..f7dbc3776 100755
--- a/src/conf_mode/service_ntp.py
+++ b/src/conf_mode/service_ntp.py
@@ -1,136 +1,145 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018-2024 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, with_defaults=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
interface = ntp['interface']
verify_interface_exists(ntp, 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 ')
+ if 'server' in ntp:
+ for host, server in ntp['server'].items():
+ if 'ptp_transport' in server:
+ if 'ptp_transport' not in ntp:
+ raise ConfigError('ptp-transport must be enabled on the service '\
+ f'before it can be used with server {host}')
+ else:
+ break
+
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)

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 26, 9:36 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4284811
Default Alt Text
(25 KB)

Event Timeline