Anatomy of the NTP packet#
Why#
The Chrony chapter explains how an offset is computed from four timestamps, but never what the packet carrying them actually looks like on the wire. Understanding this packet’s structure, a tiny 48 bytes in its simplest form, makes it possible to read a network capture, understand the mode this project uses (client-server) as opposed to the other modes NTP offers, and discover a protocol-level mechanism this guide hasn’t shown yet: the “Kiss-o’-Death”.
How: the header fields#
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Reference Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Origin Timestamp (64) [T1] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Receive Timestamp (64) [T2] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Transmit Timestamp (64) [T3] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Extensions (NTS, MAC...), variable length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+The fourth timestamp (T4, reception) never appears in the packet itself: it’s recorded locally by the recipient at the moment of reception, never transmitted over the wire.
| Field | Size | Role |
|---|---|---|
| LI (Leap Indicator) | 2 bits | Announces an imminent leap second (see dedicated section below) |
| VN (Version) | 3 bits | Protocol version, 4 today |
| Mode | 3 bits | The sender’s role in the exchange (see Modes section) |
| Stratum | 8 bits | Hierarchy level (Overview chapter), 0 has a special meaning, see Kiss-o’-Death |
| Poll | 8 bits | Polling interval, in log₂ seconds |
| Precision | 8 bits | Declared precision of the local clock, in log₂ seconds |
| Root Delay / Root Dispersion | 32 bits each | Cumulative delay and uncertainty up to the stratum 0 reference |
| Reference ID | 32 bits | Source identifier (ASCII string on stratum 0/1, e.g. PPS0; IP address of the upstream server on stratum ≥ 2) |
| Origin/Receive/Transmit Timestamps | 64 bits each | T1, T2, T3, see The four timestamps section below |
Reference ID: the catalog of stratum 1 sources#
On a stratum 1 server, the Reference ID is a 4-character ASCII string announcing the type of physical reference disciplining the clock. RFC 5905 (§ 7.3) assigns a conventional list; here are the main codes:
| Code | Physical source |
|---|---|
GPS | GPS constellation (United States), received via a satellite receiver |
GAL | Galileo constellation (Europe) |
PPS | Generic pulse-per-second, supplied by any calibrated external equipment |
DCF | DCF77 longwave transmitter (Mainflingen, Germany, 77.5 kHz), Europe’s historic radio clock, still received by millions of alarm clocks and station clocks |
MSF | MSF longwave transmitter (Anthorn, United Kingdom, 60 kHz) |
WWVB | WWVB longwave transmitter (Fort Collins, Colorado, 60 kHz) |
JJY | Japanese longwave transmitters (Fukushima 40 kHz, Saga 60 kHz) |
TDF | Allouis transmitter (France, 162 kHz), the former France Inter carrier, still broadcasting a time code driven by atomic clocks |
CHU / WWV / WWVH | HF time-signal radio stations (Ottawa, Colorado, Hawaii), the shortwave “spoken time” |
IRIG | IRIG wired signal (Inter-Range Instrumentation Group), time distribution in industrial and military environments |
NIST / ACTS / USNO / PTB | Telephone-modem time services of national institutes (the American NIST and USNO, the German PTB) |
GOES | GOES geostationary weather satellites (United States), a historic time service |
LORC | LORAN-C radionavigation stations (100 kHz), a network now largely shut down |
These codes tell the history of time references:
national longwave transmitters (DCF, MSF, WWVB, JJY, TDF) and
telephone-modem services (NIST, ACTS) predate GNSS, which has supplanted
almost all of them; PPS designates not a source per se but the
electrical alignment pulse that any source, whatever it is, feeds to the
machine.
The exact code remains declarative: the server’s configuration
chooses it, and nothing lets a client verify it. On this server, the
operator chose PPS0
(Reference ID : 50505330 (PPS0), i.e. the ASCII codes of P, P,
S, 0). From stratum 2 upward, the same field changes nature and
carries the IPv4 address of the upstream source (see Pitfalls to avoid).
The four timestamps and the offset computation#
The client-server exchange relies on four instants, measured alternately by the client’s clock and by the server’s:
Client Server
│ │
T1 ●─────────── request ─────────────► ● T2
│ │ (processing)
T4 ● ◄────────── response ─────────────● T3
│ │- T1 (Origin Timestamp): request departure, read on the client’s clock;
- T2 (Receive Timestamp): request arrival, read on the server’s clock;
- T3 (Transmit Timestamp): response departure, server’s clock;
- T4: response arrival, client’s clock. This fourth instant is never transmitted in the packet: each machine records it locally on reception (see Pitfalls to avoid).
Each difference mixes two unknowns: the real offset between the two
clocks and the network travel time. T2 - T1 contains the offset
plus the outbound trip; T3 - T4 contains the offset minus the
return trip. Averaging the two cancels the trips out, provided they are
equal:
offset = ((T2 - T1) + (T3 - T4)) / 2
delay = (T4 - T1) - (T3 - T2)The round-trip delay, for its part, subtracts from the total time seen
by the client (T4 - T1) the server’s processing time (T3 - T2),
which never crosses the network.
Worked example: a server 100 ms ahead of the client, 50 ms of travel in each direction, 20 ms of processing (in bold, the four actually measured values):
| Instant | Client clock | Server clock (+100 ms) |
|---|---|---|
| T1 (request departure) | 0 ms | 100 ms |
| T2 (server arrival) | 50 ms | 150 ms |
| T3 (response departure) | 70 ms | 170 ms |
| T4 (client return) | 120 ms | 220 ms |
offset = ((150 - 0) + (170 - 120)) / 2 = (150 + 50) / 2 = 100 ms (the real lead)
delay = (120 - 0) - (170 - 150) = 120 - 20 = 100 ms (2 × 50 ms of travel)The implicit assumption, a network symmetric in both directions, is the protocol’s fundamental limit: any asymmetry introduces a bias equal to half of it, undetectable from the four timestamps alone (Chrony chapter, Going further). The step-by-step walkthrough of this computation is also presented, with another example, in How does NTP work? (sookocheff.com), one of the founding reads of this chapter (see Sources and acknowledgements in the Overview chapter).
NTP modes#
The Mode field (3 bits) indicates the role played by the packet. This guide only uses one, but the protocol defines several:
| Mode | Usage | Used in this guide? |
|---|---|---|
| Client (3) / Server (4) | The client queries, the server replies, the mode used for every request to this Stratum 1 server and to its upstream sources | Yes |
| Symmetric active/passive (1/2) | Two servers query each other peer-to-peer, each able to discipline the other, typical between same-tier servers monitoring one another | No |
| Broadcast/Multicast (5) | A server broadcasts time without individual requests, open loop, less precise (no network delay computation possible without a two-way exchange) | No |
Client-server mode is the right choice for this project: a public server answering millions of anonymous clients has no business maintaining symmetric associations with each of them, and broadcast would sacrifice precision for a bandwidth gain that’s irrelevant here.
Kiss-o’-Death: rate control built into the protocol#
The nftables chapter shows how to rate-limit at the network level
(dropping packets past a threshold). NTP additionally has its own
retaliation mechanism, at the application level: when Stratum = 0, the
Reference ID field no longer holds a source identifier, but a 4-character
ASCII Kiss code, hence the name “Kiss-o’-Death” (KoD).
| Code | Meaning |
|---|---|
RATE | Polling frequency too high, the poll interval must be increased |
DENY / RSTR | Access denied by server policy |
INIT | The server isn’t synchronized yet |
This is a cooperative signal: a well-behaved client (like chrony) actually reduces its request rate upon receiving a
RATE. A malicious client can ignore it, which is why the network-level rate-limiting from the nftables chapter remains necessary as a complement: KoD regulates well-behaved clients, the firewall contains the ones that aren’t.
The Leap Indicator field#
The Leap seconds chapter explains the leap-seconds.list file that tells
chrony in advance that a leap second is scheduled. The 2-bit LI
field is the mechanism by which this information then gets relayed in
real time to every client, in every packet, during the hours leading up
to the event:
| LI value | Meaning |
|---|---|
00 | No warning |
01 | The last minute of the day will have 61 seconds |
10 | The last minute of the day will have 59 seconds |
11 | Clock unsynchronized (alarm) |
Pitfalls to avoid#
- Confusing the Reference ID of a stratum 1 with that of a higher
stratum. On this project (
Reference ID: 50505330 (PPS0), Chrony chapter), it’s a 4-character ASCII string. On a client querying this server, the Reference ID it sees in replies from its own upstream stratum ≥ 2 sources will instead be an IPv4 address, the same field changes meaning depending on the sender’s stratum. - Forgetting that T4 is never in the packet. A network capture of a single NTP packet only contains 3 of the 4 timestamps needed for the offset calculation, the fourth is local to each machine, never observable from outside.
Verify#
# Capture an NTP exchange to observe the actual fields
tcpdump -i any -n -vv udp port 123 -c 4
# With an NTP client able to print packet details
ntpdate -d pool.ntp.org 2>&1 | head -30📚 Going further
- RFC 5905 defines the complete NTPv4 packet structure, every field and its exact encoding.
- The Kiss-o’-Death code format is detailed in RFC 5905 §7.4.
- The historical Autokey authentication mechanism (RFC 5906), NTS’s predecessor, is mentioned in the Chrony chapter.