2023-06-20
md
Adding a Local Network Time Server in Linux
<-GNATS, a Tiny Basic ESP32 GPS Based NTP Server

The following explains how to integrate a local time server such GNATS into a local network. Some of this material was included in the first version of the post describing the construction of GNATS, but it should be separated for two reasons. First, the subject discussed here has nothing to do with GNATS in particular, it applies to any local NTP server. Second, adding a local NTP server to a LAN is a rather more complex task than described initially.

As far as I can ascertain, there are three ways of using the local NTP server.

Table of Contents

  1. Static NTP Client Configuration
    1. systemd-timesyncd
    2. chrony
    3. ntp
    4. tasmota
  2. Automatic NTP Configuration
    1. DHCP Server Configuration
      1. NTP Servers Option in OpenWRT
      2. NTP Servers Option in pfSense and OPNsense
    2. Client NTP Discovery Configuration
      1. systemd-timesyncd
      2. chrony
      3. ntp
      4. tasmota
  3. Practical Considerations

Static NTP Client Configuration toc

The Raspberry Pi uses the systemd-timesyncd service to set the time from NTP servers. However that is far from a universal choice as the following table about some of the devices in the house makes abundantly clear.

DeviceOSNTP service
Raspberry Pi 3BRaspberry Pi OSsystemd-timesyncd
Rock Pi SUbuntu 20.04 LTSsystemd-timesyncd
Orange Pi PC 2Armbian 22.11.4 Jammychrony
HP i5 NASOMV (Debian 11 bullseye)chrony
HP i7 DesktopLinux Mint 20.1ntp

Add to that list a phone and a number of tablets that I use that are running different versions of Android, my spouse's iMac and iPad, the 20 or more Tasmota based IoT devices and probably other things that I can't remember right now. It's all a bit of a mess.

systemd-timesyncd toc

This has already been covered at length in the previous post about GNATS.

Edit /etc/systemd/timesyncd.conf configuration file. It belongs to root, so upgrading privileges with sudo will be necessary unless one has opened a session as root. Modify the first or first and second entries in the [Time<] section.

[Time] #NTP= #FallbackNTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org >

As an example, here is the section on the Raspberrry Pi used for testing GNATS.

[Time] NTP=192.168.1.23 FallbackNTP=ca.pool.ntp.org debian.pool.ntp.org pool.ntp.org

To check on the configuration file

pi@tarte:~ $ date Wed 14 Jun 21:13:35 ADT 2023 pi@tarte:~ $ timedatectl status Local time: Wed 2023-06-14 21:13:55 ADT Universal time: Thu 2023-06-15 00:13:55 UTC RTC time: Thu 2023-06-15 00:13:55 UTC Time zone: America/Halifax (ADT, -0300) System clock synchronized: yes NTP service: active RTC in local TZ: no pi@tarte:~ $ timedatectl show-timesync SystemNTPServers=192.168.1.23 FallbackNTPServers=192.168.1.88 ServerName=192.168.1.23 ServerAddress=192.168.1.23 RootDistanceMaxUSec=5s PollIntervalMinUSec=32s PollIntervalMaxUSec=34min 8s PollIntervalUSec=2min 8s NTPMessage={ Leap=0, Version=4, Mode=4, Stratum=1, Precision=-15, RootDelay=15us, RootDispersion=15us, Reference=, OriginateTimestamp=Wed 2023-06-14 21:13:53 ADT, ReceiveTimestamp=Wed 2023-06-14 21:13:53 ADT, TransmitTimestamp=Wed 2023-06-14 21:13:53 ADT, DestinationTimestamp=Wed 2023-06-14 21:13:53 ADT, Ignored=no PacketCount=3, Jitter=31.612ms } Frequency=-8163025 pi@tarte:~ $ timedatectl timesync-status Server: 192.168.1.23 (192.168.1.23) Poll interval: 2min 8s (min: 32s; max 34min 8s) Leap: normal Version: 4 Stratum: 1 Reference: Precision: 31us (-15) Root distance: 22us (max: 5s) Offset: -31.887ms Delay: 29.701ms Jitter: 31.612ms Packet count: 3 Frequency: -124.558ppm

chrony toc

I know very little about chrony, consequently what follows should be seen as an indication of a possible way to go about this. Let's start with a look at list of time sources defined by default.

citrus@fruity:~$ chronyc sources MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^- prod-ntp-3.ntp4.ps5.cano> 2 10 377 1035 +1156us[+1179us] +/- 50ms ^- prod-ntp-5.ntp1.ps5.cano> 2 10 377 667 +1459us[+1459us] +/- 48ms ^+ prod-ntp-4.ntp1.ps5.cano> 2 10 377 1013 +6843us[+6866us] +/- 50ms ^- pugot.canonical.com 2 10 377 259 -1183us[-1183us] +/- 88ms ^+ ntp2.wiktel.com 1 10 377 586 +568us[ +568us] +/- 28ms ^+ four10.gac.edu 2 10 377 117 -1183us[-1183us] +/- 39ms ^* edge-iad.txryan.com 2 10 377 952 -582us[ -559us] +/- 16ms ^- 104.167.241.253 2 9 377 15 +983us[ +983us] +/- 55ms

Looking at the content of a few files gives a good indication of how to configure the time sources for this service.

citrus@fruity:~$ cat /etc/chrony/chrony.conf # Welcome to the chrony configuration file. See chrony.conf(5) for more # information about usable directives. # Include configuration files found in /etc/chrony/conf.d. confdir /etc/chrony/conf.d # This will use (up to): # - 4 sources from ntp.ubuntu.com which some are ipv6 enabled # - 2 sources from 2.ubuntu.pool.ntp.org which is ipv6 enabled as well # - 1 source from [01].ubuntu.pool.ntp.org each (ipv4 only atm) # This means by default, up to 6 dual-stack and up to 2 additional IPv4-only # sources will be used. # At the same time it retains some protection against one of the entries being # down (compare to just using one of the lines). See (LP: #1754358) for the # discussion. # # About using servers from the NTP Pool Project in general see (LP: #104525). # Approved by Ubuntu Technical Board on 2011-02-08. # See http://www.pool.ntp.org/join.html for more information. pool ntp.ubuntu.com iburst maxsources 4 pool 0.ubuntu.pool.ntp.org iburst maxsources 1 pool 1.ubuntu.pool.ntp.org iburst maxsources 1 pool 2.ubuntu.pool.ntp.org iburst maxsources 2 # Use time sources from DHCP. sourcedir /run/chrony-dhcp # Use NTP sources found in /etc/chrony/sources.d. sourcedir /etc/chrony/sources.d ... citrus@fruity:~$ cat /etc/chrony/sources.d/README Only NTP sources can be specified in the /etc/chrony/sources.d directory. Files in this directory must end with the ".sources" suffix. They can only contain the "peer", "pool" and "server" directives and must have all lines terminated by a trailing newline. There is no need to restart chronyd for these time sources to be usable, running 'chronyc reload sources' is sufficient. Example: # echo 'server 192.0.2.1 iburst' > /etc/chrony/sources.d/local-ntp-server.sources # chronyc reload sources

So let's execute this as instructed.

citrus@fruity:~$ sudo su root@fruity:/home/citrus# echo 'server 192.168.1.23 iburst' > /etc/chrony/sources.d/local-ntp-server.sources root@fruity:/home/citrus# chronyc reload sources 200 OK root@fruity:/home/citrus# exit exit citrus@fruity:~$ chronyc sources -v .-- Source mode '^' = server, '=' = peer, '#' = local clock. / .- Source state '*' = current best, '+' = combined, '-' = not combined, | / 'x' = may be in error, '~' = too variable, '?' = unusable. || .- xxxx [ yyyy ] +/- zzzz || Reachability register (octal) -. | xxxx = adjusted offset, || Log2(Polling interval) --. | | yyyy = measured offset, || \ | | zzzz = estimated error. || | | MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^- prod-ntp-3.ntp4.ps5.cano> 2 10 377 25m +1210us[+1278us] +/- 49ms ^- prod-ntp-5.ntp1.ps5.cano> 2 10 377 1158 +985us[ +970us] +/- 48ms ^+ prod-ntp-4.ntp1.ps5.cano> 2 10 377 471 +1234us[+1220us] +/- 45ms ^- pugot.canonical.com 2 10 377 751 -2765us[-2779us] +/- 89ms ^+ ntp2.wiktel.com 1 10 377 51 -886us[ -886us] +/- 27ms ^+ four10.gac.edu 2 10 377 609 +1021us[+1007us] +/- 36ms ^* edge-iad.txryan.com 2 10 377 422 -356us[ -370us] +/- 17ms ^- 104.167.241.253 2 10 377 507 +912us[ +898us] +/- 55ms ^x 192.168.1.23 1 6 377 37 +222ms[ +222ms] +/- 47ms

The * beside edge-iad.txryan.com indicates [that it is] the source to which chronyd is currently synchronised while the x beside the IP address of the local NTP server indicates a clock which chronyd thinks is a falseticker (i.e. its time is inconsistent with a majority of other sources) (source: chronyc(1) Manual Page). Adding the prefer option (server 192.168.1.23 iburst prefer) which would make GNATS the primary time source does nothing. Probably because the service will not use a "falseticker" in preference to consistent time source. So with this configuration, the local time server is the backup, and remote servers will be used preferentially. This is not unreasonable, but it is the opposite of what I wanted to achieve when creating GNATS.

One could remove all the pooled servers in the main configuration file /etc/chrony/chrony.conf, and after a while, the chrony will use the local NTP server at the time source.

citrus@fruity:~$ chronyc sources -v .-- Source mode '^' = server, '=' = peer, '#' = local clock. / .- Source state '*' = current best, '+' = combined, '-' = not combined, | / 'x' = may be in error, '~' = too variable, '?' = unusable. || .- xxxx [ yyyy ] +/- zzzz || Reachability register (octal) -. | xxxx = adjusted offset, || Log2(Polling interval) --. | | yyyy = measured offset, || \ | | zzzz = estimated error. || | | MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^* 192.168.1.23 1 6 177 24 -2719us[ -76us] +/- 13ms

Of course there is no backup in this situation.

ntp toc

There seems to be a sentiment that chrony is a newer and better replacement of the venerable ntp from the Network Time Foundation. I am in no position to judge, but I did notice a close similarity between the programs. As before, will start the list of time sources defined by default.

michel@hp:~$ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== 0.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 1.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 2.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 3.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 ntp.ubuntu.com .POOL. 16 p - 64 0 0.000 0.000 0.000 +dev.smatwebdesi 204.9.54.119 2 u 924 1024 377 58.303 1.589 0.614 -B1-66ER.matrix. 18.26.4.105 2 u 1025 1024 377 44.235 2.387 0.879 -sensei.ruselabs 164.67.62.194 2 u 279 1024 377 87.562 -0.092 0.835 +alphyn.canonica 194.58.200.20 2 u 806 1024 375 27.171 0.846 0.695 *shaka.ruselabs. 59.61.64.227 2 u 974 1024 377 35.937 0.944 0.603

It doesn't look as if ntp will load additional configuration files, so I added the local NTP server directly in the main configuration file

michel@hp:~$ sudo nano /etc/ntp.conf ... #Specify one or more NTP servers. # Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board # on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for # more information. pool 0.ubuntu.pool.ntp.org iburst pool 1.ubuntu.pool.ntp.org iburst pool 2.ubuntu.pool.ntp.org iburst pool 3.ubuntu.pool.ntp.org iburst # Use Ubuntu's ntp server as a fallback. pool ntp.ubuntu.com # Adding local server server 192.168.1.23 iburst ...

I don't know if there's a command to reload sources, so I juste restarted the ntp.service and checked that it was working properly

michel@hp:~$ sudo systemctl restart ntp.service michel@hp:~$ sudo systemctl status ntp.service ● ntp.service - Network Time Service Loaded: loaded (/lib/systemd/system/ntp.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2023-06-17 14:27:55 ADT; 4s ago Docs: man:ntpd(8) Process: 116020 ExecStart=/usr/lib/ntp/ntp-systemd-wrapper (code=exited, status=0/SUCCESS) Main PID: 116028 (ntpd) Tasks: 2 (limit: 14133) Memory: 940.0K CGroup: /system.slice/ntp.service └─116028 /usr/sbin/ntpd -p /var/run/ntpd.pid -g -u 106:111 jun 17 14:27:56 hp ntpd[116028]: Soliciting pool server 73.239.145.47 jun 17 14:27:57 hp ntpd[116028]: Soliciting pool server 204.2.134.162 jun 17 14:27:57 hp ntpd[116028]: Soliciting pool server 194.116.227.9 jun 17 14:27:58 hp ntpd[116028]: Soliciting pool server 45.79.111.167 jun 17 14:27:58 hp ntpd[116028]: Soliciting pool server 15.204.21.148 jun 17 14:27:58 hp ntpd[116028]: Soliciting pool server 171.66.97.126 jun 17 14:27:59 hp ntpd[116028]: Soliciting pool server 174.136.99.6 jun 17 14:27:59 hp ntpd[116028]: Soliciting pool server 206.82.16.3 jun 17 14:27:59 hp ntpd[116028]: Soliciting pool server 69.164.202.202 jun 17 14:27:59 hp ntpd[116028]: Soliciting pool server 168.235.89.132

One has to wait a while before the service will look at the local time server.

michel@hp:~$ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== 0.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 1.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 2.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 3.ubuntu.pool.n .POOL. 16 p - 64 0 0.000 0.000 0.000 ntp.ubuntu.com .POOL. 16 p - 64 0 0.000 0.000 0.000 192.168.1.23 1 u 38 64 1 11.052 -253.55 20.547 -ny-time.gofile. 192.5.41.40 2 u 31 64 17 40.347 0.078 2.250 #t1.time.gq1.yah 98.137.249.214 2 u 40 64 7 86.825 5.759 2.105 -ntp2.wiktel.com .GPS. 1 u 35 64 7 50.983 -1.315 3.188 #216.31.17.12 172.18.56.13 2 u 37 64 7 148.374 4.156 3.174 -dutch.arpnet.ne 66.220.9.122 2 u 37 64 7 21.145 -0.953 3.305 -li1210-167.memb 132.163.96.2 2 u 29 64 17 89.959 -6.631 3.236 #12.167.151.1 158.51.134.123 3 u 40 64 7 30.650 -69.055 3.198 -50.205.57.38 .GPS. 1 u 37 64 7 46.245 0.033 3.200 -edge-pdx.txryan 129.134.25.123 2 u 38 64 7 93.111 5.291 2.150 *clock.nyc.he.ne 66.220.9.122 2 u 49 64 37 21.228 -0.707 1.284 +50.46.167.147 ( 23.252.63.82 2 u 47 64 37 96.338 -7.885 1.410 +65-100-46-166.d 10.30.90.252 2 u 43 64 37 83.383 -1.502 1.204 -static.36.62.78 225.254.30.190 4 u 41 64 37 95.317 6.795 1.230

As can be seen, the situation appears to be very similar to what was obtained in chrony. It would be necessary to eliminate the server pool to ensure that the local NTP server is used, but then there would be no back up.

tasmota toc

Accoring to the online documentation about the set up of NTP servers in Tasmota, up to three servers can be specified. For example, NtpServer1 0.can.pool.ntp.org would set 0.can.pool.ntp.org as the first NTP server, while NtpServer1 0 would clear the value. So the following command ensures that only the local NTP server be used.

Backlog0 NtpServer1 192.168.1.23; NtpServer2 0; NtpServer3 0

However the documentation is clear. NTP servers are consulted in order, from server 1 to 3, stopping as soon as a time request is obtained. So it would probably be best to change only NtpServer1 and to let the other two unchanged or to specify explicitely which server to place in all three slots

Accordingly, the NTP servers of the Tasmota devices in the home ise the following.

Backlog0 NtpServer1 192.168.1.23 ; NtpServer2 ca.pool.ntp.org ; NtpServer3 pool.ntp.org

Since the NTP server will provide a UTC time stamp and local time is more meaningful when using triggers such as the time, sunset, or sunrise, the time zone must be defined. As one would expect Tasmota does not store a time zone database, differences between UTC and local time have to be specified numerically. Luckily, there's no need to into the arcane details about specifying the correct time zone. Just go to the Tasmota Timezone Table~ and search for the best city for your local time.

In my case there are a few entries that could be used such as the one for Halifax. It's lucky that the only Halifax in the database is in nearby Nova Scotia, because there's at least another ten municipalities named Halifax elsewhere.

America/Halifax Backlog0 Timezone 99; TimeStd 0,1,11,1,2,-240; TimeDst 0,2,3,1,2,-180

Combining these commands, the pertinent time values for a new Tasmota device could be fixed in a single backlog command.

Backlog0 NtpServer1 192.168.1.23; NtpServer2 0; NtpServer3 0; Backlog0 Timezone 99; TimeStd 0,1,11,1,2,-240; TimeDst 0,2,3,1,2,-180

However, it would be very tedious to find the IP address of the 20 plus Tasmota devices, open the Web interface of each of them and then manually enter the command in the Web console, if I wanted to change the address of an NTP server or the time zone of all the installed devices. Instead the commands can be sent to the devices by publishing MQTT messages. As far as I know, backlog is not supported in MQTT commands, so each command must be published independantly.

mqtt subjectmqtt publish message
cmnd/tasmota/NtpServer1192.168.1.23
cmnd/tasmota/NtpServer2ca.pool.ntp.org
cmnd/tasmota/NtpServer3pool.ntp.org
cmnd/tasmota/Timezone99
cmnd/tasmota/TimeStd0,1,11,1,2,-240
cmnd/tasmota/TimeDst0,2,3,1,2,-180

Suscribe to "stat/+/RESULT" to see the reply of all the Tasmota devices connected to the MQTT broker. I use my own MQTT client, lazmqttc, written in Lazarus / Free pascal to do this. Here is my tasmota.json file that I systematically use to send and receive MQTT messages from all the Tasmota devices on our home system. Of course the address of the MQTT broker would need to be adjusted.

{ "Host" : "192.168.1.22", "Port" : 1883, "User" : "", "Password" : "", "SSL" : false, "SSLCert" : "", "KeepAlives" : 60, "ReconnectDelay" : 10, "ReconnectBackoff" : true, "AutoReconnect" : true, "PubTopic" : "cmnd/tasmotas/status", "PubPayload" : "5", "PubQoS" : 0, "PubRetain" : false, "SubTopics" : [ { "Topic" : "#", "QoS" : 0, "Use" : false }, { "Topic" : "fruityticz/in", "QoS" : 0, "Use" : false }, { "Topic" : "fruityticz/out", "QoS" : 0, "Use" : false }, { "Topic" : "stat/+/STATUS5", "QoS" : 0, "Use" : true }, { "Topic" : "stat/+/RESULT", "QoS" : 0, "Use" : false }, { "Topic" : "stat/#", "QoS" : 0, "Use" : false }, { "Topic" : "tele/#", "QoS" : 0, "Use" : false } ] }

Sorry for the self-promotion, mosquitto_pub, mosquitt_rr or any of the innumerable MQTT clients available for desktops or tablets and phones could just as easily be used.

Automatic NTP Configuration toc

Would it not be better if the local network provided the address of our tiny NTP server instead of having to manually configure the address of the NTP server of each device connected to the network? Indeed DHCP servers can provide NTP server addresses along with dynamically assigned IP addresses to clients. It took a little while to figure it out, but here is what needs to be done in broad strokes.

  1. Ensure that the DHCP server (usually in the router) forwards the address of the local time server using option 42.
  2. Ensure that all DHCP clients that need to access NTP servers request their addresses from the DHCP server. In other words, DHCP clients must be told to avail themselves of option 42.

As already said, my ISP provided "all-in-one" ONT/Ethernet switch/Wifi router/DHCP server etc. is very much a "consumer grade" device with very few user settings. In particular it does not have any NTP options. Consequently this approach may not be possible for many.

Networked devices with self-assigned IP addresses never communicate with the DHCP server. So the proposed scheme only works if network peripherals that need access to the NTP server (and DNS servers for that matter) use dynamic IP addressing. For servers that need a fixed IP address, have the DHCP server assign them their fixed address. So far all "consumer grade" routers that I have seen have that capability.

DHCP Server Configuration toc

Among the optional configuration parameters that can be returned by a DHCP server to its clients there is the

8.3. Network Time Protocol Servers Option This option specifies a list of IP addresses indicating NTP [18] servers available to the client. Servers SHOULD be listed in order of preference. The code for this option is 42. Its minimum length is 4, and the length MUST be a multiple of 4. Code Len Address 1 Address 2 +-----+-----+-----+-----+-----+-----+-----+-----+-- | 42 | n | a1 | a2 | a3 | a4 | a1 | a2 | ... +-----+-----+-----+-----+-----+-----+-----+-----+--
Source: RFC 2132, p.18

The NTP servers option (or option 42) is not usually included by DHCP servers by default. It needs to be enabled in the DHCP server and DHCP clients must request this information. Explaining in detail how to enable NTP Servers option (option 42) in all possible routers / DHCP servers is just not possible. Unfortunately, I have a single complete and tested example of how to perform the first step.

By the way, there are time zone options for DHCP as per RFC4833... we are not going down that rabbit hole.

DHCP Server Option 42 (NTP Servers) in OpenWRT toc

The firmware of a Rosewill 300M Wireless N which is a rebranded TP-Link TL-WR841ND v7 router has been LEDE 17.01.4. In other words, it is running a 2017 version of OpenWRT (formerly known as LEDE, itself formerly known as OpenWRT... it's complicated). In principle, it could be upgraded but that will not be done. The router, which is not in constant use, has been a very useful tool for testing network configurations without interfering with the working LAN. Hopefully, changes to OpenWrt will not have been so so major as to make the information in this section useless.

Enabling option 42 in the DHCP server can be done from the command line or with the LuCi graphical interface. The command-line method is the easiest to present so let's start with that.

michel@hp:~$ ssh root@192.168.1.1 root@192.168.1.1's password: ********** BusyBox v1.25.1 () built-in shell (ash) _________ / /\ _ ___ ___ ___ / LE / \ | | | __| \| __| / DE / \ | |__| _|| |) | _| /________/ LE \ |____|___|___/|___| lede-project.org \ \ DE / \ LE \ / ----------------------------------------------------------- \ DE \ / Reboot (17.01.4, r3560-79f57e422d) \________\/ ----------------------------------------------------------- root@LEDE:~# uci show dhcp.lan.dhcp_option=42 uci: Entry not found the option is not set root@LEDE:~# uci set dhcp.lan.dhcp_option=42,192.168.1.23 add the local NTP server \ root@LEDE:~# uci show dhcp.lan.dhcp_option=42 as the only one available to clients dhcp.lan.dhcp_option='42,192.168.1.23' just checking root@LEDE:~# uci commit dhcp write the change to the configuration file root@LEDE:~# /etc/init.d/dnsmasq restart restart the DHCP/DNS server udhcpc: started, v1.25.1 udhcpc: sending discover udhcpc: no lease, failing Warning: the 'option dhcp_option' syntax is deprecated, use 'list dhcp_option'

I could not figure out the proper syntax, but that last line is just a warning so let's not worry about it now.

It is just as easy to enable DHCP option 42 using the graphical interface, but it is a bit more awkward to present here. Start by clicking on the Network menu and select Interfaces. Then click on the Edit button of the desired interface which in my case was the LAN interface.

Edit the LAN interface


In the bottom of the Intefaces - LAN page, in the DHCP Server section, click on the Advances Settings tab.

Edit the DHCP-Options


Add same setting as before, 42,192.168.1.23, in the DHCP-Options box as shown above.

There is not much documentation on this, basically just an example entitled DHC options. Maybe I just didn't know where to look.

NTP Servers Option in pfSense and OpnSense toc

Presumably the same can be done with other routers, but as already said, not with the one rented from my Internet service provider.

The Netgate documentation for pfSense has the following description on how to add the option to its DHCP server.

NTP Servers
To specify NTP Servers (Network Time Protocol Servers), click the Display Advanced button to the right of that field, and enter IP addresses for up to two NTP servers.

I could not find specific documentation for the equivalent in OPNsense. But in an issue in the forum, Udo says I've configured "Services -> DHCPv4 -> [Interface] -> NTP servers" with one IP which indicates that this is done in the Web interface much like in pfSense and OpenWRT.

In another topic in the forum, NTP on DHCP there is an explanation by guest19228 on how to add the option ntp-server 192.168.1.23 line in the DHCP service configuration file dhcpd.conf. Since both pfSense and OPNsense are based on FreeBDS, that explanation may be valid for both.

Client NTP Discovery Configuration toc

Enabling NTP discovery in DHCP clients is very similar to what was required to enable NTP servers option in DHCP servers. But of course, the details are different depending on the NTP service running on the client.

systemd-timesyncd toc

Remember that systemd-timesyncd is the NTP client in Raspbery Pi OS. Accordingly it is the configuration of the Raspberry Pi that will be examined in this section.

  1. Make sure that the Raspberry Pi is a DHCP client, so remove any static IP address.
  2. Remove the addition of a specific NTP server in etc/systemd/timesyncd.conf if that was done in the past
  3. Ensure that the request in /etc/dhcp/dhclient.conf has ntp-servers. This is true by default. It means that the content of option 42 will be requested from the DHCP server.
  4. Enable the ntp_servers option in the /etc/dhcpcd.conf file. This means that the list of NTP servers returned by the DHCP server will be used.

Here are commands that verify that all that was done correctly.

pi@tarte:~ $ cat /etc/dhcpcd.conf | grep static option classless_static_routes # Example static IP configuration: #static ip_address=192.168.1.10/24 #static ip6_address=fd51:42f8:caae:d92e::ff/64 #static routers=192.168.1.1 #static domain_name_servers=192.168.1.1 8.8.8.8 fd51:42f8:caae:d92e::1 # It is possible to fall back to a static IP if DHCP fails: # define static profile #profile static_eth0 #static ip_address=192.168.1.23/24 #static routers=192.168.1.1 #static domain_name_servers=192.168.1.1 # fallback to static profile on eth0 #fallback static_eth0 pi@tarte:~ $ cat /etc/systemd/timesyncd.conf | grep -A 1 \#NTP= #NTP= #FallbackNTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org pi@tarte:~ $ cat /etc/dhcp/dhclient.conf | grep -B 2 ntp-servers request subnet-mask, broadcast-address, time-offset, routers, domain-name, domain-name-servers, domain-search, host-name, dhcp6.name-servers, dhcp6.domain-search, dhcp6.fqdn, dhcp6.sntp-servers, netbios-name-servers, netbios-scope, interface-mtu, rfc3442-classless-static-routes, ntp-servers; pi@tarte:~ $ cat /etc/dhcpcd.conf | grep -B 1 ntp_servers # Most distributions have NTP support. option ntp_servers

Yes it's ntp_servers with an underscore in dhcpcd.conf and ntp-servers with a dash in dhclient.conf.

Then clear the journal files as much as possible. This is to make it easier to check what happened after the Pi is rebooted, just in case something goes wrong.

pi@tarte:~ $ sudo journalctl --rotate; sudo journalctl -m --vacuum-time=1s Vacuuming done, freed 0B of archived journals from /run/log/journal. Deleted archived journal /run/log/journal/f0fc159a3f5844b5a29aaa488532c69b/system@21b31e5e6ea940999f0233af43801239-0000000000000001-0005f06c06d67277.journal (2.2M). Deleted archived journal /run/log/journal/f0fc159a3f5844b5a29aaa488532c69b/system@21b31e5e6ea940999f0233af43801239-00000000000002d7-0005f06c2650e119.journal (2.2M). Vacuuming done, freed 4.5M of archived journals from /run/log/journal/f0fc159a3f5844b5a29aaa488532c69b.

Before rebooting, I try to wipe out as many timestamps saved in the file system as possible. It's important get rid of /var/lib/systemd/timesync/clock because timesync would set the system time to its timestamp in the absence of an NTP time. I am not too sure why fake-hwclock is installed. It does pretty much what systemd-timesync does with clock only it actually writes the time in its own timestamp fake-hwclock.data.

pi@tarte:~ $ sudo rm -r /var/log/lastlog; sudo systemctl stop fake-hwclock; sudo rm /var/lib/systemd/timesync/clock; echo "2022-12-08 00:01:00" | sudo tee /etc/fake-hwclock.data; sudo touch -t 202212080001 /etc/fake-hwclock.data; sudo reboot 2022-12-08 00:01:00 Connection to 192.168.1.22 closed by remote host. Connection to 192.168.1.22 closed.

As soon as I could log back in, I checked the date and time and it was correct.

michel@hp:~$ ssh pi@tarte.local pi@tarte.local's password: ********* Linux tarte 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Wi-Fi is currently blocked by rfkill. Use raspi-config to set the country before use. pi@tarte:~ $ date Mon 12 Jun 20:37:04 ADT 2023 pi@tarte:~ $ timedatectl timesync-status Server: 192.168.1.23 (192.168.1.23) Poll interval: 32s (min: 32s; max 34min 8s) Leap: normal Version: 4 Stratum: 2 Reference: 535047 Precision: 1.954ms (-9) Root distance: 703.246ms (max: 5s) Offset: +17.820232s Delay: 1.312ms Jitter: 0 Packet count: 1 Frequency: +0.000ppm pi@tarte:~ $ timedatectl show-timesync SystemNTPServers=192.168.1.23 FallbackNTPServers=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org ServerName=192.168.1.23 ServerAddress=192.168.1.23 RootDistanceMaxUSec=5s PollIntervalMinUSec=32s PollIntervalMaxUSec=34min 8s PollIntervalUSec=1min 4s NTPMessage={ Leap=0, Version=4, Mode=4, Stratum=2, Precision=-9, RootDelay=40.161ms, RootDispersion=683.166ms, Reference=535047, OriginateTimestamp=Mon 2023-06-12 20:37:04 ADT, ReceiveTimestamp=Mon 2023-06-12 20:37:04 ADT, TransmitTimestamp=Mon 2023-06-12 20:37:04 ADT, DestinationTimestamp=Mon 2023-06-12 20:37:04 ADT, Ignored=no PacketCount=2, Jitter=2.613ms } Frequency=0

All that looks very good. The time is correct, the router NTP server is being used by the Pi and the only way it could know to do that is because the DHCP server on the router provided the address.

Finally, here is an edited portion of the system journal showing how the system time was set.

pi@tarte:~ $ journalctl | grep -E 'ntp|dhcp|time' ... Dec 22 07:55:44 tarte systemd[1]: System time before build time, advancing clock. ... Dec 22 07:55:44 tarte fake-hwclock[130]: Current system time: 2022-12-22 11:55:43 Dec 22 07:55:44 tarte fake-hwclock[130]: To set system time to this saved clock anyway, use "force" Dec 22 07:55:48 tarte systemd[1]: apt-daily.timer: Not using persistent file timestamp Mon 2023-06-12 19:09:44 ADT as it is in the future. Dec 22 07:55:48 tarte systemd[1]: apt-daily-upgrade.timer: Not using persistent file timestamp Mon 2023-06-12 15:42:32 ADT as it is in the future. Dec 22 07:55:48 tarte systemd[1]: e2scrub_all.timer: Not using persistent file timestamp Mon 2023-06-12 15:42:32 ADT as it is in the future. Dec 22 07:55:48 tarte systemd[1]: fstrim.timer: Not using persistent file timestamp Mon 2023-06-12 16:21:29 ADT as it is in the future. Dec 22 07:55:48 tarte systemd[1]: logrotate.timer: Not using persistent file timestamp Mon 2023-06-12 15:42:32 ADT as it is in the future. Dec 22 07:55:48 tarte systemd[1]: man-db.timer: Not using persistent file timestamp Mon 2023-06-12 15:42:32 ADT as it is in the future. ... Dec 22 07:55:50 tarte dhcpcd[438]: eth0: waiting for carrier ... Dec 22 07:55:50 tarte dhcpcd[438]: eth0: carrier acquired ... Dec 22 07:55:50 tarte dhcpcd[438]: eth0: rebinding lease of 192.168.1.22 Dec 22 07:55:50 tarte dhcpcd[438]: eth0: probing address 192.168.1.22/24 ... Dec 22 07:55:55 tarte dhcpcd[438]: eth0: leased 192.168.1.22 for 43200 seconds Dec 22 07:55:55 tarte dhcpcd[438]: eth0: adding route to 192.168.1.0/24 Dec 22 07:55:55 tarte dhcpcd[438]: eth0: adding default route via 192.168.1.1 ... Dec 22 07:57:51 tarte systemd[1]: apt-daily-upgrade.timer: Succeeded. Dec 22 07:57:51 tarte systemd[1]: apt-daily.timer: Succeeded. Dec 22 07:57:51 tarte systemd[1]: e2scrub_all.timer: Succeeded. Dec 22 07:57:51 tarte systemd[1]: fstrim.timer: Succeeded. Dec 22 07:57:51 tarte systemd[1]: logrotate.timer: Succeeded. Dec 22 07:57:51 tarte systemd[1]: man-db.timer: Succeeded. Dec 22 07:57:51 tarte systemd[1]: systemd-tmpfiles-clean.timer: Succeeded. ... Dec 22 07:57:51 tarte dhcpcd[611]: dhcpcd exited ... Dec 22 07:57:53 tarte systemd[1]: dhcpcd.service: Succeeded. Dec 22 07:57:54 tarte systemd[1]: user.slice: Consumed 1.589s CPU time. Dec 22 07:57:54 tarte systemd[1]: systemd-timesyncd.service: Succeeded. ... Jun 12 20:21:41 tarte systemd-timesyncd[619]: Initial synchronization to time server 192.168.1.23:123 (192.168.1.23).

Clearly there are many more timestamps that I thought but none of those shown in the journal are used to jump the system time forward except for the build time of systemd, the modified timestamp (mtime) of clock and the modified timestamp of fake-hwclock.data or its content I am not sure which. Having deleted the first two, the initial time, Dec 22 07:55:44 is derived from the compilation date of systemd.

pi@tarte:~ $ ls -l /bin/systemd lrwxrwxrwx 1 root root 20 Dec 22 07:55 /bin/systemd -> /lib/systemd/systemd

chrony toc

As shown in the table above, Chrony is the NTP client installed in Armbian which is running on the Orange Pi PC 2 while systemd-timesyncd is not installed at all. There are other significant differences between Raspberry Pi OS and Armbian. NetworkManager is the network management service. In addition networking configuration is done with netplan. I'll paraphrase the start of the description in the Netplan man page. It reads abstract network definitions written in YAML and converts them to actual configuration files for a specific networking service. Once these configuration files are written into the /run directory, the networking service starts using the newly written configuration. It sounds rather complicated, but as will be demonstrated it works. Just as in the previous section, we test that chrony will use a time server proposed by the DHCP server by removing all other time sources.

To remove the fixed IP address assigned to the Ethernet interface, eth0, nmtui, the NetworkManager configuration tool with a text user interface, could be used. Edit the appropriate connection, which here is called Wired&nbps;connection 1.

citrus@fruity:~$ sudo nmtui

┌────────────────────────────┤ Edit Connection ├──────────────────────────┐ │ │ │ Profile name Wired connection 1______________________ │ │ Device eth0 (02:01:42:00:F8:1B)________________ │ │ │ │ ═ ETHERNET <Show> │ │ │ │ ╤ IPv4 CONFIGURATION <Manual> <Hide> │ │ │ Addresses 192.168.1.48/24__________ <Remove> │ │ │ <Add...> │ │ │ Gateway 192.168.1.1______________ │

Then change the IPv4 configuration from <Manual> to <Automatic>. I prefered adding a couple of lines in the interfaces configuration file to achieve the same end.

citrus@fruity:~$ cat /etc/network/interfaces source /etc/network/interfaces.d/* # Network is managed by Network manager auto lo iface lo inet loopback # override the Network manager settings auto eth0 iface eth0 inet dhcp

Explicit NTP server addresses can be found in the configuration file chrony.conf. They will be lines that begin with either poll or server. Other sources can be listed in files in the /etc/chrony/sources.d directory and ending with the .sources extension. The following verifies that no such explicit time sources remain.

citrus@fruity:~$ grep -c -i -E '^pool|^server' /etc/chrony/chrony.conf 0 citrus@fruity:~$ ls /etc/chrony/sources.d/*.sources ls: cannot access '/etc/chrony/sources.d/*.sources': No such file or directory

Finally let's make sure that chrony will use the NTP servers obtained from the DHCP server.

citrus@fruity:~$ grep -i -1 'sourcedir.*dhcp' /etc/chrony/chrony.conf # Use time sources from DHCP. sourcedir /run/chrony-dhcp

It was not necessary to add that line, it was already present.

After rebooting, it was obvious that the local time server was being used by chrony.

citrus@fruity:~$ timedatectl status Local time: Mon 2023-06-19 08:33:40 ADT Universal time: Mon 2023-06-19 11:33:40 UTC RTC time: Mon 2023-06-19 11:33:28 Time zone: America/Halifax (ADT, -0300) System clock synchronized: yes NTP service: active RTC in local TZ: no citrus@fruity:~$ chronyc tracking Reference ID : C0A80117 (192.168.1.23) Stratum : 2 Ref time (UTC) : Mon Jun 19 11:33:50 2023 System time : 0.007926481 seconds slow of NTP time Last offset : -0.000734664 seconds RMS offset : 0.008753908 seconds Frequency : 31.217 ppm fast Residual freq : -184.958 ppm Skew : 0.644 ppm Root delay : 0.085670374 seconds Root dispersion : 0.021219740 seconds Update interval : 64.9 seconds Leap status : Normal citrus@fruity:~$ chronyc sources MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^* 192.168.1.23 1 6 377 31 +16ms[ +15ms] +/- 43ms

Finally, I found it interesting to look at which DHCP client is used and at the file written to the /run directory.

> citrus@fruity:~$ journalctl | grep dhcp Jun 19 07:57:20 fruity dhclient[517]: For info, please visit https://www.isc.org/software/dhcp/ Jun 19 07:57:27 fruity sh[517]: For info, please visit https://www.isc.org/software/dhcp/ Jun 19 07:57:32 fruity NetworkManager[723]: <info> [1687172252.3390] dhcp-init: Using DHCP client 'internal' citrus@fruity:~$ cat /run/chrony-dhcp/eth0.sources server 192.168.1.23 iburst

ntp toc

The ntp client that is installed on my desktop Linux Mint computer is similar to chrony although one could argue that it is slightly less elegant. The complete configuration file, /etc/ntp.conf is copied to /run/ntp.conf.dhcp if the DHCP server returned a list of NTP time sources (option 42). That list of discovered sources is injected into the copied configuration file as the first command as shown below.

michel@hp:~$ cat /run/ntp.conf.dhcp # This file was copied from /etc/ntp.conf with the server options changed # to reflect the information sent by the DHCP server. Any changes made # here will be lost at the next DHCP event. Edit /etc/ntp.conf instead. # NTP server entries received from DHCP server server 192.168.1.23 iburst # /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help driftfile /var/lib/ntp/ntp.drift # Leap seconds definition provided by tzdata leapfile /usr/share/zoneinfo/leap-seconds.list # Enable this if you want statistics to be logged. #statsdir /var/log/ntpstats/ statistics loopstats peerstats clockstats filegen loopstats file loopstats type day enable filegen peerstats file peerstats type day enable filegen clockstats file clockstats type day enable # Specify one or more NTP servers. # Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board # on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for # more information. #pool 0.ubuntu.pool.ntp.org iburst #pool 1.ubuntu.pool.ntp.org iburst #pool 2.ubuntu.pool.ntp.org iburst #pool 3.ubuntu.pool.ntp.org iburst # Use Ubuntu's ntp server as a fallback. #pool ntp.ubuntu.com # Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for # details. The web page # might also be helpful. # # Note that "restrict" applies to both servers and clients, so a configuration # that might be intended to block requests from certain clients could also end # up blocking replies from your own upstream servers. # By default, exchange time with everybody, but don't allow configuration. restrict -4 default kod notrap nomodify nopeer noquery limited restrict -6 default kod notrap nomodify nopeer noquery limited # Local users may interrogate the ntp server more closely. restrict 127.0.0.1 restrict ::1 # Needed for adding pool entries restrict source notrap nomodify noquery # Clients from this (example!) subnet have unlimited access, but only if # cryptographically authenticated. #restrict 192.168.123.0 mask 255.255.255.0 notrust # If you want to provide time to your local subnet, change the next line. # (Again, the address is an example only.) #broadcast 192.168.123.255 # If you want to listen to time broadcasts on your local subnet, de-comment the # next lines. Please do this only if you trust everybody on the network! #disable auth #broadcastclient

As can be seen I had removed all other time sources, so that only the local time server is used.

michel@hp:~$ timedatectl Local time: lun 2023-06-19 19:39:09 ADT Universal time: lun 2023-06-19 22:39:09 UTC RTC time: lun 2023-06-19 22:39:09 Time zone: America/Halifax (ADT, -0300) System clock synchronized: no NTP service: n/a RTC in local TZ: no michel@hp:~$ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== *192.168.1.23 1 u 22 64 1 155.611 -11.311 33.474 michel@hp:~$ timedatectl Local time: lun 2023-06-19 22:14:50 ADT Universal time: mar 2023-06-20 01:14:50 UTC RTC time: mar 2023-06-20 01:14:50 Time zone: America/Halifax (ADT, -0300) System clock synchronized: yes NTP service: n/a RTC in local TZ: no michel@hp:~$ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== 192.168.1.23 1 u 227 256 37 43.924 -19.674 70.465

Just when the system clock was deemed synchronized can't be said with certainty, but according to the two checks done above, it took less than three hours.

tasmota toc

This section will be short. Tasmota does not support this feature. According to Jason2866 in an answer to the Timezone not set form (sic) NTP server #14804 discussion, Tasmota does not support getting NTP servers via DHCP. This feature was removed since many routers does not support (correctly). This generated issues. You have to set via command see documentation. Of we have already seen how to set the NTP servers explicitely so there's no need to repeat that here.

Practical Considerations toc

The above has all been about testing the addition of a local time server. But what would be a good configuration? Recall that this adventure into the world of clocks began in the previous post where I professed wanting to be a good Internet citizen by setting up a local NTP server to fulfill all NTP requests from the many devices on our home network. Here is how I would like to do it.

  1. One NTP server MUST be setup on the local network. Call this server LNTP.
    • The machine hosting LNTP MUST to be on at all times.
    • The LNTP SHOULD have access to the Internet to "discipline" its own clock using external time sources.
    • The LNTP MUST have access to the GPS based local NTP server (GNATS or better) as a back up.
    • The LNTP MUST be accessible from all devices connected to the LAN.
    While it may be prefereable that LNTP be on a router, but it could be on a NAS or the host of a home automation system. Access to LNTP from within the LAN will not be a problem with the typical flat configuration in most homes, but if subnets or VLANs are in place then appropriate firewall rules and routing will be needed.
  2. The LAN's DHCP server SHOULD forward the LNTP IP address to DHCP clients.
  3. All devices permanently connected to the LAN MUST use the LNTP as a time source.
  4. All devices permanently connected to the LAN MUST NOT access external time sources. They COULD use the GPS based NTP server as a time source in addition to LNTP.
  5. Portable devices SHOULD accept DHCP forwarded time sources.
  6. Where it is not possible to enable use of DHCP supplied time sources, LNTP SHOULD be added to the list of time sources of portable devices.

I am planning to create a home lab (and this) in the near future that will be based on pfSense or OPNsense. Consequently, that list of MUSTs and SHOULDs may be modified as well as some of the instructions above. In the meantime, here is how I tested some of these ideas with the old OpenWRT router.

Enabling the NTP client and server in LEDEEnabling the NTP server in LEDE

The image on the left (or top) shows how to enable the NTP client and server in the router which takes care of point 1. Do not pay attention to the URLs to external time sources. What is shown is not what is typically done and I have yet to investigate the best way to select public NTP servers. The image on the right shows that the DHCP server on the router will provide its own IP address as a time source to its client, which takes care of point 2. The image below is a simplified representation of how it would work.

Local Time Server Overview

<-GNATS, a Tiny Basic ESP32 GPS Based NTP Server