Wednesday, December 4, 2019

Running my own STRATUM 1 NTP Server

Supratim Sanyal's Blog: Stratum 1 NTP Time Server and GPS location using gpsd on Linux: cgps


I am obsessive about correct clocks, including the ones inside my hobbyist systems and have been running public-service NTP pool servers for many years. However, I have always been limited to implementing Stratum-2 NTP servers so far.

But recently I found this DIYmall VK-162 USB GPS dongle for under thirteen bucks at Amazon, and decided to run my own GPS-synchronized stratum-1 NTP server at home. Here's a link to the item at Amazon (includes my affiliate id):




I tried this on my old trusty Compaq Presario CQ-61 running MX Linux 18.3 Continuum (a Debian derivative). Since the GPS dongle adheres to the National Marine Electronics Association (NMEA) standard protocol, it should work on pretty much any computer with a USB port and NMEA-compliant software that can read serial data from a USB port.

Here's a step-by-step guide on running your own Stratum 1 GPS clock source for your NTP time server.

Plug in the GPS dongle and inspect dmesg. The Communications Device Class (CDC) Abstract Control Model (ACM) driver automatically kicked in and created a serial TTY device "ttyACM0".

$ dmesg
...
...
[27235.388161] usb 3-1: new full-speed USB device number 2 using ohci-pci
[27235.551302] usb 3-1: New USB device found, idVendor=1546, idProduct=01a7, bcdDevice= 1.00
[27235.551311] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[27235.551315] usb 3-1: Product: u-blox 7 - GPS/GNSS Receiver
[27235.551318] usb 3-1: Manufacturer: u-blox AG - www.u-blox.com
[27236.578514] cdc_acm 3-1:1.0: ttyACM0: USB ACM device
[27236.579964] usbcore: registered new interface driver cdc_acm
[27236.579967] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters


"lsusb" shows the USB GPS dongle "U-Blox AG [u-blox 7]" in the list of USB devices:

$ lsusb
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 003: ID 0bda:018a Realtek Semiconductor Corp. 
Bus 001 Device 002: ID 064e:a102 Suyin Corp. Acer/Lenovo Webcam [CN0316]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 1546:01a7 U-Blox AG [u-blox 7]
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

The system log confirms that a device "/dev/ttyACM0" was created. Not only that, the Linux Debian 4.19.37-2 kernel also recognized the GPS ACM device as a Pulse per Second (PPS) clock source.

$ sudo tail /var/log/messages
[sudo] password for localuser: 
Nov 30 22:45:40 compaq-cq61 kernel: [27235.551315] usb 3-1: Product: u-blox 7 - GPS/GNSS Receiver
Nov 30 22:45:40 compaq-cq61 kernel: [27235.551318] usb 3-1: Manufacturer: u-blox AG - www.u-blox.com
Nov 30 22:45:41 compaq-cq61 mtp-probe: checking bus 3, device 2: "/sys/devices/pci0000:00/0000:00:12.0/usb3/3-1"
Nov 30 22:45:41 compaq-cq61 mtp-probe: bus: 3, device: 2 was not an MTP device
Nov 30 22:45:41 compaq-cq61 kernel: [27236.578514] cdc_acm 3-1:1.0: ttyACM0: USB ACM device
Nov 30 22:45:41 compaq-cq61 kernel: [27236.579964] usbcore: registered new interface driver cdc_acm
Nov 30 22:45:41 compaq-cq61 kernel: [27236.579967] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
Nov 30 22:55:50 compaq-cq61 kernel: [27845.239635] pps_ldisc: PPS line discipline registered
Nov 30 22:55:50 compaq-cq61 kernel: [27845.241003] pps pps0: new PPS source acm0
Nov 30 22:55:50 compaq-cq61 kernel: [27845.241034] pps pps0: source "/dev/ttyACM0" added

The "/dev/ttyACM0" device indeed exists now:

$ ls -l /dev/ttyA*
crw-rw---- 1 root dialout 166, 0 Nov 30 22:45 /dev/ttyACM0


We can read the "/dev/ttyACM0" device to see the GPS Dongle reporting data:

$ cat /dev/ttyACM0
$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50

$GPTXT,01,01,02,HW  UBX-G70xx   00070000 FF7FFFFFo*69

$GPTXT,01,01,02,ROM CORE 1.00 (59842) Jun 27 2012 17:43:52*59

$GPTXT,01,01,02,PROTVER 14.00*1E

$GPTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*20

$GPTXT,01,01,02,ANTSTATUS=OK*3B

$GPTXT,01,01,02,LLC FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFD*2C

$GPRMC,,V,,,,,,,,,,N*53

$GPVTG,,,,,,,,,N*30

$GPGGA,,,,,,0,00,99.99,,,,,,*48

$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30

$GPGLL,,,,,,V,N*64

$GPRMC,,V,,,,,,,,,,N*53

$GPVTG,,,,,,,,,N*30

$GPGGA,,,,,,0,00,99.99,,,,,,*48

$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30

$GPGLL,,,,,,V,N*64

$GPRMC,,V,,,,,,,,,,N*53

$GPVTG,,,,,,,,,N*30

$GPGGA,,,,,,0,00,99.99,,,,,,*48

$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30

$GPGLL,,,,,,V,N*64

^C

So at this point the USB GPS dongle itself is configured and sending data over. Install the gpsd and gpsd-clients packages to read the GPS data and present it to the NTP daemon:

$ sudo apt-get install -y gpsd gpsd-clients
Reading package lists... Done
Building dependency tree     
Reading state information... Done
The following additional packages will be installed:
  libgps22 python-gps
The following NEW packages will be installed:
  gpsd gpsd-clients libgps22 python-gps
0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 868 kB of archives.
After this operation, 3,271 kB of additional disk space will be used.
Get:1 http://ftp.us.debian.org/debian stretch/main amd64 libgps22 amd64 3.16-4 [90.2 kB]
Get:2 http://ftp.us.debian.org/debian stretch/main amd64 gpsd amd64 3.16-4 [266 kB]
Get:3 http://ftp.us.debian.org/debian stretch/main amd64 python-gps amd64 3.16-4 [101 kB]                         
Get:4 http://ftp.us.debian.org/debian stretch/main amd64 gpsd-clients amd64 3.16-4 [411 kB]
Fetched 868 kB in 1min 2s (13.9 kB/s)
Selecting previously unselected package libgps22:amd64.
(Reading database ... 267737 files and directories currently installed.)
Preparing to unpack .../libgps22_3.16-4_amd64.deb ...
Unpacking libgps22:amd64 (3.16-4) ...
Selecting previously unselected package gpsd.
Preparing to unpack .../archives/gpsd_3.16-4_amd64.deb ...
Unpacking gpsd (3.16-4) ...
Selecting previously unselected package python-gps.
Preparing to unpack .../python-gps_3.16-4_amd64.deb ...
Unpacking python-gps (3.16-4) ...
Selecting previously unselected package gpsd-clients.
Preparing to unpack .../gpsd-clients_3.16-4_amd64.deb ...
Unpacking gpsd-clients (3.16-4) ...
Setting up libgps22:amd64 (3.16-4) ...
Processing triggers for menu (2.1.47+b1) ...
Processing triggers for libc-bin (2.24-11+deb9u4) ...
ldconfig: file /usr/lib/x86_64-linux-gnu/libraptor2.so.0 is truncated

ldconfig: file /usr/lib/x86_64-linux-gnu/libraptor2.so.0.0.0 is truncated

Setting up python-gps (3.16-4) ...
Processing triggers for systemd (232-25+deb9u12) ...
Setting up gpsd-clients (3.16-4) ...
Processing triggers for man-db (2.7.6.1-2) ...
Setting up gpsd (3.16-4) ...
Creating/updating gpsd user account...
Created symlink /etc/systemd/system/sockets.target.wants/gpsd.socket → /lib/systemd/system/gpsd.socket.
Processing triggers for menu (2.1.47+b1) ...
Processing triggers for systemd (232-25+deb9u12) ...

We now modify gpsd daemon's configuration file to use our /dev/ttyACM0 device and to look for satellites as soon as it starts up (the latter tip is from a post by Rapid7):

$ cd /etc/default/
$ sudo cp gpsd gpsd.orig

And edit gpsd using your favorite editor so that it reads:

# /etc/default/gpsd
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time
START_DAEMON="true"

# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="true"

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
#DEVICES=""
#Specify the device name:
DEVICES="/dev/ttyACM0"

# Other options you want to pass to gpsd
#GPSD_OPTIONS=""
#-n: look for satellites as soon as it starts up (see https://blog.rapid7.com/2015/07/27/adding-a-gps-time-source-to-ntpd/):
GPSD_OPTIONS="-n"

Here is a diff between the edited and original gpsd configuration file:

$ diff gpsd gpsd.orig
11,13c11
< #DEVICES=""
< #Specify the device name:
< DEVICES="/dev/ttyACM0"
---
> DEVICES=""
16,18c14
< #GPSD_OPTIONS=""
< #-n: look for satellites as soon as it starts up (see https://blog.rapid7.com/2015/07/27/adding-a-gps-time-source-to-ntpd/):
< GPSD_OPTIONS="-n"
---
> GPSD_OPTIONS=""

Now we can restart the gpsd daemon so that the edited configuration is effective:

$ sudo service gpsd restart
[ ok ] Restarting GPS (Global Positioning System) daemon: gpsd.

$ sudo service gpsd status
[ ok ] gpsd is running.

We can now run "cgps" to see the information our GPS dongle is returning (may take a few minutes as the GPS dongle gets fixes on the GPS satellites):

$ cgps
┌───────────────────────────────────────────┐┌─────────────────────────────────┐
│    Time:       2019-12-01T12:40:41.000Z   ││PRN:   Elev:  Azim:  SNR:  Used: │
│    Latitude:    39.191583 N               ││   2    43    102    22      N   │
│    Longitude:   77.234396 W               ││   5    66    033    27      Y   │
│    Altitude:   421.6 ft                   ││   6    03    110    00      N   │
│    Speed:      0.2 mph                    ││   7    04    051    00      N   │
│    Heading:    0.0 deg (true)             ││  13    50    145    28      Y   │
│    Climb:      0.0 ft/min                 ││  15    34    191    31      Y   │
│    Status:     3D FIX (0 secs)            ││  21    10    300    00      N   │
│    Longitude Err:   +/- 123 ft            ││  25    09    240    00      N   │
│    Latitude Err:    +/- 92 ft             ││  26    01    321    00      N   │
│    Altitude Err:    +/- 75 ft             ││  29    58    290    18      N   │
│    Course Err:      n/a                   ││  30    07    081    00      N   │
│    Speed Err:       n/a                   ││                                 │
│    Time offset:     -0.043                ││                                 │
│    Grid Square:     FM19je                ││                                 │
└───────────────────────────────────────────┘└─────────────────────────────────┘
:-77.234396822,"epx":37.684,"epy":28.091,"track":106.8098,"speed":0.102,"eps":1.31}
{"class":"TPV","device":"/dev/ttyACM0","mode":3,"time":"2019-12-01T12:40:41.000Z","ept":0.005,"lat":39.191584000,"lon"
:-77.234396833,"alt":128.500,"epx":37.684,"epy":28.091,"epv":23.000,"track":0.0000,"speed":0.100}
{"class":"TPV","device":"/dev/ttyACM0","mode":3,"time":"2019-12-01T12:40:41.000Z","ept":0.005,"lat":39.191584000,"lon"

{"PRN":6,"el":3,"az":110,"ss":0,"used":false},{"PRN":7,"el":4,"az":51,"ss":0,"used":false},{"PRN":13,"el":50,"az":145,
"ss":28,"used":true},{"PRN":15,"el":34,"az":191,"ss":31,"used":true},{"PRN":21,"el":10,"az":300,"ss":0,"used":false},{
"PRN":25,"el":9,"az":240,"ss":0,"used":false},{"PRN":26,"el":1,"az":321,"ss":0,"used":false},{"PRN":29,"el":58,"az":29
0,"ss":18,"used":false},{"PRN":30,"el":7,"az":81,"ss":0,"used":false}]}
{"class":"TPV","device":"/dev/ttyACM0","mode":3,"time":"2019-12-01T12:40:41.000Z","ept":0.005,"lat":39.191584000,"lon"
:-77.234396833,"alt":128.500,"epx":37.684,"epy":28.091,"epv":23.000,"track":0.0000,"speed":0.100,"climb":0.000}

Press "q" to quite cgps.

The next step is to configure our NTP daemon to use the information from the GSP dongle. Thankfully the gpsd daemon already implements the shared memory area that the NTP daemon understands to use as a clock source.

First, stop and disable the systemd-timesyncd service installed with some modern Linux distributions because it conflicts with ntpd:

$ sudo systemctl stop systemd-timesyncd
$ sudo systemctl disable systemd-timesyncd
Removed /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service.
Removed /etc/systemd/system/dbus-org.freedesktop.timesync1.service.

Install the ntp daemon for Linux:

$ sudo apt-get -y install ntp ntpdate

Enable the ntpd daemon but stop it for now for reconfiguration.

$ sudo systemctl enable ntp
Synchronizing state of ntp.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable ntp
$ sudo systemctl stop ntp

Now we can proceed with configuring the NTP daemon as a Stratum-1 time server serving time from the GPS receiver as the time source.

$ cd /etc
localuser@compaq-cq61:/etc
$ sudo cp ntp.conf ntp.conf.bak
localuser@compaq-cq61:/etc
$ sudo vi ntp.conf

Edit the NTP daemon configuration file to contain the following:

# /etc/ntp.conf
# Stratum-1 time server, time source = GPS
driftfile /var/lib/ntp/ntp.drift

# GPS Serial data reference
server 127.127.28.0 minpoll 4 maxpoll 4 prefer
fudge 127.127.28.0 time1 0.0 refid GPS
# GPS PPS reference
server 127.127.28.1 minpoll 4 maxpoll 4 prefer
fudge 127.127.28.1 refid PPS
# Fallback Stratum 1 Servers
#server time.nist.gov
#server time.apple.com
#server time.google.com

restrict -4 default kod notrap nomodify nopeer noquery limited
# Uncomment next line if IPv6 is enabled

#restrict -6 default kod notrap nomodify nopeer noquery limited

# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1
# Uncomment next line if IPv6 is enabled
#restrict ::1

restrict source notrap nomodify noquery

# 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

We restart the NTP daemon to use the modified ntp.conf configuration file:

$ sudo service ntp restart
[ ok ] Restarting GPS (Global Positioning System) daemon: gpsd.

$ sudo service ntp status
[ ok ] NTP server is running.

And voila! After a few minutes, the NTP daemon is a Stratum-1 clock source synchronized to our GPS receiver:

$ ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*SHM(0)          .GPS.            0 l   15   16  377    0.000   -4.541  31.811


Other public stratum-1 NTP Time Servers


#USA - NAVY
192.5.41.40
192.5.41.41
192.5.41.209
204.34.198.40
204.34.198.41
198.30.92.2
130.207.244.240
164.67.62.194
204.123.2.72
128.252.19.1
129.7.1.66
204.34.198.40
204.34.198.41

# USA - NIST
time-a-g.nist.gov
time-b-g.nist.gov
time-c-g.nist.gov
time-d-g.nist.gov
time-e-g.nist.gov
time-a-wwv.nist.gov
time-b-wwv.nist.gov
time-c-wwv.nist.gov
time-d-wwv.nist.gov
time-e-wwv.nist.gov
time-a-b.nist.gov
time-b-b.nist.gov
time-c-b.nist.gov
time-d-b.nist.gov
time-e-b.nist.gov
time.nist.gov
utcnist.colorado.edu
utcnist2.colorado.edu

#Canada
ntp1.acorn-ns.ca
ntp2.acorn-ns.ca
ntp3.acorn-ns.ca
ntp.nyy.ca
ntp.zaf.ca
ntp1.torix.ca
ntp2.torix.ca
ntp3.torix.ca
ntp4.torix.ca
clock.uregina.ca
tick.usask.ca
tock.usask.ca
ntp.wetmore.ca
ntp.ymartin.com

#France
canon.inria.fr
ntp-sop.inria.fr
ntp-p1.obspm.fr

#Germany
ntp0.fau.de
ntp1.fau.de
ntp2.fau.de
ntp3.fau.de
ntps1-0.uni-erlangen.de
ntps1-1.uni-erlangen.de
ntps1-2.uni-erlangen.de
ntps1-3.uni-erlangen.de
ntps1-0.cs.tu-berlin.de
ntps1-1.cs.tu-berlin.de
ptbtime1.ptb.de
ptbtime2.ptb.de
rustime01.rus.uni-stuttgart.de
rustime02.rus.uni-stuttgart.de

#Netherlands
ntp0.nl.net
ntp1.nl.net

#Sweden
sth1.ntp.se
sth2.ntp.se
mmo1.ntp.se
mmo2.ntp.se
svl1.ntp.se
svl2.ntp.se
gbg1.ntp.se
gbg2.ntp.se