3. Network Time Protocol
This section consists of a formal definition of the Network Time Protocol, including its data formats, entities, state variables, events and event-processing procedures. The specification is based on the implementation model illustrated in Figure 1, but it is not intended that this model is the only one upon which a specification can be based. In particular, the specification is intended to illustrate and clarify the intrinsic operations of NTP, as well as to serve as a foundation for a more rigorous, comprehensive and verifiable specification.
3.1. Data Formats
All mathematical operations expressed or implied herein are in two's-complement, fixed-point arithmetic. Data are specified as integer or fixed-point quantities, with bits numbered in big-endian fashion from zero starting at the left, or high-order, position. Since various implementations may scale externally derived quantities for internal use, neither the precision nor decimal-point placement for fixed-point quantities is specified. Unless specified otherwise, all quantities are unsigned and may occupy the full field width with an implied zero preceding bit zero. Hardware and software packages designed to work with signed quantities will thus yield surprising results when the most significant (sign) bit is set. It is suggested that externally derived, unsigned fixed-point quantities such as timestamps be shifted right one bit for internal use, since the precision represented by the full field width is seldom justified.
Since NTP timestamps are cherished data and, in fact, represent the main product of the protocol, a special timestamp format has been established. NTP timestamps are represented as a 64-bit unsigned fixed-point number, in seconds relative to 0h on 1 January 1900. The integer part is in the first 32 bits and the fraction part in the last 32 bits. This format allows convenient multiple-precision arithmetic and conversion to Time Protocol representation (seconds), but does complicate the conversion to ICMP Timestamp message representation (milliseconds). The precision of this representation is about 200 picoseconds, which should be adequate for even the most exotic requirements.
Timestamps are determined by copying the current value of the local clock to a timestamp when some significant event, such as the arrival of a message, occurs. In order to maintain the highest accuracy, it is important that this be done as close to the hardware or software driver associated with the event as possible. In particular, departure timestamps should be redetermined for each link-level retransmission. In some cases a particular timestamp may not be available, such as when the host is rebooted or the protocol first starts up. In these cases the 64-bit field is set to zero, indicating the value is invalid or undefined.
Note that since some time in 1968 the most significant bit (bit 0 of the integer part) has been set and that the 64-bit field will overflow some time in 2036. Should NTP be in use in 2036, some external means will be necessary to qualify time relative to 1900 and time relative to 2036 (and other multiples of 136 years). Timestamped data requiring such qualification will be so precious that appropriate means should be readily available. There will exist an 200-picosecond interval, henceforth ignored, every 136 years when the 64-bit field will be zero and thus considered invalid.
3.2. State Variables and Parameters
Following is a summary of the various state variables and parameters used by the protocol. They are separated into classes of system variables, which relate to the operating system environment and local-clock mechanism; peer variables, which represent the state of the protocol machine specific to each peer; packet variables, which represent the contents of the NTP message; and parameters, which represent fixed configuration constants for all implementations of the current version. For each class the description of the variable is followed by its name and the procedure or value which controls it. Note that variables are in lower case, while parameters are in upper case. Additional details on formats and use are presented in later sections and Appendices.
3.2.1. Common Variables
The following variables are common to two or more of the system, peer and packet classes. Additional variables are specific to the optional authentication mechanism as described in Appendix C. When necessary to distinguish between common variables of the same name, the variable identifier will be used.
Peer Address (peer.peeraddr, pkt.peeraddr), Peer Port (peer.peerport, pkt.peerport): These are the 32-bit Internet address and 16-bit port number of the peer.
Host Address (peer.hostaddr, pkt.hostaddr), Host Port (peer.hostport, pkt.hostport): These are the 32-bit Internet address and 16-bit port number of the host. They are included among the state variables to support multi-homing.
Leap Indicator (sys.leap, peer.leap, pkt.leap): This is a two-bit code warning of an impending leap second to be inserted in the NTP timescale. The bits are set before 23:59 on the day of insertion and reset after 00:00 on the following day. This causes the number of seconds (rollover interval) in the day of insertion to be increased or decreased by one. In the case of primary servers the bits are set by operator intervention, while in the case of secondary servers the bits are set by the protocol. The two bits, bit 0 and bit 1, respectively, are coded as follows:
| Value | Meaning |
|---|---|
| 00 | no warning |
| 01 | last minute has 61 seconds |
| 10 | last minute has 59 seconds |
| 11 | alarm condition (clock not synchronized) |
In all except the alarm condition (11₂), NTP itself does nothing with these bits, except pass them on to the time-conversion routines that are not part of NTP. The alarm condition occurs when, for whatever reason, the local clock is not synchronized, such as when first coming up or after an extended period when no primary reference source is available.
Mode (peer.mode, pkt.mode): This is an integer indicating the association mode, with values coded as follows:
| Value | Mode |
|---|---|
| 0 | unspecified |
| 1 | symmetric active |
| 2 | symmetric passive |
| 3 | client |
| 4 | server |
| 5 | broadcast |
| 6 | reserved for NTP control messages |
| 7 | reserved for private use |
Stratum (sys.stratum, peer.stratum, pkt.stratum): This is an integer indicating the stratum of the local clock, with values defined as follows:
| Value | Meaning |
|---|---|
| 0 | unspecified |
| 1 | primary reference (e.g., calibrated atomic clock, radio clock) |
| 2-255 | secondary reference (via NTP) |
For comparison purposes a value of zero is considered greater than any other value. Note that the maximum value of the integer encoded as a packet variable is limited by the parameter NTP.MAXSTRATUM.
Poll Interval (sys.poll, peer.hostpoll, peer.peerpoll, pkt.poll): This is a signed integer indicating the minimum interval between transmitted messages, in seconds as a power of two. For instance, a value of six indicates a minimum interval of 64 seconds.
Precision (sys.precision, peer.precision, pkt.precision): This is a signed integer indicating the precision of the various clocks, in seconds to the nearest power of two. The value must be rounded to the next larger power of two; for instance, a 50-Hz (20 ms) or 60-Hz (16.67 ms) power-frequency clock would be assigned the value -5 (31.25 ms), while a 1000-Hz (1 ms) crystal-controlled clock would be assigned the value -9 (1.95 ms).
Root Delay (sys.rootdelay, peer.rootdelay, pkt.rootdelay): This is a signed fixed-point number indicating the total roundtrip delay to the primary reference source at the root of the synchronization subnet, in seconds. Note that this variable can take on both positive and negative values, depending on clock precision and skew.
Root Dispersion (sys.rootdispersion, peer.rootdispersion, pkt.rootdispersion): This is a signed fixed-point number indicating the maximum error relative to the primary reference source at the root of the synchronization subnet, in seconds. Only positive values greater than zero are possible.
Reference Clock Identifier (sys.refid, peer.refid, pkt.refid): This is a 32-bit code identifying the particular reference clock. In the case of stratum 0 (unspecified) or stratum 1 (primary reference source), this is a four-octet, left-justified, zero-padded ASCII string. For example:
| Stratum | Code | Meaning |
|---|---|---|
| 0 | DCN | DCN routing protocol |
| 0 | TSP | TSP time protocol |
| 1 | ATOM | Atomic clock (calibrated) |
| 1 | WWVB | WWVB LF (band 5) radio |
| 1 | GOES | GOES UHF (band 9) satellite |
| 1 | WWV | WWV HF (band 7) radio |
In the case of stratum 2 and greater (secondary reference) this is the four-octet Internet address of the peer selected for synchronization.
Reference Timestamp (sys.reftime, peer.reftime, pkt.reftime): This is the local time, in timestamp format, when the local clock was last updated. If the local clock has never been synchronized, the value is zero.
Originate Timestamp (peer.org, pkt.org): This is the local time, in timestamp format, at the peer when its latest NTP message was sent. If the peer becomes unreachable the value is set to zero.
Receive Timestamp (peer.rec, pkt.rec): This is the local time, in timestamp format, when the latest NTP message from the peer arrived. If the peer becomes unreachable the value is set to zero.
Transmit Timestamp (peer.xmt, pkt.xmt): This is the local time, in timestamp format, at which the NTP message departed the sender.
3.2.2. System Variables
Table 1 shows the complete set of system variables. In addition to the common variables described previously, the following variables are used by the operating system in order to synchronize the local clock.
Local Clock (sys.clock): This is the current local time, in timestamp format. Local time is derived from the hardware clock of the particular machine and increments at intervals depending on the design used. An appropriate design, including slewing and skew-compensation mechanisms, is described in Section 5.
Clock Source (sys.peer): This is a selector identifying the current synchronization source. Usually this will be a pointer to a structure containing the peer variables. The special value NULL indicates there is no currently valid synchronization source.
3.2.3. Peer Variables
Table 2 shows the complete set of peer variables. In addition to the common variables described previously, the following variables are used by the peer management and measurement functions.
Configured Bit (peer.config): This is a bit indicating that the association was created from configuration information and should not be demobilized if the peer becomes unreachable.
Update Timestamp (peer.update): This is the local time, in timestamp format, when the most recent NTP message was received. It is used in calculating the skew dispersion.
Reachability Register (peer.reach): This is a shift register of NTP.WINDOW bits used to determine the reachability status of the peer, with bits entering from the least significant (rightmost) end. A peer is considered reachable if at least one bit in this register is set to one.
Peer Timer (peer.timer): This is an integer counter used to control the interval between transmitted NTP messages. Once set to a nonzero value, the counter decrements at one-second intervals until reaching zero, at which time the transmit procedure is called. Note that the operation of this timer is independent of local-clock updates, which implies that the timekeeping system and interval-timer system architecture must be independent of each other.
3.2.4. Packet Variables
Table 3 shows the complete set of packet variables. In addition to the common variables described previously, the following variables are defined.
Version Number (pkt.version): This is an integer indicating the version number of the sender. NTP messages will always be sent with the current version number NTP.VERSION and will always be accepted if the version number matches NTP.VERSION. Exceptions may be advised on a case-by-case basis at times when the version number is changed. Specific guidelines for interoperation between this version and previous versions of NTP are summarized in Appendix D.
3.2.5. Clock-Filter Variables
When the filter and selection algorithms suggested in Section 4 are used, the following state variables are defined in addition to the variables described previously.
Filter Register (peer.filter): This is a shift register of NTP.SHIFT stages, where each stage stores a 3-tuple consisting of the measured delay, measured offset and calculated dispersion associated with a single observation. These 3-tuples enter from the most significant (leftmost) end and are shifted towards the least significant (rightmost) end and eventually discarded as new observations arrive.
Valid Data Counter (peer.valid): This is an integer counter indicating the valid samples remaining in the filter register. It is used to determine the reachability state and when the poll interval should be increased or decreased.
Offset (peer.offset): This is a signed, fixed-point number indicating the offset of the peer clock relative to the local clock, in seconds.
Delay (peer.delay): This is a signed fixed-point number indicating the roundtrip delay of the peer clock relative to the local clock over the network path between them, in seconds. Note that this variable can take on both positive and negative values, depending on clock precision and skew-error accumulation.
Dispersion (peer.dispersion): This is a signed fixed-point number indicating the maximum error of the peer clock relative to the local clock over the network path between them, in seconds. Only positive values greater than zero are possible.
3.2.6. Authentication Variables
When the authentication mechanism suggested in Appendix C is used, the following state variables are defined in addition to the variables described previously. These variables are used only if the optional authentication mechanism described in Appendix C is implemented.
Authentication Enabled Bit (peer.authenable): This is a bit indicating that the association is to operate in the authenticated mode.
Authenticated Bit (peer.authentic): This is a bit indicating that the last message received from the peer has been correctly authenticated.
Key Identifier (peer.hostkeyid, peer.peerkeyid, pkt.keyid): This is an integer identifying the cryptographic key used to generate the message-authentication code.
Cryptographic Keys (sys.key): This is a set of 64-bit DES keys. Each key is constructed as in the Berkeley Unix distributions, which consists of eight octets, where the seven low-order bits of each octet correspond to the DES bits 1-7 and the high-order bit corresponds to the DES odd-parity bit 8.
Crypto-Checksum (pkt.check): This is a crypto-checksum computed by the encryption procedure.
3.2.7. Parameters
Table 4 shows the parameters assumed for all implementations operating in the Internet system. It is necessary to agree on the values for these parameters in order to avoid unnecessary network overheads and stable peer associations. The following parameters are assumed fixed and applicable to all associations.
Version Number (NTP.VERSION): This is the current NTP version number (3).
NTP Port (NTP.PORT): This is the port number (123) assigned by the Internet Assigned Numbers Authority to NTP.
Maximum Stratum (NTP.MAXSTRATUM): This is the maximum stratum value that can be encoded as a packet variable, also interpreted as "infinity" or unreachable by the subnet routing algorithm.
Maximum Clock Age (NTP.MAXAGE): This is the maximum interval a reference clock will be considered valid after its last update, in seconds.
Maximum Skew (NTP.MAXSKEW): This is the maximum offset error due to skew of the local clock over the interval determined by NTP.MAXAGE, in seconds. The ratio φ = NTP.MAXSKEW / NTP.MAXAGE is interpreted as the maximum possible skew rate due to all causes.
Maximum Distance (NTP.MAXDISTANCE): When the selection algorithm suggested in Section 4 is used, this is the maximum synchronization distance for peers acceptable for synchronization.
Minimum Poll Interval (NTP.MINPOLL): This is the minimum poll interval allowed by any peer of the Internet system, in seconds to a power of two.
Maximum Poll Interval (NTP.MAXPOLL): This is the maximum poll interval allowed by any peer of the Internet system, in seconds to a power of two.
Minimum Select Clocks (NTP.MINCLOCK): When the selection algorithm suggested in Section 4 is used, this is the minimum number of peers acceptable for synchronization.
Maximum Select Clocks (NTP.MAXCLOCK): When the selection algorithm suggested in Section 4 is used, this is the maximum number of peers considered for selection.
Minimum Dispersion (NTP.MINDISPERSE): When the filter algorithm suggested in Section 4 is used, this is the minimum dispersion increment for each stratum level, in seconds.
Maximum Dispersion (NTP.MAXDISPERSE): When the filter algorithm suggested in Section 4 is used, this is the maximum peer dispersion and the dispersion assumed for missing data, in seconds.
Reachability Register Size (NTP.WINDOW): This is the size of the reachability register (peer.reach), in bits.
Filter Size (NTP.SHIFT): When the filter algorithm suggested in Section 4 is used, this is the size of the clock filter (peer.filter) shift register, in stages.
Filter Weight (NTP.FILTER): When the filter algorithm suggested in Section 4 is used, this is the weight used to compute the filter dispersion.
Select Weight (NTP.SELECT): When the selection algorithm suggested in Section 4 is used, this is the weight used to compute the select dispersion.
3.3. Modes of Operation
Except in broadcast mode, an NTP association is formed when two peers exchange messages and one or both of them create and maintain an instantiation of the protocol machine, called an association. The association can operate in one of five modes as indicated by the host-mode variable (peer.mode): symmetric active, symmetric passive, client, server and broadcast, which are defined as follows:
Symmetric Active (1): A host operating in this mode sends periodic messages regardless of the reachability state or stratum of its peer. By operating in this mode the host announces its willingness to synchronize and be synchronized by the peer.
Symmetric Passive (2): This type of association is ordinarily created upon arrival of a message from a peer operating in the symmetric active mode and persists only as long as the peer is reachable and operating at a stratum level less than or equal to the host; otherwise, the association is dissolved. However, the association will always persist until at least one message has been sent in reply. By operating in this mode the host announces its willingness to synchronize and be synchronized by the peer.
Client (3): A host operating in this mode sends periodic messages regardless of the reachability state or stratum of its peer. By operating in this mode the host, usually a LAN workstation, announces its willingness to be synchronized by, but not to synchronize the peer.
Server (4): This type of association is ordinarily created upon arrival of a client request message and exists only in order to reply to that request, after which the association is dissolved. By operating in this mode the host, usually a LAN time server, announces its willingness to synchronize, but not to be synchronized by the peer.
Broadcast (5): A host operating in this mode sends periodic messages regardless of the reachability state or stratum of the peers. By operating in this mode the host, usually a LAN time server operating on a high-speed broadcast medium, announces its willingness to synchronize all of the peers, but not to be synchronized by any of them.
A host operating in client mode occasionally sends an NTP message to a host operating in server mode, perhaps right after rebooting and at periodic intervals thereafter. The server responds by simply interchanging addresses and ports, filling in the required information and returning the message to the client. Servers need retain no state information between client requests, while clients are free to manage the intervals between sending NTP messages to suit local conditions. In these modes the protocol machine described in this document can be considerably simplified to a simple remote-procedure-call mechanism without significant loss of accuracy or robustness, especially when operating over high-speed LANs.
In the symmetric modes the client/server distinction (almost) disappears. Symmetric passive mode is intended for use by time servers operating near the root nodes (lowest stratum) of the synchronization subnet and with a relatively large number of peers on an intermittent basis. In this mode the identity of the peer need not be known in advance, since the association with its state variables is created only when an NTP message arrives. Furthermore, the state storage can be reused when the peer becomes unreachable or is operating at a higher stratum level and thus ineligible as a synchronization source.
Symmetric active mode is intended for use by time servers operating near the end nodes (highest stratum) of the synchronization subnet. Reliable time service can usually be maintained with two peers at the next lower stratum level and one peer at the same stratum level, so the rate of ongoing polls is usually not significant, even when connectivity is lost and error messages are being returned for every poll.
Normally, one peer operates in an active mode (symmetric active, client or broadcast modes) as configured by a startup file, while the other operates in a passive mode (symmetric passive or server modes), often without prior configuration. However, both peers can be configured to operate in the symmetric active mode. An error condition results when both peers operate in the same mode, but not symmetric active mode. In such cases each peer will ignore messages from the other, so that prior associations, if any, will be demobilized due to reachability failure.
Broadcast mode is intended for operation on high-speed LANs with numerous workstations and where the highest accuracies are not required. In the typical scenario one or more time servers on the LAN send periodic broadcasts to the workstations, which then determine the time on the basis of a preconfigured latency in the order of a few milliseconds. As in the client/server modes the protocol machine can be considerably simplified in this mode; however, a modified form of the clock selection algorithm may prove useful in cases where multiple time servers are used for enhanced reliability.
3.4. Event Processing
The significant events of interest in NTP occur upon expiration of a peer timer (peer.timer), one of which is dedicated to each peer with an active association, and upon arrival of an NTP message from the various peers. An event can also occur as the result of an operator command or detected system fault, such as a primary reference source failure. This section describes the procedures invoked when these events occur.
3.4.1. Notation Conventions
The NTP filtering and selection algorithms act upon a set of variables for clock offset (θ, THETA), roundtrip delay (δ, DELTA) and dispersion (ε, EPSILON). When necessary to distinguish between them, lower-case Greek letters are used for variables relative to a peer, while upper-case Greek letters are used for variables relative to the primary reference source(s), i.e., via the peer to the root of the synchronization subnet. Subscripts will be used to identify the particular peer when this is not clear from context. The algorithms are based on a quantity called the synchronization distance (λ, LAMBDA), which is computed from the roundtrip delay and dispersion as described below.
As described in Appendix H, the peer dispersion ε includes contributions due to measurement error ρ = 1 << sys.precision, skew-error accumulation φτ, where φ = NTP.MAXSKEW / NTP.MAXAGE is the maximum skew rate and τ = sys.clock - peer.update is the interval since the last update, and filter (sample) dispersion ε_σ computed by the clock-filter algorithm. The root dispersion EPSILON includes contributions due to the selected peer dispersion ε and skew-error accumulation φτ, together with the root dispersion for the peer itself. The system dispersion includes the select (sample) dispersion ε_ξ computed by the clock-select algorithm and the absolute initial clock offset |THETA| provided to the local-clock algorithm. Both ε and EPSILON are dynamic quantities, since they depend on the elapsed time τ since the last update, as well as the sample dispersions calculated by the algorithms.
Each time the relevant peer variables are updated, all dispersions associated with that peer are updated to reflect the skew-error accumulation. The computations can be summarized as follows:
- θ = peer.offset
- δ = peer.delay
- ε = peer.dispersion = ρ + φτ + ε_σ
- λ = ε + |δ| / 2
where τ is the interval since the original timestamp (from which θ and δ were determined) was transmitted to the present time and ε_σ is the filter dispersion (see clock-filter procedure below). The variables relative to the root of the synchronization subnet via peer i are determined as follows:
- THETA_i = θ_i
- DELTA_i = peer.rootdelay + δ_i
- EPSILON_i = peer.rootdispersion + ε_i + φτ_i
- LAMBDA_i = EPSILON_i + |DELTA_i| / 2
where all variables are understood to pertain to the ith peer. Finally, assuming the ith peer is selected for synchronization, the system variables are determined as follows:
- THETA = combined final offset
- DELTA = DELTA_i
- EPSILON = EPSILON_i + ε_ξ + |THETA|
- LAMBDA = LAMBDA_i
where ε_ξ is the select dispersion (see clock-selection procedure below).
Informal pseudo-code which accomplishes these computations is presented below. Note that the pseudo-code is represented in no particular language, although it has many similarities to the C language. Specific details on the important algorithms are further illustrated in the C-language routines in Appendix I.
3.4.2. Transmit Procedure
The transmit procedure is executed when the peer timer decrements to zero for all modes except client mode with a broadcast server and server mode in all cases. In client mode with a broadcast server messages are never sent. In server mode messages are sent only in response to received messages. This procedure is also called by the receive procedure when an NTP message arrives that does not result in a persistent association.
begin transmit procedure
pkt.peeraddr <- peer.hostaddr; /* copy system and peer variables */
pkt.peerport <- peer.hostport;
pkt.hostaddr <- peer.peeraddr;
pkt.hostport <- peer.peerport;
pkt.leap <- sys.leap;
pkt.version <- NTP.VERSION;
pkt.mode <- peer.mode;
pkt.stratum <- sys.stratum;
pkt.poll <- peer.hostpoll;
pkt.precision <- sys.precision;
pkt.rootdelay <- sys.rootdelay;
if (sys.leap = 11₂ or (sys.clock - sys.reftime) > NTP.MAXAGE)
skew <- NTP.MAXSKEW;
else
skew <- φ(sys.clock - sys.reftime);
pkt.rootdispersion <- sys.rootdispersion + (1 << sys.precision) + skew;
pkt.refid <- sys.refid;
pkt.reftime <- sys.reftime;
pkt.org <- peer.org; /* copy timestamps */
pkt.rec <- peer.rec;
pkt.xmt <- sys.clock;
peer.xmt <- pkt.xmt;
#ifdef (authentication implemented) /* see Appendix C */
call encrypt;
#endef
send packet;
peer.reach <- peer.reach << 1; /* update reachability */
if (peer.reach = 0 and peer.config = 0)
begin
demobilize association;
exit;
endif
if (peer.reach & 6 != 0) /* test two low-order bits */
if (peer.valid < NTP.SHIFT) /* valid data received */
peer.valid <- peer.valid + 1;
else peer.hostpoll <- peer.hostpoll + 1;
else begin
peer.valid <- peer.valid - 1; /* nothing heard */
peer.hostpoll <- peer.hostpoll - 1;
call clock-filter(0, 0, NTP.MAXDISPERSE);
call clock-select; /* select clock source */
endif
call poll-update;
end transmit procedure;
3.4.3. Receive Procedure
The receive procedure is executed upon arrival of an NTP message. It validates the message, interprets the various modes and calls other procedures to filter the data and select the synchronization source. If the version number in the packet does not match the current version, the message may be discarded; however, exceptions may be advised on a case-by-case basis at times when the version is changed. If the NTP control messages described in Appendix B are implemented and the packet mode is 6 (control), the control-message procedure is called. The source and destination Internet addresses and ports in the IP and UDP headers are matched to the correct peer. If there is no match a new instantiation of the protocol machine is created and the association mobilized.
begin receive procedure
if (pkt.version != NTP.VERSION) exit;
#ifdef (control messages implemented)
if (pkt.mode = 6) call control-message;
#endef
for (all associations) /* access control goes here */
match addresses and ports to associations;
if (no matching association)
call receive-instantiation procedure; /* create association */
#ifdef (authentication implemented) /* see Appendix C */
call decrypt;
#endef
if (pkt.mode = 0) /* for compatibility */
mode <- (see Section 3.3);
else
mode <- pkt.mode;
case (mode, peer.hostmode) /* see Table 5 */
error: if (peer.config = 0) demobilize association;
break;
recv: call packet; /* process packet */
if (valid header) begin
peer.reach <- peer.reach | 1;
if (valid data) call clock-update;
endif
else
if (peer.config = 0) demobilize association;
break;
xmit: call packet; /* process packet */
peer.hostpoll <- peer.peerpoll;
call poll-update;
call transmit;
if (peer.config = 0) demobilize association;
break;
pkt: call packet; /* process packet */
if (valid header) begin
peer.reach <- peer.reach | 1;
if (valid data) call clock-update;
endif
else if (peer.config = 0) begin
peer.hostpoll <- peer.peerpoll;
call poll-update;
call transmit;
demobilize association;
endif
endcase
end receive procedure;
3.4.4. Packet Procedure
The packet procedure checks the message validity, computes delay/offset samples and calls other procedures to filter the data and select the synchronization source. Test 1 requires the transmit timestamp not match the last one received from the same peer; otherwise, the message might be an old duplicate. Test 2 requires the originate timestamp match the last one sent to the same peer; otherwise, the message might be out of order, bogus or worse. In case of broadcast mode (5) the apparent roundtrip delay will be zero and the full accuracy of the time-transfer operation may not be achievable. However, the accuracy achieved may be adequate for most purposes. The poll-update procedure is called with argument peer.hostpoll (peer.peerpoll may have changed).
begin packet procedure
peer.rec <- sys.clock; /* capture receive timestamp */
if (pkt.mode != 5) begin
test1 <- (pkt.xmt != peer.org); /* test 1 */
test2 <- (pkt.org = peer.xmt); /* test 2 */
endif
else begin
pkt.org <- peer.rec; /* fudge missing timestamps */
pkt.rec <- pkt.xmt;
test1 <- true; /* fake tests */
test2 <- true;
endif
peer.org <- pkt.xmt; /* update originate timestamp */
peer.peerpoll <- pkt.poll; /* adjust poll interval */
call poll-update(peer.hostpoll);
test3 <- (pkt.org != 0 and pkt.rec != 0); /* test 3 */
δ <- (T_i - T_(i-3)) - (T_(i-1) - T_(i-2))
θ <- ((T_(i-2) - T_(i-3)) + (T_(i-1) - T_i)) / 2
ε <- (1 << sys.precision) + φ(T_i - T_(i-3))
test4 <- (|δ| < NTP.MAXDISPERSE and ε < NTP.MAXDISPERSE); /* test 4 */
#ifdef (authentication implemented) /* test 5 */
test5 <- ((peer.config = 1 and peer.authenable = 0) or peer.authentic = 1);
#endef
test6 <- (pkt.leap != 11₂ and /* test 6 */
pkt.reftime <= pkt.xmt < pkt.reftime + NTP.MAXAGE)
test7 <- (pkt.stratum <= sys.stratum and /* test 7 */
pkt.stratum < NTP.MAXSTRATUM);
test8 <- (|pkt.rootdelay| < NTP.MAXDISPERSE and /* test 8 */
pkt.rootdispersion < NTP.MAXDISPERSE);
if (not valid header) exit;
peer.leap <- pkt.leap; /* copy packet variables */
peer.stratum <- pkt.stratum;
peer.precision <- pkt.precision;
peer.rootdelay <- pkt.rootdelay;
peer.rootdispersion <- pkt.rootdispersion;
peer.refid <- pkt.refid;
peer.reftime <- pkt.reftime;
if (valid data) call clock-filter(θ, δ, ε); /* process sample */
end packet procedure;
3.4.5. Clock-Update Procedure
The clock-update procedure is called from the receive procedure when valid clock offset, delay and dispersion data have been determined by the clock-filter procedure for the current peer. The result of the clock-selection and clock-combining procedures is the final clock correction THETA, which is used by the local-clock procedure to update the local clock. If no candidates survive these procedures, the clock-update procedure exits without doing anything further.
begin clock-update procedure
call clock-select; /* select clock source */
if (sys.peer != peer) exit;
LAMBDA <- distance(peer); /* update system variables */
if (LAMBDA >= NTP.MAXDISTANCE) exit;
sys.leap <- peer.leap;
sys.stratum <- peer.stratum + 1;
sys.refid <- peer.peeraddr;
call local-clock;
if (local clock reset) begin /* if reset, clear state variables */
sys.leap <- 11₂;
for (all peers) call clear;
endif
else begin
sys.peer <- peer; /* if not, adjust local clock */
sys.rootdelay <- DELTA;
sys.rootdispersion <- EPSILON + max(ε_ξ + |THETA|, NTP.MINDISPERSE);
endif
sys.reftime <- sys.clock;
end clock-update procedure;
In some system configurations a precise source of timing information is available in the form of a train of timing pulses spaced at one-second intervals. Usually, this is in addition to a source of timecode information, such as a radio clock or even NTP itself, to number the seconds, minutes, hours and days. In these configurations the system variables are set to refer to the source from which the pulses are derived. For those configurations which support a primary reference source, such as a radio clock or calibrated atomic clock, the stratum is set at one as long as this is the actual synchronization source and whether or not the primary-clock procedure is used.
Specification of the clock-selection and local-clock algorithms is not an integral part of the NTP specification, since there may be other algorithms which provide equivalent performance. However, a clock-selection algorithm found to work well in the Internet environment is described in Section 4, while a local-clock algorithm is described in Section 5 and their use is recommended. The clock-selection algorithm described in Section 4 usually picks the peer at the lowest stratum and minimum synchronization distance among all those available, unless that peer appears to be a falseticker. The result is that the algorithms all work to build a minimum-weight spanning tree relative to the primary reference time servers and thus a hierarchical-master-slave synchronization subnet.
3.4.6. Primary-Clock Procedure
When a primary reference source such as a radio clock is connected to the host, it is convenient to incorporate its information into the data base as if the clock were represented as an ordinary peer. In the primary-clock procedure the clock is polled once a minute or so and the returned timecode used to produce a new update for the local clock. When peer.timer decrements to zero for a primary clock peer, the transmit procedure is not called; rather, the radio clock is polled, usually using an ASCII string specified for this purpose. When a valid timecode is received from the radio clock, it is converted to NTP timestamp format and the peer variables updated. The value of peer.leap is set depending on the status of the leap-warning bit in the timecode, if available, or manually by the operator. The value for peer.peeraddr, which will become the value of sys.refid when the clock-update procedure is called, is set to an ASCII string describing the clock type (see Appendix A).
begin primary-clock-update procedure
peer.leap <- "from" radio or operator; /* copy variables */
peer.peeraddr <- ASCII identifier;
peer.rec <- radio timestamp;
peer.reach <- 1;
call clock-filter(sys.clock - peer.rec, 0, 1 << peer.precision);
call clock-update; /* update local clock */
end primary-clock-update procedure;
3.4.7. Initialization Procedures
The initialization procedures are used to set up and initialize the system, its peers and associations.
3.4.7.1. Initialization Procedure
The initialization procedure is called upon reboot or restart of the NTP daemon. The local clock is presumably undefined at reboot; however, in some equipment an estimate is available from the reboot environment, such as a battery-backed clock/calendar. The precision variable is determined by the intrinsic architecture of the local hardware clock. The authentication variables are used only if the authentication mechanism described in Appendix C is implemented. The values of these variables are determined using procedures beyond the scope of NTP itself.
begin initialization procedure
#ifdef (authentication implemented) /* see Appendix C */
sys.keys <- as required;
#endef;
sys.leap <- 11₂; /* copy variables */
sys.stratum <- 0 (undefined);
sys.precision <- host precision;
sys.rootdelay <- 0 (undefined);
sys.rootdispersion <- 0 (undefined);
sys.refid <- 0 (undefined);
sys.reftime <- 0 (undefined);
sys.clock <- external reference;
sys.peer <- NULL;
sys.poll <- NTP.MINPOLL;
for (all configured peers) /* create configured associations */
call initialization-instantiation procedure;
end initialization procedure;
3.4.7.2. Initialization-Instantiation Procedure
This implementation-specific procedure is called from the initialization procedure to define an association. The addresses and modes of the peers are determined using information read during the reboot procedure or as the result of operator commands. The authentication variables are used only if the authentication mechanism described in Appendix C is implemented. The values of these variables are determined using procedures beyond the scope of NTP itself. With the authentication bits set as suggested, only properly authenticated peers can become the synchronization source.
begin initialization-instantiation procedure
peer.config <- 1;
#ifdef (authentication implemented) /* see Appendix C */
peer.authenable <- 1 (suggested);
peer.authentic <- 0;
peer.hostkeyid <- as required;
peer.peerkeyid <- 0;
#endef;
peer.peeraddr <- peer IP address; /* copy variables */
peer.peerport <- NTP.PORT;
peer.hostaddr <- host IP address;
peer.hostport <- NTP.PORT;
peer.mode <- host mode;
peer.peerpoll <- 0 (undefined);
peer.timer <- 0;
peer.delay <- 0 (undefined);
peer.offset <- 0 (undefined);
call clear; /* initialize association */
end initialization-instantiation procedure;
3.4.7.3. Receive-Instantiation Procedure
The receive-instantiation procedure is called from the receive procedure when a new peer is discovered. It initializes the peer variables and mobilizes the association. If the message is from a peer operating in client mode (3), the host mode is set to server mode (4); otherwise, it is set to symmetric passive mode (2). The authentication variables are used only if the authentication mechanism described in Appendix C is implemented. If implemented, only properly authenticated non-configured peers can become the synchronization source.
begin receive-instantiation procedure
#ifdef (authentication implemented) /* see Appendix C */
peer.authenable <- 0;
peer.authentic <- 0;
peer.hostkeyid <- as required;
peer.peerkeyid <- 0;
#endef
peer.config <- 0; /* copy variables */
peer.peeraddr <- pkt.peeraddr;
peer.peerport <- pkt.peerport;
peer.hostaddr <- pkt.hostaddr;
peer.hostport <- pkt.hostport;
if (pkt.mode = 3) /* determine mode */
peer.mode <- 4;
else
peer.mode <- 2;
peer.peerpoll <- 0 (undefined);
peer.timer <- 0;
peer.delay <- 0 (undefined);
peer.offset <- 0 (undefined);
call clear; /* initialize association */
end receive-instantiation procedure;
3.4.7.4. Primary Clock-Instantiation Procedure
This procedure is called from the initialization procedure in order to set up the state variables for the primary clock. The value for peer.precision is determined from the radio clock specification and hardware interface. The value for peer.rootdispersion is nominally ten times the inherent maximum error of the radio clock; for instance, 10 μs for a calibrated atomic clock, 10 ms for a WWVB or GOES radio clock and 100 ms for a less accurate WWV radio clock.
begin clock-instantiation procedure
peer.config <- 1; /* copy variables */
peer.peeraddr <- 0 undefined;
peer.peerport <- 0 (not used);
peer.hostaddr <- 0 (not used);
peer.hostport <- 0 (not used);
peer.leap <- 11₂;
peer.mode <- 0 (not used);
peer.stratum <- 0;
peer.peerpoll <- 0 (undefined);
peer.precision <- clock precision;
peer.rootdelay <- 0;
peer.rootdispersion <- clock dispersion;
peer.refid <- 0 (not used);
peer.reftime <- 0 (undefined);
peer.timer <- 0;
peer.delay <- 0 (undefined);
peer.offset <- 0 (undefined);
call clear; /* initialize association */
end clock-instantiation procedure;
In some configurations involving a calibrated atomic clock or LORAN-C receiver, the primary reference source may provide only a seconds pulse, but lack a complete timecode from which the numbering of the seconds, etc., can be derived. In these configurations seconds numbering can be derived from other sources, such as a radio clock or even other NTP peers. In these configurations the primary clock variables should reflect the primary reference source, not the seconds-numbering source; however, if the seconds-numbering source fails or is known to be operating incorrectly, updates from the primary reference source should be suppressed as if it had failed.
3.4.8. Clear Procedure
The clear procedure is called when some event occurs that results in a significant change in reachability state or potential disruption of the local clock.
begin clear procedure
peer.org <- 0 (undefined); /* mark timestamps undefined */
peer.rec <- 0 (undefined);
peer.xmt <- 0 (undefined);
peer.reach <- 0; /* reset state variables */
peer.filter <- [0, 0, NTP.MAXDISPERSE]; /* all stages */
peer.valid <- 0;
peer.dispersion <- NTP.MAXDISPERSE;
peer.hostpoll <- NTP.MINPOLL; /* reset poll interval */
call poll-update;
call clock-select; /* select clock source */
end clear procedure;
3.4.9. Poll-Update Procedure
The poll-update procedure is called when a significant event occurs that may result in a change of the poll interval or peer timer. It checks the values of the host poll interval (peer.hostpoll) and peer poll interval (peer.peerpoll) and clamps each within the valid range. If the peer is selected for synchronization, the value is further clamped as a function of the computed compliance (see Section 5).
begin poll-update procedure
temp <- peer.hostpoll; /* determine host poll interval */
if (peer = sys.peer)
temp <- min(temp, sys.poll, NTP.MAXPOLL);
else
temp <- min(temp, NTP.MAXPOLL);
peer.hostpoll <- max(temp, NTP.MINPOLL);
temp <- 1 << min(peer.hostpoll, max(peer.peerpoll, NTP.MINPOLL));
if (peer.timer = 0) /* reset peer timer */
peer.timer <- temp;
else if (peer.timer > temp)
peer.timer <- (sys.clock & (temp - 1)) + 1;
end poll-update procedure;
3.5. Synchronization Distance Procedure
The distance procedure calculates the synchronization distance from the peer variables for the peer peer.
begin distance(peer) procedure;
DELTA <- peer.rootdelay + |peer.delay|;
EPSILON <- peer.rootdispersion + peer.dispersion + φ(sys.clock - peer.update);
LAMBDA <- EPSILON + |DELTA| / 2;
end distance procedure;
Note that, while DELTA may be negative in some cases, both EPSILON and LAMBDA are always positive.
3.6. Access Control Issues
The NTP design is such that accidental or malicious data modification (tampering) or destruction (jamming) at a time server should not in general result in timekeeping errors elsewhere in the synchronization subnet. However, the success of this approach depends on redundant time servers and diverse network paths, together with the assumption that tampering or jamming will not occur at many time servers throughout the synchronization subnet at the same time. In principle, the subnet vulnerability can be engineered through the selection of time servers known to be trusted and allowing only those time servers to become the synchronization source. The authentication procedures described in Appendix C represent one mechanism to enforce this; however, the encryption algorithms can be quite CPU-intensive and can seriously degrade accuracy, unless precautions such as mentioned in the description of the transmit procedure are taken.
While not a required feature of NTP itself, some implementations may include an access-control feature that prevents unauthorized access and controls which peers are allowed to update the local clock. For this purpose it is useful to distinguish between three categories of access: those that are preauthorized as trusted, preauthorized as friendly and all other (non-preauthorized) accesses. Presumably, preauthorization is accomplished by entries in the configuration file or some kind of ticket-management system such as Kerberos [STE88]. In this model only trusted accesses can result in the peer becoming the synchronization source. While friendly accesses cannot result in the peer becoming the synchronization source, NTP messages and timestamps are returned as specified.
It does not seem useful to maintain a secret clock, as would result from restricting non-preauthorized accesses, unless the intent is to hide the existence of the time server itself. Well-behaved Internet hosts are expected to return an ICMP service-unavailable error message if a service is not implemented or resources are not available; however, in the case of NTP the resources required are minimal, so there is little need to restrict requests intended only to read the clock. A simple but effective access-control mechanism is then to consider all associations preconfigured in a symmetric mode or client mode (modes 1, 2 and 3) as trusted and all other associations, preconfigured or not, as friendly.
If a more comprehensive trust model is required, the design can be based on an access-control list with each entry consisting of a 32-bit Internet address, 32-bit mask and three-bit mode. If the logical AND of the source address (pkt.peeraddr) and the mask in an entry matches the corresponding address in the entry and the mode (pkt.mode) matches the mode in the entry, the access is allowed; otherwise an ICMP error message is returned to the requestor. Through appropriate choice of mask, it is possible to restrict requests by mode to individual addresses, a particular subnet or net addresses, or have no restriction at all. The access-control list would then serve as a filter controlling which peers could create associations.