Compare commits

...

230 Commits

Author SHA1 Message Date
Miroslav Lichvar
ad8fb64276 doc: improve NEWS
Add a notable enhancement from 4.0-pre1 and make the description of
another enhancement less ambiguous.
2020-04-20 15:49:58 +02:00
Miroslav Lichvar
436c1d3ea2 doc: update README 2020-04-20 15:44:12 +02:00
Miroslav Lichvar
7fc5da5f80 check return value of SCK_OpenUnixSocketPair() 2020-04-20 15:44:12 +02:00
Miroslav Lichvar
105b3faa46 samplefilt: remove useless assignment 2020-04-20 15:44:12 +02:00
Miroslav Lichvar
709223826f doc: update documentation for recent NTS changes 2020-04-16 18:09:32 +02:00
Miroslav Lichvar
eace93f2af nts: allow disabling certificate time checks
Add "nocerttimecheck" directive to specify the number of clock updates
that need to be made before the time validation of certificates is
enabled. This makes NTS usable on machines that don't have a RTC.
2020-04-16 18:09:32 +02:00
Miroslav Lichvar
2775846db7 nts: provide time function to gnutls
Use the internal time instead of system time for validation checks in
gnutls.
2020-04-16 18:09:29 +02:00
Miroslav Lichvar
4aff08e95d nts: add server support for NTP server negotiation
Add ntsntpserver directive to specify the hostname of the NTP server
provided in NTS-KE response to clients.
2020-04-16 17:47:27 +02:00
Miroslav Lichvar
958d66f8a7 cmdmon: reload NTS server keys on rekey command
When ntsrotate is set to 0, allow the keys to be reloaded with the rekey
command of chronyc.
2020-04-16 15:25:51 +02:00
Miroslav Lichvar
85fa29c43d nts: enable external management of server keys
If ntsrotate is set to 0, don't generate new server keys and don't save
them to ntsdumpdir. This allows the keys to be managed externally and
shared with other servers.
2020-04-16 15:25:50 +02:00
Miroslav Lichvar
0344b9a9c9 nts: generate cookies from second newest key
Generate one server key in advance to give it time to be distributed to
other servers before it is actually used.
2020-04-16 15:23:25 +02:00
Miroslav Lichvar
04f6329773 nts: encode key ID in cookie in network order
This allows the server cookie to be decoded on different platforms.
2020-04-15 16:30:54 +02:00
Miroslav Lichvar
d690faeb19 cmdmon: save NTS cookies and server keys on dump command
Extend the dump command to save also the server NTS keys and client NTS
cookies. Remove the warning for unset dumpdir.
2020-04-15 16:30:54 +02:00
Miroslav Lichvar
0b2e77ae64 ntp: update auth-specific address sooner
When replacing an NTP source, update the NTS address before the NTP
address to save cookies with the old NTP address instead of the newly
resolved address (which may immediately change to an address provided by
NTS-KE).
2020-04-15 16:30:54 +02:00
Miroslav Lichvar
2a4fd0a5c6 nts: update TLS exporter label
Change the string to "EXPORTER-network-time-security" as specified in
the latest NTS draft.
2020-04-09 17:08:52 +02:00
Miroslav Lichvar
e569e1c9d9 test: extend 139-nts test 2020-04-09 17:08:52 +02:00
Miroslav Lichvar
7be360041c nts: extend server key file format
Include in the key dump file an identifier, the AEAD number, and the
age of the last key to improve robustness and avoid generating a new key
immediately on start.

Also, improve the code that saves and loads the file.
2020-04-09 17:08:46 +02:00
Miroslav Lichvar
2fa83b541c nts: save and load cookies on client
Save the NTS context and cookies to files in the NTS dumpdir when the
client NTS instances are destroyed or the address is changed, and reload
the data to avoid unnecessary NTS-KE requests when chronyd is restarted
or it is switching between different addresses resolved from the NTS-KE
or NTP name.
2020-04-09 16:57:32 +02:00
Miroslav Lichvar
8db9d59dac nts: rename ntscachedir directive to ntsdumpdir
This makes the naming consistent with the existing dumpdir directive and
the dump command.
2020-04-09 16:57:32 +02:00
Miroslav Lichvar
adcf073484 nts: refactor NTS context
Add a context structure for the algorithm and keys established by
NTS-KE. Modify the client to save the context and reset the SIV key to
the C2S/S2C key before each request/response instead of keeping two SIV
instances.

This will make it easier for the server to support different algorithms
and allow the client to save the context with cookies to disk.
2020-04-09 16:57:31 +02:00
Miroslav Lichvar
5296858411 nts: drop unused constant 2020-04-09 16:42:20 +02:00
Miroslav Lichvar
d603426389 util: add function to split string into words 2020-04-09 16:42:20 +02:00
Miroslav Lichvar
d3f4292968 util: constify input parameters 2020-04-09 16:42:20 +02:00
Miroslav Lichvar
4dde7198c8 sources: constify parameters of log_selection_message() 2020-04-09 16:42:20 +02:00
Miroslav Lichvar
b145d3ff51 doc: add sourcename to list of remote commands 2020-04-09 16:42:20 +02:00
Miroslav Lichvar
9b98247d9c nts: zero cookie placeholder
Zero the body of the cookie placeholder in client requests as
recommended by the latest NTS draft.
2020-03-26 15:30:34 +01:00
Miroslav Lichvar
eedabb3d27 nts: disable TLS version 1.2
Require TLS version 1.3 or later as specified in the latest NTS draft.
2020-03-26 15:30:27 +01:00
Miroslav Lichvar
66dc2b6d6b nts: rework NTS-KE retry interval
Make the NTS-KE retry interval exponentially increasing, using a factor
provided by the NKE session. Use shorter intervals when the server is
refusing TCP connections or the connection is closed or timing out
before the TLS handshake.
2020-03-26 15:30:27 +01:00
Miroslav Lichvar
bcdbbbd694 nts: include server address in client NTS-KE log messages 2020-03-26 15:30:27 +01:00
Miroslav Lichvar
7b07e47c08 nts: fix address in server NTS-KE log messages
The server session instances are reused for different clients. Separate
the server name from the label used in log messages and set it on each
start of the session.
2020-03-26 15:30:27 +01:00
Miroslav Lichvar
a608496faf ntp: fix log message for replaced source
When a source was replaced and the new source had the same slot as the
old source, a wrong message was logged. Fix the condition to distinguish
correctly between changed address and port.

Fixes: 9468fd4aa6 ("ntp: allow changing port of source")
2020-03-26 15:26:58 +01:00
Miroslav Lichvar
c687224a11 reference: improve check for close leap second
Improve the check to work with the actual timestamp of the leap second
instead of the closest midnight and don't turn it off on the leap
timeout. Also allow sample times to be checked in addition to the system
time and NTP time to avoid accumulation of samples mixing pre-leap and
post-leap timestamps (causing error of +/-0.5 or +/-1.0 seconds).
2020-03-26 11:10:08 +01:00
Miroslav Lichvar
a6f2a613f3 socket: remove obsolete comment 2020-03-26 11:06:54 +01:00
Miroslav Lichvar
cfa39af345 socket: fix severity check in debug logging
Don't waste time formatting the debug message in log_message() when
debug output is disabled.

Fixes: 86a3ef9ed1 ("socket: add new socket support")
2020-03-26 11:06:54 +01:00
Miroslav Lichvar
8bab35c122 socket: increase maximum number of received messages
The buffers are no longer on stack. Increase their number for better
performance on heavily loaded servers.
2020-03-26 11:06:54 +01:00
Miroslav Lichvar
b20ef4cd7f socket: simplify receiving messages
Don't require the caller to provide a SCK_Message (on stack). Modify the
SCK_ReceiveMessage*() functions to return a pointer to static buffers,
as the message buffer which SCK_Message points to already is.
2020-03-26 11:04:18 +01:00
Miroslav Lichvar
b8b751a932 socket: enable port sharing on Linux
On Linux, enable the SO_REUSEPORT option on sockets bound to a port in
order to support load balancing with multiple chronyd instances
(configured to not adjust the system clock).

The IP_FREEBIND option already allowed different instances to bind to
the same address and port, but only one was actually receiving packets.

As the instances don't share their state, sharing the NTP port doesn't
work well with the interleaved mode, symmetric mode, and rate limiting.

Sharing the NTS-KE port will not work until the server keys can be
derived from a shared key.
2020-03-24 16:29:33 +01:00
Miroslav Lichvar
4a390841eb doc: fix typo in smoothtime description 2020-03-19 17:03:25 +01:00
David Bohman
f506f44033 sys_macosx: fix build issue on Sierra and presumably earlier 2020-03-18 12:23:26 +01:00
Miroslav Lichvar
1f8355f154 test: make 139-nts more reliable 2020-03-18 12:23:26 +01:00
Miroslav Lichvar
ddc2761498 doc: fix typo in NEWS 2020-03-16 18:34:26 +01:00
Miroslav Lichvar
8b50a8298a doc: update NEWS 2020-03-16 13:16:14 +01:00
Bryan Christianson
3eab329042 sys_macosx: don't require clock_gettime()
Earlier versions of macOS do not provide clock_gettime(). This patch
checks for clock_gettime() at run-time and falls back to gettimeofday()
if the symbol is not present.
2020-03-16 11:35:56 +01:00
Miroslav Lichvar
552d3b53b1 main: accept zero timeout
Allow -t to specify zero timeout to exit immediately. It might be
useful for testing.
2020-03-12 14:25:21 +01:00
Miroslav Lichvar
8afd62d954 reference: update synchronization status more frequently
Update the local clock errors with each update of the leap status to
avoid the kernel marking the clock as unsynchronized when a large
number of NTP samples is dropped.
2020-03-12 14:07:12 +01:00
Miroslav Lichvar
4883086fc1 sources: update reference leap status early
When a leap second status is updated by a source, don't wait for the
next source selection and full update of the reference. Count votes from
sources that passed the previous selection and update the reference leap
status directly.

This should allow leap seconds to spread quickly even when the
samples are dropped or delayed by the filters.
2020-03-12 14:07:12 +01:00
Miroslav Lichvar
2582be8754 sources: separate update of leap status
Remove leap status from the NTP sample and set it independently from
the sample accumulation in order to accept a leap second sooner when
samples are filtered.
2020-03-12 14:07:12 +01:00
Miroslav Lichvar
ff9301567e sourcestats: move leap status to sources 2020-03-12 12:09:50 +01:00
Miroslav Lichvar
e7a254265f cmdmon: add reset command
The reset command drops all measurements and switches the reference to
the unsynchronised state. This command can help chronyd with recovery
when the measurements are known to be no longer valid or accurate, e.g.
due to moving the computer to a different network, or resuming the
computer from a low-power state (which resets the system clock).
2020-03-12 12:09:50 +01:00
Miroslav Lichvar
d5311adafb doc: fix tag in chronyc man page 2020-03-12 12:09:50 +01:00
Miroslav Lichvar
18d7ea62b3 reference: don't report synchronized status after unknown step
The source handler resets SST instances on an unknown step, which
makes the sources unselectable, but SRC_SelectSource() doesn't call
REF_SetUnsynchronised() when no source is selectable.

Handle the step in the reference handler.

Fixes: 049eae661a ("sources: keep synchronized status with unreachable/unselectable sources")
2020-03-12 12:09:50 +01:00
Miroslav Lichvar
fb2849b230 reference: convert to monotonic time
Calculate the update interval and drift file age from increments in the
monotonic time instead of real time.
2020-03-12 12:09:50 +01:00
Miroslav Lichvar
fd59877692 nts: convert to monotonic time
Use the monotonic timestamp provided by the scheduler for NTS-KE rate
limiting and refresh.
2020-03-12 12:09:50 +01:00
Miroslav Lichvar
bb0553e4c4 sched: provide low-precision monotonic time
Measure the interval since the start in order to provide a monotonic
time for periodical tasks not using timers like driftfile updates, key
refresh, etc. Return the interval in the double format, but keep an
integer remainder limiting the precision to 0.01 second to avoid issues
with very small increments in a long-running process.
2020-03-12 12:09:49 +01:00
Miroslav Lichvar
46f954490d configure: improve nettle and gnutls check
Before enabling NTS, check for more gnutls functions (some added in
3.6.3) to avoid build failures with older gnutls versions. Also, make
sure that nettle supports the new AES interface (added in 3.0).
2020-03-10 15:52:28 +01:00
Miroslav Lichvar
31e6a50386 doc: add missing dependencies to installation document 2020-03-10 11:16:23 +01:00
Miroslav Lichvar
9a9c0d7b99 configure: improve pkg-config support 2020-03-10 11:16:23 +01:00
Miroslav Lichvar
0c80f00d0b doc: update description of on/offline commands 2020-03-09 13:05:02 +01:00
Miroslav Lichvar
27b3bf48ea ntp: ignore onoffline command for unresolved sources
The onoffline command switches an unresolved source to the offline
status, even when the network is already up.

Ignore the onoffline command for unresolved sources to prevent sources
unexpectedly staying in the offline status, e.g. when the command is
issued from a network dispatcher script (and no other call is expected
later when the name is resolved).
2020-03-09 13:02:23 +01:00
Miroslav Lichvar
c3e34b8145 doc: update installation document 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
7bf3ec4aeb doc: describe NTS directives and options 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
50204a125b test: add nts unit tests 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
111d170542 test: update compilation tests 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
d6dd6f0bc9 test: add 139-nts test 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
44aac84feb cmdmon: add NTS support
Allow the nts and ntsport options to be specified for sources added from
chronyc. This is an incompatible change in the request, but there was no
release using the new REQ_ADD_SOURCE command yet.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
c41508723f ntp: enable NTS support
Add an option to enable NTS for an NTP source. Check for NTS-specific
extension fields and pass the packets to the NTS-NTP code in order to
enable the NTS client and server.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
6043632f80 nts: add NTS-NTP server and client
Add support for the NTS NTP extension fields.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
a420ed57a1 nts: add NTS-KE server and client
Add a client and server implementing the Network Time Security (NTS) Key
Establishment. Use the GnuTLS library for TLS.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
72f99033fe test: add siv unit test 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
2d798bc4cf siv: add internal implementation based on Nettle
This adds an internal implementation of the AES-SIV-CMAC-256 cipher
based on GNU Nettle and the following patch (which was later reworked
and included in Nettle):

https://gitlab.com/gnutls/gnutls/uploads/1ab02c51e317264f9dba07ddfbc01d9a/0001-Added-support-for-AES_SIV_CMAC_256-and-AES_SIV_CMAC_.patch

This implementation will be dropped when the cipher is widely supported
by gnutls or Nettle.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
881d07fa0a siv: add support for Nettle 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
c5306bed39 siv: introduce API for SIV
Add a header file for Synthetic Initialization Vector (SIV) cipher mode,
which will be used by NTS.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
934b8712a5 sys_linux: allow getuid() in seccomp filter
This will be needed by gnutls when loading certificates.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
1d4690eb64 sys_linux: add syscall filter context for NTS-KE
The NTS-KE helper process will use a more restrictive filter than the
main process.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
e6848b1e3f sys: specify context for syscall filter
Specify a context to enable different processes using different (more
restrictive) syscall filters.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
3e537416a9 sched: remove slew handler in finalization
This allows repeated calls of SCH_Initialise() and SCH_Finalise().
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
26a1ed8bc3 sched: add function to remove all timers and descriptors
This allows a helper process to be started in an *_Initialise() call
and use the scheduler (unlike the privops helper, which has its own
loop).
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
24538fe3e9 nameserv: allow concurrent asynchronous requests
Allow multiple resolving threads to be running at the same time in order
to support multiple callers, but use a mutex to avoid sending multiple
requests to the privops helper. This will be needed for the NTS-KE
server negotiation.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
e43d699973 util: add functions for printing and parsing hexadecimal data 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
1e727c4497 sources: don't reset active status
Avoid resetting the active status when an NTP source changes its
address in NCR_ChangeRemoteAddress().

This will allow an NTP source to update its address with NTS-KE
hostname negotiation and continue in a special reference mode
(e.g. -q/-Q option).
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
83010590af ntp: move definition of invalid stratum to ntp.h 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
fa402a173a ntp: pass server name to ntp_core instances
The server name will be needed for certificate verification in NTS-KE.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
ca83d2a804 test: add ntp_ext unit test 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
725beb360a ntp: add functions for adding extension fields 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
86d29221f3 ntp: add function to change authentication-specific address
When an NTS source will be replaced, the authentication-specific address
of the NTS-KE server will need to be changed too.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
e8062b7ff1 ntp: add function to update source NTP address
This will allow a source to have its address changed due to NTS-KE
server negotiation, which allows the NTS-KE server to have a different
address than the NTP server.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
9468fd4aa6 ntp: allow changing port of source
Modify the replace_source() function to not require a different IP
address when replacing a source with the same address but different
port. This will enable the NTS-KE port negotiation.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
5ed9b888ff ntp: don't accept packets with unexpected authentication
If authentication is not enabled in configuration, responses are not
expected to be authenticated. Handle such responses as having failed
authentication.

A case where this could happen is a misconfigured symmetric association
where only one peer has specified the other with a key. Before this
change synchronization would work in one direction and used packets
with an asymmetric length.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
7661a6e95b ntp: don't allow long MACs in NTPv4 packets with extension fields
MAC longer than 24 octets in NTPv4 packet is supported only for
compatibility with some pre-RFC7822 chrony versions. They didn't use
any extension fields.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
aca1daf7c9 ntp: add support for sending KoD responses
Enable the server to respond with a KoD when authentication fails. This
will be used by NTS to respond with a NAK when a client has expired
cookies.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
46cac4e22f ntp: prefix NTP_AuthMode enums 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
56a102ed4d ntp: move auth parsing to ntp_auth
Move the remaining authentication-specific code to the new file.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
ca28dbd2c3 ntp: refactor authentication
Move most of the authentication-specific code to a new file and
introduce authenticator instances in order to support other
authentication mechanisms (e.g. NTS).
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
588785e160 ntp: rework packet parsing
Rework the code to detect the authentication mode and count extension
fields in the first parsing of the packet and store this information in
the new packet info structure.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
cabcccd6c3 ntp: add functions for parsing extension fields 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
567e66a0bb ntp: count packets with invalid format
Include packets that cannot be parsed in the total RX count.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
b8ee6d6e56 ntp: don't send response longer than request
When sending a response in the server or passive mode, make sure the
response is not longer than the request to prevent amplification
attacks when resposes may contain extension fields (e.g. NTS).
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
9ea1e4e40f ntp: provide access to request in transmit_packet()
This will allow new authentication code (e.g. NTS) to get data from the
request when generating a response.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
2d492eacb5 ntp: rename receive_packet() to process_response() 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
cb8660e79a ntp: add structure with packet info
Add a structure for length and other information about received and
transmitted NTP packets to minimize the number of parameters and avoid
repeated parsing of the packet.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
d29bef93e9 ntp: refactor NTP_Packet structure for extension fields 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
5a09adebfd ntp: don't replace sources with unroutable addresses
When changing an address of a source (both known and unknown), make sure
the new address is connectable. This should avoid useless replacements,
e.g. polling an IPv6 address on IPv4-only systems.
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
8c0ee9c175 doc: list unsupported options in peer directive 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
f20fabdbf4 test: make 132-logchange more reliable 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
57cea56e6e test: extend 001-features test 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
db7d9639b4 test: fix unit tests to build with -NTP and -CMDMON 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
beb40d63ed test: extend 122-xleave test 2020-03-05 16:02:15 +01:00
Miroslav Lichvar
672b98dd3f sources: don't save or load dumpfiles for unknown addresses
Don't open a dumpfile for reading or writing if the NTP source doesn't
have a real address.

Fixes: d7e3ad17ff ("ntp: create sources for unresolved addresses")
2020-03-05 16:02:15 +01:00
Miroslav Lichvar
a24d2713cd client: add option to print all sources
Add -a option to the sources and sourcestats commands to print all
sources, including those that don't have a resolved address yet. By
default, only sources that have a real address are printed for
compatibility. Remove the "210 Number of sources" messages to avoid
confusion. Also, modify the ntpdata command to always print only sources
with a resolved address.
2020-02-19 15:03:20 +01:00
Miroslav Lichvar
a5110d3ed9 client: add support for source identifiers
Allow the new identifiers to be specified as addresses in commands that
modify or remove NTP sources.
2020-02-19 15:03:20 +01:00
Miroslav Lichvar
9d1c1505b9 ntp: repeat resolving until all pool sources are resolved
When resolving of a pool name succeeds, don't remove the remaining
unresolved sources, i.e. try to get all maxsources (default 4) sources,
even if it takes multiple DNS requests.
2020-02-19 15:03:20 +01:00
Miroslav Lichvar
8c25632321 ntp: stop resolving if unresolved source is removed
If an individual unresolved source or all unresolved sources from a pool
are removed, stop resolving their addresses on the next attempt (don't
remove them immediately as the resolver may be running).
2020-02-19 15:03:20 +01:00
Miroslav Lichvar
2507b66640 ntp: update resolving timeout ID
This will allow unresolved sources to be removed before resolving.
2020-02-19 15:03:20 +01:00
Miroslav Lichvar
d7e3ad17ff ntp: create sources for unresolved addresses
Rework the ntp_sources code to create sources for addresses that are not
resolved yet using the new identifiers.
2020-02-19 14:52:03 +01:00
Miroslav Lichvar
84902d0e00 addressing: introduce identifiers for unresolved addresses
Add a new type of address for NTP sources that don't have a resolved
address yet. This will allow the sources to be displayed, modified and
deleted by chronyc.

Update utility functions to support the new addresses.
2020-02-19 11:37:15 +01:00
Miroslav Lichvar
f7f3667bcb addrfilt: explicitly handle unexpected addresses 2020-02-18 16:01:25 +01:00
Miroslav Lichvar
794cbfbbb5 logging: restrict file log permissions
With the new file utility functions permissions can be restricted for
newly created files. For the log file specified by the -l option it
is better to remove the "other" permissions (0640) to make it similar
to the system log.
2020-02-18 16:01:25 +01:00
Miroslav Lichvar
1858104b5c util: don't log unlink() error if file is not accessible
Try stat() before calling unlink() to make sure the file is accessible.

This fixes chronyc running under a non-root/chrony user printing an
error message due to missing permissions on /var/run/chrony before
trying to bind its socket.
2020-02-18 16:01:25 +01:00
Vincent Blut
994409a036 sys_linux: allow renameat2 in seccomp filter
This is needed for architectures that support neither rename() nor
renameat() (e.g. riscv64)
2020-01-20 12:26:54 +01:00
Miroslav Lichvar
2d9eb5b6fa test: fix util unit test for NTP era split
The current default NTP era split passed the Unix epoch (~50 years ago),
which means the epoch converted to an NTP timestamp and back ends up in
the next NTP era (year 2106).

Fix the test to take into account the era split.
2020-01-03 12:01:55 +01:00
Miroslav Lichvar
3477cbe28f stubs: add NSR_GetName()
Fixes: 93f6358916 ("cmdmon: add request to get source name")
2020-01-03 11:47:53 +01:00
Miroslav Lichvar
8634158de0 stubs: update NSR_AddSourceByName()
Fixes: 3763befd62 ("ntp: check name and return status from NSR_AddSourceByName()")
2020-01-03 11:47:53 +01:00
Miroslav Lichvar
3eaf0765b0 client: add missing commands to tab-completion
Reported-by: Lonnie Abelbeck <lonnie@abelbeck.com>
2020-01-03 11:47:53 +01:00
Miroslav Lichvar
dd0679ad45 socket: add function to check supported family
Don't log error when an IPv6 socket cannot be opened and chronyd was
built without IPv6 support.

Reported-by: Lonnie Abelbeck <lonnie@abelbeck.com>
2020-01-03 11:47:41 +01:00
Miroslav Lichvar
bfcd8ecc56 client: add sourcename command
Add a new command to print the original name of a source specified by
address. This could be useful in scripts to avoid having to run the
sources command with and without -N.
2019-12-12 16:17:45 +01:00
Miroslav Lichvar
50e5865c73 client: add option to print original names
Add -N option to chronyc to print the original names by which the
sources were specified instead of using reverse DNS lookup. The option
works in the sources, sourcestats and tracking commands.
2019-12-12 16:17:44 +01:00
Miroslav Lichvar
93f6358916 cmdmon: add request to get source name
Specify a new request to get the name of the NTP source corresponding to
an address, which was originally specified in the configuration file or
chronyc add command.
2019-12-12 16:06:58 +01:00
Miroslav Lichvar
9300854439 cmdmon: add support for adding pool sources
Specify a new type for pool sources and extend the syntax of the chronyc
"add" command to add a pool.
2019-12-12 14:44:03 +01:00
Miroslav Lichvar
02914ac637 cmdmon: specify name instead of address in add request
Modify the request for adding a source to provide the name of the source
instead of its address (resolved in chronyc) in order to enable chronyd
to replace the source, support an "add pool" command, and enable an NTS
client to verify the server's certificate.

The name resolving does not block the response. Success is indicated
even if the name cannot be resolved, or a source with the same address
is already present.

To prevent unresolvable names from getting to chronyd, chronyc does not
send the request if it could not resolve the name itself (assuming they
are both running on the same host using the same resolver).
2019-12-12 14:44:03 +01:00
Miroslav Lichvar
00fff161cf cmdmon: merge add server/peer requests
Instead of having two separate requests in the protocol for adding a
server and peer, specify the type of the new source in the request data.
2019-12-12 14:44:03 +01:00
Miroslav Lichvar
3763befd62 ntp: check name and return status from NSR_AddSourceByName()
Return an error status when the name is not printable or contains a
space (don't bother with full hostname validation). If the name is an
address, return the same status as NSR_AddSource(). Otherwise, return a
"not resolved yet" status.
2019-12-12 14:44:03 +01:00
Miroslav Lichvar
2ae008bcee ntp: print name of replaced source in log message 2019-12-12 14:44:03 +01:00
Miroslav Lichvar
ea41f7ab09 doc: remove unsupported options of add commands
All options from the configuration file are supported in the chronyc add
commands.

This fixes commit 65fd30a547.
2019-12-12 14:43:55 +01:00
Miroslav Lichvar
c673b1e8b7 privops: convert to new socket API 2019-12-12 13:03:31 +01:00
Miroslav Lichvar
2bf1ba22f2 socket: change SCK_Send() declaration to const buffer 2019-12-12 13:03:31 +01:00
Miroslav Lichvar
dfc2f70876 socket: add support for opening socket pairs 2019-12-12 13:03:31 +01:00
Miroslav Lichvar
0dba2b9689 socket: add support for blocking sockets
Add a flag to open a blocking socket. The default stays non-blocking.
2019-12-12 13:03:31 +01:00
Miroslav Lichvar
e7fc2d31cc socket: remove unnecessary MSG_DONTWAIT flag
This is not needed since sockets are non-blocking by default.
2019-12-12 13:03:31 +01:00
Miroslav Lichvar
f231efb811 socket: add support for sending and receiving descriptors
Add support for the SCM_RIGHTS control message used in communication
with the privops helper.
2019-12-12 13:03:31 +01:00
Christian Ehrhardt
c4d6f98bed test: accept test result if RTC can't enable RTC_UIE_ON
The test might run on different platforms. If the platform happens
to have a RTC that does exist but unable to have RTC_UIE_ON set the
test will fail, while the chrony code is actually good.

Examples of bad clocks are:
- ppc64el: rtc-generic
- arm64: rtc-efi

To avoid that extend the log message check on 101-rtc to accept
that condition as a valid test result as well.

Signed-off-by: Christian Ehrhardt <christian.ehrhardt@canonical.com>
2019-12-12 13:03:26 +01:00
Christian Ehrhardt
bff3f51d13 rtc: extend check for RTCs that don't support interrupts
Several RTCs would only expose the broken behavior on enabling
interrupts. The reason for that is that the kernel only returns the
error if the state changes. Therefore the check has to probe
switch_interrupts(1) as well.

On platforms that work it will be switched on and off, while on those it
never works it will just stay off.

Clocks known to expose that behavior include, but are not limited to:
PPC64# dmesg | grep -i rtc   
[    0.241872] rtc-generic rtc-generic: registered as rtc0
[    0.270221] rtc-generic rtc-generic: setting system clock to ...
ARM64# dmesg | grep -i rtc
[    0.876198] rtc-efi rtc-efi: registered as rtc0
[    1.046869] rtc-efi rtc-efi: setting system clock to ...

Signed-off-by: Christian Ehrhardt <christian.ehrhardt@canonical.com>
2019-12-12 12:50:19 +01:00
Miroslav Lichvar
f5eb7daf20 rtc: disable interrupts in finalization
Don't leave interrupts enabled if chronyd is stopped when making an RTC
measurement.
2019-12-10 17:45:28 +01:00
Miroslav Lichvar
d66b2f2b24 rtc: handle RTCs that don't support interrupts
Some RTCs supported by the Linux kernel don't support the RTC_UIE_ON/OFF
ioctls, which causes chronyd started with the -s option to get stuck in
the initial RTC mode.

After opening the RTC device in the initialization, return error if
the ioctls are not supported to prevent the upper layer from calling the
time_init() function and expecting it to finish.
2019-12-10 17:45:28 +01:00
Miroslav Lichvar
a57e1eb542 rtc: don't finalize driver if initialization failed 2019-12-10 17:45:20 +01:00
Miroslav Lichvar
25bdee7a0e rtc: simplify and move switch_interrupts() 2019-12-10 17:03:15 +01:00
Miroslav Lichvar
f6001202ec test: update log checks in system tests
Measurements are no longer accepted and clock updated when polling
itself.

This fixes commit 7a88e0a87b.
2019-12-10 15:56:48 +01:00
Miroslav Lichvar
0cf506c929 sys_linux: allow clock_adjtime in seccomp filter
The adjtimex() function in glibc was switched to the clock_adjtime
system call.
2019-12-02 18:06:25 +01:00
Miroslav Lichvar
d05e9fb2ec logging: enable line buffering on stderr
This should avoid mixed lines on console or in file log when multiple
processes will be logging messages at the same time.
2019-12-02 18:06:15 +01:00
Vincent Blut
54d7e3e94a doc: fix typo in chrony.conf man page 2019-11-28 16:41:45 +01:00
Miroslav Lichvar
c7223f4c8f logging: disable all debug messages in non-debug build
For consistency, don't print debug messages that are compiled in due to
using the LOG macro instead of DEBUG_LOG.
2019-11-19 14:59:21 +01:00
Miroslav Lichvar
07badbede7 client: don't print log messages with lower severity 2019-11-19 14:43:01 +01:00
Miroslav Lichvar
468cfeeb71 privops: keep stdin/out/err open 2019-11-19 14:43:01 +01:00
Miroslav Lichvar
b3fc549622 privops: remove debug message from PRV_Name2IPAddress()
The function may be called from a separate thread, but logging is not
considered thread safe (e.g. due to using functions which read
environment variables).
2019-11-19 14:43:01 +01:00
Miroslav Lichvar
077dbd5692 main: don't try to open unspecified pidfile 2019-11-19 14:34:51 +01:00
Miroslav Lichvar
e18903a6b5 switch to new util file functions
Replace all fopen(), rename(), and unlink() calls with the new util
functions.
2019-10-24 12:48:45 +02:00
Miroslav Lichvar
7dfd4ae556 test: extend util unit test 2019-10-24 12:48:45 +02:00
Miroslav Lichvar
429c4468b0 sys_linux: allow F_GETFL in seccomp filter
This is needed for fdopen().
2019-10-24 12:48:45 +02:00
Miroslav Lichvar
7a4c396bba util: add functions for common file operations
Add a function to open a file for reading, writing, or appending.
In uppercase modes errors are handled as fatal, i.e. the caller doesn't
need to check for NULL. To avoid string manipulations in the callers,
the function accepts an optional directory and suffix. New files are
created with specified permissions, which will be needed for saving
keys. The O_EXCL flag is used in the writing mode to make sure a new
file is created (on filesystems that support it).

Also, add a function to rename a temporary file by changing its suffix,
and a function to remove a file.

All functions log all errors, at least as debug messages.
2019-10-24 12:48:45 +02:00
Miroslav Lichvar
88f846f656 rtc: don't clone file attributes of rtc file
When replacing an existing rtc file with the temporary file, don't
change the ownership or permissions of the temporary file to match the
old rtc file, as if it didn't exist.
2019-10-24 11:03:47 +02:00
Miroslav Lichvar
27c8a64977 reference: don't clone file attributes of drift file
When replacing an existing drift file with the temporary file, don't
change the ownership or permissions of the temporary file to match the
old drift file, as if it didn't exist.
2019-10-24 11:03:47 +02:00
Miroslav Lichvar
2fc8edacb8 use PATH_MAX
Include <limits.h> and use the PATH_MAX macro to define the length of
buffers containing paths to make it constistent. (It's not supposed to
fit all possible paths.)
2019-10-24 11:03:47 +02:00
Miroslav Lichvar
903fa247f8 logging: include <syslog.h>
Move the inclusion of <syslog.h> from sysincl.h to logging.c to avoid
accidentally using the LOG_* constants from the header.
2019-10-24 11:03:47 +02:00
Miroslav Lichvar
96771d6857 logging: make banner printing safer
Don't rely on the buffer filled with '=' characters to be always at
least as long as the log-specific banner string.
2019-10-24 11:03:41 +02:00
Miroslav Lichvar
f4c6a00b2a logging: call exit() in LOG_Message()
Call exit() in LOG_Message() after printing a fatal message to allow the
LOG macro or LOG_Message() to be used directly instead of the LOG_FATAL
macro.
2019-10-10 18:05:00 +02:00
Miroslav Lichvar
990f8cd89b test: extend 110-chronyc test 2019-09-24 16:39:49 +02:00
Miroslav Lichvar
813ea71b50 test: extend 105-ntpauth test 2019-09-24 16:39:49 +02:00
Miroslav Lichvar
e8be384cdf test: extend keys unit test 2019-09-24 16:39:49 +02:00
Miroslav Lichvar
61773a2c07 test: add cmac unit test 2019-09-24 16:39:49 +02:00
Miroslav Lichvar
510aa8b050 client: add CMAC support to keygen command
Allow a CMAC cipher to be specified in the keygen command. Ignore the
specified length as the key length is determined by the cipher.
2019-09-24 16:39:01 +02:00
Miroslav Lichvar
57957ab6cf keys: add support for CMAC keys
Allow a cipher (AES128 or AES256) to be specified as the type of a key
in the key file to authenticate NTP packets with a CMAC instead of the
NTPv4 (RFC 5905) MAC using a hash function. This follows RFC 8573.
2019-09-24 16:38:12 +02:00
Miroslav Lichvar
e8069a0179 cmac: add support for Nettle
Add support for AES128 and AES256 CMAC in Nettle.
2019-09-24 14:04:44 +02:00
Miroslav Lichvar
f3f840551a cmac: add CMAC interface
Add cmac.h and stubs for cipher-based message authentication code
(CMAC).
2019-09-24 11:56:05 +02:00
Miroslav Lichvar
10a42c1e04 keys: don't fudge authentication delay
Remove the magic constant compensating for copying, conversions, etc.
It cannot possibly be accurate on all hardware. The delay is supposed to
be a minimum delay.
2019-09-24 11:35:51 +02:00
Miroslav Lichvar
4a219ecbf1 hash: drop support for RIPEMD hash functions
An analysis by Tim Ruffing [1] shows that a length extension attack
adding valid extension fields to NTPv4 packets is possible with some
specific key lengths and hash functions using little-endian length like
MD5 and RIPEMD160.

chronyd currently doesn't process or generate any extension fields, but
it could be a problem in future when a non-authentication extension
field is supported.

Drop support for all RIPEMD functions as they don't seem to be secure in
the context of the NTPv4 MAC. MD5 is kept only for compatibility.

[1] https://mailarchive.ietf.org/arch/msg/ntp/gvibuB6bTbDRBumfHNdJ84Kq4kA
2019-09-24 11:32:31 +02:00
Miroslav Lichvar
0d298bfc4c makefile: improve coding style 2019-09-19 17:30:28 +02:00
Miroslav Lichvar
792c241e3a makefile: refactor to support extra client-specific objects 2019-09-19 17:30:28 +02:00
Miroslav Lichvar
6336a87855 configure: move duplicated libraries to LIBS 2019-09-19 17:30:28 +02:00
Miroslav Lichvar
f5721b1212 configure: remove unused variables 2019-09-19 17:30:28 +02:00
Miroslav Lichvar
7d3e9180c6 test: disable server on client-only nodes by default 2019-09-12 14:51:12 +02:00
Miroslav Lichvar
03b8ca186a test: add 138-syncloop test 2019-09-12 14:51:12 +02:00
Miroslav Lichvar
435cbef31a test: allow nodes to poll themselves 2019-09-12 14:51:12 +02:00
Miroslav Lichvar
4adcf58368 test: remove subdirectories in tmp directory 2019-09-12 14:50:58 +02:00
Miroslav Lichvar
004986310d ntp: skip loop test if no server socket is open
If there is no socket that could receive a request from a client or
peer, we know that nothing can be synchronized to us and no loop is
possible.
2019-09-12 13:01:18 +02:00
Miroslav Lichvar
7a88e0a87b ntp: prevent synchronization to itself
Improve the client's test D to compare the stratum, reference ID,
reference timestamp, and root delay from the received packet with its
own reference data in order to prevent it from synchronizing to itself,
e.g. due to a misconfiguration.
2019-09-12 13:01:18 +02:00
Miroslav Lichvar
64e21d6281 reference: make local reference timestamp consistent
In the local reference mode, instead of returning the adjusted current
time as the reference time, return the same timestamp updated only once
per about 62.5 seconds.

This will enable chronyd to detect polling of itself even when the local
reference mode is active.
2019-09-12 13:01:06 +02:00
Miroslav Lichvar
9ef7ea2bcb reference: rework adjustment of reference timestamp
Instead of converting the reference timestamp to the NTP format and
back, add a negative double value to the timestamp directly. Move the
code to a separate function. This will allow the timestamp to stay
outside the compiled-in NTP era, which is useful for testing of the
cmdmon protocol.
2019-09-11 17:33:57 +02:00
Miroslav Lichvar
6d1796d6be test: extend 110-chronyc test 2019-09-03 13:17:44 +02:00
Miroslav Lichvar
fcaba98101 test: add 137-pool test 2019-09-03 12:41:01 +02:00
Miroslav Lichvar
9bbda5c964 test: add 013-nameserv test 2019-09-02 16:32:58 +02:00
Miroslav Lichvar
2c81d38861 test: add option to enable name/address resolving 2019-09-02 16:27:05 +02:00
Miroslav Lichvar
78fec3f05a test: add copyright header to util unit test 2019-08-27 17:11:49 +02:00
Miroslav Lichvar
392a1a5ff6 test: extend 105-ntpauth test 2019-08-27 17:11:49 +02:00
Miroslav Lichvar
219a414cb7 test: add debug message to ntp unit test 2019-08-27 17:11:49 +02:00
Miroslav Lichvar
58fc81441b ntp: update setting of socket option 2019-08-27 17:11:49 +02:00
Miroslav Lichvar
02ada36838 socket: add support for TCP sockets
TCP sockets will be needed for NTS key establishment.
2019-08-27 17:10:13 +02:00
Miroslav Lichvar
81978f0ba0 socket: fix typo in union declaration 2019-08-08 17:32:48 +02:00
Miroslav Lichvar
622769cdfd util: add debug messages to UTI_FdSetCloexec() 2019-08-08 17:32:48 +02:00
Miroslav Lichvar
3038047f9b makefile: clean unit tests in clean target 2019-08-06 16:11:07 +02:00
Miroslav Lichvar
3e3f045ab7 doc: improve ntpdate answer in FAQ 2019-08-06 16:11:07 +02:00
Miroslav Lichvar
a6d9f41eda sourcestats: report offset even with single sample 2019-08-06 16:11:07 +02:00
Miroslav Lichvar
bf6a4e1a81 sourcestats: simplify SST_DoSourcestatsReport() 2019-08-06 16:11:07 +02:00
Miroslav Lichvar
5982d96b75 test: extend 130-quit test 2019-08-06 16:11:07 +02:00
Miroslav Lichvar
28e3e4cdca sourcestats: enable selection with maxsamples < 3
Setting maxsamples to 1 or 2 prevented the source from being selected as
the regression would always fail. Handle this as a special case with
disabled frequency tracking in order to enable a fast reference update
with the -q/-Q option.
2019-08-06 16:11:07 +02:00
Miroslav Lichvar
24134c78e8 sourcestats: update offset estimate when regression fails
If there are too few samples to make a regression, at least update the
offset estimate from the last sample and keep the previous frequency
offset unchanged. Also, reset the error estimates.
2019-08-06 13:04:59 +02:00
Miroslav Lichvar
5e8ed72b89 socket: fix compiler warning
Don't define check_socket_flag() if no supported socket flag is defined.
2019-07-25 09:52:33 +02:00
Miroslav Lichvar
45e41b7ac1 socket: avoid unnecessary bind() call
Don't call bind() if the specified local address of a socket has port 0
and the "any" address. It will be bound automatically on connect() or
sendmsg().
2019-07-24 16:27:07 +02:00
Miroslav Lichvar
27fd751915 socket: add support for socket() flags
On start, check if the SOCK_CLOEXEC and SOCK_NONBLOCK flags are
supported in the socket() call and use them instead of fcntl() in order
to reduce the number of system calls required to send a client request.
2019-07-24 15:35:00 +02:00
Miroslav Lichvar
4d26cfc92b socket: make all sockets non-blocking
All networking code in chronyd (NTP server/client, signd client, cmdmon
server) assumes sending a message will not block, but only the signd
client actually checks for a write event and only the NTP server
requests a non-blocking socket. The cmdmon server and NTP client
(if using one socket for all servers) might be blocked.

chronyc doesn't need a non-blocking socket, but it is not expected to
block as it sends only one message at a time.

Prefer dropped messages over blocking in all cases. Remove the
SCK_FLAG_NONBLOCK flag and make all sockets non-blocking.
2019-07-24 10:21:14 +02:00
Miroslav Lichvar
d78680912e ntp: improve debug messages with port number 2019-07-18 17:29:44 +02:00
Miroslav Lichvar
47e4cb31b2 util: move and improve sockaddr-specific functions
Move the functions to socket.c and improve them to require and check the
sockaddr length.
2019-07-18 17:29:44 +02:00
Miroslav Lichvar
91da65a782 util: remove UTI_SockaddrToString()
It is no longer used after the conversions.
2019-07-18 17:29:44 +02:00
Miroslav Lichvar
bb1c02e9f5 client: convert to new socket API 2019-07-18 17:29:44 +02:00
Miroslav Lichvar
c651ea9b6b refclock: remove SOCK socket on exit 2019-07-18 17:29:44 +02:00
Miroslav Lichvar
207f9fb128 refclock: convert SOCK to new socket API 2019-07-18 17:29:44 +02:00
Miroslav Lichvar
f06c1cfa97 cmdmon: respond from same address
Enable the destination address of received messages in order to respond
from the same address on multihomed hosts.
2019-07-18 17:29:44 +02:00
Miroslav Lichvar
6cd47bff8f cmdmon: convert to new socket API 2019-07-18 17:29:44 +02:00
Miroslav Lichvar
2de24cfd82 ntp: convert to new socket API
Rework the NTP I/O code to use the new socket support. There are
differences in debug messages and handling of some errors.
2019-07-18 17:29:35 +02:00
Miroslav Lichvar
86a3ef9ed1 socket: add new socket support
Add a new file implementing support for opening sockets, sending and
receiving messages with control messages (e.g. addresses, timestamps),
and related operations, which should be simpler to use than the system
functions and allow their features to be reused between different parts
of the chrony code.

It is based on the ntp_io.c and ntp_io_linux.c files. It will be used by
the NTP client/server, cmdmon server, client, and others.
2019-07-18 16:54:48 +02:00
Miroslav Lichvar
3f8c57c8f2 util: add UTI_IPSockAddrToString()
This function prints an IPSockAddr. IPv6 addresses are printed in
brackets to separate the address from the port.
2019-07-18 13:37:52 +02:00
Miroslav Lichvar
ca96946416 addressing: introduce IPSockAddr
Rename NTP_Remote_Address to IPSockAddr to make it usable in non-NTP
context and provide NTP_Remote_Address for compatibility. Also, change
the type of port to uint16_t.
2019-07-18 13:37:52 +02:00
Miroslav Lichvar
e5b9b6d701 cmdmon: limit rate of all responses
Include responses to invalid requests in the rate limiting enabled by
the cmdratelimit directive.
2019-07-18 13:37:52 +02:00
Miroslav Lichvar
8cb689a5e6 cmdmon: don't require bound UDP socket
Don't abort on start when no UDP socket could be opened/bound for
cmdmon. The Unix socket is more important and with the IP_FREEBIND
option this case was not caught anyway.
2019-07-18 13:35:54 +02:00
Miroslav Lichvar
2270234115 privops: add assertion for bind address length 2019-07-16 13:46:37 +02:00
Miroslav Lichvar
a073f383e6 test: fix building of unit tests
This fixes commit 1227873b88.
2019-07-16 13:46:37 +02:00
Miroslav Lichvar
8e74655b03 doc: improve chronyd man page 2019-07-04 17:38:13 +02:00
Miroslav Lichvar
70fa3a6905 main: add option to specify minimum log severity level
The -L option can be used to disable logging of less severe messages,
e.g informational or warnings.
2019-07-04 17:38:13 +02:00
Miroslav Lichvar
1227873b88 logging: refactor enabling of debug messages
Reorder the LOGS_Severity enum in order of severity and change the code
to not log/print messages with severity below the specified minimum
instead of having a separate debug level.
2019-07-04 17:38:13 +02:00
Miroslav Lichvar
d30e73d0d9 nameserv: request SOCK_DGRAM socktype
Specify SOCK_DGRAM socktype instead of SOCK_STREAM in hints for
getaddrinfo() as chronyd is (and will mainly be) using the returned
addresses to open UDP sockets. This shouldn't make a difference in
practice, but it might avoid some confusion.
2019-06-26 17:21:47 +02:00
Miroslav Lichvar
9e7a7008de configure: fix warnings in tests
Fix some warnings in configure tests reported by clang and coverity
static analyzer.
2019-06-18 16:24:01 +02:00
Miroslav Lichvar
62d6aed6a6 test: update processing of packet log
Two new fields have been added to the packet log, which broke some
of the simulation tests.
2019-06-18 15:42:11 +02:00
125 changed files with 12354 additions and 2595 deletions

View File

@@ -21,44 +21,42 @@
#
# Makefile template
SYSCONFDIR=@SYSCONFDIR@
BINDIR=@BINDIR@
SBINDIR=@SBINDIR@
LOCALSTATEDIR=@LOCALSTATEDIR@
CHRONYVARDIR=@CHRONYVARDIR@
SYSCONFDIR = @SYSCONFDIR@
BINDIR = @BINDIR@
SBINDIR = @SBINDIR@
LOCALSTATEDIR = @LOCALSTATEDIR@
CHRONYVARDIR = @CHRONYVARDIR@
DESTDIR =
CC = @CC@
CFLAGS = @CFLAGS@
CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
DESTDIR=
HASH_OBJ = @HASH_OBJ@
EXTRA_OBJS = @EXTRA_OBJS@
OBJS = array.o cmdparse.o conf.o local.o logging.o main.o memory.o \
reference.o regress.o rtc.o samplefilt.o sched.o sources.o sourcestats.o stubs.o \
smooth.o sys.o sys_null.o tempcomp.o util.o $(HASH_OBJ)
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)
EXTRA_OBJS=@EXTRA_OBJECTS@
EXTRA_CLI_OBJS = @EXTRA_CLI_OBJS@
CLI_OBJS = array.o client.o cmdparse.o getdate.o memory.o nameserv.o \
pktlength.o util.o $(HASH_OBJ)
pktlength.o socket.o util.o $(EXTRA_CLI_OBJS)
ALL_OBJS = $(OBJS) $(EXTRA_OBJS) $(CLI_OBJS)
ALL_OBJS = $(OBJS) $(CLI_OBJS)
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
EXTRA_LIBS=@EXTRA_LIBS@
EXTRA_CLI_LIBS=@EXTRA_CLI_LIBS@
EXTRA_LIBS = @EXTRA_LIBS@
EXTRA_CLI_LIBS = @EXTRA_CLI_LIBS@
# Until we have a main procedure we can link, just build object files
# to test compilation
all : chronyd chronyc
chronyd : $(OBJS) $(EXTRA_OBJS)
$(CC) $(CFLAGS) -o chronyd $(OBJS) $(EXTRA_OBJS) $(LDFLAGS) $(LIBS) $(EXTRA_LIBS)
chronyd : $(OBJS)
$(CC) $(CFLAGS) -o chronyd $(OBJS) $(LDFLAGS) $(LIBS) $(EXTRA_LIBS)
chronyc : $(CLI_OBJS)
$(CC) $(CFLAGS) -o chronyc $(CLI_OBJS) $(LDFLAGS) $(LIBS) $(EXTRA_CLI_LIBS)
@@ -70,6 +68,7 @@ distclean : clean
-rm -f Makefile config.h config.log
clean :
$(MAKE) -C test/unit clean
-rm -f *.o *.s chronyc chronyd core.* *~
-rm -f *.gcda *.gcno
-rm -rf .deps
@@ -121,7 +120,7 @@ check : chronyd chronyc
cd test/system && ./run
print-chronyd-objects :
@echo $(OBJS) $(EXTRA_OBJS)
@echo $(OBJS)
Makefile : Makefile.in configure
@echo

28
NEWS
View File

@@ -1,3 +1,31 @@
New in version 4.0
==================
Enhancements
------------
* Add support for Network Time Security (NTS) authentication
* Add support for AES-CMAC keys (AES128, AES256) with Nettle
* Add support for maxsamples of 1 for faster update with -q/-Q option
* Add -L option to limit log messages by severity
* Avoid replacing NTP sources with sources that have unreachable address
* Improve pools to repeat name resolution to get "maxsources" sources
* Improve NTP loop test to prevent synchronisation to itself
* Update clock synchronisation status and leap status more frequently
* Update seccomp filter
* Add "add pool" command
* Add -N option and sourcename command to print original names of sources
* Add -a option to sources/sourcestats command to print unresolved sources
* Add reset command to drop all measurements
Bug fixes
---------
* Handle RTCs that don't support interrupts
* Respond to command requests with correct address on multihomed hosts
Removed features
----------------
* Drop support for RIPEMD keys (RMD128, RMD160, RMD256, RMD320)
New in version 3.5
==================

1
README
View File

@@ -112,6 +112,7 @@ Benny Lyne Amorsen <benny@amorsen.dk>
Andrew Bishop <amb@gedanken.demon.co.uk>
Vincent Blut <vincent.debian@free.fr>
Stephan I. Boettcher <stephan@nevis1.columbia.edu>
David Bohman <debohman@gmail.com>
Goswin Brederlow <brederlo@informatik.uni-tuebingen.de>
Leigh Brown <leigh@solinno.co.uk>
Erik Bryer <ebryer@spots.ab.ca>

View File

@@ -30,16 +30,19 @@
#include "sysincl.h"
/* This type is used to represent an IPv4 address or IPv6 address.
Addresses which are not resolved yet can be represented with an ID.
All parts are in HOST order, NOT network order. */
#define IPADDR_UNSPEC 0
#define IPADDR_INET4 1
#define IPADDR_INET6 2
#define IPADDR_ID 3
typedef struct {
union {
uint32_t in4;
uint8_t in6[16];
uint32_t id;
} addr;
uint16_t family;
uint16_t _pad;
@@ -47,8 +50,10 @@ typedef struct {
typedef struct {
IPAddr ip_addr;
unsigned short port;
} NTP_Remote_Address;
uint16_t port;
} IPSockAddr;
typedef IPSockAddr NTP_Remote_Address;
#define INVALID_IF_INDEX -1

View File

@@ -247,6 +247,8 @@ set_subnet_(ADF_AuthTable table,
set_subnet(&table->base6, ip6, 4, 0, new_state, delete_children) == ADF_SUCCESS)
return ADF_SUCCESS;
break;
default:
break;
}
return ADF_BADSUBNET;
@@ -359,9 +361,9 @@ ADF_IsAllowed(ADF_AuthTable table,
case IPADDR_INET6:
split_ip6(ip_addr, ip6);
return check_ip_in_node(&table->base6, ip6);
default:
return 0;
}
return 0;
}
/* ================================================== */

31
candm.h
View File

@@ -101,7 +101,10 @@
#define REQ_ADD_PEER3 61
#define REQ_SHUTDOWN 62
#define REQ_ONOFFLINE 63
#define N_REQUEST_TYPES 64
#define REQ_ADD_SOURCE 64
#define REQ_NTP_SOURCE_NAME 65
#define REQ_RESET 66
#define N_REQUEST_TYPES 67
/* Structure used to exchange timespecs independent of time_t size */
typedef struct {
@@ -245,6 +248,11 @@ typedef struct {
int32_t EOR;
} REQ_Ac_Check;
/* Source types in NTP source requests */
#define REQ_ADDSRC_SERVER 1
#define REQ_ADDSRC_PEER 2
#define REQ_ADDSRC_POOL 3
/* Flags used in NTP source requests */
#define REQ_ADDSRC_ONLINE 0x1
#define REQ_ADDSRC_AUTOOFFLINE 0x2
@@ -255,9 +263,11 @@ typedef struct {
#define REQ_ADDSRC_REQUIRE 0x40
#define REQ_ADDSRC_INTERLEAVED 0x80
#define REQ_ADDSRC_BURST 0x100
#define REQ_ADDSRC_NTS 0x200
typedef struct {
IPAddr ip_addr;
uint32_t type;
int8_t name[256];
uint32_t port;
int32_t minpoll;
int32_t maxpoll;
@@ -269,6 +279,7 @@ typedef struct {
int32_t min_samples;
int32_t max_samples;
uint32_t authkey;
uint32_t nts_port;
Float max_delay;
Float max_delay_ratio;
Float max_delay_dev_ratio;
@@ -335,6 +346,11 @@ typedef struct {
int32_t EOR;
} REQ_NTPData;
typedef struct {
IPAddr ip_addr;
int32_t EOR;
} REQ_NTPSourceName;
/* ================================================== */
#define PKT_TYPE_CMD_REQUEST 1
@@ -437,6 +453,7 @@ typedef struct {
REQ_ReselectDistance reselect_distance;
REQ_SmoothTime smoothtime;
REQ_NTPData ntp_data;
REQ_NTPData ntp_source_name;
} data; /* Command specific parameters */
/* Padding used to prevent traffic amplification. It only defines the
@@ -473,7 +490,8 @@ typedef struct {
#define RPY_NTP_DATA 16
#define RPY_MANUAL_TIMESTAMP2 17
#define RPY_MANUAL_LIST2 18
#define N_REPLY_TYPES 19
#define RPY_NTP_SOURCE_NAME 19
#define N_REPLY_TYPES 20
/* Status codes */
#define STT_SUCCESS 0
@@ -497,6 +515,7 @@ typedef struct {
#define STT_INVALIDAF 17
#define STT_BADPKTVERSION 18
#define STT_BADPKTLENGTH 19
#define STT_INVALIDNAME 21
typedef struct {
int32_t EOR;
@@ -688,6 +707,11 @@ typedef struct {
int32_t EOR;
} RPY_NTPData;
typedef struct {
int8_t name[256];
int32_t EOR;
} RPY_NTPSourceName;
typedef struct {
uint8_t version;
uint8_t pkt_type;
@@ -717,6 +741,7 @@ typedef struct {
RPY_Activity activity;
RPY_Smoothing smoothing;
RPY_NTPData ntp_data;
RPY_NTPSourceName ntp_source_name;
} data; /* Reply specific parameters */
} CMD_Reply;

448
client.c
View File

@@ -33,12 +33,14 @@
#include "array.h"
#include "candm.h"
#include "cmac.h"
#include "logging.h"
#include "memory.h"
#include "nameserv.h"
#include "getdate.h"
#include "cmdparse.h"
#include "pktlength.h"
#include "socket.h"
#include "util.h"
#ifdef FEAT_READLINE
@@ -52,16 +54,15 @@
/* ================================================== */
union sockaddr_all {
struct sockaddr_in in4;
#ifdef FEAT_IPV6
struct sockaddr_in6 in6;
#endif
struct sockaddr_un un;
struct sockaddr sa;
struct Address {
SCK_AddressType type;
union {
IPSockAddr ip;
char *path;
} addr;
};
static ARR_Instance sockaddrs;
static ARR_Instance server_addresses;
static int sock_fd = -1;
@@ -71,13 +72,15 @@ static int on_terminal = 0;
static int no_dns = 0;
static int source_names = 0;
static int csv_mode = 0;
/* ================================================== */
/* Log a message. This is a minimalistic replacement of the logging.c
implementation to avoid linking with it and other modules. */
int log_debug_enabled = 0;
LOG_Severity log_min_severity = LOGS_INFO;
void LOG_Message(LOG_Severity severity,
#if DEBUG > 0
@@ -87,6 +90,9 @@ void LOG_Message(LOG_Severity severity,
{
va_list ap;
if (severity < log_min_severity)
return;
va_start(ap, format);
vfprintf(stderr, format, ap);
putc('\n', stderr);
@@ -145,15 +151,15 @@ read_line(void)
/* ================================================== */
static ARR_Instance
get_sockaddrs(const char *hostnames, int port)
get_addresses(const char *hostnames, int port)
{
struct Address *addr;
ARR_Instance addrs;
char *hostname, *s1, *s2;
IPAddr ip_addrs[DNS_MAX_ADDRESSES];
union sockaddr_all *addr;
int i;
addrs = ARR_CreateInstance(sizeof (union sockaddr_all));
addrs = ARR_CreateInstance(sizeof (*addr));
s1 = Strdup(hostnames);
/* Parse the comma-separated list of hostnames */
@@ -164,11 +170,9 @@ get_sockaddrs(const char *hostnames, int port)
/* hostname starting with / is considered a path of Unix domain socket */
if (hostname[0] == '/') {
addr = (union sockaddr_all *)ARR_GetNewElement(addrs);
if (snprintf(addr->un.sun_path, sizeof (addr->un.sun_path), "%s", hostname) >=
sizeof (addr->un.sun_path))
LOG_FATAL("Unix socket path too long");
addr->un.sun_family = AF_UNIX;
addr = ARR_GetNewElement(addrs);
addr->type = SCK_ADDR_UNIX;
addr->addr.path = Strdup(hostname);
} else {
if (DNS_Name2IPAddress(hostname, ip_addrs, DNS_MAX_ADDRESSES) != DNS_Success) {
DEBUG_LOG("Could not get IP address for %s", hostname);
@@ -176,8 +180,10 @@ get_sockaddrs(const char *hostnames, int port)
}
for (i = 0; i < DNS_MAX_ADDRESSES && ip_addrs[i].family != IPADDR_UNSPEC; i++) {
addr = (union sockaddr_all *)ARR_GetNewElement(addrs);
UTI_IPAndPortToSockaddr(&ip_addrs[i], port, (struct sockaddr *)addr);
addr = ARR_GetNewElement(addrs);
addr->type = SCK_ADDR_IP;
addr->addr.ip.ip_addr = ip_addrs[i];
addr->addr.ip.port = port;
DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&ip_addrs[i]));
}
}
@@ -187,70 +193,60 @@ get_sockaddrs(const char *hostnames, int port)
return addrs;
}
/* ================================================== */
static void
free_addresses(ARR_Instance addresses)
{
struct Address *addr;
unsigned int i;
for (i = 0; i < ARR_GetSize(addresses); i++) {
addr = ARR_GetElement(addresses, i);
if (addr->type == SCK_ADDR_UNIX)
Free(addr->addr.path);
}
ARR_DestroyInstance(addresses);
}
/* ================================================== */
/* Initialise the socket used to talk to the daemon */
static int
prepare_socket(union sockaddr_all *addr)
open_socket(struct Address *addr)
{
socklen_t addr_len;
char *dir;
char *dir, *local_addr;
size_t local_addr_len;
switch (addr->sa.sa_family) {
case AF_UNIX:
addr_len = sizeof (addr->un);
switch (addr->type) {
case SCK_ADDR_IP:
sock_fd = SCK_OpenUdpSocket(&addr->addr.ip, NULL, 0);
break;
case AF_INET:
addr_len = sizeof (addr->in4);
case SCK_ADDR_UNIX:
/* Construct path of our socket. Use the same directory as the server
socket and include our process ID to allow multiple chronyc instances
running at the same time. */
dir = UTI_PathToDir(addr->addr.path);
local_addr_len = strlen(dir) + 50;
local_addr = Malloc(local_addr_len);
snprintf(local_addr, local_addr_len, "%s/chronyc.%d.sock", dir, (int)getpid());
sock_fd = SCK_OpenUnixDatagramSocket(addr->addr.path, local_addr,
SCK_FLAG_ALL_PERMISSIONS);
Free(dir);
Free(local_addr);
break;
#ifdef FEAT_IPV6
case AF_INET6:
addr_len = sizeof (addr->in6);
break;
#endif
default:
assert(0);
}
sock_fd = socket(addr->sa.sa_family, SOCK_DGRAM, 0);
if (sock_fd < 0) {
DEBUG_LOG("Could not create socket : %s", strerror(errno));
if (sock_fd < 0)
return 0;
}
if (addr->sa.sa_family == AF_UNIX) {
struct sockaddr_un sa_un;
/* Construct path of our socket. Use the same directory as the server
socket and include our process ID to allow multiple chronyc instances
running at the same time. */
dir = UTI_PathToDir(addr->un.sun_path);
if (snprintf(sa_un.sun_path, sizeof (sa_un.sun_path),
"%s/chronyc.%d.sock", dir, (int)getpid()) >= sizeof (sa_un.sun_path))
LOG_FATAL("Unix socket path too long");
Free(dir);
sa_un.sun_family = AF_UNIX;
unlink(sa_un.sun_path);
/* Bind the socket to the path */
if (bind(sock_fd, (struct sockaddr *)&sa_un, sizeof (sa_un)) < 0) {
DEBUG_LOG("Could not bind socket : %s", strerror(errno));
return 0;
}
/* Allow server without root privileges to send replies to our socket */
if (chmod(sa_un.sun_path, 0666) < 0) {
DEBUG_LOG("Could not change socket permissions : %s", strerror(errno));
return 0;
}
}
if (connect(sock_fd, &addr->sa, addr_len) < 0) {
DEBUG_LOG("Could not connect socket : %s", strerror(errno));
return 0;
}
return 1;
}
@@ -260,20 +256,11 @@ prepare_socket(union sockaddr_all *addr)
static void
close_io(void)
{
union sockaddr_all addr;
socklen_t addr_len = sizeof (addr);
if (sock_fd < 0)
return;
/* Remove our Unix domain socket */
if (getsockname(sock_fd, &addr.sa, &addr_len) < 0)
LOG_FATAL("getsockname() failed : %s", strerror(errno));
if (addr_len <= sizeof (addr) && addr_len > sizeof (addr.sa.sa_family) &&
addr.sa.sa_family == AF_UNIX)
unlink(addr.un.sun_path);
close(sock_fd);
SCK_RemoveSocket(sock_fd);
SCK_CloseSocket(sock_fd);
sock_fd = -1;
}
@@ -283,7 +270,7 @@ static int
open_io(void)
{
static unsigned int address_index = 0;
union sockaddr_all *addr;
struct Address *addr;
/* If a socket is already opened, close it and try the next address */
if (sock_fd >= 0) {
@@ -292,11 +279,10 @@ open_io(void)
}
/* Find an address for which a socket can be opened and connected */
for (; address_index < ARR_GetSize(sockaddrs); address_index++) {
addr = (union sockaddr_all *)ARR_GetElement(sockaddrs, address_index);
DEBUG_LOG("Opening connection to %s", UTI_SockaddrToString(&addr->sa));
for (; address_index < ARR_GetSize(server_addresses); address_index++) {
addr = ARR_GetElement(server_addresses, address_index);
if (prepare_socket(addr))
if (open_socket(addr))
return 1;
close_io();
@@ -334,6 +320,9 @@ bits_to_mask(int bits, int family, IPAddr *mask)
for (; i < 16; i++)
mask->addr.in6[i] = 0x0;
break;
case IPADDR_ID:
mask->family = IPADDR_UNSPEC;
break;
default:
assert(0);
}
@@ -341,6 +330,20 @@ bits_to_mask(int bits, int family, IPAddr *mask)
/* ================================================== */
static int
parse_source_address(char *word, IPAddr *address)
{
if (UTI_StringToIdIP(word, address))
return 1;
if (DNS_Name2IPAddress(word, address, 1) == DNS_Success)
return 1;
return 0;
}
/* ================================================== */
static int
read_mask_address(char *line, IPAddr *mask, IPAddr *address)
{
@@ -367,7 +370,7 @@ read_mask_address(char *line, IPAddr *mask, IPAddr *address)
}
}
} else {
if (DNS_Name2IPAddress(p, address, 1) == DNS_Success) {
if (parse_source_address(p, address)) {
bits_to_mask(-1, address->family, mask);
return 1;
} else {
@@ -447,7 +450,7 @@ read_address_integer(char *line, IPAddr *address, int *value)
LOG(LOGS_ERR, "Invalid syntax for address value");
ok = 0;
} else {
if (DNS_Name2IPAddress(hostname, address, 1) != DNS_Success) {
if (!parse_source_address(hostname, address)) {
LOG(LOGS_ERR, "Could not get address for hostname");
ok = 0;
} else {
@@ -475,7 +478,7 @@ read_address_double(char *line, IPAddr *address, double *value)
LOG(LOGS_ERR, "Invalid syntax for address value");
ok = 0;
} else {
if (DNS_Name2IPAddress(hostname, address, 1) != DNS_Success) {
if (!parse_source_address(hostname, address)) {
LOG(LOGS_ERR, "Could not get address for hostname");
ok = 0;
} else {
@@ -1069,20 +1072,39 @@ process_cmd_doffset(CMD_Request *msg, char *line)
/* ================================================== */
static int
process_cmd_add_server_or_peer(CMD_Request *msg, char *line)
process_cmd_add_source(CMD_Request *msg, char *line)
{
CPS_NTP_Source data;
IPAddr ip_addr;
int result = 0, status;
const char *opt_name;
int result = 0, status, type;
const char *opt_name, *word;
msg->command = htons(REQ_ADD_SOURCE);
word = line;
line = CPS_SplitWord(line);
if (!strcmp(word, "server")) {
type = REQ_ADDSRC_SERVER;
} else if (!strcmp(word, "peer")) {
type = REQ_ADDSRC_PEER;
} else if (!strcmp(word, "pool")) {
type = REQ_ADDSRC_POOL;
} else {
LOG(LOGS_ERR, "Invalid syntax for add command");
return 0;
}
status = CPS_ParseNTPSourceAdd(line, &data);
switch (status) {
case 0:
LOG(LOGS_ERR, "Invalid syntax for add command");
break;
default:
if (DNS_Name2IPAddress(data.name, &ip_addr, 1) != DNS_Success) {
/* Verify that the address is resolvable (chronyc and chronyd are
assumed to be running on the same host) */
if (strlen(data.name) >= sizeof (msg->data.ntp_source.name) ||
DNS_Name2IPAddress(data.name, &ip_addr, 1) != DNS_Success) {
LOG(LOGS_ERR, "Invalid host/IP address");
break;
}
@@ -1093,8 +1115,12 @@ process_cmd_add_server_or_peer(CMD_Request *msg, char *line)
break;
}
msg->data.ntp_source.type = htonl(type);
if (strlen(data.name) >= sizeof (msg->data.ntp_source.name))
assert(0);
strncpy((char *)msg->data.ntp_source.name, data.name,
sizeof (msg->data.ntp_source.name));
msg->data.ntp_source.port = htonl((unsigned long) data.port);
UTI_IPHostToNetwork(&ip_addr, &msg->data.ntp_source.ip_addr);
msg->data.ntp_source.minpoll = htonl(data.params.minpoll);
msg->data.ntp_source.maxpoll = htonl(data.params.maxpoll);
msg->data.ntp_source.presend_minpoll = htonl(data.params.presend_minpoll);
@@ -1105,6 +1131,7 @@ process_cmd_add_server_or_peer(CMD_Request *msg, char *line)
msg->data.ntp_source.min_samples = htonl(data.params.min_samples);
msg->data.ntp_source.max_samples = htonl(data.params.max_samples);
msg->data.ntp_source.authkey = htonl(data.params.authkey);
msg->data.ntp_source.nts_port = htonl(data.params.nts_port);
msg->data.ntp_source.max_delay = UTI_FloatHostToNetwork(data.params.max_delay);
msg->data.ntp_source.max_delay_ratio = UTI_FloatHostToNetwork(data.params.max_delay_ratio);
msg->data.ntp_source.max_delay_dev_ratio =
@@ -1118,6 +1145,7 @@ process_cmd_add_server_or_peer(CMD_Request *msg, char *line)
(data.params.iburst ? REQ_ADDSRC_IBURST : 0) |
(data.params.interleaved ? REQ_ADDSRC_INTERLEAVED : 0) |
(data.params.burst ? REQ_ADDSRC_BURST : 0) |
(data.params.nts ? REQ_ADDSRC_NTS : 0) |
(data.params.sel_options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) |
(data.params.sel_options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) |
(data.params.sel_options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) |
@@ -1135,24 +1163,6 @@ process_cmd_add_server_or_peer(CMD_Request *msg, char *line)
/* ================================================== */
static int
process_cmd_add_server(CMD_Request *msg, char *line)
{
msg->command = htons(REQ_ADD_SERVER3);
return process_cmd_add_server_or_peer(msg, line);
}
/* ================================================== */
static int
process_cmd_add_peer(CMD_Request *msg, char *line)
{
msg->command = htons(REQ_ADD_PEER3);
return process_cmd_add_server_or_peer(msg, line);
}
/* ================================================== */
static int
process_cmd_delete(CMD_Request *msg, char *line)
{
@@ -1168,7 +1178,7 @@ process_cmd_delete(CMD_Request *msg, char *line)
LOG(LOGS_ERR, "Invalid syntax for address");
ok = 0;
} else {
if (DNS_Name2IPAddress(hostname, &address, 1) != DNS_Success) {
if (!parse_source_address(hostname, &address)) {
LOG(LOGS_ERR, "Could not get address for hostname");
ok = 0;
} else {
@@ -1197,16 +1207,17 @@ give_help(void)
"Wait until synchronised in specified limits\0"
"\0\0"
"Time sources:\0\0"
"sources [-v]\0Display information about current sources\0"
"sourcestats [-v]\0Display statistics about collected measurements\0"
"sources [-a] [-v]\0Display information about current sources\0"
"sourcestats [-a] [-v]\0Display statistics about collected measurements\0"
"reselect\0Force reselecting synchronisation source\0"
"reselectdist <dist>\0Modify reselection distance\0"
"\0\0"
"NTP sources:\0\0"
"activity\0Check how many NTP sources are online/offline\0"
"ntpdata [<address>]\0Display information about last valid measurement\0"
"add server <address> [options]\0Add new NTP server\0"
"add peer <address> [options]\0Add new NTP peer\0"
"add server <name> [options]\0Add new NTP server\0"
"add pool <name> [options]\0Add new pool of NTP servers\0"
"add peer <name> [options]\0Add new NTP peer\0"
"delete <address>\0Remove server or peer\0"
"burst <n-good>/<n-max> [<mask>/<address>]\0Start rapid set of measurements\0"
"maxdelay <address> <delay>\0Modify maximum valid sample delay\0"
@@ -1221,6 +1232,7 @@ give_help(void)
"\0according to network configuration\0"
"polltarget <address> <target>\0Modify poll target\0"
"refresh\0Refresh IP addresses\0"
"sourcename <address>\0Display original name\0"
"\0\0"
"Manual time input:\0\0"
"manual off|on|reset\0Disable/enable/reset settime command\0"
@@ -1255,8 +1267,9 @@ give_help(void)
"\0\0"
"Other daemon commands:\0\0"
"cyclelogs\0Close and re-open log files\0"
"dump\0Dump all measurements to save files\0"
"rekey\0Re-read keys from key file\0"
"dump\0Dump measurements and NTS keys/cookies\0"
"rekey\0Re-read keys\0"
"reset\0Drop all measurements\0"
"shutdown\0Stop daemon\0"
"\0\0"
"Client commands:\0\0"
@@ -1303,16 +1316,16 @@ command_name_generator(const char *text, int state)
"deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep",
"manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
"maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline",
"polltarget", "quit", "refresh", "rekey", "reselect", "reselectdist",
"polltarget", "quit", "refresh", "rekey", "reselect", "reselectdist", "reset",
"retries", "rtcdata", "serverstats", "settime", "shutdown", "smoothing",
"smoothtime", "sources", "sourcestats",
"smoothtime", "sourcename", "sources", "sourcestats",
"timeout", "tracking", "trimrtc", "waitsync", "writertc",
NULL
};
const char *add_options[] = { "peer", "server", NULL };
const char *add_options[] = { "peer", "pool", "server", NULL };
const char *manual_options[] = { "on", "off", "delete", "list", "reset", NULL };
const char *sources_options[] = { "-v", NULL };
const char *sourcestats_options[] = { "-v", NULL };
const char *sources_options[] = { "-a", "-v", NULL };
const char *sourcestats_options[] = { "-a", "-v", NULL };
static int list_index, len;
names[TAB_COMPLETE_BASE_CMDS] = base_commands;
@@ -1426,12 +1439,8 @@ submit_request(CMD_Request *request, CMD_Reply *reply)
return 0;
}
if (send(sock_fd, (void *)request, command_length, 0) < 0) {
DEBUG_LOG("Could not send %d bytes : %s", command_length, strerror(errno));
if (SCK_Send(sock_fd, (void *)request, command_length, 0) < 0)
return 0;
}
DEBUG_LOG("Sent %d bytes", command_length);
}
UTI_TimevalToTimespec(&tv, &ts_now);
@@ -1467,16 +1476,11 @@ submit_request(CMD_Request *request, CMD_Reply *reply)
/* Timeout must have elapsed, try a resend? */
new_attempt = 1;
} else {
recv_status = recv(sock_fd, (void *)reply, sizeof(CMD_Reply), 0);
recv_status = SCK_Receive(sock_fd, reply, sizeof (*reply), 0);
if (recv_status < 0) {
/* If we get connrefused here, it suggests the sendto is
going to a dead port */
DEBUG_LOG("Could not receive : %s", strerror(errno));
new_attempt = 1;
} else {
DEBUG_LOG("Received %d bytes", recv_status);
read_length = recv_status;
/* Check if the header is valid */
@@ -1607,6 +1611,9 @@ request_reply(CMD_Request *request, CMD_Reply *reply, int requested_reply, int v
case STT_INACTIVE:
printf("519 Client logging is not active in the daemon");
break;
case STT_INVALIDNAME:
printf("521 Invalid name");
break;
default:
printf("520 Got unexpected error from daemon");
}
@@ -2038,12 +2045,40 @@ print_info_field(const char *format, ...)
/* ================================================== */
static int
get_source_name(IPAddr *ip_addr, char *buf, int size)
{
CMD_Request request;
CMD_Reply reply;
int i;
request.command = htons(REQ_NTP_SOURCE_NAME);
UTI_IPHostToNetwork(ip_addr, &request.data.ntp_source_name.ip_addr);
if (!request_reply(&request, &reply, RPY_NTP_SOURCE_NAME, 0) ||
reply.data.ntp_source_name.name[sizeof (reply.data.ntp_source_name.name) - 1] != '\0' ||
snprintf(buf, size, "%s", reply.data.ntp_source_name.name) >= size)
return 0;
/* Make sure the name is printable */
for (i = 0; i < size && buf[i] != '\0'; i++) {
if (!isgraph(buf[i]))
return 0;
}
return 1;
}
/* ================================================== */
static void
format_name(char *buf, int size, int trunc_dns, int ref, uint32_t ref_id,
IPAddr *ip_addr)
int source, IPAddr *ip_addr)
{
if (ref) {
snprintf(buf, size, "%s", UTI_RefidToString(ref_id));
} else if (source && source_names) {
if (!get_source_name(ip_addr, buf, size))
snprintf(buf, size, "?");
} else if (no_dns || csv_mode) {
snprintf(buf, size, "%s", UTI_IPToString(ip_addr));
} else {
@@ -2057,12 +2092,42 @@ format_name(char *buf, int size, int trunc_dns, int ref, uint32_t ref_id,
/* ================================================== */
static int
check_for_verbose_flag(char *line)
static void
parse_sources_options(char *line, int *all, int *verbose)
{
if (!csv_mode && !strcmp(line, "-v"))
return 1;
return 0;
char *opt;
*all = *verbose = 0;
while (*line) {
opt = line;
line = CPS_SplitWord(line);
if (!strcmp(opt, "-a"))
*all = 1;
else if (!strcmp(opt, "-v"))
*verbose = !csv_mode;
}
}
/* ================================================== */
static int
process_cmd_sourcename(char *line)
{
IPAddr ip_addr;
char name[256];
if (!UTI_StringToIP(line, &ip_addr)) {
LOG(LOGS_ERR, "Could not read address");
return 0;
}
if (!get_source_name(&ip_addr, name, sizeof (name)))
return 0;
print_report("%s\n", name, REPORT_END);
return 1;
}
/* ================================================== */
@@ -2074,18 +2139,16 @@ process_cmd_sources(char *line)
CMD_Reply reply;
IPAddr ip_addr;
uint32_t i, mode, n_sources;
char name[50], mode_ch, state_ch;
int verbose;
char name[256], mode_ch, state_ch;
int all, verbose;
/* Check whether to output verbose headers */
verbose = check_for_verbose_flag(line);
parse_sources_options(line, &all, &verbose);
request.command = htons(REQ_N_SOURCES);
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
return 0;
n_sources = ntohl(reply.data.n_sources.n_sources);
print_info_field("210 Number of sources = %lu\n", (unsigned long)n_sources);
if (verbose) {
printf("\n");
@@ -2111,9 +2174,12 @@ process_cmd_sources(char *line)
mode = ntohs(reply.data.source_data.mode);
UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
if (!all && ip_addr.family == IPADDR_ID)
continue;
format_name(name, sizeof (name), 25,
mode == RPY_SD_MD_REF && ip_addr.family == IPADDR_INET4,
ip_addr.addr.in4, &ip_addr);
ip_addr.addr.in4, 1, &ip_addr);
switch (mode) {
case RPY_SD_MD_CLIENT:
@@ -2180,18 +2246,17 @@ process_cmd_sourcestats(char *line)
CMD_Request request;
CMD_Reply reply;
uint32_t i, n_sources;
int verbose = 0;
char name[50];
int all, verbose;
char name[256];
IPAddr ip_addr;
verbose = check_for_verbose_flag(line);
parse_sources_options(line, &all, &verbose);
request.command = htons(REQ_N_SOURCES);
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
return 0;
n_sources = ntohl(reply.data.n_sources.n_sources);
print_info_field("210 Number of sources = %lu\n", (unsigned long)n_sources);
if (verbose) {
printf(" .- Number of sample points in measurement set.\n");
@@ -2216,8 +2281,11 @@ process_cmd_sourcestats(char *line)
return 0;
UTI_IPNetworkToHost(&reply.data.sourcestats.ip_addr, &ip_addr);
if (!all && ip_addr.family == IPADDR_ID)
continue;
format_name(name, sizeof (name), 25, ip_addr.family == IPADDR_UNSPEC,
ntohl(reply.data.sourcestats.ref_id), &ip_addr);
ntohl(reply.data.sourcestats.ref_id), 1, &ip_addr);
print_report("%-25s %3U %3U %I %+P %P %+S %S\n",
name,
@@ -2243,7 +2311,7 @@ process_cmd_tracking(char *line)
CMD_Reply reply;
IPAddr ip_addr;
uint32_t ref_id;
char name[50];
char name[256];
struct timespec ref_time;
request.command = htons(REQ_TRACKING);
@@ -2254,7 +2322,7 @@ process_cmd_tracking(char *line)
UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
format_name(name, sizeof (name), sizeof (name),
ip_addr.family == IPADDR_UNSPEC, ref_id, &ip_addr);
ip_addr.family == IPADDR_UNSPEC, ref_id, 1, &ip_addr);
UTI_TimespecNetworkToHost(&reply.data.tracking.ref_time, &ref_time);
@@ -2314,7 +2382,7 @@ process_cmd_ntpdata(char *line)
for (i = 0; i < n_sources; i++) {
if (specified_addr) {
if (DNS_Name2IPAddress(line, &remote_addr, 1) != DNS_Success) {
if (!parse_source_address(line, &remote_addr)) {
LOG(LOGS_ERR, "Could not get address for hostname");
return 0;
}
@@ -2329,6 +2397,8 @@ process_cmd_ntpdata(char *line)
continue;
UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &remote_addr);
if (!UTI_IsIPReal(&remote_addr))
continue;
}
request.command = htons(REQ_NTP_DATA);
@@ -2550,7 +2620,7 @@ process_cmd_clients(char *line)
if (ip.family == IPADDR_UNSPEC)
continue;
format_name(name, sizeof (name), 25, 0, 0, &ip);
format_name(name, sizeof (name), 25, 0, 0, 0, &ip);
print_report("%-25s %6U %5U %C %C %I %6U %5U %C %I\n",
name,
@@ -2766,6 +2836,14 @@ process_cmd_shutdown(CMD_Request *msg, char *line)
/* ================================================== */
static void
process_cmd_reset(CMD_Request *msg, char *line)
{
msg->command = htons(REQ_RESET);
}
/* ================================================== */
static int
process_cmd_waitsync(char *line)
{
@@ -2881,28 +2959,39 @@ process_cmd_retries(const char *line)
static int
process_cmd_keygen(char *line)
{
char hash_name[17];
unsigned char key[512];
unsigned int i, length, id = 1, bits = 160;
char type[17];
unsigned int i, cmac_length, length, id = 1, bits = 160;
#ifdef FEAT_SECHASH
snprintf(hash_name, sizeof (hash_name), "SHA1");
snprintf(type, sizeof (type), "SHA1");
#else
snprintf(hash_name, sizeof (hash_name), "MD5");
snprintf(type, sizeof (type), "MD5");
#endif
if (sscanf(line, "%u %16s %u", &id, hash_name, &bits))
if (sscanf(line, "%u %16s %u", &id, type, &bits))
;
length = CLAMP(10, (bits + 7) / 8, sizeof (key));
if (HSH_GetHashId(hash_name) < 0) {
LOG(LOGS_ERR, "Unknown hash function %s", hash_name);
#ifdef HAVE_CMAC
cmac_length = CMC_GetKeyLength(type);
#else
cmac_length = 0;
#endif
if (HSH_GetHashId(type) >= 0) {
length = (bits + 7) / 8;
} else if (cmac_length > 0) {
length = cmac_length;
} else {
LOG(LOGS_ERR, "Unknown hash function or cipher %s", type);
return 0;
}
length = CLAMP(10, length, sizeof (key));
UTI_GetRandomBytesUrandom(key, length);
printf("%u %s HEX:", id, hash_name);
printf("%u %s HEX:", id, type);
for (i = 0; i < length; i++)
printf("%02hhX", key[i]);
printf("\n");
@@ -2941,10 +3030,8 @@ process_line(char *line)
} else if (!strcmp(command, "activity")) {
do_normal_submit = 0;
ret = process_cmd_activity(line);
} else if (!strcmp(command, "add") && !strncmp(line, "peer", 4)) {
do_normal_submit = process_cmd_add_peer(&tx_message, CPS_SplitWord(line));
} else if (!strcmp(command, "add") && !strncmp(line, "server", 6)) {
do_normal_submit = process_cmd_add_server(&tx_message, CPS_SplitWord(line));
} else if (!strcmp(command, "add")) {
do_normal_submit = process_cmd_add_source(&tx_message, line);
} else if (!strcmp(command, "allow")) {
if (!strncmp(line, "all", 3)) {
do_normal_submit = process_cmd_allowall(&tx_message, CPS_SplitWord(line));
@@ -3051,6 +3138,8 @@ process_line(char *line)
process_cmd_reselect(&tx_message, line);
} else if (!strcmp(command, "reselectdist")) {
do_normal_submit = process_cmd_reselectdist(&tx_message, line);
} else if (!strcmp(command, "reset")) {
process_cmd_reset(&tx_message, line);
} else if (!strcmp(command, "retries")) {
ret = process_cmd_retries(line);
do_normal_submit = 0;
@@ -3070,6 +3159,9 @@ process_line(char *line)
ret = process_cmd_smoothing(line);
} else if (!strcmp(command, "smoothtime")) {
do_normal_submit = process_cmd_smoothtime(&tx_message, line);
} else if (!strcmp(command, "sourcename")) {
do_normal_submit = 0;
ret = process_cmd_sourcename(line);
} else if (!strcmp(command, "sources")) {
do_normal_submit = 0;
ret = process_cmd_sources(line);
@@ -3171,7 +3263,7 @@ display_gpl(void)
static void
print_help(const char *progname)
{
printf("Usage: %s [-h HOST] [-p PORT] [-n] [-c] [-d] [-4|-6] [-m] [COMMAND]\n",
printf("Usage: %s [-h HOST] [-p PORT] [-n] [-N] [-c] [-d] [-4|-6] [-m] [COMMAND]\n",
progname);
}
@@ -3208,7 +3300,7 @@ main(int argc, char **argv)
optind = 1;
/* Parse short command-line options */
while ((opt = getopt(argc, argv, "+46acdf:h:mnp:v")) != -1) {
while ((opt = getopt(argc, argv, "+46acdf:h:mnNp:v")) != -1) {
switch (opt) {
case '4':
case '6':
@@ -3222,7 +3314,9 @@ main(int argc, char **argv)
csv_mode = 1;
break;
case 'd':
log_debug_enabled = 1;
#if DEBUG > 0
log_min_severity = LOGS_DEBUG;
#endif
break;
case 'h':
hostnames = optarg;
@@ -3233,6 +3327,9 @@ main(int argc, char **argv)
case 'n':
no_dns = 1;
break;
case 'N':
source_names = 1;
break;
case 'p':
port = atoi(optarg);
break;
@@ -3261,7 +3358,8 @@ main(int argc, char **argv)
UTI_SetQuitSignalsHandler(signal_handler, 0);
sockaddrs = get_sockaddrs(hostnames, port);
SCK_Initialise();
server_addresses = get_addresses(hostnames, port);
if (!open_io())
LOG_FATAL("Could not open connection to daemon");
@@ -3281,8 +3379,8 @@ main(int argc, char **argv)
}
close_io();
ARR_DestroyInstance(sockaddrs);
free_addresses(server_addresses);
SCK_Finalise();
return !ret;
}

41
cmac.h Normal file
View File

@@ -0,0 +1,41 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
Header file for CMAC.
*/
#ifndef GOT_CMAC_H
#define GOT_CMAC_H
typedef struct CMC_Instance_Record *CMC_Instance;
extern unsigned int CMC_GetKeyLength(const char *cipher);
extern CMC_Instance CMC_CreateInstance(const char *cipher, const unsigned char *key,
unsigned int length);
extern unsigned int CMC_Hash(CMC_Instance inst, const unsigned char *in, unsigned int in_len,
unsigned char *out, unsigned int out_len);
extern void CMC_DestroyInstance(CMC_Instance inst);
#endif

115
cmac_nettle.c Normal file
View File

@@ -0,0 +1,115 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
Support for AES128 and AES256 CMAC in Nettle.
*/
#include "config.h"
#include "sysincl.h"
#include <nettle/cmac.h>
#include "cmac.h"
#include "memory.h"
struct CMC_Instance_Record {
int key_length;
union {
struct cmac_aes128_ctx aes128;
struct cmac_aes256_ctx aes256;
} context;
};
/* ================================================== */
unsigned int
CMC_GetKeyLength(const char *cipher)
{
if (strcmp(cipher, "AES128") == 0)
return AES128_KEY_SIZE;
else if (strcmp(cipher, "AES256") == 0)
return AES256_KEY_SIZE;
return 0;
}
/* ================================================== */
CMC_Instance
CMC_CreateInstance(const char *cipher, const unsigned char *key, unsigned int length)
{
CMC_Instance inst;
if (length == 0 || length != CMC_GetKeyLength(cipher))
return NULL;
inst = MallocNew(struct CMC_Instance_Record);
inst->key_length = length;
switch (length) {
case AES128_KEY_SIZE:
cmac_aes128_set_key(&inst->context.aes128, key);
break;
case AES256_KEY_SIZE:
cmac_aes256_set_key(&inst->context.aes256, key);
break;
default:
assert(0);
}
return inst;
}
/* ================================================== */
unsigned int
CMC_Hash(CMC_Instance inst, const unsigned char *in, unsigned int in_len,
unsigned char *out, unsigned int out_len)
{
if (out_len > CMAC128_DIGEST_SIZE)
out_len = CMAC128_DIGEST_SIZE;
switch (inst->key_length) {
case AES128_KEY_SIZE:
cmac_aes128_update(&inst->context.aes128, in_len, in);
cmac_aes128_digest(&inst->context.aes128, out_len, out);
break;
case AES256_KEY_SIZE:
cmac_aes256_update(&inst->context.aes256, in_len, in);
cmac_aes256_digest(&inst->context.aes256, out_len, out);
break;
default:
assert(0);
}
return out_len;
}
/* ================================================== */
void
CMC_DestroyInstance(CMC_Instance inst)
{
Free(inst);
}

446
cmdmon.c
View File

@@ -38,11 +38,13 @@
#include "ntp_sources.h"
#include "ntp_core.h"
#include "smooth.h"
#include "socket.h"
#include "sources.h"
#include "sourcestats.h"
#include "reference.h"
#include "manual.h"
#include "memory.h"
#include "nts_ke_server.h"
#include "local.h"
#include "addrfilt.h"
#include "conf.h"
@@ -53,21 +55,12 @@
/* ================================================== */
union sockaddr_all {
struct sockaddr_in in4;
#ifdef FEAT_IPV6
struct sockaddr_in6 in6;
#endif
struct sockaddr_un un;
struct sockaddr sa;
};
#define INVALID_SOCK_FD (-5)
/* File descriptors for command and monitoring sockets */
static int sock_fdu;
static int sock_fd4;
#ifdef FEAT_IPV6
static int sock_fd6;
#endif
/* Flag indicating whether this module has been initialised or not */
static int initialised = 0;
@@ -140,6 +133,9 @@ static const char permissions[] = {
PERMIT_AUTH, /* ADD_PEER3 */
PERMIT_AUTH, /* SHUTDOWN */
PERMIT_AUTH, /* ONOFFLINE */
PERMIT_AUTH, /* ADD_SOURCE */
PERMIT_OPEN, /* NTP_SOURCE_NAME */
PERMIT_AUTH, /* RESET */
};
/* ================================================== */
@@ -155,99 +151,46 @@ static void read_from_cmd_socket(int sock_fd, int event, void *anything);
/* ================================================== */
static int
prepare_socket(int family, int port_number)
open_socket(int family)
{
int sock_fd;
socklen_t my_addr_len;
union sockaddr_all my_addr;
IPAddr bind_address;
int on_off = 1;
sock_fd = socket(family, SOCK_DGRAM, 0);
if (sock_fd < 0) {
LOG(LOGS_ERR, "Could not open %s command socket : %s",
UTI_SockaddrFamilyToString(family), strerror(errno));
return -1;
}
/* Close on exec */
UTI_FdSetCloexec(sock_fd);
if (family != AF_UNIX) {
/* Allow reuse of port number */
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set reuseaddr socket options");
/* Don't quit - we might survive anyway */
}
#ifdef IP_FREEBIND
/* Allow binding to address that doesn't exist yet */
if (setsockopt(sock_fd, IPPROTO_IP, IP_FREEBIND, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set free bind socket option");
}
#endif
#ifdef FEAT_IPV6
if (family == AF_INET6) {
#ifdef IPV6_V6ONLY
/* Receive IPv6 packets only */
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not request IPV6_V6ONLY socket option");
}
#endif
}
#endif
}
memset(&my_addr, 0, sizeof (my_addr));
IPSockAddr local_addr;
const char *local_path;
int sock_fd, port;
switch (family) {
case AF_INET:
my_addr_len = sizeof (my_addr.in4);
my_addr.in4.sin_family = family;
my_addr.in4.sin_port = htons((unsigned short)port_number);
case IPADDR_INET4:
case IPADDR_INET6:
port = CNF_GetCommandPort();
if (port == 0 || !SCK_IsFamilySupported(family))
return INVALID_SOCK_FD;
CNF_GetBindCommandAddress(IPADDR_INET4, &bind_address);
CNF_GetBindCommandAddress(family, &local_addr.ip_addr);
if (local_addr.ip_addr.family != family)
SCK_GetLoopbackIPAddress(family, &local_addr.ip_addr);
local_addr.port = port;
sock_fd = SCK_OpenUdpSocket(NULL, &local_addr, SCK_FLAG_RX_DEST_ADDR);
if (sock_fd < 0) {
LOG(LOGS_ERR, "Could not open command socket on %s",
UTI_IPSockAddrToString(&local_addr));
return INVALID_SOCK_FD;
}
if (bind_address.family == IPADDR_INET4)
my_addr.in4.sin_addr.s_addr = htonl(bind_address.addr.in4);
else
my_addr.in4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
break;
#ifdef FEAT_IPV6
case AF_INET6:
my_addr_len = sizeof (my_addr.in6);
my_addr.in6.sin6_family = family;
my_addr.in6.sin6_port = htons((unsigned short)port_number);
case IPADDR_UNSPEC:
local_path = CNF_GetBindCommandPath();
CNF_GetBindCommandAddress(IPADDR_INET6, &bind_address);
sock_fd = SCK_OpenUnixDatagramSocket(NULL, local_path, 0);
if (sock_fd < 0) {
LOG(LOGS_ERR, "Could not open command socket on %s", local_path);
return INVALID_SOCK_FD;
}
if (bind_address.family == IPADDR_INET6)
memcpy(my_addr.in6.sin6_addr.s6_addr, bind_address.addr.in6,
sizeof (my_addr.in6.sin6_addr.s6_addr));
else
my_addr.in6.sin6_addr = in6addr_loopback;
break;
#endif
case AF_UNIX:
my_addr_len = sizeof (my_addr.un);
my_addr.un.sun_family = family;
if (snprintf(my_addr.un.sun_path, sizeof (my_addr.un.sun_path), "%s",
CNF_GetBindCommandPath()) >= sizeof (my_addr.un.sun_path))
LOG_FATAL("Unix socket path too long");
unlink(my_addr.un.sun_path);
break;
default:
assert(0);
}
if (bind(sock_fd, &my_addr.sa, my_addr_len) < 0) {
LOG(LOGS_ERR, "Could not bind %s command socket : %s",
UTI_SockaddrFamilyToString(family), strerror(errno));
close(sock_fd);
return -1;
}
/* Register handler for read events on the socket */
SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_from_cmd_socket, NULL);
@@ -292,37 +235,22 @@ do_size_checks(void)
void
CAM_Initialise(int family)
{
int port_number;
assert(!initialised);
assert(sizeof (permissions) / sizeof (permissions[0]) == N_REQUEST_TYPES);
do_size_checks();
initialised = 1;
sock_fdu = -1;
port_number = CNF_GetCommandPort();
sock_fdu = INVALID_SOCK_FD;
sock_fd4 = INVALID_SOCK_FD;
sock_fd6 = INVALID_SOCK_FD;
if (port_number && (family == IPADDR_UNSPEC || family == IPADDR_INET4))
sock_fd4 = prepare_socket(AF_INET, port_number);
else
sock_fd4 = -1;
#ifdef FEAT_IPV6
if (port_number && (family == IPADDR_UNSPEC || family == IPADDR_INET6))
sock_fd6 = prepare_socket(AF_INET6, port_number);
else
sock_fd6 = -1;
#endif
if (family == IPADDR_UNSPEC || family == IPADDR_INET4)
sock_fd4 = open_socket(IPADDR_INET4);
if (port_number && sock_fd4 < 0
#ifdef FEAT_IPV6
&& sock_fd6 < 0
#endif
) {
LOG_FATAL("Could not open any command socket");
}
if (family == IPADDR_UNSPEC || family == IPADDR_INET6)
sock_fd6 = open_socket(IPADDR_INET6);
access_auth_table = ADF_CreateTable();
}
/* ================================================== */
@@ -330,24 +258,24 @@ CAM_Initialise(int family)
void
CAM_Finalise(void)
{
if (sock_fdu >= 0) {
if (sock_fdu != INVALID_SOCK_FD) {
SCH_RemoveFileHandler(sock_fdu);
close(sock_fdu);
unlink(CNF_GetBindCommandPath());
SCK_RemoveSocket(sock_fdu);
SCK_CloseSocket(sock_fdu);
sock_fdu = INVALID_SOCK_FD;
}
sock_fdu = -1;
if (sock_fd4 >= 0) {
if (sock_fd4 != INVALID_SOCK_FD) {
SCH_RemoveFileHandler(sock_fd4);
close(sock_fd4);
SCK_CloseSocket(sock_fd4);
sock_fd4 = INVALID_SOCK_FD;
}
sock_fd4 = -1;
#ifdef FEAT_IPV6
if (sock_fd6 >= 0) {
if (sock_fd6 != INVALID_SOCK_FD) {
SCH_RemoveFileHandler(sock_fd6);
close(sock_fd6);
SCK_CloseSocket(sock_fd6);
sock_fd6 = INVALID_SOCK_FD;
}
sock_fd6 = -1;
#endif
ADF_DestroyTable(access_auth_table);
@@ -362,50 +290,18 @@ CAM_OpenUnixSocket(void)
/* This is separated from CAM_Initialise() as it needs to be called when
the process has already dropped the root privileges */
if (CNF_GetBindCommandPath()[0])
sock_fdu = prepare_socket(AF_UNIX, 0);
sock_fdu = open_socket(IPADDR_UNSPEC);
}
/* ================================================== */
static void
transmit_reply(CMD_Reply *msg, union sockaddr_all *where_to)
transmit_reply(int sock_fd, SCK_Message *message)
{
int status;
int tx_message_length;
int sock_fd;
socklen_t addrlen;
message->length = PKL_ReplyLength((CMD_Reply *)message->data);
switch (where_to->sa.sa_family) {
case AF_INET:
sock_fd = sock_fd4;
addrlen = sizeof (where_to->in4);
break;
#ifdef FEAT_IPV6
case AF_INET6:
sock_fd = sock_fd6;
addrlen = sizeof (where_to->in6);
break;
#endif
case AF_UNIX:
sock_fd = sock_fdu;
addrlen = sizeof (where_to->un);
break;
default:
assert(0);
}
tx_message_length = PKL_ReplyLength(msg);
status = sendto(sock_fd, (void *) msg, tx_message_length, 0,
&where_to->sa, addrlen);
if (status < 0) {
DEBUG_LOG("Could not send to %s fd %d : %s",
UTI_SockaddrToString(&where_to->sa), sock_fd, strerror(errno));
if (!SCK_SendMessage(sock_fd, message, 0))
return;
}
DEBUG_LOG("Sent %d bytes to %s fd %d", status,
UTI_SockaddrToString(&where_to->sa), sock_fd);
}
/* ================================================== */
@@ -414,6 +310,8 @@ static void
handle_dump(CMD_Request *rx_message, CMD_Reply *tx_message)
{
SRC_DumpSources();
NSR_DumpAuthData();
NKS_DumpKeys();
}
/* ================================================== */
@@ -722,6 +620,7 @@ static void
handle_rekey(CMD_Request *rx_message, CMD_Reply *tx_message)
{
KEY_Reload();
NKS_ReloadKeys();
}
/* ================================================== */
@@ -783,14 +682,41 @@ handle_cmdaccheck(CMD_Request *rx_message, CMD_Reply *tx_message)
/* ================================================== */
static void
handle_add_source(NTP_Source_Type type, CMD_Request *rx_message, CMD_Reply *tx_message)
handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
{
NTP_Remote_Address rem_addr;
NTP_Source_Type type;
SourceParameters params;
NSR_Status status;
char *name;
int pool, port;
UTI_IPNetworkToHost(&rx_message->data.ntp_source.ip_addr, &rem_addr.ip_addr);
rem_addr.port = (unsigned short)(ntohl(rx_message->data.ntp_source.port));
switch (ntohl(rx_message->data.ntp_source.type)) {
case REQ_ADDSRC_SERVER:
type = NTP_SERVER;
pool = 0;
break;
case REQ_ADDSRC_PEER:
type = NTP_PEER;
pool = 0;
break;
case REQ_ADDSRC_POOL:
type = NTP_SERVER;
pool = 1;
break;
default:
tx_message->status = htons(STT_INVALID);
return;
}
name = (char *)rx_message->data.ntp_source.name;
/* Make sure the name is terminated */
if (name[sizeof (rx_message->data.ntp_source.name) - 1] != '\0') {
tx_message->status = htons(STT_INVALIDNAME);
return;
}
port = (unsigned short)(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);
params.presend_minpoll = ntohl(rx_message->data.ntp_source.presend_minpoll);
@@ -802,6 +728,7 @@ handle_add_source(NTP_Source_Type type, CMD_Request *rx_message, CMD_Reply *tx_m
params.max_samples = ntohl(rx_message->data.ntp_source.max_samples);
params.filter_length = ntohl(rx_message->data.ntp_source.filter_length);
params.authkey = ntohl(rx_message->data.ntp_source.authkey);
params.nts_port = ntohl(rx_message->data.ntp_source.nts_port);
params.max_delay = UTI_FloatNetworkToHost(rx_message->data.ntp_source.max_delay);
params.max_delay_ratio =
UTI_FloatNetworkToHost(rx_message->data.ntp_source.max_delay_ratio);
@@ -817,25 +744,31 @@ handle_add_source(NTP_Source_Type type, CMD_Request *rx_message, CMD_Reply *tx_m
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.sel_options =
(ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_PREFER ? SRC_SELECT_PREFER : 0) |
(ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_NOSELECT ? SRC_SELECT_NOSELECT : 0) |
(ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_TRUST ? SRC_SELECT_TRUST : 0) |
(ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_REQUIRE ? SRC_SELECT_REQUIRE : 0);
status = NSR_AddSource(&rem_addr, type, &params);
status = NSR_AddSourceByName(name, port, pool, type, &params);
switch (status) {
case NSR_Success:
break;
case NSR_UnresolvedName:
/* Try to resolve the name now */
NSR_ResolveSources();
break;
case NSR_AlreadyInUse:
tx_message->status = htons(STT_SOURCEALREADYKNOWN);
break;
case NSR_TooManySources:
tx_message->status = htons(STT_TOOMANYSOURCES);
break;
case NSR_InvalidAF:
tx_message->status = htons(STT_INVALIDAF);
case NSR_InvalidName:
tx_message->status = htons(STT_INVALIDNAME);
break;
case NSR_InvalidAF:
case NSR_NoSuchSource:
assert(0);
break;
@@ -863,6 +796,8 @@ handle_del_source(CMD_Request *rx_message, CMD_Reply *tx_message)
case NSR_TooManySources:
case NSR_AlreadyInUse:
case NSR_InvalidAF:
case NSR_InvalidName:
case NSR_UnresolvedName:
assert(0);
break;
}
@@ -1261,98 +1196,130 @@ handle_shutdown(CMD_Request *rx_message, CMD_Reply *tx_message)
SCH_QuitProgram();
}
/* ================================================== */
static void
handle_ntp_source_name(CMD_Request *rx_message, CMD_Reply *tx_message)
{
IPAddr addr;
char *name;
UTI_IPNetworkToHost(&rx_message->data.ntp_data.ip_addr, &addr);
name = NSR_GetName(&addr);
if (!name) {
tx_message->status = htons(STT_NOSUCHSOURCE);
return;
}
tx_message->reply = htons(RPY_NTP_SOURCE_NAME);
/* Avoid compiler warning */
if (strlen(name) >= sizeof (tx_message->data.ntp_source_name.name))
memcpy(tx_message->data.ntp_source_name.name, name,
sizeof (tx_message->data.ntp_source_name.name));
else
strncpy((char *)tx_message->data.ntp_source_name.name, name,
sizeof (tx_message->data.ntp_source_name.name));
}
/* ================================================== */
static void
handle_reset(CMD_Request *rx_message, CMD_Reply *tx_message)
{
struct timespec cooked_now, now;
SRC_ResetSources();
SCH_GetLastEventTime(&cooked_now, NULL, &now);
LCL_NotifyExternalTimeStep(&now, &cooked_now, 0.0, 0.0);
}
/* ================================================== */
/* Read a packet and process it */
static void
read_from_cmd_socket(int sock_fd, int event, void *anything)
{
SCK_Message *sck_message;
CMD_Request rx_message;
CMD_Reply tx_message;
int status, read_length, expected_length, rx_message_length;
IPAddr loopback_addr, remote_ip;
int read_length, expected_length;
int localhost, allowed, log_index;
union sockaddr_all where_from;
socklen_t from_length;
IPAddr remote_ip;
unsigned short remote_port, rx_command;
unsigned short rx_command;
struct timespec now, cooked_now;
rx_message_length = sizeof(rx_message);
from_length = sizeof(where_from);
status = recvfrom(sock_fd, (char *)&rx_message, rx_message_length, 0,
&where_from.sa, &from_length);
if (status < 0) {
LOG(LOGS_WARN, "Error [%s] reading from control socket %d",
strerror(errno), sock_fd);
sck_message = SCK_ReceiveMessage(sock_fd, 0);
if (!sck_message)
return;
}
if (from_length > sizeof (where_from) ||
from_length <= sizeof (where_from.sa.sa_family)) {
DEBUG_LOG("Read command packet without source address");
return;
}
read_length = status;
read_length = sck_message->length;
/* Get current time cheaply */
SCH_GetLastEventTime(&cooked_now, NULL, &now);
UTI_SockaddrToIPAndPort(&where_from.sa, &remote_ip, &remote_port);
/* Check if it's from localhost (127.0.0.1, ::1, or Unix domain),
or an authorised address */
switch (sck_message->addr_type) {
case SCK_ADDR_IP:
assert(sock_fd == sock_fd4 || sock_fd == sock_fd6);
remote_ip = sck_message->remote_addr.ip.ip_addr;
SCK_GetLoopbackIPAddress(remote_ip.family, &loopback_addr);
localhost = UTI_CompareIPs(&remote_ip, &loopback_addr, NULL) == 0;
/* Check if it's from localhost (127.0.0.1, ::1, or Unix domain) */
switch (remote_ip.family) {
case IPADDR_INET4:
assert(sock_fd == sock_fd4);
localhost = remote_ip.addr.in4 == INADDR_LOOPBACK;
break;
#ifdef FEAT_IPV6
case IPADDR_INET6:
assert(sock_fd == sock_fd6);
localhost = !memcmp(remote_ip.addr.in6, &in6addr_loopback,
sizeof (in6addr_loopback));
break;
#endif
case IPADDR_UNSPEC:
/* This should be the Unix domain socket */
if (where_from.sa.sa_family != AF_UNIX)
if (!localhost && !ADF_IsAllowed(access_auth_table, &remote_ip)) {
DEBUG_LOG("Unauthorised host %s",
UTI_IPSockAddrToString(&sck_message->remote_addr.ip));
return;
}
assert(remote_ip.family != IPADDR_UNSPEC);
break;
case SCK_ADDR_UNIX:
assert(sock_fd == sock_fdu);
remote_ip.family = IPADDR_UNSPEC;
localhost = 1;
break;
default:
assert(0);
}
DEBUG_LOG("Received %d bytes from %s fd %d",
status, UTI_SockaddrToString(&where_from.sa), sock_fd);
if (!(localhost || ADF_IsAllowed(access_auth_table, &remote_ip))) {
/* The client is not allowed access, so don't waste any more time
on him. Note that localhost is always allowed access
regardless of the defined access rules - otherwise, we could
shut ourselves out completely! */
return;
DEBUG_LOG("Unexpected address type");
return;
}
if (read_length < offsetof(CMD_Request, data) ||
read_length < offsetof(CMD_Reply, data) ||
rx_message.pkt_type != PKT_TYPE_CMD_REQUEST ||
rx_message.res1 != 0 ||
rx_message.res2 != 0) {
read_length > sizeof (CMD_Request)) {
/* We don't know how to process anything like this or an error reply
would be larger than the request */
DEBUG_LOG("Unexpected length");
return;
}
memcpy(&rx_message, sck_message->data, read_length);
if (rx_message.pkt_type != PKT_TYPE_CMD_REQUEST ||
rx_message.res1 != 0 ||
rx_message.res2 != 0) {
DEBUG_LOG("Command packet dropped");
return;
}
log_index = CLG_LogCommandAccess(&remote_ip, &cooked_now);
/* Don't reply to all requests from hosts other than localhost if the rate
is excessive */
if (!localhost && log_index >= 0 && CLG_LimitCommandResponseRate(log_index)) {
DEBUG_LOG("Command packet discarded to limit response rate");
return;
}
expected_length = PKL_CommandLength(&rx_message);
rx_command = ntohs(rx_message.command);
memset(&tx_message, 0, sizeof (tx_message));
sck_message->data = &tx_message;
sck_message->length = 0;
tx_message.version = PROTO_VERSION_NUMBER;
tx_message.pkt_type = PKT_TYPE_CMD_REPLY;
@@ -1367,7 +1334,7 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
if (rx_message.version >= PROTO_VERSION_MISMATCH_COMPAT_SERVER) {
tx_message.status = htons(STT_BADPKTVERSION);
transmit_reply(&tx_message, &where_from);
transmit_reply(sock_fd, sck_message);
}
return;
}
@@ -1377,7 +1344,7 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
DEBUG_LOG("Command packet has invalid command %d", rx_command);
tx_message.status = htons(STT_INVALID);
transmit_reply(&tx_message, &where_from);
transmit_reply(sock_fd, sck_message);
return;
}
@@ -1386,21 +1353,12 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
expected_length);
tx_message.status = htons(STT_BADPKTLENGTH);
transmit_reply(&tx_message, &where_from);
transmit_reply(sock_fd, sck_message);
return;
}
/* OK, we have a valid message. Now dispatch on message type and process it. */
log_index = CLG_LogCommandAccess(&remote_ip, &cooked_now);
/* Don't reply to all requests from hosts other than localhost if the rate
is excessive */
if (!localhost && log_index >= 0 && CLG_LimitCommandResponseRate(log_index)) {
DEBUG_LOG("Command packet discarded to limit response rate");
return;
}
if (rx_command >= N_REQUEST_TYPES) {
/* This should be already handled */
assert(0);
@@ -1408,7 +1366,7 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
/* Check level of authority required to issue the command. All commands
from the Unix domain socket (which is accessible only by the root and
chrony user/group) are allowed. */
if (where_from.sa.sa_family == AF_UNIX) {
if (remote_ip.family == IPADDR_UNSPEC) {
assert(sock_fd == sock_fdu);
allowed = 1;
} else {
@@ -1547,12 +1505,8 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
handle_cmdaccheck(&rx_message, &tx_message);
break;
case REQ_ADD_SERVER3:
handle_add_source(NTP_SERVER, &rx_message, &tx_message);
break;
case REQ_ADD_PEER3:
handle_add_source(NTP_PEER, &rx_message, &tx_message);
case REQ_ADD_SOURCE:
handle_add_source(&rx_message, &tx_message);
break;
case REQ_DEL_SOURCE:
@@ -1655,6 +1609,14 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
handle_onoffline(&rx_message, &tx_message);
break;
case REQ_NTP_SOURCE_NAME:
handle_ntp_source_name(&rx_message, &tx_message);
break;
case REQ_RESET:
handle_reset(&rx_message, &tx_message);
break;
default:
DEBUG_LOG("Unhandled command %d", rx_command);
tx_message.status = htons(STT_FAILED);
@@ -1672,7 +1634,7 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
static int do_it=1;
if (do_it) {
transmit_reply(&tx_message, &where_from);
transmit_reply(sock_fd, sck_message);
}
#if 0

View File

@@ -62,6 +62,8 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
src->params.filter_length = 0;
src->params.interleaved = 0;
src->params.sel_options = 0;
src->params.nts = 0;
src->params.nts_port = SRC_DEFAULT_NTSPORT;
src->params.authkey = INACTIVE_AUTHKEY;
src->params.max_delay = SRC_DEFAULT_MAXDELAY;
src->params.max_delay_ratio = SRC_DEFAULT_MAXDELAYRATIO;
@@ -140,6 +142,11 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
} else if (!strcasecmp(cmd, "minstratum")) {
if (sscanf(line, "%d%n", &src->params.min_stratum, &n) != 1)
return 0;
} else if (!strcasecmp(cmd, "nts")) {
src->params.nts = 1;
} else if (!strcasecmp(cmd, "ntsport")) {
if (sscanf(line, "%d%n", &src->params.nts_port, &n) != 1)
return 0;
} else if (!strcasecmp(cmd, "offset")) {
if (sscanf(line, "%lf%n", &src->params.offset, &n) != 1)
return 0;
@@ -261,7 +268,7 @@ CPS_SplitWord(char *line)
/* ================================================== */
int
CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key)
CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key)
{
char *s1, *s2, *s3, *s4;
@@ -278,10 +285,10 @@ CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key)
return 0;
if (*s3) {
*hash = s2;
*type = s2;
*key = s3;
} else {
*hash = "MD5";
*type = "MD5";
*key = s2;
}

View File

@@ -49,6 +49,6 @@ extern void CPS_NormalizeLine(char *line);
extern char *CPS_SplitWord(char *line);
/* Parse a key from keyfile */
extern int CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key);
extern int CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key);
#endif /* GOT_CMDPARSE_H */

153
conf.c
View File

@@ -223,6 +223,24 @@ static char *leapsec_tz = NULL;
/* Name of the user to which will be dropped root privileges. */
static char *user;
/* NTS server and client configuration */
static char *nts_dump_dir = NULL;
static char *nts_ntp_server = NULL;
static char *nts_server_cert_file = NULL;
static char *nts_server_key_file = NULL;
static int nts_server_port = 11443;
static int nts_server_processes = 1;
static int nts_server_connections = 100;
static int nts_refresh = 2419200; /* 4 weeks */
static int nts_rotate = 604800; /* 1 week */
static char *nts_trusted_cert_file = NULL;
/* Number of clock updates needed to enable certificate time checks */
static int no_cert_time_check = 0;
/* Flag disabling use of system trusted certificates */
static int no_system_cert = 0;
/* Array of CNF_HwTsInterface */
static ARR_Instance hwts_interfaces;
@@ -390,6 +408,11 @@ CNF_Finalise(void)
Free(mail_user_on_change);
Free(tempcomp_sensor_file);
Free(tempcomp_point_file);
Free(nts_dump_dir);
Free(nts_ntp_server);
Free(nts_server_cert_file);
Free(nts_server_key_file);
Free(nts_trusted_cert_file);
}
/* ================================================== */
@@ -402,14 +425,7 @@ CNF_ReadFile(const char *filename)
char line[2048];
int i;
in = fopen(filename, "r");
if (!in) {
LOG_FATAL("Could not open configuration file %s : %s",
filename, strerror(errno));
return;
}
DEBUG_LOG("Reading %s", filename);
in = UTI_OpenFile(NULL, filename, NULL, 'R', 0);
for (i = 1; fgets(line, sizeof(line), in); i++) {
CNF_ParseLine(filename, i, line);
@@ -520,6 +536,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_double(p, &max_drift);
} else if (!strcasecmp(command, "maxjitter")) {
parse_double(p, &max_jitter);
} else if (!strcasecmp(command, "maxntsconnections")) {
parse_int(p, &nts_server_connections);
} else if (!strcasecmp(command, "maxsamples")) {
parse_int(p, &max_samples);
} else if (!strcasecmp(command, "maxslewrate")) {
@@ -530,10 +548,33 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_int(p, &min_samples);
} else if (!strcasecmp(command, "minsources")) {
parse_int(p, &min_sources);
} else if (!strcasecmp(command, "nocerttimecheck")) {
parse_int(p, &no_cert_time_check);
} else if (!strcasecmp(command, "noclientlog")) {
no_client_log = parse_null(p);
} else if (!strcasecmp(command, "nosystemcert")) {
no_system_cert = parse_null(p);
} else if (!strcasecmp(command, "ntpsigndsocket")) {
parse_string(p, &ntp_signd_socket);
} else if (!strcasecmp(command, "ntstrustedcerts")) {
parse_string(p, &nts_trusted_cert_file);
} else if (!strcasecmp(command, "ntscachedir") ||
!strcasecmp(command, "ntsdumpdir")) {
parse_string(p, &nts_dump_dir);
} else if (!strcasecmp(command, "ntsntpserver")) {
parse_string(p, &nts_ntp_server);
} else if (!strcasecmp(command, "ntsport")) {
parse_int(p, &nts_server_port);
} else if (!strcasecmp(command, "ntsprocesses")) {
parse_int(p, &nts_server_processes);
} else if (!strcasecmp(command, "ntsrefresh")) {
parse_int(p, &nts_refresh);
} else if (!strcasecmp(command, "ntsrotate")) {
parse_int(p, &nts_rotate);
} else if (!strcasecmp(command, "ntsservercert")) {
parse_string(p, &nts_server_cert_file);
} else if (!strcasecmp(command, "ntsserverkey")) {
parse_string(p, &nts_server_key_file);
} else if (!strcasecmp(command, "peer")) {
parse_source(p, NTP_PEER, 0);
} else if (!strcasecmp(command, "pidfile")) {
@@ -2034,3 +2075,99 @@ CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface)
*iface = (CNF_HwTsInterface *)ARR_GetElement(hwts_interfaces, index);
return 1;
}
/* ================================================== */
char *
CNF_GetNtsDumpDir(void)
{
return nts_dump_dir;
}
/* ================================================== */
char *
CNF_GetNtsNtpServer(void)
{
return nts_ntp_server;
}
/* ================================================== */
char *
CNF_GetNtsServerCertFile(void)
{
return nts_server_cert_file;
}
/* ================================================== */
char *
CNF_GetNtsServerKeyFile(void)
{
return nts_server_key_file;
}
/* ================================================== */
int
CNF_GetNtsServerPort(void)
{
return nts_server_port;
}
/* ================================================== */
int
CNF_GetNtsServerProcesses(void)
{
return nts_server_processes;
}
/* ================================================== */
int
CNF_GetNtsServerConnections(void)
{
return nts_server_connections;
}
/* ================================================== */
int
CNF_GetNtsRefresh(void)
{
return nts_refresh;
}
/* ================================================== */
int
CNF_GetNtsRotate(void)
{
return nts_rotate;
}
/* ================================================== */
char *
CNF_GetNtsTrustedCertFile(void)
{
return nts_trusted_cert_file;
}
/* ================================================== */
int
CNF_GetNoSystemCert(void)
{
return no_system_cert;
}
/* ================================================== */
int
CNF_GetNoCertTimeCheck(void)
{
return no_cert_time_check;
}

13
conf.h
View File

@@ -139,4 +139,17 @@ typedef struct {
extern int CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface);
extern char *CNF_GetNtsDumpDir(void);
extern char *CNF_GetNtsNtpServer(void);
extern char *CNF_GetNtsServerCertFile(void);
extern char *CNF_GetNtsServerKeyFile(void);
extern int CNF_GetNtsServerPort(void);
extern int CNF_GetNtsServerProcesses(void);
extern int CNF_GetNtsServerConnections(void);
extern int CNF_GetNtsRefresh(void);
extern int CNF_GetNtsRotate(void);
extern char *CNF_GetNtsTrustedCertFile(void);
extern int CNF_GetNoSystemCert(void);
extern int CNF_GetNoCertTimeCheck(void);
#endif /* GOT_CONF_H */

145
configure vendored
View File

@@ -55,6 +55,34 @@ test_code () {
return $result
}
#}}}
#{{{ test_executable
test_executable () {
name=$1
executable=$2
options=$3
printf "%s" "Checking for $name : "
echo $executable $options >> config.log
$executable $options >> config.log 2>&1
if [ $? -eq 0 ]
then
echo "Yes"
result=0
else
echo "No"
result=1
fi
echo >> config.log
return $result
}
#}}}
#{{{ pkg_config
pkg_config () {
$PKG_CONFIG "$@" 2>> config.log
}
#}}}
#{{{ usage
usage () {
cat <<EOF
@@ -89,6 +117,8 @@ For better control, use the options below.
--without-nettle Don't use nettle 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
@@ -137,6 +167,11 @@ Some influential environment variables:
headers in a nonstandard directory <include dir>
LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
nonstandard directory <lib dir>
PKG_CONFIG path to pkg-config utility
PKG_CONFIG_PATH
directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR
path overriding pkg-config's built-in search path
Use these variables to override the choices made by \`configure' or to help
it to find libraries and programs with nonstandard names/locations.
@@ -154,13 +189,6 @@ add_def () {
fi
}
#}}}
#{{{ pkg_config
pkg_config () {
type pkg-config > /dev/null 2> /dev/null || return 1
pkg-config $@ 2> /dev/null
}
#}}}
#{{{ get_features
get_features () {
ff=1
@@ -186,11 +214,11 @@ OPERATINGSYSTEM=`uname -s`
VERSION=`uname -r`
MACHINE=`uname -m`
LIBS=""
EXTRA_LIBS=""
EXTRA_CLI_LIBS=""
EXTRA_OBJECTS=""
EXTRA_DEFS=""
SYSDEFS=""
EXTRA_CLI_OBJECTS=""
feat_debug=0
feat_cmdmon=1
@@ -203,6 +231,8 @@ feat_sechash=1
try_nettle=1
try_nss=1
try_tomcrypt=1
feat_nts=1
try_gnutls=1
feat_rtc=1
try_rtc=0
feat_droproot=1
@@ -373,6 +403,12 @@ do
--without-tomcrypt )
try_tomcrypt=0
;;
--disable-nts )
feat_nts=0
;;
--without-gnutls )
try_gnutls=0
;;
--host-system=* )
OPERATINGSYSTEM=`echo $option | sed -e 's/^.*=//;'`
;;
@@ -432,8 +468,7 @@ case $OPERATINGSYSTEM in
;;
Darwin)
EXTRA_OBJECTS="sys_macosx.o"
EXTRA_LIBS="-lresolv"
EXTRA_CLI_LIBS="-lresolv"
LIBS="$LIBS -lresolv"
add_def MACOSX
if [ $feat_droproot = "1" ]; then
add_def FEAT_PRIVDROP
@@ -452,8 +487,7 @@ case $OPERATINGSYSTEM in
;;
SunOS)
EXTRA_OBJECTS="sys_generic.o sys_solaris.o sys_timex.o sys_posix.o"
EXTRA_LIBS="-lsocket -lnsl -lresolv"
EXTRA_CLI_LIBS="-lsocket -lnsl -lresolv"
LIBS="$LIBS -lsocket -lnsl -lresolv"
try_setsched=1
try_lockmem=1
add_def SOLARIS
@@ -485,7 +519,7 @@ fi
if [ $feat_ntp = "1" ]; then
add_def FEAT_NTP
EXTRA_OBJECTS="$EXTRA_OBJECTS ntp_core.o ntp_io.o ntp_sources.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS ntp_auth.o ntp_core.o ntp_ext.o ntp_io.o ntp_sources.o"
if [ $feat_ntp_signd = "1" ]; then
add_def FEAT_SIGND
EXTRA_OBJECTS="$EXTRA_OBJECTS ntp_signd.o"
@@ -553,6 +587,16 @@ if [ "x$MYCC" = "xgcc" ] || [ "x$MYCC" = "xclang" ]; then
MYCFLAGS="$MYCFLAGS -Wmissing-prototypes -Wall"
fi
if [ "x$PKG_CONFIG" = "x" ]; then
PKG_CONFIG=pkg-config
fi
if ! test_executable "pkg-config" $PKG_CONFIG --version; then
try_nettle=0
try_nss=0
try_gnutls=0
fi
if test_code '64-bit time_t' 'time.h' '' '' '
char x[sizeof(time_t) > 4 ? 1 : -1] = {0};
return x[0];'
@@ -592,11 +636,9 @@ then
fi
MATHCODE='return (int) pow(2.0, log(sqrt((double)argc)));'
if test_code 'math' 'math.h' '' '' "$MATHCODE"; then
LIBS=""
else
if ! test_code 'math' 'math.h' '' '' "$MATHCODE"; then
if test_code 'math in -lm' 'math.h' '' '-lm' "$MATHCODE"; then
LIBS="-lm"
LIBS="$LIBS -lm"
else
echo "error: could not compile/link a program which uses sqrt(), log(), pow()"
exit 1
@@ -611,7 +653,7 @@ then
fi
if [ $feat_ipv6 = "1" ] && \
test_code 'IPv6 support' 'arpa/inet.h sys/socket.h netinet/in.h' '' "$EXTRA_LIBS" '
test_code 'IPv6 support' 'arpa/inet.h sys/socket.h netinet/in.h' '' "$LIBS" '
struct sockaddr_in6 n;
char p[100];
n.sin6_addr = in6addr_any;
@@ -647,15 +689,16 @@ if [ $try_clock_gettime = "1" ]; then
fi
fi
if test_code 'getaddrinfo()' 'sys/types.h sys/socket.h netdb.h' '' "$EXTRA_LIBS" \
if test_code 'getaddrinfo()' 'sys/types.h sys/socket.h netdb.h' '' "$LIBS" \
'return getaddrinfo(0, 0, 0, 0);'
then
add_def HAVE_GETADDRINFO
fi
if [ $feat_asyncdns = "1" ] && \
test_code 'pthread' 'pthread.h' '-pthread' '' \
'return (int)pthread_create((void *)1, NULL, (void *)1, NULL);'
test_code 'pthread' 'pthread.h' '-pthread' '' '
pthread_t thread;
return (int)pthread_create(&thread, NULL, (void *)1, NULL);'
then
add_def FEAT_ASYNCDNS
add_def USE_PTHREAD_ASYNCDNS
@@ -676,11 +719,11 @@ RECVMMSG_CODE='
struct mmsghdr hdr;
return !recvmmsg(0, &hdr, 1, MSG_DONTWAIT, 0);'
if [ $try_recvmmsg = "1" ]; then
if test_code 'recvmmsg()' 'sys/socket.h' '' "$EXTRA_LIBS" "$RECVMMSG_CODE"; then
if test_code 'recvmmsg()' 'sys/socket.h' '' "$LIBS" "$RECVMMSG_CODE"; then
add_def HAVE_RECVMMSG
else
if test_code 'recvmmsg() with _GNU_SOURCE' 'sys/socket.h' '-D_GNU_SOURCE' \
"$EXTRA_LIBS" "$RECVMMSG_CODE"
"$LIBS" "$RECVMMSG_CODE"
then
add_def _GNU_SOURCE
add_def HAVE_RECVMMSG
@@ -731,6 +774,7 @@ if [ "x$timepps_h" != "x" ] && \
pps_handle_t h = 0;
pps_info_t i;
struct timespec ts;
ts.tv_sec = ts.tv_nsec = 0;
return time_pps_fetch(h, PPS_TSFMT_TSPEC, &i, &ts);'
then
add_def FEAT_PPS
@@ -817,6 +861,7 @@ if [ $try_lockmem = "1" ] && \
'setrlimit(RLIMIT_MEMLOCK, ...)' \
'sys/resource.h' '' '' '
struct rlimit rlim;
rlim.rlim_max = rlim.rlim_cur = RLIM_INFINITY;
setrlimit(RLIMIT_MEMLOCK, &rlim);'
then
add_def HAVE_SETRLIMIT_MEMLOCK
@@ -879,9 +924,16 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nettle = "1" ];
then
HASH_OBJ="hash_nettle.o"
HASH_LINK="$test_link"
LIBS="$LIBS $HASH_LINK"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
add_def FEAT_SECHASH
if test_code 'CMAC in nettle' 'nettle/cmac.h' "$test_cflags" "$test_link" \
'cmac128_update(NULL, NULL, NULL, 0, NULL);'
then
add_def HAVE_CMAC
EXTRA_OBJECTS="$EXTRA_OBJECTS cmac_nettle.o"
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS cmac_nettle.o"
fi
fi
fi
@@ -894,7 +946,6 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nss = "1" ]; th
then
HASH_OBJ="hash_nss.o"
HASH_LINK="$test_link"
LIBS="$LIBS $HASH_LINK"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
add_def FEAT_SECHASH
fi
@@ -906,12 +957,44 @@ if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_tomcrypt = "1" ]
then
HASH_OBJ="hash_tomcrypt.o"
HASH_LINK="-ltomcrypt"
LIBS="$LIBS $HASH_LINK"
MYCPPFLAGS="$MYCPPFLAGS -I/usr/include/tomcrypt"
add_def FEAT_SECHASH
fi
fi
EXTRA_OBJECTS="$EXTRA_OBJECTS $HASH_OBJ"
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS $HASH_OBJ"
LIBS="$LIBS $HASH_LINK"
if [ $feat_ntp = "1" ] && [ $feat_nts = "1" ] && [ $try_gnutls = "1" ] && \
echo "$HASH_LINK" | grep 'nettle' > /dev/null; then
test_cflags="`pkg_config --cflags gnutls`"
test_link="`pkg_config --libs gnutls`"
if test_code 'gnutls' 'gnutls/gnutls.h' \
"$test_cflags" "$test_link" '
return gnutls_init(NULL, 0) +
gnutls_priority_init2(NULL, "", NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND) +
gnutls_prf_rfc5705(NULL, 0, "", 0, "", 16, NULL);' &&
test_code 'AES128 in nettle' 'nettle/aes.h' '' "$LIBS" \
'aes128_set_encrypt_key(NULL, NULL);'
then
EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ke_client.o nts_ke_server.o nts_ke_session.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ntp_auth.o nts_ntp_client.o nts_ntp_server.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS siv_nettle.o"
LIBS="$LIBS $test_link"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
add_def FEAT_NTS
add_def HAVE_SIV
if test_code 'SIV in nettle' \
'nettle/siv-cmac.h' "" "$LIBS" \
'siv_cmac_aes128_set_key(NULL, NULL);'
then
add_def HAVE_NETTLE_SIV_CMAC
fi
fi
fi
if [ $use_pthread = "1" ]; then
MYCFLAGS="$MYCFLAGS -pthread"
fi
@@ -981,7 +1064,7 @@ add_def MAIL_PROGRAM "\"$mail_program\""
common_features="`get_features SECHASH IPV6 DEBUG`"
chronyc_features="`get_features READLINE`"
chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER SIGND ASYNCDNS`"
chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER SIGND ASYNCDNS NTS`"
add_def CHRONYC_FEATURES "\"$chronyc_features $common_features\""
add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\""
echo "Features : $chronyd_features $chronyc_features $common_features"
@@ -997,15 +1080,15 @@ add_def CHRONY_VERSION "\"${CHRONY_VERSION}\""
for f in Makefile doc/Makefile test/unit/Makefile
do
echo Creating $f
sed -e "s%@EXTRA_OBJECTS@%${EXTRA_OBJECTS}%;\
sed -e "s%@EXTRA_OBJS@%${EXTRA_OBJECTS}%;\
s%@EXTRA_CLI_OBJS@%${EXTRA_CLI_OBJECTS}%;\
s%@CC@%${MYCC}%;\
s%@CFLAGS@%${MYCFLAGS}%;\
s%@CPPFLAGS@%${MYCPPFLAGS}%;\
s%@LIBS@%${LIBS}%;\
s%@LDFLAGS@%${MYLDFLAGS}%;\
s%@LIBS@%${LIBS}%;\
s%@EXTRA_LIBS@%${EXTRA_LIBS}%;\
s%@EXTRA_CLI_LIBS@%${EXTRA_CLI_LIBS}%;\
s%@HASH_OBJ@%${HASH_OBJ}%;\
s%@SYSCONFDIR@%${SYSCONFDIR}%;\
s%@BINDIR@%${BINDIR}%;\
s%@SBINDIR@%${SBINDIR}%;\

View File

@@ -96,7 +96,7 @@ interval in order to allow a burst with two requests.
*key* _ID_:::
The NTP protocol supports a message authentication code (MAC) to prevent
computers having their system time upset by rogue packets being sent to them.
The MAC is generated as a function of a password specified in the key file,
The MAC is generated as a function of a key specified in the key file,
which is specified by the <<keyfile,*keyfile*>> directive.
+
The *key* option specifies which key (with an ID in the range 1 through 2^32-1)
@@ -107,6 +107,12 @@ otherwise no relationship between the computers will be possible.
If the server is running *ntpd* and the output size of the hash function used
by the key is longer than 160 bits (e.g. SHA256), the *version* option needs to
be set to 4 for compatibility.
*nts*:::
This option enables authentication using the Network Time Security (NTS)
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.
*maxdelay* _delay_:::
*chronyd* uses the network round-trip delay to the server to determine how
accurate a particular measurement is likely to be. Long round-trip delays
@@ -194,14 +200,13 @@ authenticated source to be safely combined with unauthenticated sources in
order to improve the accuracy of the clock. They can be selected and used for
synchronisation only if they agree with the trusted and required source.
*xleave*:::
This option enables an interleaved mode which allows the server or the peer to
This option enables an interleaved mode which enables the server to
send transmit timestamps captured after the actual transmission (e.g. when the
server or the peer is running *chronyd* with software (kernel) or hardware
server is running *chronyd* with software (kernel) or hardware
timestamping). This can significantly improve the accuracy of the measurements.
+
The interleaved mode is compatible with servers that support only the basic
mode, but peers must both support and have enabled the interleaved mode,
otherwise the synchronisation will work only in one direction. Note that even
mode. Note that even
servers that support the interleaved mode might respond in the basic mode as
the interleaved mode requires the servers to keep some state for each client
and the state might be dropped when there are too many clients (e.g.
@@ -221,6 +226,9 @@ intervals. The default is 8 and a useful range is from 6 to 60.
This option allows the UDP port on which the server understands NTP requests to
be specified. For normal servers this option should not be required (the
default is 123, the standard NTP port).
*ntsport* _port_:::
This option specifies the TCP port on which the server is listening for NTS-KE
connections when the *nts* option is enabled. The default is 11443.
*presend* _poll_:::
If the timing measurements being made by *chronyd* are the only network data
passing between two computers, you might find that some measurements are badly
@@ -243,8 +251,8 @@ when the polling interval is 512 seconds or more, an extra NTP client packet
will be sent to the server a short time (2 seconds) before making the actual
measurement.
+
The *presend* option cannot be used in the *peer* directive. If it is used
with the *xleave* option, *chronyd* will send two extra packets instead of one.
If the *presend* option is used together with the *xleave* option, *chronyd*
will send two extra packets instead of one.
*minstratum* _stratum_:::
When the synchronisation source is selected from available sources, sources
with lower stratum are normally slightly preferred. This option can be used to
@@ -269,16 +277,18 @@ which might change over time.
+
All options valid in the <<server,*server*>> directive can be used in this
directive too. There is one option specific to the *pool* directive:
*maxsources* sets the maximum number of sources that can be used from the pool,
the default value is 4.
+
On start, when the pool name is resolved, *chronyd* will add up to 16 sources,
one for each resolved address. When the number of sources from which at least
one valid reply was received reaches the number specified by the *maxsources*
option, the other sources will be removed. When a pool source is unreachable,
*maxsources* _sources_:::
This option sets the desired number of sources to be used from the pool.
*chronyd* will repeatedly try to resolve the name until it gets this number of
sources responding to requests. The default value is 4 and the maximum value is
16.
+
::
When an NTP source is unreachable,
marked as a falseticker, or has a distance larger than the limit set by the
<<maxdistance,*maxdistance*>> directive, *chronyd* will try to replace the
source with a newly resolved address from the pool.
source with a newly resolved address of the name.
+
An example of the *pool* directive is
+
@@ -295,6 +305,12 @@ is mainly useful when the NTP implementation of the peer (e.g. *ntpd*) supports
ephemeral symmetric associations and does not need to be configured with an
address of this host. *chronyd* does not support ephemeral associations.
+
The following options of the *server* directive do not work in the *peer*
directive: *iburst*, *burst*, *nts*, *presend*.
+
When using the *xleave* option, both peers must support and have enabled the
interleaved mode, otherwise the synchronisation will work in one direction
only.
When a key is specified by the *key* option to enable authentication, both
peers must use the same key and the same key number.
+
@@ -655,6 +671,10 @@ The *maxsamples* directive sets the default maximum number of samples that
individual sources in the <<server,*server*>> and <<refclock,*refclock*>>
directives. The default value is 0, which disables the configurable limit. The
useful range is 4 to 64.
+
As a special case, setting *maxsamples* to 1 disables frequency tracking in
order to make the sources immediately selectable with only one sample. This can
be useful when *chronyd* is started with the *-q* or *-Q* option.
[[minsamples]]*minsamples* _samples_::
The *minsamples* directive sets the default minimum number of samples that
@@ -669,6 +689,54 @@ 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.
[[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
*chronyd* is started again. The cookies are saved separately for each NTP
source in files named by the IP address of the NTS-KE server (e.g.
_1.2.3.4.nts_). By default, the client does not save the cookies.
+
An example of the directive is:
+
----
ntsdumpdir @CHRONYVARDIR@
----
+
This directory is used also by the <<ntsdumpdir2,NTS server>> to save keys.
[[ntsrefresh]]*ntsrefresh* _interval_::
This directive specifies the maximum interval between NTS-KE handshakes (in
seconds) in order to refresh the keys authenticating NTP packets. The default
value is 2419200 (4 weeks).
[[ntstrustedcerts]]*ntstrustedcerts* _file_::
This directive specifies a file containing certificates (in the PEM format) of
trusted certificate authorities (CA) that should be used to verify certificates
of NTS servers in addition to the system's default trusted CAs (if the
*nosystemcert* directive is not present).
[[nosystemcert]]*nosystemcert*::
This directive disables the system's default trusted CAs.
[[nocerttimecheck]]*nocerttimecheck* _limit_::
This directive disables the checks of the activation and expiration times of
certificates for the specified number of clock updates. It allows the NTS
authentication mechanism to be used on computers which start with wrong time
(e.g. due to not having an RTC or backup battery). Disabling the time checks
has important security implications, e.g. if an NTP server was ever
compromised, its certificate could be used in an attack after the expiration
time. The default value is 0, which means the time checks are always enabled.
An example of the directive is:
+
----
nocerttimecheck 1
----
+
This would disable the time checks until the clock is updated for the first
time, assuming the first update corrects the clock and later checks can work
with correct time.
=== Source selection
[[combinelimit]]*combinelimit* _limit_::
@@ -854,7 +922,7 @@ slightly different rates when it is necessary to keep them close together, the
enable a server leap smear.
+
When smearing a leap second, the leap status is suppressed on the server and
the served time is corrected slowly be slewing instead of stepping. The clients
the served time is corrected slowly by slewing instead of stepping. The clients
do not need any special configuration as they do not know there is any leap
second and they follow the server time which eventually brings them back to
UTC. Care must be taken to ensure they use only NTP servers which smear the
@@ -1330,6 +1398,77 @@ An example of the directive is:
ntpsigndsocket /var/lib/samba/ntp_signd
----
[[ntsport]]*ntsport* _port_::
This directive specifies the TCP port on which *chronyd* will provide the NTS
Key Establishment (NTS-KE) service. The default port is 11443.
+
The port will be open only when a certificate and key is specified by the
*ntsservercert* and *ntsserverkey* directives.
[[ntsservercert]]*ntsservercert* _file_::
This directive specifies a file containing a certificate in the PEM format
for *chronyd* to operate as an NTS server.
[[ntsserverkey]]*ntsserverkey* _file_::
This directive specifies a file containing a private key in the PEM format
for *chronyd* to operate as an NTS server.
[[ntsprocesses]]*ntsprocesses* _processes_::
This directive specifies how many helper processes will *chronyd* operating
as an NTS server start for handling client NTS-KE requests in order to improve
performance with multi-core CPUs and multithreading. If set to 0, no helper
process will be started and all NTS-KE requests will be handled by the main
*chronyd* process. The default value is 1.
[[maxntsconnections]]*maxntsconnections* _connections_::
This directive specifies the maximum number of concurrent NTS-KE connections
per process that the NTS server will accept. The default value is 100.
[[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
saved to a single file named _ntskeys_. When *chronyd* is restarted, reloading
the keys allows the clients to continue using old cookies and avoids a storm of
NTS-KE requests. By default, the server does not save the keys.
+
An example of the directive is:
+
----
ntsdumpdir @CHRONYVARDIR@
----
+
This directory is used also by the <<ntsdumpdir1,NTS client>> to save NTS cookies.
[[ntsntpserver]]*ntsntpserver* _hostname_::
This directive specifies the hostname or address of the NTP server(s) which is
provided in the NTS-KE response to the clients. It allows the NTS-KE server to
be separated from the NTP server. However, the servers need to share the keys,
i.e. external key management needs to be enabled by setting
<<ntsrotate,*ntsrotate*>> to 0. By default, no hostname or address is provided
to the clients, which means they should use the same server for NTS-KE and NTP.
[[ntsrotate]]*ntsrotate* _interval_::
This directive specifies the rotation interval (in seconds) of the server key
which encrypts the NTS cookies. New keys are generated automatically. The
server keeps two previous keys to give the clients time to get new cookies
encrypted by the latest key. The default interval is 604800 seconds (1 week).
+
The automatic rotation of the keys can be disabled by setting *ntsrotate* to 0.
In this case the keys are assumed to be managed externally. *chronyd* will not
save the keys to the _ntskeys_ file and will reload the keys from the file when
the <<chronyc.adoc#rekey,*rekey*>> command is issued in *chronyc*. The file can
be periodically copied from another server running *chronyd* (which does
not have *ntsrotate* set to 0) in order to have one or more servers dedicated
to NTS-KE. The NTS-KE servers need to be configured with the
<<ntsntpname,*ntsntpname*>> directive to point the clients to the right NTP
server.
+
An example of the directive is:
+
----
ntsrotate 2592000
----
[[port]]*port* _port_::
This option allows you to configure the port on which *chronyd* will listen for
NTP requests. The port will be open only when an address is allowed by the
@@ -2013,8 +2152,10 @@ include @SYSCONFDIR@/chrony.d/*.conf
----
[[keyfile]]*keyfile* _file_::
This directive is used to specify the location of the file containing ID-key
pairs for authentication of NTP packets.
This directive is used to specify the location of the file containing symmetric
keys which are shared between NTP servers and clients, or peers, in order to
authenticate NTP packets with a message authentication code (MAC) using a
cryptographic hash function or cipher.
+
The format of the directive is shown in the example below:
+
@@ -2029,31 +2170,41 @@ format of the file is shown below:
10 tulip
11 hyacinth
20 MD5 ASCII:crocus
25 SHA1 HEX:1dc764e0791b11fa67efc7ecbc4b0d73f68a070c
25 SHA1 HEX:933F62BE1D604E68A81B557F18CFA200483F5B70
30 AES128 HEX:7EA62AE64D190114D46D5A082F948EC1
31 AES256 HEX:37DDCBC67BB902BCB8E995977FAB4D2B5642F5B32EBCEEE421921D97E5CBFE39
...
----
+
Each line consists of an ID, name of an authentication hash function (optional),
and a password. The ID can be any unsigned integer in the range 1 through
2^32-1. The default hash function is *MD5*, which is always supported.
Each line consists of an ID, optional type, and key.
+
The ID can be any positive integer in the range 1 through 2^32-1.
+
The type is a name of a cryptographic hash function or cipher which is used to
generate and verify the MAC. The default type is *MD5*, which is always
supported.
If *chronyd* was built with enabled support for hashing using a crypto library
(nettle, nss, or libtomcrypt), the following functions are available: *MD5*,
*SHA1*, *SHA256*, *SHA384*, *SHA512*. Depending on which library and version is
*chronyd* using, some or all of the following functions may also be available:
*SHA3-224*, *SHA3-256*, *SHA3-384*, *SHA3-512*, *RMD128*, *RMD160*, *RMD256*,
*RMD320*, *TIGER*, *WHIRLPOOL*.
*chronyd* using, some of the following hash functions and ciphers may
also be available:
*SHA3-224*, *SHA3-256*, *SHA3-384*, *SHA3-512*, *TIGER*, *WHIRLPOOL*, *AES128*,
*AES256*.
+
The password can be specified as a string of characters not containing white
The key can be specified as a string of ASCII characters not containing white
space with an optional *ASCII:* prefix, or as a hexadecimal number with the
*HEX:* prefix. The maximum length of the line is 2047 characters.
If the type is a cipher, the length of the key must match the cipher (i.e. 128
bits for AES128 and 256 bits for AES256).
+
The password is used with the hash function to generate and verify a message
authentication code (MAC) in NTP packets. It is recommended to use SHA1, or
stronger, hash function with random passwords specified in the hexadecimal
format that have at least 128 bits. *chronyd* will log a warning to
syslog on start if a source is specified in the configuration file with a key
that has password shorter than 80 bits.
It is recommended to use randomly generated keys, specified in the hexadecimal
format, which are at least 128 bits long (i.e. they have at least 32 characters
after the *HEX:* prefix). *chronyd* will log a warning to syslog on start if a
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.
+
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
@@ -2222,7 +2373,7 @@ actually connected to the Internet.
=== Isolated networks
This section shows how to configure *chronyd* for computers that never have
network conectivity to any computer which ultimately derives its time from a
network connectivity to any computer which ultimately derives its time from a
reference clock.
In this situation, one computer is selected to be the master timeserver. The

View File

@@ -51,7 +51,8 @@ running under a non-root user), it will try to connect to 127.0.0.1 and then
Only the following monitoring commands, which do not affect the behaviour of
*chronyd*, are allowed from the network: *activity*, *manual list*,
*rtcdata*, *smoothing*, *sources*, *sourcestats*, *tracking*, *waitsync*. The
*rtcdata*, *smoothing*, *sourcename*, *sources*, *sourcestats*, *tracking*,
*waitsync*. The
set of hosts from which *chronyd* will accept these commands can be configured
with the <<chrony.conf.adoc#cmdallow,*cmdallow*>> directive in the *chronyd*'s
configuration file or the <<cmdallow,*cmdallow*>> command in *chronyc*. By
@@ -78,11 +79,18 @@ With this option hostnames will be resolved only to IPv6 addresses.
This option disables resolving of IP addresses to hostnames, e.g. to avoid slow
DNS lookups. Long addresses will not be truncated to fit into the column.
*-N*::
This option enables printing of the original names of NTP sources that were
specified in the configuration file, or *chronyc* commands, and are internally
used by *chronyd*. Without the *-n* and *-N* option, the names of NTP sources
are obtained from reverse DNS lookups and can be different from the original
names.
*-c*::
This option enables printing of reports in a comma-separated values (CSV)
format. IP addresses will not be resolved to hostnames, time will be printed as
number of seconds since the epoch and values in seconds will not be converted
to other units.
format. Reverse DNS lookups will be disabled, time will be printed as number of
seconds since the epoch, and values in seconds will not be converted to other
units.
*-d*::
This option enables printing of debugging messages if *chronyc* was compiled
@@ -284,15 +292,18 @@ milliseconds.
=== Time sources
[[sources]]*sources* [*-v*]::
[[sources]]*sources* [*-a*] [*-v*]::
This command displays information about the current time sources that *chronyd*
is accessing.
+
The optional argument *-v* can be specified, meaning _verbose_. In this case,
If the *-a* option is specified, all sources are displayed, including those that
do not have a known address yet. Such sources have an identifier in the format
_ID#XXXXXXXXXX_, which can be used in other commands expecting a source address.
+
The *-v* option enables a verbose output. In this case,
extra caption lines are shown as a reminder of the meanings of the columns.
+
----
210 Number of sources = 3
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#* GPS0 0 4 377 11 -479ns[ -621ns] +/- 134ns
@@ -353,18 +364,21 @@ since. The number following the _+/-_ indicator shows the margin of error in
the measurement. Positive offsets indicate that the local clock is ahead of
the source.
[[sourcestats]]*sourcestats* [*-v*]::
[[sourcestats]]*sourcestats* [*-a*] [*-v*]::
The *sourcestats* command displays information about the drift rate and offset
estimation process for each of the sources currently being examined by
*chronyd*.
+
The optional argument *-v* can be specified, meaning _verbose_. In this case,
If the *-a* option is specified, all sources are displayed, including those that
do not have a known address yet. Such sources have an identifier in the format
_ID#XXXXXXXXXX_, which can be used in other commands expecting a source address.
+
The *-v* option enables a verbose output. In this case,
extra caption lines are shown as a reminder of the meanings of the columns.
+
An example report is:
+
----
210 Number of sources = 1
Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
===============================================================================
foo.example.net 11 5 46m -0.001 0.045 1us 25us
@@ -442,8 +456,9 @@ not visible in the *sources* and *sourcestats* reports.
[[ntpdata]]*ntpdata* [_address_]::
The *ntpdata* command displays the last valid measurement and other
NTP-specific information about the specified NTP source, or all NTP sources if
no address was specified. An example of the output is shown below.
NTP-specific information about the specified NTP source, or all NTP sources
(with a known address) if no address was specified. An example of the output is
shown below.
+
----
Remote address : 203.0.113.15 (CB00710F)
@@ -526,15 +541,13 @@ The number of all packets received from the source.
*Total valid RX*:::
The number of valid packets received from the source.
[[add_peer]]*add peer* _address_ [_option_]...::
[[add_peer]]*add peer* _name_ [_option_]...::
The *add peer* command allows a new NTP peer to be added whilst
*chronyd* is running.
+
Following the words *add peer*, the syntax of the following
parameters and options is similar to that for the
parameters and options is identical to that for the
<<chrony.conf.adoc#peer,*peer*>> directive in the configuration file.
The following peer options can be set in the command: *port*, *minpoll*,
*maxpoll*, *presend*, *maxdelayratio*, *maxdelay*, *key*.
+
An example of using this command is shown below.
+
@@ -542,15 +555,27 @@ An example of using this command is shown below.
add peer foo.example.net minpoll 6 maxpoll 10 key 25
----
[[add_server]]*add server* _address_ [_option_]...::
[[add_pool]]*add pool* _name_ [_option_]...::
The *add pool* command allows a pool of NTP servers to be added whilst
*chronyd* is running.
+
Following the words *add pool*, the syntax of the following parameters and
options is identical to that for the <<chrony.conf.adoc#pool,*pool*>>
directive in the configuration file.
+
An example of using this command is shown below:
+
----
add pool foo.example.net maxsources 3 iburst
----
[[add_server]]*add server* _name_ [_option_]...::
The *add server* command allows a new NTP server to be added whilst
*chronyd* is running.
+
Following the words *add server*, the syntax of the following parameters and
options is similar to that for the <<chrony.conf.adoc#server,*server*>>
options is identical to that for the <<chrony.conf.adoc#server,*server*>>
directive in the configuration file.
The following server options can be set in the command: *port*, *minpoll*,
*maxpoll*, *presend*, *maxdelayratio*, *maxdelay*, *key*.
+
An example of using this command is shown below:
+
@@ -688,7 +713,8 @@ the loaded periods. The *offline* and *online* commands can be used to achieve
this.
+
There are four forms of the *offline* command. The first form is a wildcard,
meaning all sources. The second form allows an IP address mask and a masked
meaning all sources (including sources that do not have a known address yet).
The second form allows an IP address mask and a masked
address to be specified. The third form uses CIDR notation. The fourth form
uses an IP address or a hostname. These forms are illustrated below.
+
@@ -725,10 +751,11 @@ The syntax is identical to that of the <<offline,*offline*>> command.
[[onoffline]]
*onoffline*::
The *onoffline* command tells *chronyd* to switch all sources to the online or
The *onoffline* command tells *chronyd* to switch all sources that have a known
address to the online or
offline status according to the current network configuration. A source is
considered online if it is possible to send requests to it, i.e. a route to the
network is present.
considered online if it is possible to send requests to it, i.e. a network
route to the source is present.
[[polltarget]]*polltarget* _address_ _polltarget_::
The *polltarget* command is used to modify the poll target for one of the
@@ -744,6 +771,15 @@ Sources that stop responding will be replaced with newly resolved addresses
automatically after 8 polling intervals, but this command can still be useful
to replace them immediately and not wait until they are marked as unreachable.
[[sourcename]]*sourcename* _address_::
The *sourcename* command prints the original hostname or address that was
specified for an NTP source in the configuration file, or the *add* command.
This command is an alternative to the *-N* option, which can be useful in
scripts.
+
Note that different NTP sources can share the same name, e.g. servers from a
pool.
=== Manual time input
[[manual]]
@@ -1128,15 +1164,27 @@ purged. An example of how to do this is shown below.
The *dump* command causes *chronyd* to write its current history of
measurements for each of its sources to dump files in the directory specified
in the configuration file by the <<chrony.conf.adoc#dumpdir,*dumpdir*>>
directive and also write server NTS keys and client NTS cookies to the
directory specified by the <<chrony.conf.adoc#ntsdumpdir1,*ntsdumpdir*>>
directive. Note that *chronyd* does this automatically when it exits. This
command is mainly useful for inspection of the history whilst *chronyd* is
running.
command is mainly useful for inspection whilst *chronyd* is running.
[[rekey]]*rekey*::
The *rekey* command causes *chronyd* to re-read the key file specified in the
configuration file by the <<chrony.conf.adoc#keyfile,*keyfile*>> directive.
configuration file by the <<chrony.conf.adoc#keyfile,*keyfile*>> directive. It
also re-reads the server NTS keys if
<<chrony.conf.adoc#ntsdumpdir2,*ntsdumpdir*>> is specified and
<<chrony.conf.adoc#ntsrotate,automatic rotation>> is disabled in the
configuration file.
[[rekey]]*shutdown*::
[[reset]]*reset*::
The *reset* command causes *chronyd* to drop all measurements and switch to the
unsynchronised state. This command can help *chronyd* with recovery when the
measurements are known to be no longer valid or accurate, e.g. due to moving
the computer to a different network, or resuming the computer from a low-power
state (which resets the system clock).
[[shutdown]]*shutdown*::
The *shutdown* command causes *chronyd* to exit. This is equivalent to sending
the process the SIGTERM signal.
@@ -1188,10 +1236,10 @@ generated from the _/dev/urandom_ device and it is printed to standard output.
+
The command has three optional arguments. The first argument is the key number
(by default 1), which will be specified with the *key* option of the *server*
or *peer* directives in the configuration file. The second argument is the hash
function (by default SHA1 or MD5 if SHA1 is not available) and the third
argument is the number of bits the key should have, between 80 and 4096 bits
(by default 160 bits).
or *peer* directives in the configuration file. The second argument is the name
of the hash function or cipher (by default SHA1, or MD5 if SHA1 is not
available). The third argument is the length of the key in bits if a hash
function was selected, between 80 and 4096 bits (by default 160 bits).
+
An example is:
+

View File

@@ -41,7 +41,7 @@ If no configuration directives are specified on the command line, *chronyd*
will read them from a configuration file. The compiled-in default location of
the file is _@SYSCONFDIR@/chrony.conf_.
Information messages and warnings will be logged to syslog.
Informational messages, warnings, and errors will be logged to syslog.
== OPTIONS
@@ -55,20 +55,26 @@ IPv6 sockets will be created.
*-f* _file_::
This option can be used to specify an alternate location for the configuration
file (default _@SYSCONFDIR@/chrony.conf_).
file. The default value is _@SYSCONFDIR@/chrony.conf_.
*-n*::
When run in this mode, the program will not detach itself from the terminal.
*-d*::
When run in this mode, the program will not detach itself from the terminal,
and all messages will be written to the terminal instead of syslog. When
*chronyd* was compiled with debugging support, this option can be used twice to
print also debugging messages.
and all messages will be written to the terminal instead of syslog. If
*chronyd* was compiled with enabled support for debugging, this option can be
used twice to enable debug messages.
*-l* _file_::
This option specifies a file which should be used for logging instead of syslog
or terminal.
This option enables writing of log messages to a file instead of syslog or the
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.
*-q*::
When run in this mode, *chronyd* will set the system clock once and exit. It
@@ -125,7 +131,8 @@ running, but still allow it to adjust the frequency of the system clock.
*-u* _user_::
This option sets the name of the system user to which *chronyd* will switch
after start in order to drop root privileges. It overrides the
<<chrony.conf.adoc#user,*user*>> directive (default _@DEFAULT_USER@_).
<<chrony.conf.adoc#user,*user*>> directive. The default value is
_@DEFAULT_USER@_.
+
On Linux, *chronyd* needs to be compiled with support for the *libcap* library.
On macOS, FreeBSD, NetBSD and Solaris *chronyd* forks into two processes.
@@ -136,7 +143,8 @@ range of privileged system calls on behalf of the parent.
This option configures a system call filter when *chronyd* is compiled with
support for the Linux secure computing (seccomp) facility. In level 1 the
process is killed when a forbidden system call is made, in level -1 the SIGSYS
signal is thrown instead and in level 0 the filter is disabled (default 0).
signal is thrown instead and in level 0 the filter is disabled. The default
value is 0.
+
It's recommended to enable the filter only when it's known to work on the
version of the system where *chrony* is installed as the filter needs to allow
@@ -148,9 +156,9 @@ killed even in normal operation.
*-P* _priority_::
On Linux, this option will select the SCHED_FIFO real-time scheduler at the
specified priority (which must be between 0 and 100). On macOS, this option
must have either a value of 0 (the default) to disable the thread time
must have either a value of 0 to disable the thread time
constraint policy or 1 for the policy to be enabled. Other systems do not
support this option.
support this option. The default value is 0.
*-m*::
This option will lock *chronyd* into RAM so that it will never be paged out.

View File

@@ -244,6 +244,18 @@ specified on the command line. For example:
# chronyd -q 'pool pool.ntp.org iburst'
----
The command above would normally take about 5 seconds if the servers were
well synchronised and responding to all requests. If not synchronised or
responding, it would take about 10 seconds for `chronyd` to give up and exit
with a non-zero status. A faster configuration is possible. A single server can
be used instead of four servers, the number of measurements can be reduced with
the `maxsamples` option, and a timeout can be specified with the `-t` option.
The following command would take only up to about 1 second.
----
# chronyd -q -t 1 'server pool.ntp.org iburst maxsamples 1'
----
=== Can `chronyd` be configured to control the clock like `ntpd`?
It is not possible to perfectly emulate `ntpd`, but there are some options that

View File

@@ -22,18 +22,25 @@ The software is distributed as source code which has to be compiled. The source
code is supplied in the form of a gzipped tar file, which unpacks to a
subdirectory identifying the name and version of the program.
The following programs and libraries with their development files are needed to
build `chrony`:
A C compiler (e.g. `gcc` or `clang`) and GNU Make are needed to build `chrony`.
The following libraries with their development files, and programs, are needed
to enable optional features:
* C compiler (gcc or clang recommended)
* GNU Make
* Nettle, NSS, or LibTomCrypt (optional)
* Editline (optional)
* libcap (Linux only, optional)
* libseccomp (Linux only, optional)
* timepps.h header (optional)
* Asciidoctor (for HTML documentation)
* Bash (for testing)
* pkg-config: detection of development libraries
* Nettle, NSS, or LibTomCrypt: secure hash functions (`SECHASH`)
* libcap: dropping root privileges on Linux (`DROPROOT`)
* libseccomp: system call filter on Linux (`SCFILTER`)
* GnuTLS and Nettle: Network Time Security (`NTS`)
* Editline: line editing in `chronyc` (`READLINE`)
* timepps.h header: PPS reference clock
* Asciidoctor: documentation in HTML format
* Bash: test suite
The following programs are needed when building `chrony` from the git
repository instead of a released tar file:
* Asciidoctor: manual pages
* Bison: parser for chronyc settime command
After unpacking the source code, change directory into it, and type

View File

@@ -43,7 +43,6 @@ struct hash {
static struct hash hashes[] = {
{ "MD5", "md5", NULL, NULL },
{ "RMD160", "ripemd160", NULL, NULL },
{ "SHA1", "sha1", NULL, NULL },
{ "SHA256", "sha256", NULL, NULL },
{ "SHA384", "sha384", NULL, NULL },

View File

@@ -39,18 +39,6 @@ struct hash {
static const struct hash hashes[] = {
{ "MD5", "md5", &md5_desc },
#ifdef LTC_RIPEMD128
{ "RMD128", "rmd128", &rmd128_desc },
#endif
#ifdef LTC_RIPEMD160
{ "RMD160", "rmd160", &rmd160_desc },
#endif
#ifdef LTC_RIPEMD256
{ "RMD256", "rmd256", &rmd256_desc },
#endif
#ifdef LTC_RIPEMD320
{ "RMD320", "rmd320", &rmd320_desc },
#endif
#ifdef LTC_SHA1
{ "SHA1", "sha1", &sha1_desc },
#endif

156
keys.c
View File

@@ -32,6 +32,7 @@
#include "array.h"
#include "keys.h"
#include "cmac.h"
#include "cmdparse.h"
#include "conf.h"
#include "memory.h"
@@ -42,11 +43,22 @@
/* Consider 80 bits as the absolute minimum for a secure key */
#define MIN_SECURE_KEY_LENGTH 10
typedef enum {
NTP_MAC,
CMAC,
} KeyClass;
typedef struct {
uint32_t id;
char *val;
int len;
int hash_id;
KeyClass class;
union {
struct {
unsigned char *value;
int length;
int hash_id;
} ntp_mac;
CMC_Instance cmac;
} data;
int auth_delay;
} Key;
@@ -62,9 +74,21 @@ static void
free_keys(void)
{
unsigned int i;
Key *key;
for (i = 0; i < ARR_GetSize(keys); i++)
Free(((Key *)ARR_GetElement(keys, i))->val);
for (i = 0; i < ARR_GetSize(keys); i++) {
key = ARR_GetElement(keys, i);
switch (key->class) {
case NTP_MAC:
Free(key->data.ntp_mac.value);
break;
case CMAC:
CMC_DestroyInstance(key->data.cmac);
break;
default:
assert(0);
}
}
ARR_SetSize(keys, 0);
cache_valid = 0;
@@ -111,8 +135,9 @@ determine_hash_delay(uint32_t key_id)
for (i = 0; i < 10; i++) {
LCL_ReadRawTime(&before);
KEY_GenerateAuth(key_id, (unsigned char *)&pkt, NTP_NORMAL_PACKET_LENGTH,
(unsigned char *)&pkt.auth_data, sizeof (pkt.auth_data));
KEY_GenerateAuth(key_id, (unsigned char *)&pkt, NTP_HEADER_LENGTH,
(unsigned char *)&pkt + NTP_HEADER_LENGTH,
sizeof (pkt) - NTP_HEADER_LENGTH);
LCL_ReadRawTime(&after);
diff = UTI_DiffTimespecsToDouble(&after, &before);
@@ -121,8 +146,7 @@ determine_hash_delay(uint32_t key_id)
min_diff = diff;
}
/* Add on a bit extra to allow for copying, conversions etc */
nsecs = 1.0625e9 * min_diff;
nsecs = 1.0e9 * min_diff;
DEBUG_LOG("authentication delay for key %"PRIu32": %d nsecs", key_id, nsecs);
@@ -130,30 +154,18 @@ determine_hash_delay(uint32_t key_id)
}
/* ================================================== */
/* Decode password encoded in ASCII or HEX */
/* Decode key encoded in ASCII or HEX */
static int
decode_password(char *key)
decode_key(char *key)
{
int i, j, len = strlen(key);
char buf[3], *p;
int len = strlen(key);
if (!strncmp(key, "ASCII:", 6)) {
memmove(key, key + 6, len - 6);
return len - 6;
} else if (!strncmp(key, "HEX:", 4)) {
if ((len - 4) % 2)
return 0;
for (i = 0, j = 4; j + 1 < len; i++, j += 2) {
buf[0] = key[j], buf[1] = key[j + 1], buf[2] = '\0';
key[i] = strtol(buf, &p, 16);
if (p != buf + 2)
return 0;
}
return i;
return UTI_HexToBytes(key + 4, key, len);
} else {
/* assume ASCII */
return len;
@@ -185,11 +197,11 @@ compare_keys_by_id(const void *a, const void *b)
void
KEY_Reload(void)
{
unsigned int i, line_number;
unsigned int i, line_number, key_length, cmac_key_length;
FILE *in;
uint32_t key_id;
char line[2048], *keyval, *key_file;
const char *hashname;
char line[2048], *key_file, *key_value;
const char *key_type;
int hash_id;
Key key;
free_keys();
@@ -200,7 +212,7 @@ KEY_Reload(void)
if (!key_file)
return;
in = fopen(key_file, "r");
in = UTI_OpenFile(NULL, key_file, NULL, 'r', 0);
if (!in) {
LOG(LOGS_WARN, "Could not open keyfile %s", key_file);
return;
@@ -213,26 +225,43 @@ KEY_Reload(void)
if (!*line)
continue;
if (!CPS_ParseKey(line, &key_id, &hashname, &keyval)) {
memset(&key, 0, sizeof (key));
if (!CPS_ParseKey(line, &key.id, &key_type, &key_value)) {
LOG(LOGS_WARN, "Could not parse key at line %u in file %s", line_number, key_file);
continue;
}
key.hash_id = HSH_GetHashId(hashname);
if (key.hash_id < 0) {
LOG(LOGS_WARN, "Unknown hash function in key %"PRIu32, key_id);
key_length = decode_key(key_value);
if (key_length == 0) {
LOG(LOGS_WARN, "Could not decode key %"PRIu32, key.id);
continue;
}
key.len = decode_password(keyval);
if (!key.len) {
LOG(LOGS_WARN, "Could not decode password in key %"PRIu32, key_id);
hash_id = HSH_GetHashId(key_type);
cmac_key_length = CMC_GetKeyLength(key_type);
if (hash_id >= 0) {
key.class = NTP_MAC;
key.data.ntp_mac.value = MallocArray(unsigned char, key_length);
memcpy(key.data.ntp_mac.value, key_value, key_length);
key.data.ntp_mac.length = key_length;
key.data.ntp_mac.hash_id = hash_id;
} else if (cmac_key_length > 0) {
if (cmac_key_length != key_length) {
LOG(LOGS_WARN, "Invalid length of %s key %"PRIu32" (expected %u bits)",
key_type, key.id, 8 * cmac_key_length);
continue;
}
key.class = CMAC;
key.data.cmac = CMC_CreateInstance(key_type, (unsigned char *)key_value, key_length);
assert(key.data.cmac);
} else {
LOG(LOGS_WARN, "Unknown hash function or cipher in key %"PRIu32, key.id);
continue;
}
key.id = key_id;
key.val = MallocArray(char, key.len);
memcpy(key.val, keyval, key.len);
ARR_AppendElement(keys, &key);
}
@@ -335,7 +364,15 @@ KEY_GetAuthLength(uint32_t key_id)
if (!key)
return 0;
return HSH_Hash(key->hash_id, buf, 0, buf, 0, buf, sizeof (buf));
switch (key->class) {
case NTP_MAC:
return HSH_Hash(key->data.ntp_mac.hash_id, buf, 0, buf, 0, buf, sizeof (buf));
case CMAC:
return CMC_Hash(key->data.cmac, buf, 0, buf, sizeof (buf));
default:
assert(0);
return 0;
}
}
/* ================================================== */
@@ -350,30 +387,41 @@ KEY_CheckKeyLength(uint32_t key_id)
if (!key)
return 0;
return key->len >= MIN_SECURE_KEY_LENGTH;
switch (key->class) {
case NTP_MAC:
return key->data.ntp_mac.length >= MIN_SECURE_KEY_LENGTH;
default:
return 1;
}
}
/* ================================================== */
static int
generate_ntp_auth(int hash_id, const unsigned char *key, int key_len,
const unsigned char *data, int data_len,
unsigned char *auth, int auth_len)
generate_auth(Key *key, const unsigned char *data, int data_len,
unsigned char *auth, int auth_len)
{
return HSH_Hash(hash_id, key, key_len, data, data_len, auth, auth_len);
switch (key->class) {
case NTP_MAC:
return HSH_Hash(key->data.ntp_mac.hash_id, key->data.ntp_mac.value,
key->data.ntp_mac.length, data, data_len, auth, auth_len);
case CMAC:
return CMC_Hash(key->data.cmac, data, data_len, auth, auth_len);
default:
return 0;
}
}
/* ================================================== */
static int
check_ntp_auth(int hash_id, const unsigned char *key, int key_len,
const unsigned char *data, int data_len,
const unsigned char *auth, int auth_len, int trunc_len)
check_auth(Key *key, const unsigned char *data, int data_len,
const unsigned char *auth, int auth_len, int trunc_len)
{
unsigned char buf[MAX_HASH_LENGTH];
int hash_len;
hash_len = generate_ntp_auth(hash_id, key, key_len, data, data_len, buf, sizeof (buf));
hash_len = generate_auth(key, data, data_len, buf, sizeof (buf));
return MIN(hash_len, trunc_len) == auth_len && !memcmp(buf, auth, auth_len);
}
@@ -382,7 +430,7 @@ check_ntp_auth(int hash_id, const unsigned char *key, int key_len,
int
KEY_GenerateAuth(uint32_t key_id, const unsigned char *data, int data_len,
unsigned char *auth, int auth_len)
unsigned char *auth, int auth_len)
{
Key *key;
@@ -391,8 +439,7 @@ KEY_GenerateAuth(uint32_t key_id, const unsigned char *data, int data_len,
if (!key)
return 0;
return generate_ntp_auth(key->hash_id, (unsigned char *)key->val, key->len,
data, data_len, auth, auth_len);
return generate_auth(key, data, data_len, auth, auth_len);
}
/* ================================================== */
@@ -408,6 +455,5 @@ KEY_CheckAuth(uint32_t key_id, const unsigned char *data, int data_len,
if (!key)
return 0;
return check_ntp_auth(key->hash_id, (unsigned char *)key->val, key->len,
data, data_len, auth, auth_len, trunc_len);
return check_auth(key, data, data_len, auth, auth_len, trunc_len);
}

View File

@@ -29,26 +29,24 @@
#include "sysincl.h"
#include <syslog.h>
#include "conf.h"
#include "logging.h"
#include "util.h"
/* This is used by DEBUG_LOG macro */
int log_debug_enabled = 0;
LOG_Severity log_min_severity = LOGS_INFO;
/* ================================================== */
/* Flag indicating we have initialised */
static int initialised = 0;
static FILE *file_log;
static FILE *file_log = NULL;
static int system_log = 0;
static int parent_fd = 0;
#define DEBUG_LEVEL_PRINT_FUNCTION 2
#define DEBUG_LEVEL_PRINT_DEBUG 2
static int debug_level = 0;
struct LogFile {
const char *name;
const char *banner;
@@ -70,7 +68,7 @@ void
LOG_Initialise(void)
{
initialised = 1;
file_log = stderr;
LOG_OpenFileLog(NULL);
}
/* ================================================== */
@@ -134,7 +132,7 @@ void LOG_Message(LOG_Severity severity,
time_t t;
struct tm *tm;
if (!system_log && file_log) {
if (!system_log && file_log && severity >= log_min_severity) {
/* Don't clutter up syslog with timestamps and internal debugging info */
time(&t);
tm = gmtime(&t);
@@ -143,7 +141,7 @@ void LOG_Message(LOG_Severity severity,
fprintf(file_log, "%s ", buf);
}
#if DEBUG > 0
if (debug_level >= DEBUG_LEVEL_PRINT_FUNCTION)
if (log_min_severity <= LOGS_DEBUG)
fprintf(file_log, "%s:%d:(%s) ", filename, line_number, function_name);
#endif
}
@@ -157,10 +155,12 @@ void LOG_Message(LOG_Severity severity,
case LOGS_INFO:
case LOGS_WARN:
case LOGS_ERR:
log_message(0, severity, buf);
if (severity >= log_min_severity)
log_message(0, severity, buf);
break;
case LOGS_FATAL:
log_message(1, severity, buf);
if (severity >= log_min_severity)
log_message(1, severity, buf);
/* Send the message also to the foreground process if it is
still running, or stderr if it is still open */
@@ -171,6 +171,7 @@ void LOG_Message(LOG_Severity severity,
system_log = 0;
log_message(1, severity, buf);
}
exit(1);
break;
default:
assert(0);
@@ -185,9 +186,7 @@ LOG_OpenFileLog(const char *log_file)
FILE *f;
if (log_file) {
f = fopen(log_file, "a");
if (!f)
LOG_FATAL("Could not open log file %s", log_file);
f = UTI_OpenFile(NULL, log_file, NULL, 'A', 0640);
} else {
f = stderr;
}
@@ -213,12 +212,10 @@ LOG_OpenSystemLog(void)
/* ================================================== */
void LOG_SetDebugLevel(int level)
void LOG_SetMinSeverity(LOG_Severity severity)
{
debug_level = level;
if (level >= DEBUG_LEVEL_PRINT_DEBUG) {
log_debug_enabled = 1;
}
/* Don't print any debug messages in a non-debug build */
log_min_severity = CLAMP(DEBUG > 0 ? LOGS_DEBUG : LOGS_INFO, severity, LOGS_FATAL);
}
/* ================================================== */
@@ -268,7 +265,7 @@ LOG_FileWrite(LOG_FileID id, const char *format, ...)
return;
if (!logfiles[id].file) {
char filename[512], *logdir = CNF_GetLogDir();
char *logdir = CNF_GetLogDir();
if (logdir[0] == '\0') {
LOG(LOGS_WARN, "logdir not specified");
@@ -276,16 +273,12 @@ LOG_FileWrite(LOG_FileID id, const char *format, ...)
return;
}
if (snprintf(filename, sizeof(filename), "%s/%s.log",
logdir, logfiles[id].name) >= sizeof (filename) ||
!(logfiles[id].file = fopen(filename, "a"))) {
LOG(LOGS_WARN, "Could not open log file %s", filename);
logfiles[id].file = UTI_OpenFile(logdir, logfiles[id].name, ".log", 'a', 0644);
if (!logfiles[id].file) {
/* Disable the log */
logfiles[id].name = NULL;
return;
}
/* Close on exec */
UTI_FdSetCloexec(fileno(logfiles[id].file));
}
banner = CNF_GetLogBanner();
@@ -293,7 +286,7 @@ LOG_FileWrite(LOG_FileID id, const char *format, ...)
char bannerline[256];
int i, bannerlen;
bannerlen = strlen(logfiles[id].banner);
bannerlen = MIN(strlen(logfiles[id].banner), sizeof (bannerline) - 1);
for (i = 0; i < bannerlen; i++)
bannerline[i] = '=';

View File

@@ -31,9 +31,6 @@
#include "sysincl.h"
/* Flag indicating whether debug messages are logged */
extern int log_debug_enabled;
/* Line logging macros. If the compiler is GNU C, we take advantage of
being able to get the function name also. */
@@ -55,7 +52,7 @@ extern int log_debug_enabled;
#define DEBUG_LOG(...) \
do { \
if (DEBUG && log_debug_enabled) \
if (DEBUG && log_min_severity == LOGS_DEBUG) \
LOG_MESSAGE(LOGS_DEBUG, __VA_ARGS__); \
} while (0)
@@ -69,13 +66,16 @@ extern int log_debug_enabled;
/* Definition of severity */
typedef enum {
LOGS_INFO,
LOGS_DEBUG = -1,
LOGS_INFO = 0,
LOGS_WARN,
LOGS_ERR,
LOGS_FATAL,
LOGS_DEBUG
} LOG_Severity;
/* Minimum severity of messages to be logged */
extern LOG_Severity log_min_severity;
/* Init function */
extern void LOG_Initialise(void);
@@ -92,12 +92,10 @@ FORMAT_ATTRIBUTE_PRINTF(2, 3)
extern void LOG_Message(LOG_Severity severity, const char *format, ...);
#endif
/* Set debug level:
0, 1 - only non-debug messages are logged
2 - debug messages are logged too, all messages are prefixed with
filename, line, and function name
*/
extern void LOG_SetDebugLevel(int level);
/* Set the minimum severity of a message to be logged or printed to terminal.
If the severity is LOGS_DEBUG and DEBUG is enabled, all messages will be
prefixed with the filename, line number, and function name. */
extern void LOG_SetMinSeverity(LOG_Severity severity);
/* Log messages to a file instead of stderr, or stderr again if NULL */
extern void LOG_OpenFileLog(const char *log_file);

46
main.c
View File

@@ -38,6 +38,10 @@
#include "ntp_signd.h"
#include "ntp_sources.h"
#include "ntp_core.h"
#include "nts_ke_client.h"
#include "nts_ke_server.h"
#include "nts_ntp_server.h"
#include "socket.h"
#include "sources.h"
#include "sourcestats.h"
#include "reference.h"
@@ -90,8 +94,8 @@ delete_pidfile(void)
if (!pidfile[0])
return;
/* Don't care if this fails, there's not a lot we can do */
unlink(pidfile);
if (!UTI_RemoveFile(NULL, pidfile, NULL))
;
}
/* ================================================== */
@@ -112,12 +116,16 @@ MAI_CleanupAndExit(void)
TMC_Finalise();
MNL_Finalise();
CLG_Finalise();
NKC_Finalise();
NKS_Finalise();
NNS_Finalise();
NSD_Finalise();
NSR_Finalise();
SST_Finalise();
NCR_Finalise();
NIO_Finalise();
CAM_Finalise();
SCK_Finalise();
KEY_Finalise();
RCL_Finalise();
SRC_Finalise();
@@ -253,7 +261,10 @@ check_pidfile(void)
FILE *in;
int pid, count;
in = fopen(pidfile, "r");
if (!pidfile[0])
return;
in = UTI_OpenFile(NULL, pidfile, NULL, 'r', 0);
if (!in)
return;
@@ -281,13 +292,9 @@ write_pidfile(void)
if (!pidfile[0])
return;
out = fopen(pidfile, "w");
if (!out) {
LOG_FATAL("Could not open %s : %s", pidfile, strerror(errno));
} else {
fprintf(out, "%d\n", (int)getpid());
fclose(out);
}
out = UTI_OpenFile(NULL, pidfile, NULL, 'W', 0644);
fprintf(out, "%d\n", (int)getpid());
fclose(out);
}
/* ================================================== */
@@ -404,9 +411,9 @@ int main
char *user = NULL, *log_file = NULL;
struct passwd *pw;
int opt, debug = 0, nofork = 0, address_family = IPADDR_UNSPEC;
int do_init_rtc = 0, restarted = 0, client_only = 0, timeout = 0;
int do_init_rtc = 0, restarted = 0, client_only = 0, timeout = -1;
int scfilter_level = 0, lock_memory = 0, sched_priority = 0;
int clock_control = 1, system_log = 1;
int clock_control = 1, system_log = 1, log_severity = LOGS_INFO;
int config_args = 0;
do_platform_checks();
@@ -427,7 +434,7 @@ int main
optind = 1;
/* Parse short command-line options */
while ((opt = getopt(argc, argv, "46df:F:hl:mnP:qQrRst:u:vx")) != -1) {
while ((opt = getopt(argc, argv, "46df:F:hl:L:mnP:qQrRst:u:vx")) != -1) {
switch (opt) {
case '4':
case '6':
@@ -447,6 +454,9 @@ int main
case 'l':
log_file = optarg;
break;
case 'L':
log_severity = parse_int_arg(optarg);
break;
case 'm':
lock_memory = 1;
break;
@@ -510,7 +520,7 @@ int main
LOG_OpenSystemLog();
}
LOG_SetDebugLevel(debug);
LOG_SetMinSeverity(debug >= 2 ? LOGS_DEBUG : log_severity);
LOG(LOGS_INFO, "chronyd version %s starting (%s)", CHRONY_VERSION, CHRONYD_FEATURES);
@@ -551,6 +561,7 @@ int main
SRC_Initialise();
RCL_Initialise();
KEY_Initialise();
SCK_Initialise();
/* Open privileged ports before dropping root */
CAM_Initialise(address_family);
@@ -578,6 +589,9 @@ int main
SST_Initialise();
NSR_Initialise();
NSD_Initialise();
NNS_Initialise();
NKS_Initialise(scfilter_level);
NKC_Initialise();
CLG_Initialise();
MNL_Initialise();
TMC_Initialise();
@@ -591,7 +605,7 @@ int main
CAM_OpenUnixSocket();
if (scfilter_level)
SYS_EnableSystemCallFilter(scfilter_level);
SYS_EnableSystemCallFilter(scfilter_level, SYS_MAIN_PROCESS);
if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) {
ref_mode = REF_ModeInitStepSlew;
@@ -600,7 +614,7 @@ int main
REF_SetModeEndHandler(reference_mode_end);
REF_SetMode(ref_mode);
if (timeout > 0)
if (timeout >= 0)
SCH_AddTimeoutByDelay(timeout, quit_timeout, NULL);
if (do_init_rtc) {

View File

@@ -34,6 +34,7 @@
#include <resolv.h>
#include "nameserv.h"
#include "socket.h"
#include "util.h"
/* ================================================== */
@@ -69,7 +70,7 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
default:
hints.ai_family = AF_UNSPEC;
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_socktype = SOCK_DGRAM;
result = getaddrinfo(name, NULL, &hints, &res);
@@ -156,10 +157,14 @@ DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len)
#ifdef FEAT_IPV6
struct sockaddr_in6 in6;
IPSockAddr ip_saddr;
socklen_t slen;
char hbuf[NI_MAXHOST];
slen = UTI_IPAndPortToSockaddr(ip_addr, 0, (struct sockaddr *)&in6);
ip_saddr.ip_addr = *ip_addr;
ip_saddr.port = 0;
slen = SCK_IPSockAddrToSockaddr(&ip_saddr, (struct sockaddr *)&in6, sizeof (in6));
if (!getnameinfo((struct sockaddr *)&in6, slen, hbuf, sizeof (hbuf), NULL, 0, 0))
result = hbuf;
#else

View File

@@ -51,7 +51,7 @@ struct DNS_Async_Instance {
int pipe[2];
};
static int resolving_threads = 0;
static pthread_mutex_t privops_lock = PTHREAD_MUTEX_INITIALIZER;
/* ================================================== */
@@ -60,7 +60,9 @@ start_resolving(void *anything)
{
struct DNS_Async_Instance *inst = (struct DNS_Async_Instance *)anything;
pthread_mutex_lock(&privops_lock);
inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES);
pthread_mutex_unlock(&privops_lock);
/* Notify the main thread that the result is ready */
if (write(inst->pipe[1], "", 1) < 0)
@@ -81,8 +83,6 @@ end_resolving(int fd, int event, void *anything)
LOG_FATAL("pthread_join() failed");
}
resolving_threads--;
SCH_RemoveFileHandler(inst->pipe[0]);
close(inst->pipe[0]);
close(inst->pipe[1]);
@@ -116,9 +116,6 @@ DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *
UTI_FdSetCloexec(inst->pipe[0]);
UTI_FdSetCloexec(inst->pipe[1]);
resolving_threads++;
assert(resolving_threads <= 1);
if (pthread_create(&inst->thread, NULL, start_resolving, inst)) {
LOG_FATAL("pthread_create() failed");
}

60
ntp.h
View File

@@ -47,18 +47,21 @@ typedef uint32_t NTP_int32;
/* Maximum stratum number (infinity) */
#define NTP_MAX_STRATUM 16
/* The minimum valid length of an extension field */
#define NTP_MIN_EXTENSION_LENGTH 16
/* The maximum assumed length of all extension fields in received
packets (RFC 5905 doesn't specify a limit on length or number of
extension fields in one packet) */
#define NTP_MAX_EXTENSIONS_LENGTH 1024
/* Invalid stratum number */
#define NTP_INVALID_STRATUM 0
/* The minimum and maximum supported length of MAC */
#define NTP_MIN_MAC_LENGTH (4 + 16)
#define NTP_MAX_MAC_LENGTH (4 + MAX_HASH_LENGTH)
/* The minimum valid length of an extension field */
#define NTP_MIN_EF_LENGTH 16
/* The maximum assumed length of all extension fields in an NTP packet,
including a MAC (RFC 5905 doesn't specify a limit on length or number of
extension fields in one packet) */
#define NTP_MAX_EXTENSIONS_LENGTH (1024 + NTP_MAX_MAC_LENGTH)
/* The maximum length of MAC in NTPv4 packets which allows deterministic
parsing of extension fields (RFC 7822) */
#define NTP_MAX_V4_MAC_LENGTH (4 + 20)
@@ -93,21 +96,10 @@ typedef struct {
NTP_int64 receive_ts;
NTP_int64 transmit_ts;
/* Optional extension fields, we don't send packets with them yet */
/* uint8_t extensions[] */
/* Optional message authentication code (MAC) */
NTP_int32 auth_keyid;
uint8_t auth_data[NTP_MAX_MAC_LENGTH - 4];
uint8_t extensions[NTP_MAX_EXTENSIONS_LENGTH];
} NTP_Packet;
#define NTP_NORMAL_PACKET_LENGTH (int)offsetof(NTP_Packet, auth_keyid)
/* The buffer used to hold a datagram read from the network */
typedef struct {
NTP_Packet ntp_pkt;
uint8_t extensions[NTP_MAX_EXTENSIONS_LENGTH];
} NTP_Receive_Buffer;
#define NTP_HEADER_LENGTH (int)offsetof(NTP_Packet, extensions)
/* Macros to work with the lvm field */
#define NTP_LVM_TO_LEAP(lvm) (((lvm) >> 6) & 0x3)
@@ -121,6 +113,33 @@ typedef struct {
#define NTP_REFID_LOCAL 0x7F7F0101UL /* 127.127.1.1 */
#define NTP_REFID_SMOOTH 0x7F7F01FFUL /* 127.127.1.255 */
/* Enumeration for authentication modes of NTP packets */
typedef enum {
NTP_AUTH_NONE = 0, /* No authentication */
NTP_AUTH_SYMMETRIC, /* MAC using symmetric key (RFC 1305, RFC 5905) */
NTP_AUTH_MSSNTP, /* MS-SNTP authenticator field */
NTP_AUTH_MSSNTP_EXT, /* MS-SNTP extended authenticator field */
NTP_AUTH_NTS, /* Network Time Security (RFC ????) */
} NTP_AuthMode;
/* Structure describing an NTP packet */
typedef struct {
int length;
int version;
NTP_Mode mode;
int ext_fields;
struct {
NTP_AuthMode mode;
struct {
int start;
int length;
uint32_t key_id;
} mac;
} auth;
} NTP_PacketInfo;
/* Structure used to save NTP measurements. time is the local time at which
the sample is to be considered to have been made and offset is the offset at
the time (positive indicates that the local clock is slow relative to the
@@ -133,7 +152,6 @@ typedef struct {
double root_delay;
double root_dispersion;
int stratum;
NTP_Leap leap;
} NTP_Sample;
#endif /* GOT_NTP_H */

500
ntp_auth.c Normal file
View File

@@ -0,0 +1,500 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
NTP authentication
*/
#include "config.h"
#include "sysincl.h"
#include "keys.h"
#include "logging.h"
#include "memory.h"
#include "ntp_auth.h"
#include "ntp_ext.h"
#include "ntp_signd.h"
#include "nts_ntp.h"
#include "nts_ntp_client.h"
#include "nts_ntp_server.h"
#include "srcparams.h"
#include "util.h"
/* Structure to hold authentication configuration and state */
struct NAU_Instance_Record {
NTP_AuthMode mode; /* Authentication mode of NTP packets */
uint32_t key_id; /* Identifier of a symmetric key */
NNC_Instance nts; /* Client NTS state */
};
/* ================================================== */
static int
generate_symmetric_auth(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info)
{
int auth_len, max_auth_len;
/* Truncate long MACs in NTPv4 packets to allow deterministic parsing
of extension fields (RFC 7822) */
max_auth_len = (info->version == 4 ? NTP_MAX_V4_MAC_LENGTH : NTP_MAX_MAC_LENGTH) - 4;
max_auth_len = MIN(max_auth_len, sizeof (NTP_Packet) - info->length - 4);
auth_len = KEY_GenerateAuth(key_id, (unsigned char *)packet, info->length,
(unsigned char *)packet + info->length + 4, max_auth_len);
if (!auth_len) {
DEBUG_LOG("Could not generate auth data with key %"PRIu32, key_id);
return 0;
}
*(uint32_t *)((unsigned char *)packet + info->length) = htonl(key_id);
info->length += 4 + auth_len;
return 1;
}
/* ================================================== */
static int
check_symmetric_auth(NTP_Packet *packet, NTP_PacketInfo *info)
{
int trunc_len;
if (info->auth.mac.length < NTP_MIN_MAC_LENGTH)
return 0;
trunc_len = info->version == 4 && info->auth.mac.length <= NTP_MAX_V4_MAC_LENGTH ?
NTP_MAX_V4_MAC_LENGTH : NTP_MAX_MAC_LENGTH;
if (!KEY_CheckAuth(info->auth.mac.key_id, (void *)packet, info->auth.mac.start,
(unsigned char *)packet + info->auth.mac.start + 4,
info->auth.mac.length - 4, trunc_len - 4))
return 0;
return 1;
}
/* ================================================== */
static void
adjust_timestamp(NTP_AuthMode mode, uint32_t key_id, struct timespec *ts)
{
switch (mode) {
case NTP_AUTH_SYMMETRIC:
ts->tv_nsec += KEY_GetAuthDelay(key_id);
UTI_NormaliseTimespec(ts);
break;
case NTP_AUTH_MSSNTP:
ts->tv_nsec += NSD_GetAuthDelay(key_id);
UTI_NormaliseTimespec(ts);
default:
break;
}
}
/* ================================================== */
static int
is_zero_data(unsigned char *data, int length)
{
int i;
for (i = 0; i < length; i++)
if (data[i])
return 0;
return 1;
}
/* ================================================== */
static NAU_Instance
create_instance(NTP_AuthMode mode)
{
NAU_Instance instance;
instance = MallocNew(struct NAU_Instance_Record);
instance->mode = mode;
instance->key_id = INACTIVE_AUTHKEY;
instance->nts = NULL;
assert(sizeof (instance->key_id) == 4);
return instance;
}
/* ================================================== */
NAU_Instance
NAU_CreateNoneInstance(void)
{
return create_instance(NTP_AUTH_NONE);
}
/* ================================================== */
NAU_Instance
NAU_CreateSymmetricInstance(uint32_t key_id)
{
NAU_Instance instance = create_instance(NTP_AUTH_SYMMETRIC);
instance->key_id = key_id;
if (!KEY_KeyKnown(key_id))
LOG(LOGS_WARN, "Key %"PRIu32" is %s", key_id, "missing");
else if (!KEY_CheckKeyLength(key_id))
LOG(LOGS_WARN, "Key %"PRIu32" is %s", key_id, "too short");
return instance;
}
/* ================================================== */
NAU_Instance
NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr *ntp_address)
{
NAU_Instance instance = create_instance(NTP_AUTH_NTS);
instance->nts = NNC_CreateInstance(nts_address, name, ntp_address);
return instance;
}
/* ================================================== */
void
NAU_DestroyInstance(NAU_Instance instance)
{
if (instance->nts)
NNC_DestroyInstance(instance->nts);
Free(instance);
}
/* ================================================== */
int
NAU_IsAuthEnabled(NAU_Instance instance)
{
return instance->mode != NTP_AUTH_NONE;
}
/* ================================================== */
int
NAU_GetSuggestedNtpVersion(NAU_Instance instance)
{
/* If the MAC in NTPv4 packets would be truncated, prefer NTPv3 for
compatibility with older chronyd servers */
if (instance->mode == NTP_AUTH_SYMMETRIC &&
KEY_GetAuthLength(instance->key_id) + sizeof (instance->key_id) > NTP_MAX_V4_MAC_LENGTH)
return 3;
return NTP_VERSION;
}
/* ================================================== */
int
NAU_PrepareRequestAuth(NAU_Instance instance)
{
switch (instance->mode) {
case NTP_AUTH_NTS:
if (!NNC_PrepareForAuth(instance->nts))
return 0;
break;
default:
break;
}
return 1;
}
/* ================================================== */
void
NAU_AdjustRequestTimestamp(NAU_Instance instance, struct timespec *ts)
{
adjust_timestamp(instance->mode, instance->key_id, ts);
}
/* ================================================== */
int
NAU_GenerateRequestAuth(NAU_Instance instance, NTP_Packet *request, NTP_PacketInfo *info)
{
switch (instance->mode) {
case NTP_AUTH_NONE:
break;
case NTP_AUTH_SYMMETRIC:
if (!generate_symmetric_auth(instance->key_id, request, info))
return 0;
break;
case NTP_AUTH_NTS:
if (!NNC_GenerateRequestAuth(instance->nts, request, info))
return 0;
break;
default:
assert(0);
}
return 1;
}
/* ================================================== */
int
NAU_ParsePacket(NTP_Packet *packet, NTP_PacketInfo *info)
{
int parsed, remainder, ef_length, ef_type;
unsigned char *data;
data = (void *)packet;
parsed = NTP_HEADER_LENGTH;
remainder = info->length - parsed;
info->ext_fields = 0;
/* Check if this is a plain NTP packet with no extension fields or MAC */
if (remainder <= 0)
return 1;
/* In NTPv3 and older packets don't have extension fields. Anything after
the header is assumed to be a MAC. */
if (info->version <= 3) {
info->auth.mode = NTP_AUTH_SYMMETRIC;
info->auth.mac.start = parsed;
info->auth.mac.length = remainder;
info->auth.mac.key_id = ntohl(*(uint32_t *)(data + parsed));
/* Check if it is an MS-SNTP authenticator field or extended authenticator
field with zeroes as digest */
if (info->version == 3 && info->auth.mac.key_id) {
if (remainder == 20 && is_zero_data(data + parsed + 4, remainder - 4))
info->auth.mode = NTP_AUTH_MSSNTP;
else if (remainder == 72 && is_zero_data(data + parsed + 8, remainder - 8))
info->auth.mode = NTP_AUTH_MSSNTP_EXT;
}
return 1;
}
/* Check for a crypto NAK */
if (remainder == 4 && ntohl(*(uint32_t *)(data + parsed)) == 0) {
info->auth.mode = NTP_AUTH_SYMMETRIC;
info->auth.mac.start = parsed;
info->auth.mac.length = remainder;
info->auth.mac.key_id = 0;
return 1;
}
/* Parse the rest of the NTPv4 packet */
while (remainder > 0) {
/* Check if the remaining data is a MAC */
if (remainder >= NTP_MIN_MAC_LENGTH && remainder <= NTP_MAX_V4_MAC_LENGTH)
break;
/* The NTPv4-specific limit for MAC length enables deterministic parsing of
packets with extension fields (RFC 7822), but we support longer MACs in
packets with no extension fields for compatibility with older chrony
clients. Check if the longer MAC would authenticate the packet before
trying to parse the data as an extension field. */
if (parsed == NTP_HEADER_LENGTH &&
remainder > NTP_MAX_V4_MAC_LENGTH && remainder <= NTP_MAX_MAC_LENGTH &&
KEY_CheckAuth(ntohl(*(uint32_t *)(data + parsed)), data, parsed,
(void *)(data + parsed + 4), remainder - 4, NTP_MAX_MAC_LENGTH - 4))
break;
/* Check if this is a valid NTPv4 extension field and skip it */
if (!NEF_ParseField(packet, info->length, parsed, &ef_length, &ef_type, NULL, NULL)) {
/* Invalid MAC or format error */
DEBUG_LOG("Invalid format or MAC");
return 0;
}
assert(ef_length > 0);
switch (ef_type) {
case NTP_EF_NTS_UNIQUE_IDENTIFIER:
case NTP_EF_NTS_COOKIE:
case NTP_EF_NTS_COOKIE_PLACEHOLDER:
case NTP_EF_NTS_AUTH_AND_EEF:
info->auth.mode = NTP_AUTH_NTS;
break;
default:
DEBUG_LOG("Unknown extension field type=%x", (unsigned int)ef_type);
}
info->ext_fields++;
parsed += ef_length;
remainder = info->length - parsed;
}
if (remainder == 0) {
/* No MAC */
return 1;
} else if (remainder >= NTP_MIN_MAC_LENGTH) {
/* This is not 100% reliable as a MAC could fail to authenticate and could
pass as an extension field, leaving reminder smaller than the minimum MAC
length */
info->auth.mode = NTP_AUTH_SYMMETRIC;
info->auth.mac.start = parsed;
info->auth.mac.length = remainder;
info->auth.mac.key_id = ntohl(*(uint32_t *)(data + parsed));
return 1;
}
DEBUG_LOG("Invalid format");
return 0;
}
/* ================================================== */
int
NAU_CheckRequestAuth(NTP_Packet *request, NTP_PacketInfo *info, uint32_t *kod)
{
*kod = 0;
switch (info->auth.mode) {
case NTP_AUTH_NONE:
break;
case NTP_AUTH_SYMMETRIC:
if (!check_symmetric_auth(request, info))
return 0;
break;
case NTP_AUTH_MSSNTP:
/* MS-SNTP requests are not authenticated */
break;
case NTP_AUTH_NTS:
if (!NNS_CheckRequestAuth(request, info, kod))
return 0;
break;
default:
return 0;
}
return 1;
}
/* ================================================== */
void
NAU_AdjustResponseTimestamp(NTP_Packet *request, NTP_PacketInfo *info, struct timespec *ts)
{
adjust_timestamp(info->auth.mode, info->auth.mac.key_id, ts);
}
/* ================================================== */
int
NAU_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *request_info,
NTP_Packet *response, NTP_PacketInfo *response_info,
NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
uint32_t kod)
{
switch (request_info->auth.mode) {
case NTP_AUTH_NONE:
break;
case NTP_AUTH_SYMMETRIC:
if (!generate_symmetric_auth(request_info->auth.mac.key_id, response, response_info))
return 0;
break;
case NTP_AUTH_MSSNTP:
/* Sign the packet asynchronously by ntp_signd */
if (!NSD_SignAndSendPacket(request_info->auth.mac.key_id, response, response_info,
remote_addr, local_addr))
return 0;
/* Don't send the original packet */
return 0;
case NTP_AUTH_NTS:
if (!NNS_GenerateResponseAuth(request, request_info, response, response_info, kod))
return 0;
break;
default:
DEBUG_LOG("Could not authenticate response auth_mode=%d", (int)request_info->auth.mode);
return 0;
}
return 1;
}
/* ================================================== */
int
NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response, NTP_PacketInfo *info)
{
/* The authentication must match the expected mode */
if (info->auth.mode != instance->mode)
return 0;
switch (info->auth.mode) {
case NTP_AUTH_NONE:
break;
case NTP_AUTH_SYMMETRIC:
/* Check if it is authenticated with the specified key */
if (info->auth.mac.key_id != instance->key_id)
return 0;
/* and that the MAC is valid */
if (!check_symmetric_auth(response, info))
return 0;
break;
case NTP_AUTH_NTS:
if (!NNC_CheckResponseAuth(instance->nts, response, info))
return 0;
break;
default:
return 0;
}
return 1;
}
/* ================================================== */
void
NAU_ChangeAddress(NAU_Instance instance, IPAddr *address)
{
switch (instance->mode) {
case NTP_AUTH_NONE:
case NTP_AUTH_SYMMETRIC:
break;
case NTP_AUTH_NTS:
NNC_ChangeAddress(instance->nts, address);
break;
default:
assert(0);
}
}
/* ================================================== */
void
NAU_DumpData(NAU_Instance instance)
{
switch (instance->mode) {
case NTP_AUTH_NTS:
NNC_DumpData(instance->nts);
break;
default:
break;
}
}

92
ntp_auth.h Normal file
View File

@@ -0,0 +1,92 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
Header file for NTP authentication
*/
#ifndef GOT_NTP_AUTH_H
#define GOT_NTP_AUTH_H
#include "addressing.h"
#include "ntp.h"
typedef struct NAU_Instance_Record *NAU_Instance;
/* Create an authenticator instance in a specific mode */
extern NAU_Instance NAU_CreateNoneInstance(void);
extern NAU_Instance NAU_CreateSymmetricInstance(uint32_t key_id);
extern NAU_Instance NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name,
const IPSockAddr *ntp_address);
/* Destroy an instance */
extern void NAU_DestroyInstance(NAU_Instance instance);
/* Check if an instance is not in the None mode */
extern int NAU_IsAuthEnabled(NAU_Instance instance);
/* Get NTP version recommended for better compatibility */
extern int NAU_GetSuggestedNtpVersion(NAU_Instance instance);
/* Perform operations necessary for NAU_GenerateRequestAuth() */
extern int NAU_PrepareRequestAuth(NAU_Instance instance);
/* Adjust a transmit timestamp for an estimated minimum time it takes to call
NAU_GenerateRequestAuth() */
extern void NAU_AdjustRequestTimestamp(NAU_Instance instance, struct timespec *ts);
/* Extend a request with data required by the authentication mode */
extern int NAU_GenerateRequestAuth(NAU_Instance instance, NTP_Packet *request,
NTP_PacketInfo *info);
/* Parse a request or response to detect the authentication mode */
extern int NAU_ParsePacket(NTP_Packet *packet, NTP_PacketInfo *info);
/* Verify that a request is authentic. If it is not authentic and a non-zero
kod code is returned, a KoD response should be sent back. */
extern int NAU_CheckRequestAuth(NTP_Packet *request, NTP_PacketInfo *info, uint32_t *kod);
/* Adjust a transmit timestamp for an estimated minimum time it takes to call
NAU_GenerateResponseAuth() */
extern void NAU_AdjustResponseTimestamp(NTP_Packet *request, NTP_PacketInfo *info,
struct timespec *ts);
/* Extend a response with data required by the authentication mode. This
function can be called only if the previous call of NAU_CheckRequestAuth()
was on the same request. */
extern int NAU_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *request_info,
NTP_Packet *response, NTP_PacketInfo *response_info,
NTP_Remote_Address *remote_addr,
NTP_Local_Address *local_addr,
uint32_t kod);
/* Verify that a response is authentic */
extern int NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response,
NTP_PacketInfo *info);
/* Change an authentication-specific address (e.g. after replacing a source) */
extern void NAU_ChangeAddress(NAU_Instance instance, IPAddr *address);
/* Save authentication-specific data to speed up the next start */
extern void NAU_DumpData(NAU_Instance instance);
#endif

View File

@@ -30,9 +30,9 @@
#include "sysincl.h"
#include "array.h"
#include "ntp_auth.h"
#include "ntp_core.h"
#include "ntp_io.h"
#include "ntp_signd.h"
#include "memory.h"
#include "sched.h"
#include "reference.h"
@@ -43,7 +43,6 @@
#include "util.h"
#include "conf.h"
#include "logging.h"
#include "keys.h"
#include "addrfilt.h"
#include "clientlog.h"
@@ -63,16 +62,6 @@ typedef enum {
MD_BURST_WAS_ONLINE, /* Burst sampling, return to online afterwards */
} OperatingMode;
/* ================================================== */
/* Enumeration for authentication modes of NTP packets */
typedef enum {
AUTH_NONE = 0, /* No authentication */
AUTH_SYMMETRIC, /* MAC using symmetric key (RFC 1305, RFC 5905) */
AUTH_MSSNTP, /* MS-SNTP authenticator field */
AUTH_MSSNTP_EXT, /* MS-SNTP extended authenticator field */
} AuthenticationMode;
/* ================================================== */
/* Structure used for holding a single peer/server's
protocol machine */
@@ -137,9 +126,7 @@ struct NCR_Instance_Record {
double offset_correction; /* Correction applied to measured offset
(e.g. for asymmetry in network delay) */
AuthenticationMode auth_mode; /* Authentication mode of our requests */
uint32_t auth_key_id; /* The ID of the authentication key to
use. */
NAU_Instance auth; /* Authentication */
/* Count of transmitted packets since last valid response */
unsigned int tx_count;
@@ -209,6 +196,7 @@ struct NCR_Instance_Record {
typedef struct {
NTP_Remote_Address addr;
NTP_Local_Address local_addr;
NAU_Instance auth;
int interval;
} BroadcastDestination;
@@ -253,9 +241,6 @@ static ARR_Instance broadcasts;
/* Maximum allowed dispersion - as defined in RFC 5905 (16 seconds) */
#define NTP_MAX_DISPERSION 16.0
/* Invalid stratum number */
#define NTP_INVALID_STRATUM 0
/* Maximum allowed time for server to process client packet */
#define MAX_SERVER_INTERVAL 4.0
@@ -311,6 +296,7 @@ static const char tss_chars[3] = {'D', 'K', 'H'};
static void transmit_timeout(void *arg);
static double get_transmit_delay(NCR_Instance inst, int on_tx, double last_tx);
static double get_separation(int poll);
static int parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info);
/* ================================================== */
@@ -415,8 +401,10 @@ NCR_Finalise(void)
if (server_sock_fd6 != INVALID_SOCK_FD)
NIO_CloseServerSocket(server_sock_fd6);
for (i = 0; i < ARR_GetSize(broadcasts); i++)
for (i = 0; i < ARR_GetSize(broadcasts); i++) {
NIO_CloseServerSocket(((BroadcastDestination *)ARR_GetElement(broadcasts, i))->local_addr.sock_fd);
NAU_DestroyInstance(((BroadcastDestination *)ARR_GetElement(broadcasts, i))->auth);
}
ARR_DestroyInstance(broadcasts);
ADF_DestroyTable(access_auth_table);
@@ -513,7 +501,8 @@ take_offline(NCR_Instance inst)
/* ================================================== */
NCR_Instance
NCR_GetInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SourceParameters *params)
NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
SourceParameters *params, const char *name)
{
NCR_Instance result;
@@ -570,30 +559,24 @@ NCR_GetInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SourcePar
result->auto_offline = params->auto_offline;
result->poll_target = params->poll_target;
result->version = NTP_VERSION;
if (params->nts) {
IPSockAddr nts_address;
if (params->authkey == INACTIVE_AUTHKEY) {
result->auth_mode = AUTH_NONE;
result->auth_key_id = 0;
if (result->mode == MODE_ACTIVE)
LOG(LOGS_WARN, "NTS not supported with peers");
nts_address.ip_addr = remote_addr->ip_addr;
nts_address.port = params->nts_port;
result->auth = NAU_CreateNtsInstance(&nts_address, name, &result->remote_addr);
} else if (params->authkey != INACTIVE_AUTHKEY) {
result->auth = NAU_CreateSymmetricInstance(params->authkey);
} else {
result->auth_mode = AUTH_SYMMETRIC;
result->auth_key_id = params->authkey;
if (!KEY_KeyKnown(result->auth_key_id)) {
LOG(LOGS_WARN, "Key %"PRIu32" used by source %s is %s",
result->auth_key_id, UTI_IPToString(&result->remote_addr.ip_addr),
"missing");
} else if (!KEY_CheckKeyLength(result->auth_key_id)) {
LOG(LOGS_WARN, "Key %"PRIu32" used by source %s is %s",
result->auth_key_id, UTI_IPToString(&result->remote_addr.ip_addr),
"too short");
}
/* If the MAC in NTPv4 packets would be truncated, use version 3 by
default for compatibility with older chronyd servers */
if (KEY_GetAuthLength(result->auth_key_id) + 4 > NTP_MAX_V4_MAC_LENGTH)
result->version = 3;
result->auth = NAU_CreateNoneInstance();
}
result->version = NAU_GetSuggestedNtpVersion(result->auth);
if (params->version)
result->version = CLAMP(NTP_MIN_COMPAT_VERSION, params->version, NTP_VERSION);
@@ -647,6 +630,8 @@ NCR_DestroyInstance(NCR_Instance instance)
if (instance->filter)
SPF_DestroyInstance(instance->filter);
NAU_DestroyInstance(instance->auth);
/* This will destroy the source instance inside the
structure, which will cause reselection if this was the
synchronising source etc. */
@@ -714,10 +699,15 @@ NCR_ResetPoll(NCR_Instance instance)
/* ================================================== */
void
NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr)
NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only)
{
memset(&inst->report, 0, sizeof (inst->report));
NCR_ResetInstance(inst);
/* Update the authentication-specific address before NTP address */
if (!ntp_only)
NAU_ChangeAddress(inst->auth, &remote_addr->ip_addr);
inst->remote_addr = *remote_addr;
if (inst->mode == MODE_CLIENT)
@@ -914,8 +904,7 @@ receive_timeout(void *arg)
{
NCR_Instance inst = (NCR_Instance)arg;
DEBUG_LOG("Receive timeout for [%s:%d]",
UTI_IPToString(&inst->remote_addr.ip_addr), inst->remote_addr.port);
DEBUG_LOG("Receive timeout for %s", UTI_IPSockAddrToString(&inst->remote_addr));
inst->rx_timeout_id = 0;
close_client_socket(inst);
@@ -928,8 +917,8 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
int interleaved, /* Flag enabling interleaved mode */
int my_poll, /* The log2 of the local poll interval */
int version, /* The NTP version to be set in the packet */
int auth_mode, /* The authentication mode */
uint32_t key_id, /* The authentication key ID */
uint32_t kod, /* KoD code - 0 disabled */
NAU_Instance auth, /* The authentication to be used for the packet */
NTP_int64 *remote_ntp_rx, /* The receive timestamp from received packet */
NTP_int64 *remote_ntp_tx, /* The transmit timestamp from received packet */
NTP_Local_Timestamp *local_rx, /* The RX time of the received packet */
@@ -940,13 +929,16 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
NTP_int64 *local_ntp_tx, /* The transmit timestamp from the previous packet
RESULT : transmit timestamp from this packet */
NTP_Remote_Address *where_to, /* Where to address the reponse to */
NTP_Local_Address *from /* From what address to send it */
NTP_Local_Address *from, /* From what address to send it */
NTP_Packet *request, /* The received packet if responding */
NTP_PacketInfo *request_info /* and its info */
)
{
NTP_PacketInfo info;
NTP_Packet message;
int auth_len, max_auth_len, length, ret, precision;
struct timespec local_receive, local_transmit;
double smooth_offset, local_transmit_err;
int ret, precision;
NTP_int64 ts_fuzz;
/* Parameters read from reference module */
@@ -956,6 +948,8 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
struct timespec our_ref_time;
double our_root_delay, our_root_dispersion;
assert(auth || (request && request_info));
/* Don't reply with version higher than ours */
if (version > NTP_VERSION) {
version = NTP_VERSION;
@@ -1007,6 +1001,12 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
local_receive = local_rx->ts;
}
if (kod != 0) {
leap_status = LEAP_Unsynchronised;
our_stratum = NTP_INVALID_STRATUM;
our_ref_id = kod;
}
/* Generate transmit packet */
message.lvm = NTP_LVM(leap_status, version, my_mode);
/* Stratum 16 and larger are invalid */
@@ -1057,6 +1057,9 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
}
do {
if (!parse_packet(&message, NTP_HEADER_LENGTH, &info))
return 0;
/* Prepare random bits which will be added to the transmit timestamp */
UTI_GetNtp64Fuzz(&ts_fuzz, precision);
@@ -1068,42 +1071,28 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
if (smooth_time)
UTI_AddDoubleToTimespec(&local_transmit, smooth_offset, &local_transmit);
length = NTP_NORMAL_PACKET_LENGTH;
/* Pre-compensate the transmit time by approximately how long it will take
to generate the authentication data */
if (auth)
NAU_AdjustRequestTimestamp(auth, &local_transmit);
else
NAU_AdjustResponseTimestamp(request, request_info, &local_transmit);
/* Authenticate the packet */
UTI_TimespecToNtp64(interleaved ? &local_tx->ts : &local_transmit,
&message.transmit_ts, &ts_fuzz);
if (auth_mode == AUTH_SYMMETRIC || auth_mode == AUTH_MSSNTP) {
/* Pre-compensate the transmit time by approximately how long it will
take to generate the authentication data */
local_transmit.tv_nsec += auth_mode == AUTH_SYMMETRIC ?
KEY_GetAuthDelay(key_id) : NSD_GetAuthDelay(key_id);
UTI_NormaliseTimespec(&local_transmit);
UTI_TimespecToNtp64(interleaved ? &local_tx->ts : &local_transmit,
&message.transmit_ts, &ts_fuzz);
if (auth_mode == AUTH_SYMMETRIC) {
/* Truncate long MACs in NTPv4 packets to allow deterministic parsing
of extension fields (RFC 7822) */
max_auth_len = version == 4 ?
NTP_MAX_V4_MAC_LENGTH - 4 : sizeof (message.auth_data);
auth_len = KEY_GenerateAuth(key_id, (unsigned char *) &message,
offsetof(NTP_Packet, auth_keyid),
(unsigned char *)&message.auth_data, max_auth_len);
if (!auth_len) {
DEBUG_LOG("Could not generate auth data with key %"PRIu32, key_id);
return 0;
}
message.auth_keyid = htonl(key_id);
length += sizeof (message.auth_keyid) + auth_len;
} else if (auth_mode == AUTH_MSSNTP) {
/* MS-SNTP packets are signed (asynchronously) by ntp_signd */
return NSD_SignAndSendPacket(key_id, &message, where_to, from, length);
/* Generate the authentication data */
if (auth) {
if (!NAU_GenerateRequestAuth(auth, &message, &info)) {
DEBUG_LOG("Could not generate request auth");
return 0;
}
} else {
UTI_TimespecToNtp64(interleaved ? &local_tx->ts : &local_transmit,
&message.transmit_ts, &ts_fuzz);
if (!NAU_GenerateResponseAuth(request, request_info, &message, &info,
where_to, from, kod)) {
DEBUG_LOG("Could not generate response auth");
return 0;
}
}
/* Do not send a packet with a non-zero transmit timestamp which is
@@ -1117,7 +1106,13 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
UTI_IsEqualAnyNtp64(&message.transmit_ts, &message.receive_ts,
&message.originate_ts, local_ntp_tx));
ret = NIO_SendPacket(&message, where_to, from, length, local_tx != NULL);
if (request_info && request_info->length < info.length) {
DEBUG_LOG("Response longer than request req_len=%d res_len=%d",
request_info->length, info.length);
return 0;
}
ret = NIO_SendPacket(&message, where_to, from, info.length, local_tx != NULL);
if (local_tx) {
local_tx->ts = local_transmit;
@@ -1172,8 +1167,17 @@ transmit_timeout(void *arg)
return;
}
DEBUG_LOG("Transmit timeout for [%s:%d]",
UTI_IPToString(&inst->remote_addr.ip_addr), inst->remote_addr.port);
DEBUG_LOG("Transmit timeout for %s", UTI_IPSockAddrToString(&inst->remote_addr));
/* Prepare authentication */
if (!NAU_PrepareRequestAuth(inst->auth)) {
if (inst->burst_total_samples_to_go > 0)
inst->burst_total_samples_to_go--;
adjust_poll(inst, 0.25);
SRC_UpdateReachability(inst->source, 0);
restart_timeout(inst, get_transmit_delay(inst, 1, 0.0));
return;
}
/* Open new client socket */
if (inst->mode == MODE_CLIENT) {
@@ -1225,13 +1229,13 @@ transmit_timeout(void *arg)
}
/* Send the request (which may also be a response in the symmetric mode) */
sent = transmit_packet(inst->mode, interleaved, inst->local_poll, inst->version,
inst->auth_mode, inst->auth_key_id,
sent = transmit_packet(inst->mode, interleaved, inst->local_poll, inst->version, 0,
inst->auth,
initial ? NULL : &inst->remote_ntp_rx,
initial ? &inst->init_remote_ntp_tx : &inst->remote_ntp_tx,
initial ? &inst->init_local_rx : &inst->local_rx,
&inst->local_tx, &inst->local_ntp_rx, &inst->local_ntp_tx,
&inst->remote_addr, &local_addr);
&inst->remote_addr, &local_addr, NULL, NULL);
++inst->tx_count;
if (sent)
@@ -1284,123 +1288,29 @@ transmit_timeout(void *arg)
/* ================================================== */
static int
check_packet_format(NTP_Packet *message, int length)
parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info)
{
int version;
/* Check version and length */
version = NTP_LVM_TO_VERSION(message->lvm);
if (version < NTP_MIN_COMPAT_VERSION || version > NTP_MAX_COMPAT_VERSION) {
DEBUG_LOG("NTP packet has invalid version %d", version);
return 0;
}
if (length < NTP_NORMAL_PACKET_LENGTH || (unsigned int)length % 4) {
if (length < NTP_HEADER_LENGTH || length % 4U != 0) {
DEBUG_LOG("NTP packet has invalid length %d", length);
return 0;
}
/* We can't reliably check the packet for invalid extension fields as we
support MACs longer than the shortest valid extension field */
info->length = length;
info->version = NTP_LVM_TO_VERSION(packet->lvm);
info->mode = NTP_LVM_TO_MODE(packet->lvm);
info->ext_fields = 0;
info->auth.mode = NTP_AUTH_NONE;
return 1;
}
/* ================================================== */
static int
is_zero_data(unsigned char *data, int length)
{
int i;
for (i = 0; i < length; i++)
if (data[i])
return 0;
return 1;
}
/* ================================================== */
static int
check_packet_auth(NTP_Packet *pkt, int length,
AuthenticationMode *auth_mode, uint32_t *key_id)
{
int i, version, remainder, ext_length, max_mac_length;
unsigned char *data;
uint32_t id;
/* Go through extension fields and see if there is a valid MAC */
version = NTP_LVM_TO_VERSION(pkt->lvm);
i = NTP_NORMAL_PACKET_LENGTH;
data = (void *)pkt;
while (1) {
remainder = length - i;
/* Check if the remaining data is a valid MAC. There is a limit on MAC
length in NTPv4 packets to allow deterministic parsing of extension
fields (RFC 7822), but we need to support longer MACs to not break
compatibility with older chrony clients. This needs to be done before
trying to parse the data as an extension field. */
max_mac_length = version == 4 && remainder <= NTP_MAX_V4_MAC_LENGTH ?
NTP_MAX_V4_MAC_LENGTH : NTP_MAX_MAC_LENGTH;
if (remainder >= NTP_MIN_MAC_LENGTH && remainder <= max_mac_length) {
id = ntohl(*(uint32_t *)(data + i));
if (KEY_CheckAuth(id, (void *)pkt, i, (void *)(data + i + 4),
remainder - 4, max_mac_length - 4)) {
*auth_mode = AUTH_SYMMETRIC;
*key_id = id;
/* If it's an NTPv4 packet with long MAC and no extension fields,
rewrite the version in the packet to respond with long MAC too */
if (version == 4 && NTP_NORMAL_PACKET_LENGTH + remainder == length &&
remainder > NTP_MAX_V4_MAC_LENGTH)
pkt->lvm = NTP_LVM(NTP_LVM_TO_LEAP(pkt->lvm), 3, NTP_LVM_TO_MODE(pkt->lvm));
return 1;
}
}
/* Check if this is a valid NTPv4 extension field and skip it. It should
have a 16-bit type, 16-bit length, and data padded to 32 bits. */
if (version == 4 && remainder >= NTP_MIN_EXTENSION_LENGTH) {
ext_length = ntohs(*(uint16_t *)(data + i + 2));
if (ext_length >= NTP_MIN_EXTENSION_LENGTH &&
ext_length <= remainder && ext_length % 4 == 0) {
i += ext_length;
continue;
}
}
/* Invalid or missing MAC, or format error */
break;
if (info->version < NTP_MIN_COMPAT_VERSION || info->version > NTP_MAX_COMPAT_VERSION) {
DEBUG_LOG("NTP packet has invalid version %d", info->version);
return 0;
}
/* This is not 100% reliable as a MAC could fail to authenticate and could
pass as an extension field, leaving reminder smaller than the minimum MAC
length */
if (remainder >= NTP_MIN_MAC_LENGTH) {
*auth_mode = AUTH_SYMMETRIC;
*key_id = ntohl(*(uint32_t *)(data + i));
/* Parse authentication extension fields or MAC */
if (!NAU_ParsePacket(packet, info))
return 0;
/* Check if it is an MS-SNTP authenticator field or extended authenticator
field with zeroes as digest */
if (version == 3 && *key_id) {
if (remainder == 20 && is_zero_data(data + i + 4, remainder - 4))
*auth_mode = AUTH_MSSNTP;
else if (remainder == 72 && is_zero_data(data + i + 8, remainder - 8))
*auth_mode = AUTH_MSSNTP_EXT;
}
} else {
*auth_mode = AUTH_NONE;
*key_id = 0;
}
return 0;
return 1;
}
/* ================================================== */
@@ -1466,6 +1376,52 @@ check_delay_dev_ratio(NCR_Instance inst, SST_Stats stats,
/* ================================================== */
static int
check_sync_loop(NCR_Instance inst, NTP_Packet *message, NTP_Local_Address *local_addr,
struct timespec *local_ts)
{
double our_root_delay, our_root_dispersion;
int are_we_synchronised, our_stratum;
struct timespec our_ref_time;
NTP_Leap leap_status;
uint32_t our_ref_id;
/* Check if a server socket is open, i.e. a client or peer can actually
be synchronised to us */
if (!NIO_IsServerSocketOpen())
return 1;
/* Check if the source indicates that it is synchronised to our address
(assuming it uses the same address as the one from which we send requests
to the source) */
if (message->stratum > 1 &&
message->reference_id == htonl(UTI_IPToRefid(&local_addr->ip_addr)))
return 0;
/* Compare our reference data with the source to make sure it is not us
(e.g. due to a misconfiguration) */
REF_GetReferenceParams(local_ts, &are_we_synchronised, &leap_status, &our_stratum,
&our_ref_id, &our_ref_time, &our_root_delay, &our_root_dispersion);
if (message->stratum == our_stratum &&
message->reference_id == htonl(our_ref_id) &&
message->root_delay == UTI_DoubleToNtp32(our_root_delay) &&
!UTI_IsZeroNtp64(&message->reference_ts)) {
NTP_int64 ntp_ref_time;
UTI_TimespecToNtp64(&our_ref_time, &ntp_ref_time, NULL);
if (UTI_CompareNtp64(&message->reference_ts, &ntp_ref_time) == 0) {
DEBUG_LOG("Source %s is me", UTI_IPToString(&inst->remote_addr.ip_addr));
return 0;
}
}
return 1;
}
/* ================================================== */
static void
process_sample(NCR_Instance inst, NTP_Sample *sample)
{
@@ -1513,17 +1469,16 @@ process_sample(NCR_Instance inst, NTP_Sample *sample)
/* ================================================== */
static int
receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length)
process_response(NCR_Instance inst, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *rx_ts, NTP_Packet *message, NTP_PacketInfo *info)
{
NTP_Sample sample;
SST_Stats stats;
int pkt_leap, pkt_version;
uint32_t pkt_refid, pkt_key_id;
uint32_t pkt_refid;
double pkt_root_delay;
double pkt_root_dispersion;
AuthenticationMode pkt_auth_mode;
/* The skew and estimated frequency offset relative to the remote source */
double skew, source_freq_lo, source_freq_hi;
@@ -1548,8 +1503,6 @@ receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
stats = SRC_GetSourcestats(inst->source);
inst->report.total_rx_count++;
pkt_leap = NTP_LVM_TO_LEAP(message->lvm);
pkt_version = NTP_LVM_TO_VERSION(message->lvm);
pkt_refid = ntohl(message->reference_id);
@@ -1580,14 +1533,8 @@ receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
/* Test 4 would check for denied access. It would always pass as this
function is called only for known sources. */
/* Test 5 checks for authentication failure. If we expect authenticated info
from this peer/server and the packet doesn't have it, the authentication
is bad, or it's authenticated with a different key than expected, it's got
to fail. If we don't expect the packet to be authenticated, just ignore
the test. */
test5 = inst->auth_mode == AUTH_NONE ||
(check_packet_auth(message, length, &pkt_auth_mode, &pkt_key_id) &&
pkt_auth_mode == inst->auth_mode && pkt_key_id == inst->auth_key_id);
/* Test 5 checks for authentication failure */
test5 = NAU_CheckResponseAuth(inst->auth, message, info);
/* Test 6 checks for unsynchronised server */
test6 = pkt_leap != LEAP_Unsynchronised &&
@@ -1718,10 +1665,9 @@ receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
the increase in delay */
testC = check_delay_dev_ratio(inst, stats, &sample.time, sample.offset, sample.peer_delay);
/* Test D requires that the remote peer is not synchronised to us to
prevent a synchronisation loop */
testD = message->stratum <= 1 || REF_GetMode() != REF_ModeNormal ||
pkt_refid != UTI_IPToRefid(&local_addr->ip_addr);
/* Test D requires that the source is not synchronised to us and is not us
to prevent a synchronisation loop */
testD = check_sync_loop(inst, message, local_addr, &rx_ts->ts);
} else {
remote_interval = local_interval = response_time = 0.0;
sample.offset = sample.peer_delay = sample.peer_dispersion = 0.0;
@@ -1738,7 +1684,6 @@ receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
sample.root_delay = pkt_root_delay + sample.peer_delay;
sample.root_dispersion = pkt_root_dispersion + sample.peer_dispersion;
sample.stratum = MAX(message->stratum, inst->min_stratum);
sample.leap = (NTP_Leap)pkt_leap;
/* Update the NTP timestamps. If it's a valid packet from a synchronised
source, the timestamps may be used later when processing a packet in the
@@ -1828,6 +1773,7 @@ receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
inst->tx_count = 0;
SRC_UpdateReachability(inst->source, synced_packet);
SRC_SetLeapStatus(inst->source, pkt_leap);
if (good_packet) {
/* Adjust the polling interval, accumulate the sample, etc. */
@@ -1907,7 +1853,7 @@ receive_packet(NCR_Instance inst, NTP_Local_Address *local_addr,
test5) << 1 | test6) << 1 | test7) << 1 |
testA) << 1 | testB) << 1 | testC) << 1 | testD;
inst->report.interleaved = interleaved_packet;
inst->report.authenticated = inst->auth_mode != AUTH_NONE;
inst->report.authenticated = NAU_IsAuthEnabled(inst->auth);
inst->report.tx_tss_char = tss_chars[local_transmit.source];
inst->report.rx_tss_char = tss_chars[local_receive.source];
@@ -1967,17 +1913,19 @@ int
NCR_ProcessRxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length)
{
int pkt_mode, proc_packet, proc_as_unknown;
int proc_packet, proc_as_unknown;
NTP_PacketInfo info;
if (!check_packet_format(message, length))
inst->report.total_rx_count++;
if (!parse_packet(message, length, &info))
return 0;
pkt_mode = NTP_LVM_TO_MODE(message->lvm);
proc_packet = 0;
proc_as_unknown = 0;
/* Now, depending on the mode we decide what to do */
switch (pkt_mode) {
switch (info.mode) {
case MODE_ACTIVE:
switch (inst->mode) {
case MODE_ACTIVE:
@@ -2067,13 +2015,13 @@ NCR_ProcessRxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
return 0;
}
return receive_packet(inst, local_addr, rx_ts, message, length);
return process_response(inst, local_addr, rx_ts, message, &info);
} else if (proc_as_unknown) {
NCR_ProcessRxUnknown(&inst->remote_addr, local_addr, rx_ts, message, length);
/* It's not a reply to our request, don't return success */
return 0;
} else {
DEBUG_LOG("NTP packet discarded pkt_mode=%d our_mode=%u", pkt_mode, inst->mode);
DEBUG_LOG("NTP packet discarded mode=%d our_mode=%u", (int)info.mode, inst->mode);
return 0;
}
}
@@ -2086,12 +2034,12 @@ void
NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length)
{
NTP_Mode pkt_mode, my_mode;
NTP_PacketInfo info;
NTP_Mode my_mode;
NTP_int64 *local_ntp_rx, *local_ntp_tx;
NTP_Local_Timestamp local_tx, *tx_ts;
int pkt_version, valid_auth, log_index, interleaved, poll;
AuthenticationMode auth_mode;
uint32_t key_id;
int log_index, interleaved, poll, version;
uint32_t kod;
/* Ignore the packet if it wasn't received by server socket */
if (!NIO_IsServerSocket(local_addr->sock_fd)) {
@@ -2099,20 +2047,16 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
return;
}
if (!check_packet_format(message, length))
if (!parse_packet(message, length, &info))
return;
if (!ADF_IsAllowed(access_auth_table, &remote_addr->ip_addr)) {
DEBUG_LOG("NTP packet received from unauthorised host %s port %d",
UTI_IPToString(&remote_addr->ip_addr),
remote_addr->port);
DEBUG_LOG("NTP packet received from unauthorised host %s",
UTI_IPToString(&remote_addr->ip_addr));
return;
}
pkt_mode = NTP_LVM_TO_MODE(message->lvm);
pkt_version = NTP_LVM_TO_VERSION(message->lvm);
switch (pkt_mode) {
switch (info.mode) {
case MODE_ACTIVE:
/* We are symmetric passive, even though we don't ever lock to him */
my_mode = MODE_PASSIVE;
@@ -2125,17 +2069,18 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
/* Check if it is an NTPv1 client request (NTPv1 packets have a reserved
field instead of the mode field and the actual mode is determined from
the port numbers). Don't ever respond with a mode 0 packet! */
if (pkt_version == 1 && remote_addr->port != NTP_PORT) {
if (info.version == 1 && remote_addr->port != NTP_PORT) {
my_mode = MODE_SERVER;
break;
}
/* Fall through */
default:
/* Discard */
DEBUG_LOG("NTP packet discarded pkt_mode=%u", pkt_mode);
DEBUG_LOG("NTP packet discarded mode=%d", (int)info.mode);
return;
}
kod = 0;
log_index = CLG_LogNTPAccess(&remote_addr->ip_addr, &rx_ts->ts);
/* Don't reply to all requests if the rate is excessive */
@@ -2144,25 +2089,24 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
return;
}
/* Check if the packet includes MAC that authenticates properly */
valid_auth = check_packet_auth(message, length, &auth_mode, &key_id);
/* Check authentication */
if (!NAU_CheckRequestAuth(message, &info, &kod)) {
DEBUG_LOG("NTP packet failed auth mode=%d kod=%"PRIx32, (int)info.auth.mode, kod);
/* If authentication failed, select whether and how we should respond */
if (!valid_auth) {
switch (auth_mode) {
case AUTH_NONE:
/* Reply with no MAC */
break;
case AUTH_MSSNTP:
/* Ignore the failure (MS-SNTP servers don't check client MAC) */
break;
default:
/* Discard packets in other modes */
DEBUG_LOG("NTP packet discarded auth_mode=%u", auth_mode);
return;
}
/* Don't respond unless a non-zero KoD was returned */
if (kod == 0)
return;
}
/* If it is an NTPv4 packet with a long MAC and no extension fields,
respond with a NTPv3 packet to avoid breaking RFC 7822 and keep
the length symmetric. Otherwise, respond with the same version. */
if (info.version == 4 && info.ext_fields == 0 && info.auth.mode == NTP_AUTH_SYMMETRIC &&
info.auth.mac.length > NTP_MAX_V4_MAC_LENGTH)
version = 3;
else
version = info.version;
local_ntp_rx = local_ntp_tx = NULL;
tx_ts = NULL;
interleaved = 0;
@@ -2172,7 +2116,7 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
in the interleaved mode. This means the third reply to a new client is
the earliest one that can be interleaved. We don't want to waste time
on clients that are not using the interleaved mode. */
if (log_index >= 0) {
if (kod == 0 && log_index >= 0) {
CLG_GetNtpTimestamps(log_index, &local_ntp_rx, &local_ntp_tx);
interleaved = !UTI_IsZeroNtp64(local_ntp_rx) &&
!UTI_CompareNtp64(&message->originate_ts, local_ntp_rx) &&
@@ -2193,9 +2137,10 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
poll = MAX(poll, message->poll);
/* Send a reply */
transmit_packet(my_mode, interleaved, poll, pkt_version,
auth_mode, key_id, &message->receive_ts, &message->transmit_ts,
rx_ts, tx_ts, local_ntp_rx, NULL, remote_addr, local_addr);
transmit_packet(my_mode, interleaved, poll, version, kod, NULL,
&message->receive_ts, &message->transmit_ts,
rx_ts, tx_ts, local_ntp_rx, NULL, remote_addr, local_addr,
message, &info);
/* Save the transmit timestamp */
if (tx_ts)
@@ -2240,15 +2185,13 @@ void
NCR_ProcessTxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length)
{
NTP_Mode pkt_mode;
NTP_PacketInfo info;
if (!check_packet_format(message, length))
if (!parse_packet(message, length, &info))
return;
pkt_mode = NTP_LVM_TO_MODE(message->lvm);
/* Server and passive mode packets are responses to unknown sources */
if (pkt_mode != MODE_CLIENT && pkt_mode != MODE_ACTIVE) {
if (info.mode != MODE_CLIENT && info.mode != MODE_ACTIVE) {
NCR_ProcessTxUnknown(&inst->remote_addr, local_addr, tx_ts, message, length);
return;
}
@@ -2265,19 +2208,20 @@ NCR_ProcessTxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
{
NTP_int64 *local_ntp_rx, *local_ntp_tx;
NTP_Local_Timestamp local_tx;
NTP_PacketInfo info;
int log_index;
if (!check_packet_format(message, length))
if (!parse_packet(message, length, &info))
return;
if (NTP_LVM_TO_MODE(message->lvm) == MODE_BROADCAST)
if (info.mode == MODE_BROADCAST)
return;
log_index = CLG_GetClientIndex(&remote_addr->ip_addr);
if (log_index < 0)
return;
if (SMT_IsEnabled() && NTP_LVM_TO_MODE(message->lvm) == MODE_SERVER)
if (SMT_IsEnabled() && info.mode == MODE_SERVER)
UTI_AddDoubleToTimespec(&tx_ts->ts, SMT_GetOffset(&tx_ts->ts), &tx_ts->ts);
CLG_GetNtpTimestamps(log_index, &local_ntp_rx, &local_ntp_tx);
@@ -2618,6 +2562,14 @@ int NCR_IsSyncPeer(NCR_Instance inst)
/* ================================================== */
void
NCR_DumpAuthData(NCR_Instance inst)
{
NAU_DumpData(inst->auth);
}
/* ================================================== */
static void
broadcast_timeout(void *arg)
{
@@ -2632,8 +2584,9 @@ broadcast_timeout(void *arg)
UTI_ZeroNtp64(&orig_ts);
zero_local_timestamp(&recv_ts);
transmit_packet(MODE_BROADCAST, 0, poll, NTP_VERSION, 0, 0, &orig_ts, &orig_ts, &recv_ts,
NULL, NULL, NULL, &destination->addr, &destination->local_addr);
transmit_packet(MODE_BROADCAST, 0, poll, NTP_VERSION, 0, destination->auth,
&orig_ts, &orig_ts, &recv_ts, NULL, NULL, NULL,
&destination->addr, &destination->local_addr, NULL, NULL);
/* Requeue timeout. We don't care if interval drifts gradually. */
SCH_AddTimeoutInClass(destination->interval, get_separation(poll), SAMPLING_RANDOMNESS,
@@ -2654,6 +2607,7 @@ NCR_AddBroadcastDestination(IPAddr *addr, unsigned short port, int interval)
destination->local_addr.ip_addr.family = IPADDR_UNSPEC;
destination->local_addr.if_index = INVALID_IF_INDEX;
destination->local_addr.sock_fd = NIO_OpenServerSocket(&destination->addr);
destination->auth = NAU_CreateNoneInstance();
destination->interval = CLAMP(1, interval, 1 << MAX_POLL);
SCH_AddTimeoutInClass(destination->interval, MAX_SAMPLING_SEPARATION, SAMPLING_RANDOMNESS,

View File

@@ -59,7 +59,8 @@ extern void NCR_Initialise(void);
extern void NCR_Finalise(void);
/* Get a new instance for a server or peer */
extern NCR_Instance NCR_GetInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SourceParameters *params);
extern NCR_Instance NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
SourceParameters *params, const char *name);
/* Destroy an instance */
extern void NCR_DestroyInstance(NCR_Instance instance);
@@ -74,7 +75,8 @@ extern void NCR_ResetInstance(NCR_Instance inst);
extern void NCR_ResetPoll(NCR_Instance instance);
/* Change the remote address of an instance */
extern void NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr);
extern void NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr,
int ntp_only);
/* This routine is called when a new packet arrives off the network,
and it relates to a source we have an ongoing protocol exchange with */
@@ -134,6 +136,8 @@ extern uint32_t NCR_GetLocalRefid(NCR_Instance inst);
extern int NCR_IsSyncPeer(NCR_Instance instance);
extern void NCR_DumpAuthData(NCR_Instance inst);
extern void NCR_AddBroadcastDestination(IPAddr *addr, unsigned short port, int interval);
#endif /* GOT_NTP_CORE_H */

192
ntp_ext.c Normal file
View File

@@ -0,0 +1,192 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
Functions for adding and parsing NTPv4 extension fields
*/
#include "config.h"
#include "sysincl.h"
#include "ntp_ext.h"
struct ExtFieldHeader {
uint16_t type;
uint16_t length;
};
/* ================================================== */
static int
format_field(unsigned char *buffer, int buffer_length, int start,
int type, int body_length, int *length, void **body)
{
struct ExtFieldHeader *header;
if (buffer_length < 0 || start < 0 || buffer_length <= start ||
buffer_length - start < sizeof (*header) || start % 4 != 0)
return 0;
header = (struct ExtFieldHeader *)(buffer + start);
if (body_length < 0 || sizeof (*header) + body_length > 0xffff ||
start + sizeof (*header) + body_length > buffer_length || body_length % 4 != 0)
return 0;
header->type = htons(type);
header->length = htons(sizeof (*header) + body_length);
*length = sizeof (*header) + body_length;
*body = header + 1;
return 1;
}
/* ================================================== */
int
NEF_SetField(unsigned char *buffer, int buffer_length, int start,
int type, void *body, int body_length, int *length)
{
void *ef_body;
if (!format_field(buffer, buffer_length, start, type, body_length, length, &ef_body))
return 0;
memcpy(ef_body, body, body_length);
return 1;
}
/* ================================================== */
int
NEF_AddBlankField(NTP_Packet *packet, NTP_PacketInfo *info, int type, int body_length, void **body)
{
int ef_length, length = info->length;
if (length < NTP_HEADER_LENGTH || length >= sizeof (*packet) || length % 4 != 0)
return 0;
/* Only NTPv4 packets can have extension fields */
if (info->version != 4)
return 0;
if (!format_field((unsigned char *)packet, sizeof (*packet), length,
type, body_length, &ef_length, body))
return 0;
if (ef_length < NTP_MIN_EF_LENGTH)
return 0;
info->length += ef_length;
info->ext_fields++;
return 1;
}
/* ================================================== */
int
NEF_AddField(NTP_Packet *packet, NTP_PacketInfo *info,
int type, void *body, int body_length)
{
void *ef_body;
if (!NEF_AddBlankField(packet, info, type, body_length, &ef_body))
return 0;
memcpy(ef_body, body, body_length);
return 1;
}
/* ================================================== */
int
NEF_ParseSingleField(unsigned char *buffer, int buffer_length, int start,
int *length, int *type, void **body, int *body_length)
{
struct ExtFieldHeader *header;
int ef_length;
if (buffer_length < 0 || start < 0 || buffer_length <= start ||
buffer_length - start < sizeof (*header))
return 0;
header = (struct ExtFieldHeader *)(buffer + start);
assert(sizeof (*header) == 4);
ef_length = ntohs(header->length);
if (ef_length < (int)(sizeof (*header)) || start + ef_length > buffer_length ||
ef_length % 4 != 0)
return 0;
if (length)
*length = ef_length;
if (type)
*type = ntohs(header->type);
if (body)
*body = header + 1;
if (body_length)
*body_length = ef_length - sizeof (*header);
return 1;
}
/* ================================================== */
int
NEF_ParseField(NTP_Packet *packet, int packet_length, int start,
int *length, int *type, void **body, int *body_length)
{
int ef_length;
if (packet_length <= NTP_HEADER_LENGTH || packet_length > sizeof (*packet) ||
packet_length <= start || packet_length % 4 != 0 ||
start < NTP_HEADER_LENGTH || start % 4 != 0)
return 0;
/* Only NTPv4 packets have extension fields */
if (NTP_LVM_TO_VERSION(packet->lvm) != 4)
return 0;
/* Check if the remaining data is a MAC. RFC 7822 specifies the maximum
length of a MAC in NTPv4 packets in order to enable deterministic
parsing. */
if (packet_length - start <= NTP_MAX_V4_MAC_LENGTH)
return 0;
if (!NEF_ParseSingleField((unsigned char *)packet, packet_length, start,
&ef_length, type, body, body_length))
return 0;
if (ef_length < NTP_MIN_EF_LENGTH)
return 0;
if (length)
*length = ef_length;
return 1;
}

43
ntp_ext.h Normal file
View File

@@ -0,0 +1,43 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
Header file for NTP extension fields
*/
#ifndef GOT_NTP_EXT_H
#define GOT_NTP_EXT_H
#include "ntp.h"
extern int NEF_SetField(unsigned char *buffer, int buffer_length, int start,
int type, void *body, int body_length, int *length);
extern int NEF_AddBlankField(NTP_Packet *packet, NTP_PacketInfo *info, int type,
int body_length, void **body);
extern int NEF_AddField(NTP_Packet *packet, NTP_PacketInfo *info,
int type, void *body, int body_length);
extern int NEF_ParseSingleField(unsigned char *buffer, int buffer_length, int start,
int *length, int *type, void **body, int *body_length);
extern int NEF_ParseField(NTP_Packet *packet, int packet_length, int start,
int *length, int *type, void **body, int *body_length);
#endif

644
ntp_io.c
View File

@@ -30,11 +30,11 @@
#include "sysincl.h"
#include "array.h"
#include "ntp_io.h"
#include "ntp_core.h"
#include "ntp_sources.h"
#include "sched.h"
#include "socket.h"
#include "local.h"
#include "logging.h"
#include "conf.h"
@@ -46,54 +46,16 @@
#endif
#define INVALID_SOCK_FD -1
#define CMSGBUF_SIZE 256
union sockaddr_in46 {
struct sockaddr_in in4;
#ifdef FEAT_IPV6
struct sockaddr_in6 in6;
#endif
struct sockaddr u;
};
struct Message {
union sockaddr_in46 name;
struct iovec iov;
NTP_Receive_Buffer buf;
/* Aligned buffer for control messages */
struct cmsghdr cmsgbuf[CMSGBUF_SIZE / sizeof (struct cmsghdr)];
};
#ifdef HAVE_RECVMMSG
#define MAX_RECV_MESSAGES 4
#define MessageHeader mmsghdr
#else
/* Compatible with mmsghdr */
struct MessageHeader {
struct msghdr msg_hdr;
unsigned int msg_len;
};
#define MAX_RECV_MESSAGES 1
#endif
/* Arrays of Message and MessageHeader */
static ARR_Instance recv_messages;
static ARR_Instance recv_headers;
/* The server/peer and client sockets for IPv4 and IPv6 */
static int server_sock_fd4;
static int client_sock_fd4;
#ifdef FEAT_IPV6
static int server_sock_fd6;
static int client_sock_fd4;
static int client_sock_fd6;
#endif
/* Reference counters for server sockets to keep them open only when needed */
static int server_sock_ref4;
#ifdef FEAT_IPV6
static int server_sock_ref6;
#endif
/* Flag indicating we create a new connected client socket for each
server instead of sharing client_sock_fd4 and client_sock_fd6 */
@@ -119,162 +81,45 @@ static void read_from_socket(int sock_fd, int event, void *anything);
/* ================================================== */
static int
prepare_socket(int family, int port_number, int client_only)
open_socket(int family, int local_port, int client_only, IPSockAddr *remote_addr)
{
union sockaddr_in46 my_addr;
socklen_t my_addr_len;
int sock_fd;
IPAddr bind_address;
int events = SCH_FILE_INPUT, on_off = 1;
int sock_fd, sock_flags, events = SCH_FILE_INPUT;
IPSockAddr local_addr;
/* Open Internet domain UDP socket for NTP message transmissions */
if (!SCK_IsFamilySupported(family))
return INVALID_SOCK_FD;
sock_fd = socket(family, SOCK_DGRAM, 0);
if (!client_only)
CNF_GetBindAddress(family, &local_addr.ip_addr);
else
CNF_GetBindAcquisitionAddress(family, &local_addr.ip_addr);
if (local_addr.ip_addr.family != family)
SCK_GetAnyLocalIPAddress(family, &local_addr.ip_addr);
local_addr.port = local_port;
sock_flags = SCK_FLAG_RX_DEST_ADDR | SCK_FLAG_PRIV_BIND;
if (!client_only)
sock_flags |= SCK_FLAG_BROADCAST;
sock_fd = SCK_OpenUdpSocket(remote_addr, &local_addr, sock_flags);
if (sock_fd < 0) {
if (!client_only) {
LOG(LOGS_ERR, "Could not open %s NTP socket : %s",
UTI_SockaddrFamilyToString(family), strerror(errno));
} else {
DEBUG_LOG("Could not open %s NTP socket : %s",
UTI_SockaddrFamilyToString(family), strerror(errno));
}
if (!client_only)
LOG(LOGS_ERR, "Could not open NTP socket on %s", UTI_IPSockAddrToString(&local_addr));
return INVALID_SOCK_FD;
}
/* Close on exec */
UTI_FdSetCloexec(sock_fd);
/* Enable non-blocking mode on server sockets */
if (!client_only && fcntl(sock_fd, F_SETFL, O_NONBLOCK))
DEBUG_LOG("Could not set O_NONBLOCK : %s", strerror(errno));
/* Prepare local address */
memset(&my_addr, 0, sizeof (my_addr));
my_addr_len = 0;
switch (family) {
case AF_INET:
if (!client_only)
CNF_GetBindAddress(IPADDR_INET4, &bind_address);
else
CNF_GetBindAcquisitionAddress(IPADDR_INET4, &bind_address);
if (bind_address.family == IPADDR_INET4)
my_addr.in4.sin_addr.s_addr = htonl(bind_address.addr.in4);
else if (port_number)
my_addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
else
break;
my_addr.in4.sin_family = family;
my_addr.in4.sin_port = htons(port_number);
my_addr_len = sizeof (my_addr.in4);
if (!client_only)
bound_server_sock_fd4 = my_addr.in4.sin_addr.s_addr != htonl(INADDR_ANY);
break;
#ifdef FEAT_IPV6
case AF_INET6:
if (!client_only)
CNF_GetBindAddress(IPADDR_INET6, &bind_address);
else
CNF_GetBindAcquisitionAddress(IPADDR_INET6, &bind_address);
if (bind_address.family == IPADDR_INET6)
memcpy(my_addr.in6.sin6_addr.s6_addr, bind_address.addr.in6,
sizeof (my_addr.in6.sin6_addr.s6_addr));
else if (port_number)
my_addr.in6.sin6_addr = in6addr_any;
else
break;
my_addr.in6.sin6_family = family;
my_addr.in6.sin6_port = htons(port_number);
my_addr_len = sizeof (my_addr.in6);
break;
#endif
default:
assert(0);
}
/* Make the socket capable of re-using an old address if binding to a specific port */
if (port_number &&
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "SO_REUSEADDR");
/* Don't quit - we might survive anyway */
}
/* Make the socket capable of sending broadcast pkts - needed for NTP broadcast mode */
if (!client_only &&
setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "SO_BROADCAST");
/* Don't quit - we might survive anyway */
}
if (!client_only && family == IPADDR_INET4 && local_addr.port > 0)
bound_server_sock_fd4 = local_addr.ip_addr.addr.in4 != INADDR_ANY;
/* Enable kernel/HW timestamping of packets */
#ifdef HAVE_LINUX_TIMESTAMPING
if (!NIO_Linux_SetTimestampSocketOptions(sock_fd, client_only, &events))
#endif
#ifdef SO_TIMESTAMPNS
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMPNS, (char *)&on_off, sizeof(on_off)) < 0)
#endif
#ifdef SO_TIMESTAMP
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, (char *)&on_off, sizeof(on_off)) < 0)
LOG(LOGS_ERR, "Could not set %s socket option", "SO_TIMESTAMP");
#endif
if (!SCK_EnableKernelRxTimestamping(sock_fd))
;
#ifdef IP_FREEBIND
/* Allow binding to address that doesn't exist yet */
if (my_addr_len > 0 &&
setsockopt(sock_fd, IPPROTO_IP, IP_FREEBIND, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "IP_FREEBIND");
}
#endif
if (family == AF_INET) {
#ifdef HAVE_IN_PKTINFO
if (setsockopt(sock_fd, IPPROTO_IP, IP_PKTINFO, (char *)&on_off, sizeof(on_off)) < 0)
LOG(LOGS_ERR, "Could not set %s socket option", "IP_PKTINFO");
#elif defined(IP_RECVDSTADDR)
if (setsockopt(sock_fd, IPPROTO_IP, IP_RECVDSTADDR, (char *)&on_off, sizeof(on_off)) < 0)
LOG(LOGS_ERR, "Could not set %s socket option", "IP_RECVDSTADDR");
#endif
}
#ifdef FEAT_IPV6
else if (family == AF_INET6) {
#ifdef IPV6_V6ONLY
/* Receive IPv6 packets only */
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "IPV6_V6ONLY");
}
#endif
#ifdef HAVE_IN6_PKTINFO
#ifdef IPV6_RECVPKTINFO
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "IPV6_RECVPKTINFO");
}
#else
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_PKTINFO, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "IPV6_PKTINFO");
}
#endif
#endif
}
#endif
/* Bind the socket if a port or address was specified */
if (my_addr_len > 0 && PRV_BindSocket(sock_fd, &my_addr.u, my_addr_len) < 0) {
LOG(LOGS_ERR, "Could not bind %s NTP socket : %s",
UTI_SockaddrFamilyToString(family), strerror(errno));
close(sock_fd);
return INVALID_SOCK_FD;
}
/* Register handler for read and possibly exception events on the socket */
SCH_AddFileHandler(sock_fd, events, read_from_socket, NULL);
@@ -284,40 +129,9 @@ prepare_socket(int family, int port_number, int client_only)
/* ================================================== */
static int
prepare_separate_client_socket(int family)
open_separate_client_socket(IPSockAddr *remote_addr)
{
switch (family) {
case IPADDR_INET4:
return prepare_socket(AF_INET, 0, 1);
#ifdef FEAT_IPV6
case IPADDR_INET6:
return prepare_socket(AF_INET6, 0, 1);
#endif
default:
return INVALID_SOCK_FD;
}
}
/* ================================================== */
static int
connect_socket(int sock_fd, NTP_Remote_Address *remote_addr)
{
union sockaddr_in46 addr;
socklen_t addr_len;
addr_len = UTI_IPAndPortToSockaddr(&remote_addr->ip_addr, remote_addr->port, &addr.u);
assert(addr_len);
if (connect(sock_fd, &addr.u, addr_len) < 0) {
DEBUG_LOG("Could not connect NTP socket to %s:%d : %s",
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
strerror(errno));
return 0;
}
return 1;
return open_socket(remote_addr->ip_addr.family, 0, 1, remote_addr);
}
/* ================================================== */
@@ -332,33 +146,7 @@ close_socket(int sock_fd)
NIO_Linux_NotifySocketClosing(sock_fd);
#endif
SCH_RemoveFileHandler(sock_fd);
close(sock_fd);
}
/* ================================================== */
static void
prepare_buffers(unsigned int n)
{
struct MessageHeader *hdr;
struct Message *msg;
unsigned int i;
for (i = 0; i < n; i++) {
msg = ARR_GetElement(recv_messages, i);
hdr = ARR_GetElement(recv_headers, i);
msg->iov.iov_base = &msg->buf;
msg->iov.iov_len = sizeof (msg->buf);
hdr->msg_hdr.msg_name = &msg->name;
hdr->msg_hdr.msg_namelen = sizeof (msg->name);
hdr->msg_hdr.msg_iov = &msg->iov;
hdr->msg_hdr.msg_iovlen = 1;
hdr->msg_hdr.msg_control = &msg->cmsgbuf;
hdr->msg_hdr.msg_controllen = sizeof (msg->cmsgbuf);
hdr->msg_hdr.msg_flags = 0;
hdr->msg_len = 0;
}
SCK_CloseSocket(sock_fd);
}
/* ================================================== */
@@ -371,6 +159,10 @@ NIO_Initialise(int family)
assert(!initialised);
initialised = 1;
#ifdef PRIVOPS_BINDSOCKET
SCK_SetPrivBind(PRV_BindSocket);
#endif
#ifdef HAVE_LINUX_TIMESTAMPING
NIO_Linux_Initialise();
#else
@@ -381,12 +173,6 @@ NIO_Initialise(int family)
}
#endif
recv_messages = ARR_CreateInstance(sizeof (struct Message));
ARR_SetSize(recv_messages, MAX_RECV_MESSAGES);
recv_headers = ARR_CreateInstance(sizeof (struct MessageHeader));
ARR_SetSize(recv_headers, MAX_RECV_MESSAGES);
prepare_buffers(MAX_RECV_MESSAGES);
server_port = CNF_GetNTPPort();
client_port = CNF_GetAcquisitionPort();
@@ -399,47 +185,38 @@ NIO_Initialise(int family)
client_port == server_port);
server_sock_fd4 = INVALID_SOCK_FD;
client_sock_fd4 = INVALID_SOCK_FD;
server_sock_ref4 = 0;
#ifdef FEAT_IPV6
server_sock_fd6 = INVALID_SOCK_FD;
client_sock_fd4 = INVALID_SOCK_FD;
client_sock_fd6 = INVALID_SOCK_FD;
server_sock_ref4 = 0;
server_sock_ref6 = 0;
#endif
if (family == IPADDR_UNSPEC || family == IPADDR_INET4) {
if (permanent_server_sockets && server_port)
server_sock_fd4 = prepare_socket(AF_INET, server_port, 0);
server_sock_fd4 = open_socket(IPADDR_INET4, server_port, 0, NULL);
if (!separate_client_sockets) {
if (client_port != server_port || !server_port)
client_sock_fd4 = prepare_socket(AF_INET, client_port, 1);
client_sock_fd4 = open_socket(IPADDR_INET4, client_port, 1, NULL);
else
client_sock_fd4 = server_sock_fd4;
}
}
#ifdef FEAT_IPV6
if (family == IPADDR_UNSPEC || family == IPADDR_INET6) {
if (permanent_server_sockets && server_port)
server_sock_fd6 = prepare_socket(AF_INET6, server_port, 0);
server_sock_fd6 = open_socket(IPADDR_INET6, server_port, 0, NULL);
if (!separate_client_sockets) {
if (client_port != server_port || !server_port)
client_sock_fd6 = prepare_socket(AF_INET6, client_port, 1);
client_sock_fd6 = open_socket(IPADDR_INET6, client_port, 1, NULL);
else
client_sock_fd6 = server_sock_fd6;
}
}
#endif
if ((server_port && server_sock_fd4 == INVALID_SOCK_FD &&
permanent_server_sockets
#ifdef FEAT_IPV6
&& server_sock_fd6 == INVALID_SOCK_FD
#endif
) || (!separate_client_sockets && client_sock_fd4 == INVALID_SOCK_FD
#ifdef FEAT_IPV6
&& client_sock_fd6 == INVALID_SOCK_FD
#endif
)) {
if ((server_port && permanent_server_sockets &&
server_sock_fd4 == INVALID_SOCK_FD && server_sock_fd6 == INVALID_SOCK_FD) ||
(!separate_client_sockets &&
client_sock_fd4 == INVALID_SOCK_FD && client_sock_fd6 == INVALID_SOCK_FD)) {
LOG_FATAL("Could not open NTP sockets");
}
}
@@ -453,14 +230,11 @@ NIO_Finalise(void)
close_socket(client_sock_fd4);
close_socket(server_sock_fd4);
server_sock_fd4 = client_sock_fd4 = INVALID_SOCK_FD;
#ifdef FEAT_IPV6
if (server_sock_fd6 != client_sock_fd6)
close_socket(client_sock_fd6);
close_socket(server_sock_fd6);
server_sock_fd6 = client_sock_fd6 = INVALID_SOCK_FD;
#endif
ARR_DestroyInstance(recv_headers);
ARR_DestroyInstance(recv_messages);
#ifdef HAVE_LINUX_TIMESTAMPING
NIO_Linux_Finalise();
@@ -475,25 +249,13 @@ int
NIO_OpenClientSocket(NTP_Remote_Address *remote_addr)
{
if (separate_client_sockets) {
int sock_fd = prepare_separate_client_socket(remote_addr->ip_addr.family);
if (sock_fd == INVALID_SOCK_FD)
return INVALID_SOCK_FD;
if (!connect_socket(sock_fd, remote_addr)) {
close_socket(sock_fd);
return INVALID_SOCK_FD;
}
return sock_fd;
return open_separate_client_socket(remote_addr);
} else {
switch (remote_addr->ip_addr.family) {
case IPADDR_INET4:
return client_sock_fd4;
#ifdef FEAT_IPV6
case IPADDR_INET6:
return client_sock_fd6;
#endif
default:
return INVALID_SOCK_FD;
}
@@ -510,20 +272,18 @@ NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
if (permanent_server_sockets)
return server_sock_fd4;
if (server_sock_fd4 == INVALID_SOCK_FD)
server_sock_fd4 = prepare_socket(AF_INET, CNF_GetNTPPort(), 0);
server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNTPPort(), 0, NULL);
if (server_sock_fd4 != INVALID_SOCK_FD)
server_sock_ref4++;
return server_sock_fd4;
#ifdef FEAT_IPV6
case IPADDR_INET6:
if (permanent_server_sockets)
return server_sock_fd6;
if (server_sock_fd6 == INVALID_SOCK_FD)
server_sock_fd6 = prepare_socket(AF_INET6, CNF_GetNTPPort(), 0);
server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNTPPort(), 0, NULL);
if (server_sock_fd6 != INVALID_SOCK_FD)
server_sock_ref6++;
return server_sock_fd6;
#endif
default:
return INVALID_SOCK_FD;
}
@@ -551,16 +311,12 @@ NIO_CloseServerSocket(int sock_fd)
close_socket(server_sock_fd4);
server_sock_fd4 = INVALID_SOCK_FD;
}
}
#ifdef FEAT_IPV6
else if (sock_fd == server_sock_fd6) {
} else if (sock_fd == server_sock_fd6) {
if (--server_sock_ref6 <= 0) {
close_socket(server_sock_fd6);
server_sock_fd6 = INVALID_SOCK_FD;
}
}
#endif
else {
} else {
assert(0);
}
}
@@ -571,11 +327,15 @@ int
NIO_IsServerSocket(int sock_fd)
{
return sock_fd != INVALID_SOCK_FD &&
(sock_fd == server_sock_fd4
#ifdef FEAT_IPV6
|| sock_fd == server_sock_fd6
#endif
);
(sock_fd == server_sock_fd4 || sock_fd == server_sock_fd6);
}
/* ================================================== */
int
NIO_IsServerSocketOpen(void)
{
return server_sock_fd4 != INVALID_SOCK_FD || server_sock_fd6 != INVALID_SOCK_FD;
}
/* ================================================== */
@@ -583,132 +343,60 @@ NIO_IsServerSocket(int sock_fd)
int
NIO_IsServerConnectable(NTP_Remote_Address *remote_addr)
{
int sock_fd, r;
int sock_fd;
sock_fd = prepare_separate_client_socket(remote_addr->ip_addr.family);
sock_fd = open_separate_client_socket(remote_addr);
if (sock_fd == INVALID_SOCK_FD)
return 0;
r = connect_socket(sock_fd, remote_addr);
close_socket(sock_fd);
return r;
return 1;
}
/* ================================================== */
static void
process_message(struct msghdr *hdr, int length, int sock_fd)
process_message(SCK_Message *message, int sock_fd, int event)
{
NTP_Remote_Address remote_addr;
NTP_Local_Address local_addr;
NTP_Local_Timestamp local_ts;
struct timespec sched_ts;
struct cmsghdr *cmsg;
SCH_GetLastEventTime(&local_ts.ts, &local_ts.err, NULL);
local_ts.source = NTP_TS_DAEMON;
sched_ts = local_ts.ts;
if (hdr->msg_namelen > sizeof (union sockaddr_in46)) {
DEBUG_LOG("Truncated source address");
if (message->addr_type != SCK_ADDR_IP) {
DEBUG_LOG("Unexpected address type");
return;
}
if (hdr->msg_namelen >= sizeof (((struct sockaddr *)hdr->msg_name)->sa_family)) {
UTI_SockaddrToIPAndPort((struct sockaddr *)hdr->msg_name,
&remote_addr.ip_addr, &remote_addr.port);
} else {
remote_addr.ip_addr.family = IPADDR_UNSPEC;
remote_addr.port = 0;
}
local_addr.ip_addr.family = IPADDR_UNSPEC;
local_addr.if_index = INVALID_IF_INDEX;
local_addr.ip_addr = message->local_addr.ip;
local_addr.if_index = message->if_index;;
local_addr.sock_fd = sock_fd;
if (hdr->msg_flags & MSG_TRUNC) {
DEBUG_LOG("Received truncated message from %s:%d",
UTI_IPToString(&remote_addr.ip_addr), remote_addr.port);
return;
}
if (hdr->msg_flags & MSG_CTRUNC) {
DEBUG_LOG("Truncated control message");
/* Continue */
}
for (cmsg = CMSG_FIRSTHDR(hdr); cmsg; cmsg = CMSG_NXTHDR(hdr, cmsg)) {
#ifdef HAVE_IN_PKTINFO
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
struct in_pktinfo ipi;
memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi));
local_addr.ip_addr.addr.in4 = ntohl(ipi.ipi_addr.s_addr);
local_addr.ip_addr.family = IPADDR_INET4;
local_addr.if_index = ipi.ipi_ifindex;
}
#elif defined(IP_RECVDSTADDR)
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) {
struct in_addr addr;
memcpy(&addr, CMSG_DATA(cmsg), sizeof (addr));
local_addr.ip_addr.addr.in4 = ntohl(addr.s_addr);
local_addr.ip_addr.family = IPADDR_INET4;
}
#endif
#ifdef HAVE_IN6_PKTINFO
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
struct in6_pktinfo ipi;
memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi));
memcpy(&local_addr.ip_addr.addr.in6, &ipi.ipi6_addr.s6_addr,
sizeof (local_addr.ip_addr.addr.in6));
local_addr.ip_addr.family = IPADDR_INET6;
local_addr.if_index = ipi.ipi6_ifindex;
}
#endif
#ifdef SCM_TIMESTAMP
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
struct timeval tv;
struct timespec ts;
memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv));
UTI_TimevalToTimespec(&tv, &ts);
LCL_CookTime(&ts, &local_ts.ts, &local_ts.err);
local_ts.source = NTP_TS_KERNEL;
}
#endif
#ifdef SCM_TIMESTAMPNS
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPNS) {
struct timespec ts;
memcpy(&ts, CMSG_DATA(cmsg), sizeof (ts));
LCL_CookTime(&ts, &local_ts.ts, &local_ts.err);
local_ts.source = NTP_TS_KERNEL;
}
#endif
}
#ifdef HAVE_LINUX_TIMESTAMPING
if (NIO_Linux_ProcessMessage(&remote_addr, &local_addr, &local_ts, hdr, length))
if (NIO_Linux_ProcessMessage(message, &local_addr, &local_ts, event))
return;
#else
if (!UTI_IsZeroTimespec(&message->timestamp.kernel)) {
LCL_CookTime(&message->timestamp.kernel, &local_ts.ts, &local_ts.err);
local_ts.source = NTP_TS_KERNEL;
}
#endif
DEBUG_LOG("Received %d bytes from %s:%d to %s fd=%d if=%d tss=%u delay=%.9f",
length, UTI_IPToString(&remote_addr.ip_addr), remote_addr.port,
UTI_IPToString(&local_addr.ip_addr), local_addr.sock_fd, local_addr.if_index,
local_ts.source, UTI_DiffTimespecsToDouble(&sched_ts, &local_ts.ts));
if (local_ts.source != NTP_TS_DAEMON)
DEBUG_LOG("Updated RX timestamp delay=%.9f tss=%u",
UTI_DiffTimespecsToDouble(&sched_ts, &local_ts.ts), local_ts.source);
/* Just ignore the packet if it's not of a recognized length */
if (length < NTP_NORMAL_PACKET_LENGTH || length > sizeof (NTP_Receive_Buffer))
if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet)) {
DEBUG_LOG("Unexpected length");
return;
}
NSR_ProcessRx(&remote_addr, &local_addr, &local_ts,
(NTP_Packet *)hdr->msg_iov[0].iov_base, length);
NSR_ProcessRx(&message->remote_addr.ip, &local_addr, &local_ts, message->data, message->length);
}
/* ================================================== */
@@ -716,68 +404,28 @@ process_message(struct msghdr *hdr, int length, int sock_fd)
static void
read_from_socket(int sock_fd, int event, void *anything)
{
/* This should only be called when there is something
to read, otherwise it may block */
struct MessageHeader *hdr;
unsigned int i, n;
int status, flags = 0;
SCK_Message *messages;
int i, received, flags = 0;
#ifdef HAVE_LINUX_TIMESTAMPING
if (NIO_Linux_ProcessEvent(sock_fd, event))
return;
#endif
hdr = ARR_GetElements(recv_headers);
n = ARR_GetSize(recv_headers);
assert(n >= 1);
if (event == SCH_FILE_EXCEPTION) {
#ifdef HAVE_LINUX_TIMESTAMPING
flags |= MSG_ERRQUEUE;
flags |= SCK_FLAG_MSG_ERRQUEUE;
#else
assert(0);
#endif
}
#ifdef HAVE_RECVMMSG
status = recvmmsg(sock_fd, hdr, n, flags | MSG_DONTWAIT, NULL);
if (status >= 0)
n = status;
#else
n = 1;
status = recvmsg(sock_fd, &hdr[0].msg_hdr, flags);
if (status >= 0)
hdr[0].msg_len = status;
#endif
if (status < 0) {
#ifdef HAVE_LINUX_TIMESTAMPING
/* If reading from the error queue failed, the exception should be
for a socket error. Clear the error to avoid a busy loop. */
if (flags & MSG_ERRQUEUE) {
int error = 0;
socklen_t len = sizeof (error);
if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &error, &len))
DEBUG_LOG("Could not get SO_ERROR");
if (error)
errno = error;
}
#endif
DEBUG_LOG("Could not receive from fd %d : %s", sock_fd,
strerror(errno));
messages = SCK_ReceiveMessages(sock_fd, flags, &received);
if (!messages)
return;
}
for (i = 0; i < n; i++) {
hdr = ARR_GetElement(recv_headers, i);
process_message(&hdr->msg_hdr, hdr->msg_len, sock_fd);
}
/* Restore the buffers to their original state */
prepare_buffers(n);
for (i = 0; i < received; i++)
process_message(&messages[i], sock_fd, event);
}
/* ================================================== */
@@ -787,123 +435,43 @@ int
NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,
NTP_Local_Address *local_addr, int length, int process_tx)
{
union sockaddr_in46 remote;
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg, cmsgbuf[CMSGBUF_SIZE / sizeof (struct cmsghdr)];
int cmsglen;
socklen_t addrlen = 0;
SCK_Message message;
assert(initialised);
if (local_addr->sock_fd == INVALID_SOCK_FD) {
DEBUG_LOG("No socket to send to %s:%d",
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port);
DEBUG_LOG("No socket to send to %s", UTI_IPSockAddrToString(remote_addr));
return 0;
}
/* Don't set address with connected socket */
SCK_InitMessage(&message, SCK_ADDR_IP);
message.data = packet;
message.length = length;
/* Specify remote address if the socket is not connected */
if (NIO_IsServerSocket(local_addr->sock_fd) || !separate_client_sockets) {
addrlen = UTI_IPAndPortToSockaddr(&remote_addr->ip_addr, remote_addr->port,
&remote.u);
if (!addrlen)
return 0;
message.remote_addr.ip.ip_addr = remote_addr->ip_addr;
message.remote_addr.ip.port = remote_addr->port;
}
if (addrlen) {
msg.msg_name = &remote.u;
msg.msg_namelen = addrlen;
} else {
msg.msg_name = NULL;
msg.msg_namelen = 0;
}
message.if_index = local_addr->if_index;
message.local_addr.ip = local_addr->ip_addr;
iov.iov_base = packet;
iov.iov_len = length;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
cmsglen = 0;
#ifdef HAVE_IN_PKTINFO
if (local_addr->ip_addr.family == IPADDR_INET4) {
struct in_pktinfo *ipi;
cmsg = CMSG_FIRSTHDR(&msg);
memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_pktinfo)));
cmsglen += CMSG_SPACE(sizeof(struct in_pktinfo));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
ipi = (struct in_pktinfo *) CMSG_DATA(cmsg);
ipi->ipi_spec_dst.s_addr = htonl(local_addr->ip_addr.addr.in4);
if (local_addr->if_index != INVALID_IF_INDEX)
ipi->ipi_ifindex = local_addr->if_index;
}
#elif defined(IP_SENDSRCADDR)
/* Specify the IPv4 source address only if the socket is not bound */
if (local_addr->ip_addr.family == IPADDR_INET4 &&
local_addr->sock_fd == server_sock_fd4 && !bound_server_sock_fd4) {
struct in_addr *addr;
cmsg = CMSG_FIRSTHDR(&msg);
memset(cmsg, 0, CMSG_SPACE(sizeof (struct in_addr)));
cmsglen += CMSG_SPACE(sizeof (struct in_addr));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_SENDSRCADDR;
cmsg->cmsg_len = CMSG_LEN(sizeof (struct in_addr));
addr = (struct in_addr *)CMSG_DATA(cmsg);
addr->s_addr = htonl(local_addr->ip_addr.addr.in4);
}
#endif
#ifdef HAVE_IN6_PKTINFO
if (local_addr->ip_addr.family == IPADDR_INET6) {
struct in6_pktinfo *ipi;
cmsg = CMSG_FIRSTHDR(&msg);
memset(cmsg, 0, CMSG_SPACE(sizeof(struct in6_pktinfo)));
cmsglen += CMSG_SPACE(sizeof(struct in6_pktinfo));
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
ipi = (struct in6_pktinfo *) CMSG_DATA(cmsg);
memcpy(&ipi->ipi6_addr.s6_addr, &local_addr->ip_addr.addr.in6,
sizeof(ipi->ipi6_addr.s6_addr));
if (local_addr->if_index != INVALID_IF_INDEX)
ipi->ipi6_ifindex = local_addr->if_index;
}
#if !defined(HAVE_IN_PKTINFO) && defined(IP_SENDSRCADDR)
/* On FreeBSD a local IPv4 address cannot be specified on bound socket */
if (message.local_addr.ip.family == IPADDR_INET4 &&
(local_addr->sock_fd != server_sock_fd4 || bound_server_sock_fd4))
message.local_addr.ip.family = IPADDR_UNSPEC;
#endif
#ifdef HAVE_LINUX_TIMESTAMPING
if (process_tx)
cmsglen = NIO_Linux_RequestTxTimestamp(&msg, cmsglen, local_addr->sock_fd);
NIO_Linux_RequestTxTimestamp(&message, local_addr->sock_fd);
#endif
msg.msg_controllen = cmsglen;
/* This is apparently required on some systems */
if (!cmsglen)
msg.msg_control = NULL;
if (sendmsg(local_addr->sock_fd, &msg, 0) < 0) {
DEBUG_LOG("Could not send to %s:%d from %s fd %d : %s",
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
UTI_IPToString(&local_addr->ip_addr), local_addr->sock_fd,
strerror(errno));
if (!SCK_SendMessage(local_addr->sock_fd, &message, 0))
return 0;
}
DEBUG_LOG("Sent %d bytes to %s:%d from %s fd %d", length,
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
UTI_IPToString(&local_addr->ip_addr), local_addr->sock_fd);
return 1;
}

View File

@@ -53,6 +53,9 @@ extern void NIO_CloseServerSocket(int sock_fd);
/* Function to check if socket is a server socket */
extern int NIO_IsServerSocket(int sock_fd);
/* Function to check if a server socket is currently open */
extern int NIO_IsServerSocketOpen(void);
/* Function to check if client packets can be sent to a server */
extern int NIO_IsServerConnectable(NTP_Remote_Address *remote_addr);

View File

@@ -29,7 +29,6 @@
#include "sysincl.h"
#include <ifaddrs.h>
#include <linux/errqueue.h>
#include <linux/ethtool.h>
#include <linux/net_tstamp.h>
#include <linux/sockios.h>
@@ -45,17 +44,10 @@
#include "ntp_io_linux.h"
#include "ntp_sources.h"
#include "sched.h"
#include "socket.h"
#include "sys_linux.h"
#include "util.h"
union sockaddr_in46 {
struct sockaddr_in in4;
#ifdef FEAT_IPV6
struct sockaddr_in6 in6;
#endif
struct sockaddr u;
};
struct Interface {
char name[IF_NAMESIZE];
int if_index;
@@ -133,7 +125,7 @@ add_interface(CNF_HwTsInterface *conf_iface)
return 1;
}
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
sock_fd = SCK_OpenUdpSocket(NULL, NULL, 0);
if (sock_fd < 0)
return 0;
@@ -142,13 +134,13 @@ add_interface(CNF_HwTsInterface *conf_iface)
if (snprintf(req.ifr_name, sizeof (req.ifr_name), "%s", conf_iface->name) >=
sizeof (req.ifr_name)) {
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 0;
}
if (ioctl(sock_fd, SIOCGIFINDEX, &req)) {
DEBUG_LOG("ioctl(%s) failed : %s", "SIOCGIFINDEX", strerror(errno));
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 0;
}
@@ -159,7 +151,7 @@ add_interface(CNF_HwTsInterface *conf_iface)
if (ioctl(sock_fd, SIOCETHTOOL, &req)) {
DEBUG_LOG("ioctl(%s) failed : %s", "SIOCETHTOOL", strerror(errno));
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 0;
}
@@ -167,13 +159,13 @@ add_interface(CNF_HwTsInterface *conf_iface)
SOF_TIMESTAMPING_RAW_HARDWARE;
if ((ts_info.so_timestamping & req_hwts_flags) != req_hwts_flags) {
DEBUG_LOG("HW timestamping not supported on %s", req.ifr_name);
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 0;
}
if (ts_info.phc_index < 0) {
DEBUG_LOG("PHC missing on %s", req.ifr_name);
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 0;
}
@@ -219,12 +211,12 @@ add_interface(CNF_HwTsInterface *conf_iface)
ts_config.tx_type != HWTSTAMP_TX_ON || ts_config.rx_filter != rx_filter)
#endif
{
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 0;
}
}
close(sock_fd);
SCK_CloseSocket(sock_fd);
phc_fd = SYS_Linux_OpenPHC(NULL, ts_info.phc_index);
if (phc_fd < 0)
@@ -293,7 +285,7 @@ update_interface_speed(struct Interface *iface)
struct ifreq req;
int sock_fd, link_speed;
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
sock_fd = SCK_OpenUdpSocket(NULL, NULL, 0);
if (sock_fd < 0)
return;
@@ -306,11 +298,11 @@ update_interface_speed(struct Interface *iface)
if (ioctl(sock_fd, SIOCETHTOOL, &req)) {
DEBUG_LOG("ioctl(%s) failed : %s", "SIOCETHTOOL", strerror(errno));
close(sock_fd);
SCK_CloseSocket(sock_fd);
return;
}
close(sock_fd);
SCK_CloseSocket(sock_fd);
link_speed = ethtool_cmd_speed(&cmd);
@@ -328,17 +320,16 @@ check_timestamping_option(int option)
{
int sock_fd;
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
sock_fd = SCK_OpenUdpSocket(NULL, NULL, 0);
if (sock_fd < 0)
return 0;
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, &option, sizeof (option)) < 0) {
DEBUG_LOG("Could not enable timestamping option %x", (unsigned int)option);
close(sock_fd);
if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, option)) {
SCK_CloseSocket(sock_fd);
return 0;
}
close(sock_fd);
SCK_CloseSocket(sock_fd);
return 1;
}
#endif
@@ -350,19 +341,15 @@ open_dummy_socket(void)
{
int sock_fd, events = 0;
if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0
#ifdef FEAT_IPV6
&& (sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0
#endif
)
sock_fd = SCK_OpenUdpSocket(NULL, NULL, 0);
if (sock_fd < 0)
return INVALID_SOCK_FD;
if (!NIO_Linux_SetTimestampSocketOptions(sock_fd, 1, &events)) {
close(sock_fd);
SCK_CloseSocket(sock_fd);
return INVALID_SOCK_FD;
}
UTI_FdSetCloexec(sock_fd);
return sock_fd;
}
@@ -432,7 +419,7 @@ NIO_Linux_Finalise(void)
unsigned int i;
if (dummy_rxts_socket != INVALID_SOCK_FD)
close(dummy_rxts_socket);
SCK_CloseSocket(dummy_rxts_socket);
for (i = 0; i < ARR_GetSize(interfaces); i++) {
iface = ARR_GetElement(interfaces, i);
@@ -462,14 +449,12 @@ NIO_Linux_SetTimestampSocketOptions(int sock_fd, int client_only, int *events)
if (client_only || permanent_ts_options)
flags |= ts_tx_flags;
if (setsockopt(sock_fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE, &val, sizeof (val)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "SO_SELECT_ERR_QUEUE");
if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE, val)) {
ts_flags = 0;
return 0;
}
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof (flags)) < 0) {
LOG(LOGS_ERR, "Could not set %s socket option", "SO_TIMESTAMPING");
if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, flags)) {
ts_flags = 0;
return 0;
}
@@ -633,7 +618,6 @@ static int
extract_udp_data(unsigned char *msg, NTP_Remote_Address *remote_addr, int len)
{
unsigned char *msg_start = msg;
union sockaddr_in46 addr;
remote_addr->ip_addr.family = IPADDR_UNSPEC;
remote_addr->port = 0;
@@ -656,19 +640,21 @@ extract_udp_data(unsigned char *msg, NTP_Remote_Address *remote_addr, int len)
/* Parse destination address and port from IPv4/IPv6 and UDP headers */
if (len >= 20 && msg[0] >> 4 == 4) {
int ihl = (msg[0] & 0xf) * 4;
uint32_t addr;
if (len < ihl + 8 || msg[9] != 17)
return 0;
memcpy(&addr.in4.sin_addr.s_addr, msg + 16, sizeof (uint32_t));
addr.in4.sin_port = *(uint16_t *)(msg + ihl + 2);
addr.in4.sin_family = AF_INET;
memcpy(&addr, msg + 16, sizeof (addr));
remote_addr->ip_addr.addr.in4 = ntohl(addr);
remote_addr->port = ntohs(*(uint16_t *)(msg + ihl + 2));
remote_addr->ip_addr.family = IPADDR_INET4;
len -= ihl + 8, msg += ihl + 8;
#ifdef FEAT_IPV6
} else if (len >= 48 && msg[0] >> 4 == 6) {
int eh_len, next_header = msg[6];
memcpy(&addr.in6.sin6_addr.s6_addr, msg + 24, 16);
memcpy(&remote_addr->ip_addr.addr.in6, msg + 24, sizeof (remote_addr->ip_addr.addr.in6));
len -= 40, msg += 40;
/* Skip IPv6 extension headers if present */
@@ -700,16 +686,14 @@ extract_udp_data(unsigned char *msg, NTP_Remote_Address *remote_addr, int len)
len -= eh_len, msg += eh_len;
}
addr.in6.sin6_port = *(uint16_t *)(msg + 2);
addr.in6.sin6_family = AF_INET6;
remote_addr->port = ntohs(*(uint16_t *)(msg + 2));
remote_addr->ip_addr.family = IPADDR_INET6;
len -= 8, msg += 8;
#endif
} else {
return 0;
}
UTI_SockaddrToIPAndPort(&addr.u, &remote_addr->ip_addr, &remote_addr->port);
/* Move the message to fix alignment of its fields */
if (len > 0)
memmove(msg_start, msg, len);
@@ -720,72 +704,39 @@ extract_udp_data(unsigned char *msg, NTP_Remote_Address *remote_addr, int len)
/* ================================================== */
int
NIO_Linux_ProcessMessage(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *local_ts, struct msghdr *hdr, int length)
NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *local_ts, int event)
{
struct Interface *iface;
struct cmsghdr *cmsg;
int is_tx, ts_if_index, l2_length;
is_tx = hdr->msg_flags & MSG_ERRQUEUE;
is_tx = event == SCH_FILE_EXCEPTION;
iface = NULL;
ts_if_index = local_addr->if_index;
l2_length = 0;
for (cmsg = CMSG_FIRSTHDR(hdr); cmsg; cmsg = CMSG_NXTHDR(hdr, cmsg)) {
#ifdef HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING_PKTINFO) {
struct scm_ts_pktinfo ts_pktinfo;
ts_if_index = message->timestamp.if_index;
if (ts_if_index == INVALID_IF_INDEX)
ts_if_index = message->if_index;
l2_length = message->timestamp.l2_length;
memcpy(&ts_pktinfo, CMSG_DATA(cmsg), sizeof (ts_pktinfo));
ts_if_index = ts_pktinfo.if_index;
l2_length = ts_pktinfo.pkt_length;
DEBUG_LOG("Received HW timestamp info if=%d length=%d", ts_if_index, l2_length);
}
#endif
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
struct scm_timestamping ts3;
memcpy(&ts3, CMSG_DATA(cmsg), sizeof (ts3));
if (!UTI_IsZeroTimespec(&ts3.ts[2])) {
iface = get_interface(ts_if_index);
if (iface) {
process_hw_timestamp(iface, &ts3.ts[2], local_ts, !is_tx ? length : 0,
remote_addr->ip_addr.family, l2_length);
} else {
DEBUG_LOG("HW clock not found for interface %d", ts_if_index);
}
/* If a HW transmit timestamp was received, resume processing
of non-error messages on this socket */
if (is_tx)
resume_socket(local_addr->sock_fd);
}
if (local_ts->source == NTP_TS_DAEMON && !UTI_IsZeroTimespec(&ts3.ts[0]) &&
(!is_tx || UTI_IsZeroTimespec(&ts3.ts[2]))) {
LCL_CookTime(&ts3.ts[0], &local_ts->ts, &local_ts->err);
local_ts->source = NTP_TS_KERNEL;
}
if (!UTI_IsZeroTimespec(&message->timestamp.hw)) {
iface = get_interface(ts_if_index);
if (iface) {
process_hw_timestamp(iface, &message->timestamp.hw, local_ts, !is_tx ? message->length : 0,
message->remote_addr.ip.ip_addr.family, l2_length);
} else {
DEBUG_LOG("HW clock not found for interface %d", ts_if_index);
}
if ((cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) ||
(cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_RECVERR)) {
struct sock_extended_err err;
/* If a HW transmit timestamp was received, resume processing
of non-error messages on this socket */
if (is_tx)
resume_socket(local_addr->sock_fd);
}
memcpy(&err, CMSG_DATA(cmsg), sizeof (err));
if (err.ee_errno != ENOMSG || err.ee_info != SCM_TSTAMP_SND ||
err.ee_origin != SO_EE_ORIGIN_TIMESTAMPING) {
DEBUG_LOG("Unknown extended error");
/* Drop the message */
return 1;
}
}
if (local_ts->source == NTP_TS_DAEMON && !UTI_IsZeroTimespec(&message->timestamp.kernel) &&
(!is_tx || UTI_IsZeroTimespec(&message->timestamp.hw))) {
LCL_CookTime(&message->timestamp.kernel, &local_ts->ts, &local_ts->err);
local_ts->source = NTP_TS_KERNEL;
}
/* If the kernel is slow with enabling RX timestamping, open a dummy
@@ -803,19 +754,19 @@ NIO_Linux_ProcessMessage(NTP_Remote_Address *remote_addr, NTP_Local_Address *loc
/* The data from the error queue includes all layers up to UDP. We have to
extract the UDP data and also the destination address with port as there
currently doesn't seem to be a better way to get them both. */
l2_length = length;
length = extract_udp_data(hdr->msg_iov[0].iov_base, remote_addr, length);
l2_length = message->length;
message->length = extract_udp_data(message->data, &message->remote_addr.ip, message->length);
DEBUG_LOG("Received %d (%d) bytes from error queue for %s:%d fd=%d if=%d tss=%u",
l2_length, length, UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
local_addr->sock_fd, local_addr->if_index, local_ts->source);
DEBUG_LOG("Extracted message for %s fd=%d len=%u",
UTI_IPSockAddrToString(&message->remote_addr.ip),
local_addr->sock_fd, message->length);
/* Update assumed position of UDP data at layer 2 for next received packet */
if (iface && length) {
if (remote_addr->ip_addr.family == IPADDR_INET4)
iface->l2_udp4_ntp_start = l2_length - length;
else if (remote_addr->ip_addr.family == IPADDR_INET6)
iface->l2_udp6_ntp_start = l2_length - length;
if (iface && message->length) {
if (message->remote_addr.ip.ip_addr.family == IPADDR_INET4)
iface->l2_udp4_ntp_start = l2_length - message->length;
else if (message->remote_addr.ip.ip_addr.family == IPADDR_INET6)
iface->l2_udp6_ntp_start = l2_length - message->length;
}
/* Drop the message if it has no timestamp or its processing failed */
@@ -824,24 +775,21 @@ NIO_Linux_ProcessMessage(NTP_Remote_Address *remote_addr, NTP_Local_Address *loc
return 1;
}
if (length < NTP_NORMAL_PACKET_LENGTH)
if (message->length < NTP_HEADER_LENGTH)
return 1;
NSR_ProcessTx(remote_addr, local_addr, local_ts,
(NTP_Packet *)hdr->msg_iov[0].iov_base, length);
NSR_ProcessTx(&message->remote_addr.ip, local_addr, local_ts, message->data, message->length);
return 1;
}
/* ================================================== */
int
NIO_Linux_RequestTxTimestamp(struct msghdr *msg, int cmsglen, int sock_fd)
void
NIO_Linux_RequestTxTimestamp(SCK_Message *message, int sock_fd)
{
struct cmsghdr *cmsg;
if (!ts_flags)
return cmsglen;
return;
/* If a HW transmit timestamp is requested on a client socket, monitor
events on the socket in order to avoid processing of a fast response
@@ -851,27 +799,9 @@ NIO_Linux_RequestTxTimestamp(struct msghdr *msg, int cmsglen, int sock_fd)
/* Check if TX timestamping is disabled on this socket */
if (permanent_ts_options || !NIO_IsServerSocket(sock_fd))
return cmsglen;
return;
/* Add control message that will enable TX timestamping for this message.
Don't use CMSG_NXTHDR as the one in glibc is buggy for creating new
control messages. */
cmsg = CMSG_FIRSTHDR(msg);
if (!cmsg || cmsglen + CMSG_SPACE(sizeof (ts_tx_flags)) > msg->msg_controllen)
return cmsglen;
cmsg = (struct cmsghdr *)((char *)cmsg + cmsglen);
memset(cmsg, 0, CMSG_SPACE(sizeof (ts_tx_flags)));
cmsglen += CMSG_SPACE(sizeof (ts_tx_flags));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SO_TIMESTAMPING;
cmsg->cmsg_len = CMSG_LEN(sizeof (ts_tx_flags));
memcpy(CMSG_DATA(cmsg), &ts_tx_flags, sizeof (ts_tx_flags));
return cmsglen;
message->timestamp.tx_flags = ts_tx_flags;
}
/* ================================================== */

View File

@@ -27,6 +27,8 @@
#ifndef GOT_NTP_IO_LINUX_H
#define GOT_NTP_IO_LINUX_H
#include "socket.h"
extern void NIO_Linux_Initialise(void);
extern void NIO_Linux_Finalise(void);
@@ -35,10 +37,10 @@ extern int NIO_Linux_SetTimestampSocketOptions(int sock_fd, int client_only, int
extern int NIO_Linux_ProcessEvent(int sock_fd, int event);
extern int NIO_Linux_ProcessMessage(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *local_ts, struct msghdr *hdr, int length);
extern int NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *local_ts, int event);
extern int NIO_Linux_RequestTxTimestamp(struct msghdr *msg, int cmsglen, int sock_fd);
extern void NIO_Linux_RequestTxTimestamp(SCK_Message *message, int sock_fd);
extern void NIO_Linux_NotifySocketClosing(int sock_fd);

View File

@@ -34,6 +34,7 @@
#include "ntp_io.h"
#include "ntp_signd.h"
#include "sched.h"
#include "socket.h"
#include "util.h"
/* Declarations per samba/source4/librpc/idl/ntp_signd.idl */
@@ -90,7 +91,7 @@ static ARR_Instance queue;
static unsigned int queue_head;
static unsigned int queue_tail;
#define INVALID_SOCK_FD -1
#define INVALID_SOCK_FD (-6)
/* Unix domain socket connected to ntp_signd */
static int sock_fd;
@@ -116,7 +117,7 @@ static void
close_socket(void)
{
SCH_RemoveFileHandler(sock_fd);
close(sock_fd);
SCK_CloseSocket(sock_fd);
sock_fd = INVALID_SOCK_FD;
/* Empty the queue */
@@ -128,35 +129,23 @@ close_socket(void)
static int
open_socket(void)
{
struct sockaddr_un s;
char path[PATH_MAX];
if (sock_fd >= 0)
if (sock_fd != INVALID_SOCK_FD)
return 1;
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0) {
DEBUG_LOG("Could not open signd socket : %s", strerror(errno));
return 0;
}
UTI_FdSetCloexec(sock_fd);
SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, NULL);
s.sun_family = AF_UNIX;
if (snprintf(s.sun_path, sizeof (s.sun_path), "%s/socket",
CNF_GetNtpSigndSocket()) >= sizeof (s.sun_path)) {
if (snprintf(path, sizeof (path), "%s/socket", CNF_GetNtpSigndSocket()) >= sizeof (path)) {
DEBUG_LOG("signd socket path too long");
close_socket();
return 0;
}
if (connect(sock_fd, (struct sockaddr *)&s, sizeof (s)) < 0) {
DEBUG_LOG("Could not connect to signd : %s", strerror(errno));
close_socket();
sock_fd = SCK_OpenUnixStreamSocket(path, NULL, 0);
if (sock_fd < 0) {
sock_fd = INVALID_SOCK_FD;
return 0;
}
DEBUG_LOG("Connected to signd");
SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, NULL);
return 1;
}
@@ -218,16 +207,14 @@ read_write_socket(int sock_fd, int event, void *anything)
if (!inst->sent)
SCH_GetLastEventTime(NULL, NULL, &inst->request_ts);
s = send(sock_fd, (char *)&inst->request + inst->sent,
inst->request_length - inst->sent, 0);
s = SCK_Send(sock_fd, (char *)&inst->request + inst->sent,
inst->request_length - inst->sent, 0);
if (s < 0) {
DEBUG_LOG("signd socket error: %s", strerror(errno));
close_socket();
return;
}
DEBUG_LOG("Sent %d bytes to signd", s);
inst->sent += s;
/* Try again later if the request is not complete yet */
@@ -246,20 +233,14 @@ read_write_socket(int sock_fd, int event, void *anything)
}
assert(inst->received < sizeof (inst->response));
s = recv(sock_fd, (char *)&inst->response + inst->received,
sizeof (inst->response) - inst->received, 0);
s = SCK_Receive(sock_fd, (char *)&inst->response + inst->received,
sizeof (inst->response) - inst->received, 0);
if (s <= 0) {
if (s < 0)
DEBUG_LOG("signd socket error: %s", strerror(errno));
else
DEBUG_LOG("signd socket closed");
close_socket();
return;
}
DEBUG_LOG("Received %d bytes from signd", s);
inst->received += s;
if (inst->received < sizeof (inst->response.length))
@@ -328,7 +309,8 @@ extern int NSD_GetAuthDelay(uint32_t key_id)
/* ================================================== */
int
NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, int length)
NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info,
NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr)
{
SignInstance *inst;
@@ -342,7 +324,7 @@ NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *r
return 0;
}
if (length != NTP_NORMAL_PACKET_LENGTH) {
if (info->length != NTP_HEADER_LENGTH) {
DEBUG_LOG("Invalid packet length");
return 0;
}
@@ -355,7 +337,7 @@ NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *r
inst->local_addr = *local_addr;
inst->sent = 0;
inst->received = 0;
inst->request_length = offsetof(SigndRequest, packet_to_sign) + length;
inst->request_length = offsetof(SigndRequest, packet_to_sign) + info->length;
/* The length field doesn't include itself */
inst->request.length = htonl(inst->request_length - sizeof (inst->request.length));
@@ -365,7 +347,7 @@ NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *r
inst->request._pad = 0;
inst->request.key_id = htonl(key_id);
memcpy(&inst->request.packet_to_sign, packet, length);
memcpy(&inst->request.packet_to_sign, packet, info->length);
/* Enable output if there was no pending request */
if (IS_QUEUE_EMPTY())

View File

@@ -39,6 +39,7 @@ extern void NSD_Finalise(void);
extern int NSD_GetAuthDelay(uint32_t key_id);
/* Function to sign an NTP packet and send it */
extern int NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, int length);
extern int NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info,
NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr);
#endif

View File

@@ -34,6 +34,7 @@
#include "array.h"
#include "ntp_sources.h"
#include "ntp_core.h"
#include "ntp_io.h"
#include "util.h"
#include "logging.h"
#include "local.h"
@@ -48,7 +49,9 @@
particular sources */
typedef struct {
NTP_Remote_Address *remote_addr; /* The address of this source, non-NULL
means this slot in table is in use */
means this slot in table is in use
(an IPADDR_ID address means the address
is not resolved yet) */
NCR_Instance data; /* Data for the protocol engine for this source */
char *name; /* Name of the source, may be NULL */
int pool; /* Number of the pool from which was this source
@@ -67,21 +70,21 @@ static int n_sources;
/* Flag indicating new sources will be started automatically when added */
static int auto_start_sources = 0;
/* Source with unknown address (which may be resolved later) */
/* Last assigned address ID */
static uint32_t last_address_id = 0;
/* Source scheduled for name resolving (first resolving or replacement) */
struct UnresolvedSource {
/* Current address of the source (IDADDR_ID is used for a single source
with unknown address and IPADDR_UNSPEC for a pool of sources */
NTP_Remote_Address address;
/* ID of the pool if not a single source */
int pool;
/* Name to be resolved */
char *name;
int port;
/* Flag indicating addresses should be used in a random order */
int random_order;
int replacement;
union {
struct {
NTP_Source_Type type;
SourceParameters params;
int pool;
int max_new_sources;
} new_source;
NTP_Remote_Address replace_source;
};
/* Next unresolved source in the list */
struct UnresolvedSource *next;
};
@@ -101,9 +104,13 @@ static NSR_SourceResolvingEndHandler resolving_end_handler = NULL;
/* Pool of sources with the same name */
struct SourcePool {
/* Number of sources added from this pool (ignoring tentative sources) */
/* Number of all sources from the pool */
int sources;
/* Maximum number of sources */
/* Number of sources with unresolved address */
int unresolved_sources;
/* Number of non-tentative sources */
int confirmed_sources;
/* Maximum number of confirmed sources */
int max_sources;
};
@@ -113,9 +120,11 @@ static ARR_Instance pools;
/* ================================================== */
/* Forward prototypes */
static void resolve_sources(void *arg);
static void resolve_sources(void);
static void rehash_records(void);
static void clean_source_record(SourceRecord *record);
static void remove_pool_sources(int pool, int tentative, int unresolved);
static void remove_unresolved_source(struct UnresolvedSource *us);
static void
slew_sources(struct timespec *raw,
@@ -140,6 +149,14 @@ get_record(unsigned index)
/* ================================================== */
static struct SourcePool *
get_pool(unsigned index)
{
return (struct SourcePool *)ARR_GetElement(pools, index);
}
/* ================================================== */
void
NSR_Initialise(void)
{
@@ -160,11 +177,8 @@ void
NSR_Finalise(void)
{
SourceRecord *record;
struct UnresolvedSource *us;
unsigned int i;
ARR_DestroyInstance(pools);
for (i = 0; i < ARR_GetSize(records); i++) {
record = get_record(i);
if (record->remote_addr)
@@ -172,13 +186,10 @@ NSR_Finalise(void)
}
ARR_DestroyInstance(records);
ARR_DestroyInstance(pools);
while (unresolved_sources) {
us = unresolved_sources;
unresolved_sources = us->next;
Free(us->name);
Free(us);
}
while (unresolved_sources)
remove_unresolved_source(unresolved_sources);
initialised = 0;
}
@@ -211,7 +222,8 @@ find_slot(NTP_Remote_Address *remote_addr, int *slot, int *found)
*found = 0;
if (remote_addr->ip_addr.family != IPADDR_INET4 &&
remote_addr->ip_addr.family != IPADDR_INET6)
remote_addr->ip_addr.family != IPADDR_INET6 &&
remote_addr->ip_addr.family != IPADDR_ID)
return;
hash = UTI_IPToHash(&remote_addr->ip_addr);
@@ -295,7 +307,8 @@ add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type, So
return NSR_AlreadyInUse;
} else {
if (remote_addr->ip_addr.family != IPADDR_INET4 &&
remote_addr->ip_addr.family != IPADDR_INET6) {
remote_addr->ip_addr.family != IPADDR_INET6 &&
remote_addr->ip_addr.family != IPADDR_ID) {
return NSR_InvalidAF;
} else {
n_sources++;
@@ -307,13 +320,19 @@ add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type, So
assert(!found);
record = get_record(slot);
record->data = NCR_GetInstance(remote_addr, type, params);
record->data = NCR_CreateInstance(remote_addr, type, params, name);
record->remote_addr = NCR_GetRemoteAddress(record->data);
record->name = name ? Strdup(name) : NULL;
record->pool = pool;
record->tentative = 1;
if (auto_start_sources)
if (record->pool != INVALID_POOL) {
get_pool(record->pool)->sources++;
if (!UTI_IsIPReal(&remote_addr->ip_addr))
get_pool(record->pool)->unresolved_sources++;
}
if (auto_start_sources && UTI_IsIPReal(&remote_addr->ip_addr))
NCR_StartInstance(record->data);
return NSR_Success;
@@ -324,70 +343,109 @@ add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type, So
/* ================================================== */
static NSR_Status
replace_source(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr)
change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr,
int replacement)
{
int slot1, slot2, found;
SourceRecord *record;
struct SourcePool *pool;
LOG_Severity severity;
char *name;
find_slot(old_addr, &slot1, &found);
if (!found)
return NSR_NoSuchSource;
/* Make sure there is no other source using the new address (with the same
or different port), but allow a source to have its port changed */
find_slot(new_addr, &slot2, &found);
if (found)
if (found == 2 || (found != 0 && slot1 != slot2))
return NSR_AlreadyInUse;
record = get_record(slot1);
NCR_ChangeRemoteAddress(record->data, new_addr);
NCR_ChangeRemoteAddress(record->data, new_addr, !replacement);
record->remote_addr = NCR_GetRemoteAddress(record->data);
if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr->ip_addr)) {
if (auto_start_sources)
NCR_StartInstance(record->data);
if (record->pool != INVALID_POOL)
get_pool(record->pool)->unresolved_sources--;
}
if (!record->tentative) {
record->tentative = 1;
if (record->pool != INVALID_POOL) {
pool = ARR_GetElement(pools, record->pool);
pool->sources--;
}
if (record->pool != INVALID_POOL)
get_pool(record->pool)->confirmed_sources--;
}
/* The hash table must be rebuilt for the new address */
rehash_records();
name = record->name;
severity = UTI_IsIPReal(&old_addr->ip_addr) ? LOGS_INFO : LOGS_DEBUG;
LOG(LOGS_INFO, "Source %s replaced with %s",
UTI_IPToString(&old_addr->ip_addr),
UTI_IPToString(&new_addr->ip_addr));
if (found == 0) {
/* The hash table must be rebuilt for the changed address */
rehash_records();
LOG(severity, "Source %s %s %s (%s)", UTI_IPToString(&old_addr->ip_addr),
replacement ? "replaced with" : "changed to",
UTI_IPToString(&new_addr->ip_addr), name ? name : "");
} else {
LOG(severity, "Source %s (%s) changed port to %d",
UTI_IPToString(&new_addr->ip_addr), name ? name : "", new_addr->port);
}
return NSR_Success;
}
/* ================================================== */
static int
replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr)
{
if (!NIO_IsServerConnectable(new_addr)) {
DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip_addr));
return 0;
}
if (change_source_address(old_addr, new_addr, 1) == NSR_AlreadyInUse)
return 0;
return 1;
}
/* ================================================== */
static void
process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs)
{
NTP_Remote_Address address;
int i, added;
NTP_Remote_Address old_addr, new_addr;
SourceRecord *record;
unsigned short first = 0;
int i, j;
if (us->random_order)
UTI_GetRandomBytes(&first, sizeof (first));
for (i = added = 0; i < n_addrs; i++) {
address.ip_addr = ip_addrs[((unsigned int)i + first) % n_addrs];
address.port = us->port;
for (i = 0; i < n_addrs; i++) {
new_addr.ip_addr = ip_addrs[((unsigned int)i + first) % n_addrs];
DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&address.ip_addr));
DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr));
if (us->replacement) {
if (replace_source(&us->replace_source, &address) != NSR_AlreadyInUse)
break;
if (us->pool != INVALID_POOL) {
/* In the pool resolving mode, try to replace all sources from
the pool which don't have a real address yet */
for (j = 0; j < ARR_GetSize(records); j++) {
record = get_record(j);
if (!record->remote_addr || record->pool != us->pool ||
UTI_IsIPReal(&record->remote_addr->ip_addr))
continue;
old_addr = *record->remote_addr;
new_addr.port = old_addr.port;
if (replace_source_connectable(&old_addr, &new_addr))
break;
}
} else {
if (add_source(&address, us->name, us->new_source.type, &us->new_source.params,
us->new_source.pool) == NSR_Success)
added++;
if (added >= us->new_source.max_new_sources)
new_addr.port = us->address.port;
if (replace_source_connectable(&us->address, &new_addr))
break;
}
}
@@ -395,14 +453,41 @@ process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs
/* ================================================== */
static int
is_resolved(struct UnresolvedSource *us)
{
int slot, found;
if (us->pool != INVALID_POOL) {
return get_pool(us->pool)->unresolved_sources <= 0;
} else {
/* If the address is no longer present, it was removed or replaced
(i.e. resolved) */
find_slot(&us->address, &slot, &found);
return !found;
}
}
/* ================================================== */
static void
resolve_sources_timeout(void *arg)
{
resolving_id = 0;
resolve_sources();
}
/* ================================================== */
static void
name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything)
{
struct UnresolvedSource *us, **i, *next;
struct UnresolvedSource *us, *next;
us = (struct UnresolvedSource *)anything;
assert(us == resolving_source);
assert(resolving_id == 0);
DEBUG_LOG("%s resolved to %d addrs", us->name, n_addrs);
@@ -421,18 +506,10 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any
next = us->next;
/* Remove the source from the list on success or failure, replacements
are removed on any status */
if (us->replacement || status != DNS_TryAgain) {
for (i = &unresolved_sources; *i; i = &(*i)->next) {
if (*i == us) {
*i = us->next;
Free(us->name);
Free(us);
break;
}
}
}
/* 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))
remove_unresolved_source(us);
resolving_source = next;
@@ -444,12 +521,10 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any
/* This was the last source in the list. If some sources couldn't
be resolved, try again in exponentially increasing interval. */
if (unresolved_sources) {
if (resolving_interval < MIN_RESOLVE_INTERVAL)
resolving_interval = MIN_RESOLVE_INTERVAL;
else if (resolving_interval < MAX_RESOLVE_INTERVAL)
resolving_interval++;
resolving_id = SCH_AddTimeoutByDelay(RESOLVE_INTERVAL_UNIT *
(1 << resolving_interval), resolve_sources, NULL);
resolving_interval = CLAMP(MIN_RESOLVE_INTERVAL, resolving_interval + 1,
MAX_RESOLVE_INTERVAL);
resolving_id = SCH_AddTimeoutByDelay(RESOLVE_INTERVAL_UNIT * (1 << resolving_interval),
resolve_sources_timeout, NULL);
} else {
resolving_interval = 0;
}
@@ -463,12 +538,22 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any
/* ================================================== */
static void
resolve_sources(void *arg)
resolve_sources(void)
{
struct UnresolvedSource *us;
struct UnresolvedSource *us, *next, *i;
assert(!resolving_source);
/* Remove sources that don't need to be resolved anymore */
for (i = unresolved_sources; i; i = next) {
next = i->next;
if (is_resolved(i))
remove_unresolved_source(i);
}
if (!unresolved_sources)
return;
PRV_ReloadDNS();
/* Start with the first source in the list, name_resolve_handler
@@ -495,6 +580,23 @@ append_unresolved_source(struct UnresolvedSource *us)
/* ================================================== */
static void
remove_unresolved_source(struct UnresolvedSource *us)
{
struct UnresolvedSource **i;
for (i = &unresolved_sources; *i; i = &(*i)->next) {
if (*i == us) {
*i = us->next;
Free(us->name);
Free(us);
break;
}
}
}
/* ================================================== */
NSR_Status
NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SourceParameters *params)
{
@@ -503,41 +605,60 @@ NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SourceParam
/* ================================================== */
void
NSR_Status
NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type, SourceParameters *params)
{
struct UnresolvedSource *us;
struct SourcePool *sp;
NTP_Remote_Address remote_addr;
int i, new_sources;
/* If the name is an IP address, don't bother with full resolving now
or later when trying to replace the source */
if (UTI_StringToIP(name, &remote_addr.ip_addr)) {
remote_addr.port = port;
NSR_AddSource(&remote_addr, type, params);
return;
return NSR_AddSource(&remote_addr, type, params);
}
/* Make sure the name is at least printable and has no spaces */
for (i = 0; name[i] != '\0'; i++) {
if (!isgraph(name[i]))
return NSR_InvalidName;
}
us = MallocNew(struct UnresolvedSource);
us->name = Strdup(name);
us->port = port;
us->random_order = 0;
us->replacement = 0;
us->new_source.type = type;
us->new_source.params = *params;
remote_addr.ip_addr.family = IPADDR_ID;
remote_addr.ip_addr.addr.id = ++last_address_id;
remote_addr.port = port;
if (!pool) {
us->new_source.pool = INVALID_POOL;
us->new_source.max_new_sources = 1;
us->pool = INVALID_POOL;
us->address = remote_addr;
new_sources = 1;
} else {
sp = (struct SourcePool *)ARR_GetNewElement(pools);
sp->sources = 0;
sp->max_sources = params->max_sources;
us->new_source.pool = ARR_GetSize(pools) - 1;
us->new_source.max_new_sources = MAX_POOL_SOURCES;
sp->unresolved_sources = 0;
sp->confirmed_sources = 0;
sp->max_sources = CLAMP(1, params->max_sources, MAX_POOL_SOURCES);
us->pool = ARR_GetSize(pools) - 1;
us->address.ip_addr.family = IPADDR_UNSPEC;
new_sources = MIN(2 * sp->max_sources, MAX_POOL_SOURCES);
}
append_unresolved_source(us);
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) != NSR_Success)
return NSR_TooManySources;
}
return NSR_UnresolvedName;
}
/* ================================================== */
@@ -557,11 +678,12 @@ NSR_ResolveSources(void)
if (unresolved_sources) {
/* Make sure no resolving is currently running */
if (!resolving_source) {
if (resolving_interval) {
if (resolving_id != 0) {
SCH_RemoveTimeout(resolving_id);
resolving_id = 0;
resolving_interval--;
}
resolve_sources(NULL);
resolve_sources();
}
} else {
/* No unresolved sources, we are done */
@@ -574,10 +696,12 @@ NSR_ResolveSources(void)
void NSR_StartSources(void)
{
NTP_Remote_Address *addr;
unsigned int i;
for (i = 0; i < ARR_GetSize(records); i++) {
if (!get_record(i)->remote_addr)
addr = get_record(i)->remote_addr;
if (!addr || !UTI_IsIPReal(&addr->ip_addr))
continue;
NCR_StartInstance(get_record(i)->data);
}
@@ -596,6 +720,19 @@ static void
clean_source_record(SourceRecord *record)
{
assert(record->remote_addr);
if (record->pool != INVALID_POOL) {
struct SourcePool *pool = get_pool(record->pool);
pool->sources--;
if (!UTI_IsIPReal(&record->remote_addr->ip_addr))
pool->unresolved_sources--;
if (!record->tentative)
pool->confirmed_sources--;
if (pool->max_sources > pool->sources)
pool->max_sources = pool->sources;
}
record->remote_addr = NULL;
NCR_DestroyInstance(record->data);
if (record->name)
@@ -661,14 +798,13 @@ resolve_source_replacement(SourceRecord *record)
us = MallocNew(struct UnresolvedSource);
us->name = Strdup(record->name);
us->port = record->remote_addr->port;
/* If there never was a valid reply from this source (e.g. it was a bad
replacement), ignore the order of addresses from the resolver to not get
stuck to a pair of addresses if the order doesn't change, or a group of
IPv4/IPv6 addresses if the resolver prefers inaccessible IP family */
us->random_order = record->tentative;
us->replacement = 1;
us->replace_source = *record->remote_addr;
us->pool = INVALID_POOL;
us->address = *record->remote_addr;
append_unresolved_source(us);
NSR_ResolveSources();
@@ -730,7 +866,18 @@ NSR_RefreshAddresses(void)
/* ================================================== */
static void remove_tentative_pool_sources(int pool)
NSR_Status
NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr)
{
if (new_addr->ip_addr.family == IPADDR_UNSPEC)
return NSR_InvalidAF;
return change_source_address(old_addr, new_addr, 0);
}
/* ================================================== */
static void remove_pool_sources(int pool, int tentative, int unresolved)
{
SourceRecord *record;
unsigned int i, removed;
@@ -738,10 +885,14 @@ static void remove_tentative_pool_sources(int pool)
for (i = removed = 0; i < ARR_GetSize(records); i++) {
record = get_record(i);
if (!record->remote_addr || record->pool != pool || !record->tentative)
if (!record->remote_addr || record->pool != pool)
continue;
DEBUG_LOG("removing tentative source %s",
if ((tentative && !record->tentative) ||
(unresolved && UTI_IsIPReal(&record->remote_addr->ip_addr)))
continue;
DEBUG_LOG("removing %ssource %s", tentative ? "tentative " : "",
UTI_IPToString(&record->remote_addr->ip_addr));
clean_source_record(record);
@@ -772,6 +923,29 @@ NSR_GetLocalRefid(IPAddr *address)
/* ================================================== */
char *
NSR_GetName(IPAddr *address)
{
NTP_Remote_Address remote_addr;
int slot, found;
SourceRecord *record;
remote_addr.ip_addr = *address;
remote_addr.port = 0;
find_slot(&remote_addr, &slot, &found);
if (!found)
return NULL;
record = get_record(slot);
if (record->name)
return record->name;
return UTI_IPToString(&record->remote_addr->ip_addr);
}
/* ================================================== */
/* This routine is called by ntp_io when a new packet arrives off the network,
possibly with an authentication tail */
void
@@ -796,15 +970,15 @@ NSR_ProcessRx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
record->tentative = 0;
if (record->pool != INVALID_POOL) {
pool = ARR_GetElement(pools, record->pool);
pool->sources++;
pool = get_pool(record->pool);
pool->confirmed_sources++;
DEBUG_LOG("pool %s has %d confirmed sources", record->name, pool->sources);
DEBUG_LOG("pool %s has %d confirmed sources", record->name, pool->confirmed_sources);
/* If the number of sources from the pool reached the configured
maximum, remove the remaining tentative sources */
if (pool->sources >= pool->max_sources)
remove_tentative_pool_sources(record->pool);
if (pool->confirmed_sources >= pool->max_sources)
remove_pool_sources(record->pool, 1, 0);
}
}
} else {
@@ -873,7 +1047,10 @@ NSR_SetConnectivity(IPAddr *mask, IPAddr *address, SRC_Connectivity connectivity
for (i = 0; i < ARR_GetSize(records); i++) {
record = get_record(i);
if (record->remote_addr) {
if (address->family == IPADDR_UNSPEC ||
/* Ignore SRC_MAYBE_ONLINE connectivity change for unspecified unresolved
sources as they would always end up in the offline state */
if ((address->family == IPADDR_UNSPEC &&
(connectivity != SRC_MAYBE_ONLINE || UTI_IsIPReal(&record->remote_addr->ip_addr))) ||
!UTI_CompareIPs(&record->remote_addr->ip_addr, address, mask)) {
any = 1;
if (NCR_IsSyncPeer(record->data)) {
@@ -889,17 +1066,6 @@ NSR_SetConnectivity(IPAddr *mask, IPAddr *address, SRC_Connectivity connectivity
if (syncpeer)
NCR_SetConnectivity(syncpeer->data, connectivity);
if (address->family == IPADDR_UNSPEC) {
struct UnresolvedSource *us;
for (us = unresolved_sources; us; us = us->next) {
if (us->replacement)
continue;
any = 1;
us->new_source.params.connectivity = connectivity;
}
}
return any;
}
@@ -1110,28 +1276,39 @@ NSR_GetActivityReport(RPT_ActivityReport *report)
{
SourceRecord *record;
unsigned int i;
struct UnresolvedSource *us;
report->online = 0;
report->offline = 0;
report->burst_online = 0;
report->burst_offline = 0;
report->unresolved = 0;
for (i = 0; i < ARR_GetSize(records); i++) {
record = get_record(i);
if (record->remote_addr) {
if (!record->remote_addr)
continue;
if (!UTI_IsIPReal(&record->remote_addr->ip_addr)) {
report->unresolved++;
} else {
NCR_IncrementActivityCounters(record->data, &report->online, &report->offline,
&report->burst_online, &report->burst_offline);
}
}
report->unresolved = 0;
for (us = unresolved_sources; us; us = us->next) {
report->unresolved++;
}
}
/* ================================================== */
void
NSR_DumpAuthData(void)
{
SourceRecord *record;
int i;
for (i = 0; i < ARR_GetSize(records); i++) {
record = get_record(i);
if (!record->remote_addr)
continue;
NCR_DumpAuthData(record->data);
}
}

View File

@@ -44,7 +44,9 @@ typedef enum {
NSR_NoSuchSource, /* Remove - attempt to remove a source that is not known */
NSR_AlreadyInUse, /* AddSource - attempt to add a source that is already known */
NSR_TooManySources, /* AddSource - too many sources already present */
NSR_InvalidAF /* AddSource - attempt to add a source with invalid address family */
NSR_InvalidAF, /* AddSource - attempt to add a source with invalid address family */
NSR_InvalidName, /* AddSourceByName - attempt to add a source with invalid name */
NSR_UnresolvedName, /* AddSourceByName - name will be resolved later */
} NSR_Status;
/* Procedure to add a new server or peer source. */
@@ -52,8 +54,10 @@ 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. */
extern void NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type, SourceParameters *params);
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,
SourceParameters *params);
/* Function type for handlers to be called back when an attempt
* (possibly unsuccessful) to resolve unresolved sources ends */
@@ -83,9 +87,17 @@ extern void NSR_HandleBadSource(IPAddr *address);
/* Procedure to resolve all names again */
extern void NSR_RefreshAddresses(void);
/* Procedure to update the address of a source */
extern NSR_Status NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr,
NTP_Remote_Address *new_addr);
/* Procedure to get local reference ID corresponding to a source */
extern uint32_t NSR_GetLocalRefid(IPAddr *address);
/* Procedure to get the name of a source. If the source doesn't have a name,
it returns a temporary string containing formatted address. */
extern char *NSR_GetName(IPAddr *address);
/* This routine is called by ntp_io when a new packet arrives off the network */
extern void NSR_ProcessRx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length);
@@ -128,4 +140,6 @@ extern int NSR_GetNTPReport(RPT_NTPReport *report);
extern void NSR_GetActivityReport(RPT_ActivityReport *report);
extern void NSR_DumpAuthData(void);
#endif /* GOT_NTP_SOURCES_H */

79
nts_ke.h Normal file
View File

@@ -0,0 +1,79 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for the NTS Key Establishment protocol
*/
#ifndef GOT_NTS_KE_H
#define GOT_NTS_KE_H
#include "siv.h"
#define NKE_RECORD_CRITICAL_BIT (1U << 15)
#define NKE_RECORD_END_OF_MESSAGE 0
#define NKE_RECORD_NEXT_PROTOCOL 1
#define NKE_RECORD_ERROR 2
#define NKE_RECORD_WARNING 3
#define NKE_RECORD_AEAD_ALGORITHM 4
#define NKE_RECORD_COOKIE 5
#define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6
#define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7
#define NKE_NEXT_PROTOCOL_NTPV4 0
#define NKE_ERROR_UNRECOGNIZED_CRITICAL_RECORD 0
#define NKE_ERROR_BAD_REQUEST 1
#define NKE_ERROR_INTERNAL_SERVER_ERROR 2
#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
#define NKE_MAX_COOKIE_LENGTH 256
#define NKE_MAX_COOKIES 8
#define NKE_MAX_KEY_LENGTH SIV_MAX_KEY_LENGTH
#define NKE_RETRY_FACTOR2_CONNECT 4
#define NKE_RETRY_FACTOR2_TLS 10
#define NKE_MAX_RETRY_INTERVAL2 19
typedef struct {
int length;
unsigned char key[NKE_MAX_KEY_LENGTH];
} NKE_Key;
typedef struct {
SIV_Algorithm algorithm;
NKE_Key c2s;
NKE_Key s2c;
} NKE_Context;
typedef struct {
int length;
unsigned char cookie[NKE_MAX_COOKIE_LENGTH];
} NKE_Cookie;
#endif

400
nts_ke_client.c Normal file
View File

@@ -0,0 +1,400 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
NTS-KE client
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ke_client.h"
#include "conf.h"
#include "logging.h"
#include "memory.h"
#include "nameserv_async.h"
#include "nts_ke_session.h"
#include "siv.h"
#include "socket.h"
#include "util.h"
#define CLIENT_TIMEOUT 16.0
struct NKC_Instance_Record {
char *name;
IPSockAddr address;
NKSN_Instance session;
int destroying;
int got_response;
int resolving_name;
NKE_Context context;
NKE_Cookie cookies[NKE_MAX_COOKIES];
int num_cookies;
char server_name[NKE_MAX_RECORD_BODY_LENGTH + 1];
IPSockAddr ntp_address;
};
/* ================================================== */
static void *client_credentials;
/* ================================================== */
static void
name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg)
{
NKC_Instance inst = arg;
int i;
inst->resolving_name = 0;
if (inst->destroying) {
NKC_DestroyInstance(inst);
return;
}
if (status != DNS_Success || n_addrs < 1) {
LOG(LOGS_ERR, "Could not resolve NTP server %s from %s", inst->server_name, inst->name);
/* Force restart */
inst->got_response = 0;
return;
}
inst->ntp_address.ip_addr = ip_addrs[0];
/* Prefer an address of the same family as NTS-KE */
for (i = 0; i < n_addrs; i++) {
DEBUG_LOG("%s resolved to %s", inst->server_name, UTI_IPToString(&ip_addrs[i]));
if (ip_addrs[i].family == inst->address.ip_addr.family) {
inst->ntp_address.ip_addr = ip_addrs[i];
break;
}
}
}
/* ================================================== */
static int
prepare_request(NKC_Instance inst)
{
NKSN_Instance session = inst->session;
uint16_t datum;
NKSN_BeginMessage(session);
datum = htons(NKE_NEXT_PROTOCOL_NTPV4);
if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum)))
return 0;
datum = htons(AEAD_AES_SIV_CMAC_256);
if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, &datum, sizeof (datum)))
return 0;
if (!NKSN_EndMessage(session))
return 0;
return 1;
}
/* ================================================== */
static int
process_response(NKC_Instance inst)
{
int next_protocol = -1, aead_algorithm = -1, error = 0;
int i, critical, type, length;
uint16_t data[NKE_MAX_COOKIE_LENGTH / sizeof (uint16_t)];
assert(NKE_MAX_COOKIE_LENGTH % sizeof (uint16_t) == 0);
assert(sizeof (uint16_t) == 2);
inst->num_cookies = 0;
inst->ntp_address.ip_addr.family = IPADDR_UNSPEC;
inst->ntp_address.port = 0;
inst->server_name[0] = '\0';
while (!error) {
if (!NKSN_GetRecord(inst->session, &critical, &type, &length, &data, sizeof (data)))
break;
switch (type) {
case NKE_RECORD_NEXT_PROTOCOL:
if (!critical || length != 2 || ntohs(data[0]) != NKE_NEXT_PROTOCOL_NTPV4) {
DEBUG_LOG("Unexpected NTS-KE next protocol");
error = 1;
break;
}
next_protocol = NKE_NEXT_PROTOCOL_NTPV4;
break;
case NKE_RECORD_AEAD_ALGORITHM:
if (length != 2 || ntohs(data[0]) != AEAD_AES_SIV_CMAC_256) {
DEBUG_LOG("Unexpected NTS-KE AEAD algorithm");
error = 1;
break;
}
aead_algorithm = AEAD_AES_SIV_CMAC_256;
inst->context.algorithm = aead_algorithm;
break;
case NKE_RECORD_ERROR:
if (length == 2)
DEBUG_LOG("NTS-KE error %d", ntohs(data[0]));
error = 1;
break;
case NKE_RECORD_WARNING:
if (length == 2)
DEBUG_LOG("NTS-KE warning %d", ntohs(data[0]));
error = 1;
break;
case NKE_RECORD_COOKIE:
DEBUG_LOG("Got cookie #%d length=%d", inst->num_cookies + 1, length);
assert(NKE_MAX_COOKIE_LENGTH == sizeof (inst->cookies[inst->num_cookies].cookie));
if (length <= NKE_MAX_COOKIE_LENGTH && inst->num_cookies < NKE_MAX_COOKIES) {
inst->cookies[inst->num_cookies].length = length;
memcpy(inst->cookies[inst->num_cookies].cookie, data, length);
inst->num_cookies++;
}
break;
case NKE_RECORD_NTPV4_SERVER_NEGOTIATION:
if (length < 1 || length >= sizeof (inst->server_name)) {
DEBUG_LOG("Invalid server name");
error = 1;
break;
}
memcpy(inst->server_name, data, length);
inst->server_name[length] = '\0';
/* Make sure the name is printable and has no spaces */
for (i = 0; i < length && isgraph(inst->server_name[i]); i++)
;
if (i != length) {
DEBUG_LOG("Invalid server name");
error = 1;
break;
}
DEBUG_LOG("Negotiated server %s", inst->server_name);
break;
case NKE_RECORD_NTPV4_PORT_NEGOTIATION:
if (length != 2) {
DEBUG_LOG("Invalid port");
error = 1;
break;
}
inst->ntp_address.port = ntohs(data[0]);
DEBUG_LOG("Negotiated port %d", inst->ntp_address.port);
break;
default:
DEBUG_LOG("Unknown record type=%d length=%d critical=%d", type, length, critical);
if (critical)
error = 1;
}
}
DEBUG_LOG("NTS-KE response: error=%d next=%d aead=%d",
error, next_protocol, aead_algorithm);
if (error || inst->num_cookies == 0 ||
next_protocol != NKE_NEXT_PROTOCOL_NTPV4 ||
aead_algorithm != AEAD_AES_SIV_CMAC_256)
return 0;
return 1;
}
/* ================================================== */
static int
handle_message(void *arg)
{
NKC_Instance inst = arg;
if (!process_response(inst)) {
LOG(LOGS_ERR, "Received invalid NTS-KE response from %s", inst->name);
return 0;
}
if (!NKSN_GetKeys(inst->session, inst->context.algorithm,
&inst->context.c2s, &inst->context.s2c))
return 0;
if (inst->server_name[0] != '\0') {
if (inst->resolving_name)
return 0;
if (!UTI_StringToIP(inst->server_name, &inst->ntp_address.ip_addr)) {
DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, inst);
inst->resolving_name = 1;
}
}
inst->got_response = 1;
return 1;
}
/* ================================================== */
void
NKC_Initialise(void)
{
client_credentials = NULL;
}
/* ================================================== */
void
NKC_Finalise(void)
{
if (client_credentials)
NKSN_DestroyCertCredentials(client_credentials);
}
/* ================================================== */
NKC_Instance
NKC_CreateInstance(IPSockAddr *address, const char *name)
{
NKC_Instance inst;
inst = MallocNew(struct NKC_Instance_Record);
inst->address = *address;
inst->name = Strdup(name);
inst->session = NKSN_CreateInstance(0, inst->name, handle_message, inst);
inst->resolving_name = 0;
inst->destroying = 0;
inst->got_response = 0;
/* Create the credentials with the first client instance and share them
with other instances */
if (!client_credentials)
client_credentials = NKSN_CreateCertCredentials(NULL, NULL, CNF_GetNtsTrustedCertFile());
return inst;
}
/* ================================================== */
void
NKC_DestroyInstance(NKC_Instance inst)
{
/* If the resolver is running, destroy the instance later when finished */
if (inst->resolving_name) {
inst->destroying = 1;
return;
}
NKSN_DestroyInstance(inst->session);
Free(inst->name);
Free(inst);
}
/* ================================================== */
int
NKC_Start(NKC_Instance inst)
{
IPSockAddr local_addr;
char label[512];
int sock_fd;
assert(!NKC_IsActive(inst));
if (!client_credentials) {
DEBUG_LOG("Missing client credentials");
return 0;
}
/* Follow the bindacqaddress setting */
CNF_GetBindAcquisitionAddress(inst->address.ip_addr.family, &local_addr.ip_addr);
if (local_addr.ip_addr.family != inst->address.ip_addr.family)
SCK_GetAnyLocalIPAddress(inst->address.ip_addr.family, &local_addr.ip_addr);
local_addr.port = 0;
sock_fd = SCK_OpenTcpSocket(&inst->address, &local_addr, 0);
if (sock_fd < 0)
return 0;
/* Make a label containing both the address and name of the server */
if (snprintf(label, sizeof (label), "%s (%s)",
UTI_IPSockAddrToString(&inst->address), inst->name) >= sizeof (label))
;
/* Start a NTS-KE session */
if (!NKSN_StartSession(inst->session, sock_fd, label, client_credentials, CLIENT_TIMEOUT)) {
SCK_CloseSocket(sock_fd);
return 0;
}
/* Send a request */
if (!prepare_request(inst)) {
DEBUG_LOG("Could not prepare NTS-KE request");
NKSN_StopSession(inst->session);
return 0;
}
return 1;
}
/* ================================================== */
int
NKC_IsActive(NKC_Instance inst)
{
return !NKSN_IsStopped(inst->session) || inst->resolving_name;
}
/* ================================================== */
int
NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address)
{
int i;
if (!inst->got_response || inst->resolving_name)
return 0;
*context = inst->context;
for (i = 0; i < inst->num_cookies && i < max_cookies; i++)
cookies[i] = inst->cookies[i];
*num_cookies = i;
*ntp_address = inst->ntp_address;
return i;
}
/* ================================================== */
int
NKC_GetRetryFactor(NKC_Instance inst)
{
return NKSN_GetRetryFactor(inst->session);
}

60
nts_ke_client.h Normal file
View File

@@ -0,0 +1,60 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for the NTS-KE client
*/
#ifndef GOT_NTS_KE_CLIENT_H
#define GOT_NTS_KE_CLIENT_H
#include "addressing.h"
#include "nts_ke.h"
typedef struct NKC_Instance_Record *NKC_Instance;
/* Init and fini functions */
extern void NKC_Initialise(void);
extern void NKC_Finalise(void);
/* Create a client NTS-KE instance */
extern NKC_Instance NKC_CreateInstance(IPSockAddr *address, const char *name);
/* Destroy an instance */
extern void NKC_DestroyInstance(NKC_Instance inst);
/* Connect to the server, start an NTS-KE session, send an NTS-KE request, and
process the response (asynchronously) */
extern int NKC_Start(NKC_Instance inst);
/* Check if the client is still running */
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,
NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address);
/* Get a factor to calculate retry interval (in log2 seconds) */
extern int NKC_GetRetryFactor(NKC_Instance inst);
#endif

857
nts_ke_server.c Normal file
View File

@@ -0,0 +1,857 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
NTS-KE server
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ke_server.h"
#include "array.h"
#include "conf.h"
#include "clientlog.h"
#include "logging.h"
#include "memory.h"
#include "ntp_core.h"
#include "nts_ke_session.h"
#include "siv.h"
#include "socket.h"
#include "sched.h"
#include "sys.h"
#include "util.h"
#define SERVER_TIMEOUT 2.0
#define SERVER_COOKIE_SIV AEAD_AES_SIV_CMAC_256
#define SERVER_COOKIE_NONCE_LENGTH 16
#define KEY_ID_INDEX_BITS 2
#define MAX_SERVER_KEYS (1U << KEY_ID_INDEX_BITS)
#define FUTURE_KEYS 1
#define DUMP_FILENAME "ntskeys"
#define DUMP_IDENTIFIER "NKS0\n"
#define INVALID_SOCK_FD (-7)
typedef struct {
uint32_t key_id;
uint8_t nonce[SERVER_COOKIE_NONCE_LENGTH];
} ServerCookieHeader;
typedef struct {
uint32_t id;
unsigned char key[SIV_MAX_KEY_LENGTH];
SIV_Instance siv;
} ServerKey;
typedef struct {
uint32_t key_id;
unsigned char key[SIV_MAX_KEY_LENGTH];
IPAddr client_addr;
uint16_t client_port;
uint16_t _pad;
} HelperRequest;
/* ================================================== */
static ServerKey server_keys[MAX_SERVER_KEYS];
static int current_server_key;
static double last_server_key_ts;
static int key_rotation_interval;
static int server_sock_fd4;
static int server_sock_fd6;
static int helper_sock_fd;
static int initialised = 0;
/* Array of NKSN instances */
static ARR_Instance sessions;
static void *server_credentials;
/* ================================================== */
static int handle_message(void *arg);
/* ================================================== */
static int
handle_client(int sock_fd, IPSockAddr *addr)
{
NKSN_Instance inst, *instp;
int i;
if (sock_fd > FD_SETSIZE / 2) {
DEBUG_LOG("Rejected connection from %s (%s)",
UTI_IPSockAddrToString(addr), "too many descriptors");
return 0;
}
/* Find a slot which is free or has a stopped session */
for (i = 0, inst = NULL; i < ARR_GetSize(sessions); i++) {
instp = ARR_GetElement(sessions, i);
if (!*instp) {
/* NULL handler arg will be replaced with the session instance */
inst = NKSN_CreateInstance(1, NULL, handle_message, NULL);
*instp = inst;
break;
} else if (NKSN_IsStopped(*instp)) {
inst = *instp;
break;
}
}
if (!inst) {
DEBUG_LOG("Rejected connection from %s (%s)",
UTI_IPSockAddrToString(addr), "too many connections");
return 0;
}
if (!NKSN_StartSession(inst, sock_fd, UTI_IPSockAddrToString(addr),
server_credentials, SERVER_TIMEOUT))
return 0;
return 1;
}
/* ================================================== */
static void
handle_helper_request(int fd, int event, void *arg)
{
SCK_Message *message;
HelperRequest *req;
IPSockAddr client_addr;
int sock_fd;
message = SCK_ReceiveMessage(fd, SCK_FLAG_MSG_DESCRIPTOR);
if (!message)
return;
sock_fd = message->descriptor;
if (sock_fd < 0) {
/* Message with no descriptor is a shutdown command */
SCH_QuitProgram();
return;
}
if (message->length != sizeof (HelperRequest)) {
DEBUG_LOG("Unexpected message length");
SCK_CloseSocket(sock_fd);
return;
}
req = message->data;
/* Extract the server key and client address from the request */
server_keys[current_server_key].id = ntohl(req->key_id);
memcpy(server_keys[current_server_key].key, req->key,
sizeof (server_keys[current_server_key].key));
UTI_IPNetworkToHost(&req->client_addr, &client_addr.ip_addr);
client_addr.port = ntohs(req->client_port);
if (!SIV_SetKey(server_keys[current_server_key].siv, server_keys[current_server_key].key,
SIV_GetKeyLength(SERVER_COOKIE_SIV)))
assert(0);
if (!handle_client(sock_fd, &client_addr)) {
SCK_CloseSocket(sock_fd);
return;
}
DEBUG_LOG("Accepted helper request fd=%d", sock_fd);
}
/* ================================================== */
static void
accept_connection(int server_fd, int event, void *arg)
{
SCK_Message message;
IPSockAddr addr;
int log_index, sock_fd;
struct timespec now;
sock_fd = SCK_AcceptConnection(server_fd, &addr);
if (sock_fd < 0)
return;
if (!NCR_CheckAccessRestriction(&addr.ip_addr)) {
DEBUG_LOG("Rejected connection from %s (%s)",
UTI_IPSockAddrToString(&addr), "access denied");
SCK_CloseSocket(sock_fd);
return;
}
SCH_GetLastEventTime(&now, NULL, NULL);
log_index = CLG_LogNTPAccess(&addr.ip_addr, &now);
if (log_index >= 0 && CLG_LimitNTPResponseRate(log_index)) {
DEBUG_LOG("Rejected connection from %s (%s)",
UTI_IPSockAddrToString(&addr), "rate limit");
SCK_CloseSocket(sock_fd);
return;
}
/* Pass the socket to a helper process if enabled. Otherwise, handle the
client in the main process. */
if (helper_sock_fd != INVALID_SOCK_FD) {
HelperRequest req;
/* Include the current server key and client address in the request */
memset(&req, 0, sizeof (req));
req.key_id = htonl(server_keys[current_server_key].id);
memcpy(req.key, server_keys[current_server_key].key, sizeof (req.key));
UTI_IPHostToNetwork(&addr.ip_addr, &req.client_addr);
req.client_port = htons(addr.port);
SCK_InitMessage(&message, SCK_ADDR_UNSPEC);
message.data = &req;
message.length = sizeof (req);
message.descriptor = sock_fd;
if (!SCK_SendMessage(helper_sock_fd, &message, SCK_FLAG_MSG_DESCRIPTOR)) {
SCK_CloseSocket(sock_fd);
return;
}
SCK_CloseSocket(sock_fd);
} else {
if (!handle_client(sock_fd, &addr)) {
SCK_CloseSocket(sock_fd);
return;
}
}
DEBUG_LOG("Accepted connection from %s fd=%d", UTI_IPSockAddrToString(&addr), sock_fd);
}
/* ================================================== */
static int
open_socket(int family, int port)
{
IPSockAddr local_addr;
int sock_fd;
if (!SCK_IsFamilySupported(family))
return INVALID_SOCK_FD;
CNF_GetBindAddress(family, &local_addr.ip_addr);
if (local_addr.ip_addr.family != family)
SCK_GetAnyLocalIPAddress(family, &local_addr.ip_addr);
local_addr.port = port;
sock_fd = SCK_OpenTcpSocket(NULL, &local_addr, 0);
if (sock_fd < 0) {
LOG(LOGS_ERR, "Could not open NTS-KE socket on %s", UTI_IPSockAddrToString(&local_addr));
return INVALID_SOCK_FD;
}
if (!SCK_ListenOnSocket(sock_fd, CNF_GetNtsServerConnections())) {
SCK_CloseSocket(sock_fd);
return INVALID_SOCK_FD;
}
SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, accept_connection, NULL);
return sock_fd;
}
/* ================================================== */
static void
helper_signal(int x)
{
SCH_QuitProgram();
}
/* ================================================== */
static int
prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_algorithm)
{
NKE_Context context;
NKE_Cookie cookie;
char *ntp_server;
uint16_t datum;
int i;
DEBUG_LOG("NTS KE response: error=%d next=%d aead=%d", error, next_protocol, aead_algorithm);
NKSN_BeginMessage(session);
if (error >= 0) {
datum = htons(error);
if (!NKSN_AddRecord(session, 1, NKE_RECORD_ERROR, &datum, sizeof (datum)))
return 0;
} else {
datum = htons(next_protocol);
if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum)))
return 0;
datum = htons(aead_algorithm);
if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, &datum, sizeof (datum)))
return 0;
if (CNF_GetNTPPort() != NTP_PORT) {
datum = htons(CNF_GetNTPPort());
if (!NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_PORT_NEGOTIATION, &datum, sizeof (datum)))
return 0;
}
ntp_server = CNF_GetNtsNtpServer();
if (ntp_server) {
if (!NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_SERVER_NEGOTIATION,
ntp_server, strlen(ntp_server)))
return 0;
}
context.algorithm = aead_algorithm;
if (!NKSN_GetKeys(session, aead_algorithm, &context.c2s, &context.s2c))
return 0;
for (i = 0; i < NKE_MAX_COOKIES; i++) {
if (!NKS_GenerateCookie(&context, &cookie))
return 0;
if (!NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, cookie.cookie, cookie.length))
return 0;
}
}
if (!NKSN_EndMessage(session))
return 0;
return 1;
}
/* ================================================== */
static int
process_request(NKSN_Instance session)
{
int next_protocol = -1, aead_algorithm = -1, error = -1;
int i, critical, type, length;
uint16_t data[NKE_MAX_RECORD_BODY_LENGTH / sizeof (uint16_t)];
assert(NKE_MAX_RECORD_BODY_LENGTH % sizeof (uint16_t) == 0);
assert(sizeof (uint16_t) == 2);
while (error == -1) {
if (!NKSN_GetRecord(session, &critical, &type, &length, &data, sizeof (data)))
break;
switch (type) {
case NKE_RECORD_NEXT_PROTOCOL:
if (!critical || length < 2 || length % 2 != 0) {
error = NKE_ERROR_BAD_REQUEST;
break;
}
for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
if (ntohs(data[i]) == NKE_NEXT_PROTOCOL_NTPV4)
next_protocol = NKE_NEXT_PROTOCOL_NTPV4;
}
break;
case NKE_RECORD_AEAD_ALGORITHM:
if (length < 2 || length % 2 != 0) {
error = NKE_ERROR_BAD_REQUEST;
break;
}
for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
if (ntohs(data[i]) == AEAD_AES_SIV_CMAC_256)
aead_algorithm = AEAD_AES_SIV_CMAC_256;
}
break;
case NKE_RECORD_ERROR:
case NKE_RECORD_WARNING:
case NKE_RECORD_COOKIE:
error = NKE_ERROR_BAD_REQUEST;
break;
default:
if (critical)
error = NKE_ERROR_UNRECOGNIZED_CRITICAL_RECORD;
}
}
if (aead_algorithm < 0 || next_protocol < 0)
error = NKE_ERROR_BAD_REQUEST;
if (!prepare_response(session, error, next_protocol, aead_algorithm))
return 0;
return 1;
}
/* ================================================== */
static int
handle_message(void *arg)
{
NKSN_Instance session = arg;
return process_request(session);
}
/* ================================================== */
static void
generate_key(int index)
{
int key_length;
assert(index < MAX_SERVER_KEYS);
key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV);
if (key_length > sizeof (server_keys[index].key))
assert(0);
UTI_GetRandomBytesUrandom(server_keys[index].key, key_length);
if (!SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length))
assert(0);
UTI_GetRandomBytes(&server_keys[index].id, sizeof (server_keys[index].id));
server_keys[index].id &= -1U << KEY_ID_INDEX_BITS;
server_keys[index].id |= index;
DEBUG_LOG("Generated server key %"PRIX32, server_keys[index].id);
last_server_key_ts = SCH_GetLastEventMonoTime();
}
/* ================================================== */
static void
save_keys(void)
{
char buf[SIV_MAX_KEY_LENGTH * 2 + 1], *dump_dir;
int i, index, key_length;
double last_key_age;
FILE *f;
/* Don't save the keys if rotation is disabled to enable an external
management of the keys (e.g. share them with another server) */
if (key_rotation_interval == 0)
return;
dump_dir = CNF_GetNtsDumpDir();
if (!dump_dir)
return;
f = UTI_OpenFile(dump_dir, DUMP_FILENAME, ".tmp", 'w', 0600);
if (!f)
return;
key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV);
last_key_age = SCH_GetLastEventMonoTime() - last_server_key_ts;
if (fprintf(f, "%s%d %.1f\n", DUMP_IDENTIFIER, SERVER_COOKIE_SIV, last_key_age) < 0)
goto error;
for (i = 0; i < MAX_SERVER_KEYS; i++) {
index = (current_server_key + i + 1 + FUTURE_KEYS) % MAX_SERVER_KEYS;
if (key_length > sizeof (server_keys[index].key) ||
!UTI_BytesToHex(server_keys[index].key, key_length, buf, sizeof (buf)) ||
fprintf(f, "%08"PRIX32" %s\n", server_keys[index].id, buf) < 0)
goto error;
}
fclose(f);
if (!UTI_RenameTempFile(dump_dir, DUMP_FILENAME, ".tmp", NULL)) {
if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, ".tmp"))
;
}
return;
error:
DEBUG_LOG("Could not %s server keys", "save");
fclose(f);
if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, NULL))
;
}
/* ================================================== */
#define MAX_WORDS 2
static void
load_keys(void)
{
char *dump_dir, line[1024], *words[MAX_WORDS];
int i, index, key_length, algorithm;
double key_age;
FILE *f;
uint32_t id;
dump_dir = CNF_GetNtsDumpDir();
if (!dump_dir)
return;
f = UTI_OpenFile(dump_dir, DUMP_FILENAME, NULL, 'r', 0);
if (!f)
return;
if (!fgets(line, sizeof (line), f) || strcmp(line, DUMP_IDENTIFIER) != 0 ||
!fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 2 ||
sscanf(words[0], "%d", &algorithm) != 1 || algorithm != SERVER_COOKIE_SIV ||
sscanf(words[1], "%lf", &key_age) != 1)
goto error;
key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV);
last_server_key_ts = SCH_GetLastEventMonoTime() - MAX(key_age, 0.0);
for (i = 0; i < MAX_SERVER_KEYS && fgets(line, sizeof (line), f); i++) {
if (UTI_SplitString(line, words, MAX_WORDS) != 2 ||
sscanf(words[0], "%"PRIX32, &id) != 1)
goto error;
index = id % MAX_SERVER_KEYS;
if (UTI_HexToBytes(words[1], server_keys[index].key,
sizeof (server_keys[index].key)) != key_length)
goto error;
server_keys[index].id = id;
if (!SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length))
assert(0);
DEBUG_LOG("Loaded key %"PRIX32, id);
current_server_key = (index + MAX_SERVER_KEYS - FUTURE_KEYS) % MAX_SERVER_KEYS;
}
fclose(f);
return;
error:
DEBUG_LOG("Could not %s server keys", "load");
fclose(f);
}
/* ================================================== */
static void
key_timeout(void *arg)
{
current_server_key = (current_server_key + 1) % MAX_SERVER_KEYS;
generate_key((current_server_key + FUTURE_KEYS) % MAX_SERVER_KEYS);
save_keys();
SCH_AddTimeoutByDelay(key_rotation_interval, key_timeout, NULL);
}
/* ================================================== */
static void
start_helper(int id, int scfilter_level, int main_fd, int helper_fd)
{
pid_t pid;
pid = fork();
if (pid < 0)
LOG_FATAL("fork() failed : %s", strerror(errno));
if (pid > 0)
return;
SCK_CloseSocket(main_fd);
LOG_CloseParentFd();
SCH_Reset();
SCH_AddFileHandler(helper_fd, SCH_FILE_INPUT, handle_helper_request, NULL);
UTI_SetQuitSignalsHandler(helper_signal, 1);
if (scfilter_level != 0)
SYS_EnableSystemCallFilter(scfilter_level, SYS_NTSKE_HELPER);
initialised = 1;
DEBUG_LOG("NTS-KE helper #%d started", id);
SCH_MainLoop();
NKS_Finalise();
DEBUG_LOG("NTS-KE helper #%d exiting", id);
exit(0);
}
/* ================================================== */
void
NKS_Initialise(int scfilter_level)
{
char *cert, *key;
int i, processes;
double key_delay;
server_sock_fd4 = INVALID_SOCK_FD;
server_sock_fd6 = INVALID_SOCK_FD;
helper_sock_fd = INVALID_SOCK_FD;
cert = CNF_GetNtsServerCertFile();
key = CNF_GetNtsServerKeyFile();
if (!cert || !key)
return;
server_credentials = NKSN_CreateCertCredentials(cert, key, NULL);
if (!server_credentials)
return;
sessions = ARR_CreateInstance(sizeof (NKSN_Instance));
for (i = 0; i < CNF_GetNtsServerConnections(); i++)
*(NKSN_Instance *)ARR_GetNewElement(sessions) = NULL;
for (i = 0; i < MAX_SERVER_KEYS; i++)
server_keys[i].siv = NULL;
server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNtsServerPort());
server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNtsServerPort());
for (i = 0; i < MAX_SERVER_KEYS; i++) {
server_keys[i].siv = SIV_CreateInstance(SERVER_COOKIE_SIV);
generate_key(i);
}
current_server_key = MAX_SERVER_KEYS - 1;
load_keys();
key_rotation_interval = MAX(CNF_GetNtsRotate(), 0);
if (key_rotation_interval > 0) {
key_delay = key_rotation_interval - (SCH_GetLastEventMonoTime() - last_server_key_ts);
SCH_AddTimeoutByDelay(MAX(key_delay, 0.0), key_timeout, NULL);
}
processes = CNF_GetNtsServerProcesses();
if (processes > 0) {
int sock_fd1, sock_fd2;
sock_fd1 = SCK_OpenUnixSocketPair(0, &sock_fd2);
if (sock_fd1 < 0)
LOG_FATAL("Could not open socket pair");
for (i = 0; i < processes; i++)
start_helper(i + 1, scfilter_level, sock_fd1, sock_fd2);
SCK_CloseSocket(sock_fd2);
helper_sock_fd = sock_fd1;
}
initialised = 1;
}
/* ================================================== */
void
NKS_Finalise(void)
{
int i;
if (!initialised)
return;
if (helper_sock_fd != INVALID_SOCK_FD) {
for (i = 0; i < CNF_GetNtsServerProcesses(); i++) {
if (!SCK_Send(helper_sock_fd, "", 1, 0))
;
}
SCK_CloseSocket(helper_sock_fd);
}
if (server_sock_fd4 != INVALID_SOCK_FD)
SCK_CloseSocket(server_sock_fd4);
if (server_sock_fd6 != INVALID_SOCK_FD)
SCK_CloseSocket(server_sock_fd6);
save_keys();
for (i = 0; i < MAX_SERVER_KEYS; i++) {
if (server_keys[i].siv != NULL)
SIV_DestroyInstance(server_keys[i].siv);
}
for (i = 0; i < ARR_GetSize(sessions); i++) {
NKSN_Instance session = *(NKSN_Instance *)ARR_GetElement(sessions, i);
if (session)
NKSN_DestroyInstance(session);
}
ARR_DestroyInstance(sessions);
NKSN_DestroyCertCredentials(server_credentials);
}
/* ================================================== */
void
NKS_DumpKeys(void)
{
save_keys();
}
/* ================================================== */
void
NKS_ReloadKeys(void)
{
/* Don't load the keys if they are expected to be generated by this server
instance (i.e. they are already loaded) to not delay the next rotation */
if (key_rotation_interval > 0)
return;
load_keys();
}
/* ================================================== */
/* A server cookie consists of key ID, nonce, and encrypted C2S+S2C keys */
int
NKS_GenerateCookie(NKE_Context *context, NKE_Cookie *cookie)
{
unsigned char plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext;
int plaintext_length, tag_length;
ServerCookieHeader *header;
ServerKey *key;
if (!initialised) {
DEBUG_LOG("NTS server disabled");
return 0;
}
/* The algorithm is hardcoded for now */
if (context->algorithm != AEAD_AES_SIV_CMAC_256) {
DEBUG_LOG("Unexpected SIV algorithm");
return 0;
}
if (context->c2s.length < 0 || context->c2s.length > NKE_MAX_KEY_LENGTH ||
context->s2c.length < 0 || context->s2c.length > NKE_MAX_KEY_LENGTH) {
DEBUG_LOG("Invalid key length");
return 0;
}
key = &server_keys[current_server_key];
header = (ServerCookieHeader *)cookie->cookie;
header->key_id = htonl(key->id);
UTI_GetRandomBytes(header->nonce, sizeof (header->nonce));
plaintext_length = context->c2s.length + context->s2c.length;
assert(plaintext_length <= sizeof (plaintext));
memcpy(plaintext, context->c2s.key, context->c2s.length);
memcpy(plaintext + context->c2s.length, context->s2c.key, context->s2c.length);
tag_length = SIV_GetTagLength(key->siv);
cookie->length = sizeof (*header) + plaintext_length + tag_length;
assert(cookie->length <= sizeof (cookie->cookie));
ciphertext = cookie->cookie + sizeof (*header);
if (!SIV_Encrypt(key->siv, header->nonce, sizeof (header->nonce),
"", 0,
plaintext, plaintext_length,
ciphertext, plaintext_length + tag_length)) {
DEBUG_LOG("Could not encrypt cookie");
return 0;
}
return 1;
}
/* ================================================== */
int
NKS_DecodeCookie(NKE_Cookie *cookie, NKE_Context *context)
{
unsigned char plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext;
int ciphertext_length, plaintext_length, tag_length;
ServerCookieHeader *header;
ServerKey *key;
uint32_t key_id;
if (!initialised) {
DEBUG_LOG("NTS server disabled");
return 0;
}
if (cookie->length <= sizeof (*header)) {
DEBUG_LOG("Invalid cookie length");
return 0;
}
header = (ServerCookieHeader *)cookie->cookie;
ciphertext = cookie->cookie + sizeof (*header);
ciphertext_length = cookie->length - sizeof (*header);
key_id = ntohl(header->key_id);
key = &server_keys[key_id % MAX_SERVER_KEYS];
if (key_id != key->id) {
DEBUG_LOG("Unknown key %"PRIX32, key_id);
return 0;
}
tag_length = SIV_GetTagLength(key->siv);
if (tag_length >= ciphertext_length) {
DEBUG_LOG("Invalid cookie length");
return 0;
}
plaintext_length = ciphertext_length - tag_length;
if (plaintext_length > sizeof (plaintext) || plaintext_length % 2 != 0) {
DEBUG_LOG("Invalid cookie length");
return 0;
}
if (!SIV_Decrypt(key->siv, header->nonce, sizeof (header->nonce),
"", 0,
ciphertext, ciphertext_length,
plaintext, plaintext_length)) {
DEBUG_LOG("Could not decrypt cookie");
return 0;
}
context->algorithm = AEAD_AES_SIV_CMAC_256;
context->c2s.length = plaintext_length / 2;
context->s2c.length = plaintext_length / 2;
assert(context->c2s.length <= sizeof (context->c2s.key));
memcpy(context->c2s.key, plaintext, context->c2s.length);
memcpy(context->s2c.key, plaintext + context->c2s.length, context->s2c.length);
return 1;
}

48
nts_ke_server.h Normal file
View File

@@ -0,0 +1,48 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for the NTS-KE server
*/
#ifndef GOT_NTS_KE_SERVER_H
#define GOT_NTS_KE_SERVER_H
#include "nts_ke.h"
/* Init and fini functions */
extern void NKS_Initialise(int scfilter_level);
extern void NKS_Finalise(void);
/* Save the current server keys */
extern void NKS_DumpKeys(void);
/* Reload the keys */
extern void NKS_ReloadKeys(void);
/* Generate an NTS cookie with a given context */
extern int NKS_GenerateCookie(NKE_Context *context, NKE_Cookie *cookie);
/* Validate a cookie and decode the context */
extern int NKS_DecodeCookie(NKE_Cookie *cookie, NKE_Context *context);
#endif

848
nts_ke_session.c Normal file
View File

@@ -0,0 +1,848 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
NTS-KE session used by server and client
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ke_session.h"
#include "conf.h"
#include "local.h"
#include "logging.h"
#include "memory.h"
#include "siv.h"
#include "socket.h"
#include "sched.h"
#include "util.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#define INVALID_SOCK_FD (-8)
struct RecordHeader {
uint16_t type;
uint16_t body_length;
};
struct Message {
int length;
int sent;
int parsed;
int complete;
unsigned char data[NKE_MAX_MESSAGE_LENGTH];
};
typedef enum {
KE_WAIT_CONNECT,
KE_HANDSHAKE,
KE_SEND,
KE_RECEIVE,
KE_SHUTDOWN,
KE_STOPPED,
} KeState;
struct NKSN_Instance_Record {
int server;
char *server_name;
NKSN_MessageHandler handler;
void *handler_arg;
KeState state;
int sock_fd;
char *label;
gnutls_session_t tls_session;
SCH_TimeoutID timeout_id;
int retry_factor;
struct Message message;
int new_message;
int ended_message;
};
/* ================================================== */
static gnutls_priority_t priority_cache;
static int credentials_counter = 0;
static int clock_updates = 0;
/* ================================================== */
static void
reset_message(struct Message *message)
{
message->length = 0;
message->sent = 0;
message->parsed = 0;
message->complete = 0;
}
/* ================================================== */
static int
add_record(struct Message *message, int critical, int type, const void *body, int body_length)
{
struct RecordHeader header;
if (body_length < 0 || body_length > 0xffff || type < 0 || type > 0x7fff ||
message->length + sizeof (header) + body_length > sizeof (message->data))
return 0;
header.type = htons(!!critical * NKE_RECORD_CRITICAL_BIT | type);
header.body_length = htons(body_length);
memcpy(&message->data[message->length], &header, sizeof (header));
message->length += sizeof (header);
if (body_length > 0) {
memcpy(&message->data[message->length], body, body_length);
message->length += body_length;
}
return 1;
}
/* ================================================== */
static void
reset_message_parsing(struct Message *message)
{
message->parsed = 0;
}
/* ================================================== */
static int
get_record(struct Message *message, int *critical, int *type, int *body_length,
void *body, int buffer_length)
{
struct RecordHeader header;
int blen, rlen;
if (message->length < message->parsed + sizeof (header) ||
buffer_length < 0)
return 0;
memcpy(&header, &message->data[message->parsed], sizeof (header));
blen = ntohs(header.body_length);
rlen = sizeof (header) + blen;
if (message->length < message->parsed + rlen)
return 0;
if (critical)
*critical = !!(ntohs(header.type) & NKE_RECORD_CRITICAL_BIT);
if (type)
*type = ntohs(header.type) & ~NKE_RECORD_CRITICAL_BIT;
if (body)
memcpy(body, &message->data[message->parsed + sizeof (header)], MIN(buffer_length, blen));
if (body_length)
*body_length = blen;
message->parsed += rlen;
return 1;
}
/* ================================================== */
static int
check_message_format(struct Message *message, int eof)
{
int critical = 0, type = -1, length = -1, ends = 0;
reset_message_parsing(message);
message->complete = 0;
while (get_record(message, &critical, &type, &length, NULL, 0)) {
if (type == NKE_RECORD_END_OF_MESSAGE) {
if (!critical || length != 0 || ends > 0)
return 0;
ends++;
}
}
/* If the message cannot be fully parsed, but more data may be coming,
consider the format to be ok */
if (message->length == 0 || message->parsed < message->length)
return !eof;
if (type != NKE_RECORD_END_OF_MESSAGE)
return !eof;
message->complete = 1;
return 1;
}
/* ================================================== */
static gnutls_session_t
create_tls_session(int server_mode, int sock_fd, const char *server_name,
gnutls_certificate_credentials_t credentials,
gnutls_priority_t priority)
{
unsigned char alpn_name[sizeof (NKE_ALPN_NAME)];
gnutls_session_t session;
gnutls_datum_t alpn;
unsigned int flags;
int r;
r = gnutls_init(&session, GNUTLS_NONBLOCK | (server_mode ? GNUTLS_SERVER : GNUTLS_CLIENT));
if (r < 0) {
LOG(LOGS_ERR, "Could not %s TLS session : %s", "create", gnutls_strerror(r));
return NULL;
}
if (!server_mode) {
r = gnutls_server_name_set(session, GNUTLS_NAME_DNS, server_name, strlen(server_name));
if (r < 0)
goto error;
flags = 0;
if (clock_updates < CNF_GetNoCertTimeCheck()) {
flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS | GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS;
DEBUG_LOG("Disabled time checks");
}
gnutls_session_set_verify_cert(session, server_name, flags);
}
r = gnutls_priority_set(session, priority);
if (r < 0)
goto error;
r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials);
if (r < 0)
goto error;
memcpy(alpn_name, NKE_ALPN_NAME, sizeof (alpn_name));
alpn.data = alpn_name;
alpn.size = sizeof (alpn_name) - 1;
r = gnutls_alpn_set_protocols(session, &alpn, 1, 0);
if (r < 0)
goto error;
gnutls_transport_set_int(session, sock_fd);
return session;
error:
LOG(LOGS_ERR, "Could not %s TLS session : %s", "set", gnutls_strerror(r));
gnutls_deinit(session);
return NULL;
}
/* ================================================== */
static void
stop_session(NKSN_Instance inst)
{
if (inst->state == KE_STOPPED)
return;
inst->state = KE_STOPPED;
SCH_RemoveFileHandler(inst->sock_fd);
SCK_CloseSocket(inst->sock_fd);
inst->sock_fd = INVALID_SOCK_FD;
Free(inst->label);
inst->label = NULL;
gnutls_deinit(inst->tls_session);
inst->tls_session = NULL;
SCH_RemoveTimeout(inst->timeout_id);
inst->timeout_id = 0;
}
/* ================================================== */
static void
session_timeout(void *arg)
{
NKSN_Instance inst = arg;
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE session with %s timed out", inst->label);
inst->timeout_id = 0;
stop_session(inst);
}
/* ================================================== */
static int
get_socket_error(int sock_fd)
{
int optval;
socklen_t optlen = sizeof (optval);
if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
DEBUG_LOG("getsockopt() failed : %s", strerror(errno));
return EINVAL;
}
return optval;
}
/* ================================================== */
static int
check_alpn(NKSN_Instance inst)
{
gnutls_datum_t alpn;
int r;
r = gnutls_alpn_get_selected_protocol(inst->tls_session, &alpn);
if (r < 0 || alpn.size != sizeof (NKE_ALPN_NAME) - 1 ||
strncmp((const char *)alpn.data, NKE_ALPN_NAME, sizeof (NKE_ALPN_NAME) - 1))
return 0;
return 1;
}
/* ================================================== */
static void
change_state(NKSN_Instance inst, KeState state)
{
int output;
switch (state) {
case KE_HANDSHAKE:
output = !inst->server;
break;
case KE_WAIT_CONNECT:
case KE_SEND:
case KE_SHUTDOWN:
output = 1;
break;
case KE_RECEIVE:
output = 0;
break;
default:
assert(0);
}
SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT, output);
inst->state = state;
}
/* ================================================== */
static int
handle_event(NKSN_Instance inst, int event)
{
struct Message *message = &inst->message;
int r;
DEBUG_LOG("Session event %d fd=%d state=%d", event, inst->sock_fd, (int)inst->state);
switch (inst->state) {
case KE_WAIT_CONNECT:
/* Check if connect() succeeded */
if (event != SCH_FILE_OUTPUT)
return 0;
r = get_socket_error(inst->sock_fd);
if (r) {
LOG(LOGS_ERR, "Could not connect to %s : %s", inst->label, strerror(r));
stop_session(inst);
return 0;
}
DEBUG_LOG("Connected to %s", inst->label);
change_state(inst, KE_HANDSHAKE);
return 0;
case KE_HANDSHAKE:
r = gnutls_handshake(inst->tls_session);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"TLS handshake with %s failed : %s", inst->label, gnutls_strerror(r));
stop_session(inst);
/* Increase the retry interval if the handshake did not fail due
to the other end closing the connection */
if (r != GNUTLS_E_PULL_ERROR && r != GNUTLS_E_PREMATURE_TERMINATION)
inst->retry_factor = NKE_RETRY_FACTOR2_TLS;
return 0;
}
/* Disable output when the handshake is trying to receive data */
SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT,
gnutls_record_get_direction(inst->tls_session));
return 0;
}
inst->retry_factor = NKE_RETRY_FACTOR2_TLS;
if (DEBUG) {
char *description = gnutls_session_get_desc(inst->tls_session);
DEBUG_LOG("Handshake with %s completed %s",
inst->label, description ? description : "");
gnutls_free(description);
}
if (!check_alpn(inst)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE not supported by %s", inst->label);
stop_session(inst);
return 0;
}
/* Client will send a request to the server */
change_state(inst, inst->server ? KE_RECEIVE : KE_SEND);
return 0;
case KE_SEND:
assert(inst->new_message && message->complete);
r = gnutls_record_send(inst->tls_session, &message->data[message->sent],
message->length - message->sent);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Could not send NTS-KE message to %s : %s", inst->label, gnutls_strerror(r));
stop_session(inst);
}
return 0;
}
DEBUG_LOG("Sent %d bytes to %s", r, inst->label);
message->sent += r;
if (message->sent < message->length)
return 0;
/* Client will receive a response */
change_state(inst, inst->server ? KE_SHUTDOWN : KE_RECEIVE);
reset_message(&inst->message);
inst->new_message = 0;
return 0;
case KE_RECEIVE:
do {
if (message->length >= sizeof (message->data)) {
DEBUG_LOG("Message is too long");
stop_session(inst);
return 0;
}
r = gnutls_record_recv(inst->tls_session, &message->data[message->length],
sizeof (message->data) - message->length);
if (r < 0) {
/* Handle a renegotiation request on both client and server as
a protocol error */
if (gnutls_error_is_fatal(r) || r == GNUTLS_E_REHANDSHAKE) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Could not receive NTS-KE message from %s : %s",
inst->label, gnutls_strerror(r));
stop_session(inst);
}
return 0;
}
DEBUG_LOG("Received %d bytes from %s", r, inst->label);
message->length += r;
} while (gnutls_record_check_pending(inst->tls_session) > 0);
if (!check_message_format(message, r == 0)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Received invalid NTS-KE message from %s", inst->label);
stop_session(inst);
return 0;
}
/* Wait for more data if the message is not complete yet */
if (!message->complete)
return 0;
/* Server will send a response to the client */
change_state(inst, inst->server ? KE_SEND : KE_SHUTDOWN);
break;
case KE_SHUTDOWN:
r = gnutls_bye(inst->tls_session, GNUTLS_SHUT_RDWR);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
DEBUG_LOG("Shutdown with %s failed : %s", inst->label, gnutls_strerror(r));
stop_session(inst);
return 0;
}
/* Disable output when the TLS shutdown is trying to receive data */
SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT,
gnutls_record_get_direction(inst->tls_session));
return 0;
}
SCK_ShutdownConnection(inst->sock_fd);
stop_session(inst);
DEBUG_LOG("Shutdown completed");
return 0;
default:
assert(0);
}
return 1;
}
/* ================================================== */
static void
read_write_socket(int fd, int event, void *arg)
{
NKSN_Instance inst = arg;
if (!handle_event(inst, event))
return;
reset_message_parsing(&inst->message);
if (!(inst->handler)(inst->handler_arg)) {
stop_session(inst);
return;
}
}
/* ================================================== */
static time_t
get_time(time_t *t)
{
struct timespec now;
LCL_ReadCookedTime(&now, NULL);
if (t)
*t = now.tv_sec;
return now.tv_sec;
}
/* ================================================== */
static void
handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
double doffset, LCL_ChangeType change_type, void *anything)
{
if (change_type != LCL_ChangeUnknownStep && clock_updates < INT_MAX)
clock_updates++;
}
/* ================================================== */
static int gnutls_initialised = 0;
static void
init_gnutls(void)
{
int r;
if (gnutls_initialised)
return;
r = gnutls_global_init();
if (r < 0)
LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));
/* NTS specification requires TLS1.3 or later */
r = gnutls_priority_init2(&priority_cache,
"-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2",
NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND);
if (r < 0)
LOG_FATAL("Could not initialise %s : %s", "priority cache", gnutls_strerror(r));
gnutls_global_set_time_function(get_time);
gnutls_initialised = 1;
LCL_AddParameterChangeHandler(handle_step, NULL);
}
/* ================================================== */
static void
deinit_gnutls(void)
{
assert(gnutls_initialised);
LCL_RemoveParameterChangeHandler(handle_step, NULL);
gnutls_priority_deinit(priority_cache);
gnutls_global_deinit();
gnutls_initialised = 0;
}
/* ================================================== */
void *
NKSN_CreateCertCredentials(char *cert, char *key, char *trusted_certs)
{
gnutls_certificate_credentials_t credentials = NULL;
int r;
init_gnutls();
r = gnutls_certificate_allocate_credentials(&credentials);
if (r < 0)
goto error;
if (cert && key) {
r = gnutls_certificate_set_x509_key_file(credentials, cert, key,
GNUTLS_X509_FMT_PEM);
if (r < 0)
goto error;
} else {
if (!CNF_GetNoSystemCert()) {
r = gnutls_certificate_set_x509_system_trust(credentials);
if (r < 0)
goto error;
}
if (trusted_certs) {
r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs,
GNUTLS_X509_FMT_PEM);
if (r < 0)
goto error;
}
}
credentials_counter++;
return credentials;
error:
LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r));
if (credentials)
gnutls_certificate_free_credentials(credentials);
return NULL;
}
/* ================================================== */
void
NKSN_DestroyCertCredentials(void *credentials)
{
gnutls_certificate_free_credentials(credentials);
credentials_counter--;
if (credentials_counter != 0)
return;
deinit_gnutls();
}
/* ================================================== */
NKSN_Instance
NKSN_CreateInstance(int server_mode, const char *server_name,
NKSN_MessageHandler handler, void *handler_arg)
{
NKSN_Instance inst;
inst = MallocNew(struct NKSN_Instance_Record);
inst->server = server_mode;
inst->server_name = server_name ? Strdup(server_name) : NULL;
inst->handler = handler;
inst->handler_arg = handler_arg;
/* Replace NULL arg with the session itself */
if (!inst->handler_arg)
inst->handler_arg = inst;
inst->state = KE_STOPPED;
inst->sock_fd = INVALID_SOCK_FD;
inst->label = NULL;
inst->tls_session = NULL;
inst->timeout_id = 0;
inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT;
return inst;
}
/* ================================================== */
void
NKSN_DestroyInstance(NKSN_Instance inst)
{
stop_session(inst);
Free(inst->server_name);
Free(inst);
}
/* ================================================== */
int
NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label,
void *credentials, double timeout)
{
assert(inst->state == KE_STOPPED);
inst->tls_session = create_tls_session(inst->server, sock_fd, inst->server_name,
credentials, priority_cache);
if (!inst->tls_session)
return 0;
inst->sock_fd = sock_fd;
SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, inst);
inst->label = Strdup(label);
inst->timeout_id = SCH_AddTimeoutByDelay(timeout, session_timeout, inst);
inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT;
reset_message(&inst->message);
inst->new_message = 0;
inst->ended_message = 0;
change_state(inst, inst->server ? KE_HANDSHAKE : KE_WAIT_CONNECT);
return 1;
}
/* ================================================== */
void
NKSN_BeginMessage(NKSN_Instance inst)
{
reset_message(&inst->message);
inst->new_message = 1;
}
/* ================================================== */
int
NKSN_AddRecord(NKSN_Instance inst, int critical, int type, const void *body, int body_length)
{
assert(inst->new_message && !inst->message.complete);
assert(type != NKE_RECORD_END_OF_MESSAGE);
return add_record(&inst->message, critical, type, body, body_length);
}
/* ================================================== */
int
NKSN_EndMessage(NKSN_Instance inst)
{
assert(!inst->message.complete);
if (!add_record(&inst->message, 1, NKE_RECORD_END_OF_MESSAGE, NULL, 0))
return 0;
inst->message.complete = 1;
return 1;
}
/* ================================================== */
int
NKSN_GetRecord(NKSN_Instance inst, int *critical, int *type, int *body_length,
void *body, int buffer_length)
{
int type2;
assert(inst->message.complete);
if (!get_record(&inst->message, critical, &type2, body_length, body, buffer_length))
return 0;
if (type2 == NKE_RECORD_END_OF_MESSAGE)
return 0;
if (type)
*type = type2;
return 1;
}
/* ================================================== */
int
NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c)
{
c2s->length = SIV_GetKeyLength(siv);
s2c->length = SIV_GetKeyLength(siv);
assert(c2s->length <= sizeof (c2s->key));
assert(s2c->length <= sizeof (s2c->key));
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,
c2s->length, (char *)c2s->key) < 0)
return 0;
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,
s2c->length, (char *)s2c->key) < 0)
return 0;
return 1;
}
/* ================================================== */
int
NKSN_IsStopped(NKSN_Instance inst)
{
return inst->state == KE_STOPPED;
}
/* ================================================== */
void
NKSN_StopSession(NKSN_Instance inst)
{
stop_session(inst);
}
/* ================================================== */
int
NKSN_GetRetryFactor(NKSN_Instance inst)
{
return inst->retry_factor;
}

87
nts_ke_session.h Normal file
View File

@@ -0,0 +1,87 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for the NTS-KE session
*/
#ifndef GOT_NTS_KE_SESSION_H
#define GOT_NTS_KE_SESSION_H
#include "nts_ke.h"
#include "siv.h"
typedef struct NKSN_Instance_Record *NKSN_Instance;
/* Handler for received NTS-KE messages. A non-zero return code stops
the session. */
typedef int (*NKSN_MessageHandler)(void *arg);
/* Get client or server credentials using certificates of trusted CAs,
or a server certificate and key. The credentials may be shared between
different clients or servers. */
extern void *NKSN_CreateCertCredentials(char *cert, char *key, char *trusted_certs);
/* Destroy the credentials */
extern void NKSN_DestroyCertCredentials(void *credentials);
/* Create an instance */
extern NKSN_Instance NKSN_CreateInstance(int server_mode, const char *server_name,
NKSN_MessageHandler handler, void *handler_arg);
/* Destroy an instance */
extern void NKSN_DestroyInstance(NKSN_Instance inst);
/* Start a new NTS-KE session */
extern int NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label,
void *credentials, double timeout);
/* Begin an NTS-KE message. A request should be made right after starting
the session and response should be made in the message handler. */
extern void NKSN_BeginMessage(NKSN_Instance inst);
/* Add a record to the message */
extern int NKSN_AddRecord(NKSN_Instance inst, int critical, int type,
const void *body, int body_length);
/* Terminate the message */
extern int NKSN_EndMessage(NKSN_Instance inst);
/* Get the next record from the received message. This function should be
called from the message handler. */
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);
/* Check if the session has stopped */
extern int NKSN_IsStopped(NKSN_Instance inst);
/* Stop the session */
extern void NKSN_StopSession(NKSN_Instance inst);
/* Get a factor to calculate retry interval (in log2 seconds)
based on the session state or how it was terminated */
extern int NKSN_GetRetryFactor(NKSN_Instance inst);
#endif

41
nts_ntp.h Normal file
View File

@@ -0,0 +1,41 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for the NTS-NTP protocol
*/
#ifndef GOT_NTS_NTP_H
#define GOT_NTS_NTP_H
#define NTP_EF_NTS_UNIQUE_IDENTIFIER 0x0104
#define NTP_EF_NTS_COOKIE 0x0204
#define NTP_EF_NTS_COOKIE_PLACEHOLDER 0x0304
#define NTP_EF_NTS_AUTH_AND_EEF 0x0404
#define NTP_KOD_NTS_NAK 0x4e54534e
#define NTS_MIN_UNIQ_ID_LENGTH 32
#define NTS_MIN_UNPADDED_NONCE_LENGTH 16
#define NTS_MAX_COOKIES 8
#endif

174
nts_ntp_auth.c Normal file
View File

@@ -0,0 +1,174 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
NTS Authenticator and Encrypted Extension Fields extension field
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ntp_auth.h"
#include "logging.h"
#include "ntp_ext.h"
#include "nts_ntp.h"
#include "siv.h"
#include "util.h"
struct AuthHeader {
uint16_t nonce_length;
uint16_t ciphertext_length;
};
/* ================================================== */
static int
get_padding_length(int length)
{
return length % 4U ? 4 - length % 4U : 0;
}
/* ================================================== */
static int
get_padded_length(int length)
{
return length + get_padding_length(length);
}
/* ================================================== */
int
NNA_GenerateAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv,
const unsigned char *nonce, int nonce_length,
const unsigned char *plaintext, int plaintext_length,
int min_ef_length)
{
int auth_length, ciphertext_length, assoc_length;
int nonce_padding, ciphertext_padding, additional_padding;
unsigned char *ciphertext, *body;
struct AuthHeader *header;
assert(sizeof (*header) == 4);
if (nonce_length <= 0 || plaintext_length < 0) {
DEBUG_LOG("Invalid nonce/plaintext length");
return 0;
}
assoc_length = info->length;
ciphertext_length = SIV_GetTagLength(siv) + plaintext_length;
nonce_padding = get_padding_length(nonce_length);
ciphertext_padding = get_padding_length(ciphertext_length);
min_ef_length = get_padded_length(min_ef_length);
auth_length = sizeof (*header) + nonce_length + nonce_padding +
ciphertext_length + ciphertext_padding;
additional_padding = MAX(min_ef_length - auth_length - 4, 0);
additional_padding = MAX(NTS_MIN_UNPADDED_NONCE_LENGTH - nonce_length - nonce_padding,
additional_padding);
auth_length += additional_padding;
if (!NEF_AddBlankField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, auth_length,
(void **)&header)) {
DEBUG_LOG("Could not add EF");
return 0;
}
header->nonce_length = htons(nonce_length);
header->ciphertext_length = htons(ciphertext_length);
body = (unsigned char *)(header + 1);
ciphertext = body + nonce_length + nonce_padding;
memcpy(body, nonce, nonce_length);
memset(body + nonce_length, 0, nonce_padding);
if (!SIV_Encrypt(siv, nonce, nonce_length, packet, assoc_length,
plaintext, plaintext_length, ciphertext, ciphertext_length)) {
DEBUG_LOG("SIV encrypt failed");
return 0;
}
memset(ciphertext + ciphertext_length, 0, ciphertext_padding + additional_padding);
return 1;
}
/* ================================================== */
int
NNA_DecryptAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv, int ef_start,
unsigned char *plaintext, int buffer_length, int *plaintext_length)
{
unsigned int siv_tag_length, nonce_length, ciphertext_length;
unsigned char *nonce, *ciphertext;
int ef_type, ef_body_length;
void *ef_body;
struct AuthHeader *header;
if (!NEF_ParseField(packet, info->length, ef_start,
NULL, &ef_type, &ef_body, &ef_body_length))
return 0;
if (ef_type != NTP_EF_NTS_AUTH_AND_EEF)
return 0;
header = ef_body;
nonce_length = ntohs(header->nonce_length);
ciphertext_length = ntohs(header->ciphertext_length);
if (get_padded_length(nonce_length) +
get_padded_length(ciphertext_length) > ef_body_length)
return 0;
nonce = (unsigned char *)(header + 1);
ciphertext = (unsigned char *)(header + 1) + get_padded_length(nonce_length);
siv_tag_length = SIV_GetTagLength(siv);
if (nonce_length < 1 ||
ciphertext_length < siv_tag_length ||
ciphertext_length - siv_tag_length > buffer_length) {
DEBUG_LOG("Unexpected nonce/ciphertext length");
return 0;
}
if (ef_body_length < sizeof (*header) +
NTS_MIN_UNPADDED_NONCE_LENGTH + get_padded_length(ciphertext_length)) {
DEBUG_LOG("Missing padding");
return 0;
}
*plaintext_length = ciphertext_length - siv_tag_length;
if (!SIV_Decrypt(siv, nonce, nonce_length, packet, info->length - ef_body_length - 4,
ciphertext, ciphertext_length, plaintext, *plaintext_length)) {
DEBUG_LOG("SIV decrypt failed");
return 0;
}
return 1;
}

43
nts_ntp_auth.h Normal file
View File

@@ -0,0 +1,43 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header for NTS Authenticator and Encrypted Extension Fields
extension field
*/
#ifndef GOT_NTS_NTP_AUTH_H
#define GOT_NTS_NTP_AUTH_H
#include "ntp.h"
#include "siv.h"
extern int NNA_GenerateAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv,
const unsigned char *nonce, int nonce_length,
const unsigned char *plaintext, int plaintext_length,
int min_ef_length);
extern int NNA_DecryptAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv,
int ef_start, unsigned char *plaintext, int buffer_length,
int *plaintext_length);
#endif

629
nts_ntp_client.c Normal file
View File

@@ -0,0 +1,629 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Client NTS-NTP authentication
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ntp_client.h"
#include "conf.h"
#include "logging.h"
#include "memory.h"
#include "ntp.h"
#include "ntp_ext.h"
#include "ntp_sources.h"
#include "nts_ke_client.h"
#include "nts_ntp.h"
#include "nts_ntp_auth.h"
#include "sched.h"
#include "siv.h"
#include "util.h"
#define MAX_TOTAL_COOKIE_LENGTH (8 * 108)
#define DUMP_IDENTIFIER "NNC0\n"
struct NNC_Instance_Record {
const IPSockAddr *ntp_address;
IPSockAddr nts_address;
char *name;
NKC_Instance nke;
SIV_Instance siv;
int load_attempt;
int nke_attempts;
double next_nke_attempt;
double last_nke_success;
NKE_Context context;
NKE_Cookie cookies[NTS_MAX_COOKIES];
int num_cookies;
int cookie_index;
int nak_response;
int ok_response;
unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
unsigned char uniq_id[NTS_MIN_UNIQ_ID_LENGTH];
};
/* ================================================== */
static void save_cookies(NNC_Instance inst);
static void load_cookies(NNC_Instance inst);
/* ================================================== */
static void
reset_instance(NNC_Instance inst)
{
inst->load_attempt = 0;
inst->nke_attempts = 0;
inst->next_nke_attempt = 0.0;
inst->last_nke_success = 0.0;
memset(&inst->context, 0, sizeof (inst->context));
inst->num_cookies = 0;
inst->cookie_index = 0;
inst->nak_response = 0;
inst->ok_response = 1;
memset(inst->nonce, 0, sizeof (inst->nonce));
memset(inst->uniq_id, 0, sizeof (inst->uniq_id));
}
/* ================================================== */
NNC_Instance
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr *ntp_address)
{
NNC_Instance inst;
inst = MallocNew(struct NNC_Instance_Record);
inst->ntp_address = ntp_address;
inst->nts_address = *nts_address;
inst->name = name ? Strdup(name) : NULL;
inst->siv = NULL;
inst->nke = NULL;
reset_instance(inst);
return inst;
}
/* ================================================== */
void
NNC_DestroyInstance(NNC_Instance inst)
{
save_cookies(inst);
if (inst->nke)
NKC_DestroyInstance(inst->nke);
if (inst->siv)
SIV_DestroyInstance(inst->siv);
Free(inst->name);
Free(inst);
}
/* ================================================== */
static int
check_cookies(NNC_Instance inst)
{
/* Force NKE if a NAK was received since last valid auth */
if (inst->nak_response && !inst->ok_response && inst->num_cookies > 0) {
inst->num_cookies = 0;
DEBUG_LOG("Dropped cookies");
}
/* Force NKE if the keys encrypting the cookies are too old */
if (inst->num_cookies > 0 &&
SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh())
inst->num_cookies = 0;
return inst->num_cookies > 0;
}
/* ================================================== */
static int
set_ntp_address(NNC_Instance inst, NTP_Remote_Address *negotiated_address)
{
NTP_Remote_Address old_address, new_address;
old_address = *inst->ntp_address;
new_address = *negotiated_address;
if (new_address.ip_addr.family == IPADDR_UNSPEC)
new_address.ip_addr = old_address.ip_addr;
if (new_address.port == 0)
new_address.port = old_address.port;
if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr, NULL) == 0 &&
old_address.port == new_address.port)
/* Nothing to do */
return 1;
if (NSR_UpdateSourceNtpAddress(&old_address, &new_address) != NSR_Success) {
LOG(LOGS_ERR, "Could not change %s to negotiated address %s",
UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr));
return 0;
}
return 1;
}
/* ================================================== */
static void
update_next_nke_attempt(NNC_Instance inst, double now)
{
int factor, interval;
if (!inst->nke)
return;
factor = NKC_GetRetryFactor(inst->nke);
interval = MIN(factor + inst->nke_attempts - 1, NKE_MAX_RETRY_INTERVAL2);
inst->next_nke_attempt = now + UTI_Log2ToDouble(interval);
}
/* ================================================== */
static int
get_cookies(NNC_Instance inst)
{
NTP_Remote_Address ntp_address;
double now;
int got_data;
assert(!check_cookies(inst));
now = SCH_GetLastEventMonoTime();
if (!inst->nke) {
if (now < inst->next_nke_attempt) {
DEBUG_LOG("Limiting NTS-KE request rate (%f seconds)",
inst->next_nke_attempt - now);
return 0;
}
if (!inst->name) {
LOG(LOGS_ERR, "Missing name of %s for NTS-KE",
UTI_IPToString(&inst->nts_address.ip_addr));
return 0;
}
inst->nke = NKC_CreateInstance(&inst->nts_address, inst->name);
inst->nke_attempts++;
update_next_nke_attempt(inst, now);
if (!NKC_Start(inst->nke))
return 0;
}
update_next_nke_attempt(inst, now);
if (NKC_IsActive(inst->nke))
return 0;
got_data = NKC_GetNtsData(inst->nke, &inst->context,
inst->cookies, &inst->num_cookies, NTS_MAX_COOKIES,
&ntp_address);
NKC_DestroyInstance(inst->nke);
inst->nke = NULL;
if (!got_data)
return 0;
if (inst->siv)
SIV_DestroyInstance(inst->siv);
inst->siv = NULL;
if (!set_ntp_address(inst, &ntp_address)) {
inst->num_cookies = 0;
return 0;
}
inst->cookie_index = 0;
inst->nak_response = 0;
inst->last_nke_success = now;
return 1;
}
/* ================================================== */
int
NNC_PrepareForAuth(NNC_Instance inst)
{
if (!inst->load_attempt) {
load_cookies(inst);
inst->load_attempt = 1;
}
if (!check_cookies(inst)) {
if (!get_cookies(inst))
return 0;
}
if (!inst->siv)
inst->siv = SIV_CreateInstance(inst->context.algorithm);
if (!inst->siv ||
!SIV_SetKey(inst->siv, inst->context.c2s.key, inst->context.c2s.length)) {
DEBUG_LOG("Could not set SIV key");
return 0;
}
UTI_GetRandomBytes(&inst->uniq_id, sizeof (inst->uniq_id));
UTI_GetRandomBytes(&inst->nonce, sizeof (inst->nonce));
return 1;
}
/* ================================================== */
int
NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
NTP_PacketInfo *info)
{
NKE_Cookie *cookie;
int i, req_cookies;
void *ef_body;
if (inst->num_cookies == 0 || !inst->siv)
return 0;
if (info->mode != MODE_CLIENT)
return 0;
cookie = &inst->cookies[inst->cookie_index];
req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies + 1,
MAX_TOTAL_COOKIE_LENGTH / (cookie->length + 4));
if (!NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER,
&inst->uniq_id, sizeof (inst->uniq_id)))
return 0;
if (!NEF_AddField(packet, info, NTP_EF_NTS_COOKIE,
cookie->cookie, cookie->length))
return 0;
for (i = 0; i < req_cookies - 1; i++) {
if (!NEF_AddBlankField(packet, info, NTP_EF_NTS_COOKIE_PLACEHOLDER,
cookie->length, &ef_body))
return 0;
memset(ef_body, 0, cookie->length);
}
if (!NNA_GenerateAuthEF(packet, info, inst->siv, inst->nonce, sizeof (inst->nonce),
(const unsigned char *)"", 0, NTP_MAX_V4_MAC_LENGTH + 4))
return 0;
inst->num_cookies--;
inst->cookie_index = (inst->cookie_index + 1) % NTS_MAX_COOKIES;
inst->ok_response = 0;
return 1;
}
/* ================================================== */
static int
extract_cookies(NNC_Instance inst, unsigned char *plaintext, int length)
{
int ef_type, ef_body_length, ef_length, parsed, index, acceptable, saved;
void *ef_body;
acceptable = saved = 0;
for (parsed = 0; parsed < length; parsed += ef_length) {
if (!NEF_ParseSingleField(plaintext, length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
if (ef_type != NTP_EF_NTS_COOKIE)
continue;
if (ef_length < NTP_MIN_EF_LENGTH || ef_body_length > sizeof (inst->cookies[0].cookie)) {
DEBUG_LOG("Unexpected cookie length %d", ef_body_length);
continue;
}
acceptable++;
if (inst->num_cookies >= NTS_MAX_COOKIES)
continue;
index = (inst->cookie_index + inst->num_cookies) % NTS_MAX_COOKIES;
memcpy(inst->cookies[index].cookie, ef_body, ef_body_length);
inst->cookies[index].length = ef_body_length;
inst->num_cookies++;
saved++;
}
DEBUG_LOG("Extracted %d cookies (saved %d)", acceptable, saved);
return acceptable > 0;
}
/* ================================================== */
int
NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
NTP_PacketInfo *info)
{
int ef_type, ef_body_length, ef_length, parsed, plaintext_length;
int has_valid_uniq_id = 0, has_valid_auth = 0;
unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
void *ef_body;
if (info->ext_fields == 0 || info->mode != MODE_SERVER)
return 0;
/* Accept only one response per request */
if (inst->ok_response)
return 0;
if (!inst->siv ||
!SIV_SetKey(inst->siv, inst->context.s2c.key, inst->context.s2c.length)) {
DEBUG_LOG("Could not set SIV key");
return 0;
}
for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) {
if (!NEF_ParseField(packet, info->length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
switch (ef_type) {
case NTP_EF_NTS_UNIQUE_IDENTIFIER:
if (ef_body_length != sizeof (inst->uniq_id) ||
memcmp(ef_body, inst->uniq_id, sizeof (inst->uniq_id)) != 0) {
DEBUG_LOG("Invalid uniq id");
return 0;
}
has_valid_uniq_id = 1;
break;
case NTP_EF_NTS_COOKIE:
DEBUG_LOG("Unencrypted cookie");
break;
case NTP_EF_NTS_AUTH_AND_EEF:
if (parsed + ef_length != info->length) {
DEBUG_LOG("Auth not last EF");
return 0;
}
if (!NNA_DecryptAuthEF(packet, info, inst->siv, parsed,
plaintext, sizeof (plaintext), &plaintext_length))
return 0;
has_valid_auth = 1;
break;
default:
break;
}
}
if (!has_valid_uniq_id || !has_valid_auth) {
if (has_valid_uniq_id && packet->stratum == NTP_INVALID_STRATUM &&
ntohl(packet->reference_id) == NTP_KOD_NTS_NAK) {
DEBUG_LOG("NTS NAK");
inst->nak_response = 1;
return 0;
}
DEBUG_LOG("Missing NTS EF");
return 0;
}
if (!extract_cookies(inst, plaintext, plaintext_length))
return 0;
inst->ok_response = 1;
/* At this point we know the client interoperates with the server. Allow a
new NTS-KE session to be started as soon as the cookies run out. */
inst->nke_attempts = 0;
inst->next_nke_attempt = 0.0;
return 1;
}
/* ================================================== */
void
NNC_ChangeAddress(NNC_Instance inst, IPAddr *address)
{
save_cookies(inst);
if (inst->nke)
NKC_DestroyInstance(inst->nke);
inst->nke = NULL;
inst->num_cookies = 0;
inst->nts_address.ip_addr = *address;
reset_instance(inst);
DEBUG_LOG("NTS reset");
}
/* ================================================== */
static void
save_cookies(NNC_Instance inst)
{
char buf[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename;
struct timespec now;
double context_time;
FILE *f;
int i;
if (inst->num_cookies < 1 || !UTI_IsIPReal(&inst->nts_address.ip_addr))
return;
dump_dir = CNF_GetNtsDumpDir();
if (!dump_dir)
return;
filename = UTI_IPToString(&inst->nts_address.ip_addr);
f = UTI_OpenFile(dump_dir, filename, ".tmp", 'w', 0600);
if (!f)
return;
SCH_GetLastEventTime(&now, NULL, NULL);
context_time = inst->last_nke_success - SCH_GetLastEventMonoTime();
context_time += UTI_TimespecToDouble(&now);
if (fprintf(f, "%s%.1f\n%s %d\n%d ",
DUMP_IDENTIFIER, context_time, UTI_IPToString(&inst->ntp_address->ip_addr),
inst->ntp_address->port, (int)inst->context.algorithm) < 0 ||
!UTI_BytesToHex(inst->context.s2c.key, inst->context.s2c.length, buf, sizeof (buf)) ||
fprintf(f, "%s ", buf) < 0 ||
!UTI_BytesToHex(inst->context.c2s.key, inst->context.c2s.length, buf, sizeof (buf)) ||
fprintf(f, "%s\n", buf) < 0)
goto error;
for (i = 0; i < inst->num_cookies; i++) {
if (!UTI_BytesToHex(inst->cookies[i].cookie, inst->cookies[i].length, buf, sizeof (buf)) ||
fprintf(f, "%s\n", buf) < 0)
goto error;
}
fclose(f);
if (!UTI_RenameTempFile(dump_dir, filename, ".tmp", ".nts"))
;
return;
error:
DEBUG_LOG("Could not %s cookies for %s", "save", filename);
fclose(f);
if (!UTI_RemoveFile(dump_dir, filename, ".nts"))
;
}
/* ================================================== */
#define MAX_WORDS 3
static void
load_cookies(NNC_Instance inst)
{
char line[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename, *words[MAX_WORDS];
int i, algorithm, port;
double context_time;
struct timespec now;
IPSockAddr ntp_addr;
FILE *f;
dump_dir = CNF_GetNtsDumpDir();
if (!dump_dir)
return;
filename = UTI_IPToString(&inst->nts_address.ip_addr);
f = UTI_OpenFile(dump_dir, filename, ".nts", 'r', 0);
if (!f)
return;
/* Don't load this file again */
if (!UTI_RemoveFile(dump_dir, filename, ".nts"))
;
if (inst->siv)
SIV_DestroyInstance(inst->siv);
inst->siv = NULL;
if (!fgets(line, sizeof (line), f) || strcmp(line, DUMP_IDENTIFIER) != 0 ||
!fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 1 ||
sscanf(words[0], "%lf", &context_time) != 1 ||
!fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 2 ||
!UTI_StringToIP(words[0], &ntp_addr.ip_addr) || sscanf(words[1], "%d", &port) != 1 ||
!fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 3 ||
sscanf(words[0], "%d", &algorithm) != 1)
goto error;
inst->context.algorithm = algorithm;
inst->context.s2c.length = UTI_HexToBytes(words[1], inst->context.s2c.key,
sizeof (inst->context.s2c.key));
inst->context.c2s.length = UTI_HexToBytes(words[2], inst->context.c2s.key,
sizeof (inst->context.c2s.key));
if (inst->context.s2c.length != SIV_GetKeyLength(algorithm) ||
inst->context.c2s.length != inst->context.s2c.length)
goto error;
for (i = 0; i < NTS_MAX_COOKIES && fgets(line, sizeof (line), f); i++) {
if (UTI_SplitString(line, words, MAX_WORDS) != 1)
goto error;
inst->cookies[i].length = UTI_HexToBytes(words[0], inst->cookies[i].cookie,
sizeof (inst->cookies[i].cookie));
if (inst->cookies[i].length == 0)
goto error;
}
inst->num_cookies = i;
ntp_addr.port = port;
if (!set_ntp_address(inst, &ntp_addr))
goto error;
SCH_GetLastEventTime(&now, NULL, NULL);
context_time -= UTI_TimespecToDouble(&now);
if (context_time > 0)
context_time = 0;
inst->last_nke_success = context_time + SCH_GetLastEventMonoTime();
DEBUG_LOG("Loaded %d cookies for %s", i, filename);
return;
error:
DEBUG_LOG("Could not %s cookies for %s", "load", filename);
fclose(f);
memset(&inst->context, 0, sizeof (inst->context));
inst->num_cookies = 0;
}
/* ================================================== */
void
NNC_DumpData(NNC_Instance inst)
{
save_cookies(inst);
}

48
nts_ntp_client.h Normal file
View File

@@ -0,0 +1,48 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for client NTS-NTP authentication
*/
#ifndef GOT_NTS_NTP_CLIENT_H
#define GOT_NTS_NTP_CLIENT_H
#include "addressing.h"
#include "ntp.h"
typedef struct NNC_Instance_Record *NNC_Instance;
extern NNC_Instance NNC_CreateInstance(IPSockAddr *nts_address, const char *name,
const IPSockAddr *ntp_address);
extern void NNC_DestroyInstance(NNC_Instance inst);
extern int NNC_PrepareForAuth(NNC_Instance inst);
extern int NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
NTP_PacketInfo *info);
extern int NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
NTP_PacketInfo *info);
extern void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address);
extern void NNC_DumpData(NNC_Instance inst);
#endif

260
nts_ntp_server.c Normal file
View File

@@ -0,0 +1,260 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Server NTS-NTP authentication
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ntp_server.h"
#include "conf.h"
#include "logging.h"
#include "memory.h"
#include "ntp.h"
#include "ntp_ext.h"
#include "nts_ke_server.h"
#include "nts_ntp.h"
#include "nts_ntp_auth.h"
#include "siv.h"
#include "util.h"
#define SERVER_SIV AEAD_AES_SIV_CMAC_256
struct NtsServer {
SIV_Instance siv;
unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
NKE_Cookie cookies[NTS_MAX_COOKIES];
int num_cookies;
NTP_int64 req_tx;
};
/* The server instance handling all requests */
struct NtsServer *server;
/* ================================================== */
void
NNS_Initialise(void)
{
/* Create an NTS-NTP server instance only if NTS-KE server is enabled */
if (!CNF_GetNtsServerCertFile() || !CNF_GetNtsServerKeyFile()) {
server = NULL;
return;
}
server = Malloc(sizeof (struct NtsServer));
server->siv = SIV_CreateInstance(SERVER_SIV);
}
/* ================================================== */
void
NNS_Finalise(void)
{
if (!server)
return;
SIV_DestroyInstance(server->siv);
Free(server);
server = NULL;
}
/* ================================================== */
int
NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod)
{
int ef_type, ef_body_length, ef_length, has_uniq_id = 0, has_auth = 0, has_cookie = 0;
int i, plaintext_length, parsed, requested_cookies, cookie_length = -1, auth_start = 0;
unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
NKE_Context context;
NKE_Cookie cookie;
void *ef_body;
if (!server)
return 0;
*kod = 0;
server->num_cookies = 0;
server->req_tx = packet->transmit_ts;
if (info->ext_fields == 0 || info->mode != MODE_CLIENT)
return 0;
requested_cookies = 0;
for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) {
if (!NEF_ParseField(packet, info->length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
switch (ef_type) {
case NTP_EF_NTS_UNIQUE_IDENTIFIER:
has_uniq_id = 1;
break;
case NTP_EF_NTS_COOKIE:
if (has_cookie || ef_body_length > sizeof (cookie.cookie))
return 0;
cookie.length = ef_body_length;
memcpy(cookie.cookie, ef_body, ef_body_length);
has_cookie = 1;
/* Fall through */
case NTP_EF_NTS_COOKIE_PLACEHOLDER:
requested_cookies++;
if (cookie_length >= 0 && cookie_length != ef_body_length) {
DEBUG_LOG("Invalid cookie/placeholder length");
return 0;
}
cookie_length = ef_body_length;
break;
case NTP_EF_NTS_AUTH_AND_EEF:
auth_start = parsed;
has_auth = 1;
break;
default:
break;
}
}
if (!has_uniq_id || !has_cookie || !has_auth) {
DEBUG_LOG("Missing an NTS EF");
return 0;
}
if (!NKS_DecodeCookie(&cookie, &context)) {
*kod = NTP_KOD_NTS_NAK;
return 0;
}
if (context.algorithm != SERVER_SIV) {
DEBUG_LOG("Unexpected SIV");
return 0;
}
if (!SIV_SetKey(server->siv, context.c2s.key, context.c2s.length)) {
DEBUG_LOG("Could not set C2S key");
return 0;
}
if (!NNA_DecryptAuthEF(packet, info, server->siv, auth_start,
plaintext, sizeof (plaintext), &plaintext_length)) {
*kod = NTP_KOD_NTS_NAK;
return 0;
}
for (parsed = 0; parsed < plaintext_length; parsed += ef_length) {
if (!NEF_ParseSingleField(plaintext, plaintext_length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
switch (ef_type) {
case NTP_EF_NTS_COOKIE_PLACEHOLDER:
if (cookie_length != ef_body_length) {
DEBUG_LOG("Invalid cookie/placeholder length");
return 0;
}
requested_cookies++;
break;
default:
break;
}
}
if (!SIV_SetKey(server->siv, context.s2c.key, context.s2c.length)) {
DEBUG_LOG("Could not set S2C key");
return 0;
}
UTI_GetRandomBytes(server->nonce, sizeof (server->nonce));
server->num_cookies = MIN(NTS_MAX_COOKIES, requested_cookies);
for (i = 0; i < server->num_cookies; i++)
if (!NKS_GenerateCookie(&context, &server->cookies[i]))
return 0;
return 1;
}
/* ================================================== */
int
NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
NTP_Packet *response, NTP_PacketInfo *res_info,
uint32_t kod)
{
int i, ef_type, ef_body_length, ef_length, parsed;
void *ef_body;
unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
int plaintext_length;
if (!server || req_info->mode != MODE_CLIENT || res_info->mode != MODE_SERVER)
return 0;
/* Make sure this is a response to the expected request */
if (UTI_CompareNtp64(&server->req_tx, &request->transmit_ts) != 0)
assert(0);
for (parsed = NTP_HEADER_LENGTH; parsed < req_info->length; parsed += ef_length) {
if (!NEF_ParseField(request, req_info->length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
switch (ef_type) {
case NTP_EF_NTS_UNIQUE_IDENTIFIER:
/* Copy the ID from the request */
if (!NEF_AddField(response, res_info, ef_type, ef_body, ef_body_length))
return 0;
default:
break;
}
}
/* NTS NAK response does not have any other fields */
if (kod)
return 1;
for (i = 0, plaintext_length = 0; i < server->num_cookies; i++) {
if (!NEF_SetField(plaintext, sizeof (plaintext), plaintext_length,
NTP_EF_NTS_COOKIE, &server->cookies[i].cookie,
server->cookies[i].length, &ef_length))
return 0;
plaintext_length += ef_length;
assert(plaintext_length <= sizeof (plaintext));
}
server->num_cookies = 0;
if (!NNA_GenerateAuthEF(response, res_info, server->siv,
server->nonce, sizeof (server->nonce),
plaintext, plaintext_length,
req_info->length - res_info->length))
return 0;
return 1;
}

40
nts_ntp_server.h Normal file
View File

@@ -0,0 +1,40 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020
*
* 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.
*
**********************************************************************
=======================================================================
Header file for server NTS-NTP authentication
*/
#ifndef GOT_NTS_NTP_SERVER_H
#define GOT_NTS_NTP_SERVER_H
#include "ntp.h"
extern void NNS_Initialise(void);
extern void NNS_Finalise(void);
extern int NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod);
extern int NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
NTP_Packet *response, NTP_PacketInfo *res_info,
uint32_t kod);
#endif

View File

@@ -116,10 +116,14 @@ static const struct request_length request_lengths[] = {
REQ_LENGTH_ENTRY(ntp_data, ntp_data), /* NTP_DATA */
{ 0, 0 }, /* ADD_SERVER2 */
{ 0, 0 }, /* ADD_PEER2 */
REQ_LENGTH_ENTRY(ntp_source, null), /* ADD_SERVER3 */
REQ_LENGTH_ENTRY(ntp_source, null), /* ADD_PEER3 */
{ 0, 0 }, /* ADD_SERVER3 */
{ 0, 0 }, /* ADD_PEER3 */
REQ_LENGTH_ENTRY(null, null), /* SHUTDOWN */
REQ_LENGTH_ENTRY(null, null), /* ONOFFLINE */
REQ_LENGTH_ENTRY(ntp_source, null), /* ADD_SOURCE */
REQ_LENGTH_ENTRY(ntp_source_name,
ntp_source_name), /* NTP_SOURCE_NAME */
REQ_LENGTH_ENTRY(null, null), /* RESET */
};
static const uint16_t reply_lengths[] = {
@@ -142,6 +146,7 @@ static const uint16_t reply_lengths[] = {
RPY_LENGTH_ENTRY(ntp_data), /* NTP_DATA */
RPY_LENGTH_ENTRY(manual_timestamp), /* MANUAL_TIMESTAMP2 */
RPY_LENGTH_ENTRY(manual_list), /* MANUAL_LIST2 */
RPY_LENGTH_ENTRY(ntp_source_name), /* NTP_SOURCE_NAME */
};
/* ================================================== */

129
privops.c
View File

@@ -33,6 +33,7 @@
#include "nameserv.h"
#include "logging.h"
#include "privops.h"
#include "socket.h"
#include "util.h"
#define OP_ADJUSTTIME 1024
@@ -158,7 +159,7 @@ res_fatal(PrvResponse *res, const char *fmt, ...)
static int
send_response(int fd, const PrvResponse *res)
{
if (send(fd, res, sizeof (*res), 0) != sizeof (*res))
if (SCK_Send(fd, res, sizeof (*res), 0) != sizeof (*res))
return 0;
return 1;
@@ -170,37 +171,23 @@ send_response(int fd, const PrvResponse *res)
static int
receive_from_daemon(int fd, PrvRequest *req)
{
struct msghdr msg;
struct cmsghdr *cmsg;
struct iovec iov;
char cmsgbuf[256];
SCK_Message *message;
iov.iov_base = req;
iov.iov_len = sizeof (*req);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = (void *)cmsgbuf;
msg.msg_controllen = sizeof (cmsgbuf);
msg.msg_flags = MSG_WAITALL;
/* read the data */
if (recvmsg(fd, &msg, 0) != sizeof (*req))
message = SCK_ReceiveMessage(fd, SCK_FLAG_MSG_DESCRIPTOR);
if (!message || message->length != sizeof (*req))
return 0;
memcpy(req, message->data, sizeof (*req));
if (req->op == OP_BINDSOCKET) {
/* extract transferred descriptor */
req->data.bind_socket.sock = -1;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
memcpy(&req->data.bind_socket.sock, CMSG_DATA(cmsg), sizeof (int));
}
req->data.bind_socket.sock = message->descriptor;
/* return error if valid descriptor not found */
if (req->data.bind_socket.sock < 0)
return 0;
} else if (message->descriptor >= 0) {
SCK_CloseSocket(message->descriptor);
return 0;
}
return 1;
@@ -257,8 +244,7 @@ do_set_time(const ReqSetTime *req, PrvResponse *res)
static void
do_bind_socket(ReqBindSocket *req, PrvResponse *res)
{
unsigned short port;
IPAddr ip;
IPSockAddr ip_saddr;
int sock_fd;
struct sockaddr *sa;
socklen_t sa_len;
@@ -267,10 +253,11 @@ do_bind_socket(ReqBindSocket *req, PrvResponse *res)
sa_len = req->sa_len;
sock_fd = req->sock;
UTI_SockaddrToIPAndPort(sa, &ip, &port);
if (port && port != CNF_GetNTPPort() && port != CNF_GetAcquisitionPort()) {
close(sock_fd);
res_fatal(res, "Invalid port %d", port);
SCK_SockaddrToIPSockAddr(sa, sa_len, &ip_saddr);
if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() &&
ip_saddr.port != CNF_GetAcquisitionPort()) {
SCK_CloseSocket(sock_fd);
res_fatal(res, "Invalid port %d", ip_saddr.port);
return;
}
@@ -279,7 +266,7 @@ do_bind_socket(ReqBindSocket *req, PrvResponse *res)
res->res_errno = errno;
/* sock is still open on daemon side, but we're done with it in the helper */
close(sock_fd);
SCK_CloseSocket(sock_fd);
}
#endif
@@ -373,7 +360,7 @@ helper_main(int fd)
send_response(fd, &res);
}
close(fd);
SCK_CloseSocket(fd);
exit(0);
}
@@ -386,7 +373,7 @@ receive_response(PrvResponse *res)
{
int resp_len;
resp_len = recv(helper_fd, res, sizeof (*res), 0);
resp_len = SCK_Receive(helper_fd, res, sizeof (*res), 0);
if (resp_len < 0)
LOG_FATAL("Could not read from helper : %s", strerror(errno));
if (resp_len != sizeof (*res))
@@ -409,41 +396,22 @@ receive_response(PrvResponse *res)
static void
send_request(PrvRequest *req)
{
struct msghdr msg;
struct iovec iov;
char cmsgbuf[256];
SCK_Message message;
int flags;
iov.iov_base = req;
iov.iov_len = sizeof (*req);
SCK_InitMessage(&message, SCK_ADDR_UNSPEC);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
message.data = req;
message.length = sizeof (*req);
flags = 0;
if (req->op == OP_BINDSOCKET) {
/* send file descriptor as a control message */
struct cmsghdr *cmsg;
int *ptr_send_fd;
msg.msg_control = cmsgbuf;
msg.msg_controllen = CMSG_SPACE(sizeof (int));
cmsg = CMSG_FIRSTHDR(&msg);
memset(cmsg, 0, CMSG_SPACE(sizeof (int)));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof (int));
ptr_send_fd = (int *)CMSG_DATA(cmsg);
*ptr_send_fd = req->data.bind_socket.sock;
message.descriptor = req->data.bind_socket.sock;
flags |= SCK_FLAG_MSG_DESCRIPTOR;
}
if (sendmsg(helper_fd, &msg, 0) < 0) {
if (!SCK_SendMessage(helper_fd, &message, flags)) {
/* don't try to send another request from exit() */
helper_fd = -1;
LOG_FATAL("Could not send to helper : %s", strerror(errno));
@@ -573,13 +541,13 @@ PRV_SetTime(const struct timeval *tp, const struct timezone *tzp)
int
PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len)
{
IPSockAddr ip_saddr;
PrvRequest req;
PrvResponse res;
IPAddr ip;
unsigned short port;
UTI_SockaddrToIPAndPort(address, &ip, &port);
if (port && port != CNF_GetNTPPort() && port != CNF_GetAcquisitionPort())
SCK_SockaddrToIPSockAddr(address, address_len, &ip_saddr);
if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() &&
ip_saddr.port != CNF_GetAcquisitionPort())
assert(0);
if (!have_helper())
@@ -589,6 +557,7 @@ PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len)
req.op = OP_BINDSOCKET;
req.data.bind_socket.sock = sock;
req.data.bind_socket.sa_len = address_len;
assert(address_len <= sizeof (req.data.bind_socket.sa));
memcpy(&req.data.bind_socket.sa.u, address, address_len);
submit_request(&req, &res);
@@ -616,7 +585,6 @@ PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
req.op = OP_NAME2IPADDRESS;
if (snprintf(req.data.name_to_ipaddress.name, sizeof (req.data.name_to_ipaddress.name),
"%s", name) >= sizeof (req.data.name_to_ipaddress.name)) {
DEBUG_LOG("Name too long");
return DNS_Failure;
}
@@ -670,20 +638,14 @@ void
PRV_StartHelper(void)
{
pid_t pid;
int fd, sock_pair[2];
int fd, sock_fd1, sock_fd2;
if (have_helper())
LOG_FATAL("Helper already running");
if (
#ifdef SOCK_SEQPACKET
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock_pair) &&
#endif
socketpair(AF_UNIX, SOCK_DGRAM, 0, sock_pair))
LOG_FATAL("socketpair() failed : %s", strerror(errno));
UTI_FdSetCloexec(sock_pair[0]);
UTI_FdSetCloexec(sock_pair[1]);
sock_fd1 = SCK_OpenUnixSocketPair(SCK_FLAG_BLOCK, &sock_fd2);
if (sock_fd1 < 0)
LOG_FATAL("Could not open socket pair");
pid = fork();
if (pid < 0)
@@ -691,23 +653,24 @@ PRV_StartHelper(void)
if (pid == 0) {
/* child process */
close(sock_pair[0]);
SCK_CloseSocket(sock_fd1);
/* close other descriptors inherited from the parent process */
for (fd = 0; fd < 1024; fd++) {
if (fd != sock_pair[1])
/* close other descriptors inherited from the parent process, except
stdin, stdout, and stderr */
for (fd = STDERR_FILENO + 1; fd < 1024; fd++) {
if (fd != sock_fd2)
close(fd);
}
/* ignore signals, the process will exit on OP_QUIT request */
UTI_SetQuitSignalsHandler(SIG_IGN, 1);
helper_main(sock_pair[1]);
helper_main(sock_fd2);
} else {
/* parent process */
close(sock_pair[1]);
helper_fd = sock_pair[0];
SCK_CloseSocket(sock_fd2);
helper_fd = sock_fd1;
helper_pid = pid;
/* stop the helper even when not exiting cleanly from the main function */

View File

@@ -415,7 +415,6 @@ accumulate_sample(RCL_Instance instance, struct timespec *sample_time, double of
sample.root_delay = instance->delay;
sample.peer_dispersion = dispersion;
sample.root_dispersion = dispersion;
sample.leap = instance->leap_status;
/* Handle special case when PPS is used with the local reference */
if (instance->pps_active && instance->lock_ref == -1)
@@ -704,6 +703,7 @@ poll_timeout(void *arg)
if (SPF_GetFilteredSample(inst->filter, &sample)) {
SRC_UpdateReachability(inst->source, 1);
SRC_SetLeapStatus(inst->source, inst->leap_status);
SRC_AccumulateSample(inst->source, &sample);
SRC_SelectSource(inst->source);

View File

@@ -33,6 +33,7 @@
#include "logging.h"
#include "util.h"
#include "sched.h"
#include "socket.h"
#define SOCK_MAGIC 0x534f434b
@@ -97,7 +98,6 @@ static void read_sample(int sockfd, int event, void *anything)
static int sock_initialise(RCL_Instance instance)
{
struct sockaddr_un s;
int sockfd;
char *path;
@@ -105,25 +105,9 @@ static int sock_initialise(RCL_Instance instance)
path = RCL_GetDriverParameter(instance);
s.sun_family = AF_UNIX;
if (snprintf(s.sun_path, sizeof (s.sun_path), "%s", path) >= sizeof (s.sun_path)) {
LOG_FATAL("Path %s too long", path);
return 0;
}
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd < 0) {
LOG_FATAL("socket() failed");
return 0;
}
UTI_FdSetCloexec(sockfd);
unlink(path);
if (bind(sockfd, (struct sockaddr *)&s, sizeof (s)) < 0) {
LOG_FATAL("bind(%s) failed : %s", path, strerror(errno));
return 0;
}
sockfd = SCK_OpenUnixDatagramSocket(NULL, path, 0);
if (sockfd < 0)
LOG_FATAL("Could not open socket %s", path);
RCL_SetDriverData(instance, (void *)(long)sockfd);
SCH_AddFileHandler(sockfd, SCH_FILE_INPUT, read_sample, instance);
@@ -136,7 +120,8 @@ static void sock_finalise(RCL_Instance instance)
sockfd = (long)RCL_GetDriverData(instance);
SCH_RemoveFileHandler(sockfd);
close(sockfd);
SCK_RemoveSocket(sockfd);
SCK_CloseSocket(sockfd);
}
RefclockDriver RCL_SOCK_driver = {

View File

@@ -42,11 +42,18 @@
/* The minimum allowed skew */
#define MIN_SKEW 1.0e-12
/* The update interval of the reference in the local reference mode */
#define LOCAL_REF_UPDATE_INTERVAL 64.0
/* Interval between updates of the drift file */
#define MAX_DRIFTFILE_AGE 3600.0
static int are_we_synchronised;
static int enable_local_stratum;
static int local_stratum;
static int local_orphan;
static double local_distance;
static struct timespec local_ref_time;
static NTP_Leap our_leap_status;
static int our_leap_sec;
static int our_tai_offset;
@@ -58,6 +65,8 @@ static double our_skew;
static double our_residual_freq;
static double our_root_delay;
static double our_root_dispersion;
static double our_offset_sd;
static double our_frequency_sd;
static double max_update_skew;
@@ -103,6 +112,9 @@ static void update_drift_file(double, double);
/* Leap second handling mode */
static REF_LeapMode leap_mode;
/* Time of UTC midnight of the upcoming or previous leap second */
static time_t leap_when;
/* Flag indicating the clock was recently corrected for leap second and it may
not have correct time yet (missing 23:59:60 in the UTC time scale) */
static int leap_in_progress;
@@ -134,8 +146,8 @@ static struct fb_drift *fb_drifts = NULL;
static int next_fb_drift;
static SCH_TimeoutID fb_drift_timeout_id;
/* Timestamp of last reference update */
static struct timespec last_ref_update;
/* Monotonic timestamp of the last reference update */
static double last_ref_update;
static double last_ref_update_interval;
/* ================================================== */
@@ -160,9 +172,8 @@ handle_slew(struct timespec *raw,
UTI_AdjustTimespec(&our_ref_time, cooked, &our_ref_time, &delta, dfreq, doffset);
if (change_type == LCL_ChangeUnknownStep) {
UTI_ZeroTimespec(&last_ref_update);
} else if (last_ref_update.tv_sec) {
UTI_AdjustTimespec(&last_ref_update, cooked, &last_ref_update, &delta, dfreq, doffset);
last_ref_update = 0.0;
REF_SetUnsynchronised();
}
/* When the clock was stepped, check if that doesn't change our leap status
@@ -194,12 +205,14 @@ REF_Initialise(void)
our_frequency_ppm = 0.0;
our_skew = 1.0; /* i.e. rather bad */
our_residual_freq = 0.0;
our_frequency_sd = 0.0;
our_offset_sd = 0.0;
drift_file_age = 0.0;
/* Now see if we can get the drift file opened */
drift_file = CNF_GetDriftFile();
if (drift_file) {
in = fopen(drift_file, "r");
in = UTI_OpenFile(NULL, drift_file, NULL, 'r', 0);
if (in) {
if (fscanf(in, "%lf%lf", &file_freq_ppm, &file_skew_ppm) == 2) {
/* We have read valid data */
@@ -234,7 +247,9 @@ REF_Initialise(void)
correction_time_ratio = CNF_GetCorrectionTimeRatio();
enable_local_stratum = CNF_AllowLocalReference(&local_stratum, &local_orphan, &local_distance);
UTI_ZeroTimespec(&local_ref_time);
leap_when = 0;
leap_timeout_id = 0;
leap_in_progress = 0;
leap_mode = CNF_GetLeapSecMode();
@@ -269,7 +284,7 @@ REF_Initialise(void)
}
UTI_ZeroTimespec(&our_ref_time);
UTI_ZeroTimespec(&last_ref_update);
last_ref_update = 0.0;
last_ref_update_interval = 0.0;
LCL_AddParameterChangeHandler(handle_slew, NULL);
@@ -331,61 +346,20 @@ REF_GetLeapMode(void)
static void
update_drift_file(double freq_ppm, double skew)
{
struct stat buf;
char *temp_drift_file;
FILE *out;
int r1, r2;
/* Create a temporary file with a '.tmp' extension. */
temp_drift_file = (char*) Malloc(strlen(drift_file)+8);
if(!temp_drift_file) {
out = UTI_OpenFile(NULL, drift_file, ".tmp", 'w', 0644);
if (!out)
return;
}
strcpy(temp_drift_file,drift_file);
strcat(temp_drift_file,".tmp");
out = fopen(temp_drift_file, "w");
if (!out) {
Free(temp_drift_file);
LOG(LOGS_WARN, "Could not open temporary driftfile %s.tmp for writing",
drift_file);
return;
}
/* Write the frequency and skew parameters in ppm */
r1 = fprintf(out, "%20.6f %20.6f\n", freq_ppm, 1.0e6 * skew);
r2 = fclose(out);
if (r1 < 0 || r2) {
Free(temp_drift_file);
LOG(LOGS_WARN, "Could not write to temporary driftfile %s.tmp",
drift_file);
return;
}
fprintf(out, "%20.6f %20.6f\n", freq_ppm, 1.0e6 * skew);
fclose(out);
/* Clone the file attributes from the existing file if there is one. */
if (!stat(drift_file,&buf)) {
if (chown(temp_drift_file,buf.st_uid,buf.st_gid) ||
chmod(temp_drift_file,buf.st_mode & 0777)) {
LOG(LOGS_WARN, "Could not change ownership or permissions of temporary driftfile %s.tmp",
drift_file);
}
}
/* Rename the temporary file to the correct location (see rename(2) for details). */
if (rename(temp_drift_file,drift_file)) {
unlink(temp_drift_file);
Free(temp_drift_file);
LOG(LOGS_WARN, "Could not replace old driftfile %s with new one %s.tmp",
drift_file,drift_file);
return;
}
Free(temp_drift_file);
/* Rename the temporary file to the correct location */
if (!UTI_RenameTempFile(NULL, drift_file, ".tmp", NULL))
;
}
/* ================================================== */
@@ -451,16 +425,16 @@ fb_drift_timeout(void *arg)
/* ================================================== */
static void
schedule_fb_drift(struct timespec *now)
schedule_fb_drift(void)
{
int i, c, secs;
double unsynchronised;
struct timespec when;
double unsynchronised, now;
if (fb_drift_timeout_id)
return; /* already scheduled */
unsynchronised = UTI_DiffTimespecsToDouble(now, &last_ref_update);
now = SCH_GetLastEventMonoTime();
unsynchronised = now - last_ref_update;
for (c = secs = 0, i = fb_drift_min; i <= fb_drift_max; i++) {
secs = 1 << i;
@@ -482,8 +456,7 @@ schedule_fb_drift(struct timespec *now)
if (i <= fb_drift_max) {
next_fb_drift = i;
UTI_AddDoubleToTimespec(now, secs - unsynchronised, &when);
fb_drift_timeout_id = SCH_AddTimeout(&when, fb_drift_timeout, NULL);
fb_drift_timeout_id = SCH_AddTimeoutByDelay(secs - unsynchronised, fb_drift_timeout, NULL);
DEBUG_LOG("Fallback drift %d scheduled", i);
}
}
@@ -751,10 +724,12 @@ set_leap_timeout(time_t now)
if (!our_leap_sec)
return;
leap_when = (now / (24 * 3600) + 1) * (24 * 3600);
/* Insert leap second at 0:00:00 UTC, delete at 23:59:59 UTC. If the clock
will be corrected by the system, timeout slightly sooner to be sure it
will happen before the system correction. */
when.tv_sec = (now / (24 * 3600) + 1) * (24 * 3600);
when.tv_sec = leap_when;
when.tv_nsec = 0;
if (our_leap_sec < 0)
when.tv_sec--;
@@ -798,7 +773,7 @@ update_leap_status(NTP_Leap leap, time_t now, int reset)
}
if ((leap_sec != our_leap_sec || tai_offset != our_tai_offset)
&& !REF_IsLeapSecondClose()) {
&& !REF_IsLeapSecondClose(NULL, 0.0)) {
our_leap_sec = leap_sec;
our_tai_offset = tai_offset;
@@ -837,6 +812,20 @@ get_root_dispersion(struct timespec *ts)
/* ================================================== */
static void
update_sync_status(struct timespec *now)
{
double elapsed;
elapsed = fabs(UTI_DiffTimespecsToDouble(now, &our_ref_time));
LCL_SetSyncStatus(are_we_synchronised,
our_offset_sd + elapsed * our_frequency_sd,
our_root_delay / 2.0 + get_root_dispersion(now));
}
/* ================================================== */
static void
write_log(struct timespec *now, int combined_sources, double freq,
double offset, double offset_sd, double uncorrected_offset,
@@ -959,6 +948,18 @@ get_clock_estimates(int manual,
/* ================================================== */
static void
fuzz_ref_time(struct timespec *ts)
{
uint32_t rnd;
/* Add a random value from interval [-1.0, 0.0] */
UTI_GetRandomBytes(&rnd, sizeof (rnd));
UTI_AddDoubleToTimespec(ts, -(double)rnd / (uint32_t)-1, ts);
}
/* ================================================== */
void
REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
uint32_t ref_id, IPAddr *ref_ip, struct timespec *ref_time,
@@ -968,9 +969,8 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
{
double uncorrected_offset, accumulate_offset, step_offset;
double residual_frequency, local_abs_frequency;
double elapsed, update_interval, correction_rate, orig_root_distance;
double elapsed, mono_now, update_interval, correction_rate, orig_root_distance;
struct timespec now, raw_now;
NTP_int64 ref_fuzz;
int manual;
assert(initialised);
@@ -983,17 +983,16 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
manual = leap == LEAP_Unsynchronised;
mono_now = SCH_GetLastEventMonoTime();
LCL_ReadRawTime(&raw_now);
LCL_GetOffsetCorrection(&raw_now, &uncorrected_offset, NULL);
UTI_AddDoubleToTimespec(&raw_now, uncorrected_offset, &now);
elapsed = UTI_DiffTimespecsToDouble(&now, ref_time);
offset += elapsed * frequency;
offset_sd += elapsed * frequency_sd;
if (last_ref_update.tv_sec) {
update_interval = UTI_DiffTimespecsToDouble(&now, &last_ref_update);
update_interval = MAX(update_interval, 0.0);
if (last_ref_update != 0.0) {
update_interval = mono_now - last_ref_update;
} else {
update_interval = 0.0;
}
@@ -1019,7 +1018,9 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
our_residual_freq = residual_frequency;
our_root_delay = root_delay;
our_root_dispersion = root_dispersion;
last_ref_update = now;
our_frequency_sd = offset_sd;
our_offset_sd = offset_sd;
last_ref_update = mono_now;
last_ref_update_interval = update_interval;
last_offset = offset;
@@ -1051,7 +1052,6 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
/* Adjust the clock */
LCL_AccumulateFrequencyAndOffset(frequency, accumulate_offset, correction_rate);
update_leap_status(leap, raw_now.tv_sec, 0);
maybe_log_offset(offset, raw_now.tv_sec);
if (step_offset != 0.0) {
@@ -1059,17 +1059,13 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
LOG(LOGS_WARN, "System clock was stepped by %.6f seconds", -step_offset);
}
LCL_SetSyncStatus(are_we_synchronised, offset_sd,
root_delay / 2.0 + get_root_dispersion(&now));
update_leap_status(leap, raw_now.tv_sec, 0);
update_sync_status(&now);
/* Add a random error of up to one second to the reference time to make it
less useful when disclosed to NTP and cmdmon clients for estimating
receive timestamps in the interleaved symmetric NTP mode */
UTI_GetNtp64Fuzz(&ref_fuzz, 0);
UTI_TimespecToNtp64(&our_ref_time, &ref_fuzz, &ref_fuzz);
UTI_Ntp64ToTimespec(&ref_fuzz, &our_ref_time);
if (UTI_CompareTimespecs(&our_ref_time, ref_time) >= 0)
our_ref_time.tv_sec--;
fuzz_ref_time(&our_ref_time);
local_abs_frequency = LCL_ReadAbsoluteFrequency();
@@ -1079,7 +1075,7 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
if (drift_file) {
/* Update drift file at most once per hour */
drift_file_age += update_interval;
if (drift_file_age < 0.0 || drift_file_age > 3600.0) {
if (drift_file_age >= MAX_DRIFTFILE_AGE) {
update_drift_file(local_abs_frequency, our_skew);
drift_file_age = 0.0;
}
@@ -1088,7 +1084,7 @@ REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
/* Update fallback drifts */
if (fb_drifts && are_we_synchronised) {
update_fb_drifts(local_abs_frequency, update_interval);
schedule_fb_drift(&now);
schedule_fb_drift();
}
/* Update the moving average of squares of offset, quickly on start */
@@ -1141,7 +1137,7 @@ REF_SetUnsynchronised(void)
UTI_AddDoubleToTimespec(&now_raw, uncorrected_offset, &now);
if (fb_drifts) {
schedule_fb_drift(&now);
schedule_fb_drift();
}
update_leap_status(LEAP_Unsynchronised, 0, 0);
@@ -1158,6 +1154,25 @@ REF_SetUnsynchronised(void)
/* ================================================== */
void
REF_UpdateLeapStatus(NTP_Leap leap)
{
struct timespec raw_now, now;
/* Wait for a full reference update if not already synchronised */
if (!are_we_synchronised)
return;
SCH_GetLastEventTime(&now, NULL, &raw_now);
update_leap_status(leap, raw_now.tv_sec, 0);
/* Update also the synchronisation status */
update_sync_status(&now);
}
/* ================================================== */
void
REF_GetReferenceParams
(
@@ -1171,7 +1186,7 @@ REF_GetReferenceParams
double *root_dispersion
)
{
double dispersion;
double dispersion, delta;
assert(initialised);
@@ -1203,13 +1218,17 @@ REF_GetReferenceParams
*stratum = local_stratum;
*ref_id = NTP_REFID_LOCAL;
/* Make the reference time be now less a second - this will
scarcely affect the client, but will ensure that the transmit
timestamp cannot come before this (which would cause test 7 to
fail in the client's read routine) if the local system clock's
read routine is broken in any way. */
*ref_time = *local_time;
--ref_time->tv_sec;
/* Keep the reference timestamp up to date. Adjust the timestamp to make
sure that the transmit timestamp cannot come before this (which might
fail a test of an NTP client). */
delta = UTI_DiffTimespecsToDouble(local_time, &local_ref_time);
if (delta > LOCAL_REF_UPDATE_INTERVAL || delta < 1.0) {
UTI_AddDoubleToTimespec(local_time, -1.0, &local_ref_time);
fuzz_ref_time(&local_ref_time);
}
*ref_time = local_ref_time;
/* Not much else we can do for leap second bits - maybe need to
have a way for the administrator to feed leap bits in */
@@ -1311,22 +1330,24 @@ REF_DisableLocal(void)
#define LEAP_SECOND_CLOSE 5
int REF_IsLeapSecondClose(void)
static int
is_leap_close(time_t t)
{
return t >= leap_when - LEAP_SECOND_CLOSE && t < leap_when + LEAP_SECOND_CLOSE;
}
/* ================================================== */
int REF_IsLeapSecondClose(struct timespec *ts, double offset)
{
struct timespec now, now_raw;
time_t t;
if (!our_leap_sec)
return 0;
SCH_GetLastEventTime(&now, NULL, &now_raw);
t = now.tv_sec > 0 ? now.tv_sec : -now.tv_sec;
if ((t + LEAP_SECOND_CLOSE) % (24 * 3600) < 2 * LEAP_SECOND_CLOSE)
if (is_leap_close(now.tv_sec) || is_leap_close(now_raw.tv_sec))
return 1;
t = now_raw.tv_sec > 0 ? now_raw.tv_sec : -now_raw.tv_sec;
if ((t + LEAP_SECOND_CLOSE) % (24 * 3600) < 2 * LEAP_SECOND_CLOSE)
if (ts && (is_leap_close(ts->tv_sec) || is_leap_close(ts->tv_sec + offset)))
return 1;
return 0;

View File

@@ -162,6 +162,9 @@ extern void REF_SetManualReference
extern void
REF_SetUnsynchronised(void);
/* Announce a leap second before the full reference update */
extern void REF_UpdateLeapStatus(NTP_Leap leap);
/* Return the current stratum of this host or 16 if the host is not
synchronised */
extern int REF_GetOurStratum(void);
@@ -181,9 +184,9 @@ extern void REF_ModifyMakestep(int limit, double threshold);
extern void REF_EnableLocal(int stratum, double distance, int orphan);
extern void REF_DisableLocal(void);
/* Check if current raw or cooked time is close to a leap second
and is better to discard any measurements */
extern int REF_IsLeapSecondClose(void);
/* Check if either of the current raw and cooked time, and optionally a
provided timestamp with an offset, is close to a leap second */
extern int REF_IsLeapSecondClose(struct timespec *ts, double offset);
/* Return TAI-UTC offset corresponding to a time in UTC if available */
extern int REF_GetTaiOffset(struct timespec *ts);

2
rtc.c
View File

@@ -160,7 +160,7 @@ RTC_Initialise(int initial_set)
void
RTC_Finalise(void)
{
if (driver.fini) {
if (driver_initialised) {
(driver.fini)();
}
}

View File

@@ -390,12 +390,9 @@ read_hwclock_file(const char *hwclock_file)
if (!hwclock_file || !hwclock_file[0])
return;
in = fopen(hwclock_file, "r");
if (!in) {
LOG(LOGS_WARN, "Could not open %s : %s",
hwclock_file, strerror(errno));
in = UTI_OpenFile(NULL, hwclock_file, NULL, 'r', 0);
if (!in)
return;
}
/* Read third line from the file. */
for (i = 0; i < 3; i++) {
@@ -445,7 +442,8 @@ read_coefs_from_file(void)
tried_to_load_coefs = 1;
if (coefs_file_name && (in = fopen(coefs_file_name, "r"))) {
if (coefs_file_name &&
(in = UTI_OpenFile(NULL, coefs_file_name, NULL, 'r', 0))) {
if (fscanf(in, "%d%ld%lf%lf",
&valid_coefs_from_file,
&file_ref_time,
@@ -466,67 +464,40 @@ read_coefs_from_file(void)
static int
write_coefs_to_file(int valid,time_t ref_time,double offset,double rate)
{
struct stat buf;
char *temp_coefs_file_name;
FILE *out;
int r1, r2;
/* Create a temporary file with a '.tmp' extension. */
temp_coefs_file_name = (char*) Malloc(strlen(coefs_file_name)+8);
if(!temp_coefs_file_name) {
out = UTI_OpenFile(NULL, coefs_file_name, ".tmp", 'w', 0644);
if (!out)
return RTC_ST_BADFILE;
}
strcpy(temp_coefs_file_name,coefs_file_name);
strcat(temp_coefs_file_name,".tmp");
out = fopen(temp_coefs_file_name, "w");
if (!out) {
Free(temp_coefs_file_name);
LOG(LOGS_WARN, "Could not open temporary RTC file %s.tmp for writing",
coefs_file_name);
return RTC_ST_BADFILE;
}
/* Gain rate is written out in ppm */
r1 = fprintf(out, "%1d %ld %.6f %.3f\n",
valid, ref_time, offset, 1.0e6 * rate);
r2 = fclose(out);
if (r1 < 0 || r2) {
Free(temp_coefs_file_name);
LOG(LOGS_WARN, "Could not write to temporary RTC file %s.tmp",
coefs_file_name);
fprintf(out, "%1d %ld %.6f %.3f\n", valid, ref_time, offset, 1.0e6 * rate);
fclose(out);
/* Rename the temporary file to the correct location */
if (!UTI_RenameTempFile(NULL, coefs_file_name, ".tmp", NULL))
return RTC_ST_BADFILE;
}
/* Clone the file attributes from the existing file if there is one. */
if (!stat(coefs_file_name,&buf)) {
if (chown(temp_coefs_file_name,buf.st_uid,buf.st_gid) ||
chmod(temp_coefs_file_name,buf.st_mode & 0777)) {
LOG(LOGS_WARN,
"Could not change ownership or permissions of temporary RTC file %s.tmp",
coefs_file_name);
}
}
/* Rename the temporary file to the correct location (see rename(2) for details). */
if (rename(temp_coefs_file_name,coefs_file_name)) {
unlink(temp_coefs_file_name);
Free(temp_coefs_file_name);
LOG(LOGS_WARN, "Could not replace old RTC file %s.tmp with new one %s",
coefs_file_name, coefs_file_name);
return RTC_ST_BADFILE;
}
Free(temp_coefs_file_name);
return RTC_ST_OK;
}
/* ================================================== */
static int
switch_interrupts(int on_off)
{
if (ioctl(fd, on_off ? RTC_UIE_ON : RTC_UIE_OFF, 0) < 0) {
LOG(LOGS_ERR, "Could not %s RTC interrupt : %s",
on_off ? "enable" : "disable", strerror(errno));
return 0;
}
if (on_off)
skip_interrupts = 1;
return 1;
}
/* ================================================== */
/* file_name is the name of the file where we save the RTC params
@@ -536,6 +507,23 @@ write_coefs_to_file(int valid,time_t ref_time,double offset,double rate)
int
RTC_Linux_Initialise(void)
{
/* Try to open the device */
fd = open(CNF_GetRtcDevice(), O_RDWR);
if (fd < 0) {
LOG(LOGS_ERR, "Could not open RTC device %s : %s",
CNF_GetRtcDevice(), strerror(errno));
return 0;
}
/* Make sure the RTC supports interrupts */
if (!switch_interrupts(1) || !switch_interrupts(0)) {
close(fd);
return 0;
}
/* Close on exec */
UTI_FdSetCloexec(fd);
rtc_sec = MallocArray(time_t, MAX_SAMPLES);
rtc_trim = MallocArray(double, MAX_SAMPLES);
system_times = MallocArray(struct timespec, MAX_SAMPLES);
@@ -546,18 +534,6 @@ RTC_Linux_Initialise(void)
/* In case it didn't get done by pre-init */
coefs_file_name = CNF_GetRtcFile();
/* Try to open device */
fd = open (CNF_GetRtcDevice(), O_RDWR);
if (fd < 0) {
LOG(LOGS_ERR, "Could not open RTC device %s : %s",
CNF_GetRtcDevice(), strerror(errno));
return 0;
}
/* Close on exec */
UTI_FdSetCloexec(fd);
n_samples = 0;
n_samples_since_regression = 0;
n_runs = 0;
@@ -590,6 +566,7 @@ RTC_Linux_Finalise(void)
/* Remove input file handler */
if (fd >= 0) {
SCH_RemoveFileHandler(fd);
switch_interrupts(0);
close(fd);
/* Save the RTC data */
@@ -603,29 +580,6 @@ RTC_Linux_Finalise(void)
/* ================================================== */
static void
switch_interrupts(int onoff)
{
int status;
if (onoff) {
status = ioctl(fd, RTC_UIE_ON, 0);
if (status < 0) {
LOG(LOGS_ERR, "Could not %s RTC interrupt : %s", "enable", strerror(errno));
return;
}
skip_interrupts = 1;
} else {
status = ioctl(fd, RTC_UIE_OFF, 0);
if (status < 0) {
LOG(LOGS_ERR, "Could not %s RTC interrupt : %s", "disable", strerror(errno));
return;
}
}
}
/* ================================================== */
static void
measurement_timeout(void *any)
{

View File

@@ -267,7 +267,7 @@ select_samples(SPF_Instance filter)
}
}
for (i = j = 0, k = -1; i < filter->used; i++) {
for (i = j = 0; i < filter->used; i++) {
if (selected[i] != -1)
selected[j++] = (selected[i] + filter->used - o) % filter->used;
}
@@ -387,7 +387,6 @@ combine_selected_samples(SPF_Instance filter, int n, NTP_Sample *result)
result->peer_delay = mean_peer_delay;
result->root_delay = mean_root_delay;
result->stratum = last_sample->stratum;
result->leap = last_sample->leap;
return 1;
}

59
sched.c
View File

@@ -65,6 +65,12 @@ static ARR_Instance file_handlers;
static struct timespec last_select_ts, last_select_ts_raw;
static double last_select_ts_err;
#define TS_MONO_PRECISION_NS 10000000U
/* Monotonic low-precision timestamp measuring interval since the start */
static double last_select_ts_mono;
static uint32_t last_select_ts_mono_ns;
/* ================================================== */
/* Variables to handler the timer queue */
@@ -136,6 +142,8 @@ SCH_Initialise(void)
LCL_ReadRawTime(&last_select_ts_raw);
last_select_ts = last_select_ts_raw;
last_select_ts_mono = 0.0;
last_select_ts_mono_ns = 0;
initialised = 1;
}
@@ -147,6 +155,8 @@ void
SCH_Finalise(void) {
ARR_DestroyInstance(file_handlers);
LCL_RemoveParameterChangeHandler(handle_slew, NULL);
initialised = 0;
}
@@ -247,6 +257,14 @@ SCH_GetLastEventTime(struct timespec *cooked, double *err, struct timespec *raw)
/* ================================================== */
double
SCH_GetLastEventMonoTime(void)
{
return last_select_ts_mono;
}
/* ================================================== */
#define TQE_ALLOC_QUANTUM 32
static TimerQueueEntry *
@@ -472,6 +490,20 @@ SCH_RemoveTimeout(SCH_TimeoutID id)
assert(0);
}
/* ================================================== */
void
SCH_Reset(void)
{
while (n_timer_queue_entries > 0)
SCH_RemoveTimeout(timer_queue.next->id);
while (one_highest_fd > 0) {
close(one_highest_fd - 1);
SCH_RemoveFileHandler(one_highest_fd - 1);
}
}
/* ================================================== */
/* Try to dispatch any timeouts that have already gone by, and
keep going until all are done. (The earlier ones may take so
@@ -706,6 +738,31 @@ check_current_time(struct timespec *prev_raw, struct timespec *raw, int timeout,
/* ================================================== */
static void
update_monotonic_time(struct timespec *now, struct timespec *before)
{
struct timespec diff;
/* Avoid frequent floating-point operations and handle small
increments to a large value */
UTI_DiffTimespecs(&diff, now, before);
if (diff.tv_sec == 0) {
last_select_ts_mono_ns += diff.tv_nsec;
} else {
last_select_ts_mono += fabs(UTI_TimespecToDouble(&diff) +
last_select_ts_mono_ns / 1.0e9);
last_select_ts_mono_ns = 0;
}
if (last_select_ts_mono_ns > TS_MONO_PRECISION_NS) {
last_select_ts_mono += last_select_ts_mono_ns / 1.0e9;
last_select_ts_mono_ns = 0;
}
}
/* ================================================== */
void
SCH_MainLoop(void)
{
@@ -762,6 +819,8 @@ SCH_MainLoop(void)
LCL_CookTime(&now, &cooked, &err);
}
update_monotonic_time(&cooked, &last_select_ts);
last_select_ts_raw = now;
last_select_ts = cooked;
last_select_ts_err = err;

View File

@@ -65,6 +65,9 @@ extern void SCH_SetFileHandlerEvent(int fd, int event, int enable);
/* Get the time stamp taken after a file descriptor became ready or a timeout expired */
extern void SCH_GetLastEventTime(struct timespec *cooked, double *err, struct timespec *raw);
/* Get a low-precision monotonic timestamp (starting at 0.0) */
extern double SCH_GetLastEventMonoTime(void);
/* This queues a timeout to elapse at a given (raw) local time */
extern SCH_TimeoutID SCH_AddTimeout(struct timespec *ts, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg);
@@ -82,6 +85,9 @@ extern SCH_TimeoutID SCH_AddTimeoutInClass(double min_delay, double separation,
/* The next one probably ought to return a status code */
extern void SCH_RemoveTimeout(SCH_TimeoutID);
/* Remove all timeouts and close all file descriptors */
extern void SCH_Reset(void);
extern void SCH_MainLoop(void);
extern void SCH_QuitProgram(void);

70
siv.h Normal file
View File

@@ -0,0 +1,70 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
Header file for Synthetic Initialization Vector (SIV) ciphers.
*/
#ifndef GOT_SIV_H
#define GOT_SIV_H
/* Maximum key length of all supported SIVs */
#define SIV_MAX_KEY_LENGTH 32
/* Maximum difference between lengths of ciphertext and plaintext */
#define SIV_MAX_TAG_LENGTH 16
/* Identifiers of SIV algorithms following the IANA AEAD registry */
typedef enum {
AEAD_AES_SIV_CMAC_256 = 15,
AEAD_AES_SIV_CMAC_384 = 16,
AEAD_AES_SIV_CMAC_512 = 17,
AEAD_AES_128_GCM_SIV = 30,
AEAD_AES_256_GCM_SIV = 31,
} SIV_Algorithm;
typedef struct SIV_Instance_Record *SIV_Instance;
extern SIV_Instance SIV_CreateInstance(SIV_Algorithm algorithm);
extern void SIV_DestroyInstance(SIV_Instance instance);
extern int SIV_GetKeyLength(SIV_Algorithm algorithm);
extern int SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length);
extern int SIV_GetTagLength(SIV_Instance instance);
extern int SIV_Encrypt(SIV_Instance instance,
const unsigned char *nonce, int nonce_length,
const void *assoc, int assoc_length,
const void *plaintext, int plaintext_length,
unsigned char *ciphertext, int ciphertext_length);
extern int SIV_Decrypt(SIV_Instance instance,
const unsigned char *nonce, int nonce_length,
const void *assoc, int assoc_length,
const unsigned char *ciphertext, int ciphertext_length,
void *plaintext, int plaintext_length);
#endif

142
siv_nettle.c Normal file
View File

@@ -0,0 +1,142 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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.
*
**********************************************************************
=======================================================================
SIV ciphers using the Nettle library
*/
#include "config.h"
#include "sysincl.h"
#ifdef HAVE_NETTLE_SIV_CMAC
#include <nettle/siv-cmac.h>
#else
#include "siv_nettle_int.c"
#endif
#include "memory.h"
#include "siv.h"
struct SIV_Instance_Record {
struct siv_cmac_aes128_ctx siv;
};
/* ================================================== */
SIV_Instance
SIV_CreateInstance(SIV_Algorithm algorithm)
{
SIV_Instance instance;
if (algorithm != AEAD_AES_SIV_CMAC_256)
return NULL;
instance = MallocNew(struct SIV_Instance_Record);
return instance;
}
/* ================================================== */
void
SIV_DestroyInstance(SIV_Instance instance)
{
Free(instance);
}
/* ================================================== */
int
SIV_GetKeyLength(SIV_Algorithm algorithm)
{
if (algorithm == AEAD_AES_SIV_CMAC_256)
return 32;
return 0;
}
/* ================================================== */
int
SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length)
{
if (length != 32)
return 0;
siv_cmac_aes128_set_key(&instance->siv, key);
return 1;
}
/* ================================================== */
int
SIV_GetTagLength(SIV_Instance instance)
{
return SIV_DIGEST_SIZE;
}
/* ================================================== */
int
SIV_Encrypt(SIV_Instance instance,
const unsigned char *nonce, int nonce_length,
const void *assoc, int assoc_length,
const void *plaintext, int plaintext_length,
unsigned char *ciphertext, int ciphertext_length)
{
if (nonce_length < SIV_MIN_NONCE_SIZE || assoc_length < 0 ||
plaintext_length < 0 || plaintext_length > ciphertext_length ||
plaintext_length + SIV_DIGEST_SIZE != ciphertext_length)
return 0;
assert(assoc && plaintext);
siv_cmac_aes128_encrypt_message(&instance->siv, nonce_length, nonce,
assoc_length, assoc,
ciphertext_length, ciphertext, plaintext);
return 1;
}
/* ================================================== */
int
SIV_Decrypt(SIV_Instance instance,
const unsigned char *nonce, int nonce_length,
const void *assoc, int assoc_length,
const unsigned char *ciphertext, int ciphertext_length,
void *plaintext, int plaintext_length)
{
if (nonce_length < SIV_MIN_NONCE_SIZE || assoc_length < 0 ||
plaintext_length < 0 || plaintext_length > ciphertext_length ||
plaintext_length + SIV_DIGEST_SIZE != ciphertext_length)
return 0;
assert(assoc && plaintext);
if (!siv_cmac_aes128_decrypt_message(&instance->siv, nonce_length, nonce,
assoc_length, assoc,
plaintext_length, plaintext, ciphertext))
return 0;
return 1;
}

452
siv_nettle_int.c Normal file
View File

@@ -0,0 +1,452 @@
/* This is a single-file implementation of AES-SIV-CMAC-256 based on
a patch for GNU Nettle by Nikos Mavrogiannopoulos */
/*
AES-CMAC-128 (rfc 4493)
Copyright (C) Stefan Metzmacher 2012
Copyright (C) Jeremy Allison 2012
Copyright (C) Michael Adam 2012
Copyright (C) 2017, Red Hat Inc.
This file is part of GNU Nettle.
GNU Nettle is free software: you can redistribute it and/or
modify it under the terms of either:
* the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.
or both in parallel, as here.
GNU Nettle 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 copies of the GNU General Public License and
the GNU Lesser General Public License along with this program. If
not, see http://www.gnu.org/licenses/.
*/
/* siv-aes128.c, siv-cmac.c, siv.h
AES-SIV, RFC5297
SIV-CMAC, RFC5297
Copyright (C) 2017 Nikos Mavrogiannopoulos
This file is part of GNU Nettle.
GNU Nettle is free software: you can redistribute it and/or
modify it under the terms of either:
* the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.
or both in parallel, as here.
GNU Nettle 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 copies of the GNU General Public License and
the GNU Lesser General Public License along with this program. If
not, see http://www.gnu.org/licenses/.
*/
/* cmac.h, siv-cmac.h, cmac-aes128.c
CMAC mode, as specified in RFC4493
SIV-CMAC mode, as specified in RFC5297
CMAC using AES128 as the underlying cipher.
Copyright (C) 2017 Red Hat, Inc.
Contributed by Nikos Mavrogiannopoulos
This file is part of GNU Nettle.
GNU Nettle is free software: you can redistribute it and/or
modify it under the terms of either:
* the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.
or both in parallel, as here.
GNU Nettle 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 copies of the GNU General Public License and
the GNU Lesser General Public License along with this program. If
not, see http://www.gnu.org/licenses/.
*/
# include "config.h"
#include <assert.h>
#include <string.h>
#include "nettle/aes.h"
#include "nettle/ctr.h"
#include "nettle/macros.h"
#include "nettle/memxor.h"
#include "nettle/memops.h"
#include "nettle/nettle-types.h"
/* For SIV, the block size of the block cipher shall be 128 bits. */
#define SIV_BLOCK_SIZE 16
#define SIV_DIGEST_SIZE 16
#define SIV_MIN_NONCE_SIZE 1
/*
* SIV mode requires the aad and plaintext when building the IV, which
* prevents streaming processing and it incompatible with the AEAD API.
*/
/* AES_SIV_CMAC_256 */
struct siv_cmac_aes128_ctx {
struct aes128_ctx cipher;
uint8_t s2vk[AES128_KEY_SIZE];
};
struct cmac128_ctx
{
/* Key */
union nettle_block16 K1;
union nettle_block16 K2;
/* MAC state */
union nettle_block16 X;
/* Block buffer */
union nettle_block16 block;
size_t index;
};
/* shift one and XOR with 0x87. */
static void
_cmac128_block_mulx(union nettle_block16 *dst,
const union nettle_block16 *src)
{
uint64_t b1 = READ_UINT64(src->b);
uint64_t b2 = READ_UINT64(src->b+8);
b1 = (b1 << 1) | (b2 >> 63);
b2 <<= 1;
if (src->b[0] & 0x80)
b2 ^= 0x87;
WRITE_UINT64(dst->b, b1);
WRITE_UINT64(dst->b+8, b2);
}
static void
cmac128_set_key(struct cmac128_ctx *ctx, const void *cipher,
nettle_cipher_func *encrypt)
{
static const uint8_t const_zero[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
union nettle_block16 *L = &ctx->block;
memset(ctx, 0, sizeof(*ctx));
/* step 1 - generate subkeys k1 and k2 */
encrypt(cipher, 16, L->b, const_zero);
_cmac128_block_mulx(&ctx->K1, L);
_cmac128_block_mulx(&ctx->K2, &ctx->K1);
}
#define MIN(x,y) ((x)<(y)?(x):(y))
static void
cmac128_update(struct cmac128_ctx *ctx, const void *cipher,
nettle_cipher_func *encrypt,
size_t msg_len, const uint8_t *msg)
{
union nettle_block16 Y;
/*
* check if we expand the block
*/
if (ctx->index < 16)
{
size_t len = MIN(16 - ctx->index, msg_len);
memcpy(&ctx->block.b[ctx->index], msg, len);
msg += len;
msg_len -= len;
ctx->index += len;
}
if (msg_len == 0) {
/* if it is still the last block, we are done */
return;
}
/*
* now checksum everything but the last block
*/
memxor3(Y.b, ctx->X.b, ctx->block.b, 16);
encrypt(cipher, 16, ctx->X.b, Y.b);
while (msg_len > 16)
{
memxor3(Y.b, ctx->X.b, msg, 16);
encrypt(cipher, 16, ctx->X.b, Y.b);
msg += 16;
msg_len -= 16;
}
/*
* copy the last block, it will be processed in
* cmac128_digest().
*/
memcpy(ctx->block.b, msg, msg_len);
ctx->index = msg_len;
}
static void
cmac128_digest(struct cmac128_ctx *ctx, const void *cipher,
nettle_cipher_func *encrypt,
unsigned length,
uint8_t *dst)
{
union nettle_block16 Y;
memset(ctx->block.b+ctx->index, 0, sizeof(ctx->block.b)-ctx->index);
/* re-use ctx->block for memxor output */
if (ctx->index < 16)
{
ctx->block.b[ctx->index] = 0x80;
memxor(ctx->block.b, ctx->K2.b, 16);
}
else
{
memxor(ctx->block.b, ctx->K1.b, 16);
}
memxor3(Y.b, ctx->block.b, ctx->X.b, 16);
assert(length <= 16);
if (length == 16)
{
encrypt(cipher, 16, dst, Y.b);
}
else
{
encrypt(cipher, 16, ctx->block.b, Y.b);
memcpy(dst, ctx->block.b, length);
}
/* reset state for re-use */
memset(&ctx->X, 0, sizeof(ctx->X));
ctx->index = 0;
}
#define CMAC128_CTX(type) \
{ struct cmac128_ctx ctx; type cipher; }
/* NOTE: Avoid using NULL, as we don't include anything defining it. */
#define CMAC128_SET_KEY(self, set_key, encrypt, cmac_key) \
do { \
(set_key)(&(self)->cipher, (cmac_key)); \
if (0) (encrypt)(&(self)->cipher, ~(size_t) 0, \
(uint8_t *) 0, (const uint8_t *) 0); \
cmac128_set_key(&(self)->ctx, &(self)->cipher, \
(nettle_cipher_func *) (encrypt)); \
} while (0)
#define CMAC128_UPDATE(self, encrypt, length, src) \
cmac128_update(&(self)->ctx, &(self)->cipher, \
(nettle_cipher_func *)encrypt, (length), (src))
#define CMAC128_DIGEST(self, encrypt, length, digest) \
(0 ? (encrypt)(&(self)->cipher, ~(size_t) 0, \
(uint8_t *) 0, (const uint8_t *) 0) \
: cmac128_digest(&(self)->ctx, &(self)->cipher, \
(nettle_cipher_func *) (encrypt), \
(length), (digest)))
struct cmac_aes128_ctx CMAC128_CTX(struct aes128_ctx);
static void
cmac_aes128_set_key(struct cmac_aes128_ctx *ctx, const uint8_t *key)
{
CMAC128_SET_KEY(ctx, aes128_set_encrypt_key, aes128_encrypt, key);
}
static void
cmac_aes128_update (struct cmac_aes128_ctx *ctx,
size_t length, const uint8_t *data)
{
CMAC128_UPDATE (ctx, aes128_encrypt, length, data);
}
static void
cmac_aes128_digest(struct cmac_aes128_ctx *ctx,
size_t length, uint8_t *digest)
{
CMAC128_DIGEST(ctx, aes128_encrypt, length, digest);
}
static const uint8_t const_one[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
static const uint8_t const_zero[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static
void _siv_s2v(nettle_set_key_func *cmac_set_key,
nettle_hash_update_func *cmac_update,
nettle_hash_digest_func *cmac_digest,
size_t cmac_ctx_size,
const uint8_t *s2vk, size_t alength, const uint8_t *adata,
size_t nlength, const uint8_t *nonce,
size_t plength, const uint8_t *pdata,
uint8_t *v)
{
uint8_t ctx[sizeof(struct cmac128_ctx)+sizeof(struct aes_ctx)];
union nettle_block16 D, S, T;
assert(cmac_ctx_size <= sizeof (ctx));
cmac_set_key(ctx, s2vk);
if (nlength == 0 && alength == 0) {
cmac_update(ctx, 16, const_one);
cmac_digest(ctx, 16, v);
return;
}
cmac_update(ctx, 16, const_zero);
cmac_digest(ctx, 16, D.b);
if (1) {
_cmac128_block_mulx(&D, &D);
cmac_update(ctx, alength, adata);
cmac_digest(ctx, 16, S.b);
memxor(D.b, S.b, 16);
}
if (nlength > 0) {
_cmac128_block_mulx(&D, &D);
cmac_update(ctx, nlength, nonce);
cmac_digest(ctx, 16, S.b);
memxor(D.b, S.b, 16);
}
/* Sn */
if (plength >= 16) {
cmac_update(ctx, plength-16, pdata);
pdata += plength-16;
memxor3(T.b, pdata, D.b, 16);
} else {
union nettle_block16 pad;
_cmac128_block_mulx(&T, &D);
memcpy(pad.b, pdata, plength);
pad.b[plength] = 0x80;
if (plength+1 < 16)
memset(&pad.b[plength+1], 0, 16-plength-1);
memxor(T.b, pad.b, 16);
}
cmac_update(ctx, 16, T.b);
cmac_digest(ctx, 16, v);
}
static void
siv_cmac_aes128_set_key(struct siv_cmac_aes128_ctx *ctx, const uint8_t *key)
{
memcpy(ctx->s2vk, key, 16);
aes128_set_encrypt_key(&ctx->cipher, key+16);
}
static void
siv_cmac_aes128_encrypt_message(struct siv_cmac_aes128_ctx *ctx,
size_t nlength, const uint8_t *nonce,
size_t alength, const uint8_t *adata,
size_t clength, uint8_t *dst, const uint8_t *src)
{
union nettle_block16 siv;
size_t slength;
assert (clength >= SIV_DIGEST_SIZE);
slength = clength - SIV_DIGEST_SIZE;
/* create CTR nonce */
_siv_s2v((nettle_set_key_func*)cmac_aes128_set_key,
(nettle_hash_update_func*)cmac_aes128_update,
(nettle_hash_digest_func*)cmac_aes128_digest,
sizeof(struct cmac_aes128_ctx), ctx->s2vk, alength, adata,
nlength, nonce, slength, src, siv.b);
memcpy(dst, siv.b, SIV_DIGEST_SIZE);
siv.b[8] &= ~0x80;
siv.b[12] &= ~0x80;
ctr_crypt(&ctx->cipher, (nettle_cipher_func *)aes128_encrypt, AES_BLOCK_SIZE,
siv.b, slength, dst+SIV_DIGEST_SIZE, src);
}
static int
siv_cmac_aes128_decrypt_message(struct siv_cmac_aes128_ctx *ctx,
size_t nlength, const uint8_t *nonce,
size_t alength, const uint8_t *adata,
size_t mlength, uint8_t *dst, const uint8_t *src)
{
union nettle_block16 siv;
union nettle_block16 ctr;
memcpy(ctr.b, src, SIV_DIGEST_SIZE);
ctr.b[8] &= ~0x80;
ctr.b[12] &= ~0x80;
ctr_crypt(&ctx->cipher, (nettle_cipher_func *)aes128_encrypt, AES_BLOCK_SIZE,
ctr.b, mlength, dst, src+SIV_DIGEST_SIZE);
/* create CTR nonce */
_siv_s2v((nettle_set_key_func*)cmac_aes128_set_key,
(nettle_hash_update_func*)cmac_aes128_update,
(nettle_hash_digest_func*)cmac_aes128_digest,
sizeof(struct cmac_aes128_ctx), ctx->s2vk, alength, adata,
nlength, nonce, mlength, dst, siv.b);
return memeql_sec(siv.b, src, SIV_DIGEST_SIZE);
}

1530
socket.c Normal file

File diff suppressed because it is too large Load Diff

141
socket.h Normal file
View File

@@ -0,0 +1,141 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2019
*
* 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 is the header file for socket operations.
*/
#ifndef GOT_SOCKET_H
#define GOT_SOCKET_H
#include "addressing.h"
/* Flags for opening sockets */
#define SCK_FLAG_BLOCK 1
#define SCK_FLAG_BROADCAST 2
#define SCK_FLAG_RX_DEST_ADDR 4
#define SCK_FLAG_ALL_PERMISSIONS 8
#define SCK_FLAG_PRIV_BIND 16
/* Flags for receiving and sending messages */
#define SCK_FLAG_MSG_ERRQUEUE 1
#define SCK_FLAG_MSG_DESCRIPTOR 2
typedef enum {
SCK_ADDR_UNSPEC = 0,
SCK_ADDR_IP,
SCK_ADDR_UNIX
} SCK_AddressType;
typedef struct {
void *data;
unsigned int length;
SCK_AddressType addr_type;
int if_index;
union {
IPSockAddr ip;
const char *path;
} remote_addr;
union {
IPAddr ip;
} local_addr;
struct {
struct timespec kernel;
struct timespec hw;
int if_index;
int l2_length;
int tx_flags;
} timestamp;
int descriptor;
} SCK_Message;
/* Initialisation function */
extern void SCK_Initialise(void);
/* Finalisation function */
extern void SCK_Finalise(void);
/* Check if support for the IP family was enabled in the build */
extern int SCK_IsFamilySupported(int family);
/* Get the 0.0.0.0/::0 or 127.0.0.1/::1 address */
extern void SCK_GetAnyLocalIPAddress(int family, IPAddr *local_addr);
extern void SCK_GetLoopbackIPAddress(int family, IPAddr *local_addr);
/* Specify a bind()-like function for binding sockets to privileged ports when
running in a restricted process (e.g. after dropping root privileges) */
extern void SCK_SetPrivBind(int (*function)(int sock_fd, struct sockaddr *address,
socklen_t address_len));
/* Open socket */
extern int SCK_OpenUdpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, int flags);
extern int SCK_OpenTcpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, int flags);
extern int SCK_OpenUnixDatagramSocket(const char *remote_addr, const char *local_addr,
int flags);
extern int SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_addr,
int flags);
extern int SCK_OpenUnixSocketPair(int flags, int *other_fd);
/* 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);
/* Enable RX timestamping socket option */
extern int SCK_EnableKernelRxTimestamping(int sock_fd);
/* Operate on a stream socket - listen()/accept()/shutdown() wrappers */
extern int SCK_ListenOnSocket(int sock_fd, int backlog);
extern int SCK_AcceptConnection(int sock_fd, IPSockAddr *remote_addr);
extern int SCK_ShutdownConnection(int sock_fd);
/* Receive and send data on connected sockets - recv()/send() wrappers */
extern int SCK_Receive(int sock_fd, void *buffer, unsigned int length, int flags);
extern int SCK_Send(int sock_fd, const void *buffer, unsigned int length, int flags);
/* Receive a single message or multiple messages. The functions return
a pointer to static buffers, or NULL on error. The buffers are valid until
another call of the functions and can be reused for sending messages. */
extern SCK_Message *SCK_ReceiveMessage(int sock_fd, int flags);
extern SCK_Message *SCK_ReceiveMessages(int sock_fd, int flags, int *num_messages);
/* Initialise a new message (e.g. before sending) */
extern void SCK_InitMessage(SCK_Message *message, SCK_AddressType addr_type);
/* Send a message */
extern int SCK_SendMessage(int sock_fd, SCK_Message *message, int flags);
/* Remove bound Unix socket */
extern int SCK_RemoveSocket(int sock_fd);
/* Close the socket */
extern void SCK_CloseSocket(int sock_fd);
/* Convert between IPSockAddr and sockaddr_in/in6 */
extern void SCK_SockaddrToIPSockAddr(struct sockaddr *sa, int sa_length, IPSockAddr *ip_sa);
extern int SCK_IPSockAddrToSockaddr(IPSockAddr *ip_sa, struct sockaddr *sa, int sa_length);
#endif

132
sources.c
View File

@@ -54,7 +54,6 @@ static int initialised = 0;
/* ================================================== */
/* Structure used to hold info for selecting between sources */
struct SelectInfo {
NTP_Leap leap;
int stratum;
int select_ok;
double std_dev;
@@ -126,6 +125,12 @@ struct SRC_Instance_Record {
double sel_score;
struct SelectInfo sel_info;
/* Latest leap status */
NTP_Leap leap;
/* Flag indicating the source has a leap second vote */
int leap_vote;
};
/* ================================================== */
@@ -249,6 +254,7 @@ SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int sel_optio
result->index = n_sources;
result->type = type;
result->sel_options = sel_options;
result->active = 0;
SRC_SetRefid(result, ref_id, addr);
SRC_ResetInstance(result);
@@ -291,13 +297,14 @@ void SRC_DestroyInstance(SRC_Instance instance)
void
SRC_ResetInstance(SRC_Instance instance)
{
instance->active = 0;
instance->updates = 0;
instance->reachability = 0;
instance->reachability_size = 0;
instance->distant = 0;
instance->status = SRC_BAD_STATS;
instance->sel_score = 1.0;
instance->leap = LEAP_Unsynchronised;
instance->leap_vote = 0;
SST_ResetInstance(instance->stats);
}
@@ -323,6 +330,48 @@ SRC_GetSourcestats(SRC_Instance instance)
/* ================================================== */
static NTP_Leap
get_leap_status(void)
{
int i, leap_votes, leap_ins, leap_del;
/* Accept a leap second if more than half of the sources with a vote agree */
for (i = leap_ins = leap_del = leap_votes = 0; i < n_sources; i++) {
if (!sources[i]->leap_vote)
continue;
leap_votes++;
if (sources[i]->leap == LEAP_InsertSecond)
leap_ins++;
else if (sources[i]->leap == LEAP_DeleteSecond)
leap_del++;
}
if (leap_ins > leap_votes / 2)
return LEAP_InsertSecond;
else if (leap_del > leap_votes / 2)
return LEAP_DeleteSecond;
else
return LEAP_Normal;
}
/* ================================================== */
void
SRC_SetLeapStatus(SRC_Instance inst, NTP_Leap leap)
{
if (REF_IsLeapSecondClose(NULL, 0.0))
return;
inst->leap = leap;
if (inst->leap_vote)
REF_UpdateLeapStatus(get_leap_status());
}
/* ================================================== */
/* This function is called by one of the source drivers when it has
a new sample that is to be accumulated.
@@ -341,7 +390,7 @@ SRC_AccumulateSample(SRC_Instance inst, NTP_Sample *sample)
source_to_string(inst), UTI_TimespecToString(&sample->time), -sample->offset,
sample->root_delay, sample->root_dispersion, sample->stratum);
if (REF_IsLeapSecondClose()) {
if (REF_IsLeapSecondClose(&sample->time, sample->offset)) {
LOG(LOGS_INFO, "Dropping sample around leap second");
return;
}
@@ -434,7 +483,7 @@ SRC_ResetReachability(SRC_Instance inst)
/* ================================================== */
static void
log_selection_message(char *format, char *arg)
log_selection_message(const char *format, const char *arg)
{
if (REF_GetMode() != REF_ModeNormal)
return;
@@ -595,7 +644,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
int n_badstats_sources, max_sel_reach, max_sel_reach_size, max_badstat_reach;
int depth, best_depth, trust_depth, best_trust_depth;
int combined, stratum, min_stratum, max_score_index;
int orphan_stratum, orphan_source, leap_votes, leap_ins, leap_del;
int orphan_stratum, orphan_source;
double src_offset, src_offset_sd, src_frequency, src_frequency_sd, src_skew;
double src_root_delay, src_root_dispersion;
double best_lo, best_hi, distance, sel_src_distance, max_score;
@@ -642,7 +691,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
}
si = &sources[i]->sel_info;
SST_GetSelectionData(sources[i]->stats, &now, &si->stratum, &si->leap,
SST_GetSelectionData(sources[i]->stats, &now, &si->stratum,
&si->lo_limit, &si->hi_limit, &si->root_distance,
&si->std_dev, &first_sample_ago,
&si->last_sample_ago, &si->select_ok);
@@ -678,6 +727,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
}
sources[i]->status = SRC_OK; /* For now */
sources[i]->leap_vote = 0;
if (sources[i]->reachability && max_reach_sample_ago < first_sample_ago)
max_reach_sample_ago = first_sample_ago;
@@ -914,26 +964,15 @@ SRC_SelectSource(SRC_Instance updated_inst)
return;
}
/* Accept leap second status if more than half of selectable (and trusted
if there are any) sources agree */
for (i = leap_ins = leap_del = leap_votes = 0; i < n_sel_sources; i++) {
/* Enable the selectable sources (and trusted if there are any) to
vote on leap seconds */
for (i = 0; i < n_sel_sources; i++) {
index = sel_sources[i];
if (best_trust_depth && !(sources[index]->sel_options & SRC_SELECT_TRUST))
continue;
leap_votes++;
if (sources[index]->sel_info.leap == LEAP_InsertSecond)
leap_ins++;
else if (sources[index]->sel_info.leap == LEAP_DeleteSecond)
leap_del++;
sources[index]->leap_vote = 1;
}
if (leap_ins > leap_votes / 2)
leap_status = LEAP_InsertSecond;
else if (leap_del > leap_votes / 2)
leap_status = LEAP_DeleteSecond;
else
leap_status = LEAP_Normal;
/* If there are any sources with prefer option, reduce the list again
only to the preferred sources */
for (i = 0; i < n_sel_sources; i++) {
@@ -1065,6 +1104,8 @@ SRC_SelectSource(SRC_Instance updated_inst)
for (i = 0; i < n_sources; i++)
sources[i]->updates = 0;
leap_status = get_leap_status();
/* Now just use the statistics of the selected source combined with
the other selectable sources for trimming the local clock */
@@ -1128,7 +1169,7 @@ slew_sources(struct timespec *raw, struct timespec *cooked, double dfreq,
}
if (change_type == LCL_ChangeUnknownStep) {
/* After resetting no source is selectable, set reference unsynchronised */
/* Update selection status */
SRC_SelectSource(NULL);
}
}
@@ -1150,34 +1191,24 @@ add_dispersion(double dispersion, void *anything)
/* ================================================== */
static
FILE *open_dumpfile(SRC_Instance inst, const char *mode)
FILE *open_dumpfile(SRC_Instance inst, char mode)
{
FILE *f;
char filename[1024], *dumpdir;
char filename[64], *dumpdir;
dumpdir = CNF_GetDumpDir();
if (dumpdir[0] == '\0') {
LOG(LOGS_WARN, "dumpdir not specified");
return NULL;
}
/* Include IP address in the name for NTP sources, or reference ID in hex */
if ((inst->type == SRC_NTP &&
snprintf(filename, sizeof (filename), "%s/%s.dat", dumpdir,
source_to_string(inst)) >= sizeof (filename)) ||
(inst->type != SRC_NTP &&
snprintf(filename, sizeof (filename), "%s/refid:%08"PRIx32".dat",
dumpdir, inst->ref_id) >= sizeof (filename))) {
LOG(LOGS_WARN, "dumpdir too long");
if (inst->type == SRC_NTP && UTI_IsIPReal(inst->ip_addr))
snprintf(filename, sizeof (filename), "%s", source_to_string(inst));
else if (inst->type == SRC_REFCLOCK)
snprintf(filename, sizeof (filename), "refid:%08"PRIx32, inst->ref_id);
else
return NULL;
}
f = fopen(filename, mode);
if (!f && mode[0] != 'r')
LOG(LOGS_WARN, "Could not open dump file for %s",
source_to_string(inst));
return f;
return UTI_OpenFile(dumpdir, filename, ".dat", mode, 0644);
}
/* ================================================== */
@@ -1190,7 +1221,7 @@ SRC_DumpSources(void)
int i;
for (i = 0; i < n_sources; i++) {
out = open_dumpfile(sources[i], "w");
out = open_dumpfile(sources[i], 'w');
if (!out)
continue;
SST_SaveToFile(sources[i]->stats, out);
@@ -1207,7 +1238,7 @@ SRC_ReloadSources(void)
int i;
for (i = 0; i < n_sources; i++) {
in = open_dumpfile(sources[i], "r");
in = open_dumpfile(sources[i], 'r');
if (!in)
continue;
if (!SST_LoadFromFile(sources[i]->stats, in))
@@ -1225,7 +1256,7 @@ SRC_ReloadSources(void)
void
SRC_RemoveDumpFiles(void)
{
char pattern[1024], name[64], *dumpdir, *s;
char pattern[PATH_MAX], name[64], *dumpdir, *s;
IPAddr ip_addr;
glob_t gl;
size_t i;
@@ -1252,8 +1283,8 @@ SRC_RemoveDumpFiles(void)
if (strncmp(name, "refid:", 6) && !UTI_StringToIP(name, &ip_addr))
continue;
DEBUG_LOG("Removing %s", gl.gl_pathv[i]);
unlink(gl.gl_pathv[i]);
if (!UTI_RemoveFile(NULL, gl.gl_pathv[i], NULL))
;
}
globfree(&gl);
@@ -1261,6 +1292,17 @@ SRC_RemoveDumpFiles(void)
/* ================================================== */
void
SRC_ResetSources(void)
{
int i;
for (i = 0; i < n_sources; i++)
SRC_ResetInstance(sources[i]);
}
/* ================================================== */
int
SRC_IsSyncPeer(SRC_Instance inst)
{

View File

@@ -79,8 +79,10 @@ extern void SRC_SetRefid(SRC_Instance instance, uint32_t ref_id, IPAddr *addr);
/* Function to get access to the sourcestats instance */
extern SST_Stats SRC_GetSourcestats(SRC_Instance instance);
/* This function is called by one of the source drivers when it has
a new sample that is to be accumulated */
/* Function to set the current leap status according to the source */
extern void SRC_SetLeapStatus(SRC_Instance instance, NTP_Leap leap);
/* Function to accumulate a new sample from the source */
extern void SRC_AccumulateSample(SRC_Instance instance, NTP_Sample *sample);
/* This routine sets the source as receiving reachability updates */
@@ -114,6 +116,8 @@ extern void SRC_DumpSources(void);
extern void SRC_ReloadSources(void);
extern void SRC_RemoveDumpFiles(void);
extern void SRC_ResetSources(void);
extern int SRC_IsSyncPeer(SRC_Instance inst);
extern int SRC_IsReachable(SRC_Instance inst);
extern int SRC_ReadNumberOfSources(void);

View File

@@ -54,6 +54,9 @@
/* The minimum standard deviation */
#define MIN_STDDEV 1.0e-9
/* The worst case bound on an unknown standard deviation of the offset */
#define WORST_CASE_STDDEV_BOUND 4.0
/* The asymmetry of network jitter when all jitter is in one direction */
#define MAX_ASYMMETRY 0.5
@@ -177,9 +180,6 @@ struct SST_Stats_Record {
/* The stratum from the last accumulated sample */
int stratum;
/* The leap status from the last accumulated sample */
NTP_Leap leap;
};
/* ================================================== */
@@ -249,13 +249,12 @@ SST_ResetInstance(SST_Stats inst)
inst->estimated_frequency_sd = WORST_CASE_FREQ_BOUND;
inst->skew = WORST_CASE_FREQ_BOUND;
inst->estimated_offset = 0.0;
inst->estimated_offset_sd = 86400.0; /* Assume it's at least within a day! */
inst->estimated_offset_sd = WORST_CASE_STDDEV_BOUND;
UTI_ZeroTimespec(&inst->offset_time);
inst->std_dev = 4.0;
inst->std_dev = WORST_CASE_STDDEV_BOUND;
inst->nruns = 0;
inst->asymmetry_run = 0;
inst->asymmetry = 0.0;
inst->leap = LEAP_Unsynchronised;
}
/* ================================================== */
@@ -323,7 +322,6 @@ SST_AccumulateSample(SST_Stats inst, NTP_Sample *sample)
inst->root_delays[m] = sample->root_delay;
inst->root_dispersions[m] = sample->root_dispersion;
inst->stratum = sample->stratum;
inst->leap = sample->leap;
if (inst->peer_delays[n] < inst->fixed_min_delay)
inst->peer_delays[n] = 2.0 * inst->fixed_min_delay - inst->peer_delays[n];
@@ -603,9 +601,20 @@ SST_DoNewRegression(SST_Stats inst)
times_back_start = inst->runs_samples + best_start;
prune_register(inst, best_start);
} else {
inst->estimated_frequency = 0.0;
inst->estimated_frequency_sd = WORST_CASE_FREQ_BOUND;
inst->skew = WORST_CASE_FREQ_BOUND;
inst->estimated_offset_sd = WORST_CASE_STDDEV_BOUND;
inst->std_dev = WORST_CASE_STDDEV_BOUND;
inst->nruns = 0;
if (inst->n_samples > 0) {
inst->estimated_offset = inst->offsets[inst->last_sample];
inst->offset_time = inst->sample_times[inst->last_sample];
} else {
inst->estimated_offset = 0.0;
UTI_ZeroTimespec(&inst->offset_time);
}
times_back_start = 0;
}
@@ -641,7 +650,7 @@ SST_GetFrequencyRange(SST_Stats inst,
void
SST_GetSelectionData(SST_Stats inst, struct timespec *now,
int *stratum, NTP_Leap *leap,
int *stratum,
double *offset_lo_limit,
double *offset_hi_limit,
double *root_distance,
@@ -662,7 +671,6 @@ SST_GetSelectionData(SST_Stats inst, struct timespec *now,
j = get_buf_index(inst, inst->best_single_sample);
*stratum = inst->stratum;
*leap = inst->leap;
*std_dev = inst->std_dev;
sample_elapsed = fabs(UTI_DiffTimespecsToDouble(now, &inst->sample_times[i]));
@@ -694,6 +702,13 @@ SST_GetSelectionData(SST_Stats inst, struct timespec *now,
*select_ok = inst->regression_ok;
/* If maxsamples is too small to have a successful regression, enable the
selection as a special case for a fast update/print-once reference mode */
if (!*select_ok && inst->n_samples < 3 && inst->n_samples == inst->max_samples) {
*std_dev = CNF_GetMaxJitter();
*select_ok = 1;
}
DEBUG_LOG("n=%d off=%f dist=%f sd=%f first_ago=%f last_ago=%f selok=%d",
inst->n_samples, offset, *root_distance, *std_dev,
*first_sample_ago, *last_sample_ago, *select_ok);
@@ -993,31 +1008,24 @@ SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report, struct ti
{
double dspan;
double elapsed, sample_elapsed;
int li, lj, bi, bj;
int bi, bj;
report->n_samples = inst->n_samples;
report->n_runs = inst->nruns;
if (inst->n_samples > 1) {
li = get_runsbuf_index(inst, inst->n_samples - 1);
lj = get_buf_index(inst, inst->n_samples - 1);
dspan = UTI_DiffTimespecsToDouble(&inst->sample_times[li],
&inst->sample_times[get_runsbuf_index(inst, 0)]);
report->span_seconds = (unsigned long) (dspan + 0.5);
if (inst->n_samples > 0) {
bi = get_runsbuf_index(inst, inst->best_single_sample);
bj = get_buf_index(inst, inst->best_single_sample);
if (inst->n_samples > 3) {
elapsed = UTI_DiffTimespecsToDouble(now, &inst->offset_time);
bi = get_runsbuf_index(inst, inst->best_single_sample);
bj = get_buf_index(inst, inst->best_single_sample);
sample_elapsed = UTI_DiffTimespecsToDouble(now, &inst->sample_times[bi]);
report->est_offset = inst->estimated_offset + elapsed * inst->estimated_frequency;
report->est_offset_err = (inst->estimated_offset_sd +
sample_elapsed * inst->skew +
(0.5*inst->root_delays[bj] + inst->root_dispersions[bj]));
} else {
report->est_offset = inst->offsets[li];
report->est_offset_err = 0.5*inst->root_delays[lj] + inst->root_dispersions[lj];
}
dspan = UTI_DiffTimespecsToDouble(&inst->sample_times[inst->last_sample],
&inst->sample_times[get_runsbuf_index(inst, 0)]);
elapsed = UTI_DiffTimespecsToDouble(now, &inst->offset_time);
sample_elapsed = UTI_DiffTimespecsToDouble(now, &inst->sample_times[bi]);
report->span_seconds = round(dspan);
report->est_offset = inst->estimated_offset + elapsed * inst->estimated_frequency;
report->est_offset_err = inst->estimated_offset_sd + sample_elapsed * inst->skew +
(0.5 * inst->root_delays[bj] + inst->root_dispersions[bj]);
} else {
report->span_seconds = 0;
report->est_offset = 0;

View File

@@ -69,7 +69,7 @@ extern void SST_GetFrequencyRange(SST_Stats inst, double *lo, double *hi);
/* Get data needed for selection */
extern void
SST_GetSelectionData(SST_Stats inst, struct timespec *now,
int *stratum, NTP_Leap *leap,
int *stratum,
double *offset_lo_limit,
double *offset_hi_limit,
double *root_distance,

View File

@@ -52,6 +52,8 @@ typedef struct {
int filter_length;
int interleaved;
int sel_options;
int nts;
int nts_port;
uint32_t authkey;
double max_delay;
double max_delay_ratio;
@@ -74,6 +76,7 @@ typedef struct {
#define SRC_DEFAULT_MINSAMPLES (-1)
#define SRC_DEFAULT_MAXSAMPLES (-1)
#define SRC_DEFAULT_ASYMMETRY 1.0
#define SRC_DEFAULT_NTSPORT 11443
#define INACTIVE_AUTHKEY 0
/* Flags for source selection */

150
stubs.c
View File

@@ -28,6 +28,7 @@
#include "config.h"
#include "clientlog.h"
#include "cmac.h"
#include "cmdmon.h"
#include "keys.h"
#include "logging.h"
@@ -39,6 +40,10 @@
#include "ntp_io.h"
#include "ntp_sources.h"
#include "ntp_signd.h"
#include "nts_ke_client.h"
#include "nts_ke_server.h"
#include "nts_ntp_client.h"
#include "nts_ntp_server.h"
#include "privops.h"
#include "refclock.h"
#include "sched.h"
@@ -194,9 +199,10 @@ NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SourceParam
return NSR_TooManySources;
}
void
NSR_Status
NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type, SourceParameters *params)
{
return NSR_TooManySources;
}
NSR_Status
@@ -220,6 +226,12 @@ NSR_RefreshAddresses(void)
{
}
char *
NSR_GetName(IPAddr *address)
{
return NULL;
}
void
NSR_SetSourceResolvingEndHandler(NSR_SourceResolvingEndHandler handler)
{
@@ -319,6 +331,11 @@ NSR_GetActivityReport(RPT_ActivityReport *report)
memset(report, 0, sizeof (*report));
}
void
NSR_DumpAuthData(void)
{
}
#ifndef FEAT_CMDMON
void
@@ -404,9 +421,138 @@ NSD_GetAuthDelay(uint32_t key_id)
}
int
NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, int length)
NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info,
NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr)
{
return 0;
}
#endif /* !FEAT_SIGND */
#ifndef HAVE_CMAC
unsigned int
CMC_GetKeyLength(const char *cipher)
{
return 0;
}
CMC_Instance
CMC_CreateInstance(const char *cipher, const unsigned char *key, unsigned int length)
{
return NULL;
}
unsigned int
CMC_Hash(CMC_Instance inst, const unsigned char *in, unsigned int in_len,
unsigned char *out, unsigned int out_len)
{
return 0;
}
void
CMC_DestroyInstance(CMC_Instance inst)
{
}
#endif /* !HAVE_CMAC */
#ifndef FEAT_NTS
void
NNS_Initialise(void)
{
}
void
NNS_Finalise(void)
{
}
int
NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod)
{
*kod = 0;
return 0;
}
int
NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
NTP_Packet *response, NTP_PacketInfo *res_info,
uint32_t kod)
{
return 0;
}
NNC_Instance
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr *ntp_address)
{
return NULL;
}
void
NNC_DestroyInstance(NNC_Instance inst)
{
}
int
NNC_PrepareForAuth(NNC_Instance inst)
{
return 1;
}
int
NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info)
{
DEBUG_LOG("NTS support disabled");
return 0;
}
int
NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info)
{
DEBUG_LOG("NTS support disabled");
return 0;
}
void
NNC_ChangeAddress(NNC_Instance inst, IPAddr *address)
{
}
void
NNC_DumpData(NNC_Instance inst)
{
}
void
NKC_Initialise(void)
{
}
void
NKC_Finalise(void)
{
}
void
NKS_Initialise(int scfilter_level)
{
}
void
NKS_Finalise(void)
{
}
void
NKS_DumpKeys(void)
{
}
void
NKS_ReloadKeys(void)
{
}
#endif /* !FEAT_NTS */

4
sys.c
View File

@@ -114,10 +114,10 @@ void SYS_DropRoot(uid_t uid, gid_t gid)
/* ================================================== */
void SYS_EnableSystemCallFilter(int level)
void SYS_EnableSystemCallFilter(int level, SYS_SystemCallContext context)
{
#if defined(LINUX) && defined(FEAT_SCFILTER)
SYS_Linux_EnableSystemCallFilter(level);
SYS_Linux_EnableSystemCallFilter(level, context);
#else
LOG_FATAL("system call filter not supported");
#endif

7
sys.h
View File

@@ -38,9 +38,14 @@ extern void SYS_Finalise(void);
/* Drop root privileges to the specified user and group */
extern void SYS_DropRoot(uid_t uid, gid_t gid);
typedef enum {
SYS_MAIN_PROCESS,
SYS_NTSKE_HELPER,
} SYS_SystemCallContext;
/* Enable a system call filter to allow only system calls
which chronyd normally needs after initialization */
extern void SYS_EnableSystemCallFilter(int level);
extern void SYS_EnableSystemCallFilter(int level, SYS_SystemCallContext context);
extern void SYS_SetScheduler(int SchedPriority);
extern void SYS_LockMemory(void);

View File

@@ -474,15 +474,15 @@ void check_seccomp_applicability(void)
/* ================================================== */
void
SYS_Linux_EnableSystemCallFilter(int level)
SYS_Linux_EnableSystemCallFilter(int level, SYS_SystemCallContext context)
{
const int syscalls[] = {
/* Clock */
SCMP_SYS(adjtimex), SCMP_SYS(clock_gettime), SCMP_SYS(gettimeofday),
SCMP_SYS(settimeofday), SCMP_SYS(time),
SCMP_SYS(adjtimex), SCMP_SYS(clock_adjtime), SCMP_SYS(clock_gettime),
SCMP_SYS(gettimeofday), SCMP_SYS(settimeofday), SCMP_SYS(time),
/* Process */
SCMP_SYS(clone), SCMP_SYS(exit), SCMP_SYS(exit_group), SCMP_SYS(getpid),
SCMP_SYS(getrlimit), SCMP_SYS(rt_sigaction), SCMP_SYS(rt_sigreturn),
SCMP_SYS(getrlimit), SCMP_SYS(getuid), SCMP_SYS(rt_sigaction), SCMP_SYS(rt_sigreturn),
SCMP_SYS(rt_sigprocmask), SCMP_SYS(set_tid_address), SCMP_SYS(sigreturn),
SCMP_SYS(wait4), SCMP_SYS(waitpid),
/* Memory */
@@ -493,12 +493,13 @@ SYS_Linux_EnableSystemCallFilter(int level)
SCMP_SYS(chown32), SCMP_SYS(faccessat), SCMP_SYS(fchmodat), SCMP_SYS(fchownat),
SCMP_SYS(fstat), SCMP_SYS(fstat64), SCMP_SYS(getdents), SCMP_SYS(getdents64),
SCMP_SYS(lseek), SCMP_SYS(newfstatat), SCMP_SYS(rename), SCMP_SYS(renameat),
SCMP_SYS(stat), SCMP_SYS(stat64), SCMP_SYS(statfs), SCMP_SYS(statfs64),
SCMP_SYS(unlink), SCMP_SYS(unlinkat),
SCMP_SYS(renameat2), SCMP_SYS(stat), SCMP_SYS(stat64), SCMP_SYS(statfs),
SCMP_SYS(statfs64), SCMP_SYS(unlink), SCMP_SYS(unlinkat),
/* Socket */
SCMP_SYS(bind), SCMP_SYS(connect), SCMP_SYS(getsockname), SCMP_SYS(getsockopt),
SCMP_SYS(recv), SCMP_SYS(recvfrom), SCMP_SYS(recvmmsg), SCMP_SYS(recvmsg),
SCMP_SYS(send), SCMP_SYS(sendmmsg), SCMP_SYS(sendmsg), SCMP_SYS(sendto),
SCMP_SYS(accept), SCMP_SYS(bind), SCMP_SYS(connect), SCMP_SYS(getsockname),
SCMP_SYS(getsockopt), SCMP_SYS(recv), SCMP_SYS(recvfrom),
SCMP_SYS(recvmmsg), SCMP_SYS(recvmsg), SCMP_SYS(send), SCMP_SYS(sendmmsg),
SCMP_SYS(sendmsg), SCMP_SYS(sendto), SCMP_SYS(shutdown),
/* TODO: check socketcall arguments */
SCMP_SYS(socketcall),
/* General I/O */
@@ -522,13 +523,16 @@ SYS_Linux_EnableSystemCallFilter(int level)
{ SOL_IPV6, IPV6_V6ONLY }, { SOL_IPV6, IPV6_RECVPKTINFO },
#endif
{ SOL_SOCKET, SO_BROADCAST }, { SOL_SOCKET, SO_REUSEADDR },
#ifdef SO_REUSEPORT
{ SOL_SOCKET, SO_REUSEPORT },
#endif
{ SOL_SOCKET, SO_TIMESTAMP }, { SOL_SOCKET, SO_TIMESTAMPNS },
#ifdef HAVE_LINUX_TIMESTAMPING
{ SOL_SOCKET, SO_SELECT_ERR_QUEUE }, { SOL_SOCKET, SO_TIMESTAMPING },
#endif
};
const static int fcntls[] = { F_GETFD, F_SETFD, F_SETFL };
const static int fcntls[] = { F_GETFD, F_SETFD, F_GETFL, F_SETFL };
const static unsigned long ioctls[] = {
FIONREAD, TCGETS,
@@ -558,14 +562,16 @@ SYS_Linux_EnableSystemCallFilter(int level)
scmp_filter_ctx *ctx;
int i;
/* Check if the chronyd configuration is supported */
check_seccomp_applicability();
if (context == SYS_MAIN_PROCESS) {
/* Check if the chronyd configuration is supported */
check_seccomp_applicability();
/* Start the helper process, which will run without any seccomp filter. It
will be used for getaddrinfo(), for which it's difficult to maintain a
list of required system calls (with glibc it depends on what NSS modules
are installed and enabled on the system). */
PRV_StartHelper();
/* Start the helper process, which will run without any seccomp filter. It
will be used for getaddrinfo(), for which it's difficult to maintain a
list of required system calls (with glibc it depends on what NSS modules
are installed and enabled on the system). */
PRV_StartHelper();
}
ctx = seccomp_init(level > 0 ? SCMP_ACT_KILL : SCMP_ACT_TRAP);
if (ctx == NULL)
@@ -577,42 +583,44 @@ SYS_Linux_EnableSystemCallFilter(int level)
goto add_failed;
}
/* Allow sockets to be created only in selected domains */
for (i = 0; i < sizeof (socket_domains) / sizeof (*socket_domains); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
SCMP_A0(SCMP_CMP_EQ, socket_domains[i])) < 0)
goto add_failed;
}
if (context == SYS_MAIN_PROCESS) {
/* Allow opening sockets in selected domains */
for (i = 0; i < sizeof (socket_domains) / sizeof (*socket_domains); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
SCMP_A0(SCMP_CMP_EQ, socket_domains[i])) < 0)
goto add_failed;
}
/* Allow setting only selected sockets options */
for (i = 0; i < sizeof (socket_options) / sizeof (*socket_options); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 3,
SCMP_A1(SCMP_CMP_EQ, socket_options[i][0]),
SCMP_A2(SCMP_CMP_EQ, socket_options[i][1]),
SCMP_A4(SCMP_CMP_LE, sizeof (int))) < 0)
goto add_failed;
}
/* Allow selected socket options */
for (i = 0; i < sizeof (socket_options) / sizeof (*socket_options); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 3,
SCMP_A1(SCMP_CMP_EQ, socket_options[i][0]),
SCMP_A2(SCMP_CMP_EQ, socket_options[i][1]),
SCMP_A4(SCMP_CMP_LE, sizeof (int))) < 0)
goto add_failed;
}
/* Allow only selected fcntl calls */
for (i = 0; i < sizeof (fcntls) / sizeof (*fcntls); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1,
SCMP_A1(SCMP_CMP_EQ, fcntls[i])) < 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 1,
SCMP_A1(SCMP_CMP_EQ, fcntls[i])) < 0)
goto add_failed;
}
/* Allow selected fcntl calls */
for (i = 0; i < sizeof (fcntls) / sizeof (*fcntls); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1,
SCMP_A1(SCMP_CMP_EQ, fcntls[i])) < 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 1,
SCMP_A1(SCMP_CMP_EQ, fcntls[i])) < 0)
goto add_failed;
}
/* Allow only selected ioctls */
for (i = 0; i < sizeof (ioctls) / sizeof (*ioctls); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
SCMP_A1(SCMP_CMP_EQ, ioctls[i])) < 0)
goto add_failed;
/* Allow selected ioctls */
for (i = 0; i < sizeof (ioctls) / sizeof (*ioctls); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
SCMP_A1(SCMP_CMP_EQ, ioctls[i])) < 0)
goto add_failed;
}
}
if (seccomp_load(ctx) < 0)
LOG_FATAL("Failed to load seccomp rules");
LOG(LOGS_INFO, "Loaded seccomp filter");
LOG(context == SYS_MAIN_PROCESS ? LOGS_INFO : LOGS_DEBUG, "Loaded seccomp filter");
seccomp_release(ctx);
return;

View File

@@ -27,13 +27,15 @@
#ifndef GOT_SYS_LINUX_H
#define GOT_SYS_LINUX_H
#include "sys.h"
extern void SYS_Linux_Initialise(void);
extern void SYS_Linux_Finalise(void);
extern void SYS_Linux_DropRoot(uid_t uid, gid_t gid, int clock_control);
extern void SYS_Linux_EnableSystemCallFilter(int level);
extern void SYS_Linux_EnableSystemCallFilter(int level, SYS_SystemCallContext context);
extern int SYS_Linux_CheckKernelVersion(int req_major, int req_minor);

View File

@@ -46,8 +46,9 @@
#include "privops.h"
#include "util.h"
#ifdef HAVE_MACOS_SYS_TIMEX
#include <dlfcn.h>
#ifdef HAVE_MACOS_SYS_TIMEX
#include "sys_netbsd.h"
static int have_ntp_adjtime = 0;
@@ -451,6 +452,39 @@ legacy_MacOSX_Finalise(void)
/* ================================================== */
#if HAVE_CLOCK_GETTIME
int
clock_gettime(clockid_t clock_id, struct timespec *ts)
{
/* Check that the system clock_gettime symbol is actually present before
attempting to call it. The symbol is available in macOS 10.12
and later. */
static int init = 0;
static int (*sys_clock_gettime)(clockid_t, struct timespec *) = NULL;
int ret = 0;
if (!init) {
sys_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime");
init = 1;
}
if (sys_clock_gettime != NULL) {
ret = sys_clock_gettime(clock_id, ts);
} else {
struct timeval tv;
if (gettimeofday(&tv, NULL) < 0)
LOG_FATAL("gettimeofday() failed : %s", strerror(errno));
UTI_TimevalToTimespec(&tv, ts);
}
return ret;
}
#endif
/* ================================================== */
void
SYS_MacOSX_Initialise(void)
{

View File

@@ -256,10 +256,8 @@ SYS_Timex_Adjust(struct timex *txc, int ignore_error)
state = NTP_ADJTIME(txc);
if (state < 0) {
if (!ignore_error)
LOG_FATAL(NTP_ADJTIME_NAME"(0x%x) failed : %s", txc->modes, strerror(errno));
else
DEBUG_LOG(NTP_ADJTIME_NAME"(0x%x) failed : %s", txc->modes, strerror(errno));
LOG(ignore_error ? LOGS_DEBUG : LOGS_FATAL,
NTP_ADJTIME_NAME"(0x%x) failed : %s", txc->modes, strerror(errno));
}
return state;

View File

@@ -21,9 +21,8 @@
=======================================================================
This file includes all system header files that the software
requires. This allows us to isolate system dependencies to this file
alone.
This file includes most system header files that the software
requires to better isolate system dependencies.
*/
#ifndef GOT_SYSINCL_H
@@ -37,6 +36,7 @@
#include <glob.h>
#include <grp.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <netinet/in.h>
#include <pwd.h>
@@ -54,7 +54,6 @@
#include <sys/un.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

View File

@@ -84,7 +84,7 @@ read_timeout(void *arg)
FILE *f;
double temp, comp;
f = fopen(filename, "r");
f = UTI_OpenFile(NULL, filename, NULL, 'r', 0);
if (f && fscanf(f, "%lf", &temp) == 1) {
comp = get_tempcomp(temp);
@@ -122,11 +122,7 @@ read_points(const char *filename)
char line[256];
struct Point *p;
f = fopen(filename, "r");
if (!f) {
LOG_FATAL("Could not open tempcomp point file %s", filename);
return;
}
f = UTI_OpenFile(NULL, filename, NULL, 'R', 0);
points = ARR_CreateInstance(sizeof (struct Point));

View File

@@ -18,6 +18,7 @@ for opts in \
"--disable-sechash" \
"--disable-cmdmon" \
"--disable-ntp" \
"--disable-nts" \
"--disable-refclock" \
"--disable-timestamping" \
"--disable-timestamping --disable-ntp" \
@@ -26,5 +27,7 @@ for opts in \
"--disable-cmdmon --disable-ntp --disable-refclock"
do
./configure $opts || exit 1
make clean
make "$@" || exit 1
make -C test/unit check || exit 1
done

View File

@@ -14,7 +14,7 @@ fi
if [ "$ID" = "fedora" ]; then
echo Checking test dependencies:
rpm -q {valgrind,gcc,clang}.x86_64 {libgcc,clang-libs}.{x86_64,i686} || exit 1
rpm -q {libseccomp,nettle,nss-softokn-freebl,libtomcrypt}-devel.{x86_64,i686} || exit 1
rpm -q {libseccomp,nettle,nss-softokn-freebl,libtomcrypt,gnutls}-devel.{x86_64,i686} || exit 1
echo
fi
@@ -34,6 +34,7 @@ for CC in gcc clang; do
for extra_config_opts in \
"--all-privops" \
"--disable-scfilter" \
"--without-gnutls" \
"--without-nettle" \
"--without-nettle --without-nss" \
"--without-nettle --without-nss --without-tomcrypt"; \

15
test/simulation/013-nameserv Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
. ./test.common
test_start "name resolving"
dns=1
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
test_pass

View File

@@ -23,16 +23,20 @@ EOF
keys=4
if check_config_h 'FEAT_SECHASH 1'; then
hashes="MD5 SHA1 SHA256 SHA384 SHA512"
else
hashes="MD5"
fi
types="MD5"
check_config_h 'FEAT_SECHASH 1' && types="$types SHA1 SHA256 SHA384 SHA512"
check_config_h 'HAVE_CMAC 1' && types="$types AES128 AES256"
for hash in $hashes; do
for type in $types; do
keys=$[$keys + 1]
key=$(echo $keys $hash HEX:$(tr -c -d '0-9A-F' < /dev/urandom 2> /dev/null | \
head -c $[$RANDOM % 64 * 2 + 2]))
case $type in
AES128) length=16;;
AES256) length=32;;
*) length=$[$RANDOM % 32 + 1];;
esac
key=$(echo $keys $type HEX:$(tr -c -d '0-9A-F' < /dev/urandom 2> /dev/null | \
head -c $[$length * 2]))
echo "$key" >> tmp/server.keys
echo "$key" >> tmp/client.keys
done
@@ -70,13 +74,18 @@ clients=2
peers=2
max_sync_time=500
base_delay="$default_base_delay (* -1 (equal 0.1 from 3) (equal 0.1 to 1))"
for versions in "3 3" "3 4" "4 3" "4 4"; do
for key in 1 $keys; do
client_lpeer_options="version ${versions% *} key $key"
client_rpeer_options="version ${versions#* } key $key"
run_test || test_fail
check_chronyd_exit || test_fail
check_sync || test_fail
done
done
client_lpeer_options="key 1"
client_rpeer_options="key 1"
run_test || test_fail
check_chronyd_exit || test_fail
check_sync || test_fail
client_rpeer_options="key 2"
run_test || test_fail

View File

@@ -43,12 +43,10 @@ Root delay : 0\.000...... seconds
Root dispersion : 0\.000...... seconds
Update interval : [0-9]+\.. seconds
Leap status : Normal
210 Number of sources = 2
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#\? SHM0 0 4 377 [0-9]+ [0-9 +-]+[un]s\[[0-9 +-]+[un]s\] \+/-[ 0-9]+[un]s
\^\* 192\.168\.123\.1 1 [67] 377 [0-9]+ [0-9 +-]+[un]s\[[0-9 +-]+[un]s\] \+/-[ 0-9]+[un]s
210 Number of sources = 2
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
@@ -66,6 +64,15 @@ try: 1, refid: C0A87B01, correction: 0\.000......, skew: .\....
513 RTC driver not running$" \
|| test_fail
chronyc_conf="tracking"
dns=1
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^Reference ID : C0A87B01 \(node1\.net1\.clk\)" \
|| test_fail
server_strata=0
chronyc_start=0
client_conf=""
@@ -75,6 +82,7 @@ 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 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 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" \
"add server node1.net1.clk" \
"allow 1.2.3.4" \
"allow 1.2" \
"allow 3.4.5" \
@@ -96,6 +104,7 @@ for chronyc_conf in \
"cmddeny all 1.2.3.0/24" \
"cyclelogs" \
"delete 10.0.0.0" \
"delete ID#0000000001" \
"deny 1.2.3.4" \
"deny all 1.2.3.0/24" \
"dfreq 1.0e-3" \
@@ -116,6 +125,7 @@ for chronyc_conf in \
"maxupdateskew 1.2.3.4 10.0" \
"minpoll 1.2.3.4 3" \
"minstratum 1.2.3.4 1" \
"minstratum ID#0000000001 1" \
"ntpdata 1.2.3.4" \
"offline" \
"offline 255.255.255.0/1.2.3.0" \
@@ -128,6 +138,7 @@ for chronyc_conf in \
"rekey" \
"reselect" \
"reselectdist 1e-3" \
"reset" \
"settime 16:30" \
"settime 16:30:05" \
"settime Nov 21, 2015 16:30:05" \
@@ -152,6 +163,7 @@ timeout 200
retries 1
keygen
keygen 10 MD5 128
keygen 11 MD5 40
help
quit
nosuchcommand"
@@ -160,7 +172,29 @@ run_test || test_fail
check_chronyc_output "^1 (MD5|SHA1) HEX:........................................
10 MD5 HEX:................................
11 MD5 HEX:....................
System clock:.*this help
*$" || test_fail
chronyc_conf="keygen 10 NOSUCHTYPE 128
help"
run_test || test_fail
check_chronyc_output "^Unknown hash function or cipher NOSUCHTYPE\$" || test_fail
if check_config_h 'FEAT_SECHASH 1'; then
for hash in MD5 SHA1 SHA256 SHA384 SHA512; do
chronyc_conf="keygen 5 $hash"
run_test || test_fail
check_chronyc_output "^5 $hash HEX:........................................\$" || test_fail
done
fi
if check_config_h 'HAVE_CMAC 1'; then
chronyc_conf="keygen 6 AES128
keygen 7 AES256"
run_test || test_fail
check_chronyc_output "^6 AES128 HEX:................................
7 AES256 HEX:................................................................\$" || test_fail
fi
test_pass

View File

@@ -15,11 +15,10 @@ chronyc_start="1.5"
chronyc_conf="tracking"
for year in `seq 1850 100 2300`; do
date="Jan 01 00:00:00 $year"
export CLKNETSIM_START_DATE=$(date -d "$date UTC" +'%s')
export CLKNETSIM_START_DATE=$(date -d "Jan 01 00:00:05 $year UTC" +'%s')
run_test || test_fail
check_chronyd_exit || test_fail
check_chronyc_output "^.*Ref time \(UTC\).*$date.*$" || test_fail
check_chronyc_output "^.*Ref time \(UTC\).*Jan 01 00:00:0. $year.*$" || test_fail
done
test_pass

View File

@@ -11,9 +11,9 @@ server_conf="local stratum 5 orphan
server 192.168.123.1
server 192.168.123.2
server 192.168.123.3"
max_sync_time=500
max_sync_time=900
client_start=140
chronyc_start=300
chronyc_start=700
chronyc_conf="tracking"
time_rms_limit=5e-4

View File

@@ -4,6 +4,9 @@
test_start "interleaved mode"
client_server_options="xleave"
client_conf="
logdir tmp
log rawmeasurements"
run_test || test_fail
check_chronyd_exit || test_fail
@@ -11,6 +14,10 @@ check_source_selection || test_fail
check_packet_interval || test_fail
check_sync || test_fail
check_file_messages "111 111 1111.* 4B [DKH] [DKH]\$" 2 2 measurements.log || test_fail
check_file_messages "111 111 1111.* 4I [DKH] [DKH]\$" 30 200 measurements.log || test_fail
rm -f tmp/measurements.log
clients=2
peers=2
max_sync_time=500
@@ -25,6 +32,8 @@ check_chronyd_exit || test_fail
check_source_selection && test_fail
check_sync && test_fail
rm -f tmp/measurements.log
for rpoll in 4 5 6; do
client_rpeer_options="xleave minpoll $rpoll maxpoll $rpoll"
@@ -32,6 +41,15 @@ for rpoll in 4 5 6; do
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
if [ $rpoll -le 5 ]; then
check_file_messages "111 111 1111.* 1B [DKH] [DKH]\$" 0 0 measurements.log || test_fail
check_file_messages "111 111 1111.* 1I [DKH] [DKH]\$" 200 310 measurements.log || test_fail
else
check_file_messages "111 111 1111.* 1B [DKH] [DKH]\$" 125 135 measurements.log || test_fail
check_file_messages "111 111 1111.* 1I [DKH] [DKH]\$" 20 30 measurements.log || test_fail
fi
rm -f tmp/measurements.log
done
test_pass

View File

@@ -16,6 +16,14 @@ check_chronyd_exit || test_fail
check_packet_interval || test_fail
check_sync || test_fail
min_sync_time=1
max_sync_time=1
client_server_options="iburst maxsamples 1"
run_test || test_fail
check_chronyd_exit || test_fail
check_sync || test_fail
client_chronyd_options="-Q"
run_test || test_fail
check_sync && test_fail

View File

@@ -5,7 +5,7 @@
test_start "logchange directive"
time_offset=2
min_sync_time=600
min_sync_time=590
max_sync_time=700
client_server_options="maxsamples 6"
client_conf="logchange 0.1"

View File

@@ -27,8 +27,10 @@ acquisitionport 123"; do
if check_config_h 'FEAT_DEBUG 1'; then
check_log_messages "HW clock samples" 190 200 || test_fail
check_log_messages "HW clock reset" 0 0 || test_fail
check_log_messages "Received.*tss=1" 1 1 || test_fail
check_log_messages "Received.*tss=2" 390 400 || test_fail
check_log_messages "Received message.*tss=KH" 195 200 || test_fail
check_log_messages "Received error.*message.*tss=KH" 195 200 || test_fail
check_log_messages "Updated RX timestamp.*tss=1" 1 1 || test_fail
check_log_messages "Updated RX timestamp.*tss=2" 195 200 || test_fail
check_log_messages "update_tx_timestamp.*Updated" 50 140 || test_fail
check_log_messages "update_tx_timestamp.*Unacceptable" 50 140 || test_fail
fi

49
test/simulation/137-pool Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
. ./test.common
test_start "pool directive"
limit=500
client_conf="logdir tmp
log measurements"
servers=3
client_server_conf="pool nodes-1-2-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_file_messages "20.*192.168.123.1" 5 10 measurements.log || test_fail
check_file_messages "20.*192.168.123.2" 5 10 measurements.log || test_fail
check_file_messages "20.*192.168.123.3" 5 10 measurements.log || test_fail
rm -f tmp/measurements.log
servers=6
client_server_conf="pool nodes-1-2-3-4-5-6.net1.clk minpoll 6 maxpoll 6"
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 "20.*192.168.123.*" 30 35 measurements.log || test_fail
rm -f tmp/measurements.log
servers=6
client_server_conf="pool nodes-1-2-3-4-5-6.net1.clk maxsources 2 minpoll 6 maxpoll 6"
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 "20.*192.168.123.*" 15 17 measurements.log || test_fail
rm -f tmp/measurements.log
test_pass

34
test/simulation/138-syncloop Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
. ./test.common
test_start "loop prevention"
mkdir tmp/logdir1 tmp/logdir2
server_conf="
server 192.168.123.1
server 192.168.123.2
logdir tmp/logdir1
log measurements"
client_server_conf="
server 192.168.123.1
server 192.168.123.2
logdir tmp/logdir2
log measurements
allow"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_file_messages "20.*123\.1.* 111 111 1110" 30 200 logdir1/measurements.log || test_fail
check_file_messages "20.*123\.2.* 111 111 1110" 30 200 logdir1/measurements.log || test_fail
check_file_messages "20.*123\...* 111 111 1111" 0 0 logdir1/measurements.log || test_fail
check_file_messages "20.*123\.1.* 111 111 1111" 30 200 logdir2/measurements.log || test_fail
check_file_messages "20.*123\.1.* 111 111 1110" 0 0 logdir2/measurements.log || test_fail
check_file_messages "20.*123\.2.* 111 111 1110" 30 200 logdir2/measurements.log || test_fail
check_file_messages "20.*123\.2.* 111 111 1111" 0 0 logdir1/measurements.log || test_fail
test_pass

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