What are all the fields in a Python ntplib response, and how are they used?
Asked Answered
A

3

8

I'd like to make a measurement of the difference between a local clock, and a remote processor running an NTP server.

I can get the tx_time of the response, as shown below, but a better estimate would include some estimate of the network delays. There are other fields in the NTP response message which should be used, as well.

import ntplib
from time import ctime,time

addr_remote = '128.128.204.207'

c = ntplib.NTPClient()
remote = c.request(addr_remote)
local = time()
print("REMOTE: " + ctime(remote.tx_time) + "   <reference clock>   ") 
print("LOCAL:  " + ctime(local)        + "   delta: " + str(local - remote.tx_time ))

If I look at "remote":

for attr in dir(remote):
    print("remote.%s = %r" % (attr, getattr(remote, attr)))

I see:

remote.delay = 0.0
remote.dest_time = 1531863145.9309998
remote.dest_timestamp = 3740851945.9309998
remote.from_data = <bound method NTPPacket.from_data of <ntplib.NTPStats object at 0x000000000265B2E8>>
remote.leap = 0
remote.mode = 4
remote.offset = -1.8789582252502441
remote.orig_time = 1531863145.9309998
remote.orig_timestamp = 3740851945.9309998
remote.poll = 0
remote.precision = -7
remote.recv_time = 1531863144.0520415
remote.recv_timestamp = 3740851944.0520415
remote.ref_id = 0
remote.ref_time = 0.0
remote.ref_timestamp = 2208988800.0
remote.root_delay = 0.0
remote.root_dispersion = 0.0
remote.stratum = 9
remote.to_data = <bound method NTPPacket.to_data of <ntplib.NTPStats object at 0x000000000265B2E8>>
remote.tx_time = 1531863144.0520415
remote.tx_timestamp = 3740851944.0520415

So, how do I use these:

  • dest_time
  • orig_time
  • recv_time
  • tx_time

to remove the network delays, and get a better estimate of the clock difference?

Accrescent answered 17/7, 2018 at 21:37 Comment(0)
T
10

The NTP client sends a packet with its local time orig_time, which the NTP server receives at the server time recv_time. The server then replies at server time tx_time and the client receives that reply at local time dest_time.

The round trip delay is calculated as recv_time - orig_time + dest_time - tx_time, and the offset between the clocks is offset = (recv_time - orig_time + tx_time - dest_time) / 2.

Assuming the two NTP packets take consistent paths, the correct adjusted time is simply dest_time + offset, which is equivalent to tx_time + delay/2.

Twopence answered 25/4, 2019 at 0:55 Comment(0)
P
1

I found this answer while trying to syncing my client computer to NTP time and checking that it actually synced. Based on @Vic, this means that the client clock (compared to the server NTP clock) is always off by:

dest_time + offset = tx_time + delay/2
dest_time - tx_time = delay/2 - offset

i.e.

correction = delay/2 - offset

As @Vic said, packets may take different routes going to and coming from, but in average correction should give you your clock offset (I think). Even by syncing with time.nist.gov with os.system('w32tm /resync/nowait'), my computer is always off by 40ms, somehow. Comments welcome!

This is my code.

import ntplib
from datetime import datetime, timezone
def get_ntp_time():
    ntp_pool = ['pool.ntp.org', 'time.nist.gov']
    def call_ntp(serverAddress):
        call = ntplib.NTPClient()
        return call.request(server, version=3)
    for server in ntp_pool:
        response = call_ntp(server)
        print(f"server: {server}")
        print(f"request packet sent (as LOCAL client time, orig_time): {datetime.fromtimestamp(response.orig_time, timezone.utc)}")
        print(f"request packet received (as NTP server time, recv_time): {datetime.fromtimestamp(response.recv_time, timezone.utc)}")
        print(f"response packet sent (as NTP server time, tx_time): {datetime.fromtimestamp(response.tx_time, timezone.utc)}")
        print(f"response packet received (as LOCAL client time, dest_time): {datetime.fromtimestamp(response.dest_time, timezone.utc)}")
        print(f'round trip duration: {response.delay} s')
        print(f'* adjusted time, tx_time + delay/2: {datetime.fromtimestamp(response.tx_time + response.delay/2, timezone.utc)}')
        print(f'* adjusted time, dest_time + offset: {datetime.fromtimestamp(response.dest_time + response.offset, timezone.utc)}')
        print(f'correction to client: {response.delay/2 - response.offset} s\n')
        # for attr in dir(response):
            # if not attr .startswith('_'):
                # print("response.%s = %r" % (attr, getattr(response, attr)))
        print('-')
get_ntp_time()

Response:

server: pool.ntp.org
request packet sent (as LOCAL client time, orig_time): 2021-04-23 16:14:46.544797+00:00
request packet received (as NTP server time, recv_time): 2021-04-23 16:14:46.535852+00:00
response packet sent (as NTP server time, tx_time): 2021-04-23 16:14:46.535862+00:00
response packet received (as LOCAL client time, dest_time): 2021-04-23 16:14:46.579710+00:00
round trip duration: 0.03490257263183594 s
* adjusted time, tx_time + delay/2: 2021-04-23 16:14:46.553314+00:00
* adjusted time, dest_time + offset: 2021-04-23 16:14:46.553314+00:00
correction to client: 0.04384756088256836 s

-
server: time.nist.gov
request packet sent (as LOCAL client time, orig_time): 2021-04-23 16:14:46.642192+00:00
request packet received (as NTP server time, recv_time): 2021-04-23 16:14:46.641157+00:00
response packet sent (as NTP server time, tx_time): 2021-04-23 16:14:46.641158+00:00
response packet received (as LOCAL client time, dest_time): 2021-04-23 16:14:46.689054+00:00
round trip duration: 0.04686117172241211 s
* adjusted time, tx_time + delay/2: 2021-04-23 16:14:46.664588+00:00
* adjusted time, dest_time + offset: 2021-04-23 16:14:46.664588+00:00
correction to client: 0.0478968620300293 s

-
Peloquin answered 23/4, 2021 at 16:9 Comment(0)
M
1

I found that the previous answers are a little confusing about dealy or offset(the half of delay) computing. I'd like to point out this.

In original, ntplib.py source, dest_timestamp(receiving time), orig_timestamp(sending time) are local(client) time. tx_timestamp(sending time from server) and recv_timestamp(receiving time by server) are server time. so the first term in the following code means the packet sending & receiving difference and the second term means prcoessing time in server.

Anyway, adjusted time could be computed like adjusted_time = response.tx_time + response.delay*0.5

@property
def delay(self):
    """round-trip delay"""
    return ((self.dest_timestamp - self.orig_timestamp) -
            (self.tx_timestamp - self.recv_timestamp))
Mccaffrey answered 29/9, 2021 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.