[ntp]: Source interface support for NTP (#6033)

Added source interface support for NTP.
Also made NTP start on Mgmt-VRF by default when configured.

**- How I did it**
1) Updated hostcfg to listen to global config NTP and NTP_SERVER tables and restart ntp when ever the configuration changes. NTP table includes source interface configuration.
2) The ntp script updated to by default start on Mgmt-VFT when configured.

Signed-off-by: Prabhu Sreenivasan <prabhu.sreenivasan@broadcom>
This commit is contained in:
Prabhu Sreenivasan 2020-12-21 19:04:13 +05:30 committed by GitHub
parent 0a36de3a89
commit df2a4ded98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 204 additions and 4 deletions

View File

@ -322,6 +322,8 @@ echo "ntp-config.service" | sudo tee -a $GENERATED_SERVICE_FILE
sudo cp $IMAGE_CONFIGS/ntp/ntp-config.sh $FILESYSTEM_ROOT/usr/bin/
sudo cp $IMAGE_CONFIGS/ntp/ntp.conf.j2 $FILESYSTEM_ROOT_USR_SHARE_SONIC_TEMPLATES/
sudo cp $IMAGE_CONFIGS/ntp/ntp-systemd-wrapper $FILESYSTEM_ROOT/usr/lib/ntp/
sudo cp $IMAGE_CONFIGS/ntp/ntp.service $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM
echo "ntp.service" | sudo tee -a $GENERATED_SERVICE_FILE
# Copy warmboot-finalizer files
sudo LANG=C cp $IMAGE_CONFIGS/warmboot-finalizer/finalize-warmboot.sh $FILESYSTEM_ROOT/usr/local/bin/finalize-warmboot.sh

View File

@ -50,11 +50,20 @@ case $1 in
fi
(
flock -w 180 9
# when mgmt vrf is configured, ntp starts in mgmt vrf by default unless user configures otherwise
vrfEnabled=$(/usr/local/bin/sonic-cfggen -d -v 'MGMT_VRF_CONFIG["vrf_global"]["mgmtVrfEnabled"]' 2> /dev/null)
vrfConfigured=$(/usr/local/bin/sonic-cfggen -d -v 'NTP["global"]["vrf"]' 2> /dev/null)
if [ "$vrfEnabled" = "true" ]
then
log_daemon_msg "Starting NTP server in mgmt-vrf" "ntpd"
cgexec -g l3mdev:mgmt start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
if [ "$vrfConfigured" = "default" ]
then
log_daemon_msg "Starting NTP server in default-vrf for default set as NTP vrf" "ntpd"
start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
else
log_daemon_msg "Starting NTP server in mgmt-vrf" "ntpd"
cgexec -g l3mdev:mgmt start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
fi
else
log_daemon_msg "Starting NTP server in default-vrf" "ntpd"
start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS

View File

@ -3,6 +3,7 @@ Description=Update NTP configuration
Requires=updategraph.service
After=updategraph.service
Before=ntp.service
StartLimitIntervalSec=0
[Service]
Type=oneshot

View File

@ -27,11 +27,21 @@ fi
(
flock -w 180 9
# when mgmt vrf is configured, ntp starts in mgmt vrf by default unless user configures otherwise
vrfEnabled=$(/usr/local/bin/sonic-cfggen -d -v 'MGMT_VRF_CONFIG["vrf_global"]["mgmtVrfEnabled"]' 2> /dev/null)
vrfConfigured=$(/usr/local/bin/sonic-cfggen -d -v 'NTP["global"]["vrf"]' 2> /dev/null)
if [ "$vrfEnabled" = "true" ]
then
ip vrf exec mgmt start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
if [ "$vrfConfigured" = "default" ]
then
log_daemon_msg "Starting NTP server in default-vrf for default set as NTP vrf" "ntpd"
start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
else
log_daemon_msg "Starting NTP server in mgmt-vrf" "ntpd"
ip vrf exec mgmt start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
fi
else
log_daemon_msg "Starting NTP server in default-vrf" "ntpd"
start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --startas $DAEMON -- -p $PIDFILE $NTPD_OPTS
fi
) 9>$LOCKFILE

View File

@ -31,10 +31,49 @@ filegen clockstats file clockstats type day enable
server {{ ntp_server }} iburst
{% endfor %}
#listen on source interface if configured, else
#only listen on MGMT_INTERFACE, LOOPBACK_INTERFACE ip when MGMT_INTERFACE is not defined, or eth0
# if we don't have both of them (default is to listen on all ip addresses)
interface ignore wildcard
{% if MGMT_INTERFACE %}
# set global variable for configured source interface name
# set global boolean to indicate if the ip of the configured source interface is configured
# if the source interface is configured but no ip on that interface, then listen on another
# interface based on existing logic
{%- macro check_ip_on_interface(interface_name, table_name) %}
{%- if table_name %}
{%- for (name, source_prefix) in table_name|pfx_filter %}
{%- if source_prefix and name == interface_name %}
true
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endmacro %}
{% set ns = namespace(source_intf = "") %}
{% set ns = namespace(source_intf_ip = 'false') %}
{% if (NTP) and (NTP['global']['src_intf']) %}
{% set ns.source_intf = (NTP['global']['src_intf']) %}
{% if ns.source_intf != "" %}
{% if ns.source_intf == "eth0" %}
{% set ns.source_intf_ip = 'true' %}
{% elif ns.source_intf.startswith('Vlan') %}
{% set ns.source_intf_ip = check_ip_on_interface(ns.source_intf, VLAN_INTERFACE) %}
{% elif ns.source_intf.startswith('Ethernet') %}
{% set ns.source_intf_ip = check_ip_on_interface(ns.source_intf, INTERFACE) %}
{% elif ns.source_intf.startswith('PortChannel') %}
{% set ns.source_intf_ip = check_ip_on_interface(ns.source_intf, PORTCHANNEL_INTERFACE) %}
{% elif ns.source_intf.startswith('Loopback') %}
{% set ns.source_intf_ip = check_ip_on_interface(ns.source_intf, LOOPBACK_INTERFACE) %}
{% endif %}
{% endif %}
{% endif %}
{% if ns.source_intf_ip == 'true' %}
interface listen {{ns.source_intf}}
{% elif (NTP) and NTP['global']['vrf'] == 'mgmt' %}
interface listen eth0
{% elif MGMT_INTERFACE %}
{% for (mgmt_intf, mgmt_prefix) in MGMT_INTERFACE|pfx_filter %}
interface listen {{ mgmt_prefix | ip }}
{% endfor %}

View File

@ -0,0 +1,16 @@
[Unit]
Description=Network Time Service
Documentation=man:ntpd(8)
After=network.target
Conflicts=systemd-timesyncd.service
StartLimitIntervalSec=0
[Service]
Type=forking
# Debian uses a shell wrapper to process /etc/default/ntp
# and select DHCP-provided NTP servers if available
ExecStart=/usr/lib/ntp/ntp-systemd-wrapper
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -23,6 +23,13 @@ TACPLUS_SERVER_PASSKEY_DEFAULT = ""
TACPLUS_SERVER_TIMEOUT_DEFAULT = "5"
TACPLUS_SERVER_AUTH_TYPE_DEFAULT = "pap"
def run_cmd(cmd, log_err = True):
try:
subprocess.check_call(cmd, shell = True)
except Exception as err:
if log_err:
syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}"
.format(err.cmd, err.returncode, err.output))
def is_true(val):
if val == 'True' or val == 'true':
@ -283,6 +290,104 @@ class KdumpCfg(object):
if isLoad or data.get("num_dumps") is not None:
run_cmd("sonic-kdump-config --num_dumps " + num_dumps)
class NtpCfg(object):
def __init__(self, CfgDb):
self.config_db = CfgDb
self.ntp_global = {}
self.has_ntp_servers = False
def load(self, ntp_global_conf, ntp_server_conf):
syslog.syslog(syslog.LOG_INFO, "NtpCfg load ...")
for row in ntp_global_conf:
self.ntp_global_update(row, ntp_global_conf[row], True)
self.ntp_server_update(0, ntp_server_conf, True)
def handle_ntp_source_intf_chg (self, key):
# if no ntp server configured, do nothing
if self.has_ntp_servers == False:
return
# check only the intf configured as source interface
if (len(self.ntp_global) == 0):
return
if 'src_intf' not in self.ntp_global:
return
if key[0] != self.ntp_global['src_intf']:
return
else:
# just restart ntp config
cmd = 'systemctl restart ntp-config'
run_cmd(cmd)
def ntp_global_update(self, key, data, isLoad):
syslog.syslog(syslog.LOG_INFO, "ntp global configuration update")
new_src = new_vrf = orig_src = orig_vrf = ""
if 'src_intf' in data:
new_src = data['src_intf']
if 'vrf' in data:
new_vrf = data['vrf']
if (len(self.ntp_global) != 0):
if 'src_intf' in self.ntp_global:
orig_src = self.ntp_global['src_intf']
if 'vrf' in self.ntp_global:
orig_vrf = self.ntp_global['vrf']
self.ntp_global = data
# during initial load of ntp configuration, ntp server configuration decides if to restart ntp-config
if (isLoad):
syslog.syslog(syslog.LOG_INFO, "ntp global update in load")
return
# check if ntp server configured, if not, do nothing
if self.has_ntp_servers == False:
syslog.syslog(syslog.LOG_INFO, "no ntp server when global config change, do nothing")
return
if (new_src != orig_src):
syslog.syslog(syslog.LOG_INFO, "ntp global update for source intf old {} new {}, restarting ntp-config"
.format(orig_src, new_src))
cmd = 'systemctl restart ntp-config'
run_cmd(cmd)
else:
if (new_vrf != orig_vrf):
syslog.syslog(syslog.LOG_INFO, "ntp global update for vrf old {} new {}, restarting ntp service"
.format(orig_vrf, new_vrf))
cmd = 'service ntp restart'
run_cmd(cmd)
def ntp_server_update(self, key, data, isLoad):
syslog.syslog(syslog.LOG_INFO, 'ntp server update key {} data {}'.format(key, data))
# during load, restart ntp-config regardless if ntp server is configured or not
if isLoad == True:
if data != {}:
self.has_ntp_servers = True
else:
# for runtime ntp server change, to determine if there is ntp server configured, need to
# get from configDB, as delete triggers 2 event handling
ntp_servers_tbl = self.config_db.get_table('NTP_SERVER')
if ntp_servers_tbl != {}:
self.has_ntp_servers = True
else:
self.has_ntp_servers = False
cmd = 'systemctl restart ntp-config'
syslog.syslog(syslog.LOG_INFO, 'ntp server update, restarting ntp-config, ntp server exists {}'.format(self.has_ntp_servers))
run_cmd(cmd)
class HostConfigDaemon:
def __init__(self):
self.config_db = ConfigDBConnector()
@ -291,6 +396,7 @@ class HostConfigDaemon:
self.aaacfg = AaaCfg()
self.iptables = Iptables()
self.ntpcfg = NtpCfg(self.config_db)
# Cache the values of 'state' field in 'FEATURE' table of each container
self.cached_feature_states = {}
@ -310,6 +416,10 @@ class HostConfigDaemon:
lpbk_table = self.config_db.get_table('LOOPBACK_INTERFACE')
self.iptables.load(lpbk_table)
# Load NTP configurations
ntp_server = self.config_db.get_table('NTP_SERVER')
ntp_global = self.config_db.get_table('NTP')
self.ntpcfg.load(ntp_global, ntp_server)
def update_feature_state(self, feature_name, state, feature_table):
if self.cached_feature_states[feature_name] == "always_enabled":
@ -425,6 +535,7 @@ class HostConfigDaemon:
add = False
self.iptables.iptables_handler(key, data, add)
self.ntpcfg.handle_ntp_source_intf_chg(key)
def feature_state_handler(self, key, data):
feature_name = key
@ -444,6 +555,16 @@ class HostConfigDaemon:
if self.cached_feature_states[feature_name] != state:
self.update_feature_state(feature_name, state, feature_table)
def ntp_server_handler (self, key, data):
syslog.syslog(syslog.LOG_INFO, 'NTP server handler...')
ntp_server_db = self.config_db.get_table('NTP_SERVER')
data = ntp_server_db
self.ntpcfg.ntp_server_update(key, data, False)
def ntp_global_handler (self, key, data):
syslog.syslog(syslog.LOG_INFO, 'NTP global handler...')
self.ntpcfg.ntp_global_update(key, data, False)
def kdump_handler (self, key, data):
syslog.syslog(syslog.LOG_INFO, 'Kdump handler...')
self.kdumpCfg.kdump_update(key, data, False)
@ -462,6 +583,8 @@ class HostConfigDaemon:
self.config_db.subscribe('TACPLUS', lambda table, key, data: self.tacacs_global_handler(key, data))
self.config_db.subscribe('LOOPBACK_INTERFACE', lambda table, key, data: self.lpbk_handler(key, data))
self.config_db.subscribe('FEATURE', lambda table, key, data: self.feature_state_handler(key, data))
self.config_db.subscribe('NTP_SERVER', lambda table, key, data: self.ntp_server_handler(key, data))
self.config_db.subscribe('NTP', lambda table, key, data: self.ntp_global_handler(key, data))
self.config_db.subscribe('KDUMP', lambda table, key, data: self.kdump_handler(key, data))
syslog.syslog(syslog.LOG_INFO,