Compare commits

...

127 commits
4.4 ... master

Author SHA1 Message Date
Miroslav Lichvar
b9b338a8df refclock: rework update of reachability
Update the reachability register of a refclock source by 1 if a valid
measurement is received by the drivers between source polls, and not
only when it is accumulated to sourcestats, similarly to how
reachability works with NTP sources.

This avoids drops in the reported reachability when a PHC refclock is
dropping samples due to significant changes in the measured delay (e.g.
due to high PCIe load), or a PPS refclock dropping samples due to failed
lock.
2024-11-05 15:51:43 +01:00
Vincent Blut
a5d73f692f doc: fix typo in chrony.conf man page 2024-10-09 09:05:41 +02:00
Miroslav Lichvar
b0ac5992fb doc: update NEWS 2024-10-08 14:49:43 +02:00
Miroslav Lichvar
cd65e32cf0 doc: warn about MD5 keys not protecting extension fields
Add a warning to the chrony.conf man page that MD5 keys cannot protect
NTP extension fields due to the length extension attack.
2024-10-08 14:49:41 +02:00
Miroslav Lichvar
b9f5278846 update copyright years 2024-10-08 12:11:32 +02:00
Miroslav Lichvar
b8b166044f nts: don't include compliant-128gcm record for other AEADs
If the client included the NTS-KE record requesting compliant key
exporter context for AES-128-GCM-SIV, but the server doesn't select this
AEAD algorithm (it's not supported by the crypto library or it is
disabled by the ntsaeads directive), don't include the NTS-KE record in
the response. It's not relevant to the other AEAD algorithms.
2024-10-08 12:11:05 +02:00
Miroslav Lichvar
42fbf41686 nts: make server and client AEAD algorithms configurable
Add ntsaeads directive to specify a list of AEAD algorithms enabled for
NTS. The list is shared between the server and client. For the client it
also specifies the order of priority. The default is "30 15", matching
the previously hardcoded preference of AES-128-GCM-SIV (30) over
AES-SIV-CMAC-256 (15).
2024-10-03 16:09:53 +02:00
Miroslav Lichvar
79a790e6b5 test: improve nts_ke_client unit test 2024-10-03 15:02:16 +02:00
Miroslav Lichvar
f5cd79d2df nts: check TLS session in NKSN_GetKeys()
Make sure the TLS session is not NULL in NKSN_GetKeys() before trying to
export the keys in case some future code tried to call the function
outside of the NTS-KE message handler.
2024-10-03 15:02:16 +02:00
Miroslav Lichvar
689605b6a2 nts: switch client to compliant key exporter on NTS NAK
Implement a fallback for the NTS-NTP client to switch to the compliant
AES-128-GCM-SIV exporter context when the server is using the compliant
context, but does not support the new NTS-KE record negotiating its use,
assuming it can respond with an NTS NAK to the request authenticated
with the incorrect key.

Export both sets of keys when processing the NTS-KE response. If an NTS
NAK is the only valid response from the server after the last NTS-KE
session, switch to the keys exported with the compliant context for the
following requests instead of dropping all cookies and restarting
NTS-KE. Don't switch back to the original keys if an NTS NAK is received
again.
2024-10-03 15:02:03 +02:00
Miroslav Lichvar
0707865413 nts: negotiate compliant export of AES-128-GCM-SIV keys
Add client and server support for a new NTS-KE record to negotiate use
of the compliant key exporter context with the AES-128-GCM-SIV AEAD as
specified here:

https://chrony-project.org/doc/spec/nts-compliant-128gcm.html
2024-09-26 16:04:01 +02:00
Miroslav Lichvar
2adda9c12c nts: construct key exporter context
When the NTS client and server negotiated use of AES-128-GCM-SIV keys,
the keys exported from the TLS session and used for authentication and
encryption of NTP messages do not comply to RFC8915. The exporter
context value specified in the section 5.1 of RFC8915 function is
incorrect. It is a hardcoded string which contains 15 (AES-SIV-CMAC-256)
instead of 30 (AES-128-GCM-SIV). This causes chrony to not interoperate
with NTS implementations that follow RFC8915 correctly. (At this time,
there doesn't seem to be another implementation with AES-128-GCM-SIV
support yet.)

Replace the string with a proper construction of the exporter context
from a specified AEAD ID and next protocol.

Keep using the incorrect AEAD ID for AES-128-GCM-SIV to not break
compatibility with existing chrony servers and clients. A new NTS-KE
record will be added to negotiate the compliant exporter context.

Reported-by: Martin Mayer <martin.mayer@m2-it-solutions.de>
2024-09-26 12:45:44 +02:00
Miroslav Lichvar
113d1134d1 doc: update NEWS 2024-08-29 10:28:49 +02:00
Miroslav Lichvar
b363af754d doc: update README 2024-08-29 09:37:50 +02:00
Miroslav Lichvar
0f5cf57bc2 update copyright years 2024-08-29 09:37:50 +02:00
Miroslav Lichvar
5a43f0c39b test: make 110-chronyc more reliable 2024-08-29 09:37:50 +02:00
Miroslav Lichvar
5a6fbe7a4b test: make 106-refclock more reliable 2024-08-29 09:37:50 +02:00
Miroslav Lichvar
bb34e92f96 test: make 108-peer more reliable 2024-08-29 09:37:50 +02:00
Miroslav Lichvar
78b9c13a11 sources: replace unreachable sources before selection
The commit c43efccf02 ("sources: update source selection with
unreachable sources") caused a high rate of failures in the
148-replacement test (1 falseticker vs 2 unreachable sources). This was
due to a larger fraction of the replacement attempts being made for the
source incorrectly marked as a falseticker instead of the second
unreachable source and the random process needed more time to get to the
expected state with both unreachable sources replaced.

When updating reachability of an unreachable source, try to request the
replacement of the source before calling the source selection, where
other sources may be replaced, to better balance the different
replacement attempts.
2024-08-29 09:37:42 +02:00
Luca Boccassi
1ab5b88939 conf: do not check, write and delete PID file if set to '/'
If the pid file path is specified as '/', skip handling it,
as it is not only unnecessary but complicates managing the
service. A systemd unit can manage the program without any
need for this functionality, and it makes process tracking
simpler and more robust.
The implementation matches the bindcmdaddress directive.
2024-08-22 14:24:49 +02:00
Luca Boccassi
e30f937f6a doc: explain how to disable DNSSEC validation with sd-resolved in FAQ
DNSSEC requires the system time to be synced in order to work,
as the signature date and expiration need to be checked by
resolvers. But it is possible that syncing the times requires
doing DNS queries. Add a paragraph to the FAQ explaining how
to break this cycle by asking nss-resolved to always avoid
DNSSEC when chronyd tries to resolve hostnames.
2024-08-22 14:24:49 +02:00
Miroslav Lichvar
08b67dba98 ntp: fix finalization for async resolver
If an attempt to resolve addresses of an NTP server is made right before
starting the termination sequence, the asynchronous resolver thread
could read the server name when it was already freed.

Leave unresolved sources allocated in NSR_Finalise() if the async
resolver did not finish yet, at least for now. Waiting for the resolving
result or cancelling the thread would complicate the code. The scheduler
is not expected to be running at this point.
2024-08-22 09:32:36 +02:00
Miroslav Lichvar
61f15fedcd doc: add new question about accuracy to FAQ 2024-08-21 11:24:24 +02:00
Miroslav Lichvar
6d59234995 doc: clarify hostname with nts option 2024-08-20 14:18:54 +02:00
Miroslav Lichvar
d4a4f89329 conf: don't repeat error message when adding sourcedir source
When a source from a configured sourcedir cannot be added (e.g. it is a
duplicate of another source), log the error message only on the first
attempt adding the source, until the source is removed and added to a
sourcedir again.

This avoids spamming of the system log with error messages if the
reload sources command is called frequently (e.g. from a DHCP renewal
networking script).
2024-08-07 09:48:24 +02:00
Miroslav Lichvar
916ed70c4a conf: save source status in sourcedir reload
Save the NSR status when adding a source from a sourcedir and don't
hide sources that failed the addition by clearing their name.
2024-08-07 09:48:13 +02:00
Miroslav Lichvar
8ba2da52df conf: merge ntp_source_ids with ntp_sources
Keep the configuration IDs of sources loaded from sourcedir in the
NTP_Source structure itself to simplify the code.
2024-08-07 09:46:00 +02:00
Miroslav Lichvar
fd9e956d27 test: extend 008-confload test 2024-08-06 11:27:13 +02:00
Miroslav Lichvar
43189651b0 doc: update NEWS 2024-07-30 14:05:42 +02:00
Miroslav Lichvar
f518b8d00f doc: update README 2024-07-30 12:11:09 +02:00
Miroslav Lichvar
42b3c40c32 doc: fix typo in kod option description 2024-07-30 12:11:09 +02:00
Miroslav Lichvar
66512ebcb3 ntp: make sure new configuration IDs are unused
The configuration IDs assigned to individual sources (used when they
don't have a resolved IP address) and pools of sources are 32-bit. The
ID could overflow if some sources were very frequently removed and added
again. Two unrelated sources could end up with the same ID, causing some
operations to unexpectedly impact only one or both sources.

Make sure the ID is currently unused before assigning it to a new source.
2024-07-30 12:11:09 +02:00
Miroslav Lichvar
3940d2aae3 leapdb: add explicit cast to int64_t
Add an explicit cast to int64_t to not rely on LEAP_SEC_LIST_OFFSET
not fitting in 32-bit time_t.
2024-07-30 12:09:53 +02:00
Miroslav Lichvar
34be117c9c main: check for killed foreground process
On start, if the foreground process waiting for the daemon process to
close the pipe (after finishing the RTC initialization, initstepslew,
etc) is killed, terminate the daemon too assuming that whatever killed
the foreground process it wanted all chronyd processes to stop.

In the daemon, before closing the pipe file descriptor, send an empty
message to check if the pipe isn't already closed on the other end.
2024-07-04 16:50:22 +02:00
Miroslav Lichvar
7915f52495 logging: add function to send message to foreground process 2024-07-04 16:50:22 +02:00
Miroslav Lichvar
05bd4898a9 test: fix 142-ntpoverptp 2024-06-20 15:10:42 +02:00
Miroslav Lichvar
4da088ec2f ntp: make NTP-over-PTP domain configurable
Add ptpdomain directive to set the domain number of transmitted and
accepted NTP-over-PTP messages. It might need to be changed in networks
using a PTP profile with the same domain number. The default domain
number of 123 follows the current NTP-over-PTP specification.
2024-06-20 15:00:17 +02:00
Miroslav Lichvar
c46e0549ab ntp: update NTP-over-PTP support
Following the latest version of the draft, accept NTP messages in both
PTPv2 and PTPv2.1 messages, accept sync messages in addition to delay
request messages, and check the minorSdoId field in PTPv2.1 messages.

Transmitted messages are still PTPv2 delay requests.

Don't switch to the organization-specific TLV yet. Wait for the NTP TLV
subtype and Network Correction extension field to be assigned by IANA to
avoid an additional break in compatibility.
2024-06-20 14:35:28 +02:00
Miroslav Lichvar
8f5b308414 test: make 124-tai more reliable
Reported-by: Reinhard Max <max@suse.de>
2024-06-04 16:25:55 +02:00
Miroslav Lichvar
084fe6b0cc doc: clarify prefer source option 2024-06-04 16:25:55 +02:00
Miroslav Lichvar
ebfc676d74 ntp: limit offset correction to supported NTP interval
When an NTP source is specified with the offset option, the corrected
offset may get outside of the supported NTP interval (by default -50..86
years around the build date). If the source passed the source selection,
the offset would be rejected only later in the adjustment of the local
clock.

Check the offset validity as part of the NTP test A to make the source
unselectable and make it visible in the measurements log and ntpdata
report.
2024-05-02 14:43:51 +02:00
Miroslav Lichvar
adaca0ff19 reference: switch is_leap_close() from time_t to double
Avoid undefined behavior in the timestamp conversion from double to
time_t in REF_IsLeapSecondClose() with NTP sources configured with a
large offset correction.
2024-05-02 14:43:46 +02:00
Miroslav Lichvar
84d6c7a527 sources: allow logging one selection failure on start
Allow one message about failed selection (e.g. no selectable sources)
to be logged before first successful selection when a source has
full-size reachability register (8 polls with a received or missed
response).

This should make it more obvious that chronyd has a wrong configuration
or there is a firewall/networking issue.
2024-05-02 12:51:38 +02:00
Miroslav Lichvar
c43efccf02 sources: update source selection with unreachable sources
When updating the reachability register of a source with zero, call the
source selection even if the source is not the currently selected as the
best source. But do that only if all reachability bits are zero, i.e.
there was no synchronized response for last 8 polls.

This will enable the source selection to log a message when only
unreachable sources are updating reachability and it decreases the
number of unnecessary source selections.
2024-04-30 15:52:18 +02:00
Miroslav Lichvar
1affd03cca sources: reorder unsynchronised source status
In the source selection, check for the unsynchronized leap status after
getting sourcestats data. The unsynchronized source status is supposed
to indicate an unsynchronized source that is providing samples, not a
source which doesn't have any samples.

Also, fix the comment describing the status.

Fixes: 4c29f8888c ("sources: handle unsynchronized sources in selection")
2024-04-30 15:52:18 +02:00
Miroslav Lichvar
276591172e ntp: improve copying of server status
When a server specified with the copy option responds with an
unsynchronized status (e.g. due to selection failure), reset the
source instance to immediately switch the local reference status
instead of waiting for the source to become unreachable after 8 polls.
2024-04-29 11:21:45 +02:00
Rob Gill
989ef702aa doc: fix typo in README
Typo correction only - no change to content

Signed-off-by: Rob Gill <rrobgill@protonmail.com>
2024-04-16 07:50:11 +02:00
Rob Gill
1920b1efde doc: fix typo in chronyc docs
Typo fix only - no change to content

Signed-off-by: Rob Gill <rrobgill@protonmail.com>
2024-04-16 07:49:59 +02:00
Miroslav Lichvar
bb5db828c6 ntp: log failed connection to Samba signd socket
Log an error message (in addition to the socket-specific debug message)
when the connection to signd socket fails, but only once before a
successful signd exchange to avoid flooding the system log.
2024-04-15 16:35:33 +02:00
Miroslav Lichvar
dcc94a4c10 doc: add contributing.adoc 2024-04-11 16:52:06 +02:00
Miroslav Lichvar
2ed72c49c9 test: add --enable-debug option to 002-scanbuild 2024-04-11 12:53:01 +02:00
Miroslav Lichvar
342b588e3b avoid some static analysis errors
Modify the code to avoid some false positives reported by the clang and
gcc static analyzers.
2024-04-11 10:31:02 +02:00
Miroslav Lichvar
a914140bd4 sys_linux: disable other external timestamping channels
Use new ioctls added in Linux 6.7 to disable receiving events from other
channels when enabling external timestamping on a PHC. This should save
some CPU time when other applications or chronyd instances are using
other channels of the same PHC.
2024-04-10 15:33:04 +02:00
Miroslav Lichvar
28e4eec1c4 refclock: update comment in PHC driver
Since Linux 6.7 external timestamping events are no longer shared among
all descriptors of a PHC. Each descriptor gets its own copy of each
timestamp.
2024-04-10 12:13:29 +02:00
Miroslav Lichvar
5235c51801 cmdmon: add reserved fields to local command
Add two reserved fields initialized to zero to the new REQ_LOCAL3
command to allow adding more options (e.g. delay in activation) without
changing the command number again.
2024-04-04 16:24:43 +02:00
Miroslav Lichvar
26ea4e35e7 test: add tests of local directive options 2024-04-04 16:24:02 +02:00
Andy Fiddaman
9397ae2b0a reference: add "local activate" option
This option sets an activating root distance for the local reference. The
local reference will not be used until the root distance drops below the
configured value for the first time. This can be used to prevent the local
reference from being activated on a server which has never been synchronised
with an upstream server. The default value of 0.0 causes no activating
distance to be used, such that the local reference is always eligible for
activation.
2024-04-04 15:17:05 +02:00
Miroslav Lichvar
b8ead3485b leapdb: fix leapsec list processing with 32-bit time_t
A 32-bit time_t value overflows when converted to the Y1900 epoch used
in the leapsec list. Use a 64-bit variable in get_list_leap() to fix the
comparisons on systems using 32-bit time_t.

Fixes: 53823b9f1c ("leapdb: support leap-seconds.list as second source")
2024-04-03 11:01:44 +02:00
Miroslav Lichvar
24d28cd679 ntp: add server support for KoD RATE
Add "kod" option to the ratelimit directive to respond with the KoD
RATE code to randomly selected requests exceeding the configured limit.
This complements the client support of KoD RATE. It's disabled by
default.

There can be only one KoD code in one response. If both NTS NAK and RATE
codes are triggered, drop the response. The KoD RATE code can be set in
an NTS-authenticated response.
2024-04-02 15:39:12 +02:00
Miroslav Lichvar
aac898343e clientlog: add support for KoD rate limiting
Add a third return value to CLG_LimitServiceRate() to indicate the
server should send a response requesting the client to reduce its
polling rate. It randomly selects from a fraction (configurable to 1/2,
1/4, 1/8, 1/16, or disabled) of responses which would be dropped
(after selecting responses for the leak option).
2024-04-02 15:23:26 +02:00
Miroslav Lichvar
c8c7f518b1 clientlog: return enum from CLG_LimitServiceRate()
Change CLG_LimitServiceRate() to return an enum in preparation for
adding KoD RATE support.
2024-04-02 11:55:02 +02:00
Miroslav Lichvar
ce956c99a8 nts: check for NTS NAK specifically when responding
Ignore other KoD codes than NTS NAK when deciding if the server response
should not be authenticated.
2024-04-02 11:33:04 +02:00
Miroslav Lichvar
863866354d ntp: avoid unnecessary restart of resolving round on refresh
Don't call NSR_ResolveSources() when a resolving round is already
started. This cuts the number of calls of the system resolver made due
to the refresh command to half.
2024-03-14 16:39:41 +01:00
Miroslav Lichvar
6e5513c80b ntp: don't keep refresh requests in list of unresolved sources
The refresh command adds requests to reresolve addresses of all sources.
If some sources didn't have an IP address resolved yet, the
corresponding requests were not removed after failed resolving. Repeated
refresh commands increased the number of requests and number of calls of
the system resolver, which might not be caching DNS responses.

Remove all refresh requests from the list after resolving attempt to fix
that.

Reported-by: t.barnewski@avm.de
Fixes: d7e3ad17ff ("ntp: create sources for unresolved addresses")
2024-03-14 16:39:41 +01:00
Miroslav Lichvar
6d0143e963 ntp: add more debug messages for resolving 2024-03-14 16:39:32 +01:00
Miroslav Lichvar
f49be7f063 conf: don't load sourcedir during initstepslew and RTC init
If the reload sources command was received in the chronyd start-up
sequence with initstepslew and/or RTC init (-s option), the sources
loaded from sourcedirs caused a crash due to failed assertion after
adding sources specified in the config.

Ignore the reload sources command until chronyd enters the normal
operation mode.

Fixes: 519796de37 ("conf: add sourcedirs directive")
2024-03-12 14:57:30 +01:00
Miroslav Lichvar
7fe98a83b8 test: replace another C99-style declaration in for loop 2024-03-11 12:00:12 +01:00
Miroslav Lichvar
ad37c409c9 cmdmon: add offset command
Add a new command to modify the offset option of NTP sources and
reference clocks.
2024-03-07 16:20:27 +01:00
Miroslav Lichvar
719c6f6a8a ntp+refclock: add functions to modify offset option 2024-03-07 16:19:04 +01:00
Miroslav Lichvar
b0750136b5 rtc+getdate: initialize tm_wday for mktime()
Even though mktime() is documented as ignoring the tm_wday field, the
coverity static analyzer complains about passing an uninitialized value.
Set the field to zero to make it happy.
2024-03-04 11:38:16 +01:00
Miroslav Lichvar
ad79aec946 test: avoid C99-style declaration in for loop
This fixes compilation without the -std=c99 option with an older gcc.
2024-03-04 11:38:11 +01:00
Miroslav Lichvar
008dc16727 examples: switch chrony.conf examples to leapseclist 2024-02-08 16:21:42 +01:00
Miroslav Lichvar
6cf9fe2f16 test: improve 113-leapsecond and 124-tai tests
Use leapseclist instead of leapsectz and test also negative leap
seconds. Add a test for leapsectz when the date command indicates
right/UTC is available on the system and mktime() works as expected.
Check TAI offset in the server's log.
2024-02-08 15:54:24 +01:00
Patrick Oppenlander
637b77d1bd test: add leapdb unit test 2024-02-08 15:54:24 +01:00
Patrick Oppenlander
53823b9f1c leapdb: support leap-seconds.list as second source
The existing implementation of getting leap second information from a
timezone in get_tz_leap() relies on non-portable C library behaviour.

Specifically, mktime is not required to return '60' in the tm_sec field
when a leap second is inserted leading to "Timezone right/UTC failed
leap second check, ignoring" errors on musl based systems.

This patch adds support for getting leap second information from the
leap-seconds.list file included with tzdata and adds a new configuration
directive leapseclist to switch on the feature.
2024-02-08 15:54:24 +01:00
Patrick Oppenlander
83f90279b0 leapdb: move source check into separate function
The sanity checks are valid for all possible sources of leap second
information, so move them into a separate function check_leap_source().
2024-02-08 15:54:21 +01:00
Patrick Oppenlander
02ae9a8607 leapdb: make twice per day check logic common
We want to do the twice per day check regardless of the data source.
Move the check up one level from get_tz_leap() into LDB_GetLeap().
2024-02-08 12:54:37 +01:00
Patrick Oppenlander
017d6f8f56 reference: move leap second source into leapdb
Separate out source of leap second data into a new module in preparation
for supporting more sources such as leap-seconds.list.
2024-02-08 12:54:37 +01:00
Miroslav Lichvar
eb26d13140 cmdmon: add timestamp counters to ntpdata report 2024-02-07 10:23:40 +01:00
Miroslav Lichvar
8d19f49341 ntp: add per-source counters of kernel and hardware timestamps 2024-02-07 10:23:40 +01:00
Miroslav Lichvar
637fa29e1e cmdmon: add ipv4/ipv6 options to add source command 2024-02-07 10:23:40 +01:00
Miroslav Lichvar
2d349595ee cmdmon: simplify flag checking in handle_add_source() 2024-02-07 10:23:40 +01:00
Miroslav Lichvar
5cb584d6c1 conf: add ipv4 and ipv6 options to server/pool/peer directive
Accept "ipv4" and "ipv6" options in the server/pool/peer directive to
use only IPv4 or IPv6 addresses respectively.

The configuration is different from the "server [-4|-6] hostname" syntax
supported by ntpd to avoid breaking existing scripts which expect the
hostname to always be the first argument of the directives.
2024-02-07 10:23:40 +01:00
Miroslav Lichvar
d7c2b1d2f3 ntp: support per-source IP family restriction
Add a new parameter to the NSR_AddSourceByName() function to allow
individual sources to be limited to IPv4 or IPv6 addresses. This doesn't
change the options passed to the resolver. It's just an additional
filter in the processing of resolved addresses following the -4/-6
command-line option of chronyd.
2024-02-07 10:23:36 +01:00
Miroslav Lichvar
e11b518a1f ntp: fix authenticated requests in serverstats
Fix the CLG_UpdateNtpStats() call to count requests passing the
authentication check instead of requests triggering a KoD response
(i.e. NTS NAK).
2024-01-08 11:46:32 +01:00
Miroslav Lichvar
120dfb8b36 update copyright years 2023-12-05 14:22:10 +01:00
Miroslav Lichvar
598b893e1d doc: update FAQ on improving accuracy 2023-12-05 14:22:10 +01:00
Miroslav Lichvar
89aa8fa342 doc: mention dependency of net corrections on HW timestamping 2023-12-05 14:22:08 +01:00
Miroslav Lichvar
42fdad5dcc doc: improve description of reload sources command 2023-12-04 16:50:51 +01:00
Miroslav Lichvar
3ee7b3e786 sources: rework logging of selection loss
The commit 5dd288dc0c ("sources: reselect earlier when removing
selected source") didn't cover all paths that can lead to a missing log
message when all sources are removed.

Add a flag to track the loss of selection and postpone the log message
in transient states where no message is logged to avoid spamming in
normal operation. Call SRC_SelectSource() after removing the source
to get a log message if there are no (selectable) sources left.

Reported-by: Thomas Lange <thomas@corelatus.se>
2023-11-28 12:21:23 +01:00
Miroslav Lichvar
426fe2ee58 doc: update NEWS 2023-11-22 13:53:23 +01:00
Miroslav Lichvar
3f66202d79 doc: update README 2023-11-22 11:56:16 +01:00
Miroslav Lichvar
ed6b0b55c7 doc: replace foobar naming in examples 2023-11-22 11:56:15 +01:00
Miroslav Lichvar
5e5adbea0c doc: update description of NTP over PTP in FAQ 2023-11-22 11:56:15 +01:00
Miroslav Lichvar
82959431df doc: mention version supporting socket activation in FAQ 2023-11-22 11:56:15 +01:00
Miroslav Lichvar
b92b2da24a doc: improve ntstrustedcerts description 2023-11-22 11:55:27 +01:00
Miroslav Lichvar
68a3d52086 doc: improve cmdport description 2023-11-22 09:48:36 +01:00
Miroslav Lichvar
1a15be1e9e sources: drop unreachable log message
With forced reselection during source removal selected_source_index
can only be INVALID_SOURCE if there are no sources. The "Can't
synchronise: no sources" message couldn't be logged even before that as
SRC_ReselectSource() resets the index before calling SRC_SelectSource().

Replace the message with an assertion.
2023-11-21 12:38:41 +01:00
Miroslav Lichvar
5dd288dc0c sources: reselect earlier when removing selected source
When a selected source is being removed, reset the instance and rerun
the selection while the source is still marked as selected. This forces
a "Can't synchronise" message to be logged when all sources are removed.

Reported-by: Thomas Lange <thomas@corelatus.se>
2023-11-21 12:38:41 +01:00
Miroslav Lichvar
cbee464c75 sources: reselect after resetting selected source
Avoid showing in the sources report a selected source which has no
samples (e.g. after replacement).
2023-11-21 12:38:41 +01:00
Miroslav Lichvar
4e36295889 ntp: allow reusing timestamps of unused samples
When switching from basic mode to interleaved mode following a response
which wasn't accumulated due to failed test A, B, C, or D, allow
timestamps of the failed sample to be reused in interleaved mode, i.e.
replacing the server's less accurate transmit timestamp with a more
accurate timestamp server can turn a failed sample into acceptable one.

Move the presend check into test A to simplify the code.
2023-11-21 12:38:41 +01:00
Miroslav Lichvar
2d2642bb82 ntp: fix presend in interleaved mode
The presend option in interleaved mode uses two presend requests instead
of one to get an interleaved response from servers like chrony which
delay the first interleaved response due to an optimization saving
timestamps only for clients actually using the interleaved mode.

After commit 0ae6f2485b ("ntp: don't use first response in interleaved
mode") the first interleaved response following the two presend
responses in basic mode is dropped as the preferred set of timestamps
minimizing error in delay was already used by the second sample in
basic mode. There are only three responses in the burst and no sample is
accumulated.

Increasing the number of presend requests to three to get a fourth
sample would be wasteful. Instead, allow reusing timestamps of the
second presend sample in basic mode, which is never accumulated.

Reported-by: Aaron Thompson
Fixes: 0ae6f2485b ("ntp: don't use first response in interleaved mode")
2023-11-21 12:38:41 +01:00
Miroslav Lichvar
9c6eaccc32 nts: close reusable sockets in helper process
Close all reusable sockets when the NTS-KE server helper is forked. It
is not supposed to have access to any of the server sockets, just the
socket for getting requests from the main process and the syslog socket.
2023-11-21 12:38:41 +01:00
Miroslav Lichvar
0aa4d5ac14 socket: provide function for closing reusable sockets 2023-11-21 12:38:39 +01:00
Miroslav Lichvar
ee9d721b7b socket: set close-on-exec on all reusable sockets
Set the CLOEXEC flag on all reusable sockets in the initialization to
avoid leaking them to sendmail (mailonchange directive) in case the
chrony configuration doesn't use all sockets provided by systemd.
2023-11-20 13:33:45 +01:00
Luke Valenta
b6eec0068a doc: add FAQ section on minimizing service downtime 2023-11-13 17:05:46 +01:00
Luke Valenta
e6a0476eb7 socket: add support for systemd sockets
Before opening new IPv4/IPv6 server sockets, chronyd will check for
matching reusable sockets passed from the service manager (for example,
passed via systemd socket activation:
https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html)
and use those instead.

Aside from IPV6_V6ONLY (which cannot be set on already-bound sockets),
the daemon sets the same socket options on reusable sockets as it would
on sockets it opens itself.

Unit tests test the correct parsing of the LISTEN_FDS environment
variable.

Add 011-systemd system test to test socket activation for DGRAM and
STREAM sockets (both IPv4 and IPv6).  The tests use the
systemd-socket-activate test tool, which has some limitations requiring
workarounds discussed in inline comments.
2023-11-13 17:05:26 +01:00
Luke Valenta
c063b9e78a logging: move severity_chars to fix compiler warning 2023-11-08 15:09:03 +01:00
Luke Valenta
f6f1863fe2 logging: add log severity to file log prefix when debug is enabled 2023-11-07 15:43:57 +01:00
Miroslav Lichvar
51a621bc2b ntp: initialize network correction of transmitted packets
Initialize the unused value of network correction parsed from
own transmitted packets to avoid a use-of-uninitialized-value error
in NIO_UnwrapMessage() reported by clang.

Fixes: 6372a9f93f ("ntp: save PTP correction from NTP-over-PTP messages")
2023-11-02 12:53:00 +01:00
Luke Valenta
1488b31a38 doc: document '-L -1' option for debug logging output 2023-10-24 11:37:56 +02:00
Miroslav Lichvar
70cdd8b1ef ntp: add client support for network correction
If the network correction is known for both the request and response,
and their sum is not larger that the measured peer delay, allowing the
transparent clocks to be running up to 100 ppm faster than the client's
clock, apply the corrections to the NTP offset and peer delay. Don't
correct the root delay to not change the estimated maximum error.
2023-09-26 15:14:13 +02:00
Miroslav Lichvar
8eef631009 ntp: add server support for network correction
Provide the network correction (PTP correction + RX duration) of the
request in the new extension field if included in the request and
NTP-over-PTP is enabled.
2023-09-26 15:14:13 +02:00
Miroslav Lichvar
d9ae724c60 ntp: add extension field to provide network correction
To be able to verify PTP corrections, the client will need to know both
the correction of the request received by the server and the correction
of the response. Add a new experimental NTP extension field that the
clients will use to request the correction and servers return the
value.
2023-09-26 15:14:13 +02:00
Miroslav Lichvar
6372a9f93f ntp: save PTP correction from NTP-over-PTP messages
When the RX duration is known (HW timestamping), save the PTP correction
from received PTP messages in the local RX timestamp.
2023-09-26 15:14:13 +02:00
Miroslav Lichvar
b0267475e3 ntp: extend local timestamp for PTP correction
Add two new fields to the NTP_Local_Timestamp structure:
- receive duration as the time it takes to receive the ethernet frame,
  currently known only with HW timestamping
- network correction as a generalized PTP correction

The PTP correction is provided by transparent clocks in the correction
field of PTP messages to remove the receive, processing and queueing
delays of network switches and routers. Only one-step end-to-end unicast
transparent clocks are useful for NTP-over-PTP. Two-step transparent
clocks use follow-up messages and peer-to-peer transparent clocks don't
handle delay requests.

The RX duration will be included in the network correction to compensate
for asymmetric link speeds of the server and client as the NTP RX
timestamp corresponds to the end of the reception (in order to
compensate for the asymmetry in the normal case when no corrections
are applied).
2023-09-26 15:10:19 +02:00
Miroslav Lichvar
07134f2625 ntp: add function for detection of experimental fields 2023-09-26 15:03:33 +02:00
Miroslav Lichvar
85db8e3a9c ntp: assert size of exp_mono_root field 2023-09-26 15:02:06 +02:00
Miroslav Lichvar
05f4f79cbf ntp: rename exp1 extension field
Rename the exp1 extension field to exp_mono_root (monotonic timestamp +
root delay/dispersion) to better distinguish it from future experimental
extension fields.
2023-09-26 15:01:24 +02:00
Miroslav Lichvar
bf616eafa1 util: add conversion between intervals and NTP 64-bit format
This will be needed to save PTP correction in NTP timestamp format.
2023-09-26 15:00:06 +02:00
Miroslav Lichvar
e08a0ee668 doc: don't require same version for experimental features 2023-09-26 14:58:42 +02:00
Miroslav Lichvar
f2d7baa94f configure: prefer gnutls over nss and tomcrypt for hashing
Reorder the tests in the configure script to prefer gnutls over nss and
tomcrypt as its support includes AES-CMAC.
2023-09-12 10:36:23 +02:00
Miroslav Lichvar
558931524d configure: don't try AES-SIV-CMAC in nettle when disabled
Avoid confusing message when --without-nettle is specified.
2023-09-12 10:31:36 +02:00
Miroslav Lichvar
a74b63277a siv: add support for AES-GCM-SIV in gnutls
Add support for AES-128-GCM-SIV in the current development code of
gnutls. There doesn't seem to be an API to get the cipher's minimum and
maximum nonce length and it doesn't check for invalid lengths. Hardcode
and check the limits in chrony for now.
2023-09-12 10:31:36 +02:00
Miroslav Lichvar
aa8196328c conf: improve log message for failed additions in sources reload
Describe the error status in the log message when adding a source from
sourcedir failed.
2023-09-12 08:11:25 +02:00
Miroslav Lichvar
37deee7140 conf: cast subtraction operands in source comparison
Cast the values to int to not break the sorting in case they are changed
to unsigned types.
2023-09-12 08:03:23 +02:00
Miroslav Lichvar
7ff74d9efe conf: fix reloading modified sources specified by IP address
When reloading a modified source from sourcedir which is ordered before
the original source (e.g. maxpoll was decreased), the new source is
added before the original one is removed. If the source is specified by
IP address, the addition fails due to the conflict with the original
source. Sources specified by hostname don't conflict. They are resolved
later (repeatedly if the resolver provides only conflicting addresses).

Split the processing of sorted source lists into two phases, so all
modified sources are removed before they are added again to avoid the
conflict.

Reported-by: Thomas Lange <thomas@corelatus.se>
2023-09-12 08:02:36 +02:00
107 changed files with 3301 additions and 745 deletions

View file

@ -37,7 +37,7 @@ GETDATE_CFLAGS = @GETDATE_CFLAGS@
EXTRA_OBJS = @EXTRA_OBJS@
OBJS = array.o cmdparse.o conf.o local.o logging.o main.o memory.o quantiles.o \
OBJS = array.o cmdparse.o conf.o leapdb.o local.o logging.o main.o memory.o quantiles.o \
reference.o regress.o rtc.o samplefilt.o sched.o socket.o sources.o sourcestats.o \
stubs.o smooth.o sys.o sys_null.o tempcomp.o util.o $(EXTRA_OBJS)

49
NEWS
View file

@ -1,3 +1,52 @@
New in version 4.6.1
====================
Enhancements
------------
* Add ntsaeads directive to enable only selected AEAD algorithms for NTS
Workarounds
-----------
* Negotiate use of compliant NTS keys with AES-128-GCM-SIV AEAD algorithm
(by default the keys are generated differently than in RFC 8915 for
compatibility with chrony server and client versions 4.4, 4.5, and 4.6)
* Switch to compliant NTS keys if first response from server is NTS NAK
New in version 4.6
==================
Enhancements
------------
* Add activate option to local directive to set activation threshold
* Add ipv4 and ipv6 options to server/pool/peer directive
* Add kod option to ratelimit directive for server KoD RATE support
* Add leapseclist directive to read NIST/IERS leap-seconds.list file
* Add ptpdomain directive to set PTP domain for NTP over PTP
* Allow disabling pidfile
* Improve copy server option to accept unsynchronised status instantly
* Log one selection failure on start
* Add offset command to modify source offset correction
* Add timestamp sources to ntpdata report
Bug fixes
---------
* Fix crash on sources reload during initstepslew or RTC initialisation
* Fix source refreshment to not repeat failed name resolving attempts
New in version 4.5
==================
Enhancements
------------
* Add support for AES-GCM-SIV in GnuTLS
* Add support for corrections from PTP transparent clocks
* Add support for systemd socket activation
Bug fixes
---------
* Fix presend in interleaved mode
* Fix reloading of modified sources from sourcedir
New in version 4.4
==================

7
README
View file

@ -12,7 +12,7 @@ a time service to other computers in the network.
It is designed to perform well in a wide range of conditions, including
intermittent network connections, heavily congested networks, changing
temperatures (ordinary computer clocks are sensitive to temperature),
and systems that do not run continuosly, or run on a virtual machine.
and systems that do not run continuously, or run on a virtual machine.
Typical accuracy between two machines synchronised over the Internet is
within a few milliseconds; on a LAN, accuracy is typically in tens of
@ -75,6 +75,7 @@ Lonnie Abelbeck <lonnie@abelbeck.com>
Benny Lyne Amorsen <benny@amorsen.dk>
Andrew Bishop <amb@gedanken.demon.co.uk>
Vincent Blut <vincent.debian@free.fr>
Luca Boccassi <bluca@debian.org>
Stephan I. Boettcher <stephan@nevis1.columbia.edu>
David Bohman <debohman@gmail.com>
Goswin Brederlow <brederlo@informatik.uni-tuebingen.de>
@ -89,7 +90,9 @@ Christian Ehrhardt <christian.ehrhardt@canonical.com>
Paul Elliott <pelliott@io.com>
Robert Fairley <rfairley@redhat.com>
Stefan R. Filipek <srfilipek@gmail.com>
Andy Fiddaman <illumos@fiddaman.net>
Mike Fleetwood <mike@rockover.demon.co.uk>
Rob Gill <rrobgill@protonmail.com>
Alexander Gretencord <arutha@gmx.de>
Andrew Griffiths <agriffit@redhat.com>
Walter Haidinger <walter.haidinger@gmx.at>
@ -111,6 +114,7 @@ Paul Menzel <paulepanter@users.sourceforge.net>
Vladimir Michl <vladimir.michl@seznam.cz>
Victor Moroz <vim@prv.adlum.ru>
Kalle Olavi Niemitalo <tosi@stekt.oulu.fi>
Patrick Oppenlander <patrick.oppenlander@gmail.com>
Frank Otto <sandwichmacher@web.de>
Denny Page <dennypage@me.com>
Rupesh Patel <rupatel@redhat.com>
@ -125,6 +129,7 @@ Andreas Steinmetz <ast@domdv.de>
NAKAMURA Takumi <takumi@ps.sakura.ne.jp>
Timo Teras <timo.teras@iki.fi>
Bill Unruh <unruh@physics.ubc.ca>
Luke Valenta <lvalenta@cloudflare.com>
Stephen Wadeley <swadeley@redhat.com>
Bernhard Weiss <lisnablagh@web.de>
Wolfgang Weisselberg <weissel@netcologne.de>

28
candm.h
View file

@ -110,7 +110,9 @@
#define REQ_RELOAD_SOURCES 70
#define REQ_DOFFSET2 71
#define REQ_MODIFY_SELECTOPTS 72
#define N_REQUEST_TYPES 73
#define REQ_MODIFY_OFFSET 73
#define REQ_LOCAL3 74
#define N_REQUEST_TYPES 75
/* Structure used to exchange timespecs independent of time_t size */
typedef struct {
@ -236,6 +238,8 @@ typedef struct {
int32_t stratum;
Float distance;
int32_t orphan;
Float activate;
uint32_t reserved[2];
int32_t EOR;
} REQ_Local;
@ -277,7 +281,10 @@ typedef struct {
#define REQ_ADDSRC_BURST 0x100
#define REQ_ADDSRC_NTS 0x200
#define REQ_ADDSRC_COPY 0x400
#define REQ_ADDSRC_EF_EXP1 0x800
#define REQ_ADDSRC_EF_EXP_MONO_ROOT 0x800
#define REQ_ADDSRC_EF_EXP_NET_CORRECTION 0x1000
#define REQ_ADDSRC_IPV4 0x2000
#define REQ_ADDSRC_IPV6 0x4000
typedef struct {
uint32_t type;
@ -387,6 +394,13 @@ typedef struct {
int32_t EOR;
} REQ_Modify_SelectOpts;
typedef struct {
IPAddr address;
uint32_t ref_id;
Float new_offset;
int32_t EOR;
} REQ_Modify_Offset;
/* ================================================== */
#define PKT_TYPE_CMD_REQUEST 1
@ -494,6 +508,7 @@ typedef struct {
REQ_AuthData auth_data;
REQ_SelectData select_data;
REQ_Modify_SelectOpts modify_select_opts;
REQ_Modify_Offset modify_offset;
} data; /* Command specific parameters */
/* Padding used to prevent traffic amplification. It only defines the
@ -537,7 +552,8 @@ typedef struct {
#define RPY_SELECT_DATA 23
#define RPY_SERVER_STATS3 24
#define RPY_SERVER_STATS4 25
#define N_REPLY_TYPES 26
#define RPY_NTP_DATA2 26
#define N_REPLY_TYPES 27
/* Status codes */
#define STT_SUCCESS 0
@ -760,7 +776,11 @@ typedef struct {
uint32_t total_rx_count;
uint32_t total_valid_count;
uint32_t total_good_count;
uint32_t reserved[3];
uint32_t total_kernel_tx_ts;
uint32_t total_kernel_rx_ts;
uint32_t total_hw_tx_ts;
uint32_t total_hw_rx_ts;
uint32_t reserved[4];
int32_t EOR;
} RPY_NTPData;

View file

@ -4,7 +4,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Lonnie Abelbeck 2016, 2018
* Copyright (C) Miroslav Lichvar 2009-2023
* Copyright (C) Miroslav Lichvar 2009-2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -344,6 +344,24 @@ parse_source_address(char *word, IPAddr *address)
/* ================================================== */
static int
parse_source_address_or_refid(char *s, IPAddr *address, uint32_t *ref_id)
{
address->family = IPADDR_UNSPEC;
*ref_id = 0;
/* Don't allow hostnames to avoid conflicts with reference IDs */
if (UTI_StringToIdIP(s, address) || UTI_StringToIP(s, address))
return 1;
if (CPS_ParseRefid(s, ref_id) > 0)
return 1;
return 0;
}
/* ================================================== */
static int
read_mask_address(char *line, IPAddr *mask, IPAddr *address)
{
@ -737,22 +755,24 @@ static int
process_cmd_local(CMD_Request *msg, char *line)
{
int on_off, stratum = 0, orphan = 0;
double distance = 0.0;
double distance = 0.0, activate = 0.0;
if (!strcmp(line, "off")) {
on_off = 0;
} else if (CPS_ParseLocal(line, &stratum, &orphan, &distance)) {
} else if (CPS_ParseLocal(line, &stratum, &orphan, &distance, &activate)) {
on_off = 1;
} else {
LOG(LOGS_ERR, "Invalid syntax for local command");
return 0;
}
msg->command = htons(REQ_LOCAL2);
msg->command = htons(REQ_LOCAL3);
msg->data.local.on_off = htonl(on_off);
msg->data.local.stratum = htonl(stratum);
msg->data.local.distance = UTI_FloatHostToNetwork(distance);
msg->data.local.orphan = htonl(orphan);
msg->data.local.activate = UTI_FloatHostToNetwork(activate);
memset(msg->data.local.reserved, 0, sizeof (msg->data.local.reserved));
return 1;
}
@ -958,7 +978,12 @@ process_cmd_add_source(CMD_Request *msg, char *line)
(data.params.burst ? REQ_ADDSRC_BURST : 0) |
(data.params.nts ? REQ_ADDSRC_NTS : 0) |
(data.params.copy ? REQ_ADDSRC_COPY : 0) |
(data.params.ext_fields & NTP_EF_FLAG_EXP1 ? REQ_ADDSRC_EF_EXP1 : 0) |
(data.params.ext_fields & NTP_EF_FLAG_EXP_MONO_ROOT ?
REQ_ADDSRC_EF_EXP_MONO_ROOT : 0) |
(data.params.ext_fields & NTP_EF_FLAG_EXP_NET_CORRECTION ?
REQ_ADDSRC_EF_EXP_NET_CORRECTION : 0) |
(data.family == IPADDR_INET4 ? REQ_ADDSRC_IPV4 : 0) |
(data.family == IPADDR_INET6 ? REQ_ADDSRC_IPV6 : 0) |
convert_addsrc_sel_options(data.params.sel_options));
msg->data.ntp_source.filter_length = htonl(data.params.filter_length);
msg->data.ntp_source.cert_set = htonl(data.params.cert_set);
@ -1026,6 +1051,7 @@ give_help(void)
"selectopts <address|refid> <+|-options>\0Modify selection options\0"
"reselect\0Force reselecting synchronisation source\0"
"reselectdist <dist>\0Modify reselection distance\0"
"offset <address|refid> <offset>\0Modify offset correction\0"
"\0\0"
"NTP sources:\0\0"
"activity\0Check how many NTP sources are online/offline\0"
@ -1136,7 +1162,8 @@ command_name_generator(const char *text, int state)
"clients", "cmdaccheck", "cmdallow", "cmddeny", "cyclelogs", "delete",
"deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep",
"manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
"maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline",
"maxupdateskew", "minpoll", "minstratum", "ntpdata",
"offline", "offset", "online", "onoffline",
"polltarget", "quit", "refresh", "rekey", "reload", "reselect", "reselectdist", "reset",
"retries", "rtcdata", "selectdata", "selectopts", "serverstats", "settime",
"shutdown", "smoothing", "smoothtime", "sourcename", "sources", "sourcestats",
@ -2326,7 +2353,7 @@ process_cmd_ntpdata(char *line)
request.command = htons(REQ_NTP_DATA);
UTI_IPHostToNetwork(&remote_addr, &request.data.ntp_data.ip_addr);
if (!request_reply(&request, &reply, RPY_NTP_DATA, 0))
if (!request_reply(&request, &reply, RPY_NTP_DATA2, 0))
return 0;
UTI_IPNetworkToHost(&reply.data.ntp_data.remote_addr, &remote_addr);
@ -2362,7 +2389,11 @@ process_cmd_ntpdata(char *line)
"Total TX : %U\n"
"Total RX : %U\n"
"Total valid RX : %U\n"
"Total good RX : %U\n",
"Total good RX : %U\n"
"Total kernel TX : %U\n"
"Total kernel RX : %U\n"
"Total HW TX : %U\n"
"Total HW RX : %U\n",
UTI_IPToString(&remote_addr), UTI_IPToRefid(&remote_addr),
ntohs(reply.data.ntp_data.remote_port),
UTI_IPToString(&local_addr), UTI_IPToRefid(&local_addr),
@ -2390,6 +2421,10 @@ process_cmd_ntpdata(char *line)
ntohl(reply.data.ntp_data.total_rx_count),
ntohl(reply.data.ntp_data.total_valid_count),
ntohl(reply.data.ntp_data.total_good_count),
ntohl(reply.data.ntp_data.total_kernel_tx_ts),
ntohl(reply.data.ntp_data.total_kernel_rx_ts),
ntohl(reply.data.ntp_data.total_hw_tx_ts),
ntohl(reply.data.ntp_data.total_hw_rx_ts),
REPORT_END);
}
@ -2845,6 +2880,34 @@ process_cmd_activity(const char *line)
/* ================================================== */
static int
process_cmd_offset(CMD_Request *msg, char *line)
{
uint32_t ref_id;
IPAddr ip_addr;
double offset;
char *src;
src = line;
line = CPS_SplitWord(line);
if (!parse_source_address_or_refid(src, &ip_addr, &ref_id) ||
sscanf(line, "%lf", &offset) != 1) {
LOG(LOGS_ERR, "Invalid syntax for offset command");
return 0;
}
UTI_IPHostToNetwork(&ip_addr, &msg->data.modify_offset.address);
msg->data.modify_offset.ref_id = htonl(ref_id);
msg->data.modify_offset.new_offset = UTI_FloatHostToNetwork(offset);
msg->command = htons(REQ_MODIFY_OFFSET);
return 1;
}
/* ================================================== */
static int
process_cmd_reselectdist(CMD_Request *msg, char *line)
{
@ -2926,15 +2989,10 @@ process_cmd_selectopts(CMD_Request *msg, char *line)
src = line;
line = CPS_SplitWord(line);
ref_id = 0;
/* Don't allow hostnames to avoid conflicts with reference IDs */
if (!UTI_StringToIdIP(src, &ip_addr) && !UTI_StringToIP(src, &ip_addr)) {
ip_addr.family = IPADDR_UNSPEC;
if (CPS_ParseRefid(src, &ref_id) == 0) {
LOG(LOGS_ERR, "Invalid syntax for selectopts command");
return 0;
}
if (!parse_source_address_or_refid(src, &ip_addr, &ref_id)) {
LOG(LOGS_ERR, "Invalid syntax for selectopts command");
return 0;
}
mask = options = 0;
@ -3236,6 +3294,8 @@ process_line(char *line)
ret = process_cmd_ntpdata(line);
} else if (!strcmp(command, "offline")) {
do_normal_submit = process_cmd_offline(&tx_message, line);
} else if (!strcmp(command, "offset")) {
do_normal_submit = process_cmd_offset(&tx_message, line);
} else if (!strcmp(command, "online")) {
do_normal_submit = process_cmd_online(&tx_message, line);
} else if (!strcmp(command, "onoffline")) {
@ -3380,7 +3440,7 @@ static void
display_gpl(void)
{
printf("chrony version %s\n"
"Copyright (C) 1997-2003, 2007, 2009-2023 Richard P. Curnow and others\n"
"Copyright (C) 1997-2003, 2007, 2009-2024 Richard P. Curnow and others\n"
"chrony comes with ABSOLUTELY NO WARRANTY. This is free software, and\n"
"you are welcome to redistribute it under certain conditions. See the\n"
"GNU General Public License version 2 for details.\n\n",

View file

@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2009, 2015-2017, 2021
* Copyright (C) Miroslav Lichvar 2009, 2015-2017, 2021, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -117,6 +117,14 @@ static int token_shift[MAX_SERVICES];
static int leak_rate[MAX_SERVICES];
/* Rates at which responses requesting clients to reduce their rate
(e.g. NTP KoD RATE) are randomly allowed (in log2, but 0 means disabled) */
#define MIN_KOD_RATE 0
#define MAX_KOD_RATE 4
static int kod_rate[MAX_SERVICES];
/* Limit intervals in log2 */
static int limit_interval[MAX_SERVICES];
@ -354,18 +362,19 @@ set_bucket_params(int interval, int burst, uint16_t *max_tokens,
void
CLG_Initialise(void)
{
int i, interval, burst, lrate, slots2;
int i, interval, burst, lrate, krate, slots2;
for (i = 0; i < MAX_SERVICES; i++) {
max_tokens[i] = 0;
tokens_per_hit[i] = 0;
token_shift[i] = 0;
leak_rate[i] = 0;
kod_rate[i] = 0;
limit_interval[i] = MIN_LIMIT_INTERVAL;
switch (i) {
case CLG_NTP:
if (!CNF_GetNTPRateLimit(&interval, &burst, &lrate))
if (!CNF_GetNTPRateLimit(&interval, &burst, &lrate, &krate))
continue;
break;
case CLG_NTSKE:
@ -382,6 +391,7 @@ CLG_Initialise(void)
set_bucket_params(interval, burst, &max_tokens[i], &tokens_per_hit[i], &token_shift[i]);
leak_rate[i] = CLAMP(MIN_LEAK_RATE, lrate, MAX_LEAK_RATE);
kod_rate[i] = CLAMP(MIN_KOD_RATE, krate, MAX_KOD_RATE);
limit_interval[i] = CLAMP(MIN_LIMIT_INTERVAL, interval, MAX_LIMIT_INTERVAL);
}
@ -579,28 +589,28 @@ CLG_LogServiceAccess(CLG_Service service, IPAddr *client, struct timespec *now)
/* ================================================== */
static int
limit_response_random(int leak_rate)
limit_response_random(int rate)
{
static uint32_t rnd;
static int bits_left = 0;
int r;
if (bits_left < leak_rate) {
if (bits_left < rate) {
UTI_GetRandomBytes(&rnd, sizeof (rnd));
bits_left = 8 * sizeof (rnd);
}
/* Return zero on average once per 2^leak_rate */
r = rnd % (1U << leak_rate) ? 1 : 0;
rnd >>= leak_rate;
bits_left -= leak_rate;
/* Return zero on average once per 2^rate */
r = rnd % (1U << rate) ? 1 : 0;
rnd >>= rate;
bits_left -= rate;
return r;
}
/* ================================================== */
int
CLG_Limit
CLG_LimitServiceRate(CLG_Service service, int index)
{
Record *record;
@ -609,14 +619,14 @@ CLG_LimitServiceRate(CLG_Service service, int index)
check_service_number(service);
if (tokens_per_hit[service] == 0)
return 0;
return CLG_PASS;
record = ARR_GetElement(records, index);
record->drop_flags &= ~(1U << service);
if (record->tokens[service] >= tokens_per_hit[service]) {
record->tokens[service] -= tokens_per_hit[service];
return 0;
return CLG_PASS;
}
drop = limit_response_random(leak_rate[service]);
@ -632,14 +642,18 @@ CLG_LimitServiceRate(CLG_Service service, int index)
if (!drop) {
record->tokens[service] = 0;
return 0;
return CLG_PASS;
}
if (kod_rate[service] > 0 && !limit_response_random(kod_rate[service])) {
return CLG_KOD;
}
record->drop_flags |= 1U << service;
record->drops[service]++;
total_drops[service]++;
return 1;
return CLG_DROP;
}
/* ================================================== */

View file

@ -37,11 +37,17 @@ typedef enum {
CLG_CMDMON,
} CLG_Service;
typedef enum {
CLG_PASS = 0,
CLG_DROP,
CLG_KOD,
} CLG_Limit;
extern void CLG_Initialise(void);
extern void CLG_Finalise(void);
extern int CLG_GetClientIndex(IPAddr *client);
extern int CLG_LogServiceAccess(CLG_Service service, IPAddr *client, struct timespec *now);
extern int CLG_LimitServiceRate(CLG_Service service, int index);
extern CLG_Limit CLG_LimitServiceRate(CLG_Service service, int index);
extern void CLG_UpdateNtpStats(int auth, NTP_Timestamp_Source rx_ts_src,
NTP_Timestamp_Source tx_ts_src);
extern int CLG_GetNtpMinPoll(void);

View file

@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2009-2016, 2018-2023
* Copyright (C) Miroslav Lichvar 2009-2016, 2018-2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -145,6 +145,8 @@ static const char permissions[] = {
PERMIT_AUTH, /* RELOAD_SOURCES */
PERMIT_AUTH, /* DOFFSET2 */
PERMIT_AUTH, /* MODIFY_SELECTOPTS */
PERMIT_AUTH, /* MODIFY_OFFSET */
PERMIT_AUTH, /* LOCAL3 */
};
/* ================================================== */
@ -530,7 +532,8 @@ handle_local(CMD_Request *rx_message, CMD_Reply *tx_message)
if (ntohl(rx_message->data.local.on_off)) {
REF_EnableLocal(ntohl(rx_message->data.local.stratum),
UTI_FloatNetworkToHost(rx_message->data.local.distance),
ntohl(rx_message->data.local.orphan));
ntohl(rx_message->data.local.orphan),
UTI_FloatNetworkToHost(rx_message->data.local.activate));
} else {
REF_DisableLocal();
}
@ -720,9 +723,10 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
{
NTP_Source_Type type;
SourceParameters params;
int family, pool, port;
NSR_Status status;
uint32_t flags;
char *name;
int pool, port;
switch (ntohl(rx_message->data.ntp_source.type)) {
case REQ_ADDSRC_SERVER:
@ -750,6 +754,10 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
return;
}
flags = ntohl(rx_message->data.ntp_source.flags);
family = flags & REQ_ADDSRC_IPV4 ? IPADDR_INET4 :
flags & REQ_ADDSRC_IPV6 ? IPADDR_INET6 : IPADDR_UNSPEC;
port = ntohl(rx_message->data.ntp_source.port);
params.minpoll = ntohl(rx_message->data.ntp_source.minpoll);
params.maxpoll = ntohl(rx_message->data.ntp_source.maxpoll);
@ -775,19 +783,19 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
params.asymmetry = UTI_FloatNetworkToHost(rx_message->data.ntp_source.asymmetry);
params.offset = UTI_FloatNetworkToHost(rx_message->data.ntp_source.offset);
params.connectivity = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_ONLINE ?
SRC_ONLINE : SRC_OFFLINE;
params.auto_offline = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_AUTOOFFLINE ? 1 : 0;
params.iburst = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_IBURST ? 1 : 0;
params.interleaved = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_INTERLEAVED ? 1 : 0;
params.burst = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_BURST ? 1 : 0;
params.nts = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_NTS ? 1 : 0;
params.copy = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_COPY ? 1 : 0;
params.ext_fields =
ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_EF_EXP1 ? NTP_EF_FLAG_EXP1 : 0;
params.connectivity = flags & REQ_ADDSRC_ONLINE ? SRC_ONLINE : SRC_OFFLINE;
params.auto_offline = !!(flags & REQ_ADDSRC_AUTOOFFLINE);
params.iburst = !!(flags & REQ_ADDSRC_IBURST);
params.interleaved = !!(flags & REQ_ADDSRC_INTERLEAVED);
params.burst = !!(flags & REQ_ADDSRC_BURST);
params.nts = !!(flags & REQ_ADDSRC_NTS);
params.copy = !!(flags & REQ_ADDSRC_COPY);
params.ext_fields = (flags & REQ_ADDSRC_EF_EXP_MONO_ROOT ? NTP_EF_FLAG_EXP_MONO_ROOT : 0) |
(flags & REQ_ADDSRC_EF_EXP_NET_CORRECTION ?
NTP_EF_FLAG_EXP_NET_CORRECTION : 0);
params.sel_options = convert_addsrc_select_options(ntohl(rx_message->data.ntp_source.flags));
status = NSR_AddSourceByName(name, port, pool, type, &params, NULL);
status = NSR_AddSourceByName(name, family, port, pool, type, &params, NULL);
switch (status) {
case NSR_Success:
break;
@ -805,6 +813,8 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
tx_message->status = htons(STT_INVALIDNAME);
break;
case NSR_InvalidAF:
tx_message->status = htons(STT_INVALIDAF);
break;
case NSR_NoSuchSource:
assert(0);
break;
@ -1223,7 +1233,7 @@ handle_ntp_data(CMD_Request *rx_message, CMD_Reply *tx_message)
return;
}
tx_message->reply = htons(RPY_NTP_DATA);
tx_message->reply = htons(RPY_NTP_DATA2);
UTI_IPHostToNetwork(&report.remote_addr, &tx_message->data.ntp_data.remote_addr);
UTI_IPHostToNetwork(&report.local_addr, &tx_message->data.ntp_data.local_addr);
tx_message->data.ntp_data.remote_port = htons(report.remote_port);
@ -1251,6 +1261,10 @@ handle_ntp_data(CMD_Request *rx_message, CMD_Reply *tx_message)
tx_message->data.ntp_data.total_rx_count = htonl(report.total_rx_count);
tx_message->data.ntp_data.total_valid_count = htonl(report.total_valid_count);
tx_message->data.ntp_data.total_good_count = htonl(report.total_good_count);
tx_message->data.ntp_data.total_kernel_tx_ts = htonl(report.total_kernel_tx_ts);
tx_message->data.ntp_data.total_kernel_rx_ts = htonl(report.total_kernel_rx_ts);
tx_message->data.ntp_data.total_hw_tx_ts = htonl(report.total_hw_tx_ts);
tx_message->data.ntp_data.total_hw_rx_ts = htonl(report.total_hw_rx_ts);
memset(tx_message->data.ntp_data.reserved, 0xff, sizeof (tx_message->data.ntp_data.reserved));
}
@ -1407,6 +1421,24 @@ handle_modify_selectopts(CMD_Request *rx_message, CMD_Reply *tx_message)
tx_message->status = htons(STT_NOSUCHSOURCE);
}
/* ================================================== */
static void
handle_modify_offset(CMD_Request *rx_message, CMD_Reply *tx_message)
{
uint32_t ref_id;
IPAddr ip_addr;
double offset;
UTI_IPNetworkToHost(&rx_message->data.modify_offset.address, &ip_addr);
ref_id = ntohl(rx_message->data.modify_offset.ref_id);
offset = UTI_FloatNetworkToHost(rx_message->data.modify_offset.new_offset);
if ((ip_addr.family != IPADDR_UNSPEC && !NSR_ModifyOffset(&ip_addr, offset)) ||
(ip_addr.family == IPADDR_UNSPEC && !RCL_ModifyOffset(ref_id, offset)))
tx_message->status = htons(STT_NOSUCHSOURCE);
}
/* ================================================== */
/* Read a packet and process it */
@ -1481,9 +1513,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
/* Don't reply to all requests from hosts other than localhost if the rate
is excessive */
if (!localhost && log_index >= 0 && CLG_LimitServiceRate(CLG_CMDMON, log_index)) {
DEBUG_LOG("Command packet discarded to limit response rate");
return;
if (!localhost && log_index >= 0 &&
CLG_LimitServiceRate(CLG_CMDMON, log_index) != CLG_PASS) {
DEBUG_LOG("Command packet discarded to limit response rate");
return;
}
expected_length = PKL_CommandLength(&rx_message);
@ -1618,8 +1651,8 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
case REQ_SETTIME:
handle_settime(&rx_message, &tx_message);
break;
case REQ_LOCAL2:
case REQ_LOCAL3:
handle_local(&rx_message, &tx_message);
break;
@ -1807,6 +1840,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
handle_modify_selectopts(&rx_message, &tx_message);
break;
case REQ_MODIFY_OFFSET:
handle_modify_offset(&rx_message, &tx_message);
break;
default:
DEBUG_LOG("Unhandled command %d", rx_command);
tx_message.status = htons(STT_FAILED);

View file

@ -46,6 +46,7 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
uint32_t ef_type;
int n, sel_option;
src->family = IPADDR_UNSPEC;
src->port = SRC_DEFAULT_PORT;
src->params.minpoll = SRC_DEFAULT_MINPOLL;
src->params.maxpoll = SRC_DEFAULT_MAXPOLL;
@ -115,8 +116,11 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
if (sscanf(line, "%"SCNx32"%n", &ef_type, &n) != 1)
return 0;
switch (ef_type) {
case NTP_EF_EXP1:
src->params.ext_fields |= NTP_EF_FLAG_EXP1;
case NTP_EF_EXP_MONO_ROOT:
src->params.ext_fields |= NTP_EF_FLAG_EXP_MONO_ROOT;
break;
case NTP_EF_EXP_NET_CORRECTION:
src->params.ext_fields |= NTP_EF_FLAG_EXP_NET_CORRECTION;
break;
default:
return 0;
@ -124,6 +128,10 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
} else if (!strcasecmp(cmd, "filter")) {
if (sscanf(line, "%d%n", &src->params.filter_length, &n) != 1)
return 0;
} else if (!strcasecmp(cmd, "ipv4")) {
src->family = IPADDR_INET4;
} else if (!strcasecmp(cmd, "ipv6")) {
src->family = IPADDR_INET6;
} else if (!strcasecmp(cmd, "maxdelay")) {
if (sscanf(line, "%lf%n", &src->params.max_delay, &n) != 1)
return 0;
@ -288,13 +296,14 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits)
/* ================================================== */
int
CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance)
CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double *activate)
{
int n;
char *cmd;
*stratum = 10;
*distance = 1.0;
*activate = 0.0;
*orphan = 0;
while (*line) {
@ -311,6 +320,9 @@ CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance)
} else if (!strcasecmp(cmd, "distance")) {
if (sscanf(line, "%lf%n", distance, &n) != 1)
return 0;
} else if (!strcasecmp(cmd, "activate")) {
if (sscanf(line, "%lf%n", activate, &n) != 1)
return 0;
} else {
return 0;
}

View file

@ -32,6 +32,7 @@
typedef struct {
char *name;
int family;
int port;
SourceParameters params;
} CPS_NTP_Source;
@ -46,7 +47,7 @@ extern int CPS_GetSelectOption(char *option);
extern int CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits);
/* Parse a command to enable local reference */
extern int CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance);
extern int CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double *activate);
/* Remove extra white-space and comments */
extern void CPS_NormalizeLine(char *line);

208
conf.c
View file

@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2009-2017, 2020
* Copyright (C) Miroslav Lichvar 2009-2017, 2020, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -57,6 +57,7 @@ static int parse_string(char *line, char **result);
static int parse_int(char *line, int *result);
static int parse_double(char *line, double *result);
static int parse_null(char *line);
static int parse_ints(char *line, ARR_Instance array);
static void parse_allow_deny(char *line, ARR_Instance restrictions, int allow);
static void parse_authselectmode(char *);
@ -78,8 +79,9 @@ static void parse_makestep(char *);
static void parse_maxchange(char *);
static void parse_ntsserver(char *, ARR_Instance files);
static void parse_ntstrustedcerts(char *);
static void parse_pidfile(char *line);
static void parse_ratelimit(char *line, int *enabled, int *interval,
int *burst, int *leak);
int *burst, int *leak, int *kod);
static void parse_refclock(char *);
static void parse_smoothtime(char *);
static void parse_source(char *line, char *type, int fatal);
@ -129,6 +131,7 @@ static int enable_local=0;
static int local_stratum;
static int local_orphan;
static double local_distance;
static double local_activate;
/* Threshold (in seconds) - if absolute value of initial error is less
than this, slew instead of stepping */
@ -220,6 +223,7 @@ static int ntp_ratelimit_enabled = 0;
static int ntp_ratelimit_interval = 3;
static int ntp_ratelimit_burst = 8;
static int ntp_ratelimit_leak = 2;
static int ntp_ratelimit_kod = 0;
static int nts_ratelimit_enabled = 0;
static int nts_ratelimit_interval = 6;
static int nts_ratelimit_burst = 8;
@ -249,13 +253,19 @@ static REF_LeapMode leapsec_mode = REF_LeapModeSystem;
/* Name of a system timezone containing leap seconds occuring at midnight */
static char *leapsec_tz = NULL;
/* File name of leap seconds list, usually /usr/share/zoneinfo/leap-seconds.list */
static char *leapsec_list = NULL;
/* Name of the user to which will be dropped root privileges. */
static char *user;
/* Address refresh interval */
static int refresh = 1209600; /* 2 weeks */
#define DEFAULT_NTS_AEADS "30 15"
/* NTS server and client configuration */
static ARR_Instance nts_aeads; /* array of int */
static char *nts_dump_dir = NULL;
static char *nts_ntp_server = NULL;
static ARR_Instance nts_server_cert_files; /* array of (char *) */
@ -282,19 +292,23 @@ static double hwts_timeout = 0.001;
/* PTP event port (disabled by default) */
static int ptp_port = 0;
/* PTP domain number of NTP-over-PTP messages */
static int ptp_domain = 123;
typedef struct {
NTP_Source_Type type;
int pool;
CPS_NTP_Source params;
NSR_Status status;
uint32_t conf_id;
} NTP_Source;
/* Array of NTP_Source */
static ARR_Instance ntp_sources;
/* Array of (char *) */
static ARR_Instance ntp_source_dirs;
/* Array of uint32_t corresponding to ntp_sources (for sourcedirs reload) */
static ARR_Instance ntp_source_ids;
/* Flag indicating ntp_sources is used for sourcedirs after config load */
static int conf_ntp_sources_added = 0;
/* Array of RefclockParameters */
static ARR_Instance refclock_sources;
@ -387,6 +401,8 @@ check_number_of_args(char *line, int num)
void
CNF_Initialise(int r, int client_only)
{
char buf[10];
restarted = r;
hwts_interfaces = ARR_CreateInstance(sizeof (CNF_HwTsInterface));
@ -394,13 +410,15 @@ CNF_Initialise(int r, int client_only)
init_sources = ARR_CreateInstance(sizeof (IPAddr));
ntp_sources = ARR_CreateInstance(sizeof (NTP_Source));
ntp_source_dirs = ARR_CreateInstance(sizeof (char *));
ntp_source_ids = ARR_CreateInstance(sizeof (uint32_t));
refclock_sources = ARR_CreateInstance(sizeof (RefclockParameters));
broadcasts = ARR_CreateInstance(sizeof (NTP_Broadcast_Destination));
ntp_restrictions = ARR_CreateInstance(sizeof (AllowDeny));
cmd_restrictions = ARR_CreateInstance(sizeof (AllowDeny));
nts_aeads = ARR_CreateInstance(sizeof (int));
snprintf(buf, sizeof (buf), DEFAULT_NTS_AEADS);
parse_ints(buf, nts_aeads);
nts_server_cert_files = ARR_CreateInstance(sizeof (char *));
nts_server_key_files = ARR_CreateInstance(sizeof (char *));
nts_trusted_certs_paths = ARR_CreateInstance(sizeof (char *));
@ -454,13 +472,13 @@ CNF_Finalise(void)
ARR_DestroyInstance(init_sources);
ARR_DestroyInstance(ntp_sources);
ARR_DestroyInstance(ntp_source_dirs);
ARR_DestroyInstance(ntp_source_ids);
ARR_DestroyInstance(refclock_sources);
ARR_DestroyInstance(broadcasts);
ARR_DestroyInstance(ntp_restrictions);
ARR_DestroyInstance(cmd_restrictions);
ARR_DestroyInstance(nts_aeads);
ARR_DestroyInstance(nts_server_cert_files);
ARR_DestroyInstance(nts_server_key_files);
ARR_DestroyInstance(nts_trusted_certs_paths);
@ -471,6 +489,7 @@ CNF_Finalise(void)
Free(hwclock_file);
Free(keys_file);
Free(leapsec_tz);
Free(leapsec_list);
Free(logdir);
Free(bind_ntp_iface);
Free(bind_acq_iface);
@ -585,7 +604,7 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_int(p, &cmd_port);
} else if (!strcasecmp(command, "cmdratelimit")) {
parse_ratelimit(p, &cmd_ratelimit_enabled, &cmd_ratelimit_interval,
&cmd_ratelimit_burst, &cmd_ratelimit_leak);
&cmd_ratelimit_burst, &cmd_ratelimit_leak, NULL);
} else if (!strcasecmp(command, "combinelimit")) {
parse_double(p, &combine_limit);
} else if (!strcasecmp(command, "confdir")) {
@ -620,6 +639,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_leapsecmode(p);
} else if (!strcasecmp(command, "leapsectz")) {
parse_string(p, &leapsec_tz);
} else if (!strcasecmp(command, "leapseclist")) {
parse_string(p, &leapsec_list);
} else if (!strcasecmp(command, "local")) {
parse_local(p);
} else if (!strcasecmp(command, "lock_all")) {
@ -668,9 +689,11 @@ CNF_ParseLine(const char *filename, int number, char *line)
no_system_cert = parse_null(p);
} else if (!strcasecmp(command, "ntpsigndsocket")) {
parse_string(p, &ntp_signd_socket);
} else if (!strcasecmp(command, "ntsaeads")) {
parse_ints(p, nts_aeads);
} else if (!strcasecmp(command, "ntsratelimit")) {
parse_ratelimit(p, &nts_ratelimit_enabled, &nts_ratelimit_interval,
&nts_ratelimit_burst, &nts_ratelimit_leak);
&nts_ratelimit_burst, &nts_ratelimit_leak, NULL);
} else if (!strcasecmp(command, "ntscachedir") ||
!strcasecmp(command, "ntsdumpdir")) {
parse_string(p, &nts_dump_dir);
@ -693,16 +716,18 @@ CNF_ParseLine(const char *filename, int number, char *line)
} else if (!strcasecmp(command, "peer")) {
parse_source(p, command, 1);
} else if (!strcasecmp(command, "pidfile")) {
parse_string(p, &pidfile);
parse_pidfile(p);
} else if (!strcasecmp(command, "pool")) {
parse_source(p, command, 1);
} else if (!strcasecmp(command, "port")) {
parse_int(p, &ntp_port);
} else if (!strcasecmp(command, "ptpdomain")) {
parse_int(p, &ptp_domain);
} else if (!strcasecmp(command, "ptpport")) {
parse_int(p, &ptp_port);
} else if (!strcasecmp(command, "ratelimit")) {
parse_ratelimit(p, &ntp_ratelimit_enabled, &ntp_ratelimit_interval,
&ntp_ratelimit_burst, &ntp_ratelimit_leak);
&ntp_ratelimit_burst, &ntp_ratelimit_leak, &ntp_ratelimit_kod);
} else if (!strcasecmp(command, "refclock")) {
parse_refclock(p);
} else if (!strcasecmp(command, "refresh")) {
@ -793,6 +818,25 @@ parse_null(char *line)
/* ================================================== */
static int
parse_ints(char *line, ARR_Instance array)
{
char *s;
int v;
ARR_SetSize(array, 0);
while (*line) {
s = line;
line = CPS_SplitWord(line);
parse_int(s, &v);
ARR_AppendElement(array, &v);
}
return 1;
}
/* ================================================== */
static void
parse_source(char *line, char *type, int fatal)
{
@ -823,6 +867,9 @@ parse_source(char *line, char *type, int fatal)
}
source.params.name = Strdup(source.params.name);
source.status = NSR_NoSuchSource;
source.conf_id = 0;
ARR_AppendElement(ntp_sources, &source);
}
@ -840,7 +887,7 @@ parse_sourcedir(char *line)
/* ================================================== */
static void
parse_ratelimit(char *line, int *enabled, int *interval, int *burst, int *leak)
parse_ratelimit(char *line, int *enabled, int *interval, int *burst, int *leak, int *kod)
{
int n, val;
char *opt;
@ -861,6 +908,8 @@ parse_ratelimit(char *line, int *enabled, int *interval, int *burst, int *leak)
*burst = val;
else if (!strcasecmp(opt, "leak"))
*leak = val;
else if (!strcasecmp(opt, "kod") && kod)
*kod = val;
else
command_parse_error();
}
@ -1058,7 +1107,7 @@ parse_log(char *line)
static void
parse_local(char *line)
{
if (!CPS_ParseLocal(line, &local_stratum, &local_orphan, &local_distance))
if (!CPS_ParseLocal(line, &local_stratum, &local_orphan, &local_distance, &local_activate))
command_parse_error();
enable_local = 1;
}
@ -1512,6 +1561,20 @@ parse_hwtimestamp(char *line)
/* ================================================== */
static void
parse_pidfile(char *line)
{
parse_string(line, &pidfile);
/* / disables the PID file handling */
if (strcmp(pidfile, "/") == 0) {
Free(pidfile);
pidfile = NULL;
}
}
/* ================================================== */
static const char *
get_basename(const char *path)
{
@ -1660,11 +1723,13 @@ compare_sources(const void *a, const void *b)
return 1;
if ((d = strcmp(sa->params.name, sb->params.name)) != 0)
return d;
if ((d = (int)(sa->type) - (int)(sb->type)) != 0)
if ((d = (int)sa->type - (int)sb->type) != 0)
return d;
if ((d = sa->pool - sb->pool) != 0)
if ((d = (int)sa->pool - (int)sb->pool) != 0)
return d;
if ((d = sa->params.port - sb->params.port) != 0)
if ((d = (int)sa->params.family - (int)sb->params.family) != 0)
return d;
if ((d = (int)sa->params.port - (int)sb->params.port) != 0)
return d;
return memcmp(&sa->params.params, &sb->params.params, sizeof (sa->params.params));
}
@ -1676,18 +1741,17 @@ reload_source_dirs(void)
{
NTP_Source *prev_sources, *new_sources, *source;
unsigned int i, j, prev_size, new_size, unresolved;
uint32_t *prev_ids, *new_ids;
char buf[MAX_LINE_LENGTH];
NSR_Status s;
int d;
int d, pass;
prev_size = ARR_GetSize(ntp_source_ids);
if (prev_size > 0 && ARR_GetSize(ntp_sources) != prev_size)
assert(0);
/* Ignore reload command before adding configured sources */
if (!conf_ntp_sources_added)
return;
/* Save the current sources and their configuration IDs */
prev_ids = MallocArray(uint32_t, prev_size);
memcpy(prev_ids, ARR_GetElements(ntp_source_ids), prev_size * sizeof (prev_ids[0]));
prev_size = ARR_GetSize(ntp_sources);
/* Save the current sources */
prev_sources = MallocArray(NTP_Source, prev_size);
memcpy(prev_sources, ARR_GetElements(ntp_sources), prev_size * sizeof (prev_sources[0]));
@ -1705,44 +1769,45 @@ reload_source_dirs(void)
new_size = ARR_GetSize(ntp_sources);
new_sources = ARR_GetElements(ntp_sources);
ARR_SetSize(ntp_source_ids, new_size);
new_ids = ARR_GetElements(ntp_source_ids);
unresolved = 0;
LOG_SetContext(LOGC_SourceFile);
qsort(new_sources, new_size, sizeof (new_sources[0]), compare_sources);
for (i = j = 0; i < prev_size || j < new_size; ) {
if (i < prev_size && j < new_size)
d = compare_sources(&prev_sources[i], &new_sources[j]);
else
d = i < prev_size ? -1 : 1;
for (pass = 0; pass < 2; pass++) {
for (i = j = 0; i < prev_size || j < new_size; i += d <= 0, j += d >= 0) {
if (i < prev_size && j < new_size)
d = compare_sources(&prev_sources[i], &new_sources[j]);
else
d = i < prev_size ? -1 : 1;
if (d < 0) {
/* Remove the missing source */
if (prev_sources[i].params.name[0] != '\0')
NSR_RemoveSourcesById(prev_ids[i]);
i++;
} else if (d > 0) {
/* Add a newly configured source */
source = &new_sources[j];
s = NSR_AddSourceByName(source->params.name, source->params.port, source->pool,
source->type, &source->params.params, &new_ids[j]);
if (s == NSR_UnresolvedName) {
unresolved++;
} else if (s != NSR_Success) {
LOG(LOGS_ERR, "Could not add source %s", source->params.name);
/* Mark the source as not present */
source->params.name[0] = '\0';
/* Remove missing sources before adding others to avoid conflicts */
if (pass == 0 && d < 0 && prev_sources[i].status == NSR_Success) {
NSR_RemoveSourcesById(prev_sources[i].conf_id);
}
/* Add new sources and sources that could not be added before */
if (pass == 1 && (d > 0 || (d == 0 && prev_sources[i].status != NSR_Success))) {
source = &new_sources[j];
s = NSR_AddSourceByName(source->params.name, source->params.family, source->params.port,
source->pool, source->type, &source->params.params,
&source->conf_id);
source->status = s;
if (s == NSR_UnresolvedName) {
unresolved++;
} else if (s != NSR_Success && (d > 0 || s != prev_sources[i].status)) {
LOG(LOGS_ERR, "Could not add source %s : %s",
source->params.name, NSR_StatusToString(s));
}
}
/* Keep unchanged sources */
if (pass == 1 && d == 0) {
new_sources[j].status = prev_sources[i].status;
new_sources[j].conf_id = prev_sources[i].conf_id;
}
j++;
} else {
/* Keep the existing source */
new_ids[j] = prev_ids[i];
i++, j++;
}
}
@ -1751,7 +1816,6 @@ reload_source_dirs(void)
for (i = 0; i < prev_size; i++)
Free(prev_sources[i].params.name);
Free(prev_sources);
Free(prev_ids);
if (unresolved > 0)
NSR_ResolveSources();
@ -1840,15 +1904,17 @@ CNF_AddSources(void)
for (i = 0; i < ARR_GetSize(ntp_sources); i++) {
source = (NTP_Source *)ARR_GetElement(ntp_sources, i);
s = NSR_AddSourceByName(source->params.name, source->params.port, source->pool,
source->type, &source->params.params, NULL);
s = NSR_AddSourceByName(source->params.name, source->params.family, source->params.port,
source->pool, source->type, &source->params.params, NULL);
if (s != NSR_Success && s != NSR_UnresolvedName)
LOG(LOGS_ERR, "Could not add source %s", source->params.name);
Free(source->params.name);
}
/* The arrays will be used for sourcedir (re)loading */
ARR_SetSize(ntp_sources, 0);
conf_ntp_sources_added = 1;
reload_source_dirs();
}
@ -2146,12 +2212,13 @@ CNF_GetCommandPort(void) {
/* ================================================== */
int
CNF_AllowLocalReference(int *stratum, int *orphan, double *distance)
CNF_AllowLocalReference(int *stratum, int *orphan, double *distance, double *activate)
{
if (enable_local) {
*stratum = local_stratum;
*orphan = local_orphan;
*distance = local_distance;
*activate = local_activate;
return 1;
} else {
return 0;
@ -2384,6 +2451,14 @@ CNF_GetLeapSecTimezone(void)
/* ================================================== */
char *
CNF_GetLeapSecList(void)
{
return leapsec_list;
}
/* ================================================== */
int
CNF_GetSchedPriority(void)
{
@ -2400,11 +2475,12 @@ CNF_GetLockMemory(void)
/* ================================================== */
int CNF_GetNTPRateLimit(int *interval, int *burst, int *leak)
int CNF_GetNTPRateLimit(int *interval, int *burst, int *leak, int *kod)
{
*interval = ntp_ratelimit_interval;
*burst = ntp_ratelimit_burst;
*leak = ntp_ratelimit_leak;
*kod = ntp_ratelimit_kod;
return ntp_ratelimit_enabled;
}
@ -2538,6 +2614,14 @@ CNF_GetPtpPort(void)
/* ================================================== */
int
CNF_GetPtpDomain(void)
{
return ptp_domain;
}
/* ================================================== */
int
CNF_GetRefresh(void)
{
@ -2546,6 +2630,14 @@ CNF_GetRefresh(void)
/* ================================================== */
ARR_Instance
CNF_GetNtsAeads(void)
{
return nts_aeads;
}
/* ================================================== */
char *
CNF_GetNtsDumpDir(void)
{

8
conf.h
View file

@ -29,6 +29,7 @@
#define GOT_CONF_H
#include "addressing.h"
#include "array.h"
#include "reference.h"
#include "sources.h"
@ -91,6 +92,7 @@ extern char *CNF_GetNtpSigndSocket(void);
extern char *CNF_GetPidFile(void);
extern REF_LeapMode CNF_GetLeapSecMode(void);
extern char *CNF_GetLeapSecTimezone(void);
extern char *CNF_GetLeapSecList(void);
/* Value returned in ppm, as read from file */
extern double CNF_GetMaxUpdateSkew(void);
@ -107,14 +109,14 @@ extern double CNF_GetReselectDistance(void);
extern double CNF_GetStratumWeight(void);
extern double CNF_GetCombineLimit(void);
extern int CNF_AllowLocalReference(int *stratum, int *orphan, double *distance);
extern int CNF_AllowLocalReference(int *stratum, int *orphan, double *distance, double *activate);
extern void CNF_SetupAccessRestrictions(void);
extern int CNF_GetSchedPriority(void);
extern int CNF_GetLockMemory(void);
extern int CNF_GetNTPRateLimit(int *interval, int *burst, int *leak);
extern int CNF_GetNTPRateLimit(int *interval, int *burst, int *leak, int *kod);
extern int CNF_GetNtsRateLimit(int *interval, int *burst, int *leak);
extern int CNF_GetCommandRateLimit(int *interval, int *burst, int *leak);
extern void CNF_GetSmooth(double *max_freq, double *max_wander, int *leap_only);
@ -158,9 +160,11 @@ extern int CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface);
extern double CNF_GetHwTsTimeout(void);
extern int CNF_GetPtpPort(void);
extern int CNF_GetPtpDomain(void);
extern int CNF_GetRefresh(void);
extern ARR_Instance CNF_GetNtsAeads(void);
extern char *CNF_GetNtsDumpDir(void);
extern char *CNF_GetNtsNtpServer(void);
extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***keys);

55
configure vendored
View file

@ -111,10 +111,10 @@ For better control, use the options below.
--without-editline Don't use editline even if it is available
--disable-sechash Disable support for hashes other than MD5
--without-nettle Don't use nettle even if it is available
--without-gnutls Don't use gnutls even if it is available
--without-nss Don't use NSS even if it is available
--without-tomcrypt Don't use libtomcrypt even if it is available
--disable-nts Disable NTS support
--without-gnutls Don't use gnutls even if it is available
--disable-cmdmon Disable command and monitoring support
--disable-ntp Disable NTP support
--disable-refclock Disable reference clock support
@ -926,6 +926,28 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nettle = "1" ];
fi
fi
if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_gnutls = "1" ]; then
test_cflags="`pkg_config --cflags gnutls`"
test_link="`pkg_config --libs gnutls`"
if test_code 'gnutls' 'gnutls/crypto.h' \
"$test_cflags" "$test_link" '
return gnutls_hash((void *)1, (void *)2, 1);'
then
HASH_OBJ="hash_gnutls.o"
HASH_LINK="$test_link"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
add_def FEAT_SECHASH
if test_code 'CMAC in gnutls' 'gnutls/crypto.h' "$test_cflags" "$test_link" \
'return gnutls_hmac_init((void *)1, GNUTLS_MAC_AES_CMAC_128, (void *)2, 0);'
then
add_def HAVE_CMAC
EXTRA_OBJECTS="$EXTRA_OBJECTS cmac_gnutls.o"
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS cmac_gnutls.o"
fi
fi
fi
if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nss = "1" ]; then
test_cflags="`pkg_config --cflags nss`"
test_link="`pkg_config --libs-only-L nss` -lfreebl3 -lnssutil3"
@ -951,28 +973,6 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_tomcrypt = "1" ]
fi
fi
if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_gnutls = "1" ]; then
test_cflags="`pkg_config --cflags gnutls`"
test_link="`pkg_config --libs gnutls`"
if test_code 'gnutls' 'gnutls/crypto.h' \
"$test_cflags" "$test_link" '
return gnutls_hash((void *)1, (void *)2, 1);'
then
HASH_OBJ="hash_gnutls.o"
HASH_LINK="$test_link"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
add_def FEAT_SECHASH
if test_code 'CMAC in gnutls' 'gnutls/crypto.h' "$test_cflags" "$test_link" \
'return gnutls_hmac_init((void *)1, GNUTLS_MAC_AES_CMAC_128, (void *)2, 0);'
then
add_def HAVE_CMAC
EXTRA_OBJECTS="$EXTRA_OBJECTS cmac_gnutls.o"
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS cmac_gnutls.o"
fi
fi
fi
EXTRA_OBJECTS="$EXTRA_OBJECTS $HASH_OBJ"
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS $HASH_OBJ"
LIBS="$LIBS $HASH_LINK"
@ -991,7 +991,7 @@ if [ $feat_ntp = "1" ] && [ $feat_nts = "1" ] && [ $try_gnutls = "1" ]; then
gnutls_priority_init2((void *)1, "", NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND) +
gnutls_prf_rfc5705((void *)1, 0, "", 0, "", 16, (void *)2);'
then
if test_code 'AES-SIV-CMAC in nettle' \
if [ $try_nettle = "1" ] && test_code 'AES-SIV-CMAC in nettle' \
'nettle/siv-cmac.h' "" "$LIBS" \
'siv_cmac_aes128_set_key((void *)1, (void *)2);'
then
@ -1012,6 +1012,13 @@ if [ $feat_ntp = "1" ] && [ $feat_nts = "1" ] && [ $try_gnutls = "1" ]; then
then
EXTRA_OBJECTS="$EXTRA_OBJECTS siv_gnutls.o"
add_def HAVE_SIV
if [ $try_aes_gcm_siv = "1" ] && test_code 'AES-GCM-SIV in gnutls' \
'gnutls/crypto.h' "$test_cflags" "$test_link $LIBS" '
return gnutls_aead_cipher_init((void *)1, GNUTLS_CIPHER_AES_128_SIV_GCM,
(void *)2);'
then
add_def HAVE_GNUTLS_SIV_GCM
fi
if test_code 'gnutls_aead_cipher_set_key()' 'gnutls/crypto.h' \
"$test_cflags" "$test_link $LIBS" '
return gnutls_aead_cipher_set_key((void *)1, (void *)2);'

View file

@ -3,7 +3,7 @@
// Copyright (C) Richard P. Curnow 1997-2003
// Copyright (C) Stephen Wadeley 2016
// Copyright (C) Bryan Christianson 2017
// Copyright (C) Miroslav Lichvar 2009-2023
// Copyright (C) Miroslav Lichvar 2009-2024
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as
@ -126,6 +126,15 @@ mechanism. Unlike with the *key* option, the server and client do not need to
share a key in a key file. NTS has a Key Establishment (NTS-KE) protocol using
the Transport Layer Security (TLS) protocol to get the keys and cookies
required by NTS for authentication of NTP packets.
+
With this option, the hostname specified in the server or pool directive is the
NTS-KE server or pool of NTS-KE servers respectively. The NTP server usually
runs on the same host, but it can be separated from the NTS-KE server (the
hostname or address of the NTP server is provided to the client by the NTS-KE
server).
+
The NTS-KE server can be specified by IP address if it is included in the
server's certificate as a Subject Alternative Name (SAN).
*certset* _ID_:::
This option specifies which set of trusted certificates should be used to verify
the server's certificate when the *nts* option is enabled. Sets of certificates
@ -220,7 +229,7 @@ when disconnecting the network link. (It will still be necessary to use the
<<chronyc.adoc#online,*online*>> command when the link has been established, to
enable measurements to start.)
*prefer*:::
Prefer this source over sources without the *prefer* option.
Prefer this source over other selectable sources without the *prefer* option.
*noselect*:::
Never select this source. This is particularly useful for monitoring.
*trust*:::
@ -322,16 +331,33 @@ server implementations do not respond to requests containing an unknown
extension field (*chronyd* as a server responded to such requests since
version 2.0).
+
The following extension field can be enabled by this option:
This option can be used multiple times to enable multiple extension fields.
+
The following extension fields are supported:
+
_F323_::::
This is an experimental extension field for some improvements that were
An experimental extension field to enable several improvements that were
proposed for the next version of the NTP protocol (NTPv5). The field contains
root delay and dispersion in higher resolution and a monotonic receive
timestamp, which enables a frequency transfer between the server and client. It
can significantly improve stability of the synchronization. Generally, it
should be expected to work only between servers and clients running the same
version of *chronyd*.
timestamp, which enables a frequency transfer between the server and client to
significantly improve stability of the synchronisation. This field should be
enabled only for servers known to be running *chronyd* version 4.2 or later.
_F324_::::
An experimental extension field to enable the use of the Precision Time
Protocol (PTP) correction field in NTP-over-PTP messages updated by one-step
end-to-end transparent clocks in network switches and routers to significantly
improve accuracy and stability of the synchronisation. NTP-over-PTP can be
enabled by the <<ptpport,*ptpport*>> directive and setting the *port* option to
the PTP port. The corrections are applied only to NTP measurements with HW
timestamps (enabled by the <<hwtimestamp,*hwtimestamp*>> directive). This
field should be enabled only for servers known to be running *chronyd* version
4.5 or later.
*ipv4*:::
*ipv6*:::
These options force *chronyd* to use only IPv4 or IPv6 addresses respectively
for this source. They do not override the *-4* or *-6* option on the *chronyd*
command line.
{blank}:::
[[pool]]*pool* _name_ [_option_]...::
@ -418,7 +444,7 @@ error. *chronyd* then enters its normal operating mode.
An example of the use of the directive is:
+
----
initstepslew 30 foo.example.net bar.example.net baz.example.net
initstepslew 30 ntp1.example.net ntp2.example.net ntp3.example.net
----
+
where 3 NTP servers are used to make the measurement. The _30_ indicates that
@ -644,7 +670,7 @@ default is 64. With drivers that perform their own polling (PPS, PHC, SHM), the
maximum value is adjusted to the number of driver polls per source poll, i.e.
2^(_poll_ - _dpoll_).
*prefer*:::
Prefer this source over sources without the prefer option.
Prefer this source over other selectable sources without the *prefer* option.
*noselect*:::
Never select this source. This is useful for monitoring or with sources which
are not very accurate, but are locked with a PPS refclock.
@ -663,9 +689,10 @@ trusted and required source.
*tai*:::
This option indicates that the reference clock keeps time in TAI instead of UTC
and that *chronyd* should correct its offset by the current TAI-UTC offset. The
<<leapsectz,*leapsectz*>> directive must be used with this option and the
database must be kept up to date in order for this correction to work as
expected. This option does not make sense with PPS refclocks.
<<leapsectz,*leapsectz*>> or <<leapseclist,*leapseclist*>> directive must be
used with this option and the database must be kept up to date in order for
this correction to work as expected. This option does not make sense with PPS
refclocks.
*local*:::
This option specifies that the reference clock is an unsynchronised clock which
is more stable than the system clock (e.g. TCXO, OCXO, or atomic clock) and
@ -806,6 +833,34 @@ changes in the frequency and offset of the clock. The offsets in the
<<chronyc.adoc#sourcestats,*sourcestats*>> reports (and the _tracking.log_ and
_statistics.log_ files) may be smaller than the actual offsets.
[[ntsaeads1]]*ntsaeads* _ID_...::
This directive specifies a list of IDs of Authenticated Encryption with
Associated Data (AEAD) algorithms enabled for NTS authentication of NTP
messages. The algorithms are specified in decreasing order of priority.
Algorithms that are not supported by the installed version of the crypto
library (Nettle, GnuTLS) are ignored.
+
The following IDs are supported:
+
* 15: AES-SIV-CMAC-256
* 30: AES-128-GCM-SIV
{blank}::
+
The default list of IDs is _30 15_. AES-128-GCM-SIV is preferred over
AES-SIV-CMAC-256 for shorter keys, which makes NTS cookies shorter and improves
reliability of NTS in networks that block or limit rate of longer NTP messages.
+
The ID of the used algorithm is reported for each server by the
<<chronyc.adoc#authdata,*authdata*>> command.
+
An example of the directive is:
+
----
ntsaeads 15
----
+
This list is used also by the <<ntsaeads2,NTS server>>.
[[ntsdumpdir1]]*ntsdumpdir* _directory_::
This directive specifies a directory for the client to save NTS cookies it
received from the server in order to avoid making an NTS-KE request when
@ -833,9 +888,10 @@ sources using NTS, otherwise the source with a longer polling interval will
refresh the keys on each poll and no NTP packets will be exchanged.
[[ntstrustedcerts]]*ntstrustedcerts* [_set-ID_] _file_|_directory_::
This directive specifies a file or directory containing certificates (in the
PEM format) of trusted certificate authorities (CA) which can be used to
verify certificates of NTS servers.
This directive specifies a file or directory containing trusted certificates
(in the PEM format) which are needed to verify certificates of NTS-KE servers,
e.g. certificates of trusted certificate authorities (CA) or self-signed
certificates of the servers.
+
The optional _set-ID_ argument is a number in the range 0 through 2^32-1, which
selects the set of certificates where certificates from the specified file
@ -855,10 +911,10 @@ they change (e.g. after a renewal).
An example is:
+
----
ntstrustedcerts /etc/pki/nts/foo.crt
ntstrustedcerts 1 /etc/pki/nts/bar.crt
ntstrustedcerts 1 /etc/pki/nts/baz.crt
ntstrustedcerts 2 /etc/pki/nts/qux.crt
ntstrustedcerts /etc/pki/nts/ca1.example.net.crt
ntstrustedcerts 1 /etc/pki/nts/ca2.example.net.crt
ntstrustedcerts 1 /etc/pki/nts/ca3.example.net.crt
ntstrustedcerts 2 /etc/pki/nts/ntp2.example.net.crt
----
[[nosystemcert]]*nosystemcert*::
@ -955,9 +1011,9 @@ before 4.0.
As an example, the following configuration using the default *mix* mode:
+
----
server foo.example.net nts
server bar.example.net nts
server baz.example.net
server ntp1.example.net nts
server ntp2.example.net nts
server ntp3.example.net
refclock SOCK /var/run/chrony.ttyS0.sock
----
+
@ -965,9 +1021,9 @@ is equivalent to the following configuration using the *ignore* mode:
+
----
authselectmode ignore
server foo.example.net nts require trust
server bar.example.net nts require trust
server baz.example.net
server ntp1.example.net nts require trust
server ntp2.example.net nts require trust
server ntp3.example.net
refclock /var/run/chrony.ttyS0.sock require trust
----
@ -1251,6 +1307,19 @@ $ TZ=right/UTC date -d 'Dec 31 2008 23:59:60'
Wed Dec 31 23:59:60 UTC 2008
----
[[leapseclist]]*leapseclist* _file_::
This directive specifies the path to a file containing a list of leap seconds
and TAI-UTC offsets in NIST/IERS format. It is recommended to use
the file _leap-seconds.list_ usually included with the system timezone
database. The behaviour of this directive is otherwise equivalent to
<<leapsectz,*leapsectz*>>.
+
An example of this directive is:
+
----
leapseclist /usr/share/zoneinfo/leap-seconds.list
----
[[makestep]]*makestep* _threshold_ _limit_::
Normally *chronyd* will cause the system to gradually correct any time offset,
by slowing down or speeding up the clock as required. In certain situations,
@ -1643,6 +1712,14 @@ The current root distance can be calculated from root delay and root dispersion
----
distance = delay / 2 + dispersion
----
*activate* _distance_:::
This option sets an activating root distance for the local reference. The
local reference will not be used until the root distance drops below the
configured value for the first time. This can be used to prevent the local
reference from being activated on a server which has never been synchronised
with an upstream server. The default value of 0.0 causes no activating
distance to be used, such that the local reference is always eligible for
activation.
*orphan*:::
This option enables a special '`orphan`' mode, where sources with stratum equal
to the local _stratum_ are assumed to not serve real time. They are ignored
@ -1665,7 +1742,7 @@ The *orphan* mode is compatible with the *ntpd*'s orphan mode (enabled by the
An example of the directive is:
+
----
local stratum 10 orphan distance 0.1
local stratum 10 orphan distance 0.1 activate 0.5
----
[[ntpsigndsocket]]*ntpsigndsocket* _directory_::
@ -1730,6 +1807,43 @@ per process that the NTS server will accept. The default value is 100. The
maximum practical value is half of the system *FD_SETSIZE* constant (usually
1024).
[[ntsaeads2]]*ntsaeads* _ID_...::
This directive specifies a list of IDs of Authenticated Encryption with
Associated Data (AEAD) algorithms enabled for NTS authentication of NTP
messages. *chronyd* as a server uses the first enabled algorithm from the list
provided by the client. Algorithms that are not supported by the installed
version of the crypto library (Nettle, GnuTLS) are ignored.
+
The following IDs are supported:
+
* 15: AES-SIV-CMAC-256
* 30: AES-128-GCM-SIV
{blank}::
+
The default list of IDs is _30 15_. AES-128-GCM-SIV is preferred over
AES-SIV-CMAC-256 for shorter keys, which makes NTS cookies shorter and improves
reliability of NTS in networks that block or limit rate of longer NTP messages.
+
An example of the directive is:
+
----
ntsaeads 15
----
+
This list is used also by the <<ntsaeads1,NTS client>>.
+
Note the the NTS specification (RFC 8915) requires servers to support
AES-SIV-CMAC-256, i.e. 15 should be always included in the specified list.
+
The AES-128-GCM-SIV keys used by *chronyd* do not comply to RFC 8915 for
compatibility with older *chrony* clients unless the use of compliant keys is
negotiated with an
https://chrony-project.org/doc/spec/nts-compliant-128gcm.html[NTS-KE record].
Support for this record was added in version 4.6.1. As a client, *chronyd* can
interoperate with a server that uses compliant keys, but does not support the
negotiation, if it responds to incorrectly authenticated requests with an NTS
NAK.
[[ntsdumpdir2]]*ntsdumpdir* _directory_::
This directive specifies a directory where *chronyd* operating as an NTS server
can save the keys which encrypt NTS cookies provided to clients. The keys are
@ -1829,6 +1943,14 @@ source address from completely blocking responses to that address. The leak
rate is defined as a power of 1/2 and it is 2 by default, i.e. on average at
least every fourth request has a response. The minimum value is 1 and the
maximum value is 4.
*kod* _rate_:::
This option sets the rate at which Kiss-o'-Death (KoD) RATE responses are
randomly sent when the limits specified by the *interval* and *burst* options
are exceeded. It is an additional stream of responses to the *leak* option. A
KoD RATE response is a request for the client to reduce its polling rate. Few
implementations actually support it. The rate is defined as a power of 1/2. The
default value is 0, which means disabled. The minimum value is 0 and the
maximum value is 4.
{blank}::
+
An example use of the directive is:
@ -1844,7 +1966,7 @@ packets, by up to 75% (with default *leak* of 2).
[[ntsratelimit]]*ntsratelimit* [_option_]...::
This directive enables rate limiting of NTS-KE requests. It is similar to the
<<ratelimit,*ratelimit*>> directive, except the default interval is 6
(1 connection per 64 seconds).
(1 connection per 64 seconds) and the *kod* option is not supported.
+
An example of the use of the directive is:
+
@ -1976,8 +2098,9 @@ all* directive.
[[cmdport]]*cmdport* _port_::
The *cmdport* directive allows the port that is used for run-time monitoring
(via the *chronyc* program) to be altered from its default (323). If set to 0,
*chronyd* will not open the port, this is useful to disable *chronyc*
access from the Internet. (It does not disable the Unix domain command socket.)
*chronyd* will not open the port, which disables remote *chronyc* access (with
a non-default *bindcmdaddress*) and local access for unprivileged users. It
does not disable the Unix domain command socket.
+
An example shows the syntax:
+
@ -1986,13 +2109,13 @@ cmdport 257
----
+
This would make *chronyd* use UDP 257 as its command port. (*chronyc* would
need to be run with the *-p 257* switch to inter-operate correctly.)
need to be run with the *-p 257* option to inter-operate correctly.)
[[cmdratelimit]]*cmdratelimit* [_option_]...::
This directive enables response rate limiting for command packets. It is
similar to the <<ratelimit,*ratelimit*>> directive, except responses to
localhost are never limited and the default interval is -4 (16 packets per
second).
localhost are never limited, the default interval is -4 (16 packets per
second), and the *kod* option is not supported.
+
An example of the use of the directive is:
+
@ -2130,8 +2253,8 @@ from the example line above):
. Results of the *maxdelay*, *maxdelayratio*, and *maxdelaydevratio* (or
*maxdelayquant*) tests, and a test for synchronisation loop (1=pass,
0=fail). The first test from these four also checks the server precision,
response time, and whether an interleaved response is acceptable for
synchronisation. [1111]
response time, validity of the measured offset, and whether an interleaved
response is acceptable for synchronisation. [1111]
. Local poll [10]
. Remote poll [10]
. '`Score`' (an internal score within each polling level used to decide when to
@ -2698,7 +2821,11 @@ source is specified in the configuration file with a key shorter than 80 bits.
+
The recommended key types are AES ciphers and SHA3 hash functions. MD5 should
be avoided unless no other type is supported on the server and client, or
peers.
peers. A major weakness of MD5 for the NTP MAC is a length extension attack,
where a man-in-the-middle attacker can add arbitrary extension fields to the
NTP message and update the MAC to pass the verification of the extended
message. The *extfield* option (enabling processing of the specified extension
field) should not be used for NTP sources authenticated with an MD5 key.
+
The <<chronyc.adoc#keygen,*keygen*>> command of *chronyc* can be used to
generate random keys for the key file. By default, it generates 160-bit MD5 or
@ -2723,33 +2850,43 @@ e.g.:
----
pidfile /run/chronyd.pid
----
+
Setting this directive to _/_ disables writing and checking of the PID file.
[[ptpport]]*ptpport* _port_::
The *ptpport* directive enables *chronyd* to send and receive NTP messages
contained in PTP event messages (NTP-over-PTP) to enable hardware timestamping
on NICs which cannot timestamp NTP packets, but can timestamp unicast PTP
packets. The port recognized by the NICs is 319 (PTP event port). The default
value is 0 (disabled).
packets, and also use corrections provided by PTP one-step end-to-end
transparent clocks in network switches and routers. The port recognized by the
NICs and PTP transparent clocks is 319 (PTP event port). The default value is 0
(disabled).
+
The NTP-over-PTP support is experimental. The protocol and configuration can
change in future. It should be used only in local networks and expected to work
only between servers and clients running the same version of *chronyd*.
change in future. It should be used only in local networks.
+
The PTP port will be open even if *chronyd* is not configured to operate as a
server or client. The directive does not change the default protocol of
specified NTP sources. Each NTP source that should use NTP-over-PTP needs to
be specified with the *port* option set to the PTP port. To actually enable
hardware timestamping on NICs which can timestamp PTP packets only, the
*rxfilter* option of the *hwtimestamp* directive needs to be set to _ptp_.
*rxfilter* option of the *hwtimestamp* directive needs to be set to _ptp_. The
extension field _F324_ needs to be enabled to use the corrections provided by
the PTP transparent clocks.
+
An example of client configuration is:
+
----
server foo.example.net minpoll 0 maxpoll 0 xleave port 319
server ntp1.example.net minpoll 0 maxpoll 0 xleave port 319 extfield F324
hwtimestamp * rxfilter ptp
ptpport 319
----
[[ptpdomain]]*ptpdomain* _domain_::
The *ptpdomain* directive sets the PTP domain number of transmitted and
accepted NTP-over-PTP messages. Messages from other domains are ignored.
The default is 123, the minimum is 0, and the maximum is 255.
[[sched_priority]]*sched_priority* _priority_::
On Linux, FreeBSD, NetBSD, and illumos, the *sched_priority* directive will
select the SCHED_FIFO real-time scheduler at the specified priority (which must
@ -2806,13 +2943,13 @@ the following methods:
facilities.
* Use public servers from the https://www.pool.ntp.org/[pool.ntp.org] project.
Assuming that your NTP servers are called _foo.example.net_, _bar.example.net_
and _baz.example.net_, your _chrony.conf_ file could contain as a minimum:
Assuming that your NTP servers are called _ntp1.example.net_, _ntp2.example.net_
and _ntp3.example.net_, your _chrony.conf_ file could contain as a minimum:
----
server foo.example.net
server bar.example.net
server baz.example.net
server ntp1.example.net
server ntp2.example.net
server ntp3.example.net
----
However, you will probably want to include some of the other directives. The
@ -2823,9 +2960,9 @@ synchronisation. The smallest useful configuration file would look something
like:
----
server foo.example.net iburst
server bar.example.net iburst
server baz.example.net iburst
server ntp1.example.net iburst
server ntp2.example.net iburst
server ntp3.example.net iburst
driftfile @CHRONYVARDIR@/drift
makestep 1.0 3
rtcsync
@ -2849,9 +2986,9 @@ option will enable a secure synchronisation to the servers. The configuration
file could look like:
----
server foo.example.net iburst nts
server bar.example.net iburst nts
server baz.example.net iburst nts
server ntp1.example.net iburst nts
server ntp2.example.net iburst nts
server ntp3.example.net iburst nts
driftfile @CHRONYVARDIR@/drift
makestep 1.0 3
rtcsync
@ -2865,14 +3002,14 @@ additional configuration to tell *chronyd* when the connection goes up and
down. This saves the program from continuously trying to poll the servers when
they are inaccessible.
Again, assuming that your NTP servers are called _foo.example.net_,
_bar.example.net_ and _baz.example.net_, your _chrony.conf_ file would now
Again, assuming that your NTP servers are called _ntp1.example.net_,
_ntp2.example.net_ and _ntp3.example.net_, your _chrony.conf_ file would now
contain:
----
server foo.example.net offline
server bar.example.net offline
server baz.example.net offline
server ntp1.example.net offline
server ntp2.example.net offline
server ntp3.example.net offline
driftfile @CHRONYVARDIR@/drift
makestep 1.0 3
rtcsync
@ -3056,9 +3193,9 @@ configuration files are shown.
For the _chrony.conf_ file, the following can be used as an example.
----
server foo.example.net maxdelay 0.4 offline
server bar.example.net maxdelay 0.4 offline
server baz.example.net maxdelay 0.4 offline
server ntp1.example.net maxdelay 0.4 offline
server ntp2.example.net maxdelay 0.4 offline
server ntp3.example.net maxdelay 0.4 offline
logdir /var/log/chrony
log statistics measurements tracking
driftfile @CHRONYVARDIR@/drift
@ -3117,10 +3254,10 @@ configuration).
The configuration file could look like:
----
server foo.example.net iburst
server bar.example.net iburst
server baz.example.net iburst
server qux.example.net iburst
server ntp1.example.net iburst
server ntp2.example.net iburst
server ntp3.example.net iburst
server ntp4.example.net iburst
makestep 1.0 3
rtcsync
allow

View file

@ -2,7 +2,7 @@
//
// Copyright (C) Richard P. Curnow 1997-2003
// Copyright (C) Stephen Wadeley 2016
// Copyright (C) Miroslav Lichvar 2009-2017, 2019-2023
// Copyright (C) Miroslav Lichvar 2009-2017, 2019-2024
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as
@ -144,7 +144,7 @@ The *tracking* command displays parameters about the system's clock
performance. An example of the output is shown below.
+
----
Reference ID : CB00710F (foo.example.net)
Reference ID : CB00710F (ntp1.example.net)
Stratum : 3
Ref time (UTC) : Fri Jan 27 09:49:17 2017
System time : 0.000006523 seconds slow of NTP time
@ -178,7 +178,7 @@ with an IPv4 address.
*Stratum*:::
The stratum indicates how many hops away from a computer with an attached
reference clock we are. Such a computer is a stratum-1 computer, so the
computer in the example is two hops away (i.e. _foo.example.net_ is a
computer in the example is two hops away (i.e. _ntp1.example.net_ is a
stratum-2 and is synchronised from a stratum-1).
*Ref time*:::
This is the time (UTC) at which the last measurement from the reference
@ -321,8 +321,8 @@ extra caption lines are shown as a reminder of the meanings of the columns.
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#* GPS0 0 4 377 11 -479ns[ -621ns] +/- 134ns
^? foo.example.net 2 6 377 23 -923us[ -924us] +/- 43ms
^+ bar.example.net 1 6 377 21 -2629us[-2619us] +/- 86ms
^? ntp1.example.net 2 6 377 23 -923us[ -924us] +/- 43ms
^+ ntp2.example.net 1 6 377 21 -2629us[-2619us] +/- 86ms
----
+
The columns are as follows:
@ -364,9 +364,12 @@ a measurement is being made every 64 seconds. *chronyd* automatically varies
the polling rate in response to prevailing conditions.
*Reach*:::
This shows the source's reachability register printed as an octal number. The
register has 8 bits and is updated on every received or missed packet from
the source. A value of 377 indicates that a valid reply was received for all
from the last eight transmissions.
register has 8 bits. It is shifted to left by one bit with each poll and it is
updated by 1 when a valid NTP response, or just a sample in case of a reference
clock, is received from the source. A value of 377 indicates that a valid
response or sample was received for all of the last 8 polls. Note that samples
can be dropped if they are not considered good enough for synchronisation, but
the reachability register will still have 1s for their polls.
*LastRx*:::
This column shows how long ago the last good sample (which is shown in the next
column) was received from the source. Measurements that failed some tests are
@ -400,7 +403,7 @@ An example report is:
----
Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
===============================================================================
foo.example.net 11 5 46m -0.001 0.045 1us 25us
ntp1.example.net 11 5 46m -0.001 0.045 1us 25us
----
+
The columns are as follows:
@ -444,9 +447,9 @@ An example of the output is shown below.
----
S Name/IP Address Auth COpts EOpts Last Score Interval Leap
=======================================================================
D foo.example.net Y ----- --TR- 4 1.0 -61ms +62ms N
* bar.example.net N ----- ----- 0 1.0 -6846us +7305us N
+ baz.example.net N ----- ----- 10 1.0 -7381us +7355us N
D ntp1.example.net Y ----- --TR- 4 1.0 -61ms +62ms N
* ntp2.example.net N ----- ----- 0 1.0 -6846us +7305us N
+ ntp3.example.net N ----- ----- 10 1.0 -7381us +7355us N
----
+
The columns are as follows:
@ -459,8 +462,8 @@ states are reported.
The following states indicate the source is not considered selectable for
synchronisation:
* _N_ - has the *noselect* option.
* _s_ - is not synchronised.
* _M_ - does not have enough measurements.
* _s_ - is not synchronised.
* _d_ - has a root distance larger than the maximum distance (configured by the
<<chrony.conf.adoc#maxdistance,*maxdistance*>> directive).
* _~_ - has a jitter larger than the maximum jitter (configured by the
@ -492,7 +495,7 @@ local clock:
This column shows the name or IP address of the source if it is an NTP server,
or the reference ID if it is a reference clock.
*Auth*:::
This column indicites whether an authentication mechanism is enabled for the
This column indicates whether an authentication mechanism is enabled for the
source. _Y_ means yes and _N_ means no.
*COpts*:::
This column displays the configured selection options of the source.
@ -556,6 +559,13 @@ The *reselectdist* command sets the reselection distance. It is equivalent to
the <<chrony.conf.adoc#reselectdist,*reselectdist*>> directive in the
configuration file.
[[offset]]*offset* _address|refid_ _offset_::
The *offset* command modifies the offset correction of an NTP source specified
by IP address (or the _ID#XXXXXXXXXX_ identifier used for unknown addresses),
or a reference clock specified by reference ID as a string. It is equivalent to
the *offset* option in the <<chrony.conf.adoc#server,*server*>> or
<<chrony.conf.adoc#refclock,*refclock*>> directive respectively.
=== NTP sources
[[activity]]*activity*::
@ -592,9 +602,9 @@ shown below.
----
Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
=========================================================================
foo.example.net NTS 1 15 256 135m 0 0 8 100
bar.example.net SK 30 13 128 - 0 0 0 0
baz.example.net - 0 0 0 - 0 0 0 0
ntp1.example.net NTS 1 15 256 135m 0 0 8 100
ntp2.example.net SK 30 13 128 - 0 0 0 0
ntp3.example.net - 0 0 0 - 0 0 0 0
----
+
The columns are as follows:
@ -689,6 +699,10 @@ Total TX : 24
Total RX : 24
Total valid RX : 24
Total good RX : 22
Total kernel TX : 24
Total kernel RX : 24
Total HW TX : 0
Total HW RX : 0
----
+
The fields are explained as follows:
@ -746,6 +760,18 @@ The number of packets which passed the first two groups of NTP tests.
*Total good RX*:::
The number of packets which passed all three groups of NTP tests, i.e. the NTP
measurement was accepted.
*Total kernel TX*:::
The number of packets sent to the source for which a timestamp was captured by
the kernel.
*Total kernel RX*:::
The number of packets received from the source for which a timestamp was
captured by the kernel.
*Total HW TX*:::
The number of packets sent to the source for which a timestamp was captured by
the NIC.
*Total HW RX*:::
The number of packets received from the source for which a timestamp was
captured by the NIC.
[[add_peer]]*add peer* _name_ [_option_]...::
The *add peer* command allows a new NTP peer to be added whilst
@ -758,7 +784,7 @@ parameters and options is identical to that for the
An example of using this command is shown below.
+
----
add peer foo.example.net minpoll 6 maxpoll 10 key 25
add peer ntp1.example.net minpoll 6 maxpoll 10 key 25
----
[[add_pool]]*add pool* _name_ [_option_]...::
@ -772,7 +798,7 @@ directive in the configuration file.
An example of using this command is shown below:
+
----
add pool foo.example.net maxsources 3 iburst
add pool ntp1.example.net maxsources 3 iburst
----
[[add_server]]*add server* _name_ [_option_]...::
@ -786,7 +812,7 @@ directive in the configuration file.
An example of using this command is shown below:
+
----
add server foo.example.net minpoll 6 maxpoll 10 key 25
add server ntp1.example.net minpoll 6 maxpoll 10 key 25
----
[[delete]]*delete* _address_::
@ -862,7 +888,7 @@ IPv6 addresses have first 48 bits equal to _2001:db8:789a_.
Example of the three-argument form of the command is:
+
----
burst 2/10 foo.example.net
burst 2/10 ntp1.example.net
----
[[maxdelay]]*maxdelay* _address_ _delay_::
@ -928,7 +954,7 @@ uses an IP address or a hostname. These forms are illustrated below.
offline
offline 255.255.255.0/1.2.3.0
offline 2001:db8:789a::/48
offline foo.example.net
offline ntp1.example.net
----
+
The second form means that the *offline* command is to be applied to any source
@ -986,6 +1012,10 @@ command might replace the addresses even if they are still in the pool.
The *reload sources* command causes *chronyd* to re-read all _*.sources_ files
from the directories specified by the
<<chrony.conf.adoc#sourcedir,*sourcedir*>> directive.
+
Note that modified sources (e.g. specified with a new option) are not modified
in memory. They are removed and added again, which causes them to lose old
measurements and reset the selection state.
[[sourcename]]*sourcename* _address_::
The *sourcename* command prints the original hostname or address that was
@ -1094,7 +1124,7 @@ particular host.
Examples of use, showing a named host and a numeric IP address, are as follows:
+
----
accheck foo.example.net
accheck ntp1.example.net
accheck 1.2.3.4
accheck 2001:db8::1
----
@ -1121,7 +1151,7 @@ An example of the output is:
Hostname NTP Drop Int IntL Last Cmd Drop Int Last
===============================================================================
localhost 2 0 2 - 133 15 0 -1 7
foo.example.net 12 0 6 - 23 0 0 - -
ntp1.example.net 12 0 6 - 23 0 0 - -
----
+
Each row shows the data for a single host. Only hosts that have passed the host
@ -1321,7 +1351,7 @@ used to check whether monitoring access is permitted from a named host.
Examples of use are as follows:
+
----
cmdaccheck foo.example.net
cmdaccheck ntp1.example.net
cmdaccheck 1.2.3.4
cmdaccheck 2001:db8::1
----

View file

@ -72,9 +72,9 @@ terminal.
*-L* _level_::
This option specifies the minimum severity level of messages to be written to
the log file, syslog, or terminal. The following levels can be specified:
0 (informational), 1 (warning), 2 (non-fatal error), and 3 (fatal error). The
default value is 0.
the log file, syslog, or terminal. The following levels can be specified: -1
(debug, if compiled with enabled support for debugging), 0 (informational), 1
(warning), 2 (non-fatal error), and 3 (fatal error). The default value is 0.
*-p*::
When run in this mode, *chronyd* will print the configuration and exit. It will
@ -206,6 +206,17 @@ With this option *chronyd* will print version number to the terminal and exit.
*-h*, *--help*::
With this option *chronyd* will print a help message to the terminal and exit.
== ENVIRONMENT VARIABLES
*LISTEN_FDS*::
On Linux systems, the systemd service manager may pass file descriptors for
pre-initialised sockets to *chronyd*. The service manager allocates and binds
the file descriptors, and passes a copy to each spawned instance of the
service. This allows for zero-downtime service restarts as the sockets buffer
client requests until the service is able to handle them. The service manager
sets the LISTEN_FDS environment variable to the number of passed file
descriptors.
== FILES
_@SYSCONFDIR@/chrony.conf_

74
doc/contributing.adoc Normal file
View file

@ -0,0 +1,74 @@
// This file is part of chrony
//
// Copyright (C) Miroslav Lichvar 2024
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
= Contributing
== Patches
The source code of `chrony` is maintained in a git repository at
https://gitlab.com/chrony/chrony. Patches can be submitted to the `chrony-dev`
mailing list, or as a merge request on gitlab. Before spending a lot of time
implementing a new major feature, it is recommended to ask on the mailing list
for comments about its design and whether such feature fits the goals of the
project.
Each commit should be a self-contained logical change, which does not break
the build or tests. New functionality and fixed bugs should be covered by a new
test or an extended existing test in the test suite. The test can be included
in the same commit or added as a separate commit. The same rule applies to
documentation. All command-line options, configuration directives, and
`chronyc` commands should be documented.
The most important tests can be executed by running `make check` or `make
quickcheck`. The unit and system tests run on all supported systems. The system
tests require root privileges. The simulation tests run only on Linux and
require https://gitlab.com/chrony/clknetsim[clknetsim] to be compiled in the
directory containing the tests, but they are executed with a merge request on
gitlab.
The commit message should explain any non-trivial changes, e.g. what problem is
the commit solving and how. The commit subject (first line of the message)
should be written in an imperative form, prefixed with the component name if it
is not a more general change, starting in lower case, and no period at the end.
See the git log for examples.
Simpler code is better. Less code is better. Security is a top priority.
Assertions should catch only bugs in the `chrony` code. Unexpected values in
external input (e.g. anything received from network) must be handled correctly
without crashing and memory corruption. Fuzzing support is available at
https://gitlab.com/chrony/chrony-fuzz. The fuzzing coverage is checked by the
project maintainer before each release.
The code should mostly be self-documenting. Comments should explain the
less obvious things.
== Coding style
The code uses two spaces for indentation. No tabs. The line length should
normally not exceed 95 characters. Too much indentation indicates the code will
not be very readable.
Function names are in an imperative form. Names of static functions use
lowercase characters and underscores. Public functions, structures, typedefs
are in CamelCase with a prefix specific to the module (e.g. LCL - local, NCR
- NTP core, NKS - NTS-KE server, SST - sourcestats).
Function names are not followed by space, but keywords of the language (e.g.
`if`, `for`, `while`, `sizeof`) are followed by space.
Have a look at the existing code to get a better idea what is expected.

View file

@ -1,7 +1,8 @@
// This file is part of chrony
//
// Copyright (C) Richard P. Curnow 1997-2003
// Copyright (C) Miroslav Lichvar 2014-2016, 2020-2023
// Copyright (C) Luke Valenta 2023
// Copyright (C) Miroslav Lichvar 2014-2016, 2020-2024
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as
@ -265,11 +266,11 @@ An example of a client configuration limiting the impact of the attacks could
be
----
server foo.example.net iburst nts maxdelay 0.1
server bar.example.net iburst nts maxdelay 0.2
server baz.example.net iburst nts maxdelay 0.05
server qux.example.net iburst nts maxdelay 0.1
server quux.example.net iburst nts maxdelay 0.1
server ntp1.example.net iburst nts maxdelay 0.1
server ntp2.example.net iburst nts maxdelay 0.2
server ntp3.example.net iburst nts maxdelay 0.05
server ntp4.example.net iburst nts maxdelay 0.1
server ntp5.example.net iburst nts maxdelay 0.1
minsources 3
maxchange 100 0 0
makestep 0.001 1
@ -318,7 +319,7 @@ An example of the directive for an NTP server on the Internet that you are
allowed to poll frequently could be
----
server foo.example.net minpoll 4 maxpoll 6 polltarget 16
server ntp.example.net minpoll 4 maxpoll 6 polltarget 16
----
An example using shorter polling intervals with a server located in the same
@ -381,7 +382,7 @@ outliers corrupting the minimum delay. For example:
server ntp.local minpoll -7 maxpoll -7 filter 31 maxdelayquant 0.3 xleave
----
As an experimental feature added in version 4.2, `chronyd` supports an NTPv4
Since version 4.2, `chronyd` supports an NTPv4
extension field containing an additional timestamp to enable frequency transfer
and significantly improve stability of synchronisation. It can be enabled by
the `extfield F323` option. For example:
@ -390,6 +391,18 @@ the `extfield F323` option. For example:
server ntp.local minpoll 0 maxpoll 0 xleave extfield F323
----
Since version 4.5, `chronyd` can apply corrections from PTP one-step end-to-end
transparent clocks (e.g. network switches) to significantly improve accuracy of
synchronisation in local networks. It requires the PTP transport to be enabled
by the `ptpport` directive, HW timestamping, and the `extfield F324` option.
For example:
----
server ntp.local minpoll -4 maxpoll -4 xleave extfield F323 extfield F324 port 319
ptpport 319
hwtimestamp eth0 minpoll -4
----
=== Does `chronyd` have an ntpdate mode?
Yes. With the `-q` option `chronyd` will set the system clock once and exit.
@ -497,6 +510,59 @@ pidfile /var/run/chronyd-server1.pid
driftfile /var/lib/chrony/drift-server1
----
=== How can `chronyd` be configured to minimise downtime during restarts?
The `dumpdir` directive in _chrony.conf_ provides `chronyd` a location to save
a measurement history of the sources it uses when the service exits. The `-r`
option then enables `chronyd` to load state from the dump files, reducing the
synchronisation time after a restart.
Similarly, the `ntsdumpdir` directive provides a location for `chronyd` to save
NTS cookies received from the server to avoid making a NTS-KE request when
`chronyd` is started. When operating as an NTS server, `chronyd` also saves
cookies keys to this directory to allow clients to continue to use the old keys
after a server restart for a more seamless experience.
On Linux systems,
https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html[systemd
socket activation] provides a mechanism to reuse server sockets across
`chronyd` restarts, so that client requests will be buffered until the service
is again able to handle the requests. This allows for zero-downtime service
restarts, simplified dependency logic at boot, and on-demand service spawning
(for instance, for separated server `chronyd` instances run with the `-x`
flag).
Socket activation is supported since `chrony` version 4.5.
The service manager (systemd) creates sockets and
passes file descriptors to them to the process via the `LISTEN_FDS` environment
variable. Before opening new sockets, `chronyd` first checks for and attempts
to reuse matching sockets passed from the service manager. For instance, if an
IPv4 datagram socket bound on `bindaddress` and `port` is available, it will be
used by the NTP server to accept incoming IPv4 requests.
An example systemd socket unit is below, where `chronyd` is configured with
`bindaddress 0.0.0.0`, `bindaddress ::`, `port 123`, and `ntsport 4460`.
----
[Unit]
Description=chronyd server sockets
[Socket]
Service=chronyd.service
# IPv4 NTP server
ListenDatagram=0.0.0.0:123
# IPv6 NTP server
ListenDatagram=[::]:123
# IPv4 NTS-KE server
ListenStream=0.0.0.0:4460
# IPv6 NTS-KE server
ListenStream=[::]:4460
BindIPv6Only=ipv6-only
[Install]
WantedBy=sockets.target
----
=== Should be a leap smear enabled on NTP server?
With the `smoothtime` and `leapsecmode` directives it is possible to enable a
@ -598,7 +664,10 @@ The `ethtool -T` command can be used to verify the timestamping support.
As an experimental feature added in version 4.2, `chrony` can use PTP as a
transport for NTP messages (NTP over PTP) to enable hardware timestamping on
hardware which can timestamp PTP packets only. It can be enabled by the
`ptpport` directive.
`ptpport` directive. Since version 4.5, `chrony` can also apply corrections
provided by PTP one-step end-to-end transparent clocks to reach the accuracy of
ordinary PTP clocks. The application of PTP corrections can be enabled by the
`extfield F324` option.
=== How can I avoid using wrong PHC refclock?
@ -669,9 +738,9 @@ this:
----
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* foo.example.net 2 6 377 34 +484us[ -157us] +/- 30ms
^- bar.example.net 2 6 377 34 +33ms[ +32ms] +/- 47ms
^+ baz.example.net 3 6 377 35 -1397us[-2033us] +/- 60ms
^* ntp1.example.net 2 6 377 34 +484us[ -157us] +/- 30ms
^- ntp2.example.net 2 6 377 34 +33ms[ +32ms] +/- 47ms
^+ ntp3.example.net 3 6 377 35 -1397us[-2033us] +/- 60ms
----
=== Are NTP servers specified with the `offline` option?
@ -703,6 +772,17 @@ print all sources, even those that do not have a known address yet, with their
names as they were specified in the configuration. This can be useful to verify
that the names specified in the configuration are used as expected.
When DNSSEC is enabled, it will not work until the time is synchronized, as it
requires validating a signature timestamp and its expiration date, so if the
system time is too far in the future or the past DNSSEC validation will fail and
`chronyd` will be unable to resolve the address of the NTP server. In such cases,
if hostnames are the only options and bare IP addresses cannot be used, DNSSEC
can be disabled for `chronyd` using resolver-specific mechanisms, if available,
although of course that means losing the protection afforded by DNSSEC.
For example, when using systemd-resolved, the `SYSTEMD_NSS_RESOLVE_VALIDATE=0`
environment variable can be set, for example in the `chronyd` systemd unit via
`Environment=SYSTEMD_NSS_RESOLVE_VALIDATE=0`.
=== Is `chronyd` allowed to step the system clock?
By default, `chronyd` adjusts the clock gradually by slowing it down or
@ -741,9 +821,9 @@ successful:
# chronyc -N authdata
Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
=========================================================================
foo.example.net NTS 1 15 256 33m 0 0 8 100
bar.example.net NTS 1 15 256 33m 0 0 8 100
baz.example.net NTS 1 15 256 33m 0 0 8 100
ntp1.example.net NTS 1 15 256 33m 0 0 8 100
ntp2.example.net NTS 1 15 256 33m 0 0 8 100
ntp3.example.net NTS 1 15 256 33m 0 0 8 100
----
The KeyID, Type, and KLen columns should have non-zero values. If they are
@ -867,7 +947,7 @@ Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
==============================================================================
PPS0 0 0 0 +0.000 2000.000 +0ns 4000ms
NMEA 58 30 231 -96.494 38.406 +504ms 6080us
foo.example.net 7 3 200 -2.991 16.141 -107us 492us
ntp1.example.net 7 3 200 -2.991 16.141 -107us 492us
----
the offset of the NMEA source would need to be increased by about 0.504
@ -1086,6 +1166,53 @@ There are several different clocks used by `chronyd`:
synchronised by `chronyd`. Its offset is tracked relative to the NTP clock in
order to convert the hardware timestamps.
=== How accurate is my system clock?
`chronyd` does not know how accurate really is the clock it is synchronizing.
Even if the measured offset of the clock is stable to nanoseconds, it could be
off by milliseconds due to asymmetric network delay, e.g. caused by asymmetric
routing or queuing delays in network switches. NTP provides root delay and root
dispersion to enable clients to estimate the maximum error of their clock.
Root delay measures the sum of round-trip times between all NTP servers on the
path from the client to the primary time source (e.g. a GPS receiver). Half of
the root delay is the maximum error due to asymmetric delays, assuming one
direction (e.g. from the client to the server) has a zero delay and the other
direction (from the server to the client) takes all of the measured delay. The
root delay also covers timestamping errors if the server implementation and
hardware meet the NTP requirement for transmit timestamps to never be late and
receive timestamps to never be early.
If you have additional information about the hardware and network between the
client and primary time source, you could modify the root delay to get a better
estimate of the maximum error. For example, from the physical distance of the
server and signal propagation speed in the cables a minimum symmetric
round-trip delay can be calculated and subtracted from the root delay measured
by NTP.
Root dispersion estimates errors due to instability of clocks and NTP
measurements. `chronyd` adjusts the rate at which root dispersion grows between
updates of the clock according to the stability of its NTP measurements. The
minimum rate is set by the the `maxclockerror` directive. By default it is 1
ppm (1 microsecond per second).
The estimated maximum error of the NTP clock is the sum of the root dispersion
and half of the root delay. This value is called root distance. The current
values of root dispersion and delay are included in the `tracking` report.
The estimated maximum error of the system clock, which is synchronized to the
NTP clock, is the sum of the root distance and remaining correction of the
system clock provided as `System time` in the `tracking` report. A maximum
value of this estimate between updates of the clock is included in the
`tracking` log.
Note that the resolution of the root delay and root dispersion fields in NTP
messages is about 15 microseconds and `chronyd` rounds the values up, i.e. the
minimum root distance an NTP client can normally observe is about 22.5
microseconds. An NTP extension field containing root delay and dispersion in a
better resolution of about 4 nanoseconds can be enabled by the `extfield F323`
option.
== Operating systems
=== Does `chrony` support Windows?

View file

@ -37,8 +37,8 @@ ntsdumpdir /var/lib/chrony
# Insert/delete leap seconds by slewing instead of stepping.
#leapsecmode slew
# Get TAI-UTC offset and leap seconds from the system tz database.
#leapsectz right/UTC
# Set the TAI-UTC offset of the system clock.
#leapseclist /usr/share/zoneinfo/leap-seconds.list
# Specify directory for log files.
logdir /var/log/chrony

View file

@ -27,9 +27,9 @@
# you can access at http://support.ntp.org/bin/view/Servers/WebHome or
# you can use servers from the pool.ntp.org project.
! server foo.example.net iburst
! server bar.example.net iburst
! server baz.example.net iburst
! server ntp1.example.net iburst
! server ntp2.example.net iburst
! server ntp3.example.net iburst
! pool pool.ntp.org iburst
@ -99,8 +99,8 @@ ntsdumpdir /var/lib/chrony
# and edit the following lines to specify the locations of the certificate and
# key.
! ntsservercert /etc/.../foo.example.net.crt
! ntsserverkey /etc/.../foo.example.net.key
! ntsservercert /etc/.../nts-server.crt
! ntsserverkey /etc/.../nts-server.key
# chronyd can save the measurement history for the servers to files when
# it exits. This is useful in 2 situations:
@ -126,11 +126,11 @@ ntsdumpdir /var/lib/chrony
! pidfile /var/run/chrony/chronyd.pid
# If the system timezone database is kept up to date and includes the
# right/UTC timezone, chronyd can use it to determine the current
# TAI-UTC offset and when will the next leap second occur.
# The system timezone database usually comes with a list of leap seconds and
# corresponding TAI-UTC offsets. chronyd can use it to set the offset of the
# system TAI clock and have an additional source of leap seconds.
! leapsectz right/UTC
! leapseclist /usr/share/zoneinfo/leap-seconds.list
#######################################################################
### INITIAL CLOCK CORRECTION
@ -238,7 +238,7 @@ ntsdumpdir /var/lib/chrony
# several people, you need to set up a mailing list or sendmail alias
# for them and use the address of that.)
! mailonchange wibble@foo.example.net 0.5
! mailonchange wibble@example.net 0.5
#######################################################################
### COMMAND ACCESS

View file

@ -943,6 +943,7 @@ get_date (const char *p, const time_t *now)
tm.tm_hour += yyRelHour;
tm.tm_min += yyRelMinutes;
tm.tm_sec += yyRelSeconds;
tm.tm_wday = 0;
/* Let mktime deduce tm_isdst if we have an absolute timestamp,
or if the relative timestamp mentions days, months, or years. */

272
leapdb.c Normal file
View file

@ -0,0 +1,272 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2009-2018, 2020, 2022
* Copyright (C) Patrick Oppenlander 2023, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
=======================================================================
This module provides leap second information. */
#include "config.h"
#include "sysincl.h"
#include "conf.h"
#include "leapdb.h"
#include "logging.h"
#include "util.h"
/* ================================================== */
/* Source of leap second data */
enum {
SRC_NONE,
SRC_TIMEZONE,
SRC_LIST,
} leap_src;
/* Offset between leap-seconds.list timestamp epoch and Unix epoch.
leap-seconds.list epoch is 1 Jan 1900, 00:00:00 */
#define LEAP_SEC_LIST_OFFSET 2208988800
/* ================================================== */
static NTP_Leap
get_tz_leap(time_t when, int *tai_offset)
{
struct tm stm, *tm;
time_t t;
char *tz_env, tz_orig[128];
NTP_Leap tz_leap = LEAP_Normal;
tm = gmtime(&when);
if (!tm)
return tz_leap;
stm = *tm;
/* Temporarily switch to the timezone containing leap seconds */
tz_env = getenv("TZ");
if (tz_env) {
if (strlen(tz_env) >= sizeof (tz_orig))
return tz_leap;
strcpy(tz_orig, tz_env);
}
setenv("TZ", CNF_GetLeapSecTimezone(), 1);
tzset();
/* Get the TAI-UTC offset, which started at the epoch at 10 seconds */
t = mktime(&stm);
if (t != -1)
*tai_offset = t - when + 10;
/* Set the time to 23:59:60 and see how it overflows in mktime() */
stm.tm_sec = 60;
stm.tm_min = 59;
stm.tm_hour = 23;
t = mktime(&stm);
if (tz_env)
setenv("TZ", tz_orig, 1);
else
unsetenv("TZ");
tzset();
if (t == -1)
return tz_leap;
if (stm.tm_sec == 60)
tz_leap = LEAP_InsertSecond;
else if (stm.tm_sec == 1)
tz_leap = LEAP_DeleteSecond;
return tz_leap;
}
/* ================================================== */
static NTP_Leap
get_list_leap(time_t when, int *tai_offset)
{
FILE *f;
char line[1024];
NTP_Leap ret_leap = LEAP_Normal;
int ret_tai_offset = 0, prev_lsl_tai_offset = 10;
int64_t when1900, lsl_updated = 0, lsl_expiry = 0;
const char *leap_sec_list = CNF_GetLeapSecList();
if (!(f = UTI_OpenFile(NULL, leap_sec_list, NULL, 'r', 0))) {
LOG(LOGS_ERR, "Failed to open leap seconds list %s", leap_sec_list);
goto out;
}
/* Leap second happens at midnight */
when = (when / (24 * 3600) + 1) * (24 * 3600);
/* leap-seconds.list timestamps are relative to 1 Jan 1900, 00:00:00 */
when1900 = (int64_t)when + LEAP_SEC_LIST_OFFSET;
while (fgets(line, sizeof line, f) > 0) {
int64_t lsl_when;
int lsl_tai_offset;
char *p;
/* Ignore blank lines */
for (p = line; *p && isspace(*p); ++p)
;
if (!*p)
continue;
if (*line == '#') {
/* Update time line starts with #$ */
if (line[1] == '$' && sscanf(line + 2, "%"SCNd64, &lsl_updated) != 1)
goto error;
/* Expiration time line starts with #@ */
if (line[1] == '@' && sscanf(line + 2, "%"SCNd64, &lsl_expiry) != 1)
goto error;
/* Comment or a special comment we don't care about */
continue;
}
/* Leap entry */
if (sscanf(line, "%"SCNd64" %d", &lsl_when, &lsl_tai_offset) != 2)
goto error;
if (when1900 == lsl_when) {
if (lsl_tai_offset > prev_lsl_tai_offset)
ret_leap = LEAP_InsertSecond;
else if (lsl_tai_offset < prev_lsl_tai_offset)
ret_leap = LEAP_DeleteSecond;
/* When is rounded to the end of the day, so offset hasn't changed yet! */
ret_tai_offset = prev_lsl_tai_offset;
} else if (when1900 > lsl_when) {
ret_tai_offset = lsl_tai_offset;
}
prev_lsl_tai_offset = lsl_tai_offset;
}
/* Make sure the file looks sensible */
if (!feof(f) || !lsl_updated || !lsl_expiry)
goto error;
if (when1900 >= lsl_expiry)
LOG(LOGS_WARN, "Leap second list %s needs update", leap_sec_list);
goto out;
error:
if (f)
fclose(f);
LOG(LOGS_ERR, "Failed to parse leap seconds list %s", leap_sec_list);
return LEAP_Normal;
out:
if (f)
fclose(f);
*tai_offset = ret_tai_offset;
return ret_leap;
}
/* ================================================== */
static int
check_leap_source(NTP_Leap (*src)(time_t when, int *tai_offset))
{
int tai_offset = 0;
/* Check that the leap second source has good data for Jun 30 2012 and Dec 31 2012 */
if (src(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 &&
src(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35)
return 1;
return 0;
}
/* ================================================== */
void
LDB_Initialise(void)
{
const char *leap_tzname, *leap_sec_list;
leap_tzname = CNF_GetLeapSecTimezone();
if (leap_tzname && !check_leap_source(get_tz_leap)) {
LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
leap_tzname = NULL;
}
leap_sec_list = CNF_GetLeapSecList();
if (leap_sec_list && !check_leap_source(get_list_leap)) {
LOG(LOGS_WARN, "Leap second list %s failed check, ignoring", leap_sec_list);
leap_sec_list = NULL;
}
if (leap_sec_list) {
LOG(LOGS_INFO, "Using leap second list %s", leap_sec_list);
leap_src = SRC_LIST;
} else if (leap_tzname) {
LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname);
leap_src = SRC_TIMEZONE;
}
}
/* ================================================== */
NTP_Leap
LDB_GetLeap(time_t when, int *tai_offset)
{
static time_t last_ldb_leap_check;
static NTP_Leap ldb_leap;
static int ldb_tai_offset;
/* Do this check at most twice a day */
when = when / (12 * 3600) * (12 * 3600);
if (last_ldb_leap_check == when)
goto out;
last_ldb_leap_check = when;
ldb_leap = LEAP_Normal;
ldb_tai_offset = 0;
switch (leap_src) {
case SRC_NONE:
break;
case SRC_TIMEZONE:
ldb_leap = get_tz_leap(when, &ldb_tai_offset);
break;
case SRC_LIST:
ldb_leap = get_list_leap(when, &ldb_tai_offset);
break;
}
out:
*tai_offset = ldb_tai_offset;
return ldb_leap;
}
/* ================================================== */
void
LDB_Finalise(void)
{
/* Nothing to do */
}

37
leapdb.h Normal file
View file

@ -0,0 +1,37 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Patrick Oppenlander 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
=======================================================================
This module provides leap second information.
*/
#ifndef GOT_LEAPDB_H
#define GOT_LEAPDB_H
#include "ntp.h"
extern void LDB_Initialise(void);
extern NTP_Leap LDB_GetLeap(time_t when, int *tai_offset);
extern void LDB_Finalise(void);
#endif /* GOT_LEAPDB_H */

View file

@ -145,6 +145,7 @@ void LOG_Message(LOG_Severity severity,
struct tm *tm;
assert(initialised);
severity = CLAMP(LOGS_DEBUG, severity, LOGS_FATAL);
if (!system_log && file_log && severity >= log_min_severity) {
/* Don't clutter up syslog with timestamps and internal debugging info */
@ -155,8 +156,13 @@ void LOG_Message(LOG_Severity severity,
fprintf(file_log, "%s ", buf);
}
#if DEBUG > 0
if (log_min_severity <= LOGS_DEBUG)
fprintf(file_log, "%s%s:%d:(%s) ", debug_prefix, filename, line_number, function_name);
if (log_min_severity <= LOGS_DEBUG) {
/* Log severity to character mapping (debug, info, warn, err, fatal) */
const char severity_chars[LOGS_FATAL - LOGS_DEBUG + 1] = {'D', 'I', 'W', 'E', 'F'};
fprintf(file_log, "%c:%s%s:%d:(%s) ", severity_chars[severity - LOGS_DEBUG],
debug_prefix, filename, line_number, function_name);
}
#endif
}
@ -179,7 +185,7 @@ void LOG_Message(LOG_Severity severity,
/* Send the message also to the foreground process if it is
still running, or stderr if it is still open */
if (parent_fd > 0) {
if (write(parent_fd, buf, strlen(buf) + 1) < 0)
if (!LOG_NotifyParent(buf))
; /* Not much we can do here */
} else if (system_log && parent_fd == 0) {
system_log = 0;
@ -285,6 +291,17 @@ LOG_SetParentFd(int fd)
/* ================================================== */
int
LOG_NotifyParent(const char *message)
{
if (parent_fd <= 0)
return 1;
return write(parent_fd, message, strlen(message) + 1) > 0;
}
/* ================================================== */
void
LOG_CloseParentFd()
{

View file

@ -126,7 +126,10 @@ extern void LOG_OpenSystemLog(void);
/* Stop using stderr and send fatal message to the foreground process */
extern void LOG_SetParentFd(int fd);
/* Close the pipe to the foreground process so it can exit */
/* Send a message to the foreground process */
extern int LOG_NotifyParent(const char *message);
/* Close the pipe to the foreground process */
extern void LOG_CloseParentFd(void);
/* File logging functions */

19
main.c
View file

@ -32,6 +32,7 @@
#include "main.h"
#include "sched.h"
#include "leapdb.h"
#include "local.h"
#include "sys.h"
#include "ntp_io.h"
@ -134,6 +135,7 @@ MAI_CleanupAndExit(void)
RCL_Finalise();
SRC_Finalise();
REF_Finalise();
LDB_Finalise();
RTC_Finalise();
SYS_Finalise();
@ -213,7 +215,10 @@ post_init_ntp_hook(void *anything)
REF_SetMode(ref_mode);
}
/* Close the pipe to the foreground process so it can exit */
/* Send an empty message to the foreground process so it can exit.
If that fails, indicating the process was killed, exit too. */
if (!LOG_NotifyParent(""))
SCH_QuitProgram();
LOG_CloseParentFd();
CNF_AddSources();
@ -336,8 +341,8 @@ go_daemon(void)
close(pipefd[1]);
r = read(pipefd[0], message, sizeof (message));
if (r) {
if (r > 0) {
if (r != 1 || message[0] != '\0') {
if (r > 1) {
/* Print the error message from the child */
message[sizeof (message) - 1] = '\0';
fprintf(stderr, "%s\n", message);
@ -368,9 +373,9 @@ go_daemon(void)
}
/* Don't keep stdin/out/err from before. But don't close
the parent pipe yet. */
the parent pipe yet, or reusable file descriptors. */
for (fd=0; fd<1024; fd++) {
if (fd != pipefd[1])
if (fd != pipefd[1] && !SCK_IsReusable(fd))
close(fd);
}
@ -560,6 +565,9 @@ int main
if (user_check && getuid() != 0)
LOG_FATAL("Not superuser");
/* Initialise reusable file descriptors before fork */
SCK_PreInitialise();
/* Turn into a daemon */
if (!nofork) {
go_daemon();
@ -652,6 +660,7 @@ int main
if (!geteuid())
LOG(LOGS_WARN, "Running with root privileges");
LDB_Initialise();
REF_Initialise();
SST_Initialise();
NSR_Initialise();

19
ntp.h
View file

@ -115,9 +115,11 @@ typedef struct {
/* Non-authentication extension fields and corresponding internal flags */
#define NTP_EF_EXP1 0xF323
#define NTP_EF_EXP_MONO_ROOT 0xF323
#define NTP_EF_EXP_NET_CORRECTION 0xF324
#define NTP_EF_FLAG_EXP1 0x1
#define NTP_EF_FLAG_EXP_MONO_ROOT 0x1
#define NTP_EF_FLAG_EXP_NET_CORRECTION 0x2
/* Pre-NTPv5 experimental extension field */
typedef struct {
@ -126,9 +128,18 @@ typedef struct {
NTP_int32 root_dispersion;
NTP_int64 mono_receive_ts;
uint32_t mono_epoch;
} NTP_ExtFieldExp1;
} NTP_EFExpMonoRoot;
#define NTP_EF_EXP1_MAGIC 0xF5BEDD9AU
#define NTP_EF_EXP_MONO_ROOT_MAGIC 0xF5BEDD9AU
/* Experimental extension field to provide PTP corrections */
typedef struct {
uint32_t magic;
NTP_int64 correction;
uint32_t reserved[3];
} NTP_EFExpNetCorrection;
#define NTP_EF_EXP_NET_CORRECTION_MAGIC 0x07AC2CEBU
/* Authentication extension fields */

View file

@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2009-2023
* Copyright (C) Miroslav Lichvar 2009-2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -221,7 +221,7 @@ struct NCR_Instance_Record {
int burst_good_samples_to_go;
int burst_total_samples_to_go;
/* Report from last valid response */
/* Report from last valid response and packet/timestamp statistics */
RPT_NTPReport report;
};
@ -314,6 +314,9 @@ static ARR_Instance broadcasts;
/* Maximum acceptable change in server mono<->real offset */
#define MAX_MONO_DOFFSET 16.0
/* Maximum assumed frequency error in network corrections */
#define MAX_NET_CORRECTION_FREQ 100.0e-6
/* Invalid socket, different from the one in ntp_io.c */
#define INVALID_SOCK_FD -2
@ -373,6 +376,9 @@ do_size_checks(void)
assert(offsetof(NTP_Packet, originate_ts) == 24);
assert(offsetof(NTP_Packet, receive_ts) == 32);
assert(offsetof(NTP_Packet, transmit_ts) == 40);
assert(sizeof (NTP_EFExpMonoRoot) == 24);
assert(sizeof (NTP_EFExpNetCorrection) == 24);
}
/* ================================================== */
@ -418,6 +424,8 @@ zero_local_timestamp(NTP_Local_Timestamp *ts)
UTI_ZeroTimespec(&ts->ts);
ts->err = 0.0;
ts->source = NTP_TS_DAEMON;
ts->rx_duration = 0.0;
ts->net_correction = 0.0;
}
/* ================================================== */
@ -1045,34 +1053,64 @@ receive_timeout(void *arg)
/* ================================================== */
static int
add_ext_exp1(NTP_Packet *message, NTP_PacketInfo *info, struct timespec *rx,
double root_delay, double root_dispersion)
add_ef_mono_root(NTP_Packet *message, NTP_PacketInfo *info, struct timespec *rx,
double root_delay, double root_dispersion)
{
struct timespec mono_rx;
NTP_ExtFieldExp1 exp1;
NTP_EFExpMonoRoot ef;
NTP_int64 ts_fuzz;
memset(&exp1, 0, sizeof (exp1));
exp1.magic = htonl(NTP_EF_EXP1_MAGIC);
memset(&ef, 0, sizeof (ef));
ef.magic = htonl(NTP_EF_EXP_MONO_ROOT_MAGIC);
if (info->mode != MODE_CLIENT) {
exp1.root_delay = UTI_DoubleToNtp32f28(root_delay);
exp1.root_dispersion = UTI_DoubleToNtp32f28(root_dispersion);
ef.root_delay = UTI_DoubleToNtp32f28(root_delay);
ef.root_dispersion = UTI_DoubleToNtp32f28(root_dispersion);
if (rx)
UTI_AddDoubleToTimespec(rx, server_mono_offset, &mono_rx);
else
UTI_ZeroTimespec(&mono_rx);
UTI_GetNtp64Fuzz(&ts_fuzz, message->precision);
UTI_TimespecToNtp64(&mono_rx, &exp1.mono_receive_ts, &ts_fuzz);
exp1.mono_epoch = htonl(server_mono_epoch);
UTI_TimespecToNtp64(&mono_rx, &ef.mono_receive_ts, &ts_fuzz);
ef.mono_epoch = htonl(server_mono_epoch);
}
if (!NEF_AddField(message, info, NTP_EF_EXP1, &exp1, sizeof (exp1))) {
if (!NEF_AddField(message, info, NTP_EF_EXP_MONO_ROOT, &ef, sizeof (ef))) {
DEBUG_LOG("Could not add EF");
return 0;
}
info->ext_field_flags |= NTP_EF_FLAG_EXP1;
info->ext_field_flags |= NTP_EF_FLAG_EXP_MONO_ROOT;
return 1;
}
/* ================================================== */
static int
add_ef_net_correction(NTP_Packet *message, NTP_PacketInfo *info,
NTP_Local_Timestamp *local_rx)
{
NTP_EFExpNetCorrection ef;
if (CNF_GetPtpPort() == 0) {
DEBUG_LOG("ptpport disabled");
return 1;
}
memset(&ef, 0, sizeof (ef));
ef.magic = htonl(NTP_EF_EXP_NET_CORRECTION_MAGIC);
if (info->mode != MODE_CLIENT && local_rx->net_correction > local_rx->rx_duration) {
UTI_DoubleToNtp64(local_rx->net_correction, &ef.correction);
}
if (!NEF_AddField(message, info, NTP_EF_EXP_NET_CORRECTION, &ef, sizeof (ef))) {
DEBUG_LOG("Could not add EF");
return 0;
}
info->ext_field_flags |= NTP_EF_FLAG_EXP_NET_CORRECTION;
return 1;
}
@ -1224,9 +1262,13 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
return 0;
if (ext_field_flags) {
if (ext_field_flags & NTP_EF_FLAG_EXP1) {
if (!add_ext_exp1(&message, &info, smooth_time ? NULL : &local_receive,
our_root_delay, our_root_dispersion))
if (ext_field_flags & NTP_EF_FLAG_EXP_MONO_ROOT) {
if (!add_ef_mono_root(&message, &info, smooth_time ? NULL : &local_receive,
our_root_delay, our_root_dispersion))
return 0;
}
if (ext_field_flags & NTP_EF_FLAG_EXP_NET_CORRECTION) {
if (!add_ef_net_correction(&message, &info, local_rx))
return 0;
}
}
@ -1297,6 +1339,8 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
local_tx->ts = local_transmit;
local_tx->err = local_transmit_err;
local_tx->source = NTP_TS_DAEMON;
local_tx->rx_duration = 0.0;
local_tx->net_correction = 0.0;
}
if (local_ntp_rx)
@ -1490,6 +1534,14 @@ is_zero_data(unsigned char *data, int length)
/* ================================================== */
static int
is_exp_ef(void *body, int body_length, int expected_body_length, uint32_t magic)
{
return body_length == expected_body_length && *(uint32_t *)body == htonl(magic);
}
/* ================================================== */
static int
parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info)
{
@ -1576,10 +1628,15 @@ parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info)
case NTP_EF_NTS_AUTH_AND_EEF:
info->auth.mode = NTP_AUTH_NTS;
break;
case NTP_EF_EXP1:
if (ef_body_length == sizeof (NTP_ExtFieldExp1) &&
ntohl(((NTP_ExtFieldExp1 *)ef_body)->magic) == NTP_EF_EXP1_MAGIC)
info->ext_field_flags |= NTP_EF_FLAG_EXP1;
case NTP_EF_EXP_MONO_ROOT:
if (is_exp_ef(ef_body, ef_body_length, sizeof (NTP_EFExpMonoRoot),
NTP_EF_EXP_MONO_ROOT_MAGIC))
info->ext_field_flags |= NTP_EF_FLAG_EXP_MONO_ROOT;
break;
case NTP_EF_EXP_NET_CORRECTION:
if (is_exp_ef(ef_body, ef_body_length, sizeof (NTP_EFExpNetCorrection),
NTP_EF_EXP_NET_CORRECTION_MAGIC))
info->ext_field_flags |= NTP_EF_FLAG_EXP_NET_CORRECTION;
break;
default:
DEBUG_LOG("Unknown extension field type=%x", (unsigned int)ef_type);
@ -1607,6 +1664,53 @@ parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info)
/* ================================================== */
static void
apply_net_correction(NTP_Sample *sample, NTP_Local_Timestamp *rx, NTP_Local_Timestamp *tx,
double precision)
{
double rx_correction, tx_correction, low_delay_correction;
/* Require some correction from transparent clocks to be present
in both directions (not just the local RX timestamp correction) */
if (rx->net_correction <= rx->rx_duration || tx->net_correction <= 0.0)
return;
/* With perfect corrections from PTP transparent clocks and short cables
the peer delay would be close to zero, or even negative if the server or
transparent clocks were running faster than client, which would invert the
sample weighting. Adjust the correction to get a delay corresponding to
a direct connection to the server. For simplicity, assume the TX and RX
link speeds are equal. If not, the reported delay will be wrong, but it
will not cause an error in the offset. */
rx_correction = rx->net_correction - rx->rx_duration;
tx_correction = tx->net_correction - rx->rx_duration;
/* Use a slightly smaller value in the correction of delay to not overcorrect
if the transparent clocks run up to 100 ppm fast and keep a part of the
uncorrected delay for the sample weighting */
low_delay_correction = (rx_correction + tx_correction) *
(1.0 - MAX_NET_CORRECTION_FREQ);
/* Make sure the correction is sane. The values are not authenticated! */
if (low_delay_correction < 0.0 || low_delay_correction > sample->peer_delay) {
DEBUG_LOG("Invalid correction %.9f peer_delay=%.9f",
low_delay_correction, sample->peer_delay);
return;
}
/* Correct the offset and peer delay, but not the root delay to not
change the estimated maximum error */
sample->offset += (rx_correction - tx_correction) / 2.0;
sample->peer_delay -= low_delay_correction;
if (sample->peer_delay < precision)
sample->peer_delay = precision;
DEBUG_LOG("Applied correction rx=%.9f tx=%.9f dur=%.9f",
rx->net_correction, tx->net_correction, rx->rx_duration);
}
/* ================================================== */
static int
check_delay_ratio(NCR_Instance inst, SST_Stats stats,
struct timespec *sample_time, double delay)
@ -1868,18 +1972,20 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
/* Extension fields */
int parsed, ef_length, ef_type, ef_body_length;
void *ef_body;
NTP_ExtFieldExp1 *ef_exp1;
NTP_EFExpMonoRoot *ef_mono_root;
NTP_EFExpNetCorrection *ef_net_correction;
NTP_Local_Timestamp local_receive, local_transmit;
double remote_interval, local_interval, response_time;
double delay_time, precision, mono_doffset;
double delay_time, precision, mono_doffset, net_correction;
int updated_timestamps;
/* ==================== */
stats = SRC_GetSourcestats(inst->source);
ef_exp1 = NULL;
ef_mono_root = NULL;
ef_net_correction = NULL;
/* Find requested non-authentication extension fields */
if (inst->ext_field_flags & info->ext_field_flags) {
@ -1889,11 +1995,17 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
break;
switch (ef_type) {
case NTP_EF_EXP1:
if (inst->ext_field_flags & NTP_EF_FLAG_EXP1 &&
ef_body_length == sizeof (*ef_exp1) &&
ntohl(((NTP_ExtFieldExp1 *)ef_body)->magic) == NTP_EF_EXP1_MAGIC)
ef_exp1 = ef_body;
case NTP_EF_EXP_MONO_ROOT:
if (inst->ext_field_flags & NTP_EF_FLAG_EXP_MONO_ROOT &&
is_exp_ef(ef_body, ef_body_length, sizeof (*ef_mono_root),
NTP_EF_EXP_MONO_ROOT_MAGIC))
ef_mono_root = ef_body;
break;
case NTP_EF_EXP_NET_CORRECTION:
if (inst->ext_field_flags & NTP_EF_FLAG_EXP_NET_CORRECTION &&
is_exp_ef(ef_body, ef_body_length, sizeof (*ef_net_correction),
NTP_EF_EXP_NET_CORRECTION_MAGIC))
ef_net_correction = ef_body;
break;
}
}
@ -1902,9 +2014,9 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
pkt_leap = NTP_LVM_TO_LEAP(message->lvm);
pkt_version = NTP_LVM_TO_VERSION(message->lvm);
pkt_refid = ntohl(message->reference_id);
if (ef_exp1) {
pkt_root_delay = UTI_Ntp32f28ToDouble(ef_exp1->root_delay);
pkt_root_dispersion = UTI_Ntp32f28ToDouble(ef_exp1->root_dispersion);
if (ef_mono_root) {
pkt_root_delay = UTI_Ntp32f28ToDouble(ef_mono_root->root_delay);
pkt_root_dispersion = UTI_Ntp32f28ToDouble(ef_mono_root->root_dispersion);
} else {
pkt_root_delay = UTI_Ntp32ToDouble(message->root_delay);
pkt_root_dispersion = UTI_Ntp32ToDouble(message->root_dispersion);
@ -1989,11 +2101,11 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
the new sample. In the interleaved mode, cancel the correction out in
remote timestamps of the previous request and response, which were
captured before the source accumulated the new time corrections. */
if (ef_exp1 && inst->remote_mono_epoch == ntohl(ef_exp1->mono_epoch) &&
!UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts) &&
if (ef_mono_root && inst->remote_mono_epoch == ntohl(ef_mono_root->mono_epoch) &&
!UTI_IsZeroNtp64(&ef_mono_root->mono_receive_ts) &&
!UTI_IsZeroNtp64(&inst->remote_ntp_monorx)) {
mono_doffset =
UTI_DiffNtp64ToDouble(&ef_exp1->mono_receive_ts, &inst->remote_ntp_monorx) -
UTI_DiffNtp64ToDouble(&ef_mono_root->mono_receive_ts, &inst->remote_ntp_monorx) -
UTI_DiffNtp64ToDouble(&message->receive_ts, &inst->remote_ntp_rx);
if (fabs(mono_doffset) > MAX_MONO_DOFFSET)
mono_doffset = 0.0;
@ -2001,6 +2113,12 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
mono_doffset = 0.0;
}
if (ef_net_correction) {
net_correction = UTI_Ntp64ToDouble(&ef_net_correction->correction);
} else {
net_correction = 0.0;
}
/* Select remote and local timestamps for the new sample */
if (interleaved_packet) {
/* Prefer previous local TX and remote RX timestamps if it will make
@ -2020,6 +2138,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
UTI_Ntp64ToTimespec(&message->receive_ts, &remote_receive);
UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_request_receive);
local_transmit = inst->local_tx;
local_transmit.net_correction = net_correction;
root_delay = MAX(pkt_root_delay, inst->remote_root_delay);
root_dispersion = MAX(pkt_root_dispersion, inst->remote_root_dispersion);
}
@ -2034,6 +2153,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
remote_request_receive = remote_receive;
local_receive = *rx_ts;
local_transmit = inst->local_tx;
local_transmit.net_correction = net_correction;
root_delay = pkt_root_delay;
root_dispersion = pkt_root_dispersion;
}
@ -2077,6 +2197,9 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
skew * fabs(local_interval);
sample.root_delay = root_delay + sample.peer_delay;
sample.root_dispersion = root_dispersion + sample.peer_dispersion;
/* Apply corrections from PTP transparent clocks if available and sane */
apply_net_correction(&sample, &local_receive, &local_transmit, precision);
/* If the source is an active peer, this is the minimum assumed interval
between previous two transmissions (if not constrained by minpoll) */
@ -2085,8 +2208,11 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
/* Additional tests required to pass before accumulating the sample */
/* Test A requires that the minimum estimate of the peer delay is not
larger than the configured maximum, in both client modes that the server
/* Test A combines multiple tests to avoid changing the measurements log
format and ntpdata report. It requires that the minimum estimate of the
peer delay is not larger than the configured maximum, it is not a
response in the 'warm up' exchange, the configured offset correction is
within the supported NTP interval, both client modes that the server
processing time is sane, in interleaved client/server mode that the
previous response was not in basic mode (which prevents using timestamps
that minimise delay error), and in interleaved symmetric mode that the
@ -2094,6 +2220,8 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
a missed response */
testA = sample.peer_delay - sample.peer_dispersion <= inst->max_delay &&
precision <= inst->max_delay &&
inst->presend_done <= 0 &&
UTI_IsTimeOffsetSane(&sample.time, sample.offset) &&
!(inst->mode == MODE_CLIENT && response_time > MAX_SERVER_INTERVAL) &&
!(inst->mode == MODE_CLIENT && interleaved_packet &&
UTI_IsZeroTimespec(&inst->prev_local_tx.ts) &&
@ -2132,6 +2260,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
sample.root_delay = sample.root_dispersion = 0.0;
sample.time = rx_ts->ts;
mono_doffset = 0.0;
net_correction = 0.0;
local_receive = *rx_ts;
local_transmit = inst->local_tx;
testA = testB = testC = testD = 0;
@ -2165,9 +2294,9 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
/* If available, update the monotonic timestamp and accumulate the offset.
This needs to be done here to not lose changes in remote_ntp_rx in
symmetric mode when there are multiple responses per request. */
if (ef_exp1 && !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts)) {
inst->remote_mono_epoch = ntohl(ef_exp1->mono_epoch);
inst->remote_ntp_monorx = ef_exp1->mono_receive_ts;
if (ef_mono_root && !UTI_IsZeroNtp64(&ef_mono_root->mono_receive_ts)) {
inst->remote_mono_epoch = ntohl(ef_mono_root->mono_epoch);
inst->remote_ntp_monorx = ef_mono_root->mono_receive_ts;
inst->mono_doffset += mono_doffset;
} else {
inst->remote_mono_epoch = 0;
@ -2175,8 +2304,11 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
inst->mono_doffset = 0.0;
}
/* Don't use the same set of timestamps for the next sample */
if (interleaved_packet)
inst->local_tx.net_correction = net_correction;
/* Avoid reusing timestamps of an accumulated sample when switching
from basic mode to interleaved mode */
if (interleaved_packet || !good_packet)
inst->prev_local_tx = inst->local_tx;
else
zero_local_timestamp(&inst->prev_local_tx);
@ -2195,15 +2327,11 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
/* Accept at most one response per request. The NTP specification recommends
resetting local_ntp_tx to make the following packets fail test2 or test3,
but that would not allow the code above to make multiple updates of the
timestamps in symmetric mode. Also, ignore presend responses. */
timestamps in symmetric mode. */
if (inst->valid_rx) {
test2 = test3 = 0;
valid_packet = synced_packet = good_packet = 0;
} else if (valid_packet) {
if (inst->presend_done) {
testA = 0;
good_packet = 0;
}
inst->valid_rx = 1;
}
@ -2246,13 +2374,17 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
SRC_UpdateReachability(inst->source, synced_packet);
if (synced_packet) {
if (inst->copy && inst->remote_stratum > 0) {
/* Assume the reference ID and stratum of the server */
if (inst->copy) {
/* Assume the reference ID and stratum of the server */
if (synced_packet && inst->remote_stratum > 0) {
inst->remote_stratum--;
SRC_SetRefid(inst->source, ntohl(message->reference_id), &inst->remote_addr.ip_addr);
} else {
SRC_ResetInstance(inst->source);
}
}
if (synced_packet) {
SRC_UpdateStatus(inst->source, MAX(inst->remote_stratum, inst->min_stratum), pkt_leap);
if (inst->delay_quant)
@ -2404,6 +2536,10 @@ NCR_ProcessRxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
NTP_PacketInfo info;
inst->report.total_rx_count++;
if (rx_ts->source == NTP_TS_KERNEL)
inst->report.total_kernel_rx_ts++;
else if (rx_ts->source == NTP_TS_HARDWARE)
inst->report.total_hw_rx_ts++;
if (!parse_packet(message, length, &info))
return 0;
@ -2526,6 +2662,7 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
NTP_Local_Timestamp local_tx, *tx_ts;
NTP_int64 ntp_rx, *local_ntp_rx;
int log_index, interleaved, poll, version;
CLG_Limit limit;
uint32_t kod;
/* Ignore the packet if it wasn't received by server socket */
@ -2571,7 +2708,8 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
log_index = CLG_LogServiceAccess(CLG_NTP, &remote_addr->ip_addr, &rx_ts->ts);
/* Don't reply to all requests if the rate is excessive */
if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTP, log_index)) {
limit = log_index >= 0 ? CLG_LimitServiceRate(CLG_NTP, log_index) : CLG_PASS;
if (limit == CLG_DROP) {
DEBUG_LOG("NTP packet discarded to limit response rate");
return;
}
@ -2585,6 +2723,13 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
return;
}
if (limit == CLG_KOD) {
/* Don't respond if there is a conflict with the NTS NAK */
if (kod != 0)
return;
kod = KOD_RATE;
}
local_ntp_rx = NULL;
tx_ts = NULL;
interleaved = 0;
@ -2602,8 +2747,7 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts) != 0) {
ntp_rx = message->originate_ts;
local_ntp_rx = &ntp_rx;
UTI_ZeroTimespec(&local_tx.ts);
local_tx.source = NTP_TS_DAEMON;
zero_local_timestamp(&local_tx);
interleaved = CLG_GetNtpTxTimestamp(&ntp_rx, &local_tx.ts, &local_tx.source);
tx_ts = &local_tx;
@ -2611,7 +2755,7 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
CLG_DisableNtpTimestamps(&ntp_rx);
}
CLG_UpdateNtpStats(kod != 0 && info.auth.mode != NTP_AUTH_NONE &&
CLG_UpdateNtpStats(kod == 0 && info.auth.mode != NTP_AUTH_NONE &&
info.auth.mode != NTP_AUTH_MSSNTP,
rx_ts->source, interleaved ? tx_ts->source : NTP_TS_DAEMON);
@ -2687,8 +2831,11 @@ NCR_ProcessTxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
message);
if (tx_ts->source == NTP_TS_HARDWARE) {
inst->report.total_hw_tx_ts++;
if (has_saved_response(inst))
process_saved_response(inst);
} else if (tx_ts->source == NTP_TS_KERNEL) {
inst->report.total_kernel_tx_ts++;
}
}
@ -2892,6 +3039,16 @@ NCR_ModifyMinstratum(NCR_Instance inst, int new_min_stratum)
/* ================================================== */
void
NCR_ModifyOffset(NCR_Instance inst, double new_offset)
{
inst->offset_correction = new_offset;
LOG(LOGS_INFO, "Source %s new offset %f",
UTI_IPToString(&inst->remote_addr.ip_addr), new_offset);
}
/* ================================================== */
void
NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target)
{

View file

@ -42,6 +42,8 @@ typedef struct {
struct timespec ts;
double err;
NTP_Timestamp_Source source;
double rx_duration;
double net_correction;
} NTP_Local_Timestamp;
/* This is a private data type used for storing the instance record for
@ -111,6 +113,8 @@ extern void NCR_ModifyMaxdelaydevratio(NCR_Instance inst, double new_max_delay_d
extern void NCR_ModifyMinstratum(NCR_Instance inst, int new_min_stratum);
extern void NCR_ModifyOffset(NCR_Instance inst, double new_offset);
extern void NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target);
extern void NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples);

View file

@ -431,6 +431,9 @@ process_message(SCK_Message *message, int sock_fd, int event)
SCH_GetLastEventTime(&local_ts.ts, &local_ts.err, NULL);
local_ts.source = NTP_TS_DAEMON;
local_ts.rx_duration = 0.0;
local_ts.net_correction = 0.0;
sched_ts = local_ts.ts;
if (message->addr_type != SCK_ADDR_IP) {
@ -456,7 +459,7 @@ process_message(SCK_Message *message, int sock_fd, int event)
DEBUG_LOG("Updated RX timestamp delay=%.9f tss=%u",
UTI_DiffTimespecsToDouble(&sched_ts, &local_ts.ts), local_ts.source);
if (!NIO_UnwrapMessage(message, sock_fd))
if (!NIO_UnwrapMessage(message, sock_fd, &local_ts.net_correction))
return;
/* Just ignore the packet if it's not of a recognized length */
@ -495,8 +498,9 @@ read_from_socket(int sock_fd, int event, void *anything)
/* ================================================== */
int
NIO_UnwrapMessage(SCK_Message *message, int sock_fd)
NIO_UnwrapMessage(SCK_Message *message, int sock_fd, double *net_correction)
{
double ptp_correction;
PTP_NtpMessage *msg;
if (!is_ptp_socket(sock_fd))
@ -509,9 +513,11 @@ NIO_UnwrapMessage(SCK_Message *message, int sock_fd)
msg = message->data;
if (msg->header.type != PTP_TYPE_DELAY_REQ || msg->header.version != PTP_VERSION ||
if ((msg->header.type != PTP_TYPE_DELAY_REQ && msg->header.type != PTP_TYPE_SYNC) ||
(msg->header.version != PTP_VERSION_2 &&
(msg->header.version != PTP_VERSION_2_1 || msg->header.min_sdoid != 0)) ||
ntohs(msg->header.length) != message->length ||
msg->header.domain != PTP_DOMAIN_NTP ||
msg->header.domain != CNF_GetPtpDomain() ||
ntohs(msg->header.flags) != PTP_FLAG_UNICAST ||
ntohs(msg->tlv_header.type) != PTP_TLV_NTP ||
ntohs(msg->tlv_header.length) != message->length - PTP_NTP_PREFIX_LENGTH) {
@ -522,7 +528,14 @@ NIO_UnwrapMessage(SCK_Message *message, int sock_fd)
message->data = (char *)message->data + PTP_NTP_PREFIX_LENGTH;
message->length -= PTP_NTP_PREFIX_LENGTH;
DEBUG_LOG("Unwrapped PTP->NTP len=%d", message->length);
ptp_correction = UTI_Integer64NetworkToHost(*(Integer64 *)msg->header.correction) /
((1 << 16) * 1.0e9);
/* Use the correction only if the RX duration is known (i.e. HW timestamp) */
if (*net_correction > 0.0)
*net_correction += ptp_correction;
DEBUG_LOG("Unwrapped PTP->NTP len=%d corr=%.9f", message->length, ptp_correction);
return 1;
}
@ -550,9 +563,9 @@ wrap_message(SCK_Message *message, int sock_fd)
memset(ptp_message, 0, PTP_NTP_PREFIX_LENGTH);
ptp_message->header.type = PTP_TYPE_DELAY_REQ;
ptp_message->header.version = PTP_VERSION;
ptp_message->header.version = PTP_VERSION_2;
ptp_message->header.length = htons(PTP_NTP_PREFIX_LENGTH + message->length);
ptp_message->header.domain = PTP_DOMAIN_NTP;
ptp_message->header.domain = CNF_GetPtpDomain();
ptp_message->header.flags = htons(PTP_FLAG_UNICAST);
ptp_message->header.sequence_id = htons(sequence_id++);
ptp_message->tlv_header.type = htons(PTP_TLV_NTP);

View file

@ -64,7 +64,7 @@ extern int NIO_IsServerSocketOpen(void);
extern int NIO_IsServerConnectable(NTP_Remote_Address *remote_addr);
/* Function to unwrap an NTP message from non-native transport (e.g. PTP) */
extern int NIO_UnwrapMessage(SCK_Message *message, int sock_fd);
extern int NIO_UnwrapMessage(SCK_Message *message, int sock_fd, double *net_correction);
/* Function to transmit a packet */
extern int NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,

View file

@ -559,7 +559,7 @@ process_hw_timestamp(struct Interface *iface, struct timespec *hw_ts,
NTP_Local_Timestamp *local_ts, int rx_ntp_length, int family,
int l2_length)
{
double rx_correction, ts_delay, local_err;
double rx_correction = 0.0, ts_delay, local_err;
struct timespec ts;
poll_phc(iface, &local_ts->ts);
@ -600,6 +600,10 @@ process_hw_timestamp(struct Interface *iface, struct timespec *hw_ts,
local_ts->ts = ts;
local_ts->err = local_err;
local_ts->source = NTP_TS_HARDWARE;
local_ts->rx_duration = rx_correction;
/* Network correction needs to include the RX duration to avoid
asymmetric correction with asymmetric link speeds */
local_ts->net_correction = rx_correction;
}
/* ================================================== */
@ -723,6 +727,7 @@ NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
{
struct Interface *iface;
int is_tx, ts_if_index, l2_length;
double c = 0.0;
is_tx = event == SCH_FILE_EXCEPTION;
iface = NULL;
@ -783,7 +788,7 @@ NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
return 1;
}
if (!NIO_UnwrapMessage(message, local_addr->sock_fd))
if (!NIO_UnwrapMessage(message, local_addr->sock_fd, &c))
return 1;
if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet))

View file

@ -99,6 +99,9 @@ static int sock_fd;
/* Flag indicating if the MS-SNTP authentication is enabled */
static int enabled;
/* Flag limiting logging of connection error messages */
static int logged_connection_error;
/* ================================================== */
static void read_write_socket(int sock_fd, int event, void *anything);
@ -134,6 +137,14 @@ open_socket(void)
sock_fd = SCK_OpenUnixStreamSocket(path, NULL, 0);
if (sock_fd < 0) {
sock_fd = INVALID_SOCK_FD;
/* Log an error only once before a successful exchange to avoid
flooding the system log */
if (!logged_connection_error) {
LOG(LOGS_ERR, "Could not connect to signd socket %s : %s", path, strerror(errno));
logged_connection_error = 1;
}
return 0;
}
@ -160,6 +171,8 @@ process_response(SignInstance *inst)
return;
}
logged_connection_error = 0;
/* Check if the file descriptor is still valid */
if (!NIO_IsServerSocket(inst->local_addr.sock_fd)) {
DEBUG_LOG("Invalid NTP socket");

View file

@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2011-2012, 2014, 2016, 2020-2023
* Copyright (C) Miroslav Lichvar 2011-2012, 2014, 2016, 2020-2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -61,6 +61,8 @@ typedef struct {
(may be an IP address) */
IPAddr resolved_addr; /* Address resolved from the name, which can be
different from remote_addr (e.g. NTS-KE) */
int family; /* IP family of acceptable resolved addresses
(IPADDR_UNSPEC if any) */
int pool_id; /* ID of the pool from which was this source
added or INVALID_POOL */
int tentative; /* Flag indicating there was no valid response
@ -98,6 +100,8 @@ struct UnresolvedSource {
int pool_id;
/* Name to be resolved */
char *name;
/* Address family to filter resolved addresses */
int family;
/* Flag indicating addresses should be used in a random order */
int random_order;
/* Flag indicating current address should be replaced only if it is
@ -215,8 +219,14 @@ NSR_Finalise(void)
ARR_DestroyInstance(pools);
SCH_RemoveTimeout(resolving_id);
while (unresolved_sources)
remove_unresolved_source(unresolved_sources);
/* Leave the unresolved sources allocated if the async resolver is running
to avoid reading the name from freed memory. The handler will not be
called as the scheduler should no longer be running at this point. */
if (!resolving_source) {
while (unresolved_sources)
remove_unresolved_source(unresolved_sources);
}
initialised = 0;
}
@ -353,7 +363,7 @@ log_source(SourceRecord *record, int addition, int once_per_pool)
/* Procedure to add a new source */
static NSR_Status
add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type,
add_source(NTP_Remote_Address *remote_addr, char *name, int family, NTP_Source_Type type,
SourceParameters *params, int pool_id, uint32_t conf_id)
{
SourceRecord *record;
@ -391,6 +401,7 @@ add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type,
record->data = NCR_CreateInstance(remote_addr, type, params, record->name);
record->remote_addr = NCR_GetRemoteAddress(record->data);
record->resolved_addr = remote_addr->ip_addr;
record->family = family;
record->pool_id = pool_id;
record->tentative = 1;
record->conf_id = conf_id;
@ -552,6 +563,10 @@ process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs
DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr));
/* Skip addresses not from the requested family */
if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip_addr.family)
continue;
if (us->pool_id != INVALID_POOL) {
/* In the pool resolving mode, try to replace a source from
the pool which does not have a real address yet */
@ -629,13 +644,16 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any
next = us->next;
/* Don't repeat the resolving if it (permanently) failed, it was a
replacement of a real address, or all addresses are already resolved */
if (status == DNS_Failure || UTI_IsIPReal(&us->address.ip_addr) || is_resolved(us))
replacement of a real address, a refreshment, or all addresses are
already resolved */
if (status == DNS_Failure || UTI_IsIPReal(&us->address.ip_addr) ||
us->refreshment || is_resolved(us))
remove_unresolved_source(us);
/* If a restart was requested and this was the last source in the list,
start with the first source again (if there still is one) */
if (!next && resolving_restart) {
DEBUG_LOG("Restarting");
next = unresolved_sources;
resolving_restart = 0;
}
@ -700,11 +718,15 @@ static void
append_unresolved_source(struct UnresolvedSource *us)
{
struct UnresolvedSource **i;
int n;
for (i = &unresolved_sources; *i; i = &(*i)->next)
for (i = &unresolved_sources, n = 0; *i; i = &(*i)->next, n++)
;
*i = us;
us->next = NULL;
DEBUG_LOG("Added unresolved source #%d pool_id=%d random=%d refresh=%d",
n + 1, us->pool_id, us->random_order, us->refreshment);
}
/* ================================================== */
@ -754,8 +776,19 @@ static int get_unused_pool_id(void)
static uint32_t
get_next_conf_id(uint32_t *conf_id)
{
SourceRecord *record;
unsigned int i;
again:
last_conf_id++;
/* Make sure the ID is not already used (after 32-bit wraparound) */
for (i = 0; i < ARR_GetSize(records); i++) {
record = get_record(i);
if (record->remote_addr && record->conf_id == last_conf_id)
goto again;
}
if (conf_id)
*conf_id = last_conf_id;
@ -768,14 +801,14 @@ NSR_Status
NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
SourceParameters *params, uint32_t *conf_id)
{
return add_source(remote_addr, NULL, type, params, INVALID_POOL,
return add_source(remote_addr, NULL, IPADDR_UNSPEC, type, params, INVALID_POOL,
get_next_conf_id(conf_id));
}
/* ================================================== */
NSR_Status
NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
NSR_AddSourceByName(char *name, int family, int port, int pool, NTP_Source_Type type,
SourceParameters *params, uint32_t *conf_id)
{
struct UnresolvedSource *us;
@ -787,7 +820,9 @@ NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
/* If the name is an IP address, add the source with the address directly */
if (UTI_StringToIP(name, &remote_addr.ip_addr)) {
remote_addr.port = port;
return add_source(&remote_addr, name, type, params, INVALID_POOL,
if (family != IPADDR_UNSPEC && family != remote_addr.ip_addr.family)
return NSR_InvalidAF;
return add_source(&remote_addr, name, IPADDR_UNSPEC, type, params, INVALID_POOL,
get_next_conf_id(conf_id));
}
@ -799,6 +834,7 @@ NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
us = MallocNew(struct UnresolvedSource);
us->name = Strdup(name);
us->family = family;
us->random_order = 0;
us->refreshment = 0;
@ -835,7 +871,7 @@ NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
for (i = 0; i < new_sources; i++) {
if (i > 0)
remote_addr.ip_addr.addr.id = ++last_address_id;
if (add_source(&remote_addr, name, type, params, us->pool_id, cid) != NSR_Success)
if (add_source(&remote_addr, name, family, type, params, us->pool_id, cid) != NSR_Success)
return NSR_TooManySources;
}
@ -844,6 +880,31 @@ NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
/* ================================================== */
const char *
NSR_StatusToString(NSR_Status status)
{
switch (status) {
case NSR_Success:
return "Success";
case NSR_NoSuchSource:
return "No such source";
case NSR_AlreadyInUse:
return "Already in use";
case NSR_TooManySources:
return "Too many sources";
case NSR_InvalidAF:
return "Invalid address";
case NSR_InvalidName:
return "Invalid name";
case NSR_UnresolvedName:
return "Unresolved name";
default:
return "?";
}
}
/* ================================================== */
void
NSR_SetSourceResolvingEndHandler(NSR_SourceResolvingEndHandler handler)
{
@ -1001,6 +1062,7 @@ resolve_source_replacement(SourceRecord *record, int refreshment)
us = MallocNew(struct UnresolvedSource);
us->name = Strdup(record->name);
us->family = record->family;
/* Ignore the order of addresses from the resolver to not get
stuck with a pair of unreachable or otherwise unusable servers
(e.g. falsetickers) in case the order doesn't change, or a group
@ -1011,7 +1073,10 @@ resolve_source_replacement(SourceRecord *record, int refreshment)
us->address = *record->remote_addr;
append_unresolved_source(us);
NSR_ResolveSources();
/* Don't restart resolving round if already running */
if (!resolving_source)
NSR_ResolveSources();
}
/* ================================================== */
@ -1406,6 +1471,20 @@ NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum)
/* ================================================== */
int
NSR_ModifyOffset(IPAddr *address, double new_offset)
{
int slot;
if (!find_slot(address, &slot))
return 0;
NCR_ModifyOffset(get_record(slot)->data, new_offset);
return 1;
}
/* ================================================== */
int
NSR_ModifyPolltarget(IPAddr *address, int new_poll_target)
{

View file

@ -55,11 +55,16 @@ extern NSR_Status NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type
/* Procedure to add a new server, peer source, or pool of servers specified by
name instead of address. The name is resolved in exponentially increasing
intervals until it succeeds or fails with a non-temporary error. If the
name is an address, it is equivalent to NSR_AddSource(). */
extern NSR_Status NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
intervals until it succeeds or fails with a non-temporary error. The
specified family filters resolved addresses. If the name is an address
and its family does not conflict with the specified family, it is equivalent
to NSR_AddSource(). */
extern NSR_Status NSR_AddSourceByName(char *name, int family, int port, int pool,
NTP_Source_Type type,
SourceParameters *params, uint32_t *conf_id);
extern const char *NSR_StatusToString(NSR_Status status);
/* Function type for handlers to be called back when an attempt
* (possibly unsuccessful) to resolve unresolved sources ends */
typedef void (*NSR_SourceResolvingEndHandler)(void);
@ -135,6 +140,8 @@ extern int NSR_ModifyMaxdelaydevratio(IPAddr *address, double new_max_delay_rati
extern int NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum);
extern int NSR_ModifyOffset(IPAddr *address, double new_offset);
extern int NSR_ModifyPolltarget(IPAddr *address, int new_poll_target);
extern int NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, IPAddr *mask, IPAddr *address);

View file

@ -40,6 +40,7 @@
#define NKE_RECORD_COOKIE 5
#define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6
#define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7
#define NKE_RECORD_COMPLIANT_128GCM_EXPORT 1024
#define NKE_NEXT_PROTOCOL_NTPV4 0
@ -49,8 +50,6 @@
#define NKE_ALPN_NAME "ntske/1"
#define NKE_EXPORTER_LABEL "EXPORTER-network-time-security"
#define NKE_EXPORTER_CONTEXT_C2S "\x0\x0\x0\xf\x0"
#define NKE_EXPORTER_CONTEXT_S2C "\x0\x0\x0\xf\x1"
#define NKE_MAX_MESSAGE_LENGTH 16384
#define NKE_MAX_RECORD_BODY_LENGTH 256

View file

@ -2,7 +2,7 @@
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020-2021
* Copyright (C) Miroslav Lichvar 2020-2021, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -50,7 +50,9 @@ struct NKC_Instance_Record {
int got_response;
int resolving_name;
int compliant_128gcm;
NKE_Context context;
NKE_Context alt_context;
NKE_Cookie cookies[NKE_MAX_COOKIES];
int num_cookies;
char server_name[NKE_MAX_RECORD_BODY_LENGTH + 2];
@ -98,12 +100,14 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg
/* ================================================== */
#define MAX_AEAD_ALGORITHMS 4
static int
prepare_request(NKC_Instance inst)
{
NKSN_Instance session = inst->session;
uint16_t data[2];
int length;
uint16_t data[MAX_AEAD_ALGORITHMS];
int i, aead_algorithm, length;
NKSN_BeginMessage(session);
@ -111,15 +115,24 @@ prepare_request(NKC_Instance inst)
if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, sizeof (data[0])))
return 0;
length = 0;
if (SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0)
data[length++] = htons(AEAD_AES_128_GCM_SIV);
if (SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0)
data[length++] = htons(AEAD_AES_SIV_CMAC_256);
for (i = length = 0; i < ARR_GetSize(CNF_GetNtsAeads()) && length < MAX_AEAD_ALGORITHMS;
i++) {
aead_algorithm = *(int *)ARR_GetElement(CNF_GetNtsAeads(), i);
if (SIV_GetKeyLength(aead_algorithm) > 0)
data[length++] = htons(aead_algorithm);
}
if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data,
length * sizeof (data[0])))
return 0;
for (i = 0; i < length; i++) {
if (data[i] == htons(AEAD_AES_128_GCM_SIV)) {
if (!NKSN_AddRecord(session, 0, NKE_RECORD_COMPLIANT_128GCM_EXPORT, NULL, 0))
return 0;
break;
}
}
if (!NKSN_EndMessage(session))
return 0;
@ -139,6 +152,8 @@ process_response(NKC_Instance inst)
assert(sizeof (data) % sizeof (uint16_t) == 0);
assert(sizeof (uint16_t) == 2);
inst->compliant_128gcm = 0;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
inst->num_cookies = 0;
inst->ntp_address.ip_addr.family = IPADDR_UNSPEC;
inst->ntp_address.port = 0;
@ -165,15 +180,31 @@ process_response(NKC_Instance inst)
next_protocol = NKE_NEXT_PROTOCOL_NTPV4;
break;
case NKE_RECORD_AEAD_ALGORITHM:
if (length != 2 || (ntohs(data[0]) != AEAD_AES_SIV_CMAC_256 &&
ntohs(data[0]) != AEAD_AES_128_GCM_SIV) ||
SIV_GetKeyLength(ntohs(data[0])) <= 0) {
DEBUG_LOG("Unexpected NTS-KE AEAD algorithm");
if (length != 2) {
DEBUG_LOG("Unexpected AEAD algorithm");
error = 1;
break;
}
aead_algorithm = ntohs(data[0]);
inst->context.algorithm = aead_algorithm;
for (i = 0; i < ARR_GetSize(CNF_GetNtsAeads()); i++) {
if (ntohs(data[0]) == *(int *)ARR_GetElement(CNF_GetNtsAeads(), i) &&
SIV_GetKeyLength(ntohs(data[0])) > 0) {
aead_algorithm = ntohs(data[0]);
inst->context.algorithm = aead_algorithm;
}
}
if (aead_algorithm < 0) {
DEBUG_LOG("Unexpected AEAD algorithm");
error = 1;
}
break;
case NKE_RECORD_COMPLIANT_128GCM_EXPORT:
if (length != 0) {
DEBUG_LOG("Non-empty compliant-128gcm record");
error = 1;
break;
}
DEBUG_LOG("Compliant AES-128-GCM-SIV export");
inst->compliant_128gcm = 1;
break;
case NKE_RECORD_ERROR:
if (length == 2)
@ -255,6 +286,7 @@ process_response(NKC_Instance inst)
static int
handle_message(void *arg)
{
SIV_Algorithm exporter_algorithm;
NKC_Instance inst = arg;
if (!process_response(inst)) {
@ -262,8 +294,25 @@ handle_message(void *arg)
return 0;
}
if (!NKSN_GetKeys(inst->session, inst->context.algorithm,
&inst->context.c2s, &inst->context.s2c))
exporter_algorithm = inst->context.algorithm;
/* With AES-128-GCM-SIV, set the algorithm ID in the RFC5705 key exporter
context incorrectly for compatibility with older chrony servers unless
the server confirmed support for the compliant context. Generate both
sets of keys in case the server uses the compliant context, but does not
support the negotiation record, assuming it will respond with an NTS NAK
to a request authenticated with the noncompliant key. */
if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !inst->compliant_128gcm) {
inst->alt_context.algorithm = inst->context.algorithm;
if (!NKSN_GetKeys(inst->session, inst->alt_context.algorithm, exporter_algorithm,
NKE_NEXT_PROTOCOL_NTPV4, &inst->alt_context.c2s, &inst->alt_context.s2c))
return 0;
exporter_algorithm = AEAD_AES_SIV_CMAC_256;
}
if (!NKSN_GetKeys(inst->session, inst->context.algorithm, exporter_algorithm,
NKE_NEXT_PROTOCOL_NTPV4, &inst->context.c2s, &inst->context.s2c))
return 0;
if (inst->server_name[0] != '\0') {
@ -428,7 +477,7 @@ NKC_IsActive(NKC_Instance inst)
/* ================================================== */
int
NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
NKC_GetNtsData(NKC_Instance inst, NKE_Context *context, NKE_Context *alt_context,
NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address)
{
@ -438,6 +487,7 @@ NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
return 0;
*context = inst->context;
*alt_context = inst->alt_context;
for (i = 0; i < inst->num_cookies && i < max_cookies; i++)
cookies[i] = inst->cookies[i];

View file

@ -46,7 +46,7 @@ extern int NKC_Start(NKC_Instance inst);
extern int NKC_IsActive(NKC_Instance inst);
/* Get the NTS data if the session was successful */
extern int NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
extern int NKC_GetNtsData(NKC_Instance inst, NKE_Context *context, NKE_Context *alt_context,
NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address);

View file

@ -2,7 +2,7 @@
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020, 2022
* Copyright (C) Miroslav Lichvar 2020, 2022, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -242,7 +242,7 @@ accept_connection(int listening_fd, int event, void *arg)
SCH_GetLastEventTime(&now, NULL, NULL);
log_index = CLG_LogServiceAccess(CLG_NTSKE, &addr.ip_addr, &now);
if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTSKE, log_index)) {
if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTSKE, log_index) != CLG_PASS) {
DEBUG_LOG("Rejected connection from %s (%s)",
UTI_IPSockAddrToString(&addr), "rate limit");
SCK_CloseSocket(sock_fd);
@ -337,8 +337,10 @@ helper_signal(int x)
/* ================================================== */
static int
prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_algorithm)
prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_algorithm,
int compliant_128gcm)
{
SIV_Algorithm exporter_algorithm;
NKE_Context context;
NKE_Cookie cookie;
char *ntp_server;
@ -371,6 +373,11 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a
if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, &datum, sizeof (datum)))
return 0;
if (aead_algorithm == AEAD_AES_128_GCM_SIV && compliant_128gcm) {
if (!NKSN_AddRecord(session, 0, NKE_RECORD_COMPLIANT_128GCM_EXPORT, NULL, 0))
return 0;
}
if (CNF_GetNTPPort() != NTP_PORT) {
datum = htons(CNF_GetNTPPort());
if (!NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_PORT_NEGOTIATION, &datum, sizeof (datum)))
@ -385,8 +392,16 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a
}
context.algorithm = aead_algorithm;
exporter_algorithm = aead_algorithm;
if (!NKSN_GetKeys(session, aead_algorithm, &context.c2s, &context.s2c))
/* With AES-128-GCM-SIV, set the algorithm ID in the RFC5705 key exporter
context incorrectly for compatibility with older chrony clients unless
the client requested the compliant context */
if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !compliant_128gcm)
exporter_algorithm = AEAD_AES_SIV_CMAC_256;
if (!NKSN_GetKeys(session, aead_algorithm, exporter_algorithm,
NKE_NEXT_PROTOCOL_NTPV4, &context.c2s, &context.s2c))
return 0;
for (i = 0; i < NKE_MAX_COOKIES; i++) {
@ -411,7 +426,8 @@ process_request(NKSN_Instance session)
int next_protocol_records = 0, aead_algorithm_records = 0;
int next_protocol_values = 0, aead_algorithm_values = 0;
int next_protocol = -1, aead_algorithm = -1, error = -1;
int i, critical, type, length;
int i, j, critical, type, length;
int compliant_128gcm = 0;
uint16_t data[NKE_MAX_RECORD_BODY_LENGTH / sizeof (uint16_t)];
assert(NKE_MAX_RECORD_BODY_LENGTH % sizeof (uint16_t) == 0);
@ -446,11 +462,21 @@ process_request(NKSN_Instance session)
for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
aead_algorithm_values++;
/* Use the first supported algorithm */
if (aead_algorithm < 0 && SIV_GetKeyLength(ntohs(data[i])) > 0)
aead_algorithm = ntohs(data[i]);
/* Use the first enabled and supported algorithm */
for (j = 0; j < ARR_GetSize(CNF_GetNtsAeads()); j++) {
if (ntohs(data[i]) == *(int *)ARR_GetElement(CNF_GetNtsAeads(), j) &&
aead_algorithm < 0 && SIV_GetKeyLength(ntohs(data[i])) > 0)
aead_algorithm = ntohs(data[i]);
}
}
break;
case NKE_RECORD_COMPLIANT_128GCM_EXPORT:
if (length != 0) {
error = NKE_ERROR_BAD_REQUEST;
break;
}
compliant_128gcm = 1;
break;
case NKE_RECORD_ERROR:
case NKE_RECORD_WARNING:
case NKE_RECORD_COOKIE:
@ -469,7 +495,7 @@ process_request(NKSN_Instance session)
error = NKE_ERROR_BAD_REQUEST;
}
if (!prepare_response(session, error, next_protocol, aead_algorithm))
if (!prepare_response(session, error, next_protocol, aead_algorithm, compliant_128gcm))
return 0;
return 1;
@ -685,6 +711,8 @@ run_helper(uid_t uid, gid_t gid, int scfilter_level)
DEBUG_LOG("Helper started");
SCK_CloseReusableSockets();
/* Suppress a log message about disabled clock control */
log_severity = LOG_GetMinSeverity();
LOG_SetMinSeverity(LOGS_ERR);

View file

@ -877,22 +877,42 @@ NKSN_GetRecord(NKSN_Instance inst, int *critical, int *type, int *body_length,
/* ================================================== */
int
NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c)
NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm algorithm, SIV_Algorithm exporter_algorithm,
int next_protocol, NKE_Key *c2s, NKE_Key *s2c)
{
int length = SIV_GetKeyLength(siv);
int length = SIV_GetKeyLength(algorithm);
struct {
uint16_t next_protocol;
uint16_t algorithm;
uint8_t is_s2c;
uint8_t _pad;
} context;
if (!inst->tls_session)
return 0;
if (length <= 0 || length > sizeof (c2s->key) || length > sizeof (s2c->key)) {
DEBUG_LOG("Invalid algorithm");
return 0;
}
assert(sizeof (context) == 6);
context.next_protocol = htons(next_protocol);
context.algorithm = htons(exporter_algorithm);
context.is_s2c = 0;
if (gnutls_prf_rfc5705(inst->tls_session,
sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
sizeof (NKE_EXPORTER_CONTEXT_C2S) - 1, NKE_EXPORTER_CONTEXT_C2S,
length, (char *)c2s->key) < 0 ||
gnutls_prf_rfc5705(inst->tls_session,
sizeof (context) - 1, (char *)&context,
length, (char *)c2s->key) < 0) {
DEBUG_LOG("Could not export key");
return 0;
}
context.is_s2c = 1;
if (gnutls_prf_rfc5705(inst->tls_session,
sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
sizeof (NKE_EXPORTER_CONTEXT_S2C) - 1, NKE_EXPORTER_CONTEXT_S2C,
sizeof (context) - 1, (char *)&context,
length, (char *)s2c->key) < 0) {
DEBUG_LOG("Could not export key");
return 0;

View file

@ -77,8 +77,11 @@ extern int NKSN_EndMessage(NKSN_Instance inst);
extern int NKSN_GetRecord(NKSN_Instance inst, int *critical, int *type, int *body_length,
void *body, int buffer_length);
/* Export NTS keys for a specified algorithm */
extern int NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c);
/* Export NTS keys for a specified algorithm (for compatibility reasons the
RFC5705 exporter context is allowed to have a different algorithm) */
extern int NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm algorithm,
SIV_Algorithm exporter_algorithm,
int next_protocol, NKE_Key *c2s, NKE_Key *s2c);
/* Check if the session has stopped */
extern int NKSN_IsStopped(NKSN_Instance inst);

View file

@ -72,6 +72,7 @@ struct NNC_Instance_Record {
double last_nke_success;
NKE_Context context;
NKE_Context alt_context;
unsigned int context_id;
NKE_Cookie cookies[NTS_MAX_COOKIES];
int num_cookies;
@ -105,6 +106,7 @@ reset_instance(NNC_Instance inst)
inst->last_nke_success = 0.0;
memset(&inst->context, 0, sizeof (inst->context));
memset(&inst->alt_context, 0, sizeof (inst->alt_context));
inst->context_id = 0;
memset(inst->cookies, 0, sizeof (inst->cookies));
inst->num_cookies = 0;
@ -165,6 +167,21 @@ check_cookies(NNC_Instance inst)
if (inst->num_cookies > 0 &&
((inst->nak_response && !inst->ok_response) ||
SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh())) {
/* Before dropping the cookies, check whether there is an alternate set of
keys available (exported with the compliant context for AES-128-GCM-SIV)
and the NAK was the only valid response after the last NTS-KE session,
indicating we use incorrect keys and switching to the other set of keys
for the following NTP requests might work */
if (inst->alt_context.algorithm != AEAD_SIV_INVALID &&
inst->alt_context.algorithm == inst->context.algorithm &&
inst->nke_attempts > 0 && inst->nak_response && !inst->ok_response) {
inst->context = inst->alt_context;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
DEBUG_LOG("Switched to compliant keys");
return 1;
}
inst->num_cookies = 0;
DEBUG_LOG("Dropped cookies");
}
@ -261,7 +278,7 @@ get_cookies(NNC_Instance inst)
assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES);
/* Get the new keys, cookies and NTP address if the session was successful */
got_data = NKC_GetNtsData(inst->nke, &inst->context,
got_data = NKC_GetNtsData(inst->nke, &inst->context, &inst->alt_context,
inst->cookies, &inst->num_cookies, NTS_MAX_COOKIES,
&ntp_address);
@ -520,6 +537,7 @@ NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
new NTS-KE session to be started as soon as the cookies run out. */
inst->nke_attempts = 0;
inst->next_nke_attempt = 0.0;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
return 1;
}
@ -643,6 +661,7 @@ load_cookies(NNC_Instance inst)
sscanf(words[0], "%u", &context_id) != 1 || sscanf(words[1], "%d", &algorithm) != 1)
goto error;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
inst->context.algorithm = algorithm;
inst->context.s2c.length = UTI_HexToBytes(words[2], inst->context.s2c.key,
sizeof (inst->context.s2c.key));
@ -687,6 +706,7 @@ error:
fclose(f);
memset(&inst->context, 0, sizeof (inst->context));
memset(&inst->alt_context, 0, sizeof (inst->alt_context));
inst->num_cookies = 0;
}

View file

@ -279,7 +279,7 @@ NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
}
/* NTS NAK response does not have any other fields */
if (kod)
if (kod == NTP_KOD_NTS_NAK)
return 1;
for (i = 0, plaintext_length = 0; i < server->num_cookies; i++) {

View file

@ -111,7 +111,7 @@ static const struct request_length request_lengths[] = {
REQ_LENGTH_ENTRY(null, null), /* REFRESH */
REQ_LENGTH_ENTRY(null, server_stats), /* SERVER_STATS */
{ 0, 0 }, /* CLIENT_ACCESSES_BY_INDEX2 - not supported */
REQ_LENGTH_ENTRY(local, null), /* LOCAL2 */
{ 0, 0 }, /* LOCAL2 - not supported */
REQ_LENGTH_ENTRY(ntp_data, ntp_data), /* NTP_DATA */
{ 0, 0 }, /* ADD_SERVER2 */
{ 0, 0 }, /* ADD_PEER2 */
@ -130,6 +130,8 @@ static const struct request_length request_lengths[] = {
REQ_LENGTH_ENTRY(null, null), /* RELOAD_SOURCES */
REQ_LENGTH_ENTRY(doffset, null), /* DOFFSET2 */
REQ_LENGTH_ENTRY(modify_select_opts, null), /* MODIFY_SELECTOPTS */
REQ_LENGTH_ENTRY(modify_offset, null), /* MODIFY_OFFSET */
REQ_LENGTH_ENTRY(local, null), /* LOCAL3 */
};
static const uint16_t reply_lengths[] = {
@ -149,7 +151,7 @@ static const uint16_t reply_lengths[] = {
RPY_LENGTH_ENTRY(smoothing), /* SMOOTHING */
0, /* SERVER_STATS - not supported */
0, /* CLIENT_ACCESSES_BY_INDEX2 - not supported */
RPY_LENGTH_ENTRY(ntp_data), /* NTP_DATA */
0, /* NTP_DATA - not supported */
RPY_LENGTH_ENTRY(manual_timestamp), /* MANUAL_TIMESTAMP2 */
RPY_LENGTH_ENTRY(manual_list), /* MANUAL_LIST2 */
RPY_LENGTH_ENTRY(ntp_source_name), /* NTP_SOURCE_NAME */
@ -159,6 +161,7 @@ static const uint16_t reply_lengths[] = {
RPY_LENGTH_ENTRY(select_data), /* SELECT_DATA */
0, /* SERVER_STATS3 - not supported */
RPY_LENGTH_ENTRY(server_stats), /* SERVER_STATS4 */
RPY_LENGTH_ENTRY(ntp_data), /* NTP_DATA2 */
};
/* ================================================== */

5
ptp.h
View file

@ -31,9 +31,10 @@
#include "ntp.h"
#define PTP_VERSION 2
#define PTP_VERSION_2 2
#define PTP_VERSION_2_1 (2 | 1 << 4)
#define PTP_TYPE_SYNC 0
#define PTP_TYPE_DELAY_REQ 1
#define PTP_DOMAIN_NTP 123
#define PTP_FLAG_UNICAST (1 << (2 + 8))
#define PTP_TLV_NTP 0x2023

View file

@ -63,6 +63,7 @@ struct RCL_Instance_Record {
int driver_poll;
int driver_polled;
int poll;
int reached;
int leap_status;
int local;
int pps_forced;
@ -166,8 +167,8 @@ RCL_AddRefclock(RefclockParameters *params)
if (!inst->driver->init && !inst->driver->poll)
LOG_FATAL("refclock driver %s is not compiled in", params->driver_name);
if (params->tai && !CNF_GetLeapSecTimezone())
LOG_FATAL("refclock tai option requires leapsectz");
if (params->tai && !CNF_GetLeapSecList() && !CNF_GetLeapSecTimezone())
LOG_FATAL("refclock tai option requires leapseclist or leapsectz");
inst->data = NULL;
inst->driver_parameter = Strdup(params->driver_parameter);
@ -175,6 +176,7 @@ RCL_AddRefclock(RefclockParameters *params)
inst->driver_poll = params->driver_poll;
inst->poll = params->poll;
inst->driver_polled = 0;
inst->reached = 0;
inst->leap_status = LEAP_Normal;
inst->local = params->local;
inst->pps_forced = params->pps_forced;
@ -321,6 +323,22 @@ RCL_ReportSource(RPT_SourceReport *report, struct timespec *now)
}
}
int
RCL_ModifyOffset(uint32_t ref_id, double offset)
{
unsigned int i;
for (i = 0; i < ARR_GetSize(refclocks); i++) {
RCL_Instance inst = get_refclock(i);
if (inst->ref_id == ref_id) {
inst->offset = offset;
LOG(LOGS_INFO, "Source %s new offset %f", UTI_RefidToString(ref_id), offset);
return 1;
}
}
return 0;
}
void
RCL_SetDriverData(RCL_Instance instance, void *data)
{
@ -649,6 +667,12 @@ RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
return 1;
}
void
RCL_UpdateReachability(RCL_Instance instance)
{
instance->reached++;
}
double
RCL_GetPrecision(RCL_Instance instance)
{
@ -776,6 +800,9 @@ poll_timeout(void *arg)
if (!(inst->driver->poll && inst->driver_polled < (1 << (inst->poll - inst->driver_poll)))) {
inst->driver_polled = 0;
SRC_UpdateReachability(inst->source, inst->reached > 0);
inst->reached = 0;
if (SPF_GetFilteredSample(inst->filter, &sample)) {
double local_freq, local_offset;
struct timespec local_ref_time;
@ -791,7 +818,6 @@ poll_timeout(void *arg)
inst->leap_status = LEAP_Unsynchronised;
}
SRC_UpdateReachability(inst->source, 1);
SRC_UpdateStatus(inst->source, stratum, inst->leap_status);
SRC_AccumulateSample(inst->source, &sample);
SRC_SelectSource(inst->source);
@ -800,8 +826,6 @@ poll_timeout(void *arg)
follow_local(inst, &local_ref_time, local_freq, local_offset);
log_sample(inst, &sample.time, 1, 0, 0.0, sample.offset, sample.peer_dispersion);
} else {
SRC_UpdateReachability(inst->source, 0);
}
}

View file

@ -68,6 +68,7 @@ extern void RCL_Finalise(void);
extern int RCL_AddRefclock(RefclockParameters *params);
extern void RCL_StartRefclocks(void);
extern void RCL_ReportSource(RPT_SourceReport *report, struct timespec *now);
extern int RCL_ModifyOffset(uint32_t ref_id, double offset);
/* functions used by drivers */
extern void RCL_SetDriverData(RCL_Instance instance, void *data);
@ -80,6 +81,7 @@ extern int RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
extern int RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second);
extern int RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
double second, double dispersion, double raw_correction);
extern void RCL_UpdateReachability(RCL_Instance instance);
extern double RCL_GetPrecision(RCL_Instance instance);
extern int RCL_GetDriverPoll(RCL_Instance instance);

View file

@ -154,6 +154,8 @@ static void process_ext_pulse(RCL_Instance instance, struct timespec *phc_ts)
}
phc->last_extts = *phc_ts;
RCL_UpdateReachability(instance);
if (!HCL_CookTime(phc->clock, phc_ts, &local_ts, &local_err))
return;
@ -175,7 +177,7 @@ static void read_ext_pulse(int fd, int event, void *anything)
instance = anything;
phc1 = RCL_GetDriverData(instance);
/* The Linux kernel (as of 6.2) has one shared queue of timestamps for all
/* Linux versions before 6.7 had one shared queue of timestamps for all
descriptors of the same PHC. Search for all refclocks that expect
the timestamp. */
@ -204,6 +206,9 @@ static int phc_poll(RCL_Instance instance)
if (n_readings < 1)
return 0;
if (!phc->extpps)
RCL_UpdateReachability(instance);
if (!HCL_ProcessReadings(phc->clock, n_readings, readings, &phc_ts, &sys_ts, &phc_err))
return 0;

View file

@ -143,6 +143,8 @@ static int pps_poll(RCL_Instance instance)
pps->last_seq = seq;
RCL_UpdateReachability(instance);
return RCL_AddPulse(instance, &ts, 1.0e-9 * ts.tv_nsec);
}

View file

@ -109,6 +109,8 @@ static int shm_poll(RCL_Instance instance)
shm->valid = 0;
RCL_UpdateReachability(instance);
receive_ts.tv_sec = t.receiveTimeStampSec;
clock_ts.tv_sec = t.clockTimeStampSec;

View file

@ -129,6 +129,8 @@ static void read_sample(int sockfd, int event, void *anything)
UTI_TimevalToTimespec(&sample.tv, &sys_ts);
UTI_NormaliseTimespec(&sys_ts);
RCL_UpdateReachability(instance);
if (!UTI_IsTimeOffsetSane(&sys_ts, sample.offset))
return;

View file

@ -4,6 +4,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2009-2018, 2020, 2022
* Copyright (C) Andy Fiddaman 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -33,6 +34,7 @@
#include "reference.h"
#include "util.h"
#include "conf.h"
#include "leapdb.h"
#include "logging.h"
#include "local.h"
#include "sched.h"
@ -53,6 +55,8 @@ static int enable_local_stratum;
static int local_stratum;
static int local_orphan;
static double local_distance;
static int local_activate_ok;
static double local_activate;
static struct timespec local_ref_time;
static NTP_Leap our_leap_status;
static int our_leap_sec;
@ -122,9 +126,6 @@ static int leap_in_progress;
/* Timer for the leap second handler */
static SCH_TimeoutID leap_timeout_id;
/* Name of a system timezone containing leap seconds occuring at midnight */
static char *leap_tzname;
/* ================================================== */
static LOG_FileID logfileid;
@ -155,7 +156,6 @@ static int ref_adjustments;
/* ================================================== */
static NTP_Leap get_tz_leap(time_t when, int *tai_offset);
static void update_leap_status(NTP_Leap leap, time_t now, int reset);
/* ================================================== */
@ -195,7 +195,6 @@ REF_Initialise(void)
FILE *in;
double file_freq_ppm, file_skew_ppm;
double our_frequency_ppm;
int tai_offset;
mode = REF_ModeNormal;
are_we_synchronised = 0;
@ -211,6 +210,7 @@ REF_Initialise(void)
our_frequency_sd = 0.0;
our_offset_sd = 0.0;
drift_file_age = 0.0;
local_activate_ok = 0;
/* Now see if we can get the drift file opened */
drift_file = CNF_GetDriftFile();
@ -249,7 +249,8 @@ REF_Initialise(void)
correction_time_ratio = CNF_GetCorrectionTimeRatio();
enable_local_stratum = CNF_AllowLocalReference(&local_stratum, &local_orphan, &local_distance);
enable_local_stratum = CNF_AllowLocalReference(&local_stratum, &local_orphan,
&local_distance, &local_activate);
UTI_ZeroTimespec(&local_ref_time);
leap_when = 0;
@ -260,18 +261,6 @@ REF_Initialise(void)
if (leap_mode == REF_LeapModeSystem && !LCL_CanSystemLeap())
leap_mode = REF_LeapModeStep;
leap_tzname = CNF_GetLeapSecTimezone();
if (leap_tzname) {
/* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */
if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 &&
get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) {
LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname);
} else {
LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
leap_tzname = NULL;
}
}
CNF_GetMakeStep(&make_step_limit, &make_step_threshold);
CNF_GetMaxChange(&max_offset_delay, &max_offset_ignore, &max_offset);
CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user);
@ -593,77 +582,6 @@ is_leap_second_day(time_t when)
/* ================================================== */
static NTP_Leap
get_tz_leap(time_t when, int *tai_offset)
{
static time_t last_tz_leap_check;
static NTP_Leap tz_leap;
static int tz_tai_offset;
struct tm stm, *tm;
time_t t;
char *tz_env, tz_orig[128];
*tai_offset = tz_tai_offset;
/* Do this check at most twice a day */
when = when / (12 * 3600) * (12 * 3600);
if (last_tz_leap_check == when)
return tz_leap;
last_tz_leap_check = when;
tz_leap = LEAP_Normal;
tz_tai_offset = 0;
tm = gmtime(&when);
if (!tm)
return tz_leap;
stm = *tm;
/* Temporarily switch to the timezone containing leap seconds */
tz_env = getenv("TZ");
if (tz_env) {
if (strlen(tz_env) >= sizeof (tz_orig))
return tz_leap;
strcpy(tz_orig, tz_env);
}
setenv("TZ", leap_tzname, 1);
tzset();
/* Get the TAI-UTC offset, which started at the epoch at 10 seconds */
t = mktime(&stm);
if (t != -1)
tz_tai_offset = t - when + 10;
/* Set the time to 23:59:60 and see how it overflows in mktime() */
stm.tm_sec = 60;
stm.tm_min = 59;
stm.tm_hour = 23;
t = mktime(&stm);
if (tz_env)
setenv("TZ", tz_orig, 1);
else
unsetenv("TZ");
tzset();
if (t == -1)
return tz_leap;
if (stm.tm_sec == 60)
tz_leap = LEAP_InsertSecond;
else if (stm.tm_sec == 1)
tz_leap = LEAP_DeleteSecond;
*tai_offset = tz_tai_offset;
return tz_leap;
}
/* ================================================== */
static void
leap_end_timeout(void *arg)
{
@ -751,16 +669,16 @@ set_leap_timeout(time_t now)
static void
update_leap_status(NTP_Leap leap, time_t now, int reset)
{
NTP_Leap tz_leap;
NTP_Leap ldb_leap;
int leap_sec, tai_offset;
leap_sec = 0;
tai_offset = 0;
if (leap_tzname && now) {
tz_leap = get_tz_leap(now, &tai_offset);
if (now) {
ldb_leap = LDB_GetLeap(now, &tai_offset);
if (leap == LEAP_Normal)
leap = tz_leap;
leap = ldb_leap;
}
if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) {
@ -1219,7 +1137,7 @@ REF_GetReferenceParams
double *root_dispersion
)
{
double dispersion, delta;
double dispersion, delta, distance;
assert(initialised);
@ -1229,11 +1147,16 @@ REF_GetReferenceParams
dispersion = 0.0;
}
distance = our_root_delay / 2 + dispersion;
if (local_activate == 0.0 || (are_we_synchronised && distance < local_activate))
local_activate_ok = 1;
/* Local reference is active when enabled and the clock is not synchronised
or the root distance exceeds the threshold */
if (are_we_synchronised &&
!(enable_local_stratum && our_root_delay / 2 + dispersion > local_distance)) {
!(enable_local_stratum && local_activate_ok && distance > local_distance)) {
*is_synchronised = 1;
@ -1245,7 +1168,7 @@ REF_GetReferenceParams
*root_delay = our_root_delay;
*root_dispersion = dispersion;
} else if (enable_local_stratum) {
} else if (enable_local_stratum && local_activate_ok) {
*is_synchronised = 0;
@ -1345,12 +1268,13 @@ REF_ModifyMakestep(int limit, double threshold)
/* ================================================== */
void
REF_EnableLocal(int stratum, double distance, int orphan)
REF_EnableLocal(int stratum, double distance, int orphan, double activate)
{
enable_local_stratum = 1;
local_stratum = CLAMP(1, stratum, NTP_MAX_STRATUM - 1);
local_distance = distance;
local_orphan = !!orphan;
local_activate = activate;
LOG(LOGS_INFO, "%s local reference mode", "Enabled");
}
@ -1368,7 +1292,7 @@ REF_DisableLocal(void)
#define LEAP_SECOND_CLOSE 5
static int
is_leap_close(time_t t)
is_leap_close(double t)
{
return leap_when != 0 &&
t >= leap_when - LEAP_SECOND_CLOSE && t < leap_when + LEAP_SECOND_CLOSE;
@ -1398,7 +1322,7 @@ REF_GetTaiOffset(struct timespec *ts)
{
int tai_offset;
get_tz_leap(ts->tv_sec, &tai_offset);
LDB_GetLeap(ts->tv_sec, &tai_offset);
return tai_offset;
}

View file

@ -185,7 +185,7 @@ extern void REF_ModifyMaxupdateskew(double new_max_update_skew);
/* Modify makestep settings */
extern void REF_ModifyMakestep(int limit, double threshold);
extern void REF_EnableLocal(int stratum, double distance, int orphan);
extern void REF_EnableLocal(int stratum, double distance, int orphan, double activate);
extern void REF_DisableLocal(void);
/* Check if either of the current raw and cooked time, and optionally a

View file

@ -377,7 +377,7 @@ find_ordered_entry_with_flags(double *x, int n, int index, char *flags)
r = v;
do {
while (l < v && x[l] < piv) l++;
while (x[r] > piv) r--;
while (r > 0 && x[r] > piv) r--;
if (r <= l) break;
EXCH(x[l], x[r]);
l++;

View file

@ -181,6 +181,10 @@ typedef struct {
uint32_t total_rx_count;
uint32_t total_valid_count;
uint32_t total_good_count;
uint32_t total_kernel_tx_ts;
uint32_t total_kernel_rx_ts;
uint32_t total_hw_tx_ts;
uint32_t total_hw_rx_ts;
} RPT_NTPReport;
typedef struct {

View file

@ -802,6 +802,7 @@ read_from_device(int fd_, int event, void *any)
rtc_tm.tm_mday = rtc_raw.tm_mday;
rtc_tm.tm_mon = rtc_raw.tm_mon;
rtc_tm.tm_year = rtc_raw.tm_year;
rtc_tm.tm_wday = 0;
rtc_t = t_from_rtc(&rtc_tm);

1
siv.h
View file

@ -36,6 +36,7 @@
/* Identifiers of SIV algorithms following the IANA AEAD registry */
typedef enum {
AEAD_SIV_INVALID = 0,
AEAD_AES_SIV_CMAC_256 = 15,
AEAD_AES_SIV_CMAC_384 = 16,
AEAD_AES_SIV_CMAC_512 = 17,

View file

@ -2,7 +2,7 @@
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
* Copyright (C) Miroslav Lichvar 2020, 2023
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -37,6 +37,8 @@
struct SIV_Instance_Record {
gnutls_cipher_algorithm_t algorithm;
gnutls_aead_cipher_hd_t cipher;
int min_nonce_length;
int max_nonce_length;
};
/* ================================================== */
@ -81,6 +83,10 @@ get_cipher_algorithm(SIV_Algorithm algorithm)
switch (algorithm) {
case AEAD_AES_SIV_CMAC_256:
return GNUTLS_CIPHER_AES_128_SIV;
#if HAVE_GNUTLS_SIV_GCM
case AEAD_AES_128_GCM_SIV:
return GNUTLS_CIPHER_AES_128_SIV_GCM;
#endif
default:
return 0;
}
@ -112,6 +118,19 @@ SIV_CreateInstance(SIV_Algorithm algorithm)
instance->algorithm = calgo;
instance->cipher = NULL;
switch (algorithm) {
case AEAD_AES_SIV_CMAC_256:
instance->min_nonce_length = 1;
instance->max_nonce_length = INT_MAX;
break;
case AEAD_AES_128_GCM_SIV:
instance->min_nonce_length = 12;
instance->max_nonce_length = 12;
break;
default:
assert(0);
}
instance_counter++;
return instance;
@ -143,6 +162,8 @@ SIV_GetKeyLength(SIV_Algorithm algorithm)
return 0;
len = gnutls_cipher_get_key_size(calgo);
if (len == 0)
return 0;
if (len < 1 || len > SIV_MAX_KEY_LENGTH)
LOG_FATAL("Invalid key length");
@ -198,7 +219,7 @@ SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length)
int
SIV_GetMinNonceLength(SIV_Instance instance)
{
return 1;
return instance->min_nonce_length;
}
/* ================================================== */
@ -206,7 +227,7 @@ SIV_GetMinNonceLength(SIV_Instance instance)
int
SIV_GetMaxNonceLength(SIV_Instance instance)
{
return INT_MAX;
return instance->max_nonce_length;
}
/* ================================================== */
@ -238,7 +259,8 @@ SIV_Encrypt(SIV_Instance instance,
if (!instance->cipher)
return 0;
if (nonce_length < 1 || assoc_length < 0 ||
if (nonce_length < instance->min_nonce_length ||
nonce_length > instance->max_nonce_length || assoc_length < 0 ||
plaintext_length < 0 || ciphertext_length < 0)
return 0;
@ -269,7 +291,8 @@ SIV_Decrypt(SIV_Instance instance,
if (!instance->cipher)
return 0;
if (nonce_length < 1 || assoc_length < 0 ||
if (nonce_length < instance->min_nonce_length ||
nonce_length > instance->max_nonce_length || assoc_length < 0 ||
plaintext_length < 0 || ciphertext_length < 0)
return 0;

169
socket.c
View file

@ -5,6 +5,7 @@
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Timo Teras 2009
* Copyright (C) Miroslav Lichvar 2009, 2013-2020
* Copyright (C) Luke Valenta 2023
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -89,6 +90,9 @@ struct MessageHeader {
static int initialised;
static int first_reusable_fd;
static int reusable_fds;
/* Flags indicating in which IP families sockets can be requested */
static int ip4_enabled;
static int ip6_enabled;
@ -155,6 +159,59 @@ domain_to_string(int domain)
/* ================================================== */
static int
get_reusable_socket(int type, IPSockAddr *spec)
{
#ifdef LINUX
union sockaddr_all sa;
IPSockAddr ip_sa;
int sock_fd, opt;
socklen_t l;
/* Abort early if not an IPv4/IPv6 server socket */
if (!spec || spec->ip_addr.family == IPADDR_UNSPEC || spec->port == 0)
return INVALID_SOCK_FD;
/* Loop over available reusable sockets */
for (sock_fd = first_reusable_fd; sock_fd < first_reusable_fd + reusable_fds; sock_fd++) {
/* Check that types match */
l = sizeof (opt);
if (getsockopt(sock_fd, SOL_SOCKET, SO_TYPE, &opt, &l) < 0 ||
l != sizeof (opt) || opt != type)
continue;
/* Get sockaddr for reusable socket */
l = sizeof (sa);
if (getsockname(sock_fd, &sa.sa, &l) < 0 || l < sizeof (sa_family_t))
continue;
SCK_SockaddrToIPSockAddr(&sa.sa, l, &ip_sa);
/* Check that reusable socket matches specification */
if (ip_sa.port != spec->port || UTI_CompareIPs(&ip_sa.ip_addr, &spec->ip_addr, NULL) != 0)
continue;
/* Check that STREAM socket is listening */
l = sizeof (opt);
if (type == SOCK_STREAM && (getsockopt(sock_fd, SOL_SOCKET, SO_ACCEPTCONN, &opt, &l) < 0 ||
l != sizeof (opt) || opt == 0))
continue;
#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
if (spec->ip_addr.family == IPADDR_INET6 &&
(!SCK_GetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt) || opt != 1))
LOG(LOGS_WARN, "Reusable IPv6 socket missing IPV6_V6ONLY option");
#endif
return sock_fd;
}
#endif
return INVALID_SOCK_FD;
}
/* ================================================== */
#if defined(SOCK_CLOEXEC) || defined(SOCK_NONBLOCK)
static int
check_socket_flag(int sock_flag, int fd_flag, int fs_flag)
@ -212,7 +269,7 @@ static int
set_socket_flags(int sock_fd, int flags)
{
/* Close the socket automatically on exec */
if (
if (!SCK_IsReusable(sock_fd) &&
#ifdef SOCK_CLOEXEC
(supported_socket_flags & SOCK_CLOEXEC) == 0 &&
#endif
@ -222,7 +279,7 @@ set_socket_flags(int sock_fd, int flags)
/* Enable non-blocking mode */
if ((flags & SCK_FLAG_BLOCK) == 0 &&
#ifdef SOCK_NONBLOCK
(supported_socket_flags & SOCK_NONBLOCK) == 0 &&
(SCK_IsReusable(sock_fd) || (supported_socket_flags & SOCK_NONBLOCK) == 0) &&
#endif
!set_socket_nonblock(sock_fd))
return 0;
@ -279,6 +336,32 @@ open_socket_pair(int domain, int type, int flags, int *other_fd)
/* ================================================== */
static int
get_ip_socket(int domain, int type, int flags, IPSockAddr *ip_sa)
{
int sock_fd;
/* Check if there is a matching reusable socket */
sock_fd = get_reusable_socket(type, ip_sa);
if (sock_fd < 0) {
sock_fd = open_socket(domain, type, flags);
/* Unexpected, but make sure the new socket is not in the reusable range */
if (SCK_IsReusable(sock_fd))
LOG_FATAL("Could not open %s socket : file descriptor in reusable range",
domain_to_string(domain));
} else {
/* Set socket flags on reusable socket */
if (!set_socket_flags(sock_fd, flags))
return INVALID_SOCK_FD;
}
return sock_fd;
}
/* ================================================== */
static int
set_socket_options(int sock_fd, int flags)
{
@ -295,8 +378,10 @@ static int
set_ip_options(int sock_fd, int family, int flags)
{
#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
/* Receive only IPv6 packets on an IPv6 socket */
if (family == IPADDR_INET6 && !SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))
/* Receive only IPv6 packets on an IPv6 socket, but do not attempt
to set this option on pre-initialised reuseable sockets */
if (family == IPADDR_INET6 && !SCK_IsReusable(sock_fd) &&
!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))
return 0;
#endif
@ -385,6 +470,10 @@ bind_ip_address(int sock_fd, IPSockAddr *addr, int flags)
;
#endif
/* Do not attempt to bind pre-initialised reusable socket */
if (SCK_IsReusable(sock_fd))
return 1;
saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (saddr));
if (saddr_len == 0)
return 0;
@ -457,7 +546,7 @@ open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *ifac
return INVALID_SOCK_FD;
}
sock_fd = open_socket(domain, type, flags);
sock_fd = get_ip_socket(domain, type, flags, local_addr);
if (sock_fd < 0)
return INVALID_SOCK_FD;
@ -482,7 +571,8 @@ open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *ifac
goto error;
if (remote_addr || local_addr)
DEBUG_LOG("Opened %s%s socket fd=%d%s%s%s%s",
DEBUG_LOG("%s %s%s socket fd=%d%s%s%s%s",
SCK_IsReusable(sock_fd) ? "Reusing" : "Opened",
type == SOCK_DGRAM ? "UDP" : type == SOCK_STREAM ? "TCP" : "?",
family == IPADDR_INET4 ? "v4" : "v6",
sock_fd,
@ -1170,9 +1260,44 @@ send_message(int sock_fd, SCK_Message *message, int flags)
/* ================================================== */
void
SCK_PreInitialise(void)
{
#ifdef LINUX
char *s, *ptr;
/* On Linux systems, the systemd service manager may pass file descriptors
for pre-initialised sockets to the chronyd daemon. The service manager
allocates and binds the file descriptors, and passes a copy to each
spawned instance of the service. This allows for zero-downtime service
restarts as the sockets buffer client requests until the service is able
to handle them. The service manager sets the LISTEN_FDS environment
variable to the number of passed file descriptors, and the integer file
descriptors start at 3 (see SD_LISTEN_FDS_START in
https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html). */
first_reusable_fd = 3;
reusable_fds = 0;
s = getenv("LISTEN_FDS");
if (s) {
errno = 0;
reusable_fds = strtol(s, &ptr, 10);
if (errno != 0 || *ptr != '\0' || reusable_fds < 0)
reusable_fds = 0;
}
#else
first_reusable_fd = 0;
reusable_fds = 0;
#endif
}
/* ================================================== */
void
SCK_Initialise(int family)
{
int fd;
ip4_enabled = family == IPADDR_INET4 || family == IPADDR_UNSPEC;
#ifdef FEAT_IPV6
ip6_enabled = family == IPADDR_INET6 || family == IPADDR_UNSPEC;
@ -1201,6 +1326,9 @@ SCK_Initialise(int family)
supported_socket_flags |= SOCK_NONBLOCK;
#endif
for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
UTI_FdSetCloexec(fd);
initialised = 1;
}
@ -1213,6 +1341,8 @@ SCK_Finalise(void)
ARR_DestroyInstance(recv_headers);
ARR_DestroyInstance(recv_messages);
SCK_CloseReusableSockets();
initialised = 0;
}
@ -1353,6 +1483,27 @@ SCK_OpenUnixSocketPair(int flags, int *other_fd)
/* ================================================== */
int
SCK_IsReusable(int fd)
{
return fd >= first_reusable_fd && fd < first_reusable_fd + reusable_fds;
}
/* ================================================== */
void
SCK_CloseReusableSockets(void)
{
int fd;
for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
close(fd);
reusable_fds = 0;
first_reusable_fd = 0;
}
/* ================================================== */
int
SCK_SetIntOption(int sock_fd, int level, int name, int value)
{
@ -1410,7 +1561,7 @@ SCK_EnableKernelRxTimestamping(int sock_fd)
int
SCK_ListenOnSocket(int sock_fd, int backlog)
{
if (listen(sock_fd, backlog) < 0) {
if (!SCK_IsReusable(sock_fd) && listen(sock_fd, backlog) < 0) {
DEBUG_LOG("listen() failed : %s", strerror(errno));
return 0;
}
@ -1573,6 +1724,10 @@ SCK_RemoveSocket(int sock_fd)
void
SCK_CloseSocket(int sock_fd)
{
/* Reusable sockets are closed in finalisation */
if (SCK_IsReusable(sock_fd))
return;
close(sock_fd);
}

View file

@ -73,6 +73,9 @@ typedef struct {
int descriptor;
} SCK_Message;
/* Pre-initialisation function */
extern void SCK_PreInitialise(void);
/* Initialisation function (the specified IP family is enabled,
or all if IPADDR_UNSPEC) */
extern void SCK_Initialise(int family);
@ -106,6 +109,12 @@ extern int SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_a
int flags);
extern int SCK_OpenUnixSocketPair(int flags, int *other_fd);
/* Check if a file descriptor was passed from the service manager */
extern int SCK_IsReusable(int sock_fd);
/* Close all reusable sockets before finalisation (e.g. in a helper process) */
extern void SCK_CloseReusableSockets(void);
/* Set and get a socket option of int size */
extern int SCK_SetIntOption(int sock_fd, int level, int name, int value);
extern int SCK_GetIntOption(int sock_fd, int level, int name, int *value);

109
sources.c
View file

@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2011-2016, 2018, 2020-2023
* Copyright (C) Miroslav Lichvar 2011-2016, 2018, 2020-2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -68,8 +68,8 @@ struct SelectInfo {
typedef enum {
SRC_OK, /* OK so far, not a final status! */
SRC_UNSELECTABLE, /* Has noselect option set */
SRC_UNSYNCHRONISED, /* Provides samples but not unsynchronised */
SRC_BAD_STATS, /* Doesn't have valid stats data */
SRC_UNSYNCHRONISED, /* Provides samples, but not synchronised */
SRC_BAD_DISTANCE, /* Has root distance longer than allowed maximum */
SRC_JITTERY, /* Had std dev larger than allowed maximum */
SRC_WAITS_STATS, /* Others have bad stats, selection postponed */
@ -174,6 +174,11 @@ static int selected_source_index; /* Which source index is currently
if no current valid reference) */
static int reported_no_majority; /* Flag to avoid repeated log message
about no majority */
static int report_selection_loss; /* Flag to force logging a message if
selection is lost in a transient state
(SRC_WAITS_STATS, SRC_WAITS_UPDATE) */
static int forced_first_report; /* Flag to allow one failed selection to be
logged before a successful selection */
/* Score needed to replace the currently selected source */
#define SCORE_LIMIT 10.0
@ -201,6 +206,8 @@ static LOG_FileID logfileid;
/* Forward prototype */
static void update_sel_options(void);
static void unselect_selected_source(LOG_Severity severity, const char *format,
const char *arg);
static void slew_sources(struct timespec *raw, struct timespec *cooked, double dfreq,
double doffset, LCL_ChangeType change_type, void *anything);
static void add_dispersion(double dispersion, void *anything);
@ -330,11 +337,12 @@ void SRC_DestroyInstance(SRC_Instance instance)
update_sel_options();
/* If this was the previous reference source, we have to reselect! */
if (selected_source_index == dead_index)
SRC_ReselectSource();
else if (selected_source_index > dead_index)
if (selected_source_index > dead_index)
--selected_source_index;
else if (selected_source_index == dead_index)
unselect_selected_source(LOGS_INFO, NULL, NULL);
SRC_SelectSource(NULL);
}
/* ================================================== */
@ -357,6 +365,9 @@ SRC_ResetInstance(SRC_Instance instance)
memset(&instance->sel_info, 0, sizeof (instance->sel_info));
SST_ResetInstance(instance->stats);
if (selected_source_index == instance->index)
SRC_SelectSource(NULL);
}
/* ================================================== */
@ -515,11 +526,6 @@ SRC_UpdateReachability(SRC_Instance inst, int reachable)
if (inst->reachability_size < SOURCE_REACH_BITS)
inst->reachability_size++;
if (!reachable && inst->index == selected_source_index) {
/* Try to select a better source */
SRC_SelectSource(NULL);
}
/* Check if special reference update mode failed */
if (REF_GetMode() != REF_ModeNormal && special_mode_end()) {
REF_SetUnsynchronised();
@ -528,6 +534,10 @@ SRC_UpdateReachability(SRC_Instance inst, int reachable)
/* Try to replace unreachable NTP sources */
if (inst->reachability == 0 && inst->reachability_size == SOURCE_REACH_BITS)
handle_bad_source(inst);
/* Source selection can change with unreachable sources */
if (inst->reachability == 0)
SRC_SelectSource(NULL);
}
/* ================================================== */
@ -733,6 +743,26 @@ mark_ok_sources(SRC_Status status)
}
}
/* ================================================== */
/* Reset the index of selected source and report the selection loss. If no
message is provided, assume it is a transient state and wait for another
call providing a message or selection of another source, which resets the
report_selection_loss flag. */
static void
unselect_selected_source(LOG_Severity severity, const char *format, const char *arg)
{
if (selected_source_index != INVALID_SOURCE) {
selected_source_index = INVALID_SOURCE;
report_selection_loss = 1;
}
if (report_selection_loss && format) {
log_selection_message(severity, format, arg);
report_selection_loss = 0;
}
}
/* ================================================== */
static int
@ -833,7 +863,8 @@ SRC_SelectSource(SRC_Instance updated_inst)
struct SelectInfo *si;
struct timespec now, ref_time;
int i, j, j1, j2, index, sel_prefer, n_endpoints, n_sel_sources, sel_req_source;
int n_badstats_sources, max_sel_reach, max_sel_reach_size, max_badstat_reach;
int max_badstat_reach, max_badstat_reach_size, n_badstats_sources;
int max_sel_reach, max_sel_reach_size;
int depth, best_depth, trust_depth, best_trust_depth, n_sel_trust_sources;
int combined, stratum, min_stratum, max_score_index;
int orphan_stratum, orphan_source;
@ -850,11 +881,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
}
if (n_sources == 0) {
/* In this case, we clearly cannot synchronise to anything */
if (selected_source_index != INVALID_SOURCE) {
log_selection_message(LOGS_INFO, "Can't synchronise: no sources", NULL);
selected_source_index = INVALID_SOURCE;
}
unselect_selected_source(LOGS_INFO, "Can't synchronise: no sources", NULL);
return;
}
@ -868,7 +895,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
n_badstats_sources = 0;
sel_req_source = 0;
max_sel_reach = max_badstat_reach = 0;
max_sel_reach_size = 0;
max_sel_reach_size = max_badstat_reach_size = 0;
max_reach_sample_ago = 0.0;
for (i = 0; i < n_sources; i++) {
@ -888,12 +915,6 @@ SRC_SelectSource(SRC_Instance updated_inst)
continue;
}
/* Ignore sources which are not synchronised */
if (sources[i]->leap == LEAP_Unsynchronised) {
mark_source(sources[i], SRC_UNSYNCHRONISED);
continue;
}
si = &sources[i]->sel_info;
SST_GetSelectionData(sources[i]->stats, &now,
&si->lo_limit, &si->hi_limit, &si->root_distance,
@ -905,6 +926,14 @@ SRC_SelectSource(SRC_Instance updated_inst)
mark_source(sources[i], SRC_BAD_STATS);
if (max_badstat_reach < sources[i]->reachability)
max_badstat_reach = sources[i]->reachability;
if (max_badstat_reach_size < sources[i]->reachability_size)
max_badstat_reach_size = sources[i]->reachability_size;
continue;
}
/* Ignore sources which are not synchronised */
if (sources[i]->leap == LEAP_Unsynchronised) {
mark_source(sources[i], SRC_UNSYNCHRONISED);
continue;
}
@ -1039,15 +1068,21 @@ SRC_SelectSource(SRC_Instance updated_inst)
if (n_badstats_sources && n_sel_sources && selected_source_index == INVALID_SOURCE &&
max_sel_reach_size < SOURCE_REACH_BITS && max_sel_reach >> 1 == max_badstat_reach) {
mark_ok_sources(SRC_WAITS_STATS);
unselect_selected_source(LOGS_INFO, NULL, NULL);
return;
}
/* Wait for a source to have full reachability register to allow one
failed selection to be logged before first successful selection */
if (!forced_first_report &&
MAX(max_sel_reach_size, max_badstat_reach_size) == SOURCE_REACH_BITS) {
report_selection_loss = 1;
forced_first_report = 1;
}
if (n_endpoints == 0) {
/* No sources provided valid endpoints */
if (selected_source_index != INVALID_SOURCE) {
log_selection_message(LOGS_INFO, "Can't synchronise: no selectable sources", NULL);
selected_source_index = INVALID_SOURCE;
}
unselect_selected_source(LOGS_INFO, "Can't synchronise: no selectable sources", NULL);
return;
}
@ -1128,6 +1163,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
if (!reported_no_majority) {
log_selection_message(LOGS_WARN, "Can't synchronise: no majority", NULL);
reported_no_majority = 1;
report_selection_loss = 0;
}
if (selected_source_index != INVALID_SOURCE) {
@ -1184,12 +1220,9 @@ SRC_SelectSource(SRC_Instance updated_inst)
}
if (!n_sel_sources || sel_req_source || n_sel_sources < CNF_GetMinSources()) {
if (selected_source_index != INVALID_SOURCE) {
log_selection_message(LOGS_INFO, "Can't synchronise: %s selectable sources",
!n_sel_sources ? "no" :
sel_req_source ? "no required source in" : "not enough");
selected_source_index = INVALID_SOURCE;
}
unselect_selected_source(LOGS_INFO, "Can't synchronise: %s selectable sources",
!n_sel_sources ? "no" :
sel_req_source ? "no required source in" : "not enough");
mark_ok_sources(SRC_WAITS_SOURCES);
return;
}
@ -1296,7 +1329,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
/* Before selecting the new synchronisation source wait until the reference
can be updated */
if (sources[max_score_index]->updates == 0) {
selected_source_index = INVALID_SOURCE;
unselect_selected_source(LOGS_INFO, NULL, NULL);
mark_ok_sources(SRC_WAITS_UPDATE);
return;
}
@ -1312,6 +1345,8 @@ SRC_SelectSource(SRC_Instance updated_inst)
}
reported_no_majority = 0;
report_selection_loss = 0;
forced_first_report = 1;
}
mark_source(sources[selected_source_index], SRC_SELECTED);
@ -1774,10 +1809,10 @@ get_status_char(SRC_Status status)
switch (status) {
case SRC_UNSELECTABLE:
return 'N';
case SRC_UNSYNCHRONISED:
return 's';
case SRC_BAD_STATS:
return 'M';
case SRC_UNSYNCHRONISED:
return 's';
case SRC_BAD_DISTANCE:
return 'd';
case SRC_JITTERY:

View file

@ -549,9 +549,9 @@ SST_DoNewRegression(SST_Stats inst)
sd_weight += (peer_distances[i] - min_distance) / sd;
weights[i] = SQUARE(sd_weight);
}
}
correct_asymmetry(inst, times_back, offsets);
correct_asymmetry(inst, times_back, offsets);
}
inst->regression_ok = RGR_FindBestRegression(times_back + inst->runs_samples,
offsets + inst->runs_samples, weights,

20
stubs.c
View file

@ -201,12 +201,18 @@ NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
}
NSR_Status
NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
NSR_AddSourceByName(char *name, int family, int port, int pool, NTP_Source_Type type,
SourceParameters *params, uint32_t *conf_id)
{
return NSR_TooManySources;
}
const char *
NSR_StatusToString(NSR_Status status)
{
return "NTP not supported";
}
NSR_Status
NSR_RemoveSource(IPAddr *address)
{
@ -314,6 +320,12 @@ NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum)
return 0;
}
int
NSR_ModifyOffset(IPAddr *address, double new_offset)
{
return 0;
}
int
NSR_ModifyPolltarget(IPAddr *address, int new_poll_target)
{
@ -413,6 +425,12 @@ RCL_ReportSource(RPT_SourceReport *report, struct timespec *now)
memset(report, 0, sizeof (*report));
}
int
RCL_ModifyOffset(uint32_t ref_id, double offset)
{
return 0;
}
#endif /* !FEAT_REFCLOCK */
#ifndef FEAT_SIGND

View file

@ -990,6 +990,14 @@ SYS_Linux_SetPHCExtTimestamping(int fd, int pin, int channel,
return 0;
}
#if defined(PTP_MASK_CLEAR_ALL) && defined(PTP_MASK_EN_SINGLE)
/* Disable events from other channels on this descriptor */
if (ioctl(fd, PTP_MASK_CLEAR_ALL))
DEBUG_LOG("ioctl(%s) failed : %s", "PTP_MASK_CLEAR_ALL", strerror(errno));
else if (ioctl(fd, PTP_MASK_EN_SINGLE, &channel))
DEBUG_LOG("ioctl(%s) failed : %s", "PTP_MASK_EN_SINGLE", strerror(errno));
#endif
return 1;
}

View file

@ -66,10 +66,9 @@ get_tempcomp(double temp)
return k0 + (temp - T0) * k1 + (temp - T0) * (temp - T0) * k2;
/* Otherwise interpolate/extrapolate between two nearest points */
for (i = 1; i < ARR_GetSize(points); i++) {
p2 = (struct Point *)ARR_GetElement(points, i);
if (p2->temp >= temp)
for (i = 1; ; i++) {
p2 = ARR_GetElement(points, i);
if (p2->temp >= temp || i + 1 >= ARR_GetSize(points))
break;
}
p1 = p2 - 1;

View file

@ -3,13 +3,14 @@
cd ../..
for opts in \
"--enable-debug" \
"--host-system=Linux" \
"--host-system=NetBSD" \
"--host-system=FreeBSD" \
"--without-nettle" \
"--without-nettle --without-nss" \
"--without-nettle --without-nss --without-tomcrypt" \
"--without-nettle --without-nss --without-tomcrypt --without-gnutls"
"--without-nettle --without-gnutls" \
"--without-nettle --without-gnutls --without-nss" \
"--without-nettle --without-gnutls --without-nss --without-tomcrypt"
do
./configure $opts
scan-build make "$@" || exit 1

View file

@ -25,13 +25,13 @@ touch Makefile
for extra_config_opts in \
"--all-privops" \
"--disable-ipv6" \
"--disable-nts" \
"--disable-scfilter" \
"--without-aes-gcm-siv" \
"--without-gnutls" \
"--without-nettle" \
"--without-nettle --without-nss" \
"--without-nettle --without-nss --without-tomcrypt" \
"--without-nettle --without-nss --without-tomcrypt --without-gnutls"; \
"--without-nettle --without-gnutls" \
"--without-nettle --without-gnutls --without-nss" \
"--without-nettle --without-gnutls --without-nss --without-tomcrypt"; \
do
for arch_opts in "-m32" ""; do
pushd test/simulation/clknetsim || exit 1

View file

@ -41,7 +41,6 @@ for time_offset in -1e-1 1e-1; do
export CLKNETSIM_START_DATE=$(awk "BEGIN {printf \"%.0f\", $ntp_start + $start_offset}")
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync && test_fail
done

View file

@ -36,7 +36,7 @@ Update interval : 16\.. seconds
.*$" || test_fail
if echo "$refclock" | grep -q 'PHC.*nocrossts'; then
check_file_messages "20.* GPS.*[0-9] N " 650 750 refclocks.log || test_fail
check_file_messages "20.* GPS.*[0-9] N " 620 750 refclocks.log || test_fail
else
check_file_messages "20.* GPS.*[0-9] N " 997 1001 refclocks.log || test_fail
fi
@ -64,7 +64,7 @@ Stratum.*: 1
Root delay : 0\.000000001 seconds
.*$" || test_fail
check_file_messages "20.* PPS1.*[0-9] N " 620 740 refclocks.log || test_fail
check_file_messages "20.* PPS1.*[0-9] N " 610 740 refclocks.log || test_fail
check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail
rm -f tmp/refclocks.log
@ -89,14 +89,15 @@ Root delay : 0\.000000001 seconds
check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail
rm -f tmp/refclocks.log
min_sync_time=100
max_sync_time=220
min_sync_time=80
max_sync_time=180
chronyc_start=220
client_conf="
refclock SHM 0 refid NMEA offset 0.35 delay 0.1
refclock PPS /dev/pps0
logdir tmp
log refclocks"
log refclocks
maxupdateskew 10000"
run_test || test_fail
check_chronyd_exit || test_fail
@ -108,11 +109,37 @@ Stratum.*: 1
Root delay : 0\.000000001 seconds
.*$" || test_fail
check_file_messages "20.* PPS1.*[0-9] N " 800 940 refclocks.log || test_fail
check_file_messages "20.* PPS1.*[0-9] N " 800 960 refclocks.log || test_fail
check_file_messages "20.* PPS1.*- N " 50 63 refclocks.log || test_fail
rm -f tmp/refclocks.log
fi
export CLKNETSIM_PHC_JITTER_OFF=$[2 * 25 * 492]
export CLKNETSIM_PHC_JITTER_ON=$[2 * 25 * 8]
export CLKNETSIM_PHC_JITTER=1e-6
refclock_offset=0.0
refclock_jitter=1e-9
min_sync_time=5
max_sync_time=7
time_max_limit=1e-7
time_rms_limit=1e-8
client_conf="refclock PHC /dev/ptp0:nocrossts poll 0
logdir tmp
log refclocks"
chronyc_start=500
chronyc_conf="sources"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_chronyc_output "^MS.*
=*
#\* PHC0 0 0 377 8 .*$" || test_fail
unset CLKNETSIM_PHC_JITTER_OFF
unset CLKNETSIM_PHC_JITTER_ON
export CLKNETSIM_PHC_JITTER=1e-7
refclock_offset="(+ 0.399 (sum 1e-3))"
refclock_jitter=1e-6
servers=1

View file

@ -21,6 +21,7 @@ EOF
clients=2
peers=2
freq_max_limit=1e-3
max_sync_time=1000
client_server_options="minpoll 6 maxpoll 6"
client_peer_options="minpoll 6 maxpoll 6"
@ -30,6 +31,7 @@ check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
freq_max_limit=$default_freq_max_limit
base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
client_peer_options=""

View file

@ -58,7 +58,7 @@ MS Name/IP address Stratum Poll Reach LastRx Last sample
\^\? 192\.168\.123\.2 0 [0-9]+ 0 - \+0ns\[ \+0ns\] \+/- 0ns
Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
==============================================================================
SHM0 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][01]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s
SHM0 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][012]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s
192\.168\.123\.1 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][01]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s
192\.168\.123\.2 0 0 0 \+0\.000 2000\.000 \+0ns 4000ms
210 n_samples = 0
@ -114,7 +114,7 @@ limit=1
for chronyc_conf in \
"accheck 1.2.3.4" \
"add peer 10.0.0.0 minpoll 2 maxpoll 6" \
"add server 10.0.0.0 minpoll 6 maxpoll 10 iburst burst key 1 certset 2 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 maxdelayquant 0.5 mindelay 1e-4 asymmetry 0.5 offset 1e-5 minsamples 6 maxsamples 6 filter 3 offline auto_offline prefer noselect trust require xleave polltarget 20 port 123 presend 7 minstratum 3 version 4 nts ntsport 4460 copy extfield F323" \
"add server 10.0.0.0 minpoll 6 maxpoll 10 iburst burst key 1 certset 2 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 maxdelayquant 0.5 mindelay 1e-4 asymmetry 0.5 offset 1e-5 minsamples 6 maxsamples 6 filter 3 offline auto_offline prefer noselect trust require xleave polltarget 20 port 123 presend 7 minstratum 3 version 4 nts ntsport 4460 copy extfield F323 extfield F324 ipv6 ipv4" \
"add server node1.net1.clk" \
"allow 1.2.3.4" \
"allow 1.2" \
@ -145,7 +145,7 @@ for chronyc_conf in \
"dfreq 1.0e-3" \
"doffset -1.0" \
"dump" \
"local stratum 5 distance 1.0 orphan" \
"local stratum 5 distance 1.0 activate 0.5 orphan" \
"local off" \
"makestep 10.0 3" \
"makestep" \
@ -165,6 +165,7 @@ for chronyc_conf in \
"offline" \
"offline 255.255.255.0/1.2.3.0" \
"offline 1.2.3.0/24" \
"offset 1.2.3.4 1.0" \
"online" \
"online 1.2.3.0/24" \
"onoffline" \
@ -247,6 +248,10 @@ Total TX : 1
Total RX : 1
Total valid RX : 1
Total good RX : 0
Total kernel TX : [01]
Total kernel RX : 1
Total HW TX : 0
Total HW RX : 0
S Name/IP Address Auth COpts EOpts Last Score Interval Leap
=======================================================================
M node1\.net1\.clk N ----- ----- 0 1\.0 \+0ns \+0ns N
@ -347,6 +352,7 @@ maxpoll 192.168.123.1 5
maxupdateskew 192.168.123.1 10.0
minpoll 192.168.123.1 3
minstratum 192.168.123.1 1
offset 192.168.123.1 -1.0
polltarget 192.168.123.1 10
selectopts 192.168.123.1 +trust +prefer -require
selectdata
@ -371,6 +377,7 @@ check_chronyc_output "^200 OK
200 OK
200 OK
200 OK
200 OK
S Name/IP Address Auth COpts EOpts Last Score Interval Leap
=======================================================================
M node1\.net1\.clk N \-PT\-\- \-PT\-\- 0 1\.0 \+0ns \+0ns \?
@ -433,7 +440,12 @@ server_conf="
server 192.168.123.1
noclientlog"
commands=(
check_config_h 'FEAT_IPV6 1' && commands=(
"add server ::1 ipv4" "^515 Invalid address family$"
) || commands=()
commands+=(
"add server 192.168.123.1 ipv6" "^515 Invalid address family$"
"add server nosuchnode.net1.clk" "^Invalid host/IP address$"
"allow nosuchnode.net1.clk" "^Could not read address$"
"allow 192.168.123.0/2 4" "^Could not read address$"

View file

@ -8,54 +8,86 @@ check_config_h 'FEAT_REFCLOCK 1' || test_skip
export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Dec 30 2008 0:00:00' +'%s')
leap=$[2 * 24 * 3600]
limit=$[4 * 24 * 3600]
client_start=$[2 * 3600]
server_conf="refclock SHM 0 dpoll 10 poll 10
leapsectz right/UTC"
refclock_jitter=1e-9
refclock_offset="(* -1.0 (equal 0.1 (max (sum 1.0) $leap) $leap))"
for leapmode in system step slew; do
client_conf="leapsecmode $leapmode"
if [ $leapmode = slew ]; then
max_sync_time=$[$leap + 12]
else
max_sync_time=$[$leap]
fi
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
done
client_server_options="trust"
client_conf="refclock SHM 0 dpoll 10 poll 10 delay 1e-3"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
client_server_options=""
client_conf="leapsecmode system"
min_sync_time=230000
max_sync_time=240000
for smoothmode in "" "leaponly"; do
for dir in "+1" "-1"; do
leap=$[2 * 24 * 3600 + 1 + $dir]
server_conf="refclock SHM 0 dpoll 10 poll 10
leapsectz right/UTC
leapsecmode slew
smoothtime 400 0.001 $smoothmode"
leapseclist tmp/leap.list"
refclock_offset="(* $dir (equal 0.1 (max (sum 1.0) $leap) $leap))"
cat > tmp/leap.list <<-EOF
#$ 3676924800
#@ 3928521600
3345062400 33 # 1 Jan 2006
3439756800 $[33 - $dir] # 1 Jan 2009 $(
[ "$dir" = "+1" ] && echo -e "\n3471292800 33\n3502828800 34")
3550089600 35 # 1 Jul 2012
EOF
for leapmode in system step slew; do
client_conf="leapsecmode $leapmode"
if [ $leapmode = slew ]; then
max_sync_time=$[2 * 24 * 3600 + 13]
else
max_sync_time=$[2 * 24 * 3600 + 1]
fi
min_sync_time=$[$max_sync_time - 2]
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
check_file_messages "System clock TAI offset set to" 1 1 log.1 || test_fail
check_file_messages "System clock TAI offset set to 33" 1 1 log.1 || test_fail
done
client_server_options="trust"
client_conf="refclock SHM 0 dpoll 10 poll 10 delay 1e-3"
min_sync_time=$[$leap - 2]
max_sync_time=$[$leap]
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
client_server_options=""
client_conf="leapsecmode system"
min_sync_time=230000
max_sync_time=240000
for smoothmode in "" "leaponly"; do
server_conf="refclock SHM 0 dpoll 10 poll 10
leapseclist tmp/leap.list
leapsecmode slew
smoothtime 400 0.001 $smoothmode"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
done
done
if TZ=right/UTC date -d 'Dec 31 2008 23:59:60' 2> /dev/null | grep :60; then
server_conf="refclock SHM 0 dpoll 10 poll 10
leapsectz right/UTC"
refclock_offset="(* -1 (equal 0.1 (max (sum 1.0) $leap) $leap))"
client_conf="leapsecmode system"
min_sync_time=$[$leap - 2]
max_sync_time=$[$leap]
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
fi
test_pass

View file

@ -23,4 +23,29 @@ check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
limit=10
base_delay=$default_base_delay
client_conf="logdir tmp
log measurements"
client_server_options="presend 5"
run_test || test_fail
check_chronyd_exit || test_fail
check_packet_interval || test_fail
check_file_messages "20.*123\.1.* 111 111 0111" 1 1 measurements.log || test_fail
check_file_messages "20.*123\.1.* 111 111 1111" 1 1 measurements.log || test_fail
rm -f tmp/measurements.log
client_server_options="presend 5 xleave"
run_test || test_fail
check_chronyd_exit || test_fail
check_packet_interval || test_fail
check_file_messages "20.*123\.1.* 111 111 0111" 2 2 measurements.log || test_fail
check_file_messages "20.*123\.1.* 111 111 1111" 1 1 measurements.log || test_fail
rm -f tmp/measurements.log
test_pass

View file

@ -14,7 +14,6 @@ client_server_options="maxpoll 6 maxdelay 3e-5 maxdelayratio 2.0 maxdelaydevrati
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail

View file

@ -14,7 +14,7 @@ max_sync_time=800
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_source_selection && test_fail
check_sync || test_fail
limit=10000

90
test/simulation/121-local Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env bash
. ./test.common
test_start "local options"
check_config_h 'FEAT_CMDMON 1' || test_skip
server_strata=3
server_conf="local stratum 5 orphan
server 192.168.123.1
server 192.168.123.2
server 192.168.123.3"
max_sync_time=900
client_start=140
chronyc_start=700
chronyc_conf="tracking"
time_rms_limit=5e-4
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_chronyc_output "^.*Stratum *: 7.*$" || test_fail
limit=4000
wander=0.0
jitter=0.0
server_strata=1
server_conf=""
client_server_options="minpoll 6 maxpoll 6 minsamples 64"
chronyc_start=1
chronyc_conf="timeout 1000000
tracking
tracking
tracking
tracking"
base_delay=$(cat <<-EOF | tr -d '\n'
(+ 1e-4
(* 990
(equal 0.1 from 3))
(* -1
(equal 0.1 from 1)
(equal 0.1 (max (% time 2000) 1000) 1000)))
EOF
)
client_conf="local
maxclockerror 1000"
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.*7F7F0101.*C0A87B01.*7F7F0101.*C0A87B01.*$" || test_fail
client_conf="local distance 0.5
maxclockerror 1000"
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.*7F7F0101.*C0A87B01.*7F7F0101.*C0A87B01.*$" || test_fail
client_conf="local distance 2.0
maxclockerror 1000"
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.*7F7F0101.*C0A87B01.*C0A87B01.*C0A87B01.*$" || test_fail
client_conf="local activate 1e-4
maxclockerror 1000"
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.* 00000000 .*C0A87B01.*C0A87B01.*C0A87B01.*$" || test_fail
client_conf="local activate 1e-1
maxclockerror 1000"
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.* 00000000 .*C0A87B01.*7F7F0101.*C0A87B01.*$" || test_fail
client_conf="local activate 1e-1 distance 2.0
maxclockerror 1000"
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.* 00000000 .*C0A87B01.*C0A87B01.*C0A87B01.*$" || test_fail
test_pass

View file

@ -1,26 +0,0 @@
#!/usr/bin/env bash
. ./test.common
test_start "orphan option"
check_config_h 'FEAT_CMDMON 1' || test_skip
server_strata=3
server_conf="local stratum 5 orphan
server 192.168.123.1
server 192.168.123.2
server 192.168.123.3"
max_sync_time=900
client_start=140
chronyc_start=700
chronyc_conf="tracking"
time_rms_limit=5e-4
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_chronyc_output "^.*Stratum *: 7.*$" || test_fail
test_pass

View file

@ -53,7 +53,6 @@ for rpoll in 4 5 6; do
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
if [ $rpoll -le 5 ]; then

View file

@ -18,9 +18,17 @@ servers=0
refclock_offset="(+ -34 (equal 0.1 (max (sum 1.0) $leap) $leap))"
client_conf="
refclock SHM 0 dpoll 0 poll 0 tai
leapsectz right/UTC
leapseclist tmp/leap.list
leapsecmode ignore
maxchange 1e-3 1 0"
maxchange 1e-3 10 0"
cat > tmp/leap.list <<-EOF
#$ 3676924800
#@ 3928521600
3345062400 33 # 1 Jan 2006
3439756800 34 # 1 Jan 2009
3550089600 35 # 1 Jul 2012
EOF
run_test || test_fail
check_chronyd_exit || test_fail
@ -33,9 +41,9 @@ time_offset=-1000
refclock_offset="(+ -34)"
client_conf="
refclock SHM 0 dpoll 0 poll 0 tai
leapsectz right/UTC
leapseclist tmp/leap.list
makestep 1 1
maxchange 1e-3 1 0"
maxchange 1e-3 10 0"
run_test || test_fail
check_chronyd_exit || test_fail

View file

@ -22,7 +22,7 @@ client_min_mean_out_interval=150.0
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_source_selection && test_fail
check_packet_interval || test_fail
check_sync || test_fail

View file

@ -12,7 +12,7 @@ client_min_mean_out_interval=15.9
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_source_selection && test_fail
check_packet_interval || test_fail
check_sync || test_fail

View file

@ -15,4 +15,15 @@ check_sync || test_fail
check_file_messages " 2 1 " 1200 1300 log.packets || test_fail
check_file_messages " 1 2 " 180 220 log.packets || test_fail
server_conf="ratelimit interval 6 burst 2 leak 4 kod 2"
run_test || test_fail
check_chronyd_exit || test_fail
check_packet_interval || test_fail
check_sync || test_fail
check_file_messages " 2 1 " 700 850 log.packets || test_fail
check_file_messages " 1 2 " 350 450 log.packets || test_fail
check_log_messages "Received KoD RATE.*\.123.1" 100 140 || test_fail
test_pass

View file

@ -313,4 +313,31 @@ check_sync && test_fail
check_file_messages " 3 1 .* 123 " 0 0 log.packets || test_fail
check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail
for server_aead in "" "15" "30"; do
for client_aead in "" "15" "30"; do
server_conf="
ntsaeads $server_aead
ntsserverkey tmp/server1.key
ntsservercert tmp/server1.crt
ntsprocesses 0"
client_conf="
nosystemcert
ntsaeads $client_aead
ntstrustedcerts tmp/server1.crt
ntstrustedcerts tmp/server2.crt"
client_server_conf=""
run_test || test_fail
check_chronyd_exit || test_fail
if [ -n "$server_aead" ] && [ "$server_aead" == "$client_aead" ] &&
( [ "$server_aead" != "30" ] || check_config_h '.*_SIV_GCM 1' ); then
check_source_selection || test_fail
check_sync || test_fail
else
check_source_selection && test_fail
check_sync && test_fail
fi
done
done
test_pass

123
test/simulation/142-ntpoverptp Executable file
View file

@ -0,0 +1,123 @@
#!/usr/bin/env bash
. ./test.common
test_start "NTP over PTP"
# Block communication between 3 and 1
base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
cat > tmp/peer.keys <<-EOF
1 MD5 1234567890
EOF
clients=2
peers=2
max_sync_time=420
server_conf="
ptpdomain 123
ptpport 319"
client_conf="
ptpport 319
authselectmode ignore
keyfile tmp/peer.keys"
client_server_options="minpoll 6 maxpoll 6 port 319"
client_peer_options="minpoll 6 maxpoll 6 port 319 key 1"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_file_messages " 2 1 .* 319 319 1 96 " 150 160 \
log.packets || test_fail
check_file_messages " 1 2 .* 319 319 1 96 " 150 160 \
log.packets || test_fail
check_file_messages " 2 3 .* 319 319 1 116 " 150 160 \
log.packets || test_fail
check_file_messages " 3 2 .* 319 319 1 116 " 150 160 \
log.packets || test_fail
check_config_h 'HAVE_LINUX_TIMESTAMPING 1' || test_skip
export CLKNETSIM_TIMESTAMPING=2
export CLKNETSIM_LINK_SPEED=100
client_server_options+=" extfield F324 minpoll 0 maxpoll 0"
client_peer_options+=" extfield F324 minpoll 0 maxpoll 0 maxdelaydevratio 1e6"
server_conf+="
clockprecision 1e-9
hwtimestamp eth0"
client_conf+="
clockprecision 1e-9
hwtimestamp eth0"
delay_correction="(+ delay (* -8e-8 (+ length 46)))"
wander=1e-9
limit=1000
freq_offset=-1e-4
min_sync_time=5
max_sync_time=20
time_max_limit=1e-7
time_rms_limit=2e-8
freq_max_limit=1e-7
freq_rms_limit=5e-8
client_chronyd_options="-d"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
if check_config_h 'FEAT_DEBUG 1'; then
check_log_messages "apply_net_correction.*Applied" 900 2100 || test_fail
check_log_messages "apply_net_correction.*Invalid" 0 4 || test_fail
fi
client_server_options+=" xleave"
client_peer_options+=" xleave"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
if check_config_h 'FEAT_DEBUG 1'; then
check_log_messages "apply_net_correction.*Applied" 900 2100 || test_fail
check_log_messages "apply_net_correction.*Invalid" 0 4 || test_fail
freq_offset=0.0
delay_correction="(+ -1.0e-9 (* 1.0001 delay))"
run_test || test_fail
check_chronyd_exit || test_fail
check_log_messages "apply_net_correction.*Applied" 350 1400 || test_fail
check_log_messages "apply_net_correction.*Invalid" 350 1400 || test_fail
server_conf="ptpport 319"
client_conf="ptpport 319"
run_test || test_fail
check_chronyd_exit || test_fail
check_log_messages "apply_net_correction.*Applied" 0 0 || test_fail
fi
freq_offset=-1e-4
delay_correction=""
server_conf="ptpport 319"
client_conf="ptpport 319
ptpdomain 124
authselectmode ignore
keyfile tmp/peer.keys"
time_max_limit=$default_time_max_limit
time_rms_limit=$default_time_rms_limit
freq_max_limit=$default_freq_max_limit
freq_rms_limit=$default_freq_rms_limit
run_test || test_fail
check_chronyd_exit || test_fail
check_sync && test_fail
test_pass

View file

@ -1,41 +0,0 @@
#!/usr/bin/env bash
. ./test.common
test_start "PTP port"
# Block communication between 3 and 1
base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
cat > tmp/peer.keys <<-EOF
1 MD5 1234567890
EOF
clients=2
peers=2
max_sync_time=420
server_conf="
ptpport 319"
client_conf="
ptpport 319
authselectmode ignore
keyfile tmp/peer.keys"
client_server_options="minpoll 6 maxpoll 6 port 319"
client_peer_options="minpoll 6 maxpoll 6 port 319 key 1"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_file_messages " 2 1 .* 319 319 1 96 " 150 160 \
log.packets || test_fail
check_file_messages " 1 2 .* 319 319 1 96 " 150 160 \
log.packets || test_fail
check_file_messages " 2 3 .* 319 319 1 116 " 150 160 \
log.packets || test_fail
check_file_messages " 3 2 .* 319 319 1 116 " 150 160 \
log.packets || test_fail
test_pass

View file

@ -2,7 +2,7 @@
. ./test.common
test_start "experimental extension field"
test_start "mono+root extension field"
check_config_h 'FEAT_CMDMON 1' || test_skip
@ -20,7 +20,7 @@ for options in "extfield F323" "xleave extfield F323"; do
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_source_selection && test_fail
check_sync || test_fail
done
@ -47,7 +47,7 @@ for lpoll in 5 6 7; do
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_source_selection && test_fail
check_sync || test_fail
done
done

View file

@ -53,4 +53,28 @@ check_log_messages "2010-01-01T0[5-9]:.*Source 192.168.123.. replaced with" 0 15
check_file_messages "20.*192.168.123.* 11.1 6 6 " 20 500 measurements.log || test_fail
rm -f tmp/measurements.log
# 2 replaceable falsetickers and 1 replaceable unreachable server
servers=6
falsetickers=2
base_delay="(+ 1e-4 (* -1 (equal 0.1 to 3)))"
client_server_conf="
server nodes-4-1.net1.clk
server nodes-5-2.net1.clk
server nodes-6-3.net1.clk"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection && test_fail
check_packet_interval || test_fail
check_sync || test_fail
check_log_messages "Can't synchronise: no majority" 1 1 || test_fail
check_log_messages "Detected falseticker" 0 2 || test_fail
check_log_messages "Source 192.168.123.. replaced with" 3 60 || test_fail
check_log_messages "Source 192.168.123.1 replaced with" 1 25 || test_fail
check_log_messages "Source 192.168.123.2 replaced with" 1 25 || test_fail
check_log_messages "Source 192.168.123.3 replaced with" 1 25 || test_fail
check_file_messages "20.*192.168.123.* 11.1 6 6 " 50 800 measurements.log || test_fail
rm -f tmp/measurements.log
test_pass

26
test/simulation/203-initreload Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
. ./test.common
check_config_h 'FEAT_CMDMON 1' || test_skip
# Test fix "conf: don't load sourcedir during initstepslew and RTC init"
test_start "reload during initstepslew"
client_conf="initstepslew 5 192.168.123.1
sourcedir tmp"
client_server_conf="#"
chronyc_conf="reload sources"
chronyc_start=4
echo 'server 192.168.123.1' > tmp/sources.sources
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_log_messages "Added source 192\.168\.123\.1" 1 1 || test_fail
test_pass

View file

@ -31,6 +31,7 @@ default_primary_time_offset=0.0
default_time_offset=1e-1
default_freq_offset=1e-4
default_base_delay=1e-4
default_delay_correction=""
default_jitter=1e-4
default_jitter_asymmetry=0.0
default_wander=1e-9
@ -460,6 +461,10 @@ run_test() {
for j in $(seq 1 $nodes); do
echo "node${i}_delay${j} = $(get_delay_expr up)"
echo "node${j}_delay${i} = $(get_delay_expr down)"
if [ -n "$delay_correction" ]; then
echo "node${i}_delay_correction${j} = $delay_correction"
echo "node${j}_delay_correction${i} = $delay_correction"
fi
done
done > tmp/conf

View file

@ -28,6 +28,7 @@ for command in \
"local" \
"online" \
"onoffline" \
"offset $server 0.0" \
"maxdelay $server 1e-1" \
"maxdelaydevratio $server 5.0" \
"maxdelayratio $server 3.0" \
@ -97,12 +98,16 @@ RX timestamping : (Daemon|Kernel)
Total TX : [0-9]+
Total RX : [0-9]+
Total valid RX : [0-9]+
Total good RX : [0-9]+$" || test_fail
Total good RX : [0-9]+
Total kernel TX : [0-9]+
Total kernel RX : [0-9]+
Total HW TX : 0
Total HW RX : 0$" || test_fail
run_chronyc "selectdata" || test_fail
check_chronyc_output "^S Name/IP Address Auth COpts EOpts Last Score Interval Leap
=======================================================================
s 127\.0\.0\.1 N -PTR- -PTR- 0 1\.0 \+0ns \+0ns \?$" || test_fail
M 127\.0\.0\.1 N -PTR- -PTR- 0 1\.0 \+0ns \+0ns \?$" || test_fail
run_chronyc "serverstats" || test_fail
check_chronyc_output "^NTP packets received : [0-9]+

View file

@ -30,6 +30,8 @@ echo "server 127.123.4.4" > $TEST_DIR/conf4.d/4.conf
echo "server 127.123.5.1" > $TEST_DIR/conf5.d/1.sources
echo "server 127.123.5.2" > $TEST_DIR/conf5.d/2.sources
echo "server 127.123.5.3" > $TEST_DIR/conf5.d/3.sources
echo "server 127.123.5.4" > $TEST_DIR/conf5.d/4.sources
echo "server 127.123.5.5" > $TEST_DIR/conf5.d/5.sources
start_chronyd || test_fail
@ -46,12 +48,16 @@ check_chronyc_output "^[^=]*
.. 127\.123\.1\.2 [^^]*
.. 127\.123\.5\.1 [^^]*
.. 127\.123\.5\.2 [^^]*
.. 127\.123\.5\.3 [^^]*$" || test_fail
.. 127\.123\.5\.3 [^^]*
.. 127\.123\.5\.4 [^^]*
.. 127\.123\.5\.5 [^^]*$" || test_fail
rm $TEST_DIR/conf5.d/1.sources
echo "server 127.123.5.2 minpoll 7" > $TEST_DIR/conf5.d/2.sources
echo > $TEST_DIR/conf5.d/3.sources
echo "server 127.123.5.4" > $TEST_DIR/conf5.d/4.sources
echo "server 127.123.5.2 minpoll 5" > $TEST_DIR/conf5.d/2.sources
echo "server 127.123.5.3 minpoll 7" > $TEST_DIR/conf5.d/3.sources
echo > $TEST_DIR/conf5.d/4.sources
echo "server 127.123.5.5" >> $TEST_DIR/conf5.d/5.sources
echo "server 127.123.5.6" > $TEST_DIR/conf5.d/6.sources
run_chronyc "reload sources" || test_fail
@ -66,9 +72,37 @@ check_chronyc_output "^[^=]*
.. 127\.123\.2\.3 [^^]*
.. 127\.123\.4\.4 [^^]*
.. 127\.123\.1\.2 *[05] 6 [^^]*
.. 127\.123\.5\.2 *[05] 7 [^^]*
.. 127\.123\.5\.4 [^^]*$" || test_fail
.. 127\.123\.5\.5 [^^]*
.. 127\.123\.5\.2 *[05] 5 [^^]*
.. 127\.123\.5\.3 *[05] 7 [^^]*
.. 127\.123\.5\.6 [^^]*$" || test_fail
run_chronyc "reload sources" || test_fail
run_chronyc "reload sources" || test_fail
rm $TEST_DIR/conf5.d/{3,5,6}.sources
echo "server 127.123.5.7" > $TEST_DIR/conf5.d/7.sources
run_chronyc "reload sources" || test_fail
run_chronyc "sources" || test_fail
check_chronyc_output "^[^=]*
=*
.. 127\.123\.1\.1 [^^]*
.. 127\.123\.1\.3 [^^]*
.. 127\.123\.1\.4 [^^]*
.. 127\.123\.3\.1 [^^]*
.. 127\.123\.2\.2 [^^]*
.. 127\.123\.2\.3 [^^]*
.. 127\.123\.4\.4 [^^]*
.. 127\.123\.1\.2 *[05] 6 [^^]*
.. 127\.123\.5\.2 *[05] 5 [^^]*
.. 127\.123\.5\.7 [^^]*$" || test_fail
run_chronyc "reload sources" || test_fail
stop_chronyd || test_fail
check_chronyd_message_count "Could not add source.*\.5\.5.*in use" 1 1 || test_fail
check_chronyd_message_count "Could not add source" 1 1 || test_fail
test_pass

View file

@ -45,6 +45,11 @@ check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atm
=========================================================================
127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
run_chronyc "serverstats" || test_fail
check_chronyc_output "NTS-KE connections accepted: 1
NTS-KE connections dropped : 0
Authenticated NTP packets : [1-9][0-9]*" || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_files || test_fail

140
test/system/011-systemd Executable file
View file

@ -0,0 +1,140 @@
#!/usr/bin/env bash
. ./test.common
check_chronyd_features NTS || test_skip "NTS support disabled"
certtool --help &> /dev/null || test_skip "certtool missing"
check_chronyd_features DEBUG || test_skip "DEBUG support disabled"
systemd-socket-activate -h &> /dev/null || test_skip "systemd-socket-activate missing"
has_ipv6=$(check_chronyd_features IPV6 && ping6 -c 1 ::1 > /dev/null 2>&1 && echo 1 || echo 0)
test_start "systemd socket activation"
cat > $TEST_DIR/cert.cfg <<EOF
cn = "chrony-nts-test"
dns_name = "chrony-nts-test"
ip_address = "$server"
$([ "$has_ipv6" = "1" ] && echo 'ip_address = "::1"')
serial = 001
activation_date = "$[$(date '+%Y') - 1]-01-01 00:00:00 UTC"
expiration_date = "$[$(date '+%Y') + 2]-01-01 00:00:00 UTC"
signing_key
encryption_key
EOF
certtool --generate-privkey --key-type=ed25519 --outfile $TEST_DIR/server.key \
&> $TEST_DIR/certtool.log
certtool --generate-self-signed --load-privkey $TEST_DIR/server.key \
--template $TEST_DIR/cert.cfg --outfile $TEST_DIR/server.crt &>> $TEST_DIR/certtool.log
chown $user $TEST_DIR/server.*
ntpport=$(get_free_port)
ntsport=$(get_free_port)
server_options="port $ntpport nts ntsport $ntsport"
extra_chronyd_directives="
port $ntpport
ntsport $ntsport
ntsserverkey $TEST_DIR/server.key
ntsservercert $TEST_DIR/server.crt
ntstrustedcerts $TEST_DIR/server.crt
ntsdumpdir $TEST_LIBDIR
ntsprocesses 3"
if [ "$has_ipv6" = "1" ]; then
extra_chronyd_directives="$extra_chronyd_directives
bindaddress ::1
server ::1 minpoll -6 maxpoll -6 $server_options"
fi
# enable debug logging
extra_chronyd_options="-L -1"
# Hack to trigger systemd-socket-activate to activate the service. Normally,
# chronyd.service would be configured with the WantedBy= directive so it starts
# without waiting for socket activation.
# (https://0pointer.de/blog/projects/socket-activation.html).
for i in $(seq 10); do
sleep 1
(echo "wake up" > /dev/udp/127.0.0.1/$ntpport) 2>/dev/null
(echo "wake up" > /dev/tcp/127.0.0.1/$ntsport) 2>/dev/null
done &
# Test with UDP sockets (unfortunately systemd-socket-activate doesn't support
# both datagram and stream sockets in the same invocation:
# https://github.com/systemd/systemd/issues/9983).
CHRONYD_WRAPPER="systemd-socket-activate \
--datagram \
--listen 127.0.0.1:$ntpport \
--listen 127.0.0.1:$ntsport"
if [ "$has_ipv6" = "1" ]; then
CHRONYD_WRAPPER="$CHRONYD_WRAPPER \
--listen [::1]:$ntpport \
--listen [::1]:$ntsport"
fi
start_chronyd || test_fail
wait_for_sync || test_fail
if [ "$has_ipv6" = "1" ]; then
run_chronyc "ntpdata ::1" || test_fail
check_chronyc_output "Total RX +: [1-9]" || test_fail
fi
run_chronyc "authdata" || test_fail
check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
=========================================================================\
$([ "$has_ipv6" = "1" ] && printf "\n%s\n" '::1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)')
127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
stop_chronyd || test_fail
# DGRAM ntpport socket should be used
check_chronyd_message_count "Reusing UDPv4 socket fd=3 local=127.0.0.1:$ntpport" 1 1 || test_fail
# DGRAM ntsport socket should be ignored
check_chronyd_message_count "Reusing TCPv4 socket fd=4 local=127.0.0.1:$ntsport" 0 0 || test_fail
if [ "$has_ipv6" = "1" ]; then
# DGRAM ntpport socket should be used
check_chronyd_message_count "Reusing UDPv6 socket fd=5 local=\[::1\]:$ntpport" 1 1 || test_fail
# DGRAM ntsport socket should be ignored
check_chronyd_message_count "Reusing TCPv6 socket fd=6 local=\[::1\]:$ntsport" 0 0 || test_fail
fi
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
# Test with TCP sockets
CHRONYD_WRAPPER="systemd-socket-activate \
--listen 127.0.0.1:$ntpport \
--listen 127.0.0.1:$ntsport"
if [ "$has_ipv6" = "1" ]; then
CHRONYD_WRAPPER="$CHRONYD_WRAPPER \
--listen [::1]:$ntpport \
--listen [::1]:$ntsport"
fi
start_chronyd || test_fail
wait_for_sync || test_fail
if [ "$has_ipv6" = "1" ]; then
run_chronyc "ntpdata ::1" || test_fail
check_chronyc_output "Total RX +: [1-9]" || test_fail
fi
run_chronyc "authdata" || test_fail
check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
=========================================================================\
$([ "$has_ipv6" = "1" ] && printf "\n%s\n" '::1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)')
127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
stop_chronyd || test_fail
# STREAM ntpport should be ignored
check_chronyd_message_count "Reusing TCPv4 socket fd=3 local=127.0.0.1:$ntpport" 0 0 || test_fail
# STREAM ntsport should be used
check_chronyd_message_count "Reusing TCPv4 socket fd=4 local=127.0.0.1:$ntsport" 1 1 || test_fail
if [ "$has_ipv6" = "1" ]; then
# STREAM ntpport should be ignored
check_chronyd_message_count "Reusing TCPv6 socket fd=5 local=\[::1\]:$ntpport" 0 0 || test_fail
# STREAM ntsport should be used
check_chronyd_message_count "Reusing TCPv6 socket fd=6 local=\[::1\]:$ntsport" 1 1 || test_fail
fi
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
test_pass

View file

@ -324,7 +324,7 @@ check_chronyd_messages() {
([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
grep -q 'chronyd exiting' "$logfile" && \
! grep -q 'Could not' "$logfile" && \
! (grep -v '^.\{19\}Z D:' "$logfile" | grep -q 'Could not') && \
! grep -q 'Disabled command socket' "$logfile" && \
test_ok || test_bad
}

View file

@ -1,6 +1,6 @@
/*
**********************************************************************
* Copyright (C) Miroslav Lichvar 2016, 2021
* Copyright (C) Miroslav Lichvar 2016, 2021, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -35,18 +35,18 @@ void
test_unit(void)
{
uint64_t ts64, prev_first_ts64, prev_last_ts64, max_step;
int i, j, k, kod, passes, kods, drops, index, shift;
uint32_t index2, prev_first, prev_size;
NTP_Timestamp_Source ts_src, ts_src2;
struct timespec ts, ts2;
int i, j, k, index, shift;
CLG_Service s;
NTP_int64 ntp_ts;
IPAddr ip;
char conf[][100] = {
"clientloglimit 20000",
"ratelimit interval 3 burst 4 leak 3",
"cmdratelimit interval 3 burst 4 leak 3",
"ntsratelimit interval 6 burst 8 leak 3",
"ntsratelimit interval 4 burst 8 leak 3",
"cmdratelimit interval 6 burst 4 leak 3",
};
CNF_Initialise(0, 0);
@ -80,19 +80,51 @@ test_unit(void)
DEBUG_LOG("records %u", ARR_GetSize(records));
TEST_CHECK(ARR_GetSize(records) == 128);
s = CLG_NTP;
for (kod = 0; kod <= 2; kod += 2) {
for (s = CLG_NTP; s <= CLG_CMDMON; s++) {
for (i = passes = kods = drops = 0; i < 10000; i++) {
kod_rate[s] = kod;
ts.tv_sec += 1;
index = CLG_LogServiceAccess(s, &ip, &ts);
TEST_CHECK(index >= 0);
switch (CLG_LimitServiceRate(s, index)) {
case CLG_PASS:
passes += 1;
break;
case CLG_DROP:
drops += 1;
break;
case CLG_KOD:
kods += 1;
break;
default:
assert(0);
}
}
for (i = j = 0; i < 10000; i++) {
ts.tv_sec += 1;
index = CLG_LogServiceAccess(s, &ip, &ts);
TEST_CHECK(index >= 0);
if (!CLG_LimitServiceRate(s, index))
j++;
DEBUG_LOG("service %d requests %d passes %d kods %d drops %d",
(int)s, i, passes, kods, drops);
if (kod)
TEST_CHECK(kods * 2.5 < drops && kods * 3.5 > drops);
else
TEST_CHECK(kods == 0);
switch (s) {
case CLG_NTP:
TEST_CHECK(passes > 1750 && passes < 2050);
break;
case CLG_NTSKE:
TEST_CHECK(passes > 1300 && passes < 1600);
break;
case CLG_CMDMON:
TEST_CHECK(passes > 1100 && passes < 1400);
break;
default:
assert(0);
}
}
}
DEBUG_LOG("requests %d responses %d", i, j);
TEST_CHECK(j * 4 < i && j * 6 > i);
TEST_CHECK(!ntp_ts_map.timestamps);
UTI_ZeroNtp64(&ntp_ts);

106
test/unit/leapdb.c Normal file
View file

@ -0,0 +1,106 @@
/*
**********************************************************************
* Copyright (C) Patrick Oppenlander 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
*/
#include <leapdb.c>
#include "test.h"
struct test_vector {
time_t when;
int tai_offset;
NTP_Leap leap;
int fake;
} tests[] = {
/* leapdb.list is a cut down version of leap-seconds.list */
{3439756800, 34, LEAP_InsertSecond, 0}, /* 1 Jan 2009 */
{3550089600, 35, LEAP_InsertSecond, 0}, /* 1 Jul 2012 */
{3644697600, 36, LEAP_InsertSecond, 0}, /* 1 Jul 2015 */
{3692217600, 37, LEAP_InsertSecond, 0}, /* 1 Jan 2017 */
{3786825600, 36, LEAP_DeleteSecond, 1}, /* 1 Jan 2020 fake in leapdb.list */
};
static void
test_leap_source(NTP_Leap (*fn)(time_t when, int *tai_offset),
int skip_fakes)
{
int i, prev_tai_offset = 34;
for (i = 0; i < sizeof tests / sizeof tests[0]; ++i) {
struct test_vector *t = tests + i;
NTP_Leap leap;
int tai_offset = -1;
/* Our unit test leapdb.list contains a fake entry removing a leap second.
* Skip this when testing with the right/UTC timezone using mktime(). */
if (skip_fakes && t->fake)
continue;
/* One second before leap second */
leap = fn(t->when - LEAP_SEC_LIST_OFFSET - 1, &tai_offset);
TEST_CHECK(leap == t->leap);
TEST_CHECK(tai_offset = prev_tai_offset);
/* Exactly on leap second */
leap = fn(t->when - LEAP_SEC_LIST_OFFSET, &tai_offset);
TEST_CHECK(leap == LEAP_Normal);
TEST_CHECK(tai_offset == t->tai_offset);
/* One second after leap second */
leap = fn(t->when - LEAP_SEC_LIST_OFFSET + 1, &tai_offset);
TEST_CHECK(leap == LEAP_Normal);
TEST_CHECK(tai_offset == t->tai_offset);
prev_tai_offset = t->tai_offset;
}
}
void
test_unit(void)
{
char conf[][100] = {
"leapsectz right/UTC",
"leapseclist leapdb.list"
};
int i;
CNF_Initialise(0, 0);
for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
CNF_ParseLine(NULL, i + 1, conf[i]);
LDB_Initialise();
if (check_leap_source(get_tz_leap)) {
DEBUG_LOG("testing get_tz_leap");
test_leap_source(get_tz_leap, 1);
} else {
DEBUG_LOG("Skipping get_tz_leap test. Either the right/UTC timezone is "
"missing, or mktime() doesn't support leap seconds.");
}
DEBUG_LOG("testing get_list_leap");
TEST_CHECK(check_leap_source(get_list_leap));
test_leap_source(get_list_leap, 0);
/* This exercises the twice-per-day logic */
DEBUG_LOG("testing LDB_GetLeap");
test_leap_source(LDB_GetLeap, 1);
LDB_Finalise();
CNF_Finalise();
}

22
test/unit/leapdb.list Normal file
View file

@ -0,0 +1,22 @@
#
# Cut down version of leap-seconds.list for unit test.
#
# Blank lines need to be ignored, so include a few for testing.
# Whitespace errors on non-blank lines below are copied from the original file.
#
# Leap second data update time
#$ 3676924800
#
# File update time
#@ 3928521600
3439756800 34 # 1 Jan 2009
3550089600 35 # 1 Jul 2012
3644697600 36 # 1 Jul 2015
3692217600 37 # 1 Jan 2017
3786825600 36 # 1 Jan 2020 (fake entry to test negative leap second)
# FIPS 180-1 hash
# NOTE! this value has not been recomputed for this unit test file.
#h 16edd0f0 3666784f 37db6bdd e74ced87 59af48f1

View file

@ -99,8 +99,8 @@ send_request(NCR_Instance inst, int late_hwts)
local_addr.ip_addr.family = IPADDR_UNSPEC;
local_addr.if_index = INVALID_IF_INDEX;
local_addr.sock_fd = 101;
zero_local_timestamp(&local_ts);
local_ts.ts = current_time;
local_ts.err = 0.0;
local_ts.source = NTP_TS_KERNEL;
NCR_ProcessTxKnown(inst, &local_addr, &local_ts, &req_buffer, req_length);
@ -122,8 +122,8 @@ process_request(NTP_Remote_Address *remote_addr)
local_addr.ip_addr.family = IPADDR_UNSPEC;
local_addr.if_index = INVALID_IF_INDEX;
local_addr.sock_fd = 100;
zero_local_timestamp(&local_ts);
local_ts.ts = current_time;
local_ts.err = 0.0;
local_ts.source = NTP_TS_KERNEL;
res_length = 0;
@ -289,8 +289,8 @@ proc_response(NCR_Instance inst, int good, int valid, int updated_sync,
local_addr.ip_addr.family = IPADDR_UNSPEC;
local_addr.if_index = INVALID_IF_INDEX;
local_addr.sock_fd = NTP_LVM_TO_MODE(res->lvm) != MODE_SERVER ? 100 : 101;
zero_local_timestamp(&local_ts);
local_ts.ts = current_time;
local_ts.err = 0.0;
local_ts.source = NTP_TS_KERNEL;
prev_rx_count = inst->report.total_rx_count;

View file

@ -88,6 +88,8 @@ update_random_address(NTP_Remote_Address *addr, int rand_bits)
TEST_CHECK(status == NSR_Success || status == NSR_AlreadyInUse);
}
TEST_CHECK(strlen(NSR_StatusToString(status)) > 0);
return status == NSR_Success;
}
@ -123,7 +125,7 @@ void
test_unit(void)
{
char source_line[] = "127.0.0.1 offline", conf[] = "port 0", name[64];
int i, j, k, slot, found, pool, prev_n;
int i, j, k, family, slot, found, pool, prev_n;
uint32_t hash = 0, conf_id;
NTP_Remote_Address addrs[256], addr;
NTP_Local_Address local_addr;
@ -214,7 +216,7 @@ test_unit(void)
TEST_CHECK(n_sources == 0);
status = NSR_AddSourceByName("a b", 0, 0, 0, &source.params, &conf_id);
status = NSR_AddSourceByName("a b", IPADDR_UNSPEC, 0, 0, 0, &source.params, &conf_id);
TEST_CHECK(status == NSR_InvalidName);
local_addr.ip_addr.family = IPADDR_INET4;
@ -226,11 +228,13 @@ test_unit(void)
for (i = 0; i < 500; i++) {
for (j = 0; j < 20; j++) {
snprintf(name, sizeof (name), "ntp%d.example.net", (int)(random() % 10));
family = random() % 2 ? IPADDR_UNSPEC : random() % 2 ? IPADDR_INET4 : IPADDR_INET6;
pool = random() % 2;
prev_n = n_sources;
DEBUG_LOG("%d/%d adding source %s pool=%d", i, j, name, pool);
status = NSR_AddSourceByName(name, 0, pool, random() % 2 ? NTP_SERVER : NTP_PEER,
status = NSR_AddSourceByName(name, family, 0, pool,
random() % 2 ? NTP_SERVER : NTP_PEER,
&source.params, &conf_id);
TEST_CHECK(status == NSR_UnresolvedName);
@ -240,11 +244,13 @@ test_unit(void)
for (us = unresolved_sources; us->next; us = us->next)
;
TEST_CHECK(strcmp(us->name, name) == 0);
TEST_CHECK(us->family == family);
if (pool) {
TEST_CHECK(us->address.ip_addr.family == IPADDR_UNSPEC && us->pool_id >= 0);
} else {
TEST_CHECK(strcmp(NSR_GetName(&us->address.ip_addr), name) == 0);
TEST_CHECK(find_slot2(&us->address, &slot) == 2);
TEST_CHECK(get_record(slot)->family == family);
}
if (random() % 2) {

View file

@ -1,6 +1,6 @@
/*
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
* Copyright (C) Miroslav Lichvar 2020, 2024
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
@ -23,8 +23,24 @@
#ifdef FEAT_NTS
#include <nts_ke_client.c>
#include <local.h>
#include <nts_ke_session.h>
#include <util.h>
#define NKSN_GetKeys get_keys
static int
get_keys(NKSN_Instance session, SIV_Algorithm algorithm, SIV_Algorithm exporter_algorithm,
int next_protocol, NKE_Key *c2s, NKE_Key *s2c)
{
c2s->length = SIV_GetKeyLength(algorithm);
UTI_GetRandomBytes(c2s->key, c2s->length);
s2c->length = SIV_GetKeyLength(algorithm);
UTI_GetRandomBytes(s2c->key, s2c->length);
return 1;
}
#include <nts_ke_client.c>
static void
prepare_response(NKSN_Instance session, int valid)
@ -88,12 +104,17 @@ prepare_response(NKSN_Instance session, int valid)
if (random() % 2) {
length = random() % (sizeof (data) + 1);
TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length));
TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length));
}
if (random() % 2)
TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COMPLIANT_128GCM_EXPORT, NULL, 0));
if (index != 8) {
for (i = 0; i < NKE_MAX_COOKIES; i++) {
for (i = random() % NKE_MAX_COOKIES; i >= 0; i--) {
length = (random() % sizeof (data) + 1) / 4 * 4;
if (i == 0 && length == 0)
length = 4;
if (index == 9)
length += (length < sizeof (data) ? 1 : -1) * (random() % 3 + 1);
TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, data, length));
@ -103,12 +124,16 @@ prepare_response(NKSN_Instance session, int valid)
TEST_CHECK(NKSN_EndMessage(session));
}
#define MAX_COOKIES (2 * NKE_MAX_COOKIES)
void
test_unit(void)
{
NKE_Context context, alt_context;
NKE_Cookie cookies[MAX_COOKIES];
int i, j, r, valid, num_cookies;
NKC_Instance inst;
IPSockAddr addr;
int i, r, valid;
char conf[][100] = {
"nosystemcert",
@ -127,10 +152,33 @@ test_unit(void)
TEST_CHECK(inst);
for (i = 0; i < 10000; i++) {
inst->got_response = 0;
valid = random() % 2;
prepare_response(inst->session, valid);
r = process_response(inst);
r = handle_message(inst);
TEST_CHECK(r == valid);
memset(&context, 0, sizeof (context));
memset(&alt_context, 0, sizeof (alt_context));
num_cookies = 0;
r = NKC_GetNtsData(inst, &context, &alt_context,
cookies, &num_cookies, random() % MAX_COOKIES + 1, &addr);
TEST_CHECK(r == valid);
if (r) {
TEST_CHECK(context.algorithm != AEAD_SIV_INVALID);
TEST_CHECK(context.c2s.length > 0);
TEST_CHECK(context.c2s.length == SIV_GetKeyLength(context.algorithm));
TEST_CHECK(context.s2c.length == SIV_GetKeyLength(context.algorithm));
if (alt_context.algorithm != AEAD_SIV_INVALID) {
TEST_CHECK(context.c2s.length > 0);
TEST_CHECK(alt_context.c2s.length == SIV_GetKeyLength(alt_context.algorithm));
TEST_CHECK(alt_context.s2c.length == SIV_GetKeyLength(alt_context.algorithm));
}
TEST_CHECK(num_cookies > 0 && num_cookies <= NKE_MAX_COOKIES);
for (j = 0; j < num_cookies; j++)
TEST_CHECK(cookies[j].length > 0 && cookies[j].length % 4 == 0);
}
}
NKC_DestroyInstance(inst);

Some files were not shown because too many files have changed in this diff Show more