diff --git a/chrony-dnssrv@.service b/chrony-dnssrv@.service new file mode 100644 index 0000000..139ed28 --- /dev/null +++ b/chrony-dnssrv@.service @@ -0,0 +1,8 @@ +[Unit] +Description=DNS SRV lookup of %I for chrony +After=chronyd.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/libexec/chrony-helper update-dnssrv-servers %I diff --git a/chrony-dnssrv@.timer b/chrony-dnssrv@.timer new file mode 100644 index 0000000..8495e01 --- /dev/null +++ b/chrony-dnssrv@.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Periodic DNS SRV lookup of %I for chrony + +[Timer] +OnActiveSec=0 +OnUnitInactiveSec=1h + +[Install] +WantedBy=timers.target diff --git a/chrony-service-helper.patch b/chrony-service-helper.patch index 9a34d3e..fa76883 100644 --- a/chrony-service-helper.patch +++ b/chrony-service-helper.patch @@ -5,7 +5,7 @@ diff -up chrony-1.31/examples/chronyd.service.service-helper chrony-1.31/example PIDFile=/var/run/chronyd.pid EnvironmentFile=-/etc/sysconfig/chronyd ExecStart=/usr/sbin/chronyd $OPTIONS -+ExecStartPost=/usr/libexec/chrony-helper add-dhclient-servers ++ExecStartPost=/usr/libexec/chrony-helper update-daemon [Install] WantedBy=multi-user.target diff --git a/chrony.dhclient b/chrony.dhclient index 66885cb..8b12441 100644 --- a/chrony.dhclient +++ b/chrony.dhclient @@ -8,16 +8,13 @@ chrony_config() { for server in $new_ntp_servers; do echo "$server ${NTPSERVERARGS:-iburst}" >> $SERVERFILE done - /usr/libexec/chrony-helper is-running && - /usr/libexec/chrony-helper add-dhclient-servers && - /usr/libexec/chrony-helper remove-dhclient-servers || : + /usr/libexec/chrony-helper update-daemon || : fi } chrony_restore() { if [ -f $SERVERFILE ]; then rm -f $SERVERFILE - /usr/libexec/chrony-helper is-running && - /usr/libexec/chrony-helper remove-dhclient-servers || : + /usr/libexec/chrony-helper update-daemon || : fi } diff --git a/chrony.helper b/chrony.helper index c51540d..78fd944 100644 --- a/chrony.helper +++ b/chrony.helper @@ -1,62 +1,176 @@ #!/bin/bash +# This script configures running chronyd to use NTP servers obtained from +# DHCP and _ntp._udp DNS SRV records. Files with servers from DHCP are managed +# externally (e.g. by a dhclient script). Files with servers from DNS SRV +# records are updated here using the dig utility. chronyc=/usr/bin/chronyc -dhclient_servers=/var/lib/dhclient/chrony.servers.* -dhclient_added_servers=/var/lib/dhclient/chrony.added_servers +helper_dir=/var/run/chrony-helper +added_servers_file=$helper_dir/added_servers + +network_sysconfig_file=/etc/sysconfig/network +dhclient_servers_files=/var/lib/dhclient/chrony.servers.* +dnssrv_servers_files=$helper_dir/dnssrv@* +dnssrv_timer_prefix=chrony-dnssrv@ chrony_command() { $chronyc -a -n -m "$1" } -update_dhclient_added_servers() { - new_servers=$(echo "$1" | sort -u) - old_servers=$(cat $dhclient_added_servers 2> /dev/null) - [ "$old_servers" = "$new_servers" ] && return 0 - [ -n "$new_servers" ] && echo "$new_servers" > $dhclient_added_servers || - rm -f $dhclient_added_servers -} - -add_dhclient_servers() { - shopt -s nullglob - servers_files=($dhclient_servers) - shopt -u nullglob - (( ${#servers_files[*]} )) || return 0 - - added_servers=$( - cat $dhclient_added_servers 2> /dev/null - cat ${servers_files[*]} | - while read server serverargs; do - chrony_command "add server $server $serverargs" &> /dev/null && - echo "$server" - done) - update_dhclient_added_servers "$added_servers" -} - -remove_dhclient_servers() { - [ -f $dhclient_added_servers ] || return 0 - all_servers=$( - cat $dhclient_servers 2> /dev/null | - while read server serverargs; do - echo "$server" - done | sort -u) - echo "$all_servers" | comm -23 $dhclient_added_servers - | - while read server; do - chrony_command "delete $server" &> /dev/null - done - added_servers=$(echo "$all_servers" | comm -12 $dhclient_added_servers -) - update_dhclient_added_servers "$added_servers" -} - is_running() { chrony_command "tracking" &> /dev/null } +is_update_needed() { + for file in $dhclient_servers_files $dnssrv_servers_files \ + $added_servers_file; do + [ -e "$file" ] && return 0 + done + return 1 +} + +update_daemon() { + local all_servers_with_args all_servers added_servers + + if ! is_running; then + rm -f $added_servers_file + return 0 + fi + + all_servers_with_args=$( + cat $dhclient_servers_files $dnssrv_servers_files 2> /dev/null) + + all_servers=$( + echo "$all_servers_with_args" | + while read server serverargs; do + echo "$server" + done | sort -u) + added_servers=$( ( + cat $added_servers_file 2> /dev/null + echo "$all_servers_with_args" | + while read server serverargs; do + [ -z "$server" ] && continue + chrony_command "add server $server $serverargs" &> /dev/null && + echo "$server" + done) | sort -u) + + comm -23 <(echo -n "$added_servers") <(echo -n "$all_servers") | + while read server; do + chrony_command "delete $server" &> /dev/null + done + + added_servers=$(comm -12 <(echo -n "$added_servers") <(echo -n "$all_servers")) + + [ -n "$added_servers" ] && echo "$added_servers" > $added_servers_file || + rm -f $added_servers_file +} + +get_dnssrv_servers() { + local name=$1 + + if ! command -v dig &> /dev/null; then + echo "Missing dig (DNS lookup utility)" >&2 + return 1 + fi + + ( + . $network_sysconfig_file &> /dev/null + + output=$(dig "$name" srv +short +ndots=2 +search 2> /dev/null) + [ $? -ne 0 ] && return 0 + + echo "$output" | while read prio weight port target; do + server=${target%.} + [ -z "$server" ] && continue + echo "$server port $port ${NTPSERVERARGS:-iburst}" + done + ) +} + +check_dnssrv_name() { + local name=$1 + + if [ -z "$name" ]; then + echo "No DNS SRV name specified" >&2 + return 1 + fi + + if [ "${name:0:9}" != _ntp._udp ]; then + echo "DNS SRV name $name doesn't start with _ntp._udp" >&2 + return 1 + fi +} + +update_dnssrv_servers() { + local name=$1 + local srv_file=$helper_dir/dnssrv@$name servers + + check_dnssrv_name "$name" || return 1 + + servers=$(get_dnssrv_servers "$name") + [ -n "$servers" ] && echo "$servers" > "$srv_file" || rm -f "$srv_file" +} + +set_dnssrv_timer() { + local state=$1 name=$2 + local srv_file=$helper_dir/dnssrv@$name servers + local timer=$dnssrv_timer_prefix$name.timer + + check_dnssrv_name "$name" || return 1 + + if [ "$state" = enable ]; then + systemctl enable "$timer" + systemctl start "$timer" + elif [ "$state" = disable ]; then + systemctl stop "$timer" + systemctl disable "$timer" + rm -f "$srv_file" + fi +} + +list_dnssrv_timers() { + systemctl --all --full -t timer list-units | grep "^$dnssrv_timer_prefix" | \ + sed "s|^$dnssrv_timer_prefix\(.*\)\.timer.*|\1|" +} + +prepare_helper_dir() { + mkdir -p $helper_dir + exec 100> $helper_dir/lock + if ! flock -w 20 100; then + echo "Failed to lock $helper_dir" >&2 + return 1 + fi +} + +print_help() { + echo "Usage: $0 COMMAND" + echo + echo "Commands:" + echo " update-daemon" + echo " update-dnssrv-servers NAME" + echo " enable-dnssrv NAME" + echo " disable-dnssrv NAME" + echo " list-dnssrv" + echo " is-running" + echo " command CHRONYC-COMMAND" +} + case "$1" in - add-dhclient-servers) - add_dhclient_servers + update-daemon|add-dhclient-servers|remove-dhclient-servers) + is_update_needed || exit 0 + prepare_helper_dir && update_daemon ;; - remove-dhclient-servers) - remove_dhclient_servers + update-dnssrv-servers) + prepare_helper_dir && update_dnssrv_servers "$2" && update_daemon + ;; + enable-dnssrv) + set_dnssrv_timer enable "$2" + ;; + disable-dnssrv) + set_dnssrv_timer disable "$2" && prepare_helper_dir && update_daemon + ;; + list-dnssrv) + list_dnssrv_timers ;; is-running) is_running @@ -65,8 +179,8 @@ case "$1" in chrony_command "$2" ;; *) - echo $"Usage: $0 {add-dhclient-servers|remove-dhclient-servers|is-running|command|forced-command}" + print_help exit 2 esac -exit $? +exit $? diff --git a/chrony.spec b/chrony.spec index d456e5f..671ded5 100644 --- a/chrony.spec +++ b/chrony.spec @@ -13,6 +13,8 @@ URL: http://chrony.tuxfamily.org Source0: http://download.tuxfamily.org/chrony/chrony-%{version}%{?prerelease}.tar.gz Source1: chrony.dhclient Source2: chrony.helper +Source3: chrony-dnssrv@.service +Source4: chrony-dnssrv@.timer # simulator for test suite Source10: https://github.com/mlichvar/clknetsim/archive/%{clknetsim_ver}/clknetsim-%{clknetsim_ver}.tar.gz %{?gitpatch:Patch0: chrony-%{version}%{?prerelease}-%{gitpatch}.patch.gz} @@ -58,7 +60,7 @@ md5sum -c <<-EOF | (! grep -v 'OK$') 3a5a49a9fdc344cd31893571215c2c74 examples/chrony.conf.example2 2e9fe409a17de5d53a65f9869c4119f5 examples/chrony.logrotate d7d323d0ea7ccc258710371ea79563d1 examples/chrony.nm-dispatcher - 1a5122f7f40446596777a6c69431c415 examples/chronyd.service + d65acc66bd53844a6fe72b62dfae42bd examples/chronyd.service EOF # use our vendor zone (2.*pool.ntp.org names include IPv6 addresses) @@ -108,6 +110,8 @@ install -m 644 -p examples/chronyd.service \ $RPM_BUILD_ROOT%{_unitdir}/chronyd.service install -m 644 -p examples/chrony-wait.service \ $RPM_BUILD_ROOT%{_unitdir}/chrony-wait.service +install -m 644 -p %{SOURCE3} $RPM_BUILD_ROOT%{_unitdir}/chrony-dnssrv@.service +install -m 644 -p %{SOURCE4} $RPM_BUILD_ROOT%{_unitdir}/chrony-dnssrv@.timer install -m 755 -p %{SOURCE2} $RPM_BUILD_ROOT%{_libexecdir}/chrony-helper @@ -161,6 +165,7 @@ fi %{_infodir}/chrony.info* %{_prefix}/lib/systemd/ntp-units.d/*.list %{_unitdir}/chrony*.service +%{_unitdir}/chrony*.timer %{_mandir}/man[158]/%{name}*.[158]* %dir %attr(-,chrony,chrony) %{_localstatedir}/lib/chrony %ghost %attr(-,chrony,chrony) %{_localstatedir}/lib/chrony/drift