summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/chrony/chrony.conf.j217
-rw-r--r--data/templates/rsyslog/rsyslog.conf.j24
-rw-r--r--interface-definitions/service_ntp.xml.in80
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py34
-rwxr-xr-xsmoketest/scripts/cli/test_service_ntp.py95
-rwxr-xr-xsmoketest/scripts/cli/test_system_syslog.py33
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py40
-rwxr-xr-xsrc/conf_mode/service_ntp.py20
-rwxr-xr-xsrc/conf_mode/system_syslog.py11
9 files changed, 315 insertions, 19 deletions
diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2
index e3f078fdc..2838f5524 100644
--- a/data/templates/chrony/chrony.conf.j2
+++ b/data/templates/chrony/chrony.conf.j2
@@ -42,7 +42,7 @@ user {{ user }}
{% 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 }}
+{{ 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 ' ~ ptp.port if ptp.port is vyos_defined and config.ptp is vyos_defined }}
{% endfor %}
{% endif %}
@@ -66,3 +66,18 @@ bindaddress {{ address }}
binddevice {{ interface }}
{% endif %}
{% endif %}
+
+{% if ptp.timestamp.interface is vyos_defined %}
+# Enable hardware timestamping on the specified interfaces
+{% for iface, iface_config in ptp.timestamp.interface.items() %}
+{% if iface == "all" %}
+{% set iface = "*" %}
+{% endif %}
+hwtimestamp {{ iface }} {{- ' rxfilter ' ~ iface_config.receive_filter if iface_config.receive_filter is vyos_defined }}
+{% endfor %}
+{% endif %}
+
+{% if ptp.port is vyos_defined %}
+# Enable sending and receiving NTP over PTP packets (PTP transport)
+ptpport {{ ptp.port }}
+{% endif %}
diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
index effc2ea14..7fd592d1f 100644
--- a/data/templates/rsyslog/rsyslog.conf.j2
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -10,6 +10,10 @@ $MarkMessagePeriod {{ global.marker.interval }}
$PreserveFQDN on
{% endif %}
+{% if global.local_host_name is vyos_defined %}
+$LocalHostName {{ global.local_host_name }}
+{% endif %}
+
# We always log to /var/log/messages
$outchannel global,/var/log/messages,262144,/usr/sbin/logrotate {{ logrotate }}
{% if global.facility is vyos_defined %}
diff --git a/interface-definitions/service_ntp.xml.in b/interface-definitions/service_ntp.xml.in
index c057b62b5..5dc0cd295 100644
--- a/interface-definitions/service_ntp.xml.in
+++ b/interface-definitions/service_ntp.xml.in
@@ -13,6 +13,74 @@
#include <include/generic-interface.xml.i>
#include <include/listen-address.xml.i>
#include <include/interface/vrf.xml.i>
+ <node name="ptp">
+ <properties>
+ <help>Enable Precision Time Protocol (PTP) transport</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>319</defaultValue>
+ </leafNode>
+ <node name="timestamp">
+ <properties>
+ <help>Enable timestamping of packets in the NIC hardware</help>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Interface to enable timestamping on</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ <list>all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Select all interfaces</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ <regex>all</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="receive-filter">
+ <properties>
+ <help>Selects which inbound packets are timestamped by the NIC</help>
+ <completionHelp>
+ <list>all ntp ptp none</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All packets are timestamped</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ntp</format>
+ <description>Only NTP packets are timestamped</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ptp</format>
+ <description>Only PTP or NTP packets using the PTP transport are timestamped</description>
+ </valueHelp>
+ <valueHelp>
+ <format>none</format>
+ <description>No packet is timestamped</description>
+ </valueHelp>
+ <constraint>
+ <regex>(all|ntp|ptp|none)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="leap-second">
<properties>
<help>Leap second behavior</help>
@@ -86,6 +154,18 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="ptp">
+ <properties>
+ <help>Use Precision Time Protocol (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>
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index 124c1fbcb..54c981adc 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -22,6 +22,7 @@ from base_interfaces_test import BasicInterfaceTest
from copy import deepcopy
from glob import glob
+from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.template import ip_from_cidr
from vyos.utils.process import cmd
@@ -460,5 +461,38 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_interface_config(interface)
self.assertEqual(protocol, tmp['linkinfo']['info_data']['vlan_protocol'])
+ def test_bridge_delete_with_vxlan_heighbor_suppress(self):
+ vxlan_if = 'vxlan0'
+ vni = '123'
+ br_if = 'br0'
+ eth0_addr = '192.0.2.2/30'
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'parameters', 'neighbor-suppress'])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'mtu', '1426'])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'source-address', ip_from_cidr(eth0_addr)])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'vni', vni])
+
+ self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vxlan_if])
+
+ self.cli_commit()
+
+ self.assertTrue(interface_exists(vxlan_if))
+ self.assertTrue(interface_exists(br_if))
+
+ # cannot delete bridge interface if "neighbor-suppress" parameter is configured for VXLAN interface
+ self.cli_delete(['interfaces', 'bridge', br_if])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(['interfaces', 'vxlan', vxlan_if, 'parameters', 'neighbor-suppress'])
+
+ self.cli_commit()
+
+ self.assertFalse(interface_exists(br_if))
+
+ self.cli_delete(['interfaces', 'vxlan', vxlan_if])
+ self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr])
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_ntp.py b/smoketest/scripts/cli/test_service_ntp.py
index ae45fe2f4..07af4f5eb 100755
--- a/smoketest/scripts/cli/test_service_ntp.py
+++ b/smoketest/scripts/cli/test_service_ntp.py
@@ -21,6 +21,7 @@ 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
+from vyos.xml_ref import default_value
PROCESS_NAME = 'chronyd'
NTP_CONF = '/run/chrony/chrony.conf'
@@ -165,5 +166,99 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
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']
+
+ 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']
+ ptp_port = '8319'
+
+ for server in servers:
+ self.cli_set(base_path + ['server', server, 'ptp'])
+
+ self.cli_set(base_path + ['ptp', 'port', ptp_port])
+ self.cli_set(base_path + ['ptp', 'timestamp', 'interface', 'all'])
+
+ # 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 port {ptp_port}', 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']
+
+ default_ptp_port = default_value(base_path + ['ptp', 'port'])
+
+ for server in servers:
+ for option in options:
+ self.cli_set(base_path + ['server', server, option])
+ self.cli_set(base_path + ['server', server, 'ptp'])
+
+ # 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'])
+ 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) + f' port {default_ptp_port}', config)
+
+ self.assertIn(f'ptpport {default_ptp_port}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py
index 45a5b4087..c802ceeeb 100755
--- a/smoketest/scripts/cli/test_system_syslog.py
+++ b/smoketest/scripts/cli/test_system_syslog.py
@@ -20,6 +20,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.file import read_file
+from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
PROCESS_NAME = 'rsyslogd'
@@ -61,19 +62,45 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['host', host2, 'facility', 'kern', 'level', 'err'])
self.cli_set(base_path + ['console', 'facility', 'all', 'level', 'warning'])
-
self.cli_commit()
# verify log level and facilities in config file
# *.warning /dev/console
# *.* @198.51.100.1:999
# kern.err @192.0.2.1:514
- config = [get_config_value('\*.\*'), get_config_value('kern.err'), get_config_value('\*.warning')]
+ config = [
+ get_config_value('\*.\*'),
+ get_config_value('kern.err'),
+ get_config_value('\*.warning'),
+ ]
expected = [f'@{host1}:999', f'@{host2}:514', '/dev/console']
- for i in range(0,3):
+ for i in range(0, 3):
self.assertIn(expected[i], config[i])
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_syslog_global(self):
+ self.cli_set(['system', 'host-name', 'vyos'])
+ self.cli_set(['system', 'domain-name', 'example.local'])
+ self.cli_set(base_path + ['global', 'marker', 'interval', '600'])
+ self.cli_set(base_path + ['global', 'preserve-fqdn'])
+ self.cli_set(base_path + ['global', 'facility', 'kern', 'level', 'err'])
+
+ self.cli_commit()
+
+ config = cmd(f'sudo cat {RSYSLOG_CONF}')
+ expected = [
+ '$MarkMessagePeriod 600',
+ '$PreserveFQDN on',
+ 'kern.err',
+ '$LocalHostName vyos.example.local',
+ ]
+
+ for e in expected:
+ self.assertIn(e, config)
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index 7b2c1ee0b..637db442a 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -53,20 +53,22 @@ def get_config(config=None):
tmp = node_changed(conf, base + [ifname, 'member', 'interface'])
if tmp:
if 'member' in bridge:
- bridge['member'].update({'interface_remove' : tmp })
+ bridge['member'].update({'interface_remove': {t: {} for t in tmp}})
else:
- bridge.update({'member' : {'interface_remove' : tmp }})
- for interface in tmp:
- # When using VXLAN member interfaces that are configured for Single
- # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
- # re-create VLAN to VNI mappings if required, but only if the interface
- # is already live on the system - this must not be done on first commit
- if interface.startswith('vxlan') and interface_exists(interface):
- set_dependents('vxlan', conf, interface)
- # When using Wireless member interfaces we need to inform hostapd
- # to properly set-up the bridge
- elif interface.startswith('wlan') and interface_exists(interface):
- set_dependents('wlan', conf, interface)
+ bridge.update({'member': {'interface_remove': {t: {} for t in tmp}}})
+ for interface in tmp:
+ # When using VXLAN member interfaces that are configured for Single
+ # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
+ # re-create VLAN to VNI mappings if required, but only if the interface
+ # is already live on the system - this must not be done on first commit
+ if interface.startswith('vxlan') and interface_exists(interface):
+ set_dependents('vxlan', conf, interface)
+ _, vxlan = get_interface_dict(conf, ['interfaces', 'vxlan'], ifname=interface)
+ bridge['member']['interface_remove'].update({interface: vxlan})
+ # When using Wireless member interfaces we need to inform hostapd
+ # to properly set-up the bridge
+ elif interface.startswith('wlan') and interface_exists(interface):
+ set_dependents('wlan', conf, interface)
if dict_search('member.interface', bridge) is not None:
for interface in list(bridge['member']['interface']):
@@ -118,6 +120,16 @@ def get_config(config=None):
return bridge
def verify(bridge):
+ # to delete interface or remove a member interface VXLAN first need to check if
+ # VXLAN does not require to be a member of a bridge interface
+ if dict_search('member.interface_remove', bridge):
+ for iface, iface_config in bridge['member']['interface_remove'].items():
+ if iface.startswith('vxlan') and dict_search('parameters.neighbor_suppress', iface_config) != None:
+ raise ConfigError(
+ f'To detach interface {iface} from bridge you must first '
+ f'disable "neighbor-suppress" parameter in the VXLAN interface {iface}'
+ )
+
if 'deleted' in bridge:
return None
@@ -192,7 +204,7 @@ def apply(bridge):
try:
call_dependents()
except ConfigError:
- raise ConfigError('Error updating member interface configuration after changing bridge!')
+ raise ConfigError(f'Error updating member interface {interface} configuration after changing bridge!')
return None
diff --git a/src/conf_mode/service_ntp.py b/src/conf_mode/service_ntp.py
index 83880fd72..32563aa0e 100755
--- a/src/conf_mode/service_ntp.py
+++ b/src/conf_mode/service_ntp.py
@@ -17,6 +17,7 @@
import os
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
@@ -42,13 +43,21 @@ def get_config(config=None):
if not conf.exists(base):
return None
- ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, with_defaults=True)
+ 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': {}})
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(**ntp.kwargs, recursive=True)
+ # Only defined PTP default port, if PTP feature is in use
+ if 'ptp' not in ntp:
+ del default_values['ptp']
+
+ ntp = config_dict_merge(default_values, ntp)
return ntp
def verify(ntp):
@@ -87,6 +96,15 @@ def verify(ntp):
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' in server:
+ if 'ptp' not in ntp:
+ raise ConfigError('PTP must be enabled for the NTP service '\
+ f'before it can be used for server "{host}"')
+ else:
+ break
+
return None
def generate(ntp):
diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py
index 2497c5bb6..eb2f02eb3 100755
--- a/src/conf_mode/system_syslog.py
+++ b/src/conf_mode/system_syslog.py
@@ -53,6 +53,17 @@ def get_config(config=None):
if syslog.from_defaults(['global']):
del syslog['global']
+ if (
+ 'global' in syslog
+ and 'preserve_fqdn' in syslog['global']
+ and conf.exists(['system', 'host-name'])
+ and conf.exists(['system', 'domain-name'])
+ ):
+ hostname = conf.return_value(['system', 'host-name'])
+ domain = conf.return_value(['system', 'domain-name'])
+ fqdn = f'{hostname}.{domain}'
+ syslog['global']['local_host_name'] = fqdn
+
return syslog
def verify(syslog):